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

1from __future__ import annotations 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

2 

3from collections import defaultdict 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

4from copy import copy 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

5from functools import partial 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

6from typing import TYPE_CHECKING, Any, Callable, Iterable 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

7 

8from pydantic_core import CoreSchema, PydanticCustomError, to_jsonable_python 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

9from pydantic_core import core_schema as cs 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

10 

11from ._fields import PydanticMetadata 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

12 

13if TYPE_CHECKING: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

14 from ..annotated_handlers import GetJsonSchemaHandler 

15 

16 

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

21 

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

32 

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

38 

39FLOAT_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *STRICT} 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

40INT_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *STRICT} 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

41BOOL_CONSTRAINTS = STRICT 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

42UUID_CONSTRAINTS = STRICT 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

43 

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

49 

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} 

59 

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

63 

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

103 

104 

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

110 

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

119 

120 

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

125 

126 

127def expand_grouped_metadata(annotations: Iterable[Any]) -> Iterable[Any]: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

128 """Expand the annotations. 

129 

130 Args: 

131 annotations: An iterable of annotations. 

132 

133 Returns: 

134 An iterable of expanded annotations. 

135 

136 Example: 

137 ```py 

138 from annotated_types import Ge, Len 

139 

140 from pydantic._internal._known_annotated_metadata import expand_grouped_metadata 

141 

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

147 

148 from pydantic.fields import FieldInfo # circular import 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

149 

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

165 

166 

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`. 

170 

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. 

173 

174 Assumes that GroupedMetadata has already been expanded via `expand_grouped_metadata`. 

175 

176 Args: 

177 annotation: The annotation. 

178 schema: The schema. 

179 

180 Returns: 

181 An updated schema with annotation if it is an annotation we know about, `None` otherwise. 

182 

183 Raises: 

184 PydanticCustomError: If `Predicate` fails. 

185 """ 

186 import annotated_types as at 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

187 

188 from . import _validators 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

189 

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 } 

199 

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 } 

209 

210 schema = schema.copy() 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

211 schema_update, other_metadata = collect_known_metadata([annotation]) 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

212 schema_type = schema['type'] 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

213 

214 chain_schema_steps: list[CoreSchema] = [] 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

215 

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

220 

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

226 

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

233 

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

248 

249 schema = cs.no_info_after_validator_function( 1abcdefghijklmnopqrstuvwxyzABCDEF

250 partial(COMPARISON_VALIDATORS[constraint], **{constraint: value}), schema 

251 ) 

252 

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}') 

261 

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

270 

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

279 

280 return cs.no_info_after_validator_function(val_func, schema) 1abcdefghijklmnopqrstuvwxyzABCDEF

281 # ignore any other unknown metadata 

282 return None 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

283 

284 if chain_schema_steps: 1abcdefghijklmnopqrstuvwxyzABCDEF

285 chain_schema_steps = [schema] + chain_schema_steps 1abcdefghijklmnopqrstuvwxyzABCDEF

286 return cs.chain_schema(chain_schema_steps) 1abcdefghijklmnopqrstuvwxyzABCDEF

287 

288 return schema 1abcdefghijklmnopqrstuvwxyzABCDEF

289 

290 

291def collect_known_metadata(annotations: Iterable[Any]) -> tuple[dict[str, Any], list[Any]]: 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

292 """Split `annotations` into known metadata and unknown annotations. 

293 

294 Args: 

295 annotations: An iterable of annotations. 

296 

297 Returns: 

298 A tuple contains a dict of known metadata and a list of unknown annotations. 

299 

300 Example: 

301 ```py 

302 from annotated_types import Gt, Len 

303 

304 from pydantic._internal._known_annotated_metadata import collect_known_metadata 

305 

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

311 

312 annotations = expand_grouped_metadata(annotations) 1abcdefghijklmnopqrstuvGHIJKLMNOwxyzABCDEF

313 

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

346 

347 

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. 

351 

352 Args: 

353 metadata: A dict of metadata. 

354 allowed: An iterable of allowed metadata. 

355 source_type: The source type. 

356 

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 )