GeneralManager 0.19.1__py3-none-any.whl → 0.19.2__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.
Potentially problematic release.
This version of GeneralManager might be problematic. Click here for more details.
- general_manager/_types/api.py +4 -4
- general_manager/_types/bucket.py +4 -4
- general_manager/_types/cache.py +6 -6
- general_manager/_types/factory.py +35 -35
- general_manager/_types/general_manager.py +9 -9
- general_manager/_types/interface.py +5 -5
- general_manager/_types/manager.py +4 -4
- general_manager/_types/measurement.py +1 -1
- general_manager/_types/permission.py +3 -3
- general_manager/_types/utils.py +12 -12
- general_manager/api/graphql.py +118 -95
- general_manager/api/mutation.py +9 -9
- general_manager/api/property.py +4 -4
- general_manager/apps.py +31 -31
- general_manager/bucket/{baseBucket.py → base_bucket.py} +5 -5
- general_manager/bucket/{calculationBucket.py → calculation_bucket.py} +10 -10
- general_manager/bucket/{databaseBucket.py → database_bucket.py} +16 -19
- general_manager/bucket/{groupBucket.py → group_bucket.py} +8 -8
- general_manager/cache/{cacheDecorator.py → cache_decorator.py} +5 -5
- general_manager/cache/{cacheTracker.py → cache_tracker.py} +1 -1
- general_manager/cache/{dependencyIndex.py → dependency_index.py} +1 -1
- general_manager/cache/{modelDependencyCollector.py → model_dependency_collector.py} +4 -4
- general_manager/cache/signals.py +1 -1
- general_manager/factory/{autoFactory.py → auto_factory.py} +24 -19
- general_manager/factory/factories.py +10 -13
- general_manager/factory/{factoryMethods.py → factory_methods.py} +19 -17
- general_manager/interface/{baseInterface.py → base_interface.py} +29 -21
- general_manager/interface/{calculationInterface.py → calculation_interface.py} +10 -10
- general_manager/interface/{databaseBasedInterface.py → database_based_interface.py} +42 -42
- general_manager/interface/{databaseInterface.py → database_interface.py} +15 -15
- general_manager/interface/models.py +3 -3
- general_manager/interface/{readOnlyInterface.py → read_only_interface.py} +12 -12
- general_manager/manager/{generalManager.py → general_manager.py} +11 -11
- general_manager/manager/{groupManager.py → group_manager.py} +6 -6
- general_manager/manager/input.py +1 -1
- general_manager/manager/meta.py +15 -15
- general_manager/measurement/measurement.py +3 -3
- general_manager/permission/{basePermission.py → base_permission.py} +21 -21
- general_manager/permission/{managerBasedPermission.py → manager_based_permission.py} +21 -21
- general_manager/permission/{mutationPermission.py → mutation_permission.py} +12 -12
- general_manager/permission/{permissionChecks.py → permission_checks.py} +2 -2
- general_manager/permission/{permissionDataManager.py → permission_data_manager.py} +6 -6
- general_manager/permission/utils.py +6 -6
- general_manager/public_api_registry.py +75 -66
- general_manager/rule/handler.py +2 -2
- general_manager/rule/rule.py +17 -11
- general_manager/utils/{filterParser.py → filter_parser.py} +3 -3
- general_manager/utils/{jsonEncoder.py → json_encoder.py} +1 -1
- general_manager/utils/{makeCacheKey.py → make_cache_key.py} +1 -1
- general_manager/utils/{noneToZero.py → none_to_zero.py} +1 -1
- general_manager/utils/{pathMapping.py → path_mapping.py} +14 -14
- general_manager/utils/testing.py +14 -14
- {generalmanager-0.19.1.dist-info → generalmanager-0.19.2.dist-info}/METADATA +1 -1
- generalmanager-0.19.2.dist-info/RECORD +77 -0
- generalmanager-0.19.1.dist-info/RECORD +0 -77
- /general_manager/measurement/{measurementField.py → measurement_field.py} +0 -0
- /general_manager/permission/{fileBasedPermission.py → file_based_permission.py} +0 -0
- /general_manager/utils/{argsToKwargs.py → args_to_kwargs.py} +0 -0
- /general_manager/utils/{formatString.py → format_string.py} +0 -0
- {generalmanager-0.19.1.dist-info → generalmanager-0.19.2.dist-info}/WHEEL +0 -0
- {generalmanager-0.19.1.dist-info → generalmanager-0.19.2.dist-info}/licenses/LICENSE +0 -0
- {generalmanager-0.19.1.dist-info → generalmanager-0.19.2.dist-info}/top_level.txt +0 -0
general_manager/api/mutation.py
CHANGED
|
@@ -20,10 +20,10 @@ import graphene # type: ignore[import]
|
|
|
20
20
|
from graphql import GraphQLResolveInfo
|
|
21
21
|
|
|
22
22
|
from general_manager.api.graphql import GraphQL, HANDLED_MANAGER_ERRORS
|
|
23
|
-
from general_manager.manager.
|
|
23
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
24
24
|
|
|
25
|
-
from general_manager.utils.
|
|
26
|
-
from general_manager.permission.
|
|
25
|
+
from general_manager.utils.format_string import snake_to_camel
|
|
26
|
+
from general_manager.permission.mutation_permission import MutationPermission
|
|
27
27
|
from types import UnionType
|
|
28
28
|
|
|
29
29
|
|
|
@@ -91,7 +91,7 @@ class DuplicateMutationOutputNameError(ValueError):
|
|
|
91
91
|
)
|
|
92
92
|
|
|
93
93
|
|
|
94
|
-
def
|
|
94
|
+
def graph_ql_mutation(
|
|
95
95
|
_func: FuncT | type[MutationPermission] | None = None,
|
|
96
96
|
permission: Optional[Type[MutationPermission]] = None,
|
|
97
97
|
) -> FuncT | Callable[[FuncT], FuncT]:
|
|
@@ -162,14 +162,14 @@ def graphQlMutation(
|
|
|
162
162
|
if get_origin(ann) is list or get_origin(ann) is List:
|
|
163
163
|
inner = get_args(ann)[0]
|
|
164
164
|
field = graphene.List(
|
|
165
|
-
GraphQL.
|
|
165
|
+
GraphQL._map_field_to_graphene_base_type(inner),
|
|
166
166
|
**kwargs,
|
|
167
167
|
)
|
|
168
168
|
else:
|
|
169
169
|
if inspect.isclass(ann) and issubclass(ann, GeneralManager):
|
|
170
170
|
field = graphene.ID(**kwargs)
|
|
171
171
|
else:
|
|
172
|
-
field = GraphQL.
|
|
172
|
+
field = GraphQL._map_field_to_graphene_base_type(ann)(**kwargs)
|
|
173
173
|
|
|
174
174
|
arg_fields[name] = field
|
|
175
175
|
|
|
@@ -201,7 +201,7 @@ def graphQlMutation(
|
|
|
201
201
|
|
|
202
202
|
basis_type = out.__value__ if is_named_type else out
|
|
203
203
|
|
|
204
|
-
outputs[field_name] = GraphQL.
|
|
204
|
+
outputs[field_name] = GraphQL._map_field_to_graphene_read(
|
|
205
205
|
basis_type, field_name
|
|
206
206
|
)
|
|
207
207
|
|
|
@@ -220,7 +220,7 @@ def graphQlMutation(
|
|
|
220
220
|
**kwargs: Mutation arguments provided by the client.
|
|
221
221
|
|
|
222
222
|
Returns:
|
|
223
|
-
mutation_class: Instance of the mutation with output fields populated; `success` is `True` on successful execution and `False` if a handled manager error occurred (after being forwarded to GraphQL.
|
|
223
|
+
mutation_class: Instance of the mutation with output fields populated; `success` is `True` on successful execution and `False` if a handled manager error occurred (after being forwarded to GraphQL._handle_graph_ql_error).
|
|
224
224
|
"""
|
|
225
225
|
if permission:
|
|
226
226
|
permission.check(kwargs, info.context.user)
|
|
@@ -244,7 +244,7 @@ def graphQlMutation(
|
|
|
244
244
|
data["success"] = True
|
|
245
245
|
return mutation_class(**data)
|
|
246
246
|
except HANDLED_MANAGER_ERRORS as error:
|
|
247
|
-
raise GraphQL.
|
|
247
|
+
raise GraphQL._handle_graph_ql_error(error) from error
|
|
248
248
|
|
|
249
249
|
# Assemble class dict
|
|
250
250
|
class_dict: dict[str, Any] = {
|
general_manager/api/property.py
CHANGED
|
@@ -109,9 +109,9 @@ class GraphQLProperty(property):
|
|
|
109
109
|
|
|
110
110
|
|
|
111
111
|
@overload
|
|
112
|
-
def
|
|
112
|
+
def graph_ql_property(func: T) -> GraphQLProperty: ...
|
|
113
113
|
@overload
|
|
114
|
-
def
|
|
114
|
+
def graph_ql_property(
|
|
115
115
|
*,
|
|
116
116
|
sortable: bool = False,
|
|
117
117
|
filterable: bool = False,
|
|
@@ -119,14 +119,14 @@ def graphQlProperty(
|
|
|
119
119
|
) -> Callable[[T], GraphQLProperty]: ...
|
|
120
120
|
|
|
121
121
|
|
|
122
|
-
def
|
|
122
|
+
def graph_ql_property(
|
|
123
123
|
func: Callable[..., Any] | None = None,
|
|
124
124
|
*,
|
|
125
125
|
sortable: bool = False,
|
|
126
126
|
filterable: bool = False,
|
|
127
127
|
query_annotation: Any | None = None,
|
|
128
128
|
) -> GraphQLProperty | Callable[[T], GraphQLProperty]:
|
|
129
|
-
from general_manager.cache.
|
|
129
|
+
from general_manager.cache.cache_decorator import cached
|
|
130
130
|
|
|
131
131
|
"""
|
|
132
132
|
Decorate a resolver to return a cached ``GraphQLProperty`` descriptor.
|
general_manager/apps.py
CHANGED
|
@@ -8,10 +8,10 @@ from graphene_django.views import GraphQLView # type: ignore[import]
|
|
|
8
8
|
from importlib import import_module, util
|
|
9
9
|
import importlib.abc
|
|
10
10
|
import sys
|
|
11
|
-
from general_manager.manager.
|
|
11
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
12
12
|
from general_manager.manager.meta import GeneralManagerMeta
|
|
13
13
|
from general_manager.manager.input import Input
|
|
14
|
-
from general_manager.api.property import
|
|
14
|
+
from general_manager.api.property import graph_ql_property
|
|
15
15
|
from general_manager.api.graphql import GraphQL
|
|
16
16
|
from typing import TYPE_CHECKING, Any, Callable, Type, cast
|
|
17
17
|
from django.core.checks import register
|
|
@@ -43,7 +43,7 @@ class InvalidPermissionClassError(TypeError):
|
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
if TYPE_CHECKING:
|
|
46
|
-
from general_manager.interface.
|
|
46
|
+
from general_manager.interface.read_only_interface import ReadOnlyInterface
|
|
47
47
|
|
|
48
48
|
logger = logging.getLogger(__name__)
|
|
49
49
|
|
|
@@ -58,16 +58,16 @@ class GeneralmanagerConfig(AppConfig):
|
|
|
58
58
|
|
|
59
59
|
Sets up synchronization and schema validation for read-only interfaces, initializes attributes and property accessors for general manager classes, and configures the GraphQL schema and endpoint if enabled in settings.
|
|
60
60
|
"""
|
|
61
|
-
self.
|
|
62
|
-
self.
|
|
61
|
+
self.handle_read_only_interface(GeneralManagerMeta.read_only_classes)
|
|
62
|
+
self.initialize_general_manager_classes(
|
|
63
63
|
GeneralManagerMeta.pending_attribute_initialization,
|
|
64
64
|
GeneralManagerMeta.all_classes,
|
|
65
65
|
)
|
|
66
66
|
if getattr(settings, "AUTOCREATE_GRAPHQL", False):
|
|
67
|
-
self.
|
|
67
|
+
self.handle_graph_ql(GeneralManagerMeta.pending_graphql_interfaces)
|
|
68
68
|
|
|
69
69
|
@staticmethod
|
|
70
|
-
def
|
|
70
|
+
def handle_read_only_interface(
|
|
71
71
|
read_only_classes: list[Type[GeneralManager]],
|
|
72
72
|
) -> None:
|
|
73
73
|
"""
|
|
@@ -76,8 +76,8 @@ class GeneralmanagerConfig(AppConfig):
|
|
|
76
76
|
Parameters:
|
|
77
77
|
read_only_classes (list[Type[GeneralManager]]): GeneralManager subclasses whose Interface implements a ReadOnlyInterface; each class will have its read-only data synchronized before management commands and a Django system check registered to verify the Interface schema is up to date.
|
|
78
78
|
"""
|
|
79
|
-
GeneralmanagerConfig.
|
|
80
|
-
from general_manager.interface.
|
|
79
|
+
GeneralmanagerConfig.patch_read_only_interface_sync(read_only_classes)
|
|
80
|
+
from general_manager.interface.read_only_interface import ReadOnlyInterface
|
|
81
81
|
|
|
82
82
|
logger.debug("starting to register ReadOnlyInterface schema warnings...")
|
|
83
83
|
|
|
@@ -96,7 +96,7 @@ class GeneralmanagerConfig(AppConfig):
|
|
|
96
96
|
"""
|
|
97
97
|
|
|
98
98
|
def schema_check(*_: Any, **__: Any) -> list[Any]:
|
|
99
|
-
return ReadOnlyInterface.
|
|
99
|
+
return ReadOnlyInterface.ensure_schema_is_up_to_date(manager_cls, model)
|
|
100
100
|
|
|
101
101
|
return schema_check
|
|
102
102
|
|
|
@@ -113,18 +113,18 @@ class GeneralmanagerConfig(AppConfig):
|
|
|
113
113
|
)
|
|
114
114
|
|
|
115
115
|
@staticmethod
|
|
116
|
-
def
|
|
116
|
+
def patch_read_only_interface_sync(
|
|
117
117
|
general_manager_classes: list[Type[GeneralManager]],
|
|
118
118
|
) -> None:
|
|
119
119
|
"""
|
|
120
120
|
Ensure the provided GeneralManager classes' ReadOnlyInterfaces synchronize their data before any Django management command is executed.
|
|
121
121
|
|
|
122
|
-
For each class in `general_manager_classes`, calls the class's `Interface.
|
|
122
|
+
For each class in `general_manager_classes`, calls the class's `Interface.sync_data()` to keep read-only data consistent. Skips synchronization when running the autoreload subprocess of `runserver`.
|
|
123
123
|
Parameters:
|
|
124
|
-
general_manager_classes (list[Type[GeneralManager]]): GeneralManager subclasses whose `Interface` implements `
|
|
124
|
+
general_manager_classes (list[Type[GeneralManager]]): GeneralManager subclasses whose `Interface` implements `sync_data`.
|
|
125
125
|
"""
|
|
126
126
|
"""
|
|
127
|
-
Wrap BaseCommand.run_from_argv to call `
|
|
127
|
+
Wrap BaseCommand.run_from_argv to call `sync_data()` on registered ReadOnlyInterfaces before executing the original command.
|
|
128
128
|
|
|
129
129
|
Skips synchronization when the command is `runserver` and the process is the autoreload subprocess.
|
|
130
130
|
Parameters:
|
|
@@ -133,7 +133,7 @@ class GeneralmanagerConfig(AppConfig):
|
|
|
133
133
|
Returns:
|
|
134
134
|
The result returned by the original `BaseCommand.run_from_argv` call.
|
|
135
135
|
"""
|
|
136
|
-
from general_manager.interface.
|
|
136
|
+
from general_manager.interface.read_only_interface import ReadOnlyInterface
|
|
137
137
|
|
|
138
138
|
original_run_from_argv = BaseCommand.run_from_argv
|
|
139
139
|
|
|
@@ -141,7 +141,7 @@ class GeneralmanagerConfig(AppConfig):
|
|
|
141
141
|
self: BaseCommand,
|
|
142
142
|
argv: list[str],
|
|
143
143
|
) -> None:
|
|
144
|
-
# Ensure
|
|
144
|
+
# Ensure sync_data is only called at real run of runserver
|
|
145
145
|
"""
|
|
146
146
|
Synchronizes all registered ReadOnlyInterface data before running a Django management command, except when running the autoreload subprocess of `runserver`.
|
|
147
147
|
|
|
@@ -159,7 +159,7 @@ class GeneralmanagerConfig(AppConfig):
|
|
|
159
159
|
read_only_interface = cast(
|
|
160
160
|
Type[ReadOnlyInterface], general_manager_class.Interface
|
|
161
161
|
)
|
|
162
|
-
read_only_interface.
|
|
162
|
+
read_only_interface.sync_data()
|
|
163
163
|
|
|
164
164
|
logger.debug("finished syncing ReadOnlyInterface data.")
|
|
165
165
|
|
|
@@ -169,14 +169,14 @@ class GeneralmanagerConfig(AppConfig):
|
|
|
169
169
|
BaseCommand.run_from_argv = run_from_argv_with_sync # type: ignore[assignment]
|
|
170
170
|
|
|
171
171
|
@staticmethod
|
|
172
|
-
def
|
|
172
|
+
def initialize_general_manager_classes(
|
|
173
173
|
pending_attribute_initialization: list[Type[GeneralManager]],
|
|
174
174
|
all_classes: list[Type[GeneralManager]],
|
|
175
175
|
) -> None:
|
|
176
176
|
"""
|
|
177
177
|
Initialize GeneralManager classes' interface attributes, create attribute-based accessors, wire GraphQL connection properties between related managers, and validate each class's permission configuration.
|
|
178
178
|
|
|
179
|
-
For each class in `pending_attribute_initialization` this assigns the class's Interface attributes to its internal `_attributes` and creates property accessors for those attributes. For each class in `all_classes` this scans its Interface `input_fields` for inputs whose type is another GeneralManager subclass and adds a GraphQL property on the connected manager that resolves related objects filtered by the input attribute. Finally, validate and normalize the Permission attribute on every class via GeneralmanagerConfig.
|
|
179
|
+
For each class in `pending_attribute_initialization` this assigns the class's Interface attributes to its internal `_attributes` and creates property accessors for those attributes. For each class in `all_classes` this scans its Interface `input_fields` for inputs whose type is another GeneralManager subclass and adds a GraphQL property on the connected manager that resolves related objects filtered by the input attribute. Finally, validate and normalize the Permission attribute on every class via GeneralmanagerConfig.check_permission_class.
|
|
180
180
|
|
|
181
181
|
Parameters:
|
|
182
182
|
pending_attribute_initialization (list[type[GeneralManager]]): GeneralManager classes whose Interface attributes need to be initialized and whose attribute properties should be created.
|
|
@@ -206,9 +206,9 @@ class GeneralmanagerConfig(AppConfig):
|
|
|
206
206
|
|
|
207
207
|
logger.debug("starting to create attributes for GeneralManager classes...")
|
|
208
208
|
for general_manager_class in pending_attribute_initialization:
|
|
209
|
-
attributes = general_manager_class.Interface.
|
|
209
|
+
attributes = general_manager_class.Interface.get_attributes()
|
|
210
210
|
general_manager_class._attributes = attributes
|
|
211
|
-
GeneralManagerMeta.
|
|
211
|
+
GeneralManagerMeta.create_at_properties_for_attributes(
|
|
212
212
|
attributes.keys(), general_manager_class
|
|
213
213
|
)
|
|
214
214
|
|
|
@@ -226,13 +226,13 @@ class GeneralmanagerConfig(AppConfig):
|
|
|
226
226
|
setattr(
|
|
227
227
|
connected_manager,
|
|
228
228
|
f"{general_manager_class.__name__.lower()}_list",
|
|
229
|
-
|
|
229
|
+
graph_ql_property(resolver),
|
|
230
230
|
)
|
|
231
231
|
for general_manager_class in all_classes:
|
|
232
|
-
GeneralmanagerConfig.
|
|
232
|
+
GeneralmanagerConfig.check_permission_class(general_manager_class)
|
|
233
233
|
|
|
234
234
|
@staticmethod
|
|
235
|
-
def
|
|
235
|
+
def handle_graph_ql(
|
|
236
236
|
pending_graphql_interfaces: list[Type[GeneralManager]],
|
|
237
237
|
) -> None:
|
|
238
238
|
"""
|
|
@@ -243,8 +243,8 @@ class GeneralmanagerConfig(AppConfig):
|
|
|
243
243
|
"""
|
|
244
244
|
logger.debug("Starting to create GraphQL interfaces and mutations...")
|
|
245
245
|
for general_manager_class in pending_graphql_interfaces:
|
|
246
|
-
GraphQL.
|
|
247
|
-
GraphQL.
|
|
246
|
+
GraphQL.create_graphql_interface(general_manager_class)
|
|
247
|
+
GraphQL.create_graphql_mutation(general_manager_class)
|
|
248
248
|
|
|
249
249
|
query_class = type("Query", (graphene.ObjectType,), GraphQL._query_fields)
|
|
250
250
|
GraphQL._query_class = query_class
|
|
@@ -279,10 +279,10 @@ class GeneralmanagerConfig(AppConfig):
|
|
|
279
279
|
schema_kwargs["subscription"] = GraphQL._subscription_class
|
|
280
280
|
schema = graphene.Schema(**schema_kwargs)
|
|
281
281
|
GraphQL._schema = schema
|
|
282
|
-
GeneralmanagerConfig.
|
|
282
|
+
GeneralmanagerConfig.add_graphql_url(schema)
|
|
283
283
|
|
|
284
284
|
@staticmethod
|
|
285
|
-
def
|
|
285
|
+
def add_graphql_url(schema: graphene.Schema) -> None:
|
|
286
286
|
"""
|
|
287
287
|
Add a GraphQL endpoint to the project's URL configuration and ensure the ASGI subscription route is configured.
|
|
288
288
|
|
|
@@ -498,7 +498,7 @@ class GeneralmanagerConfig(AppConfig):
|
|
|
498
498
|
setattr(asgi_module, attr_name, wrapped_application)
|
|
499
499
|
|
|
500
500
|
@staticmethod
|
|
501
|
-
def
|
|
501
|
+
def check_permission_class(general_manager_class: Type[GeneralManager]) -> None:
|
|
502
502
|
"""
|
|
503
503
|
Validate and normalize a GeneralManager class's Permission attribute.
|
|
504
504
|
|
|
@@ -510,8 +510,8 @@ class GeneralmanagerConfig(AppConfig):
|
|
|
510
510
|
Raises:
|
|
511
511
|
InvalidPermissionClassError: If the existing Permission attribute is not a subclass of BasePermission.
|
|
512
512
|
"""
|
|
513
|
-
from general_manager.permission.
|
|
514
|
-
from general_manager.permission.
|
|
513
|
+
from general_manager.permission.base_permission import BasePermission
|
|
514
|
+
from general_manager.permission.manager_based_permission import (
|
|
515
515
|
ManagerBasedPermission,
|
|
516
516
|
)
|
|
517
517
|
|
|
@@ -14,10 +14,10 @@ from typing import (
|
|
|
14
14
|
GeneralManagerType = TypeVar("GeneralManagerType", bound="GeneralManager")
|
|
15
15
|
|
|
16
16
|
if TYPE_CHECKING:
|
|
17
|
-
from general_manager.manager.
|
|
18
|
-
from general_manager.manager.
|
|
19
|
-
from general_manager.bucket.
|
|
20
|
-
from general_manager.interface.
|
|
17
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
18
|
+
from general_manager.manager.group_manager import GroupManager
|
|
19
|
+
from general_manager.bucket.group_bucket import GroupBucket
|
|
20
|
+
from general_manager.interface.base_interface import InterfaceBase
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class Bucket(ABC, Generic[GeneralManagerType]):
|
|
@@ -243,7 +243,7 @@ class Bucket(ABC, Generic[GeneralManagerType]):
|
|
|
243
243
|
Returns:
|
|
244
244
|
GroupBucket[GeneralManagerType]: Bucket grouping items by the provided keys.
|
|
245
245
|
"""
|
|
246
|
-
from general_manager.bucket.
|
|
246
|
+
from general_manager.bucket.group_bucket import GroupBucket
|
|
247
247
|
|
|
248
248
|
return GroupBucket(self._manager_class, group_by_keys, self)
|
|
249
249
|
|
|
@@ -17,13 +17,13 @@ from typing import (
|
|
|
17
17
|
)
|
|
18
18
|
from operator import attrgetter
|
|
19
19
|
from copy import deepcopy
|
|
20
|
-
from general_manager.interface.
|
|
20
|
+
from general_manager.interface.base_interface import (
|
|
21
21
|
generalManagerClassName,
|
|
22
22
|
GeneralManagerType,
|
|
23
23
|
)
|
|
24
|
-
from general_manager.bucket.
|
|
24
|
+
from general_manager.bucket.base_bucket import Bucket
|
|
25
25
|
from general_manager.manager.input import Input
|
|
26
|
-
from general_manager.utils.
|
|
26
|
+
from general_manager.utils.filter_parser import parse_filters
|
|
27
27
|
|
|
28
28
|
if TYPE_CHECKING:
|
|
29
29
|
from general_manager.api.property import GraphQLProperty
|
|
@@ -164,7 +164,7 @@ class CalculationBucket(Bucket[GeneralManagerType]):
|
|
|
164
164
|
Raises:
|
|
165
165
|
InvalidCalculationInterfaceError: If the manager_class.Interface does not inherit from CalculationInterface.
|
|
166
166
|
"""
|
|
167
|
-
from general_manager.interface.
|
|
167
|
+
from general_manager.interface.calculation_interface import (
|
|
168
168
|
CalculationInterface,
|
|
169
169
|
)
|
|
170
170
|
|
|
@@ -181,8 +181,8 @@ class CalculationBucket(Bucket[GeneralManagerType]):
|
|
|
181
181
|
{} if exclude_definitions is None else exclude_definitions
|
|
182
182
|
)
|
|
183
183
|
|
|
184
|
-
properties = self._manager_class.Interface.
|
|
185
|
-
possible_values = self.
|
|
184
|
+
properties = self._manager_class.Interface.get_graph_ql_properties()
|
|
185
|
+
possible_values = self.transform_properties_to_input_fields(
|
|
186
186
|
properties, self.input_fields
|
|
187
187
|
)
|
|
188
188
|
|
|
@@ -259,7 +259,7 @@ class CalculationBucket(Bucket[GeneralManagerType]):
|
|
|
259
259
|
IncompatibleBucketTypeError: If `other` is neither a CalculationBucket nor a compatible manager instance.
|
|
260
260
|
IncompatibleBucketManagerError: If `other` is a CalculationBucket for a different manager class.
|
|
261
261
|
"""
|
|
262
|
-
from general_manager.manager.
|
|
262
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
263
263
|
|
|
264
264
|
if isinstance(other, GeneralManager) and other.__class__ == self._manager_class:
|
|
265
265
|
return self.__or__(self.filter(id__in=[other.identification]))
|
|
@@ -322,7 +322,7 @@ class CalculationBucket(Bucket[GeneralManagerType]):
|
|
|
322
322
|
return f"{self.__class__.__name__}({self._manager_class.__name__}, {self.filter_definitions}, {self.exclude_definitions}, {self.sort_key}, {self.reverse})"
|
|
323
323
|
|
|
324
324
|
@staticmethod
|
|
325
|
-
def
|
|
325
|
+
def transform_properties_to_input_fields(
|
|
326
326
|
properties: dict[str, GraphQLProperty], input_fields: dict[str, Input]
|
|
327
327
|
) -> dict[str, Input]:
|
|
328
328
|
"""
|
|
@@ -420,7 +420,7 @@ class CalculationBucket(Bucket[GeneralManagerType]):
|
|
|
420
420
|
for combo in combinations:
|
|
421
421
|
yield self._manager_class(**combo)
|
|
422
422
|
|
|
423
|
-
def
|
|
423
|
+
def _sort_filters(self, sorted_inputs: List[str]) -> SortedFilters:
|
|
424
424
|
"""
|
|
425
425
|
Partition filters into input- and property-based buckets.
|
|
426
426
|
|
|
@@ -467,7 +467,7 @@ class CalculationBucket(Bucket[GeneralManagerType]):
|
|
|
467
467
|
|
|
468
468
|
if self._data is None:
|
|
469
469
|
sorted_inputs = self.topological_sort_inputs()
|
|
470
|
-
sorted_filters = self.
|
|
470
|
+
sorted_filters = self._sort_filters(sorted_inputs)
|
|
471
471
|
current_combinations = self._generate_input_combinations(
|
|
472
472
|
sorted_inputs,
|
|
473
473
|
sorted_filters["input_filters"],
|
|
@@ -6,15 +6,12 @@ from typing import TYPE_CHECKING, Any, Generator, Type, TypeVar
|
|
|
6
6
|
from django.core.exceptions import FieldError
|
|
7
7
|
from django.db import models
|
|
8
8
|
|
|
9
|
-
from general_manager.bucket.
|
|
10
|
-
from general_manager.
|
|
11
|
-
from general_manager.
|
|
12
|
-
from general_manager.utils.filterParser import create_filter_function
|
|
9
|
+
from general_manager.bucket.base_bucket import Bucket
|
|
10
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
11
|
+
from general_manager.utils.filter_parser import create_filter_function
|
|
13
12
|
|
|
14
13
|
modelsModel = TypeVar("modelsModel", bound=models.Model)
|
|
15
|
-
|
|
16
|
-
if TYPE_CHECKING:
|
|
17
|
-
from general_manager.interface.databaseInterface import DatabaseInterface
|
|
14
|
+
GeneralManagerType = TypeVar("GeneralManagerType", bound=GeneralManager)
|
|
18
15
|
|
|
19
16
|
|
|
20
17
|
class DatabaseBucketTypeMismatchError(TypeError):
|
|
@@ -189,7 +186,7 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
|
189
186
|
{},
|
|
190
187
|
)
|
|
191
188
|
|
|
192
|
-
def
|
|
189
|
+
def __merge_filter_definitions(
|
|
193
190
|
self, basis: dict[str, list[Any]], **kwargs: Any
|
|
194
191
|
) -> dict[str, list[Any]]:
|
|
195
192
|
"""
|
|
@@ -211,7 +208,7 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
|
211
208
|
kwarg_filter[key].append(value)
|
|
212
209
|
return kwarg_filter
|
|
213
210
|
|
|
214
|
-
def
|
|
211
|
+
def __parse_filter_definitions(
|
|
215
212
|
self,
|
|
216
213
|
**kwargs: Any,
|
|
217
214
|
) -> tuple[dict[str, Any], dict[str, Any], list[tuple[str, Any, str]]]:
|
|
@@ -233,7 +230,7 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
|
233
230
|
annotations: dict[str, Any] = {}
|
|
234
231
|
orm_kwargs: dict[str, Any] = {}
|
|
235
232
|
python_filters: list[tuple[str, Any, str]] = []
|
|
236
|
-
properties = self._manager_class.Interface.
|
|
233
|
+
properties = self._manager_class.Interface.get_graph_ql_properties()
|
|
237
234
|
|
|
238
235
|
for k, v in kwargs.items():
|
|
239
236
|
root = k.split("__")[0]
|
|
@@ -251,7 +248,7 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
|
251
248
|
|
|
252
249
|
return annotations, orm_kwargs, python_filters
|
|
253
250
|
|
|
254
|
-
def
|
|
251
|
+
def __parse_python_filters(
|
|
255
252
|
self, query_set: models.QuerySet, python_filters: list[tuple[str, Any, str]]
|
|
256
253
|
) -> list[int]:
|
|
257
254
|
"""
|
|
@@ -293,7 +290,7 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
|
293
290
|
InvalidQueryAnnotationTypeError: If a query-annotation callback returns a non-QuerySet.
|
|
294
291
|
QuerysetFilteringError: If the ORM rejects the filter arguments or filtering fails.
|
|
295
292
|
"""
|
|
296
|
-
annotations, orm_kwargs, python_filters = self.
|
|
293
|
+
annotations, orm_kwargs, python_filters = self.__parse_filter_definitions(
|
|
297
294
|
**kwargs
|
|
298
295
|
)
|
|
299
296
|
qs = self._data
|
|
@@ -313,10 +310,10 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
|
313
310
|
raise QuerysetFilteringError(error) from error
|
|
314
311
|
|
|
315
312
|
if python_filters:
|
|
316
|
-
ids = self.
|
|
313
|
+
ids = self.__parse_python_filters(qs, python_filters)
|
|
317
314
|
qs = qs.filter(pk__in=ids)
|
|
318
315
|
|
|
319
|
-
merged_filter = self.
|
|
316
|
+
merged_filter = self.__merge_filter_definitions(self.filters, **kwargs)
|
|
320
317
|
return self.__class__(qs, self._manager_class, merged_filter, self.excludes)
|
|
321
318
|
|
|
322
319
|
def exclude(self, **kwargs: Any) -> DatabaseBucket[GeneralManagerType]:
|
|
@@ -334,7 +331,7 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
|
334
331
|
Raises:
|
|
335
332
|
InvalidQueryAnnotationTypeError: If an annotation callable is applied and does not return a Django QuerySet.
|
|
336
333
|
"""
|
|
337
|
-
annotations, orm_kwargs, python_filters = self.
|
|
334
|
+
annotations, orm_kwargs, python_filters = self.__parse_filter_definitions(
|
|
338
335
|
**kwargs
|
|
339
336
|
)
|
|
340
337
|
qs = self._data
|
|
@@ -351,10 +348,10 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
|
351
348
|
qs = qs.exclude(**orm_kwargs)
|
|
352
349
|
|
|
353
350
|
if python_filters:
|
|
354
|
-
ids = self.
|
|
351
|
+
ids = self.__parse_python_filters(qs, python_filters)
|
|
355
352
|
qs = qs.exclude(pk__in=ids)
|
|
356
353
|
|
|
357
|
-
merged_exclude = self.
|
|
354
|
+
merged_exclude = self.__merge_filter_definitions(self.excludes, **kwargs)
|
|
358
355
|
return self.__class__(qs, self._manager_class, self.filters, merged_exclude)
|
|
359
356
|
|
|
360
357
|
def first(self) -> GeneralManagerType | None:
|
|
@@ -467,7 +464,7 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
|
467
464
|
Returns:
|
|
468
465
|
bool: True when the primary key exists in the queryset.
|
|
469
466
|
"""
|
|
470
|
-
from general_manager.manager.
|
|
467
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
471
468
|
|
|
472
469
|
if isinstance(item, GeneralManager):
|
|
473
470
|
return item.identification.get("id", None) in self._data.values_list(
|
|
@@ -499,7 +496,7 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
|
499
496
|
"""
|
|
500
497
|
if isinstance(key, str):
|
|
501
498
|
key = (key,)
|
|
502
|
-
properties = self._manager_class.Interface.
|
|
499
|
+
properties = self._manager_class.Interface.get_graph_ql_properties()
|
|
503
500
|
annotations: dict[str, Any] = {}
|
|
504
501
|
python_keys: list[str] = []
|
|
505
502
|
qs = self._data
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
from typing import Any, Generator, Type
|
|
5
|
-
from general_manager.manager.
|
|
6
|
-
from general_manager.bucket.
|
|
5
|
+
from general_manager.manager.group_manager import GroupManager
|
|
6
|
+
from general_manager.bucket.base_bucket import Bucket, GeneralManagerType
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class InvalidGroupByKeyTypeError(TypeError):
|
|
@@ -132,10 +132,10 @@ class GroupBucket(Bucket[GeneralManagerType]):
|
|
|
132
132
|
ValueError: If a group-by key is not a valid manager attribute.
|
|
133
133
|
"""
|
|
134
134
|
super().__init__(manager_class)
|
|
135
|
-
self.
|
|
135
|
+
self.__check_group_by_arguments(group_by_keys)
|
|
136
136
|
self._group_by_keys = group_by_keys
|
|
137
|
-
self._data: list[GroupManager[GeneralManagerType]] =
|
|
138
|
-
data
|
|
137
|
+
self._data: list[GroupManager[GeneralManagerType]] = (
|
|
138
|
+
self.__build_grouped_manager(data)
|
|
139
139
|
)
|
|
140
140
|
self._basis_data: Bucket[GeneralManagerType] = data
|
|
141
141
|
|
|
@@ -157,7 +157,7 @@ class GroupBucket(Bucket[GeneralManagerType]):
|
|
|
157
157
|
and self._group_by_keys == other._group_by_keys
|
|
158
158
|
)
|
|
159
159
|
|
|
160
|
-
def
|
|
160
|
+
def __check_group_by_arguments(self, group_by_keys: tuple[str, ...]) -> None:
|
|
161
161
|
"""
|
|
162
162
|
Validate that each provided group-by key is a string and is exposed by the manager interface.
|
|
163
163
|
|
|
@@ -171,12 +171,12 @@ class GroupBucket(Bucket[GeneralManagerType]):
|
|
|
171
171
|
if not all(isinstance(arg, str) for arg in group_by_keys):
|
|
172
172
|
raise InvalidGroupByKeyTypeError()
|
|
173
173
|
if not all(
|
|
174
|
-
arg in self._manager_class.Interface.
|
|
174
|
+
arg in self._manager_class.Interface.get_attributes()
|
|
175
175
|
for arg in group_by_keys
|
|
176
176
|
):
|
|
177
177
|
raise UnknownGroupByKeyError(self._manager_class.__name__)
|
|
178
178
|
|
|
179
|
-
def
|
|
179
|
+
def __build_grouped_manager(
|
|
180
180
|
self,
|
|
181
181
|
data: Bucket[GeneralManagerType],
|
|
182
182
|
) -> list[GroupManager[GeneralManagerType]]:
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
from typing import Any, Callable, Optional, Protocol, Set, TypeVar, cast
|
|
4
4
|
from functools import wraps
|
|
5
5
|
from django.core.cache import cache as django_cache
|
|
6
|
-
from general_manager.cache.
|
|
7
|
-
from general_manager.cache.
|
|
8
|
-
from general_manager.cache.
|
|
9
|
-
from general_manager.utils.
|
|
6
|
+
from general_manager.cache.cache_tracker import DependencyTracker
|
|
7
|
+
from general_manager.cache.dependency_index import record_dependencies, Dependency
|
|
8
|
+
from general_manager.cache.model_dependency_collector import ModelDependencyCollector
|
|
9
|
+
from general_manager.utils.make_cache_key import make_cache_key
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class CacheBackend(Protocol):
|
|
@@ -78,7 +78,7 @@ def cached(
|
|
|
78
78
|
|
|
79
79
|
with DependencyTracker() as dependencies:
|
|
80
80
|
result = func(*args, **kwargs)
|
|
81
|
-
ModelDependencyCollector.
|
|
81
|
+
ModelDependencyCollector.add_args(dependencies, args, kwargs)
|
|
82
82
|
|
|
83
83
|
cache_backend.set(key, result, timeout)
|
|
84
84
|
cache_backend.set(deps_key, dependencies, timeout)
|
|
@@ -13,7 +13,7 @@ from django.dispatch import receiver
|
|
|
13
13
|
from typing import Literal, Any, Iterable, TYPE_CHECKING, Type, Tuple, cast
|
|
14
14
|
|
|
15
15
|
if TYPE_CHECKING:
|
|
16
|
-
from general_manager.manager.
|
|
16
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
17
17
|
|
|
18
18
|
type general_manager_name = str # e.g. "Project", "Derivative", "User"
|
|
19
19
|
type attribute = str # e.g. "field", "name", "id"
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"""Helpers that derive cache dependency metadata from GeneralManager objects."""
|
|
2
2
|
|
|
3
3
|
from typing import Generator
|
|
4
|
-
from general_manager.manager.
|
|
5
|
-
from general_manager.bucket.
|
|
6
|
-
from general_manager.cache.
|
|
4
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
5
|
+
from general_manager.bucket.base_bucket import Bucket
|
|
6
|
+
from general_manager.cache.dependency_index import (
|
|
7
7
|
general_manager_name,
|
|
8
8
|
Dependency,
|
|
9
9
|
filter_type,
|
|
@@ -43,7 +43,7 @@ class ModelDependencyCollector:
|
|
|
43
43
|
yield from ModelDependencyCollector.collect(item)
|
|
44
44
|
|
|
45
45
|
@staticmethod
|
|
46
|
-
def
|
|
46
|
+
def add_args(dependencies: set[Dependency], args: tuple, kwargs: dict) -> None:
|
|
47
47
|
"""
|
|
48
48
|
Enrich the dependency set with values discovered in positional and keyword arguments.
|
|
49
49
|
|
general_manager/cache/signals.py
CHANGED