diff --git a/.github/workflows/test-integrations-common.yml b/.github/workflows/test-integrations-common.yml index 7d4a9a9566..0d1f2a90f8 100644 --- a/.github/workflows/test-integrations-common.yml +++ b/.github/workflows/test-integrations-common.yml @@ -29,7 +29,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.6","3.7","3.8","3.9","3.10","3.11","3.12","3.13","3.14"] + python-version: ["3.6","3.7","3.8","3.9","3.10","3.11","3.12","3.13","3.14","3.14t"] # python3.6 reached EOL and is no longer being supported on # new versions of hosted runners on Github Actions # ubuntu-20.04 is the last version that supported python3.6 diff --git a/.github/workflows/test-integrations-misc.yml b/.github/workflows/test-integrations-misc.yml index 365f0f47bc..56a31bff27 100644 --- a/.github/workflows/test-integrations-misc.yml +++ b/.github/workflows/test-integrations-misc.yml @@ -29,7 +29,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.6","3.7","3.8","3.9","3.10","3.11","3.12","3.13","3.14"] + python-version: ["3.6","3.7","3.8","3.9","3.10","3.11","3.12","3.13","3.14","3.14t"] # python3.6 reached EOL and is no longer being supported on # new versions of hosted runners on Github Actions # ubuntu-20.04 is the last version that supported python3.6 diff --git a/.github/workflows/test-integrations-web-2.yml b/.github/workflows/test-integrations-web-2.yml index 699915c387..2cd0980962 100644 --- a/.github/workflows/test-integrations-web-2.yml +++ b/.github/workflows/test-integrations-web-2.yml @@ -29,7 +29,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.6","3.7","3.8","3.9","3.10","3.11","3.12","3.13","3.14"] + python-version: ["3.6","3.7","3.8","3.9","3.10","3.11","3.12","3.13","3.14","3.14t"] # python3.6 reached EOL and is no longer being supported on # new versions of hosted runners on Github Actions # ubuntu-20.04 is the last version that supported python3.6 diff --git a/scripts/populate_tox/tox.jinja b/scripts/populate_tox/tox.jinja index ccf4c0e98b..4de4d94b5f 100755 --- a/scripts/populate_tox/tox.jinja +++ b/scripts/populate_tox/tox.jinja @@ -18,7 +18,7 @@ requires = virtualenv<20.26.3 envlist = # === Common === - {py3.6,py3.7,py3.8,py3.9,py3.10,py3.11,py3.12,py3.13,py3.14}-common + {py3.6,py3.7,py3.8,py3.9,py3.10,py3.11,py3.12,py3.13,py3.14,py3.14t}-common # === Gevent === {py3.6,py3.8,py3.10,py3.11,py3.12}-gevent @@ -26,7 +26,7 @@ envlist = # === Integrations === # Asgi - {py3.7,py3.12,py3.13,py3.14}-asgi + {py3.7,py3.12,py3.13,py3.14,py3.14t}-asgi # AWS Lambda {py3.8,py3.9,py3.11,py3.13}-aws_lambda @@ -38,7 +38,7 @@ envlist = {py3.7}-gcp # OpenTelemetry (OTel) - {py3.7,py3.9,py3.12,py3.13,py3.14}-opentelemetry + {py3.7,py3.9,py3.12,py3.13,py3.14,py3.14t}-opentelemetry # OpenTelemetry Experimental (POTel) {py3.8,py3.9,py3.10,py3.11,py3.12,py3.13}-potel @@ -74,7 +74,7 @@ deps = # and https://github.com/pytest-dev/pytest-forked/issues/67 # for justification of the upper bound on pytest {py3.6,py3.7}-common: pytest<7.0.0 - {py3.8,py3.9,py3.10,py3.11,py3.12,py3.13,py3.14}-common: pytest + {py3.8,py3.9,py3.10,py3.11,py3.12,py3.13,py3.14,py3.14t}-common: pytest # === Gevent === {py3.6,py3.7,py3.8,py3.9,py3.10,py3.11}-gevent: gevent>=22.10.0, <22.11.0 @@ -134,6 +134,9 @@ setenv = OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES COVERAGE_FILE=.coverage-sentry-{envname} py3.6: COVERAGE_RCFILE=.coveragerc36 + # Lowest version to support free-threading + # https://discuss.python.org/t/announcement-pip-24-1-release/56281 + py3.14t: VIRTUALENV_PIP=24.1 django: DJANGO_SETTINGS_MODULE=tests.integrations.django.myapp.settings spark-v{3.0.3,3.5.6}: JAVA_HOME=/usr/lib/jvm/temurin-11-jdk-amd64 @@ -178,6 +181,7 @@ basepython = py3.12: python3.12 py3.13: python3.13 py3.14: python3.14 + py3.14t: python3.14t # Python version is pinned here for consistency across environments. # Tools like ruff and mypy have options that pin the target Python diff --git a/scripts/split_tox_gh_actions/split_tox_gh_actions.py b/scripts/split_tox_gh_actions/split_tox_gh_actions.py index 9dea95842b..12222eff1b 100755 --- a/scripts/split_tox_gh_actions/split_tox_gh_actions.py +++ b/scripts/split_tox_gh_actions/split_tox_gh_actions.py @@ -28,7 +28,7 @@ TOXENV_REGEX = re.compile( r""" - {?(?P(py\d+\.\d+,?)+)}? + {?(?P(py\d+\.\d+t?,?)+)}? -(?P[a-z](?:[a-z_]|-(?!v{?\d))*[a-z0-9]) (?:-( (v{?(?P[0-9.]+[0-9a-z,.]*}?)) @@ -250,11 +250,19 @@ def find_frameworks_missing_from_groups(py_versions): return all_frameworks - frameworks_in_a_group +def _version_key(v): + major_version, minor_version_and_suffix = v.split(".") + if minor_version_and_suffix.endswith("t"): + return int(major_version), int(minor_version_and_suffix.rstrip("t")), 1 + + return int(major_version), int(minor_version_and_suffix), 0 + + def _normalize_py_versions(py_versions): def replace_and_sort(versions): return sorted( [py.replace("py", "") for py in versions], - key=lambda v: tuple(map(int, v.split("."))), + key=_version_key, ) if isinstance(py_versions, dict): diff --git a/tests/integrations/threading/test_threading.py b/tests/integrations/threading/test_threading.py index 9c9a24aa63..788fb24fdf 100644 --- a/tests/integrations/threading/test_threading.py +++ b/tests/integrations/threading/test_threading.py @@ -1,4 +1,5 @@ import gc +import sys from concurrent import futures from textwrap import dedent from threading import Thread @@ -68,7 +69,8 @@ def stage2(): assert exception["mechanism"]["type"] == "threading" assert not exception["mechanism"]["handled"] - if propagate_hub: + # Free-threaded builds set thread_inherit_context to True, otherwise thread_inherit_context is False + if propagate_hub or getattr(sys.flags, "thread_inherit_context", None): assert event["tags"]["stage1"] == "true" else: assert "stage1" not in event.get("tags", {}) @@ -94,7 +96,8 @@ def double(number): sentry_sdk.flush() - if propagate_hub: + # Free-threaded builds set thread_inherit_context to True, otherwise thread_inherit_context is False + if propagate_hub or getattr(sys.flags, "thread_inherit_context", None): assert len(events) == 1 (event,) = events assert event["spans"][0]["trace_id"] == event["spans"][1]["trace_id"] @@ -248,7 +251,9 @@ def do_some_work(number): t.join() (event,) = events - if propagate_scope: + + # Free-threaded builds set thread_inherit_context to True, otherwise thread_inherit_context is False + if propagate_scope or getattr(sys.flags, "thread_inherit_context", None): assert render_span_tree(event) == dedent( """\ - op="outer-trx": description=null @@ -309,7 +314,8 @@ def do_some_work(number): (event,) = events - if propagate_scope: + # Free-threaded builds set thread_inherit_context to True, otherwise thread_inherit_context is False + if propagate_scope or getattr(sys.flags, "thread_inherit_context", None): assert render_span_tree(event) == dedent( """\ - op="outer-trx": description=null diff --git a/tox.ini b/tox.ini index 5dc4c77d41..ef7b0e85b9 100644 --- a/tox.ini +++ b/tox.ini @@ -18,7 +18,7 @@ requires = virtualenv<20.26.3 envlist = # === Common === - {py3.6,py3.7,py3.8,py3.9,py3.10,py3.11,py3.12,py3.13,py3.14}-common + {py3.6,py3.7,py3.8,py3.9,py3.10,py3.11,py3.12,py3.13,py3.14,py3.14t}-common # === Gevent === {py3.6,py3.8,py3.10,py3.11,py3.12}-gevent @@ -26,7 +26,7 @@ envlist = # === Integrations === # Asgi - {py3.7,py3.12,py3.13,py3.14}-asgi + {py3.7,py3.12,py3.13,py3.14,py3.14t}-asgi # AWS Lambda {py3.8,py3.9,py3.11,py3.13}-aws_lambda @@ -38,7 +38,7 @@ envlist = {py3.7}-gcp # OpenTelemetry (OTel) - {py3.7,py3.9,py3.12,py3.13,py3.14}-opentelemetry + {py3.7,py3.9,py3.12,py3.13,py3.14,py3.14t}-opentelemetry # OpenTelemetry Experimental (POTel) {py3.8,py3.9,py3.10,py3.11,py3.12,py3.13}-potel @@ -303,7 +303,7 @@ deps = # and https://github.com/pytest-dev/pytest-forked/issues/67 # for justification of the upper bound on pytest {py3.6,py3.7}-common: pytest<7.0.0 - {py3.8,py3.9,py3.10,py3.11,py3.12,py3.13,py3.14}-common: pytest + {py3.8,py3.9,py3.10,py3.11,py3.12,py3.13,py3.14,py3.14t}-common: pytest # === Gevent === {py3.6,py3.7,py3.8,py3.9,py3.10,py3.11}-gevent: gevent>=22.10.0, <22.11.0 @@ -728,6 +728,9 @@ setenv = OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES COVERAGE_FILE=.coverage-sentry-{envname} py3.6: COVERAGE_RCFILE=.coveragerc36 + # Lowest version to support free-threading + # https://discuss.python.org/t/announcement-pip-24-1-release/56281 + py3.14t: VIRTUALENV_PIP=24.1 django: DJANGO_SETTINGS_MODULE=tests.integrations.django.myapp.settings spark-v{3.0.3,3.5.6}: JAVA_HOME=/usr/lib/jvm/temurin-11-jdk-amd64 @@ -824,6 +827,7 @@ basepython = py3.12: python3.12 py3.13: python3.13 py3.14: python3.14 + py3.14t: python3.14t # Python version is pinned here for consistency across environments. # Tools like ruff and mypy have options that pin the target Python