Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0888299
ci(riot): schedule Python 3.15 jobs for a starter set
vlad-scherbich Sep 4, 2026
5afa466
downgrade sourcecode due to errors
vlad-scherbich Sep 4, 2026
e4c5b6e
fix(ci): restore Python 3.15 sourcecode coverage imports
vlad-scherbich Sep 4, 2026
a037295
chore(py-315): mark defensive import no-op for removal
vlad-scherbich Sep 4, 2026
f1c0f55
chore(py-315): defer sourcecode coverage
vlad-scherbich Sep 4, 2026
e5db292
chore(ci): enable Python 3.15 support for dramatiq
wantsui Sep 4, 2026
efac45f
chore(ci): drop unrelated GPG fingerprint codespell ignores
wantsui Sep 4, 2026
9d2bd7f
chore(ci): downgrade 3.15 import-hook notes to plain comments
wantsui Sep 4, 2026
8c7799f
chore(py-315): defer ddtrace api coverage
vlad-scherbich Sep 4, 2026
9f288c0
Update ddtrace/internal/coverage/import_instrumentation_py3_12.py
wantsui Sep 4, 2026
a140af8
chore(ci): mark 3.15 import-hook injection gap with TODO(py-315)
wantsui Sep 4, 2026
0f524fe
Update riotfile.py
wantsui Sep 4, 2026
5c27968
Update ddtrace/internal/coverage/import_instrumentation_py3_12.py
wantsui Sep 4, 2026
52334f9
chore(ci): address remaining py-315 import injection review feedback
wantsui Sep 4, 2026
ec39669
Merge remote-tracking branch 'origin/codex-dramatiq-py315' into codex…
wantsui Sep 4, 2026
06c5be1
chore(ci): mark remaining 3.15 accurate-import test skips with TODO(p…
wantsui Sep 4, 2026
3b5d668
Merge remote-tracking branch 'origin/vlad/py315-riotfile' into codex-…
wantsui Sep 4, 2026
bf2cebc
chore(ci): simplify dramatiq py315 selection now base includes 3.15
wantsui Sep 4, 2026
2542bca
chore(ci): refresh stale dramatiq py3.15 lock to pick up dramatiq 2.2.1
wantsui Sep 4, 2026
baa80a6
Merge branch 'main' into codex-dramatiq-py315
wantsui Sep 4, 2026
67a206c
Merge branch 'main' into codex-dramatiq-py315
wantsui Sep 8, 2026
a811905
fix(ci): declare Python 3.15 for dramatiq in suitespec matrix
wantsui Sep 8, 2026
067d2c2
Merge commit '2443a9c0cd03d98934b6dcee506b05c376dd61f7' into codex/ag…
mabdinur Sep 8, 2026
bbfd228
Merge branch 'main' into codex-dramatiq-py315
wantsui Sep 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 29 additions & 28 deletions .riot/requirements/128b106.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,49 @@
# This file is autogenerated by pip-compile with Python 3.14
# by the following command:
#
# pip-compile --allow-unsafe --no-annotate .riot/requirements/128b106.in
# pip-compile --allow-unsafe --no-annotate --no-index .riot/requirements/128b106.in
#
annotated-types==0.7.0
anyio==4.11.0
attrs==25.4.0
boto3==1.40.46
botocore==1.40.46
certifi==2025.10.5
coverage[toml]==7.10.7
fastapi==0.118.0
annotated-doc==0.0.5
annotated-types==0.8.0
anyio==4.15.0
attrs==26.1.0
boto3==1.43.88
botocore==1.43.88
certifi==2026.7.22
coverage[toml]==7.16.0
fastapi==0.141.1
freezegun==1.5.5
h11==0.16.0
httpcore==1.0.9
httpretty==1.1.4
httpx==0.27.2
hypothesis==6.45.0
idna==3.10
iniconfig==2.1.0
jmespath==1.0.1
idna==3.19
iniconfig==2.3.0
jmespath==1.1.0
mock==5.2.0
msgpack==1.1.1
msgpack==1.2.2
opentracing==2.4.0
packaging==25.0
packaging==26.3
pluggy==1.6.0
pydantic==2.12.0
pydantic-core==2.41.1
pygments==2.19.2
pytest==8.4.2
pytest-cov==7.0.0
pydantic==2.13.5
pydantic-core==2.46.5
pygments==2.21.0
pytest==9.1.1
pytest-cov==7.1.0
pytest-mock==3.15.1
pytest-randomly==4.0.1
pytest-randomly==5.0.0
python-dateutil==2.9.0.post0
s3transfer==0.14.0
s3transfer==0.19.2
six==1.17.0
sniffio==1.3.1
sortedcontainers==2.4.0
starlette==0.48.0
structlog==25.4.0
typing-extensions==4.15.0
typing-inspection==0.4.2
urllib3==2.5.0
wheel==0.45.1
starlette==1.6.0
structlog==26.1.0
typing-extensions==4.16.0
typing-inspection==0.4.4
urllib3==2.7.0
wheel==0.48.0

# The following packages are considered to be unsafe in a requirements file:
setuptools==80.9.0
setuptools==84.0.0
6 changes: 3 additions & 3 deletions .riot/requirements/1381214.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
# This file is autogenerated by pip-compile with Python 3.13
# by the following command:
#
# pip-compile --allow-unsafe --no-annotate .riot/requirements/1381214.in
# pip-compile --allow-unsafe --no-annotate --no-index .riot/requirements/1381214.in
#
attrs==26.1.0
coverage[toml]==7.15.4
dramatiq==2.2.0
coverage[toml]==7.16.0
dramatiq==2.2.1
hypothesis==6.45.0
iniconfig==2.3.0
mock==5.2.0
Expand Down
6 changes: 3 additions & 3 deletions .riot/requirements/14116fa.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# pip-compile --allow-unsafe --no-annotate .riot/requirements/14116fa.in
# pip-compile --allow-unsafe --no-annotate --no-index .riot/requirements/14116fa.in
#
attrs==26.1.0
coverage[toml]==7.15.4
dramatiq==2.2.0
coverage[toml]==7.16.0
dramatiq==2.2.1
hypothesis==6.45.0
iniconfig==2.3.0
mock==5.2.0
Expand Down
12 changes: 8 additions & 4 deletions .riot/requirements/17f991e.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# This file was autogenerated by uv via the following command:
# uv pip compile --no-annotate --no-strip-extras --python /opt/homebrew/opt/python@3.14/bin/python3.14 --output-file .riot/requirements/17f991e.txt .riot/requirements/17f991e.in
#
# This file is autogenerated by pip-compile with Python 3.14
# by the following command:
#
# pip-compile --allow-unsafe --no-annotate --no-index .riot/requirements/17f991e.in
#
attrs==26.1.0
coverage[toml]==7.15.4
dramatiq==2.2.0
coverage[toml]==7.16.0
dramatiq==2.2.1
hypothesis==6.45.0
iniconfig==2.3.0
mock==5.2.0
Expand Down
6 changes: 3 additions & 3 deletions .riot/requirements/19508cd.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
# This file is autogenerated by pip-compile with Python 3.12
# by the following command:
#
# pip-compile --allow-unsafe --no-annotate .riot/requirements/19508cd.in
# pip-compile --allow-unsafe --no-annotate --no-index .riot/requirements/19508cd.in
#
attrs==26.1.0
coverage[toml]==7.15.4
dramatiq==2.2.0
coverage[toml]==7.16.0
dramatiq==2.2.1
hypothesis==6.45.0
iniconfig==2.3.0
mock==5.2.0
Expand Down
21 changes: 21 additions & 0 deletions .riot/requirements/4112544.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#
# This file is autogenerated by pip-compile with Python 3.15
# by the following command:
#
# pip-compile --allow-unsafe --no-annotate --no-index .riot/requirements/4112544.in
#
attrs==26.1.0
coverage[toml]==7.16.0
dramatiq==2.2.1
hypothesis==6.45.0
iniconfig==2.3.0
mock==5.2.0
opentracing==2.4.0
packaging==26.3
pluggy==1.6.0
pygments==2.21.0
pytest==9.1.1
pytest-cov==7.1.0
pytest-mock==3.15.1
redis==8.1.0
sortedcontainers==2.4.0
6 changes: 3 additions & 3 deletions .riot/requirements/638973a.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
# This file is autogenerated by pip-compile with Python 3.10
# by the following command:
#
# pip-compile --allow-unsafe --no-annotate .riot/requirements/638973a.in
# pip-compile --allow-unsafe --no-annotate --no-index .riot/requirements/638973a.in
#
async-timeout==5.0.1
attrs==26.1.0
coverage[toml]==7.15.4
dramatiq==2.2.0
coverage[toml]==7.16.0
dramatiq==2.2.1
exceptiongroup==1.3.1
hypothesis==6.45.0
iniconfig==2.3.0
Expand Down
25 changes: 22 additions & 3 deletions ddtrace/internal/coverage/import_instrumentation_py3_12.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
"""Import dependency bytecode helpers for Python 3.12+ coverage instrumentation."""

import sys
from types import CodeType
import typing as t

from bytecode import Bytecode
from bytecode import Instr

from ddtrace.internal.bytecode_injection import INJECTION_ASSEMBLY
from ddtrace.internal import bytecode_injection as _bytecode_injection
from ddtrace.internal.assembly import Assembly
from ddtrace.internal.bytecode_injection import HookType


_INJECTION_ASSEMBLY: t.Optional[Assembly] = (
_bytecode_injection.INJECTION_ASSEMBLY if sys.version_info < (3, 15) else None
)
# TODO(py-315): Accurate import-hook injection is intentionally unsupported on 3.15+; see the
# comment at instrumentation_py3_12._USE_ACCURATE_IMPORTS for the full rationale and the work
# required to re-enable it (the `bytecode` library's CALL codegen segfaults on 3.15, so injection
# must be reimplemented on sys.monitoring INSTRUCTION events).

Comment thread
wantsui marked this conversation as resolved.

ImportName = tuple[str, tuple[str, ...]]
ImportNamesByLine = dict[int, ImportName]
CoverageHookArg = tuple[int, str, ImportName]
Expand Down Expand Up @@ -67,7 +78,12 @@ def iter_import_events(
lineno = instr.lineno or code.co_firstlineno
if instr.name == "IMPORT_NAME":
import_depth = _decoded_import_depth(previous_previous_arg)
current_import_name = t.cast(str, instr.arg)
# TODO(py-315): bytecode 0.19 decodes Python 3.15 IMPORT_NAME args to (lazy, eager, name).
current_import_name = (
t.cast(str, instr.arg[2])
if isinstance(instr.arg, tuple) and len(instr.arg) == 3
else t.cast(str, instr.arg)
)
current_import_package = _resolve_import_package(package, import_depth)
events.append(ImportEvent(idx, lineno, (current_import_package, (current_import_name,))))
elif instr.name == "IMPORT_FROM" and current_import_name is not None:
Expand Down Expand Up @@ -101,6 +117,9 @@ def inject_import_hooks(
These injected hooks fire only when the interpreter reaches IMPORT_NAME/IMPORT_FROM, so false runtime branches do
not create dependency edges. The hook is inserted after the import opcode, meaning failed imports are not recorded.
"""
if _INJECTION_ASSEMBLY is None:
raise NotImplementedError("Accurate import tracking is not supported on Python 3.15+")
Comment thread
vlad-scherbich marked this conversation as resolved.

if isinstance(code_or_bytecode, CodeType):
bytecode = Bytecode.from_code(code_or_bytecode)
else:
Expand All @@ -111,6 +130,6 @@ def inject_import_hooks(
]

for idx, arg, lineno in reversed(pending_insertions):
bytecode[idx + 1 : idx + 1] = INJECTION_ASSEMBLY.bind(dict(hook=hook, arg=arg), lineno=lineno)
bytecode[idx + 1 : idx + 1] = _INJECTION_ASSEMBLY.bind(dict(hook=hook, arg=arg), lineno=lineno)

return bytecode.to_code()
8 changes: 8 additions & 0 deletions ddtrace/internal/coverage/instrumentation_py3_12.py
Comment thread
wantsui marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@
sys.version.split()[0],
)

# TODO(py-315): Accurate import-hook injection (_DD_COVERAGE_ACCURATE_IMPORTS) is unsupported on
Comment thread
wantsui marked this conversation as resolved.
# 3.15+ because the `bytecode` library's CALL codegen segfaults on exec under CPython 3.15.0rc1,
# which is what ddtrace.internal.bytecode_injection.INJECTION_ASSEMBLY relies on to splice hook
# calls after import opcodes (see import_instrumentation_py3_12.inject_import_hooks). Re-enabling
# this needs either an upstream `bytecode` fix, or reimplementing injection on sys.monitoring
# INSTRUCTION events. Static import tracking (iter_import_events/import_names_by_line) already
# works on 3.15+ and is used as the fallback.

EVENT = sys.monitoring.events.PY_START if _USE_FILE_LEVEL_COVERAGE else sys.monitoring.events.LINE

# NOTE: We try tool slots in priority order (4, 3, 1) to avoid colliding with other tools.
Expand Down
2 changes: 1 addition & 1 deletion riotfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -1235,7 +1235,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT
},
),
Venv(
pys=select_pys(max_version="3.14"),
pys=select_pys(max_version="3.15"),
pkgs={"dramatiq": latest, "pytest": latest, "redis": latest},
),
],
Expand Down
2 changes: 1 addition & 1 deletion scripts/integration_registry/registry.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ integrations:
tested_versions_by_dependency:
dramatiq:
min: 1.10.0
max: 2.2.0
max: 2.2.1

- integration_name: elasticsearch
is_external_package: true
Expand Down
5 changes: 3 additions & 2 deletions supported_versions.json
Original file line number Diff line number Diff line change
Expand Up @@ -1020,12 +1020,13 @@
"3.11",
"3.12",
"3.13",
"3.14"
"3.14",
"3.15"
]
},
"supportedRange": ">=1.10.0",
"tested": [
"2.2.0"
"2.2.1"
]
}
]
Expand Down
2 changes: 1 addition & 1 deletion tests/contrib/suitespec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1152,7 +1152,7 @@ suites:
python: ['3.9']
dependencies: ['dramatiq~=1.10.0', redis, pika]
- name: dramatiq
python: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14']
python: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14', '3.15']
dependencies: [dramatiq, redis]
elasticsearch:
pattern: ^elasticsearch(?!:opensearch)
Expand Down
4 changes: 4 additions & 0 deletions tests/coverage/test_import_dependency_tracking.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,10 @@ def test_no_false_dependencies():


@pytest.mark.skipif(sys.version_info < (3, 12), reason="Test specific to Python 3.12+ monitoring API")
@pytest.mark.skipif(
Comment thread
wantsui marked this conversation as resolved.
sys.version_info >= (3, 15),
reason="TODO(py-315): accurate import injection is not supported on Python 3.15+ (uses INJECTION_ASSEMBLY)",
)
@pytest.mark.subprocess(parametrize={"_DD_COVERAGE_FILE_LEVEL": ["true"], "_DD_COVERAGE_ACCURATE_IMPORTS": ["1"]})
def test_file_level_false_guarded_import_not_tracked():
"""Runtime-false import statements should not create file-level dependency edges."""
Expand Down
19 changes: 19 additions & 0 deletions tests/coverage/test_import_instrumentation_py312.py
Comment thread
wantsui marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,19 @@ def hook(arg):
return seen


@pytest.mark.skipif(
Comment thread
wantsui marked this conversation as resolved.
sys.version_info >= (3, 15), reason="TODO(py-315): accurate import hook injection is not supported on Python 3.15+"
)
def test_import_hook_injection_skips_runtime_false_import():
seen = _exec_with_import_hooks("RUNTIME_FALSE = bool(0)\nif RUNTIME_FALSE:\n import math\nimport json\n")

assert (0, "<import-hook-test>", ("", ("json",))) in seen
assert (0, "<import-hook-test>", ("", ("math",))) not in seen


@pytest.mark.skipif(
sys.version_info >= (3, 15), reason="TODO(py-315): accurate import hook injection is not supported on Python 3.15+"
)
def test_import_hook_injection_tracks_function_local_import_only_when_called():
from ddtrace.internal.coverage.import_instrumentation_py3_12 import inject_import_hooks
from ddtrace.internal.coverage.import_instrumentation_py3_12 import iter_import_events
Expand Down Expand Up @@ -56,6 +62,19 @@ def hook(arg):
assert (0, "<import-hook-test>", ("", ("decimal",))) in seen


@pytest.mark.skipif(
sys.version_info < (3, 15), reason="TODO(py-315): accurate import hook injection is supported before Python 3.15"
)
def test_import_hook_injection_is_not_supported():
from ddtrace.internal.coverage.import_instrumentation_py3_12 import inject_import_hooks
from ddtrace.internal.coverage.import_instrumentation_py3_12 import iter_import_events

code = compile("import json\n", "<import-hook-test>", "exec")

with pytest.raises(NotImplementedError, match="Accurate import tracking is not supported on Python 3.15\\+"):
inject_import_hooks(code, lambda _: None, "<import-hook-test>", iter_import_events(code, ""))


def test_import_event_extraction_groups_from_imports_by_line():
from ddtrace.internal.coverage.import_instrumentation_py3_12 import import_names_by_line
from ddtrace.internal.coverage.import_instrumentation_py3_12 import iter_import_events
Expand Down
Loading