Coverage for pydantic/utils.py: 100.00%
355 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-08-15 13:26 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2024-08-15 13:26 +0000
1import keyword 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
2import warnings 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
3import weakref 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
4from collections import OrderedDict, defaultdict, deque 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
5from copy import deepcopy 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
6from itertools import islice, zip_longest 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
7from types import BuiltinFunctionType, CodeType, FunctionType, GeneratorType, LambdaType, ModuleType 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
8from typing import ( 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
9 TYPE_CHECKING,
10 AbstractSet,
11 Any,
12 Callable,
13 Collection,
14 Dict,
15 Generator,
16 Iterable,
17 Iterator,
18 List,
19 Mapping,
20 NoReturn,
21 Optional,
22 Set,
23 Tuple,
24 Type,
25 TypeVar,
26 Union,
27)
29from typing_extensions import Annotated 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
31from pydantic.errors import ConfigError 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
32from pydantic.typing import ( 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
33 NoneType,
34 WithArgsTypes,
35 all_literal_values,
36 display_as_type,
37 get_args,
38 get_origin,
39 is_literal_type,
40 is_union,
41)
42from pydantic.version import version_info 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
44if TYPE_CHECKING: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
45 from inspect import Signature
46 from pathlib import Path
48 from pydantic.config import BaseConfig
49 from pydantic.dataclasses import Dataclass
50 from pydantic.fields import ModelField
51 from pydantic.main import BaseModel
52 from pydantic.typing import AbstractSetIntStr, DictIntStrAny, IntStr, MappingIntStrAny, ReprArgs
54 RichReprResult = Iterable[Union[Any, Tuple[Any], Tuple[str, Any], Tuple[str, Any, Any]]]
56__all__ = ( 1sBdjagkvlwtCembhnxoyPQRSTUuDfpciqzrA
57 'import_string',
58 'sequence_like',
59 'validate_field_name',
60 'lenient_isinstance',
61 'lenient_issubclass',
62 'in_ipython',
63 'is_valid_identifier',
64 'deep_update',
65 'update_not_none',
66 'almost_equal_floats',
67 'get_model',
68 'to_camel',
69 'to_lower_camel',
70 'is_valid_field',
71 'smart_deepcopy',
72 'PyObjectStr',
73 'Representation',
74 'GetterDict',
75 'ValueItems',
76 'version_info', # required here to match behaviour in v1.3
77 'ClassAttribute',
78 'path_type',
79 'ROOT_KEY',
80 'get_unique_discriminator_alias',
81 'get_discriminator_alias_and_values',
82 'DUNDER_ATTRIBUTES',
83)
85ROOT_KEY = '__root__' 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
86# these are types that are returned unchanged by deepcopy
87IMMUTABLE_NON_COLLECTIONS_TYPES: Set[Type[Any]] = { 1sBdjagkvlwtCembhnxoyPQRSTUuDfpciqzrA
88 int,
89 float,
90 complex,
91 str,
92 bool,
93 bytes,
94 type,
95 NoneType,
96 FunctionType,
97 BuiltinFunctionType,
98 LambdaType,
99 weakref.ref,
100 CodeType,
101 # note: including ModuleType will differ from behaviour of deepcopy by not producing error.
102 # It might be not a good idea in general, but considering that this function used only internally
103 # against default values of fields, this will allow to actually have a field with module as default value
104 ModuleType,
105 NotImplemented.__class__,
106 Ellipsis.__class__,
107}
109# these are types that if empty, might be copied with simple copy() instead of deepcopy()
110BUILTIN_COLLECTIONS: Set[Type[Any]] = { 1sBdjagkvlwtCembhnxoyPQRSTUuDfpciqzrA
111 list,
112 set,
113 tuple,
114 frozenset,
115 dict,
116 OrderedDict,
117 defaultdict,
118 deque,
119}
122def import_string(dotted_path: str) -> Any: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
123 """
124 Stolen approximately from django. Import a dotted module path and return the attribute/class designated by the
125 last name in the path. Raise ImportError if the import fails.
126 """
127 from importlib import import_module 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
129 try: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
130 module_path, class_name = dotted_path.strip(' ').rsplit('.', 1) 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
131 except ValueError as e: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
132 raise ImportError(f'"{dotted_path}" doesn\'t look like a module path') from e 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
134 module = import_module(module_path) 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
135 try: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
136 return getattr(module, class_name) 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
137 except AttributeError as e: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
138 raise ImportError(f'Module "{module_path}" does not define a "{class_name}" attribute') from e 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
141def truncate(v: Union[str], *, max_len: int = 80) -> str: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
142 """
143 Truncate a value and add a unicode ellipsis (three dots) to the end if it was too long
144 """
145 warnings.warn('`truncate` is no-longer used by pydantic and is deprecated', DeprecationWarning) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
146 if isinstance(v, str) and len(v) > (max_len - 2): 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
147 # -3 so quote + string + … + quote has correct length
148 return (v[: (max_len - 3)] + '…').__repr__() 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
149 try: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
150 v = v.__repr__() 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
151 except TypeError: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
152 v = v.__class__.__repr__(v) # in case v is a type 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
153 if len(v) > max_len: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
154 v = v[: max_len - 1] + '…' 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
155 return v 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
158def sequence_like(v: Any) -> bool: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
159 return isinstance(v, (list, tuple, set, frozenset, GeneratorType, deque)) 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
162def validate_field_name(bases: List[Type['BaseModel']], field_name: str) -> None: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
163 """
164 Ensure that the field's name does not shadow an existing attribute of the model.
165 """
166 for base in bases: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
167 if getattr(base, field_name, None): 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
168 raise NameError( 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
169 f'Field name "{field_name}" shadows a BaseModel attribute; '
170 f'use a different field name with "alias=\'{field_name}\'".'
171 )
174def lenient_isinstance(o: Any, class_or_tuple: Union[Type[Any], Tuple[Type[Any], ...], None]) -> bool: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
175 try: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
176 return isinstance(o, class_or_tuple) # type: ignore[arg-type] 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
177 except TypeError: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
178 return False 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
181def lenient_issubclass(cls: Any, class_or_tuple: Union[Type[Any], Tuple[Type[Any], ...], None]) -> bool: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
182 try: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
183 return isinstance(cls, type) and issubclass(cls, class_or_tuple) # type: ignore[arg-type] 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
184 except TypeError: 1djagembhfpci
185 if isinstance(cls, WithArgsTypes): 1djagembhfpci
186 return False 1djagembhfpci
187 raise # pragma: no cover
190def in_ipython() -> bool: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
191 """
192 Check whether we're in an ipython environment, including jupyter notebooks.
193 """
194 try: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
195 eval('__IPYTHON__') 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
196 except NameError: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
197 return False 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
198 else: # pragma: no cover
199 return True
202def is_valid_identifier(identifier: str) -> bool: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
203 """
204 Checks that a string is a valid identifier and not a Python keyword.
205 :param identifier: The identifier to test.
206 :return: True if the identifier is valid.
207 """
208 return identifier.isidentifier() and not keyword.iskeyword(identifier) 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
211KeyType = TypeVar('KeyType') 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
214def deep_update(mapping: Dict[KeyType, Any], *updating_mappings: Dict[KeyType, Any]) -> Dict[KeyType, Any]: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
215 updated_mapping = mapping.copy() 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
216 for updating_mapping in updating_mappings: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
217 for k, v in updating_mapping.items(): 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
218 if k in updated_mapping and isinstance(updated_mapping[k], dict) and isinstance(v, dict): 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
219 updated_mapping[k] = deep_update(updated_mapping[k], v) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
220 else:
221 updated_mapping[k] = v 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
222 return updated_mapping 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
225def update_not_none(mapping: Dict[Any, Any], **update: Any) -> None: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
226 mapping.update({k: v for k, v in update.items() if v is not None}) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
229def almost_equal_floats(value_1: float, value_2: float, *, delta: float = 1e-8) -> bool: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
230 """
231 Return True if two floats are almost equal
232 """
233 return abs(value_1 - value_2) <= delta 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
236def generate_model_signature( 1sBdjagkvlwtCembhnxoyEFGHPQRSTUIuDfpciqzrA
237 init: Callable[..., None], fields: Dict[str, 'ModelField'], config: Type['BaseConfig']
238) -> 'Signature':
239 """
240 Generate signature for model based on its fields
241 """
242 from inspect import Parameter, Signature, signature 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
244 from pydantic.config import Extra 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
246 present_params = signature(init).parameters.values() 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
247 merged_params: Dict[str, Parameter] = {} 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
248 var_kw = None 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
249 use_var_kw = False 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
251 for param in islice(present_params, 1, None): # skip self arg 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
252 if param.kind is param.VAR_KEYWORD: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
253 var_kw = param 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
254 continue 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
255 merged_params[param.name] = param 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
257 if var_kw: # if custom init has no var_kw, fields which are not declared in it cannot be passed through 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
258 allow_names = config.allow_population_by_field_name 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
259 for field_name, field in fields.items(): 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
260 param_name = field.alias 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
261 if field_name in merged_params or param_name in merged_params: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
262 continue 1agkvlwbhnxoyEFGHIciqzrA
263 elif not is_valid_identifier(param_name): 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
264 if allow_names and is_valid_identifier(field_name): 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
265 param_name = field_name 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
266 else:
267 use_var_kw = True 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
268 continue 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
270 # TODO: replace annotation with actual expected types once #1055 solved
271 kwargs = {'default': field.default} if not field.required else {} 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
272 merged_params[param_name] = Parameter( 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
273 param_name, Parameter.KEYWORD_ONLY, annotation=field.annotation, **kwargs
274 )
276 if config.extra is Extra.allow: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
277 use_var_kw = True 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
279 if var_kw and use_var_kw: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
280 # Make sure the parameter for extra kwargs
281 # does not have the same name as a field
282 default_model_signature = [ 1sBdjagkvlwtCembhnxoyEFGHIuDfpciqzrA
283 ('__pydantic_self__', Parameter.POSITIONAL_OR_KEYWORD),
284 ('data', Parameter.VAR_KEYWORD),
285 ]
286 if [(p.name, p.kind) for p in present_params] == default_model_signature: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
287 # if this is the standard model signature, use extra_data as the extra args name
288 var_kw_name = 'extra_data' 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
289 else:
290 # else start from var_kw
291 var_kw_name = var_kw.name 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
293 # generate a name that's definitely unique
294 while var_kw_name in fields: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
295 var_kw_name += '_' 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
296 merged_params[var_kw_name] = var_kw.replace(name=var_kw_name) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
298 return Signature(parameters=list(merged_params.values()), return_annotation=None) 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
301def get_model(obj: Union[Type['BaseModel'], Type['Dataclass']]) -> Type['BaseModel']: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
302 from pydantic.main import BaseModel 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
304 try: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
305 model_cls = obj.__pydantic_model__ # type: ignore 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
306 except AttributeError: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
307 model_cls = obj 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
309 if not issubclass(model_cls, BaseModel): 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
310 raise TypeError('Unsupported type, must be either BaseModel or dataclass') 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
311 return model_cls 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
314def to_camel(string: str) -> str: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
315 return ''.join(word.capitalize() for word in string.split('_')) 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
318def to_lower_camel(string: str) -> str: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
319 if len(string) >= 1: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
320 pascal_string = to_camel(string) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
321 return pascal_string[0].lower() + pascal_string[1:] 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
322 return string.lower() 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
325T = TypeVar('T') 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
328def unique_list( 1sBdjagkvlwtCembhnxoyEFGHPQRSTUIuDfpciqzrA
329 input_list: Union[List[T], Tuple[T, ...]],
330 *,
331 name_factory: Callable[[T], str] = str,
332) -> List[T]:
333 """
334 Make a list unique while maintaining order.
335 We update the list if another one with the same name is set
336 (e.g. root validator overridden in subclass)
337 """
338 result: List[T] = [] 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
339 result_names: List[str] = [] 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
340 for v in input_list: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
341 v_name = name_factory(v) 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
342 if v_name not in result_names: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
343 result_names.append(v_name) 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
344 result.append(v) 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
345 else:
346 result[result_names.index(v_name)] = v 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
348 return result 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
351class PyObjectStr(str): 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
352 """
353 String class where repr doesn't include quotes. Useful with Representation when you want to return a string
354 representation of something that valid (or pseudo-valid) python.
355 """
357 def __repr__(self) -> str: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
358 return str(self) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
361class Representation: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
362 """
363 Mixin to provide __str__, __repr__, and __pretty__ methods. See #884 for more details.
365 __pretty__ is used by [devtools](https://python-devtools.helpmanual.io/) to provide human readable representations
366 of objects.
367 """
369 __slots__: Tuple[str, ...] = tuple() 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
371 def __repr_args__(self) -> 'ReprArgs': 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
372 """
373 Returns the attributes to show in __str__, __repr__, and __pretty__ this is generally overridden.
375 Can either return:
376 * name - value pairs, e.g.: `[('foo_name', 'foo'), ('bar_name', ['b', 'a', 'r'])]`
377 * or, just values, e.g.: `[(None, 'foo'), (None, ['b', 'a', 'r'])]`
378 """
379 attrs = ((s, getattr(self, s)) for s in self.__slots__) 1JsdaklKtebnoEFGHILufcqr
380 return [(a, v) for a, v in attrs if v is not None] 1JsdaklKtebnoEFGHILufcqr
382 def __repr_name__(self) -> str: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
383 """
384 Name of the instance's class, used in __repr__.
385 """
386 return self.__class__.__name__ 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
388 def __repr_str__(self, join_str: str) -> str: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
389 return join_str.join(repr(v) if a is None else f'{a}={v!r}' for a, v in self.__repr_args__()) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
391 def __pretty__(self, fmt: Callable[[Any], Any], **kwargs: Any) -> Generator[Any, None, None]: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
392 """
393 Used by devtools (https://python-devtools.helpmanual.io/) to provide a human readable representations of objects
394 """
395 yield self.__repr_name__() + '(' 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
396 yield 1 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
397 for name, value in self.__repr_args__(): 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
398 if name is not None: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
399 yield name + '=' 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
400 yield fmt(value) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
401 yield ',' 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
402 yield 0 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
403 yield -1 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
404 yield ')' 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
406 def __str__(self) -> str: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
407 return self.__repr_str__(' ') 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
409 def __repr__(self) -> str: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
410 return f'{self.__repr_name__()}({self.__repr_str__(", ")})' 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
412 def __rich_repr__(self) -> 'RichReprResult': 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
413 """Get fields for Rich library"""
414 for name, field_repr in self.__repr_args__(): 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
415 if name is None: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
416 yield field_repr 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
417 else:
418 yield name, field_repr 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
421class GetterDict(Representation): 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
422 """
423 Hack to make object's smell just enough like dicts for validate_model.
425 We can't inherit from Mapping[str, Any] because it upsets cython so we have to implement all methods ourselves.
426 """
428 __slots__ = ('_obj',) 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
430 def __init__(self, obj: Any): 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
431 self._obj = obj 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
433 def __getitem__(self, key: str) -> Any: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
434 try: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
435 return getattr(self._obj, key) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
436 except AttributeError as e: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
437 raise KeyError(key) from e 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
439 def get(self, key: Any, default: Any = None) -> Any: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
440 return getattr(self._obj, key, default) 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
442 def extra_keys(self) -> Set[Any]: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
443 """
444 We don't want to get any other attributes of obj if the model didn't explicitly ask for them
445 """
446 return set() 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
448 def keys(self) -> List[Any]: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
449 """
450 Keys of the pseudo dictionary, uses a list not set so order information can be maintained like python
451 dictionaries.
452 """
453 return list(self) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
455 def values(self) -> List[Any]: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
456 return [self[k] for k in self] 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
458 def items(self) -> Iterator[Tuple[str, Any]]: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
459 for k in self: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
460 yield k, self.get(k) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
462 def __iter__(self) -> Iterator[str]: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
463 for name in dir(self._obj): 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
464 if not name.startswith('_'): 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
465 yield name 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
467 def __len__(self) -> int: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
468 return sum(1 for _ in self) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
470 def __contains__(self, item: Any) -> bool: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
471 return item in self.keys() 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
473 def __eq__(self, other: Any) -> bool: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
474 return dict(self) == dict(other.items()) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
476 def __repr_args__(self) -> 'ReprArgs': 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
477 return [(None, dict(self))] 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
479 def __repr_name__(self) -> str: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
480 return f'GetterDict[{display_as_type(self._obj)}]' 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
483class ValueItems(Representation): 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
484 """
485 Class for more convenient calculation of excluded or included fields on values.
486 """
488 __slots__ = ('_items', '_type') 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
490 def __init__(self, value: Any, items: Union['AbstractSetIntStr', 'MappingIntStrAny']) -> None: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
491 items = self._coerce_items(items) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
493 if isinstance(value, (list, tuple)): 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
494 items = self._normalize_indexes(items, len(value)) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
496 self._items: 'MappingIntStrAny' = items 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
498 def is_excluded(self, item: Any) -> bool: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
499 """
500 Check if item is fully excluded.
502 :param item: key or index of a value
503 """
504 return self.is_true(self._items.get(item)) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
506 def is_included(self, item: Any) -> bool: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
507 """
508 Check if value is contained in self._items
510 :param item: key or index of value
511 """
512 return item in self._items 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
514 def for_element(self, e: 'IntStr') -> Optional[Union['AbstractSetIntStr', 'MappingIntStrAny']]: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
515 """
516 :param e: key or index of element on value
517 :return: raw values for element if self._items is dict and contain needed element
518 """
520 item = self._items.get(e) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
521 return item if not self.is_true(item) else None 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
523 def _normalize_indexes(self, items: 'MappingIntStrAny', v_length: int) -> 'DictIntStrAny': 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
524 """
525 :param items: dict or set of indexes which will be normalized
526 :param v_length: length of sequence indexes of which will be
528 >>> self._normalize_indexes({0: True, -2: True, -1: True}, 4)
529 {0: True, 2: True, 3: True}
530 >>> self._normalize_indexes({'__all__': True}, 4)
531 {0: True, 1: True, 2: True, 3: True}
532 """
534 normalized_items: 'DictIntStrAny' = {} 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
535 all_items = None 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
536 for i, v in items.items(): 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
537 if not (isinstance(v, Mapping) or isinstance(v, AbstractSet) or self.is_true(v)): 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
538 raise TypeError(f'Unexpected type of exclude value for index "{i}" {v.__class__}') 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
539 if i == '__all__': 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
540 all_items = self._coerce_value(v) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
541 continue 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
542 if not isinstance(i, int): 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
543 raise TypeError( 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
544 'Excluding fields from a sequence of sub-models or dicts must be performed index-wise: '
545 'expected integer keys or keyword "__all__"'
546 )
547 normalized_i = v_length + i if i < 0 else i 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
548 normalized_items[normalized_i] = self.merge(v, normalized_items.get(normalized_i)) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
550 if not all_items: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
551 return normalized_items 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
552 if self.is_true(all_items): 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
553 for i in range(v_length): 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
554 normalized_items.setdefault(i, ...) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
555 return normalized_items 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
556 for i in range(v_length): 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
557 normalized_item = normalized_items.setdefault(i, {}) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
558 if not self.is_true(normalized_item): 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
559 normalized_items[i] = self.merge(all_items, normalized_item) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
560 return normalized_items 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
562 @classmethod 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
563 def merge(cls, base: Any, override: Any, intersect: bool = False) -> Any: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
564 """
565 Merge a ``base`` item with an ``override`` item.
567 Both ``base`` and ``override`` are converted to dictionaries if possible.
568 Sets are converted to dictionaries with the sets entries as keys and
569 Ellipsis as values.
571 Each key-value pair existing in ``base`` is merged with ``override``,
572 while the rest of the key-value pairs are updated recursively with this function.
574 Merging takes place based on the "union" of keys if ``intersect`` is
575 set to ``False`` (default) and on the intersection of keys if
576 ``intersect`` is set to ``True``.
577 """
578 override = cls._coerce_value(override) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
579 base = cls._coerce_value(base) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
580 if override is None: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
581 return base 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
582 if cls.is_true(base) or base is None: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
583 return override 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
584 if cls.is_true(override): 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
585 return base if intersect else override 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
587 # intersection or union of keys while preserving ordering:
588 if intersect: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
589 merge_keys = [k for k in base if k in override] + [k for k in override if k in base] 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
590 else:
591 merge_keys = list(base) + [k for k in override if k not in base] 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
593 merged: 'DictIntStrAny' = {} 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
594 for k in merge_keys: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
595 merged_item = cls.merge(base.get(k), override.get(k), intersect=intersect) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
596 if merged_item is not None: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
597 merged[k] = merged_item 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
599 return merged 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
601 @staticmethod 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
602 def _coerce_items(items: Union['AbstractSetIntStr', 'MappingIntStrAny']) -> 'MappingIntStrAny': 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
603 if isinstance(items, Mapping): 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
604 pass 1JMsBdjagkvlwKNtCembhnxoyLOuDfpciqzrA
605 elif isinstance(items, AbstractSet): 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
606 items = dict.fromkeys(items, ...) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
607 else:
608 class_name = getattr(items, '__class__', '???') 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
609 assert_never( 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
610 items,
611 f'Unexpected type of exclude value {class_name}',
612 )
613 return items 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
615 @classmethod 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
616 def _coerce_value(cls, value: Any) -> Any: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
617 if value is None or cls.is_true(value): 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
618 return value 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
619 return cls._coerce_items(value) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
621 @staticmethod 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
622 def is_true(v: Any) -> bool: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
623 return v is True or v is ... 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
625 def __repr_args__(self) -> 'ReprArgs': 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
626 return [(None, self._items)] 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
629class ClassAttribute: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
630 """
631 Hide class attribute from its instances
632 """
634 __slots__ = ( 1sBdjagkvlwtCembhnxoyPQRSTUuDfpciqzrA
635 'name',
636 'value',
637 )
639 def __init__(self, name: str, value: Any) -> None: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
640 self.name = name 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
641 self.value = value 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
643 def __get__(self, instance: Any, owner: Type[Any]) -> None: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
644 if instance is None: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
645 return self.value 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
646 raise AttributeError(f'{self.name!r} attribute of {owner.__name__!r} is class-only') 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
649path_types = { 1sBdjagkvlwtCembhnxoyPQRSTUuDfpciqzrA
650 'is_dir': 'directory',
651 'is_file': 'file',
652 'is_mount': 'mount point',
653 'is_symlink': 'symlink',
654 'is_block_device': 'block device',
655 'is_char_device': 'char device',
656 'is_fifo': 'FIFO',
657 'is_socket': 'socket',
658}
661def path_type(p: 'Path') -> str: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
662 """
663 Find out what sort of thing a path is.
664 """
665 assert p.exists(), 'path does not exist' 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
666 for method, name in path_types.items(): 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
667 if getattr(p, method)(): 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
668 return name 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
670 return 'unknown' 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
673Obj = TypeVar('Obj') 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
676def smart_deepcopy(obj: Obj) -> Obj: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
677 """
678 Return type as is for immutable built-in types
679 Use obj.copy() for built-in empty collections
680 Use copy.deepcopy() for non-empty collections and unknown objects
681 """
683 obj_type = obj.__class__ 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
684 if obj_type in IMMUTABLE_NON_COLLECTIONS_TYPES: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
685 return obj # fastest case: obj is immutable and not collection therefore will not be copied anyway 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
686 try: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
687 if not obj and obj_type in BUILTIN_COLLECTIONS: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
688 # faster way for empty collections, no need to copy its members
689 return obj if obj_type is tuple else obj.copy() # type: ignore # tuple doesn't have copy method 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
690 except (TypeError, ValueError, RuntimeError): 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
691 # do we really dare to catch ALL errors? Seems a bit risky
692 pass 1JMsBdjagkvlwKNtCembhnxoyLOuDfpciqzrA
694 return deepcopy(obj) # slowest way when we actually might need a deepcopy 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
697def is_valid_field(name: str) -> bool: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
698 if not name.startswith('_'): 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
699 return True 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
700 return ROOT_KEY == name 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
703DUNDER_ATTRIBUTES = { 1sBdjagkvlwtCembhnxoyPQRSTUuDfpciqzrA
704 '__annotations__',
705 '__classcell__',
706 '__doc__',
707 '__module__',
708 '__orig_bases__',
709 '__orig_class__',
710 '__qualname__',
711}
714def is_valid_private_name(name: str) -> bool: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
715 return not is_valid_field(name) and name not in DUNDER_ATTRIBUTES 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
718_EMPTY = object() 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
721def all_identical(left: Iterable[Any], right: Iterable[Any]) -> bool: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
722 """
723 Check that the items of `left` are the same objects as those in `right`.
725 >>> a, b = object(), object()
726 >>> all_identical([a, b, a], [a, b, a])
727 True
728 >>> all_identical([a, b, [a]], [a, b, [a]]) # new list object, while "equal" is not "identical"
729 False
730 """
731 for left_item, right_item in zip_longest(left, right, fillvalue=_EMPTY): 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
732 if left_item is not right_item: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
733 return False 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
734 return True 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
737def assert_never(obj: NoReturn, msg: str) -> NoReturn: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
738 """
739 Helper to make sure that we have covered all possible types.
741 This is mostly useful for ``mypy``, docs:
742 https://mypy.readthedocs.io/en/latest/literal_types.html#exhaustive-checks
743 """
744 raise TypeError(msg) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
747def get_unique_discriminator_alias(all_aliases: Collection[str], discriminator_key: str) -> str: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
748 """Validate that all aliases are the same and if that's the case return the alias"""
749 unique_aliases = set(all_aliases) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
750 if len(unique_aliases) > 1: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
751 raise ConfigError( 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
752 f'Aliases for discriminator {discriminator_key!r} must be the same (got {", ".join(sorted(all_aliases))})'
753 )
754 return unique_aliases.pop() 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
757def get_discriminator_alias_and_values(tp: Any, discriminator_key: str) -> Tuple[str, Tuple[str, ...]]: 1JMsBdjagkvlwKNtCembhnxoyEFGHPQRSTUILOuDfpciqzrA
758 """
759 Get alias and all valid values in the `Literal` type of the discriminator field
760 `tp` can be a `BaseModel` class or directly an `Annotated` `Union` of many.
761 """
762 is_root_model = getattr(tp, '__custom_root_type__', False) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
764 if get_origin(tp) is Annotated: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
765 tp = get_args(tp)[0] 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
767 if hasattr(tp, '__pydantic_model__'): 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
768 tp = tp.__pydantic_model__ 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
770 if is_union(get_origin(tp)): 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
771 alias, all_values = _get_union_alias_and_all_values(tp, discriminator_key) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
772 return alias, tuple(v for values in all_values for v in values) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
773 elif is_root_model: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
774 union_type = tp.__fields__[ROOT_KEY].type_ 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
775 alias, all_values = _get_union_alias_and_all_values(union_type, discriminator_key) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
777 if len(set(all_values)) > 1: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
778 raise ConfigError( 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
779 f'Field {discriminator_key!r} is not the same for all submodels of {display_as_type(tp)!r}'
780 )
782 return alias, all_values[0] 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
784 else:
785 try: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
786 t_discriminator_type = tp.__fields__[discriminator_key].type_ 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
787 except AttributeError as e: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
788 raise TypeError(f'Type {tp.__name__!r} is not a valid `BaseModel` or `dataclass`') from e 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
789 except KeyError as e: 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
790 raise ConfigError(f'Model {tp.__name__!r} needs a discriminator field for key {discriminator_key!r}') from e 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
792 if not is_literal_type(t_discriminator_type): 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
793 raise ConfigError(f'Field {discriminator_key!r} of model {tp.__name__!r} needs to be a `Literal`') 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
795 return tp.__fields__[discriminator_key].alias, all_literal_values(t_discriminator_type) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
798def _get_union_alias_and_all_values( 1sBdjagkvlwtCembhnxoyEFGHPQRSTUIuDfpciqzrA
799 union_type: Type[Any], discriminator_key: str
800) -> Tuple[str, Tuple[Tuple[str, ...], ...]]:
801 zipped_aliases_values = [get_discriminator_alias_and_values(t, discriminator_key) for t in get_args(union_type)] 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
802 # unzip: [('alias_a',('v1', 'v2)), ('alias_b', ('v3',))] => [('alias_a', 'alias_b'), (('v1', 'v2'), ('v3',))]
803 all_aliases, all_values = zip(*zipped_aliases_values) 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA
804 return get_unique_discriminator_alias(all_aliases, discriminator_key), all_values 1JMsBdjagkvlwKNtCembhnxoyEFGHILOuDfpciqzrA