3
3
from typing import Type
4
4
5
5
from fastapi import Body
6
+ from fastapi import Depends
6
7
from fastapi import Form
7
8
from pydantic import BaseModel
8
9
from pydantic .fields import ModelField
9
10
10
11
11
- class PydanticConverter :
12
-
12
+ class PydanticConverterUtils :
13
13
@classmethod
14
14
def form (cls , field : ModelField ) -> Body :
15
15
if field .required is True :
@@ -32,28 +32,60 @@ def __form(cls, model_field: ModelField, default: Any):
32
32
regex = model_field .field_info .regex or None ,
33
33
)
34
34
35
- @classmethod
36
- def body (cls , parent_cls : Type [BaseModel ]):
37
- new_parameters = []
38
-
39
- for field_name , model_field in parent_cls .__fields__ .items ():
40
- model_field : ModelField
41
-
42
- new_parameters .append (
43
- inspect .Parameter (
44
- field_name ,
45
- inspect .Parameter .POSITIONAL_ONLY ,
46
- default = cls .form (field = model_field ),
47
- annotation = model_field .outer_type_ ,
35
+
36
+ class PydanticConverter :
37
+
38
+ @staticmethod
39
+ def body (cls : Type [BaseModel ]) -> Type [BaseModel ]:
40
+ """
41
+ Adds an `body` class method to decorated models. The `body` class
42
+ method can be used with `FastAPI` endpoints.
43
+
44
+ Args:
45
+ cls: The model class to decorate.
46
+
47
+ Returns:
48
+ The decorated class.
49
+ """
50
+
51
+ def make_form_parameter (field : ModelField ) -> Any :
52
+ """
53
+ Converts a field from a `Pydantic` model to the appropriate `FastAPI`
54
+ parameter type.
55
+
56
+ Args:
57
+ field: The field to convert.
58
+
59
+ Returns:
60
+ Either the result of `Form`, if the field is not a sub-model, or
61
+ the result of `Depends` if it is.
62
+
63
+ """
64
+ if issubclass (field .type_ , BaseModel ):
65
+ # This is a sub-model.
66
+ assert hasattr (field .type_ , "body" ), (
67
+ f"Sub-model class for { field .name } field must be decorated with"
68
+ f" `as_form` too."
48
69
)
70
+ return Depends (field .type_ .body ) # noqa
71
+ else :
72
+ return PydanticConverterUtils .form (field = field )
73
+
74
+ new_params = [
75
+ inspect .Parameter (
76
+ field .alias ,
77
+ inspect .Parameter .POSITIONAL_ONLY ,
78
+ default = make_form_parameter (field ),
49
79
)
80
+ for field in cls .__fields__ .values ()
81
+ ]
50
82
51
- def as_form_func ( * args , ** kwargs ):
52
- return parent_cls ( * args , ** kwargs ) # noqa
83
+ async def _as_form ( ** data ):
84
+ return cls ( ** data )
53
85
54
- sig = inspect .signature (as_form_func )
55
- sig = sig .replace (parameters = new_parameters )
56
- as_form_func .__signature__ = sig
57
- setattr (cls , ' body' , as_form_func )
86
+ sig = inspect .signature (_as_form )
87
+ sig = sig .replace (parameters = new_params )
88
+ _as_form .__signature__ = sig
89
+ setattr (cls , " body" , _as_form )
58
90
return cls
59
91
0 commit comments