GeneralManager 0.14.0__py3-none-any.whl → 0.15.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.
Files changed (63) hide show
  1. general_manager/__init__.py +49 -0
  2. general_manager/api/__init__.py +36 -0
  3. general_manager/api/graphql.py +92 -43
  4. general_manager/api/mutation.py +35 -10
  5. general_manager/api/property.py +26 -3
  6. general_manager/apps.py +23 -16
  7. general_manager/bucket/__init__.py +32 -0
  8. general_manager/bucket/baseBucket.py +76 -64
  9. general_manager/bucket/calculationBucket.py +188 -108
  10. general_manager/bucket/databaseBucket.py +130 -49
  11. general_manager/bucket/groupBucket.py +113 -60
  12. general_manager/cache/__init__.py +38 -0
  13. general_manager/cache/cacheDecorator.py +29 -17
  14. general_manager/cache/cacheTracker.py +34 -15
  15. general_manager/cache/dependencyIndex.py +117 -33
  16. general_manager/cache/modelDependencyCollector.py +17 -8
  17. general_manager/cache/signals.py +17 -6
  18. general_manager/factory/__init__.py +34 -5
  19. general_manager/factory/autoFactory.py +57 -60
  20. general_manager/factory/factories.py +39 -14
  21. general_manager/factory/factoryMethods.py +38 -1
  22. general_manager/interface/__init__.py +36 -0
  23. general_manager/interface/baseInterface.py +71 -27
  24. general_manager/interface/calculationInterface.py +18 -10
  25. general_manager/interface/databaseBasedInterface.py +102 -71
  26. general_manager/interface/databaseInterface.py +66 -20
  27. general_manager/interface/models.py +10 -4
  28. general_manager/interface/readOnlyInterface.py +44 -30
  29. general_manager/manager/__init__.py +36 -3
  30. general_manager/manager/generalManager.py +73 -47
  31. general_manager/manager/groupManager.py +72 -17
  32. general_manager/manager/input.py +23 -15
  33. general_manager/manager/meta.py +53 -53
  34. general_manager/measurement/__init__.py +37 -2
  35. general_manager/measurement/measurement.py +135 -58
  36. general_manager/measurement/measurementField.py +161 -61
  37. general_manager/permission/__init__.py +32 -1
  38. general_manager/permission/basePermission.py +29 -12
  39. general_manager/permission/managerBasedPermission.py +32 -26
  40. general_manager/permission/mutationPermission.py +32 -3
  41. general_manager/permission/permissionChecks.py +9 -1
  42. general_manager/permission/permissionDataManager.py +49 -15
  43. general_manager/permission/utils.py +14 -3
  44. general_manager/rule/__init__.py +27 -1
  45. general_manager/rule/handler.py +90 -5
  46. general_manager/rule/rule.py +40 -27
  47. general_manager/utils/__init__.py +44 -2
  48. general_manager/utils/argsToKwargs.py +17 -9
  49. general_manager/utils/filterParser.py +29 -30
  50. general_manager/utils/formatString.py +2 -0
  51. general_manager/utils/jsonEncoder.py +14 -1
  52. general_manager/utils/makeCacheKey.py +18 -12
  53. general_manager/utils/noneToZero.py +8 -6
  54. general_manager/utils/pathMapping.py +92 -29
  55. general_manager/utils/public_api.py +49 -0
  56. general_manager/utils/testing.py +135 -69
  57. {generalmanager-0.14.0.dist-info → generalmanager-0.15.0.dist-info}/METADATA +38 -4
  58. generalmanager-0.15.0.dist-info/RECORD +62 -0
  59. generalmanager-0.15.0.dist-info/licenses/LICENSE +21 -0
  60. generalmanager-0.14.0.dist-info/RECORD +0 -58
  61. generalmanager-0.14.0.dist-info/licenses/LICENSE +0 -29
  62. {generalmanager-0.14.0.dist-info → generalmanager-0.15.0.dist-info}/WHEEL +0 -0
  63. {generalmanager-0.14.0.dist-info → generalmanager-0.15.0.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,11 @@
1
+ """Database-backed interface implementation for GeneralManager classes."""
2
+
1
3
  from __future__ import annotations
2
4
  from typing import Type, Any, Callable, TYPE_CHECKING, TypeVar, Generic, cast
3
5
  from django.db import models
4
6
 
5
- from datetime import datetime, timedelta
7
+ from datetime import datetime, date, time, timedelta
8
+ from django.utils import timezone
6
9
  from general_manager.measurement.measurement import Measurement
7
10
  from general_manager.measurement.measurementField import MeasurementField
8
11
  from decimal import Decimal
@@ -37,42 +40,64 @@ MODEL_TYPE = TypeVar("MODEL_TYPE", bound=GeneralManagerBasisModel)
37
40
 
38
41
 
39
42
  class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
43
+ """Interface implementation that persists data using Django ORM models."""
44
+
40
45
  _model: Type[MODEL_TYPE]
41
46
  input_fields: dict[str, Input] = {"id": Input(int)}
42
47
 
43
48
  def __init__(
44
49
  self,
45
- *args: list[Any],
50
+ *args: Any,
46
51
  search_date: datetime | None = None,
47
52
  **kwargs: Any,
48
- ):
53
+ ) -> None:
49
54
  """
50
- Initialize the interface and load the associated model instance by primary key.
55
+ Build the interface and hydrate the underlying model instance.
56
+
57
+ Parameters:
58
+ *args (list[Any]): Positional identification arguments forwarded to the parent interface.
59
+ search_date (datetime | None): When provided, load historical data for the given timestamp.
60
+ **kwargs (Any): Keyword identification arguments forwarded to the parent interface.
51
61
 
52
- If a `search_date` is provided, retrieves the historical record as of that date; otherwise, loads the current record.
62
+ Returns:
63
+ None
53
64
  """
54
65
  super().__init__(*args, **kwargs)
55
66
  self.pk = self.identification["id"]
56
- self._instance = self.getData(search_date)
67
+ self._instance: MODEL_TYPE = self.getData(search_date)
57
68
 
58
- def getData(self, search_date: datetime | None = None) -> GeneralManagerBasisModel:
69
+ def getData(self, search_date: datetime | None = None) -> MODEL_TYPE:
59
70
  """
60
- Retrieves the model instance by primary key, optionally as of a specified historical date.
71
+ Fetch the underlying model instance, optionally as of a historical date.
72
+
73
+ Parameters:
74
+ search_date (datetime | None): When provided, retrieve the state closest to this timestamp.
61
75
 
62
- If a `search_date` is provided and is not within the last 5 seconds, returns the historical record of the instance as of that date; otherwise, returns the current instance.
76
+ Returns:
77
+ MODEL_TYPE: Current or historical instance matching the primary key.
63
78
  """
64
79
  model = self._model
65
- instance = model.objects.get(pk=self.pk)
66
- if search_date and not search_date > datetime.now() - timedelta(seconds=5):
67
- instance = self.getHistoricalRecord(instance, search_date)
80
+ instance = cast(MODEL_TYPE, model.objects.get(pk=self.pk))
81
+ if search_date is not None:
82
+ # Normalize to aware datetime if needed
83
+ if timezone.is_naive(search_date):
84
+ search_date = timezone.make_aware(search_date)
85
+ if search_date <= timezone.now() - timedelta(seconds=5):
86
+ historical = self.getHistoricalRecord(instance, search_date)
87
+ if historical is not None:
88
+ instance = historical
68
89
  return instance
69
90
 
70
91
  @staticmethod
71
92
  def __parseKwargs(**kwargs: Any) -> dict[str, Any]:
72
93
  """
73
- Parses keyword arguments to ensure they are compatible with the model's fields.
94
+ Convert keyword arguments into ORM-friendly values.
74
95
 
75
- Converts GeneralManager instances to their primary key values and returns a dictionary of parsed arguments.
96
+ Parameters:
97
+ **kwargs (Any): Filter or update arguments potentially containing manager instances.
98
+
99
+ Returns:
100
+ dict[str, Any]: Arguments ready to be passed to Django ORM methods.
76
101
  """
77
102
  from general_manager.manager.generalManager import GeneralManager
78
103
 
@@ -89,13 +114,13 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
89
114
  @classmethod
90
115
  def filter(cls, **kwargs: Any) -> DatabaseBucket:
91
116
  """
92
- Returns a DatabaseBucket containing model instances filtered by the given criteria.
117
+ Return a bucket of model instances filtered by the provided lookups.
93
118
 
94
- Args:
95
- **kwargs: Field lookups to filter the queryset.
119
+ Parameters:
120
+ **kwargs (Any): Django-style filter expressions.
96
121
 
97
122
  Returns:
98
- A DatabaseBucket wrapping the filtered queryset and associated metadata.
123
+ DatabaseBucket: Bucket wrapping the filtered queryset.
99
124
  """
100
125
 
101
126
  kwargs = cls.__parseKwargs(**kwargs)
@@ -109,13 +134,13 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
109
134
  @classmethod
110
135
  def exclude(cls, **kwargs: Any) -> DatabaseBucket:
111
136
  """
112
- Returns a DatabaseBucket containing model instances that do not match the given filter criteria.
137
+ Return a bucket excluding model instances that match the provided lookups.
113
138
 
114
- Args:
115
- **kwargs: Field lookups to exclude from the queryset.
139
+ Parameters:
140
+ **kwargs (Any): Django-style exclusion expressions.
116
141
 
117
142
  Returns:
118
- A DatabaseBucket wrapping the queryset of excluded model instances.
143
+ DatabaseBucket: Bucket wrapping the excluded queryset.
119
144
  """
120
145
  kwargs = cls.__parseKwargs(**kwargs)
121
146
 
@@ -128,13 +153,13 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
128
153
  @staticmethod
129
154
  def __createFilterDefinitions(**kwargs: Any) -> dict[str, Any]:
130
155
  """
131
- Creates a dictionary of filter definitions from the provided keyword arguments.
156
+ Build a filter-definition mapping from Django-style kwargs.
132
157
 
133
- Args:
134
- **kwargs: Key-value pairs representing filter criteria.
158
+ Parameters:
159
+ **kwargs (Any): Filter expressions provided by the caller.
135
160
 
136
161
  Returns:
137
- A dictionary mapping filter keys to their corresponding values.
162
+ dict[str, Any]: Mapping of filter names to their values.
138
163
  """
139
164
  filter_definitions: dict[str, Any] = {}
140
165
  for key, value in kwargs.items():
@@ -143,19 +168,20 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
143
168
 
144
169
  @classmethod
145
170
  def getHistoricalRecord(
146
- cls, instance: GeneralManagerBasisModel, search_date: datetime | None = None
147
- ) -> GeneralManagerBasisModel:
171
+ cls, instance: MODEL_TYPE, search_date: datetime | None = None
172
+ ) -> MODEL_TYPE | None:
148
173
  """
149
174
  Retrieves the most recent historical record of a model instance at or before a specified date.
150
175
 
151
- Args:
152
- instance: The model instance whose history is queried.
153
- search_date: The cutoff datetime; returns the last record at or before this date.
176
+ Parameters:
177
+ instance (MODEL_TYPE): Model instance whose history is queried.
178
+ search_date (datetime | None): Cutoff datetime used to select the historical record.
154
179
 
155
180
  Returns:
156
- The historical model instance as of the specified date, or None if no such record exists.
181
+ MODEL_TYPE | None: Historical instance as of the specified date, if available.
157
182
  """
158
- return instance.history.filter(history_date__lte=search_date).last() # type: ignore
183
+ historical = instance.history.filter(history_date__lte=search_date).last() # type: ignore[attr-defined]
184
+ return cast(MODEL_TYPE | None, historical)
159
185
 
160
186
  @classmethod
161
187
  def getAttributeTypes(cls) -> dict[str, AttributeTypedDict]:
@@ -363,11 +389,13 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
363
389
  model: Type[models.Model] | models.Model,
364
390
  ) -> tuple[list[str], list[str]]:
365
391
  """
366
- Identifies custom fields on a model and their associated utils fields to ignore.
392
+ Identify custom fields on a model and related helper fields to ignore.
393
+
394
+ Parameters:
395
+ model (type[models.Model] | models.Model): Model class or instance to inspect.
367
396
 
368
397
  Returns:
369
- A tuple containing a list of custom field names and a list of related field names
370
- (typically suffixed with '_value' and '_unit') that should be ignored.
398
+ tuple[list[str], list[str]]: Names of custom fields and associated helper fields to ignore.
371
399
  """
372
400
  field_name_list: list[str] = []
373
401
  to_ignore_list: list[str] = []
@@ -381,13 +409,13 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
381
409
  @staticmethod
382
410
  def _getCustomFields(model: Type[models.Model] | models.Model) -> list[str]:
383
411
  """
384
- Return a list of custom field names defined directly as class attributes on the given Django model.
412
+ Return names of fields declared directly on the model class.
385
413
 
386
414
  Parameters:
387
- model: The Django model class or instance to inspect.
415
+ model (type[models.Model] | models.Model): Model class or instance to inspect.
388
416
 
389
417
  Returns:
390
- A list of field names for fields declared directly on the model class, excluding those defined via Django's meta system.
418
+ list[str]: Field names declared as class attributes.
391
419
  """
392
420
  return [
393
421
  field.name
@@ -397,11 +425,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
397
425
 
398
426
  @classmethod
399
427
  def __getModelFields(cls) -> list[str]:
400
- """
401
- Return a list of field names for the model that are neither many-to-many nor related fields.
402
-
403
- Fields representing many-to-many relationships or relations to other models are excluded from the result.
404
- """
428
+ """Return names of non-relational fields defined on the model."""
405
429
  return [
406
430
  field.name
407
431
  for field in cls._model._meta.get_fields()
@@ -410,9 +434,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
410
434
 
411
435
  @classmethod
412
436
  def __getForeignKeyFields(cls) -> list[str]:
413
- """
414
- Return a list of field names for all foreign key and one-to-one relations on the model, excluding generic foreign keys.
415
- """
437
+ """Return names of foreign-key and one-to-one relations on the model."""
416
438
  return [
417
439
  field.name
418
440
  for field in cls._model._meta.get_fields()
@@ -421,11 +443,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
421
443
 
422
444
  @classmethod
423
445
  def __getManyToManyFields(cls) -> list[tuple[str, str]]:
424
- """
425
- Return a list of tuples representing all many-to-many fields on the model.
426
-
427
- Each tuple contains the field name twice. Fields that are generic foreign keys are excluded.
428
- """
446
+ """Return (field_name, accessor_name) tuples for many-to-many fields."""
429
447
  return [
430
448
  (field.name, field.name)
431
449
  for field in cls._model._meta.get_fields()
@@ -434,11 +452,7 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
434
452
 
435
453
  @classmethod
436
454
  def __getReverseRelations(cls) -> list[tuple[str, str]]:
437
- """
438
- Return a list of reverse one-to-many relations for the model, excluding generic foreign keys.
439
-
440
- Each tuple contains the related field's name and its default related accessor name (e.g., `fieldname_set`).
441
- """
455
+ """Return (field_name, accessor_name) tuples for reverse one-to-many relations."""
442
456
  return [
443
457
  (field.name, f"{field.name}_set")
444
458
  for field in cls._model._meta.get_fields()
@@ -450,37 +464,37 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
450
464
  name: generalManagerClassName,
451
465
  attrs: attributes,
452
466
  interface: interfaceBaseClass,
453
- base_model_class=GeneralManagerModel,
467
+ base_model_class: type[GeneralManagerBasisModel] = GeneralManagerModel,
454
468
  ) -> tuple[attributes, interfaceBaseClass, relatedClass]:
455
- # Felder aus der Interface-Klasse sammeln
469
+ # Collect fields defined directly on the interface class
456
470
  """
457
471
  Dynamically generates a Django model class, its associated interface class, and a factory class from an interface definition.
458
472
 
459
473
  This method collects fields and metadata from the provided interface class, creates a new Django model inheriting from the specified base model class, attaches custom validation rules if present, and constructs corresponding interface and factory classes. The updated attributes dictionary, the new interface class, and the newly created model class are returned for integration into the general manager framework.
460
474
 
461
475
  Parameters:
462
- name: The name for the dynamically created model class.
463
- attrs: The attributes dictionary to be updated with the new interface and factory classes.
464
- interface: The interface base class defining the model structure and metadata.
465
- base_model_class: The base class to use for the new model (defaults to GeneralManagerModel).
476
+ name (generalManagerClassName): Name for the dynamically created model class.
477
+ attrs (attributes): Attribute dictionary updated with interface and factory definitions.
478
+ interface (interfaceBaseClass): Interface definition used to derive the model.
479
+ base_model_class (type[GeneralManagerBasisModel]): Base Django model class (defaults to GeneralManagerModel).
466
480
 
467
481
  Returns:
468
- tuple: A tuple containing the updated attributes dictionary, the new interface class, and the newly created model class.
482
+ tuple[attributes, interfaceBaseClass, relatedClass]: Updated attributes, interface class, and the generated model.
469
483
  """
470
484
  model_fields: dict[str, Any] = {}
471
485
  meta_class = None
472
486
  for attr_name, attr_value in interface.__dict__.items():
473
487
  if not attr_name.startswith("__"):
474
488
  if attr_name == "Meta" and isinstance(attr_value, type):
475
- # Meta-Klasse speichern
489
+ # Store the Meta class definition for later use
476
490
  meta_class = attr_value
477
491
  elif attr_name == "Factory":
478
- # Factory nicht in model_fields speichern
492
+ # Do not register the factory on the model
479
493
  pass
480
494
  else:
481
495
  model_fields[attr_name] = attr_value
482
496
  model_fields["__module__"] = attrs.get("__module__")
483
- # Meta-Klasse hinzufügen oder erstellen
497
+ # Attach the Meta class or create a default one
484
498
  rules: list[Rule] | None = None
485
499
  if meta_class:
486
500
  model_fields["Meta"] = meta_class
@@ -489,19 +503,22 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
489
503
  rules = getattr(meta_class, "rules")
490
504
  delattr(meta_class, "rules")
491
505
 
492
- # Modell erstellen
493
- model = type(name, (base_model_class,), model_fields)
506
+ # Create the concrete Django model dynamically
507
+ model = cast(
508
+ type[GeneralManagerBasisModel],
509
+ type(name, (base_model_class,), model_fields),
510
+ )
494
511
  if meta_class and rules:
495
512
  setattr(model._meta, "rules", rules)
496
513
  # full_clean Methode hinzufügen
497
- model.full_clean = getFullCleanMethode(model)
514
+ setattr(model, "full_clean", getFullCleanMethode(model))
498
515
  # Interface-Typ bestimmen
499
516
  attrs["_interface_type"] = interface._interface_type
500
517
  interface_cls = type(interface.__name__, (interface,), {})
501
518
  setattr(interface_cls, "_model", model)
502
519
  attrs["Interface"] = interface_cls
503
520
 
504
- # add factory class
521
+ # Build the associated factory class
505
522
  factory_definition = getattr(interface, "Factory", None)
506
523
  factory_attributes: dict[str, Any] = {}
507
524
  if factory_definition:
@@ -526,6 +543,11 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
526
543
  Finalizes the setup of dynamically created classes by linking the interface and model to the new general manager class.
527
544
 
528
545
  This method sets the `_parent_class` attribute on the interface class and attaches the new general manager class to the model via the `_general_manager_class` attribute.
546
+
547
+ Parameters:
548
+ new_class (newlyCreatedGeneralManagerClass): Generated GeneralManager subclass.
549
+ interface_class (newlyCreatedInterfaceClass): Concrete interface class created for the model.
550
+ model (relatedClass): Django model linked to the manager.
529
551
  """
530
552
  interface_class._parent_class = new_class
531
553
  setattr(model, "_general_manager_class", new_class)
@@ -538,6 +560,9 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
538
560
  Returns the pre- and post-creation hooks for initializing the interface.
539
561
 
540
562
  The pre-creation method is called before the GeneralManager class is created to allow customization, while the post-creation method is called after creation to finalize setup.
563
+
564
+ Returns:
565
+ tuple[classPreCreationMethod, classPostCreationMethod]: Hooks used during manager class creation.
541
566
  """
542
567
  return cls._preCreate, cls._postCreate
543
568
 
@@ -547,6 +572,12 @@ class DBBasedInterface(InterfaceBase, Generic[MODEL_TYPE]):
547
572
  Return the type associated with a given model field name.
548
573
 
549
574
  If the field is a relation and its related model has a `_general_manager_class` attribute, that class is returned; otherwise, returns the Django field type.
575
+
576
+ Parameters:
577
+ field_name (str): Name of the model field.
578
+
579
+ Returns:
580
+ type: Type or GeneralManager class representing the field.
550
581
  """
551
582
  field = cls._model._meta.get_field(field_name)
552
583
  if (
@@ -1,3 +1,5 @@
1
+ """Concrete interface providing CRUD operations via Django ORM."""
2
+
1
3
  from __future__ import annotations
2
4
  from typing import (
3
5
  Type,
@@ -13,6 +15,8 @@ from django.db.models import NOT_PROVIDED
13
15
 
14
16
 
15
17
  class DatabaseInterface(DBBasedInterface[GeneralManagerModel]):
18
+ """CRUD-capable interface backed by a concrete Django model."""
19
+
16
20
  _interface_type = "database"
17
21
 
18
22
  @classmethod
@@ -20,16 +24,19 @@ class DatabaseInterface(DBBasedInterface[GeneralManagerModel]):
20
24
  cls, creator_id: int | None, history_comment: str | None = None, **kwargs: Any
21
25
  ) -> int:
22
26
  """
23
- Create a new model instance with the provided attributes and optional history tracking.
24
-
25
- Validates input attributes, separates and sets many-to-many relationships, saves the instance with optional creator and history comment, and returns the primary key of the created instance.
27
+ Create a new model instance and return its primary key.
26
28
 
27
29
  Parameters:
28
- creator_id (int | None): The ID of the user creating the instance, or None if not applicable.
29
- history_comment (str | None): Optional comment to record in the instance's history.
30
+ creator_id (int | None): Identifier of the user creating the instance.
31
+ history_comment (str | None): Optional comment stored in the history log.
32
+ **kwargs (Any): Field values used to populate the model.
30
33
 
31
34
  Returns:
32
- int: The primary key of the newly created instance.
35
+ int: Primary key of the newly created instance.
36
+
37
+ Raises:
38
+ ValueError: If unknown fields are supplied.
39
+ ValidationError: Propagated when model validation fails.
33
40
  """
34
41
  cls._checkForInvalidKwargs(cls._model, kwargs=kwargs)
35
42
  kwargs, many_to_many_kwargs = cls._sortKwargs(cls._model, kwargs)
@@ -42,14 +49,19 @@ class DatabaseInterface(DBBasedInterface[GeneralManagerModel]):
42
49
  self, creator_id: int | None, history_comment: str | None = None, **kwargs: Any
43
50
  ) -> int:
44
51
  """
45
- Update the current model instance with new attribute values and many-to-many relationships, saving changes with optional history tracking.
52
+ Update the current model instance and return its primary key.
46
53
 
47
54
  Parameters:
48
- creator_id (int | None): The ID of the user making the update, or None if not specified.
49
- history_comment (str | None): An optional comment describing the reason for the update.
55
+ creator_id (int | None): Identifier of the user performing the update.
56
+ history_comment (str | None): Optional comment stored in the history log.
57
+ **kwargs (Any): Field updates applied to the model.
50
58
 
51
59
  Returns:
52
- int: The primary key of the updated instance.
60
+ int: Primary key of the updated instance.
61
+
62
+ Raises:
63
+ ValueError: If unknown fields are supplied.
64
+ ValidationError: Propagated when model validation fails.
53
65
  """
54
66
  self._checkForInvalidKwargs(self._model, kwargs=kwargs)
55
67
  kwargs, many_to_many_kwargs = self._sortKwargs(self._model, kwargs)
@@ -62,14 +74,14 @@ class DatabaseInterface(DBBasedInterface[GeneralManagerModel]):
62
74
  self, creator_id: int | None, history_comment: str | None = None
63
75
  ) -> int:
64
76
  """
65
- Deactivate the current model instance by setting its `is_active` flag to `False` and recording the change with an optional history comment.
77
+ Mark the current model instance as inactive and record the change.
66
78
 
67
79
  Parameters:
68
- creator_id (int | None): The ID of the user performing the deactivation, or None if not specified.
69
- history_comment (str | None): An optional comment to include in the instance's history log.
80
+ creator_id (int | None): Identifier of the user performing the action.
81
+ history_comment (str | None): Optional comment stored in the history log.
70
82
 
71
83
  Returns:
72
- int: The primary key of the deactivated instance.
84
+ int: Primary key of the deactivated instance.
73
85
  """
74
86
  instance = self._model.objects.get(pk=self.pk)
75
87
  instance.is_active = False
@@ -84,12 +96,14 @@ class DatabaseInterface(DBBasedInterface[GeneralManagerModel]):
84
96
  instance: GeneralManagerModel, many_to_many_kwargs: dict[str, list[Any]]
85
97
  ) -> GeneralManagerModel:
86
98
  """
87
- Set many-to-many relationship fields on a model instance using the provided values.
88
-
89
- For each field, converts lists of `GeneralManager` instances to their IDs if necessary, and updates the corresponding many-to-many relationship on the instance. Fields with values of `None` or `NOT_PROVIDED` are ignored.
90
-
99
+ Set many-to-many relationship values on the provided instance.
100
+
101
+ Parameters:
102
+ instance (GeneralManagerModel): Model instance whose relations are updated.
103
+ many_to_many_kwargs (dict[str, list[Any]]): Mapping of relation names to values.
104
+
91
105
  Returns:
92
- GeneralManagerModel: The updated model instance with many-to-many relationships set.
106
+ GeneralManagerModel: Updated instance.
93
107
  """
94
108
  from general_manager.manager.generalManager import GeneralManager
95
109
 
@@ -113,6 +127,16 @@ class DatabaseInterface(DBBasedInterface[GeneralManagerModel]):
113
127
  instance: GeneralManagerModel,
114
128
  kwargs: dict[str, Any],
115
129
  ) -> GeneralManagerModel:
130
+ """
131
+ Populate non-relational fields on the instance before saving.
132
+
133
+ Parameters:
134
+ instance (GeneralManagerModel): Model instance that will receive the values.
135
+ kwargs (dict[str, Any]): Key-value pairs to assign to the instance.
136
+
137
+ Returns:
138
+ GeneralManagerModel: Instance with updated attributes.
139
+ """
116
140
  from general_manager.manager.generalManager import GeneralManager
117
141
 
118
142
  for key, value in kwargs.items():
@@ -130,7 +154,19 @@ class DatabaseInterface(DBBasedInterface[GeneralManagerModel]):
130
154
  return instance
131
155
 
132
156
  @staticmethod
133
- def _checkForInvalidKwargs(model: Type[models.Model], kwargs: dict[str, Any]):
157
+ def _checkForInvalidKwargs(
158
+ model: Type[models.Model], kwargs: dict[str, Any]
159
+ ) -> None:
160
+ """
161
+ Ensure provided keyword arguments map to known fields or attributes.
162
+
163
+ Parameters:
164
+ model (type[models.Model]): Django model being validated.
165
+ kwargs (dict[str, Any]): Keyword arguments supplied by the caller.
166
+
167
+ Raises:
168
+ ValueError: If an unknown field name is encountered.
169
+ """
134
170
  attributes = vars(model)
135
171
  field_names = {f.name for f in model._meta.get_fields()}
136
172
  for key in kwargs:
@@ -142,6 +178,16 @@ class DatabaseInterface(DBBasedInterface[GeneralManagerModel]):
142
178
  def _sortKwargs(
143
179
  model: Type[models.Model], kwargs: dict[Any, Any]
144
180
  ) -> tuple[dict[str, Any], dict[str, list[Any]]]:
181
+ """
182
+ Split keyword arguments into simple fields and many-to-many relations.
183
+
184
+ Parameters:
185
+ model (type[models.Model]): Model whose relation metadata is inspected.
186
+ kwargs (dict[Any, Any]): Keyword arguments supplied by the caller.
187
+
188
+ Returns:
189
+ tuple[dict[str, Any], dict[str, list[Any]]]: Tuple containing simple-field kwargs and many-to-many kwargs.
190
+ """
145
191
  many_to_many_fields = [field.name for field in model._meta.many_to_many]
146
192
  many_to_many_kwargs: dict[Any, Any] = {}
147
193
  for key, value in list(kwargs.items()):
@@ -1,6 +1,9 @@
1
+ """Django model mixins and helpers backing GeneralManager interfaces."""
2
+
1
3
  from __future__ import annotations
2
4
  from typing import Type, ClassVar, Any, Callable, TYPE_CHECKING, TypeVar
3
5
  from django.db import models
6
+ from django.contrib.auth.models import AbstractUser
4
7
  from django.conf import settings
5
8
  from simple_history.models import HistoricalRecords # type: ignore
6
9
  from django.core.exceptions import ValidationError
@@ -8,7 +11,6 @@ from django.core.exceptions import ValidationError
8
11
 
9
12
  if TYPE_CHECKING:
10
13
  from general_manager.manager.generalManager import GeneralManager
11
- from django.contrib.auth.models import AbstractUser
12
14
  from general_manager.rule.rule import Rule
13
15
 
14
16
  modelsModel = TypeVar("modelsModel", bound=models.Model)
@@ -27,7 +29,7 @@ def getFullCleanMethode(model: Type[models.Model]) -> Callable[..., None]:
27
29
  Callable[..., None]: A `full_clean` method that can be assigned to the model class.
28
30
  """
29
31
 
30
- def full_clean(self: models.Model, *args: Any, **kwargs: Any):
32
+ def full_clean(self: models.Model, *args: Any, **kwargs: Any) -> None:
31
33
  """
32
34
  Performs full validation on the model instance, including both standard Django validation and custom rule-based checks.
33
35
 
@@ -53,6 +55,8 @@ def getFullCleanMethode(model: Type[models.Model]) -> Callable[..., None]:
53
55
 
54
56
 
55
57
  class GeneralManagerBasisModel(models.Model):
58
+ """Abstract base model providing shared fields for GeneralManager storage."""
59
+
56
60
  _general_manager_class: ClassVar[Type[GeneralManager]]
57
61
  is_active = models.BooleanField(default=True)
58
62
  history = HistoricalRecords(inherit=True)
@@ -62,6 +66,8 @@ class GeneralManagerBasisModel(models.Model):
62
66
 
63
67
 
64
68
  class GeneralManagerModel(GeneralManagerBasisModel):
69
+ """Abstract model adding change-tracking metadata for writeable managers."""
70
+
65
71
  changed_by = models.ForeignKey(
66
72
  settings.AUTH_USER_MODEL, on_delete=models.PROTECT, null=True, blank=True
67
73
  )
@@ -75,14 +81,14 @@ class GeneralManagerModel(GeneralManagerBasisModel):
75
81
  return self.changed_by
76
82
 
77
83
  @_history_user.setter
78
- def _history_user(self, value: AbstractUser) -> None:
84
+ def _history_user(self, value: AbstractUser | None) -> None:
79
85
  """
80
86
  Set the user responsible for the most recent change to the model instance.
81
87
 
82
88
  Parameters:
83
89
  value (AbstractUser): The user to associate with the latest modification.
84
90
  """
85
- self.changed_by = value
91
+ setattr(self, "changed_by", value)
86
92
 
87
93
  class Meta: # type: ignore
88
94
  abstract = True