Skip to content

Raise agnostic HTTP exceptions and narrow integration catch sites#24310

Merged
mwdd146980 merged 23 commits into
mwdd146980/httpx-migration-basefrom
mwdd146980/floor-exception-translation
Jul 13, 2026
Merged

Raise agnostic HTTP exceptions and narrow integration catch sites#24310
mwdd146980 merged 23 commits into
mwdd146980/httpx-migration-basefrom
mwdd146980/floor-exception-translation

Conversation

@mwdd146980

@mwdd146980 mwdd146980 commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Establishes the backend-agnostic exception floor for the httpx migration: the shared HTTP wrapper in datadog_checks_base raises library-neutral exception types, and the integrations that catch HTTP errors are moved onto those types in the same change.

Note on the openstack integration: This integration specifically has some bugs that AI reviewers may flag, but given that it is legacy/deprecated in favor of the openstack_controller integration, we've decided to preserve existing behavior.

Motivation

Integrations today catch requests.exceptions.* directly, which couples every check to the HTTP library and blocks any incremental move to httpx. Translating errors once, in the wrapper, turns the underlying library into an implementation detail: integrations catch stable, neutral exceptions and won't need to change again when the transport is swapped.

The base change and the integration updates are inseparable. The moment the wrapper stops emitting requests.exceptions.*, any catch site still keyed to those types silently stops matching, for example a health check that quietly fails to report CRITICAL. Shipping them together, and on the mwdd146980/httpx-migration-base feature branch rather than master, guarantees no Agent release is ever exposed to that half-migrated state.

Verification

Base and per-integration unit suites pass; openstack was additionally verified locally. Docker-gated suites rely on CI. Run ddev test -fs datadog_checks_base for the base package.

Review checklist (to be filled by reviewers)

  • Feature or bugfix MUST have appropriate tests (unit, integration, e2e)
  • Add qa/required if this PR needs QA validation, or qa/skip-qa if it does not. Exactly one of the two is required.
  • If you need to backport this PR to another branch, you can add the backport/<branch-name> label to the PR and it will automatically open a backport PR once this one is merged

@mwdd146980 mwdd146980 added the qa/skip-qa Automatically skip this PR for the next QA label Jul 2, 2026
@datadog-official

datadog-official Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Pipelines  Tests  Code Coverage

Fix all issues with BitsAI

⚠️ Warnings

🚦 2 Pipeline jobs failed

PR All | test / j06ca546 / SNMP   View in Datadog   GitHub Actions

PR All | test / jeebd4ae / Kubernetes Scheduler   View in Datadog   GitHub Actions

🧪 2 Tests failed in 1 job

PR All | run   GitHub Actions

test_e2e from test_e2e.py   View in Datadog
Needed exactly 2 candidates for &#39;kube_scheduler.prometheus.health&#39;, got 0
Expected:
        ServiceCheckStub(check_id=None, name=&#39;kube_scheduler.prometheus.health&#39;, status=2, tags=[&#39;endpoint:http://localhost:10251/metrics&#39;], hostname=None, message=None)
Difference to closest:


Similar submitted:
Score   Most similar

❄️ Known flaky: test_e2e_snmp_listener from test_e2e_snmp_listener.py   View in Datadog
Needed at least 1 candidates for &#39;datadog.snmp.check_duration&#39;, got 0
Expected:
        MetricStub(name=&#39;datadog.snmp.check_duration&#39;, type=0, value=None, tags=[&#39;autodiscovery_subnet:172.18.0.0/28&#39;, &#39;device_vendor:apc&#39;, &#39;firmware_version:2.0.3-test&#39;, &#39;loader:python&#39;, &#39;model:APC Smart-UPS 600&#39;, &#39;serial_num:test_serial&#39;, &#39;snmp_device:172.18.0.1&#39;, &#39;snmp_profile:apc_ups&#39;, &#39;ups_name:testIdentName&#39;], hostname=None, device=None, flush_first_value=None)
Difference to closest:
        Expected tag snmp_device:172.18.0.1
        Found snmp_device:172.18.0.2

Similar submitted:
Score   Most similar
1.00    MetricStub(name=&#39;datadog.snmp.check_duration&#39;, type=0, value=0.16432833671569824, tags=[&#39;autodiscovery_subnet:172.18.0.0/28&#39;, &#39;device_vendor:apc&#39;, &#39;firmware_version:2.0.3-test&#39;, &#39;loader:python&#39;, &#39;model:APC Smart-UPS 600&#39;, &#39;serial_num:test_serial&#39;, &#39;snmp_device:172.18.0.2&#39;, &#39;snmp_profile:apc_ups&#39;, &#39;ups_name:testIdentName&#39;], hostname=&#39;runnervmos4r4&#39;, device=None, flush_first_value=False)
...

Not introduced in this PR.

ℹ️ Info

No other issues found (see more)

❄️ No new flaky tests detected

🎯 Code Coverage (details)
Patch Coverage: 89.33%
Overall Coverage: 88.37% (+0.01%)

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: aec3f5a | Docs | Datadog PR Page | Give us feedback!

…r boundary

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mwdd146980
mwdd146980 force-pushed the mwdd146980/floor-exception-translation branch from e2668ca to 35c85f5 Compare July 2, 2026 22:04
mwdd146980 and others added 7 commits July 6, 2026 13:12
Cover the translator directly (full mapping table with an exact-type ordering
guard), the streaming seam (iter_content/iter_lines), the buffered seam
(content/text/json via a failing-response stand-in), and JSON parse convergence
to the stdlib json.JSONDecodeError.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The wrapper now raises HTTPConnectionError, which the requests-only catch in
stream_connection_lines no longer matched, silently disabling
ignore_connection_errors for OM v2. Narrow the catch to the agnostic type and
drop the dead requests import.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ix poll docstrings

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… raw response

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dary

ResponseWrapper overrode iter_content/iter_lines but not __iter__, so a
direct `for chunk in response` forwarded through wrapt to the raw
requests.Response, whose __iter__ delegates to the raw iter_content and
re-raised untranslated requests exceptions. Override __iter__ to route
through the translating iter_content, keeping the wrapper the sole
translation choke point.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mwdd146980 mwdd146980 changed the title Translate requests exceptions to agnostic types at the RequestsWrapper boundary Raise backend-agnostic HTTP exceptions from the base check request wrapper Jul 7, 2026
mwdd146980 and others added 6 commits July 7, 2026 15:27
Round-2 review of PR #24310 found the OpenMetrics v2 stream scraper
narrowed its `except ConnectionError` to `except HTTPConnectionError`.
The translator maps requests' ConnectTimeout to HTTPTimeoutError (a
sibling, not a subclass), so connect timeouts under
ignore_connection_errors=True escaped instead of being swallowed. Catch
both agnostic types and parametrize the test over them.

Also address the round's minor findings:
- route ResponseWrapper.raise_for_status through _translate_requests_exception
  via a new optional response= param, dropping duplicated extraction
- add type hints to the two new module-level translation helpers
- drop the unlinked Step 0.1 subpage reference from the translator docstring
- finish the half-migrated poll() docstrings (requests.Response -> response)
- add the 24310.changed changelog entry

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Round-3 review polish for PR #24310:
- annotate the response kwarg on _translate_requests_exception (ResponseWrapper | None)
- declare __iter__ on HTTPResponseProtocol so the frozen contract matches the
  ResponseWrapper.__iter__ seam this PR added

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
HTTPResponseProtocol gained __iter__ in this PR; give the mock the same
iter_content(128) delegation so a test iterating a mock_http response
doesn't raise TypeError.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The (HTTPConnectionError, HTTPTimeoutError) catch in stream_connection_lines
also swallows a mid-stream ReadTimeout under ignore_connection_errors, since
the agnostic hierarchy has no connect-vs-read timeout distinction. Document
this as intentional per round-5 review feedback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
24310 merges into the httpx-migration-base feature branch (PR 22676), which
merges to master. Rename 24310.changed to 22676.changed so the behavior-change
entry appears under the PR that actually reaches master. It coexists with the
existing 22676.added (additive infra); together they reflect the full batch, and
the .changed entry preserves the major version bump.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fold 22676.added into 22676.changed so the batch merging to master carries a
single entry. The behavior change (agnostic exceptions) makes the whole batch a
major bump, so the consolidated entry is typed changed and also covers the
added agnostic mocks/proto/exceptions and test migration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mwdd146980 and others added 2 commits July 8, 2026 15:08
The base RequestsWrapper now raises the backend-agnostic exceptions in
datadog_checks.base.utils.http_exceptions instead of requests.exceptions,
so these checks must catch the agnostic types to keep working after the
httpx migration. Narrow the production catch sites and update the coupled
test assertions accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Route the seven bare requests calls (three instance methods and the four
auth/catalog classmethods, via a threaded http client) through self.http,
so they raise backend-agnostic exceptions after the httpx migration. Narrow
the ~10 catch sites to the agnostic types and drop import requests. ssl_verify
is an init_config option here, so bridge it to the instance-level tls_verify
the shared client resolves.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mwdd146980 mwdd146980 changed the title Raise backend-agnostic HTTP exceptions from the base check request wrapper Raise backend-agnostic HTTP exceptions and narrow integration catch sites Jul 8, 2026
@mwdd146980 mwdd146980 self-assigned this Jul 9, 2026
mwdd146980 and others added 3 commits July 9, 2026 09:40
The agnostic reachability-failure tuple (HTTPStatusError,
HTTPTimeoutError, HTTPConnectionError) was repeated at six catch
sites. Extract it into the API_TRANSPORT_ERRORS module constant so
the next migration batch has a single place to update.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
request_auth_token, get_token_for_project, and request_project_list
dereference http immediately and are always called with a real client.
Drop the http=None default so a missing argument fails fast with a
TypeError at the call site instead of an opaque AttributeError deep in
the method. The from_config entry points keep the default because they
can raise IncompleteConfig before http is used.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The standalone openstack integration is legacy and close to
deprecation. Per team decision, leave it exactly as it is rather than
migrate it onto the shared HTTP client. This backs out all of this
PR's openstack changes (self.http routing, the ssl_verify to
tls_verify bridge, the required-http signatures, and the agnostic
catch-site narrowing), restoring the file to the feature-branch base.
openstack keeps its bare requests calls and catches requests
exceptions, so it is unaffected by the wrapper exception translation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Route the OpenStack API and Keystone auth requests through the shared
HTTP client and catch the backend-agnostic HTTPStatusError,
HTTPTimeoutError, and HTTPConnectionError instead of requests
exceptions. Behavior is preserved: the Keystone auth request keeps its
legacy default of skipping TLS verification unless ssl_verify is set,
carried through as an explicit per-call verify override rather than
inheriting the shared client's verify-by-default. The dormant
unscoped-catalog fallback is left in place untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mwdd146980
mwdd146980 marked this pull request as ready for review July 9, 2026 15:36
@mwdd146980
mwdd146980 requested review from a team as code owners July 9, 2026 15:36
@mwdd146980 mwdd146980 changed the title Raise backend-agnostic HTTP exceptions and narrow integration catch sites Raiseagnostic HTTP exceptions and narrow integration catch sites Jul 9, 2026
@mwdd146980 mwdd146980 changed the title Raiseagnostic HTTP exceptions and narrow integration catch sites Raise agnostic HTTP exceptions and narrow integration catch sites Jul 9, 2026
Rewrite the httpx floor changelog entries in plain language centered on
the actual change (backend-agnostic HTTP exceptions), dropping the
internal migration references and the exception class-name lists. The
base entry keeps its .changed classification since raising different
exception types is breaking for anything catching requests errors off
self.http.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@triviajon triviajon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Migrating to generic HTTP erros in the openmetrics scraper LGTM

self.log.error("Invalid SSL settings for requesting %s endpoint", endpoint)
raise
except IOError:
except (IOError, HTTPRequestError):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this intentional?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, basically because transport failures are translated as HTTPRequestError or a subclass here and that no longer subclasses IOError, but it's still possible for an IOError to be raised (e.g. FileNotFoundError)

…exception-translation

# Conflicts:
#	teleport/changelog.d/22676.fixed
@dd-octo-sts

dd-octo-sts Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Validation Report

All 21 validations passed.

Show details
Validation Description Status
agent-reqs Verify check versions match the Agent requirements file
ci Validate CI configuration and code coverage settings
codeowners Validate every integration has a CODEOWNERS entry
config Validate default configuration files against spec.yaml
dep Verify dependency pins are consistent and Agent-compatible
http Validate integrations use the HTTP wrapper correctly
imports Validate check imports do not use deprecated modules
integration-style Validate check code style conventions
jmx-metrics Validate JMX metrics definition files and config
labeler Validate PR labeler config matches integration directories
legacy-signature Validate no integration uses the legacy Agent check signature
license-headers Validate Python files have proper license headers
licenses Validate third-party license attribution list
metadata Validate metadata.csv metric definitions
models Validate configuration data models match spec.yaml
openmetrics Validate OpenMetrics integrations disable the metric limit
package Validate Python package metadata and naming
qa-label Validate the pull request declares whether it needs QA for the next Agent release
readmes Validate README files have required sections
saved-views Validate saved view JSON file structure and fields
version Validate version consistency between package and changelog

View full run

@mwdd146980
mwdd146980 merged commit 2eaab66 into mwdd146980/httpx-migration-base Jul 13, 2026
344 of 347 checks passed
@mwdd146980
mwdd146980 deleted the mwdd146980/floor-exception-translation branch July 13, 2026 14:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment