GeneralManager 0.10.0__tar.gz → 0.10.1__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.10.0 → generalmanager-0.10.1}/GeneralManager.egg-info/PKG-INFO +1 -1
- {generalmanager-0.10.0 → generalmanager-0.10.1}/PKG-INFO +1 -1
- {generalmanager-0.10.0 → generalmanager-0.10.1}/pyproject.toml +1 -1
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/measurement/measurement.py +67 -61
- generalmanager-0.10.1/src/general_manager/utils/testing.py +232 -0
- generalmanager-0.10.0/src/general_manager/utils/testing.py +0 -124
- {generalmanager-0.10.0 → generalmanager-0.10.1}/GeneralManager.egg-info/SOURCES.txt +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/GeneralManager.egg-info/dependency_links.txt +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/GeneralManager.egg-info/requires.txt +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/GeneralManager.egg-info/top_level.txt +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/LICENSE +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/README.md +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/setup.cfg +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/__init__.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/api/graphql.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/api/mutation.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/api/property.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/apps.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/bucket/baseBucket.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/bucket/calculationBucket.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/bucket/databaseBucket.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/bucket/groupBucket.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/cache/cacheDecorator.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/cache/cacheTracker.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/cache/dependencyIndex.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/cache/modelDependencyCollector.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/cache/signals.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/factory/__init__.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/factory/autoFactory.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/factory/factories.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/factory/factoryMethods.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/interface/__init__.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/interface/baseInterface.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/interface/calculationInterface.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/interface/databaseBasedInterface.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/interface/databaseInterface.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/interface/models.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/interface/readOnlyInterface.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/manager/__init__.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/manager/generalManager.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/manager/groupManager.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/manager/input.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/manager/meta.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/measurement/__init__.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/measurement/measurementField.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/permission/__init__.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/permission/basePermission.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/permission/fileBasedPermission.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/permission/managerBasedPermission.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/permission/permissionChecks.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/permission/permissionDataManager.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/rule/__init__.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/rule/handler.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/rule/rule.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/utils/__init__.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/utils/argsToKwargs.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/utils/filterParser.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/utils/formatString.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/utils/jsonEncoder.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/utils/makeCacheKey.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/utils/noneToZero.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/utils/pathMapping.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/tests/test_settings.py +0 -0
- {generalmanager-0.10.0 → generalmanager-0.10.1}/tests/test_urls.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: GeneralManager
|
3
|
-
Version: 0.10.
|
3
|
+
Version: 0.10.1
|
4
4
|
Summary: Modular Django-based data management framework with ORM, GraphQL, fine-grained permissions, rule validation, calculations and caching.
|
5
5
|
Author-email: Tim Kleindick <tkleindick@yahoo.de>
|
6
6
|
License-Expression: MIT
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: GeneralManager
|
3
|
-
Version: 0.10.
|
3
|
+
Version: 0.10.1
|
4
4
|
Summary: Modular Django-based data management framework with ORM, GraphQL, fine-grained permissions, rule validation, calculations and caching.
|
5
5
|
Author-email: Tim Kleindick <tkleindick@yahoo.de>
|
6
6
|
License-Expression: MIT
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "GeneralManager"
|
7
|
-
version = "0.10.
|
7
|
+
version = "0.10.1"
|
8
8
|
description = "Modular Django-based data management framework with ORM, GraphQL, fine-grained permissions, rule validation, calculations and caching."
|
9
9
|
readme = "README.md"
|
10
10
|
authors = [{ name = "Tim Kleindick", email = "tkleindick@yahoo.de" }]
|
{generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/measurement/measurement.py
RENAMED
@@ -22,12 +22,12 @@ for currency in currency_units:
|
|
22
22
|
class Measurement:
|
23
23
|
def __init__(self, value: Decimal | float | int | str, unit: str):
|
24
24
|
"""
|
25
|
-
|
26
|
-
|
25
|
+
Create a Measurement with the specified numeric value and unit.
|
26
|
+
|
27
27
|
Parameters:
|
28
|
-
value
|
29
|
-
unit
|
30
|
-
|
28
|
+
value: The numeric value to associate with the unit. Accepts Decimal, float, int, or a string convertible to Decimal.
|
29
|
+
unit: The unit of measurement as a string.
|
30
|
+
|
31
31
|
Raises:
|
32
32
|
TypeError: If the value cannot be converted to a Decimal.
|
33
33
|
"""
|
@@ -42,10 +42,10 @@ class Measurement:
|
|
42
42
|
|
43
43
|
def __getstate__(self):
|
44
44
|
"""
|
45
|
-
Return a serializable state
|
46
|
-
|
45
|
+
Return a dictionary representing the serializable state of the measurement, including its magnitude and unit as strings.
|
46
|
+
|
47
47
|
Returns:
|
48
|
-
dict:
|
48
|
+
dict: Contains 'magnitude' and 'unit' keys for serialization purposes.
|
49
49
|
"""
|
50
50
|
state = {
|
51
51
|
"magnitude": str(self.magnitude),
|
@@ -55,10 +55,10 @@ class Measurement:
|
|
55
55
|
|
56
56
|
def __setstate__(self, state):
|
57
57
|
"""
|
58
|
-
Restore the Measurement object from a serialized state
|
59
|
-
|
58
|
+
Restore the Measurement object from a serialized state.
|
59
|
+
|
60
60
|
Parameters:
|
61
|
-
state (dict):
|
61
|
+
state (dict): Dictionary with 'magnitude' (as a string) and 'unit' (as a string) representing the measurement.
|
62
62
|
"""
|
63
63
|
value = Decimal(state["magnitude"])
|
64
64
|
unit = state["unit"]
|
@@ -82,16 +82,15 @@ class Measurement:
|
|
82
82
|
@classmethod
|
83
83
|
def from_string(cls, value: str) -> Measurement:
|
84
84
|
"""
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
85
|
+
Creates a Measurement instance from a string containing a numeric value and a unit.
|
86
|
+
|
87
|
+
If the string contains only a value, it is treated as dimensionless. If the string contains both a value and a unit separated by a space, both are used to construct the Measurement. Raises ValueError if the format is invalid or the value cannot be parsed.
|
88
|
+
|
90
89
|
Returns:
|
91
|
-
Measurement: The
|
92
|
-
|
90
|
+
Measurement: The constructed Measurement object.
|
91
|
+
|
93
92
|
Raises:
|
94
|
-
ValueError: If the
|
93
|
+
ValueError: If the string format is invalid or the value cannot be parsed as a number.
|
95
94
|
"""
|
96
95
|
splitted = value.split(" ")
|
97
96
|
if len(splitted) == 1:
|
@@ -118,19 +117,19 @@ class Measurement:
|
|
118
117
|
|
119
118
|
def to(self, target_unit: str, exchange_rate: float | None = None):
|
120
119
|
"""
|
121
|
-
Convert
|
122
|
-
|
123
|
-
For currency
|
124
|
-
|
120
|
+
Convert this measurement to a specified target unit, supporting both currency and physical unit conversions.
|
121
|
+
|
122
|
+
For currency conversions between different currencies, an explicit exchange rate must be provided; if converting to the same currency, the original measurement is returned. For physical units, standard unit conversion is performed using the unit registry.
|
123
|
+
|
125
124
|
Parameters:
|
126
|
-
target_unit (str): The unit to convert
|
127
|
-
exchange_rate (float, optional):
|
128
|
-
|
125
|
+
target_unit (str): The unit to convert to.
|
126
|
+
exchange_rate (float, optional): Required for currency conversion between different currencies.
|
127
|
+
|
129
128
|
Returns:
|
130
|
-
Measurement:
|
131
|
-
|
129
|
+
Measurement: The converted measurement in the target unit.
|
130
|
+
|
132
131
|
Raises:
|
133
|
-
ValueError: If converting between different currencies without
|
132
|
+
ValueError: If converting between different currencies without an exchange rate.
|
134
133
|
"""
|
135
134
|
if self.is_currency():
|
136
135
|
if self.unit == ureg(target_unit):
|
@@ -159,10 +158,10 @@ class Measurement:
|
|
159
158
|
|
160
159
|
def __add__(self, other: Any) -> Measurement:
|
161
160
|
"""
|
162
|
-
Add
|
163
|
-
|
164
|
-
Addition is
|
165
|
-
|
161
|
+
Add this measurement to another, supporting both currency and physical units.
|
162
|
+
|
163
|
+
Addition is permitted only if both operands are currencies of the same unit or both are physical units with compatible dimensions. Raises a TypeError if operands are of different types (currency vs. physical unit) or not Measurement instances, and raises a ValueError if units are incompatible.
|
164
|
+
|
166
165
|
Returns:
|
167
166
|
Measurement: A new Measurement representing the sum.
|
168
167
|
"""
|
@@ -198,11 +197,11 @@ class Measurement:
|
|
198
197
|
def __sub__(self, other: Any) -> Measurement:
|
199
198
|
"""
|
200
199
|
Subtracts another Measurement from this one, enforcing unit compatibility.
|
201
|
-
|
202
|
-
Subtraction is
|
203
|
-
|
200
|
+
|
201
|
+
Subtraction is permitted only between two currency measurements of the same unit or two physical measurements with compatible dimensions. Raises a TypeError if the operand is not a Measurement or if subtracting between a currency and a physical unit. Raises a ValueError if subtracting different currencies or incompatible physical units.
|
202
|
+
|
204
203
|
Returns:
|
205
|
-
Measurement:
|
204
|
+
Measurement: A new Measurement representing the result of the subtraction.
|
206
205
|
"""
|
207
206
|
if not isinstance(other, Measurement):
|
208
207
|
raise TypeError(
|
@@ -229,15 +228,15 @@ class Measurement:
|
|
229
228
|
|
230
229
|
def __mul__(self, other: Any) -> Measurement:
|
231
230
|
"""
|
232
|
-
|
233
|
-
|
234
|
-
Multiplication between two currency measurements is not
|
235
|
-
|
231
|
+
Returns the product of this measurement and another measurement or numeric value.
|
232
|
+
|
233
|
+
Multiplication between two currency measurements is not permitted. When multiplied by another measurement, the result combines their units. When multiplied by a numeric value, the magnitude is scaled and the unit remains unchanged.
|
234
|
+
|
236
235
|
Returns:
|
237
|
-
Measurement:
|
238
|
-
|
236
|
+
Measurement: A new Measurement representing the result.
|
237
|
+
|
239
238
|
Raises:
|
240
|
-
TypeError: If
|
239
|
+
TypeError: If multiplying two currency measurements or if the operand is neither a Measurement nor a numeric value.
|
241
240
|
"""
|
242
241
|
if isinstance(other, Measurement):
|
243
242
|
if self.is_currency() or other.is_currency():
|
@@ -261,15 +260,22 @@ class Measurement:
|
|
261
260
|
def __truediv__(self, other: Any) -> Measurement:
|
262
261
|
"""
|
263
262
|
Divide this measurement by another measurement or a numeric value.
|
264
|
-
|
265
|
-
If dividing by another `Measurement
|
266
|
-
|
263
|
+
|
264
|
+
If dividing by another `Measurement`:
|
265
|
+
- Division between two *different* currencies is disallowed (raises TypeError).
|
266
|
+
- Division between the *same* currency is allowed and yields a dimensionless result.
|
267
|
+
Returns a new `Measurement` with the resulting value and unit.
|
268
|
+
|
267
269
|
Raises:
|
268
|
-
TypeError: If dividing two currency measurements, or if the operand is not a `Measurement` or numeric value.
|
270
|
+
TypeError: If dividing two currency measurements with different units, or if the operand is not a `Measurement` or numeric value.
|
271
|
+
Returns:
|
272
|
+
Measurement: The result of the division as a new `Measurement` instance.
|
269
273
|
"""
|
270
274
|
if isinstance(other, Measurement):
|
271
|
-
if self.is_currency() and other.is_currency():
|
272
|
-
raise TypeError(
|
275
|
+
if self.is_currency() and other.is_currency() and self.unit != other.unit:
|
276
|
+
raise TypeError(
|
277
|
+
"Division between two different currency amounts is not allowed."
|
278
|
+
)
|
273
279
|
result_quantity = self.quantity / other.quantity
|
274
280
|
return Measurement(
|
275
281
|
Decimal(str(result_quantity.magnitude)), str(result_quantity.units)
|
@@ -300,14 +306,14 @@ class Measurement:
|
|
300
306
|
|
301
307
|
def _compare(self, other: Any, operation: Callable[..., bool]) -> bool:
|
302
308
|
"""
|
303
|
-
|
304
|
-
|
305
|
-
If `other` is a string, it is parsed
|
306
|
-
|
309
|
+
Compares this Measurement to another using a specified comparison operation.
|
310
|
+
|
311
|
+
If `other` is a string, it is parsed as a Measurement. The comparison is performed after converting `other` to this instance's unit. Raises a TypeError if `other` is not a Measurement or a valid string, and a ValueError if the measurements have incompatible dimensions.
|
312
|
+
|
307
313
|
Parameters:
|
308
314
|
other: The object to compare, either a Measurement or a string in the format "value unit".
|
309
315
|
operation: A callable that takes two magnitudes and returns a boolean.
|
310
|
-
|
316
|
+
|
311
317
|
Returns:
|
312
318
|
bool: The result of the comparison.
|
313
319
|
"""
|
@@ -348,16 +354,16 @@ class Measurement:
|
|
348
354
|
|
349
355
|
def __ge__(self, other: Any) -> bool:
|
350
356
|
"""
|
351
|
-
Return True if this measurement is greater than or equal to another measurement.
|
352
|
-
|
353
|
-
The comparison
|
357
|
+
Return True if this measurement is greater than or equal to another measurement or compatible value.
|
358
|
+
|
359
|
+
The comparison converts the other operand to this measurement's unit before evaluating. Raises TypeError if the operand is not a Measurement or convertible string, or ValueError if units are incompatible.
|
354
360
|
"""
|
355
361
|
return self._compare(other, ge)
|
356
362
|
|
357
363
|
def __hash__(self) -> int:
|
358
364
|
"""
|
359
|
-
Return a hash
|
360
|
-
|
361
|
-
Enables
|
365
|
+
Return a hash based on the measurement's magnitude and unit.
|
366
|
+
|
367
|
+
Enables Measurement instances to be used in hash-based collections.
|
362
368
|
"""
|
363
369
|
return hash((self.magnitude, str(self.unit)))
|
@@ -0,0 +1,232 @@
|
|
1
|
+
from graphene_django.utils.testing import GraphQLTransactionTestCase
|
2
|
+
from general_manager.apps import GeneralmanagerConfig
|
3
|
+
from importlib import import_module
|
4
|
+
from django.db import connection
|
5
|
+
from django.conf import settings
|
6
|
+
from typing import cast
|
7
|
+
from django.db import models
|
8
|
+
from general_manager.manager.generalManager import GeneralManager
|
9
|
+
from general_manager.api.graphql import GraphQL
|
10
|
+
from django.apps import apps as global_apps
|
11
|
+
|
12
|
+
|
13
|
+
from unittest.mock import ANY
|
14
|
+
from general_manager.cache.cacheDecorator import _SENTINEL
|
15
|
+
|
16
|
+
|
17
|
+
from django.test import override_settings
|
18
|
+
from django.core.cache import caches
|
19
|
+
from django.core.cache.backends.locmem import LocMemCache
|
20
|
+
|
21
|
+
_original_get_app = global_apps.get_containing_app_config
|
22
|
+
|
23
|
+
|
24
|
+
def createFallbackGetApp(fallback_app: str):
|
25
|
+
"""
|
26
|
+
Creates a fallback function for getting the app config, which returns the specified fallback app if the original lookup fails.
|
27
|
+
|
28
|
+
Parameters:
|
29
|
+
fallback_app (str): The name of the app to return if the original lookup fails.
|
30
|
+
|
31
|
+
Returns:
|
32
|
+
function: A function that attempts to get the app config for a given object name, falling back to the specified app if not found.
|
33
|
+
"""
|
34
|
+
|
35
|
+
def _fallback_get_app(object_name: str):
|
36
|
+
cfg = _original_get_app(object_name)
|
37
|
+
if cfg is not None:
|
38
|
+
return cfg
|
39
|
+
try:
|
40
|
+
return global_apps.get_app_config(fallback_app)
|
41
|
+
except LookupError:
|
42
|
+
return None
|
43
|
+
|
44
|
+
return _fallback_get_app
|
45
|
+
|
46
|
+
|
47
|
+
def _default_graphql_url_clear():
|
48
|
+
"""
|
49
|
+
Removes the first URL pattern for the GraphQL view from the project's root URL configuration.
|
50
|
+
|
51
|
+
This function searches the root URL patterns for a pattern whose callback is a `GraphQLView` and removes it, effectively clearing the default GraphQL endpoint from the URL configuration.
|
52
|
+
"""
|
53
|
+
urlconf = import_module(settings.ROOT_URLCONF)
|
54
|
+
for pattern in urlconf.urlpatterns:
|
55
|
+
if (
|
56
|
+
hasattr(pattern, "callback")
|
57
|
+
and hasattr(pattern.callback, "view_class")
|
58
|
+
and pattern.callback.view_class.__name__ == "GraphQLView"
|
59
|
+
):
|
60
|
+
urlconf.urlpatterns.remove(pattern)
|
61
|
+
break
|
62
|
+
|
63
|
+
|
64
|
+
class GMTestCaseMeta(type):
|
65
|
+
"""
|
66
|
+
Metaclass that wraps setUpClass: first calls user-defined setup,
|
67
|
+
then performs GM environment initialization, then super().setUpClass().
|
68
|
+
"""
|
69
|
+
|
70
|
+
def __new__(mcs, name, bases, attrs):
|
71
|
+
"""
|
72
|
+
Creates a new test case class with a customized setUpClass method for GeneralManager and GraphQL integration tests.
|
73
|
+
|
74
|
+
The generated setUpClass ensures the test environment is properly initialized by resetting GraphQL registries, applying any user-defined setup, clearing default GraphQL URL patterns, creating missing database tables for specified GeneralManager models and their history, initializing GeneralManager and GraphQL configurations, and invoking the base GraphQLTransactionTestCase setup.
|
75
|
+
"""
|
76
|
+
user_setup = attrs.get("setUpClass")
|
77
|
+
fallback_app = attrs.get("fallback_app", "general_manager")
|
78
|
+
# MERKE dir das echte GraphQLTransactionTestCase.setUpClass
|
79
|
+
base_setup = GraphQLTransactionTestCase.setUpClass
|
80
|
+
|
81
|
+
def wrapped_setUpClass(cls):
|
82
|
+
"""
|
83
|
+
Performs setup for a test case class by resetting GraphQL internals, configuring fallback app lookup, clearing default GraphQL URL patterns, ensuring database tables exist for specified GeneralManager models and their history, initializing GeneralManager and GraphQL configurations, and invoking the base test case setup.
|
84
|
+
|
85
|
+
Skips database table creation for any GeneralManager class lacking an `Interface` or `_model` attribute.
|
86
|
+
"""
|
87
|
+
GraphQL._query_class = None
|
88
|
+
GraphQL._mutation_class = None
|
89
|
+
GraphQL._mutations = {}
|
90
|
+
GraphQL._query_fields = {}
|
91
|
+
GraphQL.graphql_type_registry = {}
|
92
|
+
GraphQL.graphql_filter_type_registry = {}
|
93
|
+
|
94
|
+
if fallback_app is not None:
|
95
|
+
global_apps.get_containing_app_config = createFallbackGetApp(
|
96
|
+
fallback_app
|
97
|
+
)
|
98
|
+
|
99
|
+
# 1) user-defined setUpClass (if any)
|
100
|
+
if user_setup:
|
101
|
+
user_setup.__func__(cls)
|
102
|
+
# 2) clear URL patterns
|
103
|
+
_default_graphql_url_clear()
|
104
|
+
# 3) register models & create tables
|
105
|
+
existing = connection.introspection.table_names()
|
106
|
+
with connection.schema_editor() as editor:
|
107
|
+
for manager_class in cls.general_manager_classes:
|
108
|
+
if not hasattr(manager_class, "Interface") or not hasattr(
|
109
|
+
manager_class.Interface, "_model"
|
110
|
+
):
|
111
|
+
continue
|
112
|
+
model_class = cast(
|
113
|
+
type[models.Model], manager_class.Interface._model # type: ignore
|
114
|
+
)
|
115
|
+
if model_class._meta.db_table not in existing:
|
116
|
+
editor.create_model(model_class)
|
117
|
+
editor.create_model(model_class.history.model) # type: ignore
|
118
|
+
# 4) GM & GraphQL initialization
|
119
|
+
GeneralmanagerConfig.initializeGeneralManagerClasses(
|
120
|
+
cls.general_manager_classes, cls.general_manager_classes
|
121
|
+
)
|
122
|
+
GeneralmanagerConfig.handleReadOnlyInterface(cls.read_only_classes)
|
123
|
+
GeneralmanagerConfig.handleGraphQL(cls.general_manager_classes)
|
124
|
+
# 5) GraphQLTransactionTestCase.setUpClass
|
125
|
+
base_setup.__func__(cls)
|
126
|
+
|
127
|
+
attrs["setUpClass"] = classmethod(wrapped_setUpClass)
|
128
|
+
return super().__new__(mcs, name, bases, attrs)
|
129
|
+
|
130
|
+
|
131
|
+
class LoggingCache(LocMemCache):
|
132
|
+
def __init__(self, *args, **kwargs):
|
133
|
+
"""
|
134
|
+
Initialize the LoggingCache and set up an empty list to record cache operations.
|
135
|
+
"""
|
136
|
+
super().__init__(*args, **kwargs)
|
137
|
+
self.ops = []
|
138
|
+
|
139
|
+
def get(self, key, default=None, version=None):
|
140
|
+
"""
|
141
|
+
Retrieve a value from the cache and log whether it was a cache hit or miss.
|
142
|
+
|
143
|
+
Parameters:
|
144
|
+
key (str): The cache key to retrieve.
|
145
|
+
default: The value to return if the key is not found.
|
146
|
+
version: Optional cache version.
|
147
|
+
|
148
|
+
Returns:
|
149
|
+
The cached value if found; otherwise, the default value.
|
150
|
+
"""
|
151
|
+
val = super().get(key, default)
|
152
|
+
self.ops.append(("get", key, val is not _SENTINEL))
|
153
|
+
return val
|
154
|
+
|
155
|
+
def set(self, key, value, timeout=None, version=None):
|
156
|
+
"""
|
157
|
+
Store a value in the cache and log the set operation.
|
158
|
+
|
159
|
+
Parameters:
|
160
|
+
key (str): The cache key to set.
|
161
|
+
value (Any): The value to store in the cache.
|
162
|
+
timeout (Optional[int]): The cache timeout in seconds.
|
163
|
+
version (Optional[int]): The cache version (unused).
|
164
|
+
"""
|
165
|
+
super().set(key, value, timeout)
|
166
|
+
self.ops.append(("set", key))
|
167
|
+
|
168
|
+
|
169
|
+
@override_settings(
|
170
|
+
CACHES={
|
171
|
+
"default": {
|
172
|
+
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
|
173
|
+
"LOCATION": "test-cache",
|
174
|
+
}
|
175
|
+
}
|
176
|
+
)
|
177
|
+
class GeneralManagerTransactionTestCase(
|
178
|
+
GraphQLTransactionTestCase, metaclass=GMTestCaseMeta
|
179
|
+
):
|
180
|
+
general_manager_classes: list[type[GeneralManager]] = []
|
181
|
+
read_only_classes: list[type[GeneralManager]] = []
|
182
|
+
fallback_app: str | None = "general_manager"
|
183
|
+
|
184
|
+
def setUp(self) -> None:
|
185
|
+
"""
|
186
|
+
Prepares the test environment by replacing the default cache with a LoggingCache and resetting the cache operations log.
|
187
|
+
"""
|
188
|
+
super().setUp()
|
189
|
+
setattr(caches._connections, "default", LoggingCache("test-cache", {})) # type: ignore
|
190
|
+
self.__resetCacheCounter()
|
191
|
+
|
192
|
+
#
|
193
|
+
def assertCacheMiss(self):
|
194
|
+
"""
|
195
|
+
Assert that a cache miss occurred, followed by a cache set operation.
|
196
|
+
|
197
|
+
Checks that the cache's `get` method was called and did not find a value, and that the `set` method was subsequently called to store a value. Resets the cache operation log after the assertion.
|
198
|
+
"""
|
199
|
+
ops = getattr(caches["default"], "ops")
|
200
|
+
self.assertIn(
|
201
|
+
("get", ANY, False),
|
202
|
+
ops,
|
203
|
+
"Cache.get should have been called and found nothing",
|
204
|
+
)
|
205
|
+
self.assertIn(("set", ANY), ops, "Cache.set should have stored the value")
|
206
|
+
self.__resetCacheCounter()
|
207
|
+
|
208
|
+
def assertCacheHit(self):
|
209
|
+
"""
|
210
|
+
Assert that a cache get operation resulted in a cache hit and no cache set operation occurred.
|
211
|
+
|
212
|
+
Raises an assertion error if the cache did not return a value for a get operation or if a set operation was performed. Resets the cache operation log after the check.
|
213
|
+
"""
|
214
|
+
ops = getattr(caches["default"], "ops")
|
215
|
+
self.assertIn(
|
216
|
+
("get", ANY, True),
|
217
|
+
ops,
|
218
|
+
"Cache.get should have been called and found something",
|
219
|
+
)
|
220
|
+
|
221
|
+
self.assertNotIn(
|
222
|
+
("set", ANY),
|
223
|
+
ops,
|
224
|
+
"Cache.set should not have stored anything",
|
225
|
+
)
|
226
|
+
self.__resetCacheCounter()
|
227
|
+
|
228
|
+
def __resetCacheCounter(self):
|
229
|
+
"""
|
230
|
+
Clear the log of cache operations recorded by the LoggingCache instance.
|
231
|
+
"""
|
232
|
+
caches["default"].ops = [] # type: ignore
|
@@ -1,124 +0,0 @@
|
|
1
|
-
from graphene_django.utils.testing import GraphQLTransactionTestCase
|
2
|
-
from general_manager.apps import GeneralmanagerConfig
|
3
|
-
from importlib import import_module
|
4
|
-
from django.db import connection
|
5
|
-
from django.conf import settings
|
6
|
-
from typing import cast
|
7
|
-
from django.db import models
|
8
|
-
from general_manager.manager.generalManager import GeneralManager
|
9
|
-
from general_manager.api.graphql import GraphQL
|
10
|
-
from django.apps import apps as global_apps
|
11
|
-
|
12
|
-
|
13
|
-
_original_get_app = global_apps.get_containing_app_config
|
14
|
-
|
15
|
-
|
16
|
-
def createFallbackGetApp(fallback_app: str):
|
17
|
-
"""
|
18
|
-
Creates a fallback function for getting the app config, which returns the specified fallback app if the original lookup fails.
|
19
|
-
|
20
|
-
Parameters:
|
21
|
-
fallback_app (str): The name of the app to return if the original lookup fails.
|
22
|
-
|
23
|
-
Returns:
|
24
|
-
function: A function that attempts to get the app config for a given object name, falling back to the specified app if not found.
|
25
|
-
"""
|
26
|
-
|
27
|
-
def _fallback_get_app(object_name: str):
|
28
|
-
cfg = _original_get_app(object_name)
|
29
|
-
if cfg is not None:
|
30
|
-
return cfg
|
31
|
-
try:
|
32
|
-
return global_apps.get_app_config(fallback_app)
|
33
|
-
except LookupError:
|
34
|
-
return None
|
35
|
-
|
36
|
-
return _fallback_get_app
|
37
|
-
|
38
|
-
|
39
|
-
def _default_graphql_url_clear():
|
40
|
-
"""
|
41
|
-
Removes the first URL pattern for the GraphQL view from the project's root URL configuration.
|
42
|
-
|
43
|
-
This function searches the root URL patterns for a pattern whose callback is a `GraphQLView` and removes it, effectively clearing the default GraphQL endpoint from the URL configuration.
|
44
|
-
"""
|
45
|
-
urlconf = import_module(settings.ROOT_URLCONF)
|
46
|
-
for pattern in urlconf.urlpatterns:
|
47
|
-
if (
|
48
|
-
hasattr(pattern, "callback")
|
49
|
-
and hasattr(pattern.callback, "view_class")
|
50
|
-
and pattern.callback.view_class.__name__ == "GraphQLView"
|
51
|
-
):
|
52
|
-
urlconf.urlpatterns.remove(pattern)
|
53
|
-
break
|
54
|
-
|
55
|
-
|
56
|
-
class GMTestCaseMeta(type):
|
57
|
-
"""
|
58
|
-
Metaclass that wraps setUpClass: first calls user-defined setup,
|
59
|
-
then performs GM environment initialization, then super().setUpClass().
|
60
|
-
"""
|
61
|
-
|
62
|
-
def __new__(mcs, name, bases, attrs):
|
63
|
-
"""
|
64
|
-
Creates a new test case class with a customized setUpClass that prepares the database schema and GraphQL environment for GeneralManager integration tests.
|
65
|
-
|
66
|
-
The generated setUpClass method resets GraphQL class registries, invokes any user-defined setUpClass, clears default GraphQL URL patterns, creates missing database tables for specified GeneralManager classes and their history models, initializes GeneralManager and GraphQL configurations, and finally calls the original GraphQLTransactionTestCase setUpClass.
|
67
|
-
"""
|
68
|
-
user_setup = attrs.get("setUpClass")
|
69
|
-
fallback_app = attrs.get("fallback_app", "general_manager")
|
70
|
-
# MERKE dir das echte GraphQLTransactionTestCase.setUpClass
|
71
|
-
base_setup = GraphQLTransactionTestCase.setUpClass
|
72
|
-
|
73
|
-
def wrapped_setUpClass(cls):
|
74
|
-
"""
|
75
|
-
Performs comprehensive setup for a test case class, initializing GraphQL and GeneralManager environments and ensuring required database tables exist.
|
76
|
-
|
77
|
-
This method resets internal GraphQL registries, invokes any user-defined setup, removes default GraphQL URL patterns, creates missing database tables for models and their history associated with specified GeneralManager classes, initializes GeneralManager and GraphQL configurations, and finally calls the base test case setup.
|
78
|
-
"""
|
79
|
-
GraphQL._query_class = None
|
80
|
-
GraphQL._mutation_class = None
|
81
|
-
GraphQL._mutations = {}
|
82
|
-
GraphQL._query_fields = {}
|
83
|
-
GraphQL.graphql_type_registry = {}
|
84
|
-
GraphQL.graphql_filter_type_registry = {}
|
85
|
-
|
86
|
-
if fallback_app is not None:
|
87
|
-
global_apps.get_containing_app_config = createFallbackGetApp(
|
88
|
-
fallback_app
|
89
|
-
)
|
90
|
-
|
91
|
-
# 1) user-defined setUpClass (if any)
|
92
|
-
if user_setup:
|
93
|
-
user_setup.__func__(cls)
|
94
|
-
# 2) clear URL patterns
|
95
|
-
_default_graphql_url_clear()
|
96
|
-
# 3) register models & create tables
|
97
|
-
existing = connection.introspection.table_names()
|
98
|
-
with connection.schema_editor() as editor:
|
99
|
-
for manager_class in cls.general_manager_classes:
|
100
|
-
model_class = cast(
|
101
|
-
type[models.Model], manager_class.Interface._model # type: ignore
|
102
|
-
)
|
103
|
-
if model_class._meta.db_table not in existing:
|
104
|
-
editor.create_model(model_class)
|
105
|
-
editor.create_model(model_class.history.model) # type: ignore
|
106
|
-
# 4) GM & GraphQL initialization
|
107
|
-
GeneralmanagerConfig.initializeGeneralManagerClasses(
|
108
|
-
cls.general_manager_classes, cls.general_manager_classes
|
109
|
-
)
|
110
|
-
GeneralmanagerConfig.handleReadOnlyInterface(cls.read_only_classes)
|
111
|
-
GeneralmanagerConfig.handleGraphQL(cls.general_manager_classes)
|
112
|
-
# 5) GraphQLTransactionTestCase.setUpClass
|
113
|
-
base_setup.__func__(cls)
|
114
|
-
|
115
|
-
attrs["setUpClass"] = classmethod(wrapped_setUpClass)
|
116
|
-
return super().__new__(mcs, name, bases, attrs)
|
117
|
-
|
118
|
-
|
119
|
-
class GeneralManagerTransactionTestCase(
|
120
|
-
GraphQLTransactionTestCase, metaclass=GMTestCaseMeta
|
121
|
-
):
|
122
|
-
general_manager_classes: list[type[GeneralManager]] = []
|
123
|
-
read_only_classes: list[type[GeneralManager]] = []
|
124
|
-
fallback_app: str | None = "general_manager"
|
File without changes
|
{generalmanager-0.10.0 → generalmanager-0.10.1}/GeneralManager.egg-info/dependency_links.txt
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
|
{generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/bucket/calculationBucket.py
RENAMED
File without changes
|
{generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/bucket/databaseBucket.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/cache/dependencyIndex.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/factory/factoryMethods.py
RENAMED
File without changes
|
File without changes
|
{generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/interface/baseInterface.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
{generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/interface/databaseInterface.py
RENAMED
File without changes
|
File without changes
|
{generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/interface/readOnlyInterface.py
RENAMED
File without changes
|
File without changes
|
{generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/manager/generalManager.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/measurement/measurementField.py
RENAMED
File without changes
|
File without changes
|
{generalmanager-0.10.0 → generalmanager-0.10.1}/src/general_manager/permission/basePermission.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
{generalmanager-0.10.0 → generalmanager-0.10.1}/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
|