@@ -174,6 +174,21 @@ async def aclose(self) -> None:
174174 weakref .WeakKeyDictionary ()
175175)
176176
177+ # Separate HTTP/1.1 pool for bulk file upload/download. Keeping these off the
178+ # main HTTP/2 connection avoids H2 session-window / stream-slot contention with
179+ # latency-sensitive RPCs (create, wait_for_status, execute, …).
180+ _shared_sync_transfer_transport : _SharedTransport | None = None
181+ _shared_async_transfer_transports : weakref .WeakKeyDictionary [
182+ asyncio .AbstractEventLoop , _SharedAsyncTransport
183+ ] = weakref .WeakKeyDictionary ()
184+
185+ # Paths that carry large request/response bodies and should use the transfer pool.
186+ _FILE_TRANSFER_PATH_SUFFIXES = ("/upload_file" , "/download_file" )
187+
188+
189+ def _is_file_transfer_path (path : str ) -> bool :
190+ return path .endswith (_FILE_TRANSFER_PATH_SUFFIXES )
191+
177192# TODO: make base page type vars covariant
178193SyncPageT = TypeVar ("SyncPageT" , bound = "BaseSyncPage[Any]" )
179194AsyncPageT = TypeVar ("AsyncPageT" , bound = "BaseAsyncPage[Any]" )
@@ -929,8 +944,10 @@ def __del__(self) -> None:
929944
930945class SyncAPIClient (BaseClient [httpx .Client , Stream [Any ]]):
931946 _client : httpx .Client
947+ _transfer_client : httpx .Client | None
932948 _default_stream_cls : type [Stream [Any ]] | None = None
933949 _uses_shared_pool : bool
950+ _isolate_file_transfers : bool
934951 _closed : bool
935952
936953 def __init__ (
@@ -976,6 +993,9 @@ def __init__(
976993 )
977994
978995 self ._closed = False
996+ self ._transfer_client = None
997+ # Custom http_client owns the full transport stack; don't invent a sibling pool.
998+ self ._isolate_file_transfers = http_client is None
979999
9801000 if http_client is not None :
9811001 self ._client = http_client
@@ -1000,6 +1020,38 @@ def __init__(
10001020 )
10011021 self ._uses_shared_pool = False
10021022
1023+ def _ensure_transfer_client (self ) -> httpx .Client :
1024+ """Lazy HTTP/1.1 client for upload_file / download_file."""
1025+ if self ._transfer_client is not None :
1026+ return self ._transfer_client
1027+
1028+ timeout = cast (Timeout , self .timeout )
1029+ if self ._uses_shared_pool :
1030+ global _shared_sync_transfer_transport
1031+ with _pool_lock :
1032+ if _shared_sync_transfer_transport is None or not _shared_sync_transfer_transport .acquire ():
1033+ _shared_sync_transfer_transport = _SharedTransport (
1034+ httpx .HTTPTransport (limits = DEFAULT_CONNECTION_LIMITS , http2 = False ),
1035+ )
1036+ self ._transfer_client = SyncHttpxClientWrapper (
1037+ base_url = self ._base_url ,
1038+ timeout = timeout ,
1039+ transport = _shared_sync_transfer_transport ,
1040+ http2 = False ,
1041+ )
1042+ else :
1043+ self ._transfer_client = SyncHttpxClientWrapper (
1044+ base_url = self ._base_url ,
1045+ timeout = timeout ,
1046+ http2 = False ,
1047+ )
1048+ return self ._transfer_client
1049+
1050+ def _send_client_for_request (self , request : httpx .Request ) -> httpx .Client :
1051+ if self ._isolate_file_transfers and _is_file_transfer_path (request .url .path ):
1052+ return self ._ensure_transfer_client ()
1053+ return self ._client
1054+
10031055 def is_closed (self ) -> bool :
10041056 return self ._closed or self ._client .is_closed
10051057
@@ -1014,6 +1066,10 @@ def close(self) -> None:
10141066 return
10151067 self ._closed = True
10161068 self ._client .close ()
1069+ transfer = self ._transfer_client
1070+ self ._transfer_client = None
1071+ if transfer is not None :
1072+ transfer .close ()
10171073
10181074 def __enter__ (self : _T ) -> _T :
10191075 return self
@@ -1114,7 +1170,7 @@ def request(
11141170
11151171 response = None
11161172 try :
1117- response = self ._client .send (
1173+ response = self ._send_client_for_request ( request ) .send (
11181174 request ,
11191175 stream = stream or self ._should_stream_response_body (request = request ),
11201176 ** kwargs ,
@@ -1561,8 +1617,10 @@ def __del__(self) -> None:
15611617
15621618class AsyncAPIClient (BaseClient [httpx .AsyncClient , AsyncStream [Any ]]):
15631619 _client : httpx .AsyncClient
1620+ _transfer_client : httpx .AsyncClient | None
15641621 _default_stream_cls : type [AsyncStream [Any ]] | None = None
15651622 _uses_shared_pool : bool
1623+ _isolate_file_transfers : bool
15661624 _closed : bool
15671625
15681626 def __init__ (
@@ -1608,6 +1666,9 @@ def __init__(
16081666 )
16091667
16101668 self ._closed = False
1669+ self ._transfer_client = None
1670+ # Custom http_client owns the full transport stack; don't invent a sibling pool.
1671+ self ._isolate_file_transfers = http_client is None
16111672
16121673 if http_client is not None :
16131674 self ._client = http_client
@@ -1646,6 +1707,47 @@ def __init__(
16461707 )
16471708 self ._uses_shared_pool = False
16481709
1710+ def _ensure_transfer_client (self ) -> httpx .AsyncClient :
1711+ """Lazy HTTP/1.1 client for upload_file / download_file."""
1712+ if self ._transfer_client is not None :
1713+ return self ._transfer_client
1714+
1715+ timeout = cast (Timeout , self .timeout )
1716+ if self ._uses_shared_pool :
1717+ try :
1718+ loop : asyncio .AbstractEventLoop | None = asyncio .get_running_loop ()
1719+ except RuntimeError :
1720+ loop = None
1721+ if loop is not None :
1722+ with _pool_lock :
1723+ existing = _shared_async_transfer_transports .get (loop )
1724+ if existing is not None and existing .acquire ():
1725+ transport : _SharedAsyncTransport = existing
1726+ else :
1727+ transport = _SharedAsyncTransport (
1728+ httpx .AsyncHTTPTransport (limits = DEFAULT_CONNECTION_LIMITS , http2 = False ),
1729+ )
1730+ _shared_async_transfer_transports [loop ] = transport
1731+ self ._transfer_client = AsyncHttpxClientWrapper (
1732+ base_url = self ._base_url ,
1733+ timeout = timeout ,
1734+ transport = transport ,
1735+ http2 = False ,
1736+ )
1737+ return self ._transfer_client
1738+
1739+ self ._transfer_client = AsyncHttpxClientWrapper (
1740+ base_url = self ._base_url ,
1741+ timeout = timeout ,
1742+ http2 = False ,
1743+ )
1744+ return self ._transfer_client
1745+
1746+ def _send_client_for_request (self , request : httpx .Request ) -> httpx .AsyncClient :
1747+ if self ._isolate_file_transfers and _is_file_transfer_path (request .url .path ):
1748+ return self ._ensure_transfer_client ()
1749+ return self ._client
1750+
16491751 def is_closed (self ) -> bool :
16501752 return self ._closed or self ._client .is_closed
16511753
@@ -1660,6 +1762,10 @@ async def close(self) -> None:
16601762 return
16611763 self ._closed = True
16621764 await self ._client .aclose ()
1765+ transfer = self ._transfer_client
1766+ self ._transfer_client = None
1767+ if transfer is not None :
1768+ await transfer .aclose ()
16631769
16641770 async def __aenter__ (self : _T ) -> _T :
16651771 return self
@@ -1765,7 +1871,7 @@ async def request(
17651871
17661872 response = None
17671873 try :
1768- response = await self ._client .send (
1874+ response = await self ._send_client_for_request ( request ) .send (
17691875 request ,
17701876 stream = stream or self ._should_stream_response_body (request = request ),
17711877 ** kwargs ,
0 commit comments