GeneralManager 0.19.1__py3-none-any.whl → 0.20.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.
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 +11 -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 +207 -98
- general_manager/api/mutation.py +9 -9
- general_manager/api/property.py +4 -4
- general_manager/apps.py +120 -65
- 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} +27 -6
- general_manager/cache/{cacheTracker.py → cache_tracker.py} +1 -1
- general_manager/cache/{dependencyIndex.py → dependency_index.py} +24 -8
- 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} +30 -22
- 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} +21 -21
- general_manager/interface/models.py +3 -3
- general_manager/interface/{readOnlyInterface.py → read_only_interface.py} +34 -25
- general_manager/logging.py +133 -0
- general_manager/manager/{generalManager.py → general_manager.py} +75 -17
- general_manager/manager/{groupManager.py → group_manager.py} +6 -6
- general_manager/manager/input.py +1 -1
- general_manager/manager/meta.py +63 -17
- general_manager/measurement/measurement.py +3 -3
- general_manager/permission/{basePermission.py → base_permission.py} +55 -32
- 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 +76 -66
- general_manager/rule/handler.py +2 -2
- general_manager/rule/rule.py +102 -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/public_api.py +19 -0
- general_manager/utils/testing.py +14 -14
- {generalmanager-0.19.1.dist-info → generalmanager-0.20.0.dist-info}/METADATA +1 -1
- generalmanager-0.20.0.dist-info/RECORD +78 -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.20.0.dist-info}/WHEEL +0 -0
- {generalmanager-0.19.1.dist-info → generalmanager-0.20.0.dist-info}/licenses/LICENSE +0 -0
- {generalmanager-0.19.1.dist-info → generalmanager-0.20.0.dist-info}/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
from typing import Callable, Optional, TypeVar, Generic, cast
|
|
5
5
|
from django.contrib.auth.models import AbstractUser
|
|
6
6
|
|
|
7
|
-
from general_manager.manager.
|
|
7
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class InvalidPermissionDataError(TypeError):
|
|
@@ -42,7 +42,7 @@ class PermissionDataManager(Generic[GeneralManagerData]):
|
|
|
42
42
|
Raises:
|
|
43
43
|
InvalidPermissionDataError: If `permission_data` is neither a dict nor an instance of GeneralManager.
|
|
44
44
|
"""
|
|
45
|
-
self.
|
|
45
|
+
self.get_data: Callable[[str], object]
|
|
46
46
|
self._permission_data = permission_data
|
|
47
47
|
self._manager: type[GeneralManagerData] | None
|
|
48
48
|
if isinstance(permission_data, GeneralManager):
|
|
@@ -51,7 +51,7 @@ class PermissionDataManager(Generic[GeneralManagerData]):
|
|
|
51
51
|
def manager_getter(name: str) -> object:
|
|
52
52
|
return getattr(gm_instance, name)
|
|
53
53
|
|
|
54
|
-
self.
|
|
54
|
+
self.get_data = manager_getter
|
|
55
55
|
self._manager = cast(type[GeneralManagerData], permission_data.__class__)
|
|
56
56
|
elif isinstance(permission_data, dict):
|
|
57
57
|
data_mapping = permission_data
|
|
@@ -59,13 +59,13 @@ class PermissionDataManager(Generic[GeneralManagerData]):
|
|
|
59
59
|
def dict_getter(name: str) -> object:
|
|
60
60
|
return data_mapping.get(name)
|
|
61
61
|
|
|
62
|
-
self.
|
|
62
|
+
self.get_data = dict_getter
|
|
63
63
|
self._manager = manager
|
|
64
64
|
else:
|
|
65
65
|
raise InvalidPermissionDataError()
|
|
66
66
|
|
|
67
67
|
@classmethod
|
|
68
|
-
def
|
|
68
|
+
def for_update(
|
|
69
69
|
cls,
|
|
70
70
|
base_data: GeneralManagerData,
|
|
71
71
|
update_data: dict[str, object],
|
|
@@ -95,4 +95,4 @@ class PermissionDataManager(Generic[GeneralManagerData]):
|
|
|
95
95
|
|
|
96
96
|
def __getattr__(self, name: str) -> object:
|
|
97
97
|
"""Proxy attribute access to the wrapped permission data."""
|
|
98
|
-
return self.
|
|
98
|
+
return self.get_data(name)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"""Utility helpers for evaluating permission expressions."""
|
|
2
2
|
|
|
3
|
-
from general_manager.permission.
|
|
3
|
+
from general_manager.permission.permission_checks import (
|
|
4
4
|
permission_functions,
|
|
5
5
|
)
|
|
6
|
-
from general_manager.permission.
|
|
6
|
+
from general_manager.permission.permission_data_manager import PermissionDataManager
|
|
7
7
|
from django.contrib.auth.models import AbstractUser, AnonymousUser
|
|
8
8
|
|
|
9
|
-
from general_manager.manager.
|
|
9
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
10
10
|
from general_manager.manager.meta import GeneralManagerMeta
|
|
11
11
|
|
|
12
12
|
|
|
@@ -23,7 +23,7 @@ class PermissionNotFoundError(ValueError):
|
|
|
23
23
|
super().__init__(f"Permission {permission} not found.")
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
def
|
|
26
|
+
def validate_permission_string(
|
|
27
27
|
permission: str,
|
|
28
28
|
data: PermissionDataManager | GeneralManager | GeneralManagerMeta,
|
|
29
29
|
request_user: AbstractUser | AnonymousUser,
|
|
@@ -43,7 +43,7 @@ def validatePermissionString(
|
|
|
43
43
|
PermissionNotFoundError: If a referenced permission function is not registered.
|
|
44
44
|
"""
|
|
45
45
|
|
|
46
|
-
def
|
|
46
|
+
def _validate_single_permission(
|
|
47
47
|
permission: str,
|
|
48
48
|
) -> bool:
|
|
49
49
|
"""
|
|
@@ -68,7 +68,7 @@ def validatePermissionString(
|
|
|
68
68
|
|
|
69
69
|
return all(
|
|
70
70
|
[
|
|
71
|
-
|
|
71
|
+
_validate_single_permission(sub_permission)
|
|
72
72
|
for sub_permission in permission.split("&")
|
|
73
73
|
]
|
|
74
74
|
)
|
|
@@ -14,24 +14,25 @@ LazyExportMap = Mapping[str, str | tuple[str, str]]
|
|
|
14
14
|
|
|
15
15
|
GENERAL_MANAGER_EXPORTS: LazyExportMap = {
|
|
16
16
|
"GraphQL": ("general_manager.api.graphql", "GraphQL"),
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"GeneralManager": ("general_manager.manager.
|
|
17
|
+
"graph_ql_property": ("general_manager.api.property", "graph_ql_property"),
|
|
18
|
+
"graph_ql_mutation": ("general_manager.api.mutation", "graph_ql_mutation"),
|
|
19
|
+
"GeneralManager": ("general_manager.manager.general_manager", "GeneralManager"),
|
|
20
20
|
"Input": ("general_manager.manager.input", "Input"),
|
|
21
|
+
"get_logger": ("general_manager.logging", "get_logger"),
|
|
21
22
|
"CalculationInterface": (
|
|
22
|
-
"general_manager.interface.
|
|
23
|
+
"general_manager.interface.calculation_interface",
|
|
23
24
|
"CalculationInterface",
|
|
24
25
|
),
|
|
25
26
|
"DatabaseInterface": (
|
|
26
|
-
"general_manager.interface.
|
|
27
|
+
"general_manager.interface.database_interface",
|
|
27
28
|
"DatabaseInterface",
|
|
28
29
|
),
|
|
29
30
|
"ReadOnlyInterface": (
|
|
30
|
-
"general_manager.interface.
|
|
31
|
+
"general_manager.interface.read_only_interface",
|
|
31
32
|
"ReadOnlyInterface",
|
|
32
33
|
),
|
|
33
34
|
"ManagerBasedPermission": (
|
|
34
|
-
"general_manager.permission.
|
|
35
|
+
"general_manager.permission.manager_based_permission",
|
|
35
36
|
"ManagerBasedPermission",
|
|
36
37
|
),
|
|
37
38
|
"Rule": ("general_manager.rule.rule", "Rule"),
|
|
@@ -42,36 +43,45 @@ API_EXPORTS: LazyExportMap = {
|
|
|
42
43
|
"GraphQL": ("general_manager.api.graphql", "GraphQL"),
|
|
43
44
|
"MeasurementType": ("general_manager.api.graphql", "MeasurementType"),
|
|
44
45
|
"MeasurementScalar": ("general_manager.api.graphql", "MeasurementScalar"),
|
|
45
|
-
"
|
|
46
|
-
"
|
|
46
|
+
"graph_ql_property": ("general_manager.api.property", "graph_ql_property"),
|
|
47
|
+
"graph_ql_mutation": ("general_manager.api.mutation", "graph_ql_mutation"),
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
|
|
50
51
|
FACTORY_EXPORTS: LazyExportMap = {
|
|
51
|
-
"AutoFactory": ("general_manager.factory.
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
),
|
|
73
|
-
"
|
|
74
|
-
"
|
|
52
|
+
"AutoFactory": ("general_manager.factory.auto_factory", "AutoFactory"),
|
|
53
|
+
"lazy_measurement": ("general_manager.factory.factory_methods", "lazy_measurement"),
|
|
54
|
+
"lazy_delta_date": ("general_manager.factory.factory_methods", "lazy_delta_date"),
|
|
55
|
+
"lazy_project_name": (
|
|
56
|
+
"general_manager.factory.factory_methods",
|
|
57
|
+
"lazy_project_name",
|
|
58
|
+
),
|
|
59
|
+
"lazy_date_today": ("general_manager.factory.factory_methods", "lazy_date_today"),
|
|
60
|
+
"lazy_date_between": (
|
|
61
|
+
"general_manager.factory.factory_methods",
|
|
62
|
+
"lazy_date_between",
|
|
63
|
+
),
|
|
64
|
+
"lazy_date_time_between": (
|
|
65
|
+
"general_manager.factory.factory_methods",
|
|
66
|
+
"lazy_date_time_between",
|
|
67
|
+
),
|
|
68
|
+
"lazy_integer": ("general_manager.factory.factory_methods", "lazy_integer"),
|
|
69
|
+
"lazy_decimal": ("general_manager.factory.factory_methods", "lazy_decimal"),
|
|
70
|
+
"lazy_choice": ("general_manager.factory.factory_methods", "lazy_choice"),
|
|
71
|
+
"lazy_sequence": ("general_manager.factory.factory_methods", "lazy_sequence"),
|
|
72
|
+
"lazy_boolean": ("general_manager.factory.factory_methods", "lazy_boolean"),
|
|
73
|
+
"lazy_uuid": ("general_manager.factory.factory_methods", "lazy_uuid"),
|
|
74
|
+
"lazy_faker_name": ("general_manager.factory.factory_methods", "lazy_faker_name"),
|
|
75
|
+
"lazy_faker_email": ("general_manager.factory.factory_methods", "lazy_faker_email"),
|
|
76
|
+
"lazy_faker_sentence": (
|
|
77
|
+
"general_manager.factory.factory_methods",
|
|
78
|
+
"lazy_faker_sentence",
|
|
79
|
+
),
|
|
80
|
+
"lazy_faker_address": (
|
|
81
|
+
"general_manager.factory.factory_methods",
|
|
82
|
+
"lazy_faker_address",
|
|
83
|
+
),
|
|
84
|
+
"lazy_faker_url": ("general_manager.factory.factory_methods", "lazy_faker_url"),
|
|
75
85
|
}
|
|
76
86
|
|
|
77
87
|
|
|
@@ -80,88 +90,88 @@ MEASUREMENT_EXPORTS: LazyExportMap = {
|
|
|
80
90
|
"ureg": ("general_manager.measurement.measurement", "ureg"),
|
|
81
91
|
"currency_units": ("general_manager.measurement.measurement", "currency_units"),
|
|
82
92
|
"MeasurementField": (
|
|
83
|
-
"general_manager.measurement.
|
|
93
|
+
"general_manager.measurement.measurement_field",
|
|
84
94
|
"MeasurementField",
|
|
85
95
|
),
|
|
86
96
|
}
|
|
87
97
|
|
|
88
98
|
|
|
89
99
|
UTILS_EXPORTS: LazyExportMap = {
|
|
90
|
-
"
|
|
91
|
-
"args_to_kwargs": ("general_manager.utils.
|
|
92
|
-
"make_cache_key": ("general_manager.utils.
|
|
93
|
-
"parse_filters": ("general_manager.utils.
|
|
100
|
+
"none_to_zero": ("general_manager.utils.none_to_zero", "none_to_zero"),
|
|
101
|
+
"args_to_kwargs": ("general_manager.utils.args_to_kwargs", "args_to_kwargs"),
|
|
102
|
+
"make_cache_key": ("general_manager.utils.make_cache_key", "make_cache_key"),
|
|
103
|
+
"parse_filters": ("general_manager.utils.filter_parser", "parse_filters"),
|
|
94
104
|
"create_filter_function": (
|
|
95
|
-
"general_manager.utils.
|
|
105
|
+
"general_manager.utils.filter_parser",
|
|
96
106
|
"create_filter_function",
|
|
97
107
|
),
|
|
98
|
-
"snake_to_pascal": ("general_manager.utils.
|
|
99
|
-
"snake_to_camel": ("general_manager.utils.
|
|
100
|
-
"pascal_to_snake": ("general_manager.utils.
|
|
101
|
-
"camel_to_snake": ("general_manager.utils.
|
|
102
|
-
"CustomJSONEncoder": ("general_manager.utils.
|
|
103
|
-
"PathMap": ("general_manager.utils.
|
|
108
|
+
"snake_to_pascal": ("general_manager.utils.format_string", "snake_to_pascal"),
|
|
109
|
+
"snake_to_camel": ("general_manager.utils.format_string", "snake_to_camel"),
|
|
110
|
+
"pascal_to_snake": ("general_manager.utils.format_string", "pascal_to_snake"),
|
|
111
|
+
"camel_to_snake": ("general_manager.utils.format_string", "camel_to_snake"),
|
|
112
|
+
"CustomJSONEncoder": ("general_manager.utils.json_encoder", "CustomJSONEncoder"),
|
|
113
|
+
"PathMap": ("general_manager.utils.path_mapping", "PathMap"),
|
|
104
114
|
}
|
|
105
115
|
|
|
106
116
|
|
|
107
117
|
PERMISSION_EXPORTS: LazyExportMap = {
|
|
108
|
-
"BasePermission": ("general_manager.permission.
|
|
118
|
+
"BasePermission": ("general_manager.permission.base_permission", "BasePermission"),
|
|
109
119
|
"ManagerBasedPermission": (
|
|
110
|
-
"general_manager.permission.
|
|
120
|
+
"general_manager.permission.manager_based_permission",
|
|
111
121
|
"ManagerBasedPermission",
|
|
112
122
|
),
|
|
113
123
|
"MutationPermission": (
|
|
114
|
-
"general_manager.permission.
|
|
124
|
+
"general_manager.permission.mutation_permission",
|
|
115
125
|
"MutationPermission",
|
|
116
126
|
),
|
|
117
127
|
}
|
|
118
128
|
|
|
119
129
|
|
|
120
130
|
INTERFACE_EXPORTS: LazyExportMap = {
|
|
121
|
-
"InterfaceBase": "general_manager.interface.
|
|
122
|
-
"DBBasedInterface": "general_manager.interface.
|
|
123
|
-
"DatabaseInterface": "general_manager.interface.
|
|
124
|
-
"ReadOnlyInterface": "general_manager.interface.
|
|
125
|
-
"CalculationInterface": "general_manager.interface.
|
|
131
|
+
"InterfaceBase": "general_manager.interface.base_interface",
|
|
132
|
+
"DBBasedInterface": "general_manager.interface.database_based_interface",
|
|
133
|
+
"DatabaseInterface": "general_manager.interface.database_interface",
|
|
134
|
+
"ReadOnlyInterface": "general_manager.interface.read_only_interface",
|
|
135
|
+
"CalculationInterface": "general_manager.interface.calculation_interface",
|
|
126
136
|
}
|
|
127
137
|
|
|
128
138
|
|
|
129
139
|
CACHE_EXPORTS: LazyExportMap = {
|
|
130
|
-
"cached": ("general_manager.cache.
|
|
131
|
-
"CacheBackend": ("general_manager.cache.
|
|
132
|
-
"DependencyTracker": ("general_manager.cache.
|
|
140
|
+
"cached": ("general_manager.cache.cache_decorator", "cached"),
|
|
141
|
+
"CacheBackend": ("general_manager.cache.cache_decorator", "CacheBackend"),
|
|
142
|
+
"DependencyTracker": ("general_manager.cache.cache_tracker", "DependencyTracker"),
|
|
133
143
|
"record_dependencies": (
|
|
134
|
-
"general_manager.cache.
|
|
144
|
+
"general_manager.cache.dependency_index",
|
|
135
145
|
"record_dependencies",
|
|
136
146
|
),
|
|
137
147
|
"remove_cache_key_from_index": (
|
|
138
|
-
"general_manager.cache.
|
|
148
|
+
"general_manager.cache.dependency_index",
|
|
139
149
|
"remove_cache_key_from_index",
|
|
140
150
|
),
|
|
141
151
|
"invalidate_cache_key": (
|
|
142
|
-
"general_manager.cache.
|
|
152
|
+
"general_manager.cache.dependency_index",
|
|
143
153
|
"invalidate_cache_key",
|
|
144
154
|
),
|
|
145
155
|
}
|
|
146
156
|
|
|
147
157
|
|
|
148
158
|
BUCKET_EXPORTS: LazyExportMap = {
|
|
149
|
-
"Bucket": ("general_manager.bucket.
|
|
150
|
-
"DatabaseBucket": ("general_manager.bucket.
|
|
159
|
+
"Bucket": ("general_manager.bucket.base_bucket", "Bucket"),
|
|
160
|
+
"DatabaseBucket": ("general_manager.bucket.database_bucket", "DatabaseBucket"),
|
|
151
161
|
"CalculationBucket": (
|
|
152
|
-
"general_manager.bucket.
|
|
162
|
+
"general_manager.bucket.calculation_bucket",
|
|
153
163
|
"CalculationBucket",
|
|
154
164
|
),
|
|
155
|
-
"GroupBucket": ("general_manager.bucket.
|
|
165
|
+
"GroupBucket": ("general_manager.bucket.group_bucket", "GroupBucket"),
|
|
156
166
|
}
|
|
157
167
|
|
|
158
168
|
|
|
159
169
|
MANAGER_EXPORTS: LazyExportMap = {
|
|
160
|
-
"GeneralManager": ("general_manager.manager.
|
|
170
|
+
"GeneralManager": ("general_manager.manager.general_manager", "GeneralManager"),
|
|
161
171
|
"GeneralManagerMeta": ("general_manager.manager.meta", "GeneralManagerMeta"),
|
|
162
172
|
"Input": ("general_manager.manager.input", "Input"),
|
|
163
|
-
"GroupManager": ("general_manager.manager.
|
|
164
|
-
"
|
|
173
|
+
"GroupManager": ("general_manager.manager.group_manager", "GroupManager"),
|
|
174
|
+
"graph_ql_property": ("general_manager.api.property", "graph_ql_property"),
|
|
165
175
|
}
|
|
166
176
|
|
|
167
177
|
|
general_manager/rule/handler.py
CHANGED
|
@@ -213,7 +213,7 @@ class LenHandler(FunctionHandler):
|
|
|
213
213
|
var_name = rule._get_node_name(arg_node)
|
|
214
214
|
var_value = var_values.get(var_name)
|
|
215
215
|
|
|
216
|
-
# ---
|
|
216
|
+
# --- Type guard for right_value ---
|
|
217
217
|
raw = rule._eval_node(right_node)
|
|
218
218
|
if not isinstance(raw, (int, float)):
|
|
219
219
|
raise InvalidLenThresholdError()
|
|
@@ -230,7 +230,7 @@ class LenHandler(FunctionHandler):
|
|
|
230
230
|
else:
|
|
231
231
|
threshold = right_value
|
|
232
232
|
|
|
233
|
-
#
|
|
233
|
+
# Formulate the error message
|
|
234
234
|
if op_symbol in (">", ">="):
|
|
235
235
|
msg = f"[{var_name}] ({var_value}) is too short (min length {threshold})!"
|
|
236
236
|
elif op_symbol in ("<", "<="):
|
general_manager/rule/rule.py
CHANGED
|
@@ -18,11 +18,13 @@ from general_manager.rule.handler import (
|
|
|
18
18
|
MinHandler,
|
|
19
19
|
SumHandler,
|
|
20
20
|
)
|
|
21
|
-
from general_manager.manager.
|
|
21
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
22
|
+
from general_manager.logging import get_logger
|
|
22
23
|
|
|
23
24
|
GeneralManagerType = TypeVar("GeneralManagerType", bound=GeneralManager)
|
|
24
25
|
|
|
25
26
|
NOTEXISTENT = object()
|
|
27
|
+
logger = get_logger("rule.engine")
|
|
26
28
|
|
|
27
29
|
|
|
28
30
|
class NonexistentAttributeError(AttributeError):
|
|
@@ -131,13 +133,20 @@ class Rule(Generic[GeneralManagerType]):
|
|
|
131
133
|
handler_cls: type[BaseRuleHandler] = import_string(path)
|
|
132
134
|
inst = handler_cls()
|
|
133
135
|
self._handlers[inst.function_name] = inst
|
|
136
|
+
logger.debug(
|
|
137
|
+
"initialised rule",
|
|
138
|
+
context={
|
|
139
|
+
"rule": self._func.__qualname__,
|
|
140
|
+
"variables": self._variables,
|
|
141
|
+
},
|
|
142
|
+
)
|
|
134
143
|
|
|
135
144
|
@property
|
|
136
145
|
def func(self) -> Callable[[GeneralManagerType], bool]:
|
|
137
146
|
return self._func
|
|
138
147
|
|
|
139
148
|
@property
|
|
140
|
-
def
|
|
149
|
+
def custom_error_message(self) -> Optional[str]:
|
|
141
150
|
return self._custom_error_message
|
|
142
151
|
|
|
143
152
|
@property
|
|
@@ -145,15 +154,15 @@ class Rule(Generic[GeneralManagerType]):
|
|
|
145
154
|
return self._variables
|
|
146
155
|
|
|
147
156
|
@property
|
|
148
|
-
def
|
|
157
|
+
def last_evaluation_result(self) -> Optional[bool]:
|
|
149
158
|
return self._last_result
|
|
150
159
|
|
|
151
160
|
@property
|
|
152
|
-
def
|
|
161
|
+
def last_evaluation_input(self) -> Optional[GeneralManagerType]:
|
|
153
162
|
return self._last_input
|
|
154
163
|
|
|
155
164
|
@property
|
|
156
|
-
def
|
|
165
|
+
def ignore_if_none(self) -> bool:
|
|
157
166
|
return self._ignore_if_none
|
|
158
167
|
|
|
159
168
|
def evaluate(self, x: GeneralManagerType) -> Optional[bool]:
|
|
@@ -173,15 +182,50 @@ class Rule(Generic[GeneralManagerType]):
|
|
|
173
182
|
if self._primary_param is not None:
|
|
174
183
|
self._last_args[self._primary_param] = x
|
|
175
184
|
|
|
185
|
+
logger.debug(
|
|
186
|
+
"evaluating rule",
|
|
187
|
+
context={
|
|
188
|
+
"rule": self._func.__qualname__,
|
|
189
|
+
"manager": type(x).__name__,
|
|
190
|
+
},
|
|
191
|
+
)
|
|
192
|
+
|
|
176
193
|
vals = self._extract_variable_values(x)
|
|
177
194
|
if self._ignore_if_none and any(v is None for v in vals.values()):
|
|
178
195
|
self._last_result = None
|
|
196
|
+
logger.debug(
|
|
197
|
+
"skipped rule evaluation due to missing values",
|
|
198
|
+
context={
|
|
199
|
+
"rule": self._func.__qualname__,
|
|
200
|
+
"manager": type(x).__name__,
|
|
201
|
+
"null_variables": [
|
|
202
|
+
name for name, value in vals.items() if value is None
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
)
|
|
179
206
|
return None
|
|
180
207
|
|
|
181
208
|
self._last_result = self._func(x)
|
|
209
|
+
if self._last_result:
|
|
210
|
+
logger.debug(
|
|
211
|
+
"rule evaluation passed",
|
|
212
|
+
context={
|
|
213
|
+
"rule": self._func.__qualname__,
|
|
214
|
+
"manager": type(x).__name__,
|
|
215
|
+
},
|
|
216
|
+
)
|
|
217
|
+
else:
|
|
218
|
+
logger.info(
|
|
219
|
+
"rule evaluation failed",
|
|
220
|
+
context={
|
|
221
|
+
"rule": self._func.__qualname__,
|
|
222
|
+
"manager": type(x).__name__,
|
|
223
|
+
"variables": self._variables,
|
|
224
|
+
},
|
|
225
|
+
)
|
|
182
226
|
return self._last_result
|
|
183
227
|
|
|
184
|
-
def
|
|
228
|
+
def validate_custom_error_message(self) -> None:
|
|
185
229
|
"""
|
|
186
230
|
Validate that a provided custom error message template includes placeholders for every variable referenced by the rule.
|
|
187
231
|
|
|
@@ -196,7 +240,7 @@ class Rule(Generic[GeneralManagerType]):
|
|
|
196
240
|
if missing:
|
|
197
241
|
raise MissingErrorTemplateVariableError(missing)
|
|
198
242
|
|
|
199
|
-
def
|
|
243
|
+
def get_error_message(self) -> Optional[Dict[str, str]]:
|
|
200
244
|
"""
|
|
201
245
|
Constructs error messages for the last failed evaluation and returns them keyed by variable name.
|
|
202
246
|
|
|
@@ -212,8 +256,16 @@ class Rule(Generic[GeneralManagerType]):
|
|
|
212
256
|
raise ErrorMessageGenerationError()
|
|
213
257
|
|
|
214
258
|
# Validate and substitute template placeholders
|
|
215
|
-
self.
|
|
259
|
+
self.validate_custom_error_message()
|
|
216
260
|
vals = self._extract_variable_values(self._last_input)
|
|
261
|
+
manager_class = type(self._last_input).__name__
|
|
262
|
+
logger.debug(
|
|
263
|
+
"generating rule error messages",
|
|
264
|
+
context={
|
|
265
|
+
"rule": self._func.__qualname__,
|
|
266
|
+
"manager": manager_class,
|
|
267
|
+
},
|
|
268
|
+
)
|
|
217
269
|
|
|
218
270
|
if self._custom_error_message:
|
|
219
271
|
formatted = re.sub(
|
|
@@ -221,9 +273,34 @@ class Rule(Generic[GeneralManagerType]):
|
|
|
221
273
|
lambda m: str(vals.get(m.group(1), m.group(0))),
|
|
222
274
|
self._custom_error_message,
|
|
223
275
|
)
|
|
276
|
+
logger.info(
|
|
277
|
+
"rule produced custom error message",
|
|
278
|
+
context={
|
|
279
|
+
"rule": self._func.__qualname__,
|
|
280
|
+
"manager": manager_class,
|
|
281
|
+
"variables": self._variables,
|
|
282
|
+
},
|
|
283
|
+
)
|
|
224
284
|
return {v: formatted for v in self._variables}
|
|
225
285
|
|
|
226
286
|
errors = self._generate_error_messages(vals)
|
|
287
|
+
if errors:
|
|
288
|
+
logger.info(
|
|
289
|
+
"rule produced error messages",
|
|
290
|
+
context={
|
|
291
|
+
"rule": self._func.__qualname__,
|
|
292
|
+
"manager": manager_class,
|
|
293
|
+
"variables": list(errors.keys()),
|
|
294
|
+
},
|
|
295
|
+
)
|
|
296
|
+
else:
|
|
297
|
+
logger.debug(
|
|
298
|
+
"rule generated no error messages",
|
|
299
|
+
context={
|
|
300
|
+
"rule": self._func.__qualname__,
|
|
301
|
+
"manager": manager_class,
|
|
302
|
+
},
|
|
303
|
+
)
|
|
227
304
|
return errors or None
|
|
228
305
|
|
|
229
306
|
def _extract_variables(self) -> List[str]:
|
|
@@ -250,7 +327,7 @@ class Rule(Generic[GeneralManagerType]):
|
|
|
250
327
|
self.vars: set[str] = set()
|
|
251
328
|
self.params = params
|
|
252
329
|
|
|
253
|
-
def
|
|
330
|
+
def visit_attribute(self, node: ast.Attribute) -> None:
|
|
254
331
|
"""
|
|
255
332
|
Record dotted attribute accesses that originate from allowed parameter names.
|
|
256
333
|
|
|
@@ -268,6 +345,8 @@ class Rule(Generic[GeneralManagerType]):
|
|
|
268
345
|
self.vars.add(".".join(reversed(parts)))
|
|
269
346
|
self.generic_visit(node)
|
|
270
347
|
|
|
348
|
+
visit_Attribute = visit_attribute
|
|
349
|
+
|
|
271
350
|
visitor = VarVisitor(param_names)
|
|
272
351
|
visitor.visit(self._tree)
|
|
273
352
|
return sorted(visitor.vars)
|
|
@@ -301,10 +380,12 @@ class Rule(Generic[GeneralManagerType]):
|
|
|
301
380
|
def __init__(self) -> None:
|
|
302
381
|
self.comps: list[ast.Compare] = []
|
|
303
382
|
|
|
304
|
-
def
|
|
383
|
+
def visit_compare(self, node: ast.Compare) -> None:
|
|
305
384
|
self.comps.append(node)
|
|
306
385
|
self.generic_visit(node)
|
|
307
386
|
|
|
387
|
+
visit_Compare = visit_compare
|
|
388
|
+
|
|
308
389
|
visitor = CompVisitor()
|
|
309
390
|
visitor.visit(self._tree)
|
|
310
391
|
return visitor.comps
|
|
@@ -313,11 +394,13 @@ class Rule(Generic[GeneralManagerType]):
|
|
|
313
394
|
class LogicVisitor(ast.NodeVisitor):
|
|
314
395
|
found: bool = False
|
|
315
396
|
|
|
316
|
-
def
|
|
397
|
+
def visit_bool_op(self, node: ast.BoolOp) -> None:
|
|
317
398
|
if isinstance(node.op, (ast.And, ast.Or)):
|
|
318
399
|
self.found = True
|
|
319
400
|
self.generic_visit(node)
|
|
320
401
|
|
|
402
|
+
visit_BoolOp = visit_bool_op
|
|
403
|
+
|
|
321
404
|
visitor = LogicVisitor()
|
|
322
405
|
visitor.visit(self._tree)
|
|
323
406
|
return visitor.found
|
|
@@ -349,6 +432,14 @@ class Rule(Generic[GeneralManagerType]):
|
|
|
349
432
|
fn = self._get_node_name(left.func)
|
|
350
433
|
handler = self._handlers.get(fn)
|
|
351
434
|
if handler:
|
|
435
|
+
logger.debug(
|
|
436
|
+
"rule handler invoked",
|
|
437
|
+
context={
|
|
438
|
+
"rule": self._func.__qualname__,
|
|
439
|
+
"handler": handler.__class__.__name__,
|
|
440
|
+
"function": fn,
|
|
441
|
+
},
|
|
442
|
+
)
|
|
352
443
|
errors.update(
|
|
353
444
|
handler.handle(cmp, left, right, op, var_values, self)
|
|
354
445
|
)
|
|
@@ -36,7 +36,7 @@ def parse_filters(
|
|
|
36
36
|
Raises:
|
|
37
37
|
UnknownInputFieldError: If a filter references a field name not present in `possible_values`.
|
|
38
38
|
"""
|
|
39
|
-
from general_manager.manager.
|
|
39
|
+
from general_manager.manager.general_manager import GeneralManager
|
|
40
40
|
|
|
41
41
|
filters: dict[str, dict[str, Any]] = {}
|
|
42
42
|
for kwarg, value in filter_kwargs.items():
|
|
@@ -49,7 +49,7 @@ def parse_filters(
|
|
|
49
49
|
lookup = "__".join(parts[1:]) if len(parts) > 1 else ""
|
|
50
50
|
|
|
51
51
|
if issubclass(input_field.type, GeneralManager):
|
|
52
|
-
#
|
|
52
|
+
# Collect filter keyword arguments for the input field
|
|
53
53
|
if lookup == "":
|
|
54
54
|
lookup = "id"
|
|
55
55
|
if not isinstance(value, GeneralManager):
|
|
@@ -59,7 +59,7 @@ def parse_filters(
|
|
|
59
59
|
lookup
|
|
60
60
|
] = value
|
|
61
61
|
else:
|
|
62
|
-
#
|
|
62
|
+
# Build filter functions for non-bucket types
|
|
63
63
|
if isinstance(value, (list, tuple)) and not isinstance(
|
|
64
64
|
value, input_field.type
|
|
65
65
|
):
|