Skip to content

Add Python 3.15 support - #866

Merged
quinnmil merged 4 commits into
masterfrom
add-python-3.15-support
Oct 27, 2026
Merged

quinnmil merged 4 commits into
masterfrom
add-python-3.15-support

Conversation

@quinnmil

@quinnmil quinnmil commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Add Python 3.15 to the CI test matrix (main.yml).
  • Add a py315-django52 tox env. Like the older Pythons, it runs pytest with --asyncio-mode=auto, which the fastmcp integration tests rely on.
  • Add the Programming Language :: Python :: 3.15 classifier.

The release workflow already builds cp315 wheels: cibuildwheel builds every CPython version that matches python_requires. This PR also cleans up stale labels in release.yml:

  • cibuildwheel_py38plus / "Build python 3.8+ … wheels" becomes build_linux_wheels / "Build Linux … wheels". It actually builds 3.10 through 3.15, and the name no longer includes a version that can go stale.
  • "Build pure python wheels (3.14)" becomes "Build pure Python wheel". The 3.14 was only the Python used to run the build, and the py3-none-any wheel works on every version.
  • The CIBW_SKIP: ${{ matrix.cibw_skip }} line is removed. It referenced a matrix key removed in build with python 3.14 #826, so it was always empty.

Behavior change on Python 3.15: plain @instrument now works on async functions

In Python 3.15, contextlib.ContextDecorator wraps coroutine functions itself (the long-standing bpo-37398, referenced in AsyncDecoratorMixin). So on 3.15+, using instrument, WebTransaction or BackgroundTransaction without .async_ on an async function now keeps the span open while the coroutine runs. Nested spans show up correctly, the same as with .async_. On earlier Pythons the span still closes as soon as the coroutine is created, so .async_ is still needed there.

The three *_async_misconfigured tests in tests/integration/test_api.py asserted the pre-3.15 behavior. They now check the Python version, and the CHANGELOG has an entry about the change.

Related

🤖 Generated with Claude Code

https://claude.ai/code/session_01NgEia2eCsEQTpSH8BMfR1d

quinnmil and others added 2 commits October 27, 2026 13:06
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NgEia1ZreHugm7HhSBhBN3
Add py315 to the tox section that runs pytest with --asyncio-mode=auto,
which the unmarked fastmcp tests rely on.

Python 3.15's contextlib.ContextDecorator wraps coroutine functions, so
instrument/WebTransaction/BackgroundTransaction used without .async_ on
an async function now keep the span open while the coroutine runs.
Update the *_async_misconfigured tests to expect the nested span on 3.15+.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NgEia2eCsEQTpSH8BMfR1d
@quinnmil
quinnmil force-pushed the add-python-3.15-support branch from d7c6bcd to 69acc6c Compare October 27, 2026 20:06
@quinnmil
quinnmil marked this pull request as ready for review October 27, 2026 20:07
"Build python 3.8+" predates dropping 3.8/3.9; cibuildwheel builds
whatever python_requires allows (currently 3.10-3.15). Rename the job
to build_linux_wheels with no version in its name so it can't go stale
again. Drop the single-entry matrix from the pure wheel job so its
check name no longer reads "(3.14)", which looked like a target
version. Also remove CIBW_SKIP, which referenced a matrix key removed
in #826 and has evaluated to an empty string since.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NgEia2eCsEQTpSH8BMfR1d

@jrothrock jrothrock left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — looks good. I left a few nits inline; they're all optional, none change behavior, and none need to be addressed before merging.

  • tests/integration/test_api.py:42, 181, 239 — the """Test case where .async_ isn't used…""" docstrings now read as if the result is always wrong, but on 3.15+ the plain decorator is correct. Maybe add "(only misbehaves on Python < 3.15)". (docstring accuracy)

Comment thread tests/integration/test_api.py Outdated
Comment on lines +55 to +63
if sys.version_info >= (3, 15):
# Python 3.15's ContextDecorator wraps coroutine functions, so the
# span stays open while the coroutine runs, same as with .async_.
assert len(tracked_request.complete_spans) == 2
assert tracked_request.complete_spans[0].operation == "Custom/Foo"
assert tracked_request.complete_spans[1].operation == "Custom/Bar"
else:
assert len(tracked_request.complete_spans) == 1
assert tracked_request.complete_spans[0].operation == "Custom/Bar"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this if sys.version_info >= (3, 15): … else: … block (and its comment) is repeated at L194-202 and L252-260. The rest of the suite gates on versions with pytest.mark.skipif markers rather than branching inside a test body (e.g. the shared markers in tests/tools.py, tests/unit/test_fastmcp.py:11). You could add a marker like skip_if_context_decorator_wraps_coroutines to tests/tools.py and split each test into a < 3.15 case and a >= 3.15 case. The 3.15 branch asserts the same thing as the matching .async_ test, so the >= 3.15 case could even be dropped. (sibling convention: no conditional logic in tests)

Address review feedback: instead of branching on sys.version_info inside
the *_async_misconfigured tests, gate them with a shared marker in
tests/tools.py, matching the rest of the suite. Add separate
*_without_async_ tests for Python 3.15+ that check the plain decorator
now times the whole coroutine, and note in the misconfigured tests'
docstrings that they only misbehave below 3.15.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NgEia2eCsEQTpSH8BMfR1d
@quinnmil
quinnmil merged commit 1bf003c into master Oct 27, 2026
13 checks passed
@quinnmil
quinnmil deleted the add-python-3.15-support branch October 27, 2026 22:10
This was referenced Oct 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants