6060 RequestOptions ,
6161 ModelBuilderProtocol ,
6262)
63- from ._utils import is_dict , is_list , is_given , lru_cache , is_mapping
63+ from ._utils import is_dict , is_list , asyncify , is_given , lru_cache , is_mapping
6464from ._compat import model_copy , model_dump
6565from ._models import GenericModel , FinalRequestOptions , validate_type , construct_type
6666from ._response import (
@@ -358,6 +358,7 @@ def __init__(
358358 self ._custom_query = custom_query or {}
359359 self ._strict_response_validation = _strict_response_validation
360360 self ._idempotency_header = None
361+ self ._platform : Platform | None = None
361362
362363 if max_retries is None : # pyright: ignore[reportUnnecessaryComparison]
363364 raise TypeError (
@@ -456,7 +457,7 @@ def _build_request(
456457 raise RuntimeError (f"Unexpected JSON data type, { type (json_data )} , cannot merge with `extra_body`" )
457458
458459 headers = self ._build_headers (options )
459- params = _merge_mappings (self ._custom_query , options .params )
460+ params = _merge_mappings (self .default_query , options .params )
460461 content_type = headers .get ("Content-Type" )
461462
462463 # If the given Content-Type header is multipart/form-data then it
@@ -592,6 +593,12 @@ def default_headers(self) -> dict[str, str | Omit]:
592593 ** self ._custom_headers ,
593594 }
594595
596+ @property
597+ def default_query (self ) -> dict [str , object ]:
598+ return {
599+ ** self ._custom_query ,
600+ }
601+
595602 def _validate_headers (
596603 self ,
597604 headers : Headers , # noqa: ARG002
@@ -616,7 +623,10 @@ def base_url(self, url: URL | str) -> None:
616623 self ._base_url = self ._enforce_trailing_slash (url if isinstance (url , URL ) else URL (url ))
617624
618625 def platform_headers (self ) -> Dict [str , str ]:
619- return platform_headers (self ._version )
626+ # the actual implementation is in a separate `lru_cache` decorated
627+ # function because adding `lru_cache` to methods will leak memory
628+ # https://github.com/python/cpython/issues/88476
629+ return platform_headers (self ._version , platform = self ._platform )
620630
621631 def _parse_retry_after_header (self , response_headers : Optional [httpx .Headers ] = None ) -> float | None :
622632 """Returns a float of the number of seconds (not milliseconds) to wait after retrying, or None if unspecified.
@@ -1492,6 +1502,11 @@ async def _request(
14921502 stream_cls : type [_AsyncStreamT ] | None ,
14931503 remaining_retries : int | None ,
14941504 ) -> ResponseT | _AsyncStreamT :
1505+ if self ._platform is None :
1506+ # `get_platform` can make blocking IO calls so we
1507+ # execute it earlier while we are in an async context
1508+ self ._platform = await asyncify (get_platform )()
1509+
14951510 cast_to = self ._maybe_override_cast_to (cast_to , options )
14961511 await self ._prepare_options (options )
14971512
@@ -1915,11 +1930,11 @@ def get_platform() -> Platform:
19151930
19161931
19171932@lru_cache (maxsize = None )
1918- def platform_headers (version : str ) -> Dict [str , str ]:
1933+ def platform_headers (version : str , * , platform : Platform | None ) -> Dict [str , str ]:
19191934 return {
19201935 "X-Stainless-Lang" : "python" ,
19211936 "X-Stainless-Package-Version" : version ,
1922- "X-Stainless-OS" : str (get_platform ()),
1937+ "X-Stainless-OS" : str (platform or get_platform ()),
19231938 "X-Stainless-Arch" : str (get_architecture ()),
19241939 "X-Stainless-Runtime" : get_python_runtime (),
19251940 "X-Stainless-Runtime-Version" : get_python_version (),
0 commit comments