GeneralManager 0.46.0__tar.gz → 0.47.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. {generalmanager-0.46.0/src/GeneralManager.egg-info → generalmanager-0.47.0}/PKG-INFO +1 -1
  2. {generalmanager-0.46.0 → generalmanager-0.47.0}/pyproject.toml +1 -1
  3. {generalmanager-0.46.0 → generalmanager-0.47.0/src/GeneralManager.egg-info}/PKG-INFO +1 -1
  4. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/GeneralManager.egg-info/SOURCES.txt +2 -0
  5. generalmanager-0.47.0/src/general_manager/api/graphql_prefetch.py +182 -0
  6. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/api/graphql_resolvers.py +25 -0
  7. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/api/graphql_subscriptions.py +9 -51
  8. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/cache/cache_decorator.py +44 -30
  9. generalmanager-0.47.0/src/general_manager/cache/dependency_cache.py +154 -0
  10. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/cache/dependency_publish.py +25 -23
  11. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/cache/run_context.py +22 -2
  12. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/utils/testing.py +25 -2
  13. {generalmanager-0.46.0 → generalmanager-0.47.0}/LICENSE +0 -0
  14. {generalmanager-0.46.0 → generalmanager-0.47.0}/README.md +0 -0
  15. {generalmanager-0.46.0 → generalmanager-0.47.0}/setup.cfg +0 -0
  16. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/GeneralManager.egg-info/dependency_links.txt +0 -0
  17. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/GeneralManager.egg-info/requires.txt +0 -0
  18. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/GeneralManager.egg-info/top_level.txt +0 -0
  19. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/__init__.py +0 -0
  20. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/_types/__init__.py +0 -0
  21. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/_types/api.py +0 -0
  22. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/_types/bucket.py +0 -0
  23. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/_types/cache.py +0 -0
  24. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/_types/factory.py +0 -0
  25. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/_types/general_manager.py +0 -0
  26. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/_types/interface.py +0 -0
  27. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/_types/manager.py +0 -0
  28. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/_types/measurement.py +0 -0
  29. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/_types/permission.py +0 -0
  30. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/_types/rule.py +0 -0
  31. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/_types/search.py +0 -0
  32. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/_types/utils.py +0 -0
  33. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/api/__init__.py +0 -0
  34. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/api/graphql.py +0 -0
  35. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/api/graphql_errors.py +0 -0
  36. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/api/graphql_mutations.py +0 -0
  37. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/api/graphql_search.py +0 -0
  38. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/api/graphql_subscription_consumer.py +0 -0
  39. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/api/graphql_view.py +0 -0
  40. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/api/mutation.py +0 -0
  41. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/api/property.py +0 -0
  42. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/api/registry.py +0 -0
  43. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/api/remote_api.py +0 -0
  44. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/api/remote_invalidation.py +0 -0
  45. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/api/remote_invalidation_client.py +0 -0
  46. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/apps.py +0 -0
  47. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/bootstrap.py +0 -0
  48. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/bucket/__init__.py +0 -0
  49. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/bucket/base_bucket.py +0 -0
  50. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/bucket/calculation_bucket.py +0 -0
  51. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/bucket/database_bucket.py +0 -0
  52. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/bucket/group_bucket.py +0 -0
  53. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/bucket/request_bucket.py +0 -0
  54. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/cache/__init__.py +0 -0
  55. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/cache/cache_tracker.py +0 -0
  56. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/cache/dependency_index.py +0 -0
  57. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/cache/model_dependency_collector.py +0 -0
  58. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/cache/signals.py +0 -0
  59. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/conf.py +0 -0
  60. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/factory/__init__.py +0 -0
  61. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/factory/auto_factory.py +0 -0
  62. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/factory/factories.py +0 -0
  63. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/factory/factory_methods.py +0 -0
  64. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/__init__.py +0 -0
  65. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/base_interface.py +0 -0
  66. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/bundles/__init__.py +0 -0
  67. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/bundles/calculation.py +0 -0
  68. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/bundles/database.py +0 -0
  69. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/bundles/remote_manager.py +0 -0
  70. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/bundles/request.py +0 -0
  71. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/__init__.py +0 -0
  72. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/base.py +0 -0
  73. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/builtin.py +0 -0
  74. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/calculation/__init__.py +0 -0
  75. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/calculation/_compat.py +0 -0
  76. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/calculation/lifecycle.py +0 -0
  77. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/configuration.py +0 -0
  78. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/core/observability.py +0 -0
  79. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/core/utils.py +0 -0
  80. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/exceptions.py +0 -0
  81. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/existing_model/__init__.py +0 -0
  82. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/existing_model/_compat.py +0 -0
  83. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/existing_model/resolution.py +0 -0
  84. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/factory.py +0 -0
  85. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/orm/__init__.py +0 -0
  86. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/orm/_compat.py +0 -0
  87. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/orm/history.py +0 -0
  88. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/orm/lifecycle.py +0 -0
  89. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/orm/mutations.py +0 -0
  90. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/orm/support.py +0 -0
  91. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/orm_utils/__init__.py +0 -0
  92. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/orm_utils/django_manager_utils.py +0 -0
  93. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/orm_utils/field_descriptors.py +0 -0
  94. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/orm_utils/payload_normalizer.py +0 -0
  95. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/read_only/__init__.py +0 -0
  96. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/read_only/_compat.py +0 -0
  97. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/read_only/lifecycle.py +0 -0
  98. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/read_only/management.py +0 -0
  99. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/registry.py +0 -0
  100. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/remote_manager.py +0 -0
  101. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/capabilities/request/__init__.py +0 -0
  102. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/infrastructure/__init__.py +0 -0
  103. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/infrastructure/startup_hooks.py +0 -0
  104. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/infrastructure/system_checks.py +0 -0
  105. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/interfaces/__init__.py +0 -0
  106. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/interfaces/calculation.py +0 -0
  107. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/interfaces/database.py +0 -0
  108. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/interfaces/existing_model.py +0 -0
  109. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/interfaces/read_only.py +0 -0
  110. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/interfaces/remote_manager.py +0 -0
  111. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/interfaces/request.py +0 -0
  112. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/manifests/__init__.py +0 -0
  113. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/manifests/capability_builder.py +0 -0
  114. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/manifests/capability_manifest.py +0 -0
  115. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/manifests/capability_models.py +0 -0
  116. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/orm_interface.py +0 -0
  117. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/requests.py +0 -0
  118. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/utils/__init__.py +0 -0
  119. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/utils/database_interface_protocols.py +0 -0
  120. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/utils/errors.py +0 -0
  121. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/interface/utils/models.py +0 -0
  122. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/logging.py +0 -0
  123. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/management/commands/search_index.py +0 -0
  124. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/management/commands/seed_manager_landscape.py +0 -0
  125. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/management/commands/shell.py +0 -0
  126. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/management/commands/workflow_drain_outbox.py +0 -0
  127. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/management/commands/workflow_replay_dead_letters.py +0 -0
  128. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/manager/__init__.py +0 -0
  129. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/manager/general_manager.py +0 -0
  130. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/manager/group_manager.py +0 -0
  131. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/manager/input.py +0 -0
  132. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/manager/meta.py +0 -0
  133. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/measurement/__init__.py +0 -0
  134. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/measurement/measurement.py +0 -0
  135. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/measurement/measurement_field.py +0 -0
  136. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/metrics/__init__.py +0 -0
  137. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/metrics/graphql.py +0 -0
  138. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/migrations/0001_initial.py +0 -0
  139. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/migrations/0002_workflow_outbox_scaling_indexes.py +0 -0
  140. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/migrations/0003_workflow_execution_correlation_constraint.py +0 -0
  141. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/migrations/__init__.py +0 -0
  142. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/models.py +0 -0
  143. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/permission/__init__.py +0 -0
  144. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/permission/audit.py +0 -0
  145. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/permission/base_permission.py +0 -0
  146. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/permission/graphql_capabilities.py +0 -0
  147. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/permission/manager_based_permission.py +0 -0
  148. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/permission/mutation_permission.py +0 -0
  149. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/permission/permission_checks.py +0 -0
  150. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/permission/permission_data_manager.py +0 -0
  151. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/permission/utils.py +0 -0
  152. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/public_api_registry.py +0 -0
  153. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/py.typed +0 -0
  154. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/rule/__init__.py +0 -0
  155. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/rule/handler.py +0 -0
  156. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/rule/rule.py +0 -0
  157. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/search/__init__.py +0 -0
  158. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/search/async_tasks.py +0 -0
  159. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/search/backend.py +0 -0
  160. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/search/backend_registry.py +0 -0
  161. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/search/backends/__init__.py +0 -0
  162. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/search/backends/dev.py +0 -0
  163. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/search/backends/meilisearch.py +0 -0
  164. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/search/backends/opensearch.py +0 -0
  165. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/search/backends/typesense.py +0 -0
  166. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/search/config.py +0 -0
  167. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/search/indexer.py +0 -0
  168. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/search/registry.py +0 -0
  169. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/search/utils.py +0 -0
  170. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/seeding/__init__.py +0 -0
  171. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/seeding/manager_landscape.py +0 -0
  172. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/utils/__init__.py +0 -0
  173. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/utils/args_to_kwargs.py +0 -0
  174. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/utils/filter_parser.py +0 -0
  175. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/utils/format_string.py +0 -0
  176. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/utils/json_encoder.py +0 -0
  177. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/utils/make_cache_key.py +0 -0
  178. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/utils/none_to_zero.py +0 -0
  179. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/utils/path_mapping.py +0 -0
  180. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/utils/public_api.py +0 -0
  181. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/utils/type_checks.py +0 -0
  182. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/workflow/__init__.py +0 -0
  183. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/workflow/actions.py +0 -0
  184. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/workflow/backend_registry.py +0 -0
  185. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/workflow/backends/__init__.py +0 -0
  186. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/workflow/backends/celery.py +0 -0
  187. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/workflow/backends/local.py +0 -0
  188. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/workflow/backends/n8n.py +0 -0
  189. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/workflow/config.py +0 -0
  190. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/workflow/engine.py +0 -0
  191. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/workflow/event_registry.py +0 -0
  192. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/workflow/events.py +0 -0
  193. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/workflow/models.py +0 -0
  194. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/workflow/signal_bridge.py +0 -0
  195. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/workflow/tasks.py +0 -0
  196. {generalmanager-0.46.0 → generalmanager-0.47.0}/src/general_manager/workflow/telemetry.py +0 -0
  197. {generalmanager-0.46.0 → generalmanager-0.47.0}/tests/test_settings.py +0 -0
  198. {generalmanager-0.46.0 → generalmanager-0.47.0}/tests/test_urls.py +0 -0
  199. {generalmanager-0.46.0 → generalmanager-0.47.0}/tests/testing_asgi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GeneralManager
3
- Version: 0.46.0
3
+ Version: 0.47.0
4
4
  Summary: Modular Django-based data management framework with ORM, GraphQL, fine-grained permissions, rule validation, calculations and caching.
5
5
  Author-email: Tim Kleindick <tkleindick@yahoo.de>
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "GeneralManager"
7
- version = "0.46.0"
7
+ version = "0.47.0"
8
8
  description = "Modular Django-based data management framework with ORM, GraphQL, fine-grained permissions, rule validation, calculations and caching."
9
9
  readme = "README.md"
10
10
  authors = [{ name = "Tim Kleindick", email = "tkleindick@yahoo.de" }]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GeneralManager
3
- Version: 0.46.0
3
+ Version: 0.47.0
4
4
  Summary: Modular Django-based data management framework with ORM, GraphQL, fine-grained permissions, rule validation, calculations and caching.
5
5
  Author-email: Tim Kleindick <tkleindick@yahoo.de>
6
6
  License: MIT License
@@ -31,6 +31,7 @@ src/general_manager/api/__init__.py
31
31
  src/general_manager/api/graphql.py
32
32
  src/general_manager/api/graphql_errors.py
33
33
  src/general_manager/api/graphql_mutations.py
34
+ src/general_manager/api/graphql_prefetch.py
34
35
  src/general_manager/api/graphql_resolvers.py
35
36
  src/general_manager/api/graphql_search.py
36
37
  src/general_manager/api/graphql_subscription_consumer.py
@@ -51,6 +52,7 @@ src/general_manager/bucket/request_bucket.py
51
52
  src/general_manager/cache/__init__.py
52
53
  src/general_manager/cache/cache_decorator.py
53
54
  src/general_manager/cache/cache_tracker.py
55
+ src/general_manager/cache/dependency_cache.py
54
56
  src/general_manager/cache/dependency_index.py
55
57
  src/general_manager/cache/dependency_publish.py
56
58
  src/general_manager/cache/model_dependency_collector.py
@@ -0,0 +1,182 @@
1
+ """Internal GraphQL helpers for dependency-cache prefetch planning."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable, Iterable, Mapping
6
+ from dataclasses import dataclass
7
+ import re
8
+ from typing import Any
9
+
10
+ from django.core.cache import cache as django_cache
11
+ from graphql import GraphQLResolveInfo
12
+ from graphql.language.ast import (
13
+ FieldNode,
14
+ FragmentSpreadNode,
15
+ InlineFragmentNode,
16
+ SelectionSetNode,
17
+ )
18
+
19
+ from general_manager.api.property import GraphQLProperty
20
+ from general_manager.cache.dependency_cache import (
21
+ DependencyCacheBackend,
22
+ DependencyCacheHit,
23
+ read_many_dependency_cache_hits,
24
+ )
25
+ from general_manager.cache.run_context import current_calculation_run_context
26
+ from general_manager.manager.general_manager import GeneralManager
27
+ from general_manager.utils.make_cache_key import make_cache_key
28
+
29
+
30
+ def normalize_graphql_name(name: str) -> str:
31
+ """Convert a GraphQL field name to the Python attribute name."""
32
+ if "_" in name:
33
+ return name
34
+ snake = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
35
+ snake = re.sub("([a-z0-9])([A-Z])", r"\1_\2", snake)
36
+ return snake.lower()
37
+
38
+
39
+ def collect_selected_graphql_property_names(
40
+ info: GraphQLResolveInfo,
41
+ manager_class: type[Any],
42
+ *,
43
+ root_field: str,
44
+ normalize_name: Callable[[str], str] = normalize_graphql_name,
45
+ ) -> set[str]:
46
+ """Return GraphQLProperty names selected directly under root_field."""
47
+ interface_cls = getattr(manager_class, "Interface", None)
48
+ if interface_cls is None:
49
+ return set()
50
+ available_properties = set(interface_cls.get_graph_ql_properties().keys())
51
+ if not available_properties:
52
+ return set()
53
+
54
+ property_names: set[str] = set()
55
+
56
+ def collect_direct_fields(
57
+ selection_set: SelectionSetNode | None,
58
+ visited: frozenset[str],
59
+ ) -> None:
60
+ if selection_set is None:
61
+ return
62
+ for selection in selection_set.selections:
63
+ if isinstance(selection, FieldNode):
64
+ normalized = normalize_name(selection.name.value)
65
+ if normalized in available_properties:
66
+ property_names.add(normalized)
67
+ elif isinstance(selection, InlineFragmentNode):
68
+ collect_direct_fields(selection.selection_set, visited)
69
+ elif isinstance(selection, FragmentSpreadNode):
70
+ fragment_name = selection.name.value
71
+ if fragment_name in visited:
72
+ continue
73
+ fragment = info.fragments.get(fragment_name)
74
+ if fragment is not None:
75
+ collect_direct_fields(
76
+ fragment.selection_set,
77
+ visited | frozenset((fragment_name,)),
78
+ )
79
+
80
+ def inspect_for_root(
81
+ selection_set: SelectionSetNode | None,
82
+ visited: frozenset[str],
83
+ ) -> None:
84
+ if selection_set is None:
85
+ return
86
+ for selection in selection_set.selections:
87
+ if isinstance(selection, FieldNode):
88
+ if selection.name.value == root_field:
89
+ collect_direct_fields(selection.selection_set, visited)
90
+ else:
91
+ inspect_for_root(selection.selection_set, visited)
92
+ elif isinstance(selection, InlineFragmentNode):
93
+ inspect_for_root(selection.selection_set, visited)
94
+ elif isinstance(selection, FragmentSpreadNode):
95
+ fragment_name = selection.name.value
96
+ if fragment_name in visited:
97
+ continue
98
+ fragment = info.fragments.get(fragment_name)
99
+ if fragment is not None:
100
+ inspect_for_root(
101
+ fragment.selection_set,
102
+ visited | frozenset((fragment_name,)),
103
+ )
104
+
105
+ for field_node in getattr(info, "field_nodes", ()):
106
+ inspect_for_root(field_node.selection_set, frozenset())
107
+ return property_names
108
+
109
+
110
+ @dataclass(frozen=True, slots=True)
111
+ class DependencyCachePrefetchPlan:
112
+ """A dependency-cache key planned for one GraphQL list item field."""
113
+
114
+ cache_key: str
115
+ instance: GeneralManager
116
+ property_name: str
117
+
118
+
119
+ def plan_dependency_cache_prefetches(
120
+ instances: Iterable[GeneralManager],
121
+ manager_class: type[Any],
122
+ property_names: Iterable[str],
123
+ *,
124
+ can_read_field: Callable[[GeneralManager, str], bool],
125
+ ) -> dict[str, DependencyCachePrefetchPlan]:
126
+ """Build dependency-cache key plans for selected readable properties."""
127
+ interface_cls = getattr(manager_class, "Interface", None)
128
+ if interface_cls is None:
129
+ return {}
130
+
131
+ available_properties = interface_cls.get_graph_ql_properties()
132
+ instance_list = list(instances)
133
+ selected = set(property_names)
134
+ plans: dict[str, DependencyCachePrefetchPlan] = {}
135
+
136
+ for property_name in selected:
137
+ prop = available_properties.get(property_name)
138
+ if not isinstance(prop, GraphQLProperty):
139
+ continue
140
+ if prop.cache != "dependency":
141
+ continue
142
+
143
+ cached_getter = prop._get_cached_fget()
144
+ for instance in instance_list:
145
+ if not can_read_field(instance, property_name):
146
+ continue
147
+ cache_key = make_cache_key(cached_getter, (instance,), {})
148
+ plans.setdefault(
149
+ cache_key,
150
+ DependencyCachePrefetchPlan(
151
+ cache_key=cache_key,
152
+ instance=instance,
153
+ property_name=property_name,
154
+ ),
155
+ )
156
+ return plans
157
+
158
+
159
+ DependencyCacheBulkReader = Callable[
160
+ [DependencyCacheBackend, Iterable[str]],
161
+ dict[str, DependencyCacheHit],
162
+ ]
163
+
164
+
165
+ def prefetch_dependency_cache_hits(
166
+ plans: Mapping[str, DependencyCachePrefetchPlan],
167
+ *,
168
+ cache_backend: DependencyCacheBackend = django_cache,
169
+ reader: DependencyCacheBulkReader | None = None,
170
+ ) -> dict[str, DependencyCacheHit]:
171
+ """Bulk-read planned dependency-cache hits into the active run context."""
172
+ context = current_calculation_run_context()
173
+ if context is None or not plans:
174
+ return {}
175
+
176
+ if reader is None:
177
+ reader = read_many_dependency_cache_hits
178
+ cache_keys = tuple(plans.keys())
179
+ hits = reader(cache_backend, cache_keys)
180
+ if hits:
181
+ context.set_dependency_cache_hits(hits)
182
+ return hits
@@ -21,6 +21,11 @@ from general_manager.bucket.base_bucket import Bucket
21
21
  from general_manager.manager.general_manager import GeneralManager
22
22
  from general_manager.measurement.measurement import Measurement
23
23
  from general_manager.api.graphql_errors import get_read_permission_filter
24
+ from general_manager.api.graphql_prefetch import (
25
+ collect_selected_graphql_property_names,
26
+ plan_dependency_cache_prefetches,
27
+ prefetch_dependency_cache_hits,
28
+ )
24
29
  from general_manager.permission.graphql_capabilities import (
25
30
  get_capability_context,
26
31
  get_graphql_capabilities,
@@ -543,6 +548,26 @@ def create_list_resolver(
543
548
  qs_paginated: Any = apply_pagination(qs_grouped, page, page_size)
544
549
  if not hasattr(qs_paginated, "groups"):
545
550
  qs_paginated = list(qs_paginated)
551
+ if isinstance(qs_paginated, list):
552
+ selected_property_names = collect_selected_graphql_property_names(
553
+ info,
554
+ manager_class,
555
+ root_field="items",
556
+ )
557
+ if selected_property_names:
558
+ prefetch_plans = plan_dependency_cache_prefetches(
559
+ qs_paginated,
560
+ manager_class,
561
+ selected_property_names,
562
+ can_read_field=lambda instance, property_name: (
563
+ check_read_permission(
564
+ instance,
565
+ info,
566
+ property_name,
567
+ )
568
+ ),
569
+ )
570
+ prefetch_dependency_cache_hits(prefetch_plans)
546
571
  if isinstance(qs_paginated, list) and selection_includes_path(
547
572
  info, ("items", "capabilities")
548
573
  ):
@@ -16,19 +16,15 @@ from typing import Any, Callable, Iterable, TYPE_CHECKING, cast
16
16
 
17
17
  from channels.layers import BaseChannelLayer, get_channel_layer # type: ignore[import]
18
18
 
19
- from graphql.language.ast import (
20
- FieldNode,
21
- FragmentSpreadNode,
22
- InlineFragmentNode,
23
- SelectionSetNode,
24
- )
25
-
26
19
  from general_manager.cache.cache_tracker import DependencyTracker
27
20
  from general_manager.cache.dependency_index import (
28
21
  Dependency,
29
22
  parse_dependency_identifier,
30
23
  serialize_dependency_identifier,
31
24
  )
25
+ from general_manager.api.graphql_prefetch import (
26
+ collect_selected_graphql_property_names,
27
+ )
32
28
  from general_manager.logging import get_logger
33
29
  from general_manager.manager.general_manager import GeneralManager
34
30
  from general_manager.api.graphql_errors import MissingChannelLayerError
@@ -334,50 +330,12 @@ def subscription_property_names(
334
330
  Returns:
335
331
  Set of selected ``GraphQLProperty`` names; empty set if none found.
336
332
  """
337
- interface_cls = getattr(manager_class, "Interface", None)
338
- if interface_cls is None:
339
- return set()
340
- available_properties = set(interface_cls.get_graph_ql_properties().keys())
341
- if not available_properties:
342
- return set()
343
-
344
- property_names: set[str] = set()
345
-
346
- def collect_from_selection(selection_set: SelectionSetNode | None) -> None:
347
- if selection_set is None:
348
- return
349
- for selection in selection_set.selections:
350
- if isinstance(selection, FieldNode):
351
- name = selection.name.value
352
- normalized = normalize_graphql_name(name)
353
- if normalized in available_properties:
354
- property_names.add(normalized)
355
- elif isinstance(selection, FragmentSpreadNode):
356
- fragment = info.fragments.get(selection.name.value)
357
- if fragment is not None:
358
- collect_from_selection(fragment.selection_set)
359
- elif isinstance(selection, InlineFragmentNode):
360
- collect_from_selection(selection.selection_set)
361
-
362
- def inspect_selection_set(selection_set: SelectionSetNode | None) -> None:
363
- if selection_set is None:
364
- return
365
- for selection in selection_set.selections:
366
- if isinstance(selection, FieldNode):
367
- if selection.name.value == "item":
368
- collect_from_selection(selection.selection_set)
369
- else:
370
- inspect_selection_set(selection.selection_set)
371
- elif isinstance(selection, FragmentSpreadNode):
372
- fragment = info.fragments.get(selection.name.value)
373
- if fragment is not None:
374
- inspect_selection_set(fragment.selection_set)
375
- elif isinstance(selection, InlineFragmentNode):
376
- inspect_selection_set(selection.selection_set)
377
-
378
- for node in info.field_nodes:
379
- inspect_selection_set(node.selection_set)
380
- return property_names
333
+ return collect_selected_graphql_property_names(
334
+ info,
335
+ manager_class,
336
+ root_field="item",
337
+ normalize_name=normalize_graphql_name,
338
+ )
381
339
 
382
340
 
383
341
  # ---------------------------------------------------------------------------
@@ -16,6 +16,11 @@ from typing import (
16
16
  from django.core.cache import cache as django_cache
17
17
 
18
18
  from general_manager.cache.cache_tracker import DependencyTracker
19
+ from general_manager.cache.dependency_cache import (
20
+ DependencyCacheHit,
21
+ read_dependency_cache_hit,
22
+ replay_dependency_cache_hit,
23
+ )
19
24
  from general_manager.cache.dependency_index import (
20
25
  Dependency,
21
26
  get_dependency_generation,
@@ -26,9 +31,12 @@ from general_manager.cache.dependency_publish import (
26
31
  acquire_compute_lease,
27
32
  publish_dependency_cache_entry,
28
33
  release_compute_lease,
29
- wait_for_cached_dependency_value,
34
+ wait_for_cached_dependency_hit,
35
+ )
36
+ from general_manager.cache.run_context import (
37
+ current_calculation_run_context,
38
+ ensure_calculation_run_context,
30
39
  )
31
- from general_manager.cache.run_context import ensure_calculation_run_context
32
40
  from general_manager.cache.model_dependency_collector import ModelDependencyCollector
33
41
  from general_manager.logging import get_logger
34
42
  from general_manager.utils.make_cache_key import make_cache_key
@@ -179,52 +187,59 @@ def cached(
179
187
  )
180
188
  return result
181
189
 
182
- deps_key = f"{key}:deps"
183
-
184
- def track_cached_dependencies() -> None:
185
- cached_deps = cache_backend.get(deps_key)
186
- if cached_deps:
187
- for class_name, operation, identifier in cached_deps:
188
- DependencyTracker.track(class_name, operation, identifier)
189
-
190
- cached_result = cache_backend.get(key, _SENTINEL)
191
- if cached_result is not _SENTINEL:
192
- track_cached_dependencies()
190
+ def return_cached_hit(hit: DependencyCacheHit, message: str) -> object:
191
+ replay_dependency_cache_hit(hit)
193
192
  logger.debug(
194
- "cache hit",
193
+ message,
195
194
  context={
196
195
  "function": func.__qualname__,
197
196
  "key": key,
198
197
  "scope": scope,
199
198
  },
200
199
  )
201
- return cached_result
200
+ return hit.value
201
+
202
+ prefetch_context = current_calculation_run_context()
203
+ if prefetch_context is not None:
204
+ prefetched_hit = prefetch_context.get_dependency_cache_hit(
205
+ key, _SENTINEL
206
+ )
207
+ if isinstance(prefetched_hit, DependencyCacheHit):
208
+ return return_cached_hit(
209
+ prefetched_hit,
210
+ "cache hit from dependency prefetch",
211
+ )
212
+
213
+ cached_hit = read_dependency_cache_hit(
214
+ cache_backend,
215
+ key,
216
+ sentinel=_SENTINEL,
217
+ )
218
+ if cached_hit is not _SENTINEL:
219
+ return return_cached_hit(cached_hit, "cache hit")
202
220
 
203
221
  lease = acquire_compute_lease(key)
204
222
  while lease is None:
205
- cached_result = wait_for_cached_dependency_value(
223
+ cached_hit = wait_for_cached_dependency_hit(
206
224
  cache_backend,
207
225
  key,
208
226
  sentinel=_SENTINEL,
209
227
  )
210
- if cached_result is not _SENTINEL:
211
- track_cached_dependencies()
212
- logger.debug(
228
+ if cached_hit is not _SENTINEL:
229
+ return return_cached_hit(
230
+ cached_hit,
213
231
  "cache hit after waiting for dependency publish",
214
- context={
215
- "function": func.__qualname__,
216
- "key": key,
217
- "scope": scope,
218
- },
219
232
  )
220
- return cached_result
221
233
  lease = acquire_compute_lease(key)
222
234
 
223
235
  try:
224
- cached_result = cache_backend.get(key, _SENTINEL)
225
- if cached_result is not _SENTINEL:
226
- track_cached_dependencies()
227
- return cached_result
236
+ cached_hit = read_dependency_cache_hit(
237
+ cache_backend,
238
+ key,
239
+ sentinel=_SENTINEL,
240
+ )
241
+ if cached_hit is not _SENTINEL:
242
+ return return_cached_hit(cached_hit, "cache hit")
228
243
 
229
244
  started_generation = get_dependency_generation()
230
245
  with DependencyTracker() as dependencies:
@@ -240,7 +255,6 @@ def cached(
240
255
  try:
241
256
  publish_dependency_cache_entry(
242
257
  cache_key=key,
243
- deps_key=deps_key,
244
258
  result=result,
245
259
  dependencies=dependencies,
246
260
  cache_backend=cache_backend,
@@ -0,0 +1,154 @@
1
+ """Internal helpers for dependency-scoped cache entry reads."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Any, Iterable, Mapping, Protocol, TypeGuard
7
+
8
+ from general_manager.cache.cache_tracker import DependencyTracker
9
+ from general_manager.cache.dependency_index import Dependency
10
+
11
+ DEPENDENCY_CACHE_ENTRY_VERSION = 1
12
+ _MISSING = object()
13
+
14
+
15
+ @dataclass(frozen=True, slots=True)
16
+ class DependencyCacheEntry:
17
+ """Persisted dependency-cache payload stored at the main cache key."""
18
+
19
+ version: int
20
+ value: Any
21
+ dependencies: frozenset[Dependency]
22
+
23
+
24
+ @dataclass(frozen=True, slots=True)
25
+ class DependencyCacheHit:
26
+ """In-memory representation of a dependency-cache hit."""
27
+
28
+ value: Any
29
+ dependencies: frozenset[Dependency]
30
+
31
+
32
+ class DependencyCacheBackend(Protocol):
33
+ def get(self, key: str, default: Any = None) -> Any:
34
+ """Return a cached value or *default* when absent."""
35
+ ...
36
+
37
+ def set(self, key: str, value: Any, timeout: int | None = None) -> None:
38
+ """Store a cached value."""
39
+ ...
40
+
41
+
42
+ class DependencyCacheGetManyBackend(DependencyCacheBackend, Protocol):
43
+ def get_many(self, keys: Iterable[str]) -> Mapping[str, Any]:
44
+ """Return cached values for all found keys."""
45
+ ...
46
+
47
+
48
+ def make_dependency_cache_entry(
49
+ value: Any,
50
+ dependencies: Iterable[Dependency],
51
+ ) -> DependencyCacheEntry:
52
+ """Build the current persisted dependency-cache payload."""
53
+ return DependencyCacheEntry(
54
+ version=DEPENDENCY_CACHE_ENTRY_VERSION,
55
+ value=value,
56
+ dependencies=frozenset(dependencies),
57
+ )
58
+
59
+
60
+ def read_dependency_cache_hit(
61
+ cache_backend: DependencyCacheBackend,
62
+ cache_key: str,
63
+ *,
64
+ sentinel: Any = _MISSING,
65
+ ) -> DependencyCacheHit | Any:
66
+ """Read one dependency-cache entry, including legacy split entries."""
67
+ payload = cache_backend.get(cache_key, sentinel)
68
+ if payload is sentinel:
69
+ return sentinel
70
+ combined_hit = _combined_payload_to_hit(payload)
71
+ if combined_hit is _MISSING:
72
+ return sentinel
73
+ if combined_hit is not None:
74
+ return combined_hit
75
+ dependency_payload = cache_backend.get(_legacy_deps_key(cache_key), ())
76
+ return DependencyCacheHit(
77
+ value=payload,
78
+ dependencies=frozenset(dependency_payload or ()),
79
+ )
80
+
81
+
82
+ def read_many_dependency_cache_hits(
83
+ cache_backend: DependencyCacheBackend,
84
+ cache_keys: Iterable[str],
85
+ ) -> dict[str, DependencyCacheHit]:
86
+ """Bulk-read dependency-cache hits for known keys."""
87
+ keys = tuple(dict.fromkeys(cache_keys))
88
+ if not keys:
89
+ return {}
90
+ if not _supports_get_many(cache_backend):
91
+ return _read_many_without_get_many(cache_backend, keys)
92
+
93
+ payloads = cache_backend.get_many(keys)
94
+ hits: dict[str, DependencyCacheHit] = {}
95
+ legacy_keys: list[str] = []
96
+ for key in keys:
97
+ if key not in payloads:
98
+ continue
99
+ combined_hit = _combined_payload_to_hit(payloads[key])
100
+ if combined_hit is _MISSING:
101
+ continue
102
+ if isinstance(combined_hit, DependencyCacheHit):
103
+ hits[key] = combined_hit
104
+ continue
105
+ legacy_keys.append(key)
106
+
107
+ if legacy_keys:
108
+ deps_keys = {_legacy_deps_key(key): key for key in legacy_keys}
109
+ legacy_deps = cache_backend.get_many(deps_keys.keys())
110
+ for deps_key, key in deps_keys.items():
111
+ hits[key] = DependencyCacheHit(
112
+ value=payloads[key],
113
+ dependencies=frozenset(legacy_deps.get(deps_key, ()) or ()),
114
+ )
115
+ return hits
116
+
117
+
118
+ def replay_dependency_cache_hit(hit: DependencyCacheHit) -> None:
119
+ """Replay cached dependencies into active dependency tracking scopes."""
120
+ for class_name, operation, identifier in hit.dependencies:
121
+ DependencyTracker.track(class_name, operation, identifier)
122
+
123
+
124
+ def _legacy_deps_key(cache_key: str) -> str:
125
+ return f"{cache_key}:deps"
126
+
127
+
128
+ def _combined_payload_to_hit(payload: Any) -> DependencyCacheHit | None | object:
129
+ if not isinstance(payload, DependencyCacheEntry):
130
+ return None
131
+ if payload.version != DEPENDENCY_CACHE_ENTRY_VERSION:
132
+ return _MISSING
133
+ return DependencyCacheHit(
134
+ value=payload.value,
135
+ dependencies=payload.dependencies,
136
+ )
137
+
138
+
139
+ def _supports_get_many(
140
+ cache_backend: DependencyCacheBackend,
141
+ ) -> TypeGuard[DependencyCacheGetManyBackend]:
142
+ return callable(getattr(cache_backend, "get_many", None))
143
+
144
+
145
+ def _read_many_without_get_many(
146
+ cache_backend: DependencyCacheBackend,
147
+ cache_keys: tuple[str, ...],
148
+ ) -> dict[str, DependencyCacheHit]:
149
+ hits: dict[str, DependencyCacheHit] = {}
150
+ for key in cache_keys:
151
+ hit = read_dependency_cache_hit(cache_backend, key, sentinel=_MISSING)
152
+ if hit is not _MISSING:
153
+ hits[key] = hit
154
+ return hits