GeneralManager 0.44.0__tar.gz → 0.46.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. {generalmanager-0.44.0/src/GeneralManager.egg-info → generalmanager-0.46.0}/PKG-INFO +1 -1
  2. {generalmanager-0.44.0 → generalmanager-0.46.0}/pyproject.toml +1 -1
  3. {generalmanager-0.44.0 → generalmanager-0.46.0/src/GeneralManager.egg-info}/PKG-INFO +1 -1
  4. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/GeneralManager.egg-info/SOURCES.txt +1 -0
  5. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/cache/cache_decorator.py +88 -13
  6. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/cache/dependency_index.py +224 -92
  7. generalmanager-0.46.0/src/general_manager/cache/dependency_publish.py +153 -0
  8. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/cache/run_context.py +6 -0
  9. generalmanager-0.46.0/src/general_manager/cache/signals.py +114 -0
  10. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/orm/mutations.py +10 -1
  11. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/orm/support.py +35 -1
  12. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/utils/testing.py +23 -4
  13. generalmanager-0.44.0/src/general_manager/cache/signals.py +0 -85
  14. {generalmanager-0.44.0 → generalmanager-0.46.0}/LICENSE +0 -0
  15. {generalmanager-0.44.0 → generalmanager-0.46.0}/README.md +0 -0
  16. {generalmanager-0.44.0 → generalmanager-0.46.0}/setup.cfg +0 -0
  17. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/GeneralManager.egg-info/dependency_links.txt +0 -0
  18. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/GeneralManager.egg-info/requires.txt +0 -0
  19. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/GeneralManager.egg-info/top_level.txt +0 -0
  20. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/__init__.py +0 -0
  21. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/_types/__init__.py +0 -0
  22. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/_types/api.py +0 -0
  23. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/_types/bucket.py +0 -0
  24. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/_types/cache.py +0 -0
  25. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/_types/factory.py +0 -0
  26. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/_types/general_manager.py +0 -0
  27. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/_types/interface.py +0 -0
  28. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/_types/manager.py +0 -0
  29. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/_types/measurement.py +0 -0
  30. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/_types/permission.py +0 -0
  31. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/_types/rule.py +0 -0
  32. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/_types/search.py +0 -0
  33. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/_types/utils.py +0 -0
  34. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/__init__.py +0 -0
  35. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/graphql.py +0 -0
  36. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/graphql_errors.py +0 -0
  37. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/graphql_mutations.py +0 -0
  38. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/graphql_resolvers.py +0 -0
  39. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/graphql_search.py +0 -0
  40. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/graphql_subscription_consumer.py +0 -0
  41. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/graphql_subscriptions.py +0 -0
  42. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/graphql_view.py +0 -0
  43. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/mutation.py +0 -0
  44. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/property.py +0 -0
  45. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/registry.py +0 -0
  46. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/remote_api.py +0 -0
  47. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/remote_invalidation.py +0 -0
  48. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/remote_invalidation_client.py +0 -0
  49. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/apps.py +0 -0
  50. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/bootstrap.py +0 -0
  51. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/bucket/__init__.py +0 -0
  52. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/bucket/base_bucket.py +0 -0
  53. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/bucket/calculation_bucket.py +0 -0
  54. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/bucket/database_bucket.py +0 -0
  55. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/bucket/group_bucket.py +0 -0
  56. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/bucket/request_bucket.py +0 -0
  57. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/cache/__init__.py +0 -0
  58. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/cache/cache_tracker.py +0 -0
  59. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/cache/model_dependency_collector.py +0 -0
  60. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/conf.py +0 -0
  61. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/factory/__init__.py +0 -0
  62. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/factory/auto_factory.py +0 -0
  63. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/factory/factories.py +0 -0
  64. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/factory/factory_methods.py +0 -0
  65. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/__init__.py +0 -0
  66. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/base_interface.py +0 -0
  67. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/bundles/__init__.py +0 -0
  68. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/bundles/calculation.py +0 -0
  69. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/bundles/database.py +0 -0
  70. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/bundles/remote_manager.py +0 -0
  71. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/bundles/request.py +0 -0
  72. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/__init__.py +0 -0
  73. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/base.py +0 -0
  74. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/builtin.py +0 -0
  75. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/calculation/__init__.py +0 -0
  76. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/calculation/_compat.py +0 -0
  77. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/calculation/lifecycle.py +0 -0
  78. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/configuration.py +0 -0
  79. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/core/observability.py +0 -0
  80. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/core/utils.py +0 -0
  81. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/exceptions.py +0 -0
  82. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/existing_model/__init__.py +0 -0
  83. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/existing_model/_compat.py +0 -0
  84. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/existing_model/resolution.py +0 -0
  85. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/factory.py +0 -0
  86. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/orm/__init__.py +0 -0
  87. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/orm/_compat.py +0 -0
  88. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/orm/history.py +0 -0
  89. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/orm/lifecycle.py +0 -0
  90. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/orm_utils/__init__.py +0 -0
  91. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/orm_utils/django_manager_utils.py +0 -0
  92. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/orm_utils/field_descriptors.py +0 -0
  93. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/orm_utils/payload_normalizer.py +0 -0
  94. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/read_only/__init__.py +0 -0
  95. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/read_only/_compat.py +0 -0
  96. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/read_only/lifecycle.py +0 -0
  97. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/read_only/management.py +0 -0
  98. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/registry.py +0 -0
  99. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/remote_manager.py +0 -0
  100. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/request/__init__.py +0 -0
  101. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/infrastructure/__init__.py +0 -0
  102. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/infrastructure/startup_hooks.py +0 -0
  103. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/infrastructure/system_checks.py +0 -0
  104. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/interfaces/__init__.py +0 -0
  105. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/interfaces/calculation.py +0 -0
  106. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/interfaces/database.py +0 -0
  107. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/interfaces/existing_model.py +0 -0
  108. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/interfaces/read_only.py +0 -0
  109. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/interfaces/remote_manager.py +0 -0
  110. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/interfaces/request.py +0 -0
  111. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/manifests/__init__.py +0 -0
  112. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/manifests/capability_builder.py +0 -0
  113. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/manifests/capability_manifest.py +0 -0
  114. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/manifests/capability_models.py +0 -0
  115. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/orm_interface.py +0 -0
  116. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/requests.py +0 -0
  117. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/utils/__init__.py +0 -0
  118. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/utils/database_interface_protocols.py +0 -0
  119. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/utils/errors.py +0 -0
  120. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/utils/models.py +0 -0
  121. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/logging.py +0 -0
  122. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/management/commands/search_index.py +0 -0
  123. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/management/commands/seed_manager_landscape.py +0 -0
  124. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/management/commands/shell.py +0 -0
  125. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/management/commands/workflow_drain_outbox.py +0 -0
  126. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/management/commands/workflow_replay_dead_letters.py +0 -0
  127. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/manager/__init__.py +0 -0
  128. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/manager/general_manager.py +0 -0
  129. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/manager/group_manager.py +0 -0
  130. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/manager/input.py +0 -0
  131. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/manager/meta.py +0 -0
  132. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/measurement/__init__.py +0 -0
  133. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/measurement/measurement.py +0 -0
  134. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/measurement/measurement_field.py +0 -0
  135. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/metrics/__init__.py +0 -0
  136. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/metrics/graphql.py +0 -0
  137. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/migrations/0001_initial.py +0 -0
  138. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/migrations/0002_workflow_outbox_scaling_indexes.py +0 -0
  139. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/migrations/0003_workflow_execution_correlation_constraint.py +0 -0
  140. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/migrations/__init__.py +0 -0
  141. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/models.py +0 -0
  142. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/permission/__init__.py +0 -0
  143. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/permission/audit.py +0 -0
  144. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/permission/base_permission.py +0 -0
  145. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/permission/graphql_capabilities.py +0 -0
  146. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/permission/manager_based_permission.py +0 -0
  147. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/permission/mutation_permission.py +0 -0
  148. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/permission/permission_checks.py +0 -0
  149. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/permission/permission_data_manager.py +0 -0
  150. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/permission/utils.py +0 -0
  151. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/public_api_registry.py +0 -0
  152. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/py.typed +0 -0
  153. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/rule/__init__.py +0 -0
  154. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/rule/handler.py +0 -0
  155. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/rule/rule.py +0 -0
  156. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/search/__init__.py +0 -0
  157. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/search/async_tasks.py +0 -0
  158. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/search/backend.py +0 -0
  159. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/search/backend_registry.py +0 -0
  160. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/search/backends/__init__.py +0 -0
  161. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/search/backends/dev.py +0 -0
  162. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/search/backends/meilisearch.py +0 -0
  163. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/search/backends/opensearch.py +0 -0
  164. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/search/backends/typesense.py +0 -0
  165. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/search/config.py +0 -0
  166. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/search/indexer.py +0 -0
  167. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/search/registry.py +0 -0
  168. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/search/utils.py +0 -0
  169. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/seeding/__init__.py +0 -0
  170. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/seeding/manager_landscape.py +0 -0
  171. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/utils/__init__.py +0 -0
  172. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/utils/args_to_kwargs.py +0 -0
  173. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/utils/filter_parser.py +0 -0
  174. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/utils/format_string.py +0 -0
  175. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/utils/json_encoder.py +0 -0
  176. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/utils/make_cache_key.py +0 -0
  177. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/utils/none_to_zero.py +0 -0
  178. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/utils/path_mapping.py +0 -0
  179. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/utils/public_api.py +0 -0
  180. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/utils/type_checks.py +0 -0
  181. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/__init__.py +0 -0
  182. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/actions.py +0 -0
  183. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/backend_registry.py +0 -0
  184. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/backends/__init__.py +0 -0
  185. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/backends/celery.py +0 -0
  186. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/backends/local.py +0 -0
  187. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/backends/n8n.py +0 -0
  188. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/config.py +0 -0
  189. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/engine.py +0 -0
  190. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/event_registry.py +0 -0
  191. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/events.py +0 -0
  192. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/models.py +0 -0
  193. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/signal_bridge.py +0 -0
  194. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/tasks.py +0 -0
  195. {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/telemetry.py +0 -0
  196. {generalmanager-0.44.0 → generalmanager-0.46.0}/tests/test_settings.py +0 -0
  197. {generalmanager-0.44.0 → generalmanager-0.46.0}/tests/test_urls.py +0 -0
  198. {generalmanager-0.44.0 → generalmanager-0.46.0}/tests/testing_asgi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GeneralManager
3
- Version: 0.44.0
3
+ Version: 0.46.0
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.44.0"
7
+ version = "0.46.0"
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.44.0
3
+ Version: 0.46.0
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
@@ -52,6 +52,7 @@ src/general_manager/cache/__init__.py
52
52
  src/general_manager/cache/cache_decorator.py
53
53
  src/general_manager/cache/cache_tracker.py
54
54
  src/general_manager/cache/dependency_index.py
55
+ src/general_manager/cache/dependency_publish.py
55
56
  src/general_manager/cache/model_dependency_collector.py
56
57
  src/general_manager/cache/run_context.py
57
58
  src/general_manager/cache/signals.py
@@ -1,12 +1,33 @@
1
1
  """Helpers for caching GeneralManager computations with dependency tracking."""
2
2
 
3
3
  from functools import wraps
4
- from typing import Any, Callable, Literal, Optional, Protocol, Set, TypeVar, cast
4
+ from typing import (
5
+ Any,
6
+ Callable,
7
+ Iterable,
8
+ Literal,
9
+ Optional,
10
+ Protocol,
11
+ Set,
12
+ TypeVar,
13
+ cast,
14
+ )
5
15
 
6
16
  from django.core.cache import cache as django_cache
7
17
 
8
18
  from general_manager.cache.cache_tracker import DependencyTracker
9
- from general_manager.cache.dependency_index import Dependency, record_dependencies
19
+ from general_manager.cache.dependency_index import (
20
+ Dependency,
21
+ get_dependency_generation,
22
+ record_dependencies,
23
+ )
24
+ from general_manager.cache.dependency_publish import (
25
+ CachePublishAborted,
26
+ acquire_compute_lease,
27
+ publish_dependency_cache_entry,
28
+ release_compute_lease,
29
+ wait_for_cached_dependency_value,
30
+ )
10
31
  from general_manager.cache.run_context import ensure_calculation_run_context
11
32
  from general_manager.cache.model_dependency_collector import ModelDependencyCollector
12
33
  from general_manager.logging import get_logger
@@ -160,32 +181,86 @@ def cached(
160
181
 
161
182
  deps_key = f"{key}:deps"
162
183
 
163
- cached_result = cache_backend.get(key, _SENTINEL)
164
- if cached_result is not _SENTINEL:
165
- # saved dependencies are added to the current tracker
184
+ def track_cached_dependencies() -> None:
166
185
  cached_deps = cache_backend.get(deps_key)
167
186
  if cached_deps:
168
187
  for class_name, operation, identifier in cached_deps:
169
188
  DependencyTracker.track(class_name, operation, identifier)
189
+
190
+ cached_result = cache_backend.get(key, _SENTINEL)
191
+ if cached_result is not _SENTINEL:
192
+ track_cached_dependencies()
170
193
  logger.debug(
171
194
  "cache hit",
172
195
  context={
173
196
  "function": func.__qualname__,
174
197
  "key": key,
175
- "dependency_count": len(cached_deps) if cached_deps else 0,
198
+ "scope": scope,
176
199
  },
177
200
  )
178
201
  return cached_result
179
202
 
180
- with DependencyTracker() as dependencies:
181
- result = func(*args, **kwargs)
182
- ModelDependencyCollector.add_args(dependencies, args, kwargs)
203
+ lease = acquire_compute_lease(key)
204
+ while lease is None:
205
+ cached_result = wait_for_cached_dependency_value(
206
+ cache_backend,
207
+ key,
208
+ sentinel=_SENTINEL,
209
+ )
210
+ if cached_result is not _SENTINEL:
211
+ track_cached_dependencies()
212
+ logger.debug(
213
+ "cache hit after waiting for dependency publish",
214
+ context={
215
+ "function": func.__qualname__,
216
+ "key": key,
217
+ "scope": scope,
218
+ },
219
+ )
220
+ return cached_result
221
+ lease = acquire_compute_lease(key)
183
222
 
184
- cache_backend.set(key, result, timeout)
185
- cache_backend.set(deps_key, dependencies, timeout)
223
+ try:
224
+ cached_result = cache_backend.get(key, _SENTINEL)
225
+ if cached_result is not _SENTINEL:
226
+ track_cached_dependencies()
227
+ return cached_result
186
228
 
187
- if dependencies and timeout is None:
188
- record_fn(key, dependencies)
229
+ started_generation = get_dependency_generation()
230
+ with DependencyTracker() as dependencies:
231
+ result = func(*args, **kwargs)
232
+ ModelDependencyCollector.add_args(dependencies, args, kwargs)
233
+
234
+ def record_many(
235
+ entries: Iterable[tuple[str, Iterable[Dependency]]],
236
+ ) -> None:
237
+ for entry_key, entry_dependencies in entries:
238
+ record_fn(entry_key, set(entry_dependencies))
239
+
240
+ try:
241
+ publish_dependency_cache_entry(
242
+ cache_key=key,
243
+ deps_key=deps_key,
244
+ result=result,
245
+ dependencies=dependencies,
246
+ cache_backend=cache_backend,
247
+ timeout=timeout,
248
+ started_generation=started_generation,
249
+ record_many_fn=(
250
+ None if record_fn is record_dependencies else record_many
251
+ ),
252
+ )
253
+ except CachePublishAborted:
254
+ logger.debug(
255
+ "dependency cache publish aborted",
256
+ context={
257
+ "function": func.__qualname__,
258
+ "key": key,
259
+ "scope": scope,
260
+ },
261
+ )
262
+ finally:
263
+ release_compute_lease(lease)
189
264
 
190
265
  logger.debug(
191
266
  "cache miss recorded",
@@ -62,6 +62,9 @@ class DependencyLockTimeoutError(TimeoutError):
62
62
  # -----------------------------------------------------------------------------
63
63
  INDEX_KEY = "dependency_index" # Cache key storing the complete dependency index
64
64
  LOCK_KEY = "dependency_index_lock" # Cache key used for the dependency lock
65
+ DEPENDENCY_GENERATION_KEY = "dependency_index_generation"
66
+ DATA_CHANGE_LOCK_KEY = "dependency_index_data_change_lock"
67
+ DATA_CHANGE_COUNT_KEY = "dependency_index_data_change_count"
65
68
  LOCK_TIMEOUT = 5 # Lock TTL in seconds
66
69
  UNDEFINED = object() # Sentinel for undefined values
67
70
  ACTIONS: tuple[Literal["filter"], Literal["exclude"]] = ("filter", "exclude")
@@ -100,6 +103,64 @@ def release_lock() -> None:
100
103
  cache.delete(LOCK_KEY)
101
104
 
102
105
 
106
+ def get_dependency_generation() -> int:
107
+ """Return the current dependency-cache mutation generation."""
108
+ generation = cache.get(DEPENDENCY_GENERATION_KEY, 0)
109
+ return int(generation or 0)
110
+
111
+
112
+ def _set_dependency_generation(generation: int) -> int:
113
+ cache.set(DEPENDENCY_GENERATION_KEY, generation, None)
114
+ return generation
115
+
116
+
117
+ def _get_dependency_data_change_count() -> int:
118
+ count = cache.get(DATA_CHANGE_COUNT_KEY, 0)
119
+ return max(int(count or 0), 0)
120
+
121
+
122
+ def _set_dependency_data_change_count(count: int) -> int:
123
+ cache.set(DATA_CHANGE_COUNT_KEY, count, None)
124
+ return count
125
+
126
+
127
+ def begin_dependency_data_change() -> int:
128
+ """
129
+ Mark a data change as active and bump the dependency generation.
130
+
131
+ The generation bump happens before the underlying mutation, so computations
132
+ that started before the mutation cannot publish dependency-scoped values.
133
+ """
134
+ acquire_lock_with_retry("begin_dependency_data_change")
135
+ try:
136
+ generation = _set_dependency_generation(get_dependency_generation() + 1)
137
+ _set_dependency_data_change_count(_get_dependency_data_change_count() + 1)
138
+ cache.set(DATA_CHANGE_LOCK_KEY, "1", None)
139
+ return generation
140
+ finally:
141
+ release_lock()
142
+
143
+
144
+ def end_dependency_data_change() -> None:
145
+ """Release the publish barrier for a completed data change."""
146
+ acquire_lock_with_retry("end_dependency_data_change")
147
+ try:
148
+ count = _set_dependency_data_change_count(
149
+ max(_get_dependency_data_change_count() - 1, 0)
150
+ )
151
+ if count == 0:
152
+ cache.delete(DATA_CHANGE_LOCK_KEY)
153
+ else:
154
+ cache.set(DATA_CHANGE_LOCK_KEY, "1", None)
155
+ finally:
156
+ release_lock()
157
+
158
+
159
+ def is_dependency_data_change_active() -> bool:
160
+ """Return whether dependency-scoped cache publishing should pause."""
161
+ return cache.get(DATA_CHANGE_LOCK_KEY) is not None
162
+
163
+
103
164
  def acquire_lock_with_retry(operation: str) -> None:
104
165
  """
105
166
  Acquire the dependency index lock, retrying with exponential backoff.
@@ -212,6 +273,62 @@ def parse_dependency_identifier(identifier: str) -> Any:
212
273
  # -----------------------------------------------------------------------------
213
274
  # DEPENDENCY RECORDING
214
275
  # -----------------------------------------------------------------------------
276
+ def _record_dependencies_locked(
277
+ idx: dependency_index,
278
+ cache_key: str,
279
+ dependencies: Iterable[Dependency],
280
+ ) -> None:
281
+ """Mutate an already-loaded dependency index for one cache key."""
282
+ for model_name, action, identifier in set(dependencies):
283
+ if action in ("filter", "exclude"):
284
+ action_key = cast(Literal["filter", "exclude"], action)
285
+ params = parse_dependency_identifier(identifier)
286
+ if not isinstance(params, dict):
287
+ continue
288
+ action_section = cast(
289
+ dict[general_manager_name, manager_dependency_section],
290
+ idx[action_key],
291
+ )
292
+ section = action_section.setdefault(model_name, {})
293
+ if not params:
294
+ lookup_map = section.setdefault(ALL_RECORDS_LOOKUP, {})
295
+ lookup_map.setdefault(ALL_RECORDS_VALUE, set()).add(cache_key)
296
+ continue
297
+ if len(params) > 1:
298
+ cache_dependencies = section.setdefault("__cache_dependencies__", {})
299
+ cache_dependencies.setdefault(cache_key, set()).add(identifier)
300
+ for lookup, val in params.items():
301
+ lookup_map = section.setdefault(lookup, {})
302
+ val_key = json.dumps(
303
+ _normalize_dependency_identifier(val), sort_keys=True
304
+ )
305
+ lookup_map.setdefault(val_key, set()).add(cache_key)
306
+
307
+ elif action == "request_query":
308
+ request_index = cast(
309
+ dict[str, dict[str, set[str]]],
310
+ idx.setdefault("request_query", {}),
311
+ )
312
+ request_section = request_index.setdefault(model_name, {})
313
+ request_section.setdefault(identifier, set()).add(cache_key)
314
+
315
+ elif action == "all":
316
+ all_index = cast(
317
+ dict[str, set[str]],
318
+ idx.setdefault("all", {}),
319
+ )
320
+ all_index.setdefault(model_name, set()).add(cache_key)
321
+
322
+ else:
323
+ filter_section = cast(
324
+ dict[general_manager_name, manager_dependency_section],
325
+ idx["filter"],
326
+ )
327
+ section = filter_section.setdefault(model_name, {})
328
+ lookup_map = section.setdefault("identification", {})
329
+ lookup_map.setdefault(identifier, set()).add(cache_key)
330
+
331
+
215
332
  def record_dependencies(
216
333
  cache_key: str,
217
334
  dependencies: Iterable[Dependency],
@@ -232,61 +349,32 @@ def record_dependencies(
232
349
  acquire_lock_with_retry("record_dependencies")
233
350
  try:
234
351
  idx = get_full_index()
235
- for model_name, action, identifier in dependencies:
236
- if action in ("filter", "exclude"):
237
- action_key = cast(Literal["filter", "exclude"], action)
238
- params = parse_dependency_identifier(identifier)
239
- if not isinstance(params, dict):
240
- continue
241
- action_section = cast(
242
- dict[general_manager_name, manager_dependency_section],
243
- idx[action_key],
244
- )
245
- section = action_section.setdefault(model_name, {})
246
- if not params:
247
- lookup_map = section.setdefault(ALL_RECORDS_LOOKUP, {})
248
- lookup_map.setdefault(ALL_RECORDS_VALUE, set()).add(cache_key)
249
- continue
250
- if len(params) > 1:
251
- cache_dependencies = section.setdefault(
252
- "__cache_dependencies__", {}
253
- )
254
- cache_dependencies.setdefault(cache_key, set()).add(identifier)
255
- for lookup, val in params.items():
256
- lookup_map = section.setdefault(lookup, {})
257
- val_key = json.dumps(
258
- _normalize_dependency_identifier(val), sort_keys=True
259
- )
260
- lookup_map.setdefault(val_key, set()).add(cache_key)
261
-
262
- elif action == "request_query":
263
- request_index = cast(
264
- dict[str, dict[str, set[str]]],
265
- idx.setdefault("request_query", {}),
266
- )
267
- request_section = request_index.setdefault(model_name, {})
268
- request_section.setdefault(identifier, set()).add(cache_key)
352
+ _record_dependencies_locked(idx, cache_key, dependencies)
353
+ set_full_index(idx)
354
+ finally:
355
+ release_lock()
269
356
 
270
- elif action == "all":
271
- all_index = cast(
272
- dict[str, set[str]],
273
- idx.setdefault("all", {}),
274
- )
275
- all_index.setdefault(model_name, set()).add(cache_key)
276
357
 
277
- else:
278
- # Treat identification lookups as a simple filter on `id`
279
- filter_section = cast(
280
- dict[general_manager_name, manager_dependency_section],
281
- idx["filter"],
282
- )
283
- section = filter_section.setdefault(model_name, {})
284
- lookup_map = section.setdefault("identification", {})
285
- val_key = identifier
286
- lookup_map.setdefault(val_key, set()).add(cache_key)
358
+ def record_many_dependencies(
359
+ entries: Iterable[tuple[str, Iterable[Dependency]]],
360
+ ) -> None:
361
+ """
362
+ Register dependency metadata for many cache keys while holding one index lock.
363
+ """
364
+ normalized: dict[str, set[Dependency]] = {}
365
+ for cache_key, dependencies in entries:
366
+ dep_set = set(dependencies)
367
+ if dep_set:
368
+ normalized.setdefault(cache_key, set()).update(dep_set)
369
+ if not normalized:
370
+ return
287
371
 
372
+ acquire_lock_with_retry("record_many_dependencies")
373
+ try:
374
+ idx = get_full_index()
375
+ for cache_key, dependencies in normalized.items():
376
+ _record_dependencies_locked(idx, cache_key, dependencies)
288
377
  set_full_index(idx)
289
-
290
378
  finally:
291
379
  release_lock()
292
380
 
@@ -380,7 +468,8 @@ def _remove_cache_keys_from_index_locked(
380
468
  all_section = cast(dict[str, set[str]], idx.get("all", {}))
381
469
  for mname, key_set in list(all_section.items()):
382
470
  for cache_key in cache_keys:
383
- key_set.discard(cache_key)
471
+ if cache_key in key_set:
472
+ key_set.remove(cache_key)
384
473
  if not key_set:
385
474
  del all_section[mname]
386
475
  for action in ACTIONS:
@@ -396,7 +485,8 @@ def _remove_cache_keys_from_index_locked(
396
485
  lookup_map = cast(lookup_dependency_map, lookup_map)
397
486
  for val_key, key_set in list(lookup_map.items()):
398
487
  for cache_key in cache_keys:
399
- key_set.discard(cache_key)
488
+ if cache_key in key_set:
489
+ key_set.remove(cache_key)
400
490
  if not key_set:
401
491
  del lookup_map[val_key]
402
492
  if not lookup_map:
@@ -415,7 +505,8 @@ def _remove_cache_keys_from_index_locked(
415
505
  for mname, query_section in list(request_query_section.items()):
416
506
  for identifier, key_set in list(query_section.items()):
417
507
  for cache_key in cache_keys:
418
- key_set.discard(cache_key)
508
+ if cache_key in key_set:
509
+ key_set.remove(cache_key)
419
510
  if not key_set:
420
511
  del query_section[identifier]
421
512
  if not query_section:
@@ -443,6 +534,25 @@ def invalidate_and_remove_cache_keys(cache_keys: Iterable[str]) -> None:
443
534
  release_lock()
444
535
 
445
536
 
537
+ def _invalidate_request_query_dependencies_locked(
538
+ idx: dependency_index,
539
+ manager_name: str,
540
+ ) -> tuple[str, ...]:
541
+ request_queries = cast(
542
+ request_query_manager_section,
543
+ idx.get("request_query", {}).get(manager_name, {}),
544
+ )
545
+ cache_keys = tuple(
546
+ dict.fromkeys(
547
+ cache_key for key_set in request_queries.values() for cache_key in key_set
548
+ )
549
+ )
550
+ for cache_key in cache_keys:
551
+ cache.delete(cache_key)
552
+ _remove_cache_keys_from_index_locked(idx, cache_keys)
553
+ return cache_keys
554
+
555
+
446
556
  def invalidate_request_query_dependencies(manager_name: str) -> tuple[str, ...]:
447
557
  """
448
558
  Invalidate all request-query cache keys tracked for a manager atomically.
@@ -453,24 +563,12 @@ def invalidate_request_query_dependencies(manager_name: str) -> tuple[str, ...]:
453
563
  acquire_lock_with_retry("invalidate_request_query_dependencies")
454
564
  try:
455
565
  idx = get_full_index()
456
- request_queries = cast(
457
- request_query_manager_section,
458
- idx.get("request_query", {}).get(manager_name, {}),
459
- )
460
- cache_keys = tuple(
461
- dict.fromkeys(
462
- cache_key
463
- for key_set in request_queries.values()
464
- for cache_key in key_set
465
- )
566
+ invalidated_keys = _invalidate_request_query_dependencies_locked(
567
+ idx, manager_name
466
568
  )
467
- if not cache_keys:
468
- return ()
469
- for cache_key in cache_keys:
470
- cache.delete(cache_key)
471
- _remove_cache_keys_from_index_locked(idx, cache_keys)
472
- set_full_index(idx)
473
- return cache_keys
569
+ if invalidated_keys:
570
+ set_full_index(idx)
571
+ return invalidated_keys
474
572
  finally:
475
573
  release_lock()
476
574
 
@@ -522,25 +620,26 @@ def capture_old_values(
522
620
  instance._old_values = vals
523
621
 
524
622
 
525
- @receiver(post_data_change)
526
- def generic_cache_invalidation(
527
- sender: type[GeneralManager],
623
+ def _generic_cache_invalidation_locked(
624
+ idx: dependency_index,
625
+ manager_name: str,
528
626
  instance: GeneralManager,
529
627
  old_relevant_values: dict[str, Any],
530
- **kwargs: object,
531
628
  ) -> None:
532
629
  """
533
- Invalidate cache entries whose recorded dependencies are affected by changes to a GeneralManager instance.
630
+ Invalidate cache entries affected by a change while the dependency lock is held.
534
631
 
535
632
  Uses the dependency index to compare previously captured values against the instance's current values for tracked lookups, evaluates both simple and composite dependency conditions for "filter" and "exclude" actions, and for any dependency that warrants invalidation it deletes the corresponding cache entry and removes its references from the index.
536
633
 
537
634
  Parameters:
538
- sender (type[GeneralManager]): Manager class that emitted the signal.
635
+ idx (dependency_index): Dependency index loaded by the public receiver.
636
+ manager_name (str): Name of the manager class that emitted the signal.
539
637
  instance (GeneralManager): The manager instance that was changed.
540
638
  old_relevant_values (dict[str, Any]): Mapping of lookup paths (joined by "__") to their values as captured before the change; used to compare old vs. new values for invalidation decisions.
541
639
  """
542
- manager_name = sender.__name__
543
- invalidated_request_query_keys = invalidate_request_query_dependencies(manager_name)
640
+ invalidated_request_query_keys = _invalidate_request_query_dependencies_locked(
641
+ idx, manager_name
642
+ )
544
643
  for cache_key in invalidated_request_query_keys:
545
644
  logger.info(
546
645
  "invalidating request query cache key",
@@ -550,7 +649,6 @@ def generic_cache_invalidation(
550
649
  "action": REQUEST_QUERY_ACTION,
551
650
  },
552
651
  )
553
- idx = get_full_index()
554
652
  all_cache_keys = tuple(
555
653
  cast(dict[str, set[str]], idx.get("all", {})).get(manager_name, set())
556
654
  )
@@ -563,8 +661,8 @@ def generic_cache_invalidation(
563
661
  "action": "all",
564
662
  },
565
663
  )
566
- invalidate_cache_key(cache_key)
567
- remove_cache_key_from_index(cache_key)
664
+ cache.delete(cache_key)
665
+ _remove_cache_keys_from_index_locked(idx, (cache_key,))
568
666
 
569
667
  def _json_loads_val_key(val_key: Any) -> Any:
570
668
  if isinstance(val_key, str):
@@ -928,10 +1026,12 @@ def generic_cache_invalidation(
928
1026
  model_section = action_section.get(manager_name)
929
1027
  if not isinstance(model_section, dict):
930
1028
  continue
931
- for lookup, lookup_map in model_section.items():
1029
+ for lookup, lookup_map in list(model_section.items()):
932
1030
  if lookup.startswith("__"):
933
1031
  if lookup == ALL_RECORDS_LOOKUP:
934
- for cache_keys in cast(lookup_dependency_map, lookup_map).values():
1032
+ for cache_keys in list(
1033
+ cast(lookup_dependency_map, lookup_map).values()
1034
+ ):
935
1035
  for ck in list(cache_keys):
936
1036
  logger.info(
937
1037
  "invalidating cache key",
@@ -943,8 +1043,8 @@ def generic_cache_invalidation(
943
1043
  "value": ALL_RECORDS_VALUE,
944
1044
  },
945
1045
  )
946
- invalidate_cache_key(ck)
947
- remove_cache_key_from_index(ck)
1046
+ cache.delete(ck)
1047
+ _remove_cache_keys_from_index_locked(idx, (ck,))
948
1048
  elif lookup.startswith("__sort__"):
949
1049
  sort_lookup = lookup.removeprefix("__sort__")
950
1050
  attr_path = sort_lookup.split("__")
@@ -979,8 +1079,8 @@ def generic_cache_invalidation(
979
1079
  "value": val_key,
980
1080
  },
981
1081
  )
982
- invalidate_cache_key(ck)
983
- remove_cache_key_from_index(ck)
1082
+ cache.delete(ck)
1083
+ _remove_cache_keys_from_index_locked(idx, (ck,))
984
1084
  continue
985
1085
  lookup_map = cast(lookup_dependency_map, lookup_map)
986
1086
  # 1) get operator and attribute path
@@ -1039,8 +1139,8 @@ def generic_cache_invalidation(
1039
1139
  "value": val_key,
1040
1140
  },
1041
1141
  )
1042
- invalidate_cache_key(ck)
1043
- remove_cache_key_from_index(ck)
1142
+ cache.delete(ck)
1143
+ _remove_cache_keys_from_index_locked(idx, (ck,))
1044
1144
 
1045
1145
  else: # action == 'exclude'
1046
1146
  # Excludes: invalidate only if matches changed
@@ -1064,5 +1164,37 @@ def generic_cache_invalidation(
1064
1164
  "value": val_key,
1065
1165
  },
1066
1166
  )
1067
- invalidate_cache_key(ck)
1068
- remove_cache_key_from_index(ck)
1167
+ cache.delete(ck)
1168
+ _remove_cache_keys_from_index_locked(idx, (ck,))
1169
+
1170
+
1171
+ @receiver(post_data_change)
1172
+ def generic_cache_invalidation(
1173
+ sender: type[GeneralManager],
1174
+ instance: GeneralManager,
1175
+ old_relevant_values: dict[str, Any],
1176
+ **kwargs: object,
1177
+ ) -> None:
1178
+ """
1179
+ Invalidate cache entries whose recorded dependencies are affected by changes to a GeneralManager instance.
1180
+
1181
+ Uses the dependency index to compare previously captured values against the instance's current values for tracked lookups, evaluates both simple and composite dependency conditions for "filter" and "exclude" actions, and for any dependency that warrants invalidation it deletes the corresponding cache entry and removes its references from the index.
1182
+
1183
+ Parameters:
1184
+ sender (type[GeneralManager]): Manager class that emitted the signal.
1185
+ instance (GeneralManager): The manager instance that was changed.
1186
+ old_relevant_values (dict[str, Any]): Mapping of lookup paths (joined by "__") to their values as captured before the change; used to compare old vs. new values for invalidation decisions.
1187
+ """
1188
+ manager_name = sender.__name__
1189
+ acquire_lock_with_retry("generic_cache_invalidation")
1190
+ try:
1191
+ idx = get_full_index()
1192
+ _generic_cache_invalidation_locked(
1193
+ idx,
1194
+ manager_name,
1195
+ instance,
1196
+ old_relevant_values,
1197
+ )
1198
+ set_full_index(idx)
1199
+ finally:
1200
+ release_lock()