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.
- cliv1-0.0.7.dist-info/METADATA +212 -0
- cliv1-0.0.7.dist-info/RECORD +92 -0
- cliv1-0.0.7.dist-info/WHEEL +4 -0
- cliv1-0.0.7.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,590 @@
|
|
|
1
|
+
"""Request bodies: the wire shapes a transport receives, and the factories that build them.
|
|
2
|
+
|
|
3
|
+
A buffered body **is** its wire bytes by the time it exists -- UTF-8 for JSON (RFC 8259) and for a
|
|
4
|
+
percent-encoded form (pure ASCII, so the same bytes under any ASCII-compatible charset), and the
|
|
5
|
+
declared charset for a text body. A multipart body's text parts carry their UTF-8 bytes the same
|
|
6
|
+
way; only the framing around them is the encoder's. File content is deliberately
|
|
7
|
+
the opposite: a file is *described* (a ``Path``, a handle, an iterable of chunks) and first read
|
|
8
|
+
inside the transport when the request is sent, so building a body never opens a resource. Each
|
|
9
|
+
factory validates any typed value against the type the endpoint declared and dumps it through that
|
|
10
|
+
type's adapter, so a transport, including a caller-supplied one, never serializes anything, and
|
|
11
|
+
:class:`HttpRequest` never carries a pydantic adapter. A body also carries its own label:
|
|
12
|
+
:func:`content_headers` is the one place a body's ``Content-Type`` (and a raw body's
|
|
13
|
+
``Content-Disposition``) is spelled, folded onto the request beneath every parameter layer.
|
|
14
|
+
|
|
15
|
+
``RequestBody`` is a closed union rather than a Protocol: all five shapes live here and are
|
|
16
|
+
generator-emitted, so the set cannot grow behind the runtime's back."""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import codecs
|
|
21
|
+
from collections.abc import Sequence
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Any, Final, Generic, TypeAlias, TypeVar, overload
|
|
25
|
+
|
|
26
|
+
from pydantic import TypeAdapter
|
|
27
|
+
from typing_extensions import TypeForm, assert_never
|
|
28
|
+
|
|
29
|
+
from ._internal.content_disposition import render_content_disposition
|
|
30
|
+
from ._internal.flattening import flatten
|
|
31
|
+
from ._internal.subscripts import SubscriptOnly
|
|
32
|
+
from ._internal.urls import urlencode_params
|
|
33
|
+
from ._internal.wire import compact_json, to_text
|
|
34
|
+
from .adapters import adapter_for, validation_target
|
|
35
|
+
from .files import AsyncBinaryContent, AsyncFileInput, NamedFile
|
|
36
|
+
from .optionality import strip_unset
|
|
37
|
+
from .params import Param
|
|
38
|
+
|
|
39
|
+
T = TypeVar("T")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True, slots=True)
|
|
43
|
+
class JsonBody(Generic[T]):
|
|
44
|
+
"""A JSON body of declared type ``T``: its wire bytes, under the operation's declared media type.
|
|
45
|
+
|
|
46
|
+
``T`` records the type the value was validated and dumped *as*; ``content`` is that dump --
|
|
47
|
+
compact, UTF-8, produced once by ``json_body[...]`` -- so the transport sends it unchanged and a
|
|
48
|
+
log line describing the body shows the same bytes. ``media_type`` is the operation's content key:
|
|
49
|
+
``application/json`` almost always, ``application/merge-patch+json`` and the ``+json`` vendor
|
|
50
|
+
types being the same wire shape under a different label. Construct it through ``json_body[...]``,
|
|
51
|
+
whose subscript binds ``T``."""
|
|
52
|
+
|
|
53
|
+
content: bytes
|
|
54
|
+
media_type: str
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True, slots=True)
|
|
58
|
+
class FormBody:
|
|
59
|
+
"""A url-encoded form body: its wire bytes.
|
|
60
|
+
|
|
61
|
+
Rendered by the encoder the query string uses (``urlencode_params``), so a form field and a
|
|
62
|
+
query parameter of one value are the same bytes, in the same order. Percent-encoding leaves
|
|
63
|
+
ASCII, so the UTF-8 bytes of that text are the same bytes under any ASCII-compatible charset."""
|
|
64
|
+
|
|
65
|
+
content: bytes
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass(frozen=True, slots=True)
|
|
69
|
+
class MultipartText:
|
|
70
|
+
"""One non-file part: a named text value, already encoded as UTF-8, optionally under a declared media type.
|
|
71
|
+
|
|
72
|
+
A JSON part is not a separate shape: it is a text part under the media type the operation
|
|
73
|
+
declares for it (``application/json``, unless its encoding object says otherwise) -- the
|
|
74
|
+
identical ``(None, content, media_type)`` wire spelling either way. Classes split only where the
|
|
75
|
+
transport must act differently, and here it does not. The bytes are the factory's, as every
|
|
76
|
+
in-memory body's are: the encoder frames a part and encodes nothing."""
|
|
77
|
+
|
|
78
|
+
name: str
|
|
79
|
+
content: bytes
|
|
80
|
+
media_type: str | None = None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass(frozen=True, slots=True)
|
|
84
|
+
class MultipartFile:
|
|
85
|
+
"""One file part, its filename and media type already resolved.
|
|
86
|
+
|
|
87
|
+
Kept distinct from :class:`NamedFile` deliberately: there ``filename=None`` means *derive it*;
|
|
88
|
+
here it means *resolved to absent*. Merging the two would conflate an unresolved state with a
|
|
89
|
+
resolved one. ``media_type`` has no absent state at all -- :func:`file_part` always resolves
|
|
90
|
+
one, so a transport is never handed the question and the underlying library's own guess is
|
|
91
|
+
unreachable."""
|
|
92
|
+
|
|
93
|
+
name: str
|
|
94
|
+
|
|
95
|
+
content: AsyncBinaryContent
|
|
96
|
+
"""The bytes, however the caller held them.
|
|
97
|
+
|
|
98
|
+
The *async* union, for the reason :attr:`BinaryBody.content` is one: ``HttpRequest`` is a single
|
|
99
|
+
shape for both flavours, and which arms are reachable is fixed by the emitted endpoint's own
|
|
100
|
+
parameter type. A transport is handed them unresolved -- the sync one rejects the async arms
|
|
101
|
+
with a guided ``TypeError``, and an async one must read them before encoding, because a
|
|
102
|
+
multipart encoder pulls from a synchronous generator and cannot await."""
|
|
103
|
+
|
|
104
|
+
filename: str | None
|
|
105
|
+
media_type: str
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
MultipartPart: TypeAlias = MultipartText | MultipartFile
|
|
109
|
+
"""One part of a multipart body -- text under a name, or a file. The transport's ``match`` over
|
|
110
|
+
this union is exhaustiveness-checked, so a new part kind cannot be half-wired."""
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@dataclass(frozen=True, slots=True)
|
|
114
|
+
class MultipartFiles:
|
|
115
|
+
"""One file field carrying several files -- the resolved part for each, under one name.
|
|
116
|
+
|
|
117
|
+
What ``file_part`` returns for a list, and what ``multipart_body`` unfolds into its
|
|
118
|
+
:class:`MultipartFile` members, exactly as it flattens a :class:`Param` into
|
|
119
|
+
:class:`MultipartText` parts. Deliberately **not** a member of :data:`MultipartPart`: a
|
|
120
|
+
transport sees one part per file and never this wrapper, so the wire vocabulary stays two
|
|
121
|
+
classes and no ``match`` over it gains an arm."""
|
|
122
|
+
|
|
123
|
+
parts: Sequence[MultipartFile]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@dataclass(frozen=True, slots=True)
|
|
127
|
+
class MultipartBody:
|
|
128
|
+
"""A multipart body: its parts, in the order they go on the wire.
|
|
129
|
+
|
|
130
|
+
One ordered sequence rather than a fields/files mapping pair, because part order is observable
|
|
131
|
+
on the wire and a mapping cannot interleave text parts with file parts."""
|
|
132
|
+
|
|
133
|
+
parts: Sequence[MultipartPart]
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@dataclass(frozen=True, slots=True)
|
|
137
|
+
class BinaryBody:
|
|
138
|
+
"""A raw request body -- its declared media type, the caller's name for it, and the bytes.
|
|
139
|
+
|
|
140
|
+
``content`` is typed as the *async* union because ``HttpRequest`` is one shape for both
|
|
141
|
+
flavours; which arms are reachable is fixed by the endpoint's own parameter type, exactly as
|
|
142
|
+
``custom_http_client`` vs ``custom_async_http_client`` fixes the transport -- and the sync
|
|
143
|
+
transport rejects the two async arms with a guided ``TypeError``.
|
|
144
|
+
|
|
145
|
+
``filename`` is resolved, in :class:`MultipartFile`'s exact sense: ``None`` means *resolved to
|
|
146
|
+
absent*, never *derive one*. :func:`binary_body` has already applied the same fallback a part
|
|
147
|
+
gets, so the two classes' ``filename`` fields mean the same thing. :func:`content_headers`
|
|
148
|
+
renders it onto the request as ``Content-Disposition``."""
|
|
149
|
+
|
|
150
|
+
content: AsyncBinaryContent
|
|
151
|
+
filename: str | None
|
|
152
|
+
media_type: str
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@dataclass(frozen=True, slots=True)
|
|
156
|
+
class TextBody:
|
|
157
|
+
"""A body that is one text value, already encoded, under a declared media type and charset.
|
|
158
|
+
|
|
159
|
+
``charset`` is the spec's own label (``utf-8``, ``iso-8859-1``, ``windows-1252``) and does two
|
|
160
|
+
jobs from one field: the factory encoded the text with it, and :func:`content_headers` writes
|
|
161
|
+
the label ``media_type; charset=<charset>`` onto the request from that same field -- so the bytes
|
|
162
|
+
and the label cannot disagree, and a transport encodes nothing. Text the charset cannot represent
|
|
163
|
+
is refused at the factory, before any request is composed. The factory defaults ``charset`` to
|
|
164
|
+
``utf-8`` and ``media_type`` to ``text/plain``, and an endpoint writes either only where the spec
|
|
165
|
+
differs -- so the body a transport sees is always explicit."""
|
|
166
|
+
|
|
167
|
+
content: bytes
|
|
168
|
+
media_type: str
|
|
169
|
+
charset: str
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
RequestBody: TypeAlias = JsonBody[Any] | FormBody | MultipartBody | BinaryBody | TextBody
|
|
173
|
+
"""The body an endpoint hands to ``execute`` -- exactly one of the five shapes above.
|
|
174
|
+
|
|
175
|
+
A union rather than one class with five optional fields: of the combinations such a class would
|
|
176
|
+
admit, only five are legal, and the illegal rest had to be rejected at runtime. Here they cannot
|
|
177
|
+
be written down. A transport ``match``es on this to pick its body arguments."""
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def content_headers(body: RequestBody | None) -> dict[str, str]:
|
|
181
|
+
"""The headers a body carries for itself: its media type, and a raw body's name.
|
|
182
|
+
|
|
183
|
+
Folded **under** the resolved parameter layers by ``BaseRawClient._build_request``, so a caller's
|
|
184
|
+
``extra_headers`` still wins, and a transport sends ``request.headers`` as given. A multipart body
|
|
185
|
+
contributes nothing: its ``Content-Type`` carries the boundary the encoder mints, so only the
|
|
186
|
+
transport can write it (ADR-0051 keeps httpx's encoder).
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
body: The body an endpoint built, or ``None`` for a request that carries none.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
Lowercased header names to values; empty for no body and for a multipart one."""
|
|
193
|
+
match body:
|
|
194
|
+
case None | MultipartBody():
|
|
195
|
+
return {}
|
|
196
|
+
case JsonBody(_, media_type):
|
|
197
|
+
return {"content-type": media_type}
|
|
198
|
+
case FormBody():
|
|
199
|
+
return {"content-type": "application/x-www-form-urlencoded"}
|
|
200
|
+
case BinaryBody(_, str() as filename, media_type):
|
|
201
|
+
return {"content-type": media_type, "content-disposition": render_content_disposition(filename)}
|
|
202
|
+
case BinaryBody(_, _, media_type):
|
|
203
|
+
return {"content-type": media_type}
|
|
204
|
+
case TextBody(_, media_type, charset):
|
|
205
|
+
return {"content-type": f"{media_type}; charset={charset}"}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _json_value(adapter: TypeAdapter[T], value: T) -> object:
|
|
209
|
+
# The one validate -> dump -> strip_unset pipeline json_body and json_part share.
|
|
210
|
+
validated = adapter.validate_python(value)
|
|
211
|
+
return strip_unset(validated, adapter.dump_python(validated, mode="json"))
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _derive_filename(content: AsyncBinaryContent) -> str | None:
|
|
215
|
+
# Both destinations' fallback. A Path names its file; a handle may carry a filesystem-shaped
|
|
216
|
+
# ``name`` -- used only when it is a str, since ``open(fd)`` yields an int one. The basename
|
|
217
|
+
# only, so a caller's directories never reach the wire. Otherwise no filename at all: the
|
|
218
|
+
# underlying library's fallback is the literal string "upload", which is worse than omitting an
|
|
219
|
+
# optional parameter (RFC 7578's at a part, RFC 6266's at a raw body).
|
|
220
|
+
if isinstance(content, Path):
|
|
221
|
+
return content.name
|
|
222
|
+
name = getattr(content, "name", None)
|
|
223
|
+
if not isinstance(name, str):
|
|
224
|
+
return None
|
|
225
|
+
return Path(name).name or None
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@dataclass(frozen=True, slots=True)
|
|
229
|
+
class _DeclaredJsonBody(Generic[T]):
|
|
230
|
+
"""A JSON body's declared type, bound to its adapter; call it with the value and its media type.
|
|
231
|
+
|
|
232
|
+
Built by ``json_body[T]``, the ``_DeclaredParam`` shape: the keyword-only ``media_type`` is
|
|
233
|
+
checked at the call, and a ``Callable`` could spell neither it nor the check. It defaults to
|
|
234
|
+
``application/json``, so an endpoint writes it only where the operation's key differs -- the
|
|
235
|
+
rule ``param``'s ``serialization_format`` set."""
|
|
236
|
+
|
|
237
|
+
adapter: TypeAdapter[T]
|
|
238
|
+
|
|
239
|
+
def __call__(self, value: T, /, *, media_type: str = "application/json") -> JsonBody[T]:
|
|
240
|
+
return JsonBody(compact_json(_json_value(self.adapter, value), ensure_ascii=False).encode("utf-8"), media_type)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class _JsonBodyFactory(SubscriptOnly):
|
|
244
|
+
"""``json_body[T](value)`` -- name the body's declared type in the subscript.
|
|
245
|
+
|
|
246
|
+
**A subscript and then a call, and the split is the whole point.** ``T`` is solved from the
|
|
247
|
+
subscript alone, *before* the value is compared against it, which is what makes a generator
|
|
248
|
+
emitting the wrong type a build failure: ``json_body[int](employee)`` is rejected with
|
|
249
|
+
``Argument 1 has incompatible type "Employee | EmployeeDict"; expected "int"``. Written as one
|
|
250
|
+
call it could not be: ``json_body(employee, int)`` makes mypy solve ``T`` from *both*
|
|
251
|
+
arguments and join them to ``object``, so every mismatch type-checks -- measured, along with
|
|
252
|
+
the other shapes that fail the same way, in ADR-0013 (whose curried spelling this subscript
|
|
253
|
+
replaced; the re-measurement is in docs/plans/subscripted-typed-factories.md).
|
|
254
|
+
|
|
255
|
+
The factory declares no ``__call__``, so *omitting* the declared type is a build failure too:
|
|
256
|
+
``json_body(employee)`` is rejected statically (``[operator]``) -- the guarantee the curried
|
|
257
|
+
signature used to carry. The runtime ``__call__`` inherited from :class:`SubscriptOnly` is
|
|
258
|
+
invisible to type checkers and
|
|
259
|
+
exists only to turn that same mistake into a guided ``TypeError``.
|
|
260
|
+
|
|
261
|
+
The subscript is a ``TypeForm``, exactly as in ``json_decoder[...]``, so a runtime union alias
|
|
262
|
+
(``Person``, which is ``Employee | Boss``) binds ``T`` as precisely as a concrete class does.
|
|
263
|
+
|
|
264
|
+
The subscript is the endpoint's declared parameter type **in full**, dict-shaped companion
|
|
265
|
+
included: ``json_body[Employee | EmployeeDict]``. The accepted value is then exactly ``T``, so
|
|
266
|
+
there is no permissive mapping half for a stray ``dict[str, Any]`` to slip through -- and only
|
|
267
|
+
the model arm reaches :func:`adapter_for`, via :func:`validation_target`, because a companion
|
|
268
|
+
is an input shape and never a wire shape.
|
|
269
|
+
|
|
270
|
+
Handles scalars, dicts, lists and nested structures, and honours ``Annotated`` serializers
|
|
271
|
+
(``PlainSerializer`` and friends) because serialization goes through the type's own adapter.
|
|
272
|
+
Validation runs before the dump, so dict-shaped input is coerced into the model through its own
|
|
273
|
+
validation; a model instance passes through unchanged (``revalidate_instances`` defaults to
|
|
274
|
+
``"never"``), making this byte-identical for non-dict values. ``strip_unset`` then distinguishes
|
|
275
|
+
a field the caller never touched (``OptionalNullable[...] = UNSET``, omitted) from one set
|
|
276
|
+
explicitly to ``None`` (kept as null) -- see docs/designs/optional-nullable-fields.md.
|
|
277
|
+
|
|
278
|
+
``media_type`` is the operation's content key -- ``application/json`` by default, and written at
|
|
279
|
+
the emission site only where the key differs: a ``+json`` vendor type is the same wire shape under
|
|
280
|
+
its own label, only the endpoint knows which, and the transport carries whichever the body holds
|
|
281
|
+
(ADR-0048)."""
|
|
282
|
+
|
|
283
|
+
factory_name = "json_body"
|
|
284
|
+
spelling = "json_body[T](value), e.g. json_body[Employee | EmployeeDict](model)"
|
|
285
|
+
|
|
286
|
+
def __getitem__(self, declared: TypeForm[T]) -> _DeclaredJsonBody[T]:
|
|
287
|
+
return _DeclaredJsonBody(adapter_for(validation_target(declared)))
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
json_body: Final = _JsonBodyFactory()
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def form_body(*params: Param[Any]) -> FormBody:
|
|
294
|
+
"""Render ``params`` into a url-encoded form body.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
params: The form parameters, in wire order, each already carrying its declared type's adapter.
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
A :class:`FormBody` holding the wire bytes.
|
|
301
|
+
|
|
302
|
+
Raises:
|
|
303
|
+
ValueError: If a parameter's value fails validation against its declared type."""
|
|
304
|
+
return FormBody(urlencode_params(params).encode("utf-8"))
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def multipart_body(*parts: Param[Any] | MultipartPart | MultipartFiles | None) -> MultipartBody:
|
|
308
|
+
"""Build a multipart body from parameters and parts, in the order written.
|
|
309
|
+
|
|
310
|
+
Variadic, like ``AllSchemes``: the parts *are* the argument list, so an emission site nests no
|
|
311
|
+
list literal. A field that is an array of files is still one argument --
|
|
312
|
+
``file_part("files", files)`` -- and is unfolded here, one part per file, so no emission site
|
|
313
|
+
loops or splats.
|
|
314
|
+
|
|
315
|
+
A :class:`Param` is flattened exactly as a form field is -- so an array explodes and a map
|
|
316
|
+
becomes bracketed keys -- and each resulting pair becomes one text part. A
|
|
317
|
+
:class:`MultipartFiles` contributes each of its files as its own part. A part built by
|
|
318
|
+
:func:`file_part` for one file, or by ``json_part[...]``, passes through untouched. ``None`` is
|
|
319
|
+
an optional part the caller omitted and contributes nothing -- the part counterpart of a
|
|
320
|
+
``param`` whose value is ``None``, spelled ``file_part(...) if x is not None else None`` at the
|
|
321
|
+
emission site, for one file and for a list alike.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
parts: Parameters and prebuilt parts, in wire order.
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
A :class:`MultipartBody` holding one part per flattened pair, per file, or per prebuilt part.
|
|
328
|
+
|
|
329
|
+
Raises:
|
|
330
|
+
ValueError: If a parameter's value fails validation against its declared type."""
|
|
331
|
+
resolved: list[MultipartPart] = []
|
|
332
|
+
for part in parts:
|
|
333
|
+
match part:
|
|
334
|
+
case None:
|
|
335
|
+
pass
|
|
336
|
+
case Param():
|
|
337
|
+
resolved.extend(MultipartText(key, text.encode("utf-8")) for key, text in flatten([part]))
|
|
338
|
+
case MultipartFiles(files):
|
|
339
|
+
resolved.extend(files)
|
|
340
|
+
case MultipartText() | MultipartFile():
|
|
341
|
+
resolved.append(part)
|
|
342
|
+
case _:
|
|
343
|
+
assert_never(part)
|
|
344
|
+
return MultipartBody(resolved)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _resolve_file(name: str, value: AsyncFileInput, media_type: str) -> MultipartFile:
|
|
348
|
+
"""One file's part, its filename and media type resolved through the two tiers.
|
|
349
|
+
|
|
350
|
+
The override first, then the operation's declared value -- the rule :func:`binary_body` applies
|
|
351
|
+
to the one of the two fields a raw body can carry.
|
|
352
|
+
|
|
353
|
+
Lifted out of :func:`file_part` rather than reached by recursion through its own overloads: the
|
|
354
|
+
list arm needs a :class:`MultipartFile` per item, and saying so beats resolving an overload
|
|
355
|
+
against the function being defined.
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
name: The form field name the part is sent under.
|
|
359
|
+
value: One file's content, or a ``NamedFile`` overriding its filename or media type.
|
|
360
|
+
media_type: The media type the operation declares for this field.
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
A :class:`MultipartFile` with no unresolved field left."""
|
|
364
|
+
match value:
|
|
365
|
+
case NamedFile(content, filename, override):
|
|
366
|
+
return MultipartFile(
|
|
367
|
+
name=name,
|
|
368
|
+
content=content,
|
|
369
|
+
filename=filename if filename is not None else _derive_filename(content),
|
|
370
|
+
media_type=override if override is not None else media_type,
|
|
371
|
+
)
|
|
372
|
+
case _:
|
|
373
|
+
return MultipartFile(name=name, content=value, filename=_derive_filename(value), media_type=media_type)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
@overload
|
|
377
|
+
def file_part(
|
|
378
|
+
name: str, value: Sequence[AsyncFileInput], *, media_type: str = "application/octet-stream"
|
|
379
|
+
) -> MultipartFiles: ...
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
@overload
|
|
383
|
+
def file_part(name: str, value: AsyncFileInput, *, media_type: str = "application/octet-stream") -> MultipartFile: ...
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def file_part(
|
|
387
|
+
name: str,
|
|
388
|
+
value: AsyncFileInput | Sequence[AsyncFileInput],
|
|
389
|
+
*,
|
|
390
|
+
media_type: str = "application/octet-stream",
|
|
391
|
+
) -> MultipartFile | MultipartFiles:
|
|
392
|
+
"""One file part -- or one part per file of a list -- under ``name``; the annotation decides which.
|
|
393
|
+
|
|
394
|
+
A binary field is emitted the same way whether its schema is one file or an array of them:
|
|
395
|
+
``file_part(name, value, media_type=...)`` with the parameter as declared, ``FileInput`` or
|
|
396
|
+
``list[FileInput]``. A list yields a :class:`MultipartFiles` that ``multipart_body`` unfolds
|
|
397
|
+
into one :class:`MultipartFile` per item under the same name (RFC 7578's repeated field), each
|
|
398
|
+
resolved exactly as a single file is -- so an item may be a ``NamedFile`` overriding its own
|
|
399
|
+
filename or media type.
|
|
400
|
+
|
|
401
|
+
``media_type`` is the operation's declared one, defaulting to ``application/octet-stream`` -- RFC
|
|
402
|
+
9110 8.3's answer for a payload of unknown type, and OpenAPI's own default for a binary part that
|
|
403
|
+
declares none. An endpoint writes it only where the spec differs (ADR-0048), and a ``NamedFile``
|
|
404
|
+
overrides whatever it is, per item for a list.
|
|
405
|
+
|
|
406
|
+
**The filename is never read as a media type.** An extension is a claim the caller typed, not
|
|
407
|
+
evidence about the bytes -- ``report.pdf`` may hold a PNG -- and checking would mean opening the
|
|
408
|
+
content here, which nothing in this module does. ``application/octet-stream`` is what the SDK
|
|
409
|
+
actually knows; a caller who knows better says so through ``NamedFile(content, media_type=...)``.
|
|
410
|
+
|
|
411
|
+
**A list is always many files, and nothing has to arbitrate that.** One file's content streams
|
|
412
|
+
as an ``Iterator[bytes]``, never a ``Sequence`` of them, so a ``list[bytes]`` satisfies only the
|
|
413
|
+
first overload and the two arms do not overlap. A chunk *list* that is one file's content says
|
|
414
|
+
so by handing over an iterator -- ``NamedFile(iter([b"a", b"b"]))`` -- and that is the whole of
|
|
415
|
+
the rule (ADR-0049).
|
|
416
|
+
|
|
417
|
+
The runtime agrees by a different route: PEP 634 defines a sequence pattern to exclude ``str``,
|
|
418
|
+
``bytes`` and ``bytearray``, the one place the language already knows a byte string is content
|
|
419
|
+
rather than a collection, and an iterator is no ``Sequence`` at either level.
|
|
420
|
+
``isinstance(value, Sequence)`` would be true of ``bytes`` and send each byte as its own part.
|
|
421
|
+
|
|
422
|
+
The overloads earn their keep beyond the return type: they give a heterogeneous list literal --
|
|
423
|
+
``[b"one", NamedFile(b"two", filename="2.bin")]`` -- an element type to check against. Collapsed
|
|
424
|
+
into one signature over a union, that literal joins to ``list[object]`` and is rejected.
|
|
425
|
+
|
|
426
|
+
Nothing is opened or read here -- the content descriptor is carried as given, and the first read
|
|
427
|
+
happens in the transport when the request is sent.
|
|
428
|
+
|
|
429
|
+
A part with no filename reads as a form *field* rather than a file to many servers (measured:
|
|
430
|
+
ASP.NET's ``IFormFile`` binding requires one). Bare ``bytes`` and a nameless reader derive no
|
|
431
|
+
filename -- deliberately, since the underlying library's fallback is the literal string
|
|
432
|
+
``"upload"`` -- so name them through ``NamedFile(content, filename=...)`` when the operation
|
|
433
|
+
expects a file.
|
|
434
|
+
|
|
435
|
+
Args:
|
|
436
|
+
name: The form field name this part -- or every part of the list -- is sent under.
|
|
437
|
+
value: One file's content, a ``NamedFile`` overriding its filename or media type, or a
|
|
438
|
+
list of either.
|
|
439
|
+
media_type: The media type the operation declares for this field;
|
|
440
|
+
``application/octet-stream`` when omitted.
|
|
441
|
+
|
|
442
|
+
Returns:
|
|
443
|
+
A part carrying the content descriptor with its filename and media type resolved -- or, for
|
|
444
|
+
a list, the resolved part for each item."""
|
|
445
|
+
match value:
|
|
446
|
+
case [*items]:
|
|
447
|
+
return MultipartFiles(tuple(_resolve_file(name, item, media_type) for item in items))
|
|
448
|
+
case _:
|
|
449
|
+
return _resolve_file(name, value, media_type)
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
@dataclass(frozen=True, slots=True)
|
|
453
|
+
class _DeclaredJsonPart(Generic[T]):
|
|
454
|
+
"""A JSON part's declared type, bound to its adapter; call it with the name, value and media type.
|
|
455
|
+
|
|
456
|
+
Built by ``json_part[T]``, the ``_DeclaredParam`` shape, for the reason ``_DeclaredJsonBody``
|
|
457
|
+
gives; ``media_type`` defaults to ``application/json`` for the same reason."""
|
|
458
|
+
|
|
459
|
+
adapter: TypeAdapter[T]
|
|
460
|
+
|
|
461
|
+
def __call__(self, name: str, value: T, /, *, media_type: str = "application/json") -> MultipartText:
|
|
462
|
+
return MultipartText(name, compact_json(_json_value(self.adapter, value)).encode("utf-8"), media_type)
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
class _JsonPartFactory(SubscriptOnly):
|
|
466
|
+
"""``json_part[T](name, value)`` -- a multipart part whose body is JSON, not a form field.
|
|
467
|
+
|
|
468
|
+
Subscripted for the reason ``json_body`` documents: ``T`` is solved from the subscript alone,
|
|
469
|
+
so a mismatched emission is a build failure. The declared type is the endpoint's in full, dict
|
|
470
|
+
companion included, and only the model arm reaches the adapter.
|
|
471
|
+
|
|
472
|
+
This is the part spelling ``(None, json, media_type)`` that the field hand-rolls at the call
|
|
473
|
+
site, under the media type the operation's encoding object declares for the part --
|
|
474
|
+
``application/json`` by default, written at the emission site only where the encoding object
|
|
475
|
+
says otherwise (ADR-0048). Declaring it is what makes a map-as-JSON-part and a
|
|
476
|
+
map-as-form-fields two different factories rather than one factory and a flag."""
|
|
477
|
+
|
|
478
|
+
factory_name = "json_part"
|
|
479
|
+
spelling = 'json_part[T](name, value), e.g. json_part[FileMetadata | FileMetadataDict]("metadata", metadata)'
|
|
480
|
+
|
|
481
|
+
def __getitem__(self, declared: TypeForm[T]) -> _DeclaredJsonPart[T]:
|
|
482
|
+
return _DeclaredJsonPart(adapter_for(validation_target(declared)))
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
json_part: Final = _JsonPartFactory()
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def binary_body(content: AsyncFileInput, *, media_type: str = "application/octet-stream") -> BinaryBody:
|
|
489
|
+
"""A raw request body under the operation's declared media type.
|
|
490
|
+
|
|
491
|
+
``media_type`` is the operation's ``requestBody`` content key, defaulting to RFC 9110 8.3's own
|
|
492
|
+
answer for a payload of unknown type -- so an endpoint writes it only where the key differs
|
|
493
|
+
(``image/png``), the rule every other body factory follows (ADR-0048).
|
|
494
|
+
|
|
495
|
+
A ``NamedFile`` here contributes both of its overrides, and both resolve through the two tiers
|
|
496
|
+
:func:`file_part` applies -- the override, then the SDK's own answer. ``media_type`` falls back
|
|
497
|
+
to the operation's declared value; ``filename`` falls back to :func:`_derive_filename`, so a
|
|
498
|
+
``Path`` or a named handle is sent under its name and a nameless source under none at all
|
|
499
|
+
(ADR-0053, amended). :func:`content_headers` renders it onto the request as a
|
|
500
|
+
``Content-Disposition``.
|
|
501
|
+
|
|
502
|
+
Only the basename is sent, so a caller's directories never reach the wire.
|
|
503
|
+
|
|
504
|
+
Nothing is opened or read here -- the content descriptor is carried as given, and the first
|
|
505
|
+
read happens in the transport when the request is sent.
|
|
506
|
+
|
|
507
|
+
Args:
|
|
508
|
+
content: The body content descriptor, or a ``NamedFile`` overriding its media type or
|
|
509
|
+
naming it.
|
|
510
|
+
media_type: The media type the operation declares for the body;
|
|
511
|
+
``application/octet-stream`` when omitted.
|
|
512
|
+
|
|
513
|
+
Returns:
|
|
514
|
+
A :class:`BinaryBody` carrying the content, its resolved name and its resolved media
|
|
515
|
+
type."""
|
|
516
|
+
match content:
|
|
517
|
+
case NamedFile(inner, filename, override):
|
|
518
|
+
return BinaryBody(
|
|
519
|
+
inner,
|
|
520
|
+
filename if filename is not None else _derive_filename(inner),
|
|
521
|
+
override if override is not None else media_type,
|
|
522
|
+
)
|
|
523
|
+
case _:
|
|
524
|
+
return BinaryBody(content, _derive_filename(content), media_type)
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
@dataclass(frozen=True, slots=True)
|
|
528
|
+
class _DeclaredTextBody(Generic[T]):
|
|
529
|
+
"""A text body's declared type, bound to its adapter; call it with the value and its wire facts.
|
|
530
|
+
|
|
531
|
+
Built by ``text_body[T]`` exactly as ``_DeclaredParam`` is by ``param[T]``: a frozen dataclass
|
|
532
|
+
with a typed ``__call__``, so the keyword-only arguments are checked at the call without a
|
|
533
|
+
callback Protocol -- which would be the one Protocol in the package not standing for an open
|
|
534
|
+
seam (ADR-0041)."""
|
|
535
|
+
|
|
536
|
+
adapter: TypeAdapter[T]
|
|
537
|
+
|
|
538
|
+
def __call__(self, value: T, /, *, media_type: str = "text/plain", charset: str = "utf-8") -> TextBody:
|
|
539
|
+
"""Validate and dump ``value`` through the declared type, under the operation's wire facts.
|
|
540
|
+
|
|
541
|
+
Args:
|
|
542
|
+
value: The body value, of the declared type.
|
|
543
|
+
media_type: The operation's content key, less its ``charset`` parameter; ``text/plain``
|
|
544
|
+
when omitted.
|
|
545
|
+
charset: That ``charset`` parameter -- the spec's label, which the codec registry
|
|
546
|
+
resolves as written; ``utf-8`` when omitted.
|
|
547
|
+
|
|
548
|
+
Returns:
|
|
549
|
+
A :class:`TextBody` carrying the dumped text encoded with ``charset``, and both facts unchanged.
|
|
550
|
+
|
|
551
|
+
Raises:
|
|
552
|
+
ValueError: If ``charset`` names no codec, ``value`` fails validation, or the text holds a
|
|
553
|
+
character ``charset`` cannot represent (``UnicodeEncodeError``, a ``ValueError``)."""
|
|
554
|
+
try:
|
|
555
|
+
codecs.lookup(charset)
|
|
556
|
+
except LookupError as error:
|
|
557
|
+
# ``str.encode`` would report this as LookupError, outside the ValueError contract every
|
|
558
|
+
# other rejection here honours.
|
|
559
|
+
raise ValueError(f"Unknown charset for a text body: {charset!r}") from error
|
|
560
|
+
validated = self.adapter.validate_python(value)
|
|
561
|
+
dumped = self.adapter.dump_python(validated, mode="json")
|
|
562
|
+
text = dumped if isinstance(dumped, str) else to_text(dumped)
|
|
563
|
+
return TextBody(text.encode(charset), media_type, charset)
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
class _TextBodyFactory(SubscriptOnly):
|
|
567
|
+
"""``text_body[T](value)`` -- a text body whose *type* picks the wire text and whose *charset*
|
|
568
|
+
picks the bytes.
|
|
569
|
+
|
|
570
|
+
``text_body[Base64AsciiEncodedBytes](data)`` is what carries an operation whose whole body is a
|
|
571
|
+
base64 string: validation passes the raw bytes through, the dump encodes -- the encoding is the
|
|
572
|
+
declared type's own dump, not a call at the emission site. It is fully resident by construction,
|
|
573
|
+
and the peak is about **3.7x** the payload: the caller's ``bytes``, the base64 ``str`` at 1.33x,
|
|
574
|
+
and the factory's ``encode(charset)`` of that at 1.33x again. Base64 has no streaming path here
|
|
575
|
+
-- the parameter is ``bytes``, so a caller cannot hand over a stream to begin with. It *is*
|
|
576
|
+
chunkable at any multiple of three bytes should a spec ever need it; the arm is absent rather
|
|
577
|
+
than impossible (ADR-0051 decision 6). The two
|
|
578
|
+
keywords come from the operation's content key, split once at emission: the type and any other
|
|
579
|
+
parameters in ``media_type``, the ``charset`` parameter on its own -- and each is written only
|
|
580
|
+
where the spec differs from the default (``text/plain``, ``utf-8``), the rule ``param``'s
|
|
581
|
+
``serialization_format`` set."""
|
|
582
|
+
|
|
583
|
+
factory_name = "text_body"
|
|
584
|
+
spelling = "text_body[T](value), e.g. text_body[Base64AsciiEncodedBytes](data)"
|
|
585
|
+
|
|
586
|
+
def __getitem__(self, declared: TypeForm[T]) -> _DeclaredTextBody[T]:
|
|
587
|
+
return _DeclaredTextBody(adapter_for(validation_target(declared)))
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
text_body: Final = _TextBodyFactory()
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Wire-format converters a generated model names in its annotations.
|
|
2
|
+
|
|
3
|
+
A datetime/date field names one of the four ``Annotated`` aliases -- ``RFC3339DateTime``,
|
|
4
|
+
``RFC1123DateTime``, ``UnixSecondsDateTime``, ``Date``. A binary value declares two facts instead of
|
|
5
|
+
one, its RFC 4648 alphabet and the charset that renders it: a **model property** states both as
|
|
6
|
+
arguments to :func:`ByteField`, beside its wire key, while an **endpoint** names one of the twenty
|
|
7
|
+
``Base64AsciiEncodedBytes``-style aliases in a subscript, as every other parameter does. An open enum
|
|
8
|
+
field pairs its type with :func:`open_enum_validator`. The bare parse/dump pairs that build the
|
|
9
|
+
aliases stay internal to their modules."""
|
|
10
|
+
|
|
11
|
+
from .date_time import Date, RFC1123DateTime, RFC3339DateTime, UnixSecondsDateTime
|
|
12
|
+
from .encoded_bytes import (
|
|
13
|
+
Base16AsciiEncodedBytes,
|
|
14
|
+
Base16Cp1252EncodedBytes,
|
|
15
|
+
Base16Latin1EncodedBytes,
|
|
16
|
+
Base16Utf8EncodedBytes,
|
|
17
|
+
Base32AsciiEncodedBytes,
|
|
18
|
+
Base32Cp1252EncodedBytes,
|
|
19
|
+
Base32HexAsciiEncodedBytes,
|
|
20
|
+
Base32HexCp1252EncodedBytes,
|
|
21
|
+
Base32HexLatin1EncodedBytes,
|
|
22
|
+
Base32HexUtf8EncodedBytes,
|
|
23
|
+
Base32Latin1EncodedBytes,
|
|
24
|
+
Base32Utf8EncodedBytes,
|
|
25
|
+
Base64AsciiEncodedBytes,
|
|
26
|
+
Base64Cp1252EncodedBytes,
|
|
27
|
+
Base64Latin1EncodedBytes,
|
|
28
|
+
Base64UrlAsciiEncodedBytes,
|
|
29
|
+
Base64UrlCp1252EncodedBytes,
|
|
30
|
+
Base64UrlLatin1EncodedBytes,
|
|
31
|
+
Base64UrlUtf8EncodedBytes,
|
|
32
|
+
Base64Utf8EncodedBytes,
|
|
33
|
+
ByteField,
|
|
34
|
+
)
|
|
35
|
+
from .open_enum import open_enum_validator
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
# Wire-format datetime/date aliases
|
|
39
|
+
"Date",
|
|
40
|
+
"RFC3339DateTime",
|
|
41
|
+
"RFC1123DateTime",
|
|
42
|
+
"UnixSecondsDateTime",
|
|
43
|
+
# Wire-format binary aliases -- one per (RFC 4648 alphabet, charset) pair
|
|
44
|
+
"Base64AsciiEncodedBytes",
|
|
45
|
+
"Base64Utf8EncodedBytes",
|
|
46
|
+
"Base64Latin1EncodedBytes",
|
|
47
|
+
"Base64Cp1252EncodedBytes",
|
|
48
|
+
"Base64UrlAsciiEncodedBytes",
|
|
49
|
+
"Base64UrlUtf8EncodedBytes",
|
|
50
|
+
"Base64UrlLatin1EncodedBytes",
|
|
51
|
+
"Base64UrlCp1252EncodedBytes",
|
|
52
|
+
"Base32AsciiEncodedBytes",
|
|
53
|
+
"Base32Utf8EncodedBytes",
|
|
54
|
+
"Base32Latin1EncodedBytes",
|
|
55
|
+
"Base32Cp1252EncodedBytes",
|
|
56
|
+
"Base32HexAsciiEncodedBytes",
|
|
57
|
+
"Base32HexUtf8EncodedBytes",
|
|
58
|
+
"Base32HexLatin1EncodedBytes",
|
|
59
|
+
"Base32HexCp1252EncodedBytes",
|
|
60
|
+
"Base16AsciiEncodedBytes",
|
|
61
|
+
"Base16Utf8EncodedBytes",
|
|
62
|
+
"Base16Latin1EncodedBytes",
|
|
63
|
+
"Base16Cp1252EncodedBytes",
|
|
64
|
+
# Binary property declaration
|
|
65
|
+
"ByteField",
|
|
66
|
+
# Open enums
|
|
67
|
+
"open_enum_validator",
|
|
68
|
+
]
|