Загрузить файлы в «venv/Lib/site-packages/pydantic/v1»
This commit is contained in:
1163
venv/Lib/site-packages/pydantic/v1/schema.py
Normal file
1163
venv/Lib/site-packages/pydantic/v1/schema.py
Normal file
File diff suppressed because it is too large
Load Diff
92
venv/Lib/site-packages/pydantic/v1/tools.py
Normal file
92
venv/Lib/site-packages/pydantic/v1/tools.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import json
|
||||||
|
from functools import lru_cache
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING, Any, Callable, Optional, Type, TypeVar, Union
|
||||||
|
|
||||||
|
from pydantic.v1.parse import Protocol, load_file, load_str_bytes
|
||||||
|
from pydantic.v1.types import StrBytes
|
||||||
|
from pydantic.v1.typing import display_as_type
|
||||||
|
|
||||||
|
__all__ = ('parse_file_as', 'parse_obj_as', 'parse_raw_as', 'schema_of', 'schema_json_of')
|
||||||
|
|
||||||
|
NameFactory = Union[str, Callable[[Type[Any]], str]]
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from pydantic.v1.typing import DictStrAny
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_parsing_type_name(type_: Any) -> str:
|
||||||
|
return f'ParsingModel[{display_as_type(type_)}]'
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=2048)
|
||||||
|
def _get_parsing_type(type_: Any, *, type_name: Optional[NameFactory] = None) -> Any:
|
||||||
|
from pydantic.v1.main import create_model
|
||||||
|
|
||||||
|
if type_name is None:
|
||||||
|
type_name = _generate_parsing_type_name
|
||||||
|
if not isinstance(type_name, str):
|
||||||
|
type_name = type_name(type_)
|
||||||
|
return create_model(type_name, __root__=(type_, ...))
|
||||||
|
|
||||||
|
|
||||||
|
T = TypeVar('T')
|
||||||
|
|
||||||
|
|
||||||
|
def parse_obj_as(type_: Type[T], obj: Any, *, type_name: Optional[NameFactory] = None) -> T:
|
||||||
|
model_type = _get_parsing_type(type_, type_name=type_name) # type: ignore[arg-type]
|
||||||
|
return model_type(__root__=obj).__root__
|
||||||
|
|
||||||
|
|
||||||
|
def parse_file_as(
|
||||||
|
type_: Type[T],
|
||||||
|
path: Union[str, Path],
|
||||||
|
*,
|
||||||
|
content_type: str = None,
|
||||||
|
encoding: str = 'utf8',
|
||||||
|
proto: Protocol = None,
|
||||||
|
allow_pickle: bool = False,
|
||||||
|
json_loads: Callable[[str], Any] = json.loads,
|
||||||
|
type_name: Optional[NameFactory] = None,
|
||||||
|
) -> T:
|
||||||
|
obj = load_file(
|
||||||
|
path,
|
||||||
|
proto=proto,
|
||||||
|
content_type=content_type,
|
||||||
|
encoding=encoding,
|
||||||
|
allow_pickle=allow_pickle,
|
||||||
|
json_loads=json_loads,
|
||||||
|
)
|
||||||
|
return parse_obj_as(type_, obj, type_name=type_name)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_raw_as(
|
||||||
|
type_: Type[T],
|
||||||
|
b: StrBytes,
|
||||||
|
*,
|
||||||
|
content_type: str = None,
|
||||||
|
encoding: str = 'utf8',
|
||||||
|
proto: Protocol = None,
|
||||||
|
allow_pickle: bool = False,
|
||||||
|
json_loads: Callable[[str], Any] = json.loads,
|
||||||
|
type_name: Optional[NameFactory] = None,
|
||||||
|
) -> T:
|
||||||
|
obj = load_str_bytes(
|
||||||
|
b,
|
||||||
|
proto=proto,
|
||||||
|
content_type=content_type,
|
||||||
|
encoding=encoding,
|
||||||
|
allow_pickle=allow_pickle,
|
||||||
|
json_loads=json_loads,
|
||||||
|
)
|
||||||
|
return parse_obj_as(type_, obj, type_name=type_name)
|
||||||
|
|
||||||
|
|
||||||
|
def schema_of(type_: Any, *, title: Optional[NameFactory] = None, **schema_kwargs: Any) -> 'DictStrAny':
|
||||||
|
"""Generate a JSON schema (as dict) for the passed model or dynamically generated one"""
|
||||||
|
return _get_parsing_type(type_, type_name=title).schema(**schema_kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def schema_json_of(type_: Any, *, title: Optional[NameFactory] = None, **schema_json_kwargs: Any) -> str:
|
||||||
|
"""Generate a JSON schema (as JSON) for the passed model or dynamically generated one"""
|
||||||
|
return _get_parsing_type(type_, type_name=title).schema_json(**schema_json_kwargs)
|
||||||
1205
venv/Lib/site-packages/pydantic/v1/types.py
Normal file
1205
venv/Lib/site-packages/pydantic/v1/types.py
Normal file
File diff suppressed because it is too large
Load Diff
627
venv/Lib/site-packages/pydantic/v1/typing.py
Normal file
627
venv/Lib/site-packages/pydantic/v1/typing.py
Normal file
@@ -0,0 +1,627 @@
|
|||||||
|
import functools
|
||||||
|
import operator
|
||||||
|
import sys
|
||||||
|
import typing
|
||||||
|
from collections.abc import Callable
|
||||||
|
from os import PathLike
|
||||||
|
from typing import ( # type: ignore
|
||||||
|
TYPE_CHECKING,
|
||||||
|
AbstractSet,
|
||||||
|
Any,
|
||||||
|
Callable as TypingCallable,
|
||||||
|
ClassVar,
|
||||||
|
Dict,
|
||||||
|
ForwardRef,
|
||||||
|
Generator,
|
||||||
|
Iterable,
|
||||||
|
List,
|
||||||
|
Mapping,
|
||||||
|
NewType,
|
||||||
|
Optional,
|
||||||
|
Sequence,
|
||||||
|
Set,
|
||||||
|
Tuple,
|
||||||
|
Type,
|
||||||
|
TypeVar,
|
||||||
|
Union,
|
||||||
|
_eval_type,
|
||||||
|
cast,
|
||||||
|
get_type_hints,
|
||||||
|
)
|
||||||
|
|
||||||
|
from typing_extensions import (
|
||||||
|
Annotated,
|
||||||
|
Final,
|
||||||
|
Literal,
|
||||||
|
NotRequired as TypedDictNotRequired,
|
||||||
|
Required as TypedDictRequired,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from typing import _TypingBase as typing_base # type: ignore
|
||||||
|
except ImportError:
|
||||||
|
from typing import _Final as typing_base # type: ignore
|
||||||
|
|
||||||
|
try:
|
||||||
|
from typing import GenericAlias as TypingGenericAlias # type: ignore
|
||||||
|
except ImportError:
|
||||||
|
# python < 3.9 does not have GenericAlias (list[int], tuple[str, ...] and so on)
|
||||||
|
TypingGenericAlias = ()
|
||||||
|
|
||||||
|
try:
|
||||||
|
from types import UnionType as TypesUnionType # type: ignore
|
||||||
|
except ImportError:
|
||||||
|
# python < 3.10 does not have UnionType (str | int, byte | bool and so on)
|
||||||
|
TypesUnionType = ()
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info < (3, 9):
|
||||||
|
|
||||||
|
def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any:
|
||||||
|
return type_._evaluate(globalns, localns)
|
||||||
|
|
||||||
|
elif sys.version_info < (3, 12, 4):
|
||||||
|
|
||||||
|
def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any:
|
||||||
|
# Even though it is the right signature for python 3.9, mypy complains with
|
||||||
|
# `error: Too many arguments for "_evaluate" of "ForwardRef"` hence the cast...
|
||||||
|
# Python 3.13/3.12.4+ made `recursive_guard` a kwarg, so name it explicitly to avoid:
|
||||||
|
# TypeError: ForwardRef._evaluate() missing 1 required keyword-only argument: 'recursive_guard'
|
||||||
|
return cast(Any, type_)._evaluate(globalns, localns, recursive_guard=set())
|
||||||
|
|
||||||
|
elif sys.version_info < (3, 14):
|
||||||
|
|
||||||
|
def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any:
|
||||||
|
# Pydantic 1.x will not support PEP 695 syntax, but provide `type_params` to avoid
|
||||||
|
# warnings:
|
||||||
|
return cast(Any, type_)._evaluate(globalns, localns, type_params=(), recursive_guard=set())
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any:
|
||||||
|
# Pydantic 1.x will not support PEP 695 syntax, but provide `type_params` to avoid
|
||||||
|
# warnings:
|
||||||
|
return typing.evaluate_forward_ref(
|
||||||
|
type_,
|
||||||
|
globals=globalns,
|
||||||
|
locals=localns,
|
||||||
|
type_params=(),
|
||||||
|
_recursive_guard=set(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info < (3, 9):
|
||||||
|
# Ensure we always get all the whole `Annotated` hint, not just the annotated type.
|
||||||
|
# For 3.7 to 3.8, `get_type_hints` doesn't recognize `typing_extensions.Annotated`,
|
||||||
|
# so it already returns the full annotation
|
||||||
|
get_all_type_hints = get_type_hints
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
def get_all_type_hints(obj: Any, globalns: Any = None, localns: Any = None) -> Any:
|
||||||
|
return get_type_hints(obj, globalns, localns, include_extras=True)
|
||||||
|
|
||||||
|
|
||||||
|
_T = TypeVar('_T')
|
||||||
|
|
||||||
|
AnyCallable = TypingCallable[..., Any]
|
||||||
|
NoArgAnyCallable = TypingCallable[[], Any]
|
||||||
|
|
||||||
|
# workaround for https://github.com/python/mypy/issues/9496
|
||||||
|
AnyArgTCallable = TypingCallable[..., _T]
|
||||||
|
|
||||||
|
|
||||||
|
# Annotated[...] is implemented by returning an instance of one of these classes, depending on
|
||||||
|
# python/typing_extensions version.
|
||||||
|
AnnotatedTypeNames = {'AnnotatedMeta', '_AnnotatedAlias'}
|
||||||
|
|
||||||
|
|
||||||
|
LITERAL_TYPES: Set[Any] = {Literal}
|
||||||
|
if hasattr(typing, 'Literal'):
|
||||||
|
LITERAL_TYPES.add(typing.Literal)
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info < (3, 8):
|
||||||
|
|
||||||
|
def get_origin(t: Type[Any]) -> Optional[Type[Any]]:
|
||||||
|
if type(t).__name__ in AnnotatedTypeNames:
|
||||||
|
# weirdly this is a runtime requirement, as well as for mypy
|
||||||
|
return cast(Type[Any], Annotated)
|
||||||
|
return getattr(t, '__origin__', None)
|
||||||
|
|
||||||
|
else:
|
||||||
|
from typing import get_origin as _typing_get_origin
|
||||||
|
|
||||||
|
def get_origin(tp: Type[Any]) -> Optional[Type[Any]]:
|
||||||
|
"""
|
||||||
|
We can't directly use `typing.get_origin` since we need a fallback to support
|
||||||
|
custom generic classes like `ConstrainedList`
|
||||||
|
It should be useless once https://github.com/cython/cython/issues/3537 is
|
||||||
|
solved and https://github.com/pydantic/pydantic/pull/1753 is merged.
|
||||||
|
"""
|
||||||
|
if type(tp).__name__ in AnnotatedTypeNames:
|
||||||
|
return cast(Type[Any], Annotated) # mypy complains about _SpecialForm
|
||||||
|
return _typing_get_origin(tp) or getattr(tp, '__origin__', None)
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info < (3, 8):
|
||||||
|
from typing import _GenericAlias
|
||||||
|
|
||||||
|
def get_args(t: Type[Any]) -> Tuple[Any, ...]:
|
||||||
|
"""Compatibility version of get_args for python 3.7.
|
||||||
|
|
||||||
|
Mostly compatible with the python 3.8 `typing` module version
|
||||||
|
and able to handle almost all use cases.
|
||||||
|
"""
|
||||||
|
if type(t).__name__ in AnnotatedTypeNames:
|
||||||
|
return t.__args__ + t.__metadata__
|
||||||
|
if isinstance(t, _GenericAlias):
|
||||||
|
res = t.__args__
|
||||||
|
if t.__origin__ is Callable and res and res[0] is not Ellipsis:
|
||||||
|
res = (list(res[:-1]), res[-1])
|
||||||
|
return res
|
||||||
|
return getattr(t, '__args__', ())
|
||||||
|
|
||||||
|
else:
|
||||||
|
from typing import get_args as _typing_get_args
|
||||||
|
|
||||||
|
def _generic_get_args(tp: Type[Any]) -> Tuple[Any, ...]:
|
||||||
|
"""
|
||||||
|
In python 3.9, `typing.Dict`, `typing.List`, ...
|
||||||
|
do have an empty `__args__` by default (instead of the generic ~T for example).
|
||||||
|
In order to still support `Dict` for example and consider it as `Dict[Any, Any]`,
|
||||||
|
we retrieve the `_nparams` value that tells us how many parameters it needs.
|
||||||
|
"""
|
||||||
|
if hasattr(tp, '_nparams'):
|
||||||
|
return (Any,) * tp._nparams
|
||||||
|
# Special case for `tuple[()]`, which used to return ((),) with `typing.Tuple`
|
||||||
|
# in python 3.10- but now returns () for `tuple` and `Tuple`.
|
||||||
|
# This will probably be clarified in pydantic v2
|
||||||
|
try:
|
||||||
|
if tp == Tuple[()] or sys.version_info >= (3, 9) and tp == tuple[()]: # type: ignore[misc]
|
||||||
|
return ((),)
|
||||||
|
# there is a TypeError when compiled with cython
|
||||||
|
except TypeError: # pragma: no cover
|
||||||
|
pass
|
||||||
|
return ()
|
||||||
|
|
||||||
|
def get_args(tp: Type[Any]) -> Tuple[Any, ...]:
|
||||||
|
"""Get type arguments with all substitutions performed.
|
||||||
|
|
||||||
|
For unions, basic simplifications used by Union constructor are performed.
|
||||||
|
Examples::
|
||||||
|
get_args(Dict[str, int]) == (str, int)
|
||||||
|
get_args(int) == ()
|
||||||
|
get_args(Union[int, Union[T, int], str][int]) == (int, str)
|
||||||
|
get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int])
|
||||||
|
get_args(Callable[[], T][int]) == ([], int)
|
||||||
|
"""
|
||||||
|
if type(tp).__name__ in AnnotatedTypeNames:
|
||||||
|
return tp.__args__ + tp.__metadata__
|
||||||
|
# the fallback is needed for the same reasons as `get_origin` (see above)
|
||||||
|
return _typing_get_args(tp) or getattr(tp, '__args__', ()) or _generic_get_args(tp)
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info < (3, 9):
|
||||||
|
|
||||||
|
def convert_generics(tp: Type[Any]) -> Type[Any]:
|
||||||
|
"""Python 3.9 and older only supports generics from `typing` module.
|
||||||
|
They convert strings to ForwardRef automatically.
|
||||||
|
|
||||||
|
Examples::
|
||||||
|
typing.List['Hero'] == typing.List[ForwardRef('Hero')]
|
||||||
|
"""
|
||||||
|
return tp
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
def convert_generics(tp: Type[Any]) -> Type[Any]:
|
||||||
|
"""
|
||||||
|
Recursively searches for `str` type hints and replaces them with ForwardRef.
|
||||||
|
|
||||||
|
Examples::
|
||||||
|
convert_generics(list['Hero']) == list[ForwardRef('Hero')]
|
||||||
|
convert_generics(dict['Hero', 'Team']) == dict[ForwardRef('Hero'), ForwardRef('Team')]
|
||||||
|
convert_generics(typing.Dict['Hero', 'Team']) == typing.Dict[ForwardRef('Hero'), ForwardRef('Team')]
|
||||||
|
convert_generics(list[str | 'Hero'] | int) == list[str | ForwardRef('Hero')] | int
|
||||||
|
"""
|
||||||
|
origin = get_origin(tp)
|
||||||
|
if not origin or not hasattr(tp, '__args__'):
|
||||||
|
return tp
|
||||||
|
|
||||||
|
args = get_args(tp)
|
||||||
|
|
||||||
|
# typing.Annotated needs special treatment
|
||||||
|
if origin is Annotated:
|
||||||
|
return Annotated[(convert_generics(args[0]), *args[1:])] # type: ignore
|
||||||
|
|
||||||
|
# recursively replace `str` instances inside of `GenericAlias` with `ForwardRef(arg)`
|
||||||
|
converted = tuple(
|
||||||
|
ForwardRef(arg) if isinstance(arg, str) and isinstance(tp, TypingGenericAlias) else convert_generics(arg)
|
||||||
|
for arg in args
|
||||||
|
)
|
||||||
|
|
||||||
|
if converted == args:
|
||||||
|
return tp
|
||||||
|
elif isinstance(tp, TypingGenericAlias):
|
||||||
|
return TypingGenericAlias(origin, converted)
|
||||||
|
elif isinstance(tp, TypesUnionType):
|
||||||
|
# recreate types.UnionType (PEP604, Python >= 3.10)
|
||||||
|
return functools.reduce(operator.or_, converted) # type: ignore
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
setattr(tp, '__args__', converted)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
return tp
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info < (3, 10):
|
||||||
|
|
||||||
|
def is_union(tp: Optional[Type[Any]]) -> bool:
|
||||||
|
return tp is Union
|
||||||
|
|
||||||
|
WithArgsTypes = (TypingGenericAlias,)
|
||||||
|
|
||||||
|
else:
|
||||||
|
import types
|
||||||
|
import typing
|
||||||
|
|
||||||
|
def is_union(tp: Optional[Type[Any]]) -> bool:
|
||||||
|
return tp is Union or tp is types.UnionType # noqa: E721
|
||||||
|
|
||||||
|
WithArgsTypes = (typing._GenericAlias, types.GenericAlias, types.UnionType)
|
||||||
|
|
||||||
|
|
||||||
|
StrPath = Union[str, PathLike]
|
||||||
|
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from pydantic.v1.fields import ModelField
|
||||||
|
|
||||||
|
TupleGenerator = Generator[Tuple[str, Any], None, None]
|
||||||
|
DictStrAny = Dict[str, Any]
|
||||||
|
DictAny = Dict[Any, Any]
|
||||||
|
SetStr = Set[str]
|
||||||
|
ListStr = List[str]
|
||||||
|
IntStr = Union[int, str]
|
||||||
|
AbstractSetIntStr = AbstractSet[IntStr]
|
||||||
|
DictIntStrAny = Dict[IntStr, Any]
|
||||||
|
MappingIntStrAny = Mapping[IntStr, Any]
|
||||||
|
CallableGenerator = Generator[AnyCallable, None, None]
|
||||||
|
ReprArgs = Sequence[Tuple[Optional[str], Any]]
|
||||||
|
|
||||||
|
MYPY = False
|
||||||
|
if MYPY:
|
||||||
|
AnyClassMethod = classmethod[Any]
|
||||||
|
else:
|
||||||
|
# classmethod[TargetType, CallableParamSpecType, CallableReturnType]
|
||||||
|
AnyClassMethod = classmethod[Any, Any, Any]
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'AnyCallable',
|
||||||
|
'NoArgAnyCallable',
|
||||||
|
'NoneType',
|
||||||
|
'is_none_type',
|
||||||
|
'display_as_type',
|
||||||
|
'resolve_annotations',
|
||||||
|
'is_callable_type',
|
||||||
|
'is_literal_type',
|
||||||
|
'all_literal_values',
|
||||||
|
'is_namedtuple',
|
||||||
|
'is_typeddict',
|
||||||
|
'is_typeddict_special',
|
||||||
|
'is_new_type',
|
||||||
|
'new_type_supertype',
|
||||||
|
'is_classvar',
|
||||||
|
'is_finalvar',
|
||||||
|
'update_field_forward_refs',
|
||||||
|
'update_model_forward_refs',
|
||||||
|
'TupleGenerator',
|
||||||
|
'DictStrAny',
|
||||||
|
'DictAny',
|
||||||
|
'SetStr',
|
||||||
|
'ListStr',
|
||||||
|
'IntStr',
|
||||||
|
'AbstractSetIntStr',
|
||||||
|
'DictIntStrAny',
|
||||||
|
'CallableGenerator',
|
||||||
|
'ReprArgs',
|
||||||
|
'AnyClassMethod',
|
||||||
|
'CallableGenerator',
|
||||||
|
'WithArgsTypes',
|
||||||
|
'get_args',
|
||||||
|
'get_origin',
|
||||||
|
'get_sub_types',
|
||||||
|
'typing_base',
|
||||||
|
'get_all_type_hints',
|
||||||
|
'is_union',
|
||||||
|
'StrPath',
|
||||||
|
'MappingIntStrAny',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
NoneType = None.__class__
|
||||||
|
|
||||||
|
|
||||||
|
NONE_TYPES: Tuple[Any, Any, Any] = (None, NoneType, Literal[None])
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info < (3, 8):
|
||||||
|
# Even though this implementation is slower, we need it for python 3.7:
|
||||||
|
# In python 3.7 "Literal" is not a builtin type and uses a different
|
||||||
|
# mechanism.
|
||||||
|
# for this reason `Literal[None] is Literal[None]` evaluates to `False`,
|
||||||
|
# breaking the faster implementation used for the other python versions.
|
||||||
|
|
||||||
|
def is_none_type(type_: Any) -> bool:
|
||||||
|
return type_ in NONE_TYPES
|
||||||
|
|
||||||
|
elif sys.version_info[:2] == (3, 8):
|
||||||
|
|
||||||
|
def is_none_type(type_: Any) -> bool:
|
||||||
|
for none_type in NONE_TYPES:
|
||||||
|
if type_ is none_type:
|
||||||
|
return True
|
||||||
|
# With python 3.8, specifically 3.8.10, Literal "is" check sare very flakey
|
||||||
|
# can change on very subtle changes like use of types in other modules,
|
||||||
|
# hopefully this check avoids that issue.
|
||||||
|
if is_literal_type(type_): # pragma: no cover
|
||||||
|
return all_literal_values(type_) == (None,)
|
||||||
|
return False
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
def is_none_type(type_: Any) -> bool:
|
||||||
|
return type_ in NONE_TYPES
|
||||||
|
|
||||||
|
|
||||||
|
def display_as_type(v: Type[Any]) -> str:
|
||||||
|
if not isinstance(v, typing_base) and not isinstance(v, WithArgsTypes) and not isinstance(v, type):
|
||||||
|
v = v.__class__
|
||||||
|
|
||||||
|
if is_union(get_origin(v)):
|
||||||
|
return f'Union[{", ".join(map(display_as_type, get_args(v)))}]'
|
||||||
|
|
||||||
|
if isinstance(v, WithArgsTypes):
|
||||||
|
# Generic alias are constructs like `list[int]`
|
||||||
|
return str(v).replace('typing.', '')
|
||||||
|
|
||||||
|
try:
|
||||||
|
return v.__name__
|
||||||
|
except AttributeError:
|
||||||
|
# happens with typing objects
|
||||||
|
return str(v).replace('typing.', '')
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_annotations(raw_annotations: Dict[str, Type[Any]], module_name: Optional[str]) -> Dict[str, Type[Any]]:
|
||||||
|
"""
|
||||||
|
Partially taken from typing.get_type_hints.
|
||||||
|
|
||||||
|
Resolve string or ForwardRef annotations into type objects if possible.
|
||||||
|
"""
|
||||||
|
base_globals: Optional[Dict[str, Any]] = None
|
||||||
|
if module_name:
|
||||||
|
try:
|
||||||
|
module = sys.modules[module_name]
|
||||||
|
except KeyError:
|
||||||
|
# happens occasionally, see https://github.com/pydantic/pydantic/issues/2363
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
base_globals = module.__dict__
|
||||||
|
|
||||||
|
annotations = {}
|
||||||
|
for name, value in raw_annotations.items():
|
||||||
|
if isinstance(value, str):
|
||||||
|
if (3, 10) > sys.version_info >= (3, 9, 8) or sys.version_info >= (3, 10, 1):
|
||||||
|
value = ForwardRef(value, is_argument=False, is_class=True)
|
||||||
|
else:
|
||||||
|
value = ForwardRef(value, is_argument=False)
|
||||||
|
try:
|
||||||
|
if sys.version_info >= (3, 13):
|
||||||
|
value = _eval_type(value, base_globals, None, type_params=())
|
||||||
|
else:
|
||||||
|
value = _eval_type(value, base_globals, None)
|
||||||
|
except NameError:
|
||||||
|
# this is ok, it can be fixed with update_forward_refs
|
||||||
|
pass
|
||||||
|
annotations[name] = value
|
||||||
|
return annotations
|
||||||
|
|
||||||
|
|
||||||
|
def is_callable_type(type_: Type[Any]) -> bool:
|
||||||
|
return type_ is Callable or get_origin(type_) is Callable
|
||||||
|
|
||||||
|
|
||||||
|
def is_literal_type(type_: Type[Any]) -> bool:
|
||||||
|
return Literal is not None and get_origin(type_) in LITERAL_TYPES
|
||||||
|
|
||||||
|
|
||||||
|
def literal_values(type_: Type[Any]) -> Tuple[Any, ...]:
|
||||||
|
return get_args(type_)
|
||||||
|
|
||||||
|
|
||||||
|
def all_literal_values(type_: Type[Any]) -> Tuple[Any, ...]:
|
||||||
|
"""
|
||||||
|
This method is used to retrieve all Literal values as
|
||||||
|
Literal can be used recursively (see https://www.python.org/dev/peps/pep-0586)
|
||||||
|
e.g. `Literal[Literal[Literal[1, 2, 3], "foo"], 5, None]`
|
||||||
|
"""
|
||||||
|
if not is_literal_type(type_):
|
||||||
|
return (type_,)
|
||||||
|
|
||||||
|
values = literal_values(type_)
|
||||||
|
return tuple(x for value in values for x in all_literal_values(value))
|
||||||
|
|
||||||
|
|
||||||
|
def is_namedtuple(type_: Type[Any]) -> bool:
|
||||||
|
"""
|
||||||
|
Check if a given class is a named tuple.
|
||||||
|
It can be either a `typing.NamedTuple` or `collections.namedtuple`
|
||||||
|
"""
|
||||||
|
from pydantic.v1.utils import lenient_issubclass
|
||||||
|
|
||||||
|
return lenient_issubclass(type_, tuple) and hasattr(type_, '_fields')
|
||||||
|
|
||||||
|
|
||||||
|
def is_typeddict(type_: Type[Any]) -> bool:
|
||||||
|
"""
|
||||||
|
Check if a given class is a typed dict (from `typing` or `typing_extensions`)
|
||||||
|
In 3.10, there will be a public method (https://docs.python.org/3.10/library/typing.html#typing.is_typeddict)
|
||||||
|
"""
|
||||||
|
from pydantic.v1.utils import lenient_issubclass
|
||||||
|
|
||||||
|
return lenient_issubclass(type_, dict) and hasattr(type_, '__total__')
|
||||||
|
|
||||||
|
|
||||||
|
def _check_typeddict_special(type_: Any) -> bool:
|
||||||
|
return type_ is TypedDictRequired or type_ is TypedDictNotRequired
|
||||||
|
|
||||||
|
|
||||||
|
def is_typeddict_special(type_: Any) -> bool:
|
||||||
|
"""
|
||||||
|
Check if type is a TypedDict special form (Required or NotRequired).
|
||||||
|
"""
|
||||||
|
return _check_typeddict_special(type_) or _check_typeddict_special(get_origin(type_))
|
||||||
|
|
||||||
|
|
||||||
|
test_type = NewType('test_type', str)
|
||||||
|
|
||||||
|
|
||||||
|
def is_new_type(type_: Type[Any]) -> bool:
|
||||||
|
"""
|
||||||
|
Check whether type_ was created using typing.NewType
|
||||||
|
"""
|
||||||
|
return isinstance(type_, test_type.__class__) and hasattr(type_, '__supertype__') # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
def new_type_supertype(type_: Type[Any]) -> Type[Any]:
|
||||||
|
while hasattr(type_, '__supertype__'):
|
||||||
|
type_ = type_.__supertype__
|
||||||
|
return type_
|
||||||
|
|
||||||
|
|
||||||
|
def _check_classvar(v: Optional[Type[Any]]) -> bool:
|
||||||
|
if v is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return v.__class__ == ClassVar.__class__ and getattr(v, '_name', None) == 'ClassVar'
|
||||||
|
|
||||||
|
|
||||||
|
def _check_finalvar(v: Optional[Type[Any]]) -> bool:
|
||||||
|
"""
|
||||||
|
Check if a given type is a `typing.Final` type.
|
||||||
|
"""
|
||||||
|
if v is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return v.__class__ == Final.__class__ and (sys.version_info < (3, 8) or getattr(v, '_name', None) == 'Final')
|
||||||
|
|
||||||
|
|
||||||
|
def is_classvar(ann_type: Type[Any]) -> bool:
|
||||||
|
if _check_classvar(ann_type) or _check_classvar(get_origin(ann_type)):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# this is an ugly workaround for class vars that contain forward references and are therefore themselves
|
||||||
|
# forward references, see #3679
|
||||||
|
if ann_type.__class__ == ForwardRef and ann_type.__forward_arg__.startswith('ClassVar['):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def is_finalvar(ann_type: Type[Any]) -> bool:
|
||||||
|
return _check_finalvar(ann_type) or _check_finalvar(get_origin(ann_type))
|
||||||
|
|
||||||
|
|
||||||
|
def update_field_forward_refs(field: 'ModelField', globalns: Any, localns: Any) -> None:
|
||||||
|
"""
|
||||||
|
Try to update ForwardRefs on fields based on this ModelField, globalns and localns.
|
||||||
|
"""
|
||||||
|
prepare = False
|
||||||
|
if field.type_.__class__ == ForwardRef:
|
||||||
|
prepare = True
|
||||||
|
field.type_ = evaluate_forwardref(field.type_, globalns, localns or None)
|
||||||
|
if field.outer_type_.__class__ == ForwardRef:
|
||||||
|
prepare = True
|
||||||
|
field.outer_type_ = evaluate_forwardref(field.outer_type_, globalns, localns or None)
|
||||||
|
if prepare:
|
||||||
|
field.prepare()
|
||||||
|
|
||||||
|
if field.sub_fields:
|
||||||
|
for sub_f in field.sub_fields:
|
||||||
|
update_field_forward_refs(sub_f, globalns=globalns, localns=localns)
|
||||||
|
|
||||||
|
if field.discriminator_key is not None:
|
||||||
|
field.prepare_discriminated_union_sub_fields()
|
||||||
|
|
||||||
|
|
||||||
|
def update_model_forward_refs(
|
||||||
|
model: Type[Any],
|
||||||
|
fields: Iterable['ModelField'],
|
||||||
|
json_encoders: Dict[Union[Type[Any], str, ForwardRef], AnyCallable],
|
||||||
|
localns: 'DictStrAny',
|
||||||
|
exc_to_suppress: Tuple[Type[BaseException], ...] = (),
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Try to update model fields ForwardRefs based on model and localns.
|
||||||
|
"""
|
||||||
|
if model.__module__ in sys.modules:
|
||||||
|
globalns = sys.modules[model.__module__].__dict__.copy()
|
||||||
|
else:
|
||||||
|
globalns = {}
|
||||||
|
|
||||||
|
globalns.setdefault(model.__name__, model)
|
||||||
|
|
||||||
|
for f in fields:
|
||||||
|
try:
|
||||||
|
update_field_forward_refs(f, globalns=globalns, localns=localns)
|
||||||
|
except exc_to_suppress:
|
||||||
|
pass
|
||||||
|
|
||||||
|
for key in set(json_encoders.keys()):
|
||||||
|
if isinstance(key, str):
|
||||||
|
fr: ForwardRef = ForwardRef(key)
|
||||||
|
elif isinstance(key, ForwardRef):
|
||||||
|
fr = key
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
new_key = evaluate_forwardref(fr, globalns, localns or None)
|
||||||
|
except exc_to_suppress: # pragma: no cover
|
||||||
|
continue
|
||||||
|
|
||||||
|
json_encoders[new_key] = json_encoders.pop(key)
|
||||||
|
|
||||||
|
|
||||||
|
def get_class(type_: Type[Any]) -> Union[None, bool, Type[Any]]:
|
||||||
|
"""
|
||||||
|
Tries to get the class of a Type[T] annotation. Returns True if Type is used
|
||||||
|
without brackets. Otherwise returns None.
|
||||||
|
"""
|
||||||
|
if type_ is type:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if get_origin(type_) is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
args = get_args(type_)
|
||||||
|
if not args or not isinstance(args[0], type):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return args[0]
|
||||||
|
|
||||||
|
|
||||||
|
def get_sub_types(tp: Any) -> List[Any]:
|
||||||
|
"""
|
||||||
|
Return all the types that are allowed by type `tp`
|
||||||
|
`tp` can be a `Union` of allowed types or an `Annotated` type
|
||||||
|
"""
|
||||||
|
origin = get_origin(tp)
|
||||||
|
if origin is Annotated:
|
||||||
|
return get_sub_types(get_args(tp)[0])
|
||||||
|
elif is_union(origin):
|
||||||
|
return [x for t in get_args(tp) for x in get_sub_types(t)]
|
||||||
|
else:
|
||||||
|
return [tp]
|
||||||
807
venv/Lib/site-packages/pydantic/v1/utils.py
Normal file
807
venv/Lib/site-packages/pydantic/v1/utils.py
Normal file
@@ -0,0 +1,807 @@
|
|||||||
|
import keyword
|
||||||
|
import warnings
|
||||||
|
import weakref
|
||||||
|
from collections import OrderedDict, defaultdict, deque
|
||||||
|
from copy import deepcopy
|
||||||
|
from itertools import islice, zip_longest
|
||||||
|
from types import BuiltinFunctionType, CodeType, FunctionType, GeneratorType, LambdaType, ModuleType
|
||||||
|
from typing import (
|
||||||
|
TYPE_CHECKING,
|
||||||
|
AbstractSet,
|
||||||
|
Any,
|
||||||
|
Callable,
|
||||||
|
Collection,
|
||||||
|
Dict,
|
||||||
|
Generator,
|
||||||
|
Iterable,
|
||||||
|
Iterator,
|
||||||
|
List,
|
||||||
|
Mapping,
|
||||||
|
NoReturn,
|
||||||
|
Optional,
|
||||||
|
Set,
|
||||||
|
Tuple,
|
||||||
|
Type,
|
||||||
|
TypeVar,
|
||||||
|
Union,
|
||||||
|
)
|
||||||
|
|
||||||
|
from typing_extensions import Annotated
|
||||||
|
|
||||||
|
from pydantic.v1.errors import ConfigError
|
||||||
|
from pydantic.v1.typing import (
|
||||||
|
NoneType,
|
||||||
|
WithArgsTypes,
|
||||||
|
all_literal_values,
|
||||||
|
display_as_type,
|
||||||
|
get_args,
|
||||||
|
get_origin,
|
||||||
|
is_literal_type,
|
||||||
|
is_union,
|
||||||
|
)
|
||||||
|
from pydantic.v1.version import version_info
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from inspect import Signature
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from pydantic.v1.config import BaseConfig
|
||||||
|
from pydantic.v1.dataclasses import Dataclass
|
||||||
|
from pydantic.v1.fields import ModelField
|
||||||
|
from pydantic.v1.main import BaseModel
|
||||||
|
from pydantic.v1.typing import AbstractSetIntStr, DictIntStrAny, IntStr, MappingIntStrAny, ReprArgs
|
||||||
|
|
||||||
|
RichReprResult = Iterable[Union[Any, Tuple[Any], Tuple[str, Any], Tuple[str, Any, Any]]]
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'import_string',
|
||||||
|
'sequence_like',
|
||||||
|
'validate_field_name',
|
||||||
|
'lenient_isinstance',
|
||||||
|
'lenient_issubclass',
|
||||||
|
'in_ipython',
|
||||||
|
'is_valid_identifier',
|
||||||
|
'deep_update',
|
||||||
|
'update_not_none',
|
||||||
|
'almost_equal_floats',
|
||||||
|
'get_model',
|
||||||
|
'to_camel',
|
||||||
|
'to_lower_camel',
|
||||||
|
'is_valid_field',
|
||||||
|
'smart_deepcopy',
|
||||||
|
'PyObjectStr',
|
||||||
|
'Representation',
|
||||||
|
'GetterDict',
|
||||||
|
'ValueItems',
|
||||||
|
'version_info', # required here to match behaviour in v1.3
|
||||||
|
'ClassAttribute',
|
||||||
|
'path_type',
|
||||||
|
'ROOT_KEY',
|
||||||
|
'get_unique_discriminator_alias',
|
||||||
|
'get_discriminator_alias_and_values',
|
||||||
|
'DUNDER_ATTRIBUTES',
|
||||||
|
)
|
||||||
|
|
||||||
|
ROOT_KEY = '__root__'
|
||||||
|
# these are types that are returned unchanged by deepcopy
|
||||||
|
IMMUTABLE_NON_COLLECTIONS_TYPES: Set[Type[Any]] = {
|
||||||
|
int,
|
||||||
|
float,
|
||||||
|
complex,
|
||||||
|
str,
|
||||||
|
bool,
|
||||||
|
bytes,
|
||||||
|
type,
|
||||||
|
NoneType,
|
||||||
|
FunctionType,
|
||||||
|
BuiltinFunctionType,
|
||||||
|
LambdaType,
|
||||||
|
weakref.ref,
|
||||||
|
CodeType,
|
||||||
|
# note: including ModuleType will differ from behaviour of deepcopy by not producing error.
|
||||||
|
# It might be not a good idea in general, but considering that this function used only internally
|
||||||
|
# against default values of fields, this will allow to actually have a field with module as default value
|
||||||
|
ModuleType,
|
||||||
|
NotImplemented.__class__,
|
||||||
|
Ellipsis.__class__,
|
||||||
|
}
|
||||||
|
|
||||||
|
# these are types that if empty, might be copied with simple copy() instead of deepcopy()
|
||||||
|
BUILTIN_COLLECTIONS: Set[Type[Any]] = {
|
||||||
|
list,
|
||||||
|
set,
|
||||||
|
tuple,
|
||||||
|
frozenset,
|
||||||
|
dict,
|
||||||
|
OrderedDict,
|
||||||
|
defaultdict,
|
||||||
|
deque,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def import_string(dotted_path: str) -> Any:
|
||||||
|
"""
|
||||||
|
Stolen approximately from django. Import a dotted module path and return the attribute/class designated by the
|
||||||
|
last name in the path. Raise ImportError if the import fails.
|
||||||
|
"""
|
||||||
|
from importlib import import_module
|
||||||
|
|
||||||
|
try:
|
||||||
|
module_path, class_name = dotted_path.strip(' ').rsplit('.', 1)
|
||||||
|
except ValueError as e:
|
||||||
|
raise ImportError(f'"{dotted_path}" doesn\'t look like a module path') from e
|
||||||
|
|
||||||
|
module = import_module(module_path)
|
||||||
|
try:
|
||||||
|
return getattr(module, class_name)
|
||||||
|
except AttributeError as e:
|
||||||
|
raise ImportError(f'Module "{module_path}" does not define a "{class_name}" attribute') from e
|
||||||
|
|
||||||
|
|
||||||
|
def truncate(v: Union[str], *, max_len: int = 80) -> str:
|
||||||
|
"""
|
||||||
|
Truncate a value and add a unicode ellipsis (three dots) to the end if it was too long
|
||||||
|
"""
|
||||||
|
warnings.warn('`truncate` is no-longer used by pydantic and is deprecated', DeprecationWarning)
|
||||||
|
if isinstance(v, str) and len(v) > (max_len - 2):
|
||||||
|
# -3 so quote + string + … + quote has correct length
|
||||||
|
return (v[: (max_len - 3)] + '…').__repr__()
|
||||||
|
try:
|
||||||
|
v = v.__repr__()
|
||||||
|
except TypeError:
|
||||||
|
v = v.__class__.__repr__(v) # in case v is a type
|
||||||
|
if len(v) > max_len:
|
||||||
|
v = v[: max_len - 1] + '…'
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
def sequence_like(v: Any) -> bool:
|
||||||
|
return isinstance(v, (list, tuple, set, frozenset, GeneratorType, deque))
|
||||||
|
|
||||||
|
|
||||||
|
def validate_field_name(bases: Iterable[Type[Any]], field_name: str) -> None:
|
||||||
|
"""
|
||||||
|
Ensure that the field's name does not shadow an existing attribute of the model.
|
||||||
|
"""
|
||||||
|
for base in bases:
|
||||||
|
if getattr(base, field_name, None):
|
||||||
|
raise NameError(
|
||||||
|
f'Field name "{field_name}" shadows a BaseModel attribute; '
|
||||||
|
f'use a different field name with "alias=\'{field_name}\'".'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def lenient_isinstance(o: Any, class_or_tuple: Union[Type[Any], Tuple[Type[Any], ...], None]) -> bool:
|
||||||
|
try:
|
||||||
|
return isinstance(o, class_or_tuple) # type: ignore[arg-type]
|
||||||
|
except TypeError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def lenient_issubclass(cls: Any, class_or_tuple: Union[Type[Any], Tuple[Type[Any], ...], None]) -> bool:
|
||||||
|
try:
|
||||||
|
return isinstance(cls, type) and issubclass(cls, class_or_tuple) # type: ignore[arg-type]
|
||||||
|
except TypeError:
|
||||||
|
if isinstance(cls, WithArgsTypes):
|
||||||
|
return False
|
||||||
|
raise # pragma: no cover
|
||||||
|
|
||||||
|
|
||||||
|
def in_ipython() -> bool:
|
||||||
|
"""
|
||||||
|
Check whether we're in an ipython environment, including jupyter notebooks.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
eval('__IPYTHON__')
|
||||||
|
except NameError:
|
||||||
|
return False
|
||||||
|
else: # pragma: no cover
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def is_valid_identifier(identifier: str) -> bool:
|
||||||
|
"""
|
||||||
|
Checks that a string is a valid identifier and not a Python keyword.
|
||||||
|
:param identifier: The identifier to test.
|
||||||
|
:return: True if the identifier is valid.
|
||||||
|
"""
|
||||||
|
return identifier.isidentifier() and not keyword.iskeyword(identifier)
|
||||||
|
|
||||||
|
|
||||||
|
KeyType = TypeVar('KeyType')
|
||||||
|
|
||||||
|
|
||||||
|
def deep_update(mapping: Dict[KeyType, Any], *updating_mappings: Dict[KeyType, Any]) -> Dict[KeyType, Any]:
|
||||||
|
updated_mapping = mapping.copy()
|
||||||
|
for updating_mapping in updating_mappings:
|
||||||
|
for k, v in updating_mapping.items():
|
||||||
|
if k in updated_mapping and isinstance(updated_mapping[k], dict) and isinstance(v, dict):
|
||||||
|
updated_mapping[k] = deep_update(updated_mapping[k], v)
|
||||||
|
else:
|
||||||
|
updated_mapping[k] = v
|
||||||
|
return updated_mapping
|
||||||
|
|
||||||
|
|
||||||
|
def update_not_none(mapping: Dict[Any, Any], **update: Any) -> None:
|
||||||
|
mapping.update({k: v for k, v in update.items() if v is not None})
|
||||||
|
|
||||||
|
|
||||||
|
def almost_equal_floats(value_1: float, value_2: float, *, delta: float = 1e-8) -> bool:
|
||||||
|
"""
|
||||||
|
Return True if two floats are almost equal
|
||||||
|
"""
|
||||||
|
return abs(value_1 - value_2) <= delta
|
||||||
|
|
||||||
|
|
||||||
|
def generate_model_signature(
|
||||||
|
init: Callable[..., None], fields: Dict[str, 'ModelField'], config: Type['BaseConfig']
|
||||||
|
) -> 'Signature':
|
||||||
|
"""
|
||||||
|
Generate signature for model based on its fields
|
||||||
|
"""
|
||||||
|
from inspect import Parameter, Signature, signature
|
||||||
|
|
||||||
|
from pydantic.v1.config import Extra
|
||||||
|
|
||||||
|
present_params = signature(init).parameters.values()
|
||||||
|
merged_params: Dict[str, Parameter] = {}
|
||||||
|
var_kw = None
|
||||||
|
use_var_kw = False
|
||||||
|
|
||||||
|
for param in islice(present_params, 1, None): # skip self arg
|
||||||
|
if param.kind is param.VAR_KEYWORD:
|
||||||
|
var_kw = param
|
||||||
|
continue
|
||||||
|
merged_params[param.name] = param
|
||||||
|
|
||||||
|
if var_kw: # if custom init has no var_kw, fields which are not declared in it cannot be passed through
|
||||||
|
allow_names = config.allow_population_by_field_name
|
||||||
|
for field_name, field in fields.items():
|
||||||
|
param_name = field.alias
|
||||||
|
if field_name in merged_params or param_name in merged_params:
|
||||||
|
continue
|
||||||
|
elif not is_valid_identifier(param_name):
|
||||||
|
if allow_names and is_valid_identifier(field_name):
|
||||||
|
param_name = field_name
|
||||||
|
else:
|
||||||
|
use_var_kw = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
# TODO: replace annotation with actual expected types once #1055 solved
|
||||||
|
kwargs = {'default': field.default} if not field.required else {}
|
||||||
|
merged_params[param_name] = Parameter(
|
||||||
|
param_name, Parameter.KEYWORD_ONLY, annotation=field.annotation, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
if config.extra is Extra.allow:
|
||||||
|
use_var_kw = True
|
||||||
|
|
||||||
|
if var_kw and use_var_kw:
|
||||||
|
# Make sure the parameter for extra kwargs
|
||||||
|
# does not have the same name as a field
|
||||||
|
default_model_signature = [
|
||||||
|
('__pydantic_self__', Parameter.POSITIONAL_OR_KEYWORD),
|
||||||
|
('data', Parameter.VAR_KEYWORD),
|
||||||
|
]
|
||||||
|
if [(p.name, p.kind) for p in present_params] == default_model_signature:
|
||||||
|
# if this is the standard model signature, use extra_data as the extra args name
|
||||||
|
var_kw_name = 'extra_data'
|
||||||
|
else:
|
||||||
|
# else start from var_kw
|
||||||
|
var_kw_name = var_kw.name
|
||||||
|
|
||||||
|
# generate a name that's definitely unique
|
||||||
|
while var_kw_name in fields:
|
||||||
|
var_kw_name += '_'
|
||||||
|
merged_params[var_kw_name] = var_kw.replace(name=var_kw_name)
|
||||||
|
|
||||||
|
return Signature(parameters=list(merged_params.values()), return_annotation=None)
|
||||||
|
|
||||||
|
|
||||||
|
def get_model(obj: Union[Type['BaseModel'], Type['Dataclass']]) -> Type['BaseModel']:
|
||||||
|
from pydantic.v1.main import BaseModel
|
||||||
|
|
||||||
|
try:
|
||||||
|
model_cls = obj.__pydantic_model__ # type: ignore
|
||||||
|
except AttributeError:
|
||||||
|
model_cls = obj
|
||||||
|
|
||||||
|
if not issubclass(model_cls, BaseModel):
|
||||||
|
raise TypeError('Unsupported type, must be either BaseModel or dataclass')
|
||||||
|
return model_cls
|
||||||
|
|
||||||
|
|
||||||
|
def to_camel(string: str) -> str:
|
||||||
|
return ''.join(word.capitalize() for word in string.split('_'))
|
||||||
|
|
||||||
|
|
||||||
|
def to_lower_camel(string: str) -> str:
|
||||||
|
if len(string) >= 1:
|
||||||
|
pascal_string = to_camel(string)
|
||||||
|
return pascal_string[0].lower() + pascal_string[1:]
|
||||||
|
return string.lower()
|
||||||
|
|
||||||
|
|
||||||
|
T = TypeVar('T')
|
||||||
|
|
||||||
|
|
||||||
|
def unique_list(
|
||||||
|
input_list: Union[List[T], Tuple[T, ...]],
|
||||||
|
*,
|
||||||
|
name_factory: Callable[[T], str] = str,
|
||||||
|
) -> List[T]:
|
||||||
|
"""
|
||||||
|
Make a list unique while maintaining order.
|
||||||
|
We update the list if another one with the same name is set
|
||||||
|
(e.g. root validator overridden in subclass)
|
||||||
|
"""
|
||||||
|
result: List[T] = []
|
||||||
|
result_names: List[str] = []
|
||||||
|
for v in input_list:
|
||||||
|
v_name = name_factory(v)
|
||||||
|
if v_name not in result_names:
|
||||||
|
result_names.append(v_name)
|
||||||
|
result.append(v)
|
||||||
|
else:
|
||||||
|
result[result_names.index(v_name)] = v
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class PyObjectStr(str):
|
||||||
|
"""
|
||||||
|
String class where repr doesn't include quotes. Useful with Representation when you want to return a string
|
||||||
|
representation of something that valid (or pseudo-valid) python.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
|
||||||
|
class Representation:
|
||||||
|
"""
|
||||||
|
Mixin to provide __str__, __repr__, and __pretty__ methods. See #884 for more details.
|
||||||
|
|
||||||
|
__pretty__ is used by [devtools](https://python-devtools.helpmanual.io/) to provide human readable representations
|
||||||
|
of objects.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__: Tuple[str, ...] = tuple()
|
||||||
|
|
||||||
|
def __repr_args__(self) -> 'ReprArgs':
|
||||||
|
"""
|
||||||
|
Returns the attributes to show in __str__, __repr__, and __pretty__ this is generally overridden.
|
||||||
|
|
||||||
|
Can either return:
|
||||||
|
* name - value pairs, e.g.: `[('foo_name', 'foo'), ('bar_name', ['b', 'a', 'r'])]`
|
||||||
|
* or, just values, e.g.: `[(None, 'foo'), (None, ['b', 'a', 'r'])]`
|
||||||
|
"""
|
||||||
|
attrs = ((s, getattr(self, s)) for s in self.__slots__)
|
||||||
|
return [(a, v) for a, v in attrs if v is not None]
|
||||||
|
|
||||||
|
def __repr_name__(self) -> str:
|
||||||
|
"""
|
||||||
|
Name of the instance's class, used in __repr__.
|
||||||
|
"""
|
||||||
|
return self.__class__.__name__
|
||||||
|
|
||||||
|
def __repr_str__(self, join_str: str) -> str:
|
||||||
|
return join_str.join(repr(v) if a is None else f'{a}={v!r}' for a, v in self.__repr_args__())
|
||||||
|
|
||||||
|
def __pretty__(self, fmt: Callable[[Any], Any], **kwargs: Any) -> Generator[Any, None, None]:
|
||||||
|
"""
|
||||||
|
Used by devtools (https://python-devtools.helpmanual.io/) to provide a human readable representations of objects
|
||||||
|
"""
|
||||||
|
yield self.__repr_name__() + '('
|
||||||
|
yield 1
|
||||||
|
for name, value in self.__repr_args__():
|
||||||
|
if name is not None:
|
||||||
|
yield name + '='
|
||||||
|
yield fmt(value)
|
||||||
|
yield ','
|
||||||
|
yield 0
|
||||||
|
yield -1
|
||||||
|
yield ')'
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.__repr_str__(' ')
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'{self.__repr_name__()}({self.__repr_str__(", ")})'
|
||||||
|
|
||||||
|
def __rich_repr__(self) -> 'RichReprResult':
|
||||||
|
"""Get fields for Rich library"""
|
||||||
|
for name, field_repr in self.__repr_args__():
|
||||||
|
if name is None:
|
||||||
|
yield field_repr
|
||||||
|
else:
|
||||||
|
yield name, field_repr
|
||||||
|
|
||||||
|
|
||||||
|
class GetterDict(Representation):
|
||||||
|
"""
|
||||||
|
Hack to make object's smell just enough like dicts for validate_model.
|
||||||
|
|
||||||
|
We can't inherit from Mapping[str, Any] because it upsets cython so we have to implement all methods ourselves.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ('_obj',)
|
||||||
|
|
||||||
|
def __init__(self, obj: Any):
|
||||||
|
self._obj = obj
|
||||||
|
|
||||||
|
def __getitem__(self, key: str) -> Any:
|
||||||
|
try:
|
||||||
|
return getattr(self._obj, key)
|
||||||
|
except AttributeError as e:
|
||||||
|
raise KeyError(key) from e
|
||||||
|
|
||||||
|
def get(self, key: Any, default: Any = None) -> Any:
|
||||||
|
return getattr(self._obj, key, default)
|
||||||
|
|
||||||
|
def extra_keys(self) -> Set[Any]:
|
||||||
|
"""
|
||||||
|
We don't want to get any other attributes of obj if the model didn't explicitly ask for them
|
||||||
|
"""
|
||||||
|
return set()
|
||||||
|
|
||||||
|
def keys(self) -> List[Any]:
|
||||||
|
"""
|
||||||
|
Keys of the pseudo dictionary, uses a list not set so order information can be maintained like python
|
||||||
|
dictionaries.
|
||||||
|
"""
|
||||||
|
return list(self)
|
||||||
|
|
||||||
|
def values(self) -> List[Any]:
|
||||||
|
return [self[k] for k in self]
|
||||||
|
|
||||||
|
def items(self) -> Iterator[Tuple[str, Any]]:
|
||||||
|
for k in self:
|
||||||
|
yield k, self.get(k)
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator[str]:
|
||||||
|
for name in dir(self._obj):
|
||||||
|
if not name.startswith('_'):
|
||||||
|
yield name
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
return sum(1 for _ in self)
|
||||||
|
|
||||||
|
def __contains__(self, item: Any) -> bool:
|
||||||
|
return item in self.keys()
|
||||||
|
|
||||||
|
def __eq__(self, other: Any) -> bool:
|
||||||
|
return dict(self) == dict(other.items())
|
||||||
|
|
||||||
|
def __repr_args__(self) -> 'ReprArgs':
|
||||||
|
return [(None, dict(self))]
|
||||||
|
|
||||||
|
def __repr_name__(self) -> str:
|
||||||
|
return f'GetterDict[{display_as_type(self._obj)}]'
|
||||||
|
|
||||||
|
|
||||||
|
class ValueItems(Representation):
|
||||||
|
"""
|
||||||
|
Class for more convenient calculation of excluded or included fields on values.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ('_items', '_type')
|
||||||
|
|
||||||
|
def __init__(self, value: Any, items: Union['AbstractSetIntStr', 'MappingIntStrAny']) -> None:
|
||||||
|
items = self._coerce_items(items)
|
||||||
|
|
||||||
|
if isinstance(value, (list, tuple)):
|
||||||
|
items = self._normalize_indexes(items, len(value))
|
||||||
|
|
||||||
|
self._items: 'MappingIntStrAny' = items
|
||||||
|
|
||||||
|
def is_excluded(self, item: Any) -> bool:
|
||||||
|
"""
|
||||||
|
Check if item is fully excluded.
|
||||||
|
|
||||||
|
:param item: key or index of a value
|
||||||
|
"""
|
||||||
|
return self.is_true(self._items.get(item))
|
||||||
|
|
||||||
|
def is_included(self, item: Any) -> bool:
|
||||||
|
"""
|
||||||
|
Check if value is contained in self._items
|
||||||
|
|
||||||
|
:param item: key or index of value
|
||||||
|
"""
|
||||||
|
return item in self._items
|
||||||
|
|
||||||
|
def for_element(self, e: 'IntStr') -> Optional[Union['AbstractSetIntStr', 'MappingIntStrAny']]:
|
||||||
|
"""
|
||||||
|
:param e: key or index of element on value
|
||||||
|
:return: raw values for element if self._items is dict and contain needed element
|
||||||
|
"""
|
||||||
|
|
||||||
|
item = self._items.get(e)
|
||||||
|
return item if not self.is_true(item) else None
|
||||||
|
|
||||||
|
def _normalize_indexes(self, items: 'MappingIntStrAny', v_length: int) -> 'DictIntStrAny':
|
||||||
|
"""
|
||||||
|
:param items: dict or set of indexes which will be normalized
|
||||||
|
:param v_length: length of sequence indexes of which will be
|
||||||
|
|
||||||
|
>>> self._normalize_indexes({0: True, -2: True, -1: True}, 4)
|
||||||
|
{0: True, 2: True, 3: True}
|
||||||
|
>>> self._normalize_indexes({'__all__': True}, 4)
|
||||||
|
{0: True, 1: True, 2: True, 3: True}
|
||||||
|
"""
|
||||||
|
|
||||||
|
normalized_items: 'DictIntStrAny' = {}
|
||||||
|
all_items = None
|
||||||
|
for i, v in items.items():
|
||||||
|
if not (isinstance(v, Mapping) or isinstance(v, AbstractSet) or self.is_true(v)):
|
||||||
|
raise TypeError(f'Unexpected type of exclude value for index "{i}" {v.__class__}')
|
||||||
|
if i == '__all__':
|
||||||
|
all_items = self._coerce_value(v)
|
||||||
|
continue
|
||||||
|
if not isinstance(i, int):
|
||||||
|
raise TypeError(
|
||||||
|
'Excluding fields from a sequence of sub-models or dicts must be performed index-wise: '
|
||||||
|
'expected integer keys or keyword "__all__"'
|
||||||
|
)
|
||||||
|
normalized_i = v_length + i if i < 0 else i
|
||||||
|
normalized_items[normalized_i] = self.merge(v, normalized_items.get(normalized_i))
|
||||||
|
|
||||||
|
if not all_items:
|
||||||
|
return normalized_items
|
||||||
|
if self.is_true(all_items):
|
||||||
|
for i in range(v_length):
|
||||||
|
normalized_items.setdefault(i, ...)
|
||||||
|
return normalized_items
|
||||||
|
for i in range(v_length):
|
||||||
|
normalized_item = normalized_items.setdefault(i, {})
|
||||||
|
if not self.is_true(normalized_item):
|
||||||
|
normalized_items[i] = self.merge(all_items, normalized_item)
|
||||||
|
return normalized_items
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def merge(cls, base: Any, override: Any, intersect: bool = False) -> Any:
|
||||||
|
"""
|
||||||
|
Merge a ``base`` item with an ``override`` item.
|
||||||
|
|
||||||
|
Both ``base`` and ``override`` are converted to dictionaries if possible.
|
||||||
|
Sets are converted to dictionaries with the sets entries as keys and
|
||||||
|
Ellipsis as values.
|
||||||
|
|
||||||
|
Each key-value pair existing in ``base`` is merged with ``override``,
|
||||||
|
while the rest of the key-value pairs are updated recursively with this function.
|
||||||
|
|
||||||
|
Merging takes place based on the "union" of keys if ``intersect`` is
|
||||||
|
set to ``False`` (default) and on the intersection of keys if
|
||||||
|
``intersect`` is set to ``True``.
|
||||||
|
"""
|
||||||
|
override = cls._coerce_value(override)
|
||||||
|
base = cls._coerce_value(base)
|
||||||
|
if override is None:
|
||||||
|
return base
|
||||||
|
if cls.is_true(base) or base is None:
|
||||||
|
return override
|
||||||
|
if cls.is_true(override):
|
||||||
|
return base if intersect else override
|
||||||
|
|
||||||
|
# intersection or union of keys while preserving ordering:
|
||||||
|
if intersect:
|
||||||
|
merge_keys = [k for k in base if k in override] + [k for k in override if k in base]
|
||||||
|
else:
|
||||||
|
merge_keys = list(base) + [k for k in override if k not in base]
|
||||||
|
|
||||||
|
merged: 'DictIntStrAny' = {}
|
||||||
|
for k in merge_keys:
|
||||||
|
merged_item = cls.merge(base.get(k), override.get(k), intersect=intersect)
|
||||||
|
if merged_item is not None:
|
||||||
|
merged[k] = merged_item
|
||||||
|
|
||||||
|
return merged
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _coerce_items(items: Union['AbstractSetIntStr', 'MappingIntStrAny']) -> 'MappingIntStrAny':
|
||||||
|
if isinstance(items, Mapping):
|
||||||
|
pass
|
||||||
|
elif isinstance(items, AbstractSet):
|
||||||
|
items = dict.fromkeys(items, ...)
|
||||||
|
else:
|
||||||
|
class_name = getattr(items, '__class__', '???')
|
||||||
|
assert_never(
|
||||||
|
items,
|
||||||
|
f'Unexpected type of exclude value {class_name}',
|
||||||
|
)
|
||||||
|
return items
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _coerce_value(cls, value: Any) -> Any:
|
||||||
|
if value is None or cls.is_true(value):
|
||||||
|
return value
|
||||||
|
return cls._coerce_items(value)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_true(v: Any) -> bool:
|
||||||
|
return v is True or v is ...
|
||||||
|
|
||||||
|
def __repr_args__(self) -> 'ReprArgs':
|
||||||
|
return [(None, self._items)]
|
||||||
|
|
||||||
|
|
||||||
|
class ClassAttribute:
|
||||||
|
"""
|
||||||
|
Hide class attribute from its instances
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = (
|
||||||
|
'name',
|
||||||
|
'value',
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, name: str, value: Any) -> None:
|
||||||
|
self.name = name
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def __get__(self, instance: Any, owner: Type[Any]) -> None:
|
||||||
|
if instance is None:
|
||||||
|
return self.value
|
||||||
|
raise AttributeError(f'{self.name!r} attribute of {owner.__name__!r} is class-only')
|
||||||
|
|
||||||
|
|
||||||
|
path_types = {
|
||||||
|
'is_dir': 'directory',
|
||||||
|
'is_file': 'file',
|
||||||
|
'is_mount': 'mount point',
|
||||||
|
'is_symlink': 'symlink',
|
||||||
|
'is_block_device': 'block device',
|
||||||
|
'is_char_device': 'char device',
|
||||||
|
'is_fifo': 'FIFO',
|
||||||
|
'is_socket': 'socket',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def path_type(p: 'Path') -> str:
|
||||||
|
"""
|
||||||
|
Find out what sort of thing a path is.
|
||||||
|
"""
|
||||||
|
assert p.exists(), 'path does not exist'
|
||||||
|
for method, name in path_types.items():
|
||||||
|
if getattr(p, method)():
|
||||||
|
return name
|
||||||
|
|
||||||
|
return 'unknown'
|
||||||
|
|
||||||
|
|
||||||
|
Obj = TypeVar('Obj')
|
||||||
|
|
||||||
|
|
||||||
|
def smart_deepcopy(obj: Obj) -> Obj:
|
||||||
|
"""
|
||||||
|
Return type as is for immutable built-in types
|
||||||
|
Use obj.copy() for built-in empty collections
|
||||||
|
Use copy.deepcopy() for non-empty collections and unknown objects
|
||||||
|
"""
|
||||||
|
|
||||||
|
obj_type = obj.__class__
|
||||||
|
if obj_type in IMMUTABLE_NON_COLLECTIONS_TYPES:
|
||||||
|
return obj # fastest case: obj is immutable and not collection therefore will not be copied anyway
|
||||||
|
try:
|
||||||
|
if not obj and obj_type in BUILTIN_COLLECTIONS:
|
||||||
|
# faster way for empty collections, no need to copy its members
|
||||||
|
return obj if obj_type is tuple else obj.copy() # type: ignore # tuple doesn't have copy method
|
||||||
|
except (TypeError, ValueError, RuntimeError):
|
||||||
|
# do we really dare to catch ALL errors? Seems a bit risky
|
||||||
|
pass
|
||||||
|
|
||||||
|
return deepcopy(obj) # slowest way when we actually might need a deepcopy
|
||||||
|
|
||||||
|
|
||||||
|
def is_valid_field(name: str) -> bool:
|
||||||
|
if not name.startswith('_'):
|
||||||
|
return True
|
||||||
|
return ROOT_KEY == name
|
||||||
|
|
||||||
|
|
||||||
|
DUNDER_ATTRIBUTES = {
|
||||||
|
'__annotations__',
|
||||||
|
'__classcell__',
|
||||||
|
'__doc__',
|
||||||
|
'__module__',
|
||||||
|
'__orig_bases__',
|
||||||
|
'__orig_class__',
|
||||||
|
'__qualname__',
|
||||||
|
'__firstlineno__',
|
||||||
|
'__static_attributes__',
|
||||||
|
'__classdictcell__',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def is_valid_private_name(name: str) -> bool:
|
||||||
|
return not is_valid_field(name) and name not in DUNDER_ATTRIBUTES
|
||||||
|
|
||||||
|
|
||||||
|
_EMPTY = object()
|
||||||
|
|
||||||
|
|
||||||
|
def all_identical(left: Iterable[Any], right: Iterable[Any]) -> bool:
|
||||||
|
"""
|
||||||
|
Check that the items of `left` are the same objects as those in `right`.
|
||||||
|
|
||||||
|
>>> a, b = object(), object()
|
||||||
|
>>> all_identical([a, b, a], [a, b, a])
|
||||||
|
True
|
||||||
|
>>> all_identical([a, b, [a]], [a, b, [a]]) # new list object, while "equal" is not "identical"
|
||||||
|
False
|
||||||
|
"""
|
||||||
|
for left_item, right_item in zip_longest(left, right, fillvalue=_EMPTY):
|
||||||
|
if left_item is not right_item:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def assert_never(obj: NoReturn, msg: str) -> NoReturn:
|
||||||
|
"""
|
||||||
|
Helper to make sure that we have covered all possible types.
|
||||||
|
|
||||||
|
This is mostly useful for ``mypy``, docs:
|
||||||
|
https://mypy.readthedocs.io/en/latest/literal_types.html#exhaustive-checks
|
||||||
|
"""
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def get_unique_discriminator_alias(all_aliases: Collection[str], discriminator_key: str) -> str:
|
||||||
|
"""Validate that all aliases are the same and if that's the case return the alias"""
|
||||||
|
unique_aliases = set(all_aliases)
|
||||||
|
if len(unique_aliases) > 1:
|
||||||
|
raise ConfigError(
|
||||||
|
f'Aliases for discriminator {discriminator_key!r} must be the same (got {", ".join(sorted(all_aliases))})'
|
||||||
|
)
|
||||||
|
return unique_aliases.pop()
|
||||||
|
|
||||||
|
|
||||||
|
def get_discriminator_alias_and_values(tp: Any, discriminator_key: str) -> Tuple[str, Tuple[str, ...]]:
|
||||||
|
"""
|
||||||
|
Get alias and all valid values in the `Literal` type of the discriminator field
|
||||||
|
`tp` can be a `BaseModel` class or directly an `Annotated` `Union` of many.
|
||||||
|
"""
|
||||||
|
is_root_model = getattr(tp, '__custom_root_type__', False)
|
||||||
|
|
||||||
|
if get_origin(tp) is Annotated:
|
||||||
|
tp = get_args(tp)[0]
|
||||||
|
|
||||||
|
if hasattr(tp, '__pydantic_model__'):
|
||||||
|
tp = tp.__pydantic_model__
|
||||||
|
|
||||||
|
if is_union(get_origin(tp)):
|
||||||
|
alias, all_values = _get_union_alias_and_all_values(tp, discriminator_key)
|
||||||
|
return alias, tuple(v for values in all_values for v in values)
|
||||||
|
elif is_root_model:
|
||||||
|
union_type = tp.__fields__[ROOT_KEY].type_
|
||||||
|
alias, all_values = _get_union_alias_and_all_values(union_type, discriminator_key)
|
||||||
|
|
||||||
|
if len(set(all_values)) > 1:
|
||||||
|
raise ConfigError(
|
||||||
|
f'Field {discriminator_key!r} is not the same for all submodels of {display_as_type(tp)!r}'
|
||||||
|
)
|
||||||
|
|
||||||
|
return alias, all_values[0]
|
||||||
|
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
t_discriminator_type = tp.__fields__[discriminator_key].type_
|
||||||
|
except AttributeError as e:
|
||||||
|
raise TypeError(f'Type {tp.__name__!r} is not a valid `BaseModel` or `dataclass`') from e
|
||||||
|
except KeyError as e:
|
||||||
|
raise ConfigError(f'Model {tp.__name__!r} needs a discriminator field for key {discriminator_key!r}') from e
|
||||||
|
|
||||||
|
if not is_literal_type(t_discriminator_type):
|
||||||
|
raise ConfigError(f'Field {discriminator_key!r} of model {tp.__name__!r} needs to be a `Literal`')
|
||||||
|
|
||||||
|
return tp.__fields__[discriminator_key].alias, all_literal_values(t_discriminator_type)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_union_alias_and_all_values(
|
||||||
|
union_type: Type[Any], discriminator_key: str
|
||||||
|
) -> Tuple[str, Tuple[Tuple[str, ...], ...]]:
|
||||||
|
zipped_aliases_values = [get_discriminator_alias_and_values(t, discriminator_key) for t in get_args(union_type)]
|
||||||
|
# unzip: [('alias_a',('v1', 'v2)), ('alias_b', ('v3',))] => [('alias_a', 'alias_b'), (('v1', 'v2'), ('v3',))]
|
||||||
|
all_aliases, all_values = zip(*zipped_aliases_values)
|
||||||
|
return get_unique_discriminator_alias(all_aliases, discriminator_key), all_values
|
||||||
Reference in New Issue
Block a user