pyprig 1.2.0__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.
Potentially problematic release.
This version of pyprig might be problematic. Click here for more details.
- pyprig/__init__.py +464 -0
- pyprig/_internal/__init__.py +0 -0
- pyprig/_internal/_config.py +408 -0
- pyprig/_internal/_core_metadata.py +97 -0
- pyprig/_internal/_core_utils.py +156 -0
- pyprig/_internal/_dataclasses.py +327 -0
- pyprig/_internal/_decorators.py +906 -0
- pyprig/_internal/_decorators_v1.py +174 -0
- pyprig/_internal/_discriminated_union.py +510 -0
- pyprig/_internal/_docs_extraction.py +113 -0
- pyprig/_internal/_fields.py +883 -0
- pyprig/_internal/_forward_ref.py +23 -0
- pyprig/_internal/_generate_schema.py +2980 -0
- pyprig/_internal/_generics.py +553 -0
- pyprig/_internal/_git.py +27 -0
- pyprig/_internal/_import_utils.py +20 -0
- pyprig/_internal/_known_annotated_metadata.py +405 -0
- pyprig/_internal/_mock_val_ser.py +237 -0
- pyprig/_internal/_model_construction.py +833 -0
- pyprig/_internal/_namespace_utils.py +299 -0
- pyprig/_internal/_repr.py +122 -0
- pyprig/_internal/_schema_gather.py +49 -0
- pyprig/_internal/_schema_generation_shared.py +126 -0
- pyprig/_internal/_serializers.py +53 -0
- pyprig/_internal/_signature.py +193 -0
- pyprig/_internal/_type_refs.py +128 -0
- pyprig/_internal/_typing_extra.py +502 -0
- pyprig/_internal/_utils.py +433 -0
- pyprig/_internal/_validate_call.py +141 -0
- pyprig/_internal/_validators.py +541 -0
- pyprig/_migration.py +317 -0
- pyprig/alias_generators.py +62 -0
- pyprig/aliases.py +136 -0
- pyprig/annotated_handlers.py +123 -0
- pyprig/class_validators.py +5 -0
- pyprig/color.py +605 -0
- pyprig/config.py +1295 -0
- pyprig/dataclasses.py +385 -0
- pyprig/datetime_parse.py +5 -0
- pyprig/decorator.py +5 -0
- pyprig/deprecated/__init__.py +0 -0
- pyprig/deprecated/class_validators.py +257 -0
- pyprig/deprecated/config.py +72 -0
- pyprig/deprecated/copy_internals.py +224 -0
- pyprig/deprecated/decorator.py +286 -0
- pyprig/deprecated/json.py +142 -0
- pyprig/deprecated/parse.py +81 -0
- pyprig/deprecated/tools.py +104 -0
- pyprig/env_settings.py +5 -0
- pyprig/error_wrappers.py +5 -0
- pyprig/errors.py +185 -0
- pyprig/experimental/__init__.py +1 -0
- pyprig/experimental/arguments_schema.py +44 -0
- pyprig/experimental/missing_sentinel.py +29 -0
- pyprig/experimental/pipeline.py +684 -0
- pyprig/fields.py +2065 -0
- pyprig/functional_serializers.py +461 -0
- pyprig/functional_validators.py +920 -0
- pyprig/generics.py +5 -0
- pyprig/help.py +68 -0
- pyprig/json.py +5 -0
- pyprig/json_schema.py +3253 -0
- pyprig/main.py +1942 -0
- pyprig/mypy.py +1465 -0
- pyprig/networks.py +1417 -0
- pyprig/parse.py +5 -0
- pyprig/plugin/__init__.py +190 -0
- pyprig/plugin/_loader.py +58 -0
- pyprig/plugin/_schema_validator.py +152 -0
- pyprig/py.typed +0 -0
- pyprig/root_model.py +186 -0
- pyprig/schema.py +5 -0
- pyprig/tools.py +5 -0
- pyprig/type_adapter.py +776 -0
- pyprig/types.py +3405 -0
- pyprig/typing.py +5 -0
- pyprig/utils.py +5 -0
- pyprig/v1/__init__.py +131 -0
- pyprig/v1/_hypothesis_plugin.py +391 -0
- pyprig/v1/annotated_types.py +72 -0
- pyprig/v1/class_validators.py +361 -0
- pyprig/v1/color.py +494 -0
- pyprig/v1/config.py +191 -0
- pyprig/v1/dataclasses.py +500 -0
- pyprig/v1/datetime_parse.py +248 -0
- pyprig/v1/decorator.py +264 -0
- pyprig/v1/env_settings.py +350 -0
- pyprig/v1/error_wrappers.py +161 -0
- pyprig/v1/errors.py +646 -0
- pyprig/v1/fields.py +1253 -0
- pyprig/v1/generics.py +400 -0
- pyprig/v1/json.py +112 -0
- pyprig/v1/main.py +1130 -0
- pyprig/v1/mypy.py +949 -0
- pyprig/v1/networks.py +747 -0
- pyprig/v1/parse.py +66 -0
- pyprig/v1/py.typed +0 -0
- pyprig/v1/schema.py +1163 -0
- pyprig/v1/tools.py +92 -0
- pyprig/v1/types.py +1205 -0
- pyprig/v1/typing.py +627 -0
- pyprig/v1/utils.py +807 -0
- pyprig/v1/validators.py +768 -0
- pyprig/v1/version.py +38 -0
- pyprig/validate_call_decorator.py +121 -0
- pyprig/validators.py +5 -0
- pyprig/version.py +118 -0
- pyprig/warnings.py +130 -0
- pyprig-1.2.0.dist-info/METADATA +111 -0
- pyprig-1.2.0.dist-info/RECORD +112 -0
- pyprig-1.2.0.dist-info/WHEEL +4 -0
- pyprig-1.2.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
from __future__ import annotations as _annotations
|
|
2
|
+
|
|
3
|
+
import warnings
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from contextlib import contextmanager
|
|
6
|
+
from re import Pattern
|
|
7
|
+
from typing import (
|
|
8
|
+
TYPE_CHECKING,
|
|
9
|
+
Any,
|
|
10
|
+
Literal,
|
|
11
|
+
cast,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from pydantic_core import core_schema
|
|
15
|
+
from typing_extensions import Self
|
|
16
|
+
|
|
17
|
+
from ..aliases import AliasGenerator
|
|
18
|
+
from ..config import ConfigDict, ExtraValues, JsonDict, JsonEncoder, JsonSchemaExtraCallable
|
|
19
|
+
from ..errors import PydanticUserError
|
|
20
|
+
from ..warnings import PydanticDeprecatedSince20, PydanticDeprecatedSince210
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from .._internal._schema_generation_shared import GenerateSchema
|
|
24
|
+
from ..fields import ComputedFieldInfo, FieldInfo
|
|
25
|
+
|
|
26
|
+
DEPRECATION_MESSAGE = 'Support for class-based `config` is deprecated, use ConfigDict instead.'
|
|
27
|
+
|
|
28
|
+
# Maps the `ConfigDict` keys that have a `CoreConfig` counterpart to the corresponding
|
|
29
|
+
# `CoreConfig` key (`'title'` is omitted as it requires special handling, see `ConfigWrapper.core_config()`):
|
|
30
|
+
_config_dict_to_core_config_key: dict[str, str] = {
|
|
31
|
+
'extra': 'extra_fields_behavior',
|
|
32
|
+
'allow_inf_nan': 'allow_inf_nan',
|
|
33
|
+
'str_strip_whitespace': 'str_strip_whitespace',
|
|
34
|
+
'str_to_lower': 'str_to_lower',
|
|
35
|
+
'str_to_upper': 'str_to_upper',
|
|
36
|
+
'strict': 'strict',
|
|
37
|
+
'ser_json_timedelta': 'ser_json_timedelta',
|
|
38
|
+
'ser_json_temporal': 'ser_json_temporal',
|
|
39
|
+
'val_temporal_unit': 'val_temporal_unit',
|
|
40
|
+
'ser_json_bytes': 'ser_json_bytes',
|
|
41
|
+
'val_json_bytes': 'val_json_bytes',
|
|
42
|
+
'ser_json_inf_nan': 'ser_json_inf_nan',
|
|
43
|
+
'from_attributes': 'from_attributes',
|
|
44
|
+
'loc_by_alias': 'loc_by_alias',
|
|
45
|
+
'revalidate_instances': 'revalidate_instances',
|
|
46
|
+
'validate_default': 'validate_default',
|
|
47
|
+
'str_max_length': 'str_max_length',
|
|
48
|
+
'str_min_length': 'str_min_length',
|
|
49
|
+
'hide_input_in_errors': 'hide_input_in_errors',
|
|
50
|
+
'coerce_numbers_to_str': 'coerce_numbers_to_str',
|
|
51
|
+
'regex_engine': 'regex_engine',
|
|
52
|
+
'validation_error_cause': 'validation_error_cause',
|
|
53
|
+
'cache_strings': 'cache_strings',
|
|
54
|
+
'validate_by_alias': 'validate_by_alias',
|
|
55
|
+
'validate_by_name': 'validate_by_name',
|
|
56
|
+
'serialize_by_alias': 'serialize_by_alias',
|
|
57
|
+
'url_preserve_empty_path': 'url_preserve_empty_path',
|
|
58
|
+
'polymorphic_serialization': 'polymorphic_serialization',
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ConfigWrapper:
|
|
63
|
+
"""Internal wrapper for Config which exposes ConfigDict items as attributes."""
|
|
64
|
+
|
|
65
|
+
__slots__ = ('config_dict', 'effective_config')
|
|
66
|
+
|
|
67
|
+
config_dict: ConfigDict
|
|
68
|
+
"""The configuration as declared by the user.
|
|
69
|
+
|
|
70
|
+
This is the mapping exposed as the `model_config` attribute of models (and the
|
|
71
|
+
`__pydantic_config__` attribute of dataclasses), and the one merged with the
|
|
72
|
+
configuration of the bases when subclassing. As such, it must never be mutated.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
effective_config: ConfigDict
|
|
76
|
+
"""The configuration actually in effect, with the deprecated settings applied.
|
|
77
|
+
|
|
78
|
+
This is what every consumer of the configuration should read (both the attribute
|
|
79
|
+
access on this wrapper and `core_config()` do), as `config_dict` alone does not
|
|
80
|
+
account for the deprecated settings (see `_build_effective_config()`).
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
# all annotations are copied directly from ConfigDict, and should be kept up to date, a test will fail if they
|
|
84
|
+
# stop matching
|
|
85
|
+
title: str | None
|
|
86
|
+
str_to_lower: bool
|
|
87
|
+
str_to_upper: bool
|
|
88
|
+
str_strip_whitespace: bool
|
|
89
|
+
str_min_length: int
|
|
90
|
+
str_max_length: int | None
|
|
91
|
+
extra: ExtraValues | None
|
|
92
|
+
frozen: bool
|
|
93
|
+
populate_by_name: bool
|
|
94
|
+
use_enum_values: bool
|
|
95
|
+
validate_assignment: bool
|
|
96
|
+
arbitrary_types_allowed: bool
|
|
97
|
+
from_attributes: bool
|
|
98
|
+
# whether to use the actual key provided in the data (e.g. alias or first alias for "field required" errors) instead of field_names
|
|
99
|
+
# to construct error `loc`s, default `True`
|
|
100
|
+
loc_by_alias: bool
|
|
101
|
+
alias_generator: Callable[[str], str] | AliasGenerator | None
|
|
102
|
+
model_title_generator: Callable[[type], str] | None
|
|
103
|
+
field_title_generator: Callable[[str, FieldInfo | ComputedFieldInfo], str] | None
|
|
104
|
+
ignored_types: tuple[type, ...]
|
|
105
|
+
allow_inf_nan: bool
|
|
106
|
+
json_schema_extra: JsonDict | JsonSchemaExtraCallable | None
|
|
107
|
+
json_encoders: dict[type[object], JsonEncoder] | None
|
|
108
|
+
|
|
109
|
+
# new in V2
|
|
110
|
+
strict: bool
|
|
111
|
+
# whether instances of models and dataclasses (including subclass instances) should re-validate, default 'never'
|
|
112
|
+
revalidate_instances: Literal['always', 'never', 'subclass-instances']
|
|
113
|
+
ser_json_timedelta: Literal['iso8601', 'float']
|
|
114
|
+
ser_json_temporal: Literal['iso8601', 'seconds', 'milliseconds']
|
|
115
|
+
val_temporal_unit: Literal['seconds', 'milliseconds', 'infer']
|
|
116
|
+
ser_json_bytes: Literal['utf8', 'base64', 'hex']
|
|
117
|
+
val_json_bytes: Literal['utf8', 'base64', 'hex']
|
|
118
|
+
ser_json_inf_nan: Literal['null', 'constants', 'strings']
|
|
119
|
+
# whether to validate default values during validation, default False
|
|
120
|
+
validate_default: bool
|
|
121
|
+
validate_return: bool
|
|
122
|
+
protected_namespaces: tuple[str | Pattern[str], ...]
|
|
123
|
+
hide_input_in_errors: bool
|
|
124
|
+
defer_build: bool
|
|
125
|
+
plugin_settings: dict[str, object] | None
|
|
126
|
+
schema_generator: type[GenerateSchema] | None
|
|
127
|
+
json_schema_serialization_defaults_required: bool
|
|
128
|
+
json_schema_mode_override: Literal['validation', 'serialization', None]
|
|
129
|
+
coerce_numbers_to_str: bool
|
|
130
|
+
regex_engine: Literal['rust-regex', 'python-re']
|
|
131
|
+
validation_error_cause: bool
|
|
132
|
+
use_attribute_docstrings: bool
|
|
133
|
+
cache_strings: bool | Literal['all', 'keys', 'none']
|
|
134
|
+
validate_by_alias: bool
|
|
135
|
+
validate_by_name: bool
|
|
136
|
+
serialize_by_alias: bool
|
|
137
|
+
url_preserve_empty_path: bool
|
|
138
|
+
polymorphic_serialization: bool
|
|
139
|
+
|
|
140
|
+
def __init__(self, config: ConfigDict | dict[str, Any] | type[Any] | None, *, check: bool = True):
|
|
141
|
+
if check:
|
|
142
|
+
self.config_dict = prepare_config(config)
|
|
143
|
+
else:
|
|
144
|
+
self.config_dict = cast(ConfigDict, config)
|
|
145
|
+
self.effective_config = _build_effective_config(self.config_dict)
|
|
146
|
+
|
|
147
|
+
@classmethod
|
|
148
|
+
def for_model(
|
|
149
|
+
cls,
|
|
150
|
+
bases: tuple[type[Any], ...],
|
|
151
|
+
namespace: dict[str, Any],
|
|
152
|
+
kwargs: dict[str, Any],
|
|
153
|
+
) -> Self:
|
|
154
|
+
"""Build a new `ConfigWrapper` instance for a `BaseModel`.
|
|
155
|
+
|
|
156
|
+
The config wrapper built based on (in descending order of priority):
|
|
157
|
+
- options from `kwargs`
|
|
158
|
+
- options from the `namespace`
|
|
159
|
+
- options from the base classes (`bases`)
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
bases: A tuple of base classes.
|
|
163
|
+
namespace: The namespace of the class being created.
|
|
164
|
+
kwargs: The kwargs passed to the class being created.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
A `ConfigWrapper` instance for `BaseModel`.
|
|
168
|
+
"""
|
|
169
|
+
config_new = ConfigDict()
|
|
170
|
+
for base in bases:
|
|
171
|
+
config = getattr(base, 'model_config', None)
|
|
172
|
+
if config:
|
|
173
|
+
config_new.update(config.copy())
|
|
174
|
+
|
|
175
|
+
config_class_from_namespace = namespace.get('Config')
|
|
176
|
+
config_dict_from_namespace = namespace.get('model_config')
|
|
177
|
+
|
|
178
|
+
if config_class_from_namespace and config_dict_from_namespace:
|
|
179
|
+
raise PydanticUserError('"Config" and "model_config" cannot be used together', code='config-both')
|
|
180
|
+
|
|
181
|
+
config_from_namespace = config_dict_from_namespace or prepare_config(config_class_from_namespace)
|
|
182
|
+
|
|
183
|
+
config_new.update(config_from_namespace)
|
|
184
|
+
|
|
185
|
+
for k in list(kwargs.keys()):
|
|
186
|
+
if k in config_keys:
|
|
187
|
+
config_new[k] = kwargs.pop(k)
|
|
188
|
+
|
|
189
|
+
return cls(config_new)
|
|
190
|
+
|
|
191
|
+
# we don't show `__getattr__` to type checkers so missing attributes cause errors
|
|
192
|
+
if not TYPE_CHECKING: # pragma: no branch
|
|
193
|
+
|
|
194
|
+
def __getattr__(self, name: str) -> Any:
|
|
195
|
+
try:
|
|
196
|
+
return self.effective_config[name]
|
|
197
|
+
except KeyError:
|
|
198
|
+
try:
|
|
199
|
+
return config_defaults[name]
|
|
200
|
+
except KeyError:
|
|
201
|
+
raise AttributeError(f'Config has no attribute {name!r}') from None
|
|
202
|
+
|
|
203
|
+
def core_config(self, title: str | None) -> core_schema.CoreConfig:
|
|
204
|
+
"""Create a pydantic-core config.
|
|
205
|
+
|
|
206
|
+
We don't use getattr here since we don't want to populate with defaults.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
title: The title to use if not set in config.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
A `CoreConfig` object created from config.
|
|
213
|
+
"""
|
|
214
|
+
config = self.effective_config
|
|
215
|
+
|
|
216
|
+
if not config:
|
|
217
|
+
# Fast path for the common case of an empty (default) config:
|
|
218
|
+
return core_schema.CoreConfig(title=title) if title else core_schema.CoreConfig()
|
|
219
|
+
|
|
220
|
+
core_config_values: dict[str, Any] = {}
|
|
221
|
+
for k, v in config.items():
|
|
222
|
+
if v is not None and (core_config_key := _config_dict_to_core_config_key.get(k)) is not None:
|
|
223
|
+
core_config_values[core_config_key] = v
|
|
224
|
+
title = config.get('title') or title
|
|
225
|
+
if title:
|
|
226
|
+
core_config_values['title'] = title
|
|
227
|
+
|
|
228
|
+
return cast(core_schema.CoreConfig, core_config_values)
|
|
229
|
+
|
|
230
|
+
def __repr__(self):
|
|
231
|
+
c = ', '.join(f'{k}={v!r}' for k, v in self.config_dict.items())
|
|
232
|
+
return f'ConfigWrapper({c})'
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class ConfigWrapperStack:
|
|
236
|
+
"""A stack of `ConfigWrapper` instances."""
|
|
237
|
+
|
|
238
|
+
def __init__(self, config_wrapper: ConfigWrapper):
|
|
239
|
+
self._config_wrapper_stack: list[ConfigWrapper] = [config_wrapper]
|
|
240
|
+
|
|
241
|
+
@property
|
|
242
|
+
def tail(self) -> ConfigWrapper:
|
|
243
|
+
return self._config_wrapper_stack[-1]
|
|
244
|
+
|
|
245
|
+
@contextmanager
|
|
246
|
+
def push(self, config_wrapper: ConfigWrapper | ConfigDict | None):
|
|
247
|
+
if config_wrapper is None:
|
|
248
|
+
yield
|
|
249
|
+
return
|
|
250
|
+
|
|
251
|
+
if not isinstance(config_wrapper, ConfigWrapper):
|
|
252
|
+
config_wrapper = ConfigWrapper(config_wrapper, check=False)
|
|
253
|
+
|
|
254
|
+
self._config_wrapper_stack.append(config_wrapper)
|
|
255
|
+
try:
|
|
256
|
+
yield
|
|
257
|
+
finally:
|
|
258
|
+
self._config_wrapper_stack.pop()
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
config_defaults = ConfigDict(
|
|
262
|
+
title=None,
|
|
263
|
+
str_to_lower=False,
|
|
264
|
+
str_to_upper=False,
|
|
265
|
+
str_strip_whitespace=False,
|
|
266
|
+
str_min_length=0,
|
|
267
|
+
str_max_length=None,
|
|
268
|
+
# let the model / dataclass decide how to handle it
|
|
269
|
+
extra=None,
|
|
270
|
+
frozen=False,
|
|
271
|
+
populate_by_name=False,
|
|
272
|
+
use_enum_values=False,
|
|
273
|
+
validate_assignment=False,
|
|
274
|
+
arbitrary_types_allowed=False,
|
|
275
|
+
from_attributes=False,
|
|
276
|
+
loc_by_alias=True,
|
|
277
|
+
alias_generator=None,
|
|
278
|
+
model_title_generator=None,
|
|
279
|
+
field_title_generator=None,
|
|
280
|
+
ignored_types=(),
|
|
281
|
+
allow_inf_nan=True,
|
|
282
|
+
json_schema_extra=None,
|
|
283
|
+
strict=False,
|
|
284
|
+
revalidate_instances='never',
|
|
285
|
+
ser_json_timedelta='iso8601',
|
|
286
|
+
ser_json_temporal='iso8601',
|
|
287
|
+
val_temporal_unit='infer',
|
|
288
|
+
ser_json_bytes='utf8',
|
|
289
|
+
val_json_bytes='utf8',
|
|
290
|
+
ser_json_inf_nan='null',
|
|
291
|
+
validate_default=False,
|
|
292
|
+
validate_return=False,
|
|
293
|
+
protected_namespaces=('model_validate', 'model_dump'),
|
|
294
|
+
hide_input_in_errors=False,
|
|
295
|
+
json_encoders=None,
|
|
296
|
+
defer_build=False,
|
|
297
|
+
schema_generator=None,
|
|
298
|
+
plugin_settings=None,
|
|
299
|
+
json_schema_serialization_defaults_required=False,
|
|
300
|
+
json_schema_mode_override=None,
|
|
301
|
+
coerce_numbers_to_str=False,
|
|
302
|
+
regex_engine='rust-regex',
|
|
303
|
+
validation_error_cause=False,
|
|
304
|
+
use_attribute_docstrings=False,
|
|
305
|
+
cache_strings=True,
|
|
306
|
+
validate_by_alias=True,
|
|
307
|
+
validate_by_name=False,
|
|
308
|
+
serialize_by_alias=False,
|
|
309
|
+
url_preserve_empty_path=False,
|
|
310
|
+
polymorphic_serialization=False,
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _build_effective_config(config: ConfigDict) -> ConfigDict:
|
|
315
|
+
"""Build the configuration in effect from the configuration as declared.
|
|
316
|
+
|
|
317
|
+
Raises:
|
|
318
|
+
PydanticUserError: If validation can happen by neither alias nor name.
|
|
319
|
+
"""
|
|
320
|
+
if config.get('validate_by_name') is None:
|
|
321
|
+
if (populate_by_name := config.get('populate_by_name')) is not None:
|
|
322
|
+
config = cast(ConfigDict, {**config, 'validate_by_alias': True, 'validate_by_name': populate_by_name})
|
|
323
|
+
elif config.get('validate_by_alias') is False:
|
|
324
|
+
config = cast(ConfigDict, {**config, 'validate_by_name': True})
|
|
325
|
+
|
|
326
|
+
if (not config.get('validate_by_alias', True)) and (not config.get('validate_by_name', False)):
|
|
327
|
+
raise PydanticUserError(
|
|
328
|
+
'At least one of `validate_by_alias` or `validate_by_name` must be set to True.',
|
|
329
|
+
code='validate-by-alias-and-name-false',
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
return config
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def prepare_config(config: ConfigDict | dict[str, Any] | type[Any] | None) -> ConfigDict:
|
|
336
|
+
"""Create a `ConfigDict` instance from an existing dict, a class (e.g. old class-based config) or None.
|
|
337
|
+
|
|
338
|
+
Args:
|
|
339
|
+
config: The input config.
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
A ConfigDict object created from config.
|
|
343
|
+
"""
|
|
344
|
+
if config is None:
|
|
345
|
+
return ConfigDict()
|
|
346
|
+
|
|
347
|
+
if not isinstance(config, dict):
|
|
348
|
+
warnings.warn(DEPRECATION_MESSAGE, PydanticDeprecatedSince20, stacklevel=4)
|
|
349
|
+
config = {k: getattr(config, k) for k in dir(config) if not k.startswith('__')}
|
|
350
|
+
else:
|
|
351
|
+
# Copy, so that the `ConfigWrapper` owns its configuration and can never
|
|
352
|
+
# mutate the mapping provided by the user:
|
|
353
|
+
config = config.copy()
|
|
354
|
+
|
|
355
|
+
config_dict = cast(ConfigDict, config)
|
|
356
|
+
check_deprecated(config_dict)
|
|
357
|
+
return config_dict
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
config_keys = set(ConfigDict.__annotations__.keys())
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
V2_REMOVED_KEYS = {
|
|
364
|
+
'allow_mutation',
|
|
365
|
+
'error_msg_templates',
|
|
366
|
+
'fields',
|
|
367
|
+
'getter_dict',
|
|
368
|
+
'smart_union',
|
|
369
|
+
'underscore_attrs_are_private',
|
|
370
|
+
'json_loads',
|
|
371
|
+
'json_dumps',
|
|
372
|
+
'copy_on_model_validation',
|
|
373
|
+
'post_init_call',
|
|
374
|
+
}
|
|
375
|
+
V2_RENAMED_KEYS = {
|
|
376
|
+
'allow_population_by_field_name': 'validate_by_name',
|
|
377
|
+
'anystr_lower': 'str_to_lower',
|
|
378
|
+
'anystr_strip_whitespace': 'str_strip_whitespace',
|
|
379
|
+
'anystr_upper': 'str_to_upper',
|
|
380
|
+
'keep_untouched': 'ignored_types',
|
|
381
|
+
'max_anystr_length': 'str_max_length',
|
|
382
|
+
'min_anystr_length': 'str_min_length',
|
|
383
|
+
'orm_mode': 'from_attributes',
|
|
384
|
+
'schema_extra': 'json_schema_extra',
|
|
385
|
+
'validate_all': 'validate_default',
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def check_deprecated(config_dict: ConfigDict) -> None:
|
|
390
|
+
"""Check for deprecated config keys and warn the user.
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
config_dict: The input config.
|
|
394
|
+
"""
|
|
395
|
+
if config_dict.get('schema_generator') is not None:
|
|
396
|
+
warnings.warn(
|
|
397
|
+
'The `schema_generator` setting has been deprecated since v2.10. This setting no longer has any effect.',
|
|
398
|
+
PydanticDeprecatedSince210,
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
deprecated_removed_keys = V2_REMOVED_KEYS & config_dict.keys()
|
|
402
|
+
deprecated_renamed_keys = V2_RENAMED_KEYS.keys() & config_dict.keys()
|
|
403
|
+
if deprecated_removed_keys or deprecated_renamed_keys:
|
|
404
|
+
renamings = {k: V2_RENAMED_KEYS[k] for k in sorted(deprecated_renamed_keys)}
|
|
405
|
+
renamed_bullets = [f'* {k!r} has been renamed to {v!r}' for k, v in renamings.items()]
|
|
406
|
+
removed_bullets = [f'* {k!r} has been removed' for k in sorted(deprecated_removed_keys)]
|
|
407
|
+
message = '\n'.join(['Valid config keys have changed in V2:'] + renamed_bullets + removed_bullets)
|
|
408
|
+
warnings.warn(message, UserWarning)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from __future__ import annotations as _annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, TypedDict, cast
|
|
4
|
+
from warnings import warn
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from ..config import JsonDict, JsonSchemaExtraCallable
|
|
8
|
+
from ._schema_generation_shared import (
|
|
9
|
+
GetJsonSchemaFunction,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CoreMetadata(TypedDict, total=False):
|
|
14
|
+
"""A `TypedDict` for holding the metadata dict of the schema.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
pydantic_js_functions: List of JSON schema functions that resolve refs during application.
|
|
18
|
+
pydantic_js_annotation_functions: List of JSON schema functions that don't resolve refs during application.
|
|
19
|
+
pydantic_js_prefer_positional_arguments: Whether JSON schema generator will
|
|
20
|
+
prefer positional over keyword arguments for an 'arguments' schema.
|
|
21
|
+
custom validation function. Only applies to before, plain, and wrap validators.
|
|
22
|
+
pydantic_js_updates: key / value pair updates to apply to the JSON schema for a type.
|
|
23
|
+
pydantic_js_extra: WIP, either key/value pair updates to apply to the JSON schema, or a custom callable.
|
|
24
|
+
pydantic_internal_union_tag_key: Used internally by the `Tag` metadata to specify the tag used for a discriminated union.
|
|
25
|
+
pydantic_internal_union_discriminator: Used internally to specify the discriminator value for a discriminated union
|
|
26
|
+
when the discriminator was applied to a `'definition-ref'` schema, and that reference was missing at the time
|
|
27
|
+
of the annotation application.
|
|
28
|
+
|
|
29
|
+
TODO: Perhaps we should move this structure to pydantic-core. At the moment, though,
|
|
30
|
+
it's easier to iterate on if we leave it in pydantic until we feel there is a semi-stable API.
|
|
31
|
+
|
|
32
|
+
TODO: It's unfortunate how functionally oriented JSON schema generation is, especially that which occurs during
|
|
33
|
+
the core schema generation process. It's inevitable that we need to store some json schema related information
|
|
34
|
+
on core schemas, given that we generate JSON schemas directly from core schemas. That being said, debugging related
|
|
35
|
+
issues is quite difficult when JSON schema information is disguised via dynamically defined functions.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
pydantic_js_functions: list[GetJsonSchemaFunction]
|
|
39
|
+
pydantic_js_annotation_functions: list[GetJsonSchemaFunction]
|
|
40
|
+
pydantic_js_prefer_positional_arguments: bool
|
|
41
|
+
pydantic_js_updates: JsonDict
|
|
42
|
+
pydantic_js_extra: JsonDict | JsonSchemaExtraCallable
|
|
43
|
+
pydantic_internal_union_tag_key: str
|
|
44
|
+
pydantic_internal_union_discriminator: str
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def update_core_metadata(
|
|
48
|
+
core_metadata: Any,
|
|
49
|
+
/,
|
|
50
|
+
*,
|
|
51
|
+
pydantic_js_functions: list[GetJsonSchemaFunction] | None = None,
|
|
52
|
+
pydantic_js_annotation_functions: list[GetJsonSchemaFunction] | None = None,
|
|
53
|
+
pydantic_js_updates: JsonDict | None = None,
|
|
54
|
+
pydantic_js_extra: JsonDict | JsonSchemaExtraCallable | None = None,
|
|
55
|
+
) -> None:
|
|
56
|
+
from ..json_schema import PydanticJsonSchemaWarning
|
|
57
|
+
|
|
58
|
+
"""Update CoreMetadata instance in place. When we make modifications in this function, they
|
|
59
|
+
take effect on the `core_metadata` reference passed in as the first (and only) positional argument.
|
|
60
|
+
|
|
61
|
+
First, cast to `CoreMetadata`, then finish with a cast to `dict[str, Any]` for core schema compatibility.
|
|
62
|
+
We do this here, instead of before / after each call to this function so that this typing hack
|
|
63
|
+
can be easily removed if/when we move `CoreMetadata` to `pydantic-core`.
|
|
64
|
+
|
|
65
|
+
For parameter descriptions, see `CoreMetadata` above.
|
|
66
|
+
"""
|
|
67
|
+
core_metadata = cast(CoreMetadata, core_metadata)
|
|
68
|
+
|
|
69
|
+
if pydantic_js_functions:
|
|
70
|
+
core_metadata.setdefault('pydantic_js_functions', []).extend(pydantic_js_functions)
|
|
71
|
+
|
|
72
|
+
if pydantic_js_annotation_functions:
|
|
73
|
+
core_metadata.setdefault('pydantic_js_annotation_functions', []).extend(pydantic_js_annotation_functions)
|
|
74
|
+
|
|
75
|
+
if pydantic_js_updates:
|
|
76
|
+
if (existing_updates := core_metadata.get('pydantic_js_updates')) is not None:
|
|
77
|
+
core_metadata['pydantic_js_updates'] = {**existing_updates, **pydantic_js_updates}
|
|
78
|
+
else:
|
|
79
|
+
core_metadata['pydantic_js_updates'] = pydantic_js_updates
|
|
80
|
+
|
|
81
|
+
if pydantic_js_extra is not None:
|
|
82
|
+
existing_pydantic_js_extra = core_metadata.get('pydantic_js_extra')
|
|
83
|
+
if existing_pydantic_js_extra is None:
|
|
84
|
+
core_metadata['pydantic_js_extra'] = pydantic_js_extra
|
|
85
|
+
if isinstance(existing_pydantic_js_extra, dict):
|
|
86
|
+
if isinstance(pydantic_js_extra, dict):
|
|
87
|
+
core_metadata['pydantic_js_extra'] = {**existing_pydantic_js_extra, **pydantic_js_extra}
|
|
88
|
+
if callable(pydantic_js_extra):
|
|
89
|
+
warn(
|
|
90
|
+
'Composing `dict` and `callable` type `json_schema_extra` is not supported.'
|
|
91
|
+
'The `callable` type is being ignored.'
|
|
92
|
+
"If you'd like support for this behavior, please open an issue on pydantic.",
|
|
93
|
+
PydanticJsonSchemaWarning,
|
|
94
|
+
)
|
|
95
|
+
if callable(existing_pydantic_js_extra):
|
|
96
|
+
# if ever there's a case of a callable, we'll just keep the last json schema extra spec
|
|
97
|
+
core_metadata['pydantic_js_extra'] = pydantic_js_extra
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
from collections.abc import Mapping, Sequence
|
|
5
|
+
from typing import TYPE_CHECKING, Any, TypeAlias, TypeGuard
|
|
6
|
+
|
|
7
|
+
from pydantic_core import CoreSchema, core_schema
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
|
|
12
|
+
AnyFunctionSchema: TypeAlias = (
|
|
13
|
+
core_schema.AfterValidatorFunctionSchema
|
|
14
|
+
| core_schema.BeforeValidatorFunctionSchema
|
|
15
|
+
| core_schema.WrapValidatorFunctionSchema
|
|
16
|
+
| core_schema.PlainValidatorFunctionSchema
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
FunctionSchemaWithInnerSchema: TypeAlias = (
|
|
21
|
+
core_schema.AfterValidatorFunctionSchema
|
|
22
|
+
| core_schema.BeforeValidatorFunctionSchema
|
|
23
|
+
| core_schema.WrapValidatorFunctionSchema
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
CoreSchemaField: TypeAlias = (
|
|
28
|
+
core_schema.ModelField | core_schema.DataclassField | core_schema.TypedDictField | core_schema.ComputedField
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
CoreSchemaOrField: TypeAlias = core_schema.CoreSchema | CoreSchemaField
|
|
32
|
+
|
|
33
|
+
_CORE_SCHEMA_FIELD_TYPES = {'typed-dict-field', 'dataclass-field', 'model-field', 'computed-field'}
|
|
34
|
+
_FUNCTION_WITH_INNER_SCHEMA_TYPES = {'function-before', 'function-after', 'function-wrap'}
|
|
35
|
+
_LIST_LIKE_SCHEMA_WITH_ITEMS_TYPES = {'list', 'deque', 'set', 'frozenset'}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def is_core_schema(
|
|
39
|
+
schema: CoreSchemaOrField,
|
|
40
|
+
) -> TypeGuard[CoreSchema]:
|
|
41
|
+
return schema['type'] not in _CORE_SCHEMA_FIELD_TYPES
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def is_core_schema_field(
|
|
45
|
+
schema: CoreSchemaOrField,
|
|
46
|
+
) -> TypeGuard[CoreSchemaField]:
|
|
47
|
+
return schema['type'] in _CORE_SCHEMA_FIELD_TYPES
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def is_function_with_inner_schema(
|
|
51
|
+
schema: CoreSchemaOrField,
|
|
52
|
+
) -> TypeGuard[FunctionSchemaWithInnerSchema]:
|
|
53
|
+
return schema['type'] in _FUNCTION_WITH_INNER_SCHEMA_TYPES
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def as_ser_schema(schema: CoreSchema) -> core_schema.SerSchema:
|
|
57
|
+
"""Return a schema suitable for the `'serialization'` key of a core schema, from an arbitrary core schema.
|
|
58
|
+
|
|
59
|
+
Any core schema can be used as a serialization schema, except `'function-plain'` and `'function-wrap'`
|
|
60
|
+
schemas, as these types are already used by the plain and wrap serializer *function* schemas.
|
|
61
|
+
For these, mimic what pydantic-core would do if they were used as the *main* schema:
|
|
62
|
+
|
|
63
|
+
- if the function schema has a `'serialization'` schema, use it.
|
|
64
|
+
- otherwise, a `'function-plain'` schema is serialized as `'any'`, and a `'function-wrap'` schema
|
|
65
|
+
is serialized using its inner schema.
|
|
66
|
+
|
|
67
|
+
Note that `schema` is expected to be a *core* schema (e.g. as returned by the schema generator),
|
|
68
|
+
not an existing `'serialization'` schema.
|
|
69
|
+
"""
|
|
70
|
+
if schema['type'] == 'function-plain' or schema['type'] == 'function-wrap':
|
|
71
|
+
if (ser_schema := schema.get('serialization')) is not None:
|
|
72
|
+
return ser_schema
|
|
73
|
+
if schema['type'] == 'function-plain':
|
|
74
|
+
return core_schema.any_schema()
|
|
75
|
+
return as_ser_schema(schema['schema'])
|
|
76
|
+
return schema
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def is_list_like_schema_with_items_schema(
|
|
80
|
+
schema: CoreSchema,
|
|
81
|
+
) -> TypeGuard[core_schema.ListSchema | core_schema.SetSchema | core_schema.FrozenSetSchema]:
|
|
82
|
+
return schema['type'] in _LIST_LIKE_SCHEMA_WITH_ITEMS_TYPES
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_ref(s: core_schema.CoreSchema) -> None | str:
|
|
86
|
+
"""Get the ref from the schema if it has one.
|
|
87
|
+
This exists just for type checking to work correctly.
|
|
88
|
+
"""
|
|
89
|
+
return s.get('ref', None)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _clean_schema_for_pretty_print(obj: Any, strip_metadata: bool = True) -> Any: # pragma: no cover
|
|
93
|
+
"""A utility function to remove irrelevant information from a core schema."""
|
|
94
|
+
if isinstance(obj, Mapping):
|
|
95
|
+
new_dct = {}
|
|
96
|
+
for k, v in obj.items():
|
|
97
|
+
if k == 'metadata' and strip_metadata:
|
|
98
|
+
new_metadata = {}
|
|
99
|
+
|
|
100
|
+
for meta_k, meta_v in v.items():
|
|
101
|
+
if meta_k in ('pydantic_js_functions', 'pydantic_js_annotation_functions'):
|
|
102
|
+
new_metadata['js_metadata'] = '<stripped>'
|
|
103
|
+
else:
|
|
104
|
+
new_metadata[meta_k] = _clean_schema_for_pretty_print(meta_v, strip_metadata=strip_metadata)
|
|
105
|
+
|
|
106
|
+
if list(new_metadata.keys()) == ['js_metadata']:
|
|
107
|
+
new_metadata = {'<stripped>'}
|
|
108
|
+
|
|
109
|
+
new_dct[k] = new_metadata
|
|
110
|
+
# Remove some defaults:
|
|
111
|
+
elif k in ('custom_init', 'root_model') and not v:
|
|
112
|
+
continue
|
|
113
|
+
else:
|
|
114
|
+
new_dct[k] = _clean_schema_for_pretty_print(v, strip_metadata=strip_metadata)
|
|
115
|
+
|
|
116
|
+
return new_dct
|
|
117
|
+
elif isinstance(obj, Sequence) and not isinstance(obj, str):
|
|
118
|
+
return [_clean_schema_for_pretty_print(v, strip_metadata=strip_metadata) for v in obj]
|
|
119
|
+
else:
|
|
120
|
+
return obj
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def pretty_print_core_schema(
|
|
124
|
+
val: Any,
|
|
125
|
+
*,
|
|
126
|
+
console: Console | None = None,
|
|
127
|
+
max_depth: int | None = None,
|
|
128
|
+
strip_metadata: bool = True,
|
|
129
|
+
) -> None: # pragma: no cover
|
|
130
|
+
"""Pretty-print a core schema using the `rich` library.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
val: The core schema to print, or a Pydantic model/dataclass/type adapter
|
|
134
|
+
(in which case the cached core schema is fetched and printed).
|
|
135
|
+
console: A rich console to use when printing. Defaults to the global rich console instance.
|
|
136
|
+
max_depth: The number of nesting levels which may be printed.
|
|
137
|
+
strip_metadata: Whether to strip metadata in the output. If `True` any known core metadata
|
|
138
|
+
attributes will be stripped (but custom attributes are kept). Defaults to `True`.
|
|
139
|
+
"""
|
|
140
|
+
# lazy import:
|
|
141
|
+
from rich.pretty import pprint
|
|
142
|
+
|
|
143
|
+
# circ. imports:
|
|
144
|
+
from pyprig import BaseModel, TypeAdapter
|
|
145
|
+
from pyprig.dataclasses import is_pydantic_dataclass
|
|
146
|
+
|
|
147
|
+
if (inspect.isclass(val) and issubclass(val, BaseModel)) or is_pydantic_dataclass(val):
|
|
148
|
+
val = val.__pydantic_core_schema__
|
|
149
|
+
if isinstance(val, TypeAdapter):
|
|
150
|
+
val = val.core_schema
|
|
151
|
+
cleaned_schema = _clean_schema_for_pretty_print(val, strip_metadata=strip_metadata)
|
|
152
|
+
|
|
153
|
+
pprint(cleaned_schema, console=console, max_depth=max_depth)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
pps = pretty_print_core_schema
|