14 __slots__ = (
"_annotation",)
16 def __init__(self, annotation: typing.Type):
18 Initialize the field information.
21 annotation: The type annotation of the field.
26 def annotation(self) -> typing.Type[typing.Any] | None:
28 Get the type annotation of the field.
35 Check if the field is required for the schema.
41 if isinstance(value, collections.abc.Mapping):
43 elif isinstance(value, collections.abc.Sequence)
and not isinstance(value, str):
46 return collections.Sequence[Any]
49 element_type = type(element)
51 original = typing.get_origin(element_type)
53 original = element_type
54 if original
is typing.Mapping
or isinstance(original, type)
and issubclass(original,
55 collections.abc.Mapping):
58 element_types.add(element_type)
59 if len(element_types) == 1:
60 return collections.abc.Sequence[next(iter(element_types))]
61 union_type = typing.Union[tuple(element_types)]
62 return collections.abc.Sequence[union_type]
69 Recursively infer a schema from a mapping.
71 Types that are specially handled:
72 - collections.abc.Mapping: converted to a schema. Keys are converted to field names and corresponding value types are converted to field types.
73 - collections.abc.Sequence with heterogeneous elements: all different element types are included in a union type.
75 Other types are directly inferred from the type of the value with no special handling.
78 for key, value
in mapping.items():
79 assert isinstance(key, str), f
"Key must be a string, got {key} of type {type(key)}"
80 assert key.isidentifier(), f
"Key must be a valid identifier, got {key}"
82 field_infos[key] =
FieldInfo(inferred_type)
84 _class.from_json = classmethod(
lambda cls, json_str: json.loads(json_str))