|
2 | 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
4 | 4 |
|
5 | | - |
6 | 5 | import pprint |
7 | | -from typing import List |
| 6 | +from functools import reduce |
| 7 | +from typing import Dict, List, Literal, Union |
8 | 8 |
|
9 | 9 | import msgspec |
10 | 10 |
|
@@ -37,72 +37,18 @@ def validate_schema(schema, obj, msg_prefix): |
37 | 37 | raise Exception(f"{msg_prefix}\n{str(exc)}\n{pprint.pformat(obj)}") |
38 | 38 |
|
39 | 39 |
|
40 | | -def optionally_keyed_by(*arguments): |
| 40 | +def UnionTypes(*types): |
| 41 | + """Use `functools.reduce` to simulate `Union[*allowed_types]` on older |
| 42 | + Python versions. |
41 | 43 | """ |
42 | | - Mark a schema value as optionally keyed by any of a number of fields. The |
43 | | - schema is the last argument, and the remaining fields are taken to be the |
44 | | - field names. For example: |
| 44 | + return reduce(lambda a, b: Union[a, b], types) |
45 | 45 |
|
46 | | - 'some-value': optionally_keyed_by( |
47 | | - 'test-platform', 'build-platform', |
48 | | - Any('a', 'b', 'c')) |
49 | 46 |
|
50 | | - The resulting schema will allow nesting of `by-test-platform` and |
51 | | - `by-build-platform` in either order. |
52 | | - """ |
53 | | - schema = arguments[-1] |
| 47 | +def optionally_keyed_by(*arguments): |
| 48 | + _type = arguments[-1] |
54 | 49 | fields = arguments[:-1] |
55 | | - |
56 | | - def validator(obj): |
57 | | - if isinstance(obj, dict) and len(obj) == 1: |
58 | | - k, v = list(obj.items())[0] |
59 | | - if k.startswith("by-") and k[len("by-") :] in fields: |
60 | | - res = {} |
61 | | - for kk, vv in v.items(): |
62 | | - try: |
63 | | - res[kk] = validator(vv) |
64 | | - except Exception as e: |
65 | | - raise ValueError(f"Error in {k}.{kk}: {str(e)}") from e |
66 | | - return res |
67 | | - elif k.startswith("by-"): |
68 | | - # Unknown by-field |
69 | | - raise ValueError(f"Unknown key {k}") |
70 | | - # Validate against the schema |
71 | | - if isinstance(schema, type) and issubclass(schema, Schema): |
72 | | - return schema.validate(obj) |
73 | | - elif schema is str: |
74 | | - # String validation |
75 | | - if not isinstance(obj, str): |
76 | | - raise TypeError(f"Expected string, got {type(obj).__name__}") |
77 | | - return obj |
78 | | - elif schema is int: |
79 | | - # Int validation |
80 | | - if not isinstance(obj, int): |
81 | | - raise TypeError(f"Expected int, got {type(obj).__name__}") |
82 | | - return obj |
83 | | - elif isinstance(schema, type): |
84 | | - # Type validation for built-in types |
85 | | - if not isinstance(obj, schema): |
86 | | - raise TypeError(f"Expected {schema.__name__}, got {type(obj).__name__}") |
87 | | - return obj |
88 | | - elif callable(schema): |
89 | | - # Other callable validators |
90 | | - try: |
91 | | - return schema(obj) |
92 | | - except: |
93 | | - raise |
94 | | - else: |
95 | | - # Simple type validation |
96 | | - if not isinstance(obj, schema): |
97 | | - raise TypeError( |
98 | | - f"Expected {getattr(schema, '__name__', str(schema))}, got {type(obj).__name__}" |
99 | | - ) |
100 | | - return obj |
101 | | - |
102 | | - # set to assist autodoc |
103 | | - setattr(validator, "schema", schema) |
104 | | - setattr(validator, "fields", fields) |
105 | | - return validator |
| 50 | + bykeys = [Literal[f"by-{field}"] for field in fields] |
| 51 | + return Union[_type, Dict[UnionTypes(*bykeys), Dict[str, _type]]] |
106 | 52 |
|
107 | 53 |
|
108 | 54 | def resolve_keyed_by( |
|
0 commit comments