GeneralManager 0.64.3__tar.gz → 0.65.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 (296) hide show
  1. {generalmanager-0.64.3/src/GeneralManager.egg-info → generalmanager-0.65.0}/PKG-INFO +5 -2
  2. {generalmanager-0.64.3 → generalmanager-0.65.0}/README.md +4 -1
  3. {generalmanager-0.64.3 → generalmanager-0.65.0}/pyproject.toml +1 -1
  4. {generalmanager-0.64.3 → generalmanager-0.65.0/src/GeneralManager.egg-info}/PKG-INFO +5 -2
  5. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/GeneralManager.egg-info/SOURCES.txt +6 -0
  6. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/_types/general_manager.py +4 -0
  7. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/_types/search.py +4 -0
  8. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/apps.py +9 -2
  9. generalmanager-0.65.0/src/general_manager/cache/data_change_context.py +93 -0
  10. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/cache/signals.py +110 -52
  11. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/models.py +70 -5
  12. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/orm/mutations.py +27 -30
  13. generalmanager-0.65.0/src/general_manager/migrations/0010_chat_pending_confirmation_portable_uniqueness.py +64 -0
  14. generalmanager-0.65.0/src/general_manager/migrations/0011_search_index_state_dirty_generation.py +20 -0
  15. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/public_api_registry.py +10 -0
  16. generalmanager-0.65.0/src/general_manager/search/async_tasks.py +507 -0
  17. generalmanager-0.65.0/src/general_manager/search/checks.py +259 -0
  18. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/search/config.py +41 -7
  19. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/search/indexer.py +164 -119
  20. generalmanager-0.65.0/src/general_manager/search/invalidation.py +1231 -0
  21. generalmanager-0.65.0/src/general_manager/search/m2m_invalidation.py +372 -0
  22. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/search/models.py +55 -37
  23. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/search/reconciliation.py +221 -53
  24. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/uploads/finalization.py +51 -2
  25. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/utils/testing.py +219 -179
  26. generalmanager-0.65.0/tests/test_settings.py +79 -0
  27. generalmanager-0.64.3/src/general_manager/search/async_tasks.py +0 -217
  28. generalmanager-0.64.3/tests/test_settings.py +0 -34
  29. {generalmanager-0.64.3 → generalmanager-0.65.0}/LICENSE +0 -0
  30. {generalmanager-0.64.3 → generalmanager-0.65.0}/setup.cfg +0 -0
  31. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/GeneralManager.egg-info/dependency_links.txt +0 -0
  32. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/GeneralManager.egg-info/requires.txt +0 -0
  33. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/GeneralManager.egg-info/top_level.txt +0 -0
  34. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/__init__.py +0 -0
  35. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/_types/__init__.py +0 -0
  36. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/_types/api.py +3 -3
  37. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/_types/bucket.py +0 -0
  38. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/_types/cache.py +2 -2
  39. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/_types/dataframes.py +2 -2
  40. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/_types/factory.py +0 -0
  41. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/_types/interface.py +0 -0
  42. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/_types/manager.py +2 -2
  43. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/_types/measurement.py +0 -0
  44. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/_types/permission.py +2 -2
  45. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/_types/rule.py +0 -0
  46. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/_types/utils.py +2 -2
  47. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/api/__init__.py +0 -0
  48. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/api/graphql.py +0 -0
  49. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/api/graphql_errors.py +0 -0
  50. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/api/graphql_mutations.py +0 -0
  51. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/api/graphql_prefetch.py +0 -0
  52. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/api/graphql_relations.py +0 -0
  53. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/api/graphql_resolvers.py +0 -0
  54. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/api/graphql_search.py +0 -0
  55. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/api/graphql_subscription_consumer.py +0 -0
  56. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/api/graphql_subscriptions.py +0 -0
  57. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/api/graphql_view.py +0 -0
  58. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/api/graphql_warmup.py +0 -0
  59. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/api/graphql_warmup_registry.py +0 -0
  60. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/api/graphql_warmup_tasks.py +0 -0
  61. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/api/mutation.py +0 -0
  62. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/api/notification_batching.py +0 -0
  63. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/api/property.py +0 -0
  64. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/api/registry.py +0 -0
  65. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/api/remote_api.py +0 -0
  66. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/api/remote_invalidation.py +0 -0
  67. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/api/remote_invalidation_client.py +0 -0
  68. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/bootstrap.py +0 -0
  69. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/bucket/__init__.py +0 -0
  70. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/bucket/base_bucket.py +0 -0
  71. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/bucket/calculation_bucket.py +0 -0
  72. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/bucket/database_bucket.py +0 -0
  73. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/bucket/group_bucket.py +0 -0
  74. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/bucket/indexing.py +0 -0
  75. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/bucket/request_bucket.py +0 -0
  76. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/cache/__init__.py +0 -0
  77. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/cache/cache_decorator.py +0 -0
  78. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/cache/cache_tracker.py +0 -0
  79. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/cache/dependency_cache.py +0 -0
  80. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/cache/dependency_index.py +0 -0
  81. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/cache/dependency_matching.py +0 -0
  82. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/cache/dependency_publish.py +0 -0
  83. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/cache/dependency_shards.py +0 -0
  84. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/cache/model_dependency_collector.py +0 -0
  85. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/cache/run_context.py +0 -0
  86. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/__init__.py +0 -0
  87. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/audit.py +0 -0
  88. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/bootstrap.py +0 -0
  89. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/checks.py +0 -0
  90. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/consumer.py +0 -0
  91. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/errors.py +0 -0
  92. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/evals/__init__.py +0 -0
  93. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/evals/__main__.py +0 -0
  94. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/evals/baseline.py +0 -0
  95. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/evals/datasets/basic_queries.yaml +0 -0
  96. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/evals/datasets/demo_readiness.yaml +0 -0
  97. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/evals/datasets/edge_cases.yaml +0 -0
  98. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/evals/datasets/follow_ups.yaml +0 -0
  99. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/evals/datasets/large_schema.yaml +0 -0
  100. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/evals/datasets/multi_hop.yaml +0 -0
  101. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/evals/diagnostics.py +0 -0
  102. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/evals/fingerprints.py +0 -0
  103. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/evals/fixtures.py +0 -0
  104. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/evals/judges/__init__.py +0 -0
  105. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/evals/judges/answer_quality.py +0 -0
  106. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/evals/judges/contract.py +0 -0
  107. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/evals/judges/result_accuracy.py +0 -0
  108. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/evals/judges/tool_sequence.py +0 -0
  109. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/evals/readiness.py +0 -0
  110. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/evals/runner.py +0 -0
  111. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/evals/traces.py +0 -0
  112. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/grounding.py +0 -0
  113. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/providers/__init__.py +0 -0
  114. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/providers/_shared.py +0 -0
  115. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/providers/anthropic.py +0 -0
  116. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/providers/base.py +0 -0
  117. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/providers/google.py +0 -0
  118. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/providers/ollama.py +0 -0
  119. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/providers/openai.py +0 -0
  120. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/rate_limits.py +0 -0
  121. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/routing.py +0 -0
  122. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/schema_index.py +0 -0
  123. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/settings.py +0 -0
  124. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/signals.py +0 -0
  125. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/system_prompt.py +0 -0
  126. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/tool_metadata.py +0 -0
  127. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/tools.py +0 -0
  128. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/chat/views.py +0 -0
  129. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/conf.py +0 -0
  130. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/dataframes/__init__.py +0 -0
  131. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/dataframes/measurements.py +0 -0
  132. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/factory/__init__.py +0 -0
  133. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/factory/auto_factory.py +0 -0
  134. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/factory/factories.py +0 -0
  135. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/factory/factory_methods.py +0 -0
  136. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/__init__.py +0 -0
  137. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/base_interface.py +0 -0
  138. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/bundles/__init__.py +0 -0
  139. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/bundles/calculation.py +0 -0
  140. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/bundles/database.py +0 -0
  141. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/bundles/remote_manager.py +0 -0
  142. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/bundles/request.py +0 -0
  143. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/__init__.py +0 -0
  144. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/base.py +0 -0
  145. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/builtin.py +0 -0
  146. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/calculation/__init__.py +0 -0
  147. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/calculation/_compat.py +0 -0
  148. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/calculation/lifecycle.py +0 -0
  149. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/configuration.py +0 -0
  150. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/core/observability.py +0 -0
  151. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/core/utils.py +0 -0
  152. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/exceptions.py +0 -0
  153. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/existing_model/__init__.py +0 -0
  154. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/existing_model/_compat.py +0 -0
  155. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/existing_model/resolution.py +0 -0
  156. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/factory.py +0 -0
  157. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/orm/__init__.py +0 -0
  158. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/orm/_compat.py +0 -0
  159. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/orm/history.py +0 -0
  160. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/orm/lifecycle.py +0 -0
  161. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/orm/support.py +0 -0
  162. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/orm_utils/__init__.py +0 -0
  163. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/orm_utils/django_manager_utils.py +0 -0
  164. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/orm_utils/field_descriptors.py +0 -0
  165. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/orm_utils/payload_normalizer.py +0 -0
  166. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/read_only/__init__.py +0 -0
  167. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/read_only/_compat.py +0 -0
  168. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/read_only/_types.py +0 -0
  169. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/read_only/lifecycle.py +0 -0
  170. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/read_only/management.py +0 -0
  171. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/registry.py +0 -0
  172. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/remote_manager.py +0 -0
  173. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/capabilities/request/__init__.py +0 -0
  174. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/infrastructure/__init__.py +0 -0
  175. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/infrastructure/startup_hooks.py +0 -0
  176. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/infrastructure/system_checks.py +0 -0
  177. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/interfaces/__init__.py +0 -0
  178. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/interfaces/calculation.py +0 -0
  179. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/interfaces/database.py +0 -0
  180. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/interfaces/existing_model.py +0 -0
  181. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/interfaces/read_only.py +0 -0
  182. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/interfaces/remote_manager.py +0 -0
  183. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/interfaces/request.py +0 -0
  184. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/manifests/__init__.py +0 -0
  185. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/manifests/capability_builder.py +0 -0
  186. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/manifests/capability_manifest.py +0 -0
  187. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/manifests/capability_models.py +0 -0
  188. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/orm_interface.py +0 -0
  189. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/requests.py +0 -0
  190. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/utils/__init__.py +0 -0
  191. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/utils/database_interface_protocols.py +0 -0
  192. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/utils/errors.py +0 -0
  193. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/utils/history.py +0 -0
  194. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/interface/utils/models.py +0 -0
  195. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/logging.py +0 -0
  196. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/management/commands/chat_cleanup.py +0 -0
  197. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/management/commands/cleanup_upload_intents.py +0 -0
  198. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/management/commands/graphql_warmup.py +0 -0
  199. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/management/commands/graphql_warmup_refresh_due.py +0 -0
  200. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/management/commands/search_index.py +0 -0
  201. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/management/commands/search_reconcile.py +0 -0
  202. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/management/commands/seed_manager_landscape.py +0 -0
  203. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/management/commands/shell.py +0 -0
  204. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/management/commands/workflow_drain_outbox.py +0 -0
  205. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/management/commands/workflow_replay_dead_letters.py +0 -0
  206. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/manager/__init__.py +0 -0
  207. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/manager/general_manager.py +0 -0
  208. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/manager/group_manager.py +0 -0
  209. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/manager/input.py +0 -0
  210. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/manager/meta.py +0 -0
  211. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/measurement/__init__.py +0 -0
  212. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/measurement/measurement.py +0 -0
  213. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/measurement/measurement_field.py +0 -0
  214. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/metrics/__init__.py +0 -0
  215. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/metrics/graphql.py +0 -0
  216. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/migrations/0001_initial.py +0 -0
  217. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/migrations/0002_workflow_outbox_scaling_indexes.py +0 -0
  218. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/migrations/0003_workflow_execution_correlation_constraint.py +0 -0
  219. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/migrations/0004_chat_persistence_models.py +0 -0
  220. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/migrations/0004_search_index_state.py +0 -0
  221. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/migrations/0005_merge_chat_search.py +0 -0
  222. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/migrations/0006_chat_pending_confirmation_scoped_ids.py +0 -0
  223. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/migrations/0007_upload_intent.py +0 -0
  224. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/migrations/0008_upload_finalization_versions.py +0 -0
  225. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/migrations/0009_upload_cleanup_state.py +0 -0
  226. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/migrations/__init__.py +0 -0
  227. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/models.py +0 -0
  228. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/permission/__init__.py +0 -0
  229. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/permission/audit.py +0 -0
  230. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/permission/base_permission.py +0 -0
  231. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/permission/graphql_capabilities.py +0 -0
  232. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/permission/manager_based_permission.py +0 -0
  233. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/permission/mutation_permission.py +0 -0
  234. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/permission/permission_checks.py +0 -0
  235. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/permission/permission_data_manager.py +0 -0
  236. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/permission/utils.py +0 -0
  237. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/py.typed +0 -0
  238. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/rule/__init__.py +0 -0
  239. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/rule/handler.py +0 -0
  240. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/rule/rule.py +0 -0
  241. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/search/__init__.py +0 -0
  242. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/search/backend.py +0 -0
  243. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/search/backend_registry.py +0 -0
  244. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/search/backends/__init__.py +0 -0
  245. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/search/backends/dev.py +0 -0
  246. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/search/backends/meilisearch.py +0 -0
  247. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/search/backends/opensearch.py +0 -0
  248. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/search/backends/typesense.py +0 -0
  249. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/search/registry.py +0 -0
  250. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/search/tasks.py +0 -0
  251. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/search/utils.py +0 -0
  252. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/seeding/__init__.py +0 -0
  253. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/seeding/manager_landscape.py +0 -0
  254. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/uploads/__init__.py +0 -0
  255. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/uploads/adapters.py +0 -0
  256. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/uploads/checks.py +0 -0
  257. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/uploads/config.py +0 -0
  258. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/uploads/errors.py +0 -0
  259. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/uploads/graphql.py +0 -0
  260. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/uploads/graphql_types.py +0 -0
  261. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/uploads/metrics.py +0 -0
  262. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/uploads/models.py +0 -0
  263. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/uploads/public.py +0 -0
  264. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/uploads/s3.py +0 -0
  265. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/uploads/services.py +0 -0
  266. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/uploads/tokens.py +0 -0
  267. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/uploads/types.py +0 -0
  268. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/uploads/urls.py +0 -0
  269. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/uploads/views.py +0 -0
  270. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/utils/__init__.py +0 -0
  271. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/utils/args_to_kwargs.py +0 -0
  272. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/utils/filter_parser.py +0 -0
  273. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/utils/format_string.py +0 -0
  274. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/utils/json_encoder.py +0 -0
  275. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/utils/make_cache_key.py +0 -0
  276. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/utils/none_to_zero.py +0 -0
  277. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/utils/path_mapping.py +0 -0
  278. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/utils/public_api.py +0 -0
  279. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/utils/type_checks.py +0 -0
  280. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/workflow/__init__.py +0 -0
  281. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/workflow/actions.py +0 -0
  282. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/workflow/backend_registry.py +0 -0
  283. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/workflow/backends/__init__.py +0 -0
  284. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/workflow/backends/celery.py +0 -0
  285. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/workflow/backends/local.py +0 -0
  286. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/workflow/backends/n8n.py +0 -0
  287. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/workflow/config.py +0 -0
  288. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/workflow/engine.py +0 -0
  289. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/workflow/event_registry.py +0 -0
  290. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/workflow/events.py +0 -0
  291. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/workflow/models.py +0 -0
  292. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/workflow/signal_bridge.py +0 -0
  293. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/workflow/tasks.py +0 -0
  294. {generalmanager-0.64.3 → generalmanager-0.65.0}/src/general_manager/workflow/telemetry.py +0 -0
  295. {generalmanager-0.64.3 → generalmanager-0.65.0}/tests/test_urls.py +0 -0
  296. {generalmanager-0.64.3 → generalmanager-0.65.0}/tests/testing_asgi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GeneralManager
3
- Version: 0.64.3
3
+ Version: 0.65.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
@@ -149,7 +149,10 @@ and [interface concepts](https://timkleindick.github.io/general_manager/concepts
149
149
  - CI exercises Python 3.12, 3.13, and 3.14 on SQLite
150
150
 
151
151
  GeneralManager builds on Django's database layer, but this project only claims
152
- backend support covered by its tests or maintained examples.
152
+ backend support covered by its tests or maintained examples. SQLite,
153
+ PostgreSQL, and MariaDB are exercised by CI and are fully supported. Other
154
+ Django-supported backends may work, but are not currently claimed as supported
155
+ by GeneralManager.
153
156
 
154
157
  ## Five-minute setup
155
158
 
@@ -60,7 +60,10 @@ and [interface concepts](https://timkleindick.github.io/general_manager/concepts
60
60
  - CI exercises Python 3.12, 3.13, and 3.14 on SQLite
61
61
 
62
62
  GeneralManager builds on Django's database layer, but this project only claims
63
- backend support covered by its tests or maintained examples.
63
+ backend support covered by its tests or maintained examples. SQLite,
64
+ PostgreSQL, and MariaDB are exercised by CI and are fully supported. Other
65
+ Django-supported backends may work, but are not currently claimed as supported
66
+ by GeneralManager.
64
67
 
65
68
  ## Five-minute setup
66
69
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "GeneralManager"
7
- version = "0.64.3"
7
+ version = "0.65.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.64.3
3
+ Version: 0.65.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
@@ -149,7 +149,10 @@ and [interface concepts](https://timkleindick.github.io/general_manager/concepts
149
149
  - CI exercises Python 3.12, 3.13, and 3.14 on SQLite
150
150
 
151
151
  GeneralManager builds on Django's database layer, but this project only claims
152
- backend support covered by its tests or maintained examples.
152
+ backend support covered by its tests or maintained examples. SQLite,
153
+ PostgreSQL, and MariaDB are exercised by CI and are fully supported. Other
154
+ Django-supported backends may work, but are not currently claimed as supported
155
+ by GeneralManager.
153
156
 
154
157
  ## Five-minute setup
155
158
 
@@ -59,6 +59,7 @@ src/general_manager/bucket/request_bucket.py
59
59
  src/general_manager/cache/__init__.py
60
60
  src/general_manager/cache/cache_decorator.py
61
61
  src/general_manager/cache/cache_tracker.py
62
+ src/general_manager/cache/data_change_context.py
62
63
  src/general_manager/cache/dependency_cache.py
63
64
  src/general_manager/cache/dependency_index.py
64
65
  src/general_manager/cache/dependency_matching.py
@@ -207,6 +208,8 @@ src/general_manager/migrations/0006_chat_pending_confirmation_scoped_ids.py
207
208
  src/general_manager/migrations/0007_upload_intent.py
208
209
  src/general_manager/migrations/0008_upload_finalization_versions.py
209
210
  src/general_manager/migrations/0009_upload_cleanup_state.py
211
+ src/general_manager/migrations/0010_chat_pending_confirmation_portable_uniqueness.py
212
+ src/general_manager/migrations/0011_search_index_state_dirty_generation.py
210
213
  src/general_manager/migrations/__init__.py
211
214
  src/general_manager/permission/__init__.py
212
215
  src/general_manager/permission/audit.py
@@ -224,8 +227,11 @@ src/general_manager/search/__init__.py
224
227
  src/general_manager/search/async_tasks.py
225
228
  src/general_manager/search/backend.py
226
229
  src/general_manager/search/backend_registry.py
230
+ src/general_manager/search/checks.py
227
231
  src/general_manager/search/config.py
228
232
  src/general_manager/search/indexer.py
233
+ src/general_manager/search/invalidation.py
234
+ src/general_manager/search/m2m_invalidation.py
229
235
  src/general_manager/search/models.py
230
236
  src/general_manager/search/reconciliation.py
231
237
  src/general_manager/search/registry.py
@@ -19,9 +19,11 @@ __all__ = [
19
19
  "RequestInterface",
20
20
  "Rule",
21
21
  "SearchBackend",
22
+ "SearchChange",
22
23
  "SearchConfigProtocol",
23
24
  "SearchConfigSpec",
24
25
  "SearchIndexer",
26
+ "SearchInvalidationRule",
25
27
  "configure_search_backend",
26
28
  "configure_search_backend_from_settings",
27
29
  "get_logger",
@@ -54,9 +56,11 @@ from general_manager.interface.interfaces.remote_manager import RemoteManagerInt
54
56
  from general_manager.interface.interfaces.request import RequestInterface
55
57
  from general_manager.rule.rule import Rule
56
58
  from general_manager.search.backend import SearchBackend
59
+ from general_manager.search.config import SearchChange
57
60
  from general_manager.search.config import SearchConfigProtocol
58
61
  from general_manager.search.config import SearchConfigSpec
59
62
  from general_manager.search.indexer import SearchIndexer
63
+ from general_manager.search.config import SearchInvalidationRule
60
64
  from general_manager.search.backend_registry import configure_search_backend
61
65
  from general_manager.search.backend_registry import (
62
66
  configure_search_backend_from_settings,
@@ -13,11 +13,13 @@ __all__ = [
13
13
  "SearchBackendError",
14
14
  "SearchBackendNotConfiguredError",
15
15
  "SearchBackendNotImplementedError",
16
+ "SearchChange",
16
17
  "SearchConfigProtocol",
17
18
  "SearchConfigSpec",
18
19
  "SearchDocument",
19
20
  "SearchHit",
20
21
  "SearchIndexer",
22
+ "SearchInvalidationRule",
21
23
  "SearchResult",
22
24
  "TypesenseBackend",
23
25
  "configure_search_backend",
@@ -37,11 +39,13 @@ from general_manager.search.backend import SearchBackendClientMissingError
37
39
  from general_manager.search.backend import SearchBackendError
38
40
  from general_manager.search.backend import SearchBackendNotConfiguredError
39
41
  from general_manager.search.backend import SearchBackendNotImplementedError
42
+ from general_manager.search.config import SearchChange
40
43
  from general_manager.search.config import SearchConfigProtocol
41
44
  from general_manager.search.config import SearchConfigSpec
42
45
  from general_manager.search.backend import SearchDocument
43
46
  from general_manager.search.backend import SearchHit
44
47
  from general_manager.search.indexer import SearchIndexer
48
+ from general_manager.search.config import SearchInvalidationRule
45
49
  from general_manager.search.backend import SearchResult
46
50
  from general_manager.search.backends.typesense import TypesenseBackend
47
51
  from general_manager.search.backend_registry import configure_search_backend
@@ -168,8 +168,10 @@ class GeneralmanagerConfig(AppConfig):
168
168
  self.install_startup_hook_runner()
169
169
  self.register_system_checks()
170
170
  from general_manager.uploads.checks import register_upload_checks
171
+ from general_manager.search.checks import register_search_checks
171
172
 
172
173
  register_upload_checks()
174
+ register_search_checks()
173
175
  _autoload_app_managers_modules()
174
176
  self.initialize_general_manager_classes(
175
177
  GeneralManagerMeta.pending_attribute_initialization,
@@ -178,14 +180,19 @@ class GeneralmanagerConfig(AppConfig):
178
180
  handle_remote_api(GeneralManagerMeta.all_classes)
179
181
  configure_audit_logger_from_settings(settings)
180
182
  configure_search_backend_from_settings(settings)
183
+ from general_manager.search.invalidation import configure_search_invalidation
184
+ from general_manager.search.m2m_invalidation import (
185
+ configure_search_m2m_invalidation,
186
+ )
187
+
188
+ configure_search_invalidation()
189
+ configure_search_m2m_invalidation()
181
190
  configure_workflow_engine_from_settings(settings)
182
191
  configure_event_registry_from_settings(settings)
183
192
  configure_workflow_signal_bridge_from_settings(settings)
184
193
  configure_workflow_beat_schedule_from_settings(settings)
185
194
  configure_search_reconcile_beat_schedule_from_settings(settings)
186
195
  configure_graphql_warmup_beat_schedule_from_settings(settings)
187
- import general_manager.search.indexer as _search_indexer # noqa: F401
188
-
189
196
  from general_manager.conf import get_setting
190
197
 
191
198
  if get_setting("AUTOCREATE_GRAPHQL", False):
@@ -0,0 +1,93 @@
1
+ """Context-local ownership metadata for ORM data-change transactions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterator
6
+ from contextlib import contextmanager
7
+ from contextvars import ContextVar
8
+ from dataclasses import dataclass
9
+
10
+
11
+ @dataclass
12
+ class _DataChangeTransaction:
13
+ """Live marker for one framework-owned transaction envelope."""
14
+
15
+ database_alias: str
16
+ caller_in_atomic_block: bool
17
+ active: bool = True
18
+
19
+
20
+ _owned_transactions: ContextVar[tuple[_DataChangeTransaction, ...]] = ContextVar(
21
+ "owned_data_change_transactions",
22
+ default=(),
23
+ )
24
+ _authorized_operations: ContextVar[tuple[_DataChangeTransaction, ...]] = ContextVar(
25
+ "authorized_data_change_operations",
26
+ default=(),
27
+ )
28
+
29
+
30
+ def _current_owner(database_alias: str) -> _DataChangeTransaction | None:
31
+ for owner in reversed(_owned_transactions.get()):
32
+ if owner.active and owner.database_alias == database_alias:
33
+ return owner
34
+ return None
35
+
36
+
37
+ @contextmanager
38
+ def own_data_change_transaction(
39
+ database_alias: str,
40
+ *,
41
+ caller_in_atomic_block: bool,
42
+ ) -> Iterator[None]:
43
+ """Record a live framework envelope without inspecting Django internals."""
44
+ owner = _DataChangeTransaction(
45
+ database_alias=database_alias,
46
+ caller_in_atomic_block=caller_in_atomic_block,
47
+ )
48
+ token = _owned_transactions.set((*_owned_transactions.get(), owner))
49
+ try:
50
+ yield
51
+ finally:
52
+ owner.active = False
53
+ _owned_transactions.reset(token)
54
+
55
+
56
+ @contextmanager
57
+ def authorize_data_change_operation(database_alias: str) -> Iterator[None]:
58
+ """Authorize only the decorated manager method to reuse its envelope."""
59
+ owner = _current_owner(database_alias)
60
+ if owner is None:
61
+ yield
62
+ return
63
+ token = _authorized_operations.set((*_authorized_operations.get(), owner))
64
+ try:
65
+ yield
66
+ finally:
67
+ _authorized_operations.reset(token)
68
+
69
+
70
+ def owns_data_change_transaction(database_alias: str) -> bool:
71
+ """Return whether this context has a live framework envelope for an alias."""
72
+ return _current_owner(database_alias) is not None
73
+
74
+
75
+ def is_data_change_operation_authorized(database_alias: str) -> bool:
76
+ """Return whether the current owner is running its decorated method body."""
77
+ owner = _current_owner(database_alias)
78
+ if owner is None:
79
+ return False
80
+ return any(
81
+ authorized is owner and authorized.active
82
+ for authorized in _authorized_operations.get()
83
+ )
84
+
85
+
86
+ def may_reuse_data_change_transaction(database_alias: str) -> bool:
87
+ """Conservatively authorize reuse when the manager opened the outer block."""
88
+ owner = _current_owner(database_alias)
89
+ return bool(
90
+ owner is not None
91
+ and not owner.caller_in_atomic_block
92
+ and is_data_change_operation_authorized(database_alias)
93
+ )
@@ -2,12 +2,19 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from contextlib import nullcontext
5
6
  from copy import deepcopy
6
- from django.dispatch import Signal
7
- from typing import Callable, TypeVar, ParamSpec, cast, overload
8
-
9
7
  from functools import wraps
8
+ from typing import Callable, ParamSpec, TypeVar, cast, overload
9
+
10
+ from django.db import DEFAULT_DB_ALIAS, connections, transaction
11
+ from django.dispatch import Signal
12
+ from django.utils.connection import ConnectionDoesNotExist
10
13
 
14
+ from general_manager.cache.data_change_context import (
15
+ authorize_data_change_operation,
16
+ own_data_change_transaction,
17
+ )
11
18
  from general_manager.logging import get_logger
12
19
 
13
20
  post_data_change = Signal()
@@ -20,6 +27,16 @@ R = TypeVar("R")
20
27
  logger = get_logger("cache.signals")
21
28
 
22
29
 
30
+ def _caller_in_atomic_block(database_alias: str) -> bool:
31
+ """Read public transaction state when the configured alias is available."""
32
+ try:
33
+ return bool(connections[database_alias].in_atomic_block)
34
+ except ConnectionDoesNotExist:
35
+ # Preserve lightweight callers that replace ``transaction.atomic`` in
36
+ # isolation. A real atomic call will still reject an unknown alias.
37
+ return False
38
+
39
+
23
40
  @overload
24
41
  def data_change(func: Callable[P, R]) -> Callable[P, R]: ...
25
42
 
@@ -39,9 +56,12 @@ def data_change(
39
56
  clears run-scoped ORM bucket/index caches, emits `pre_data_change`, invokes
40
57
  the wrapped callable, then emits `post_data_change` with the changed
41
58
  instance, previous instance, action name, identification, and copied
42
- `_old_values` payload. GraphQL warm-up requeue keys collected during signal
43
- handling are drained only after the outermost active data-change barrier has
44
- closed; failed mutations drain pending keys but do not enqueue rewarm work.
59
+ `_old_values` payload. Both signals receive a per-call `change_context` and
60
+ the manager interface's database alias. ORM-backed signal dispatch and the
61
+ mutation share one transaction. GraphQL warm-up requeue keys collected
62
+ during signal handling are drained only after the outermost active
63
+ data-change barrier has closed; failed mutations drain pending keys but do
64
+ not enqueue rewarm work.
45
65
 
46
66
  Parameters:
47
67
  func: Function that performs a data mutation. Methods named `create`
@@ -85,6 +105,29 @@ def data_change(
85
105
  is_dependency_data_change_active,
86
106
  )
87
107
  from general_manager.cache.run_context import current_calculation_run_context
108
+ from general_manager.interface.orm_interface import OrmInterfaceBase
109
+
110
+ action = decorator_source.__name__
111
+ if action == "create":
112
+ sender = args[0]
113
+ instance_before = None
114
+ else:
115
+ instance_before = args[0]
116
+ sender = instance_before.__class__
117
+
118
+ interface = getattr(sender, "Interface", None)
119
+ is_orm_backed = isinstance(interface, type) and issubclass(
120
+ interface, OrmInterfaceBase
121
+ )
122
+ database_alias = DEFAULT_DB_ALIAS
123
+ if is_orm_backed:
124
+ database_alias = getattr(interface, "database", None) or DEFAULT_DB_ALIAS
125
+ change_context: dict[str, object] = {}
126
+ signal_kwargs = {
127
+ **kwargs,
128
+ "change_context": change_context,
129
+ "database_alias": database_alias,
130
+ }
88
131
 
89
132
  primary_exc: BaseException | None = None
90
133
  completed = False
@@ -95,55 +138,70 @@ def data_change(
95
138
  context.clear_bucket_indexes()
96
139
  context.clear_trusted_orm_managers()
97
140
  try:
98
- action = func.__name__
99
- if func.__name__ == "create":
100
- sender = args[0]
101
- instance_before = None
102
- else:
103
- instance = args[0]
104
- sender = instance.__class__
105
- instance_before = instance
106
- pre_data_change.send(
107
- sender=sender,
108
- instance=instance_before,
109
- action=action,
110
- **kwargs,
141
+ transaction_context = (
142
+ transaction.atomic(using=database_alias)
143
+ if is_orm_backed
144
+ else nullcontext()
111
145
  )
112
- old_relevant_values = getattr(instance_before, "_old_values", {})
113
- pre_identification = deepcopy(
114
- getattr(instance_before, "identification", None)
146
+ caller_in_atomic_block = (
147
+ _caller_in_atomic_block(database_alias) if is_orm_backed else False
115
148
  )
116
- if isinstance(func, classmethod):
117
- inner = cast(Callable[P, R], func.__func__)
118
- result = inner(*args, **kwargs)
119
- else:
120
- result = func(*args, **kwargs)
121
-
122
- context = current_calculation_run_context()
123
- if context is not None:
124
- context.clear_orm_bucket_results()
125
- context.clear_bucket_indexes()
126
- context.clear_trusted_orm_managers()
127
-
128
- instance = result
129
- identification = getattr(instance, "identification", None)
130
- if identification is None:
131
- identification = pre_identification
132
-
133
- post_data_change.send(
134
- sender=sender,
135
- instance=instance,
136
- previous_instance=instance_before,
137
- identification=identification,
138
- action=action,
139
- old_relevant_values=old_relevant_values,
140
- **kwargs,
149
+ ownership_context = (
150
+ own_data_change_transaction(
151
+ database_alias,
152
+ caller_in_atomic_block=caller_in_atomic_block,
153
+ )
154
+ if is_orm_backed
155
+ else nullcontext()
141
156
  )
142
- if instance_before is not None:
143
- try:
144
- delattr(instance_before, "_old_values")
145
- except AttributeError:
146
- pass
157
+ with transaction_context, ownership_context:
158
+ pre_data_change.send(
159
+ sender=sender,
160
+ instance=instance_before,
161
+ action=action,
162
+ **signal_kwargs,
163
+ )
164
+ old_relevant_values = getattr(instance_before, "_old_values", {})
165
+ pre_identification = deepcopy(
166
+ getattr(instance_before, "identification", None)
167
+ )
168
+ operation_context = (
169
+ authorize_data_change_operation(database_alias)
170
+ if is_orm_backed
171
+ else nullcontext()
172
+ )
173
+ with operation_context:
174
+ if isinstance(func, classmethod):
175
+ inner = cast(Callable[P, R], func.__func__)
176
+ result = inner(*args, **kwargs)
177
+ else:
178
+ result = func(*args, **kwargs)
179
+
180
+ context = current_calculation_run_context()
181
+ if context is not None:
182
+ context.clear_orm_bucket_results()
183
+ context.clear_bucket_indexes()
184
+ context.clear_trusted_orm_managers()
185
+
186
+ instance = result
187
+ identification = getattr(instance, "identification", None)
188
+ if identification is None:
189
+ identification = pre_identification
190
+
191
+ post_data_change.send(
192
+ sender=sender,
193
+ instance=instance,
194
+ previous_instance=instance_before,
195
+ identification=identification,
196
+ action=action,
197
+ old_relevant_values=old_relevant_values,
198
+ **signal_kwargs,
199
+ )
200
+ if instance_before is not None:
201
+ try:
202
+ delattr(instance_before, "_old_values")
203
+ except AttributeError:
204
+ pass
147
205
  completed = True
148
206
  except BaseException as error:
149
207
  primary_exc = error
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from collections.abc import Iterable
5
6
  from datetime import timedelta
6
7
  from typing import Any, ClassVar, cast
7
8
 
@@ -103,6 +104,11 @@ class ChatPendingConfirmation(models.Model):
103
104
  payload: Any = models.JSONField(default=dict)
104
105
  expires_at: Any = models.DateTimeField()
105
106
  resolved_at: Any = models.DateTimeField(null=True, blank=True)
107
+ unresolved_marker: Any = models.BooleanField(
108
+ null=True,
109
+ default=True,
110
+ editable=False,
111
+ )
106
112
  created_at: Any = models.DateTimeField(auto_now_add=True)
107
113
 
108
114
  class Meta:
@@ -113,12 +119,70 @@ class ChatPendingConfirmation(models.Model):
113
119
  ]
114
120
  constraints: ClassVar[list[models.BaseConstraint]] = [
115
121
  models.UniqueConstraint(
116
- fields=["conversation", "confirmation_id"],
117
- condition=models.Q(resolved_at__isnull=True),
122
+ fields=["conversation", "confirmation_id", "unresolved_marker"],
118
123
  name="gm_chat_pending_conv_conf_uniq",
119
- )
124
+ ),
125
+ models.CheckConstraint(
126
+ condition=(
127
+ models.Q(
128
+ resolved_at__isnull=True,
129
+ unresolved_marker=True,
130
+ unresolved_marker__isnull=False,
131
+ )
132
+ | models.Q(
133
+ resolved_at__isnull=False,
134
+ unresolved_marker__isnull=True,
135
+ )
136
+ ),
137
+ name="gm_chat_pending_resolution_state",
138
+ ),
120
139
  ]
121
140
 
141
+ def save(
142
+ self,
143
+ *args: Any,
144
+ force_insert: bool = False,
145
+ force_update: bool = False,
146
+ using: str | None = None,
147
+ update_fields: Iterable[str] | None = None,
148
+ ) -> None:
149
+ """Keep the internal uniqueness marker aligned with resolution state."""
150
+ if args:
151
+ parse_save_params = getattr(
152
+ cast(Any, self),
153
+ "_parse_save_params",
154
+ None,
155
+ )
156
+ if parse_save_params is None:
157
+ cast(Any, super().save)(
158
+ *args,
159
+ force_insert=force_insert,
160
+ force_update=force_update,
161
+ using=using,
162
+ update_fields=update_fields,
163
+ )
164
+ return
165
+ force_insert, force_update, using, update_fields = parse_save_params(
166
+ *args,
167
+ method_name="save",
168
+ force_insert=force_insert,
169
+ force_update=force_update,
170
+ using=using,
171
+ update_fields=update_fields,
172
+ )
173
+ if update_fields is not None:
174
+ update_fields = set(update_fields)
175
+ if update_fields is None or "resolved_at" in update_fields:
176
+ self.unresolved_marker = True if self.resolved_at is None else None
177
+ if update_fields is not None:
178
+ update_fields.add("unresolved_marker")
179
+ super().save(
180
+ force_insert=force_insert,
181
+ force_update=force_update,
182
+ using=using,
183
+ update_fields=update_fields,
184
+ )
185
+
122
186
  @classmethod
123
187
  def active_for_conversation(
124
188
  cls,
@@ -161,7 +225,8 @@ class ChatPendingConfirmation(models.Model):
161
225
  if pending is None:
162
226
  return None
163
227
  pending.resolved_at = current_time
164
- pending.save(update_fields=["resolved_at"])
228
+ pending.unresolved_marker = None
229
+ pending.save(update_fields=["resolved_at", "unresolved_marker"])
165
230
  return pending
166
231
 
167
232
 
@@ -283,7 +348,7 @@ def create_pending_confirmation(
283
348
  confirmation_id=confirmation_id,
284
349
  resolved_at__isnull=True,
285
350
  expires_at__lte=current_time,
286
- ).update(resolved_at=current_time)
351
+ ).update(resolved_at=current_time, unresolved_marker=None)
287
352
  unresolved_duplicate_exists = ChatPendingConfirmation.objects.filter(
288
353
  conversation=conversation,
289
354
  confirmation_id=confirmation_id,