Skip to content

Commit c27343f

Browse files
committed
don't share httpx.AsyncHTTPTransport between event loops
1 parent 63af922 commit c27343f

File tree

1 file changed

+19
-8
lines changed

1 file changed

+19
-8
lines changed

pydantic_ai_slim/pydantic_ai/models/__init__.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66

77
from __future__ import annotations as _annotations
88

9+
import asyncio
910
from abc import ABC, abstractmethod
1011
from collections.abc import AsyncIterator, Iterator
1112
from contextlib import asynccontextmanager, contextmanager
1213
from dataclasses import dataclass, field
1314
from datetime import datetime
14-
from functools import cache
15+
from functools import cache, lru_cache
1516

1617
import httpx
1718
from typing_extensions import Literal, TypeAliasType
@@ -495,25 +496,35 @@ def cached_async_http_client(*, provider: str | None = None, timeout: int = 600,
495496
The default timeouts match those of OpenAI,
496497
see <https://github.com/openai/openai-python/blob/v1.54.4/src/openai/_constants.py#L9>.
497498
"""
498-
client = _cached_async_http_client(provider=provider, timeout=timeout, connect=connect)
499+
try:
500+
loop = asyncio.get_running_loop()
501+
except RuntimeError:
502+
loop = None
503+
504+
client = _cached_async_http_client(loop=loop, provider=provider, timeout=timeout, connect=connect)
499505
if client.is_closed:
500506
# This happens if the context manager is used, so we need to create a new client.
501507
_cached_async_http_client.cache_clear()
502-
client = _cached_async_http_client(provider=provider, timeout=timeout, connect=connect)
508+
client = _cached_async_http_client(loop=loop, provider=provider, timeout=timeout, connect=connect)
503509
return client
504510

505511

506-
@cache
507-
def _cached_async_http_client(provider: str | None, timeout: int = 600, connect: int = 5) -> httpx.AsyncClient:
512+
@lru_cache(maxsize=32)
513+
def _cached_async_http_client(
514+
loop: asyncio.AbstractEventLoop, provider: str | None, timeout: int = 600, connect: int = 5
515+
) -> httpx.AsyncClient:
508516
return httpx.AsyncClient(
509-
transport=_cached_async_http_transport(),
517+
transport=_cached_async_http_transport(loop=loop),
510518
timeout=httpx.Timeout(timeout=timeout, connect=connect),
511519
headers={'User-Agent': get_user_agent()},
512520
)
513521

514522

515-
@cache
516-
def _cached_async_http_transport() -> httpx.AsyncHTTPTransport:
523+
@lru_cache(maxsize=32)
524+
def _cached_async_http_transport(loop: asyncio.AbstractEventLoop) -> httpx.AsyncHTTPTransport:
525+
# The loop argument is unused, but it's here to ensure the cache key is different
526+
# for each event loop, because a `httpx.AsyncHTTPTransport instanciated in a loop
527+
# cannot be used in another loop.
517528
return httpx.AsyncHTTPTransport()
518529

519530

0 commit comments

Comments
 (0)