66import strawberry
77import uvicorn
88from fastapi import FastAPI
9- from pydantic import create_model
109from strawberry .asgi import GraphQL
1110from strawberry .tools import create_type
11+ from strawberry .types .field import StrawberryField
1212
1313from fastcs .attributes import AttrR , AttrRW , AttrW , T
1414from fastcs .controller import BaseController
@@ -22,39 +22,30 @@ class GraphQLServerOptions:
2222 log_level : str = "info"
2323
2424
25- @strawberry .type
26- class User :
27- name : str
28- age : int
29-
30-
31- @strawberry .type
32- class Query :
33- @strawberry .field
34- def user (self ) -> User :
35- return User (name = "Patrick" , age = 100 )
36-
37-
38- schema = strawberry .Schema (query = Query )
39-
40-
4125class GraphQLServer :
4226 def __init__ (self , mapping : Mapping ):
4327 self ._mapping = mapping
28+ self ._field_dict : dict [str , list [StrawberryField ]] = {
29+ "Query" : [],
30+ "Mutation" : [],
31+ }
4432 self ._app = self ._create_app ()
4533
46- def _create_app (self ):
47- Query = self ._create_query (self ._mapping )
48- Mutation = self ._create_mutation ()
49- # https://docs.github.com/en/graphql/guides/forming-calls-with-graphql
50- schema = strawberry .Schema (query = Query , mutation = Mutation )
51- graphql_app = GraphQL (schema )
34+ def _create_app (self ) -> FastAPI :
35+ _add_dev_attributes (self ._field_dict , self ._mapping )
36+ _add_dev_commands (self ._field_dict , self ._mapping )
37+
38+ schema_kwargs = {}
39+ for key , value in self ._field_dict .items ():
40+ if self ._field_dict [key ]:
41+ # Strawberry types map to graphql object
42+ schema_kwargs [key .lower ()] = create_type (key , value )
43+ schema = strawberry .Schema (** schema_kwargs ) # type: ignore
44+ graphql_app : GraphQL = GraphQL (schema )
5245
5346 app = FastAPI ()
5447 app .add_route ("/graphql" , graphql_app )
5548 app .add_websocket_route ("/graphql" , graphql_app )
56- # _add_dev_attributes(app, self._mapping)
57- # _add_dev_commands(app, self._mapping)
5849
5950 return app
6051
@@ -70,106 +61,95 @@ def run(self, options: GraphQLServerOptions | None = None) -> None:
7061 )
7162
7263
73- def _put_request_body (attribute : AttrW [T ]):
74- return create_model (
75- f"Put{ str (attribute .datatype .dtype )} Value" ,
76- ** {"value" : (attribute .datatype .dtype , ...)}, # type: ignore
77- )
78-
79-
80- def _wrap_attr_put (
64+ def _wrap_attr_set (
65+ d_attr_name : str ,
8166 attribute : AttrW [T ],
8267) -> Callable [[T ], Coroutine [Any , Any , None ]]:
83- async def attr_set (request ):
84- await attribute .process_without_display_update (request .value )
68+ async def _dynamic_f (value ):
69+ await attribute .process_without_display_update (value )
70+ return value
8571
86- # Fast api uses type annotations for validation, schema, conversions
87- attr_set .__annotations__ ["request" ] = _put_request_body (attribute )
72+ # Add type annotations for validation, schema, conversions
73+ _dynamic_f .__name__ = d_attr_name
74+ _dynamic_f .__annotations__ ["value" ] = attribute .datatype .dtype
75+ _dynamic_f .__annotations__ ["return" ] = attribute .datatype .dtype
8876
89- return attr_set
90-
91-
92- def _get_response_body (attribute : AttrR [T ]):
93- return create_model (
94- f"Get{ str (attribute .datatype .dtype )} Value" ,
95- ** {"value" : (attribute .datatype .dtype , ...)}, # type: ignore
96- )
77+ return _dynamic_f
9778
9879
9980def _wrap_attr_get (
81+ d_attr_name : str ,
10082 attribute : AttrR [T ],
10183) -> Callable [[], Coroutine [Any , Any , Any ]]:
102- async def attr_get () -> Any : # Must be any as response_model is set
103- value = attribute .get () # type: ignore
104- return {"value" : value }
84+ async def _dynamic_f () -> Any :
85+ return attribute .get ()
10586
106- return attr_get
87+ _dynamic_f .__name__ = d_attr_name
88+ _dynamic_f .__annotations__ ["return" ] = attribute .datatype .dtype
10789
90+ return _dynamic_f
10891
109- def _add_dev_attributes (app : FastAPI , mapping : Mapping ) -> None :
92+
93+ def _add_dev_attributes (
94+ field_dict : dict [str , list [StrawberryField ]], mapping : Mapping
95+ ) -> None :
96+ pass
11097 for single_mapping in mapping .get_controller_mappings ():
11198 path = single_mapping .controller .path
99+ # nest for each controller
100+ # if path:
112101
113102 for attr_name , attribute in single_mapping .attributes .items ():
114103 attr_name = attr_name .title ().replace ("_" , "" )
115104 d_attr_name = f"{ '/' .join (path )} /{ attr_name } " if path else attr_name
116105
117106 match attribute :
118- # https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods
119107 case AttrRW ():
120- strawberry .type
121- app .add_api_route (
122- f"/{ d_attr_name } " ,
123- _wrap_attr_get (attribute ),
124- methods = ["GET" ], # Idemponent and safe data retrieval,
125- status_code = 200 , # https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET
126- response_model = _get_response_body (attribute ),
127- )
128- app .add_api_route (
129- f"/{ d_attr_name } " ,
130- _wrap_attr_put (attribute ),
131- methods = ["PUT" ], # Idempotent state change
132- status_code = 204 , # https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT
108+ field_dict ["Query" ].append (
109+ strawberry .field (_wrap_attr_get (d_attr_name , attribute ))
133110 )
111+ field_dict ["Mutation" ].append (
112+ strawberry .mutation (_wrap_attr_set (d_attr_name , attribute ))
113+ ) # mutation for server changes https://graphql.org/learn/queries/
114+
134115 case AttrR ():
135- app .add_api_route (
136- f"/{ d_attr_name } " ,
137- _wrap_attr_get (attribute ),
138- methods = ["GET" ],
139- status_code = 200 ,
140- response_model = _get_response_body (attribute ),
116+ field_dict ["Query" ].append (
117+ strawberry .field (_wrap_attr_get (d_attr_name , attribute ))
141118 )
119+
142120 case AttrW ():
143- app .add_api_route (
144- f"/{ d_attr_name } " ,
145- _wrap_attr_put (attribute ),
146- methods = ["PUT" ],
147- status_code = 204 ,
121+ field_dict ["Mutation" ].append (
122+ strawberry .mutation (_wrap_attr_set (d_attr_name , attribute ))
148123 )
149124
150125
151126def _wrap_command (
152- method : Callable , controller : BaseController
153- ) -> Callable [..., Awaitable [None ]]:
154- async def command () -> None :
127+ method_name : str , method : Callable , controller : BaseController
128+ ) -> Callable [..., Awaitable [bool ]]:
129+ async def _dynamic_f () -> bool :
155130 await MethodType (method , controller )()
131+ return True
156132
157- return command
133+ _dynamic_f . __name__ = method_name
158134
135+ return _dynamic_f
159136
160- def _add_dev_commands (app : FastAPI , mapping : Mapping ) -> None :
137+
138+ def _add_dev_commands (
139+ field_dict : dict [str , list [StrawberryField ]], mapping : Mapping
140+ ) -> None :
161141 for single_mapping in mapping .get_controller_mappings ():
162142 path = single_mapping .controller .path
163143
164144 for name , method in single_mapping .command_methods .items ():
165145 cmd_name = name .title ().replace ("_" , "" )
166146 d_cmd_name = f"{ '/' .join (path )} /{ cmd_name } " if path else cmd_name
167- app . add_api_route (
168- f"/ { d_cmd_name } " ,
169- _wrap_command (
170- method . fn ,
171- single_mapping . controller ,
172- ) ,
173- methods = [ "PUT" ],
174- status_code = 204 ,
147+ field_dict [ "Mutation" ]. append (
148+ strawberry . mutation (
149+ _wrap_command (
150+ d_cmd_name ,
151+ method . fn ,
152+ single_mapping . controller ,
153+ )
154+ )
175155 )
0 commit comments