GeneralManager 0.47.0__tar.gz → 0.49.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.
- {generalmanager-0.47.0/src/GeneralManager.egg-info → generalmanager-0.49.0}/PKG-INFO +1 -1
- {generalmanager-0.47.0 → generalmanager-0.49.0}/pyproject.toml +1 -1
- {generalmanager-0.47.0 → generalmanager-0.49.0/src/GeneralManager.egg-info}/PKG-INFO +1 -1
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/bucket/database_bucket.py +176 -1
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/cache/cache_decorator.py +41 -21
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/cache/dependency_cache.py +10 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/cache/dependency_index.py +17 -1
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/cache/dependency_publish.py +91 -1
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/cache/run_context.py +111 -6
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/cache/signals.py +4 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/LICENSE +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/README.md +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/setup.cfg +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/GeneralManager.egg-info/SOURCES.txt +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/GeneralManager.egg-info/dependency_links.txt +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/GeneralManager.egg-info/requires.txt +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/GeneralManager.egg-info/top_level.txt +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/_types/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/_types/api.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/_types/bucket.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/_types/cache.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/_types/factory.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/_types/general_manager.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/_types/interface.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/_types/manager.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/_types/measurement.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/_types/permission.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/_types/rule.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/_types/search.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/_types/utils.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/api/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/api/graphql.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/api/graphql_errors.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/api/graphql_mutations.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/api/graphql_prefetch.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/api/graphql_resolvers.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/api/graphql_search.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/api/graphql_subscription_consumer.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/api/graphql_subscriptions.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/api/graphql_view.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/api/mutation.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/api/property.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/api/registry.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/api/remote_api.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/api/remote_invalidation.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/api/remote_invalidation_client.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/apps.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/bootstrap.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/bucket/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/bucket/base_bucket.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/bucket/calculation_bucket.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/bucket/group_bucket.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/bucket/request_bucket.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/cache/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/cache/cache_tracker.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/cache/model_dependency_collector.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/conf.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/factory/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/factory/auto_factory.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/factory/factories.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/factory/factory_methods.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/base_interface.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/bundles/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/bundles/calculation.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/bundles/database.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/bundles/remote_manager.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/bundles/request.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/base.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/builtin.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/calculation/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/calculation/_compat.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/calculation/lifecycle.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/configuration.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/core/observability.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/core/utils.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/exceptions.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/existing_model/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/existing_model/_compat.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/existing_model/resolution.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/factory.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/orm/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/orm/_compat.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/orm/history.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/orm/lifecycle.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/orm/mutations.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/orm/support.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/orm_utils/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/orm_utils/django_manager_utils.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/orm_utils/field_descriptors.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/orm_utils/payload_normalizer.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/read_only/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/read_only/_compat.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/read_only/lifecycle.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/read_only/management.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/registry.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/remote_manager.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/capabilities/request/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/infrastructure/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/infrastructure/startup_hooks.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/infrastructure/system_checks.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/interfaces/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/interfaces/calculation.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/interfaces/database.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/interfaces/existing_model.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/interfaces/read_only.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/interfaces/remote_manager.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/interfaces/request.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/manifests/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/manifests/capability_builder.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/manifests/capability_manifest.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/manifests/capability_models.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/orm_interface.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/requests.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/utils/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/utils/database_interface_protocols.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/utils/errors.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/interface/utils/models.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/logging.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/management/commands/search_index.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/management/commands/seed_manager_landscape.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/management/commands/shell.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/management/commands/workflow_drain_outbox.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/management/commands/workflow_replay_dead_letters.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/manager/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/manager/general_manager.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/manager/group_manager.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/manager/input.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/manager/meta.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/measurement/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/measurement/measurement.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/measurement/measurement_field.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/metrics/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/metrics/graphql.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/migrations/0001_initial.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/migrations/0002_workflow_outbox_scaling_indexes.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/migrations/0003_workflow_execution_correlation_constraint.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/migrations/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/models.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/permission/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/permission/audit.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/permission/base_permission.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/permission/graphql_capabilities.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/permission/manager_based_permission.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/permission/mutation_permission.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/permission/permission_checks.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/permission/permission_data_manager.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/permission/utils.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/public_api_registry.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/py.typed +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/rule/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/rule/handler.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/rule/rule.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/search/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/search/async_tasks.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/search/backend.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/search/backend_registry.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/search/backends/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/search/backends/dev.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/search/backends/meilisearch.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/search/backends/opensearch.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/search/backends/typesense.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/search/config.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/search/indexer.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/search/registry.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/search/utils.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/seeding/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/seeding/manager_landscape.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/utils/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/utils/args_to_kwargs.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/utils/filter_parser.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/utils/format_string.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/utils/json_encoder.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/utils/make_cache_key.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/utils/none_to_zero.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/utils/path_mapping.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/utils/public_api.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/utils/testing.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/utils/type_checks.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/workflow/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/workflow/actions.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/workflow/backend_registry.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/workflow/backends/__init__.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/workflow/backends/celery.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/workflow/backends/local.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/workflow/backends/n8n.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/workflow/config.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/workflow/engine.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/workflow/event_registry.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/workflow/events.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/workflow/models.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/workflow/signal_bridge.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/workflow/tasks.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/workflow/telemetry.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/tests/test_settings.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/tests/test_urls.py +0 -0
- {generalmanager-0.47.0 → generalmanager-0.49.0}/tests/testing_asgi.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: GeneralManager
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.49.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.
|
|
7
|
+
version = "0.49.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.
|
|
3
|
+
Version: 0.49.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
|
{generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/bucket/database_bucket.py
RENAMED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
"""Database-backed bucket implementation for GeneralManager collections."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
|
+
from collections.abc import Hashable
|
|
4
5
|
from datetime import date, datetime
|
|
5
6
|
from typing import Any, Generator, Type, TypeVar
|
|
6
7
|
|
|
7
|
-
from django.core.exceptions import FieldError
|
|
8
|
+
from django.core.exceptions import EmptyResultSet, FieldError
|
|
8
9
|
from django.db import models
|
|
10
|
+
from django.db.models.sql.query import Query
|
|
9
11
|
|
|
10
12
|
from general_manager.bucket.base_bucket import Bucket
|
|
11
13
|
from general_manager.cache.cache_tracker import DependencyTracker
|
|
12
14
|
from general_manager.cache.dependency_index import serialize_dependency_identifier
|
|
15
|
+
from general_manager.cache.run_context import current_calculation_run_context
|
|
13
16
|
from general_manager.manager.general_manager import GeneralManager
|
|
14
17
|
from general_manager.utils.filter_parser import create_filter_function
|
|
15
18
|
|
|
16
19
|
modelsModel = TypeVar("modelsModel", bound=models.Model)
|
|
17
20
|
GeneralManagerType = TypeVar("GeneralManagerType", bound=GeneralManager)
|
|
21
|
+
MAX_RUN_SCOPED_BUCKET_RESULT_ROWS = 1000
|
|
18
22
|
|
|
19
23
|
|
|
20
24
|
class DatabaseBucketTypeMismatchError(TypeError):
|
|
@@ -204,6 +208,7 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
|
204
208
|
search_date: datetime | date | None = None,
|
|
205
209
|
sort_keys: tuple[str, ...] | None = None,
|
|
206
210
|
sort_reverse: bool = False,
|
|
211
|
+
run_scoped_cacheable: bool = True,
|
|
207
212
|
) -> None:
|
|
208
213
|
"""
|
|
209
214
|
Instantiate a database-backed bucket with optional filter state.
|
|
@@ -214,6 +219,9 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
|
214
219
|
filter_definitions (dict[str, list[Any]] | None): Pre-existing filter expressions captured from parent buckets.
|
|
215
220
|
exclude_definitions (dict[str, list[Any]] | None): Pre-existing exclusion expressions captured from parent buckets.
|
|
216
221
|
search_date (datetime | date | None): Optional timestamp applied when instantiating manager instances.
|
|
222
|
+
sort_keys (tuple[str, ...] | None): Property names used by a previous bucket sort operation.
|
|
223
|
+
sort_reverse (bool): Whether sorted keys should be interpreted in descending order for dependency tracking.
|
|
224
|
+
run_scoped_cacheable (bool): Whether terminal results from this bucket are safe to reuse inside the active calculation run.
|
|
217
225
|
|
|
218
226
|
Returns:
|
|
219
227
|
None
|
|
@@ -225,6 +233,7 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
|
225
233
|
self._search_date = search_date
|
|
226
234
|
self._sort_keys = sort_keys
|
|
227
235
|
self._sort_reverse = sort_reverse
|
|
236
|
+
self._run_scoped_cacheable = run_scoped_cacheable
|
|
228
237
|
|
|
229
238
|
def __reduce__(self) -> str | tuple[Any, ...]:
|
|
230
239
|
"""
|
|
@@ -281,6 +290,127 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
|
281
290
|
for key, values in definitions.items()
|
|
282
291
|
}
|
|
283
292
|
|
|
293
|
+
def _query_signature(self) -> tuple[Hashable, ...] | None:
|
|
294
|
+
"""
|
|
295
|
+
Return a conservative run-scoped cache signature for this queryset.
|
|
296
|
+
|
|
297
|
+
The signature is only produced for queryset shapes whose SQL, database
|
|
298
|
+
alias, manager class, model, search date, and sort state can safely
|
|
299
|
+
distinguish equivalent terminal results. Unsupported or risky query
|
|
300
|
+
forms return ``None`` so callers fall back to normal ORM evaluation.
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
tuple[Hashable, ...] | None: Cache key components for equivalent
|
|
304
|
+
queryset results, or ``None`` when reuse should be bypassed.
|
|
305
|
+
"""
|
|
306
|
+
if not self._run_scoped_cacheable:
|
|
307
|
+
return None
|
|
308
|
+
query = self._data.query
|
|
309
|
+
if not isinstance(query, Query):
|
|
310
|
+
return None
|
|
311
|
+
if query.select_for_update:
|
|
312
|
+
return None
|
|
313
|
+
if query.combinator:
|
|
314
|
+
return None
|
|
315
|
+
if query.distinct:
|
|
316
|
+
return None
|
|
317
|
+
try:
|
|
318
|
+
sql, params = self._data.query.sql_with_params()
|
|
319
|
+
except (EmptyResultSet, FieldError, TypeError, ValueError):
|
|
320
|
+
return None
|
|
321
|
+
return (
|
|
322
|
+
self._manager_class,
|
|
323
|
+
self._data.model,
|
|
324
|
+
self._data.db,
|
|
325
|
+
sql,
|
|
326
|
+
tuple(params),
|
|
327
|
+
self._search_date,
|
|
328
|
+
self._sort_keys,
|
|
329
|
+
self._sort_reverse,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
def _get_run_scoped_primary_keys(self) -> tuple[Any, ...] | None:
|
|
333
|
+
"""
|
|
334
|
+
Load or reuse a bounded primary-key snapshot for this bucket.
|
|
335
|
+
|
|
336
|
+
The method materializes at most ``MAX_RUN_SCOPED_BUCKET_RESULT_ROWS``
|
|
337
|
+
primary keys and stores them in the active
|
|
338
|
+
:class:`CalculationRunContext`. Buckets without an active context,
|
|
339
|
+
without a safe signature, or above the materialization guardrail return
|
|
340
|
+
``None`` so terminal operations continue through the database.
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
tuple[Any, ...] | None: Cached or newly materialized primary keys,
|
|
344
|
+
or ``None`` when run-scoped reuse is unavailable.
|
|
345
|
+
"""
|
|
346
|
+
context = current_calculation_run_context()
|
|
347
|
+
if context is None:
|
|
348
|
+
return None
|
|
349
|
+
signature = self._query_signature()
|
|
350
|
+
if signature is None:
|
|
351
|
+
return None
|
|
352
|
+
cached = context.get_orm_bucket_result(signature)
|
|
353
|
+
if cached is not None:
|
|
354
|
+
return cached # type: ignore[return-value]
|
|
355
|
+
|
|
356
|
+
primary_keys = tuple(
|
|
357
|
+
self._data.values_list("pk", flat=True)[
|
|
358
|
+
: MAX_RUN_SCOPED_BUCKET_RESULT_ROWS + 1
|
|
359
|
+
]
|
|
360
|
+
)
|
|
361
|
+
if len(primary_keys) > MAX_RUN_SCOPED_BUCKET_RESULT_ROWS:
|
|
362
|
+
return None
|
|
363
|
+
context.set_orm_bucket_result(signature, primary_keys)
|
|
364
|
+
return primary_keys
|
|
365
|
+
|
|
366
|
+
def _peek_run_scoped_primary_keys(self) -> tuple[Any, ...] | None:
|
|
367
|
+
"""
|
|
368
|
+
Return an already cached primary-key snapshot without evaluating the ORM.
|
|
369
|
+
|
|
370
|
+
Terminal operations such as ``count()`` and membership checks call this
|
|
371
|
+
helper so they can reuse a snapshot created by iteration without forcing
|
|
372
|
+
materialization on their own.
|
|
373
|
+
|
|
374
|
+
Returns:
|
|
375
|
+
tuple[Any, ...] | None: Cached primary keys for this bucket, or
|
|
376
|
+
``None`` when no snapshot exists.
|
|
377
|
+
"""
|
|
378
|
+
context = current_calculation_run_context()
|
|
379
|
+
if context is None:
|
|
380
|
+
return None
|
|
381
|
+
signature = self._query_signature()
|
|
382
|
+
if signature is None:
|
|
383
|
+
return None
|
|
384
|
+
cached = context.get_orm_bucket_result(signature)
|
|
385
|
+
if cached is None:
|
|
386
|
+
return None
|
|
387
|
+
return cached # type: ignore[return-value]
|
|
388
|
+
|
|
389
|
+
@staticmethod
|
|
390
|
+
def _snapshot_get_primary_key(kwargs: dict[str, Any]) -> Any | None:
|
|
391
|
+
"""
|
|
392
|
+
Extract the primary key from a snapshot-safe ``get()`` lookup.
|
|
393
|
+
|
|
394
|
+
Only single-key ``pk`` and ``id`` lookups can be answered from the
|
|
395
|
+
cached primary-key tuple while preserving Django's ``QuerySet.get()``
|
|
396
|
+
semantics. All other lookup shapes return ``None`` and use the normal
|
|
397
|
+
ORM path.
|
|
398
|
+
|
|
399
|
+
Parameters:
|
|
400
|
+
kwargs (dict[str, Any]): Lookup arguments passed to ``get()``.
|
|
401
|
+
|
|
402
|
+
Returns:
|
|
403
|
+
Any | None: Requested primary key when the lookup is snapshot-safe,
|
|
404
|
+
otherwise ``None``.
|
|
405
|
+
"""
|
|
406
|
+
if len(kwargs) != 1:
|
|
407
|
+
return None
|
|
408
|
+
if "pk" in kwargs:
|
|
409
|
+
return kwargs["pk"]
|
|
410
|
+
if "id" in kwargs:
|
|
411
|
+
return kwargs["id"]
|
|
412
|
+
return None
|
|
413
|
+
|
|
284
414
|
def _track_effective_dependencies(self) -> None:
|
|
285
415
|
"""Record the bucket's effective filter/exclude state when it is evaluated."""
|
|
286
416
|
manager_name = self._manager_class.__name__
|
|
@@ -321,6 +451,11 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
|
321
451
|
GeneralManagerType: Manager instance for each primary key in the queryset.
|
|
322
452
|
"""
|
|
323
453
|
self._track_effective_dependencies()
|
|
454
|
+
primary_keys = self._get_run_scoped_primary_keys()
|
|
455
|
+
if primary_keys is not None:
|
|
456
|
+
for primary_key in primary_keys:
|
|
457
|
+
yield self._build_manager(primary_key)
|
|
458
|
+
return
|
|
324
459
|
for item in self._data:
|
|
325
460
|
yield self._build_manager(item.pk)
|
|
326
461
|
|
|
@@ -365,6 +500,7 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
|
365
500
|
search_date=self._search_date,
|
|
366
501
|
sort_keys=self._sort_keys,
|
|
367
502
|
sort_reverse=self._sort_reverse,
|
|
503
|
+
run_scoped_cacheable=False,
|
|
368
504
|
)
|
|
369
505
|
|
|
370
506
|
def __merge_filter_definitions(
|
|
@@ -502,6 +638,7 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
|
502
638
|
search_date=search_date,
|
|
503
639
|
sort_keys=self._sort_keys,
|
|
504
640
|
sort_reverse=self._sort_reverse,
|
|
641
|
+
run_scoped_cacheable=self._run_scoped_cacheable,
|
|
505
642
|
)
|
|
506
643
|
|
|
507
644
|
def exclude(self, **kwargs: Any) -> DatabaseBucket[GeneralManagerType]:
|
|
@@ -549,6 +686,7 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
|
549
686
|
search_date=search_date,
|
|
550
687
|
sort_keys=self._sort_keys,
|
|
551
688
|
sort_reverse=self._sort_reverse,
|
|
689
|
+
run_scoped_cacheable=self._run_scoped_cacheable,
|
|
552
690
|
)
|
|
553
691
|
|
|
554
692
|
def first(self) -> GeneralManagerType | None:
|
|
@@ -559,6 +697,11 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
|
559
697
|
GeneralManagerType | None: First manager instance if available.
|
|
560
698
|
"""
|
|
561
699
|
self._track_effective_dependencies()
|
|
700
|
+
primary_keys = self._peek_run_scoped_primary_keys()
|
|
701
|
+
if primary_keys is not None:
|
|
702
|
+
if not primary_keys:
|
|
703
|
+
return None
|
|
704
|
+
return self._build_manager(primary_keys[0])
|
|
562
705
|
first_element = self._data.first()
|
|
563
706
|
if first_element is None:
|
|
564
707
|
return None
|
|
@@ -572,6 +715,11 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
|
572
715
|
GeneralManagerType | None: Last manager instance if available.
|
|
573
716
|
"""
|
|
574
717
|
self._track_effective_dependencies()
|
|
718
|
+
primary_keys = self._peek_run_scoped_primary_keys()
|
|
719
|
+
if primary_keys is not None:
|
|
720
|
+
if not primary_keys:
|
|
721
|
+
return None
|
|
722
|
+
return self._build_manager(primary_keys[-1])
|
|
575
723
|
first_element = self._data.last()
|
|
576
724
|
if first_element is None:
|
|
577
725
|
return None
|
|
@@ -585,6 +733,9 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
|
585
733
|
int: Number of queryset rows.
|
|
586
734
|
"""
|
|
587
735
|
self._track_effective_dependencies()
|
|
736
|
+
primary_keys = self._peek_run_scoped_primary_keys()
|
|
737
|
+
if primary_keys is not None:
|
|
738
|
+
return len(primary_keys)
|
|
588
739
|
return self._data.count()
|
|
589
740
|
|
|
590
741
|
def all(self) -> DatabaseBucket[GeneralManagerType]:
|
|
@@ -602,6 +753,7 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
|
602
753
|
search_date=self._search_date,
|
|
603
754
|
sort_keys=self._sort_keys,
|
|
604
755
|
sort_reverse=self._sort_reverse,
|
|
756
|
+
run_scoped_cacheable=self._run_scoped_cacheable,
|
|
605
757
|
)
|
|
606
758
|
|
|
607
759
|
def get(self, **kwargs: Any) -> GeneralManagerType:
|
|
@@ -619,6 +771,16 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
|
619
771
|
models.MultipleObjectsReturned: Propagated when multiple rows satisfy the lookup.
|
|
620
772
|
"""
|
|
621
773
|
self._track_effective_dependencies()
|
|
774
|
+
primary_keys = self._peek_run_scoped_primary_keys()
|
|
775
|
+
if primary_keys is not None:
|
|
776
|
+
requested_primary_key = self._snapshot_get_primary_key(kwargs)
|
|
777
|
+
if requested_primary_key is not None:
|
|
778
|
+
match_count = primary_keys.count(requested_primary_key)
|
|
779
|
+
if match_count == 1:
|
|
780
|
+
return self._build_manager(requested_primary_key)
|
|
781
|
+
if match_count > 1:
|
|
782
|
+
raise self._data.model.MultipleObjectsReturned
|
|
783
|
+
raise self._data.model.DoesNotExist
|
|
622
784
|
element = self._data.get(**kwargs)
|
|
623
785
|
return self._build_manager(element.pk)
|
|
624
786
|
|
|
@@ -643,8 +805,14 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
|
643
805
|
search_date=self._search_date,
|
|
644
806
|
sort_keys=self._sort_keys,
|
|
645
807
|
sort_reverse=self._sort_reverse,
|
|
808
|
+
run_scoped_cacheable=self._run_scoped_cacheable,
|
|
646
809
|
)
|
|
647
810
|
self._track_effective_dependencies()
|
|
811
|
+
primary_keys = self._peek_run_scoped_primary_keys()
|
|
812
|
+
if primary_keys is not None:
|
|
813
|
+
if item < 0:
|
|
814
|
+
raise ValueError
|
|
815
|
+
return self._build_manager(primary_keys[item])
|
|
648
816
|
return self._build_manager(self._data[item].pk)
|
|
649
817
|
|
|
650
818
|
def __len__(self) -> int:
|
|
@@ -655,6 +823,9 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
|
655
823
|
int: Size of the queryset.
|
|
656
824
|
"""
|
|
657
825
|
self._track_effective_dependencies()
|
|
826
|
+
primary_keys = self._peek_run_scoped_primary_keys()
|
|
827
|
+
if primary_keys is not None:
|
|
828
|
+
return len(primary_keys)
|
|
658
829
|
return self._data.count()
|
|
659
830
|
|
|
660
831
|
def __str__(self) -> str:
|
|
@@ -694,6 +865,9 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
|
694
865
|
pk = item.pk
|
|
695
866
|
if pk is None:
|
|
696
867
|
return False
|
|
868
|
+
primary_keys = self._peek_run_scoped_primary_keys()
|
|
869
|
+
if primary_keys is not None:
|
|
870
|
+
return pk in primary_keys
|
|
697
871
|
return self._data.filter(pk=pk).exists()
|
|
698
872
|
|
|
699
873
|
def sort(
|
|
@@ -779,6 +953,7 @@ class DatabaseBucket(Bucket[GeneralManagerType]):
|
|
|
779
953
|
search_date=self._search_date,
|
|
780
954
|
sort_keys=key,
|
|
781
955
|
sort_reverse=reverse,
|
|
956
|
+
run_scoped_cacheable=self._run_scoped_cacheable,
|
|
782
957
|
)
|
|
783
958
|
|
|
784
959
|
def none(self) -> DatabaseBucket[GeneralManagerType]:
|
{generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/cache/cache_decorator.py
RENAMED
|
@@ -28,6 +28,7 @@ from general_manager.cache.dependency_index import (
|
|
|
28
28
|
)
|
|
29
29
|
from general_manager.cache.dependency_publish import (
|
|
30
30
|
CachePublishAborted,
|
|
31
|
+
PendingDependencyCachePublication,
|
|
31
32
|
acquire_compute_lease,
|
|
32
33
|
publish_dependency_cache_entry,
|
|
33
34
|
release_compute_lease,
|
|
@@ -232,6 +233,7 @@ def cached(
|
|
|
232
233
|
)
|
|
233
234
|
lease = acquire_compute_lease(key)
|
|
234
235
|
|
|
236
|
+
lease_transferred_to_context = False
|
|
235
237
|
try:
|
|
236
238
|
cached_hit = read_dependency_cache_hit(
|
|
237
239
|
cache_backend,
|
|
@@ -252,29 +254,47 @@ def cached(
|
|
|
252
254
|
for entry_key, entry_dependencies in entries:
|
|
253
255
|
record_fn(entry_key, set(entry_dependencies))
|
|
254
256
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
except CachePublishAborted:
|
|
268
|
-
logger.debug(
|
|
269
|
-
"dependency cache publish aborted",
|
|
270
|
-
context={
|
|
271
|
-
"function": func.__qualname__,
|
|
272
|
-
"key": key,
|
|
273
|
-
"scope": scope,
|
|
274
|
-
},
|
|
257
|
+
publish_context = current_calculation_run_context()
|
|
258
|
+
if publish_context is not None and record_fn is record_dependencies:
|
|
259
|
+
publish_context.buffer_dependency_cache_publication(
|
|
260
|
+
PendingDependencyCachePublication(
|
|
261
|
+
cache_key=key,
|
|
262
|
+
result=result,
|
|
263
|
+
dependencies=frozenset(dependencies),
|
|
264
|
+
cache_backend=cache_backend,
|
|
265
|
+
timeout=timeout,
|
|
266
|
+
started_generation=started_generation,
|
|
267
|
+
lease=lease,
|
|
268
|
+
)
|
|
275
269
|
)
|
|
270
|
+
lease_transferred_to_context = True
|
|
271
|
+
else:
|
|
272
|
+
try:
|
|
273
|
+
publish_dependency_cache_entry(
|
|
274
|
+
cache_key=key,
|
|
275
|
+
result=result,
|
|
276
|
+
dependencies=dependencies,
|
|
277
|
+
cache_backend=cache_backend,
|
|
278
|
+
timeout=timeout,
|
|
279
|
+
started_generation=started_generation,
|
|
280
|
+
record_many_fn=(
|
|
281
|
+
None
|
|
282
|
+
if record_fn is record_dependencies
|
|
283
|
+
else record_many
|
|
284
|
+
),
|
|
285
|
+
)
|
|
286
|
+
except CachePublishAborted:
|
|
287
|
+
logger.debug(
|
|
288
|
+
"dependency cache publish aborted",
|
|
289
|
+
context={
|
|
290
|
+
"function": func.__qualname__,
|
|
291
|
+
"key": key,
|
|
292
|
+
"scope": scope,
|
|
293
|
+
},
|
|
294
|
+
)
|
|
276
295
|
finally:
|
|
277
|
-
|
|
296
|
+
if not lease_transferred_to_context:
|
|
297
|
+
release_compute_lease(lease)
|
|
278
298
|
|
|
279
299
|
logger.debug(
|
|
280
300
|
"cache miss recorded",
|
{generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/cache/dependency_cache.py
RENAMED
|
@@ -45,6 +45,16 @@ class DependencyCacheGetManyBackend(DependencyCacheBackend, Protocol):
|
|
|
45
45
|
...
|
|
46
46
|
|
|
47
47
|
|
|
48
|
+
class DependencyCacheSetManyBackend(DependencyCacheBackend, Protocol):
|
|
49
|
+
def set_many(
|
|
50
|
+
self,
|
|
51
|
+
data: Mapping[str, Any],
|
|
52
|
+
timeout: int | None = None,
|
|
53
|
+
) -> Any:
|
|
54
|
+
"""Store cached values for many keys."""
|
|
55
|
+
...
|
|
56
|
+
|
|
57
|
+
|
|
48
58
|
def make_dependency_cache_entry(
|
|
49
59
|
value: Any,
|
|
50
60
|
dependencies: Iterable[Dependency],
|
{generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/cache/dependency_index.py
RENAMED
|
@@ -124,6 +124,14 @@ def _set_dependency_data_change_count(count: int) -> int:
|
|
|
124
124
|
return count
|
|
125
125
|
|
|
126
126
|
|
|
127
|
+
def _discard_active_context_dependency_cache_state() -> None:
|
|
128
|
+
from general_manager.cache.run_context import current_calculation_run_context
|
|
129
|
+
|
|
130
|
+
context = current_calculation_run_context()
|
|
131
|
+
if context is not None:
|
|
132
|
+
context.discard_dependency_cache_state()
|
|
133
|
+
|
|
134
|
+
|
|
127
135
|
def begin_dependency_data_change() -> int:
|
|
128
136
|
"""
|
|
129
137
|
Mark a data change as active and bump the dependency generation.
|
|
@@ -136,9 +144,17 @@ def begin_dependency_data_change() -> int:
|
|
|
136
144
|
generation = _set_dependency_generation(get_dependency_generation() + 1)
|
|
137
145
|
_set_dependency_data_change_count(_get_dependency_data_change_count() + 1)
|
|
138
146
|
cache.set(DATA_CHANGE_LOCK_KEY, "1", None)
|
|
139
|
-
return generation
|
|
140
147
|
finally:
|
|
141
148
|
release_lock()
|
|
149
|
+
try:
|
|
150
|
+
_discard_active_context_dependency_cache_state()
|
|
151
|
+
except Exception:
|
|
152
|
+
try:
|
|
153
|
+
end_dependency_data_change()
|
|
154
|
+
except Exception:
|
|
155
|
+
logger.exception("dependency data-change cleanup rollback failed")
|
|
156
|
+
raise
|
|
157
|
+
return generation
|
|
142
158
|
|
|
143
159
|
|
|
144
160
|
def end_dependency_data_change() -> None:
|
{generalmanager-0.47.0 → generalmanager-0.49.0}/src/general_manager/cache/dependency_publish.py
RENAMED
|
@@ -4,13 +4,15 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import time
|
|
6
6
|
import uuid
|
|
7
|
+
from collections import defaultdict
|
|
7
8
|
from dataclasses import dataclass
|
|
8
|
-
from typing import Any, Callable, Iterable
|
|
9
|
+
from typing import Any, Callable, Iterable, TypeGuard
|
|
9
10
|
|
|
10
11
|
from django.core.cache import cache as coordination_cache
|
|
11
12
|
|
|
12
13
|
from general_manager.cache.dependency_cache import (
|
|
13
14
|
DependencyCacheBackend,
|
|
15
|
+
DependencyCacheSetManyBackend,
|
|
14
16
|
make_dependency_cache_entry,
|
|
15
17
|
read_dependency_cache_hit,
|
|
16
18
|
)
|
|
@@ -39,6 +41,19 @@ class CacheComputeLease:
|
|
|
39
41
|
token: str
|
|
40
42
|
|
|
41
43
|
|
|
44
|
+
@dataclass(frozen=True)
|
|
45
|
+
class PendingDependencyCachePublication:
|
|
46
|
+
"""A dependency-cache miss waiting for guarded publication."""
|
|
47
|
+
|
|
48
|
+
cache_key: str
|
|
49
|
+
result: Any
|
|
50
|
+
dependencies: frozenset[Dependency]
|
|
51
|
+
cache_backend: DependencyCacheBackend
|
|
52
|
+
timeout: int | None
|
|
53
|
+
started_generation: int
|
|
54
|
+
lease: CacheComputeLease
|
|
55
|
+
|
|
56
|
+
|
|
42
57
|
RecordManyDependenciesFn = Callable[[Iterable[tuple[str, Iterable[Dependency]]]], None]
|
|
43
58
|
|
|
44
59
|
COMPUTE_LOCK_PREFIX = "dependency_cache_compute_lock"
|
|
@@ -111,6 +126,81 @@ def _ensure_publish_current(started_generation: int) -> None:
|
|
|
111
126
|
raise CachePublishAborted()
|
|
112
127
|
|
|
113
128
|
|
|
129
|
+
def _supports_set_many(
|
|
130
|
+
cache_backend: DependencyCacheBackend,
|
|
131
|
+
) -> TypeGuard[DependencyCacheSetManyBackend]:
|
|
132
|
+
return callable(getattr(cache_backend, "set_many", None))
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _set_dependency_cache_entries(
|
|
136
|
+
entries: Iterable[PendingDependencyCachePublication],
|
|
137
|
+
) -> None:
|
|
138
|
+
grouped: dict[
|
|
139
|
+
tuple[int, int | None],
|
|
140
|
+
list[PendingDependencyCachePublication],
|
|
141
|
+
] = defaultdict(list)
|
|
142
|
+
for entry in entries:
|
|
143
|
+
grouped[(id(entry.cache_backend), entry.timeout)].append(entry)
|
|
144
|
+
|
|
145
|
+
for group_entries in grouped.values():
|
|
146
|
+
cache_backend = group_entries[0].cache_backend
|
|
147
|
+
timeout = group_entries[0].timeout
|
|
148
|
+
payloads = {
|
|
149
|
+
entry.cache_key: make_dependency_cache_entry(
|
|
150
|
+
entry.result,
|
|
151
|
+
entry.dependencies,
|
|
152
|
+
)
|
|
153
|
+
for entry in group_entries
|
|
154
|
+
}
|
|
155
|
+
if _supports_set_many(cache_backend):
|
|
156
|
+
failed_keys = cache_backend.set_many(payloads, timeout) or ()
|
|
157
|
+
for key in failed_keys:
|
|
158
|
+
if key in payloads:
|
|
159
|
+
cache_backend.set(key, payloads[key], timeout)
|
|
160
|
+
continue
|
|
161
|
+
for key, payload in payloads.items():
|
|
162
|
+
cache_backend.set(key, payload, timeout)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def publish_dependency_cache_entries(
|
|
166
|
+
entries: Iterable[PendingDependencyCachePublication],
|
|
167
|
+
) -> None:
|
|
168
|
+
"""Publish dependency metadata and values for current entries in one batch."""
|
|
169
|
+
pending_entries = tuple(entries)
|
|
170
|
+
if not pending_entries:
|
|
171
|
+
return
|
|
172
|
+
|
|
173
|
+
acquire_lock_with_retry("publish_dependency_cache_entries")
|
|
174
|
+
try:
|
|
175
|
+
if is_dependency_data_change_active():
|
|
176
|
+
raise CachePublishAborted()
|
|
177
|
+
|
|
178
|
+
current_generation = get_dependency_generation()
|
|
179
|
+
publishable_entries = tuple(
|
|
180
|
+
entry
|
|
181
|
+
for entry in pending_entries
|
|
182
|
+
if entry.started_generation == current_generation
|
|
183
|
+
)
|
|
184
|
+
if not publishable_entries:
|
|
185
|
+
return
|
|
186
|
+
|
|
187
|
+
idx = get_full_index()
|
|
188
|
+
for entry in publishable_entries:
|
|
189
|
+
if entry.dependencies:
|
|
190
|
+
_record_dependencies_locked(
|
|
191
|
+
idx,
|
|
192
|
+
entry.cache_key,
|
|
193
|
+
entry.dependencies,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
_ensure_publish_current(current_generation)
|
|
197
|
+
set_full_index(idx)
|
|
198
|
+
_ensure_publish_current(current_generation)
|
|
199
|
+
_set_dependency_cache_entries(publishable_entries)
|
|
200
|
+
finally:
|
|
201
|
+
release_lock()
|
|
202
|
+
|
|
203
|
+
|
|
114
204
|
def publish_dependency_cache_entry(
|
|
115
205
|
*,
|
|
116
206
|
cache_key: str,
|