cliV1 0.0.8__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.8.dist-info/METADATA +168 -0
- cliv1-0.0.8.dist-info/RECORD +87 -0
- cliv1-0.0.8.dist-info/WHEEL +4 -0
- cliv1-0.0.8.dist-info/licenses/LICENSE +21 -0
- swagger_petstore_open_api_3_0/__init__.py +15 -0
- swagger_petstore_open_api_3_0/apis/__init__.py +5 -0
- swagger_petstore_open_api_3_0/apis/pet_api.py +883 -0
- swagger_petstore_open_api_3_0/apis/store.py +413 -0
- swagger_petstore_open_api_3_0/apis/user_api.py +794 -0
- swagger_petstore_open_api_3_0/async_client.py +79 -0
- swagger_petstore_open_api_3_0/auth.py +17 -0
- swagger_petstore_open_api_3_0/base_client.py +18 -0
- swagger_petstore_open_api_3_0/client.py +77 -0
- swagger_petstore_open_api_3_0/core/__init__.py +358 -0
- swagger_petstore_open_api_3_0/core/_internal/__init__.py +9 -0
- swagger_petstore_open_api_3_0/core/_internal/content_disposition.py +154 -0
- swagger_petstore_open_api_3_0/core/_internal/flattening.py +139 -0
- swagger_petstore_open_api_3_0/core/_internal/headers.py +144 -0
- swagger_petstore_open_api_3_0/core/_internal/sse_parser.py +124 -0
- swagger_petstore_open_api_3_0/core/_internal/subscripts.py +50 -0
- swagger_petstore_open_api_3_0/core/_internal/urls.py +186 -0
- swagger_petstore_open_api_3_0/core/_internal/wire.py +198 -0
- swagger_petstore_open_api_3_0/core/adapters.py +177 -0
- swagger_petstore_open_api_3_0/core/auth/__init__.py +70 -0
- swagger_petstore_open_api_3_0/core/auth/composition.py +175 -0
- swagger_petstore_open_api_3_0/core/auth/models.py +216 -0
- swagger_petstore_open_api_3_0/core/auth/protocols.py +103 -0
- swagger_petstore_open_api_3_0/core/auth/schemes/__init__.py +94 -0
- swagger_petstore_open_api_3_0/core/auth/schemes/api_key_auth.py +53 -0
- swagger_petstore_open_api_3_0/core/auth/schemes/basic_auth.py +73 -0
- swagger_petstore_open_api_3_0/core/auth/schemes/bearer_auth.py +21 -0
- swagger_petstore_open_api_3_0/core/auth/schemes/oauth2_authorization_code.py +393 -0
- swagger_petstore_open_api_3_0/core/auth/schemes/oauth2_client_credentials.py +127 -0
- swagger_petstore_open_api_3_0/core/auth/schemes/oauth2_password.py +142 -0
- swagger_petstore_open_api_3_0/core/auth/schemes/oauth2_schemes.py +300 -0
- swagger_petstore_open_api_3_0/core/base_raw_response.py +49 -0
- swagger_petstore_open_api_3_0/core/bodies.py +550 -0
- swagger_petstore_open_api_3_0/core/converters/__init__.py +68 -0
- swagger_petstore_open_api_3_0/core/converters/date_time.py +237 -0
- swagger_petstore_open_api_3_0/core/converters/encoded_bytes.py +273 -0
- swagger_petstore_open_api_3_0/core/converters/open_enum.py +35 -0
- swagger_petstore_open_api_3_0/core/decoding.py +498 -0
- swagger_petstore_open_api_3_0/core/discriminators.py +129 -0
- swagger_petstore_open_api_3_0/core/event_streams.py +225 -0
- swagger_petstore_open_api_3_0/core/exceptions.py +38 -0
- swagger_petstore_open_api_3_0/core/file_responses.py +335 -0
- swagger_petstore_open_api_3_0/core/files.py +173 -0
- swagger_petstore_open_api_3_0/core/httpx_transport.py +748 -0
- swagger_petstore_open_api_3_0/core/models.py +143 -0
- swagger_petstore_open_api_3_0/core/optionality.py +130 -0
- swagger_petstore_open_api_3_0/core/params.py +157 -0
- swagger_petstore_open_api_3_0/core/raw_client.py +338 -0
- swagger_petstore_open_api_3_0/core/request_options.py +94 -0
- swagger_petstore_open_api_3_0/core/results.py +123 -0
- swagger_petstore_open_api_3_0/core/runtime_env.py +43 -0
- swagger_petstore_open_api_3_0/core/servers.py +33 -0
- swagger_petstore_open_api_3_0/core/transport.py +191 -0
- swagger_petstore_open_api_3_0/errors/__init__.py +48 -0
- swagger_petstore_open_api_3_0/errors/add_pet_error.py +21 -0
- swagger_petstore_open_api_3_0/errors/delete_order_error.py +21 -0
- swagger_petstore_open_api_3_0/errors/delete_pet_error.py +21 -0
- swagger_petstore_open_api_3_0/errors/delete_user_error.py +21 -0
- swagger_petstore_open_api_3_0/errors/find_pets_by_status_error.py +21 -0
- swagger_petstore_open_api_3_0/errors/find_pets_by_tags_error.py +21 -0
- swagger_petstore_open_api_3_0/errors/get_order_by_id_error.py +21 -0
- swagger_petstore_open_api_3_0/errors/get_pet_by_id_error.py +21 -0
- swagger_petstore_open_api_3_0/errors/get_user_by_name_error.py +21 -0
- swagger_petstore_open_api_3_0/errors/login_user_error.py +21 -0
- swagger_petstore_open_api_3_0/errors/place_order_error.py +21 -0
- swagger_petstore_open_api_3_0/errors/update_pet_error.py +21 -0
- swagger_petstore_open_api_3_0/errors/update_pet_with_form_error.py +21 -0
- swagger_petstore_open_api_3_0/errors/update_user_error.py +21 -0
- swagger_petstore_open_api_3_0/errors/upload_file_error.py +21 -0
- swagger_petstore_open_api_3_0/models/__init__.py +23 -0
- swagger_petstore_open_api_3_0/models/api_response.py +18 -0
- swagger_petstore_open_api_3_0/models/category.py +15 -0
- swagger_petstore_open_api_3_0/models/enums/__init__.py +4 -0
- swagger_petstore_open_api_3_0/models/enums/order_status.py +17 -0
- swagger_petstore_open_api_3_0/models/enums/pet_status.py +17 -0
- swagger_petstore_open_api_3_0/models/order.py +27 -0
- swagger_petstore_open_api_3_0/models/pet.py +28 -0
- swagger_petstore_open_api_3_0/models/tag_model.py +15 -0
- swagger_petstore_open_api_3_0/models/user.py +29 -0
- swagger_petstore_open_api_3_0/py.typed +0 -0
- swagger_petstore_open_api_3_0/server/__init__.py +21 -0
- swagger_petstore_open_api_3_0/server/server.py +17 -0
- swagger_petstore_open_api_3_0/server/server_config.py +52 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""The base model every domain model subclasses."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Any, Literal
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, ConfigDict
|
|
9
|
+
|
|
10
|
+
from .optionality import strip_unset
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SdkBaseModel(BaseModel):
|
|
14
|
+
"""Base class for every domain model.
|
|
15
|
+
|
|
16
|
+
Immutable (``frozen``), **preserves** unknown JSON fields rather than dropping
|
|
17
|
+
them (``extra="allow"``), accepts either the Python field name or its wire alias
|
|
18
|
+
on input, and serializes declared fields back under the wire alias. Attribute
|
|
19
|
+
docstrings on fields are lifted into their schema ``description``.
|
|
20
|
+
|
|
21
|
+
A field the server adds after this SDK was generated survives on the instance --
|
|
22
|
+
readable via ``model_extra``, or by attribute access unless the key collides with
|
|
23
|
+
the model API -- and is re-emitted under the key it arrived with, at every nesting
|
|
24
|
+
level. Preservation is deliberately symmetric: an unknown key a caller supplies is
|
|
25
|
+
likewise sent, so a model received from the server can be echoed back without
|
|
26
|
+
silently discarding server-side state. See
|
|
27
|
+
``docs/designs/unknown-field-preservation.md``.
|
|
28
|
+
|
|
29
|
+
``frozen`` blocks rebinding an attribute; it does not deep-freeze values. The
|
|
30
|
+
contents of a ``list``/``dict`` field and the ``model_extra`` mapping remain
|
|
31
|
+
mutable in place.
|
|
32
|
+
|
|
33
|
+
:meth:`to_dict` and :meth:`to_json` are the ergonomic front door to serialization;
|
|
34
|
+
``model_dump``/``model_dump_json`` remain available for the full option set. See
|
|
35
|
+
``docs/designs/model-serialization-helpers.md``."""
|
|
36
|
+
|
|
37
|
+
model_config = ConfigDict(
|
|
38
|
+
frozen=True,
|
|
39
|
+
extra="allow",
|
|
40
|
+
validate_by_name=True,
|
|
41
|
+
validate_by_alias=True,
|
|
42
|
+
serialize_by_alias=True,
|
|
43
|
+
use_attribute_docstrings=True,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def to_dict(
|
|
47
|
+
self,
|
|
48
|
+
*,
|
|
49
|
+
mode: Literal["json", "python"] = "json",
|
|
50
|
+
by_alias: bool = True,
|
|
51
|
+
exclude_unset: bool = False,
|
|
52
|
+
exclude_defaults: bool = False,
|
|
53
|
+
exclude_none: bool = False,
|
|
54
|
+
) -> dict[str, Any]:
|
|
55
|
+
"""Return this model as a dictionary in the shape the API exchanges.
|
|
56
|
+
|
|
57
|
+
Defaults produce JSON-safe values under the wire alias of every field, so the
|
|
58
|
+
result is ``json.dumps``-able, equals ``json.loads(self.to_json())``, and
|
|
59
|
+
validates back into an equal model -- including through a discriminated-union
|
|
60
|
+
alias. Unknown fields the server sent are included (see
|
|
61
|
+
``docs/designs/unknown-field-preservation.md``).
|
|
62
|
+
|
|
63
|
+
This is a convenience wrapper over ``model_dump``; reach for that directly when
|
|
64
|
+
you need the options this deliberately does not surface (``include``/``exclude``,
|
|
65
|
+
``context``, ``round_trip``, ``warnings``, ``serialize_as_any``, ``fallback``).
|
|
66
|
+
|
|
67
|
+
A field typed ``OptionalNullable[...]`` (e.g. an omit-vs-null field) that the caller
|
|
68
|
+
never touched is always omitted here, even though plain ``model_dump`` shows it as
|
|
69
|
+
``null`` -- this is the one documented exception to "exactly the underlying dump".
|
|
70
|
+
See ``docs/designs/optional-nullable-fields.md``.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
mode: ``"json"`` coerces values to JSON-safe types (``datetime`` becomes a
|
|
74
|
+
string); ``"python"`` keeps the Python objects.
|
|
75
|
+
by_alias: Emit each field under its wire alias rather than its Python name.
|
|
76
|
+
The base config already serializes by alias, so this restates the
|
|
77
|
+
contract; pass ``False`` for Python-name keys.
|
|
78
|
+
exclude_unset: Omit fields that were never explicitly set, narrowing the
|
|
79
|
+
output to what the server actually sent. **Opt-in, not the default**:
|
|
80
|
+
a variant that pins its tag in its own schema carries it as a
|
|
81
|
+
*defaulted* field, so on a locally constructed model this drops the tag
|
|
82
|
+
and the result no longer validates against the union alias.
|
|
83
|
+
exclude_defaults: Omit fields still holding their default value.
|
|
84
|
+
exclude_none: Omit fields whose value is ``None``. The round-trip-safe way
|
|
85
|
+
to suppress nulls -- it keeps defaulted discriminators.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
The model's fields keyed by wire alias, plus any unknown fields it preserved."""
|
|
89
|
+
return strip_unset(
|
|
90
|
+
self,
|
|
91
|
+
self.model_dump(
|
|
92
|
+
mode=mode,
|
|
93
|
+
by_alias=by_alias,
|
|
94
|
+
exclude_unset=exclude_unset,
|
|
95
|
+
exclude_defaults=exclude_defaults,
|
|
96
|
+
exclude_none=exclude_none,
|
|
97
|
+
),
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def to_json(
|
|
101
|
+
self,
|
|
102
|
+
*,
|
|
103
|
+
indent: int | None = 2,
|
|
104
|
+
by_alias: bool = True,
|
|
105
|
+
exclude_unset: bool = False,
|
|
106
|
+
exclude_defaults: bool = False,
|
|
107
|
+
exclude_none: bool = False,
|
|
108
|
+
) -> str:
|
|
109
|
+
"""Return this model as a JSON string in the shape the API exchanges.
|
|
110
|
+
|
|
111
|
+
Indented by default for logs and debugging, and otherwise identical in content
|
|
112
|
+
to :meth:`to_dict` -- ``json.loads(self.to_json()) == self.to_dict()``.
|
|
113
|
+
|
|
114
|
+
This is a convenience wrapper over ``model_dump``/``model_dump_json``; reach for
|
|
115
|
+
those directly when you need the options this deliberately does not surface
|
|
116
|
+
(``include``/``exclude``, ``context``, ``round_trip``, ``warnings``,
|
|
117
|
+
``ensure_ascii``, ``serialize_as_any``, ``fallback``).
|
|
118
|
+
|
|
119
|
+
Built on :meth:`to_dict` (rather than delegating to ``model_dump_json`` directly)
|
|
120
|
+
so the pair can never drift apart -- including the ``OptionalNullable`` omission
|
|
121
|
+
documented there.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
indent: Spaces of indentation; ``None`` emits compact JSON.
|
|
125
|
+
by_alias: Emit each field under its wire alias rather than its Python name.
|
|
126
|
+
exclude_unset: Omit fields that were never explicitly set. Carries the same
|
|
127
|
+
discriminator caveat as :meth:`to_dict`.
|
|
128
|
+
exclude_defaults: Omit fields still holding their default value.
|
|
129
|
+
exclude_none: Omit fields whose value is ``None``.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
The JSON text the API exchanges, indented unless ``indent`` is ``None``."""
|
|
133
|
+
return json.dumps(
|
|
134
|
+
self.to_dict(
|
|
135
|
+
mode="json",
|
|
136
|
+
by_alias=by_alias,
|
|
137
|
+
exclude_unset=exclude_unset,
|
|
138
|
+
exclude_defaults=exclude_defaults,
|
|
139
|
+
exclude_none=exclude_none,
|
|
140
|
+
),
|
|
141
|
+
indent=indent,
|
|
142
|
+
ensure_ascii=False,
|
|
143
|
+
)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""The unset sentinel and the states it enables: omitted vs ``null`` vs a value.
|
|
2
|
+
|
|
3
|
+
:data:`UNSET` marks a field the caller never touched and :func:`strip_unset` is where "omitted" is
|
|
4
|
+
recovered at dump time, by dropping any key whose live value is still the sentinel. Two annotations
|
|
5
|
+
admit it, one per spelling a spec can declare: :data:`Optional` for a property that may be omitted,
|
|
6
|
+
:data:`OptionalNullable` for one that may be omitted *or* explicitly ``null``. See
|
|
7
|
+
``docs/designs/optional-nullable-fields.md``.
|
|
8
|
+
|
|
9
|
+
:data:`Optional` is deliberately **not** ``typing.Optional``. Here it means ``T | UnsetType`` --
|
|
10
|
+
present or absent, with no ``None`` arm -- where ``typing.Optional[T]`` means ``T | None``. They
|
|
11
|
+
differ by exactly the arm that matters: a property the spec does not declare nullable must not be
|
|
12
|
+
able to reach the wire as ``null``, so passing ``None`` to one of these fields is a type error
|
|
13
|
+
rather than a value that serializes. Nothing in a generated SDK imports ``typing.Optional`` -- unions
|
|
14
|
+
are spelled with PEP 604 ``|`` -- so the two never appear in the same module."""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from typing import Any, ClassVar, Final, Literal, TypeVar, cast, final
|
|
19
|
+
|
|
20
|
+
from pydantic import BaseModel, GetCoreSchemaHandler
|
|
21
|
+
from pydantic_core import core_schema
|
|
22
|
+
from typing_extensions import TypeAliasType
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@final
|
|
26
|
+
class UnsetType:
|
|
27
|
+
"""Marks a field the caller never touched, distinct from one set to ``None``.
|
|
28
|
+
|
|
29
|
+
Only ever seen as a field's default -- never constructed or passed by a caller. Dumps
|
|
30
|
+
as plain ``None`` through pydantic's normal fast path (see :func:`strip_unset` for
|
|
31
|
+
where the tri-state is actually recovered). See
|
|
32
|
+
``docs/designs/optional-nullable-fields.md``.
|
|
33
|
+
|
|
34
|
+
A singleton, and identity-stable through every channel that could duplicate it: ``UnsetType()``
|
|
35
|
+
returns the existing instance, ``copy`` and ``deepcopy`` return it unchanged, and unpickling
|
|
36
|
+
reduces back through the constructor. Without that, ``model_copy(deep=True)`` would deep-copy
|
|
37
|
+
the sentinel into a *second* object, and ``field is UNSET`` -- the natural way to test for
|
|
38
|
+
it -- would start returning ``False`` on the copy. :func:`strip_unset` uses ``isinstance`` and
|
|
39
|
+
so is immune either way, but callers should not have to know that. ``final`` is the same
|
|
40
|
+
guarantee stated statically: a subclass would inherit ``_instance`` already set, so its
|
|
41
|
+
constructor would hand back a ``UnsetType`` rather than an instance of itself."""
|
|
42
|
+
|
|
43
|
+
_instance: ClassVar[UnsetType | None] = None
|
|
44
|
+
|
|
45
|
+
def __new__(cls) -> UnsetType:
|
|
46
|
+
if cls._instance is None:
|
|
47
|
+
cls._instance = super().__new__(cls)
|
|
48
|
+
return cls._instance
|
|
49
|
+
|
|
50
|
+
def __bool__(self) -> Literal[False]:
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
def __repr__(self) -> str:
|
|
54
|
+
return "UNSET"
|
|
55
|
+
|
|
56
|
+
def __copy__(self) -> UnsetType:
|
|
57
|
+
return self
|
|
58
|
+
|
|
59
|
+
def __deepcopy__(self, memo: dict[int, Any]) -> UnsetType:
|
|
60
|
+
return self
|
|
61
|
+
|
|
62
|
+
def __reduce__(self) -> tuple[type[UnsetType], tuple[()]]:
|
|
63
|
+
# Protocols >= 2 reduce via ``cls.__new__(cls)`` and reach the guard above by luck of
|
|
64
|
+
# protocol; 0 and 1 reconstruct through ``copyreg._reconstructor``, which calls
|
|
65
|
+
# ``object.__new__`` and bypasses ``__new__`` entirely -- measured, ``restored is UNSET``
|
|
66
|
+
# was ``False`` at both. Reducing to a plain call makes every protocol uniform.
|
|
67
|
+
return (UnsetType, ())
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def __get_pydantic_core_schema__(cls, source_type: Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
|
|
71
|
+
return core_schema.is_instance_schema(
|
|
72
|
+
cls, serialization=core_schema.plain_serializer_function_ser_schema(lambda _: None)
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
UNSET: Final = UnsetType()
|
|
77
|
+
|
|
78
|
+
T = TypeVar("T")
|
|
79
|
+
Optional = TypeAliasType("Optional", T | UnsetType, type_params=(T,))
|
|
80
|
+
OptionalNullable = TypeAliasType("OptionalNullable", T | None | UnsetType, type_params=(T,))
|
|
81
|
+
|
|
82
|
+
DumpedT = TypeVar("DumpedT")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def strip_unset(value: object, dumped: DumpedT) -> DumpedT:
|
|
86
|
+
"""Recursively drop any dumped key whose live field value is :class:`UnsetType`.
|
|
87
|
+
|
|
88
|
+
Walks ``value`` (the validated model, list or dict, pre-dump) alongside ``dumped`` (its
|
|
89
|
+
``mode="json"`` dump) so a field that dumped as ``None`` only because it defaulted to
|
|
90
|
+
``UNSET`` is omitted, while one explicitly set to ``None`` stays ``null``.
|
|
91
|
+
|
|
92
|
+
The mapping branch pairs the two by *key* rather than by position, because ``mode="json"``
|
|
93
|
+
renders a non-``str`` key as text (``{1: "x"}`` dumps as ``{"1": "x"}``): an entry the dump
|
|
94
|
+
renamed keeps its dumped form instead of misaligning the walk. ``list`` and ``dict`` are the
|
|
95
|
+
whole container set on purpose -- an array and a map are what a spec can express, so they are
|
|
96
|
+
the only shapes an emitted declared type nests, and they are the same two ``adapters.py``
|
|
97
|
+
descends. A ``tuple`` or ``set`` passes through like any other value.
|
|
98
|
+
|
|
99
|
+
Shape-preserving: a dict in, a dict out; a list in, a list out. The three ``cast`` calls
|
|
100
|
+
are that invariant, which mypy cannot verify through the rebuild -- they keep the
|
|
101
|
+
imprecision here rather than returning ``Any`` to every caller.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
value: The validated model, list or dict, before dumping -- the side that still
|
|
105
|
+
holds the sentinel.
|
|
106
|
+
dumped: That same value's ``mode="json"`` dump, walked alongside it.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
``dumped`` with every never-touched key removed, in the shape it arrived in."""
|
|
110
|
+
if isinstance(value, BaseModel):
|
|
111
|
+
result = dict(cast(Any, dumped))
|
|
112
|
+
for name, field in type(value).model_fields.items():
|
|
113
|
+
# ``dumped`` may be keyed by wire alias (the default) or by Python name
|
|
114
|
+
# (``to_dict(by_alias=False)``); prefer whichever spelling is actually there.
|
|
115
|
+
key = field.alias if field.alias is not None and field.alias in result else name
|
|
116
|
+
if key not in result:
|
|
117
|
+
continue
|
|
118
|
+
attr = getattr(value, name)
|
|
119
|
+
if isinstance(attr, UnsetType):
|
|
120
|
+
del result[key]
|
|
121
|
+
else:
|
|
122
|
+
result[key] = strip_unset(attr, result[key])
|
|
123
|
+
return cast(DumpedT, result)
|
|
124
|
+
if isinstance(value, list) and isinstance(dumped, list):
|
|
125
|
+
stripped = [strip_unset(item, d) for item, d in zip(value, dumped, strict=True)]
|
|
126
|
+
return cast(DumpedT, stripped)
|
|
127
|
+
if isinstance(value, dict) and isinstance(dumped, dict):
|
|
128
|
+
stripped_values = {key: strip_unset(value[key], item) if key in value else item for key, item in dumped.items()}
|
|
129
|
+
return cast(DumpedT, stripped_values)
|
|
130
|
+
return dumped
|
|
@@ -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]] = ()
|