cliV1 0.0.6__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.
- cliv1-0.0.6.dist-info/METADATA +212 -0
- cliv1-0.0.6.dist-info/RECORD +92 -0
- cliv1-0.0.6.dist-info/WHEEL +4 -0
- cliv1-0.0.6.dist-info/licenses/LICENSE +21 -0
- swagger_petstore_open_api_3_1_0/__init__.py +15 -0
- swagger_petstore_open_api_3_1_0/apis/__init__.py +5 -0
- swagger_petstore_open_api_3_1_0/apis/pet_api.py +922 -0
- swagger_petstore_open_api_3_1_0/apis/store.py +432 -0
- swagger_petstore_open_api_3_1_0/apis/user_api.py +829 -0
- swagger_petstore_open_api_3_1_0/async_client.py +87 -0
- swagger_petstore_open_api_3_1_0/auth.py +17 -0
- swagger_petstore_open_api_3_1_0/base_client.py +27 -0
- swagger_petstore_open_api_3_1_0/client.py +85 -0
- swagger_petstore_open_api_3_1_0/core/__init__.py +395 -0
- swagger_petstore_open_api_3_1_0/core/_internal/__init__.py +9 -0
- swagger_petstore_open_api_3_1_0/core/_internal/content_disposition.py +154 -0
- swagger_petstore_open_api_3_1_0/core/_internal/flattening.py +104 -0
- swagger_petstore_open_api_3_1_0/core/_internal/headers.py +243 -0
- swagger_petstore_open_api_3_1_0/core/_internal/redaction.py +208 -0
- swagger_petstore_open_api_3_1_0/core/_internal/sse_parser.py +124 -0
- swagger_petstore_open_api_3_1_0/core/_internal/subscripts.py +50 -0
- swagger_petstore_open_api_3_1_0/core/_internal/urls.py +172 -0
- swagger_petstore_open_api_3_1_0/core/_internal/wire.py +201 -0
- swagger_petstore_open_api_3_1_0/core/adapters.py +177 -0
- swagger_petstore_open_api_3_1_0/core/auth/__init__.py +70 -0
- swagger_petstore_open_api_3_1_0/core/auth/composition.py +175 -0
- swagger_petstore_open_api_3_1_0/core/auth/models.py +216 -0
- swagger_petstore_open_api_3_1_0/core/auth/protocols.py +103 -0
- swagger_petstore_open_api_3_1_0/core/auth/schemes/__init__.py +94 -0
- swagger_petstore_open_api_3_1_0/core/auth/schemes/api_key_auth.py +53 -0
- swagger_petstore_open_api_3_1_0/core/auth/schemes/basic_auth.py +73 -0
- swagger_petstore_open_api_3_1_0/core/auth/schemes/bearer_auth.py +21 -0
- swagger_petstore_open_api_3_1_0/core/auth/schemes/oauth2_authorization_code.py +395 -0
- swagger_petstore_open_api_3_1_0/core/auth/schemes/oauth2_client_credentials.py +130 -0
- swagger_petstore_open_api_3_1_0/core/auth/schemes/oauth2_password.py +145 -0
- swagger_petstore_open_api_3_1_0/core/auth/schemes/oauth2_schemes.py +300 -0
- swagger_petstore_open_api_3_1_0/core/base_raw_response.py +49 -0
- swagger_petstore_open_api_3_1_0/core/bodies.py +590 -0
- swagger_petstore_open_api_3_1_0/core/converters/__init__.py +68 -0
- swagger_petstore_open_api_3_1_0/core/converters/date_time.py +237 -0
- swagger_petstore_open_api_3_1_0/core/converters/encoded_bytes.py +273 -0
- swagger_petstore_open_api_3_1_0/core/converters/open_enum.py +35 -0
- swagger_petstore_open_api_3_1_0/core/decoding.py +498 -0
- swagger_petstore_open_api_3_1_0/core/discriminators.py +129 -0
- swagger_petstore_open_api_3_1_0/core/event_streams.py +225 -0
- swagger_petstore_open_api_3_1_0/core/exceptions.py +78 -0
- swagger_petstore_open_api_3_1_0/core/file_responses.py +337 -0
- swagger_petstore_open_api_3_1_0/core/files.py +174 -0
- swagger_petstore_open_api_3_1_0/core/httpx2_transport.py +748 -0
- swagger_petstore_open_api_3_1_0/core/logging.py +472 -0
- swagger_petstore_open_api_3_1_0/core/models.py +143 -0
- swagger_petstore_open_api_3_1_0/core/optionality.py +144 -0
- swagger_petstore_open_api_3_1_0/core/pagination.py +452 -0
- swagger_petstore_open_api_3_1_0/core/paginator.py +55 -0
- swagger_petstore_open_api_3_1_0/core/params.py +157 -0
- swagger_petstore_open_api_3_1_0/core/raw_client.py +480 -0
- swagger_petstore_open_api_3_1_0/core/request_options.py +172 -0
- swagger_petstore_open_api_3_1_0/core/results.py +178 -0
- swagger_petstore_open_api_3_1_0/core/retries.py +237 -0
- swagger_petstore_open_api_3_1_0/core/runtime_env.py +43 -0
- swagger_petstore_open_api_3_1_0/core/servers.py +33 -0
- swagger_petstore_open_api_3_1_0/core/transport.py +218 -0
- swagger_petstore_open_api_3_1_0/errors/__init__.py +48 -0
- swagger_petstore_open_api_3_1_0/errors/add_pet_error.py +21 -0
- swagger_petstore_open_api_3_1_0/errors/delete_order_error.py +21 -0
- swagger_petstore_open_api_3_1_0/errors/delete_pet_error.py +21 -0
- swagger_petstore_open_api_3_1_0/errors/delete_user_error.py +21 -0
- swagger_petstore_open_api_3_1_0/errors/find_pets_by_status_error.py +21 -0
- swagger_petstore_open_api_3_1_0/errors/find_pets_by_tags_error.py +21 -0
- swagger_petstore_open_api_3_1_0/errors/get_order_by_id_error.py +21 -0
- swagger_petstore_open_api_3_1_0/errors/get_pet_by_id_error.py +21 -0
- swagger_petstore_open_api_3_1_0/errors/get_user_by_name_error.py +21 -0
- swagger_petstore_open_api_3_1_0/errors/login_user_error.py +21 -0
- swagger_petstore_open_api_3_1_0/errors/place_order_error.py +21 -0
- swagger_petstore_open_api_3_1_0/errors/update_pet_error.py +21 -0
- swagger_petstore_open_api_3_1_0/errors/update_pet_with_form_error.py +21 -0
- swagger_petstore_open_api_3_1_0/errors/update_user_error.py +21 -0
- swagger_petstore_open_api_3_1_0/errors/upload_file_error.py +21 -0
- swagger_petstore_open_api_3_1_0/models/__init__.py +23 -0
- swagger_petstore_open_api_3_1_0/models/api_response.py +18 -0
- swagger_petstore_open_api_3_1_0/models/category.py +15 -0
- swagger_petstore_open_api_3_1_0/models/enums/__init__.py +4 -0
- swagger_petstore_open_api_3_1_0/models/enums/order_status.py +17 -0
- swagger_petstore_open_api_3_1_0/models/enums/pet_status.py +17 -0
- swagger_petstore_open_api_3_1_0/models/order.py +27 -0
- swagger_petstore_open_api_3_1_0/models/pet.py +28 -0
- swagger_petstore_open_api_3_1_0/models/tag_model.py +15 -0
- swagger_petstore_open_api_3_1_0/models/user.py +29 -0
- swagger_petstore_open_api_3_1_0/py.typed +0 -0
- swagger_petstore_open_api_3_1_0/server/__init__.py +21 -0
- swagger_petstore_open_api_3_1_0/server/server.py +17 -0
- swagger_petstore_open_api_3_1_0/server/server_config.py +52 -0
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
"""Turning wire bytes into typed values: response decoding and error-body mapping.
|
|
2
|
+
|
|
3
|
+
One verb per act, and they are not interchangeable. ``decode`` builds a 2xx payload from a response
|
|
4
|
+
whose body has not been read; ``parse`` is the step inside it, bytes to an untyped Python value for
|
|
5
|
+
the adapter to validate; ``map`` is the error path's, because there the work is selection -- an
|
|
6
|
+
:class:`ErrorMapper` picks the documented schema for a response's status.
|
|
7
|
+
|
|
8
|
+
Every decoder is handed the same unread response and owns it from there: it either reads the body to
|
|
9
|
+
the end and releases the connection, or hands the open connection to the value it returns. There is
|
|
10
|
+
no third option, which is what lets the raw client own no connection and take no branch.
|
|
11
|
+
|
|
12
|
+
The file reads in that order -- the three contracts, then the buffered arm, the error side, the file
|
|
13
|
+
arm, the event-stream arm. Within an arm, sync precedes async, a private step sits immediately above
|
|
14
|
+
what holds it, and an arm's singletons come after the last class they need."""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from collections.abc import Callable
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from typing import Any, Final, Generic, Protocol, TypeVar
|
|
21
|
+
|
|
22
|
+
from pydantic import TypeAdapter
|
|
23
|
+
from typing_extensions import TypeForm
|
|
24
|
+
|
|
25
|
+
from ._internal.subscripts import SubscriptOnly
|
|
26
|
+
from ._internal.wire import parse_json
|
|
27
|
+
from .adapters import adapter_for
|
|
28
|
+
from .event_streams import AsyncEventStream, EventStream
|
|
29
|
+
from .file_responses import AsyncFileResponse, FileResponse
|
|
30
|
+
from .results import RawError
|
|
31
|
+
from .transport import AsyncHttpResponse, HttpResponse
|
|
32
|
+
|
|
33
|
+
T = TypeVar("T")
|
|
34
|
+
T_co = TypeVar("T_co", covariant=True)
|
|
35
|
+
E_co = TypeVar("E_co", covariant=True)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ResponseDecoder(Protocol[T_co]):
|
|
39
|
+
"""Builds a 2xx payload from a response whose body has not been read.
|
|
40
|
+
|
|
41
|
+
The decoder owns that body. It either reads it to the end and releases the connection -- what
|
|
42
|
+
every model, text and no-content decoder does, through :class:`BufferedDecoder` -- or hands the
|
|
43
|
+
still-open connection to the value it returns, which becomes responsible for releasing it
|
|
44
|
+
(:class:`FileResponse`). There is no third option: a 2xx response leaves ``execute`` owned by
|
|
45
|
+
exactly one object, and a decoder that neither read nor handed over would strand a pool
|
|
46
|
+
connection."""
|
|
47
|
+
|
|
48
|
+
def decode(self, response: HttpResponse) -> T_co: ...
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class AsyncResponseDecoder(Protocol[T_co]):
|
|
52
|
+
"""The twin of :class:`ResponseDecoder`, over the async response.
|
|
53
|
+
|
|
54
|
+
``decode`` is an ``async def`` here rather than returning ``T | Awaitable[T]``. That union
|
|
55
|
+
spelling exists so one *static* auth scheme can satisfy both flavours; nothing about reading a
|
|
56
|
+
body is static, so there is nothing for it to buy -- the same reasoning that settled
|
|
57
|
+
:meth:`AsyncTokenSource.fetch`. The cost is an ``await`` on :data:`async_file_decoder` that
|
|
58
|
+
never suspends, which is the honest price of one signature per flavour."""
|
|
59
|
+
|
|
60
|
+
async def decode(self, response: AsyncHttpResponse) -> T_co: ...
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ErrorMapper(Protocol[E_co]):
|
|
64
|
+
"""Maps a non-2xx response to the operation's typed error body -- a union of the
|
|
65
|
+
documented error schemas, or ``RawError`` for an unmapped status.
|
|
66
|
+
|
|
67
|
+
Takes the two facts a mapper uses and no envelope around them: the status it switches on, and
|
|
68
|
+
the body it decodes. Both are values by the time this runs -- the raw client has read the body
|
|
69
|
+
and released the connection before calling -- so there is nothing here that could hold a
|
|
70
|
+
connection open, and nothing to ask whether it has been read yet."""
|
|
71
|
+
|
|
72
|
+
def map(self, status_code: int, content: bytes) -> E_co: ...
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass(frozen=True, slots=True)
|
|
76
|
+
class BufferedDecoder(Generic[T]):
|
|
77
|
+
"""Reads a 2xx body to the end, releases the connection, then decodes what it read.
|
|
78
|
+
|
|
79
|
+
The bridge between a seam that hands every decoder an unread response and the decoders whose
|
|
80
|
+
payload is a value rather than a connection. ``decode_body`` is the step that turns the buffered
|
|
81
|
+
response into that value -- a JSON parse, a text parse, or nothing at all.
|
|
82
|
+
|
|
83
|
+
Written once per flavour instead of once per format, which is what lets the three steps below
|
|
84
|
+
stay plain ``bytes -> T`` functions: they are the same callables a generated error mapper reaches
|
|
85
|
+
through ``decode_json``, so one parse serves the success path and the error path alike.
|
|
86
|
+
|
|
87
|
+
The body is read even where the payload ignores it, as :data:`empty_response`'s does: an unread
|
|
88
|
+
body cannot be returned to the pool, only abandoned. The ``finally`` is what makes a failed read
|
|
89
|
+
release rather than leak, and the parse runs *after* it, so nothing holds a socket while pydantic
|
|
90
|
+
works and a rejected payload cannot strand a connection either."""
|
|
91
|
+
|
|
92
|
+
decode_body: Callable[[bytes], T]
|
|
93
|
+
|
|
94
|
+
def decode(self, response: HttpResponse) -> T:
|
|
95
|
+
try:
|
|
96
|
+
content = response.read()
|
|
97
|
+
finally:
|
|
98
|
+
response.close()
|
|
99
|
+
return self.decode_body(content)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass(frozen=True, slots=True)
|
|
103
|
+
class AsyncBufferedDecoder(Generic[T]):
|
|
104
|
+
"""The twin of :class:`BufferedDecoder`, over the async response."""
|
|
105
|
+
|
|
106
|
+
decode_body: Callable[[bytes], T]
|
|
107
|
+
|
|
108
|
+
async def decode(self, response: AsyncHttpResponse) -> T:
|
|
109
|
+
try:
|
|
110
|
+
content = await response.aread()
|
|
111
|
+
finally:
|
|
112
|
+
await response.aclose()
|
|
113
|
+
return self.decode_body(content)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _decode_utf8(content: bytes) -> str:
|
|
117
|
+
"""Read a buffered body as UTF-8 text, refusing a byte that is not.
|
|
118
|
+
|
|
119
|
+
``strict``: on the payload path an undecodable byte is corruption, and the caller is owed the
|
|
120
|
+
failure rather than a U+FFFD standing in for it. A diagnostic reader replaces instead -- see
|
|
121
|
+
:meth:`RawError.text`. The charset is fixed at UTF-8 here; were a response charset ever
|
|
122
|
+
spec-derived the way a request's is, this is the argument it would become.
|
|
123
|
+
|
|
124
|
+
The plain-text counterpart of :func:`parse_json`, and the only other step a :class:`BodyDecoder`
|
|
125
|
+
is built over. Private, because nothing outside this module reads a body as text:
|
|
126
|
+
:meth:`RawError.text` has its own, replacing posture.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
content: The buffered body.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
The body as text.
|
|
133
|
+
|
|
134
|
+
Raises:
|
|
135
|
+
UnicodeDecodeError: If the body is not UTF-8."""
|
|
136
|
+
return content.decode("utf-8", errors="strict")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@dataclass(frozen=True, slots=True)
|
|
140
|
+
class BodyDecoder(Generic[T]):
|
|
141
|
+
"""Reads a buffered body as a Python value, then validates it through ``adapter``.
|
|
142
|
+
|
|
143
|
+
The decode *step*, not a decoder for the seam: :class:`BufferedDecoder` wraps it for an endpoint,
|
|
144
|
+
and ``decode_json`` hands the same bound method to a generated error mapper.
|
|
145
|
+
|
|
146
|
+
``parse`` is the whole difference between a JSON body and a plain-text one -- ``parse_json``
|
|
147
|
+
for the first, :func:`_decode_utf8` for the second. It is the *format*, and a format is data:
|
|
148
|
+
both produce the same ``BodyDecoder[T]``, so nothing above this class asks which it holds. What
|
|
149
|
+
may never become data is the *shape* a factory returns -- see :class:`_BufferedDecoderFactory`.
|
|
150
|
+
|
|
151
|
+
Its result is deliberately untyped. What a body parses to is the adapter's to judge and ``T`` is
|
|
152
|
+
solved from ``adapter`` alone: typing it ``str`` would refuse ``parse_json``, and typing it ``T``
|
|
153
|
+
would claim a parse had already validated."""
|
|
154
|
+
|
|
155
|
+
parse: Callable[[bytes], Any]
|
|
156
|
+
adapter: TypeAdapter[T]
|
|
157
|
+
|
|
158
|
+
def decode(self, content: bytes) -> T:
|
|
159
|
+
return self.adapter.validate_python(self.parse(content))
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@dataclass(frozen=True, slots=True)
|
|
163
|
+
class _BufferedDecoderFactory(SubscriptOnly):
|
|
164
|
+
"""``json_decoder[T]`` / ``text_decoder[T]`` -- build a decoder that reads a buffered body.
|
|
165
|
+
|
|
166
|
+
One class for two formats, because both produce the same thing: a ``ResponseDecoder[T]`` whose
|
|
167
|
+
``T`` is the subscript. The format is ``parse``, a field, and ``json_decoder`` / ``text_decoder``
|
|
168
|
+
below are the whole difference between them. Its adapter comes from :func:`adapter_for`, the one
|
|
169
|
+
cache the request path (``json_body``, ``param``) draws from too.
|
|
170
|
+
|
|
171
|
+
The line a field may not cross is the *return shape*. ``ResponseDecoder[T]``,
|
|
172
|
+
``AsyncResponseDecoder[T]`` and ``Callable[[bytes], T]`` each depend on the subscripted type, and
|
|
173
|
+
Python has no higher-kinded types, so no one class can produce all three -- which is the precision
|
|
174
|
+
that makes ``json_decoder[int]`` an error where ``ApiResult[str, ...]`` is declared. One class per
|
|
175
|
+
shape is the floor, and this is one of the three.
|
|
176
|
+
|
|
177
|
+
The subscript is a ``TypeForm`` (PEP 747) rather than a ``type[T]``, so a runtime union alias
|
|
178
|
+
(``Person``, which is ``Employee | Boss``), an ``Annotated`` wire-format alias and a
|
|
179
|
+
``list[...]`` wrapper are each as acceptable as a concrete class and ``T`` stays precise for all
|
|
180
|
+
of them. That precision is what makes an endpoint's declared payload type checkable against what
|
|
181
|
+
it decodes. A ``type[T]``-plus-``object`` overload pair cannot: given a declared type the precise
|
|
182
|
+
overload does not fit, inference falls through to the ``object`` one, yields ``Any``, and the
|
|
183
|
+
disagreement has nowhere left to surface. And because the factory declares no ``__call__``, the
|
|
184
|
+
type cannot be omitted (``[operator]``); the one inherited from :class:`SubscriptOnly` is
|
|
185
|
+
checker-invisible and only guides."""
|
|
186
|
+
|
|
187
|
+
parse: Callable[[bytes], Any]
|
|
188
|
+
factory_name: str
|
|
189
|
+
spelling: str
|
|
190
|
+
|
|
191
|
+
def __getitem__(self, declared: TypeForm[T]) -> ResponseDecoder[T]:
|
|
192
|
+
return BufferedDecoder(BodyDecoder(self.parse, adapter_for(declared)).decode)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@dataclass(frozen=True, slots=True)
|
|
196
|
+
class _AsyncBufferedDecoderFactory(SubscriptOnly):
|
|
197
|
+
"""``async_json_decoder[T]`` / ``async_text_decoder[T]`` -- the async twins; see the sync one.
|
|
198
|
+
|
|
199
|
+
A separate class rather than a third field, and that is the rule stated there read the other way
|
|
200
|
+
round: an argument position is contravariant, so an ``AsyncResponseDecoder[T]`` can never satisfy
|
|
201
|
+
the sync seam nor the reverse, and the flavour is exactly what may not become data. Which is what
|
|
202
|
+
makes this a build-time distinction rather than a coroutine nobody awaited."""
|
|
203
|
+
|
|
204
|
+
parse: Callable[[bytes], Any]
|
|
205
|
+
factory_name: str
|
|
206
|
+
spelling: str
|
|
207
|
+
|
|
208
|
+
def __getitem__(self, declared: TypeForm[T]) -> AsyncResponseDecoder[T]:
|
|
209
|
+
return AsyncBufferedDecoder(BodyDecoder(self.parse, adapter_for(declared)).decode)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
json_decoder: Final = _BufferedDecoderFactory(
|
|
213
|
+
parse_json, "json_decoder", "decoder=json_decoder[T], e.g. json_decoder[Person]"
|
|
214
|
+
)
|
|
215
|
+
text_decoder: Final = _BufferedDecoderFactory(
|
|
216
|
+
_decode_utf8, "text_decoder", "decoder=text_decoder[T], e.g. text_decoder[int]"
|
|
217
|
+
)
|
|
218
|
+
async_json_decoder: Final = _AsyncBufferedDecoderFactory(
|
|
219
|
+
parse_json, "async_json_decoder", "decoder=async_json_decoder[T], e.g. async_json_decoder[Person]"
|
|
220
|
+
)
|
|
221
|
+
async_text_decoder: Final = _AsyncBufferedDecoderFactory(
|
|
222
|
+
_decode_utf8, "async_text_decoder", "decoder=async_text_decoder[T], e.g. async_text_decoder[int]"
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class _DecodeJsonFactory(SubscriptOnly):
|
|
227
|
+
"""``decode_json[T](content)`` -- decode a JSON body as the subscripted type, now.
|
|
228
|
+
|
|
229
|
+
The result is typed as the subscripted type whatever shape it takes -- a concrete class, a
|
|
230
|
+
union such as ``Accountant | Manager``, an ``Annotated`` alias -- so a generated error mapper
|
|
231
|
+
returns its documented body union directly, with no ``cast`` standing in for what the type
|
|
232
|
+
system now carries itself.
|
|
233
|
+
|
|
234
|
+
Its format is written in the body rather than passed as a field, unlike the two buffered
|
|
235
|
+
factories: this shape has one other member, ``decode_text``, and that one is callable bare.
|
|
236
|
+
Parametrising a class for effectively one instance would hand ``decode_text`` a ``factory_name``
|
|
237
|
+
and a ``spelling`` it can never raise."""
|
|
238
|
+
|
|
239
|
+
factory_name = "decode_json"
|
|
240
|
+
spelling = "decode_json[T](content), e.g. decode_json[ProblemDetails](content)"
|
|
241
|
+
|
|
242
|
+
def __getitem__(self, declared: TypeForm[T]) -> Callable[[bytes], T]:
|
|
243
|
+
return BodyDecoder(parse_json, adapter_for(declared)).decode
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class _DecodeTextFactory:
|
|
247
|
+
"""``decode_text[T](content)`` -- decode a plain-text body, now; see ``decode_json``.
|
|
248
|
+
|
|
249
|
+
Also callable bare -- ``decode_text(content)`` -- which is the ``str`` default the retired
|
|
250
|
+
two-member overload existed to carry: the default lives on ``__call__`` and a named type can
|
|
251
|
+
only take the subscript path, so a mismatch has nowhere to fall through to."""
|
|
252
|
+
|
|
253
|
+
def __getitem__(self, declared: TypeForm[T]) -> Callable[[bytes], T]:
|
|
254
|
+
return BodyDecoder(_decode_utf8, adapter_for(declared)).decode
|
|
255
|
+
|
|
256
|
+
def __call__(self, content: bytes) -> str:
|
|
257
|
+
return self[str](content)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
decode_json: Final = _DecodeJsonFactory()
|
|
261
|
+
decode_text: Final = _DecodeTextFactory()
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _discard_body(content: bytes) -> None:
|
|
265
|
+
"""Discard a buffered body, for an operation whose 2xx declares none.
|
|
266
|
+
|
|
267
|
+
The third decode step, beside :func:`parse_json` and :func:`_decode_utf8`, and the only one that
|
|
268
|
+
reads nothing: what arrived is never inspected, so correctness does not rest on the body being
|
|
269
|
+
empty in fact. It is read all the same, one layer up in :class:`BufferedDecoder`, because an
|
|
270
|
+
unread body has to abandon its connection rather than return it to the pool.
|
|
271
|
+
|
|
272
|
+
Private, like :func:`_decode_utf8`: what an operation names is the singleton below, never this.
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
content: The buffered body, whatever arrived in it.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
``None``, always."""
|
|
279
|
+
return None
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
empty_response: Final[ResponseDecoder[None]] = BufferedDecoder(_discard_body)
|
|
283
|
+
"""Decoder for an operation whose 2xx body is empty, and the success-side twin of
|
|
284
|
+
:data:`raw_error_response`.
|
|
285
|
+
|
|
286
|
+
It is the reason ``decoder`` is a required argument rather than an optional one: an operation that
|
|
287
|
+
returns no content says so by naming this, instead of by leaving the argument out. Presence is
|
|
288
|
+
auditable where absence is not -- a missing ``decoder=`` could be a no-content operation or a
|
|
289
|
+
generator that dropped the line, and only one of those should compile.
|
|
290
|
+
|
|
291
|
+
Naming it is also what keeps the payload type honest. ``T`` is solved from this argument, so the
|
|
292
|
+
operation can declare ``ApiResult[None, E]`` and nothing else. Were the argument omitted instead,
|
|
293
|
+
``T`` would be solved from the declared type, and an operation that parses nothing could claim any
|
|
294
|
+
payload it liked and hand back ``None`` at runtime -- the last place a declared type could go
|
|
295
|
+
unstated, now that each factory makes its own subscript mandatory.
|
|
296
|
+
|
|
297
|
+
Not subscripted, and not a factory: there is no type to name. ``json_decoder[None]`` would still
|
|
298
|
+
parse the body and raise on an empty one, which is the opposite of what this does.
|
|
299
|
+
|
|
300
|
+
Carries nothing SDK-specific, so it lives here rather than in the generated error layer, and is
|
|
301
|
+
shared by every such operation."""
|
|
302
|
+
|
|
303
|
+
async_empty_response: Final[AsyncResponseDecoder[None]] = AsyncBufferedDecoder(_discard_body)
|
|
304
|
+
"""The async twin of :data:`empty_response`; a sync decoder on the async seam is a build failure."""
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
@dataclass(frozen=True, slots=True)
|
|
308
|
+
class RawErrorResponse:
|
|
309
|
+
"""Error mapper for an operation that declares no error schemas.
|
|
310
|
+
|
|
311
|
+
Deliberately has **no** ``match`` on the status code: the absence is the
|
|
312
|
+
signal. Where an operation's spec documents error responses, its own mapper
|
|
313
|
+
in the generated error layer selects a schema per status; where it documents
|
|
314
|
+
none there is nothing to select, so every non-2xx response maps to the same
|
|
315
|
+
:class:`RawError` -- status plus the undecoded body, decoded on demand.
|
|
316
|
+
|
|
317
|
+
Carries nothing SDK-specific, so it lives here rather than in the generated
|
|
318
|
+
error layer, and is shared by every such operation."""
|
|
319
|
+
|
|
320
|
+
def map(self, status_code: int, content: bytes) -> RawError:
|
|
321
|
+
return RawError(status_code, content)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
raw_error_response: Final[ErrorMapper[RawError]] = RawErrorResponse()
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
@dataclass(frozen=True, slots=True)
|
|
328
|
+
class FileDecoder:
|
|
329
|
+
"""Decoder for an operation whose 2xx body is a file, handing it the open connection.
|
|
330
|
+
|
|
331
|
+
Not subscripted, and not a factory: a file bypasses the model layer, so there is no adapter to
|
|
332
|
+
name -- the same absence that leaves ``file_part`` unsubscripted on the request side.
|
|
333
|
+
|
|
334
|
+
Naming it is what keeps the payload solved from an *argument* rather than from the seam's own
|
|
335
|
+
return type, which is what lets a second streamed payload shape join ``execute`` instead of
|
|
336
|
+
forking it."""
|
|
337
|
+
|
|
338
|
+
def decode(self, response: HttpResponse) -> FileResponse:
|
|
339
|
+
return FileResponse(response)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
@dataclass(frozen=True, slots=True)
|
|
343
|
+
class AsyncFileDecoder:
|
|
344
|
+
"""Decoder for an operation whose 2xx body is a file; the twin of :class:`FileDecoder`.
|
|
345
|
+
|
|
346
|
+
``async def`` with nothing awaited: the payload is ready, and the keyword is what makes this
|
|
347
|
+
decoder unusable at the sync seam."""
|
|
348
|
+
|
|
349
|
+
async def decode(self, response: AsyncHttpResponse) -> AsyncFileResponse:
|
|
350
|
+
return AsyncFileResponse(response)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
file_decoder: Final[ResponseDecoder[FileResponse]] = FileDecoder()
|
|
354
|
+
async_file_decoder: Final[AsyncResponseDecoder[AsyncFileResponse]] = AsyncFileDecoder()
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
@dataclass(frozen=True, slots=True)
|
|
358
|
+
class EventStreamDecoder(Generic[T]):
|
|
359
|
+
"""Response decoder for an operation whose 2xx body is an event stream.
|
|
360
|
+
|
|
361
|
+
Hands the open connection to the :class:`EventStream` it builds, exactly as :class:`FileDecoder`
|
|
362
|
+
does with its file: the body is the payload, so nothing is read here.
|
|
363
|
+
|
|
364
|
+
``decode_data`` is how each event's ``data`` becomes a ``T`` -- the adapter's JSON entry point or
|
|
365
|
+
its plain-value one, chosen by the factory that built this -- and ``sentinel`` is the ``data``
|
|
366
|
+
value that ends the stream, ``None`` where the operation declares none. The format is which
|
|
367
|
+
callable was handed in, never a flag read at runtime."""
|
|
368
|
+
|
|
369
|
+
decode_data: Callable[[str], T]
|
|
370
|
+
sentinel: str | None
|
|
371
|
+
|
|
372
|
+
def decode(self, response: HttpResponse) -> EventStream[T]:
|
|
373
|
+
return EventStream(response, self.decode_data, self.sentinel)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
@dataclass(frozen=True, slots=True)
|
|
377
|
+
class AsyncEventStreamDecoder(Generic[T]):
|
|
378
|
+
"""The twin of :class:`EventStreamDecoder`, over the async response.
|
|
379
|
+
|
|
380
|
+
``async def`` with nothing awaited, as :class:`AsyncFileDecoder` is: the payload is ready, and the
|
|
381
|
+
keyword is what makes this decoder unusable at the sync seam."""
|
|
382
|
+
|
|
383
|
+
decode_data: Callable[[str], T]
|
|
384
|
+
sentinel: str | None
|
|
385
|
+
|
|
386
|
+
async def decode(self, response: AsyncHttpResponse) -> AsyncEventStream[T]:
|
|
387
|
+
return AsyncEventStream(response, self.decode_data, self.sentinel)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
@dataclass(frozen=True, slots=True)
|
|
391
|
+
class EventStreamDecoderBuilder(Generic[T]):
|
|
392
|
+
"""What ``json_event_decoder[T]`` hands back: the type resolved, the sentinel still to name.
|
|
393
|
+
|
|
394
|
+
Calling it -- ``(sentinel="[DONE]")``, or ``()`` for an operation declaring none -- yields the
|
|
395
|
+
decoder. It is not itself one: it has no ``decode``, so ``decoder=json_event_decoder[T]`` with the
|
|
396
|
+
call left off is an ``[arg-type]`` build failure rather than a stream that never ends. The
|
|
397
|
+
sentinel is keyword-only, so a positional one is a build failure too."""
|
|
398
|
+
|
|
399
|
+
decode_data: Callable[[str], T]
|
|
400
|
+
|
|
401
|
+
def __call__(self, *, sentinel: str | None = None) -> ResponseDecoder[EventStream[T]]:
|
|
402
|
+
return EventStreamDecoder(self.decode_data, sentinel)
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
@dataclass(frozen=True, slots=True)
|
|
406
|
+
class AsyncEventStreamDecoderBuilder(Generic[T]):
|
|
407
|
+
"""The twin of :class:`EventStreamDecoderBuilder`, yielding the async decoder."""
|
|
408
|
+
|
|
409
|
+
decode_data: Callable[[str], T]
|
|
410
|
+
|
|
411
|
+
def __call__(self, *, sentinel: str | None = None) -> AsyncResponseDecoder[AsyncEventStream[T]]:
|
|
412
|
+
return AsyncEventStreamDecoder(self.decode_data, sentinel)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def _validates_json(adapter: TypeAdapter[Any]) -> Callable[[str], Any]:
|
|
416
|
+
"""An event's ``data`` parsed and validated in one pass, for an event stream carrying JSON.
|
|
417
|
+
|
|
418
|
+
The adapter's own JSON entry point rather than a parse followed by a validation: one pass, no
|
|
419
|
+
intermediate Python object, on a path that runs once per event rather than once per response.
|
|
420
|
+
That is why the two event factories name an entry point where the two buffered ones name a
|
|
421
|
+
``parse`` -- the step they share comes *after* the adapter there, and *is* the adapter here.
|
|
422
|
+
|
|
423
|
+
Args:
|
|
424
|
+
adapter: The adapter for the subscripted event type.
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
A callable turning one event's ``data`` into that type."""
|
|
428
|
+
return adapter.validate_json
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def _validates_value(adapter: TypeAdapter[Any]) -> Callable[[str], Any]:
|
|
432
|
+
"""An event's ``data`` validated as the value it already is, for an event stream carrying text.
|
|
433
|
+
|
|
434
|
+
``[str]`` passes the string through and ``[int]`` coerces it, exactly as ``text_decoder[int]``
|
|
435
|
+
would; see :func:`_validates_json`.
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
adapter: The adapter for the subscripted event type.
|
|
439
|
+
|
|
440
|
+
Returns:
|
|
441
|
+
A callable turning one event's ``data`` into that type."""
|
|
442
|
+
return adapter.validate_python
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
@dataclass(frozen=True, slots=True)
|
|
446
|
+
class _EventDecoderFactory(SubscriptOnly):
|
|
447
|
+
"""``json_event_decoder[T](sentinel=...)`` / ``text_event_decoder[T](...)`` -- an event stream.
|
|
448
|
+
|
|
449
|
+
One class for two formats, for the reason :class:`_BufferedDecoderFactory` gives: both produce
|
|
450
|
+
the same thing, an :class:`EventStreamDecoderBuilder` whose ``T`` is the subscript. ``read_data``
|
|
451
|
+
is the whole difference between them, and it may be a field because it carries no type of its
|
|
452
|
+
own; only the return *shape* may not.
|
|
453
|
+
|
|
454
|
+
The subscript is a ``TypeForm``, exactly as ``json_decoder``'s is, so a union alias such as a
|
|
455
|
+
discriminated event union routes each event through its ``WireDiscriminator``. What comes back is
|
|
456
|
+
a *builder* rather than a decoder, because an event stream carries one spec fact the type cannot:
|
|
457
|
+
the ``data`` value that ends it. And because the factory declares no ``__call__``, the type cannot
|
|
458
|
+
be omitted (``[operator]``); the one inherited from :class:`SubscriptOnly` is checker-invisible
|
|
459
|
+
and only guides."""
|
|
460
|
+
|
|
461
|
+
read_data: Callable[[TypeAdapter[Any]], Callable[[str], Any]]
|
|
462
|
+
factory_name: str
|
|
463
|
+
spelling: str
|
|
464
|
+
|
|
465
|
+
def __getitem__(self, declared: TypeForm[T]) -> EventStreamDecoderBuilder[T]:
|
|
466
|
+
return EventStreamDecoderBuilder(self.read_data(adapter_for(declared)))
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
@dataclass(frozen=True, slots=True)
|
|
470
|
+
class _AsyncEventDecoderFactory(SubscriptOnly):
|
|
471
|
+
"""The async twins; see the sync one.
|
|
472
|
+
|
|
473
|
+
A separate class rather than a third field, and that is the rule stated there read the other way
|
|
474
|
+
round: an :class:`AsyncEventStreamDecoderBuilder` builds a decoder the sync seam cannot take, so
|
|
475
|
+
the flavour is exactly what may not become data."""
|
|
476
|
+
|
|
477
|
+
read_data: Callable[[TypeAdapter[Any]], Callable[[str], Any]]
|
|
478
|
+
factory_name: str
|
|
479
|
+
spelling: str
|
|
480
|
+
|
|
481
|
+
def __getitem__(self, declared: TypeForm[T]) -> AsyncEventStreamDecoderBuilder[T]:
|
|
482
|
+
return AsyncEventStreamDecoderBuilder(self.read_data(adapter_for(declared)))
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
json_event_decoder: Final = _EventDecoderFactory(
|
|
486
|
+
_validates_json,
|
|
487
|
+
"json_event_decoder",
|
|
488
|
+
"decoder=json_event_decoder[T](), e.g. json_event_decoder[Chunk](sentinel='[DONE]')",
|
|
489
|
+
)
|
|
490
|
+
text_event_decoder: Final = _EventDecoderFactory(
|
|
491
|
+
_validates_value, "text_event_decoder", "decoder=text_event_decoder[T](), e.g. text_event_decoder[str]()"
|
|
492
|
+
)
|
|
493
|
+
async_json_event_decoder: Final = _AsyncEventDecoderFactory(
|
|
494
|
+
_validates_json, "async_json_event_decoder", "decoder=async_json_event_decoder[T]()"
|
|
495
|
+
)
|
|
496
|
+
async_text_event_decoder: Final = _AsyncEventDecoderFactory(
|
|
497
|
+
_validates_value, "async_text_event_decoder", "decoder=async_text_event_decoder[T]()"
|
|
498
|
+
)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""A discriminated union whose tag the variants need not carry.
|
|
2
|
+
|
|
3
|
+
OpenAPI puts ``discriminator`` on the ``oneOf``/``anyOf`` node rather than on the schemas it
|
|
4
|
+
selects, so the property name and the value-to-schema mapping are the union's facts. A schema that
|
|
5
|
+
does not pin that property itself can appear in two unions under two different property names and
|
|
6
|
+
two different tag values, and a model carrying one union's tag in a field could never serve the
|
|
7
|
+
other.
|
|
8
|
+
|
|
9
|
+
:class:`WireDiscriminator` routes on that tag without requiring the variants to carry it. Each arm
|
|
10
|
+
carries pydantic's ``Tag`` with the mapping value the spec gave it; the metadata names the wire
|
|
11
|
+
property. **The union supplies the tag exactly where the variant has no field for it** -- removing
|
|
12
|
+
the key before such a variant validates, and writing it back when such a variant is dumped -- so one
|
|
13
|
+
model round-trips through every union that references it, and a variant's field set stays exactly its
|
|
14
|
+
own schema's.
|
|
15
|
+
|
|
16
|
+
A variant whose schema *does* declare the property keeps that field, and the union leaves it alone in
|
|
17
|
+
both directions: the key reaches the field, and the field dumps it back. The field is read through
|
|
18
|
+
its alias, so the tag may arrive under the wire spelling or under the field's own name.
|
|
19
|
+
|
|
20
|
+
Several mapping values may select the same variant. Each value is its own arm, so each routes; the
|
|
21
|
+
first one the union lists is the spelling a model instance dumps through, since a model carries no
|
|
22
|
+
tag of its own to disambiguate them.
|
|
23
|
+
|
|
24
|
+
**Every discriminated union names this one metadata, whatever its arms declare.** Pydantic's own
|
|
25
|
+
``Field(discriminator=...)`` can serve only the narrowest case -- every arm pinning the property to a
|
|
26
|
+
constant equal to its own mapping value, under one shared field name, one value per arm -- and it
|
|
27
|
+
requires a ``Literal``, refusing a plain ``str``. Reserving it for that case would make the emitting
|
|
28
|
+
template resolve every arm's schema and compare it against the mapping before it could choose a
|
|
29
|
+
shape, and a wrong comparison routes on a value the spec never named. One shape, decided from the
|
|
30
|
+
union node alone, is the trade."""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
from dataclasses import dataclass
|
|
35
|
+
from typing import Any, get_args
|
|
36
|
+
|
|
37
|
+
from pydantic import GetCoreSchemaHandler, Tag
|
|
38
|
+
from pydantic_core import core_schema
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True, slots=True)
|
|
42
|
+
class WireDiscriminator:
|
|
43
|
+
"""``Annotated`` metadata turning a union of ``Tag``-marked arms into a wire-tagged union.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
property_name: The wire key the tag is read from and written to -- the spec's
|
|
47
|
+
``discriminator.propertyName`` in its wire spelling, not a Python field name, because the
|
|
48
|
+
tag is read before any variant's alias machinery runs.
|
|
49
|
+
companion_key: The key the dict companions declare the tag under, given wherever it differs
|
|
50
|
+
from ``property_name`` and some variant needs the union to declare it -- the member
|
|
51
|
+
spelling of the wire key, sanitized where that key is a Python keyword or no identifier
|
|
52
|
+
at all. Read alongside ``property_name``; never written on dump, since only the wire
|
|
53
|
+
spelling belongs on the wire."""
|
|
54
|
+
|
|
55
|
+
property_name: str
|
|
56
|
+
companion_key: str | None = None
|
|
57
|
+
|
|
58
|
+
def __get_pydantic_core_schema__(self, source: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
|
|
59
|
+
"""Build the tagged union, supplying the tag for the arms that declare no field for it.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
source: The union of ``Annotated[Variant, Tag(...)]`` arms this metadata annotates.
|
|
63
|
+
handler: Pydantic's schema generator, called once per arm.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
A tagged-union schema keyed by the variants' declared tags.
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
TypeError: If an arm carries no ``Tag``, leaving its mapping value undeclared."""
|
|
70
|
+
property_name = self.property_name
|
|
71
|
+
arms: list[tuple[type[Any], str]] = []
|
|
72
|
+
spellings: dict[type[Any], frozenset[str]] = {}
|
|
73
|
+
for arm in get_args(source):
|
|
74
|
+
variant, *metadata = get_args(arm)
|
|
75
|
+
tag = next((item.tag for item in metadata if isinstance(item, Tag)), None)
|
|
76
|
+
if tag is None:
|
|
77
|
+
raise TypeError(f"{variant!r} carries no Tag, so WireDiscriminator has no mapping value for it")
|
|
78
|
+
arms.append((variant, tag))
|
|
79
|
+
if variant not in spellings:
|
|
80
|
+
spellings[variant] = frozenset(
|
|
81
|
+
name for name, field in variant.model_fields.items() if property_name in (name, field.alias)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# The value a model instance dumps through, for a variant several mapping values select. It
|
|
85
|
+
# carries no tag of its own, so the union's first listing is the one deterministic answer.
|
|
86
|
+
canonical: dict[type[Any], str] = {}
|
|
87
|
+
for variant, tag in arms:
|
|
88
|
+
canonical.setdefault(variant, tag)
|
|
89
|
+
|
|
90
|
+
declared = {name for names in spellings.values() for name in names}
|
|
91
|
+
substitute = {self.companion_key} if self.companion_key is not None else set[str]()
|
|
92
|
+
keys = [property_name, *sorted((declared | substitute) - {property_name})]
|
|
93
|
+
|
|
94
|
+
def read_tag(value: Any) -> str | None:
|
|
95
|
+
if not isinstance(value, dict):
|
|
96
|
+
return canonical.get(type(value))
|
|
97
|
+
for key in keys:
|
|
98
|
+
tag_ = value.get(key)
|
|
99
|
+
if isinstance(tag_, str):
|
|
100
|
+
return tag_
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
# pydantic-core names the reader in both union-tag errors, so this reads ``using petKind()``
|
|
104
|
+
# rather than ``using read_tag()`` -- the wire key the spec declares, which is what a caller
|
|
105
|
+
# reading the message is looking for.
|
|
106
|
+
read_tag.__name__ = property_name
|
|
107
|
+
|
|
108
|
+
def choice(variant_: type[Any], tag_: str) -> core_schema.CoreSchema:
|
|
109
|
+
schema = handler.generate_schema(variant_)
|
|
110
|
+
if spellings[variant_]:
|
|
111
|
+
return schema
|
|
112
|
+
foreign = frozenset(keys)
|
|
113
|
+
|
|
114
|
+
def without_tag(value: Any, validate: core_schema.ValidatorFunctionWrapHandler) -> Any:
|
|
115
|
+
if isinstance(value, dict) and not foreign.isdisjoint(value):
|
|
116
|
+
return validate({key: item for key, item in value.items() if key not in foreign})
|
|
117
|
+
return validate(value)
|
|
118
|
+
|
|
119
|
+
def with_tag(value: Any, serialize: core_schema.SerializerFunctionWrapHandler) -> Any:
|
|
120
|
+
dumped = serialize(value)
|
|
121
|
+
if isinstance(dumped, dict):
|
|
122
|
+
return {**dumped, property_name: tag_}
|
|
123
|
+
return dumped
|
|
124
|
+
|
|
125
|
+
return core_schema.no_info_wrap_validator_function(
|
|
126
|
+
without_tag, schema, serialization=core_schema.wrap_serializer_function_ser_schema(with_tag)
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
return core_schema.tagged_union_schema({tag: choice(variant, tag) for variant, tag in arms}, read_tag)
|