GeneralManager 0.60.3__tar.gz → 0.60.5__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 (258) hide show
  1. {generalmanager-0.60.3/src/GeneralManager.egg-info → generalmanager-0.60.5}/PKG-INFO +1 -1
  2. {generalmanager-0.60.3 → generalmanager-0.60.5}/pyproject.toml +1 -1
  3. {generalmanager-0.60.3 → generalmanager-0.60.5/src/GeneralManager.egg-info}/PKG-INFO +1 -1
  4. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/system_prompt.py +15 -10
  5. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/measurement/measurement.py +78 -7
  6. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/utils/path_mapping.py +225 -73
  7. {generalmanager-0.60.3 → generalmanager-0.60.5}/LICENSE +0 -0
  8. {generalmanager-0.60.3 → generalmanager-0.60.5}/README.md +0 -0
  9. {generalmanager-0.60.3 → generalmanager-0.60.5}/setup.cfg +0 -0
  10. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/GeneralManager.egg-info/SOURCES.txt +0 -0
  11. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/GeneralManager.egg-info/dependency_links.txt +0 -0
  12. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/GeneralManager.egg-info/requires.txt +0 -0
  13. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/GeneralManager.egg-info/top_level.txt +0 -0
  14. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/__init__.py +0 -0
  15. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/_types/__init__.py +0 -0
  16. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/_types/api.py +0 -0
  17. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/_types/bucket.py +0 -0
  18. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/_types/cache.py +0 -0
  19. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/_types/dataframes.py +0 -0
  20. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/_types/factory.py +0 -0
  21. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/_types/general_manager.py +0 -0
  22. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/_types/interface.py +0 -0
  23. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/_types/manager.py +0 -0
  24. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/_types/measurement.py +0 -0
  25. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/_types/permission.py +0 -0
  26. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/_types/rule.py +0 -0
  27. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/_types/search.py +0 -0
  28. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/_types/utils.py +0 -0
  29. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/api/__init__.py +0 -0
  30. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/api/graphql.py +0 -0
  31. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/api/graphql_errors.py +0 -0
  32. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/api/graphql_mutations.py +0 -0
  33. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/api/graphql_prefetch.py +0 -0
  34. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/api/graphql_resolvers.py +0 -0
  35. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/api/graphql_search.py +0 -0
  36. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/api/graphql_subscription_consumer.py +0 -0
  37. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/api/graphql_subscriptions.py +0 -0
  38. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/api/graphql_view.py +0 -0
  39. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/api/graphql_warmup.py +0 -0
  40. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/api/graphql_warmup_registry.py +0 -0
  41. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/api/graphql_warmup_tasks.py +0 -0
  42. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/api/mutation.py +0 -0
  43. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/api/property.py +0 -0
  44. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/api/registry.py +0 -0
  45. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/api/remote_api.py +0 -0
  46. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/api/remote_invalidation.py +0 -0
  47. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/api/remote_invalidation_client.py +0 -0
  48. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/apps.py +0 -0
  49. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/bootstrap.py +0 -0
  50. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/bucket/__init__.py +0 -0
  51. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/bucket/base_bucket.py +0 -0
  52. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/bucket/calculation_bucket.py +0 -0
  53. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/bucket/database_bucket.py +0 -0
  54. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/bucket/group_bucket.py +0 -0
  55. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/bucket/indexing.py +0 -0
  56. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/bucket/request_bucket.py +0 -0
  57. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/cache/__init__.py +0 -0
  58. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/cache/cache_decorator.py +0 -0
  59. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/cache/cache_tracker.py +0 -0
  60. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/cache/dependency_cache.py +0 -0
  61. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/cache/dependency_index.py +0 -0
  62. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/cache/dependency_matching.py +0 -0
  63. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/cache/dependency_publish.py +0 -0
  64. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/cache/dependency_shards.py +0 -0
  65. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/cache/model_dependency_collector.py +0 -0
  66. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/cache/run_context.py +0 -0
  67. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/cache/signals.py +0 -0
  68. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/__init__.py +0 -0
  69. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/audit.py +0 -0
  70. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/bootstrap.py +0 -0
  71. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/checks.py +0 -0
  72. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/consumer.py +0 -0
  73. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/errors.py +0 -0
  74. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/evals/__init__.py +0 -0
  75. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/evals/__main__.py +0 -0
  76. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/evals/baseline.py +0 -0
  77. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/evals/diagnostics.py +0 -0
  78. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/evals/fingerprints.py +0 -0
  79. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/evals/fixtures.py +0 -0
  80. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/evals/judges/__init__.py +0 -0
  81. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/evals/judges/answer_quality.py +0 -0
  82. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/evals/judges/contract.py +0 -0
  83. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/evals/judges/result_accuracy.py +0 -0
  84. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/evals/judges/tool_sequence.py +0 -0
  85. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/evals/readiness.py +0 -0
  86. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/evals/runner.py +0 -0
  87. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/evals/traces.py +0 -0
  88. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/grounding.py +0 -0
  89. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/models.py +0 -0
  90. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/providers/__init__.py +0 -0
  91. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/providers/_shared.py +0 -0
  92. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/providers/anthropic.py +0 -0
  93. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/providers/base.py +0 -0
  94. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/providers/google.py +0 -0
  95. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/providers/ollama.py +0 -0
  96. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/providers/openai.py +0 -0
  97. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/rate_limits.py +0 -0
  98. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/routing.py +0 -0
  99. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/schema_index.py +0 -0
  100. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/settings.py +0 -0
  101. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/signals.py +0 -0
  102. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/tool_metadata.py +0 -0
  103. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/tools.py +0 -0
  104. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/chat/views.py +0 -0
  105. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/conf.py +0 -0
  106. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/dataframes/__init__.py +0 -0
  107. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/dataframes/measurements.py +0 -0
  108. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/factory/__init__.py +0 -0
  109. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/factory/auto_factory.py +0 -0
  110. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/factory/factories.py +0 -0
  111. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/factory/factory_methods.py +0 -0
  112. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/__init__.py +0 -0
  113. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/base_interface.py +0 -0
  114. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/bundles/__init__.py +0 -0
  115. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/bundles/calculation.py +0 -0
  116. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/bundles/database.py +0 -0
  117. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/bundles/remote_manager.py +0 -0
  118. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/bundles/request.py +0 -0
  119. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/__init__.py +0 -0
  120. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/base.py +0 -0
  121. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/builtin.py +0 -0
  122. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/calculation/__init__.py +0 -0
  123. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/calculation/_compat.py +0 -0
  124. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/calculation/lifecycle.py +0 -0
  125. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/configuration.py +0 -0
  126. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/core/observability.py +0 -0
  127. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/core/utils.py +0 -0
  128. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/exceptions.py +0 -0
  129. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/existing_model/__init__.py +0 -0
  130. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/existing_model/_compat.py +0 -0
  131. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/existing_model/resolution.py +0 -0
  132. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/factory.py +0 -0
  133. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/orm/__init__.py +0 -0
  134. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/orm/_compat.py +0 -0
  135. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/orm/history.py +0 -0
  136. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/orm/lifecycle.py +0 -0
  137. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/orm/mutations.py +0 -0
  138. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/orm/support.py +0 -0
  139. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/orm_utils/__init__.py +0 -0
  140. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/orm_utils/django_manager_utils.py +0 -0
  141. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/orm_utils/field_descriptors.py +0 -0
  142. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/orm_utils/payload_normalizer.py +0 -0
  143. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/read_only/__init__.py +0 -0
  144. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/read_only/_compat.py +0 -0
  145. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/read_only/_types.py +0 -0
  146. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/read_only/lifecycle.py +0 -0
  147. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/read_only/management.py +0 -0
  148. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/registry.py +0 -0
  149. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/remote_manager.py +0 -0
  150. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/capabilities/request/__init__.py +0 -0
  151. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/infrastructure/__init__.py +0 -0
  152. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/infrastructure/startup_hooks.py +0 -0
  153. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/infrastructure/system_checks.py +0 -0
  154. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/interfaces/__init__.py +0 -0
  155. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/interfaces/calculation.py +0 -0
  156. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/interfaces/database.py +0 -0
  157. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/interfaces/existing_model.py +0 -0
  158. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/interfaces/read_only.py +0 -0
  159. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/interfaces/remote_manager.py +0 -0
  160. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/interfaces/request.py +0 -0
  161. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/manifests/__init__.py +0 -0
  162. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/manifests/capability_builder.py +0 -0
  163. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/manifests/capability_manifest.py +0 -0
  164. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/manifests/capability_models.py +0 -0
  165. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/orm_interface.py +0 -0
  166. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/requests.py +0 -0
  167. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/utils/__init__.py +0 -0
  168. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/utils/database_interface_protocols.py +0 -0
  169. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/utils/errors.py +0 -0
  170. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/interface/utils/models.py +0 -0
  171. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/logging.py +0 -0
  172. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/management/commands/chat_cleanup.py +0 -0
  173. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/management/commands/graphql_warmup.py +0 -0
  174. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/management/commands/graphql_warmup_refresh_due.py +0 -0
  175. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/management/commands/search_index.py +0 -0
  176. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/management/commands/search_reconcile.py +0 -0
  177. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/management/commands/seed_manager_landscape.py +0 -0
  178. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/management/commands/shell.py +0 -0
  179. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/management/commands/workflow_drain_outbox.py +0 -0
  180. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/management/commands/workflow_replay_dead_letters.py +0 -0
  181. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/manager/__init__.py +0 -0
  182. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/manager/general_manager.py +0 -0
  183. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/manager/group_manager.py +0 -0
  184. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/manager/input.py +0 -0
  185. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/manager/meta.py +0 -0
  186. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/measurement/__init__.py +0 -0
  187. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/measurement/measurement_field.py +0 -0
  188. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/metrics/__init__.py +0 -0
  189. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/metrics/graphql.py +0 -0
  190. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/migrations/0001_initial.py +0 -0
  191. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/migrations/0002_workflow_outbox_scaling_indexes.py +0 -0
  192. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/migrations/0003_workflow_execution_correlation_constraint.py +0 -0
  193. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/migrations/0004_chat_persistence_models.py +0 -0
  194. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/migrations/0004_search_index_state.py +0 -0
  195. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/migrations/0005_merge_chat_search.py +0 -0
  196. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/migrations/0006_chat_pending_confirmation_scoped_ids.py +0 -0
  197. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/migrations/__init__.py +0 -0
  198. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/models.py +0 -0
  199. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/permission/__init__.py +0 -0
  200. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/permission/audit.py +0 -0
  201. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/permission/base_permission.py +0 -0
  202. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/permission/graphql_capabilities.py +0 -0
  203. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/permission/manager_based_permission.py +0 -0
  204. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/permission/mutation_permission.py +0 -0
  205. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/permission/permission_checks.py +0 -0
  206. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/permission/permission_data_manager.py +0 -0
  207. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/permission/utils.py +0 -0
  208. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/public_api_registry.py +0 -0
  209. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/py.typed +0 -0
  210. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/rule/__init__.py +0 -0
  211. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/rule/handler.py +0 -0
  212. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/rule/rule.py +0 -0
  213. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/search/__init__.py +0 -0
  214. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/search/async_tasks.py +0 -0
  215. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/search/backend.py +0 -0
  216. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/search/backend_registry.py +0 -0
  217. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/search/backends/__init__.py +0 -0
  218. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/search/backends/dev.py +0 -0
  219. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/search/backends/meilisearch.py +0 -0
  220. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/search/backends/opensearch.py +0 -0
  221. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/search/backends/typesense.py +0 -0
  222. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/search/config.py +0 -0
  223. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/search/indexer.py +0 -0
  224. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/search/models.py +0 -0
  225. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/search/reconciliation.py +0 -0
  226. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/search/registry.py +0 -0
  227. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/search/tasks.py +0 -0
  228. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/search/utils.py +0 -0
  229. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/seeding/__init__.py +0 -0
  230. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/seeding/manager_landscape.py +0 -0
  231. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/utils/__init__.py +0 -0
  232. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/utils/args_to_kwargs.py +0 -0
  233. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/utils/filter_parser.py +0 -0
  234. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/utils/format_string.py +0 -0
  235. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/utils/json_encoder.py +0 -0
  236. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/utils/make_cache_key.py +0 -0
  237. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/utils/none_to_zero.py +0 -0
  238. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/utils/public_api.py +0 -0
  239. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/utils/testing.py +0 -0
  240. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/utils/type_checks.py +0 -0
  241. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/workflow/__init__.py +0 -0
  242. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/workflow/actions.py +0 -0
  243. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/workflow/backend_registry.py +0 -0
  244. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/workflow/backends/__init__.py +0 -0
  245. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/workflow/backends/celery.py +0 -0
  246. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/workflow/backends/local.py +0 -0
  247. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/workflow/backends/n8n.py +0 -0
  248. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/workflow/config.py +0 -0
  249. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/workflow/engine.py +0 -0
  250. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/workflow/event_registry.py +0 -0
  251. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/workflow/events.py +0 -0
  252. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/workflow/models.py +0 -0
  253. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/workflow/signal_bridge.py +0 -0
  254. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/workflow/tasks.py +0 -0
  255. {generalmanager-0.60.3 → generalmanager-0.60.5}/src/general_manager/workflow/telemetry.py +0 -0
  256. {generalmanager-0.60.3 → generalmanager-0.60.5}/tests/test_settings.py +0 -0
  257. {generalmanager-0.60.3 → generalmanager-0.60.5}/tests/test_urls.py +0 -0
  258. {generalmanager-0.60.3 → generalmanager-0.60.5}/tests/testing_asgi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GeneralManager
3
- Version: 0.60.3
3
+ Version: 0.60.5
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.60.3"
7
+ version = "0.60.5"
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.60.3
3
+ Version: 0.60.5
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
@@ -255,16 +255,21 @@ def _relationship_lines(
255
255
  index: dict[str, dict[str, object]], manager_names: list[str]
256
256
  ) -> list[str]:
257
257
  relationship_lines: list[str] = []
258
- if not PathMap.mapping and manager_names:
259
- PathMap(manager_names[0])
260
- for (from_manager, to_manager), tracer in sorted(PathMap.mapping.items()):
261
- if from_manager not in index or to_manager not in index:
262
- continue
263
- path = getattr(tracer, "path", None)
264
- if path:
265
- relationship_lines.append(
266
- f"{from_manager} -> {to_manager}: {' -> '.join(path)}"
267
- )
258
+ exposed_names = [name for name in manager_names if name in index]
259
+ if len(exposed_names) > PROMPT_MANAGER_DETAIL_LIMIT:
260
+ # Bound lazy path resolution to avoid reintroducing CPU-bound O(n^2) work.
261
+ return relationship_lines
262
+ for from_manager in exposed_names:
263
+ path_map = PathMap(from_manager)
264
+ for to_manager in exposed_names:
265
+ if from_manager == to_manager:
266
+ continue
267
+ tracer = path_map.to(to_manager)
268
+ path = getattr(tracer, "path", None) if tracer is not None else None
269
+ if path:
270
+ relationship_lines.append(
271
+ f"{from_manager} -> {to_manager}: {' -> '.join(path)}"
272
+ )
268
273
  return relationship_lines
269
274
 
270
275
 
@@ -91,6 +91,7 @@ def _unit_uses_offset_for_unit_string(unit: str) -> bool:
91
91
  return False
92
92
 
93
93
 
94
+ @lru_cache(maxsize=512)
94
95
  def _pint_unit_components(unit: str) -> tuple[tuple[str, object], ...]:
95
96
  """Return Pint unit component names and powers for one unit expression."""
96
97
 
@@ -98,6 +99,13 @@ def _pint_unit_components(unit: str) -> tuple[tuple[str, object], ...]:
98
99
  return tuple(parsed_unit._units.items())
99
100
 
100
101
 
102
+ @lru_cache(maxsize=512)
103
+ def _unit_dimensionality(unit: str) -> object:
104
+ """Return Pint dimensionality metadata for one unit expression."""
105
+
106
+ return _parse_unit(unit).dimensionality
107
+
108
+
101
109
  def _pint_unit_component_uses_offset(unit_name: str) -> bool:
102
110
  """Return whether a Pint unit component has offset conversion semantics."""
103
111
 
@@ -187,12 +195,12 @@ def _convert_quantity(
187
195
  return source_quantity.to(target_unit)
188
196
 
189
197
 
198
+ @lru_cache(maxsize=512)
190
199
  def _currency_component(unit: str) -> tuple[str, int] | None:
191
200
  """Return the single configured currency component in a unit expression."""
192
201
 
193
- parsed_unit = ureg.parse_units(str(unit))
194
202
  currency_components: list[tuple[str, int]] = []
195
- for unit_name, power in parsed_unit._units.items():
203
+ for unit_name, power in _pint_unit_components(unit):
196
204
  currency_power = _integer_unit_power(power)
197
205
  if currency_power is not None and unit_name in currency_units:
198
206
  currency_components.append((unit_name, currency_power))
@@ -213,13 +221,69 @@ def _integer_unit_power(power: object) -> int | None:
213
221
  return None
214
222
 
215
223
 
224
+ @lru_cache(maxsize=512)
216
225
  def _unit_without_currency(unit: str, currency: str, power: int) -> str:
217
226
  """Return a unit expression with one currency component removed."""
218
227
 
219
- stripped_unit = ureg.parse_units(str(unit)) / (ureg.parse_units(currency) ** power)
228
+ stripped_unit = _parse_unit(unit) / (_parse_unit(currency) ** power)
220
229
  return str(stripped_unit)
221
230
 
222
231
 
232
+ def _is_implicit_cross_currency_conversion(
233
+ source_unit: str,
234
+ target_unit: str,
235
+ ) -> bool:
236
+ """Return whether a conversion would require an explicit exchange rate."""
237
+
238
+ source_currency = _currency_component(source_unit)
239
+ target_currency = _currency_component(target_unit)
240
+ return (
241
+ source_currency is not None
242
+ and target_currency is not None
243
+ and source_currency[0] != target_currency[0]
244
+ )
245
+
246
+
247
+ @lru_cache(maxsize=1024)
248
+ def _multiplicative_conversion_factor(
249
+ source_unit: str,
250
+ target_unit: str,
251
+ ) -> Decimal | None:
252
+ """Return a cached Decimal factor for safe multiplicative conversions."""
253
+
254
+ try:
255
+ if (
256
+ _unit_uses_offset_for_unit_string(source_unit)
257
+ or _unit_uses_offset_for_unit_string(target_unit)
258
+ or _is_implicit_cross_currency_conversion(source_unit, target_unit)
259
+ ):
260
+ return None
261
+ converted_quantity = ureg.Quantity(
262
+ Decimal("1"),
263
+ _parse_unit(source_unit),
264
+ ).to(_parse_unit(target_unit))
265
+ except pint.errors.PintError:
266
+ return None
267
+ return _decimal_from_magnitude(converted_quantity.magnitude)
268
+
269
+
270
+ def _cached_multiplicative_conversion_factor(
271
+ source_unit: str,
272
+ target_unit: str,
273
+ ) -> Decimal | None:
274
+ """Return a cached factor keyed by canonical source and target units."""
275
+
276
+ try:
277
+ source_canonical_unit = _canonical_unit_string(source_unit)
278
+ target_canonical_unit = _canonical_unit_string(target_unit)
279
+ except pint.errors.PintError:
280
+ return None
281
+ return _multiplicative_conversion_factor(
282
+ source_canonical_unit,
283
+ target_canonical_unit,
284
+ )
285
+
286
+
223
287
  def convert_magnitude(value: Decimal, source_unit: str, target_unit: str) -> Decimal:
224
288
  """
225
289
  Convert a magnitude between units while keeping offset-unit math away from Decimal.
@@ -230,6 +294,13 @@ def convert_magnitude(value: Decimal, source_unit: str, target_unit: str) -> Dec
230
294
  round-trip back to ``Decimal`` via ``str``.
231
295
  """
232
296
 
297
+ conversion_factor = _cached_multiplicative_conversion_factor(
298
+ source_unit,
299
+ target_unit,
300
+ )
301
+ if conversion_factor is not None:
302
+ return _decimal_from_magnitude(value * conversion_factor)
303
+
233
304
  converted_quantity = _convert_quantity(
234
305
  _build_quantity(value, source_unit), target_unit
235
306
  )
@@ -830,10 +901,10 @@ class Measurement:
830
901
  )
831
902
  elif not self.is_currency() and not other.is_currency():
832
903
  # Both are physical units
904
+ if _unit_dimensionality(left_unit) != _unit_dimensionality(right_unit):
905
+ raise IncompatibleUnitsError("addition")
833
906
  left_quantity = self.__current_quantity()
834
907
  right_quantity = other.__current_quantity()
835
- if left_quantity.dimensionality != right_quantity.dimensionality:
836
- raise IncompatibleUnitsError("addition")
837
908
  left_quantity, right_quantity = _prepare_quantities_for_binary_operation(
838
909
  left_quantity,
839
910
  right_quantity,
@@ -883,10 +954,10 @@ class Measurement:
883
954
  return Measurement(Decimal(str(result_quantity.magnitude)), str(self.unit))
884
955
  elif not self.is_currency() and not other.is_currency():
885
956
  # Both are physical units
957
+ if _unit_dimensionality(left_unit) != _unit_dimensionality(right_unit):
958
+ raise IncompatibleUnitsError("subtraction")
886
959
  left_quantity = self.__current_quantity()
887
960
  right_quantity = other.__current_quantity()
888
- if left_quantity.dimensionality != right_quantity.dimensionality:
889
- raise IncompatibleUnitsError("subtraction")
890
961
  left_quantity, right_quantity = _prepare_quantities_for_binary_operation(
891
962
  left_quantity,
892
963
  right_quantity,
@@ -1,6 +1,7 @@
1
1
  """Utilities for tracing relationships between GeneralManager classes."""
2
2
 
3
3
  from __future__ import annotations
4
+ from collections import deque
4
5
  from collections.abc import Mapping
5
6
  from typing import ClassVar, cast, get_args
6
7
  from general_manager.manager.meta import GeneralManagerMeta
@@ -12,6 +13,8 @@ from general_manager.manager.general_manager import GeneralManager
12
13
 
13
14
  type PathStart = str
14
15
  type PathDestination = str
16
+ type PathEdge = tuple[str, PathDestination]
17
+ type PathClassEdge = tuple[str, type[GeneralManager]]
15
18
  type TraversalValue = GeneralManager | Bucket[GeneralManager]
16
19
 
17
20
 
@@ -36,47 +39,196 @@ class InvalidPathTraversalValueError(TypeError):
36
39
  )
37
40
 
38
41
 
42
+ def _as_manager_class(value: object) -> type[GeneralManager] | None:
43
+ """Return value when it is a GeneralManager subclass."""
44
+ if value is None or not isinstance(value, type):
45
+ return None
46
+ if not issubclass(value, GeneralManager):
47
+ return None
48
+ return value
49
+
50
+
51
+ def _iter_manager_connections(
52
+ manager_class: type[GeneralManager],
53
+ ) -> list[tuple[str, type[GeneralManager]]]:
54
+ """Return traversable manager edges declared by interface fields and GraphQL properties."""
55
+ current_connections: dict[str, object] = {}
56
+ for attr_name, attr_value in manager_class.Interface.get_attribute_types().items():
57
+ if isinstance(attr_value, Mapping):
58
+ current_connections[attr_name] = attr_value.get("type")
59
+ seen_attrs: set[str] = set()
60
+ for mro_class in manager_class.__mro__:
61
+ for attr_name, attr_value in mro_class.__dict__.items():
62
+ if attr_name in seen_attrs:
63
+ continue
64
+ seen_attrs.add(attr_name)
65
+ if not isinstance(attr_value, GraphQLProperty):
66
+ continue
67
+ type_hints = get_args(attr_value.graphql_type_hint)
68
+ field_type = (
69
+ type_hints[0]
70
+ if type_hints
71
+ else cast(type[object], attr_value.graphql_type_hint)
72
+ )
73
+ current_connections[attr_name] = field_type
74
+
75
+ connections: list[tuple[str, type[GeneralManager]]] = []
76
+ for attr_name, attr_type in current_connections.items():
77
+ manager_type = _as_manager_class(attr_type)
78
+ if manager_type is not None:
79
+ connections.append((attr_name, manager_type))
80
+ return connections
81
+
82
+
39
83
  class PathMap:
40
84
  """
41
85
  Maintain cached traversal paths between GeneralManager classes.
42
86
 
43
- The class-level mapping stores one PathTracer for each registered
44
- start/destination class-name pair. A cached tracer may have ``path is None``
45
- when the classes are known but no route exists.
87
+ The class-level mapping stores lazily resolved PathTracer objects for
88
+ requested start/destination class-name pairs. A cached tracer may have
89
+ ``path is None`` when the classes are known but no route exists.
46
90
  """
47
91
 
48
92
  instance: PathMap
49
93
  mapping: ClassVar[dict[tuple[PathStart, PathDestination], PathTracer]] = {}
94
+ _registry_signature: ClassVar[tuple[type[GeneralManager], ...]] = ()
95
+ _classes_by_name: ClassVar[dict[str, type[GeneralManager]]] = {}
96
+ _adjacency: ClassVar[dict[PathStart, tuple[PathEdge, ...]]] = {}
97
+ _class_adjacency: ClassVar[
98
+ dict[type[GeneralManager], tuple[PathClassEdge, ...]]
99
+ ] = {}
50
100
 
51
101
  def __new__(cls, *args: object, **kwargs: object) -> PathMap:
52
102
  """
53
- Obtain the singleton PathMap, initializing the path mapping on first instantiation.
103
+ Obtain the singleton PathMap, refreshing graph metadata on each construction.
54
104
 
55
105
  Returns:
56
106
  PathMap: The singleton PathMap instance.
57
107
  """
108
+ force_refresh = False
58
109
  if not hasattr(cls, "instance"):
59
110
  cls.instance = super().__new__(cls)
60
- cls.create_path_mapping()
111
+ force_refresh = not cls.mapping
112
+ cls._ensure_graph_current(force=force_refresh)
61
113
  return cls.instance
62
114
 
63
115
  @classmethod
64
116
  def create_path_mapping(cls) -> None:
65
117
  """
66
- Populate the path mapping with tracers for every distinct pair of managed classes.
67
-
68
- The generated tracers capture the attribute sequence needed to navigate from the start class to the destination class and are cached on the singleton instance.
118
+ Refresh path graph metadata without eagerly creating all pair tracers.
69
119
 
70
120
  Returns:
71
121
  None
72
122
  """
73
- all_managed_classes = GeneralManagerMeta.all_classes
74
- for start_class in all_managed_classes:
75
- for destination_class in all_managed_classes:
76
- if start_class != destination_class:
77
- cls.instance.mapping[
78
- (start_class.__name__, destination_class.__name__)
79
- ] = PathTracer(start_class, destination_class)
123
+ cls._ensure_graph_current(force=True)
124
+
125
+ @classmethod
126
+ def _ensure_graph_current(cls, *, force: bool = False) -> None:
127
+ """Refresh class and adjacency caches when the manager registry changes."""
128
+ registry_signature = tuple(GeneralManagerMeta.all_classes)
129
+ if not force and cls._registry_signature == registry_signature:
130
+ return
131
+
132
+ cls._registry_signature = registry_signature
133
+ cls._classes_by_name = {
134
+ manager_class.__name__: manager_class
135
+ for manager_class in registry_signature
136
+ }
137
+ cls._class_adjacency = {
138
+ manager_class: tuple(_iter_manager_connections(manager_class))
139
+ for manager_class in registry_signature
140
+ }
141
+ cls._adjacency = {
142
+ manager_class.__name__: tuple(
143
+ (attr, target_class.__name__)
144
+ for attr, target_class in cls._class_adjacency[manager_class]
145
+ )
146
+ for manager_class in registry_signature
147
+ }
148
+ cls.mapping.clear()
149
+
150
+ @classmethod
151
+ def _edges_for_class(
152
+ cls,
153
+ manager_class: type[GeneralManager],
154
+ ) -> tuple[PathClassEdge, ...]:
155
+ """Return cached outgoing edges for registered or discovered manager classes."""
156
+ edges = cls._class_adjacency.get(manager_class)
157
+ if edges is not None:
158
+ return edges
159
+
160
+ edges = tuple(_iter_manager_connections(manager_class))
161
+ cls._class_adjacency[manager_class] = edges
162
+ cls._adjacency.setdefault(
163
+ manager_class.__name__,
164
+ tuple((attr, target_class.__name__) for attr, target_class in edges),
165
+ )
166
+ return edges
167
+
168
+ @classmethod
169
+ def _find_path(
170
+ cls,
171
+ start_class_name: PathStart,
172
+ destination_class_name: PathDestination,
173
+ ) -> list[str] | None:
174
+ """Find the shortest attribute path between two registered managers."""
175
+ if start_class_name == destination_class_name:
176
+ return []
177
+
178
+ start_class = cls._classes_by_name.get(start_class_name)
179
+ destination_class = cls._classes_by_name.get(destination_class_name)
180
+ if start_class is None or destination_class is None:
181
+ return None
182
+
183
+ visited = {start_class}
184
+ queue: deque[tuple[type[GeneralManager], list[str]]] = deque(
185
+ [(start_class, [])]
186
+ )
187
+
188
+ while queue:
189
+ current_class, current_path = queue.popleft()
190
+ for attr, next_class in cls._edges_for_class(current_class):
191
+ if next_class in visited:
192
+ continue
193
+ next_path = [*current_path, attr]
194
+ if next_class == destination_class:
195
+ return next_path
196
+ visited.add(next_class)
197
+ queue.append((next_class, next_path))
198
+
199
+ return None
200
+
201
+ @staticmethod
202
+ def _destination_name(
203
+ path_destination: PathDestination | type[GeneralManager] | str,
204
+ ) -> PathDestination:
205
+ """Return the class-name key for a path destination input."""
206
+ if isinstance(path_destination, type):
207
+ return path_destination.__name__
208
+ return path_destination
209
+
210
+ def _get_or_create_tracer(
211
+ self,
212
+ destination_class_name: PathDestination,
213
+ ) -> PathTracer | None:
214
+ """Return the cached tracer for one pair, computing only that pair when needed."""
215
+ if self.start_class_name == destination_class_name:
216
+ return None
217
+
218
+ key = (self.start_class_name, destination_class_name)
219
+ tracer = self.mapping.get(key)
220
+ if tracer is not None:
221
+ return tracer
222
+
223
+ start_class = self._classes_by_name.get(self.start_class_name)
224
+ destination_class = self._classes_by_name.get(destination_class_name)
225
+ if start_class is None or destination_class is None:
226
+ return None
227
+
228
+ path = self._find_path(self.start_class_name, destination_class_name)
229
+ tracer = PathTracer(start_class, destination_class, path, search=False)
230
+ self.mapping[key] = tracer
231
+ return tracer
80
232
 
81
233
  def __init__(
82
234
  self,
@@ -113,23 +265,18 @@ class PathMap:
113
265
  """
114
266
  Retrieve the cached path tracer from the start class to the desired destination.
115
267
 
116
- ``None`` means no cached key exists for the start/destination names. A
117
- non-``None`` tracer can still be unreachable; inspect ``tracer.path`` for
118
- ``None`` before treating it as a usable route.
268
+ ``None`` means either the start or destination class name is not
269
+ registered, or the requested pair uses the same start and destination
270
+ class. A non-``None`` tracer can still be unreachable; inspect
271
+ ``tracer.path`` for ``None`` before treating it as a usable route.
119
272
 
120
273
  Parameters:
121
274
  path_destination (PathDestination | type[GeneralManager] | str): Target manager identifier, either as a manager class or string class name. Destination instances are not accepted.
122
275
 
123
276
  Returns:
124
- PathTracer | None: The cached tracer for the registered class-name pair, or None when no mapping key exists.
277
+ PathTracer | None: The cached tracer for the registered class-name pair, or None when no mapping key can be created.
125
278
  """
126
- if isinstance(path_destination, type):
127
- path_destination = path_destination.__name__
128
-
129
- tracer = self.mapping.get((self.start_class_name, path_destination), None)
130
- if not tracer:
131
- return None
132
- return tracer
279
+ return self._get_or_create_tracer(self._destination_name(path_destination))
133
280
 
134
281
  def go_to(
135
282
  self, path_destination: PathDestination | type[GeneralManager] | str
@@ -137,11 +284,11 @@ class PathMap:
137
284
  """
138
285
  Traverse the cached path from the configured start to the given destination.
139
286
 
140
- The lookup first resolves a cached tracer. Missing tracer keys and
141
- tracers with ``path is None`` return ``None``. A tracer with an empty
142
- path also returns ``None`` because no traversal is required. If a
143
- traversable path exists but this PathMap was created from a class or
144
- string start, MissingStartInstanceError is raised.
287
+ The lookup lazily resolves and caches the requested tracer. Missing
288
+ tracer keys and tracers with ``path is None`` return ``None``. A tracer
289
+ with an empty path also returns ``None`` because no traversal is
290
+ required. If a traversable path exists but this PathMap was created from
291
+ a class or string start, MissingStartInstanceError is raised.
145
292
 
146
293
  Parameters:
147
294
  path_destination (PathDestination | type[GeneralManager] | str): Destination specified as a manager class or string class name. Destination instances are not accepted.
@@ -152,10 +299,7 @@ class PathMap:
152
299
  Raises:
153
300
  MissingStartInstanceError: If the cached path requires a concrete start instance but the PathMap was constructed without one.
154
301
  """
155
- if isinstance(path_destination, type):
156
- path_destination = path_destination.__name__
157
-
158
- tracer = self.mapping.get((self.start_class_name, path_destination), None)
302
+ tracer = self._get_or_create_tracer(self._destination_name(path_destination))
159
303
  if not tracer:
160
304
  return None
161
305
  if not tracer.path:
@@ -166,18 +310,29 @@ class PathMap:
166
310
 
167
311
  def get_all_connected(self) -> set[str]:
168
312
  """
169
- Return the set of destination class names that are reachable from the configured start.
313
+ Return the set of destination class names reachable from the configured start.
170
314
 
171
315
  Returns:
172
316
  set[str]: Destination class names reachable from the current start_class_name.
173
317
  """
318
+ start_class = self._classes_by_name.get(self.start_class_name)
319
+ if start_class is None:
320
+ return set()
321
+
174
322
  connected_classes: set[str] = set()
175
- for path_tuple, path_obj in self.mapping.items():
176
- if path_tuple[0] == self.start_class_name:
177
- destination_class_name = path_tuple[1]
178
- if path_obj.path is None:
323
+ visited = {start_class}
324
+ queue: deque[type[GeneralManager]] = deque([start_class])
325
+
326
+ while queue:
327
+ current_class = queue.popleft()
328
+ for _attr, next_class in self._edges_for_class(current_class):
329
+ if next_class in visited:
179
330
  continue
180
- connected_classes.add(destination_class_name)
331
+ visited.add(next_class)
332
+ if self._classes_by_name.get(next_class.__name__) is next_class:
333
+ connected_classes.add(next_class.__name__)
334
+ queue.append(next_class)
335
+
181
336
  return connected_classes
182
337
 
183
338
 
@@ -191,7 +346,12 @@ class PathTracer:
191
346
  """
192
347
 
193
348
  def __init__(
194
- self, start_class: type[GeneralManager], destination_class: type[GeneralManager]
349
+ self,
350
+ start_class: type[GeneralManager],
351
+ destination_class: type[GeneralManager],
352
+ path: list[str] | None = None,
353
+ *,
354
+ search: bool = True,
195
355
  ) -> None:
196
356
  """
197
357
  Initialise a path tracer between two manager classes.
@@ -199,63 +359,55 @@ class PathTracer:
199
359
  Parameters:
200
360
  start_class (type[GeneralManager]): Origin manager class where traversal begins.
201
361
  destination_class (type[GeneralManager]): Target manager class to reach.
362
+ path (list[str] | None): Precomputed path used by lazy PathMap lookup when search is False.
363
+ search (bool): Whether to compute the path during construction.
202
364
 
203
365
  Returns:
204
366
  None
205
367
  """
206
368
  self.start_class = start_class
207
369
  self.destination_class = destination_class
208
- if self.start_class == self.destination_class:
209
- self.path: list[str] | None = []
370
+ if not search:
371
+ self.path = path
372
+ elif self.start_class == self.destination_class:
373
+ self.path = []
210
374
  else:
211
- self.path = self.create_path(start_class, [])
375
+ self.path = self.create_path(start_class, [], {start_class})
212
376
 
213
377
  def create_path(
214
- self, current_manager: type[GeneralManager], path: list[str]
378
+ self,
379
+ current_manager: type[GeneralManager],
380
+ path: list[str],
381
+ visited_managers: set[type[GeneralManager]] | None = None,
215
382
  ) -> list[str] | None:
216
383
  """
217
384
  Recursively compute the traversal path from `current_manager` to the destination class.
218
385
 
219
386
  Candidate edges come from `Interface.get_attribute_types()` entries that
220
387
  expose a `type` key and from `@GraphQLProperty` return annotations. Only
221
- GeneralManager subclasses are traversed. The search skips attributes
222
- already in the current path and skips edges back to the original start
223
- class to avoid cycles.
388
+ GeneralManager subclasses are traversed. Each manager class is expanded
389
+ at most once, which bounds missing-path and cyclic graph searches.
224
390
 
225
391
  Parameters:
226
392
  current_manager (type[GeneralManager]): Manager class used as the current traversal node.
227
393
  path (list[str]): Sequence of attribute names accumulated along the traversal.
394
+ visited_managers (set[type[GeneralManager]] | None): Manager classes already expanded during this search.
228
395
 
229
396
  Returns:
230
397
  list[str] | None: Updated list of attribute names leading to the destination, or None if no route exists.
231
398
  """
232
- current_connections: dict[str, object] = {}
233
- for (
234
- attr_name,
235
- attr_value,
236
- ) in current_manager.Interface.get_attribute_types().items():
237
- if isinstance(attr_value, Mapping):
238
- current_connections[attr_name] = attr_value.get("type")
239
- for attr_name, attr_value in current_manager.__dict__.items():
240
- if not isinstance(attr_value, GraphQLProperty):
241
- continue
242
- type_hints = get_args(attr_value.graphql_type_hint)
243
- field_type = (
244
- type_hints[0]
245
- if type_hints
246
- else cast(type[object], attr_value.graphql_type_hint)
247
- )
248
- current_connections[attr_name] = field_type
249
- for attr, attr_type in current_connections.items():
250
- if attr in path or attr_type == self.start_class:
251
- continue
252
- if attr_type is None or not isinstance(attr_type, type):
253
- continue
254
- if not issubclass(attr_type, GeneralManager):
399
+ if visited_managers is None:
400
+ visited_managers = {current_manager}
401
+
402
+ for attr, attr_type in _iter_manager_connections(current_manager):
403
+ if attr_type == self.start_class:
255
404
  continue
256
405
  if attr_type == self.destination_class:
257
406
  return [*path, attr]
258
- result = self.create_path(attr_type, [*path, attr])
407
+ if attr_type in visited_managers:
408
+ continue
409
+ visited_managers.add(attr_type)
410
+ result = self.create_path(attr_type, [*path, attr], visited_managers)
259
411
  if result:
260
412
  return result
261
413
 
File without changes