cliV1 0.0.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. cliv1-0.0.7.dist-info/METADATA +212 -0
  2. cliv1-0.0.7.dist-info/RECORD +92 -0
  3. cliv1-0.0.7.dist-info/WHEEL +4 -0
  4. cliv1-0.0.7.dist-info/licenses/LICENSE +21 -0
  5. swagger_petstore_open_api_3_1_0/__init__.py +15 -0
  6. swagger_petstore_open_api_3_1_0/apis/__init__.py +5 -0
  7. swagger_petstore_open_api_3_1_0/apis/pet_api.py +922 -0
  8. swagger_petstore_open_api_3_1_0/apis/store.py +432 -0
  9. swagger_petstore_open_api_3_1_0/apis/user_api.py +829 -0
  10. swagger_petstore_open_api_3_1_0/async_client.py +87 -0
  11. swagger_petstore_open_api_3_1_0/auth.py +17 -0
  12. swagger_petstore_open_api_3_1_0/base_client.py +27 -0
  13. swagger_petstore_open_api_3_1_0/client.py +85 -0
  14. swagger_petstore_open_api_3_1_0/core/__init__.py +395 -0
  15. swagger_petstore_open_api_3_1_0/core/_internal/__init__.py +9 -0
  16. swagger_petstore_open_api_3_1_0/core/_internal/content_disposition.py +154 -0
  17. swagger_petstore_open_api_3_1_0/core/_internal/flattening.py +104 -0
  18. swagger_petstore_open_api_3_1_0/core/_internal/headers.py +243 -0
  19. swagger_petstore_open_api_3_1_0/core/_internal/redaction.py +208 -0
  20. swagger_petstore_open_api_3_1_0/core/_internal/sse_parser.py +124 -0
  21. swagger_petstore_open_api_3_1_0/core/_internal/subscripts.py +50 -0
  22. swagger_petstore_open_api_3_1_0/core/_internal/urls.py +172 -0
  23. swagger_petstore_open_api_3_1_0/core/_internal/wire.py +201 -0
  24. swagger_petstore_open_api_3_1_0/core/adapters.py +177 -0
  25. swagger_petstore_open_api_3_1_0/core/auth/__init__.py +70 -0
  26. swagger_petstore_open_api_3_1_0/core/auth/composition.py +175 -0
  27. swagger_petstore_open_api_3_1_0/core/auth/models.py +216 -0
  28. swagger_petstore_open_api_3_1_0/core/auth/protocols.py +103 -0
  29. swagger_petstore_open_api_3_1_0/core/auth/schemes/__init__.py +94 -0
  30. swagger_petstore_open_api_3_1_0/core/auth/schemes/api_key_auth.py +53 -0
  31. swagger_petstore_open_api_3_1_0/core/auth/schemes/basic_auth.py +73 -0
  32. swagger_petstore_open_api_3_1_0/core/auth/schemes/bearer_auth.py +21 -0
  33. swagger_petstore_open_api_3_1_0/core/auth/schemes/oauth2_authorization_code.py +395 -0
  34. swagger_petstore_open_api_3_1_0/core/auth/schemes/oauth2_client_credentials.py +130 -0
  35. swagger_petstore_open_api_3_1_0/core/auth/schemes/oauth2_password.py +145 -0
  36. swagger_petstore_open_api_3_1_0/core/auth/schemes/oauth2_schemes.py +300 -0
  37. swagger_petstore_open_api_3_1_0/core/base_raw_response.py +49 -0
  38. swagger_petstore_open_api_3_1_0/core/bodies.py +590 -0
  39. swagger_petstore_open_api_3_1_0/core/converters/__init__.py +68 -0
  40. swagger_petstore_open_api_3_1_0/core/converters/date_time.py +237 -0
  41. swagger_petstore_open_api_3_1_0/core/converters/encoded_bytes.py +273 -0
  42. swagger_petstore_open_api_3_1_0/core/converters/open_enum.py +35 -0
  43. swagger_petstore_open_api_3_1_0/core/decoding.py +498 -0
  44. swagger_petstore_open_api_3_1_0/core/discriminators.py +129 -0
  45. swagger_petstore_open_api_3_1_0/core/event_streams.py +225 -0
  46. swagger_petstore_open_api_3_1_0/core/exceptions.py +78 -0
  47. swagger_petstore_open_api_3_1_0/core/file_responses.py +337 -0
  48. swagger_petstore_open_api_3_1_0/core/files.py +174 -0
  49. swagger_petstore_open_api_3_1_0/core/httpx2_transport.py +748 -0
  50. swagger_petstore_open_api_3_1_0/core/logging.py +472 -0
  51. swagger_petstore_open_api_3_1_0/core/models.py +143 -0
  52. swagger_petstore_open_api_3_1_0/core/optionality.py +144 -0
  53. swagger_petstore_open_api_3_1_0/core/pagination.py +452 -0
  54. swagger_petstore_open_api_3_1_0/core/paginator.py +55 -0
  55. swagger_petstore_open_api_3_1_0/core/params.py +157 -0
  56. swagger_petstore_open_api_3_1_0/core/raw_client.py +480 -0
  57. swagger_petstore_open_api_3_1_0/core/request_options.py +172 -0
  58. swagger_petstore_open_api_3_1_0/core/results.py +178 -0
  59. swagger_petstore_open_api_3_1_0/core/retries.py +237 -0
  60. swagger_petstore_open_api_3_1_0/core/runtime_env.py +43 -0
  61. swagger_petstore_open_api_3_1_0/core/servers.py +33 -0
  62. swagger_petstore_open_api_3_1_0/core/transport.py +218 -0
  63. swagger_petstore_open_api_3_1_0/errors/__init__.py +48 -0
  64. swagger_petstore_open_api_3_1_0/errors/add_pet_error.py +21 -0
  65. swagger_petstore_open_api_3_1_0/errors/delete_order_error.py +21 -0
  66. swagger_petstore_open_api_3_1_0/errors/delete_pet_error.py +21 -0
  67. swagger_petstore_open_api_3_1_0/errors/delete_user_error.py +21 -0
  68. swagger_petstore_open_api_3_1_0/errors/find_pets_by_status_error.py +21 -0
  69. swagger_petstore_open_api_3_1_0/errors/find_pets_by_tags_error.py +21 -0
  70. swagger_petstore_open_api_3_1_0/errors/get_order_by_id_error.py +21 -0
  71. swagger_petstore_open_api_3_1_0/errors/get_pet_by_id_error.py +21 -0
  72. swagger_petstore_open_api_3_1_0/errors/get_user_by_name_error.py +21 -0
  73. swagger_petstore_open_api_3_1_0/errors/login_user_error.py +21 -0
  74. swagger_petstore_open_api_3_1_0/errors/place_order_error.py +21 -0
  75. swagger_petstore_open_api_3_1_0/errors/update_pet_error.py +21 -0
  76. swagger_petstore_open_api_3_1_0/errors/update_pet_with_form_error.py +21 -0
  77. swagger_petstore_open_api_3_1_0/errors/update_user_error.py +21 -0
  78. swagger_petstore_open_api_3_1_0/errors/upload_file_error.py +21 -0
  79. swagger_petstore_open_api_3_1_0/models/__init__.py +23 -0
  80. swagger_petstore_open_api_3_1_0/models/api_response.py +18 -0
  81. swagger_petstore_open_api_3_1_0/models/category.py +15 -0
  82. swagger_petstore_open_api_3_1_0/models/enums/__init__.py +4 -0
  83. swagger_petstore_open_api_3_1_0/models/enums/order_status.py +17 -0
  84. swagger_petstore_open_api_3_1_0/models/enums/pet_status.py +17 -0
  85. swagger_petstore_open_api_3_1_0/models/order.py +27 -0
  86. swagger_petstore_open_api_3_1_0/models/pet.py +28 -0
  87. swagger_petstore_open_api_3_1_0/models/tag_model.py +15 -0
  88. swagger_petstore_open_api_3_1_0/models/user.py +29 -0
  89. swagger_petstore_open_api_3_1_0/py.typed +0 -0
  90. swagger_petstore_open_api_3_1_0/server/__init__.py +21 -0
  91. swagger_petstore_open_api_3_1_0/server/server.py +17 -0
  92. swagger_petstore_open_api_3_1_0/server/server_config.py +52 -0
@@ -0,0 +1,157 @@
1
+ """Request parameters and the unresolved URL they are attached to.
2
+
3
+ A :class:`Param` is one query, header, path or form parameter: a key, the value the endpoint
4
+ received, and the adapter for the type it was declared as. The declared type decides how the value
5
+ reaches the wire -- a temporal alias carries its own serializer, so ``RFC1123DateTime`` and
6
+ ``UnixSecondsDateTime`` render the same ``datetime`` as a string and as an int -- and is what
7
+ dict-shaped input is validated against. How a *collection* explodes into keys is the one choice a
8
+ type cannot express, so it stays a field (:class:`SerializationFormat`)."""
9
+
10
+ from __future__ import annotations
11
+
12
+ from collections.abc import Sequence
13
+ from dataclasses import dataclass
14
+ from enum import Enum
15
+ from typing import Any, Final, Generic, TypeVar
16
+
17
+ from pydantic import TypeAdapter
18
+ from typing_extensions import TypeForm
19
+
20
+ from ._internal.subscripts import SubscriptOnly
21
+ from .adapters import adapter_for, validation_target
22
+
23
+ T = TypeVar("T")
24
+
25
+
26
+ class SerializationFormat(Enum):
27
+ """How a multi-valued *query or form* parameter explodes into keys.
28
+
29
+ Only those two destinations can honour it, because only they can repeat or subscript a key. A
30
+ path array folds into one comma-separated segment and a header array into one comma-separated
31
+ field value -- OpenAPI's default ``style: simple`` for both, and RFC 9110's own rule for a
32
+ repeated header -- so a path or header :class:`Param` carries this field and nothing reads it.
33
+ That is the price of one ``Param`` and one :func:`param` factory serving every location a
34
+ parameter can occupy, and it is a price rather than a defect: the alternative is a parameter
35
+ type per destination, and with it a generator branch deciding which to emit -- the conditional
36
+ emission ADR-0012 removed.
37
+
38
+ The remaining path and header styles -- ``label``, ``matrix``, ``explode: true`` -- are not
39
+ modelled, and cannot be from here: nothing below this module carries ``style`` or ``explode``,
40
+ so the default is the whole of what there is to render."""
41
+
42
+ INDEXED = "indexed"
43
+ """Example: variableName[0]=value1"""
44
+
45
+ UNINDEXED = "unindexed"
46
+ """Example: variableName[]=value1"""
47
+
48
+ PLAIN = "plain"
49
+ """Example: variableName=value1&variableName=value2"""
50
+
51
+ CSV = "csv"
52
+ """Example: variableName=value1,value2"""
53
+
54
+ TSV = "tsv"
55
+ r"""Example: variableName=value1\tvalue2"""
56
+
57
+ PSV = "psv"
58
+ """Example: variableName=value1|value2"""
59
+
60
+
61
+ @dataclass(frozen=True, slots=True)
62
+ class Param(Generic[T]):
63
+ """One request parameter, not yet flattened onto the wire.
64
+
65
+ ``T`` is the endpoint's declared type in full, dict-shaped companion included -- exactly what
66
+ ``param[...]`` accepts -- while the adapter targets :func:`validation_target` of it, which is
67
+ what the value is validated *into*. ``value`` is typed ``object`` all the same, deliberately:
68
+ every consumer holds a ``Param[Any]``, where a ``T``-typed field would read as ``Any`` and be
69
+ checked as nothing, so ``object`` leaves the adapter as the only route to the value. The
70
+ *resolved* adapter is held rather than the type expression it came from, so the one cache
71
+ lookup happens when the parameter is built rather than on every flatten.
72
+
73
+ Unlike a request body, a parameter is deliberately *not* serialized when it is built: query,
74
+ header and path parameters are merged per key with the API-wide ``global_*`` sets while the
75
+ request is being built, so they have to stay inspectable until then. ``wire_value``
76
+ (``_internal/wire.py``) performs the same validate-then-dump through this ``adapter`` at that
77
+ point, for every destination; each destination then decides only how the result is arranged --
78
+ into query pairs, path segments, or one header value.
79
+
80
+ Construct it through ``param[...]``, whose subscript is where ``T`` is bound.
81
+
82
+ Two parameters compare equal when key, value and format match *and* they share an adapter.
83
+ Adapters are cached per type by :func:`adapter_for`, so that holds for any cacheable type; an
84
+ unhashable type form bypasses the cache and would make two otherwise-identical parameters
85
+ unequal. Nothing depends on it -- resolution keys on ``key`` alone."""
86
+
87
+ key: str
88
+ value: object
89
+ adapter: TypeAdapter[T]
90
+ serialization_format: SerializationFormat = SerializationFormat.INDEXED
91
+
92
+
93
+ @dataclass(frozen=True, slots=True)
94
+ class _DeclaredParam(Generic[T]):
95
+ """A parameter's declared type, bound to its adapter; call it with the key and value.
96
+
97
+ Built by ``param[T]``; resolving the adapter there is what lets the parameter hold a typed
98
+ ``TypeAdapter[T]``."""
99
+
100
+ adapter: TypeAdapter[T]
101
+
102
+ def __call__(
103
+ self,
104
+ key: str,
105
+ value: T,
106
+ *,
107
+ serialization_format: SerializationFormat = SerializationFormat.INDEXED,
108
+ ) -> Param[T]:
109
+ return Param(key, value, self.adapter, serialization_format)
110
+
111
+
112
+ class _ParamFactory(SubscriptOnly):
113
+ """``param[T](key, value)`` -- name a parameter's declared type in the subscript.
114
+
115
+ Subscripted for the reason ``json_body`` documents in full: ``T`` is solved from the
116
+ subscript alone, before the value is compared against it, so ``param[bool]("array",
117
+ employee)`` is a build failure where a one-call ``param("array", employee, bool)`` would
118
+ have inference absorb the disagreement. And because the factory declares no ``__call__``,
119
+ *omitting* the declared type is a build failure too: ``param("array", True)`` is rejected
120
+ statically (``[operator]``); the runtime ``__call__`` inherited from :class:`SubscriptOnly` is
121
+ invisible to type checkers and exists only to turn that same mistake into a guided ``TypeError``.
122
+
123
+ The subscript is a ``TypeForm``, exactly as in ``json_body[...]``, so a runtime union alias
124
+ (``Person``, which is ``Employee | Boss``) binds ``T`` as precisely as a concrete class
125
+ does. It is the endpoint's declared type in full, companion included --
126
+ ``param[Person | PersonDict]`` -- and :func:`validation_target` keeps the companion out of
127
+ the adapter."""
128
+
129
+ factory_name = "param"
130
+ spelling = 'param[T](key, value), e.g. param[bool]("array", True)'
131
+
132
+ def __getitem__(self, declared: TypeForm[T]) -> _DeclaredParam[T]:
133
+ return _DeclaredParam(adapter_for(validation_target(declared)))
134
+
135
+
136
+ param: Final = _ParamFactory()
137
+
138
+
139
+ @dataclass(frozen=True, slots=True)
140
+ class UrlTemplate:
141
+ """An unresolved endpoint URL.
142
+
143
+ Holds a (possibly templated) base URL, the endpoint-relative path, and the
144
+ server variables that fill the base URL's ``{placeholder}`` tokens. The base
145
+ URL and the path are expanded separately when the request is built.
146
+
147
+ A server variable is *configuration* rather than a spec-declared parameter --
148
+ it is resolved from ``ServerConfig`` before any request exists -- but it fills
149
+ a placeholder exactly as a path parameter does, so it is carried as a
150
+ :class:`Param` and rendered by the same function. Naming its declared type is
151
+ what retires the hand ``.value`` unwrap an enum-typed variable used to need,
152
+ and what makes forgetting one a type error rather than a base URL reading
153
+ ``Region.EU``."""
154
+
155
+ base_url: str
156
+ path: str
157
+ variables: Sequence[Param[Any]] = ()
@@ -0,0 +1,480 @@
1
+ """The seams every endpoint crosses: build a request, send it, shape a result.
2
+
3
+ Pagination does not live here. ``pagination.paginate`` and ``async_paginate`` run that loop, calling the
4
+ endpoint's ``execute`` once per page.
5
+
6
+ The twins live in one module so the few ``await``s they differ by stay visible on one screen."""
7
+
8
+ from __future__ import annotations
9
+
10
+ import asyncio
11
+ import time
12
+ from collections.abc import Awaitable, Callable, Sequence
13
+ from dataclasses import dataclass
14
+ from datetime import datetime, timezone
15
+ from typing import Any, Generic, TypeVar
16
+
17
+ from ._internal.headers import resolve_headers
18
+ from ._internal.urls import build_url, resolve_params
19
+ from .auth import AsyncAuthScheme, AuthParams, AuthScheme, invalidate, no_auth, resolve_auth
20
+ from .bodies import RequestBody, content_headers
21
+ from .decoding import AsyncResponseDecoder, ErrorMapper, ResponseDecoder
22
+ from .exceptions import TransportError
23
+ from .logging import HttpLog, LoggingOptions
24
+ from .params import Param, UrlTemplate
25
+ from .request_options import RequestOptions, RequestOptionsOrDict, apply_retry_overrides
26
+ from .results import ApiResult, Failure, Success
27
+ from .retries import RetryOptions, backoff_seconds, is_retryable_body, retry_after_seconds
28
+ from .transport import (
29
+ AsyncHttpClient,
30
+ AsyncHttpResponse,
31
+ HttpClient,
32
+ HttpMethod,
33
+ HttpRequest,
34
+ HttpResponse,
35
+ )
36
+
37
+ T = TypeVar("T")
38
+ E = TypeVar("E")
39
+ TransportT = TypeVar("TransportT", bound=HttpClient | AsyncHttpClient)
40
+
41
+
42
+ @dataclass(frozen=True, slots=True)
43
+ class BaseRawClient(Generic[TransportT]):
44
+ """Shared state for the raw clients, and the one place a request is composed from it.
45
+
46
+ The type parameter pins the one transport a flavor can use, so :class:`RawClient` is provably
47
+ sync and :class:`AsyncRawClient` provably async. The ``global_*`` fields are spec-derived -- the
48
+ generator writes them, an API declaring none leaves them empty -- and each is combined with a
49
+ call's own parameters by the matching ``resolve_*`` function, so this class decides nothing
50
+ about precedence. For headers and query parameters alike that order is::
51
+
52
+ global_* -> the endpoint's own -> auth -> the caller's extra_headers
53
+
54
+ Auth outranks the endpoint so an operation parameter cannot clobber a credential, and loses to
55
+ ``extra_headers`` because that is how a caller deliberately overrides one. ``_build_request`` takes
56
+ the scheme already resolved, as :class:`~.auth.AuthParams`, because resolving it may await; it
57
+ never sends and never logs."""
58
+
59
+ http_client: TransportT
60
+
61
+ retry_options: RetryOptions
62
+
63
+ logging_options: LoggingOptions
64
+
65
+ global_headers: Sequence[Param[Any]] = ()
66
+ global_query_params: Sequence[Param[Any]] = ()
67
+ global_path_params: Sequence[Param[Any]] = ()
68
+
69
+ def _build_request(
70
+ self,
71
+ *,
72
+ http_method: HttpMethod,
73
+ url_template: UrlTemplate,
74
+ path_params: Sequence[Param[Any]] | None,
75
+ query_params: Sequence[Param[Any]] | None,
76
+ headers: Sequence[Param[Any]] | None,
77
+ body: RequestBody | None,
78
+ auth: AuthParams,
79
+ options: RequestOptions,
80
+ ) -> HttpRequest:
81
+ """Resolve the API-wide, endpoint, auth and caller layers into one request, the body's own
82
+ label beneath them all."""
83
+ return HttpRequest(
84
+ method=http_method,
85
+ url=build_url(
86
+ url_template,
87
+ path_params=resolve_params(self.global_path_params, path_params),
88
+ query_params=resolve_params(self.global_query_params, (*(query_params or ()), *auth.query_params)),
89
+ ),
90
+ headers={
91
+ **content_headers(body),
92
+ **resolve_headers(
93
+ self.global_headers,
94
+ (*(headers or ()), *auth.request_headers()),
95
+ extra_headers=options.extra_headers,
96
+ ),
97
+ },
98
+ body=body,
99
+ timeout=options.timeout,
100
+ )
101
+
102
+
103
+ @dataclass(frozen=True, slots=True)
104
+ class RawClient(BaseRawClient[HttpClient]):
105
+ """Synchronous raw client: builds the request, sends it, shapes the result."""
106
+
107
+ def execute(
108
+ self,
109
+ *,
110
+ http_method: HttpMethod,
111
+ url_template: UrlTemplate,
112
+ path_params: Sequence[Param[Any]] | None = None,
113
+ query_params: Sequence[Param[Any]] | None = None,
114
+ headers: Sequence[Param[Any]] | None = None,
115
+ body: RequestBody | None = None,
116
+ auth_scheme: AuthScheme = no_auth,
117
+ decoder: ResponseDecoder[T],
118
+ error_mapper: ErrorMapper[E],
119
+ request_options: RequestOptionsOrDict | None = None,
120
+ ) -> ApiResult[T, E]:
121
+ """Send a request and shape the response into a result.
122
+
123
+ Returns once the response head has arrived; the body is the ``decoder``'s from there -- a
124
+ buffered one reads and releases it, ``file_decoder`` hands the open connection to the
125
+ :class:`FileResponse` it builds -- and ``T`` is solved from that argument. A non-2xx body is
126
+ read and the connection released *before* the mapper runs, so a :class:`Failure` never holds
127
+ a connection, even when the mapper raises.
128
+
129
+ Args:
130
+ http_method: The HTTP verb to send.
131
+ url_template: The unresolved URL for the configured environment.
132
+ path_params: The endpoint's path parameters, if any.
133
+ query_params: The endpoint's query parameters, if any.
134
+ headers: The endpoint's header parameters, if any.
135
+ body: The request body, already wire-ready, or ``None``.
136
+ auth_scheme: The scheme the operation declares; ``no_auth`` when it declares none.
137
+ decoder: The operation's 2xx decoder; ``empty_response`` when it declares no body.
138
+ error_mapper: The operation's error mapper; ``raw_error_response`` when it declares none.
139
+ request_options: The caller's per-call overrides, if any.
140
+
141
+ Returns:
142
+ A :class:`Success` carrying the decoded payload for a 2xx status, otherwise a
143
+ :class:`Failure` carrying the decoded error body.
144
+
145
+ Raises:
146
+ ValueError: If the body does not deserialize; not an API error, so it propagates in both
147
+ response modes.
148
+ Exception: What the transport raised when the last attempt produced no response, or when
149
+ the body failed after the head arrived -- never the :class:`TransportError` wrapper."""
150
+ options = RequestOptions.coerce(request_options)
151
+ retry_options = apply_retry_overrides(options, self.retry_options)
152
+ log = HttpLog(
153
+ options=self.logging_options, log_level_override=options.log_level, started_at=time.perf_counter()
154
+ )
155
+
156
+ def send() -> HttpResponse:
157
+ request = self._build_request(
158
+ http_method=http_method,
159
+ url_template=url_template,
160
+ path_params=path_params,
161
+ query_params=query_params,
162
+ headers=headers,
163
+ body=body,
164
+ auth=auth_scheme.apply(),
165
+ options=options,
166
+ )
167
+ log.request_sending(request)
168
+ return self.http_client.send(request)
169
+
170
+ try:
171
+ response = self._attempt_send(
172
+ auth_scheme=auth_scheme,
173
+ retry_options=retry_options,
174
+ is_retryable=is_retryable_body(body) and http_method in retry_options.http_methods_to_retry,
175
+ send=send,
176
+ log=log,
177
+ )
178
+ except Exception as error:
179
+ # No response was kept: the last attempt's transport error, already unwrapped, or anything raised while
180
+ # an attempt was built, sent or its head handled. ``failed`` writes nothing when no request was reported.
181
+ # A BaseException -- the caller's cancellation -- is not this call failing, and passes untouched.
182
+ log.failed(error)
183
+ raise
184
+
185
+ try:
186
+ status_code, response_headers = response.status_code, dict(response.headers)
187
+ if 200 <= status_code < 300:
188
+ # Returns before the read: the body is the decoder's now. The excepts below must never
189
+ # become a finally -- they would close the connection a FileResponse was just handed.
190
+ return Success(
191
+ payload=decoder.decode(response),
192
+ status_code=status_code,
193
+ headers=response_headers,
194
+ )
195
+
196
+ content = response.read()
197
+ response.close()
198
+ # Mapped after the close, so a mapper raise cannot leak a connection; the second close it
199
+ # causes is a no-op, since close is required to be idempotent.
200
+ return Failure(
201
+ error=error_mapper.map(status_code, content),
202
+ status_code=status_code,
203
+ headers=response_headers,
204
+ )
205
+ except Exception as error:
206
+ # After the head: a body read the transport refused, a body the decoder rejected, an error body the
207
+ # mapper rejected -- each this call producing no result. Closed before the line is written.
208
+ response.close()
209
+ log.failed(error)
210
+ raise
211
+ except BaseException:
212
+ # The caller's cancellation or interrupt: closed, and not logged as this call's failure.
213
+ response.close()
214
+ raise
215
+
216
+ @staticmethod
217
+ def _attempt_send(
218
+ *,
219
+ auth_scheme: AuthScheme,
220
+ retry_options: RetryOptions,
221
+ is_retryable: bool,
222
+ send: Callable[[], HttpResponse],
223
+ log: HttpLog,
224
+ ) -> HttpResponse:
225
+ """Send, and send again while every gate agrees; hand back the attempt that was kept.
226
+
227
+ ``send`` is called once per attempt: it builds the request -- resolving auth afresh, so a
228
+ credential a 401 invalidated is re-obtained rather than resent -- reports it to ``log`` and
229
+ sends it. Each head is reported to ``log`` as it arrives, with whether it is a success by the
230
+ same 2xx rule ``execute`` shapes the result by, so the scope holds no status rule of its own;
231
+ if that report, or the 401 invalidation after it, raises -- a log handler or the caller's
232
+ scheme -- the response is closed before the exception leaves. The retry line is written after
233
+ the discarded response is closed and before the wait, so nothing a log handler does there can
234
+ hold a connection. A 401 invalidates the scheme's cache per attempt but is not retried under
235
+ the shipped defaults.
236
+
237
+ Args:
238
+ auth_scheme: The scheme this operation declares; invalidated where the server answers 401.
239
+ retry_options: The policy in force for this call, per-call terms already merged.
240
+ is_retryable: Whether this request may be repeated at all -- the method gate and the
241
+ body's replayability, folded once per call since neither changes between attempts.
242
+ send: Builds, reports and sends one attempt's request.
243
+ log: The call's log scope.
244
+
245
+ Returns:
246
+ The kept attempt's response, open and its body unread.
247
+
248
+ Raises:
249
+ Exception: What the transport raised when the last attempt produced no response; the
250
+ :class:`TransportError` wrapper is unwrapped and its ``inner_exception`` raised in
251
+ its place."""
252
+ for retries_taken in range(retry_options.max_retries + 1):
253
+ try:
254
+ response = send()
255
+ except TransportError as error:
256
+ # No response arrived, so there is nothing to close.
257
+ inner = error.inner_exception
258
+ if retries_taken >= retry_options.max_retries or not is_retryable:
259
+ raise inner from inner.__cause__
260
+ wait_seconds = backoff_seconds(retries_taken, retry_options)
261
+ log.retrying(max_retries=retry_options.max_retries, delay=wait_seconds, reason=inner)
262
+ time.sleep(wait_seconds)
263
+ continue
264
+
265
+ status_code, response_headers = response.status_code, dict(response.headers)
266
+
267
+ # The two calls here run code the SDK does not own -- a log handler, the caller's scheme -- while the
268
+ # response is open; one that raises must not leave the connection held.
269
+ try:
270
+ log.response_received(status_code, response_headers, is_success=200 <= status_code < 300)
271
+ if status_code == 401:
272
+ invalidate(auth_scheme)
273
+ except BaseException:
274
+ response.close()
275
+ raise
276
+
277
+ if not (
278
+ is_retryable
279
+ and status_code in retry_options.status_codes_to_retry
280
+ and retries_taken < retry_options.max_retries
281
+ ):
282
+ return response
283
+
284
+ retry_after_header = retry_after_seconds(response_headers, datetime.now(timezone.utc))
285
+ wait_seconds = (
286
+ backoff_seconds(retries_taken, retry_options)
287
+ if retry_after_header is None
288
+ else min(retry_after_header, retry_options.max_delay)
289
+ )
290
+
291
+ # Closed before the retry line and the wait, so neither can hold a connection.
292
+ response.close()
293
+ log.retrying(max_retries=retry_options.max_retries, delay=wait_seconds, reason=status_code)
294
+ time.sleep(wait_seconds)
295
+
296
+ raise AssertionError("the retry loop ended without a response to shape")
297
+
298
+
299
+ @dataclass(frozen=True, slots=True)
300
+ class AsyncRawClient(BaseRawClient[AsyncHttpClient]):
301
+ """Asynchronous raw client.
302
+
303
+ The method is named ``execute``, not ``execute_async``: sync and async peers
304
+ carry identical names throughout this SDK, and the transport already makes the
305
+ flavor unambiguous."""
306
+
307
+ async def execute(
308
+ self,
309
+ *,
310
+ http_method: HttpMethod,
311
+ url_template: UrlTemplate,
312
+ path_params: Sequence[Param[Any]] | None = None,
313
+ query_params: Sequence[Param[Any]] | None = None,
314
+ headers: Sequence[Param[Any]] | None = None,
315
+ body: RequestBody | None = None,
316
+ auth_scheme: AsyncAuthScheme = no_auth,
317
+ decoder: AsyncResponseDecoder[T],
318
+ error_mapper: ErrorMapper[E],
319
+ request_options: RequestOptionsOrDict | None = None,
320
+ ) -> ApiResult[T, E]:
321
+ """Send a request and shape the response into a result.
322
+
323
+ Returns once the response head has arrived; the body is the ``decoder``'s from there -- a
324
+ buffered one reads and releases it, ``file_decoder`` hands the open connection to the
325
+ :class:`FileResponse` it builds -- and ``T`` is solved from that argument. A non-2xx body is
326
+ read and the connection released *before* the mapper runs, so a :class:`Failure` never holds
327
+ a connection, even when the mapper raises.
328
+
329
+ Args:
330
+ http_method: The HTTP verb to send.
331
+ url_template: The unresolved URL for the configured environment.
332
+ path_params: The endpoint's path parameters, if any.
333
+ query_params: The endpoint's query parameters, if any.
334
+ headers: The endpoint's header parameters, if any.
335
+ body: The request body, already wire-ready, or ``None``.
336
+ auth_scheme: The scheme the operation declares; ``no_auth`` when it declares none.
337
+ decoder: The operation's 2xx decoder; ``empty_response`` when it declares no body.
338
+ error_mapper: The operation's error mapper; ``raw_error_response`` when it declares none.
339
+ request_options: The caller's per-call overrides, if any.
340
+
341
+ Returns:
342
+ A :class:`Success` carrying the decoded payload for a 2xx status, otherwise a
343
+ :class:`Failure` carrying the decoded error body.
344
+
345
+ Raises:
346
+ ValueError: If the body does not deserialize; not an API error, so it propagates in both
347
+ response modes.
348
+ Exception: What the transport raised when the last attempt produced no response, or when
349
+ the body failed after the head arrived -- never the :class:`TransportError` wrapper."""
350
+ options = RequestOptions.coerce(request_options)
351
+ retry_options = apply_retry_overrides(options, self.retry_options)
352
+ log = HttpLog(
353
+ options=self.logging_options, log_level_override=options.log_level, started_at=time.perf_counter()
354
+ )
355
+
356
+ async def send() -> AsyncHttpResponse:
357
+ request = self._build_request(
358
+ http_method=http_method,
359
+ url_template=url_template,
360
+ path_params=path_params,
361
+ query_params=query_params,
362
+ headers=headers,
363
+ body=body,
364
+ auth=await resolve_auth(auth_scheme),
365
+ options=options,
366
+ )
367
+ log.request_sending(request)
368
+ return await self.http_client.send(request)
369
+
370
+ try:
371
+ response = await self._attempt_send(
372
+ auth_scheme=auth_scheme,
373
+ retry_options=retry_options,
374
+ is_retryable=is_retryable_body(body) and http_method in retry_options.http_methods_to_retry,
375
+ send=send,
376
+ log=log,
377
+ )
378
+ except Exception as error:
379
+ # No response was kept: the last attempt's transport error, already unwrapped, or anything raised while
380
+ # an attempt was built, sent or its head handled. ``failed`` writes nothing when no request was reported.
381
+ # A BaseException -- the caller's cancellation -- is not this call failing, and passes untouched.
382
+ log.failed(error)
383
+ raise
384
+
385
+ try:
386
+ status_code, response_headers = response.status_code, dict(response.headers)
387
+ if 200 <= status_code < 300:
388
+ # Returns before the read: the body is the decoder's now. The excepts below must never
389
+ # become a finally -- they would close the connection an AsyncFileResponse was just handed.
390
+ return Success(
391
+ payload=await decoder.decode(response),
392
+ status_code=status_code,
393
+ headers=response_headers,
394
+ )
395
+
396
+ content = await response.aread()
397
+ await response.aclose()
398
+ # Mapped after the close, so a mapper raise cannot leak a connection; the second close it
399
+ # causes is a no-op, since aclose is required to be idempotent.
400
+ return Failure(
401
+ error=error_mapper.map(status_code, content),
402
+ status_code=status_code,
403
+ headers=response_headers,
404
+ )
405
+ except Exception as error:
406
+ # After the head: a body read the transport refused, a body the decoder rejected, an error body the
407
+ # mapper rejected -- each this call producing no result. Closed before the line is written.
408
+ await response.aclose()
409
+ log.failed(error)
410
+ raise
411
+ except BaseException:
412
+ # The caller's cancellation or interrupt: closed, and not logged as this call's failure.
413
+ await response.aclose()
414
+ raise
415
+
416
+ @staticmethod
417
+ async def _attempt_send(
418
+ *,
419
+ auth_scheme: AsyncAuthScheme,
420
+ retry_options: RetryOptions,
421
+ is_retryable: bool,
422
+ send: Callable[[], Awaitable[AsyncHttpResponse]],
423
+ log: HttpLog,
424
+ ) -> AsyncHttpResponse:
425
+ """The awaited twin of :meth:`RawClient._attempt_send`, with that contract exactly.
426
+
427
+ Three tokens differ: ``send`` is awaited, the close is ``aclose``, and the wait is
428
+ ``asyncio.sleep``, so it yields the event loop instead of blocking it."""
429
+ for retries_taken in range(retry_options.max_retries + 1):
430
+ try:
431
+ response = await send()
432
+ except TransportError as error:
433
+ # No response arrived, so there is nothing to close.
434
+ inner = error.inner_exception
435
+ if retries_taken >= retry_options.max_retries or not is_retryable:
436
+ raise inner from inner.__cause__
437
+ wait_seconds = backoff_seconds(retries_taken, retry_options)
438
+ log.retrying(max_retries=retry_options.max_retries, delay=wait_seconds, reason=inner)
439
+ await asyncio.sleep(wait_seconds)
440
+ continue
441
+
442
+ status_code, response_headers = response.status_code, dict(response.headers)
443
+
444
+ # The two calls here run code the SDK does not own -- a log handler, the caller's scheme -- while the
445
+ # response is open; one that raises must not leave the connection held.
446
+ try:
447
+ log.response_received(status_code, response_headers, is_success=200 <= status_code < 300)
448
+ if status_code == 401:
449
+ invalidate(auth_scheme)
450
+ except BaseException:
451
+ await response.aclose()
452
+ raise
453
+
454
+ if not (
455
+ is_retryable
456
+ and status_code in retry_options.status_codes_to_retry
457
+ and retries_taken < retry_options.max_retries
458
+ ):
459
+ return response
460
+
461
+ retry_after_header = retry_after_seconds(response_headers, datetime.now(timezone.utc))
462
+ wait_seconds = (
463
+ backoff_seconds(retries_taken, retry_options)
464
+ if retry_after_header is None
465
+ else min(retry_after_header, retry_options.max_delay)
466
+ )
467
+
468
+ # Closed before the retry line and the wait, so neither can hold a connection.
469
+ await response.aclose()
470
+ log.retrying(max_retries=retry_options.max_retries, delay=wait_seconds, reason=status_code)
471
+ await asyncio.sleep(wait_seconds)
472
+
473
+ raise AssertionError("the retry loop ended without a response to shape")
474
+
475
+
476
+ RawClientT = TypeVar("RawClientT", bound=RawClient | AsyncRawClient)
477
+ """Fixes which raw client a generic holder carries -- ``RawClient`` or ``AsyncRawClient``.
478
+
479
+ Bounded by the two concrete clients, not ``BaseRawClient[Any]``: the base has no ``execute``, so the
480
+ looser bound could not prove a holder's ``self._client.execute(...)`` exists."""