Skip to content

draft: create_async_http_client#4421

Draft
dsfaccini wants to merge 7 commits intomainfrom
dsfaccini/remove-http-client-cache-provider-lifecycle
Draft

draft: create_async_http_client#4421
dsfaccini wants to merge 7 commits intomainfrom
dsfaccini/remove-http-client-cache-provider-lifecycle

Conversation

@dsfaccini
Copy link
Collaborator

  • Add plan: remove HTTP client cache + provider lifecycle management
  • replace cached_async_http_client with create_async_http_client

dsfaccini and others added 2 commits February 23, 2026 17:56
Proposal to fix #3913 — cached_async_http_client uses @functools.cache
with no event-loop awareness, causing RuntimeError in multi-worker envs.

Three-layer approach:
1. Replace cache with factory (create_async_http_client)
2. Add __aenter__/__aexit__ to Provider base class
3. Thread lifecycle through Agent → Model → Provider

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions github-actions bot added size: L Large PR (501-1500 weighted lines) feature New feature request, or PR implementing a feature (enhancement) labels Feb 24, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Feb 24, 2026

Docs Preview

commit: febb2d6
Preview URL: https://8af7c374-pydantic-ai-previews.pydantic.workers.dev


def _with_http_client(provider: Provider[Any]) -> Provider[Any]:
if own_http_client:
provider._own_http_client = http_client # pyright: ignore[reportPrivateUsage]
Copy link
Contributor

Choose a reason for hiding this comment

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

This helper reaches into the provider's private _own_http_client from outside the class. This happens because the gateway creates the httpx client and passes it to provider constructors in a way that hits the "user-provided client" branch (e.g. AnthropicProvider(anthropic_client=AsyncAnthropic(..., http_client=http_client))), so the provider constructor doesn't know it should claim ownership.

Consider whether providers should accept a parameter to indicate ownership (e.g. an internal _http_client_owned: bool flag, or a dedicated constructor path), so external callers don't need to mutate private state. @DouweM

@github-actions
Copy link
Contributor

A few high-level notes:

Issue reference: The PR body doesn't reference the issue this implements (#3913). Please add Closes #3913 (or Related to #3913 if this is only a partial implementation) so reviewers and maintainers can easily trace the context and DouweM's design guidance.

Documentation: This PR adds new public behavior — providers and models are now async context managers, and async with agent: now enters the model/provider chain for clean HTTP client teardown. This should be documented (at minimum in the agents docs where async with agent: is already covered, and possibly in a new section on provider lifecycle). The create_async_http_client replacement for cached_async_http_client should also be noted in the changelog/migration notes.

Missing FallbackModel handling: See inline commentFallbackModel wraps multiple models but doesn't override __aenter__/__aexit__, so none of its wrapped models' providers will be lifecycle-managed.

Resource leak in _ssrf.py: See inline commentsafe_download creates a new httpx client per call that's never closed.

Good progress on a tricky refactor — the core idea of replacing the process-global cache with per-provider ownership is sound and well-aligned with DouweM's guidance on #3913.



def cached_async_http_client(
def create_async_http_client(
Copy link
Contributor

Choose a reason for hiding this comment

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

The provider parameter in this new public function is dead code — it's not passed to httpx.AsyncClient() and there's no caching to use it as a discriminator anymore. Since this is a brand new function (not a deprecated alias), now is the cleanest time to remove it rather than shipping an unused parameter in the public API that users will wonder about.

If there's value in keeping it for debugging/logging (e.g. custom User-Agent per provider, or as a label in error messages), it should actually do something. Otherwise, remove it and let the test fixture use its own caching key that isn't coupled to the public API signature.

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

Labels

feature New feature request, or PR implementing a feature (enhancement) size: L Large PR (501-1500 weighted lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant