Coverage for pydantic/_internal/_known_annotated_metadata.py: 94.44%
182 statements
« prev ^ index » next coverage.py v7.5.3, created at 2024-06-21 17:00 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2024-06-21 17:00 +0000
1from __future__ import annotations 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
3from collections import defaultdict 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
4from copy import copy 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
5from functools import partial 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
6from typing import TYPE_CHECKING, Any, Callable, Iterable 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
8from pydantic_core import CoreSchema, PydanticCustomError, to_jsonable_python 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
9from pydantic_core import core_schema as cs 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
11from ._fields import PydanticMetadata 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
13if TYPE_CHECKING: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
14 from ..annotated_handlers import GetJsonSchemaHandler
17STRICT = {'strict'} 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
18SEQUENCE_CONSTRAINTS = {'min_length', 'max_length'} 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
19INEQUALITY = {'le', 'ge', 'lt', 'gt'} 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
20NUMERIC_CONSTRAINTS = {'multiple_of', 'allow_inf_nan', *INEQUALITY} 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
22STR_CONSTRAINTS = { 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
23 *SEQUENCE_CONSTRAINTS,
24 *STRICT,
25 'strip_whitespace',
26 'to_lower',
27 'to_upper',
28 'pattern',
29 'coerce_numbers_to_str',
30}
31BYTES_CONSTRAINTS = {*SEQUENCE_CONSTRAINTS, *STRICT} 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
33LIST_CONSTRAINTS = {*SEQUENCE_CONSTRAINTS, *STRICT} 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
34TUPLE_CONSTRAINTS = {*SEQUENCE_CONSTRAINTS, *STRICT} 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
35SET_CONSTRAINTS = {*SEQUENCE_CONSTRAINTS, *STRICT} 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
36DICT_CONSTRAINTS = {*SEQUENCE_CONSTRAINTS, *STRICT} 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
37GENERATOR_CONSTRAINTS = {*SEQUENCE_CONSTRAINTS, *STRICT} 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
39FLOAT_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *STRICT} 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
40INT_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *STRICT} 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
41BOOL_CONSTRAINTS = STRICT 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
42UUID_CONSTRAINTS = STRICT 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
44DATE_TIME_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *STRICT} 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
45TIMEDELTA_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *STRICT} 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
46TIME_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *STRICT} 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
47LAX_OR_STRICT_CONSTRAINTS = STRICT 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
48ENUM_CONSTRAINTS = STRICT 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
50UNION_CONSTRAINTS = {'union_mode'} 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
51URL_CONSTRAINTS = { 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
52 'max_length',
53 'allowed_schemes',
54 'host_required',
55 'default_host',
56 'default_port',
57 'default_path',
58}
60TEXT_SCHEMA_TYPES = ('str', 'bytes', 'url', 'multi-host-url') 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
61SEQUENCE_SCHEMA_TYPES = ('list', 'tuple', 'set', 'frozenset', 'generator', *TEXT_SCHEMA_TYPES) 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
62NUMERIC_SCHEMA_TYPES = ('float', 'int', 'date', 'time', 'timedelta', 'datetime') 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
64CONSTRAINTS_TO_ALLOWED_SCHEMAS: dict[str, set[str]] = defaultdict(set) 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
65for constraint in STR_CONSTRAINTS: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
66 CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(TEXT_SCHEMA_TYPES) 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
67for constraint in BYTES_CONSTRAINTS: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
68 CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('bytes',)) 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
69for constraint in LIST_CONSTRAINTS: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
70 CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('list',)) 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
71for constraint in TUPLE_CONSTRAINTS: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
72 CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('tuple',)) 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
73for constraint in SET_CONSTRAINTS: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
74 CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('set', 'frozenset')) 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
75for constraint in DICT_CONSTRAINTS: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
76 CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('dict',)) 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
77for constraint in GENERATOR_CONSTRAINTS: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
78 CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('generator',)) 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
79for constraint in FLOAT_CONSTRAINTS: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
80 CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('float',)) 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
81for constraint in INT_CONSTRAINTS: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
82 CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('int',)) 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
83for constraint in DATE_TIME_CONSTRAINTS: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
84 CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('date', 'time', 'datetime')) 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
85for constraint in TIMEDELTA_CONSTRAINTS: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
86 CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('timedelta',)) 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
87for constraint in TIME_CONSTRAINTS: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
88 CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('time',)) 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
89for schema_type in (*TEXT_SCHEMA_TYPES, *SEQUENCE_SCHEMA_TYPES, *NUMERIC_SCHEMA_TYPES, 'typed-dict', 'model'): 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
90 CONSTRAINTS_TO_ALLOWED_SCHEMAS['strict'].add(schema_type) 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
91for constraint in UNION_CONSTRAINTS: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
92 CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('union',)) 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
93for constraint in URL_CONSTRAINTS: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
94 CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('url', 'multi-host-url')) 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
95for constraint in BOOL_CONSTRAINTS: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
96 CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('bool',)) 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
97for constraint in UUID_CONSTRAINTS: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
98 CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('uuid',)) 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
99for constraint in LAX_OR_STRICT_CONSTRAINTS: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
100 CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('lax-or-strict',)) 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
101for constraint in ENUM_CONSTRAINTS: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
102 CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint].update(('enum',)) 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
105def add_js_update_schema(s: cs.CoreSchema, f: Callable[[], dict[str, Any]]) -> None: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
106 def update_js_schema(s: cs.CoreSchema, handler: GetJsonSchemaHandler) -> dict[str, Any]: 1abcdefghijklmnopqrstuvwxyzABCDEF
107 js_schema = handler(s) 1abcdefghijklmnopqrstuvwxyzABCDEF
108 js_schema.update(f()) 1abcdefghijklmnopqrstuvwxyzABCDEF
109 return js_schema 1abcdefghijklmnopqrstuvwxyzABCDEF
111 if 'metadata' in s: 111 ↛ 112line 111 didn't jump to line 112, because the condition on line 111 was never true1abcdefghijklmnopqrstuvwxyzABCDEF
112 metadata = s['metadata']
113 if 'pydantic_js_functions' in s:
114 metadata['pydantic_js_functions'].append(update_js_schema)
115 else:
116 metadata['pydantic_js_functions'] = [update_js_schema]
117 else:
118 s['metadata'] = {'pydantic_js_functions': [update_js_schema]} 1abcdefghijklmnopqrstuvwxyzABCDEF
121def as_jsonable_value(v: Any) -> Any: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
122 if type(v) not in (int, str, float, bytes, bool, type(None)): 122 ↛ 123line 122 didn't jump to line 123, because the condition on line 122 was never true1abcdefghijklmnopqrstuvwxyzABCDEF
123 return to_jsonable_python(v)
124 return v 1abcdefghijklmnopqrstuvwxyzABCDEF
127def expand_grouped_metadata(annotations: Iterable[Any]) -> Iterable[Any]: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
128 """Expand the annotations.
130 Args:
131 annotations: An iterable of annotations.
133 Returns:
134 An iterable of expanded annotations.
136 Example:
137 ```py
138 from annotated_types import Ge, Len
140 from pydantic._internal._known_annotated_metadata import expand_grouped_metadata
142 print(list(expand_grouped_metadata([Ge(4), Len(5)])))
143 #> [Ge(ge=4), MinLen(min_length=5)]
144 ```
145 """
146 import annotated_types as at 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
148 from pydantic.fields import FieldInfo # circular import 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
150 for annotation in annotations: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
151 if isinstance(annotation, at.GroupedMetadata): 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
152 yield from annotation 1abcdefghijklmnopqrstuvwxyzABCDEF
153 elif isinstance(annotation, FieldInfo): 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
154 yield from annotation.metadata 1abcdefghijklmnopqrstuvwxyzABCDEF
155 # this is a bit problematic in that it results in duplicate metadata
156 # all of our "consumers" can handle it, but it is not ideal
157 # we probably should split up FieldInfo into:
158 # - annotated types metadata
159 # - individual metadata known only to Pydantic
160 annotation = copy(annotation) 1abcdefghijklmnopqrstuvwxyzABCDEF
161 annotation.metadata = [] 1abcdefghijklmnopqrstuvwxyzABCDEF
162 yield annotation 1abcdefghijklmnopqrstuvwxyzABCDEF
163 else:
164 yield annotation 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
167def apply_known_metadata(annotation: Any, schema: CoreSchema) -> CoreSchema | None: # noqa: C901 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
168 """Apply `annotation` to `schema` if it is an annotation we know about (Gt, Le, etc.).
169 Otherwise return `None`.
171 This does not handle all known annotations. If / when it does, it can always
172 return a CoreSchema and return the unmodified schema if the annotation should be ignored.
174 Assumes that GroupedMetadata has already been expanded via `expand_grouped_metadata`.
176 Args:
177 annotation: The annotation.
178 schema: The schema.
180 Returns:
181 An updated schema with annotation if it is an annotation we know about, `None` otherwise.
183 Raises:
184 PydanticCustomError: If `Predicate` fails.
185 """
186 import annotated_types as at 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
188 from . import _validators 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
190 COMPARISON_VALIDATORS = { 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
191 'gt': _validators.greater_than_validator,
192 'ge': _validators.greater_than_or_equal_validator,
193 'lt': _validators.less_than_validator,
194 'le': _validators.less_than_or_equal_validator,
195 'multiple_of': _validators.multiple_of_validator,
196 'min_length': _validators.min_length_validator,
197 'max_length': _validators.max_length_validator,
198 }
200 CONSTRAINT_STR_FROM_ANNOTATED_TYPE = { 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
201 at.Gt: 'gt',
202 at.Ge: 'ge',
203 at.Lt: 'lt',
204 at.Le: 'le',
205 at.MultipleOf: 'multiple_of',
206 at.MinLen: 'min_length',
207 at.MaxLen: 'max_length',
208 }
210 schema = schema.copy() 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
211 schema_update, other_metadata = collect_known_metadata([annotation]) 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
212 schema_type = schema['type'] 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
214 chain_schema_steps: list[CoreSchema] = [] 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
216 for constraint, value in schema_update.items(): 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
217 if constraint not in CONSTRAINTS_TO_ALLOWED_SCHEMAS: 217 ↛ 218line 217 didn't jump to line 218, because the condition on line 217 was never true1abcdefghijklmnopqrstuvwxyzABCDEF
218 raise ValueError(f'Unknown constraint {constraint}')
219 allowed_schemas = CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint] 1abcdefghijklmnopqrstuvwxyzABCDEF
221 # if it becomes necessary to handle more than one constraint
222 # in this recursive case with function-after or function-wrap, we should refactor
223 if schema_type in {'function-before', 'function-wrap', 'function-after'} and constraint == 'strict': 1abcdefghijklmnopqrstuvwxyzABCDEF
224 schema['schema'] = apply_known_metadata(annotation, schema['schema']) # type: ignore # schema is function-after schema 1abcdefghijklmnopqrstuvwxyzABCDEF
225 return schema 1abcdefghijklmnopqrstuvwxyzABCDEF
227 if schema_type in allowed_schemas: 1abcdefghijklmnopqrstuvwxyzABCDEF
228 if constraint == 'union_mode' and schema_type == 'union': 1abcdefghijklmnopqrstuvwxyzABCDEF
229 schema['mode'] = value # type: ignore # schema is UnionSchema 1abcdefghijklmnopqrstuvwxyzABCDEF
230 else:
231 schema[constraint] = value 1abcdefghijklmnopqrstuvwxyzABCDEF
232 continue 1abcdefghijklmnopqrstuvwxyzABCDEF
234 if constraint in {'pattern', 'strip_whitespace', 'to_lower', 'to_upper', 'coerce_numbers_to_str'}: 1abcdefghijklmnopqrstuvwxyzABCDEF
235 chain_schema_steps.append(cs.str_schema(**{constraint: value})) 1abcdefghijklmnopqrstuvwxyzABCDEF
236 elif constraint in {'gt', 'ge', 'lt', 'le', 'multiple_of', 'min_length', 'max_length'}: 1abcdefghijklmnopqrstuvwxyzABCDEF
237 if constraint == 'multiple_of': 1abcdefghijklmnopqrstuvwxyzABCDEF
238 json_schema_constraint = 'multiple_of' 1abcdefghijklmnopqrstuvwxyzABCDEF
239 elif constraint in {'min_length', 'max_length'}: 1abcdefghijklmnopqrstuvwxyzABCDEF
240 if schema['type'] == 'list' or ( 1abcdefghijklmnopqrstuvwxyzABCDEF
241 schema['type'] == 'json-or-python' and schema['json_schema']['type'] == 'list'
242 ):
243 json_schema_constraint = 'minItems' if constraint == 'min_length' else 'maxItems' 1abcdefghijklmnopqrstuvwxyzABCDEF
244 else:
245 json_schema_constraint = 'minLength' if constraint == 'min_length' else 'maxLength' 1abcdefghijklmnopqrstuvwxyzABCDEF
246 else:
247 json_schema_constraint = constraint 1abcdefghijklmnopqrstuvwxyzABCDEF
249 schema = cs.no_info_after_validator_function( 1abcdefghijklmnopqrstuvwxyzABCDEF
250 partial(COMPARISON_VALIDATORS[constraint], **{constraint: value}), schema
251 )
253 add_js_update_schema(schema, lambda: {json_schema_constraint: as_jsonable_value(value)}) 1abcdefghijklmnopqrstuvwxyzABCDEF
254 elif constraint == 'allow_inf_nan' and value is False: 254 ↛ 260line 254 didn't jump to line 260, because the condition on line 254 was always true1abcdefghijklmnopqrstuvwxyzABCDEF
255 schema = cs.no_info_after_validator_function( 1abcdefghijklmnopqrstuvwxyzABCDEF
256 _validators.forbid_inf_nan_check,
257 schema,
258 )
259 else:
260 raise RuntimeError(f'Unable to apply constraint {constraint} to schema {schema_type}')
262 for annotation in other_metadata: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
263 if isinstance(annotation, (at.Gt, at.Ge, at.Lt, at.Le, at.MultipleOf, at.MinLen, at.MaxLen)): 263 ↛ 264line 263 didn't jump to line 264, because the condition on line 263 was never true1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
264 constraint = CONSTRAINT_STR_FROM_ANNOTATED_TYPE[type(annotation)]
265 schema = cs.no_info_after_validator_function(
266 partial(COMPARISON_VALIDATORS[constraint], {constraint: getattr(annotation, constraint)}), schema
267 )
268 elif isinstance(annotation, at.Predicate): 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
269 predicate_name = f'{annotation.func.__qualname__} ' if hasattr(annotation.func, '__qualname__') else '' 1abcdefghijklmnopqrstuvwxyzABCDEF
271 def val_func(v: Any) -> Any: 1abcdefghijklmnopqrstuvwxyzABCDEF
272 # annotation.func may also raise an exception, let it pass through
273 if not annotation.func(v): 1abcdefghijklmnopqrstuvwxyzABCDEF
274 raise PydanticCustomError( 1abcdefghijklmnopqrstuvwxyzABCDEF
275 'predicate_failed',
276 f'Predicate {predicate_name}failed', # type: ignore
277 )
278 return v 1abcdefghijklmnopqrstuvwxyzABCDEF
280 return cs.no_info_after_validator_function(val_func, schema) 1abcdefghijklmnopqrstuvwxyzABCDEF
281 # ignore any other unknown metadata
282 return None 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
284 if chain_schema_steps: 1abcdefghijklmnopqrstuvwxyzABCDEF
285 chain_schema_steps = [schema] + chain_schema_steps 1abcdefghijklmnopqrstuvwxyzABCDEF
286 return cs.chain_schema(chain_schema_steps) 1abcdefghijklmnopqrstuvwxyzABCDEF
288 return schema 1abcdefghijklmnopqrstuvwxyzABCDEF
291def collect_known_metadata(annotations: Iterable[Any]) -> tuple[dict[str, Any], list[Any]]: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
292 """Split `annotations` into known metadata and unknown annotations.
294 Args:
295 annotations: An iterable of annotations.
297 Returns:
298 A tuple contains a dict of known metadata and a list of unknown annotations.
300 Example:
301 ```py
302 from annotated_types import Gt, Len
304 from pydantic._internal._known_annotated_metadata import collect_known_metadata
306 print(collect_known_metadata([Gt(1), Len(42), ...]))
307 #> ({'gt': 1, 'min_length': 42}, [Ellipsis])
308 ```
309 """
310 import annotated_types as at 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
312 annotations = expand_grouped_metadata(annotations) 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
314 res: dict[str, Any] = {} 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
315 remaining: list[Any] = [] 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
316 for annotation in annotations: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
317 # isinstance(annotation, PydanticMetadata) also covers ._fields:_PydanticGeneralMetadata
318 if isinstance(annotation, PydanticMetadata): 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
319 res.update(annotation.__dict__) 1abcdefghijklmnopqrstuvwxyzABCDEF
320 # we don't use dataclasses.asdict because that recursively calls asdict on the field values
321 elif isinstance(annotation, at.MinLen): 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
322 res.update({'min_length': annotation.min_length}) 1abcdefghijklmnopqrstuvwxyzABCDEF
323 elif isinstance(annotation, at.MaxLen): 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
324 res.update({'max_length': annotation.max_length}) 1abcdefghijklmnopqrstuvwxyzABCDEF
325 elif isinstance(annotation, at.Gt): 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
326 res.update({'gt': annotation.gt}) 1abcdefghijklmnopqrstuvwxyzABCDEF
327 elif isinstance(annotation, at.Ge): 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
328 res.update({'ge': annotation.ge}) 1abcdefghijklmnopqrstuvwxyzABCDEF
329 elif isinstance(annotation, at.Lt): 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
330 res.update({'lt': annotation.lt}) 1abcdefghijklmnopqrstuvwxyzABCDEF
331 elif isinstance(annotation, at.Le): 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
332 res.update({'le': annotation.le}) 1abcdefghijklmnopqrstuvwxyzABCDEF
333 elif isinstance(annotation, at.MultipleOf): 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
334 res.update({'multiple_of': annotation.multiple_of}) 1abcdefghijklmnopqrstuvwxyzABCDEF
335 elif isinstance(annotation, type) and issubclass(annotation, PydanticMetadata): 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
336 # also support PydanticMetadata classes being used without initialisation,
337 # e.g. `Annotated[int, Strict]` as well as `Annotated[int, Strict()]`
338 res.update({k: v for k, v in vars(annotation).items() if not k.startswith('_')}) 1abcdefghijklmnopqrstuvwxyzABCDEF
339 else:
340 remaining.append(annotation) 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
341 # Nones can sneak in but pydantic-core will reject them
342 # it'd be nice to clean things up so we don't put in None (we probably don't _need_ to, it was just easier)
343 # but this is simple enough to kick that can down the road
344 res = {k: v for k, v in res.items() if v is not None} 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
345 return res, remaining 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
348def check_metadata(metadata: dict[str, Any], allowed: Iterable[str], source_type: Any) -> None: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
349 """A small utility function to validate that the given metadata can be applied to the target.
350 More than saving lines of code, this gives us a consistent error message for all of our internal implementations.
352 Args:
353 metadata: A dict of metadata.
354 allowed: An iterable of allowed metadata.
355 source_type: The source type.
357 Raises:
358 TypeError: If there is metadatas that can't be applied on source type.
359 """
360 unknown = metadata.keys() - set(allowed) 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
361 if unknown: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF
362 raise TypeError( 1abcdefghijklmnopqrstuvwxyzABCDEF
363 f'The following constraints cannot be applied to {source_type!r}: {", ".join([f"{k!r}" for k in unknown])}'
364 )