GeneralManager 0.20.0__tar.gz → 0.20.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.

Potentially problematic release.


This version of GeneralManager might be problematic. Click here for more details.

Files changed (86) hide show
  1. {generalmanager-0.20.0/src/GeneralManager.egg-info → generalmanager-0.20.1}/PKG-INFO +1 -1
  2. {generalmanager-0.20.0 → generalmanager-0.20.1}/pyproject.toml +1 -1
  3. {generalmanager-0.20.0 → generalmanager-0.20.1/src/GeneralManager.egg-info}/PKG-INFO +1 -1
  4. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/interface/read_only_interface.py +31 -14
  5. {generalmanager-0.20.0 → generalmanager-0.20.1}/LICENSE +0 -0
  6. {generalmanager-0.20.0 → generalmanager-0.20.1}/README.md +0 -0
  7. {generalmanager-0.20.0 → generalmanager-0.20.1}/setup.cfg +0 -0
  8. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/GeneralManager.egg-info/SOURCES.txt +0 -0
  9. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/GeneralManager.egg-info/dependency_links.txt +0 -0
  10. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/GeneralManager.egg-info/requires.txt +0 -0
  11. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/GeneralManager.egg-info/top_level.txt +0 -0
  12. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/__init__.py +0 -0
  13. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/_types/__init__.py +0 -0
  14. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/_types/api.py +0 -0
  15. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/_types/bucket.py +0 -0
  16. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/_types/cache.py +0 -0
  17. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/_types/factory.py +0 -0
  18. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/_types/general_manager.py +0 -0
  19. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/_types/interface.py +0 -0
  20. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/_types/manager.py +0 -0
  21. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/_types/measurement.py +0 -0
  22. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/_types/permission.py +0 -0
  23. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/_types/rule.py +0 -0
  24. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/_types/utils.py +0 -0
  25. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/api/__init__.py +0 -0
  26. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/api/graphql.py +0 -0
  27. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/api/graphql_subscription_consumer.py +0 -0
  28. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/api/mutation.py +0 -0
  29. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/api/property.py +0 -0
  30. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/apps.py +0 -0
  31. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/bucket/__init__.py +0 -0
  32. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/bucket/base_bucket.py +0 -0
  33. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/bucket/calculation_bucket.py +0 -0
  34. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/bucket/database_bucket.py +0 -0
  35. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/bucket/group_bucket.py +0 -0
  36. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/cache/__init__.py +0 -0
  37. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/cache/cache_decorator.py +0 -0
  38. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/cache/cache_tracker.py +0 -0
  39. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/cache/dependency_index.py +0 -0
  40. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/cache/model_dependency_collector.py +0 -0
  41. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/cache/signals.py +0 -0
  42. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/factory/__init__.py +0 -0
  43. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/factory/auto_factory.py +0 -0
  44. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/factory/factories.py +0 -0
  45. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/factory/factory_methods.py +0 -0
  46. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/interface/__init__.py +0 -0
  47. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/interface/base_interface.py +0 -0
  48. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/interface/calculation_interface.py +0 -0
  49. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/interface/database_based_interface.py +0 -0
  50. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/interface/database_interface.py +0 -0
  51. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/interface/models.py +0 -0
  52. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/logging.py +0 -0
  53. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/manager/__init__.py +0 -0
  54. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/manager/general_manager.py +0 -0
  55. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/manager/group_manager.py +0 -0
  56. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/manager/input.py +0 -0
  57. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/manager/meta.py +0 -0
  58. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/measurement/__init__.py +0 -0
  59. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/measurement/measurement.py +0 -0
  60. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/measurement/measurement_field.py +0 -0
  61. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/permission/__init__.py +0 -0
  62. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/permission/base_permission.py +0 -0
  63. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/permission/file_based_permission.py +0 -0
  64. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/permission/manager_based_permission.py +0 -0
  65. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/permission/mutation_permission.py +0 -0
  66. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/permission/permission_checks.py +0 -0
  67. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/permission/permission_data_manager.py +0 -0
  68. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/permission/utils.py +0 -0
  69. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/public_api_registry.py +0 -0
  70. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/py.typed +0 -0
  71. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/rule/__init__.py +0 -0
  72. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/rule/handler.py +0 -0
  73. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/rule/rule.py +0 -0
  74. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/utils/__init__.py +0 -0
  75. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/utils/args_to_kwargs.py +0 -0
  76. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/utils/filter_parser.py +0 -0
  77. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/utils/format_string.py +0 -0
  78. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/utils/json_encoder.py +0 -0
  79. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/utils/make_cache_key.py +0 -0
  80. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/utils/none_to_zero.py +0 -0
  81. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/utils/path_mapping.py +0 -0
  82. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/utils/public_api.py +0 -0
  83. {generalmanager-0.20.0 → generalmanager-0.20.1}/src/general_manager/utils/testing.py +0 -0
  84. {generalmanager-0.20.0 → generalmanager-0.20.1}/tests/test_settings.py +0 -0
  85. {generalmanager-0.20.0 → generalmanager-0.20.1}/tests/test_urls.py +0 -0
  86. {generalmanager-0.20.0 → generalmanager-0.20.1}/tests/testing_asgi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GeneralManager
3
- Version: 0.20.0
3
+ Version: 0.20.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: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "GeneralManager"
7
- version = "0.20.0"
7
+ version = "0.20.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" }]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GeneralManager
3
- Version: 0.20.0
3
+ Version: 0.20.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: MIT License
@@ -6,7 +6,7 @@ import json
6
6
  from typing import TYPE_CHECKING, Any, Callable, ClassVar, Type, cast
7
7
 
8
8
  from django.core.checks import Warning
9
- from django.db import connection, models, transaction
9
+ from django.db import connection, models, transaction, IntegrityError
10
10
 
11
11
  from general_manager.interface.database_based_interface import (
12
12
  DBBasedInterface,
@@ -159,6 +159,7 @@ class ReadOnlyInterface(DBBasedInterface[GeneralManagerBasisModel]):
159
159
  data_list = cast(list[dict[str, Any]], parsed_data)
160
160
 
161
161
  unique_fields = cls.get_unique_fields(model)
162
+ unique_field_order = tuple(sorted(unique_fields))
162
163
  if not unique_fields:
163
164
  raise MissingUniqueFieldError(parent_class.__name__)
164
165
 
@@ -168,29 +169,43 @@ class ReadOnlyInterface(DBBasedInterface[GeneralManagerBasisModel]):
168
169
  "deactivated": [],
169
170
  }
170
171
 
172
+ editable_fields = {
173
+ f.name
174
+ for f in model._meta.local_fields
175
+ if getattr(f, "editable", True) and not getattr(f, "primary_key", False)
176
+ } - {"is_active"}
177
+
171
178
  with transaction.atomic():
172
- json_unique_values: set[Any] = set()
179
+ json_unique_values: set[tuple[Any, ...]] = set()
173
180
 
174
181
  # data synchronization
175
182
  for idx, data in enumerate(data_list):
176
183
  try:
177
- lookup = {field: data[field] for field in unique_fields}
184
+ lookup = {field: data[field] for field in unique_field_order}
178
185
  except KeyError as e:
179
186
  missing = e.args[0]
180
187
  raise InvalidReadOnlyDataFormatError() from KeyError(
181
188
  f"Item {idx} missing unique field '{missing}'."
182
189
  )
183
- unique_identifier = tuple(lookup[field] for field in unique_fields)
190
+ unique_identifier = tuple(lookup[field] for field in unique_field_order)
184
191
  json_unique_values.add(unique_identifier)
185
-
186
- instance, is_created = model.objects.get_or_create(**lookup)
192
+ instance = model.objects.filter(**lookup).first()
193
+ is_created = False
194
+ if instance is None:
195
+ # sanitize input and create with race-safety
196
+ allowed_fields = {f.name for f in model._meta.local_fields}
197
+ create_kwargs = {
198
+ k: v for k, v in data.items() if k in allowed_fields
199
+ }
200
+ try:
201
+ instance = model.objects.create(**create_kwargs)
202
+ is_created = True
203
+ except IntegrityError:
204
+ # created concurrently — fetch it
205
+ instance = model.objects.filter(**lookup).first()
206
+ if instance is None:
207
+ raise
187
208
  updated = False
188
- editable_fields = {
189
- f.name
190
- for f in model._meta.local_fields
191
- if getattr(f, "editable", True)
192
- and not getattr(f, "primary_key", False)
193
- } - {"is_active"}
194
209
  for field_name in editable_fields.intersection(data.keys()):
195
210
  value = data[field_name]
196
211
  if getattr(instance, field_name, None) != value:
@@ -204,8 +219,10 @@ class ReadOnlyInterface(DBBasedInterface[GeneralManagerBasisModel]):
204
219
  # deactivate instances not in JSON data
205
220
  existing_instances = model.objects.filter(is_active=True)
206
221
  for instance in existing_instances:
207
- lookup = {field: getattr(instance, field) for field in unique_fields}
208
- unique_identifier = tuple(lookup[field] for field in unique_fields)
222
+ lookup = {
223
+ field: getattr(instance, field) for field in unique_field_order
224
+ }
225
+ unique_identifier = tuple(lookup[field] for field in unique_field_order)
209
226
  if unique_identifier not in json_unique_values:
210
227
  instance.is_active = False
211
228
  instance.save()
File without changes