GeneralManager 0.59.0__tar.gz → 0.59.2__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 (255) hide show
  1. {generalmanager-0.59.0/src/GeneralManager.egg-info → generalmanager-0.59.2}/PKG-INFO +2 -2
  2. {generalmanager-0.59.0 → generalmanager-0.59.2}/pyproject.toml +2 -2
  3. {generalmanager-0.59.0 → generalmanager-0.59.2/src/GeneralManager.egg-info}/PKG-INFO +2 -2
  4. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/GeneralManager.egg-info/requires.txt +1 -1
  5. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/factory/auto_factory.py +86 -12
  6. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/factory/factories.py +193 -58
  7. {generalmanager-0.59.0 → generalmanager-0.59.2}/LICENSE +0 -0
  8. {generalmanager-0.59.0 → generalmanager-0.59.2}/README.md +0 -0
  9. {generalmanager-0.59.0 → generalmanager-0.59.2}/setup.cfg +0 -0
  10. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/GeneralManager.egg-info/SOURCES.txt +0 -0
  11. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/GeneralManager.egg-info/dependency_links.txt +0 -0
  12. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/GeneralManager.egg-info/top_level.txt +0 -0
  13. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/__init__.py +0 -0
  14. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/_types/__init__.py +0 -0
  15. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/_types/api.py +0 -0
  16. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/_types/bucket.py +0 -0
  17. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/_types/cache.py +0 -0
  18. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/_types/factory.py +0 -0
  19. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/_types/general_manager.py +0 -0
  20. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/_types/interface.py +0 -0
  21. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/_types/manager.py +0 -0
  22. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/_types/measurement.py +0 -0
  23. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/_types/permission.py +0 -0
  24. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/_types/rule.py +0 -0
  25. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/_types/search.py +0 -0
  26. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/_types/utils.py +0 -0
  27. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/api/__init__.py +0 -0
  28. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/api/graphql.py +0 -0
  29. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/api/graphql_errors.py +0 -0
  30. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/api/graphql_mutations.py +0 -0
  31. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/api/graphql_prefetch.py +0 -0
  32. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/api/graphql_resolvers.py +0 -0
  33. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/api/graphql_search.py +0 -0
  34. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/api/graphql_subscription_consumer.py +0 -0
  35. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/api/graphql_subscriptions.py +0 -0
  36. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/api/graphql_view.py +0 -0
  37. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/api/graphql_warmup.py +0 -0
  38. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/api/graphql_warmup_registry.py +0 -0
  39. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/api/graphql_warmup_tasks.py +0 -0
  40. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/api/mutation.py +0 -0
  41. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/api/property.py +0 -0
  42. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/api/registry.py +0 -0
  43. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/api/remote_api.py +0 -0
  44. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/api/remote_invalidation.py +0 -0
  45. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/api/remote_invalidation_client.py +0 -0
  46. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/apps.py +0 -0
  47. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/bootstrap.py +0 -0
  48. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/bucket/__init__.py +0 -0
  49. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/bucket/base_bucket.py +0 -0
  50. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/bucket/calculation_bucket.py +0 -0
  51. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/bucket/database_bucket.py +0 -0
  52. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/bucket/group_bucket.py +0 -0
  53. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/bucket/indexing.py +0 -0
  54. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/bucket/request_bucket.py +0 -0
  55. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/cache/__init__.py +0 -0
  56. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/cache/cache_decorator.py +0 -0
  57. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/cache/cache_tracker.py +0 -0
  58. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/cache/dependency_cache.py +0 -0
  59. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/cache/dependency_index.py +0 -0
  60. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/cache/dependency_matching.py +0 -0
  61. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/cache/dependency_publish.py +0 -0
  62. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/cache/dependency_shards.py +0 -0
  63. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/cache/model_dependency_collector.py +0 -0
  64. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/cache/run_context.py +0 -0
  65. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/cache/signals.py +0 -0
  66. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/__init__.py +0 -0
  67. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/audit.py +0 -0
  68. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/bootstrap.py +0 -0
  69. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/checks.py +0 -0
  70. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/consumer.py +0 -0
  71. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/errors.py +0 -0
  72. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/evals/__init__.py +0 -0
  73. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/evals/__main__.py +0 -0
  74. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/evals/baseline.py +0 -0
  75. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/evals/diagnostics.py +0 -0
  76. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/evals/fingerprints.py +0 -0
  77. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/evals/fixtures.py +0 -0
  78. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/evals/judges/__init__.py +0 -0
  79. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/evals/judges/answer_quality.py +0 -0
  80. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/evals/judges/contract.py +0 -0
  81. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/evals/judges/result_accuracy.py +0 -0
  82. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/evals/judges/tool_sequence.py +0 -0
  83. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/evals/readiness.py +0 -0
  84. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/evals/runner.py +0 -0
  85. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/evals/traces.py +0 -0
  86. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/grounding.py +0 -0
  87. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/models.py +0 -0
  88. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/providers/__init__.py +0 -0
  89. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/providers/_shared.py +0 -0
  90. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/providers/anthropic.py +0 -0
  91. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/providers/base.py +0 -0
  92. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/providers/google.py +0 -0
  93. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/providers/ollama.py +0 -0
  94. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/providers/openai.py +0 -0
  95. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/rate_limits.py +0 -0
  96. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/routing.py +0 -0
  97. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/schema_index.py +0 -0
  98. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/settings.py +0 -0
  99. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/signals.py +0 -0
  100. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/system_prompt.py +0 -0
  101. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/tool_metadata.py +0 -0
  102. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/tools.py +0 -0
  103. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/chat/views.py +0 -0
  104. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/conf.py +0 -0
  105. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/factory/__init__.py +0 -0
  106. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/factory/factory_methods.py +0 -0
  107. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/__init__.py +0 -0
  108. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/base_interface.py +0 -0
  109. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/bundles/__init__.py +0 -0
  110. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/bundles/calculation.py +0 -0
  111. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/bundles/database.py +0 -0
  112. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/bundles/remote_manager.py +0 -0
  113. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/bundles/request.py +0 -0
  114. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/__init__.py +0 -0
  115. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/base.py +0 -0
  116. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/builtin.py +0 -0
  117. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/calculation/__init__.py +0 -0
  118. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/calculation/_compat.py +0 -0
  119. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/calculation/lifecycle.py +0 -0
  120. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/configuration.py +0 -0
  121. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/core/observability.py +0 -0
  122. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/core/utils.py +0 -0
  123. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/exceptions.py +0 -0
  124. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/existing_model/__init__.py +0 -0
  125. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/existing_model/_compat.py +0 -0
  126. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/existing_model/resolution.py +0 -0
  127. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/factory.py +0 -0
  128. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/orm/__init__.py +0 -0
  129. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/orm/_compat.py +0 -0
  130. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/orm/history.py +0 -0
  131. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/orm/lifecycle.py +0 -0
  132. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/orm/mutations.py +0 -0
  133. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/orm/support.py +0 -0
  134. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/orm_utils/__init__.py +0 -0
  135. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/orm_utils/django_manager_utils.py +0 -0
  136. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/orm_utils/field_descriptors.py +0 -0
  137. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/orm_utils/payload_normalizer.py +0 -0
  138. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/read_only/__init__.py +0 -0
  139. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/read_only/_compat.py +0 -0
  140. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/read_only/_types.py +0 -0
  141. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/read_only/lifecycle.py +0 -0
  142. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/read_only/management.py +0 -0
  143. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/registry.py +0 -0
  144. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/remote_manager.py +0 -0
  145. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/capabilities/request/__init__.py +0 -0
  146. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/infrastructure/__init__.py +0 -0
  147. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/infrastructure/startup_hooks.py +0 -0
  148. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/infrastructure/system_checks.py +0 -0
  149. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/interfaces/__init__.py +0 -0
  150. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/interfaces/calculation.py +0 -0
  151. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/interfaces/database.py +0 -0
  152. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/interfaces/existing_model.py +0 -0
  153. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/interfaces/read_only.py +0 -0
  154. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/interfaces/remote_manager.py +0 -0
  155. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/interfaces/request.py +0 -0
  156. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/manifests/__init__.py +0 -0
  157. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/manifests/capability_builder.py +0 -0
  158. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/manifests/capability_manifest.py +0 -0
  159. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/manifests/capability_models.py +0 -0
  160. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/orm_interface.py +0 -0
  161. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/requests.py +0 -0
  162. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/utils/__init__.py +0 -0
  163. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/utils/database_interface_protocols.py +0 -0
  164. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/utils/errors.py +0 -0
  165. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/interface/utils/models.py +0 -0
  166. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/logging.py +0 -0
  167. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/management/commands/chat_cleanup.py +0 -0
  168. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/management/commands/graphql_warmup.py +0 -0
  169. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/management/commands/graphql_warmup_refresh_due.py +0 -0
  170. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/management/commands/search_index.py +0 -0
  171. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/management/commands/search_reconcile.py +0 -0
  172. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/management/commands/seed_manager_landscape.py +0 -0
  173. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/management/commands/shell.py +0 -0
  174. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/management/commands/workflow_drain_outbox.py +0 -0
  175. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/management/commands/workflow_replay_dead_letters.py +0 -0
  176. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/manager/__init__.py +0 -0
  177. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/manager/general_manager.py +0 -0
  178. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/manager/group_manager.py +0 -0
  179. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/manager/input.py +0 -0
  180. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/manager/meta.py +0 -0
  181. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/measurement/__init__.py +0 -0
  182. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/measurement/measurement.py +0 -0
  183. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/measurement/measurement_field.py +0 -0
  184. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/metrics/__init__.py +0 -0
  185. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/metrics/graphql.py +0 -0
  186. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/migrations/0001_initial.py +0 -0
  187. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/migrations/0002_workflow_outbox_scaling_indexes.py +0 -0
  188. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/migrations/0003_workflow_execution_correlation_constraint.py +0 -0
  189. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/migrations/0004_chat_persistence_models.py +0 -0
  190. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/migrations/0004_search_index_state.py +0 -0
  191. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/migrations/0005_merge_chat_search.py +0 -0
  192. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/migrations/0006_chat_pending_confirmation_scoped_ids.py +0 -0
  193. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/migrations/__init__.py +0 -0
  194. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/models.py +0 -0
  195. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/permission/__init__.py +0 -0
  196. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/permission/audit.py +0 -0
  197. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/permission/base_permission.py +0 -0
  198. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/permission/graphql_capabilities.py +0 -0
  199. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/permission/manager_based_permission.py +0 -0
  200. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/permission/mutation_permission.py +0 -0
  201. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/permission/permission_checks.py +0 -0
  202. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/permission/permission_data_manager.py +0 -0
  203. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/permission/utils.py +0 -0
  204. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/public_api_registry.py +0 -0
  205. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/py.typed +0 -0
  206. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/rule/__init__.py +0 -0
  207. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/rule/handler.py +0 -0
  208. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/rule/rule.py +0 -0
  209. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/search/__init__.py +0 -0
  210. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/search/async_tasks.py +0 -0
  211. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/search/backend.py +0 -0
  212. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/search/backend_registry.py +0 -0
  213. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/search/backends/__init__.py +0 -0
  214. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/search/backends/dev.py +0 -0
  215. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/search/backends/meilisearch.py +0 -0
  216. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/search/backends/opensearch.py +0 -0
  217. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/search/backends/typesense.py +0 -0
  218. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/search/config.py +0 -0
  219. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/search/indexer.py +0 -0
  220. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/search/models.py +0 -0
  221. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/search/reconciliation.py +0 -0
  222. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/search/registry.py +0 -0
  223. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/search/tasks.py +0 -0
  224. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/search/utils.py +0 -0
  225. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/seeding/__init__.py +0 -0
  226. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/seeding/manager_landscape.py +0 -0
  227. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/utils/__init__.py +0 -0
  228. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/utils/args_to_kwargs.py +0 -0
  229. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/utils/filter_parser.py +0 -0
  230. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/utils/format_string.py +0 -0
  231. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/utils/json_encoder.py +0 -0
  232. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/utils/make_cache_key.py +0 -0
  233. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/utils/none_to_zero.py +0 -0
  234. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/utils/path_mapping.py +0 -0
  235. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/utils/public_api.py +0 -0
  236. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/utils/testing.py +0 -0
  237. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/utils/type_checks.py +0 -0
  238. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/workflow/__init__.py +0 -0
  239. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/workflow/actions.py +0 -0
  240. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/workflow/backend_registry.py +0 -0
  241. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/workflow/backends/__init__.py +0 -0
  242. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/workflow/backends/celery.py +0 -0
  243. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/workflow/backends/local.py +0 -0
  244. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/workflow/backends/n8n.py +0 -0
  245. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/workflow/config.py +0 -0
  246. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/workflow/engine.py +0 -0
  247. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/workflow/event_registry.py +0 -0
  248. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/workflow/events.py +0 -0
  249. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/workflow/models.py +0 -0
  250. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/workflow/signal_bridge.py +0 -0
  251. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/workflow/tasks.py +0 -0
  252. {generalmanager-0.59.0 → generalmanager-0.59.2}/src/general_manager/workflow/telemetry.py +0 -0
  253. {generalmanager-0.59.0 → generalmanager-0.59.2}/tests/test_settings.py +0 -0
  254. {generalmanager-0.59.0 → generalmanager-0.59.2}/tests/test_urls.py +0 -0
  255. {generalmanager-0.59.0 → generalmanager-0.59.2}/tests/testing_asgi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GeneralManager
3
- Version: 0.59.0
3
+ Version: 0.59.2
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
@@ -47,7 +47,7 @@ License-File: LICENSE
47
47
  Requires-Dist: asgiref>=3.8.1
48
48
  Requires-Dist: celery<6.0.0,>=5.3.6
49
49
  Requires-Dist: channels>=4.1.0
50
- Requires-Dist: Django>=5.2.12
50
+ Requires-Dist: Django>=5.2.15
51
51
  Requires-Dist: django-simple-history>=3.8.0
52
52
  Requires-Dist: exrex>=0.12.0
53
53
  Requires-Dist: factory_boy>=3.3.3
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "GeneralManager"
7
- version = "0.59.0"
7
+ version = "0.59.2"
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" }]
@@ -35,7 +35,7 @@ dependencies = [
35
35
  "asgiref>=3.8.1",
36
36
  "celery>=5.3.6,<6.0.0", # async search indexing (docs/concepts/search.md)
37
37
  "channels>=4.1.0",
38
- "Django>=5.2.12",
38
+ "Django>=5.2.15",
39
39
  "django-simple-history>=3.8.0",
40
40
  "exrex>=0.12.0",
41
41
  "factory_boy>=3.3.3",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GeneralManager
3
- Version: 0.59.0
3
+ Version: 0.59.2
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
@@ -47,7 +47,7 @@ License-File: LICENSE
47
47
  Requires-Dist: asgiref>=3.8.1
48
48
  Requires-Dist: celery<6.0.0,>=5.3.6
49
49
  Requires-Dist: channels>=4.1.0
50
- Requires-Dist: Django>=5.2.12
50
+ Requires-Dist: Django>=5.2.15
51
51
  Requires-Dist: django-simple-history>=3.8.0
52
52
  Requires-Dist: exrex>=0.12.0
53
53
  Requires-Dist: factory_boy>=3.3.3
@@ -1,7 +1,7 @@
1
1
  asgiref>=3.8.1
2
2
  celery<6.0.0,>=5.3.6
3
3
  channels>=4.1.0
4
- Django>=5.2.12
4
+ Django>=5.2.15
5
5
  django-simple-history>=3.8.0
6
6
  exrex>=0.12.0
7
7
  factory_boy>=3.3.3
@@ -1,7 +1,7 @@
1
1
  """Auto-generating factory utilities for GeneralManager models."""
2
2
 
3
3
  from __future__ import annotations
4
- from collections.abc import Callable
4
+ from collections.abc import Callable, Mapping
5
5
  from typing import (
6
6
  TYPE_CHECKING,
7
7
  ClassVar,
@@ -15,6 +15,7 @@ from django.db import models
15
15
  from django.db import transaction
16
16
  from factory.django import DjangoModelFactory
17
17
  from general_manager.factory.factories import (
18
+ RelationGenerationMode,
18
19
  UnableToResolveManagerInstanceError,
19
20
  get_field_value,
20
21
  get_many_to_many_field_value,
@@ -115,6 +116,53 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
115
116
 
116
117
  interface: type[OrmInterfaceBase[models.Model]]
117
118
  _adjustmentMethod: ClassVar[AdjustmentMethod | None] = None
119
+ _related_factory_mode: ClassVar[RelationGenerationMode] = "reuse_existing"
120
+ _related_factory_modes: ClassVar[Mapping[str, RelationGenerationMode]] = {}
121
+
122
+ def __init_subclass__(cls, **kwargs: object) -> None:
123
+ super().__init_subclass__(**kwargs)
124
+ cls._related_factory_modes = dict(cls._related_factory_modes)
125
+
126
+ @classmethod
127
+ def _setup_next_sequence(cls) -> int:
128
+ """Initialize factory_boy sequences after existing persisted rows."""
129
+ model = cls._meta.model
130
+ try:
131
+ is_model = isinstance(model, type) and issubclass(model, models.Model)
132
+ except TypeError:
133
+ is_model = False
134
+ if not is_model:
135
+ return cls._factory_boy_next_sequence()
136
+
137
+ model_cls = cast(type[models.Model], model)
138
+ manager = model_cls._default_manager
139
+ database_alias = cls._get_database_alias()
140
+ if database_alias:
141
+ manager = manager.using(database_alias)
142
+ try:
143
+ count = manager.count()
144
+ except Exception: # noqa: BLE001
145
+ return cls._factory_boy_next_sequence()
146
+ return max(cls._factory_boy_next_sequence(), count)
147
+
148
+ @classmethod
149
+ def _factory_boy_next_sequence(cls) -> int:
150
+ """Call factory_boy's untyped sequence initializer with a typed boundary."""
151
+ setup_next_sequence = cast(Callable[[], int], super()._setup_next_sequence)
152
+ return setup_next_sequence()
153
+
154
+ @classmethod
155
+ def _get_related_factory_mode(cls, field_name: str) -> RelationGenerationMode:
156
+ """Return the relation generation mode configured for a model field."""
157
+ return cls._related_factory_modes.get(field_name, cls._related_factory_mode)
158
+
159
+ @classmethod
160
+ def _get_database_alias(cls) -> str | None:
161
+ """Return the interface database alias, when the interface defines one."""
162
+ if not hasattr(cls.interface, "_get_database_alias"):
163
+ return None
164
+ database_alias = cls.interface._get_database_alias()
165
+ return database_alias or None
118
166
 
119
167
  @classmethod
120
168
  def _generate(
@@ -158,6 +206,8 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
158
206
  if not is_model:
159
207
  raise InvalidAutoFactoryModelError
160
208
  field_name_list, to_ignore_list = cls.interface.handle_custom_fields(model)
209
+ supplied_field_names = set(params)
210
+ database_alias = cls._get_database_alias()
161
211
 
162
212
  fields = [
163
213
  field
@@ -186,6 +236,16 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
186
236
  if declared_default is not None:
187
237
  params[field.name] = declared_default
188
238
  continue
239
+ if getattr(field, "is_relation", False) and (
240
+ getattr(field, "many_to_one", False)
241
+ or getattr(field, "one_to_one", False)
242
+ ):
243
+ params[field.name] = get_field_value(
244
+ field,
245
+ relation_generation=cls._get_related_factory_mode(field.name),
246
+ database_alias=database_alias,
247
+ )
248
+ continue
189
249
  params[field.name] = get_field_value(field)
190
250
 
191
251
  generate = cast(
@@ -201,16 +261,23 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
201
261
  raise InvalidGeneratedObjectError()
202
262
  if strategy == "create":
203
263
  for item in obj:
204
- cls._handle_many_to_many_fields_after_creation(item, params)
264
+ cls._handle_many_to_many_fields_after_creation(
265
+ item, params, supplied_field_names
266
+ )
205
267
  elif strategy == "create":
206
- cls._handle_many_to_many_fields_after_creation(obj, params)
268
+ cls._handle_many_to_many_fields_after_creation(
269
+ obj, params, supplied_field_names
270
+ )
207
271
  if strategy == "create":
208
272
  return cls._wrap_generated_objects(obj)
209
273
  return obj
210
274
 
211
275
  @classmethod
212
276
  def _handle_many_to_many_fields_after_creation(
213
- cls, obj: models.Model, attrs: FactoryParams
277
+ cls,
278
+ obj: models.Model,
279
+ attrs: FactoryParams,
280
+ supplied_field_names: set[str] | None = None,
214
281
  ) -> None:
215
282
  """
216
283
  Assign related objects to many-to-many fields after saved creation.
@@ -223,11 +290,20 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
223
290
  obj (models.Model): Instance whose many-to-many relations should be populated.
224
291
  attrs: Original attributes passed to the factory.
225
292
  """
293
+ if supplied_field_names is None:
294
+ supplied_field_names = set(attrs)
226
295
  for field in obj._meta.many_to_many:
227
- if field.name in attrs:
296
+ relation_generation = cls._get_related_factory_mode(field.name)
297
+ if field.name in attrs and field.name in supplied_field_names:
228
298
  m2m_values = attrs[field.name]
299
+ elif relation_generation == "reuse_existing" and field.blank:
300
+ continue
229
301
  else:
230
- m2m_values = get_many_to_many_field_value(field)
302
+ m2m_values = get_many_to_many_field_value(
303
+ field,
304
+ relation_generation=relation_generation,
305
+ database_alias=cls._get_database_alias(),
306
+ )
231
307
  if m2m_values:
232
308
  normalized_values = cls._coerce_many_to_many_values(m2m_values)
233
309
  getattr(obj, field.name).set(normalized_values)
@@ -317,10 +393,10 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
317
393
  obj = model_class()
318
394
  for field, value in kwargs.items():
319
395
  setattr(obj, field, value)
396
+ database_alias = cls._get_database_alias()
397
+ if database_alias:
398
+ obj._state.db = database_alias
320
399
  obj.full_clean()
321
- database_alias: str | None = None
322
- if hasattr(cls.interface, "_get_database_alias"):
323
- database_alias = cls.interface._get_database_alias()
324
400
 
325
401
  if database_alias:
326
402
  obj.save(using=database_alias)
@@ -373,9 +449,7 @@ class AutoFactory(DjangoModelFactory[modelsModel]):
373
449
 
374
450
  created_objects: list[models.Model] = []
375
451
  if use_creation_method:
376
- database_alias: str | None = None
377
- if hasattr(cls.interface, "_get_database_alias"):
378
- database_alias = cls.interface._get_database_alias()
452
+ database_alias = cls._get_database_alias()
379
453
  with transaction.atomic(using=database_alias):
380
454
  for record in records:
381
455
  created_objects.append(cls._model_creation(model_cls, **record))
@@ -4,14 +4,14 @@ from __future__ import annotations
4
4
 
5
5
  from collections.abc import Callable
6
6
  import string
7
- from typing import Protocol, cast
7
+ from typing import Any, Literal, Protocol, cast
8
8
 
9
9
  from factory.declarations import LazyFunction
10
10
  from factory.faker import Faker
11
11
  import exrex
12
12
  from django.apps import apps
13
13
  from django.core.validators import RegexValidator
14
- from django.db import models
14
+ from django.db import DatabaseError, models
15
15
  from datetime import timezone
16
16
  from decimal import Decimal
17
17
  from random import SystemRandom
@@ -24,6 +24,7 @@ _RNG = SystemRandom()
24
24
  _NO_DEFAULT = object()
25
25
  type DjangoField = models.Field[object, object]
26
26
  type RelatedFactory = Callable[[], object]
27
+ type RelationGenerationMode = Literal["reuse_existing", "create", "random"]
27
28
 
28
29
  _LazyFunctionConstructor = cast(
29
30
  Callable[[Callable[[], object]], LazyFunction],
@@ -137,6 +138,9 @@ class UnableToResolveManagerInstanceError(ValueError):
137
138
 
138
139
  def get_field_value(
139
140
  field: DjangoField | models.ForeignObjectRel,
141
+ *,
142
+ relation_generation: RelationGenerationMode = "reuse_existing",
143
+ database_alias: str | None = None,
140
144
  ) -> object:
141
145
  """
142
146
  Generate a realistic sample value appropriate for the given Django model field or relation.
@@ -164,6 +168,10 @@ def get_field_value(
164
168
  relation descriptor to generate a value for. Many-to-many fields are
165
169
  accepted by the broad type shape but intentionally return `None`
166
170
  here; pass them to `get_many_to_many_field_value()` instead.
171
+ relation_generation: Strategy for relation fields. Defaults to reusing
172
+ existing related rows before creating new related rows.
173
+ database_alias: Optional Django database alias used when querying
174
+ existing related rows.
167
175
 
168
176
  Returns:
169
177
  A value suitable for assignment to the field (scalar, string, Measurement-producing LazyFunction, model instance, LazyFunction that yields a related instance, or `None`).
@@ -173,14 +181,16 @@ def get_field_value(
173
181
  MissingRelatedModelError: When a relational field does not declare a related model.
174
182
  InvalidRelatedModelTypeError: When a relational field's related value is not a Django model class.
175
183
  """
184
+ field_is_relation = _is_relation_field(field)
176
185
  if (
177
- getattr(field, "null", False)
186
+ relation_generation != "create"
187
+ and getattr(field, "null", False)
178
188
  and getattr(field, "default", _NO_DEFAULT) is None
179
- and getattr(field, "is_relation", False)
189
+ and field_is_relation
180
190
  ):
181
191
  return None
182
192
 
183
- if field.null:
193
+ if relation_generation != "create" and getattr(field, "null", False):
184
194
  if _RNG.choice([True] + 9 * [False]):
185
195
  return None
186
196
 
@@ -269,39 +279,65 @@ def get_field_value(
269
279
  return _faker("text", max_nb_chars=max_length)
270
280
  elif isinstance(field, models.OneToOneField):
271
281
  related_model = get_related_model(field)
272
- if hasattr(related_model, "_general_manager_class"):
273
- related_factory = _get_related_factory(related_model)
274
- return _ensure_model_instance(related_factory())
275
- else:
276
- # If no factory exists, pick a random existing instance
277
- related_instances = list(related_model.objects.all())
278
- if related_instances:
279
- return _lazy_function(lambda: _RNG.choice(related_instances))
282
+ related_factory = (
283
+ _get_related_factory(related_model)
284
+ if hasattr(related_model, "_general_manager_class")
285
+ else None
286
+ )
287
+ if relation_generation == "create":
288
+ if related_factory is not None:
289
+ return _ensure_model_instance(related_factory())
280
290
  if field.null:
281
291
  return None
282
292
  raise MissingFactoryOrInstancesError(related_model)
293
+ related_instances = _existing_related_instances(
294
+ field,
295
+ related_model,
296
+ database_alias=database_alias,
297
+ )
298
+ if related_instances:
299
+ return _lazy_existing_choice(related_instances)
300
+ if related_factory is not None:
301
+ return _ensure_model_instance(related_factory())
302
+ if field.null:
303
+ return None
304
+ raise MissingFactoryOrInstancesError(related_model)
283
305
  elif isinstance(field, models.ForeignKey):
284
306
  related_model = get_related_model(field)
285
- # Create or get an instance of the related model
286
- if hasattr(related_model, "_general_manager_class"):
287
- create_a_new_instance = _RNG.choice([True, True, False])
288
- if not create_a_new_instance:
289
- existing_instances = list(related_model.objects.all())
290
- if existing_instances:
291
- # Pick a random existing instance
292
- return _lazy_function(lambda: _RNG.choice(existing_instances))
293
-
294
- related_factory = _get_related_factory(related_model)
295
- return _ensure_model_instance(related_factory())
296
-
297
- else:
298
- # If no factory exists, pick a random existing instance
299
- related_instances = list(related_model.objects.all())
300
- if related_instances:
301
- return _lazy_function(lambda: _RNG.choice(related_instances))
307
+ related_factory = (
308
+ _get_related_factory(related_model)
309
+ if hasattr(related_model, "_general_manager_class")
310
+ else None
311
+ )
312
+ if relation_generation == "create":
313
+ if related_factory is not None:
314
+ return _ensure_model_instance(related_factory())
302
315
  if field.null:
303
316
  return None
304
317
  raise MissingFactoryOrInstancesError(related_model)
318
+ if relation_generation == "random" and related_factory is not None:
319
+ create_a_new_instance = _RNG.choice([True, True, False])
320
+ if not create_a_new_instance:
321
+ related_instances = _existing_related_instances(
322
+ field,
323
+ related_model,
324
+ database_alias=database_alias,
325
+ )
326
+ if related_instances:
327
+ return _lazy_existing_choice(related_instances)
328
+ return _ensure_model_instance(related_factory())
329
+ related_instances = _existing_related_instances(
330
+ field,
331
+ related_model,
332
+ database_alias=database_alias,
333
+ )
334
+ if related_instances:
335
+ return _lazy_existing_choice(related_instances)
336
+ if related_factory is not None:
337
+ return _ensure_model_instance(related_factory())
338
+ if field.null:
339
+ return None
340
+ raise MissingFactoryOrInstancesError(related_model)
305
341
  else:
306
342
  return None
307
343
 
@@ -372,19 +408,26 @@ def _resolve_related_model_string(
372
408
 
373
409
  def get_many_to_many_field_value(
374
410
  field: models.ManyToManyField[models.Model, models.Model],
411
+ *,
412
+ relation_generation: RelationGenerationMode = "reuse_existing",
413
+ database_alias: str | None = None,
375
414
  ) -> list[models.Model]:
376
415
  """
377
416
  Generate a list of related model instances suitable for assigning to a ManyToManyField.
378
417
 
379
418
  The function selects a random number of related objects (at least one when
380
- the field is not blank, up to 10). It will use the related model's factory
381
- to create new instances when available, prefer a mix of created and existing
382
- instances if both are present, or return existing instances when no factory
383
- is available. `blank=True` allows an empty result; `blank=False` requires at
384
- least one related instance.
419
+ the field is not blank, up to 10). Default generation samples existing
420
+ related rows when they are available and creates through the related model's
421
+ factory only when no existing rows are available. Create mode bypasses
422
+ existing rows and uses the related factory. `blank=True` allows an empty
423
+ result; `blank=False` requires at least one related instance.
385
424
 
386
425
  Parameters:
387
426
  field (models.ManyToManyField): The ManyToMany field to generate values for.
427
+ relation_generation: Strategy for relation values. Defaults to reusing
428
+ existing related rows before creating new related rows.
429
+ database_alias: Optional Django database alias used when querying
430
+ existing related rows.
388
431
 
389
432
  Returns:
390
433
  list[models.Model]: A list of related model instances to assign to the field.
@@ -400,37 +443,129 @@ def get_many_to_many_field_value(
400
443
  """
401
444
  related_factory: RelatedFactory | None = None
402
445
  related_model = get_related_model(field)
403
- related_instances: list[models.Model] = list(related_model.objects.all())
404
446
  if hasattr(related_model, "_general_manager_class"):
405
447
  related_factory = _get_related_factory(related_model)
406
448
 
407
449
  min_required = 0 if field.blank else 1
408
450
  number_of_instances = _RNG.randint(min_required, 10)
409
- if related_factory and related_instances:
410
- number_to_create = _RNG.randint(min_required, number_of_instances)
411
- number_to_pick = number_of_instances - number_to_create
412
- if number_to_pick > len(related_instances):
413
- number_to_pick = len(related_instances)
414
- existing_instances = _RNG.sample(related_instances, number_to_pick)
415
- new_factory_values = [related_factory() for _ in range(number_to_create)]
416
- return existing_instances + [
417
- _ensure_model_instance(instance) for instance in new_factory_values
418
- ]
419
- elif related_factory:
420
- number_to_create = number_of_instances
421
- new_instances = [
422
- _ensure_model_instance(related_factory()) for _ in range(number_to_create)
423
- ]
424
- return new_instances
425
- elif related_instances:
426
- number_to_create = 0
451
+ if relation_generation == "create":
452
+ if related_factory:
453
+ return [
454
+ _ensure_model_instance(related_factory())
455
+ for _ in range(number_of_instances)
456
+ ]
457
+ raise MissingFactoryOrInstancesError(related_model)
458
+
459
+ related_instances = _existing_related_instances(
460
+ field,
461
+ related_model,
462
+ database_alias=database_alias,
463
+ )
464
+ if related_instances:
427
465
  number_to_pick = number_of_instances
428
466
  if number_to_pick > len(related_instances):
429
467
  number_to_pick = len(related_instances)
430
- existing_instances = _RNG.sample(related_instances, number_to_pick)
431
- return existing_instances
432
- else:
433
- raise MissingFactoryOrInstancesError(related_model)
468
+ return _RNG.sample(related_instances, number_to_pick)
469
+ if related_factory:
470
+ return [
471
+ _ensure_model_instance(related_factory())
472
+ for _ in range(number_of_instances)
473
+ ]
474
+ raise MissingFactoryOrInstancesError(related_model)
475
+
476
+
477
+ def _is_relation_field(field: object) -> bool:
478
+ return (
479
+ isinstance(field, models.OneToOneField)
480
+ or isinstance(field, models.ForeignKey)
481
+ or getattr(field, "is_relation", False) is True
482
+ or getattr(field, "many_to_one", False) is True
483
+ or getattr(field, "one_to_one", False) is True
484
+ or getattr(field, "many_to_many", False) is True
485
+ )
486
+
487
+
488
+ def _existing_related_instances(
489
+ field: object,
490
+ related_model: type[models.Model] | None = None,
491
+ *,
492
+ database_alias: str | None = None,
493
+ ) -> list[models.Model]:
494
+ """Return existing related rows that are reusable for this relation field."""
495
+ field_obj = cast(Any, field)
496
+ if related_model is None:
497
+ related_model = cast(type[models.Model], field_obj.related_model)
498
+ related_instances = list(
499
+ _get_model_manager(related_model, database_alias=database_alias).all()
500
+ )
501
+ if not (
502
+ isinstance(field, models.OneToOneField)
503
+ or getattr(field, "one_to_one", False) is True
504
+ ):
505
+ return related_instances
506
+
507
+ model = field_obj.model
508
+ attname = getattr(field_obj, "attname", field_obj.name)
509
+ try:
510
+ linked_values = set(
511
+ _get_model_manager(
512
+ model,
513
+ prefer_base=True,
514
+ database_alias=database_alias,
515
+ )
516
+ .exclude(**{attname: None})
517
+ .values_list(attname, flat=True)
518
+ )
519
+ except DatabaseError:
520
+ if getattr(getattr(model, "_meta", None), "managed", True) is not False:
521
+ raise
522
+ linked_values = set()
523
+ return [
524
+ instance
525
+ for instance in related_instances
526
+ if _related_target_value(instance, getattr(field_obj, "target_field", None))
527
+ not in linked_values
528
+ ]
529
+
530
+
531
+ def _lazy_existing_choice(instances: list[models.Model]) -> LazyFunction:
532
+ return _lazy_function(lambda: _RNG.choice(instances))
533
+
534
+
535
+ def _related_target_value(instance: object, target_field: object | None) -> object:
536
+ if target_field is not None:
537
+ target_attr = getattr(
538
+ target_field,
539
+ "attname",
540
+ getattr(target_field, "name", None),
541
+ )
542
+ if target_attr is not None and hasattr(instance, target_attr):
543
+ return getattr(instance, target_attr)
544
+ return getattr(instance, "pk", None)
545
+
546
+
547
+ def _get_model_manager(
548
+ model: object,
549
+ *,
550
+ prefer_base: bool = False,
551
+ database_alias: str | None = None,
552
+ ) -> Any:
553
+ model_obj = cast(Any, model)
554
+ if prefer_base:
555
+ base_manager = getattr(model_obj, "_base_manager", None)
556
+ if base_manager is not None:
557
+ if database_alias:
558
+ return base_manager.using(database_alias)
559
+ return base_manager
560
+ default_manager = getattr(model_obj, "_default_manager", None)
561
+ if default_manager is not None:
562
+ if database_alias:
563
+ return default_manager.using(database_alias)
564
+ return default_manager
565
+ objects_manager = model_obj.objects
566
+ if database_alias:
567
+ return objects_manager.using(database_alias)
568
+ return objects_manager
434
569
 
435
570
 
436
571
  def _get_related_factory(related_model: type[models.Model]) -> RelatedFactory:
File without changes