GeneralManager 0.3.1__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- general_manager/cache/dependencyIndex.py +41 -45
- general_manager/factory/__init__.py +1 -1
- general_manager/factory/factories.py +71 -54
- general_manager/factory/factoryMethods.py +109 -0
- general_manager/measurement/measurement.py +8 -0
- {generalmanager-0.3.1.dist-info → generalmanager-0.4.0.dist-info}/METADATA +1 -1
- {generalmanager-0.3.1.dist-info → generalmanager-0.4.0.dist-info}/RECORD +10 -10
- {generalmanager-0.3.1.dist-info → generalmanager-0.4.0.dist-info}/WHEEL +1 -1
- general_manager/factory/lazy_methods.py +0 -38
- {generalmanager-0.3.1.dist-info → generalmanager-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {generalmanager-0.3.1.dist-info → generalmanager-0.4.0.dist-info}/top_level.txt +0 -0
@@ -33,18 +33,19 @@ type Dependency = Tuple[general_manager_name, filter_type, str]
|
|
33
33
|
INDEX_KEY = "dependency_index" # Key unter dem der gesamte Index liegt
|
34
34
|
LOCK_KEY = "dependency_index_lock" # Key für das Sperr‑Mutex
|
35
35
|
LOCK_TIMEOUT = 5 # Sekunden TTL für den Lock
|
36
|
+
UNDEFINED = object() # Dummy für nicht definierte Werte
|
36
37
|
|
37
38
|
|
38
39
|
# -----------------------------------------------------------------------------
|
39
40
|
# LOCKING HELPERS
|
40
41
|
# -----------------------------------------------------------------------------
|
41
42
|
def acquire_lock(timeout: int = LOCK_TIMEOUT) -> bool:
|
42
|
-
"""Atomar:
|
43
|
+
"""Atomar: create Lock key if it doesn't exist."""
|
43
44
|
return cache.add(LOCK_KEY, "1", timeout)
|
44
45
|
|
45
46
|
|
46
47
|
def release_lock() -> None:
|
47
|
-
"""
|
48
|
+
"""Release Lock key."""
|
48
49
|
cache.delete(LOCK_KEY)
|
49
50
|
|
50
51
|
|
@@ -52,7 +53,7 @@ def release_lock() -> None:
|
|
52
53
|
# INDEX ACCESS
|
53
54
|
# -----------------------------------------------------------------------------
|
54
55
|
def get_full_index() -> dependency_index:
|
55
|
-
"""
|
56
|
+
"""Load or initialize the full index."""
|
56
57
|
idx = cache.get(INDEX_KEY, None)
|
57
58
|
if idx is None:
|
58
59
|
idx: dependency_index = {"filter": {}, "exclude": {}}
|
@@ -61,7 +62,7 @@ def get_full_index() -> dependency_index:
|
|
61
62
|
|
62
63
|
|
63
64
|
def set_full_index(idx: dependency_index) -> None:
|
64
|
-
"""
|
65
|
+
"""Write the complete index back to the cache."""
|
65
66
|
cache.set(INDEX_KEY, idx, None)
|
66
67
|
|
67
68
|
|
@@ -78,15 +79,6 @@ def record_dependencies(
|
|
78
79
|
]
|
79
80
|
],
|
80
81
|
) -> None:
|
81
|
-
"""
|
82
|
-
Speichert die Abhängigkeiten eines Cache Eintrags.
|
83
|
-
:param cache_key: der Key unter dem das Ergebnis im cache steht
|
84
|
-
:param dependencies: Iterable von Tuplen (model_name, action, identifier)
|
85
|
-
action ∈ {'filter','exclude'} oder sonstige → 'id'
|
86
|
-
identifier = für filter/exclude: Dict String,
|
87
|
-
sonst: Primärschlüssel als String
|
88
|
-
"""
|
89
|
-
# 1) Lock holen (Spin‑Lock mit Timeout)
|
90
82
|
start = time.time()
|
91
83
|
while not acquire_lock():
|
92
84
|
if time.time() - start > LOCK_TIMEOUT:
|
@@ -105,7 +97,7 @@ def record_dependencies(
|
|
105
97
|
lookup_map.setdefault(val_key, set()).add(cache_key)
|
106
98
|
|
107
99
|
else:
|
108
|
-
#
|
100
|
+
# director ID Lookup as simple filter on 'id'
|
109
101
|
section = idx["filter"].setdefault(model_name, {})
|
110
102
|
lookup_map = section.setdefault("identification", {})
|
111
103
|
val_key = identifier
|
@@ -120,15 +112,12 @@ def record_dependencies(
|
|
120
112
|
# -----------------------------------------------------------------------------
|
121
113
|
# INDEX CLEANUP
|
122
114
|
# -----------------------------------------------------------------------------
|
123
|
-
def remove_cache_key_from_index(cache_key: str) ->
|
124
|
-
"""
|
125
|
-
Entfernt einen cache_key aus allen Einträgen in filter/exclude.
|
126
|
-
Nützlich, sobald Du den Cache gelöscht hast.
|
127
|
-
"""
|
115
|
+
def remove_cache_key_from_index(cache_key: str) -> None:
|
116
|
+
"""Remove a cache key from the index."""
|
128
117
|
start = time.time()
|
129
118
|
while not acquire_lock():
|
130
119
|
if time.time() - start > LOCK_TIMEOUT:
|
131
|
-
|
120
|
+
raise TimeoutError("Could not aquire lock for remove_cache_key_from_index")
|
132
121
|
time.sleep(0.05)
|
133
122
|
|
134
123
|
try:
|
@@ -149,14 +138,12 @@ def remove_cache_key_from_index(cache_key: str) -> bool:
|
|
149
138
|
set_full_index(idx)
|
150
139
|
finally:
|
151
140
|
release_lock()
|
152
|
-
return True
|
153
141
|
|
154
142
|
|
155
143
|
# -----------------------------------------------------------------------------
|
156
144
|
# CACHE INVALIDATION
|
157
145
|
# -----------------------------------------------------------------------------
|
158
146
|
def invalidate_cache_key(cache_key: str) -> None:
|
159
|
-
"""Löscht den CacheEintrag – hier nutzt du deinen CacheBackend."""
|
160
147
|
cache.delete(cache_key)
|
161
148
|
|
162
149
|
|
@@ -165,33 +152,28 @@ def capture_old_values(
|
|
165
152
|
sender: Type[GeneralManager], instance: GeneralManager | None, **kwargs
|
166
153
|
) -> None:
|
167
154
|
if instance is None:
|
168
|
-
# Wenn es kein Modell ist, gibt es nichts zu tun
|
169
155
|
return
|
170
156
|
manager_name = sender.__name__
|
171
157
|
idx = get_full_index()
|
172
|
-
#
|
158
|
+
# get all lookups for this model
|
173
159
|
lookups = set()
|
174
160
|
for action in ("filter", "exclude"):
|
175
161
|
lookups |= set(idx.get(action, {}).get(manager_name, {}))
|
176
162
|
if lookups and instance.identification:
|
177
|
-
#
|
163
|
+
# save old values for later comparison
|
178
164
|
vals = {}
|
179
165
|
for lookup in lookups:
|
180
166
|
attr_path = lookup.split("__")
|
181
167
|
obj = instance
|
182
|
-
for attr in attr_path:
|
183
|
-
|
184
|
-
|
168
|
+
for i, attr in enumerate(attr_path):
|
169
|
+
if getattr(obj, attr, UNDEFINED) is UNDEFINED:
|
170
|
+
lookup = "__".join(attr_path[:i])
|
185
171
|
break
|
172
|
+
obj = getattr(obj, attr, None)
|
186
173
|
vals[lookup] = obj
|
187
174
|
setattr(instance, "_old_values", vals)
|
188
175
|
|
189
176
|
|
190
|
-
# -----------------------------------------------------------------------------
|
191
|
-
# GENERIC CACHE INVALIDATION: vergleicht alt vs. neu und invalidiert nur bei Übergang
|
192
|
-
# -----------------------------------------------------------------------------
|
193
|
-
|
194
|
-
|
195
177
|
@receiver(post_data_change)
|
196
178
|
def generic_cache_invalidation(
|
197
179
|
sender: type[GeneralManager],
|
@@ -236,18 +218,32 @@ def generic_cache_invalidation(
|
|
236
218
|
# wildcard / regex
|
237
219
|
if op in ("contains", "startswith", "endswith", "regex"):
|
238
220
|
try:
|
239
|
-
|
240
|
-
except:
|
241
|
-
|
242
|
-
|
243
|
-
|
221
|
+
literal = ast.literal_eval(val_key)
|
222
|
+
except Exception:
|
223
|
+
literal = val_key
|
224
|
+
|
225
|
+
# ensure we always work with strings to avoid TypeErrors
|
226
|
+
text = "" if value is None else str(value)
|
227
|
+
if op == "contains":
|
228
|
+
return literal in text
|
229
|
+
if op == "startswith":
|
230
|
+
return text.startswith(literal)
|
231
|
+
if op == "endswith":
|
232
|
+
return text.endswith(literal)
|
233
|
+
# regex: val_key selbst als Pattern benutzen
|
234
|
+
if op == "regex":
|
235
|
+
try:
|
236
|
+
pattern = re.compile(val_key)
|
237
|
+
except re.error:
|
238
|
+
return False
|
239
|
+
return bool(pattern.search(text))
|
244
240
|
|
245
241
|
return False
|
246
242
|
|
247
243
|
for action in ("filter", "exclude"):
|
248
244
|
model_section = idx.get(action, {}).get(manager_name, {})
|
249
245
|
for lookup, lookup_map in model_section.items():
|
250
|
-
# 1)
|
246
|
+
# 1) get operator and attribute path
|
251
247
|
parts = lookup.split("__")
|
252
248
|
if parts[-1] in (
|
253
249
|
"gt",
|
@@ -266,8 +262,8 @@ def generic_cache_invalidation(
|
|
266
262
|
op = "eq"
|
267
263
|
attr_path = parts
|
268
264
|
|
269
|
-
# 2)
|
270
|
-
old_val = old_relevant_values.get(
|
265
|
+
# 2) get old & new value
|
266
|
+
old_val = old_relevant_values.get("__".join(attr_path))
|
271
267
|
|
272
268
|
obj = instance
|
273
269
|
for attr in attr_path:
|
@@ -276,14 +272,14 @@ def generic_cache_invalidation(
|
|
276
272
|
break
|
277
273
|
new_val = obj
|
278
274
|
|
279
|
-
# 3)
|
275
|
+
# 3) check against all cache_keys
|
280
276
|
for val_key, cache_keys in list(lookup_map.items()):
|
281
277
|
old_match = matches(op, old_val, val_key)
|
282
278
|
new_match = matches(op, new_val, val_key)
|
283
279
|
|
284
280
|
if action == "filter":
|
285
|
-
#
|
286
|
-
if new_match:
|
281
|
+
# Filter: invalidate if new match or old match
|
282
|
+
if new_match or old_match:
|
287
283
|
print(
|
288
284
|
f"Invalidate cache key {cache_keys} for filter {lookup} with value {val_key}"
|
289
285
|
)
|
@@ -292,7 +288,7 @@ def generic_cache_invalidation(
|
|
292
288
|
remove_cache_key_from_index(ck)
|
293
289
|
|
294
290
|
else: # action == 'exclude'
|
295
|
-
# Excludes:
|
291
|
+
# Excludes: invalidate only if matches changed
|
296
292
|
if old_match != new_match:
|
297
293
|
print(
|
298
294
|
f"Invalidate cache key {cache_keys} for exclude {lookup} with value {val_key}"
|
@@ -1,17 +1,16 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
from typing import TYPE_CHECKING, Type, Callable, Union, Any, TypeVar, Literal, cast
|
3
|
-
from factory.declarations import LazyFunction
|
3
|
+
from factory.declarations import LazyFunction
|
4
4
|
from factory.faker import Faker
|
5
5
|
import exrex # type: ignore
|
6
6
|
from django.db import models
|
7
7
|
from django.core.validators import RegexValidator
|
8
8
|
from factory.django import DjangoModelFactory
|
9
|
-
from django.utils import timezone
|
10
9
|
import random
|
11
10
|
from decimal import Decimal
|
12
11
|
from general_manager.measurement.measurement import Measurement
|
13
12
|
from general_manager.measurement.measurementField import MeasurementField
|
14
|
-
from datetime import date, datetime, time
|
13
|
+
from datetime import date, datetime, time, timezone
|
15
14
|
|
16
15
|
if TYPE_CHECKING:
|
17
16
|
from general_manager.interface.databaseInterface import (
|
@@ -33,7 +32,7 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
|
|
33
32
|
) = None
|
34
33
|
|
35
34
|
@classmethod
|
36
|
-
def _generate(
|
35
|
+
def _generate(
|
37
36
|
cls, strategy: Literal["build", "create"], params: dict[str, Any]
|
38
37
|
) -> models.Model | list[models.Model]:
|
39
38
|
cls._original_params = params
|
@@ -64,7 +63,7 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
|
|
64
63
|
continue # Skip fields that are already set
|
65
64
|
if isinstance(field, models.AutoField) or field.auto_created:
|
66
65
|
continue # Skip auto fields
|
67
|
-
params[field.name] =
|
66
|
+
params[field.name] = getFieldValue(field)
|
68
67
|
|
69
68
|
obj: list[models.Model] | models.Model = super()._generate(strategy, params)
|
70
69
|
if isinstance(obj, list):
|
@@ -84,7 +83,7 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
|
|
84
83
|
if field.name in attrs:
|
85
84
|
m2m_values = attrs[field.name]
|
86
85
|
else:
|
87
|
-
m2m_values =
|
86
|
+
m2m_values = getManyToManyFieldValue(field)
|
88
87
|
if m2m_values:
|
89
88
|
getattr(obj, field.name).set(m2m_values)
|
90
89
|
|
@@ -98,7 +97,7 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
|
|
98
97
|
return kwargs
|
99
98
|
|
100
99
|
@classmethod
|
101
|
-
def _create(
|
100
|
+
def _create(
|
102
101
|
cls, model_class: Type[models.Model], *args: list[Any], **kwargs: dict[str, Any]
|
103
102
|
) -> models.Model | list[models.Model]:
|
104
103
|
kwargs = cls._adjust_kwargs(**kwargs)
|
@@ -107,7 +106,7 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
|
|
107
106
|
return cls._modelCreation(model_class, **kwargs)
|
108
107
|
|
109
108
|
@classmethod
|
110
|
-
def _build(
|
109
|
+
def _build(
|
111
110
|
cls, model_class: Type[models.Model], *args: list[Any], **kwargs: dict[str, Any]
|
112
111
|
) -> models.Model | list[models.Model]:
|
113
112
|
kwargs = cls._adjust_kwargs(**kwargs)
|
@@ -157,7 +156,7 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
|
|
157
156
|
return created_objects
|
158
157
|
|
159
158
|
|
160
|
-
def
|
159
|
+
def getFieldValue(field: models.Field[Any, Any] | models.ForeignObjectRel) -> object:
|
161
160
|
"""
|
162
161
|
Returns a suitable value for a given Django model field.
|
163
162
|
"""
|
@@ -166,22 +165,12 @@ def get_field_value(field: models.Field[Any, Any] | models.ForeignObjectRel) ->
|
|
166
165
|
return None
|
167
166
|
|
168
167
|
if isinstance(field, MeasurementField):
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
regex = None
|
176
|
-
for validator in field.validators:
|
177
|
-
if isinstance(validator, RegexValidator):
|
178
|
-
regex = getattr(validator.regex, "pattern", None)
|
179
|
-
break
|
180
|
-
if regex:
|
181
|
-
# Use exrex to generate a string matching the regex
|
182
|
-
return LazyFunction(lambda: exrex.getone(regex)) # type: ignore
|
183
|
-
else:
|
184
|
-
return cast(str, Faker("text", max_nb_chars=max_length))
|
168
|
+
|
169
|
+
def _measurement():
|
170
|
+
value = Decimal(random.randrange(0, 10_000_000)) / Decimal("100") # two dp
|
171
|
+
return Measurement(value, field.base_unit)
|
172
|
+
|
173
|
+
return LazyFunction(_measurement)
|
185
174
|
elif isinstance(field, models.TextField):
|
186
175
|
return cast(str, Faker("paragraph"))
|
187
176
|
elif isinstance(field, models.IntegerField):
|
@@ -201,8 +190,6 @@ def get_field_value(field: models.Field[Any, Any] | models.ForeignObjectRel) ->
|
|
201
190
|
)
|
202
191
|
elif isinstance(field, models.FloatField):
|
203
192
|
return cast(float, Faker("pyfloat", positive=True))
|
204
|
-
elif isinstance(field, models.DateField):
|
205
|
-
return cast(date, Faker("date_between", start_date="-1y", end_date="today"))
|
206
193
|
elif isinstance(field, models.DateTimeField):
|
207
194
|
return cast(
|
208
195
|
datetime,
|
@@ -213,10 +200,11 @@ def get_field_value(field: models.Field[Any, Any] | models.ForeignObjectRel) ->
|
|
213
200
|
tzinfo=timezone.utc,
|
214
201
|
),
|
215
202
|
)
|
203
|
+
elif isinstance(field, models.DateField):
|
204
|
+
return cast(date, Faker("date_between", start_date="-1y", end_date="today"))
|
216
205
|
elif isinstance(field, models.BooleanField):
|
217
206
|
return cast(bool, Faker("pybool"))
|
218
|
-
elif isinstance(field, models.
|
219
|
-
# Create or get an instance of the related model
|
207
|
+
elif isinstance(field, models.OneToOneField):
|
220
208
|
if hasattr(field.related_model, "_general_manager_class"):
|
221
209
|
related_factory = field.related_model._general_manager_class.Factory
|
222
210
|
return related_factory()
|
@@ -229,11 +217,19 @@ def get_field_value(field: models.Field[Any, Any] | models.ForeignObjectRel) ->
|
|
229
217
|
raise ValueError(
|
230
218
|
f"No factory found for {field.related_model.__name__} and no instances found"
|
231
219
|
)
|
232
|
-
elif isinstance(field, models.
|
233
|
-
#
|
220
|
+
elif isinstance(field, models.ForeignKey):
|
221
|
+
# Create or get an instance of the related model
|
234
222
|
if hasattr(field.related_model, "_general_manager_class"):
|
223
|
+
create_a_new_instance = random.choice([True, True, False])
|
224
|
+
if not create_a_new_instance:
|
225
|
+
existing_instances = list(field.related_model.objects.all())
|
226
|
+
if existing_instances:
|
227
|
+
# Pick a random existing instance
|
228
|
+
return LazyFunction(lambda: random.choice(existing_instances))
|
229
|
+
|
235
230
|
related_factory = field.related_model._general_manager_class.Factory
|
236
231
|
return related_factory()
|
232
|
+
|
237
233
|
else:
|
238
234
|
# If no factory exists, pick a random existing instance
|
239
235
|
related_instances = list(field.related_model.objects.all())
|
@@ -253,35 +249,56 @@ def get_field_value(field: models.Field[Any, Any] | models.ForeignObjectRel) ->
|
|
253
249
|
return cast(str, Faker("uuid4"))
|
254
250
|
elif isinstance(field, models.DurationField):
|
255
251
|
return cast(time, Faker("time_delta"))
|
252
|
+
elif isinstance(field, models.CharField):
|
253
|
+
max_length = field.max_length or 100
|
254
|
+
# Check for RegexValidator
|
255
|
+
regex = None
|
256
|
+
for validator in field.validators:
|
257
|
+
if isinstance(validator, RegexValidator):
|
258
|
+
regex = getattr(validator.regex, "pattern", None)
|
259
|
+
break
|
260
|
+
if regex:
|
261
|
+
# Use exrex to generate a string matching the regex
|
262
|
+
return LazyFunction(lambda: exrex.getone(regex)) # type: ignore
|
263
|
+
else:
|
264
|
+
return cast(str, Faker("text", max_nb_chars=max_length))
|
256
265
|
else:
|
257
266
|
return None # For unsupported field types
|
258
267
|
|
259
268
|
|
260
|
-
def
|
269
|
+
def getManyToManyFieldValue(
|
270
|
+
field: models.ManyToManyField,
|
271
|
+
) -> list[models.Model]:
|
261
272
|
"""
|
262
273
|
Returns a list of instances for a ManyToMany field.
|
263
274
|
"""
|
264
|
-
related_factory =
|
265
|
-
|
275
|
+
related_factory = None
|
276
|
+
related_instances = list(field.related_model.objects.all())
|
277
|
+
if hasattr(field.related_model, "_general_manager_class"):
|
278
|
+
related_factory = field.related_model._general_manager_class.Factory
|
266
279
|
|
267
|
-
if
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
280
|
+
min_required = 0 if field.blank else 1
|
281
|
+
number_of_instances = random.randint(min_required, 10)
|
282
|
+
if related_factory and related_instances:
|
283
|
+
number_to_create = random.randint(min_required, number_of_instances)
|
284
|
+
number_to_pick = number_of_instances - number_to_create
|
285
|
+
if number_to_pick > len(related_instances):
|
286
|
+
number_to_pick = len(related_instances)
|
287
|
+
existing_instances = random.sample(related_instances, number_to_pick)
|
288
|
+
new_instances = [related_factory() for _ in range(number_to_create)]
|
289
|
+
return existing_instances + new_instances
|
290
|
+
elif related_factory:
|
291
|
+
number_to_create = number_of_instances
|
292
|
+
new_instances = [related_factory() for _ in range(number_to_create)]
|
293
|
+
return new_instances
|
294
|
+
elif related_instances:
|
295
|
+
number_to_create = 0
|
296
|
+
number_to_pick = number_of_instances
|
297
|
+
if number_to_pick > len(related_instances):
|
298
|
+
number_to_pick = len(related_instances)
|
299
|
+
existing_instances = random.sample(related_instances, number_to_pick)
|
300
|
+
return existing_instances
|
278
301
|
else:
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
num_instances = random.randint(0, max_instances)
|
283
|
-
return random.sample(existing_instances, num_instances)
|
284
|
-
else:
|
285
|
-
raise ValueError(
|
286
|
-
f"No factory found for {field.related_model.__name__} and no instances found"
|
287
|
-
)
|
302
|
+
raise ValueError(
|
303
|
+
f"No factory found for {field.related_model.__name__} and no instances found"
|
304
|
+
)
|
@@ -0,0 +1,109 @@
|
|
1
|
+
from typing import Any, Optional
|
2
|
+
from factory.declarations import LazyFunction, LazyAttribute, LazyAttributeSequence
|
3
|
+
import random
|
4
|
+
from general_manager.measurement.measurement import Measurement
|
5
|
+
from datetime import timedelta, date, datetime
|
6
|
+
from faker import Faker
|
7
|
+
import uuid
|
8
|
+
from decimal import Decimal
|
9
|
+
|
10
|
+
fake = Faker()
|
11
|
+
|
12
|
+
|
13
|
+
def LazyMeasurement(
|
14
|
+
min_value: int | float, max_value: int | float, unit: str
|
15
|
+
) -> LazyFunction:
|
16
|
+
return LazyFunction(
|
17
|
+
lambda: Measurement(str(random.uniform(min_value, max_value))[:10], unit)
|
18
|
+
)
|
19
|
+
|
20
|
+
|
21
|
+
def LazyDeltaDate(avg_delta_days: int, base_attribute: str) -> LazyAttribute:
|
22
|
+
return LazyAttribute(
|
23
|
+
lambda obj: (getattr(obj, base_attribute) or date.today())
|
24
|
+
+ timedelta(days=random.randint(avg_delta_days // 2, avg_delta_days * 3 // 2))
|
25
|
+
)
|
26
|
+
|
27
|
+
|
28
|
+
def LazyProjectName() -> LazyFunction:
|
29
|
+
return LazyFunction(
|
30
|
+
lambda: (
|
31
|
+
f"{fake.word().capitalize()} "
|
32
|
+
f"{fake.word().capitalize()} "
|
33
|
+
f"{fake.random_element(elements=('X', 'Z', 'G'))}"
|
34
|
+
f"-{fake.random_int(min=1, max=1000)}"
|
35
|
+
)
|
36
|
+
)
|
37
|
+
|
38
|
+
|
39
|
+
def LazyDateToday() -> LazyFunction:
|
40
|
+
return LazyFunction(lambda: date.today())
|
41
|
+
|
42
|
+
|
43
|
+
def LazyDateBetween(start_date: date, end_date: date) -> LazyAttribute:
|
44
|
+
delta = (end_date - start_date).days
|
45
|
+
return LazyAttribute(
|
46
|
+
lambda obj: start_date + timedelta(days=random.randint(0, delta))
|
47
|
+
)
|
48
|
+
|
49
|
+
|
50
|
+
def LazyDateTimeBetween(start: datetime, end: datetime) -> LazyAttribute:
|
51
|
+
span = (end - start).total_seconds()
|
52
|
+
return LazyAttribute(
|
53
|
+
lambda obj: start + timedelta(seconds=random.randint(0, int(span)))
|
54
|
+
)
|
55
|
+
|
56
|
+
|
57
|
+
def LazyInteger(min_value: int, max_value: int) -> LazyFunction:
|
58
|
+
return LazyFunction(lambda: random.randint(min_value, max_value))
|
59
|
+
|
60
|
+
|
61
|
+
def LazyDecimal(min_value: float, max_value: float, precision: int = 2) -> LazyFunction:
|
62
|
+
fmt = f"{{:.{precision}f}}"
|
63
|
+
return LazyFunction(
|
64
|
+
lambda: Decimal(fmt.format(random.uniform(min_value, max_value)))
|
65
|
+
)
|
66
|
+
|
67
|
+
|
68
|
+
def LazyChoice(options: list[Any]) -> LazyFunction:
|
69
|
+
return LazyFunction(lambda: random.choice(options))
|
70
|
+
|
71
|
+
|
72
|
+
def LazySequence(start: int = 0, step: int = 1) -> LazyAttributeSequence:
|
73
|
+
return LazyAttributeSequence(lambda obj, n: start + n * step)
|
74
|
+
|
75
|
+
|
76
|
+
def LazyBoolean(trues_ratio: float = 0.5) -> LazyFunction:
|
77
|
+
return LazyFunction(lambda: random.random() < trues_ratio)
|
78
|
+
|
79
|
+
|
80
|
+
def LazyUUID() -> LazyFunction:
|
81
|
+
return LazyFunction(lambda: str(uuid.uuid4()))
|
82
|
+
|
83
|
+
|
84
|
+
def LazyFakerName() -> LazyFunction:
|
85
|
+
return LazyFunction(lambda: fake.name())
|
86
|
+
|
87
|
+
|
88
|
+
def LazyFakerEmail(
|
89
|
+
name: Optional[str] = None, domain: Optional[str] = None
|
90
|
+
) -> LazyFunction:
|
91
|
+
if not name and not domain:
|
92
|
+
return LazyFunction(lambda: fake.email(domain=domain))
|
93
|
+
if not name:
|
94
|
+
name = fake.name()
|
95
|
+
if not domain:
|
96
|
+
domain = fake.domain_name()
|
97
|
+
return LazyFunction(lambda: name.replace(" ", "_") + "@" + domain)
|
98
|
+
|
99
|
+
|
100
|
+
def LazyFakerSentence(number_of_words: int = 6) -> LazyFunction:
|
101
|
+
return LazyFunction(lambda: fake.sentence(nb_words=number_of_words))
|
102
|
+
|
103
|
+
|
104
|
+
def LazyFakerAddress() -> LazyFunction:
|
105
|
+
return LazyFunction(lambda: fake.address())
|
106
|
+
|
107
|
+
|
108
|
+
def LazyFakerUrl() -> LazyFunction:
|
109
|
+
return LazyFunction(lambda: fake.url())
|
@@ -46,6 +46,14 @@ class Measurement:
|
|
46
46
|
def quantity(self) -> pint.Quantity:
|
47
47
|
return self.__quantity
|
48
48
|
|
49
|
+
@property
|
50
|
+
def magnitude(self) -> Decimal:
|
51
|
+
return self.__quantity.magnitude
|
52
|
+
|
53
|
+
@property
|
54
|
+
def unit(self) -> str:
|
55
|
+
return str(self.__quantity.units)
|
56
|
+
|
49
57
|
@classmethod
|
50
58
|
def from_string(cls, value: str) -> Measurement:
|
51
59
|
value, unit = value.split(" ")
|
@@ -12,12 +12,12 @@ general_manager/auxiliary/noneToZero.py,sha256=KfQtMQnrT6vsYST0K7lv6pVujkDcK3XL8
|
|
12
12
|
general_manager/auxiliary/pathMapping.py,sha256=nrz5owQg2a69Yig1eCXorR9U0NSw7NmBAk5OkeoHTdA,6842
|
13
13
|
general_manager/cache/cacheDecorator.py,sha256=DK2ANIJgPpMxazfMSiFrI9OuVE_7K9zlIZQRrgaC2Lw,3268
|
14
14
|
general_manager/cache/cacheTracker.py,sha256=rRw3OhBDf86hTC2Xbt1ocRgZqwu8_kXk4lczamcADFg,2955
|
15
|
-
general_manager/cache/dependencyIndex.py,sha256=
|
15
|
+
general_manager/cache/dependencyIndex.py,sha256=kEbIAzzMzKlQgplKfcMYBPZ562zCBkOBKvJusxO_iC4,10537
|
16
16
|
general_manager/cache/modelDependencyCollector.py,sha256=wS2edbZsQ1aTfRlHj02lhuasZHCc2ucRGob-E7ejuoY,2433
|
17
17
|
general_manager/cache/signals.py,sha256=ZHeXKFMN7tj9t0J-vSqf_05_NhGqEF2sZtbZO3vaRqI,1234
|
18
|
-
general_manager/factory/__init__.py,sha256=
|
19
|
-
general_manager/factory/factories.py,sha256=
|
20
|
-
general_manager/factory/
|
18
|
+
general_manager/factory/__init__.py,sha256=wbPIGyBlWBHa7aGWUd-1IUMPWUS-M6YqtPUL1iKXW8U,93
|
19
|
+
general_manager/factory/factories.py,sha256=R2_iAVRspfysDNT_cKyJuMT38RFkNbjnPR3yjAXZRkc,12188
|
20
|
+
general_manager/factory/factoryMethods.py,sha256=9Bag891j0XHe3dUBAFi7gUKcKeUwcBZN3cDLBobyBiI,3225
|
21
21
|
general_manager/interface/__init__.py,sha256=6x5adQLefTugvrJeyPcAxstyqgLAYeaJ1EPdAbac9pE,213
|
22
22
|
general_manager/interface/baseInterface.py,sha256=mvSKUlA-0fazNnaIXGBwkiZxmX8DM_sOn-SaAIpaW8I,10273
|
23
23
|
general_manager/interface/calculationInterface.py,sha256=GzSNXjU6Z7bFz60gHyMKkI5xNUDIPuniV8wbyVtQT50,14250
|
@@ -28,7 +28,7 @@ general_manager/manager/groupManager.py,sha256=O4FABqbm7KlZw6t36Ot3HU1FsBYN0h6Zh
|
|
28
28
|
general_manager/manager/input.py,sha256=iKawV3P1QICz-0AQUF00OvH7LZYxussg3svpvCUl8hE,2977
|
29
29
|
general_manager/manager/meta.py,sha256=5wHrCVnua5c38vpVZSCesrNvgydQDH8h6pxW6_QgCDg,3107
|
30
30
|
general_manager/measurement/__init__.py,sha256=X97meFujBldE5v0WMF7SmKeGpC5R0JTczfLo_Lq1Xek,84
|
31
|
-
general_manager/measurement/measurement.py,sha256=
|
31
|
+
general_manager/measurement/measurement.py,sha256=IMUY8vmoEIrclMW9FWLgL2Wi61rH1J0NMSmjYdMkMHM,9516
|
32
32
|
general_manager/measurement/measurementField.py,sha256=iq9Hqe6ZGX8CxXm4nIqTAWTRkQVptzpqE9ExX-jFyNs,5928
|
33
33
|
general_manager/permission/__init__.py,sha256=5UlDERN60Vn8obGVkT-cOM8kHjzmoxgK5w5FgTCDhGE,59
|
34
34
|
general_manager/permission/basePermission.py,sha256=PsJiX-UNeSh6xUlcUwuQNCLYvHZipWUJ0kAoMkdxXJc,6113
|
@@ -39,8 +39,8 @@ general_manager/permission/permissionDataManager.py,sha256=Ji7fsnuaKTa6M8yzCGyzr
|
|
39
39
|
general_manager/rule/__init__.py,sha256=4Har5cfPD1fmOsilTDod-ZUz3Com-tkl58jz7yY4fD0,23
|
40
40
|
general_manager/rule/handler.py,sha256=z8SFHTIZ0LbLh3fV56Mud0V4_OvWkqJjlHvFqau7Qfk,7334
|
41
41
|
general_manager/rule/rule.py,sha256=3FVCKGL7BTVoStdgOTdWQwuoVRIxAIAilV4VOzouDpc,10759
|
42
|
-
generalmanager-0.
|
43
|
-
generalmanager-0.
|
44
|
-
generalmanager-0.
|
45
|
-
generalmanager-0.
|
46
|
-
generalmanager-0.
|
42
|
+
generalmanager-0.4.0.dist-info/licenses/LICENSE,sha256=YGFm0ieb4KpkMRRt2qnWue6uFh0cUMtobwEBkHwajhc,1450
|
43
|
+
generalmanager-0.4.0.dist-info/METADATA,sha256=hA9V_hws-DSJegQQBxWgjhOnrDxIdeEJq_f7gHeOzPo,8188
|
44
|
+
generalmanager-0.4.0.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
45
|
+
generalmanager-0.4.0.dist-info/top_level.txt,sha256=sTDtExP9ga-YP3h3h42yivUY-A2Q23C2nw6LNKOho4I,16
|
46
|
+
generalmanager-0.4.0.dist-info/RECORD,,
|
@@ -1,38 +0,0 @@
|
|
1
|
-
from typing import Any
|
2
|
-
from factory.declarations import LazyFunction, LazyAttribute, LazyAttributeSequence
|
3
|
-
import random
|
4
|
-
from general_manager.measurement.measurement import Measurement
|
5
|
-
from datetime import timedelta, date
|
6
|
-
from faker import Faker
|
7
|
-
|
8
|
-
fake = Faker()
|
9
|
-
|
10
|
-
|
11
|
-
def LazyMeasurement(
|
12
|
-
min_value: int | float, max_value: int | float, unit: str
|
13
|
-
) -> LazyFunction:
|
14
|
-
return LazyFunction(
|
15
|
-
lambda: Measurement(str(random.uniform(min_value, max_value))[:10], unit)
|
16
|
-
)
|
17
|
-
|
18
|
-
|
19
|
-
def LazyDeltaDate(avg_delta_days: int, base_attribute: str) -> LazyAttribute:
|
20
|
-
return LazyAttribute(
|
21
|
-
lambda obj: (getattr(obj, base_attribute) or date.today())
|
22
|
-
+ timedelta(days=random.randint(avg_delta_days // 2, avg_delta_days * 3 // 2))
|
23
|
-
)
|
24
|
-
|
25
|
-
|
26
|
-
def LazyProjectName() -> LazyFunction:
|
27
|
-
return LazyFunction(
|
28
|
-
lambda: (
|
29
|
-
f"{fake.word().capitalize()} "
|
30
|
-
f"{fake.word().capitalize()} "
|
31
|
-
f"{fake.random_element(elements=('X', 'Z', 'G'))}"
|
32
|
-
f"-{fake.random_int(min=1, max=1000)}"
|
33
|
-
)
|
34
|
-
)
|
35
|
-
|
36
|
-
|
37
|
-
def LazySapNumber() -> LazyAttributeSequence:
|
38
|
-
return LazyAttributeSequence(lambda obj, n: f"60{n:04d}")
|
File without changes
|
File without changes
|