diff --git a/.github/workflows/test-integrations-network.yml b/.github/workflows/test-integrations-network.yml index e34706ff09..b2f88d261c 100644 --- a/.github/workflows/test-integrations-network.yml +++ b/.github/workflows/test-integrations-network.yml @@ -22,73 +22,6 @@ env: CACHED_BUILD_PATHS: | ${{ github.workspace }}/dist-serverless jobs: - test-network-latest: - name: Network (latest) - timeout-minutes: 30 - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - python-version: ["3.9","3.12","3.13"] - # 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-22.04] - # Use Docker container only for Python 3.6 - container: ${{ matrix.python-version == '3.6' && 'python:3.6' || null }} - steps: - - uses: actions/checkout@v5.0.0 - - uses: actions/setup-python@v6 - if: ${{ matrix.python-version != '3.6' }} - 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 grpc latest - run: | - set -x # print commands that are executed - ./scripts/runtox.sh "py${{ matrix.python-version }}-grpc-latest" - - name: Test httpx latest - run: | - set -x # print commands that are executed - ./scripts/runtox.sh "py${{ matrix.python-version }}-httpx-latest" - - name: Test requests latest - run: | - set -x # print commands that are executed - ./scripts/runtox.sh "py${{ matrix.python-version }}-requests-latest" - - 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.5.1 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: coverage.xml - # make sure no plugins alter our coverage reports - plugins: 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 test-network-pinned: name: Network (pinned) timeout-minutes: 30 diff --git a/scripts/populate_tox/README.md b/scripts/populate_tox/README.md index 4acebd3259..8014acff3f 100644 --- a/scripts/populate_tox/README.md +++ b/scripts/populate_tox/README.md @@ -107,9 +107,14 @@ This key is optional. ### `python` Sometimes, the whole test suite should only run on specific Python versions. -This can be achieved via the `python` key, which expects a version specifier. +This can be achieved via the `python` key. -For example, if you want AIOHTTP tests to only run on Python 3.7+, you can say: +There are two variants how to define the Python versions to run the test suite +on. + +If you want the test suite to only be run on specific Python versions, you can +set `python` to a version specifier. For example, if you want AIOHTTP tests to +only run on Python 3.7+, you can say: ```python "aiohttp": { @@ -118,12 +123,27 @@ For example, if you want AIOHTTP tests to only run on Python 3.7+, you can say: } ``` +If the Python version to use is dependent on the version of the package under +test, you can use the more expressive dictionary variant. For instance, while +HTTPX v0.28 supports Python 3.8, a test dependency of ours, `pytest-httpx`, +doesn't. If you want to specify that HTTPX test suite should not be run on +a Python version older than 3.9 if the HTTPX version is 0.28 or higher, you can +say: + +```python +"httpx": { + "python": { + # run the test suite for httpx v0.28+ on Python 3.9+ only + ">=0.28": ">=3.9", + }, +} +``` + The `python` key is optional, and when possible, it should be omitted. The script -should automatically detect which Python versions the package supports. -However, if a package has broken -metadata or the SDK is explicitly not supporting some packages on specific -Python versions (because of, for example, broken context vars), the `python` -key can be used. +should automatically detect which Python versions the package supports. However, +if a package has broken metadata or the SDK is explicitly not supporting some +packages on specific Python versions (because of, for example, broken context +vars), the `python` key can be used. ### `include` diff --git a/scripts/populate_tox/config.py b/scripts/populate_tox/config.py index 346cabc2db..cf8b9ae9dc 100644 --- a/scripts/populate_tox/config.py +++ b/scripts/populate_tox/config.py @@ -150,6 +150,25 @@ }, "python": ">=3.7", }, + "httpx": { + "package": "httpx", + "deps": { + "*": ["anyio<4.0.0"], + ">=0.16,<0.17": ["pytest-httpx==0.10.0"], + ">=0.17,<0.19": ["pytest-httpx==0.12.0"], + ">=0.19,<0.21": ["pytest-httpx==0.14.0"], + ">=0.21,<0.23": ["pytest-httpx==0.19.0"], + ">=0.23,<0.24": ["pytest-httpx==0.21.0"], + ">=0.24,<0.25": ["pytest-httpx==0.22.0"], + ">=0.25,<0.26": ["pytest-httpx==0.25.0"], + ">=0.26,<0.27": ["pytest-httpx==0.28.0"], + ">=0.27,<0.28": ["pytest-httpx==0.30.0"], + ">=0.28,<0.29": ["pytest-httpx==0.35.0"], + }, + "python": { + ">=0.28": ">=3.9", + }, + }, "huey": { "package": "huey", }, diff --git a/scripts/populate_tox/populate_tox.py b/scripts/populate_tox/populate_tox.py index a3f1ec8fea..afcef05438 100644 --- a/scripts/populate_tox/populate_tox.py +++ b/scripts/populate_tox/populate_tox.py @@ -63,13 +63,12 @@ "aws_lambda", "cloud_resource_context", "common", + "gcp", "gevent", "opentelemetry", "potel", # Integrations that can be migrated -- we should eventually remove all # of these from the IGNORE list - "gcp", - "httpx", "redis", "requests", "rq", @@ -240,9 +239,9 @@ def _supports_lowest(release: Version) -> bool: sys.exit(1) py_versions = determine_python_versions(pypi_data) - target_python_versions = TEST_SUITE_CONFIG[integration].get("python") - if target_python_versions: - target_python_versions = SpecifierSet(target_python_versions) + target_python_versions = _transform_target_python_versions( + TEST_SUITE_CONFIG[integration].get("python") + ) return bool(supported_python_versions(py_versions, target_python_versions)) if not _supports_lowest(releases[0]): @@ -327,7 +326,10 @@ def _pick_releases( def supported_python_versions( package_python_versions: Union[SpecifierSet, list[Version]], - custom_supported_versions: Optional[SpecifierSet] = None, + custom_supported_versions: Optional[ + Union[SpecifierSet, dict[SpecifierSet, SpecifierSet]] + ] = None, + version: Optional[Version] = None, ) -> list[Version]: """ Get the intersection of Python versions supported by the package and the SDK. @@ -354,9 +356,25 @@ def supported_python_versions( curr = MIN_PYTHON_VERSION while curr <= MAX_PYTHON_VERSION: if curr in package_python_versions: - if not custom_supported_versions or curr in custom_supported_versions: + if not custom_supported_versions: supported.append(curr) + else: + if isinstance(custom_supported_versions, SpecifierSet): + if curr in custom_supported_versions: + supported.append(curr) + + elif version is not None and isinstance( + custom_supported_versions, dict + ): + for v, py in custom_supported_versions.items(): + if version in v: + if curr in py: + supported.append(curr) + break + else: + supported.append(curr) + # Construct the next Python version (i.e., bump the minor) next = [int(v) for v in str(curr).split(".")] next[1] += 1 @@ -535,20 +553,38 @@ def _add_python_versions_to_release( time.sleep(PYPI_COOLDOWN) # give PYPI some breathing room - target_python_versions = TEST_SUITE_CONFIG[integration].get("python") - if target_python_versions: - target_python_versions = SpecifierSet(target_python_versions) + target_python_versions = _transform_target_python_versions( + TEST_SUITE_CONFIG[integration].get("python") + ) release.python_versions = pick_python_versions_to_test( supported_python_versions( determine_python_versions(release_pypi_data), target_python_versions, + release, ) ) release.rendered_python_versions = _render_python_versions(release.python_versions) +def _transform_target_python_versions( + python_versions: Union[str, dict[str, str], None] +) -> Union[SpecifierSet, dict[SpecifierSet, SpecifierSet], None]: + """Wrap the contents of the `python` key in SpecifierSets.""" + if not python_versions: + return None + + if isinstance(python_versions, str): + return SpecifierSet(python_versions) + + if isinstance(python_versions, dict): + updated = {} + for key, value in python_versions.items(): + updated[SpecifierSet(key)] = SpecifierSet(value) + return updated + + def get_file_hash() -> str: """Calculate a hash of the tox.ini file.""" hasher = hashlib.md5() diff --git a/scripts/populate_tox/tox.jinja b/scripts/populate_tox/tox.jinja index 9c511d3f1e..40980197ab 100755 --- a/scripts/populate_tox/tox.jinja +++ b/scripts/populate_tox/tox.jinja @@ -48,13 +48,6 @@ envlist = # GCP {py3.7}-gcp - # HTTPX - {py3.6,py3.9}-httpx-v{0.16,0.18} - {py3.6,py3.10}-httpx-v{0.20,0.22} - {py3.7,py3.11,py3.12}-httpx-v{0.23,0.24} - {py3.9,py3.11,py3.12}-httpx-v{0.25,0.27} - {py3.9,py3.12,py3.13}-httpx-latest - # OpenTelemetry (OTel) {py3.7,py3.9,py3.12,py3.13}-opentelemetry @@ -137,27 +130,6 @@ deps = aws_lambda: requests aws_lambda: uvicorn - # HTTPX - httpx-v0.16: pytest-httpx==0.10.0 - httpx-v0.18: pytest-httpx==0.12.0 - httpx-v0.20: pytest-httpx==0.14.0 - httpx-v0.22: pytest-httpx==0.19.0 - httpx-v0.23: pytest-httpx==0.21.0 - httpx-v0.24: pytest-httpx==0.22.0 - httpx-v0.25: pytest-httpx==0.25.0 - httpx: pytest-httpx - # anyio is a dep of httpx - httpx: anyio<4.0.0 - httpx-v0.16: httpx~=0.16.0 - httpx-v0.18: httpx~=0.18.0 - httpx-v0.20: httpx~=0.20.0 - httpx-v0.22: httpx~=0.22.0 - httpx-v0.23: httpx~=0.23.0 - httpx-v0.24: httpx~=0.24.0 - httpx-v0.25: httpx~=0.25.0 - httpx-v0.27: httpx~=0.27.0 - httpx-latest: httpx - # OpenTelemetry (OTel) opentelemetry: opentelemetry-distro diff --git a/sentry_sdk/integrations/__init__.py b/sentry_sdk/integrations/__init__.py index 2f5a1f397e..e397c9986a 100644 --- a/sentry_sdk/integrations/__init__.py +++ b/sentry_sdk/integrations/__init__.py @@ -141,6 +141,7 @@ def iter_default_integrations(with_auto_enabling_integrations): "gql": (3, 4, 1), "graphene": (3, 3), "grpc": (1, 32, 0), # grpcio + "httpx": (0, 16, 0), "huggingface_hub": (0, 24, 7), "langchain": (0, 1, 0), "langgraph": (0, 6, 6), diff --git a/tox.ini b/tox.ini index 15192bc0ad..b646228b7f 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,7 @@ # The file (and all resulting CI YAMLs) then need to be regenerated via # "scripts/generate-test-files.sh". # -# Last generated: 2025-09-18T10:26:22.484602+00:00 +# Last generated: 2025-09-18T10:42:56.677852+00:00 [tox] requires = @@ -48,13 +48,6 @@ envlist = # GCP {py3.7}-gcp - # HTTPX - {py3.6,py3.9}-httpx-v{0.16,0.18} - {py3.6,py3.10}-httpx-v{0.20,0.22} - {py3.7,py3.11,py3.12}-httpx-v{0.23,0.24} - {py3.9,py3.11,py3.12}-httpx-v{0.25,0.27} - {py3.9,py3.12,py3.13}-httpx-latest - # OpenTelemetry (OTel) {py3.7,py3.9,py3.12,py3.13}-opentelemetry @@ -202,6 +195,11 @@ envlist = {py3.7,py3.11,py3.12}-grpc-v1.62.3 {py3.9,py3.12,py3.13}-grpc-v1.75.0 + {py3.6,py3.8,py3.9}-httpx-v0.16.1 + {py3.6,py3.9,py3.10}-httpx-v0.20.0 + {py3.7,py3.10,py3.11}-httpx-v0.24.1 + {py3.9,py3.11,py3.12}-httpx-v0.28.1 + # ~~~ Tasks ~~~ {py3.7,py3.9,py3.10}-arq-v0.23 @@ -368,27 +366,6 @@ deps = aws_lambda: requests aws_lambda: uvicorn - # HTTPX - httpx-v0.16: pytest-httpx==0.10.0 - httpx-v0.18: pytest-httpx==0.12.0 - httpx-v0.20: pytest-httpx==0.14.0 - httpx-v0.22: pytest-httpx==0.19.0 - httpx-v0.23: pytest-httpx==0.21.0 - httpx-v0.24: pytest-httpx==0.22.0 - httpx-v0.25: pytest-httpx==0.25.0 - httpx: pytest-httpx - # anyio is a dep of httpx - httpx: anyio<4.0.0 - httpx-v0.16: httpx~=0.16.0 - httpx-v0.18: httpx~=0.18.0 - httpx-v0.20: httpx~=0.20.0 - httpx-v0.22: httpx~=0.22.0 - httpx-v0.23: httpx~=0.23.0 - httpx-v0.24: httpx~=0.24.0 - httpx-v0.25: httpx~=0.25.0 - httpx-v0.27: httpx~=0.27.0 - httpx-latest: httpx - # OpenTelemetry (OTel) opentelemetry: opentelemetry-distro @@ -592,6 +569,16 @@ deps = grpc: types-protobuf grpc: pytest-asyncio + httpx-v0.16.1: httpx==0.16.1 + httpx-v0.20.0: httpx==0.20.0 + httpx-v0.24.1: httpx==0.24.1 + httpx-v0.28.1: httpx==0.28.1 + httpx: anyio<4.0.0 + httpx-v0.16.1: pytest-httpx==0.10.0 + httpx-v0.20.0: pytest-httpx==0.14.0 + httpx-v0.24.1: pytest-httpx==0.22.0 + httpx-v0.28.1: pytest-httpx==0.35.0 + # ~~~ Tasks ~~~ arq-v0.23: arq==0.23