Mercurial > repos > shellac > sam_consensus_v3
comparison env/lib/python3.9/site-packages/schema_salad/validate.py @ 0:4f3585e2f14b draft default tip
"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author | shellac |
---|---|
date | Mon, 22 Mar 2021 18:12:50 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4f3585e2f14b |
---|---|
1 import logging | |
2 import pprint | |
3 from typing import Any, List, MutableMapping, MutableSequence, Optional, Set | |
4 from urllib.parse import urlsplit | |
5 | |
6 from . import avro | |
7 from .avro.schema import Schema # pylint: disable=no-name-in-module, import-error | |
8 from .exceptions import ( | |
9 ClassValidationException, | |
10 SchemaSaladException, | |
11 ValidationException, | |
12 ) | |
13 from .sourceline import SourceLine | |
14 | |
15 _logger = logging.getLogger("salad") | |
16 | |
17 | |
18 def validate( | |
19 expected_schema: Schema, | |
20 datum: Any, | |
21 identifiers: Optional[List[str]] = None, | |
22 strict: bool = False, | |
23 foreign_properties: Optional[Set[str]] = None, | |
24 ) -> bool: | |
25 if not identifiers: | |
26 identifiers = [] | |
27 if not foreign_properties: | |
28 foreign_properties = set() | |
29 return validate_ex( | |
30 expected_schema, | |
31 datum, | |
32 identifiers, | |
33 strict=strict, | |
34 foreign_properties=foreign_properties, | |
35 raise_ex=False, | |
36 ) | |
37 | |
38 | |
39 INT_MIN_VALUE = -(1 << 31) | |
40 INT_MAX_VALUE = (1 << 31) - 1 | |
41 LONG_MIN_VALUE = -(1 << 63) | |
42 LONG_MAX_VALUE = (1 << 63) - 1 | |
43 | |
44 | |
45 def friendly(v): # type: (Any) -> Any | |
46 if isinstance(v, avro.schema.NamedSchema): | |
47 return v.name | |
48 if isinstance(v, avro.schema.ArraySchema): | |
49 return "array of <{}>".format(friendly(v.items)) | |
50 elif isinstance(v, avro.schema.PrimitiveSchema): | |
51 return v.type | |
52 elif isinstance(v, avro.schema.UnionSchema): | |
53 return " or ".join([friendly(s) for s in v.schemas]) | |
54 else: | |
55 return v | |
56 | |
57 | |
58 def vpformat(datum): # type: (Any) -> str | |
59 a = pprint.pformat(datum) | |
60 if len(a) > 160: | |
61 a = a[0:160] + "[...]" | |
62 return a | |
63 | |
64 | |
65 def validate_ex( | |
66 expected_schema: Schema, | |
67 datum, # type: Any | |
68 identifiers=None, # type: Optional[List[str]] | |
69 strict=False, # type: bool | |
70 foreign_properties=None, # type: Optional[Set[str]] | |
71 raise_ex=True, # type: bool | |
72 strict_foreign_properties=False, # type: bool | |
73 logger=_logger, # type: logging.Logger | |
74 skip_foreign_properties=False, # type: bool | |
75 ): | |
76 # type: (...) -> bool | |
77 """Determine if a python datum is an instance of a schema.""" | |
78 | |
79 if not identifiers: | |
80 identifiers = [] | |
81 | |
82 if not foreign_properties: | |
83 foreign_properties = set() | |
84 | |
85 schema_type = expected_schema.type | |
86 | |
87 if schema_type == "null": | |
88 if datum is None: | |
89 return True | |
90 if raise_ex: | |
91 raise ValidationException("the value is not null") | |
92 return False | |
93 elif schema_type == "boolean": | |
94 if isinstance(datum, bool): | |
95 return True | |
96 if raise_ex: | |
97 raise ValidationException("the value is not boolean") | |
98 return False | |
99 elif schema_type == "string": | |
100 if isinstance(datum, str): | |
101 return True | |
102 if isinstance(datum, bytes): | |
103 return True | |
104 if raise_ex: | |
105 raise ValidationException("the value is not string") | |
106 return False | |
107 elif schema_type == "int": | |
108 if isinstance(datum, int) and INT_MIN_VALUE <= datum <= INT_MAX_VALUE: | |
109 return True | |
110 if raise_ex: | |
111 raise ValidationException("`{}` is not int".format(vpformat(datum))) | |
112 return False | |
113 elif schema_type == "long": | |
114 if (isinstance(datum, int)) and LONG_MIN_VALUE <= datum <= LONG_MAX_VALUE: | |
115 return True | |
116 if raise_ex: | |
117 raise ValidationException( | |
118 "the value `{}` is not long".format(vpformat(datum)) | |
119 ) | |
120 return False | |
121 elif schema_type in ["float", "double"]: | |
122 if isinstance(datum, int) or isinstance(datum, float): | |
123 return True | |
124 if raise_ex: | |
125 raise ValidationException( | |
126 "the value `{}` is not float or double".format(vpformat(datum)) | |
127 ) | |
128 return False | |
129 elif isinstance(expected_schema, avro.schema.EnumSchema): | |
130 if expected_schema.name == "Any": | |
131 if datum is not None: | |
132 return True | |
133 if raise_ex: | |
134 raise ValidationException("'Any' type must be non-null") | |
135 return False | |
136 if not isinstance(datum, str): | |
137 if raise_ex: | |
138 raise ValidationException( | |
139 "value is a {} but expected a string".format(type(datum).__name__) | |
140 ) | |
141 return False | |
142 if expected_schema.name == "Expression": | |
143 if "$(" in datum or "${" in datum: | |
144 return True | |
145 if raise_ex: | |
146 raise ValidationException( | |
147 "value `{}` does not contain an expression in the form $() or ${{}}".format( | |
148 datum | |
149 ) | |
150 ) | |
151 return False | |
152 if datum in expected_schema.symbols: | |
153 return True | |
154 else: | |
155 if raise_ex: | |
156 raise ValidationException( | |
157 "the value {} is not a valid {}, expected {}{}".format( | |
158 vpformat(datum), | |
159 expected_schema.name, | |
160 "one of " if len(expected_schema.symbols) > 1 else "", | |
161 "'" + "', '".join(expected_schema.symbols) + "'", | |
162 ) | |
163 ) | |
164 return False | |
165 elif isinstance(expected_schema, avro.schema.ArraySchema): | |
166 if isinstance(datum, MutableSequence): | |
167 for i, d in enumerate(datum): | |
168 try: | |
169 sl = SourceLine(datum, i, ValidationException) | |
170 if not validate_ex( | |
171 expected_schema.items, | |
172 d, | |
173 identifiers, | |
174 strict=strict, | |
175 foreign_properties=foreign_properties, | |
176 raise_ex=raise_ex, | |
177 strict_foreign_properties=strict_foreign_properties, | |
178 logger=logger, | |
179 skip_foreign_properties=skip_foreign_properties, | |
180 ): | |
181 return False | |
182 except ValidationException as v: | |
183 if raise_ex: | |
184 raise ValidationException("item is invalid because", sl, [v]) | |
185 return False | |
186 return True | |
187 else: | |
188 if raise_ex: | |
189 raise ValidationException( | |
190 "the value {} is not a list, expected list of {}".format( | |
191 vpformat(datum), friendly(expected_schema.items) | |
192 ) | |
193 ) | |
194 return False | |
195 elif isinstance(expected_schema, avro.schema.UnionSchema): | |
196 for s in expected_schema.schemas: | |
197 if validate_ex( | |
198 s, | |
199 datum, | |
200 identifiers, | |
201 strict=strict, | |
202 raise_ex=False, | |
203 strict_foreign_properties=strict_foreign_properties, | |
204 logger=logger, | |
205 skip_foreign_properties=skip_foreign_properties, | |
206 ): | |
207 return True | |
208 | |
209 if not raise_ex: | |
210 return False | |
211 | |
212 errors = [] # type: List[SchemaSaladException] | |
213 checked = [] | |
214 for s in expected_schema.schemas: | |
215 if isinstance(datum, MutableSequence) and not isinstance( | |
216 s, avro.schema.ArraySchema | |
217 ): | |
218 continue | |
219 elif isinstance(datum, MutableMapping) and not isinstance( | |
220 s, avro.schema.RecordSchema | |
221 ): | |
222 continue | |
223 elif isinstance(datum, (bool, int, float, str)) and isinstance( | |
224 s, (avro.schema.ArraySchema, avro.schema.RecordSchema) | |
225 ): | |
226 continue | |
227 elif datum is not None and s.type == "null": | |
228 continue | |
229 | |
230 checked.append(s) | |
231 try: | |
232 validate_ex( | |
233 s, | |
234 datum, | |
235 identifiers, | |
236 strict=strict, | |
237 foreign_properties=foreign_properties, | |
238 raise_ex=True, | |
239 strict_foreign_properties=strict_foreign_properties, | |
240 logger=logger, | |
241 skip_foreign_properties=skip_foreign_properties, | |
242 ) | |
243 except ClassValidationException: | |
244 raise | |
245 except ValidationException as e: | |
246 errors.append(e) | |
247 if bool(errors): | |
248 raise ValidationException( | |
249 "", | |
250 None, | |
251 [ | |
252 ValidationException( | |
253 "tried {} but".format(friendly(check)), None, [err] | |
254 ) | |
255 for (check, err) in zip(checked, errors) | |
256 ], | |
257 "-", | |
258 ) | |
259 else: | |
260 raise ValidationException( | |
261 "value is a {}, expected {}".format( | |
262 type(datum).__name__, friendly(expected_schema) | |
263 ) | |
264 ) | |
265 | |
266 elif isinstance(expected_schema, avro.schema.RecordSchema): | |
267 if not isinstance(datum, MutableMapping): | |
268 if raise_ex: | |
269 raise ValidationException("is not a dict") | |
270 else: | |
271 return False | |
272 | |
273 classmatch = None | |
274 for f in expected_schema.fields: | |
275 if f.name in ("class",): | |
276 d = datum.get(f.name) | |
277 if not d: | |
278 if raise_ex: | |
279 raise ValidationException(f"Missing '{f.name}' field") | |
280 else: | |
281 return False | |
282 if expected_schema.name != d: | |
283 if raise_ex: | |
284 raise ValidationException( | |
285 "Expected class '{}' but this is '{}'".format( | |
286 expected_schema.name, d | |
287 ) | |
288 ) | |
289 else: | |
290 return False | |
291 classmatch = d | |
292 break | |
293 | |
294 errors = [] | |
295 for f in expected_schema.fields: | |
296 if f.name in ("class",): | |
297 continue | |
298 | |
299 if f.name in datum: | |
300 fieldval = datum[f.name] | |
301 else: | |
302 try: | |
303 fieldval = f.default | |
304 except KeyError: | |
305 fieldval = None | |
306 | |
307 try: | |
308 sl = SourceLine(datum, f.name, str) | |
309 if not validate_ex( | |
310 f.type, | |
311 fieldval, | |
312 identifiers, | |
313 strict=strict, | |
314 foreign_properties=foreign_properties, | |
315 raise_ex=raise_ex, | |
316 strict_foreign_properties=strict_foreign_properties, | |
317 logger=logger, | |
318 skip_foreign_properties=skip_foreign_properties, | |
319 ): | |
320 return False | |
321 except ValidationException as v: | |
322 if f.name not in datum: | |
323 errors.append( | |
324 ValidationException(f"missing required field `{f.name}`") | |
325 ) | |
326 else: | |
327 errors.append( | |
328 ValidationException( | |
329 f"the `{f.name}` field is not valid because", | |
330 sl, | |
331 [v], | |
332 ) | |
333 ) | |
334 | |
335 for d in datum: | |
336 found = False | |
337 for f in expected_schema.fields: | |
338 if d == f.name: | |
339 found = True | |
340 if not found: | |
341 sl = SourceLine(datum, d, str) | |
342 if d is None: | |
343 err = ValidationException("mapping with implicit null key", sl) | |
344 if strict: | |
345 errors.append(err) | |
346 else: | |
347 logger.warning(err.as_warning()) | |
348 continue | |
349 if ( | |
350 d not in identifiers | |
351 and d not in foreign_properties | |
352 and d[0] not in ("@", "$") | |
353 ): | |
354 if ( | |
355 (d not in identifiers and strict) | |
356 and ( | |
357 d not in foreign_properties | |
358 and strict_foreign_properties | |
359 and not skip_foreign_properties | |
360 ) | |
361 and not raise_ex | |
362 ): | |
363 return False | |
364 split = urlsplit(d) | |
365 if split.scheme: | |
366 if not skip_foreign_properties: | |
367 err = ValidationException( | |
368 "unrecognized extension field `{}`{}.{}".format( | |
369 d, | |
370 " and strict_foreign_properties checking is enabled" | |
371 if strict_foreign_properties | |
372 else "", | |
373 "\nForeign properties from $schemas:\n {}".format( | |
374 "\n ".join(sorted(foreign_properties)) | |
375 ) | |
376 if len(foreign_properties) > 0 | |
377 else "", | |
378 ), | |
379 sl, | |
380 ) | |
381 if strict_foreign_properties: | |
382 errors.append(err) | |
383 elif len(foreign_properties) > 0: | |
384 logger.warning(err.as_warning()) | |
385 else: | |
386 err = ValidationException( | |
387 "invalid field `{}`, expected one of: {}".format( | |
388 d, | |
389 ", ".join( | |
390 f"'{fn.name}'" for fn in expected_schema.fields | |
391 ), | |
392 ), | |
393 sl, | |
394 ) | |
395 if strict: | |
396 errors.append(err) | |
397 else: | |
398 logger.warning(err.as_warning()) | |
399 | |
400 if bool(errors): | |
401 if raise_ex: | |
402 if classmatch: | |
403 raise ClassValidationException("", None, errors, "*") | |
404 else: | |
405 raise ValidationException("", None, errors, "*") | |
406 else: | |
407 return False | |
408 else: | |
409 return True | |
410 if raise_ex: | |
411 raise ValidationException(f"Unrecognized schema_type {schema_type}") | |
412 else: | |
413 return False |