Загрузить файлы в «venv/Lib/site-packages/pydantic/_internal»
This commit is contained in:
BIN
venv/Lib/site-packages/pydantic/_internal/__init__.py
Normal file
BIN
venv/Lib/site-packages/pydantic/_internal/__init__.py
Normal file
Binary file not shown.
386
venv/Lib/site-packages/pydantic/_internal/_config.py
Normal file
386
venv/Lib/site-packages/pydantic/_internal/_config.py
Normal file
@@ -0,0 +1,386 @@
|
|||||||
|
from __future__ import annotations as _annotations
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from re import Pattern
|
||||||
|
from typing import (
|
||||||
|
TYPE_CHECKING,
|
||||||
|
Any,
|
||||||
|
Callable,
|
||||||
|
Literal,
|
||||||
|
cast,
|
||||||
|
)
|
||||||
|
|
||||||
|
from pydantic_core import core_schema
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
from ..aliases import AliasGenerator
|
||||||
|
from ..config import ConfigDict, ExtraValues, JsonDict, JsonEncoder, JsonSchemaExtraCallable
|
||||||
|
from ..errors import PydanticUserError
|
||||||
|
from ..warnings import PydanticDeprecatedSince20, PydanticDeprecatedSince210
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .._internal._schema_generation_shared import GenerateSchema
|
||||||
|
from ..fields import ComputedFieldInfo, FieldInfo
|
||||||
|
|
||||||
|
DEPRECATION_MESSAGE = 'Support for class-based `config` is deprecated, use ConfigDict instead.'
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigWrapper:
|
||||||
|
"""Internal wrapper for Config which exposes ConfigDict items as attributes."""
|
||||||
|
|
||||||
|
__slots__ = ('config_dict',)
|
||||||
|
|
||||||
|
config_dict: ConfigDict
|
||||||
|
|
||||||
|
# all annotations are copied directly from ConfigDict, and should be kept up to date, a test will fail if they
|
||||||
|
# stop matching
|
||||||
|
title: str | None
|
||||||
|
str_to_lower: bool
|
||||||
|
str_to_upper: bool
|
||||||
|
str_strip_whitespace: bool
|
||||||
|
str_min_length: int
|
||||||
|
str_max_length: int | None
|
||||||
|
extra: ExtraValues | None
|
||||||
|
frozen: bool
|
||||||
|
populate_by_name: bool
|
||||||
|
use_enum_values: bool
|
||||||
|
validate_assignment: bool
|
||||||
|
arbitrary_types_allowed: bool
|
||||||
|
from_attributes: bool
|
||||||
|
# whether to use the actual key provided in the data (e.g. alias or first alias for "field required" errors) instead of field_names
|
||||||
|
# to construct error `loc`s, default `True`
|
||||||
|
loc_by_alias: bool
|
||||||
|
alias_generator: Callable[[str], str] | AliasGenerator | None
|
||||||
|
model_title_generator: Callable[[type], str] | None
|
||||||
|
field_title_generator: Callable[[str, FieldInfo | ComputedFieldInfo], str] | None
|
||||||
|
ignored_types: tuple[type, ...]
|
||||||
|
allow_inf_nan: bool
|
||||||
|
json_schema_extra: JsonDict | JsonSchemaExtraCallable | None
|
||||||
|
json_encoders: dict[type[object], JsonEncoder] | None
|
||||||
|
|
||||||
|
# new in V2
|
||||||
|
strict: bool
|
||||||
|
# whether instances of models and dataclasses (including subclass instances) should re-validate, default 'never'
|
||||||
|
revalidate_instances: Literal['always', 'never', 'subclass-instances']
|
||||||
|
ser_json_timedelta: Literal['iso8601', 'float']
|
||||||
|
ser_json_temporal: Literal['iso8601', 'seconds', 'milliseconds']
|
||||||
|
val_temporal_unit: Literal['seconds', 'milliseconds', 'infer']
|
||||||
|
ser_json_bytes: Literal['utf8', 'base64', 'hex']
|
||||||
|
val_json_bytes: Literal['utf8', 'base64', 'hex']
|
||||||
|
ser_json_inf_nan: Literal['null', 'constants', 'strings']
|
||||||
|
# whether to validate default values during validation, default False
|
||||||
|
validate_default: bool
|
||||||
|
validate_return: bool
|
||||||
|
protected_namespaces: tuple[str | Pattern[str], ...]
|
||||||
|
hide_input_in_errors: bool
|
||||||
|
defer_build: bool
|
||||||
|
plugin_settings: dict[str, object] | None
|
||||||
|
schema_generator: type[GenerateSchema] | None
|
||||||
|
json_schema_serialization_defaults_required: bool
|
||||||
|
json_schema_mode_override: Literal['validation', 'serialization', None]
|
||||||
|
coerce_numbers_to_str: bool
|
||||||
|
regex_engine: Literal['rust-regex', 'python-re']
|
||||||
|
validation_error_cause: bool
|
||||||
|
use_attribute_docstrings: bool
|
||||||
|
cache_strings: bool | Literal['all', 'keys', 'none']
|
||||||
|
validate_by_alias: bool
|
||||||
|
validate_by_name: bool
|
||||||
|
serialize_by_alias: bool
|
||||||
|
url_preserve_empty_path: bool
|
||||||
|
polymorphic_serialization: bool
|
||||||
|
|
||||||
|
def __init__(self, config: ConfigDict | dict[str, Any] | type[Any] | None, *, check: bool = True):
|
||||||
|
if check:
|
||||||
|
self.config_dict = prepare_config(config)
|
||||||
|
else:
|
||||||
|
self.config_dict = cast(ConfigDict, config)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def for_model(
|
||||||
|
cls,
|
||||||
|
bases: tuple[type[Any], ...],
|
||||||
|
namespace: dict[str, Any],
|
||||||
|
raw_annotations: dict[str, Any],
|
||||||
|
kwargs: dict[str, Any],
|
||||||
|
) -> Self:
|
||||||
|
"""Build a new `ConfigWrapper` instance for a `BaseModel`.
|
||||||
|
|
||||||
|
The config wrapper built based on (in descending order of priority):
|
||||||
|
- options from `kwargs`
|
||||||
|
- options from the `namespace`
|
||||||
|
- options from the base classes (`bases`)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bases: A tuple of base classes.
|
||||||
|
namespace: The namespace of the class being created.
|
||||||
|
raw_annotations: The (non-evaluated) annotations of the model.
|
||||||
|
kwargs: The kwargs passed to the class being created.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A `ConfigWrapper` instance for `BaseModel`.
|
||||||
|
"""
|
||||||
|
config_new = ConfigDict()
|
||||||
|
for base in bases:
|
||||||
|
config = getattr(base, 'model_config', None)
|
||||||
|
if config:
|
||||||
|
config_new.update(config.copy())
|
||||||
|
|
||||||
|
config_class_from_namespace = namespace.get('Config')
|
||||||
|
config_dict_from_namespace = namespace.get('model_config')
|
||||||
|
|
||||||
|
if raw_annotations.get('model_config') and config_dict_from_namespace is None:
|
||||||
|
raise PydanticUserError(
|
||||||
|
'`model_config` cannot be used as a model field name. Use `model_config` for model configuration.',
|
||||||
|
code='model-config-invalid-field-name',
|
||||||
|
)
|
||||||
|
|
||||||
|
if config_class_from_namespace and config_dict_from_namespace:
|
||||||
|
raise PydanticUserError('"Config" and "model_config" cannot be used together', code='config-both')
|
||||||
|
|
||||||
|
config_from_namespace = config_dict_from_namespace or prepare_config(config_class_from_namespace)
|
||||||
|
|
||||||
|
config_new.update(config_from_namespace)
|
||||||
|
|
||||||
|
for k in list(kwargs.keys()):
|
||||||
|
if k in config_keys:
|
||||||
|
config_new[k] = kwargs.pop(k)
|
||||||
|
|
||||||
|
return cls(config_new)
|
||||||
|
|
||||||
|
# we don't show `__getattr__` to type checkers so missing attributes cause errors
|
||||||
|
if not TYPE_CHECKING: # pragma: no branch
|
||||||
|
|
||||||
|
def __getattr__(self, name: str) -> Any:
|
||||||
|
try:
|
||||||
|
return self.config_dict[name]
|
||||||
|
except KeyError:
|
||||||
|
try:
|
||||||
|
return config_defaults[name]
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError(f'Config has no attribute {name!r}') from None
|
||||||
|
|
||||||
|
def core_config(self, title: str | None) -> core_schema.CoreConfig:
|
||||||
|
"""Create a pydantic-core config.
|
||||||
|
|
||||||
|
We don't use getattr here since we don't want to populate with defaults.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
title: The title to use if not set in config.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A `CoreConfig` object created from config.
|
||||||
|
"""
|
||||||
|
config = self.config_dict
|
||||||
|
|
||||||
|
if config.get('schema_generator') is not None:
|
||||||
|
warnings.warn(
|
||||||
|
'The `schema_generator` setting has been deprecated since v2.10. This setting no longer has any effect.',
|
||||||
|
PydanticDeprecatedSince210,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (populate_by_name := config.get('populate_by_name')) is not None:
|
||||||
|
# We include this patch for backwards compatibility purposes, but this config setting will be deprecated in v3.0, and likely removed in v4.0.
|
||||||
|
# Thus, the above warning and this patch can be removed then as well.
|
||||||
|
if config.get('validate_by_name') is None:
|
||||||
|
config['validate_by_alias'] = True
|
||||||
|
config['validate_by_name'] = populate_by_name
|
||||||
|
|
||||||
|
# We dynamically patch validate_by_name to be True if validate_by_alias is set to False
|
||||||
|
# and validate_by_name is not explicitly set.
|
||||||
|
if config.get('validate_by_alias') is False and config.get('validate_by_name') is None:
|
||||||
|
config['validate_by_name'] = True
|
||||||
|
|
||||||
|
if (not config.get('validate_by_alias', True)) and (not config.get('validate_by_name', False)):
|
||||||
|
raise PydanticUserError(
|
||||||
|
'At least one of `validate_by_alias` or `validate_by_name` must be set to True.',
|
||||||
|
code='validate-by-alias-and-name-false',
|
||||||
|
)
|
||||||
|
|
||||||
|
return core_schema.CoreConfig(
|
||||||
|
**{ # pyright: ignore[reportArgumentType]
|
||||||
|
k: v
|
||||||
|
for k, v in (
|
||||||
|
('title', config.get('title') or title or None),
|
||||||
|
('extra_fields_behavior', config.get('extra')),
|
||||||
|
('allow_inf_nan', config.get('allow_inf_nan')),
|
||||||
|
('str_strip_whitespace', config.get('str_strip_whitespace')),
|
||||||
|
('str_to_lower', config.get('str_to_lower')),
|
||||||
|
('str_to_upper', config.get('str_to_upper')),
|
||||||
|
('strict', config.get('strict')),
|
||||||
|
('ser_json_timedelta', config.get('ser_json_timedelta')),
|
||||||
|
('ser_json_temporal', config.get('ser_json_temporal')),
|
||||||
|
('val_temporal_unit', config.get('val_temporal_unit')),
|
||||||
|
('ser_json_bytes', config.get('ser_json_bytes')),
|
||||||
|
('val_json_bytes', config.get('val_json_bytes')),
|
||||||
|
('ser_json_inf_nan', config.get('ser_json_inf_nan')),
|
||||||
|
('from_attributes', config.get('from_attributes')),
|
||||||
|
('loc_by_alias', config.get('loc_by_alias')),
|
||||||
|
('revalidate_instances', config.get('revalidate_instances')),
|
||||||
|
('validate_default', config.get('validate_default')),
|
||||||
|
('str_max_length', config.get('str_max_length')),
|
||||||
|
('str_min_length', config.get('str_min_length')),
|
||||||
|
('hide_input_in_errors', config.get('hide_input_in_errors')),
|
||||||
|
('coerce_numbers_to_str', config.get('coerce_numbers_to_str')),
|
||||||
|
('regex_engine', config.get('regex_engine')),
|
||||||
|
('validation_error_cause', config.get('validation_error_cause')),
|
||||||
|
('cache_strings', config.get('cache_strings')),
|
||||||
|
('validate_by_alias', config.get('validate_by_alias')),
|
||||||
|
('validate_by_name', config.get('validate_by_name')),
|
||||||
|
('serialize_by_alias', config.get('serialize_by_alias')),
|
||||||
|
('url_preserve_empty_path', config.get('url_preserve_empty_path')),
|
||||||
|
('polymorphic_serialization', config.get('polymorphic_serialization')),
|
||||||
|
)
|
||||||
|
if v is not None
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
c = ', '.join(f'{k}={v!r}' for k, v in self.config_dict.items())
|
||||||
|
return f'ConfigWrapper({c})'
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigWrapperStack:
|
||||||
|
"""A stack of `ConfigWrapper` instances."""
|
||||||
|
|
||||||
|
def __init__(self, config_wrapper: ConfigWrapper):
|
||||||
|
self._config_wrapper_stack: list[ConfigWrapper] = [config_wrapper]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tail(self) -> ConfigWrapper:
|
||||||
|
return self._config_wrapper_stack[-1]
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def push(self, config_wrapper: ConfigWrapper | ConfigDict | None):
|
||||||
|
if config_wrapper is None:
|
||||||
|
yield
|
||||||
|
return
|
||||||
|
|
||||||
|
if not isinstance(config_wrapper, ConfigWrapper):
|
||||||
|
config_wrapper = ConfigWrapper(config_wrapper, check=False)
|
||||||
|
|
||||||
|
self._config_wrapper_stack.append(config_wrapper)
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
self._config_wrapper_stack.pop()
|
||||||
|
|
||||||
|
|
||||||
|
config_defaults = ConfigDict(
|
||||||
|
title=None,
|
||||||
|
str_to_lower=False,
|
||||||
|
str_to_upper=False,
|
||||||
|
str_strip_whitespace=False,
|
||||||
|
str_min_length=0,
|
||||||
|
str_max_length=None,
|
||||||
|
# let the model / dataclass decide how to handle it
|
||||||
|
extra=None,
|
||||||
|
frozen=False,
|
||||||
|
populate_by_name=False,
|
||||||
|
use_enum_values=False,
|
||||||
|
validate_assignment=False,
|
||||||
|
arbitrary_types_allowed=False,
|
||||||
|
from_attributes=False,
|
||||||
|
loc_by_alias=True,
|
||||||
|
alias_generator=None,
|
||||||
|
model_title_generator=None,
|
||||||
|
field_title_generator=None,
|
||||||
|
ignored_types=(),
|
||||||
|
allow_inf_nan=True,
|
||||||
|
json_schema_extra=None,
|
||||||
|
strict=False,
|
||||||
|
revalidate_instances='never',
|
||||||
|
ser_json_timedelta='iso8601',
|
||||||
|
ser_json_temporal='iso8601',
|
||||||
|
val_temporal_unit='infer',
|
||||||
|
ser_json_bytes='utf8',
|
||||||
|
val_json_bytes='utf8',
|
||||||
|
ser_json_inf_nan='null',
|
||||||
|
validate_default=False,
|
||||||
|
validate_return=False,
|
||||||
|
protected_namespaces=('model_validate', 'model_dump'),
|
||||||
|
hide_input_in_errors=False,
|
||||||
|
json_encoders=None,
|
||||||
|
defer_build=False,
|
||||||
|
schema_generator=None,
|
||||||
|
plugin_settings=None,
|
||||||
|
json_schema_serialization_defaults_required=False,
|
||||||
|
json_schema_mode_override=None,
|
||||||
|
coerce_numbers_to_str=False,
|
||||||
|
regex_engine='rust-regex',
|
||||||
|
validation_error_cause=False,
|
||||||
|
use_attribute_docstrings=False,
|
||||||
|
cache_strings=True,
|
||||||
|
validate_by_alias=True,
|
||||||
|
validate_by_name=False,
|
||||||
|
serialize_by_alias=False,
|
||||||
|
url_preserve_empty_path=False,
|
||||||
|
polymorphic_serialization=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_config(config: ConfigDict | dict[str, Any] | type[Any] | None) -> ConfigDict:
|
||||||
|
"""Create a `ConfigDict` instance from an existing dict, a class (e.g. old class-based config) or None.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config: The input config.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A ConfigDict object created from config.
|
||||||
|
"""
|
||||||
|
if config is None:
|
||||||
|
return ConfigDict()
|
||||||
|
|
||||||
|
if not isinstance(config, dict):
|
||||||
|
warnings.warn(DEPRECATION_MESSAGE, PydanticDeprecatedSince20, stacklevel=4)
|
||||||
|
config = {k: getattr(config, k) for k in dir(config) if not k.startswith('__')}
|
||||||
|
|
||||||
|
config_dict = cast(ConfigDict, config)
|
||||||
|
check_deprecated(config_dict)
|
||||||
|
return config_dict
|
||||||
|
|
||||||
|
|
||||||
|
config_keys = set(ConfigDict.__annotations__.keys())
|
||||||
|
|
||||||
|
|
||||||
|
V2_REMOVED_KEYS = {
|
||||||
|
'allow_mutation',
|
||||||
|
'error_msg_templates',
|
||||||
|
'fields',
|
||||||
|
'getter_dict',
|
||||||
|
'smart_union',
|
||||||
|
'underscore_attrs_are_private',
|
||||||
|
'json_loads',
|
||||||
|
'json_dumps',
|
||||||
|
'copy_on_model_validation',
|
||||||
|
'post_init_call',
|
||||||
|
}
|
||||||
|
V2_RENAMED_KEYS = {
|
||||||
|
'allow_population_by_field_name': 'validate_by_name',
|
||||||
|
'anystr_lower': 'str_to_lower',
|
||||||
|
'anystr_strip_whitespace': 'str_strip_whitespace',
|
||||||
|
'anystr_upper': 'str_to_upper',
|
||||||
|
'keep_untouched': 'ignored_types',
|
||||||
|
'max_anystr_length': 'str_max_length',
|
||||||
|
'min_anystr_length': 'str_min_length',
|
||||||
|
'orm_mode': 'from_attributes',
|
||||||
|
'schema_extra': 'json_schema_extra',
|
||||||
|
'validate_all': 'validate_default',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def check_deprecated(config_dict: ConfigDict) -> None:
|
||||||
|
"""Check for deprecated config keys and warn the user.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config_dict: The input config.
|
||||||
|
"""
|
||||||
|
deprecated_removed_keys = V2_REMOVED_KEYS & config_dict.keys()
|
||||||
|
deprecated_renamed_keys = V2_RENAMED_KEYS.keys() & config_dict.keys()
|
||||||
|
if deprecated_removed_keys or deprecated_renamed_keys:
|
||||||
|
renamings = {k: V2_RENAMED_KEYS[k] for k in sorted(deprecated_renamed_keys)}
|
||||||
|
renamed_bullets = [f'* {k!r} has been renamed to {v!r}' for k, v in renamings.items()]
|
||||||
|
removed_bullets = [f'* {k!r} has been removed' for k in sorted(deprecated_removed_keys)]
|
||||||
|
message = '\n'.join(['Valid config keys have changed in V2:'] + renamed_bullets + removed_bullets)
|
||||||
|
warnings.warn(message, UserWarning)
|
||||||
97
venv/Lib/site-packages/pydantic/_internal/_core_metadata.py
Normal file
97
venv/Lib/site-packages/pydantic/_internal/_core_metadata.py
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
from __future__ import annotations as _annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, Any, TypedDict, cast
|
||||||
|
from warnings import warn
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..config import JsonDict, JsonSchemaExtraCallable
|
||||||
|
from ._schema_generation_shared import (
|
||||||
|
GetJsonSchemaFunction,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CoreMetadata(TypedDict, total=False):
|
||||||
|
"""A `TypedDict` for holding the metadata dict of the schema.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
pydantic_js_functions: List of JSON schema functions that resolve refs during application.
|
||||||
|
pydantic_js_annotation_functions: List of JSON schema functions that don't resolve refs during application.
|
||||||
|
pydantic_js_prefer_positional_arguments: Whether JSON schema generator will
|
||||||
|
prefer positional over keyword arguments for an 'arguments' schema.
|
||||||
|
custom validation function. Only applies to before, plain, and wrap validators.
|
||||||
|
pydantic_js_updates: key / value pair updates to apply to the JSON schema for a type.
|
||||||
|
pydantic_js_extra: WIP, either key/value pair updates to apply to the JSON schema, or a custom callable.
|
||||||
|
pydantic_internal_union_tag_key: Used internally by the `Tag` metadata to specify the tag used for a discriminated union.
|
||||||
|
pydantic_internal_union_discriminator: Used internally to specify the discriminator value for a discriminated union
|
||||||
|
when the discriminator was applied to a `'definition-ref'` schema, and that reference was missing at the time
|
||||||
|
of the annotation application.
|
||||||
|
|
||||||
|
TODO: Perhaps we should move this structure to pydantic-core. At the moment, though,
|
||||||
|
it's easier to iterate on if we leave it in pydantic until we feel there is a semi-stable API.
|
||||||
|
|
||||||
|
TODO: It's unfortunate how functionally oriented JSON schema generation is, especially that which occurs during
|
||||||
|
the core schema generation process. It's inevitable that we need to store some json schema related information
|
||||||
|
on core schemas, given that we generate JSON schemas directly from core schemas. That being said, debugging related
|
||||||
|
issues is quite difficult when JSON schema information is disguised via dynamically defined functions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pydantic_js_functions: list[GetJsonSchemaFunction]
|
||||||
|
pydantic_js_annotation_functions: list[GetJsonSchemaFunction]
|
||||||
|
pydantic_js_prefer_positional_arguments: bool
|
||||||
|
pydantic_js_updates: JsonDict
|
||||||
|
pydantic_js_extra: JsonDict | JsonSchemaExtraCallable
|
||||||
|
pydantic_internal_union_tag_key: str
|
||||||
|
pydantic_internal_union_discriminator: str
|
||||||
|
|
||||||
|
|
||||||
|
def update_core_metadata(
|
||||||
|
core_metadata: Any,
|
||||||
|
/,
|
||||||
|
*,
|
||||||
|
pydantic_js_functions: list[GetJsonSchemaFunction] | None = None,
|
||||||
|
pydantic_js_annotation_functions: list[GetJsonSchemaFunction] | None = None,
|
||||||
|
pydantic_js_updates: JsonDict | None = None,
|
||||||
|
pydantic_js_extra: JsonDict | JsonSchemaExtraCallable | None = None,
|
||||||
|
) -> None:
|
||||||
|
from ..json_schema import PydanticJsonSchemaWarning
|
||||||
|
|
||||||
|
"""Update CoreMetadata instance in place. When we make modifications in this function, they
|
||||||
|
take effect on the `core_metadata` reference passed in as the first (and only) positional argument.
|
||||||
|
|
||||||
|
First, cast to `CoreMetadata`, then finish with a cast to `dict[str, Any]` for core schema compatibility.
|
||||||
|
We do this here, instead of before / after each call to this function so that this typing hack
|
||||||
|
can be easily removed if/when we move `CoreMetadata` to `pydantic-core`.
|
||||||
|
|
||||||
|
For parameter descriptions, see `CoreMetadata` above.
|
||||||
|
"""
|
||||||
|
core_metadata = cast(CoreMetadata, core_metadata)
|
||||||
|
|
||||||
|
if pydantic_js_functions:
|
||||||
|
core_metadata.setdefault('pydantic_js_functions', []).extend(pydantic_js_functions)
|
||||||
|
|
||||||
|
if pydantic_js_annotation_functions:
|
||||||
|
core_metadata.setdefault('pydantic_js_annotation_functions', []).extend(pydantic_js_annotation_functions)
|
||||||
|
|
||||||
|
if pydantic_js_updates:
|
||||||
|
if (existing_updates := core_metadata.get('pydantic_js_updates')) is not None:
|
||||||
|
core_metadata['pydantic_js_updates'] = {**existing_updates, **pydantic_js_updates}
|
||||||
|
else:
|
||||||
|
core_metadata['pydantic_js_updates'] = pydantic_js_updates
|
||||||
|
|
||||||
|
if pydantic_js_extra is not None:
|
||||||
|
existing_pydantic_js_extra = core_metadata.get('pydantic_js_extra')
|
||||||
|
if existing_pydantic_js_extra is None:
|
||||||
|
core_metadata['pydantic_js_extra'] = pydantic_js_extra
|
||||||
|
if isinstance(existing_pydantic_js_extra, dict):
|
||||||
|
if isinstance(pydantic_js_extra, dict):
|
||||||
|
core_metadata['pydantic_js_extra'] = {**existing_pydantic_js_extra, **pydantic_js_extra}
|
||||||
|
if callable(pydantic_js_extra):
|
||||||
|
warn(
|
||||||
|
'Composing `dict` and `callable` type `json_schema_extra` is not supported.'
|
||||||
|
'The `callable` type is being ignored.'
|
||||||
|
"If you'd like support for this behavior, please open an issue on pydantic.",
|
||||||
|
PydanticJsonSchemaWarning,
|
||||||
|
)
|
||||||
|
if callable(existing_pydantic_js_extra):
|
||||||
|
# if ever there's a case of a callable, we'll just keep the last json schema extra spec
|
||||||
|
core_metadata['pydantic_js_extra'] = pydantic_js_extra
|
||||||
174
venv/Lib/site-packages/pydantic/_internal/_core_utils.py
Normal file
174
venv/Lib/site-packages/pydantic/_internal/_core_utils.py
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import inspect
|
||||||
|
from collections.abc import Mapping, Sequence
|
||||||
|
from typing import TYPE_CHECKING, Any, Union
|
||||||
|
|
||||||
|
from pydantic_core import CoreSchema, core_schema
|
||||||
|
from typing_extensions import TypeGuard, get_args, get_origin
|
||||||
|
from typing_inspection import typing_objects
|
||||||
|
|
||||||
|
from . import _repr
|
||||||
|
from ._typing_extra import is_generic_alias
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from rich.console import Console
|
||||||
|
|
||||||
|
AnyFunctionSchema = Union[
|
||||||
|
core_schema.AfterValidatorFunctionSchema,
|
||||||
|
core_schema.BeforeValidatorFunctionSchema,
|
||||||
|
core_schema.WrapValidatorFunctionSchema,
|
||||||
|
core_schema.PlainValidatorFunctionSchema,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
FunctionSchemaWithInnerSchema = Union[
|
||||||
|
core_schema.AfterValidatorFunctionSchema,
|
||||||
|
core_schema.BeforeValidatorFunctionSchema,
|
||||||
|
core_schema.WrapValidatorFunctionSchema,
|
||||||
|
]
|
||||||
|
|
||||||
|
CoreSchemaField = Union[
|
||||||
|
core_schema.ModelField, core_schema.DataclassField, core_schema.TypedDictField, core_schema.ComputedField
|
||||||
|
]
|
||||||
|
CoreSchemaOrField = Union[core_schema.CoreSchema, CoreSchemaField]
|
||||||
|
|
||||||
|
_CORE_SCHEMA_FIELD_TYPES = {'typed-dict-field', 'dataclass-field', 'model-field', 'computed-field'}
|
||||||
|
_FUNCTION_WITH_INNER_SCHEMA_TYPES = {'function-before', 'function-after', 'function-wrap'}
|
||||||
|
_LIST_LIKE_SCHEMA_WITH_ITEMS_TYPES = {'list', 'set', 'frozenset'}
|
||||||
|
|
||||||
|
|
||||||
|
def is_core_schema(
|
||||||
|
schema: CoreSchemaOrField,
|
||||||
|
) -> TypeGuard[CoreSchema]:
|
||||||
|
return schema['type'] not in _CORE_SCHEMA_FIELD_TYPES
|
||||||
|
|
||||||
|
|
||||||
|
def is_core_schema_field(
|
||||||
|
schema: CoreSchemaOrField,
|
||||||
|
) -> TypeGuard[CoreSchemaField]:
|
||||||
|
return schema['type'] in _CORE_SCHEMA_FIELD_TYPES
|
||||||
|
|
||||||
|
|
||||||
|
def is_function_with_inner_schema(
|
||||||
|
schema: CoreSchemaOrField,
|
||||||
|
) -> TypeGuard[FunctionSchemaWithInnerSchema]:
|
||||||
|
return schema['type'] in _FUNCTION_WITH_INNER_SCHEMA_TYPES
|
||||||
|
|
||||||
|
|
||||||
|
def is_list_like_schema_with_items_schema(
|
||||||
|
schema: CoreSchema,
|
||||||
|
) -> TypeGuard[core_schema.ListSchema | core_schema.SetSchema | core_schema.FrozenSetSchema]:
|
||||||
|
return schema['type'] in _LIST_LIKE_SCHEMA_WITH_ITEMS_TYPES
|
||||||
|
|
||||||
|
|
||||||
|
def get_type_ref(type_: Any, args_override: tuple[type[Any], ...] | None = None) -> str:
|
||||||
|
"""Produces the ref to be used for this type by pydantic_core's core schemas.
|
||||||
|
|
||||||
|
This `args_override` argument was added for the purpose of creating valid recursive references
|
||||||
|
when creating generic models without needing to create a concrete class.
|
||||||
|
"""
|
||||||
|
origin = get_origin(type_) or type_
|
||||||
|
|
||||||
|
args = get_args(type_) if is_generic_alias(type_) else (args_override or ())
|
||||||
|
generic_metadata = getattr(type_, '__pydantic_generic_metadata__', None)
|
||||||
|
if generic_metadata:
|
||||||
|
origin = generic_metadata['origin'] or origin
|
||||||
|
args = generic_metadata['args'] or args
|
||||||
|
|
||||||
|
module_name = getattr(origin, '__module__', '<No __module__>')
|
||||||
|
if typing_objects.is_typealiastype(origin):
|
||||||
|
type_ref = f'{module_name}.{origin.__name__}:{id(origin)}'
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
qualname = getattr(origin, '__qualname__', f'<No __qualname__: {origin}>')
|
||||||
|
except Exception:
|
||||||
|
qualname = getattr(origin, '__qualname__', '<No __qualname__>')
|
||||||
|
type_ref = f'{module_name}.{qualname}:{id(origin)}'
|
||||||
|
|
||||||
|
arg_refs: list[str] = []
|
||||||
|
for arg in args:
|
||||||
|
if isinstance(arg, str):
|
||||||
|
# Handle string literals as a special case; we may be able to remove this special handling if we
|
||||||
|
# wrap them in a ForwardRef at some point.
|
||||||
|
arg_ref = f'{arg}:str-{id(arg)}'
|
||||||
|
else:
|
||||||
|
arg_ref = f'{_repr.display_as_type(arg)}:{id(arg)}'
|
||||||
|
arg_refs.append(arg_ref)
|
||||||
|
if arg_refs:
|
||||||
|
type_ref = f'{type_ref}[{",".join(arg_refs)}]'
|
||||||
|
return type_ref
|
||||||
|
|
||||||
|
|
||||||
|
def get_ref(s: core_schema.CoreSchema) -> None | str:
|
||||||
|
"""Get the ref from the schema if it has one.
|
||||||
|
This exists just for type checking to work correctly.
|
||||||
|
"""
|
||||||
|
return s.get('ref', None)
|
||||||
|
|
||||||
|
|
||||||
|
def _clean_schema_for_pretty_print(obj: Any, strip_metadata: bool = True) -> Any: # pragma: no cover
|
||||||
|
"""A utility function to remove irrelevant information from a core schema."""
|
||||||
|
if isinstance(obj, Mapping):
|
||||||
|
new_dct = {}
|
||||||
|
for k, v in obj.items():
|
||||||
|
if k == 'metadata' and strip_metadata:
|
||||||
|
new_metadata = {}
|
||||||
|
|
||||||
|
for meta_k, meta_v in v.items():
|
||||||
|
if meta_k in ('pydantic_js_functions', 'pydantic_js_annotation_functions'):
|
||||||
|
new_metadata['js_metadata'] = '<stripped>'
|
||||||
|
else:
|
||||||
|
new_metadata[meta_k] = _clean_schema_for_pretty_print(meta_v, strip_metadata=strip_metadata)
|
||||||
|
|
||||||
|
if list(new_metadata.keys()) == ['js_metadata']:
|
||||||
|
new_metadata = {'<stripped>'}
|
||||||
|
|
||||||
|
new_dct[k] = new_metadata
|
||||||
|
# Remove some defaults:
|
||||||
|
elif k in ('custom_init', 'root_model') and not v:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
new_dct[k] = _clean_schema_for_pretty_print(v, strip_metadata=strip_metadata)
|
||||||
|
|
||||||
|
return new_dct
|
||||||
|
elif isinstance(obj, Sequence) and not isinstance(obj, str):
|
||||||
|
return [_clean_schema_for_pretty_print(v, strip_metadata=strip_metadata) for v in obj]
|
||||||
|
else:
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
def pretty_print_core_schema(
|
||||||
|
val: Any,
|
||||||
|
*,
|
||||||
|
console: Console | None = None,
|
||||||
|
max_depth: int | None = None,
|
||||||
|
strip_metadata: bool = True,
|
||||||
|
) -> None: # pragma: no cover
|
||||||
|
"""Pretty-print a core schema using the `rich` library.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
val: The core schema to print, or a Pydantic model/dataclass/type adapter
|
||||||
|
(in which case the cached core schema is fetched and printed).
|
||||||
|
console: A rich console to use when printing. Defaults to the global rich console instance.
|
||||||
|
max_depth: The number of nesting levels which may be printed.
|
||||||
|
strip_metadata: Whether to strip metadata in the output. If `True` any known core metadata
|
||||||
|
attributes will be stripped (but custom attributes are kept). Defaults to `True`.
|
||||||
|
"""
|
||||||
|
# lazy import:
|
||||||
|
from rich.pretty import pprint
|
||||||
|
|
||||||
|
# circ. imports:
|
||||||
|
from pydantic import BaseModel, TypeAdapter
|
||||||
|
from pydantic.dataclasses import is_pydantic_dataclass
|
||||||
|
|
||||||
|
if (inspect.isclass(val) and issubclass(val, BaseModel)) or is_pydantic_dataclass(val):
|
||||||
|
val = val.__pydantic_core_schema__
|
||||||
|
if isinstance(val, TypeAdapter):
|
||||||
|
val = val.core_schema
|
||||||
|
cleaned_schema = _clean_schema_for_pretty_print(val, strip_metadata=strip_metadata)
|
||||||
|
|
||||||
|
pprint(cleaned_schema, console=console, max_depth=max_depth)
|
||||||
|
|
||||||
|
|
||||||
|
pps = pretty_print_core_schema
|
||||||
315
venv/Lib/site-packages/pydantic/_internal/_dataclasses.py
Normal file
315
venv/Lib/site-packages/pydantic/_internal/_dataclasses.py
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
"""Private logic for creating pydantic dataclasses."""
|
||||||
|
|
||||||
|
from __future__ import annotations as _annotations
|
||||||
|
|
||||||
|
import copy
|
||||||
|
import dataclasses
|
||||||
|
import sys
|
||||||
|
import warnings
|
||||||
|
from collections.abc import Generator
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from functools import partial
|
||||||
|
from typing import TYPE_CHECKING, Any, ClassVar, Protocol, cast
|
||||||
|
|
||||||
|
from pydantic_core import (
|
||||||
|
ArgsKwargs,
|
||||||
|
SchemaSerializer,
|
||||||
|
SchemaValidator,
|
||||||
|
core_schema,
|
||||||
|
)
|
||||||
|
from typing_extensions import TypeAlias, TypeIs
|
||||||
|
|
||||||
|
from ..errors import PydanticUndefinedAnnotation
|
||||||
|
from ..fields import FieldInfo
|
||||||
|
from ..plugin._schema_validator import PluggableSchemaValidator, create_schema_validator
|
||||||
|
from ..warnings import PydanticDeprecatedSince20
|
||||||
|
from . import _config, _decorators
|
||||||
|
from ._fields import collect_dataclass_fields
|
||||||
|
from ._generate_schema import GenerateSchema, InvalidSchemaError
|
||||||
|
from ._generics import get_standard_typevars_map
|
||||||
|
from ._mock_val_ser import set_dataclass_mocks
|
||||||
|
from ._namespace_utils import NsResolver
|
||||||
|
from ._signature import generate_pydantic_signature
|
||||||
|
from ._utils import LazyClassAttribute
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from _typeshed import DataclassInstance as StandardDataclass
|
||||||
|
|
||||||
|
from ..config import ConfigDict
|
||||||
|
|
||||||
|
class PydanticDataclass(StandardDataclass, Protocol):
|
||||||
|
"""A protocol containing attributes only available once a class has been decorated as a Pydantic dataclass.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
__pydantic_config__: Pydantic-specific configuration settings for the dataclass.
|
||||||
|
__pydantic_complete__: Whether dataclass building is completed, or if there are still undefined fields.
|
||||||
|
__pydantic_core_schema__: The pydantic-core schema used to build the SchemaValidator and SchemaSerializer.
|
||||||
|
__pydantic_decorators__: Metadata containing the decorators defined on the dataclass.
|
||||||
|
__pydantic_fields__: Metadata about the fields defined on the dataclass.
|
||||||
|
__pydantic_serializer__: The pydantic-core SchemaSerializer used to dump instances of the dataclass.
|
||||||
|
__pydantic_validator__: The pydantic-core SchemaValidator used to validate instances of the dataclass.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__pydantic_config__: ClassVar[ConfigDict]
|
||||||
|
__pydantic_complete__: ClassVar[bool]
|
||||||
|
__pydantic_core_schema__: ClassVar[core_schema.CoreSchema]
|
||||||
|
__pydantic_decorators__: ClassVar[_decorators.DecoratorInfos]
|
||||||
|
__pydantic_fields__: ClassVar[dict[str, FieldInfo]]
|
||||||
|
__pydantic_serializer__: ClassVar[SchemaSerializer]
|
||||||
|
__pydantic_validator__: ClassVar[SchemaValidator | PluggableSchemaValidator]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __pydantic_fields_complete__(cls) -> bool: ...
|
||||||
|
|
||||||
|
|
||||||
|
def set_dataclass_fields(
|
||||||
|
cls: type[StandardDataclass],
|
||||||
|
config_wrapper: _config.ConfigWrapper,
|
||||||
|
ns_resolver: NsResolver | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Collect and set `cls.__pydantic_fields__`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cls: The class.
|
||||||
|
config_wrapper: The config wrapper instance.
|
||||||
|
ns_resolver: Namespace resolver to use when getting dataclass annotations.
|
||||||
|
"""
|
||||||
|
typevars_map = get_standard_typevars_map(cls)
|
||||||
|
fields = collect_dataclass_fields(
|
||||||
|
cls, ns_resolver=ns_resolver, typevars_map=typevars_map, config_wrapper=config_wrapper
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.__pydantic_fields__ = fields # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
def complete_dataclass(
|
||||||
|
cls: type[Any],
|
||||||
|
config_wrapper: _config.ConfigWrapper,
|
||||||
|
*,
|
||||||
|
raise_errors: bool = True,
|
||||||
|
ns_resolver: NsResolver | None = None,
|
||||||
|
_force_build: bool = False,
|
||||||
|
) -> bool:
|
||||||
|
"""Finish building a pydantic dataclass.
|
||||||
|
|
||||||
|
This logic is called on a class which has already been wrapped in `dataclasses.dataclass()`.
|
||||||
|
|
||||||
|
This is somewhat analogous to `pydantic._internal._model_construction.complete_model_class`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cls: The class.
|
||||||
|
config_wrapper: The config wrapper instance.
|
||||||
|
raise_errors: Whether to raise errors, defaults to `True`.
|
||||||
|
ns_resolver: The namespace resolver instance to use when collecting dataclass fields
|
||||||
|
and during schema building.
|
||||||
|
_force_build: Whether to force building the dataclass, no matter if
|
||||||
|
[`defer_build`][pydantic.config.ConfigDict.defer_build] is set.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
`True` if building a pydantic dataclass is successfully completed, `False` otherwise.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
PydanticUndefinedAnnotation: If `raise_error` is `True` and there is an undefined annotations.
|
||||||
|
"""
|
||||||
|
original_init = cls.__init__
|
||||||
|
|
||||||
|
# dataclass.__init__ must be defined here so its `__qualname__` can be changed since functions can't be copied,
|
||||||
|
# and so that the mock validator is used if building was deferred:
|
||||||
|
def __init__(__dataclass_self__: PydanticDataclass, *args: Any, **kwargs: Any) -> None:
|
||||||
|
__tracebackhide__ = True
|
||||||
|
s = __dataclass_self__
|
||||||
|
s.__pydantic_validator__.validate_python(ArgsKwargs(args, kwargs), self_instance=s)
|
||||||
|
|
||||||
|
__init__.__qualname__ = f'{cls.__qualname__}.__init__'
|
||||||
|
|
||||||
|
cls.__init__ = __init__ # type: ignore
|
||||||
|
cls.__pydantic_config__ = config_wrapper.config_dict # type: ignore
|
||||||
|
|
||||||
|
set_dataclass_fields(cls, config_wrapper=config_wrapper, ns_resolver=ns_resolver)
|
||||||
|
|
||||||
|
if not _force_build and config_wrapper.defer_build:
|
||||||
|
set_dataclass_mocks(cls)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if hasattr(cls, '__post_init_post_parse__'):
|
||||||
|
warnings.warn(
|
||||||
|
'Support for `__post_init_post_parse__` has been dropped, the method will not be called',
|
||||||
|
PydanticDeprecatedSince20,
|
||||||
|
)
|
||||||
|
|
||||||
|
typevars_map = get_standard_typevars_map(cls)
|
||||||
|
gen_schema = GenerateSchema(
|
||||||
|
config_wrapper,
|
||||||
|
ns_resolver=ns_resolver,
|
||||||
|
typevars_map=typevars_map,
|
||||||
|
)
|
||||||
|
|
||||||
|
# set __signature__ attr only for the class, but not for its instances
|
||||||
|
# (because instances can define `__call__`, and `inspect.signature` shouldn't
|
||||||
|
# use the `__signature__` attribute and instead generate from `__call__`).
|
||||||
|
cls.__signature__ = LazyClassAttribute(
|
||||||
|
'__signature__',
|
||||||
|
partial(
|
||||||
|
generate_pydantic_signature,
|
||||||
|
# It's important that we reference the `original_init` here,
|
||||||
|
# as it is the one synthesized by the stdlib `dataclass` module:
|
||||||
|
init=original_init,
|
||||||
|
fields=cls.__pydantic_fields__, # type: ignore
|
||||||
|
validate_by_name=config_wrapper.validate_by_name,
|
||||||
|
extra=config_wrapper.extra,
|
||||||
|
is_dataclass=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
schema = gen_schema.generate_schema(cls)
|
||||||
|
except PydanticUndefinedAnnotation as e:
|
||||||
|
if raise_errors:
|
||||||
|
raise
|
||||||
|
set_dataclass_mocks(cls, f'`{e.name}`')
|
||||||
|
return False
|
||||||
|
|
||||||
|
core_config = config_wrapper.core_config(title=cls.__name__)
|
||||||
|
|
||||||
|
try:
|
||||||
|
schema = gen_schema.clean_schema(schema)
|
||||||
|
except InvalidSchemaError:
|
||||||
|
set_dataclass_mocks(cls)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# We are about to set all the remaining required properties expected for this cast;
|
||||||
|
# __pydantic_decorators__ and __pydantic_fields__ should already be set
|
||||||
|
cls = cast('type[PydanticDataclass]', cls)
|
||||||
|
|
||||||
|
cls.__pydantic_core_schema__ = schema
|
||||||
|
cls.__pydantic_validator__ = create_schema_validator(
|
||||||
|
schema, cls, cls.__module__, cls.__qualname__, 'dataclass', core_config, config_wrapper.plugin_settings
|
||||||
|
)
|
||||||
|
cls.__pydantic_serializer__ = SchemaSerializer(schema, core_config)
|
||||||
|
cls.__pydantic_complete__ = True
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def is_stdlib_dataclass(cls: type[Any], /) -> TypeIs[type[StandardDataclass]]:
|
||||||
|
"""Returns `True` if the class is a stdlib dataclass and *not* a Pydantic dataclass.
|
||||||
|
|
||||||
|
Unlike the stdlib `dataclasses.is_dataclass()` function, this does *not* include subclasses
|
||||||
|
of a dataclass that are themselves not dataclasses.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cls: The class.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
`True` if the class is a stdlib dataclass, `False` otherwise.
|
||||||
|
"""
|
||||||
|
return '__dataclass_fields__' in cls.__dict__ and not hasattr(cls, '__pydantic_validator__')
|
||||||
|
|
||||||
|
|
||||||
|
def as_dataclass_field(pydantic_field: FieldInfo) -> dataclasses.Field[Any]:
|
||||||
|
field_args: dict[str, Any] = {'default': pydantic_field}
|
||||||
|
|
||||||
|
# Needed because if `doc` is set, the dataclass slots will be a dict (field name -> doc) instead of a tuple:
|
||||||
|
if sys.version_info >= (3, 14) and pydantic_field.description is not None:
|
||||||
|
field_args['doc'] = pydantic_field.description
|
||||||
|
|
||||||
|
# Needed as the stdlib dataclass module processes kw_only in a specific way during class construction:
|
||||||
|
if sys.version_info >= (3, 10) and pydantic_field.kw_only is not None:
|
||||||
|
field_args['kw_only'] = pydantic_field.kw_only
|
||||||
|
|
||||||
|
# Needed as the stdlib dataclass modules generates `__repr__()` during class construction:
|
||||||
|
if pydantic_field.repr is not True:
|
||||||
|
field_args['repr'] = pydantic_field.repr
|
||||||
|
|
||||||
|
return dataclasses.field(**field_args)
|
||||||
|
|
||||||
|
|
||||||
|
DcFields: TypeAlias = dict[str, dataclasses.Field[Any]]
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def patch_base_fields(cls: type[Any]) -> Generator[None]:
|
||||||
|
"""Temporarily patch the stdlib dataclasses bases of `cls` if the Pydantic `Field()` function is used.
|
||||||
|
|
||||||
|
When creating a Pydantic dataclass, it is possible to inherit from stdlib dataclasses, where
|
||||||
|
the Pydantic `Field()` function is used. To create this Pydantic dataclass, we first apply
|
||||||
|
the stdlib `@dataclass` decorator on it. During the construction of the stdlib dataclass,
|
||||||
|
the `kw_only` and `repr` field arguments need to be understood by the stdlib *during* the
|
||||||
|
dataclass construction. To do so, we temporarily patch the fields dictionary of the affected
|
||||||
|
bases.
|
||||||
|
|
||||||
|
For instance, with the following example:
|
||||||
|
|
||||||
|
```python {test="skip" lint="skip"}
|
||||||
|
import dataclasses as stdlib_dc
|
||||||
|
|
||||||
|
import pydantic
|
||||||
|
import pydantic.dataclasses as pydantic_dc
|
||||||
|
|
||||||
|
@stdlib_dc.dataclass
|
||||||
|
class A:
|
||||||
|
a: int = pydantic.Field(repr=False)
|
||||||
|
|
||||||
|
# Notice that the `repr` attribute of the dataclass field is `True`:
|
||||||
|
A.__dataclass_fields__['a']
|
||||||
|
#> dataclass.Field(default=FieldInfo(repr=False), repr=True, ...)
|
||||||
|
|
||||||
|
@pydantic_dc.dataclass
|
||||||
|
class B(A):
|
||||||
|
b: int = pydantic.Field(repr=False)
|
||||||
|
```
|
||||||
|
|
||||||
|
When passing `B` to the stdlib `@dataclass` decorator, it will look for fields in the parent classes
|
||||||
|
and reuse them directly. When this context manager is active, `A` will be temporarily patched to be
|
||||||
|
equivalent to:
|
||||||
|
|
||||||
|
```python {test="skip" lint="skip"}
|
||||||
|
@stdlib_dc.dataclass
|
||||||
|
class A:
|
||||||
|
a: int = stdlib_dc.field(default=Field(repr=False), repr=False)
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
This is only applied to the bases of `cls`, and not `cls` itself. The reason is that the Pydantic
|
||||||
|
dataclass decorator "owns" `cls` (in the previous example, `B`). As such, we instead modify the fields
|
||||||
|
directly (in the previous example, we simply do `setattr(B, 'b', as_dataclass_field(pydantic_field))`).
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
This approach is far from ideal, and can probably be the source of unwanted side effects/race conditions.
|
||||||
|
The previous implemented approach was mutating the `__annotations__` dict of `cls`, which is no longer a
|
||||||
|
safe operation in Python 3.14+, and resulted in unexpected behavior with field ordering anyway.
|
||||||
|
"""
|
||||||
|
# A list of two-tuples, the first element being a reference to the
|
||||||
|
# dataclass fields dictionary, the second element being a mapping between
|
||||||
|
# the field names that were modified, and their original `Field`:
|
||||||
|
original_fields_list: list[tuple[DcFields, DcFields]] = []
|
||||||
|
|
||||||
|
for base in cls.__mro__[1:]:
|
||||||
|
dc_fields: dict[str, dataclasses.Field[Any]] = base.__dict__.get('__dataclass_fields__', {})
|
||||||
|
dc_fields_with_pydantic_field_defaults = {
|
||||||
|
field_name: field
|
||||||
|
for field_name, field in dc_fields.items()
|
||||||
|
if isinstance(field.default, FieldInfo)
|
||||||
|
# Only do the patching if one of the affected attributes is set:
|
||||||
|
and (field.default.description is not None or field.default.kw_only or field.default.repr is not True)
|
||||||
|
}
|
||||||
|
if dc_fields_with_pydantic_field_defaults:
|
||||||
|
original_fields_list.append((dc_fields, dc_fields_with_pydantic_field_defaults))
|
||||||
|
for field_name, field in dc_fields_with_pydantic_field_defaults.items():
|
||||||
|
default = cast(FieldInfo, field.default)
|
||||||
|
# `dataclasses.Field` isn't documented as working with `copy.copy()`.
|
||||||
|
# It is a class with `__slots__`, so should work (and we hope for the best):
|
||||||
|
new_dc_field = copy.copy(field)
|
||||||
|
# For base fields, no need to set `doc` from `FieldInfo.description`, this is only relevant
|
||||||
|
# for the class under construction and handled in `as_dataclass_field()`.
|
||||||
|
if sys.version_info >= (3, 10) and default.kw_only:
|
||||||
|
new_dc_field.kw_only = True
|
||||||
|
if default.repr is not True:
|
||||||
|
new_dc_field.repr = default.repr
|
||||||
|
dc_fields[field_name] = new_dc_field
|
||||||
|
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
for fields, original_fields in original_fields_list:
|
||||||
|
for field_name, original_field in original_fields.items():
|
||||||
|
fields[field_name] = original_field
|
||||||
Reference in New Issue
Block a user