GeneralManager 0.63.2__tar.gz → 0.64.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. {generalmanager-0.63.2/src/GeneralManager.egg-info → generalmanager-0.64.1}/PKG-INFO +1 -1
  2. {generalmanager-0.63.2 → generalmanager-0.64.1}/pyproject.toml +1 -1
  3. {generalmanager-0.63.2 → generalmanager-0.64.1/src/GeneralManager.egg-info}/PKG-INFO +1 -1
  4. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/GeneralManager.egg-info/SOURCES.txt +2 -0
  5. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/_types/api.py +2 -0
  6. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/api/graphql.py +256 -79
  7. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/api/graphql_mutations.py +47 -10
  8. generalmanager-0.64.1/src/general_manager/api/graphql_relations.py +72 -0
  9. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/api/graphql_resolvers.py +10 -3
  10. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/api/graphql_search.py +34 -12
  11. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/api/graphql_subscriptions.py +50 -0
  12. generalmanager-0.64.1/src/general_manager/api/notification_batching.py +121 -0
  13. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/api/remote_invalidation.py +51 -16
  14. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/bootstrap.py +8 -1
  15. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/cache/dependency_index.py +1 -1
  16. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/migrations/0007_upload_intent.py +5 -5
  17. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/public_api_registry.py +4 -0
  18. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/search/backends/meilisearch.py +1 -1
  19. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/uploads/finalization.py +4 -4
  20. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/uploads/models.py +5 -5
  21. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/utils/filter_parser.py +1 -1
  22. {generalmanager-0.63.2 → generalmanager-0.64.1}/LICENSE +0 -0
  23. {generalmanager-0.63.2 → generalmanager-0.64.1}/README.md +0 -0
  24. {generalmanager-0.63.2 → generalmanager-0.64.1}/setup.cfg +0 -0
  25. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/GeneralManager.egg-info/dependency_links.txt +0 -0
  26. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/GeneralManager.egg-info/requires.txt +0 -0
  27. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/GeneralManager.egg-info/top_level.txt +0 -0
  28. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/__init__.py +0 -0
  29. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/_types/__init__.py +0 -0
  30. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/_types/bucket.py +0 -0
  31. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/_types/cache.py +0 -0
  32. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/_types/dataframes.py +0 -0
  33. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/_types/factory.py +0 -0
  34. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/_types/general_manager.py +0 -0
  35. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/_types/interface.py +0 -0
  36. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/_types/manager.py +0 -0
  37. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/_types/measurement.py +0 -0
  38. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/_types/permission.py +0 -0
  39. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/_types/rule.py +0 -0
  40. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/_types/search.py +0 -0
  41. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/_types/utils.py +0 -0
  42. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/api/__init__.py +0 -0
  43. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/api/graphql_errors.py +0 -0
  44. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/api/graphql_prefetch.py +0 -0
  45. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/api/graphql_subscription_consumer.py +0 -0
  46. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/api/graphql_view.py +0 -0
  47. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/api/graphql_warmup.py +0 -0
  48. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/api/graphql_warmup_registry.py +0 -0
  49. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/api/graphql_warmup_tasks.py +0 -0
  50. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/api/mutation.py +0 -0
  51. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/api/property.py +0 -0
  52. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/api/registry.py +0 -0
  53. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/api/remote_api.py +0 -0
  54. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/api/remote_invalidation_client.py +0 -0
  55. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/apps.py +0 -0
  56. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/bucket/__init__.py +0 -0
  57. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/bucket/base_bucket.py +0 -0
  58. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/bucket/calculation_bucket.py +0 -0
  59. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/bucket/database_bucket.py +0 -0
  60. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/bucket/group_bucket.py +0 -0
  61. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/bucket/indexing.py +0 -0
  62. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/bucket/request_bucket.py +0 -0
  63. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/cache/__init__.py +0 -0
  64. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/cache/cache_decorator.py +0 -0
  65. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/cache/cache_tracker.py +0 -0
  66. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/cache/dependency_cache.py +0 -0
  67. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/cache/dependency_matching.py +0 -0
  68. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/cache/dependency_publish.py +0 -0
  69. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/cache/dependency_shards.py +0 -0
  70. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/cache/model_dependency_collector.py +0 -0
  71. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/cache/run_context.py +0 -0
  72. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/cache/signals.py +0 -0
  73. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/__init__.py +0 -0
  74. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/audit.py +0 -0
  75. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/bootstrap.py +0 -0
  76. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/checks.py +0 -0
  77. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/consumer.py +0 -0
  78. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/errors.py +0 -0
  79. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/evals/__init__.py +0 -0
  80. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/evals/__main__.py +0 -0
  81. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/evals/baseline.py +0 -0
  82. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/evals/datasets/basic_queries.yaml +0 -0
  83. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/evals/datasets/demo_readiness.yaml +0 -0
  84. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/evals/datasets/edge_cases.yaml +0 -0
  85. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/evals/datasets/follow_ups.yaml +0 -0
  86. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/evals/datasets/large_schema.yaml +0 -0
  87. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/evals/datasets/multi_hop.yaml +0 -0
  88. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/evals/diagnostics.py +0 -0
  89. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/evals/fingerprints.py +0 -0
  90. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/evals/fixtures.py +0 -0
  91. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/evals/judges/__init__.py +0 -0
  92. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/evals/judges/answer_quality.py +0 -0
  93. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/evals/judges/contract.py +0 -0
  94. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/evals/judges/result_accuracy.py +0 -0
  95. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/evals/judges/tool_sequence.py +0 -0
  96. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/evals/readiness.py +0 -0
  97. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/evals/runner.py +0 -0
  98. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/evals/traces.py +0 -0
  99. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/grounding.py +0 -0
  100. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/models.py +0 -0
  101. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/providers/__init__.py +0 -0
  102. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/providers/_shared.py +0 -0
  103. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/providers/anthropic.py +0 -0
  104. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/providers/base.py +0 -0
  105. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/providers/google.py +0 -0
  106. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/providers/ollama.py +0 -0
  107. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/providers/openai.py +0 -0
  108. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/rate_limits.py +0 -0
  109. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/routing.py +0 -0
  110. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/schema_index.py +0 -0
  111. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/settings.py +0 -0
  112. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/signals.py +0 -0
  113. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/system_prompt.py +0 -0
  114. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/tool_metadata.py +0 -0
  115. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/tools.py +0 -0
  116. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/chat/views.py +0 -0
  117. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/conf.py +0 -0
  118. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/dataframes/__init__.py +0 -0
  119. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/dataframes/measurements.py +0 -0
  120. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/factory/__init__.py +0 -0
  121. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/factory/auto_factory.py +0 -0
  122. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/factory/factories.py +0 -0
  123. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/factory/factory_methods.py +0 -0
  124. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/__init__.py +0 -0
  125. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/base_interface.py +0 -0
  126. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/bundles/__init__.py +0 -0
  127. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/bundles/calculation.py +0 -0
  128. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/bundles/database.py +0 -0
  129. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/bundles/remote_manager.py +0 -0
  130. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/bundles/request.py +0 -0
  131. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/__init__.py +0 -0
  132. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/base.py +0 -0
  133. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/builtin.py +0 -0
  134. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/calculation/__init__.py +0 -0
  135. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/calculation/_compat.py +0 -0
  136. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/calculation/lifecycle.py +0 -0
  137. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/configuration.py +0 -0
  138. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/core/observability.py +0 -0
  139. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/core/utils.py +0 -0
  140. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/exceptions.py +0 -0
  141. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/existing_model/__init__.py +0 -0
  142. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/existing_model/_compat.py +0 -0
  143. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/existing_model/resolution.py +0 -0
  144. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/factory.py +0 -0
  145. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/orm/__init__.py +0 -0
  146. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/orm/_compat.py +0 -0
  147. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/orm/history.py +0 -0
  148. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/orm/lifecycle.py +0 -0
  149. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/orm/mutations.py +0 -0
  150. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/orm/support.py +0 -0
  151. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/orm_utils/__init__.py +0 -0
  152. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/orm_utils/django_manager_utils.py +0 -0
  153. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/orm_utils/field_descriptors.py +0 -0
  154. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/orm_utils/payload_normalizer.py +0 -0
  155. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/read_only/__init__.py +0 -0
  156. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/read_only/_compat.py +0 -0
  157. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/read_only/_types.py +0 -0
  158. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/read_only/lifecycle.py +0 -0
  159. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/read_only/management.py +0 -0
  160. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/registry.py +0 -0
  161. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/remote_manager.py +0 -0
  162. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/capabilities/request/__init__.py +0 -0
  163. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/infrastructure/__init__.py +0 -0
  164. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/infrastructure/startup_hooks.py +0 -0
  165. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/infrastructure/system_checks.py +0 -0
  166. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/interfaces/__init__.py +0 -0
  167. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/interfaces/calculation.py +0 -0
  168. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/interfaces/database.py +0 -0
  169. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/interfaces/existing_model.py +0 -0
  170. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/interfaces/read_only.py +0 -0
  171. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/interfaces/remote_manager.py +0 -0
  172. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/interfaces/request.py +0 -0
  173. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/manifests/__init__.py +0 -0
  174. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/manifests/capability_builder.py +0 -0
  175. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/manifests/capability_manifest.py +0 -0
  176. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/manifests/capability_models.py +0 -0
  177. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/orm_interface.py +0 -0
  178. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/requests.py +0 -0
  179. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/utils/__init__.py +0 -0
  180. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/utils/database_interface_protocols.py +0 -0
  181. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/utils/errors.py +0 -0
  182. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/utils/history.py +0 -0
  183. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/interface/utils/models.py +0 -0
  184. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/logging.py +0 -0
  185. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/management/commands/chat_cleanup.py +0 -0
  186. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/management/commands/cleanup_upload_intents.py +0 -0
  187. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/management/commands/graphql_warmup.py +0 -0
  188. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/management/commands/graphql_warmup_refresh_due.py +0 -0
  189. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/management/commands/search_index.py +0 -0
  190. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/management/commands/search_reconcile.py +0 -0
  191. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/management/commands/seed_manager_landscape.py +0 -0
  192. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/management/commands/shell.py +0 -0
  193. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/management/commands/workflow_drain_outbox.py +0 -0
  194. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/management/commands/workflow_replay_dead_letters.py +0 -0
  195. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/manager/__init__.py +0 -0
  196. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/manager/general_manager.py +0 -0
  197. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/manager/group_manager.py +0 -0
  198. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/manager/input.py +0 -0
  199. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/manager/meta.py +0 -0
  200. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/measurement/__init__.py +0 -0
  201. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/measurement/measurement.py +0 -0
  202. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/measurement/measurement_field.py +0 -0
  203. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/metrics/__init__.py +0 -0
  204. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/metrics/graphql.py +0 -0
  205. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/migrations/0001_initial.py +0 -0
  206. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/migrations/0002_workflow_outbox_scaling_indexes.py +0 -0
  207. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/migrations/0003_workflow_execution_correlation_constraint.py +0 -0
  208. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/migrations/0004_chat_persistence_models.py +0 -0
  209. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/migrations/0004_search_index_state.py +0 -0
  210. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/migrations/0005_merge_chat_search.py +0 -0
  211. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/migrations/0006_chat_pending_confirmation_scoped_ids.py +0 -0
  212. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/migrations/0008_upload_finalization_versions.py +0 -0
  213. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/migrations/0009_upload_cleanup_state.py +0 -0
  214. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/migrations/__init__.py +0 -0
  215. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/models.py +0 -0
  216. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/permission/__init__.py +0 -0
  217. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/permission/audit.py +0 -0
  218. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/permission/base_permission.py +0 -0
  219. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/permission/graphql_capabilities.py +0 -0
  220. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/permission/manager_based_permission.py +0 -0
  221. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/permission/mutation_permission.py +0 -0
  222. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/permission/permission_checks.py +0 -0
  223. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/permission/permission_data_manager.py +0 -0
  224. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/permission/utils.py +0 -0
  225. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/py.typed +0 -0
  226. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/rule/__init__.py +0 -0
  227. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/rule/handler.py +0 -0
  228. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/rule/rule.py +0 -0
  229. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/search/__init__.py +0 -0
  230. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/search/async_tasks.py +0 -0
  231. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/search/backend.py +0 -0
  232. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/search/backend_registry.py +0 -0
  233. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/search/backends/__init__.py +0 -0
  234. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/search/backends/dev.py +0 -0
  235. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/search/backends/opensearch.py +0 -0
  236. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/search/backends/typesense.py +0 -0
  237. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/search/config.py +0 -0
  238. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/search/indexer.py +0 -0
  239. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/search/models.py +0 -0
  240. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/search/reconciliation.py +0 -0
  241. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/search/registry.py +0 -0
  242. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/search/tasks.py +0 -0
  243. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/search/utils.py +0 -0
  244. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/seeding/__init__.py +0 -0
  245. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/seeding/manager_landscape.py +0 -0
  246. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/uploads/__init__.py +0 -0
  247. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/uploads/adapters.py +0 -0
  248. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/uploads/checks.py +0 -0
  249. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/uploads/config.py +0 -0
  250. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/uploads/errors.py +0 -0
  251. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/uploads/graphql.py +0 -0
  252. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/uploads/graphql_types.py +0 -0
  253. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/uploads/metrics.py +0 -0
  254. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/uploads/public.py +0 -0
  255. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/uploads/s3.py +0 -0
  256. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/uploads/services.py +0 -0
  257. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/uploads/tokens.py +0 -0
  258. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/uploads/types.py +0 -0
  259. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/uploads/urls.py +0 -0
  260. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/uploads/views.py +0 -0
  261. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/utils/__init__.py +0 -0
  262. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/utils/args_to_kwargs.py +0 -0
  263. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/utils/format_string.py +0 -0
  264. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/utils/json_encoder.py +0 -0
  265. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/utils/make_cache_key.py +0 -0
  266. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/utils/none_to_zero.py +0 -0
  267. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/utils/path_mapping.py +0 -0
  268. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/utils/public_api.py +0 -0
  269. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/utils/testing.py +0 -0
  270. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/utils/type_checks.py +0 -0
  271. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/workflow/__init__.py +0 -0
  272. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/workflow/actions.py +0 -0
  273. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/workflow/backend_registry.py +0 -0
  274. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/workflow/backends/__init__.py +0 -0
  275. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/workflow/backends/celery.py +0 -0
  276. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/workflow/backends/local.py +0 -0
  277. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/workflow/backends/n8n.py +0 -0
  278. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/workflow/config.py +0 -0
  279. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/workflow/engine.py +0 -0
  280. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/workflow/event_registry.py +0 -0
  281. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/workflow/events.py +0 -0
  282. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/workflow/models.py +0 -0
  283. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/workflow/signal_bridge.py +0 -0
  284. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/workflow/tasks.py +0 -0
  285. {generalmanager-0.63.2 → generalmanager-0.64.1}/src/general_manager/workflow/telemetry.py +0 -0
  286. {generalmanager-0.63.2 → generalmanager-0.64.1}/tests/test_settings.py +0 -0
  287. {generalmanager-0.63.2 → generalmanager-0.64.1}/tests/test_urls.py +0 -0
  288. {generalmanager-0.63.2 → generalmanager-0.64.1}/tests/testing_asgi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GeneralManager
3
- Version: 0.63.2
3
+ Version: 0.64.1
4
4
  Summary: Modular Django-based data management framework with ORM, GraphQL, fine-grained permissions, rule validation, calculations and caching.
5
5
  Author-email: Tim Kleindick <tkleindick@yahoo.de>
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "GeneralManager"
7
- version = "0.63.2"
7
+ version = "0.64.1"
8
8
  description = "Modular Django-based data management framework with ORM, GraphQL, fine-grained permissions, rule validation, calculations and caching."
9
9
  readme = "README.md"
10
10
  authors = [{ name = "Tim Kleindick", email = "tkleindick@yahoo.de" }]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GeneralManager
3
- Version: 0.63.2
3
+ Version: 0.64.1
4
4
  Summary: Modular Django-based data management framework with ORM, GraphQL, fine-grained permissions, rule validation, calculations and caching.
5
5
  Author-email: Tim Kleindick <tkleindick@yahoo.de>
6
6
  License: MIT License
@@ -33,6 +33,7 @@ src/general_manager/api/graphql.py
33
33
  src/general_manager/api/graphql_errors.py
34
34
  src/general_manager/api/graphql_mutations.py
35
35
  src/general_manager/api/graphql_prefetch.py
36
+ src/general_manager/api/graphql_relations.py
36
37
  src/general_manager/api/graphql_resolvers.py
37
38
  src/general_manager/api/graphql_search.py
38
39
  src/general_manager/api/graphql_subscription_consumer.py
@@ -42,6 +43,7 @@ src/general_manager/api/graphql_warmup.py
42
43
  src/general_manager/api/graphql_warmup_registry.py
43
44
  src/general_manager/api/graphql_warmup_tasks.py
44
45
  src/general_manager/api/mutation.py
46
+ src/general_manager/api/notification_batching.py
45
47
  src/general_manager/api/property.py
46
48
  src/general_manager/api/registry.py
47
49
  src/general_manager/api/remote_api.py
@@ -56,6 +56,7 @@ __all__ = [
56
56
  "UploadTokenInvalidError",
57
57
  "UploadTransferConflictError",
58
58
  "UploadTransport",
59
+ "bulk_data_change_notifications",
59
60
  "graph_ql_mutation",
60
61
  "graph_ql_property",
61
62
  "register_upload_adapter",
@@ -115,5 +116,6 @@ from general_manager.uploads.errors import UploadTokenInvalidError
115
116
  from general_manager.uploads.errors import UploadTransferConflictError
116
117
  from general_manager.uploads.types import UploadTransport
117
118
  from general_manager.api.mutation import graph_ql_mutation
119
+ from general_manager.api.notification_batching import bulk_data_change_notifications
118
120
  from general_manager.api.property import graph_ql_property
119
121
  from general_manager.uploads.public import register_upload_adapter
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import asyncio
6
- from contextlib import suppress
6
+ from contextlib import asynccontextmanager
7
7
  from copy import deepcopy
8
8
  import re
9
9
  from types import UnionType
@@ -101,6 +101,7 @@ from general_manager.api.graphql_resolvers import (
101
101
  create_list_resolver as _create_list_resolver_fn,
102
102
  create_resolver as _create_resolver_fn,
103
103
  )
104
+ from general_manager.api.graphql_relations import resolve_general_manager_type
104
105
  from general_manager.api.graphql_search import (
105
106
  register_search_query as _register_search_query_fn,
106
107
  create_search_union as _create_search_union_fn,
@@ -113,10 +114,13 @@ from general_manager.api.graphql_search import (
113
114
  create_filter_options as _create_filter_options_fn,
114
115
  normalize_filter_input as _normalize_filter_input_fn,
115
116
  )
117
+ from general_manager.api.notification_batching import _queue_notification
116
118
  from general_manager.api.graphql_subscriptions import (
117
119
  get_channel_layer_safe as _get_channel_layer_fn,
118
120
  group_name as _group_name_fn,
119
121
  class_group_name as _class_group_name_fn,
122
+ refresh_group_name as _refresh_group_name_fn,
123
+ dispatch_subscription_event as _dispatch_subscription_event_fn,
120
124
  channel_listener as _channel_listener_fn,
121
125
  channel_message_listener as _channel_message_listener_fn,
122
126
  prime_graphql_properties as _prime_graphql_properties_fn,
@@ -155,6 +159,101 @@ GraphQLFilterNormalizer = Callable[
155
159
  dict[str, GraphQLFilterMapping],
156
160
  ]
157
161
  GraphQLMutationMap = dict[str, type[graphene.Mutation]]
162
+ _SUBSCRIPTION_CLEANUP_FAILURE_MESSAGE = "subscription cleanup failed"
163
+ _SUBSCRIPTION_ROLLBACK_FAILURE_MESSAGE = "subscription setup and rollback failed"
164
+ _SUBSCRIPTION_STREAM_CLEANUP_FAILURE_MESSAGE = "subscription stream and cleanup failed"
165
+
166
+
167
+ async def _cleanup_subscription_resources(
168
+ channel_layer: BaseChannelLayer,
169
+ channel_name: str,
170
+ joined_groups: Iterable[str],
171
+ listener_task: asyncio.Task[None] | None = None,
172
+ ) -> None:
173
+ """Stop a listener and attempt to discard every successfully joined group."""
174
+ cleanup_errors: list[BaseException] = []
175
+ if listener_task is not None:
176
+ listener_task.cancel()
177
+ try:
178
+ await listener_task
179
+ except asyncio.CancelledError:
180
+ pass
181
+ except BaseException as exc: # noqa: BLE001
182
+ cleanup_errors.append(exc)
183
+
184
+ for group in joined_groups:
185
+ try:
186
+ await channel_layer.group_discard(group, channel_name)
187
+ except BaseException as exc: # noqa: BLE001
188
+ cleanup_errors.append(exc)
189
+
190
+ if len(cleanup_errors) == 1:
191
+ raise cleanup_errors[0]
192
+ if cleanup_errors:
193
+ raise BaseExceptionGroup(
194
+ _SUBSCRIPTION_CLEANUP_FAILURE_MESSAGE,
195
+ cleanup_errors,
196
+ )
197
+
198
+
199
+ async def _join_subscription_groups(
200
+ channel_layer: BaseChannelLayer,
201
+ channel_name: str,
202
+ group_names: Iterable[str],
203
+ ) -> list[str]:
204
+ """Join groups and roll back every successful join if a later join fails."""
205
+ joined_groups: list[str] = []
206
+ try:
207
+ for group in group_names:
208
+ await channel_layer.group_add(group, channel_name)
209
+ joined_groups.append(group)
210
+ except BaseException as setup_error:
211
+ try:
212
+ await _cleanup_subscription_resources(
213
+ channel_layer,
214
+ channel_name,
215
+ joined_groups,
216
+ )
217
+ except BaseException as cleanup_error: # noqa: BLE001
218
+ raise BaseExceptionGroup(
219
+ _SUBSCRIPTION_ROLLBACK_FAILURE_MESSAGE,
220
+ [setup_error, cleanup_error],
221
+ ) from None
222
+ raise
223
+ return joined_groups
224
+
225
+
226
+ @asynccontextmanager
227
+ async def _subscription_cleanup_scope(
228
+ channel_layer: BaseChannelLayer,
229
+ channel_name: str,
230
+ joined_groups: Iterable[str],
231
+ listener_task: asyncio.Task[None],
232
+ ) -> AsyncIterator[None]:
233
+ """Clean subscription resources without masking an active stream failure."""
234
+ try:
235
+ yield
236
+ except BaseException as stream_error:
237
+ try:
238
+ await _cleanup_subscription_resources(
239
+ channel_layer,
240
+ channel_name,
241
+ joined_groups,
242
+ listener_task,
243
+ )
244
+ except BaseException as cleanup_error: # noqa: BLE001
245
+ raise BaseExceptionGroup(
246
+ _SUBSCRIPTION_STREAM_CLEANUP_FAILURE_MESSAGE,
247
+ [stream_error, cleanup_error],
248
+ ) from None
249
+ raise
250
+ else:
251
+ await _cleanup_subscription_resources(
252
+ channel_layer,
253
+ channel_name,
254
+ joined_groups,
255
+ listener_task,
256
+ )
158
257
 
159
258
 
160
259
  class GraphQL:
@@ -281,6 +380,15 @@ class GraphQL:
281
380
  """
282
381
  return _class_group_name_fn(manager_class)
283
382
 
383
+ @staticmethod
384
+ def _refresh_group_name(manager_class: type[GeneralManager]) -> str:
385
+ """
386
+ Compatibility wrapper for ``graphql_subscriptions.refresh_group_name(manager_class)``.
387
+
388
+ Returns the deterministic manager-wide refresh group name.
389
+ """
390
+ return _refresh_group_name_fn(manager_class)
391
+
284
392
  @staticmethod
285
393
  async def _channel_listener(
286
394
  channel_layer: BaseChannelLayer,
@@ -439,8 +547,13 @@ class GraphQL:
439
547
  # Map Attribute Types to Graphene Fields
440
548
  for field_name, field_info in interface_cls.get_attribute_types().items():
441
549
  field_type = field_info["type"]
442
- fields[field_name] = cls._map_field_to_graphene_read(
550
+ resolved_manager_type = resolve_general_manager_type(
443
551
  field_type,
552
+ cls.manager_registry,
553
+ )
554
+ resolved_field_type = resolved_manager_type or field_type
555
+ fields[field_name] = cls._map_field_to_graphene_read(
556
+ resolved_field_type,
444
557
  field_name,
445
558
  field_info,
446
559
  )
@@ -469,7 +582,10 @@ class GraphQL:
469
582
 
470
583
  fields[resolver_name] = resolve_stored_file
471
584
  else:
472
- fields[resolver_name] = cls._create_resolver(field_name, field_type)
585
+ fields[resolver_name] = cls._create_resolver(
586
+ field_name,
587
+ resolved_field_type,
588
+ )
473
589
 
474
590
  # handle GraphQLProperty attributes
475
591
  for (
@@ -487,9 +603,13 @@ class GraphQL:
487
603
 
488
604
  if origin in (list, tuple, set):
489
605
  element = type_args[0] if type_args else object
490
- if isinstance(element, type) and issubclass(element, GeneralManager):
606
+ manager_type = resolve_general_manager_type(
607
+ element,
608
+ cls.manager_registry,
609
+ )
610
+ if manager_type is not None:
491
611
  graphene_field = graphene.List(
492
- lambda elem=element: GraphQL.graphql_type_registry[
612
+ lambda elem=manager_type: GraphQL.graphql_type_registry[
493
613
  elem.__name__
494
614
  ]
495
615
  )
@@ -498,13 +618,22 @@ class GraphQL:
498
618
  element if isinstance(element, type) else str
499
619
  )
500
620
  graphene_field = graphene.List(base_type)
501
- resolved_type = element if isinstance(element, type) else str
621
+ resolved_type = manager_type or (
622
+ element if isinstance(element, type) else str
623
+ )
502
624
  else:
503
625
  resolved_type = (
504
626
  cast(type[object], type_args[0])
505
627
  if type_args
506
628
  else cast(type[object], raw_hint)
507
629
  )
630
+ resolved_type = (
631
+ resolve_general_manager_type(
632
+ resolved_type,
633
+ cls.manager_registry,
634
+ )
635
+ or resolved_type
636
+ )
508
637
  graphene_field = cls._map_field_to_graphene_read(
509
638
  resolved_type, attr_name
510
639
  )
@@ -714,7 +843,7 @@ class GraphQL:
714
843
  field_info,
715
844
  ) in generalManagerClass.Interface.get_attribute_types().items():
716
845
  field_type = field_info["type"]
717
- if safe_issubclass(field_type, GeneralManager):
846
+ if resolve_general_manager_type(field_type, GraphQL.manager_registry):
718
847
  continue
719
848
  else:
720
849
  sort_options.append(field_name)
@@ -863,7 +992,7 @@ class GraphQL:
863
992
 
864
993
  @staticmethod
865
994
  def _map_field_to_graphene_read(
866
- field_type: type,
995
+ field_type: object,
867
996
  field_name: str,
868
997
  field_info: Mapping[str, object] | None = None,
869
998
  ) -> object:
@@ -891,6 +1020,13 @@ class GraphQL:
891
1020
  Returns:
892
1021
  object: Graphene field or type configured for the attribute.
893
1022
  """
1023
+ manager_type = resolve_general_manager_type(
1024
+ field_type,
1025
+ GraphQL.manager_registry,
1026
+ )
1027
+ if manager_type is not None:
1028
+ field_type = manager_type
1029
+
894
1030
  if safe_issubclass(field_type, Measurement):
895
1031
  return graphene.Field(MeasurementType, target_unit=graphene.String())
896
1032
  elif safe_issubclass(field_type, GeneralManager):
@@ -925,8 +1061,9 @@ class GraphQL:
925
1061
  return graphene.Field(StoredFile)
926
1062
  if orm_field_kind == "image":
927
1063
  return graphene.Field(StoredImage)
1064
+ scalar_type = cast(type, field_type)
928
1065
  return GraphQL._map_field_to_graphene_base_type(
929
- field_type,
1066
+ scalar_type,
930
1067
  field_info,
931
1068
  )()
932
1069
 
@@ -1084,7 +1221,10 @@ class GraphQL:
1084
1221
  input_field_name,
1085
1222
  input_field,
1086
1223
  ) in generalManagerClass.Interface.input_fields.items():
1087
- if safe_issubclass(input_field.type, GeneralManager):
1224
+ if resolve_general_manager_type(
1225
+ input_field.type,
1226
+ cls.manager_registry,
1227
+ ):
1088
1228
  key = f"{input_field_name}_id"
1089
1229
  identification_fields[key] = graphene.Argument(
1090
1230
  graphene.ID, required=input_field.required
@@ -1352,7 +1492,7 @@ class GraphQL:
1352
1492
  """
1353
1493
  Stream subscription events for a specific manager instance identified by the provided arguments.
1354
1494
 
1355
- Yields an initial `SubscriptionEvent` with `action` set to `"snapshot"` containing the current manager instance, then yields `SubscriptionEvent`s for each subsequent action. For update events, `item` will be the re-instantiated manager instance or `None` if instantiation fails. The subscriber is registered on the manager's channel groups (including dependent managers' groups) and the channel subscriptions and background listener are cleaned up when the iterator is closed or cancelled.
1495
+ Yields an initial `SubscriptionEvent` with `action` set to `"snapshot"` containing the current manager instance, then yields `SubscriptionEvent`s for each subsequent action. For subsequent events, `item` will be the re-instantiated manager instance or `None` if instantiation fails. The subscriber is registered on the manager's channel groups (including dependent managers' groups) and the channel subscriptions and background listener are cleaned up when the iterator is closed or cancelled.
1356
1496
 
1357
1497
  Parameters:
1358
1498
  identification: Identification fields required to locate the manager instance (maps to the manager's identification signature).
@@ -1379,14 +1519,13 @@ class GraphQL:
1379
1519
  )
1380
1520
  except RuntimeError as exc:
1381
1521
  raise GraphQLError(str(exc)) from exc
1382
- channel_name = cast(str, await channel_layer.new_channel())
1383
- queue: asyncio.Queue[str] = asyncio.Queue[str]()
1384
1522
 
1385
1523
  group_names = {
1386
1524
  cls._group_name(
1387
1525
  generalManagerClass,
1388
1526
  instance.identification,
1389
- )
1527
+ ),
1528
+ cls._refresh_group_name(generalManagerClass),
1390
1529
  }
1391
1530
  dependencies = cls._resolve_subscription_dependencies(
1392
1531
  generalManagerClass,
@@ -1397,13 +1536,7 @@ class GraphQL:
1397
1536
  group_names.add(
1398
1537
  cls._group_name(dependency_class, dependency_identification)
1399
1538
  )
1400
-
1401
- for group in group_names:
1402
- await channel_layer.group_add(group, channel_name)
1403
-
1404
- listener_task = asyncio.create_task(
1405
- cls._channel_listener(channel_layer, channel_name, queue)
1406
- )
1539
+ group_names.add(cls._refresh_group_name(dependency_class))
1407
1540
 
1408
1541
  async def event_stream() -> AsyncIterator[SubscriptionEvent]:
1409
1542
  """
@@ -1415,7 +1548,22 @@ class GraphQL:
1415
1548
  Notes:
1416
1549
  When the iterator is closed or exits, the background listener task is cancelled and the subscription's channel group memberships are discarded.
1417
1550
  """
1418
- try:
1551
+ channel_name = cast(str, await channel_layer.new_channel())
1552
+ queue: asyncio.Queue[str] = asyncio.Queue[str]()
1553
+ joined_groups = await _join_subscription_groups(
1554
+ channel_layer,
1555
+ channel_name,
1556
+ group_names,
1557
+ )
1558
+ listener_task = asyncio.create_task(
1559
+ cls._channel_listener(channel_layer, channel_name, queue)
1560
+ )
1561
+ async with _subscription_cleanup_scope(
1562
+ channel_layer,
1563
+ channel_name,
1564
+ joined_groups,
1565
+ listener_task,
1566
+ ):
1419
1567
  clear_capability_context(info)
1420
1568
  yield SubscriptionEvent(item=instance, action="snapshot")
1421
1569
  while True:
@@ -1431,12 +1579,6 @@ class GraphQL:
1431
1579
  item = None
1432
1580
  clear_capability_context(info)
1433
1581
  yield SubscriptionEvent(item=item, action=action)
1434
- finally:
1435
- listener_task.cancel()
1436
- with suppress(asyncio.CancelledError):
1437
- await listener_task
1438
- for group in group_names:
1439
- await channel_layer.group_discard(group, channel_name)
1440
1582
 
1441
1583
  return event_stream()
1442
1584
 
@@ -1466,9 +1608,10 @@ class GraphQL:
1466
1608
  Stream future authorized change events for any instance of a manager class.
1467
1609
 
1468
1610
  Unlike single-entity subscriptions, class-wide subscriptions do not
1469
- yield an initial snapshot. Each channel event is hydrated and
1470
- checked against the request user's object-level read permission
1471
- before it is yielded.
1611
+ yield an initial snapshot. Aggregate refresh events without an
1612
+ identification carry no item; identified row events, including
1613
+ row-level refresh actions, are hydrated and checked against the
1614
+ request user's object-level read permission before they are yielded.
1472
1615
  """
1473
1616
  try:
1474
1617
  channel_layer = cast(
@@ -1476,25 +1619,41 @@ class GraphQL:
1476
1619
  )
1477
1620
  except RuntimeError as exc:
1478
1621
  raise GraphQLError(str(exc)) from exc
1479
- channel_name = cast(str, await channel_layer.new_channel())
1480
- queue: asyncio.Queue[GraphQLFieldMap] = asyncio.Queue()
1481
- group_name = cls._class_group_name(generalManagerClass)
1482
- await channel_layer.group_add(group_name, channel_name)
1483
- listener_task = asyncio.create_task(
1484
- cls._channel_message_listener(channel_layer, channel_name, queue)
1485
- )
1622
+ group_names = {
1623
+ cls._class_group_name(generalManagerClass),
1624
+ cls._refresh_group_name(generalManagerClass),
1625
+ }
1486
1626
 
1487
1627
  async def event_stream() -> AsyncIterator[SubscriptionEvent]:
1488
- try:
1628
+ channel_name = cast(str, await channel_layer.new_channel())
1629
+ queue: asyncio.Queue[GraphQLFieldMap] = asyncio.Queue()
1630
+ joined_groups = await _join_subscription_groups(
1631
+ channel_layer,
1632
+ channel_name,
1633
+ group_names,
1634
+ )
1635
+ listener_task = asyncio.create_task(
1636
+ cls._channel_message_listener(channel_layer, channel_name, queue)
1637
+ )
1638
+ async with _subscription_cleanup_scope(
1639
+ channel_layer,
1640
+ channel_name,
1641
+ joined_groups,
1642
+ listener_task,
1643
+ ):
1489
1644
  while True:
1490
1645
  message = await queue.get()
1491
1646
  if message.get("manager") != generalManagerClass.__name__:
1492
1647
  continue
1493
1648
  action = message.get("action")
1649
+ if not isinstance(action, str):
1650
+ continue
1494
1651
  identification = message.get("identification")
1495
- if not isinstance(action, str) or not isinstance(
1496
- identification, dict
1497
- ):
1652
+ if action == "refresh" and identification is None:
1653
+ clear_capability_context(info)
1654
+ yield SubscriptionEvent(item=None, action=action)
1655
+ continue
1656
+ if not isinstance(identification, dict):
1498
1657
  continue
1499
1658
  identification_copy = deepcopy(identification)
1500
1659
  try:
@@ -1509,11 +1668,6 @@ class GraphQL:
1509
1668
  continue
1510
1669
  clear_capability_context(info)
1511
1670
  yield SubscriptionEvent(item=item, action=action)
1512
- finally:
1513
- listener_task.cancel()
1514
- with suppress(asyncio.CancelledError):
1515
- await listener_task
1516
- await channel_layer.group_discard(group_name, channel_name)
1517
1671
 
1518
1672
  return event_stream()
1519
1673
 
@@ -1595,14 +1749,15 @@ class GraphQL:
1595
1749
  Send a "gm.subscription.event" message to the channel group corresponding to a changed GeneralManager instance.
1596
1750
 
1597
1751
  If the provided instance is a registered GeneralManager and a channel
1598
- layer is configured, publish a message containing the given action to the
1599
- instance channel group and the class-wide channel group. Identification
1600
- comes from the provided `identification` mapping when supplied,
1601
- otherwise from the changed instance, and is deep-copied before dispatch.
1602
- If the instance is `None`, the manager type is not registered, or no
1603
- channel layer is available, the function returns without dispatching.
1604
- Group dispatch failures are logged and do not stop attempts for the
1605
- remaining target groups.
1752
+ layer is configured, queue one manager-wide refresh while notification
1753
+ batching is active. Outside a batch, publish the row-level message to
1754
+ the instance and class-wide groups through one async bridge.
1755
+ Identification comes from the provided `identification` mapping when
1756
+ supplied, otherwise from the changed instance, and is deep-copied
1757
+ before dispatch. If the instance is `None`, the manager type is not
1758
+ registered, or no channel layer is available, the function returns
1759
+ without dispatching. Ordinary group dispatch failures are logged and
1760
+ do not stop attempts for remaining groups.
1606
1761
 
1607
1762
  Parameters:
1608
1763
  sender (type[GeneralManager] | GeneralManager): The signal sender; either a GeneralManager subclass or an instance.
@@ -1646,35 +1801,57 @@ class GraphQL:
1646
1801
  )
1647
1802
  group_name = cls._group_name(manager_class, event_identification)
1648
1803
  class_group_name = cls._class_group_name(manager_class)
1649
- message = {
1804
+ message: dict[str, object] = {
1650
1805
  "type": "gm.subscription.event",
1651
1806
  "action": action,
1652
1807
  "manager": manager_class.__name__,
1653
1808
  "identification": event_identification,
1654
1809
  }
1655
- group_send = async_to_sync(channel_layer.group_send)
1656
- for target_group_name in (group_name, class_group_name):
1657
- try:
1658
- group_send(target_group_name, message)
1659
- except Exception as exc: # noqa: BLE001
1660
- logger.warning(
1661
- "failed to dispatch subscription event",
1662
- context={
1663
- "manager": manager_class.__name__,
1664
- "action": action,
1665
- "group": target_group_name,
1666
- "message": message,
1667
- },
1668
- exc_info=exc,
1669
- )
1670
- logger.debug(
1671
- "dispatched subscription event",
1672
- context={
1673
- "manager": manager_class.__name__,
1674
- "action": action,
1675
- "groups": [group_name, class_group_name],
1676
- },
1677
- )
1810
+ refresh_group_name = cls._refresh_group_name(manager_class)
1811
+ refresh_message: dict[str, object] = {
1812
+ "type": "gm.subscription.event",
1813
+ "action": "refresh",
1814
+ "manager": manager_class.__name__,
1815
+ }
1816
+ if _queue_notification(
1817
+ key=("graphql", refresh_group_name),
1818
+ group_send=channel_layer.group_send,
1819
+ group=refresh_group_name,
1820
+ message=refresh_message,
1821
+ ):
1822
+ return
1823
+
1824
+ target_groups = (group_name, class_group_name)
1825
+ try:
1826
+ dispatched = async_to_sync(_dispatch_subscription_event_fn)(
1827
+ channel_layer,
1828
+ target_groups,
1829
+ message,
1830
+ )
1831
+ except MemoryError:
1832
+ raise
1833
+ except Exception as exc: # noqa: BLE001
1834
+ logger.warning(
1835
+ "failed to dispatch subscription event",
1836
+ context={
1837
+ "manager": manager_class.__name__,
1838
+ "action": action,
1839
+ "target_groups": list(target_groups),
1840
+ "message": message,
1841
+ },
1842
+ exc_info=exc,
1843
+ )
1844
+ return
1845
+ if dispatched > 0:
1846
+ logger.debug(
1847
+ "dispatched subscription event",
1848
+ context={
1849
+ "manager": manager_class.__name__,
1850
+ "action": action,
1851
+ "target_groups": list(target_groups),
1852
+ "successful_group_count": dispatched,
1853
+ },
1854
+ )
1678
1855
 
1679
1856
 
1680
1857
  post_data_change.connect(GraphQL._handle_data_change, weak=False)