Skip to content

feat: Add experimental async transport (port of PR #4572)#5646

Draft
BYK wants to merge 5 commits intomasterfrom
feat/async-transport
Draft

feat: Add experimental async transport (port of PR #4572)#5646
BYK wants to merge 5 commits intomasterfrom
feat/async-transport

Conversation

@BYK
Copy link
Member

@BYK BYK commented Mar 12, 2026

Add an experimental async transport using httpcore's async backend,
enabled via _experiments={"transport_async": True}.

This is a manual port of PR #4572 (originally merged into potel-base)
onto the current master branch.

Key changes

  • transport.py: Refactor BaseHttpTransport into HttpTransportCore
    (shared base) + BaseHttpTransport (sync) + AsyncHttpTransport
    (async, conditional on httpcore[asyncio]). Extract shared helpers:
    _handle_request_error, _handle_response, _update_headers,
    _prepare_envelope. Update make_transport() to detect the
    transport_async experiment.

  • worker.py: Add Worker ABC base class and AsyncWorker
    implementation using asyncio.Queue / asyncio.Task.

  • client.py: Add close_async() / flush_async() with async-vs-sync
    transport detection. Extract _close_components() / _flush_components().

  • api.py: Expose flush_async() as a public API.

  • integrations/asyncio.py: Patch loop.close to flush pending events
    before shutdown. Skip span wrapping for internal Sentry tasks.

  • utils.py: Add is_internal_task() / mark_sentry_task_internal()
    via ContextVar for async task filtering.

  • setup.py: Add "asyncio" extras_require (httpcore[asyncio]==1.*).

  • config.py / tox.ini: Widen anyio to >=3,<5 for httpx and FastAPI.

Notes

  • tox.ini was manually edited (the generation script requires a
    free-threaded Python interpreter). A full regeneration should be done
    before merge.

Refs: GH-4568

@github-actions
Copy link
Contributor

github-actions bot commented Mar 12, 2026

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

Pydantic Ai

  • Support ImageUrl content type in span instrumentation by ericapisani in #5629
  • Add tool description to execute_tool spans by ericapisani in #5596

Other

  • (crons) Add owner field to MonitorConfig by julwhitney13 in #5610
  • (otlp) Add collector_url option to OTLPIntegration by sl0thentr0py in #5603

Bug Fixes 🐛

  • (celery) Propagate user-set headers by sentrivana in #5581
  • (utils) Avoid double serialization of strings in safe_serialize by ericapisani in #5587

Documentation 📚

  • (openai-agents) Remove inapplicable comment by alexander-alderman-webb in #5495
  • Add AGENTS.md by sentrivana in #5579
  • Add set_attribute example to changelog by sentrivana in #5578

Internal Changes 🔧

Docs

  • Switch agentic workflows from Copilot to Claude engine by dingsdax in #5654
  • Add agentic workflows for codebase documentation by dingsdax in #5649

Openai Agents

  • Do not fail on new tool fields by alexander-alderman-webb in #5625
  • Stop expecting a specific function name by alexander-alderman-webb in #5623
  • Set streaming header when library uses with_streaming_response() by alexander-alderman-webb in #5583
  • Replace mocks with httpx for streamed responses by alexander-alderman-webb in #5580
  • Replace mocks with httpx in non-MCP tool tests by alexander-alderman-webb in #5602
  • Replace mocks with httpx in MCP tool tests by alexander-alderman-webb in #5605
  • Replace mocks with httpx in handoff tests by alexander-alderman-webb in #5604
  • Replace mocks with httpx in API error test by alexander-alderman-webb in #5601
  • Replace mocks with httpx in non-error single-response tests by alexander-alderman-webb in #5600
  • Remove test for unreachable state by alexander-alderman-webb in #5584
  • Expect namespace tool field for new openai versions by alexander-alderman-webb in #5599

Other

  • (graphene) Simplify span creation by sentrivana in #5648
  • (httpx) Resolve type checking failures by alexander-alderman-webb in #5626
  • (pyramid) Support alpha suffixes in version parsing by alexander-alderman-webb in #5618
  • (rust) Don't implement separate scope management by sentrivana in #5639
  • (strawberry) Simplify span creation by sentrivana in #5647
  • Add httpx to linting requirements by alexander-alderman-webb in #5644
  • Remove CodeQL action by sentrivana in #5616
  • Normalize dots in package names in populate_tox.py by alexander-alderman-webb in #5574
  • Do not run actions on potel-base by sentrivana in #5614

🤖 This preview updates automatically when you update the PR.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 12, 2026

Codecov Results 📊

13 passed | Total: 13 | Pass Rate: 100% | Execution Time: 7.72s

All tests are passing successfully.

❌ Patch coverage is 18.41%. Project has 14359 uncovered lines.

Files with missing lines (7)
File Patch % Lines
utils.py 52.65% ⚠️ 437 Missing and 79 partials
transport.py 20.99% ⚠️ 429 Missing and 6 partials
client.py 53.42% ⚠️ 252 Missing and 56 partials
worker.py 22.37% ⚠️ 170 Missing
asyncio.py 0.00% ⚠️ 113 Missing
api.py 64.56% ⚠️ 56 Missing
consts.py 99.43% ⚠️ 2 Missing

Generated by Codecov Action

@github-actions
Copy link
Contributor

Codecov Results 📊


Generated by Codecov Action

Add an experimental async transport using httpcore's async backend,
enabled via `_experiments={"transport_async": True}`.

This is a manual port of PR #4572 (originally merged into `potel-base`)
onto the current `master` branch.

Key changes:
- Refactor `BaseHttpTransport` into `HttpTransportCore` (shared base) +
  `BaseHttpTransport` (sync) + `AsyncHttpTransport` (async, conditional
  on httpcore[asyncio])
- Add `Worker` ABC and `AsyncWorker` using asyncio.Queue/Task
- Add `close_async()` / `flush_async()` to client and public API
- Patch `loop.close` in asyncio integration to flush before shutdown
- Add `is_internal_task()` ContextVar to skip wrapping Sentry-internal tasks
- Add `asyncio` extras_require (`httpcore[asyncio]==1.*`)
- Widen anyio constraint to `>=3,<5` for httpx and FastAPI

Refs: GH-4568

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@BYK BYK force-pushed the feat/async-transport branch from 8c808bf to 4f8a00c Compare March 12, 2026 15:45
The base class _make_pool returns a union of sync and async pool types,
so mypy sees _pool.request() as possibly returning a non-awaitable.
Add type: ignore[misc] since within AsyncHttpTransport the pool is
always an async type.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
BYK and others added 2 commits March 12, 2026 16:12
The asyncio extra on httpcore pulls in anyio, which conflicts with
starlette's anyio<4.0.0 pin and causes pip to downgrade httpcore to
0.18.0. That old version crashes on Python 3.14 due to typing.Union
not having __module__.

Keep httpcore[http2] in requirements-testing.txt (shared by all envs)
and add httpcore[asyncio] only to linters, mypy, and common envs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- AsyncWorker.kill() now calls self._task.cancel() before clearing the
  reference, preventing duplicate consumers if submit() is called later
- close() with AsyncHttpTransport now does best-effort sync cleanup
  (kill transport, close components) instead of silently returning
- flush()/close() log warnings instead of debug when async transport used
- Add __aenter__/__aexit__ to _Client for 'async with' support

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Asyncio and gevent don't mix — async tests using asyncio.run() fail
under gevent's monkey-patching. Add skip_under_gevent decorator to
all async tests in test_transport.py and test_client.py.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment on lines +932 to +945
assert calls[0] == ("on_dropped_event", "connection_error")
assert calls[1][0:2] == ("record_lost_event", "network_error")
assert calls[2][0:2] == ("record_lost_event", "network_error")


@skip_under_gevent
@pytest.mark.asyncio
@pytest.mark.parametrize("debug", (True, False))
@pytest.mark.parametrize("client_flush_method", ["close", "flush"])
@pytest.mark.parametrize("use_pickle", (True, False))
@pytest.mark.parametrize("compression_level", (0, 9, None))
@pytest.mark.parametrize("compression_algo", ("gzip", "br", "<invalid>", None))
@pytest.mark.skipif(not PY38, reason="Async transport only supported in Python 3.8+")
async def test_transport_works_async(
Copy link

Choose a reason for hiding this comment

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

Excessive test parameterization creates combinatorial explosion

The test_transport_works_async test at line 932 has 5 parametrized dimensions: debug (2), client_flush_method (2), use_pickle (2), compression_level (3), and compression_algo (4). This creates 2×2×2×3×4 = 96 test combinations. Combined with inherent async test overhead, this may significantly slow down CI runs and makes debugging failures difficult.

Verification

Read the test file to count parametrization decorators and calculated the Cartesian product of all parameter values.

Identified by Warden code-review · 9RA-X2A

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant