Repository: getsentry/sentry-python

Issue:
Deprecate `enable_tracing` option
### Problem Statement

We are cleaning up our options to control tracing. The option `enable_tracing` should be deprecated.

See also: https://github.com/getsentry/team-sdks/issues/84

### Solution Brainstorm

Add a deprecation warning telling people that the option will be removed in next major and that they should use `traces_sample_rate` for controlling tracing. 

Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/getsentry/sentry-python/pull/3935?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getsentry) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 78.90%. Comparing base [(`9ff100a`)](https://app.codecov.io/gh/getsentry/sentry-python/commit/9ff100a981e11c8f9bebd1ff51aee59864d693d4?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getsentry) to head [(`e1d7f64`)](https://app.codecov.io/gh/getsentry/sentry-python/commit/e1d7f6464046da5ba79994220fa1424860530102?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getsentry).
> Report is 1 commits behind head on master.

:white_check_mark: All tests successful. No failed tests found.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #3935      +/-   ##
==========================================
- Coverage   80.15%   78.90%   -1.25%     
==========================================
  Files         139      139              
  Lines       15391    15394       +3     
  Branches     2596     2597       +1     
==========================================
- Hits        12337    12147     -190     
- Misses       2208     2403     +195     
+ Partials      846      844       -2     
```

| [Files with missing lines](https://app.codecov.io/gh/getsentry/sentry-python/pull/3935?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getsentry) | Coverage Δ | |
|---|---|---|
| [sentry\_sdk/client.py](https://app.codecov.io/gh/getsentry/sentry-python/pull/3935?src=pr&el=tree&filepath=sentry_sdk%2Fclient.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getsentry#diff-c2VudHJ5X3Nkay9jbGllbnQucHk=) | `77.25% <100.00%> (-1.21%)` | :arrow_down: |

... and [20 files with indirect coverage changes](https://app.codecov.io/gh/getsentry/sentry-python/pull/3935/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getsentry)

</details>

Patch:
diff --git a/.craft.yml b/.craft.yml
index 70875d54..665f0683 100644
--- a/.craft.yml
+++ b/.craft.yml
@@ -25,6 +25,8 @@ targets:
           - python3.9
           - python3.10
           - python3.11
+          - python3.12
+          - python3.13
     license: MIT
   - name: sentry-pypi
     internalPypiRepo: getsentry/pypi
diff --git a/LICENSE b/LICENSE
index c4c8162f..4477bfef 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2018-2024 Functional Software, Inc. dba Sentry
+Copyright (c) 2018-2025 Functional Software, Inc. dba Sentry
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
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 1537ad83..43307c30 100755
--- a/scripts/split_tox_gh_actions/split_tox_gh_actions.py
+++ b/scripts/split_tox_gh_actions/split_tox_gh_actions.py
@@ -89,6 +89,9 @@ GROUPS = {
         "openfeature",
         "unleash",
     ],
+    "Gevent": [
+        "gevent",
+    ],
     "GraphQL": [
         "ariadne",
         "gql",
@@ -96,7 +99,6 @@ GROUPS = {
         "strawberry",
     ],
     "Network": [
-        "gevent",
         "grpc",
         "httpx",
         "requests",
diff --git a/sentry_sdk/client.py b/sentry_sdk/client.py
index cf345c41..cace8cc2 100644
--- a/sentry_sdk/client.py
+++ b/sentry_sdk/client.py
@@ -6,6 +6,7 @@ from collections.abc import Mapping
 from datetime import datetime, timezone
 from importlib import import_module
 from typing import cast, overload
+import warnings
 
 from sentry_sdk._compat import PY37, check_uwsgi_thread_support
 from sentry_sdk.utils import (
@@ -140,6 +141,13 @@ def _get_options(*args, **kwargs):
         )
         rv["socket_options"] = None
 
+    if rv["enable_tracing"] is not None:
+        warnings.warn(
+            "The `enable_tracing` parameter is deprecated. Please use `traces_sample_rate` instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+
     return rv
 
 
diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py
index 7a8917fe..0fead483 100644
--- a/sentry_sdk/utils.py
+++ b/sentry_sdk/utils.py
@@ -1501,7 +1501,7 @@ def qualname_from_function(func):
 
     # Python 3: methods, functions, classes
     if func_qualname is not None:
-        if hasattr(func, "__module__"):
+        if hasattr(func, "__module__") and isinstance(func.__module__, str):
             func_qualname = func.__module__ + "." + func_qualname
         func_qualname = prefix + func_qualname + suffix
 
diff --git a/tox.ini b/tox.ini
index 95c09a57..3cab20a1 100644
--- a/tox.ini
+++ b/tox.ini
@@ -391,7 +391,7 @@ deps =
     celery-v5.3: Celery~=5.3.0
     celery-v5.4: Celery~=5.4.0
     # TODO: update when stable is out
-    celery-v5.5: Celery==5.5.0rc3
+    celery-v5.5: Celery==5.5.0rc4
     celery-latest: Celery
 
     celery: newrelic


Test patch:
diff --git a/.github/workflows/test-integrations-gevent.yml b/.github/workflows/test-integrations-gevent.yml
new file mode 100644
index 00000000..088f952e
--- /dev/null
+++ b/.github/workflows/test-integrations-gevent.yml
@@ -0,0 +1,91 @@
+# Do not edit this YAML file. This file is generated automatically by executing
+# python scripts/split_tox_gh_actions/split_tox_gh_actions.py
+# The template responsible for it is in
+# scripts/split_tox_gh_actions/templates/base.jinja
+name: Test Gevent
+on:
+  push:
+    branches:
+      - master
+      - release/**
+      - potel-base
+  pull_request:
+# Cancel in progress workflows on pull_requests.
+# https://docs.github.com/en/actions/using-jobs/using-concurrency#example-using-a-fallback-value
+concurrency:
+  group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
+  cancel-in-progress: true
+permissions:
+  contents: read
+env:
+  BUILD_CACHE_KEY: ${{ github.sha }}
+  CACHED_BUILD_PATHS: |
+    ${{ github.workspace }}/dist-serverless
+jobs:
+  test-gevent-pinned:
+    name: Gevent (pinned)
+    timeout-minutes: 30
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        python-version: ["3.6","3.8","3.10","3.11","3.12"]
+        # 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
+        # see https://github.com/actions/setup-python/issues/544#issuecomment-1332535877
+        os: [ubuntu-20.04]
+    steps:
+      - uses: actions/checkout@v4.2.2
+      - uses: actions/setup-python@v5
+        with:
+          python-version: ${{ matrix.python-version }}
+          allow-prereleases: true
+      - name: Setup Test Env
+        run: |
+          pip install "coverage[toml]" tox
+      - name: Erase coverage
+        run: |
+          coverage erase
+      - name: Test gevent pinned
+        run: |
+          set -x # print commands that are executed
+          ./scripts/runtox.sh --exclude-latest "py${{ matrix.python-version }}-gevent"
+      - name: Generate coverage XML (Python 3.6)
+        if: ${{ !cancelled() && matrix.python-version == '3.6' }}
+        run: |
+          export COVERAGE_RCFILE=.coveragerc36
+          coverage combine .coverage-sentry-*
+          coverage xml --ignore-errors
+      - name: Generate coverage XML
+        if: ${{ !cancelled() && matrix.python-version != '3.6' }}
+        run: |
+          coverage combine .coverage-sentry-*
+          coverage xml
+      - name: Upload coverage to Codecov
+        if: ${{ !cancelled() }}
+        uses: codecov/codecov-action@v5.1.2
+        with:
+          token: ${{ secrets.CODECOV_TOKEN }}
+          files: coverage.xml
+          # make sure no plugins alter our coverage reports
+          plugin: noop
+          verbose: true
+      - name: Upload test results to Codecov
+        if: ${{ !cancelled() }}
+        uses: codecov/test-results-action@v1
+        with:
+          token: ${{ secrets.CODECOV_TOKEN }}
+          files: .junitxml
+          verbose: true
+  check_required_tests:
+    name: All pinned Gevent tests passed
+    needs: test-gevent-pinned
+    # Always run this, even if a dependent job failed
+    if: always()
+    runs-on: ubuntu-20.04
+    steps:
+      - name: Check for failures
+        if: contains(needs.test-gevent-pinned.result, 'failure') || contains(needs.test-gevent-pinned.result, 'skipped')
+        run: |
+          echo "One of the dependent jobs has failed. You may need to re-run it." && exit 1
diff --git a/.github/workflows/test-integrations-network.yml b/.github/workflows/test-integrations-network.yml
index ab1c5b06..b5593a58 100644
--- a/.github/workflows/test-integrations-network.yml
+++ b/.github/workflows/test-integrations-network.yml
@@ -47,10 +47,6 @@ jobs:
       - name: Erase coverage
         run: |
           coverage erase
-      - name: Test gevent latest
-        run: |
-          set -x # print commands that are executed
-          ./scripts/runtox.sh "py${{ matrix.python-version }}-gevent-latest"
       - name: Test grpc latest
         run: |
           set -x # print commands that are executed
@@ -115,10 +111,6 @@ jobs:
       - name: Erase coverage
         run: |
           coverage erase
-      - name: Test gevent pinned
-        run: |
-          set -x # print commands that are executed
-          ./scripts/runtox.sh --exclude-latest "py${{ matrix.python-version }}-gevent"
       - name: Test grpc pinned
         run: |
           set -x # print commands that are executed
diff --git a/tests/integrations/aws_lambda/test_aws.py b/tests/integrations/aws_lambda/test_aws.py
index e2298123..f60bedc8 100644
--- a/tests/integrations/aws_lambda/test_aws.py
+++ b/tests/integrations/aws_lambda/test_aws.py
@@ -38,10 +38,9 @@ import pytest
 
 RUNTIMES_TO_TEST = [
     "python3.8",
-    "python3.9",
     "python3.10",
-    "python3.11",
     "python3.12",
+    "python3.13",
 ]
 
 LAMBDA_PRELUDE = """
diff --git a/tests/integrations/httpx/test_httpx.py b/tests/integrations/httpx/test_httpx.py
index 17bf7017..107f873a 100644
--- a/tests/integrations/httpx/test_httpx.py
+++ b/tests/integrations/httpx/test_httpx.py
@@ -3,7 +3,6 @@ from unittest import mock
 
 import httpx
 import pytest
-import responses
 
 import sentry_sdk
 from sentry_sdk import capture_message, start_transaction
@@ -16,7 +15,9 @@ from tests.conftest import ApproxDict
     "httpx_client",
     (httpx.Client(), httpx.AsyncClient()),
 )
-def test_crumb_capture_and_hint(sentry_init, capture_events, httpx_client):
+def test_crumb_capture_and_hint(sentry_init, capture_events, httpx_client, httpx_mock):
+    httpx_mock.add_response()
+
     def before_breadcrumb(crumb, hint):
         crumb["data"]["extra"] = "foo"
         return crumb
@@ -24,7 +25,6 @@ def test_crumb_capture_and_hint(sentry_init, capture_events, httpx_client):
     sentry_init(integrations=[HttpxIntegration()], before_breadcrumb=before_breadcrumb)
 
     url = "http://example.com/"
-    responses.add(responses.GET, url, status=200)
 
     with start_transaction():
         events = capture_events()
@@ -61,11 +61,15 @@ def test_crumb_capture_and_hint(sentry_init, capture_events, httpx_client):
     "httpx_client",
     (httpx.Client(), httpx.AsyncClient()),
 )
-def test_outgoing_trace_headers(sentry_init, httpx_client):
-    sentry_init(traces_sample_rate=1.0, integrations=[HttpxIntegration()])
+def test_outgoing_trace_headers(sentry_init, httpx_client, httpx_mock):
+    httpx_mock.add_response()
+
+    sentry_init(
+        traces_sample_rate=1.0,
+        integrations=[HttpxIntegration()],
+    )
 
     url = "http://example.com/"
-    responses.add(responses.GET, url, status=200)
 
     with start_transaction(
         name="/interactions/other-dogs/new-dog",
@@ -93,7 +97,13 @@ def test_outgoing_trace_headers(sentry_init, httpx_client):
     "httpx_client",
     (httpx.Client(), httpx.AsyncClient()),
 )
-def test_outgoing_trace_headers_append_to_baggage(sentry_init, httpx_client):
+def test_outgoing_trace_headers_append_to_baggage(
+    sentry_init,
+    httpx_client,
+    httpx_mock,
+):
+    httpx_mock.add_response()
+
     sentry_init(
         traces_sample_rate=1.0,
         integrations=[HttpxIntegration()],
@@ -101,7 +111,6 @@ def test_outgoing_trace_headers_append_to_baggage(sentry_init, httpx_client):
     )
 
     url = "http://example.com/"
-    responses.add(responses.GET, url, status=200)
 
     with start_transaction(
         name="/interactions/other-dogs/new-dog",
@@ -290,12 +299,13 @@ def test_do_not_propagate_outside_transaction(sentry_init, httpx_mock):
 
 
 @pytest.mark.tests_internal_exceptions
-def test_omit_url_data_if_parsing_fails(sentry_init, capture_events):
+def test_omit_url_data_if_parsing_fails(sentry_init, capture_events, httpx_mock):
+    httpx_mock.add_response()
+
     sentry_init(integrations=[HttpxIntegration()])
 
     httpx_client = httpx.Client()
     url = "http://example.com"
-    responses.add(responses.GET, url, status=200)
 
     events = capture_events()
     with mock.patch(
@@ -326,7 +336,9 @@ def test_omit_url_data_if_parsing_fails(sentry_init, capture_events):
     "httpx_client",
     (httpx.Client(), httpx.AsyncClient()),
 )
-def test_span_origin(sentry_init, capture_events, httpx_client):
+def test_span_origin(sentry_init, capture_events, httpx_client, httpx_mock):
+    httpx_mock.add_response()
+
     sentry_init(
         integrations=[HttpxIntegration()],
         traces_sample_rate=1.0,
@@ -335,7 +347,6 @@ def test_span_origin(sentry_init, capture_events, httpx_client):
     events = capture_events()
 
     url = "http://example.com/"
-    responses.add(responses.GET, url, status=200)
 
     with start_transaction(name="test_transaction"):
         if asyncio.iscoroutinefunction(httpx_client.get):
diff --git a/tests/test_client.py b/tests/test_client.py
index 450e1960..67f53d98 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -1490,3 +1490,9 @@ class TestSpanClientReports:
 )
 def test_dropped_transaction(sentry_init, capture_record_lost_event_calls, test_config):
     test_config.run(sentry_init, capture_record_lost_event_calls)
+
+
+@pytest.mark.parametrize("enable_tracing", [True, False])
+def test_enable_tracing_deprecated(sentry_init, enable_tracing):
+    with pytest.warns(DeprecationWarning):
+        sentry_init(enable_tracing=enable_tracing)
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 6e01bb4f..894638bf 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -951,3 +951,23 @@ def test_format_timestamp_naive():
     # Ensure that some timestamp is returned, without error. We currently treat these as local time, but this is an
     # implementation detail which we should not assert here.
     assert re.fullmatch(timestamp_regex, format_timestamp(datetime_object))
+
+
+def test_qualname_from_function_inner_function():
+    def test_function(): ...
+
+    assert (
+        sentry_sdk.utils.qualname_from_function(test_function)
+        == "tests.test_utils.test_qualname_from_function_inner_function.<locals>.test_function"
+    )
+
+
+def test_qualname_from_function_none_name():
+    def test_function(): ...
+
+    test_function.__module__ = None
+
+    assert (
+        sentry_sdk.utils.qualname_from_function(test_function)
+        == "test_qualname_from_function_none_name.<locals>.test_function"
+    )