2A module that implements the Schema interface using pydantic.
9from pydantic
import BaseModel, validate_call, ConfigDict, Field
11import formatron.schemas.schema
as schema
12from formatron.schemas.schema
import Schema, TypeWithMetadata
17 A wrapper for pydantic FieldInfo.
19 __slots__ = (
"_field",)
21 def __init__(self, field: pydantic.fields.FieldInfo):
23 Initialize the field information.
29 for constraint
in [
"min_length",
"max_length",
"pattern",
"gt",
"ge",
"lt",
"le",
"substring_of"]:
30 value = next((getattr(m, constraint)
for m
in self.
_field.metadata
if hasattr(m, constraint)),
None)
32 metadata[constraint] = value
37 def annotation(self) -> typing.Type[typing.Any] | None:
42 return self.
_field.is_required()
53 A wrapper for pydantic BaseModel that implements the Schema interface.
55 __cached_fields__ =
None
58 def fields(cls) -> typing.Dict[str, typing.Any]:
59 if cls.__cached_fields__
is not None:
60 return cls.__cached_fields__
61 cls.__cached_fields__ = {k:
FieldInfo(v)
for k, v
in cls.model_fields.items()}
62 return cls.__cached_fields__
65 def from_json(cls, _json: str) ->
"ClassSchema":
67 Create a ClassSchema from a JSON string.
69 return cls.model_validate_json(_json)
72CallableT = typing.TypeVar(
'CallableT', bound=typing.Callable)
75def callable_schema(func: CallableT, /, *, config: ConfigDict =
None, validate_return: bool =
False) -> CallableT:
77 A decorator that wraps pydantic's validate_call. The decorated callable also implements the Schema interface.
80 func: The function to decorate.
81 config: The pydantic configuration of validate_call.
82 validate_return: Whether to validate the return value.
85 The decorated callable.
87 pydantic_wrapper = validate_call(config=config, validate_return=validate_return)(func)
88 signature = inspect.signature(func, eval_str=
True)
90 for k, p
in signature.parameters.items():
92 if p.default
is not inspect.Signature.empty:
94 actual_type = p.annotation
96 if isinstance(p.default, pydantic.fields.FieldInfo):
98 if typing.get_origin(p.annotation)
is typing.Annotated:
99 actual_type, *meta = typing.get_args(p.annotation)
102 if isinstance(i, pydantic.fields.FieldInfo):
106 if fieldinfo
is not None:
107 fields[k] = fieldinfo
109 fields[k].default = default
110 fields[k].annotation = actual_type
111 fields[k].metadata.extend(metadata)
113 if default
is not None:
114 fields[k] = Field(default)
117 fields[k].annotation = actual_type
118 fields[k].metadata.extend(metadata)
122 def from_json(cls, json_str):
123 json_data = json.loads(json_str)
126 for k, p
in signature.parameters.items():
127 if p.kind == p.POSITIONAL_ONLY:
128 positional_only.append(json_data[k])
130 others[k] = json_data[k]
131 return cls(*positional_only, **others)
134 f
'{func.__qualname__}_PydanticSchema_{id(func)}',
137 "_func": pydantic_wrapper,
138 '__new__':
lambda cls, *args, **kwargs: pydantic_wrapper(*args, **kwargs),
139 '__call__':
lambda *args, **kwargs: pydantic_wrapper(*args, **kwargs)
142 _class.from_json = classmethod(from_json)
143 _class.fields = classmethod(
lambda cls: fields)