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.
Files changed (76) hide show
  1. {generalmanager-0.4.6 → generalmanager-0.5.0}/GeneralManager.egg-info/PKG-INFO +1 -1
  2. {generalmanager-0.4.6 → generalmanager-0.5.0}/GeneralManager.egg-info/SOURCES.txt +1 -0
  3. {generalmanager-0.4.6 → generalmanager-0.5.0}/PKG-INFO +1 -1
  4. {generalmanager-0.4.6 → generalmanager-0.5.0}/pyproject.toml +1 -1
  5. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/apps.py +1 -1
  6. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/manager/meta.py +18 -5
  7. generalmanager-0.5.0/tests/test_generalManagerMeta.py +581 -0
  8. {generalmanager-0.4.6 → generalmanager-0.5.0}/GeneralManager.egg-info/dependency_links.txt +0 -0
  9. {generalmanager-0.4.6 → generalmanager-0.5.0}/GeneralManager.egg-info/requires.txt +0 -0
  10. {generalmanager-0.4.6 → generalmanager-0.5.0}/GeneralManager.egg-info/top_level.txt +0 -0
  11. {generalmanager-0.4.6 → generalmanager-0.5.0}/LICENSE +0 -0
  12. {generalmanager-0.4.6 → generalmanager-0.5.0}/README.md +0 -0
  13. {generalmanager-0.4.6 → generalmanager-0.5.0}/setup.cfg +0 -0
  14. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/__init__.py +0 -0
  15. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/api/graphql.py +0 -0
  16. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/api/mutation.py +0 -0
  17. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/api/property.py +0 -0
  18. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/auxiliary/__init__.py +0 -0
  19. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/auxiliary/argsToKwargs.py +0 -0
  20. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/auxiliary/filterParser.py +0 -0
  21. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/auxiliary/jsonEncoder.py +0 -0
  22. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/auxiliary/makeCacheKey.py +0 -0
  23. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/auxiliary/noneToZero.py +0 -0
  24. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/auxiliary/pathMapping.py +0 -0
  25. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/cache/cacheDecorator.py +0 -0
  26. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/cache/cacheTracker.py +0 -0
  27. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/cache/dependencyIndex.py +0 -0
  28. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/cache/modelDependencyCollector.py +0 -0
  29. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/cache/signals.py +0 -0
  30. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/factory/__init__.py +0 -0
  31. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/factory/autoFactory.py +0 -0
  32. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/factory/factories.py +0 -0
  33. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/factory/factoryMethods.py +0 -0
  34. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/interface/__init__.py +0 -0
  35. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/interface/baseInterface.py +0 -0
  36. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/interface/calculationInterface.py +0 -0
  37. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/interface/databaseInterface.py +0 -0
  38. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/manager/__init__.py +0 -0
  39. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/manager/generalManager.py +0 -0
  40. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/manager/groupManager.py +0 -0
  41. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/manager/input.py +0 -0
  42. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/measurement/__init__.py +0 -0
  43. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/measurement/measurement.py +0 -0
  44. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/measurement/measurementField.py +0 -0
  45. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/permission/__init__.py +0 -0
  46. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/permission/basePermission.py +0 -0
  47. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/permission/fileBasedPermission.py +0 -0
  48. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/permission/managerBasedPermission.py +0 -0
  49. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/permission/permissionChecks.py +0 -0
  50. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/permission/permissionDataManager.py +0 -0
  51. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/rule/__init__.py +0 -0
  52. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/rule/handler.py +0 -0
  53. {generalmanager-0.4.6 → generalmanager-0.5.0}/src/general_manager/rule/rule.py +0 -0
  54. {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_argsToKwargs.py +0 -0
  55. {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_autoFactory.py +0 -0
  56. {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_basePermission.py +0 -0
  57. {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_cacheDecorator.py +0 -0
  58. {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_cacheTracker.py +0 -0
  59. {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_dependencyIndex.py +0 -0
  60. {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_factories.py +0 -0
  61. {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_factoryMethods.py +0 -0
  62. {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_filterParser.py +0 -0
  63. {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_generalManager.py +0 -0
  64. {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_graph_ql.py +0 -0
  65. {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_input.py +0 -0
  66. {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_jsonEncoder.py +0 -0
  67. {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_makeCacheKey.py +0 -0
  68. {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_managerBasedPermission.py +0 -0
  69. {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_measurement.py +0 -0
  70. {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_measurement_field.py +0 -0
  71. {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_modelDependencyCollector.py +0 -0
  72. {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_noneToZero.py +0 -0
  73. {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_rule_handler.py +0 -0
  74. {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_rules.py +0 -0
  75. {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_settings.py +0 -0
  76. {generalmanager-0.4.6 → generalmanager-0.5.0}/tests/test_signals.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GeneralManager
3
- Version: 0.4.6
3
+ Version: 0.5.0
4
4
  Summary: Kurzbeschreibung deines Pakets
5
5
  Author-email: Tim Kleindick <tkleindick@yahoo.de>
6
6
  License-Expression: MIT
@@ -58,6 +58,7 @@ tests/test_factories.py
58
58
  tests/test_factoryMethods.py
59
59
  tests/test_filterParser.py
60
60
  tests/test_generalManager.py
61
+ tests/test_generalManagerMeta.py
61
62
  tests/test_graph_ql.py
62
63
  tests/test_input.py
63
64
  tests/test_jsonEncoder.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GeneralManager
3
- Version: 0.4.6
3
+ Version: 0.5.0
4
4
  Summary: Kurzbeschreibung deines Pakets
5
5
  Author-email: Tim Kleindick <tkleindick@yahoo.de>
6
6
  License-Expression: MIT
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
 
8
8
  [project]
9
9
  name = "GeneralManager"
10
- version = "0.4.6"
10
+ version = "0.5.0"
11
11
  description = "Kurzbeschreibung deines Pakets"
12
12
  readme = "README.md"
13
13
  authors = [
@@ -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: dict[str, Any], new_class: Type[GeneralManager]
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[attr_name]
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
- return attribute(instance._interface)
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.keys():
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