GeneralManager 0.89.1__tar.gz → 0.89.3__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 (379) hide show
  1. {generalmanager-0.89.1/src/GeneralManager.egg-info → generalmanager-0.89.3}/PKG-INFO +1 -1
  2. {generalmanager-0.89.1 → generalmanager-0.89.3}/pyproject.toml +1 -1
  3. {generalmanager-0.89.1 → generalmanager-0.89.3/src/GeneralManager.egg-info}/PKG-INFO +1 -1
  4. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/GeneralManager.egg-info/SOURCES.txt +2 -0
  5. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/graphql_query_permissions.py +53 -6
  6. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/graphql_resolvers.py +16 -1
  7. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/base_interface.py +12 -3
  8. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/orm/mutations.py +242 -14
  9. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/orm_utils/payload_normalizer.py +54 -7
  10. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/request/__init__.py +20 -5
  11. generalmanager-0.89.3/src/general_manager/interface/capabilities/request/relation_inputs.py +249 -0
  12. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/manager/general_manager.py +21 -5
  13. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/permission/manager_based_permission.py +142 -18
  14. generalmanager-0.89.3/src/general_manager/permission/permission_data_manager.py +443 -0
  15. generalmanager-0.89.3/src/general_manager/permission/relation_aliases.py +58 -0
  16. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/search/authorization.py +4 -1
  17. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/search/backends/meilisearch.py +46 -1
  18. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/search/config.py +9 -1
  19. generalmanager-0.89.1/src/general_manager/permission/permission_data_manager.py +0 -132
  20. {generalmanager-0.89.1 → generalmanager-0.89.3}/LICENSE +0 -0
  21. {generalmanager-0.89.1 → generalmanager-0.89.3}/README.md +0 -0
  22. {generalmanager-0.89.1 → generalmanager-0.89.3}/setup.cfg +0 -0
  23. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/GeneralManager.egg-info/dependency_links.txt +0 -0
  24. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/GeneralManager.egg-info/requires.txt +0 -0
  25. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/GeneralManager.egg-info/top_level.txt +0 -0
  26. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/__init__.py +0 -0
  27. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/_types/__init__.py +0 -0
  28. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/_types/api.py +0 -0
  29. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/_types/bucket.py +0 -0
  30. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/_types/cache.py +0 -0
  31. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/_types/dataframes.py +0 -0
  32. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/_types/factory.py +0 -0
  33. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/_types/general_manager.py +0 -0
  34. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/_types/interface.py +0 -0
  35. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/_types/manager.py +0 -0
  36. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/_types/measurement.py +0 -0
  37. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/_types/permission.py +0 -0
  38. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/_types/rule.py +0 -0
  39. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/_types/search.py +0 -0
  40. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/_types/utils.py +0 -0
  41. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/__init__.py +0 -0
  42. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/graphql.py +0 -0
  43. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/graphql_as_of.py +0 -0
  44. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/graphql_errors.py +0 -0
  45. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/graphql_filter_contract.py +0 -0
  46. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/graphql_groups.py +0 -0
  47. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/graphql_identifiers.py +0 -0
  48. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/graphql_mutations.py +0 -0
  49. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/graphql_ordering.py +0 -0
  50. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/graphql_output.py +0 -0
  51. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/graphql_prefetch.py +0 -0
  52. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/graphql_relations.py +0 -0
  53. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/graphql_search.py +0 -0
  54. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/graphql_subscription_consumer.py +0 -0
  55. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/graphql_subscriptions.py +0 -0
  56. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/graphql_type.py +0 -0
  57. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/graphql_units.py +0 -0
  58. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/graphql_value_contract.py +0 -0
  59. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/graphql_view.py +0 -0
  60. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/graphql_warmup.py +0 -0
  61. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/graphql_warmup_registry.py +0 -0
  62. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/graphql_warmup_tasks.py +0 -0
  63. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/mutation.py +0 -0
  64. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/mutation_controls.py +0 -0
  65. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/notification_batching.py +0 -0
  66. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/property.py +0 -0
  67. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/registry.py +0 -0
  68. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/remote_api.py +0 -0
  69. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/remote_invalidation.py +0 -0
  70. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/api/remote_invalidation_client.py +0 -0
  71. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/apps.py +0 -0
  72. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/as_of.py +0 -0
  73. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/bootstrap.py +0 -0
  74. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/bucket/__init__.py +0 -0
  75. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/bucket/_materialized_bucket.py +0 -0
  76. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/bucket/_ordering.py +0 -0
  77. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/bucket/base_bucket.py +0 -0
  78. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/bucket/calculation_bucket.py +0 -0
  79. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/bucket/database_bucket.py +0 -0
  80. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/bucket/excel_bucket.py +0 -0
  81. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/bucket/group_bucket.py +0 -0
  82. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/bucket/indexing.py +0 -0
  83. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/bucket/projection.py +0 -0
  84. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/bucket/request_bucket.py +0 -0
  85. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/cache/__init__.py +0 -0
  86. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/cache/_dependency_graph.py +0 -0
  87. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/cache/batch_refresh.py +0 -0
  88. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/cache/cache_decorator.py +0 -0
  89. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/cache/cache_tracker.py +0 -0
  90. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/cache/data_change_context.py +0 -0
  91. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/cache/dependency_cache.py +0 -0
  92. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/cache/dependency_index.py +0 -0
  93. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/cache/dependency_matching.py +0 -0
  94. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/cache/dependency_publish.py +0 -0
  95. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/cache/dependency_shards.py +0 -0
  96. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/cache/model_dependency_collector.py +0 -0
  97. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/cache/run_context.py +0 -0
  98. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/cache/run_context_lru.py +0 -0
  99. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/cache/signals.py +0 -0
  100. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/cache/transaction_barrier.py +0 -0
  101. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/__init__.py +0 -0
  102. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/audit.py +0 -0
  103. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/audit_identity.py +0 -0
  104. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/bootstrap.py +0 -0
  105. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/checks.py +0 -0
  106. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/consumer.py +0 -0
  107. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/context.py +0 -0
  108. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/errors.py +0 -0
  109. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/evals/__init__.py +0 -0
  110. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/evals/__main__.py +0 -0
  111. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/evals/baseline.py +0 -0
  112. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/evals/datasets/basic_queries.yaml +0 -0
  113. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/evals/datasets/demo_readiness.yaml +0 -0
  114. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/evals/datasets/edge_cases.yaml +0 -0
  115. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/evals/datasets/follow_ups.yaml +0 -0
  116. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/evals/datasets/large_schema.yaml +0 -0
  117. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/evals/datasets/multi_hop.yaml +0 -0
  118. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/evals/datasets/planned_orchestration.yaml +0 -0
  119. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/evals/diagnostics.py +0 -0
  120. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/evals/execution.py +0 -0
  121. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/evals/fingerprints.py +0 -0
  122. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/evals/fixtures.py +0 -0
  123. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/evals/judges/__init__.py +0 -0
  124. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/evals/judges/answer_quality.py +0 -0
  125. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/evals/judges/contract.py +0 -0
  126. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/evals/judges/result_accuracy.py +0 -0
  127. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/evals/judges/result_set.py +0 -0
  128. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/evals/judges/tool_sequence.py +0 -0
  129. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/evals/outcomes.py +0 -0
  130. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/evals/readiness.py +0 -0
  131. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/evals/runner.py +0 -0
  132. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/evals/traces.py +0 -0
  133. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/graphql_contract.py +0 -0
  134. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/grounding.py +0 -0
  135. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/models.py +0 -0
  136. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/mutation_inputs.py +0 -0
  137. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/__init__.py +0 -0
  138. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/action_contract.py +0 -0
  139. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/binding_diagnostics.py +0 -0
  140. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/budget.py +0 -0
  141. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/calculation_scope.py +0 -0
  142. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/calculations.py +0 -0
  143. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/catalog.py +0 -0
  144. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/category_binding.py +0 -0
  145. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/choice_context.py +0 -0
  146. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/clarification.py +0 -0
  147. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/config.py +0 -0
  148. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/contract.py +0 -0
  149. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/conversion.py +0 -0
  150. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/cross_query.py +0 -0
  151. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/deferred_calculations.py +0 -0
  152. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/deferred_schema.py +0 -0
  153. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/events.py +0 -0
  154. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/evidence.py +0 -0
  155. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/evidence_selection.py +0 -0
  156. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/group_actions.py +0 -0
  157. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/models.py +0 -0
  158. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/planner.py +0 -0
  159. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/planner_context.py +0 -0
  160. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/provider_calls.py +0 -0
  161. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/resolver.py +0 -0
  162. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/scheduler.py +0 -0
  163. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/schema_projection.py +0 -0
  164. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/selector_clarification.py +0 -0
  165. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/selector_question.py +0 -0
  166. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/synthesis.py +0 -0
  167. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/planned/validation.py +0 -0
  168. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/providers/__init__.py +0 -0
  169. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/providers/_shared.py +0 -0
  170. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/providers/anthropic.py +0 -0
  171. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/providers/base.py +0 -0
  172. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/providers/google.py +0 -0
  173. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/providers/ollama.py +0 -0
  174. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/providers/openai.py +0 -0
  175. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/rate_limits.py +0 -0
  176. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/routing.py +0 -0
  177. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/schema_index.py +0 -0
  178. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/schema_inspection.py +0 -0
  179. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/settings.py +0 -0
  180. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/signals.py +0 -0
  181. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/system_prompt.py +0 -0
  182. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/tool_metadata.py +0 -0
  183. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/tools.py +0 -0
  184. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/turns.py +0 -0
  185. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/chat/views.py +0 -0
  186. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/conf.py +0 -0
  187. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/dataframes/__init__.py +0 -0
  188. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/dataframes/measurements.py +0 -0
  189. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/factory/__init__.py +0 -0
  190. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/factory/auto_factory.py +0 -0
  191. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/factory/factories.py +0 -0
  192. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/factory/factory_methods.py +0 -0
  193. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/__init__.py +0 -0
  194. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/bundles/__init__.py +0 -0
  195. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/bundles/calculation.py +0 -0
  196. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/bundles/database.py +0 -0
  197. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/bundles/excel.py +0 -0
  198. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/bundles/remote_manager.py +0 -0
  199. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/bundles/request.py +0 -0
  200. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/__init__.py +0 -0
  201. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/base.py +0 -0
  202. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/builtin.py +0 -0
  203. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/calculation/__init__.py +0 -0
  204. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/calculation/_compat.py +0 -0
  205. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/calculation/input_resolution.py +0 -0
  206. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/calculation/lifecycle.py +0 -0
  207. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/configuration.py +0 -0
  208. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/core/observability.py +0 -0
  209. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/core/utils.py +0 -0
  210. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/excel/__init__.py +0 -0
  211. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/exceptions.py +0 -0
  212. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/existing_model/__init__.py +0 -0
  213. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/existing_model/_compat.py +0 -0
  214. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/existing_model/resolution.py +0 -0
  215. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/factory.py +0 -0
  216. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/orm/__init__.py +0 -0
  217. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/orm/_compat.py +0 -0
  218. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/orm/bulk.py +0 -0
  219. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/orm/create_many.py +0 -0
  220. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/orm/history.py +0 -0
  221. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/orm/history_query.py +0 -0
  222. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/orm/lifecycle.py +0 -0
  223. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/orm/support.py +0 -0
  224. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/orm_utils/__init__.py +0 -0
  225. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/orm_utils/collection_probes.py +0 -0
  226. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/orm_utils/django_manager_utils.py +0 -0
  227. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/orm_utils/field_descriptors.py +0 -0
  228. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/orm_utils/update_state.py +0 -0
  229. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/read_only/__init__.py +0 -0
  230. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/read_only/_compat.py +0 -0
  231. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/read_only/_types.py +0 -0
  232. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/read_only/lifecycle.py +0 -0
  233. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/read_only/management.py +0 -0
  234. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/registry.py +0 -0
  235. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/capabilities/remote_manager.py +0 -0
  236. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/excel.py +0 -0
  237. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/excel_store.py +0 -0
  238. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/excel_workbook.py +0 -0
  239. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/infrastructure/__init__.py +0 -0
  240. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/infrastructure/startup_hooks.py +0 -0
  241. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/infrastructure/system_checks.py +0 -0
  242. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/interfaces/__init__.py +0 -0
  243. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/interfaces/calculation.py +0 -0
  244. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/interfaces/database.py +0 -0
  245. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/interfaces/excel.py +0 -0
  246. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/interfaces/existing_model.py +0 -0
  247. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/interfaces/read_only.py +0 -0
  248. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/interfaces/remote_manager.py +0 -0
  249. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/interfaces/request.py +0 -0
  250. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/manifests/__init__.py +0 -0
  251. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/manifests/capability_builder.py +0 -0
  252. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/manifests/capability_manifest.py +0 -0
  253. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/manifests/capability_models.py +0 -0
  254. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/orm_interface.py +0 -0
  255. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/requests.py +0 -0
  256. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/unit_contract.py +0 -0
  257. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/utils/__init__.py +0 -0
  258. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/utils/database_interface_protocols.py +0 -0
  259. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/utils/errors.py +0 -0
  260. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/utils/history.py +0 -0
  261. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/interface/utils/models.py +0 -0
  262. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/logging.py +0 -0
  263. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/management/commands/chat_cleanup.py +0 -0
  264. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/management/commands/cleanup_upload_intents.py +0 -0
  265. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/management/commands/graphql_warmup.py +0 -0
  266. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/management/commands/graphql_warmup_refresh_due.py +0 -0
  267. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/management/commands/search_index.py +0 -0
  268. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/management/commands/search_reconcile.py +0 -0
  269. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/management/commands/seed_manager_landscape.py +0 -0
  270. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/management/commands/shell.py +0 -0
  271. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/management/commands/sync_read_only.py +0 -0
  272. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/management/commands/workflow_drain_outbox.py +0 -0
  273. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/management/commands/workflow_replay_dead_letters.py +0 -0
  274. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/manager/__init__.py +0 -0
  275. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/manager/bulk_create.py +0 -0
  276. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/manager/group_manager.py +0 -0
  277. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/manager/input.py +0 -0
  278. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/manager/meta.py +0 -0
  279. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/measurement/__init__.py +0 -0
  280. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/measurement/measurement.py +0 -0
  281. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/measurement/measurement_field.py +0 -0
  282. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/metrics/__init__.py +0 -0
  283. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/metrics/graphql.py +0 -0
  284. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/migrations/0001_initial.py +0 -0
  285. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/migrations/0002_workflow_outbox_scaling_indexes.py +0 -0
  286. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/migrations/0003_workflow_execution_correlation_constraint.py +0 -0
  287. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/migrations/0004_chat_persistence_models.py +0 -0
  288. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/migrations/0004_search_index_state.py +0 -0
  289. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/migrations/0005_merge_chat_search.py +0 -0
  290. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/migrations/0006_chat_pending_confirmation_scoped_ids.py +0 -0
  291. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/migrations/0007_upload_intent.py +0 -0
  292. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/migrations/0008_upload_finalization_versions.py +0 -0
  293. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/migrations/0009_upload_cleanup_state.py +0 -0
  294. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/migrations/0010_chat_pending_confirmation_portable_uniqueness.py +0 -0
  295. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/migrations/0011_search_index_state_dirty_generation.py +0 -0
  296. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/migrations/0012_chat_context_watermarks.py +0 -0
  297. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/migrations/__init__.py +0 -0
  298. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/models.py +0 -0
  299. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/permission/__init__.py +0 -0
  300. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/permission/audit.py +0 -0
  301. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/permission/base_permission.py +0 -0
  302. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/permission/graphql_capabilities.py +0 -0
  303. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/permission/mutation_permission.py +0 -0
  304. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/permission/permission_checks.py +0 -0
  305. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/permission/utils.py +0 -0
  306. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/public_api_registry.py +0 -0
  307. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/py.typed +0 -0
  308. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/rule/__init__.py +0 -0
  309. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/rule/handler.py +0 -0
  310. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/rule/rule.py +0 -0
  311. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/search/__init__.py +0 -0
  312. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/search/async_tasks.py +0 -0
  313. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/search/backend.py +0 -0
  314. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/search/backend_registry.py +0 -0
  315. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/search/backends/__init__.py +0 -0
  316. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/search/backends/dev.py +0 -0
  317. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/search/backends/opensearch.py +0 -0
  318. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/search/backends/typesense.py +0 -0
  319. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/search/checks.py +0 -0
  320. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/search/identity.py +0 -0
  321. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/search/indexer.py +0 -0
  322. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/search/invalidation.py +0 -0
  323. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/search/m2m_invalidation.py +0 -0
  324. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/search/models.py +0 -0
  325. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/search/projection.py +0 -0
  326. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/search/reconciliation.py +0 -0
  327. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/search/registry.py +0 -0
  328. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/search/tasks.py +0 -0
  329. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/search/utils.py +0 -0
  330. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/seeding/__init__.py +0 -0
  331. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/seeding/manager_landscape.py +0 -0
  332. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/uploads/__init__.py +0 -0
  333. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/uploads/adapters.py +0 -0
  334. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/uploads/checks.py +0 -0
  335. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/uploads/config.py +0 -0
  336. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/uploads/errors.py +0 -0
  337. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/uploads/finalization.py +0 -0
  338. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/uploads/graphql.py +0 -0
  339. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/uploads/graphql_types.py +0 -0
  340. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/uploads/metrics.py +0 -0
  341. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/uploads/models.py +0 -0
  342. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/uploads/public.py +0 -0
  343. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/uploads/s3.py +0 -0
  344. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/uploads/services.py +0 -0
  345. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/uploads/tokens.py +0 -0
  346. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/uploads/types.py +0 -0
  347. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/uploads/urls.py +0 -0
  348. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/uploads/views.py +0 -0
  349. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/utils/__init__.py +0 -0
  350. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/utils/_args_to_kwargs.py +0 -0
  351. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/utils/_cache_key_encoder.py +0 -0
  352. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/utils/_make_cache_key.py +0 -0
  353. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/utils/_none_to_zero.py +0 -0
  354. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/utils/executable_contract.py +0 -0
  355. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/utils/filter_parser.py +0 -0
  356. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/utils/format_string.py +0 -0
  357. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/utils/json_encoder.py +0 -0
  358. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/utils/path_mapping.py +0 -0
  359. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/utils/public_api.py +0 -0
  360. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/utils/testing.py +0 -0
  361. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/utils/type_checks.py +0 -0
  362. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/workflow/__init__.py +0 -0
  363. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/workflow/actions.py +0 -0
  364. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/workflow/backend_registry.py +0 -0
  365. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/workflow/backends/__init__.py +0 -0
  366. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/workflow/backends/celery.py +0 -0
  367. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/workflow/backends/local.py +0 -0
  368. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/workflow/backends/n8n.py +0 -0
  369. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/workflow/config.py +0 -0
  370. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/workflow/engine.py +0 -0
  371. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/workflow/event_registry.py +0 -0
  372. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/workflow/events.py +0 -0
  373. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/workflow/models.py +0 -0
  374. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/workflow/signal_bridge.py +0 -0
  375. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/workflow/tasks.py +0 -0
  376. {generalmanager-0.89.1 → generalmanager-0.89.3}/src/general_manager/workflow/telemetry.py +0 -0
  377. {generalmanager-0.89.1 → generalmanager-0.89.3}/tests/test_settings.py +0 -0
  378. {generalmanager-0.89.1 → generalmanager-0.89.3}/tests/test_urls.py +0 -0
  379. {generalmanager-0.89.1 → generalmanager-0.89.3}/tests/testing_asgi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GeneralManager
3
- Version: 0.89.1
3
+ Version: 0.89.3
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.89.1"
7
+ version = "0.89.3"
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.89.1
3
+ Version: 0.89.3
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
@@ -232,6 +232,7 @@ src/general_manager/interface/capabilities/read_only/_types.py
232
232
  src/general_manager/interface/capabilities/read_only/lifecycle.py
233
233
  src/general_manager/interface/capabilities/read_only/management.py
234
234
  src/general_manager/interface/capabilities/request/__init__.py
235
+ src/general_manager/interface/capabilities/request/relation_inputs.py
235
236
  src/general_manager/interface/infrastructure/__init__.py
236
237
  src/general_manager/interface/infrastructure/startup_hooks.py
237
238
  src/general_manager/interface/infrastructure/system_checks.py
@@ -296,6 +297,7 @@ src/general_manager/permission/manager_based_permission.py
296
297
  src/general_manager/permission/mutation_permission.py
297
298
  src/general_manager/permission/permission_checks.py
298
299
  src/general_manager/permission/permission_data_manager.py
300
+ src/general_manager/permission/relation_aliases.py
299
301
  src/general_manager/permission/utils.py
300
302
  src/general_manager/rule/__init__.py
301
303
  src/general_manager/rule/handler.py
@@ -219,7 +219,7 @@ class _NativeQueryStructure:
219
219
  for name in names:
220
220
  if name not in self.additional:
221
221
  self.additional[name] = additional_local_read_permission(
222
- self.permission, name
222
+ self.permission, name, manager=self.manager
223
223
  )
224
224
  if self.additional[name] is None:
225
225
  return None
@@ -459,7 +459,8 @@ def authorize_query_fields(
459
459
  if getattr_static(permission, "__based_on__", None) is not None:
460
460
  return True
461
461
  return any(
462
- additional_local_read_permission(permission, name) is not False
462
+ additional_local_read_permission(permission, name, manager=type(instance))
463
+ is not False
463
464
  for name in names
464
465
  )
465
466
 
@@ -490,8 +491,37 @@ def authorize_query_fields(
490
491
  aliases, names, native_relations = _operand_field_names(
491
492
  type(instance), field, attributes, native_paths=native_paths
492
493
  )
493
- if budget_additional_permissions_only and needs_additional_budget(
494
- instance, names
494
+ # A standard delegated policy already resolves its concrete target
495
+ # during the normal field check. Inspect that same permission object,
496
+ # not a second relation read or a static declared target type.
497
+ from general_manager.permission.manager_based_permission import (
498
+ _can_prepare_local_read_definitions,
499
+ additional_resolved_read_permission,
500
+ )
501
+ from general_manager.permission.base_permission import BasePermission
502
+
503
+ permission = getattr_static(instance, "Permission", None)
504
+ resolved_budget = (
505
+ budget_additional_permissions_only
506
+ and getattr_static(permission, "__based_on__", None) is not None
507
+ and _can_prepare_local_read_definitions(permission)
508
+ )
509
+ charged = False
510
+
511
+ def charge_resolved(resolved: BasePermission, name: str) -> None:
512
+ """Charge this operand once when its resolved policy adds or may add restrictions."""
513
+ nonlocal charged
514
+ if (
515
+ not charged
516
+ and additional_resolved_read_permission(resolved, name) is not False
517
+ ):
518
+ budget.charge()
519
+ charged = True
520
+
521
+ if (
522
+ budget_additional_permissions_only
523
+ and not resolved_budget
524
+ and needs_additional_budget(instance, names)
495
525
  ):
496
526
  budget.charge()
497
527
  known_fields = set(attributes) | set(instance.identification)
@@ -500,8 +530,25 @@ def authorize_query_fields(
500
530
  known_fields.update(instance.Interface.input_fields)
501
531
  if not names.intersection(known_fields):
502
532
  raise QueryFieldPermissionError(field)
503
- if any(not check_read_permission(instance, info, name) for name in names):
504
- raise QueryFieldPermissionError(field)
533
+ for name in names:
534
+ if resolved_budget:
535
+
536
+ def before_check(
537
+ resolved: BasePermission, field_name: str = name
538
+ ) -> None:
539
+ """Account for the exact resolved permission before its normal field check."""
540
+ charge_resolved(resolved, field_name)
541
+
542
+ allowed = check_read_permission(
543
+ instance,
544
+ info,
545
+ name,
546
+ before_check=before_check,
547
+ )
548
+ else:
549
+ allowed = check_read_permission(instance, info, name)
550
+ if not allowed:
551
+ raise QueryFieldPermissionError(field)
505
552
  for relation in sorted(native_relations):
506
553
  # An ORM attname is a scalar spelling of a related field. Give it
507
554
  # the same target row/leaf gates as an explicit relation path.
@@ -1140,6 +1140,8 @@ def check_read_permission_for_user(
1140
1140
  instance: GeneralManager,
1141
1141
  user: object,
1142
1142
  field_name: str,
1143
+ *,
1144
+ before_check: Callable[[BasePermission], None] | None = None,
1143
1145
  ) -> bool:
1144
1146
  """
1145
1147
  Return ``True`` if *user* may read *field_name* on *instance*.
@@ -1147,10 +1149,15 @@ def check_read_permission_for_user(
1147
1149
  When the manager defines a Permission class, this calls
1148
1150
  ``Permission(instance, user).check_permission("read", field_name)``.
1149
1151
  Managers without a Permission class default to allowing the field read.
1152
+ The internal ``before_check`` hook may account for the resolved policy,
1153
+ without constructing it twice or replacing its authorization decision.
1150
1154
  """
1151
1155
  PermissionClass: type[BasePermission] | None = getattr(instance, "Permission", None)
1152
1156
  if PermissionClass:
1153
- return PermissionClass(instance, user).check_permission("read", field_name)
1157
+ permission = PermissionClass(instance, user)
1158
+ if before_check is not None:
1159
+ before_check(permission)
1160
+ return permission.check_permission("read", field_name)
1154
1161
  return True
1155
1162
 
1156
1163
 
@@ -1158,6 +1165,8 @@ def check_read_permission(
1158
1165
  instance: GeneralManager,
1159
1166
  info: GraphQLResolveInfo,
1160
1167
  field_name: str,
1168
+ *,
1169
+ before_check: Callable[[BasePermission], None] | None = None,
1161
1170
  ) -> bool:
1162
1171
  """
1163
1172
  Return ``True`` if the request user may read *field_name* on *instance*.
@@ -1165,7 +1174,13 @@ def check_read_permission(
1165
1174
  When the manager defines a Permission class, this calls
1166
1175
  ``Permission(instance, user).check_permission("read", field_name)``.
1167
1176
  Managers without a Permission class default to allowing the field read.
1177
+ The internal ``before_check`` hook may account for the resolved policy,
1178
+ without constructing it twice or replacing its authorization decision.
1168
1179
  """
1180
+ if before_check is not None:
1181
+ return check_read_permission_for_user(
1182
+ instance, info.context.user, field_name, before_check=before_check
1183
+ )
1169
1184
  return check_read_permission_for_user(instance, info.context.user, field_name)
1170
1185
 
1171
1186
 
@@ -1183,7 +1183,6 @@ class InterfaceBase(ABC):
1183
1183
  The capability-level create result mapping.
1184
1184
  """
1185
1185
  reject_historical_mutation()
1186
- observer = cls.get_capability_handler("observability")
1187
1186
 
1188
1187
  def _invoke() -> dict[str, object]:
1189
1188
  """
@@ -1201,12 +1200,22 @@ class InterfaceBase(ABC):
1201
1200
  return create_handler(cls, *args, **kwargs)
1202
1201
  raise NotImplementedError(f"{cls.__name__} does not support create.")
1203
1202
 
1203
+ return cls._observe_create(args, kwargs, _invoke)
1204
+
1205
+ @classmethod
1206
+ def _observe_create(
1207
+ cls,
1208
+ args: tuple[object, ...],
1209
+ kwargs: dict[str, object],
1210
+ invoke: Callable[[], dict[str, object]],
1211
+ ) -> dict[str, object]:
1212
+ """Share the public create observation envelope with ORM manager writes."""
1204
1213
  return cls._execute_with_observability(
1205
1214
  target=cls,
1206
1215
  operation="create",
1207
1216
  payload={"args": args, "kwargs": kwargs},
1208
- func=_invoke,
1209
- observer=observer,
1217
+ func=invoke,
1218
+ observer=cls.get_capability_handler("observability"),
1210
1219
  )
1211
1220
 
1212
1221
  @classmethod
@@ -5,11 +5,14 @@ from __future__ import annotations
5
5
  from collections.abc import Iterator
6
6
  from contextlib import AbstractContextManager, contextmanager
7
7
  from contextvars import ContextVar
8
- from typing import TYPE_CHECKING, Any, ClassVar, cast
8
+ from copy import copy, deepcopy
9
+ from dataclasses import dataclass, field
10
+ from typing import TYPE_CHECKING, Any, ClassVar, Protocol, cast
9
11
 
10
12
  from django.contrib.auth import get_user_model
11
13
  from django.db import DEFAULT_DB_ALIAS, models, router, transaction
12
14
  from django.db.models import NOT_PROVIDED
15
+ from django.db.models.fields.related_descriptors import ForwardManyToOneDescriptor
13
16
 
14
17
  from general_manager.cache.data_change_context import (
15
18
  mark_data_change_unchanged,
@@ -53,7 +56,9 @@ from .support import (
53
56
  )
54
57
 
55
58
  if TYPE_CHECKING: # pragma: no cover
59
+ from django.db.models.fields.reverse_related import ForeignObjectRel
56
60
  from general_manager.interface.orm_interface import OrmInterfaceBase
61
+ from general_manager.manager.general_manager import GeneralManager
57
62
 
58
63
  type OrmInterfaceClass = type["OrmInterfaceBase[models.Model]"]
59
64
  type OrmInterfaceInstance = "OrmInterfaceBase[models.Model]"
@@ -61,6 +66,110 @@ type MutationPayload = dict[str, object]
61
66
  type ManyToManyPayload = dict[str, list[object]]
62
67
  type MutationResult = dict[str, object]
63
68
 
69
+
70
+ class _CreatePermissionTargetChangedError(PermissionError):
71
+ """The prepared create no longer targets the database or key authorized."""
72
+
73
+ def __init__(self) -> None:
74
+ super().__init__("The authorized ORM create relation target changed.")
75
+
76
+
77
+ class _UnboundRoutedCreatePermissionError(PermissionError):
78
+ """A custom writer cannot bind its dynamic route to the permission check."""
79
+
80
+ def __init__(self) -> None:
81
+ super().__init__(
82
+ "Resolving a related object during a custom ORM create "
83
+ "requires a fixed database alias."
84
+ )
85
+
86
+
87
+ @dataclass(slots=True)
88
+ class _RoutedCreatePermission:
89
+ """Share one prepared model between its permission check and write."""
90
+
91
+ manager: type[GeneralManager]
92
+ payload: MutationPayload
93
+ interface: OrmInterfaceClass
94
+ shares_source: bool
95
+ source: models.Model | None = None
96
+ routing_hint: models.Model | None = None
97
+ checked_relations: dict[str, tuple[models.ForeignKey[models.Model], object]] = (
98
+ field(default_factory=dict)
99
+ )
100
+
101
+ def get_source(self) -> models.Model:
102
+ """Initialize Django defaults only when a permission needs the source."""
103
+ if not self.shares_source:
104
+ raise _UnboundRoutedCreatePermissionError
105
+ if self.source is None:
106
+ self.source = self.interface._model()
107
+ return self.source
108
+
109
+ @property
110
+ def database_alias(self) -> str | None:
111
+ """Return the chosen database once the check resolves a related row."""
112
+ return self.routing_hint._state.db if self.routing_hint is not None else None
113
+
114
+ def verify_target(
115
+ self, instance: models.Model, configured_database: str | None
116
+ ) -> None:
117
+ """Reject routing or relation-key changes introduced by model validation."""
118
+ if self.database_alias is not None and self.database_alias != (
119
+ configured_database
120
+ or router.db_for_write(type(instance), instance=instance)
121
+ ):
122
+ raise _CreatePermissionTargetChangedError
123
+ if any(
124
+ relation.target_field.to_python(getattr(instance, attname)) != value
125
+ for attname, (relation, value) in self.checked_relations.items()
126
+ ):
127
+ raise _CreatePermissionTargetChangedError
128
+
129
+
130
+ _routed_create_check = ContextVar[_RoutedCreatePermission | None](
131
+ "routed_create_check", default=None
132
+ )
133
+ _routed_create_write = ContextVar[tuple[models.Model, _RoutedCreatePermission] | None](
134
+ "routed_create_write", default=None
135
+ )
136
+
137
+
138
+ def _copy_create_source(
139
+ source: models.Model, *, isolate_defaults: bool = False
140
+ ) -> models.Model:
141
+ """Copy ORM state, with isolated field defaults for retryable writes."""
142
+ instance = object.__new__(type(source))
143
+ instance.__dict__.update(source.__dict__)
144
+ if isolate_defaults:
145
+ for concrete in source._meta.concrete_fields:
146
+ if concrete.attname in source.__dict__:
147
+ instance.__dict__[concrete.attname] = deepcopy(
148
+ source.__dict__[concrete.attname]
149
+ )
150
+ instance._state = copy(source._state)
151
+ vars(instance._state)["fields_cache"] = dict(
152
+ cast(dict[str, object], source._state.fields_cache)
153
+ )
154
+ return instance
155
+
156
+
157
+ @contextmanager
158
+ def _bind_routed_create_write(
159
+ instance: models.Model,
160
+ permission: _RoutedCreatePermission | None,
161
+ ) -> Iterator[None]:
162
+ """Give the existing save hook the authorization for this exact model."""
163
+ if permission is None:
164
+ yield
165
+ return
166
+ token = _routed_create_write.set((instance, permission))
167
+ try:
168
+ yield
169
+ finally:
170
+ _routed_create_write.reset(token)
171
+
172
+
64
173
  _save_with_history_savepoint = ContextVar(
65
174
  "save_with_history_savepoint",
66
175
  default=True,
@@ -94,6 +203,37 @@ def _mutation_atomic(
94
203
  return transaction.atomic(savepoint=False)
95
204
 
96
205
 
206
+ class _ModelPkState(Protocol):
207
+ """Django's PK completeness check, absent from older stubs."""
208
+
209
+ def _is_pk_set(self) -> bool: ...
210
+
211
+
212
+ def _discard_alternate_key_cache(
213
+ instance: models.Model, name: str, related: models.Model
214
+ ) -> None:
215
+ """Keep Django assignment checks and reload the current alternate-key owner."""
216
+ descriptor = getattr(type(instance), name, None)
217
+ if not isinstance(descriptor, ForwardManyToOneDescriptor):
218
+ return
219
+ relation = descriptor.field
220
+ if (
221
+ not isinstance(relation, models.ForeignKey)
222
+ or relation.target_field.primary_key
223
+ or not cast(_ModelPkState, related)._is_pk_set()
224
+ or not relation.is_cached(instance)
225
+ ):
226
+ # Leave unsaved objects cached so Django still rejects them at save.
227
+ return
228
+ relation.delete_cached_value(instance)
229
+ reverse = cast("ForeignObjectRel", relation.remote_field)
230
+ if (
231
+ not reverse.multiple
232
+ and reverse.get_cached_value(related, default=None) is instance
233
+ ):
234
+ reverse.delete_cached_value(related)
235
+
236
+
97
237
  class OrmMutationCapability(BaseCapability):
98
238
  """Common utilities to modify ORM instances."""
99
239
 
@@ -144,6 +284,8 @@ class OrmMutationCapability(BaseCapability):
144
284
  raise InvalidFieldValueError(key, value) from error
145
285
  except TypeError as error:
146
286
  raise InvalidFieldTypeError(key, error) from error
287
+ if isinstance(value, models.Model):
288
+ _discard_alternate_key_cache(instance, key, value)
147
289
  return instance
148
290
 
149
291
  return call_with_observability(
@@ -202,6 +344,15 @@ class OrmMutationCapability(BaseCapability):
202
344
  """
203
345
  support = get_support_capability(interface_cls)
204
346
  database_alias = support.get_database_alias(interface_cls)
347
+ create_write = _routed_create_write.get()
348
+ create_permission = (
349
+ create_write[1]
350
+ if create_write is not None and create_write[0] is instance
351
+ else None
352
+ )
353
+ write_database = (
354
+ create_permission.database_alias if create_permission else None
355
+ ) or database_alias
205
356
  from general_manager.manager.bulk_create import (
206
357
  CreateManyUnsupportedError,
207
358
  enclosing_create_many_batch,
@@ -216,7 +367,7 @@ class OrmMutationCapability(BaseCapability):
216
367
  if database_alias:
217
368
  instance._state.db = database_alias
218
369
  atomic_context = _mutation_atomic(
219
- database_alias,
370
+ write_database,
220
371
  savepoint=_save_with_history_savepoint.get(),
221
372
  )
222
373
  with atomic_context:
@@ -237,8 +388,10 @@ class OrmMutationCapability(BaseCapability):
237
388
  != batch.database_alias
238
389
  ):
239
390
  raise CreateManyUnsupportedError.routing()
240
- if database_alias:
241
- instance.save(using=database_alias)
391
+ if create_permission is not None:
392
+ create_permission.verify_target(instance, database_alias)
393
+ if write_database:
394
+ instance.save(using=write_database)
242
395
  else:
243
396
  instance.save()
244
397
  return instance.pk
@@ -371,6 +524,16 @@ class OrmCreateCapability(BaseCapability):
371
524
  wrapped.
372
525
  """
373
526
  _ = args
527
+ return self._create(interface_cls, kwargs)
528
+
529
+ def _create(
530
+ self,
531
+ interface_cls: OrmInterfaceClass,
532
+ kwargs: MutationPayload,
533
+ *,
534
+ permission: _RoutedCreatePermission | None = None,
535
+ ) -> MutationResult:
536
+ """Persist a create using the initialized source shared with its check."""
374
537
  payload_snapshot = {"kwargs": dict(kwargs)}
375
538
 
376
539
  def _perform() -> MutationResult:
@@ -399,6 +562,10 @@ class OrmCreateCapability(BaseCapability):
399
562
  if enclosing_create_many_batch() is not None:
400
563
  validate_create_many_record(normalized_simple)
401
564
  mutation = _mutation_capability_for(interface_cls)
565
+ source = permission.source if permission is not None else None
566
+ authorized_database = (
567
+ permission.database_alias if permission is not None else None
568
+ )
402
569
  if has_upload_candidates(normalized_simple):
403
570
  prepared = prepare_upload_claims(
404
571
  interface_cls,
@@ -407,10 +574,18 @@ class OrmCreateCapability(BaseCapability):
407
574
  actor_id=creator_id,
408
575
  target_pk=None,
409
576
  )
577
+ if authorized_database not in (None, prepared.database_alias):
578
+ raise _CreatePermissionTargetChangedError
410
579
 
411
580
  def _create_claimed() -> MutationResult:
412
581
  locked = lock_upload_claims(prepared)
413
- instance = interface_cls._model()
582
+ # An upload may retry the transaction. Each attempt starts
583
+ # with the same authorized defaults and a fresh model state.
584
+ instance = (
585
+ _copy_create_source(source, isolate_defaults=True)
586
+ if source is not None
587
+ else interface_cls._model()
588
+ )
414
589
  ordinary_values = {
415
590
  key: value
416
591
  for key, value in normalized_simple.items()
@@ -431,12 +606,13 @@ class OrmCreateCapability(BaseCapability):
431
606
  instance,
432
607
  reserved_values,
433
608
  )
434
- claimed_pk = mutation.save_with_history(
435
- interface_cls,
436
- claimed_instance,
437
- creator_id=creator_id,
438
- history_comment=history_comment,
439
- )
609
+ with _bind_routed_create_write(claimed_instance, permission):
610
+ claimed_pk = mutation.save_with_history(
611
+ interface_cls,
612
+ claimed_instance,
613
+ creator_id=creator_id,
614
+ history_comment=history_comment,
615
+ )
440
616
  mutation.apply_many_to_many(
441
617
  interface_cls,
442
618
  claimed_instance,
@@ -448,12 +624,16 @@ class OrmCreateCapability(BaseCapability):
448
624
 
449
625
  return run_upload_transaction(prepared, _create_claimed)
450
626
  instance = mutation.assign_simple_attributes(
451
- interface_cls, interface_cls._model(), normalized_simple
627
+ interface_cls,
628
+ source if source is not None else interface_cls._model(),
629
+ normalized_simple,
452
630
  )
453
631
  support = get_support_capability(interface_cls)
454
632
  database_alias = support.get_database_alias(interface_cls)
455
- atomic_context = _mutation_atomic(database_alias)
456
- with atomic_context:
633
+ with (
634
+ _mutation_atomic(authorized_database or database_alias),
635
+ _bind_routed_create_write(instance, permission),
636
+ ):
457
637
  with _without_save_with_history_savepoint():
458
638
  pk = mutation.save_with_history(
459
639
  interface_cls,
@@ -477,6 +657,54 @@ class OrmCreateCapability(BaseCapability):
477
657
  )
478
658
 
479
659
 
660
+ def _create_for_manager(
661
+ interface: OrmInterfaceClass,
662
+ manager: type[GeneralManager],
663
+ payload: MutationPayload,
664
+ creator_id: int | None,
665
+ history_comment: str | None,
666
+ ) -> MutationResult:
667
+ """Use the prepared model for permission checks in canonical routed creates."""
668
+ from general_manager.interface.base_interface import InterfaceBase
669
+
670
+ create = interface.create
671
+ if not router.routers:
672
+ manager.Permission.check_create_permission(payload, manager, creator_id)
673
+ return create(creator_id=creator_id, history_comment=history_comment, **payload)
674
+
675
+ handler = interface.get_capability_handler("create")
676
+ builtin_create = (
677
+ isinstance(handler, OrmCreateCapability)
678
+ and getattr(handler.create, "__func__", None) is OrmCreateCapability.create
679
+ and getattr(create, "__func__", None)
680
+ is getattr(InterfaceBase.create, "__func__", None)
681
+ )
682
+ mutation = interface.get_capability_handler("orm_mutation")
683
+ builtin_save = (
684
+ isinstance(mutation, OrmMutationCapability)
685
+ and getattr(mutation.save_with_history, "__func__", None)
686
+ is OrmMutationCapability.save_with_history
687
+ )
688
+ permission = _RoutedCreatePermission(
689
+ manager, payload, interface, shares_source=builtin_create and builtin_save
690
+ )
691
+ token = _routed_create_check.set(permission)
692
+ try:
693
+ manager.Permission.check_create_permission(payload, manager, creator_id)
694
+ finally:
695
+ _routed_create_check.reset(token)
696
+
697
+ if not builtin_create:
698
+ return create(creator_id=creator_id, history_comment=history_comment, **payload)
699
+ assert isinstance(handler, OrmCreateCapability)
700
+ kwargs = {"creator_id": creator_id, "history_comment": history_comment, **payload}
701
+ return interface._observe_create(
702
+ (),
703
+ kwargs,
704
+ lambda: handler._create(interface, kwargs, permission=permission),
705
+ )
706
+
707
+
480
708
  class OrmUpdateCapability(BaseCapability):
481
709
  """Update existing ORM instances."""
482
710
 
@@ -31,10 +31,15 @@ class PayloadNormalizer:
31
31
  self._field_names = {field.name for field in model._meta.get_fields()}
32
32
  self._many_to_many_fields = {field.name for field in model._meta.many_to_many}
33
33
  self._relation_fields = {
34
- field.name
34
+ field.name: field
35
35
  for field in model._meta.get_fields()
36
36
  if isinstance(field, (models.ForeignKey, models.OneToOneField))
37
37
  }
38
+ self._relation_aliases = {
39
+ alias: field
40
+ for field in self._relation_fields.values()
41
+ for alias in (field.name, field.attname)
42
+ }
38
43
 
39
44
  # region filter/exclude helpers
40
45
  def normalize_filter_kwargs(self, kwargs: PayloadMapping) -> PayloadMapping:
@@ -139,18 +144,56 @@ class PayloadNormalizer:
139
144
  remaining_kwargs[key] = value
140
145
  return remaining_kwargs, many_kwargs
141
146
 
147
+ def get_relation_field(self, name: str) -> models.ForeignKey[models.Model] | None:
148
+ """Resolve an actual forward relation or its raw identifier attribute."""
149
+ return self._relation_aliases.get(name)
150
+
151
+ @staticmethod
152
+ def get_relation_identifier(
153
+ field: models.ForeignKey[models.Model], value: object
154
+ ) -> object:
155
+ """Extract the relation's target-field value, not necessarily its PK."""
156
+ if _is_general_manager_instance(value):
157
+ manager = cast("GeneralManager", value)
158
+ value = (
159
+ manager.identification["id"]
160
+ if field.target_field.primary_key
161
+ else getattr(manager, field.target_field.attname)
162
+ )
163
+ elif isinstance(value, models.Model):
164
+ value = getattr(value, field.target_field.attname)
165
+ return field.target_field.to_python(value)
166
+
167
+ def resolve_relation_aliases(self, kwargs: PayloadMapping) -> PayloadMapping:
168
+ """Prefer an explicitly supplied raw ID over the same relation object.
169
+
170
+ Only aliases present together in this mapping compete. In particular,
171
+ an explicit raw ID of ``None`` wins too. Callers handling updates must
172
+ apply this to submitted values, not to an old-state overlay.
173
+ """
174
+ return {
175
+ key: value
176
+ for key, value in kwargs.items()
177
+ if key not in self._relation_fields
178
+ or self._relation_fields[key].attname not in kwargs
179
+ }
180
+
142
181
  def normalize_simple_values(self, kwargs: PayloadMapping) -> PayloadMapping:
143
182
  """
144
183
  Normalize simple (single-valued) payload entries by converting general-manager objects to their identifier form and adjusting keys.
145
184
 
146
- For each key/value in `kwargs`, if `value` is recognized as a general-manager instance its identifier is used as the value; when the original key does not end with `_id`, the key is renamed to `{key}_id`. Non-manager values are kept unchanged.
185
+ General-manager values for declared relations use the target field's
186
+ value, including non-primary-key ``to_field`` relations, under the
187
+ relation's raw ID attribute. Other general-manager values retain the
188
+ identifier conversion and ``_id`` suffix behavior. Django model objects
189
+ retain object assignment so Django can validate their type and routing.
147
190
  Returns a new mapping and does not mutate `kwargs`. Relation-key
148
191
  renaming applies only to forward `ForeignKey` and `OneToOneField`
149
192
  names cached from the model metadata. Reverse relations, generic
150
193
  relations, and many-to-many fields are not relation-renamed here. If
151
- both a relation key and its `<relation>_id` key appear, the later
152
- normalized key overwrites the earlier value according to input
153
- iteration order.
194
+ both a relation key and its raw ID key are explicitly present, the raw
195
+ ID wins independently of iteration order, including when it is None.
196
+ A single supplied alias retains its usual meaning.
154
197
 
155
198
  Parameters:
156
199
  kwargs: Mapping of field names to single values to normalize.
@@ -159,11 +202,15 @@ class PayloadNormalizer:
159
202
  A new mapping with manager values replaced by their identifiers and keys suffixed with `_id` when appropriate.
160
203
  """
161
204
  normalized: PayloadMapping = {}
162
- for key, value in kwargs.items():
205
+ for key, value in self.resolve_relation_aliases(kwargs).items():
163
206
  normalized_key = key
164
207
  normalized_value = value
165
208
  manager_value = self._maybe_general_manager(value)
166
- if manager_value is not None and not key.endswith("_id"):
209
+ relation = self.get_relation_field(key)
210
+ if manager_value is not None and relation is not None:
211
+ normalized_key = relation.attname
212
+ normalized_value = self.get_relation_identifier(relation, value)
213
+ elif manager_value is not None and not key.endswith("_id"):
167
214
  normalized_key = f"{key}_id"
168
215
  normalized_value = manager_value
169
216
  elif manager_value is not None: