GeneralManager 0.13.1__py3-none-any.whl → 0.14.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- general_manager/api/graphql.py +162 -67
- general_manager/api/mutation.py +15 -7
- general_manager/api/property.py +95 -10
- general_manager/apps.py +14 -11
- general_manager/bucket/calculationBucket.py +203 -45
- general_manager/bucket/databaseBucket.py +149 -17
- general_manager/interface/baseInterface.py +32 -10
- general_manager/permission/mutationPermission.py +3 -1
- general_manager/utils/argsToKwargs.py +7 -7
- {generalmanager-0.13.1.dist-info → generalmanager-0.14.0.dist-info}/METADATA +1 -1
- {generalmanager-0.13.1.dist-info → generalmanager-0.14.0.dist-info}/RECORD +14 -14
- {generalmanager-0.13.1.dist-info → generalmanager-0.14.0.dist-info}/WHEEL +0 -0
- {generalmanager-0.13.1.dist-info → generalmanager-0.14.0.dist-info}/licenses/LICENSE +0 -0
- {generalmanager-0.13.1.dist-info → generalmanager-0.14.0.dist-info}/top_level.txt +0 -0
general_manager/api/graphql.py
CHANGED
@@ -1,6 +1,18 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
import graphene
|
3
|
-
from typing import
|
3
|
+
from typing import (
|
4
|
+
Any,
|
5
|
+
Callable,
|
6
|
+
get_args,
|
7
|
+
get_origin,
|
8
|
+
TYPE_CHECKING,
|
9
|
+
cast,
|
10
|
+
Type,
|
11
|
+
Generator,
|
12
|
+
Union,
|
13
|
+
)
|
14
|
+
from types import UnionType
|
15
|
+
|
4
16
|
from decimal import Decimal
|
5
17
|
from datetime import date, datetime
|
6
18
|
import json
|
@@ -156,18 +168,45 @@ class GraphQL:
|
|
156
168
|
fields[resolver_name] = cls._createResolver(field_name, field_type)
|
157
169
|
|
158
170
|
# handle GraphQLProperty attributes
|
159
|
-
for
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
171
|
+
for (
|
172
|
+
attr_name,
|
173
|
+
attr_value,
|
174
|
+
) in generalManagerClass.Interface.getGraphQLProperties().items():
|
175
|
+
raw_hint = attr_value.graphql_type_hint
|
176
|
+
origin = get_origin(raw_hint)
|
177
|
+
type_args = [t for t in get_args(raw_hint) if t is not type(None)]
|
178
|
+
|
179
|
+
if origin in (Union, UnionType) and type_args:
|
180
|
+
raw_hint = type_args[0]
|
181
|
+
origin = get_origin(raw_hint)
|
182
|
+
type_args = [t for t in get_args(raw_hint) if t is not type(None)]
|
183
|
+
|
184
|
+
if origin in (list, tuple, set):
|
185
|
+
element = type_args[0] if type_args else Any
|
186
|
+
if isinstance(element, type) and issubclass(element, GeneralManager): # type: ignore
|
187
|
+
graphene_field = graphene.List(
|
188
|
+
lambda elem=element: GraphQL.graphql_type_registry[
|
189
|
+
elem.__name__
|
190
|
+
]
|
191
|
+
)
|
192
|
+
else:
|
193
|
+
base_type = GraphQL._mapFieldToGrapheneBaseType(
|
194
|
+
cast(type, element if isinstance(element, type) else str)
|
195
|
+
)
|
196
|
+
graphene_field = graphene.List(base_type)
|
197
|
+
resolved_type = cast(
|
198
|
+
type, element if isinstance(element, type) else str
|
166
199
|
)
|
167
|
-
|
168
|
-
|
169
|
-
|
200
|
+
else:
|
201
|
+
resolved_type = (
|
202
|
+
cast(type, type_args[0]) if type_args else cast(type, raw_hint)
|
170
203
|
)
|
204
|
+
graphene_field = cls._mapFieldToGrapheneRead(resolved_type, attr_name)
|
205
|
+
|
206
|
+
fields[attr_name] = graphene_field
|
207
|
+
fields[f"resolve_{attr_name}"] = cls._createResolver(
|
208
|
+
attr_name, resolved_type
|
209
|
+
)
|
171
210
|
|
172
211
|
graphene_type = type(graphene_type_name, (graphene.ObjectType,), fields)
|
173
212
|
cls.graphql_type_registry[generalManagerClass.__name__] = graphene_type
|
@@ -194,6 +233,20 @@ class GraphQL:
|
|
194
233
|
else:
|
195
234
|
sort_options.append(field_name)
|
196
235
|
|
236
|
+
for (
|
237
|
+
prop_name,
|
238
|
+
prop,
|
239
|
+
) in generalManagerClass.Interface.getGraphQLProperties().items():
|
240
|
+
if prop.sortable is False:
|
241
|
+
continue
|
242
|
+
type_hints = [
|
243
|
+
t for t in get_args(prop.graphql_type_hint) if t is not type(None)
|
244
|
+
]
|
245
|
+
field_type = (
|
246
|
+
type_hints[0] if type_hints else cast(type, prop.graphql_type_hint)
|
247
|
+
)
|
248
|
+
sort_options.append(prop_name)
|
249
|
+
|
197
250
|
if not sort_options:
|
198
251
|
return None
|
199
252
|
|
@@ -203,9 +256,54 @@ class GraphQL:
|
|
203
256
|
{option: option for option in sort_options},
|
204
257
|
)
|
205
258
|
|
259
|
+
@staticmethod
|
260
|
+
def _getFilterOptions(attribute_type: type, attribute_name: str) -> Generator[
|
261
|
+
tuple[
|
262
|
+
str, type[graphene.ObjectType] | MeasurementScalar | graphene.List | None
|
263
|
+
],
|
264
|
+
None,
|
265
|
+
None,
|
266
|
+
]:
|
267
|
+
number_options = ["exact", "gt", "gte", "lt", "lte"]
|
268
|
+
string_options = [
|
269
|
+
"exact",
|
270
|
+
"icontains",
|
271
|
+
"contains",
|
272
|
+
"in",
|
273
|
+
"startswith",
|
274
|
+
"endswith",
|
275
|
+
]
|
276
|
+
|
277
|
+
if issubclass(attribute_type, GeneralManager):
|
278
|
+
yield attribute_name, None
|
279
|
+
elif issubclass(attribute_type, Measurement):
|
280
|
+
yield attribute_name, MeasurementScalar()
|
281
|
+
for option in number_options:
|
282
|
+
yield f"{attribute_name}__{option}", MeasurementScalar()
|
283
|
+
else:
|
284
|
+
yield attribute_name, GraphQL._mapFieldToGrapheneRead(
|
285
|
+
attribute_type, attribute_name
|
286
|
+
)
|
287
|
+
if issubclass(attribute_type, (int, float, Decimal, date, datetime)):
|
288
|
+
for option in number_options:
|
289
|
+
yield f"{attribute_name}__{option}", (
|
290
|
+
GraphQL._mapFieldToGrapheneRead(attribute_type, attribute_name)
|
291
|
+
)
|
292
|
+
elif issubclass(attribute_type, str):
|
293
|
+
base_type = GraphQL._mapFieldToGrapheneBaseType(attribute_type)
|
294
|
+
for option in string_options:
|
295
|
+
if option == "in":
|
296
|
+
yield f"{attribute_name}__in", graphene.List(base_type)
|
297
|
+
else:
|
298
|
+
yield f"{attribute_name}__{option}", (
|
299
|
+
GraphQL._mapFieldToGrapheneRead(
|
300
|
+
attribute_type, attribute_name
|
301
|
+
)
|
302
|
+
)
|
303
|
+
|
206
304
|
@staticmethod
|
207
305
|
def _createFilterOptions(
|
208
|
-
|
306
|
+
field_type: GeneralManagerMeta,
|
209
307
|
) -> type[graphene.InputObjectType] | None:
|
210
308
|
"""
|
211
309
|
Dynamically generates a Graphene InputObjectType for filtering fields of a GeneralManager subclass.
|
@@ -213,21 +311,11 @@ class GraphQL:
|
|
213
311
|
Creates filter fields for each attribute based on its type, supporting numeric and string filter operations, and specialized handling for Measurement attributes. Returns the generated InputObjectType, or None if no applicable filter fields exist.
|
214
312
|
|
215
313
|
Parameters:
|
216
|
-
field_name (str): The name of the field to generate filter options for.
|
217
314
|
field_type (GeneralManagerMeta): The manager class whose attributes are used to build filter fields.
|
218
315
|
|
219
316
|
Returns:
|
220
317
|
type[graphene.InputObjectType] | None: The generated filter input type, or None if no filter fields are applicable.
|
221
318
|
"""
|
222
|
-
number_options = ["exact", "gt", "gte", "lt", "lte"]
|
223
|
-
string_options = [
|
224
|
-
"exact",
|
225
|
-
"icontains",
|
226
|
-
"contains",
|
227
|
-
"in",
|
228
|
-
"startswith",
|
229
|
-
"endswith",
|
230
|
-
]
|
231
319
|
|
232
320
|
graphene_filter_type_name = f"{field_type.__name__}FilterType"
|
233
321
|
if graphene_filter_type_name in GraphQL.graphql_filter_type_registry:
|
@@ -236,26 +324,28 @@ class GraphQL:
|
|
236
324
|
filter_fields = {}
|
237
325
|
for attr_name, attr_info in field_type.Interface.getAttributeTypes().items():
|
238
326
|
attr_type = attr_info["type"]
|
239
|
-
|
327
|
+
filter_fields = {
|
328
|
+
**filter_fields,
|
329
|
+
**{
|
330
|
+
k: v
|
331
|
+
for k, v in GraphQL._getFilterOptions(attr_type, attr_name)
|
332
|
+
if v is not None
|
333
|
+
},
|
334
|
+
}
|
335
|
+
for prop_name, prop in field_type.Interface.getGraphQLProperties().items():
|
336
|
+
if not prop.filterable:
|
240
337
|
continue
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
GraphQL._mapFieldToGrapheneRead(attr_type, attr_name)
|
253
|
-
)
|
254
|
-
elif issubclass(attr_type, str):
|
255
|
-
for option in string_options:
|
256
|
-
filter_fields[f"{attr_name}__{option}"] = (
|
257
|
-
GraphQL._mapFieldToGrapheneRead(attr_type, attr_name)
|
258
|
-
)
|
338
|
+
hints = [t for t in get_args(prop.graphql_type_hint) if t is not type(None)]
|
339
|
+
prop_type = hints[0] if hints else cast(type, prop.graphql_type_hint)
|
340
|
+
filter_fields = {
|
341
|
+
**filter_fields,
|
342
|
+
**{
|
343
|
+
k: v
|
344
|
+
for k, v in GraphQL._getFilterOptions(prop_type, prop_name)
|
345
|
+
if v is not None
|
346
|
+
},
|
347
|
+
}
|
348
|
+
|
259
349
|
if not filter_fields:
|
260
350
|
return None
|
261
351
|
|
@@ -284,14 +374,14 @@ class GraphQL:
|
|
284
374
|
"page_size": graphene.Int(),
|
285
375
|
"group_by": graphene.List(graphene.String),
|
286
376
|
}
|
287
|
-
filter_options = GraphQL._createFilterOptions(
|
377
|
+
filter_options = GraphQL._createFilterOptions(field_type)
|
288
378
|
if filter_options:
|
289
|
-
attributes["filter"] = filter_options
|
290
|
-
attributes["exclude"] = filter_options
|
379
|
+
attributes["filter"] = graphene.Argument(filter_options)
|
380
|
+
attributes["exclude"] = graphene.Argument(filter_options)
|
291
381
|
|
292
382
|
sort_by_options = GraphQL._sortByOptions(field_type)
|
293
383
|
if sort_by_options:
|
294
|
-
attributes["sort_by"] = sort_by_options
|
384
|
+
attributes["sort_by"] = graphene.Argument(sort_by_options)
|
295
385
|
|
296
386
|
page_type = GraphQL._getOrCreatePageType(
|
297
387
|
field_type.__name__ + "Page",
|
@@ -299,10 +389,6 @@ class GraphQL:
|
|
299
389
|
)
|
300
390
|
return graphene.Field(page_type, **attributes)
|
301
391
|
|
302
|
-
return graphene.List(
|
303
|
-
lambda: GraphQL.graphql_type_registry[field_type.__name__],
|
304
|
-
**attributes,
|
305
|
-
)
|
306
392
|
return graphene.Field(
|
307
393
|
lambda: GraphQL.graphql_type_registry[field_type.__name__]
|
308
394
|
)
|
@@ -314,6 +400,8 @@ class GraphQL:
|
|
314
400
|
"""
|
315
401
|
Ordnet einen Python-Typ einem entsprechenden Graphene-Feld zu.
|
316
402
|
"""
|
403
|
+
if issubclass(field_type, dict):
|
404
|
+
raise TypeError("GraphQL does not support dict fields")
|
317
405
|
if issubclass(field_type, str):
|
318
406
|
return graphene.String
|
319
407
|
elif issubclass(field_type, bool):
|
@@ -322,7 +410,9 @@ class GraphQL:
|
|
322
410
|
return graphene.Int
|
323
411
|
elif issubclass(field_type, (float, Decimal)):
|
324
412
|
return graphene.Float
|
325
|
-
elif issubclass(field_type,
|
413
|
+
elif issubclass(field_type, datetime):
|
414
|
+
return graphene.DateTime
|
415
|
+
elif issubclass(field_type, date):
|
326
416
|
return graphene.Date
|
327
417
|
elif issubclass(field_type, Measurement):
|
328
418
|
return MeasurementScalar
|
@@ -385,7 +475,7 @@ class GraphQL:
|
|
385
475
|
) -> Bucket:
|
386
476
|
"""
|
387
477
|
Applies permission-based filters to a queryset according to the permission interface of the given manager class.
|
388
|
-
|
478
|
+
|
389
479
|
Returns:
|
390
480
|
A queryset containing only the items allowed by the user's read permissions. If no permission filters are defined, returns the original queryset unchanged.
|
391
481
|
"""
|
@@ -541,7 +631,7 @@ class GraphQL:
|
|
541
631
|
result = result.to(target_unit)
|
542
632
|
return {
|
543
633
|
"value": result.quantity.magnitude,
|
544
|
-
"unit": result.quantity.units,
|
634
|
+
"unit": str(result.quantity.units),
|
545
635
|
}
|
546
636
|
|
547
637
|
return resolver
|
@@ -621,15 +711,13 @@ class GraphQL:
|
|
621
711
|
"page_size": graphene.Int(),
|
622
712
|
"group_by": graphene.List(graphene.String),
|
623
713
|
}
|
624
|
-
filter_options = cls._createFilterOptions(
|
625
|
-
generalManagerClass.__name__.lower(), generalManagerClass
|
626
|
-
)
|
714
|
+
filter_options = cls._createFilterOptions(generalManagerClass)
|
627
715
|
if filter_options:
|
628
|
-
attributes["filter"] = filter_options
|
629
|
-
attributes["exclude"] = filter_options
|
716
|
+
attributes["filter"] = graphene.Argument(filter_options)
|
717
|
+
attributes["exclude"] = graphene.Argument(filter_options)
|
630
718
|
sort_by_options = cls._sortByOptions(generalManagerClass)
|
631
719
|
if sort_by_options:
|
632
|
-
attributes["sort_by"] = sort_by_options
|
720
|
+
attributes["sort_by"] = graphene.Argument(sort_by_options)
|
633
721
|
|
634
722
|
page_type = cls._getOrCreatePageType(
|
635
723
|
graphene_type.__name__ + "Page", graphene_type
|
@@ -829,7 +917,9 @@ class GraphQL:
|
|
829
917
|
"""
|
830
918
|
try:
|
831
919
|
manager_id = kwargs.pop("id", None)
|
832
|
-
|
920
|
+
if manager_id is None:
|
921
|
+
raise ValueError("id is required")
|
922
|
+
instance = generalManagerClass(id=manager_id).update(
|
833
923
|
creator_id=info.context.user.id, **kwargs
|
834
924
|
)
|
835
925
|
except Exception as e:
|
@@ -844,7 +934,7 @@ class GraphQL:
|
|
844
934
|
}
|
845
935
|
|
846
936
|
return type(
|
847
|
-
f"
|
937
|
+
f"Update{generalManagerClass.__name__}",
|
848
938
|
(graphene.Mutation,),
|
849
939
|
{
|
850
940
|
**default_return_values,
|
@@ -853,11 +943,14 @@ class GraphQL:
|
|
853
943
|
"Arguments",
|
854
944
|
(),
|
855
945
|
{
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
946
|
+
"id": graphene.ID(required=True),
|
947
|
+
**{
|
948
|
+
field_name: field
|
949
|
+
for field_name, field in cls.createWriteFields(
|
950
|
+
interface_cls
|
951
|
+
).items()
|
952
|
+
if field.editable
|
953
|
+
},
|
861
954
|
},
|
862
955
|
),
|
863
956
|
"mutate": update_mutation,
|
@@ -897,7 +990,9 @@ class GraphQL:
|
|
897
990
|
"""
|
898
991
|
try:
|
899
992
|
manager_id = kwargs.pop("id", None)
|
900
|
-
|
993
|
+
if manager_id is None:
|
994
|
+
raise ValueError("id is required")
|
995
|
+
instance = generalManagerClass(id=manager_id).deactivate(
|
901
996
|
creator_id=info.context.user.id
|
902
997
|
)
|
903
998
|
except Exception as e:
|
@@ -946,7 +1041,7 @@ class GraphQL:
|
|
946
1041
|
"code": "PERMISSION_DENIED",
|
947
1042
|
},
|
948
1043
|
)
|
949
|
-
elif isinstance(error, (ValueError, ValidationError)):
|
1044
|
+
elif isinstance(error, (ValueError, ValidationError, TypeError)):
|
950
1045
|
raise GraphQLError(
|
951
1046
|
str(error),
|
952
1047
|
extensions={
|
general_manager/api/mutation.py
CHANGED
@@ -19,23 +19,30 @@ from typing import TypeAliasType
|
|
19
19
|
from general_manager.permission.mutationPermission import MutationPermission
|
20
20
|
|
21
21
|
|
22
|
-
def graphQlMutation(permission: Optional[Type[MutationPermission]] = None):
|
22
|
+
def graphQlMutation(_func=None, permission: Optional[Type[MutationPermission]] = None):
|
23
23
|
"""
|
24
24
|
Decorator that converts a function into a GraphQL mutation class for use with Graphene, automatically generating argument and output fields from the function's signature and type annotations.
|
25
|
-
|
25
|
+
|
26
26
|
The decorated function must provide type hints for all parameters (except `info`) and a return annotation. The decorator dynamically constructs a mutation class with appropriate Graphene fields, enforces permission checks if a `permission` class is provided, and registers the mutation for use in the GraphQL API.
|
27
|
-
|
27
|
+
|
28
28
|
Parameters:
|
29
29
|
permission (Optional[Type[MutationPermission]]): An optional permission class to enforce access control on the mutation.
|
30
|
-
|
30
|
+
|
31
31
|
Returns:
|
32
32
|
Callable: A decorator that registers the mutation and returns the original function.
|
33
33
|
"""
|
34
|
+
if (
|
35
|
+
_func is not None
|
36
|
+
and inspect.isclass(_func)
|
37
|
+
and issubclass(_func, MutationPermission)
|
38
|
+
):
|
39
|
+
permission = _func
|
40
|
+
_func = None
|
34
41
|
|
35
42
|
def decorator(fn):
|
36
43
|
"""
|
37
44
|
Decorator that transforms a function into a GraphQL mutation class compatible with Graphene.
|
38
|
-
|
45
|
+
|
39
46
|
Analyzes the decorated function's signature and type hints to dynamically generate a mutation class with appropriate argument and output fields. Handles permission checks if a permission class is provided, manages mutation execution, and registers the mutation for use in the GraphQL API. On success, returns output fields and a `success` flag; on error, returns only `success=False`.
|
40
47
|
"""
|
41
48
|
sig = inspect.signature(fn)
|
@@ -122,10 +129,9 @@ def graphQlMutation(permission: Optional[Type[MutationPermission]] = None):
|
|
122
129
|
|
123
130
|
# Define mutate method
|
124
131
|
def _mutate(root, info, **kwargs):
|
125
|
-
|
126
132
|
"""
|
127
133
|
Handles the execution of a GraphQL mutation, including permission checks, result unpacking, and error handling.
|
128
|
-
|
134
|
+
|
129
135
|
Returns:
|
130
136
|
An instance of the mutation class with output fields populated from the mutation result and a success status.
|
131
137
|
"""
|
@@ -169,4 +175,6 @@ def graphQlMutation(permission: Optional[Type[MutationPermission]] = None):
|
|
169
175
|
|
170
176
|
return fn
|
171
177
|
|
178
|
+
if _func is not None and inspect.isfunction(_func):
|
179
|
+
return decorator(_func)
|
172
180
|
return decorator
|
general_manager/api/property.py
CHANGED
@@ -1,20 +1,105 @@
|
|
1
|
-
from typing import Any, Callable, get_type_hints
|
1
|
+
from typing import Any, Callable, get_type_hints, overload, TypeVar
|
2
|
+
import sys
|
3
|
+
|
4
|
+
T = TypeVar("T", bound=Callable[..., Any])
|
2
5
|
|
3
6
|
|
4
7
|
class GraphQLProperty(property):
|
5
|
-
|
8
|
+
sortable: bool
|
9
|
+
filterable: bool
|
10
|
+
query_annotation: Any | None
|
11
|
+
|
12
|
+
def __init__(
|
13
|
+
self,
|
14
|
+
fget: Callable[..., Any],
|
15
|
+
doc: str | None = None,
|
16
|
+
*,
|
17
|
+
sortable: bool = False,
|
18
|
+
filterable: bool = False,
|
19
|
+
query_annotation: Any | None = None,
|
20
|
+
) -> None:
|
6
21
|
super().__init__(fget, doc=doc)
|
7
22
|
self.is_graphql_resolver = True
|
8
|
-
self.
|
23
|
+
self._owner: type | None = None
|
24
|
+
self._name: str | None = None
|
25
|
+
self._graphql_type_hint: Any | None = None
|
26
|
+
|
27
|
+
self.sortable = sortable
|
28
|
+
self.filterable = filterable
|
29
|
+
self.query_annotation = query_annotation
|
30
|
+
|
31
|
+
orig = getattr(
|
32
|
+
fget, "__wrapped__", fget
|
33
|
+
) # falls decorator Annotations durchreicht
|
34
|
+
ann = getattr(orig, "__annotations__", {}) or {}
|
35
|
+
if "return" not in ann:
|
36
|
+
raise TypeError(
|
37
|
+
"GraphQLProperty requires a return type hint for the property function."
|
38
|
+
)
|
39
|
+
|
40
|
+
def __set_name__(self, owner: type, name: str) -> None:
|
41
|
+
self._owner = owner
|
42
|
+
self._name = name
|
43
|
+
|
44
|
+
def _try_resolve_type_hint(self) -> None:
|
45
|
+
if self._graphql_type_hint is not None:
|
46
|
+
return
|
47
|
+
|
48
|
+
try:
|
49
|
+
mod = sys.modules.get(self.fget.__module__)
|
50
|
+
globalns = vars(mod) if mod else {}
|
51
|
+
|
52
|
+
localns: dict[str, Any] = {}
|
53
|
+
if self._owner is not None:
|
54
|
+
localns = dict(self._owner.__dict__)
|
55
|
+
localns[self._owner.__name__] = self._owner
|
9
56
|
|
57
|
+
hints = get_type_hints(self.fget, globalns=globalns, localns=localns)
|
58
|
+
self._graphql_type_hint = hints.get("return", None)
|
59
|
+
except Exception:
|
60
|
+
self._graphql_type_hint = None
|
10
61
|
|
11
|
-
|
62
|
+
@property
|
63
|
+
def graphql_type_hint(self) -> Any | None:
|
64
|
+
if self._graphql_type_hint is None:
|
65
|
+
self._try_resolve_type_hint()
|
66
|
+
return self._graphql_type_hint
|
67
|
+
|
68
|
+
|
69
|
+
@overload
|
70
|
+
def graphQlProperty(func: T) -> GraphQLProperty: ...
|
71
|
+
@overload
|
72
|
+
def graphQlProperty(
|
73
|
+
*,
|
74
|
+
sortable: bool = False,
|
75
|
+
filterable: bool = False,
|
76
|
+
query_annotation: Any | None = None,
|
77
|
+
) -> Callable[[T], GraphQLProperty]: ...
|
78
|
+
|
79
|
+
|
80
|
+
def graphQlProperty(
|
81
|
+
func: Callable[..., Any] | None = None,
|
82
|
+
*,
|
83
|
+
sortable: bool = False,
|
84
|
+
filterable: bool = False,
|
85
|
+
query_annotation: Any | None = None,
|
86
|
+
) -> GraphQLProperty | Callable[[T], GraphQLProperty]:
|
12
87
|
from general_manager.cache.cacheDecorator import cached
|
13
88
|
|
89
|
+
"""Decorator to create a :class:`GraphQLProperty`.
|
90
|
+
|
91
|
+
It can be used without arguments or with optional configuration for
|
92
|
+
filtering, sorting and queryset annotation.
|
14
93
|
"""
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
94
|
+
|
95
|
+
def wrapper(f: Callable[..., Any]) -> GraphQLProperty:
|
96
|
+
return GraphQLProperty(
|
97
|
+
cached()(f),
|
98
|
+
sortable=sortable,
|
99
|
+
query_annotation=query_annotation,
|
100
|
+
filterable=filterable,
|
101
|
+
)
|
102
|
+
|
103
|
+
if func is None:
|
104
|
+
return wrapper
|
105
|
+
return wrapper(func)
|
general_manager/apps.py
CHANGED
@@ -162,17 +162,20 @@ class GeneralmanagerConfig(AppConfig):
|
|
162
162
|
query_class = type("Query", (graphene.ObjectType,), GraphQL._query_fields)
|
163
163
|
GraphQL._query_class = query_class
|
164
164
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
165
|
+
if GraphQL._mutations:
|
166
|
+
mutation_class = type(
|
167
|
+
"Mutation",
|
168
|
+
(graphene.ObjectType,),
|
169
|
+
{name: mutation.Field() for name, mutation in GraphQL._mutations.items()},
|
170
|
+
)
|
171
|
+
GraphQL._mutation_class = mutation_class
|
172
|
+
schema = graphene.Schema(
|
173
|
+
query=GraphQL._query_class,
|
174
|
+
mutation=mutation_class,
|
175
|
+
)
|
176
|
+
else:
|
177
|
+
GraphQL._mutation_class = None
|
178
|
+
schema = graphene.Schema(query=GraphQL._query_class)
|
176
179
|
GeneralmanagerConfig.addGraphqlUrl(schema)
|
177
180
|
|
178
181
|
@staticmethod
|