Skip to content

Commit c8b4968

Browse files
chore(compat): add CURRENT_PY_VERSION and bump NEXT_PY_VERSION to 3.16
Centralize the 3.15 wiring bound and the 3.16 fail-closed bound so wrap() can keep raising on 3.15 while NEXT_PY means first unsupported.
1 parent 39cfe79 commit c8b4968

7 files changed

Lines changed: 45 additions & 18 deletions

File tree

ddtrace/internal/compat.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
__all__ = [
1313
"maybe_stringify",
14+
"CURRENT_PY_VERSION",
15+
"CURRENT_PY_VERSION_INFO",
1416
"NEXT_PY_UNSUPPORTED_MSG",
1517
"NEXT_PY_VERSION",
1618
"NEXT_PY_VERSION_INFO",
@@ -19,10 +21,19 @@
1921

2022
PYTHON_VERSION_INFO = sys.version_info
2123

22-
# First CPython version that wrapping / bytecode injection do not support yet.
23-
NEXT_PY_VERSION: str = "3.15"
24-
_next_py_parts = NEXT_PY_VERSION.split(".")[:2]
25-
NEXT_PY_VERSION_INFO: tuple[int, int] = (int(_next_py_parts[0]), int(_next_py_parts[1]))
24+
25+
def _py_version_info(version: str) -> tuple[int, int]:
26+
major, minor = version.split(".")[:2]
27+
return (int(major), int(minor))
28+
29+
30+
# CPython version currently being wired.
31+
CURRENT_PY_VERSION: str = "3.15"
32+
CURRENT_PY_VERSION_INFO: tuple[int, int] = _py_version_info(CURRENT_PY_VERSION)
33+
34+
# First unsupported CPython (exclusive upper bound / fail-closed default).
35+
NEXT_PY_VERSION: str = "3.16"
36+
NEXT_PY_VERSION_INFO: tuple[int, int] = _py_version_info(NEXT_PY_VERSION)
2637
NEXT_PY_UNSUPPORTED_MSG: str = "This version of CPython is not supported yet: {}.{}".format(*sys.version_info[:2])
2738

2839

ddtrace/internal/coverage/instrumentation_py3_12.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from bytecode import Bytecode
1717

1818
from ddtrace.internal.bytecode_injection import HookType
19+
from ddtrace.internal.compat import CURRENT_PY_VERSION_INFO
1920
from ddtrace.internal.coverage.import_instrumentation_py3_12 import ImportName
2021
from ddtrace.internal.coverage.import_instrumentation_py3_12 import ImportNamesByLine
2122
from ddtrace.internal.coverage.import_instrumentation_py3_12 import import_names_by_line
@@ -44,7 +45,7 @@
4445
# In Python 3.15 (PEP 810 lazy imports), IMPORT_NAME's arg is bit-packed:
4546
# bits 2+ = name index into co_names, bits 0-1 = lazy/eager flags.
4647
# So the index is arg >> 2. On 3.12-3.14, arg is a plain index (shift by 0).
47-
_IMPORT_NAME_ARG_SHIFT = 2 if sys.version_info >= (3, 15) else 0
48+
_IMPORT_NAME_ARG_SHIFT = 2 if sys.version_info >= CURRENT_PY_VERSION_INFO else 0
4849

4950
# Detect empty modules: the bytecode pattern varies across Python versions.
5051
# Python 3.12-3.13: RESUME + RETURN_CONST
@@ -56,7 +57,7 @@
5657
# Check if file-level coverage is requested
5758
_USE_FILE_LEVEL_COVERAGE = asbool(env.get("_DD_COVERAGE_FILE_LEVEL", "true"))
5859
_ACCURATE_IMPORTS_REQUESTED = asbool(env.get("_DD_COVERAGE_ACCURATE_IMPORTS", "false"))
59-
_USE_ACCURATE_IMPORTS = sys.version_info < (3, 15) and _ACCURATE_IMPORTS_REQUESTED
60+
_USE_ACCURATE_IMPORTS = sys.version_info < CURRENT_PY_VERSION_INFO and _ACCURATE_IMPORTS_REQUESTED
6061
if _ACCURATE_IMPORTS_REQUESTED and not _USE_ACCURATE_IMPORTS:
6162
log.info(
6263
"_DD_COVERAGE_ACCURATE_IMPORTS is enabled, but accurate import tracking is not supported on Python %s; "

ddtrace/internal/module.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -829,18 +829,18 @@ def _trace(frame, event, arg):
829829

830830

831831
def lazy(f: t.Callable[[], None]) -> None:
832-
from ddtrace.internal.compat import NEXT_PY_VERSION_INFO
832+
from ddtrace.internal.compat import CURRENT_PY_VERSION_INFO
833833
from ddtrace.internal.compat import PYTHON_VERSION_INFO
834834

835835
_globals = sys._getframe(1).f_globals
836836
_initialized = False
837837

838-
if PYTHON_VERSION_INFO < NEXT_PY_VERSION_INFO:
838+
if PYTHON_VERSION_INFO < CURRENT_PY_VERSION_INFO:
839839
LazyWrappingContext(t.cast(FunctionType, f)).wrap()
840840

841841
def __getattr__(name: str) -> t.Any:
842842
nonlocal _initialized
843-
if PYTHON_VERSION_INFO >= NEXT_PY_VERSION_INFO:
843+
if PYTHON_VERSION_INFO >= CURRENT_PY_VERSION_INFO:
844844
if not _initialized:
845845
_exec_lazy_init(t.cast(FunctionType, f), _globals)
846846
_initialized = True

ddtrace/internal/monitoring.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@
2121
from typing import Optional
2222
import weakref
2323

24+
from ddtrace.internal.compat import CURRENT_PY_VERSION
25+
from ddtrace.internal.compat import CURRENT_PY_VERSION_INFO
2426
from ddtrace.internal.logger import get_logger
2527
from ddtrace.internal.threads import Lock
2628

2729

28-
if sys.version_info < (3, 15):
29-
raise ImportError("ddtrace.internal.monitoring requires Python 3.15+")
30+
if sys.version_info < CURRENT_PY_VERSION_INFO:
31+
raise ImportError("ddtrace.internal.monitoring requires Python %s+" % CURRENT_PY_VERSION)
3032

3133
log = get_logger(__name__)
3234

ddtrace/internal/wrapping/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
from bytecode import Instr
1414

1515
from ddtrace.internal.assembly import Assembly
16+
from ddtrace.internal.compat import CURRENT_PY_VERSION_INFO
1617
from ddtrace.internal.compat import NEXT_PY_UNSUPPORTED_MSG
17-
from ddtrace.internal.compat import NEXT_PY_VERSION_INFO
1818
from ddtrace.internal.threads import Lock
1919
from ddtrace.internal.wrapping.asyncs import wrap_async
2020
from ddtrace.internal.wrapping.generators import wrap_generator
@@ -300,7 +300,8 @@ def wrap_bytecode(wrapper: Wrapper, wrapped: FunctionType) -> bc.Bytecode:
300300
return a coroutine function, and so on. The signature is also preserved to
301301
avoid breaking, e.g., usages of the ``inspect`` module.
302302
"""
303-
if PY >= NEXT_PY_VERSION_INFO:
303+
# Still fail-closed on CURRENT_PY_VERSION; NEXT_PY_VERSION is the exclusive upper bound.
304+
if PY >= CURRENT_PY_VERSION_INFO:
304305
raise NotImplementedError(NEXT_PY_UNSUPPORTED_MSG)
305306

306307
code = wrapped.__code__
@@ -349,7 +350,7 @@ def wrap(f: FunctionType, wrapper: Wrapper) -> WrappedFunction:
349350
Note that this changes the behavior of the original function with the
350351
wrapper function, instead of creating a new function object.
351352
"""
352-
if PY >= NEXT_PY_VERSION_INFO:
353+
if PY >= CURRENT_PY_VERSION_INFO:
353354
raise NotImplementedError(NEXT_PY_UNSUPPORTED_MSG)
354355
wrapped = FunctionType(
355356
code := f.__code__,

lib-injection/sources/sitecustomize.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ def parse_version(version):
4343
RUNTIMES_ALLOW_LIST = {
4444
"cpython": {
4545
"min": Version(version=(3, 9), constraint=""),
46+
# Exclusive upper bound; keep in sync with NEXT_PY_VERSION in ddtrace.internal.compat.
4647
"max": Version(version=(3, 16), constraint=""),
4748
}
4849
}

tests/internal/test_py315_import_degrade.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
import pytest
1111

12+
from ddtrace.internal.compat import CURRENT_PY_VERSION
13+
from ddtrace.internal.compat import CURRENT_PY_VERSION_INFO
1214
from ddtrace.internal.compat import NEXT_PY_UNSUPPORTED_MSG
1315
from ddtrace.internal.compat import NEXT_PY_VERSION
1416
from ddtrace.internal.compat import NEXT_PY_VERSION_INFO
@@ -23,6 +25,13 @@ def test_unsupported_msg_includes_running_version():
2325
assert NEXT_PY_UNSUPPORTED_MSG == _UNSUPPORTED_MSG
2426

2527

28+
def test_current_and_next_py_version_constants():
29+
assert CURRENT_PY_VERSION == "3.15"
30+
assert CURRENT_PY_VERSION_INFO == (3, 15)
31+
assert NEXT_PY_VERSION == "3.16"
32+
assert NEXT_PY_VERSION_INFO == (3, 16)
33+
34+
2635
def test_wrapping_modules_import():
2736
import ddtrace.internal.bytecode_injection # noqa: F401
2837
import ddtrace.internal.module # noqa: F401
@@ -31,7 +40,7 @@ def test_wrapping_modules_import():
3140
import ddtrace.internal.wrapping.generators # noqa: F401
3241

3342

34-
@pytest.mark.skipif(PYTHON_VERSION_INFO < NEXT_PY_VERSION_INFO, reason=f"{NEXT_PY_VERSION} wrap() degrade")
43+
@pytest.mark.skipif(PYTHON_VERSION_INFO < CURRENT_PY_VERSION_INFO, reason=f"{CURRENT_PY_VERSION} wrap() degrade")
3544
def test_wrap_raises_not_implemented_on_315():
3645
from ddtrace.internal.wrapping import wrap
3746

@@ -45,7 +54,7 @@ def wrapper(wrapped, args, kwargs): # noqa: ANN001, ANN202
4554
wrap(f, wrapper)
4655

4756

48-
@pytest.mark.skipif(PYTHON_VERSION_INFO < NEXT_PY_VERSION_INFO, reason=f"{NEXT_PY_VERSION} lazy module degrade")
57+
@pytest.mark.skipif(PYTHON_VERSION_INFO < CURRENT_PY_VERSION_INFO, reason=f"{CURRENT_PY_VERSION} lazy module degrade")
4958
def test_lazy_module_decorator_without_bytecode_wrap():
5059
import tests.internal.lazy as lazy_module
5160

@@ -62,7 +71,9 @@ def test_exec_lazy_init_without_source():
6271
assert module_globals["exported"] == 123
6372

6473

65-
@pytest.mark.skipif(PYTHON_VERSION_INFO < NEXT_PY_VERSION_INFO, reason=f"{NEXT_PY_VERSION} debugging products degrade")
74+
@pytest.mark.skipif(
75+
PYTHON_VERSION_INFO < CURRENT_PY_VERSION_INFO, reason=f"{CURRENT_PY_VERSION} debugging products degrade"
76+
)
6677
def test_debugging_products_load_without_failure():
6778
from ddtrace.internal.products import ProductManager
6879

@@ -77,7 +88,7 @@ def test_debugging_products_load_without_failure():
7788
assert product_name not in product_manager._failed
7889

7990

80-
@pytest.mark.skipif(PYTHON_VERSION_INFO < NEXT_PY_VERSION_INFO, reason=f"{NEXT_PY_VERSION} inject_hook degrade")
91+
@pytest.mark.skipif(PYTHON_VERSION_INFO < CURRENT_PY_VERSION_INFO, reason=f"{CURRENT_PY_VERSION} inject_hook degrade")
8192
def test_inject_hook_raises_not_implemented_on_315():
8293
from ddtrace.internal.bytecode_injection import inject_hook
8394

0 commit comments

Comments
 (0)