Formatron v0.4.2
Formatron empowers everyone to control the output format of language models with minimal overhead.
Loading...
Searching...
No Matches
pydantic.py
Go to the documentation of this file.
1"""
2A module that implements the Schema interface using pydantic.
3"""
4import inspect
5import json
6import typing
7
8import pydantic.fields
9from pydantic import BaseModel, validate_call, ConfigDict, Field
10
11from formatron.schemas.schema import FieldInfo, Schema
12
13
14class FieldInfo(FieldInfo):
15 """
16 A wrapper for pydantic FieldInfo.
17 """
18 __slots__ = ("_field",)
19
20 def __init__(self, field: pydantic.fields.FieldInfo):
21 """
22 Initialize the field information.
23 """
24 self._field = field
26 @property
27 def annotation(self) -> typing.Type[typing.Any] | None:
28 return self._field.annotation
29
30 @property
31 def required(self) -> bool:
32 return self._field.is_required()
33
34 def __repr__(self):
35 return repr(self._field)
36
37 def __str__(self):
38 return str(self._field)
39
40
41class ClassSchema(BaseModel, Schema):
42 """
43 A wrapper for pydantic BaseModel that implements the Schema interface.
44 """
45 __cached_fields__ = None
46
47 @classmethod
48 def fields(cls) -> typing.Dict[str, typing.Any]:
49 if cls.__cached_fields__ is not None:
50 return cls.__cached_fields__
51 cls.__cached_fields__ = {k: FieldInfo(v) for k, v in cls.model_fields.items()}
52 return cls.__cached_fields__
54 @classmethod
55 def from_json(cls, _json: str) -> "ClassSchema":
56 """
57 Create a ClassSchema from a JSON string.
58 """
59 return cls.model_validate_json(_json)
60
61
62CallableT = typing.TypeVar('CallableT', bound=typing.Callable)
64
65def callable_schema(func: CallableT, /, *, config: ConfigDict = None, validate_return: bool = False) -> CallableT:
66 """
67 A decorator that wraps pydantic's validate_call. The decorated callable also implements the Schema interface.
68
69 Args:
70 func: The function to decorate.
71 config: The pydantic configuration of validate_call.
72 validate_return: Whether to validate the return value.
73
74 Returns:
75 The decorated callable.
76 """
77 pydantic_wrapper = validate_call(config=config, validate_return=validate_return)(func)
78 signature = inspect.signature(func, eval_str=True)
79 fields = {}
80 for k, p in signature.parameters.items():
81 default = None
82 if p.default is not inspect.Signature.empty:
83 default = p.default
84 actual_type = p.annotation
85 metadata = []
86 if isinstance(p.default, pydantic.fields.FieldInfo):
87 fields[k] = p.default
88 if typing.get_origin(p.annotation) is typing.Annotated:
89 actual_type, *meta = typing.get_args(p.annotation)
90 fieldinfo = None
91 for i in meta:
92 if isinstance(i, pydantic.fields.FieldInfo):
93 fieldinfo = i
94 else:
95 metadata.append(i)
96 if fieldinfo is not None:
97 fields[k] = fieldinfo
98 if k in fields:
99 fields[k].default = default
100 fields[k].annotation = actual_type
101 fields[k].metadata.extend(metadata)
102 continue
103 if default is not None:
104 fields[k] = Field(default)
105 else:
106 fields[k] = Field()
107 fields[k].annotation = actual_type
108 fields[k].metadata.extend(metadata)
109 for k in fields:
110 fields[k] = FieldInfo(fields[k])
111
112 def from_json(cls, json_str):
113 json_data = json.loads(json_str)
114 positional_only = []
115 others = {}
116 for k, p in signature.parameters.items():
117 if p.kind == p.POSITIONAL_ONLY:
118 positional_only.append(json_data[k])
119 else:
120 others[k] = json_data[k]
121 return cls(*positional_only, **others)
122
123 _class = type(
124 f'{func.__qualname__}_PydanticSchema_{id(func)}',
125 (Schema,),
126 {
127 "_func": pydantic_wrapper,
128 '__new__': lambda cls, *args, **kwargs: pydantic_wrapper(*args, **kwargs),
129 '__call__': lambda *args, **kwargs: pydantic_wrapper(*args, **kwargs) # make duck typer happy
130 }
131 )
132 _class.from_json = classmethod(from_json)
133 _class.fields = classmethod(lambda cls: fields)
134 return _class
A wrapper for pydantic BaseModel that implements the Schema interface.
Definition pydantic.py:63
A wrapper for pydantic FieldInfo.
Definition pydantic.py:18
__init__(self, pydantic.fields.FieldInfo field)
Initialize the field information.
Definition pydantic.py:24
typing.Type[typing.Any]|None annotation(self)
Definition pydantic.py:35
An abstract schema that describes some data.
Definition schema.py:48
CallableT callable_schema(CallableT func, *, ConfigDict config=None, bool validate_return=False)
A decorator that wraps pydantic's validate_call.
Definition pydantic.py:113