GeneralManager 0.4.6__tar.gz → 0.5.0__tar.gz
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.
- {generalmanager-0.4.6 → generalmanager-0.5.0}/GeneralManager.egg-info/PKG-INFO +1 -1
- {generalmanager-0.4.6 → generalmanager-0.5.0}/GeneralManager.egg-info/SOURCES.txt +1 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/PKG-INFO +1 -1
- {generalmanager-0.4.6 → generalmanager-0.5.0}/pyproject.toml +1 -1
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/apps.py +1 -1
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/manager/meta.py +18 -5
- generalmanager-0.5.0/tests/test_generalManagerMeta.py +581 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/GeneralManager.egg-info/dependency_links.txt +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/GeneralManager.egg-info/requires.txt +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/GeneralManager.egg-info/top_level.txt +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/LICENSE +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/README.md +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/setup.cfg +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/__init__.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/api/graphql.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/api/mutation.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/api/property.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/auxiliary/__init__.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/auxiliary/argsToKwargs.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/auxiliary/filterParser.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/auxiliary/jsonEncoder.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/auxiliary/makeCacheKey.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/auxiliary/noneToZero.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/auxiliary/pathMapping.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/cache/cacheDecorator.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/cache/cacheTracker.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/cache/dependencyIndex.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/cache/modelDependencyCollector.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/cache/signals.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/factory/__init__.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/factory/autoFactory.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/factory/factories.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/factory/factoryMethods.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/interface/__init__.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/interface/baseInterface.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/interface/calculationInterface.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/interface/databaseInterface.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/manager/__init__.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/manager/generalManager.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/manager/groupManager.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/manager/input.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/measurement/__init__.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/measurement/measurement.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/measurement/measurementField.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/permission/__init__.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/permission/basePermission.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/permission/fileBasedPermission.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/permission/managerBasedPermission.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/permission/permissionChecks.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/permission/permissionDataManager.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/rule/__init__.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/rule/handler.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/rule/rule.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_argsToKwargs.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_autoFactory.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_basePermission.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_cacheDecorator.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_cacheTracker.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_dependencyIndex.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_factories.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_factoryMethods.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_filterParser.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_generalManager.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_graph_ql.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_input.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_jsonEncoder.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_makeCacheKey.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_managerBasedPermission.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_measurement.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_measurement_field.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_modelDependencyCollector.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_noneToZero.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_rule_handler.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_rules.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_settings.py +0 -0
- {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_signals.py +0 -0
@@ -41,7 +41,7 @@ class GeneralmanagerConfig(AppConfig):
|
|
41
41
|
attributes = general_manager_class.Interface.getAttributes()
|
42
42
|
setattr(general_manager_class, "_attributes", attributes)
|
43
43
|
GeneralManagerMeta.createAtPropertiesForAttributes(
|
44
|
-
attributes, general_manager_class
|
44
|
+
attributes.keys(), general_manager_class
|
45
45
|
)
|
46
46
|
|
47
47
|
if getattr(settings, "AUTOCREATE_GRAPHQL", False):
|
@@ -3,7 +3,7 @@ from general_manager.interface.baseInterface import (
|
|
3
3
|
InterfaceBase,
|
4
4
|
)
|
5
5
|
from django.conf import settings
|
6
|
-
from typing import Any, Type, TYPE_CHECKING, Generic, TypeVar
|
6
|
+
from typing import Any, Type, TYPE_CHECKING, Generic, TypeVar, Iterable
|
7
7
|
|
8
8
|
if TYPE_CHECKING:
|
9
9
|
from general_manager.interface.databaseInterface import ReadOnlyInterface
|
@@ -12,6 +12,10 @@ if TYPE_CHECKING:
|
|
12
12
|
GeneralManagerType = TypeVar("GeneralManagerType", bound="GeneralManager")
|
13
13
|
|
14
14
|
|
15
|
+
class _nonExistent:
|
16
|
+
pass
|
17
|
+
|
18
|
+
|
15
19
|
class GeneralManagerMeta(type):
|
16
20
|
all_classes: list[Type[GeneralManager]] = []
|
17
21
|
read_only_classes: list[Type[ReadOnlyInterface]] = []
|
@@ -48,7 +52,7 @@ class GeneralManagerMeta(type):
|
|
48
52
|
|
49
53
|
@staticmethod
|
50
54
|
def createAtPropertiesForAttributes(
|
51
|
-
attributes:
|
55
|
+
attributes: Iterable[str], new_class: Type[GeneralManager]
|
52
56
|
):
|
53
57
|
|
54
58
|
def desciptorMethod(attr_name: str, new_class: type):
|
@@ -64,12 +68,21 @@ class GeneralManagerMeta(type):
|
|
64
68
|
):
|
65
69
|
if instance is None:
|
66
70
|
return self.new_class.Interface.getFieldType(self.attr_name)
|
67
|
-
attribute = instance._attributes
|
71
|
+
attribute = instance._attributes.get(attr_name, _nonExistent)
|
72
|
+
if attribute is _nonExistent:
|
73
|
+
raise AttributeError(
|
74
|
+
f"{self.attr_name} not found in {instance.__class__.__name__}"
|
75
|
+
)
|
68
76
|
if callable(attribute):
|
69
|
-
|
77
|
+
try:
|
78
|
+
attribute = attribute(instance._interface)
|
79
|
+
except Exception as e:
|
80
|
+
raise AttributeError(
|
81
|
+
f"Error calling attribute {self.attr_name}: {e}"
|
82
|
+
) from e
|
70
83
|
return attribute
|
71
84
|
|
72
85
|
return Descriptor(attr_name, new_class)
|
73
86
|
|
74
|
-
for attr_name in attributes
|
87
|
+
for attr_name in attributes:
|
75
88
|
setattr(new_class, attr_name, desciptorMethod(attr_name, new_class))
|
@@ -0,0 +1,581 @@
|
|
1
|
+
from django.test import SimpleTestCase, override_settings
|
2
|
+
|
3
|
+
from general_manager.manager.meta import GeneralManagerMeta
|
4
|
+
from general_manager.interface.baseInterface import InterfaceBase
|
5
|
+
|
6
|
+
|
7
|
+
class dummyInterface:
|
8
|
+
@staticmethod
|
9
|
+
def getAttributes():
|
10
|
+
return {
|
11
|
+
"test_int": 42,
|
12
|
+
"test_field": "value",
|
13
|
+
"dummy_manager1": DummyManager1,
|
14
|
+
"dummy_manager2": DummyManager2,
|
15
|
+
}
|
16
|
+
|
17
|
+
|
18
|
+
class DummyGeneralManager:
|
19
|
+
_attributes: dict
|
20
|
+
_interface = dummyInterface
|
21
|
+
|
22
|
+
class Interface:
|
23
|
+
@staticmethod
|
24
|
+
def getFieldType(field_name: str) -> type:
|
25
|
+
if field_name == "test_int":
|
26
|
+
return int
|
27
|
+
elif field_name == "dummy_manager2":
|
28
|
+
return DummyManager2
|
29
|
+
elif field_name == "dummy_manager1":
|
30
|
+
return DummyManager1
|
31
|
+
return str
|
32
|
+
|
33
|
+
|
34
|
+
class DummyManager1(DummyGeneralManager):
|
35
|
+
pass
|
36
|
+
|
37
|
+
|
38
|
+
class DummyManager2(DummyGeneralManager):
|
39
|
+
pass
|
40
|
+
|
41
|
+
|
42
|
+
class TestPropertyInitialization(SimpleTestCase):
|
43
|
+
def setUp(self):
|
44
|
+
self.dummy_manager1 = DummyManager1()
|
45
|
+
self.dummy_manager2 = DummyManager2()
|
46
|
+
|
47
|
+
def tearDown(self):
|
48
|
+
del self.dummy_manager1
|
49
|
+
del self.dummy_manager2
|
50
|
+
|
51
|
+
for manager_cls in (DummyManager1, DummyManager2):
|
52
|
+
for attr in set(vars(manager_cls).keys()):
|
53
|
+
if not attr.startswith("_"):
|
54
|
+
delattr(manager_cls, attr)
|
55
|
+
|
56
|
+
def test_properties_initialization(self):
|
57
|
+
self.dummy_manager1._attributes = {
|
58
|
+
"test_field": "value",
|
59
|
+
}
|
60
|
+
|
61
|
+
GeneralManagerMeta.createAtPropertiesForAttributes(
|
62
|
+
["test_field"], DummyManager1 # type: ignore
|
63
|
+
)
|
64
|
+
|
65
|
+
self.assertTrue(hasattr(DummyManager1, "test_field")) # type: ignore
|
66
|
+
self.assertEqual(DummyManager1.test_field, str) # type: ignore
|
67
|
+
|
68
|
+
self.assertTrue(hasattr(self.dummy_manager1, "test_field")) # type: ignore
|
69
|
+
self.assertEqual(self.dummy_manager1.test_field, "value") # type: ignore
|
70
|
+
self.assertIsInstance(self.dummy_manager1.test_field, str) # type: ignore
|
71
|
+
|
72
|
+
def test_nested_manager_property(self):
|
73
|
+
self.dummy_manager1._attributes = {
|
74
|
+
"dummy_manager2": self.dummy_manager2,
|
75
|
+
}
|
76
|
+
|
77
|
+
GeneralManagerMeta.createAtPropertiesForAttributes(
|
78
|
+
["dummy_manager2"], DummyManager1 # type: ignore
|
79
|
+
)
|
80
|
+
|
81
|
+
self.assertTrue(hasattr(DummyManager1, "dummy_manager2")) # type: ignore
|
82
|
+
self.assertEqual(DummyManager1.dummy_manager2, DummyManager2) # type: ignore
|
83
|
+
|
84
|
+
self.assertTrue(hasattr(self.dummy_manager1, "dummy_manager2")) # type: ignore
|
85
|
+
self.assertIsInstance(self.dummy_manager1.dummy_manager2, DummyManager2) # type: ignore
|
86
|
+
self.assertEqual(self.dummy_manager1.dummy_manager2, self.dummy_manager2) # type: ignore
|
87
|
+
|
88
|
+
def test_circular_nested_manager_property(self):
|
89
|
+
self.dummy_manager1._attributes = {
|
90
|
+
"dummy_manager2": self.dummy_manager2,
|
91
|
+
}
|
92
|
+
self.dummy_manager2._attributes = {
|
93
|
+
"dummy_manager1": self.dummy_manager1,
|
94
|
+
}
|
95
|
+
|
96
|
+
GeneralManagerMeta.createAtPropertiesForAttributes(
|
97
|
+
["dummy_manager2"], DummyManager1 # type: ignore
|
98
|
+
)
|
99
|
+
|
100
|
+
GeneralManagerMeta.createAtPropertiesForAttributes(
|
101
|
+
["dummy_manager1"], DummyManager2 # type: ignore
|
102
|
+
)
|
103
|
+
|
104
|
+
self.assertTrue(hasattr(DummyManager1, "dummy_manager2"))
|
105
|
+
self.assertEqual(DummyManager1.dummy_manager2, DummyManager2) # type: ignore
|
106
|
+
self.assertTrue(hasattr(DummyManager2, "dummy_manager1")) # type: ignore
|
107
|
+
self.assertEqual(DummyManager2.dummy_manager1, DummyManager1) # type: ignore
|
108
|
+
|
109
|
+
self.assertTrue(hasattr(self.dummy_manager1, "dummy_manager2")) # type: ignore
|
110
|
+
self.assertIsInstance(self.dummy_manager1.dummy_manager2, DummyManager2) # type: ignore
|
111
|
+
self.assertEqual(self.dummy_manager1.dummy_manager2, self.dummy_manager2) # type: ignore
|
112
|
+
|
113
|
+
self.assertTrue(hasattr(self.dummy_manager1.dummy_manager2, "dummy_manager1")) # type: ignore
|
114
|
+
self.assertIsInstance(self.dummy_manager1.dummy_manager2.dummy_manager1, DummyManager1) # type: ignore
|
115
|
+
self.assertEqual(self.dummy_manager1.dummy_manager2.dummy_manager1, self.dummy_manager1) # type: ignore
|
116
|
+
|
117
|
+
def test_multiple_properties_initialization(self):
|
118
|
+
self.dummy_manager1._attributes = {
|
119
|
+
"test_int": 42,
|
120
|
+
"test_field": "value",
|
121
|
+
}
|
122
|
+
|
123
|
+
GeneralManagerMeta.createAtPropertiesForAttributes(
|
124
|
+
["test_int", "test_field"], DummyManager1 # type: ignore
|
125
|
+
)
|
126
|
+
|
127
|
+
self.assertTrue(hasattr(DummyManager1, "test_int"))
|
128
|
+
self.assertEqual(DummyManager1.test_int, int) # type: ignore
|
129
|
+
self.assertTrue(hasattr(DummyManager1, "test_field")) # type: ignore
|
130
|
+
self.assertEqual(DummyManager1.test_field, str) # type: ignore
|
131
|
+
|
132
|
+
self.assertTrue(hasattr(self.dummy_manager1, "test_int"))
|
133
|
+
self.assertEqual(self.dummy_manager1.test_int, 42) # type: ignore
|
134
|
+
self.assertIsInstance(self.dummy_manager1.test_int, int) # type: ignore
|
135
|
+
self.assertTrue(hasattr(self.dummy_manager1, "test_field")) # type: ignore
|
136
|
+
self.assertEqual(self.dummy_manager1.test_field, "value") # type: ignore
|
137
|
+
self.assertIsInstance(self.dummy_manager1.test_field, str) # type: ignore
|
138
|
+
|
139
|
+
def test_property_with_callable(self):
|
140
|
+
def test_callable(interface):
|
141
|
+
return "callable_value"
|
142
|
+
|
143
|
+
self.dummy_manager1._attributes = {
|
144
|
+
"test_field": test_callable,
|
145
|
+
}
|
146
|
+
|
147
|
+
GeneralManagerMeta.createAtPropertiesForAttributes(
|
148
|
+
["test_field"], DummyManager1 # type: ignore
|
149
|
+
)
|
150
|
+
|
151
|
+
self.assertTrue(hasattr(DummyManager1, "test_field"))
|
152
|
+
self.assertEqual(DummyManager1.test_field, str) # type: ignore
|
153
|
+
self.assertTrue(hasattr(self.dummy_manager1, "test_field"))
|
154
|
+
self.assertEqual(self.dummy_manager1.test_field, "callable_value") # type: ignore
|
155
|
+
self.assertIsInstance(self.dummy_manager1.test_field, str) # type: ignore
|
156
|
+
|
157
|
+
def test_property_with_complex_callable(self):
|
158
|
+
def test_complex_callable1(interface):
|
159
|
+
return interface.getAttributes().get("test_field")
|
160
|
+
|
161
|
+
def test_complex_callable2(interface):
|
162
|
+
return interface.getAttributes().get("test_int")
|
163
|
+
|
164
|
+
self.dummy_manager1._attributes = {
|
165
|
+
"test_field": test_complex_callable1,
|
166
|
+
"test_int": test_complex_callable2,
|
167
|
+
}
|
168
|
+
|
169
|
+
GeneralManagerMeta.createAtPropertiesForAttributes(
|
170
|
+
["test_field", "test_int"], DummyManager1 # type: ignore
|
171
|
+
)
|
172
|
+
|
173
|
+
self.assertTrue(hasattr(DummyManager1, "test_field"))
|
174
|
+
self.assertEqual(DummyManager1.test_field, str) # type: ignore
|
175
|
+
self.assertTrue(hasattr(DummyManager1, "test_int"))
|
176
|
+
self.assertEqual(DummyManager1.test_int, int) # type: ignore
|
177
|
+
|
178
|
+
self.assertTrue(hasattr(self.dummy_manager1, "test_field"))
|
179
|
+
self.assertEqual(self.dummy_manager1.test_field, "value") # type: ignore
|
180
|
+
self.assertIsInstance(self.dummy_manager1.test_field, str) # type: ignore
|
181
|
+
self.assertTrue(hasattr(self.dummy_manager1, "test_int"))
|
182
|
+
self.assertEqual(self.dummy_manager1.test_int, 42) # type: ignore
|
183
|
+
self.assertIsInstance(self.dummy_manager1.test_int, int) # type: ignore
|
184
|
+
|
185
|
+
def test_property_with_non_existent_attribute(self):
|
186
|
+
self.dummy_manager1._attributes = {}
|
187
|
+
|
188
|
+
GeneralManagerMeta.createAtPropertiesForAttributes(
|
189
|
+
["non_existent_field"], DummyManager1 # type: ignore
|
190
|
+
)
|
191
|
+
|
192
|
+
with self.assertRaises(AttributeError):
|
193
|
+
getattr(self.dummy_manager1, "non_existent_field")
|
194
|
+
|
195
|
+
def test_property_with_callable_error(self):
|
196
|
+
|
197
|
+
def test_callable_error(interface):
|
198
|
+
raise ValueError("This is a test error")
|
199
|
+
|
200
|
+
self.dummy_manager1._attributes = {
|
201
|
+
"test_field": test_callable_error,
|
202
|
+
}
|
203
|
+
|
204
|
+
GeneralManagerMeta.createAtPropertiesForAttributes(
|
205
|
+
["test_field"], DummyManager1 # type: ignore
|
206
|
+
)
|
207
|
+
|
208
|
+
with self.assertRaises(AttributeError) as context:
|
209
|
+
getattr(self.dummy_manager1, "test_field")
|
210
|
+
self.assertIn("Error calling attribute test_field", str(context.exception))
|
211
|
+
|
212
|
+
|
213
|
+
# -----------------------------------------------------------------------------
|
214
|
+
# 1. Minimal stub classes for Input and Bucket (if referenced by InterfaceBase)
|
215
|
+
# -----------------------------------------------------------------------------
|
216
|
+
# These stubs ensure that InterfaceBase logic relying on Input and Bucket
|
217
|
+
# does not fail during tests.
|
218
|
+
|
219
|
+
|
220
|
+
class Input:
|
221
|
+
def __init__(self, type_, depends_on=(), possible_values=None):
|
222
|
+
self.type = type_
|
223
|
+
self.depends_on = depends_on
|
224
|
+
self.possible_values = possible_values
|
225
|
+
|
226
|
+
|
227
|
+
class Bucket(list):
|
228
|
+
pass
|
229
|
+
|
230
|
+
|
231
|
+
# -----------------------------------------------------------------------------
|
232
|
+
# 2. DummyInterface: minimal implementation of InterfaceBase
|
233
|
+
# -----------------------------------------------------------------------------
|
234
|
+
class DummyInterface(InterfaceBase):
|
235
|
+
"""
|
236
|
+
Minimal subclass of InterfaceBase that:
|
237
|
+
- Defines input_fields as an empty dict so parseInputFieldsToIdentification won’t fail.
|
238
|
+
- Stubs all abstract methods.
|
239
|
+
- Implements handleInterface() to return custom pre/post creation hooks.
|
240
|
+
"""
|
241
|
+
|
242
|
+
input_fields: dict[str, Input] = {} # no required fields # type: ignore
|
243
|
+
|
244
|
+
@classmethod
|
245
|
+
def create(cls, *args, **kwargs):
|
246
|
+
raise NotImplementedError
|
247
|
+
|
248
|
+
def update(self, *args, **kwargs):
|
249
|
+
raise NotImplementedError
|
250
|
+
|
251
|
+
def deactivate(self, *args, **kwargs):
|
252
|
+
raise NotImplementedError
|
253
|
+
|
254
|
+
def getData(self, search_date=None):
|
255
|
+
raise NotImplementedError
|
256
|
+
|
257
|
+
@classmethod
|
258
|
+
def getAttributeTypes(cls) -> dict[str, dict]: # type: ignore
|
259
|
+
return {}
|
260
|
+
|
261
|
+
@classmethod
|
262
|
+
def getAttributes(cls) -> dict[str, dict]:
|
263
|
+
return {}
|
264
|
+
|
265
|
+
@classmethod
|
266
|
+
def filter(cls, **kwargs): # type: ignore
|
267
|
+
return Bucket()
|
268
|
+
|
269
|
+
@classmethod
|
270
|
+
def exclude(cls, **kwargs): # type: ignore
|
271
|
+
return Bucket()
|
272
|
+
|
273
|
+
@classmethod
|
274
|
+
def getFieldType(cls, field_name: str) -> type:
|
275
|
+
return str
|
276
|
+
|
277
|
+
@classmethod
|
278
|
+
def handleInterface(cls):
|
279
|
+
"""
|
280
|
+
Returns two functions:
|
281
|
+
- preCreation: modifies attrs before the class is created (adds 'marker').
|
282
|
+
- postCreation: sets a flag on the newly created class.
|
283
|
+
"""
|
284
|
+
|
285
|
+
def preCreation(name, attrs, interface):
|
286
|
+
attrs["marker"] = "initialized_by_dummy"
|
287
|
+
return attrs, cls, None
|
288
|
+
|
289
|
+
def postCreation(new_cls, interface_cls, model):
|
290
|
+
new_cls.post_mark = True
|
291
|
+
|
292
|
+
return preCreation, postCreation
|
293
|
+
|
294
|
+
|
295
|
+
# -----------------------------------------------------------------------------
|
296
|
+
# 3. Test cases for GeneralManagerMeta
|
297
|
+
# -----------------------------------------------------------------------------
|
298
|
+
class GeneralManagerMetaTests(SimpleTestCase):
|
299
|
+
def setUp(self):
|
300
|
+
# Reset the metaclass’s global lists before each test
|
301
|
+
GeneralManagerMeta.all_classes.clear()
|
302
|
+
GeneralManagerMeta.pending_graphql_interfaces.clear()
|
303
|
+
GeneralManagerMeta.pending_attribute_initialization.clear()
|
304
|
+
|
305
|
+
def test_register_with_valid_interface(self):
|
306
|
+
"""
|
307
|
+
1. Define a class that sets Interface = DummyInterface.
|
308
|
+
2. After definition, it should appear in all_classes and pending_attribute_initialization.
|
309
|
+
3. preCreation hook adds 'marker'; postCreation hook sets 'post_mark'.
|
310
|
+
"""
|
311
|
+
|
312
|
+
class MyManager(metaclass=GeneralManagerMeta):
|
313
|
+
Interface = DummyInterface
|
314
|
+
|
315
|
+
# a) MyManager should be in all_classes
|
316
|
+
self.assertIn(
|
317
|
+
MyManager,
|
318
|
+
GeneralManagerMeta.all_classes,
|
319
|
+
msg="MyManager should be registered in all_classes.",
|
320
|
+
)
|
321
|
+
|
322
|
+
# b) MyManager should be in pending_attribute_initialization
|
323
|
+
self.assertIn(
|
324
|
+
MyManager,
|
325
|
+
GeneralManagerMeta.pending_attribute_initialization,
|
326
|
+
msg="MyManager should be registered in pending_attribute_initialization.",
|
327
|
+
)
|
328
|
+
|
329
|
+
# c) preCreation hook added the 'marker' attribute
|
330
|
+
self.assertTrue(
|
331
|
+
hasattr(MyManager, "marker") and MyManager.marker == "initialized_by_dummy", # type: ignore
|
332
|
+
msg="MyManager.marker should be set by the DummyInterface preCreation hook.",
|
333
|
+
)
|
334
|
+
|
335
|
+
# d) postCreation hook set the 'post_mark' attribute to True
|
336
|
+
self.assertTrue(
|
337
|
+
hasattr(MyManager, "post_mark") and MyManager.post_mark is True, # type: ignore
|
338
|
+
msg="MyManager.post_mark should be True set by the DummyInterface postCreation hook.",
|
339
|
+
)
|
340
|
+
|
341
|
+
def test_invalid_interface_raises_type_error(self):
|
342
|
+
"""
|
343
|
+
A class with Interface = object (not a subclass of InterfaceBase)
|
344
|
+
should raise TypeError when defined.
|
345
|
+
"""
|
346
|
+
with self.assertRaises(TypeError) as cm:
|
347
|
+
|
348
|
+
class BadManager(metaclass=GeneralManagerMeta):
|
349
|
+
Interface = (
|
350
|
+
object # not a valid subclass of InterfaceBase # type: ignore
|
351
|
+
)
|
352
|
+
|
353
|
+
self.assertIn(
|
354
|
+
f"Interface must be a subclass of {InterfaceBase.__name__}",
|
355
|
+
str(cm.exception),
|
356
|
+
msg="Exception message should indicate that InterfaceBase is required.",
|
357
|
+
)
|
358
|
+
|
359
|
+
# The lists should remain empty after the failure
|
360
|
+
self.assertEqual(
|
361
|
+
len(GeneralManagerMeta.all_classes),
|
362
|
+
0,
|
363
|
+
msg="all_classes should remain empty after the failed definition.",
|
364
|
+
)
|
365
|
+
self.assertEqual(
|
366
|
+
len(GeneralManagerMeta.pending_attribute_initialization),
|
367
|
+
0,
|
368
|
+
msg="pending_attribute_initialization should remain empty.",
|
369
|
+
)
|
370
|
+
|
371
|
+
def test_plain_manager_without_interface_does_nothing(self):
|
372
|
+
"""
|
373
|
+
A class without an Interface attribute:
|
374
|
+
- should not be added to all_classes
|
375
|
+
- should not be added to pending_attribute_initialization
|
376
|
+
- (when AUTOCREATE_GRAPHQL=False) should not be added to pending_graphql_interfaces
|
377
|
+
"""
|
378
|
+
before_all = list(GeneralManagerMeta.all_classes)
|
379
|
+
before_pending_init = list(GeneralManagerMeta.pending_attribute_initialization)
|
380
|
+
before_pending_graphql = list(GeneralManagerMeta.pending_graphql_interfaces)
|
381
|
+
|
382
|
+
class PlainManager(metaclass=GeneralManagerMeta):
|
383
|
+
pass
|
384
|
+
|
385
|
+
# a) all_classes should remain unchanged
|
386
|
+
self.assertEqual(
|
387
|
+
GeneralManagerMeta.all_classes,
|
388
|
+
before_all,
|
389
|
+
msg="all_classes should remain unchanged.",
|
390
|
+
)
|
391
|
+
|
392
|
+
# b) pending_attribute_initialization should remain unchanged
|
393
|
+
self.assertEqual(
|
394
|
+
GeneralManagerMeta.pending_attribute_initialization,
|
395
|
+
before_pending_init,
|
396
|
+
msg="pending_attribute_initialization should remain unchanged.",
|
397
|
+
)
|
398
|
+
|
399
|
+
# c) pending_graphql_interfaces should remain unchanged (AUTOCREATE_GRAPHQL=False by default)
|
400
|
+
self.assertEqual(
|
401
|
+
GeneralManagerMeta.pending_graphql_interfaces,
|
402
|
+
before_pending_graphql,
|
403
|
+
msg="pending_graphql_interfaces should remain unchanged.",
|
404
|
+
)
|
405
|
+
|
406
|
+
@override_settings(AUTOCREATE_GRAPHQL=True)
|
407
|
+
def test_autocreate_graphql_flag_adds_to_pending(self):
|
408
|
+
"""
|
409
|
+
When AUTOCREATE_GRAPHQL=True, every created class
|
410
|
+
(with or without Interface) should be added to pending_graphql_interfaces.
|
411
|
+
"""
|
412
|
+
GeneralManagerMeta.pending_graphql_interfaces.clear()
|
413
|
+
|
414
|
+
# 1) Class without Interface
|
415
|
+
class GQLManagerPlain(metaclass=GeneralManagerMeta):
|
416
|
+
pass
|
417
|
+
|
418
|
+
self.assertIn(
|
419
|
+
GQLManagerPlain,
|
420
|
+
GeneralManagerMeta.pending_graphql_interfaces,
|
421
|
+
msg="GQLManagerPlain should be in pending_graphql_interfaces when AUTOCREATE_GRAPHQL=True.",
|
422
|
+
)
|
423
|
+
|
424
|
+
# Clear the list again
|
425
|
+
GeneralManagerMeta.pending_graphql_interfaces.clear()
|
426
|
+
|
427
|
+
# 2) Class WITH Interface
|
428
|
+
class GQLManagerWithInterface(metaclass=GeneralManagerMeta):
|
429
|
+
Interface = DummyInterface
|
430
|
+
|
431
|
+
self.assertIn(
|
432
|
+
GQLManagerWithInterface,
|
433
|
+
GeneralManagerMeta.pending_graphql_interfaces,
|
434
|
+
msg="GQLManagerWithInterface should be in pending_graphql_interfaces when AUTOCREATE_GRAPHQL=True.",
|
435
|
+
)
|
436
|
+
|
437
|
+
def test_multiple_classes_register_in_order(self):
|
438
|
+
"""
|
439
|
+
Define two different DummyInterface variants, each with its own hooks.
|
440
|
+
Verify that all_classes and pending_attribute_initialization
|
441
|
+
appear in the correct order: [ManagerA, ManagerB],
|
442
|
+
and that each manager has the attributes set by its hooks.
|
443
|
+
"""
|
444
|
+
|
445
|
+
# DummyInterfaceA: adds 'fromA' and 'a_post'
|
446
|
+
class DummyInterfaceA(InterfaceBase):
|
447
|
+
input_fields: dict[str, Input] = {} # type: ignore
|
448
|
+
|
449
|
+
@classmethod
|
450
|
+
def create(cls, *args, **kwargs):
|
451
|
+
raise NotImplementedError
|
452
|
+
|
453
|
+
def update(self, *args, **kwargs):
|
454
|
+
raise NotImplementedError
|
455
|
+
|
456
|
+
def deactivate(self, *args, **kwargs):
|
457
|
+
raise NotImplementedError
|
458
|
+
|
459
|
+
def getData(self, search_date=None):
|
460
|
+
raise NotImplementedError
|
461
|
+
|
462
|
+
@classmethod
|
463
|
+
def getAttributeTypes(cls) -> dict[str, dict]: # type: ignore
|
464
|
+
return {}
|
465
|
+
|
466
|
+
@classmethod
|
467
|
+
def getAttributes(cls) -> dict[str, dict]:
|
468
|
+
return {}
|
469
|
+
|
470
|
+
@classmethod
|
471
|
+
def filter(cls, **kwargs): # type: ignore
|
472
|
+
return Bucket()
|
473
|
+
|
474
|
+
@classmethod
|
475
|
+
def exclude(cls, **kwargs): # type: ignore
|
476
|
+
return Bucket()
|
477
|
+
|
478
|
+
@classmethod
|
479
|
+
def getFieldType(cls, field_name: str) -> type:
|
480
|
+
return str
|
481
|
+
|
482
|
+
@classmethod
|
483
|
+
def handleInterface(cls):
|
484
|
+
def preCreation(name, attrs, interface):
|
485
|
+
attrs["fromA"] = True
|
486
|
+
return attrs, cls, None
|
487
|
+
|
488
|
+
def postCreation(new_cls, interface_cls, model):
|
489
|
+
new_cls.a_post = True
|
490
|
+
|
491
|
+
return preCreation, postCreation
|
492
|
+
|
493
|
+
# DummyInterfaceB: adds 'fromB' and 'b_post'
|
494
|
+
class DummyInterfaceB(InterfaceBase):
|
495
|
+
input_fields: dict[str, Input] = {} # type: ignore
|
496
|
+
|
497
|
+
@classmethod
|
498
|
+
def create(cls, *args, **kwargs):
|
499
|
+
raise NotImplementedError
|
500
|
+
|
501
|
+
def update(self, *args, **kwargs):
|
502
|
+
raise NotImplementedError
|
503
|
+
|
504
|
+
def deactivate(self, *args, **kwargs):
|
505
|
+
raise NotImplementedError
|
506
|
+
|
507
|
+
def getData(self, search_date=None):
|
508
|
+
raise NotImplementedError
|
509
|
+
|
510
|
+
@classmethod
|
511
|
+
def getAttributeTypes(cls) -> dict[str, dict]: # type: ignore
|
512
|
+
return {}
|
513
|
+
|
514
|
+
@classmethod
|
515
|
+
def getAttributes(cls) -> dict[str, dict]:
|
516
|
+
return {}
|
517
|
+
|
518
|
+
@classmethod
|
519
|
+
def filter(cls, **kwargs): # type: ignore
|
520
|
+
return Bucket()
|
521
|
+
|
522
|
+
@classmethod
|
523
|
+
def exclude(cls, **kwargs): # type: ignore
|
524
|
+
return Bucket()
|
525
|
+
|
526
|
+
@classmethod
|
527
|
+
def getFieldType(cls, field_name: str) -> type:
|
528
|
+
return str
|
529
|
+
|
530
|
+
@classmethod
|
531
|
+
def handleInterface(cls):
|
532
|
+
def preCreation(name, attrs, interface):
|
533
|
+
attrs["fromB"] = True
|
534
|
+
return attrs, cls, None
|
535
|
+
|
536
|
+
def postCreation(new_cls, interface_cls, model):
|
537
|
+
new_cls.b_post = True
|
538
|
+
|
539
|
+
return preCreation, postCreation
|
540
|
+
|
541
|
+
# 1. Define ManagerA with DummyInterfaceA
|
542
|
+
class ManagerA(metaclass=GeneralManagerMeta):
|
543
|
+
Interface = DummyInterfaceA
|
544
|
+
|
545
|
+
# 2. Define ManagerB with DummyInterfaceB
|
546
|
+
class ManagerB(metaclass=GeneralManagerMeta):
|
547
|
+
Interface = DummyInterfaceB
|
548
|
+
|
549
|
+
# a) all_classes must be [ManagerA, ManagerB] in that order
|
550
|
+
self.assertEqual(
|
551
|
+
GeneralManagerMeta.all_classes,
|
552
|
+
[ManagerA, ManagerB],
|
553
|
+
msg="all_classes should be [ManagerA, ManagerB] in this exact order.",
|
554
|
+
)
|
555
|
+
|
556
|
+
# b) pending_attribute_initialization must be [ManagerA, ManagerB] as well
|
557
|
+
self.assertEqual(
|
558
|
+
GeneralManagerMeta.pending_attribute_initialization,
|
559
|
+
[ManagerA, ManagerB],
|
560
|
+
msg="pending_attribute_initialization should be [ManagerA, ManagerB].",
|
561
|
+
)
|
562
|
+
|
563
|
+
# c) ManagerA should have attributes 'fromA' and 'a_post'
|
564
|
+
self.assertTrue(
|
565
|
+
hasattr(ManagerA, "fromA") and ManagerA.fromA is True, # type: ignore
|
566
|
+
msg="ManagerA should have the attribute 'fromA'.",
|
567
|
+
)
|
568
|
+
self.assertTrue(
|
569
|
+
hasattr(ManagerA, "a_post") and ManagerA.a_post is True, # type: ignore
|
570
|
+
msg="ManagerA should have the attribute 'a_post'.",
|
571
|
+
)
|
572
|
+
|
573
|
+
# d) ManagerB should have attributes 'fromB' and 'b_post'
|
574
|
+
self.assertTrue(
|
575
|
+
hasattr(ManagerB, "fromB") and ManagerB.fromB is True, # type: ignore
|
576
|
+
msg="ManagerB should have the attribute 'fromB'.",
|
577
|
+
)
|
578
|
+
self.assertTrue(
|
579
|
+
hasattr(ManagerB, "b_post") and ManagerB.b_post is True, # type: ignore
|
580
|
+
msg="ManagerB should have the attribute 'b_post'.",
|
581
|
+
)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/cache/modelDependencyCollector.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/interface/baseInterface.py
RENAMED
File without changes
|
{generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/interface/calculationInterface.py
RENAMED
File without changes
|
{generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/interface/databaseInterface.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/measurement/measurement.py
RENAMED
File without changes
|
{generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/measurement/measurementField.py
RENAMED
File without changes
|
File without changes
|
{generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/permission/basePermission.py
RENAMED
File without changes
|
{generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/permission/fileBasedPermission.py
RENAMED
File without changes
|
File without changes
|
{generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/permission/permissionChecks.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|