GeneralManager 0.44.0__tar.gz → 0.46.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.44.0/src/GeneralManager.egg-info → generalmanager-0.46.0}/PKG-INFO +1 -1
- {generalmanager-0.44.0 → generalmanager-0.46.0}/pyproject.toml +1 -1
- {generalmanager-0.44.0 → generalmanager-0.46.0/src/GeneralManager.egg-info}/PKG-INFO +1 -1
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/GeneralManager.egg-info/SOURCES.txt +1 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/cache/cache_decorator.py +88 -13
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/cache/dependency_index.py +224 -92
- generalmanager-0.46.0/src/general_manager/cache/dependency_publish.py +153 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/cache/run_context.py +6 -0
- generalmanager-0.46.0/src/general_manager/cache/signals.py +114 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/orm/mutations.py +10 -1
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/orm/support.py +35 -1
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/utils/testing.py +23 -4
- generalmanager-0.44.0/src/general_manager/cache/signals.py +0 -85
- {generalmanager-0.44.0 → generalmanager-0.46.0}/LICENSE +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/README.md +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/setup.cfg +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/GeneralManager.egg-info/dependency_links.txt +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/GeneralManager.egg-info/requires.txt +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/GeneralManager.egg-info/top_level.txt +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/_types/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/_types/api.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/_types/bucket.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/_types/cache.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/_types/factory.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/_types/general_manager.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/_types/interface.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/_types/manager.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/_types/measurement.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/_types/permission.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/_types/rule.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/_types/search.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/_types/utils.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/graphql.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/graphql_errors.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/graphql_mutations.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/graphql_resolvers.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/graphql_search.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/graphql_subscription_consumer.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/graphql_subscriptions.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/graphql_view.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/mutation.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/property.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/registry.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/remote_api.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/remote_invalidation.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/api/remote_invalidation_client.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/apps.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/bootstrap.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/bucket/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/bucket/base_bucket.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/bucket/calculation_bucket.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/bucket/database_bucket.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/bucket/group_bucket.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/bucket/request_bucket.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/cache/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/cache/cache_tracker.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/cache/model_dependency_collector.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/conf.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/factory/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/factory/auto_factory.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/factory/factories.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/factory/factory_methods.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/base_interface.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/bundles/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/bundles/calculation.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/bundles/database.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/bundles/remote_manager.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/bundles/request.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/base.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/builtin.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/calculation/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/calculation/_compat.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/calculation/lifecycle.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/configuration.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/core/observability.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/core/utils.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/exceptions.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/existing_model/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/existing_model/_compat.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/existing_model/resolution.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/factory.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/orm/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/orm/_compat.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/orm/history.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/orm/lifecycle.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/orm_utils/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/orm_utils/django_manager_utils.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/orm_utils/field_descriptors.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/orm_utils/payload_normalizer.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/read_only/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/read_only/_compat.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/read_only/lifecycle.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/read_only/management.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/registry.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/remote_manager.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/capabilities/request/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/infrastructure/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/infrastructure/startup_hooks.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/infrastructure/system_checks.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/interfaces/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/interfaces/calculation.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/interfaces/database.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/interfaces/existing_model.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/interfaces/read_only.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/interfaces/remote_manager.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/interfaces/request.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/manifests/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/manifests/capability_builder.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/manifests/capability_manifest.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/manifests/capability_models.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/orm_interface.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/requests.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/utils/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/utils/database_interface_protocols.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/utils/errors.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/interface/utils/models.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/logging.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/management/commands/search_index.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/management/commands/seed_manager_landscape.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/management/commands/shell.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/management/commands/workflow_drain_outbox.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/management/commands/workflow_replay_dead_letters.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/manager/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/manager/general_manager.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/manager/group_manager.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/manager/input.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/manager/meta.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/measurement/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/measurement/measurement.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/measurement/measurement_field.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/metrics/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/metrics/graphql.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/migrations/0001_initial.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/migrations/0002_workflow_outbox_scaling_indexes.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/migrations/0003_workflow_execution_correlation_constraint.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/migrations/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/models.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/permission/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/permission/audit.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/permission/base_permission.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/permission/graphql_capabilities.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/permission/manager_based_permission.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/permission/mutation_permission.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/permission/permission_checks.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/permission/permission_data_manager.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/permission/utils.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/public_api_registry.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/py.typed +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/rule/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/rule/handler.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/rule/rule.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/search/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/search/async_tasks.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/search/backend.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/search/backend_registry.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/search/backends/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/search/backends/dev.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/search/backends/meilisearch.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/search/backends/opensearch.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/search/backends/typesense.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/search/config.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/search/indexer.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/search/registry.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/search/utils.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/seeding/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/seeding/manager_landscape.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/utils/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/utils/args_to_kwargs.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/utils/filter_parser.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/utils/format_string.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/utils/json_encoder.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/utils/make_cache_key.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/utils/none_to_zero.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/utils/path_mapping.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/utils/public_api.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/utils/type_checks.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/actions.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/backend_registry.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/backends/__init__.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/backends/celery.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/backends/local.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/backends/n8n.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/config.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/engine.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/event_registry.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/events.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/models.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/signal_bridge.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/tasks.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/workflow/telemetry.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/tests/test_settings.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.0}/tests/test_urls.py +0 -0
- {generalmanager-0.44.0 → generalmanager-0.46.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.46.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.46.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.46.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
|
|
@@ -52,6 +52,7 @@ src/general_manager/cache/__init__.py
|
|
|
52
52
|
src/general_manager/cache/cache_decorator.py
|
|
53
53
|
src/general_manager/cache/cache_tracker.py
|
|
54
54
|
src/general_manager/cache/dependency_index.py
|
|
55
|
+
src/general_manager/cache/dependency_publish.py
|
|
55
56
|
src/general_manager/cache/model_dependency_collector.py
|
|
56
57
|
src/general_manager/cache/run_context.py
|
|
57
58
|
src/general_manager/cache/signals.py
|
{generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/cache/cache_decorator.py
RENAMED
|
@@ -1,12 +1,33 @@
|
|
|
1
1
|
"""Helpers for caching GeneralManager computations with dependency tracking."""
|
|
2
2
|
|
|
3
3
|
from functools import wraps
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import (
|
|
5
|
+
Any,
|
|
6
|
+
Callable,
|
|
7
|
+
Iterable,
|
|
8
|
+
Literal,
|
|
9
|
+
Optional,
|
|
10
|
+
Protocol,
|
|
11
|
+
Set,
|
|
12
|
+
TypeVar,
|
|
13
|
+
cast,
|
|
14
|
+
)
|
|
5
15
|
|
|
6
16
|
from django.core.cache import cache as django_cache
|
|
7
17
|
|
|
8
18
|
from general_manager.cache.cache_tracker import DependencyTracker
|
|
9
|
-
from general_manager.cache.dependency_index import
|
|
19
|
+
from general_manager.cache.dependency_index import (
|
|
20
|
+
Dependency,
|
|
21
|
+
get_dependency_generation,
|
|
22
|
+
record_dependencies,
|
|
23
|
+
)
|
|
24
|
+
from general_manager.cache.dependency_publish import (
|
|
25
|
+
CachePublishAborted,
|
|
26
|
+
acquire_compute_lease,
|
|
27
|
+
publish_dependency_cache_entry,
|
|
28
|
+
release_compute_lease,
|
|
29
|
+
wait_for_cached_dependency_value,
|
|
30
|
+
)
|
|
10
31
|
from general_manager.cache.run_context import ensure_calculation_run_context
|
|
11
32
|
from general_manager.cache.model_dependency_collector import ModelDependencyCollector
|
|
12
33
|
from general_manager.logging import get_logger
|
|
@@ -160,32 +181,86 @@ def cached(
|
|
|
160
181
|
|
|
161
182
|
deps_key = f"{key}:deps"
|
|
162
183
|
|
|
163
|
-
|
|
164
|
-
if cached_result is not _SENTINEL:
|
|
165
|
-
# saved dependencies are added to the current tracker
|
|
184
|
+
def track_cached_dependencies() -> None:
|
|
166
185
|
cached_deps = cache_backend.get(deps_key)
|
|
167
186
|
if cached_deps:
|
|
168
187
|
for class_name, operation, identifier in cached_deps:
|
|
169
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()
|
|
170
193
|
logger.debug(
|
|
171
194
|
"cache hit",
|
|
172
195
|
context={
|
|
173
196
|
"function": func.__qualname__,
|
|
174
197
|
"key": key,
|
|
175
|
-
"
|
|
198
|
+
"scope": scope,
|
|
176
199
|
},
|
|
177
200
|
)
|
|
178
201
|
return cached_result
|
|
179
202
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
203
|
+
lease = acquire_compute_lease(key)
|
|
204
|
+
while lease is None:
|
|
205
|
+
cached_result = wait_for_cached_dependency_value(
|
|
206
|
+
cache_backend,
|
|
207
|
+
key,
|
|
208
|
+
sentinel=_SENTINEL,
|
|
209
|
+
)
|
|
210
|
+
if cached_result is not _SENTINEL:
|
|
211
|
+
track_cached_dependencies()
|
|
212
|
+
logger.debug(
|
|
213
|
+
"cache hit after waiting for dependency publish",
|
|
214
|
+
context={
|
|
215
|
+
"function": func.__qualname__,
|
|
216
|
+
"key": key,
|
|
217
|
+
"scope": scope,
|
|
218
|
+
},
|
|
219
|
+
)
|
|
220
|
+
return cached_result
|
|
221
|
+
lease = acquire_compute_lease(key)
|
|
183
222
|
|
|
184
|
-
|
|
185
|
-
cache_backend.
|
|
223
|
+
try:
|
|
224
|
+
cached_result = cache_backend.get(key, _SENTINEL)
|
|
225
|
+
if cached_result is not _SENTINEL:
|
|
226
|
+
track_cached_dependencies()
|
|
227
|
+
return cached_result
|
|
186
228
|
|
|
187
|
-
|
|
188
|
-
|
|
229
|
+
started_generation = get_dependency_generation()
|
|
230
|
+
with DependencyTracker() as dependencies:
|
|
231
|
+
result = func(*args, **kwargs)
|
|
232
|
+
ModelDependencyCollector.add_args(dependencies, args, kwargs)
|
|
233
|
+
|
|
234
|
+
def record_many(
|
|
235
|
+
entries: Iterable[tuple[str, Iterable[Dependency]]],
|
|
236
|
+
) -> None:
|
|
237
|
+
for entry_key, entry_dependencies in entries:
|
|
238
|
+
record_fn(entry_key, set(entry_dependencies))
|
|
239
|
+
|
|
240
|
+
try:
|
|
241
|
+
publish_dependency_cache_entry(
|
|
242
|
+
cache_key=key,
|
|
243
|
+
deps_key=deps_key,
|
|
244
|
+
result=result,
|
|
245
|
+
dependencies=dependencies,
|
|
246
|
+
cache_backend=cache_backend,
|
|
247
|
+
timeout=timeout,
|
|
248
|
+
started_generation=started_generation,
|
|
249
|
+
record_many_fn=(
|
|
250
|
+
None if record_fn is record_dependencies else record_many
|
|
251
|
+
),
|
|
252
|
+
)
|
|
253
|
+
except CachePublishAborted:
|
|
254
|
+
logger.debug(
|
|
255
|
+
"dependency cache publish aborted",
|
|
256
|
+
context={
|
|
257
|
+
"function": func.__qualname__,
|
|
258
|
+
"key": key,
|
|
259
|
+
"scope": scope,
|
|
260
|
+
},
|
|
261
|
+
)
|
|
262
|
+
finally:
|
|
263
|
+
release_compute_lease(lease)
|
|
189
264
|
|
|
190
265
|
logger.debug(
|
|
191
266
|
"cache miss recorded",
|
{generalmanager-0.44.0 → generalmanager-0.46.0}/src/general_manager/cache/dependency_index.py
RENAMED
|
@@ -62,6 +62,9 @@ class DependencyLockTimeoutError(TimeoutError):
|
|
|
62
62
|
# -----------------------------------------------------------------------------
|
|
63
63
|
INDEX_KEY = "dependency_index" # Cache key storing the complete dependency index
|
|
64
64
|
LOCK_KEY = "dependency_index_lock" # Cache key used for the dependency lock
|
|
65
|
+
DEPENDENCY_GENERATION_KEY = "dependency_index_generation"
|
|
66
|
+
DATA_CHANGE_LOCK_KEY = "dependency_index_data_change_lock"
|
|
67
|
+
DATA_CHANGE_COUNT_KEY = "dependency_index_data_change_count"
|
|
65
68
|
LOCK_TIMEOUT = 5 # Lock TTL in seconds
|
|
66
69
|
UNDEFINED = object() # Sentinel for undefined values
|
|
67
70
|
ACTIONS: tuple[Literal["filter"], Literal["exclude"]] = ("filter", "exclude")
|
|
@@ -100,6 +103,64 @@ def release_lock() -> None:
|
|
|
100
103
|
cache.delete(LOCK_KEY)
|
|
101
104
|
|
|
102
105
|
|
|
106
|
+
def get_dependency_generation() -> int:
|
|
107
|
+
"""Return the current dependency-cache mutation generation."""
|
|
108
|
+
generation = cache.get(DEPENDENCY_GENERATION_KEY, 0)
|
|
109
|
+
return int(generation or 0)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _set_dependency_generation(generation: int) -> int:
|
|
113
|
+
cache.set(DEPENDENCY_GENERATION_KEY, generation, None)
|
|
114
|
+
return generation
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _get_dependency_data_change_count() -> int:
|
|
118
|
+
count = cache.get(DATA_CHANGE_COUNT_KEY, 0)
|
|
119
|
+
return max(int(count or 0), 0)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _set_dependency_data_change_count(count: int) -> int:
|
|
123
|
+
cache.set(DATA_CHANGE_COUNT_KEY, count, None)
|
|
124
|
+
return count
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def begin_dependency_data_change() -> int:
|
|
128
|
+
"""
|
|
129
|
+
Mark a data change as active and bump the dependency generation.
|
|
130
|
+
|
|
131
|
+
The generation bump happens before the underlying mutation, so computations
|
|
132
|
+
that started before the mutation cannot publish dependency-scoped values.
|
|
133
|
+
"""
|
|
134
|
+
acquire_lock_with_retry("begin_dependency_data_change")
|
|
135
|
+
try:
|
|
136
|
+
generation = _set_dependency_generation(get_dependency_generation() + 1)
|
|
137
|
+
_set_dependency_data_change_count(_get_dependency_data_change_count() + 1)
|
|
138
|
+
cache.set(DATA_CHANGE_LOCK_KEY, "1", None)
|
|
139
|
+
return generation
|
|
140
|
+
finally:
|
|
141
|
+
release_lock()
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def end_dependency_data_change() -> None:
|
|
145
|
+
"""Release the publish barrier for a completed data change."""
|
|
146
|
+
acquire_lock_with_retry("end_dependency_data_change")
|
|
147
|
+
try:
|
|
148
|
+
count = _set_dependency_data_change_count(
|
|
149
|
+
max(_get_dependency_data_change_count() - 1, 0)
|
|
150
|
+
)
|
|
151
|
+
if count == 0:
|
|
152
|
+
cache.delete(DATA_CHANGE_LOCK_KEY)
|
|
153
|
+
else:
|
|
154
|
+
cache.set(DATA_CHANGE_LOCK_KEY, "1", None)
|
|
155
|
+
finally:
|
|
156
|
+
release_lock()
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def is_dependency_data_change_active() -> bool:
|
|
160
|
+
"""Return whether dependency-scoped cache publishing should pause."""
|
|
161
|
+
return cache.get(DATA_CHANGE_LOCK_KEY) is not None
|
|
162
|
+
|
|
163
|
+
|
|
103
164
|
def acquire_lock_with_retry(operation: str) -> None:
|
|
104
165
|
"""
|
|
105
166
|
Acquire the dependency index lock, retrying with exponential backoff.
|
|
@@ -212,6 +273,62 @@ def parse_dependency_identifier(identifier: str) -> Any:
|
|
|
212
273
|
# -----------------------------------------------------------------------------
|
|
213
274
|
# DEPENDENCY RECORDING
|
|
214
275
|
# -----------------------------------------------------------------------------
|
|
276
|
+
def _record_dependencies_locked(
|
|
277
|
+
idx: dependency_index,
|
|
278
|
+
cache_key: str,
|
|
279
|
+
dependencies: Iterable[Dependency],
|
|
280
|
+
) -> None:
|
|
281
|
+
"""Mutate an already-loaded dependency index for one cache key."""
|
|
282
|
+
for model_name, action, identifier in set(dependencies):
|
|
283
|
+
if action in ("filter", "exclude"):
|
|
284
|
+
action_key = cast(Literal["filter", "exclude"], action)
|
|
285
|
+
params = parse_dependency_identifier(identifier)
|
|
286
|
+
if not isinstance(params, dict):
|
|
287
|
+
continue
|
|
288
|
+
action_section = cast(
|
|
289
|
+
dict[general_manager_name, manager_dependency_section],
|
|
290
|
+
idx[action_key],
|
|
291
|
+
)
|
|
292
|
+
section = action_section.setdefault(model_name, {})
|
|
293
|
+
if not params:
|
|
294
|
+
lookup_map = section.setdefault(ALL_RECORDS_LOOKUP, {})
|
|
295
|
+
lookup_map.setdefault(ALL_RECORDS_VALUE, set()).add(cache_key)
|
|
296
|
+
continue
|
|
297
|
+
if len(params) > 1:
|
|
298
|
+
cache_dependencies = section.setdefault("__cache_dependencies__", {})
|
|
299
|
+
cache_dependencies.setdefault(cache_key, set()).add(identifier)
|
|
300
|
+
for lookup, val in params.items():
|
|
301
|
+
lookup_map = section.setdefault(lookup, {})
|
|
302
|
+
val_key = json.dumps(
|
|
303
|
+
_normalize_dependency_identifier(val), sort_keys=True
|
|
304
|
+
)
|
|
305
|
+
lookup_map.setdefault(val_key, set()).add(cache_key)
|
|
306
|
+
|
|
307
|
+
elif action == "request_query":
|
|
308
|
+
request_index = cast(
|
|
309
|
+
dict[str, dict[str, set[str]]],
|
|
310
|
+
idx.setdefault("request_query", {}),
|
|
311
|
+
)
|
|
312
|
+
request_section = request_index.setdefault(model_name, {})
|
|
313
|
+
request_section.setdefault(identifier, set()).add(cache_key)
|
|
314
|
+
|
|
315
|
+
elif action == "all":
|
|
316
|
+
all_index = cast(
|
|
317
|
+
dict[str, set[str]],
|
|
318
|
+
idx.setdefault("all", {}),
|
|
319
|
+
)
|
|
320
|
+
all_index.setdefault(model_name, set()).add(cache_key)
|
|
321
|
+
|
|
322
|
+
else:
|
|
323
|
+
filter_section = cast(
|
|
324
|
+
dict[general_manager_name, manager_dependency_section],
|
|
325
|
+
idx["filter"],
|
|
326
|
+
)
|
|
327
|
+
section = filter_section.setdefault(model_name, {})
|
|
328
|
+
lookup_map = section.setdefault("identification", {})
|
|
329
|
+
lookup_map.setdefault(identifier, set()).add(cache_key)
|
|
330
|
+
|
|
331
|
+
|
|
215
332
|
def record_dependencies(
|
|
216
333
|
cache_key: str,
|
|
217
334
|
dependencies: Iterable[Dependency],
|
|
@@ -232,61 +349,32 @@ def record_dependencies(
|
|
|
232
349
|
acquire_lock_with_retry("record_dependencies")
|
|
233
350
|
try:
|
|
234
351
|
idx = get_full_index()
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if not isinstance(params, dict):
|
|
240
|
-
continue
|
|
241
|
-
action_section = cast(
|
|
242
|
-
dict[general_manager_name, manager_dependency_section],
|
|
243
|
-
idx[action_key],
|
|
244
|
-
)
|
|
245
|
-
section = action_section.setdefault(model_name, {})
|
|
246
|
-
if not params:
|
|
247
|
-
lookup_map = section.setdefault(ALL_RECORDS_LOOKUP, {})
|
|
248
|
-
lookup_map.setdefault(ALL_RECORDS_VALUE, set()).add(cache_key)
|
|
249
|
-
continue
|
|
250
|
-
if len(params) > 1:
|
|
251
|
-
cache_dependencies = section.setdefault(
|
|
252
|
-
"__cache_dependencies__", {}
|
|
253
|
-
)
|
|
254
|
-
cache_dependencies.setdefault(cache_key, set()).add(identifier)
|
|
255
|
-
for lookup, val in params.items():
|
|
256
|
-
lookup_map = section.setdefault(lookup, {})
|
|
257
|
-
val_key = json.dumps(
|
|
258
|
-
_normalize_dependency_identifier(val), sort_keys=True
|
|
259
|
-
)
|
|
260
|
-
lookup_map.setdefault(val_key, set()).add(cache_key)
|
|
261
|
-
|
|
262
|
-
elif action == "request_query":
|
|
263
|
-
request_index = cast(
|
|
264
|
-
dict[str, dict[str, set[str]]],
|
|
265
|
-
idx.setdefault("request_query", {}),
|
|
266
|
-
)
|
|
267
|
-
request_section = request_index.setdefault(model_name, {})
|
|
268
|
-
request_section.setdefault(identifier, set()).add(cache_key)
|
|
352
|
+
_record_dependencies_locked(idx, cache_key, dependencies)
|
|
353
|
+
set_full_index(idx)
|
|
354
|
+
finally:
|
|
355
|
+
release_lock()
|
|
269
356
|
|
|
270
|
-
elif action == "all":
|
|
271
|
-
all_index = cast(
|
|
272
|
-
dict[str, set[str]],
|
|
273
|
-
idx.setdefault("all", {}),
|
|
274
|
-
)
|
|
275
|
-
all_index.setdefault(model_name, set()).add(cache_key)
|
|
276
357
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
358
|
+
def record_many_dependencies(
|
|
359
|
+
entries: Iterable[tuple[str, Iterable[Dependency]]],
|
|
360
|
+
) -> None:
|
|
361
|
+
"""
|
|
362
|
+
Register dependency metadata for many cache keys while holding one index lock.
|
|
363
|
+
"""
|
|
364
|
+
normalized: dict[str, set[Dependency]] = {}
|
|
365
|
+
for cache_key, dependencies in entries:
|
|
366
|
+
dep_set = set(dependencies)
|
|
367
|
+
if dep_set:
|
|
368
|
+
normalized.setdefault(cache_key, set()).update(dep_set)
|
|
369
|
+
if not normalized:
|
|
370
|
+
return
|
|
287
371
|
|
|
372
|
+
acquire_lock_with_retry("record_many_dependencies")
|
|
373
|
+
try:
|
|
374
|
+
idx = get_full_index()
|
|
375
|
+
for cache_key, dependencies in normalized.items():
|
|
376
|
+
_record_dependencies_locked(idx, cache_key, dependencies)
|
|
288
377
|
set_full_index(idx)
|
|
289
|
-
|
|
290
378
|
finally:
|
|
291
379
|
release_lock()
|
|
292
380
|
|
|
@@ -380,7 +468,8 @@ def _remove_cache_keys_from_index_locked(
|
|
|
380
468
|
all_section = cast(dict[str, set[str]], idx.get("all", {}))
|
|
381
469
|
for mname, key_set in list(all_section.items()):
|
|
382
470
|
for cache_key in cache_keys:
|
|
383
|
-
key_set
|
|
471
|
+
if cache_key in key_set:
|
|
472
|
+
key_set.remove(cache_key)
|
|
384
473
|
if not key_set:
|
|
385
474
|
del all_section[mname]
|
|
386
475
|
for action in ACTIONS:
|
|
@@ -396,7 +485,8 @@ def _remove_cache_keys_from_index_locked(
|
|
|
396
485
|
lookup_map = cast(lookup_dependency_map, lookup_map)
|
|
397
486
|
for val_key, key_set in list(lookup_map.items()):
|
|
398
487
|
for cache_key in cache_keys:
|
|
399
|
-
key_set
|
|
488
|
+
if cache_key in key_set:
|
|
489
|
+
key_set.remove(cache_key)
|
|
400
490
|
if not key_set:
|
|
401
491
|
del lookup_map[val_key]
|
|
402
492
|
if not lookup_map:
|
|
@@ -415,7 +505,8 @@ def _remove_cache_keys_from_index_locked(
|
|
|
415
505
|
for mname, query_section in list(request_query_section.items()):
|
|
416
506
|
for identifier, key_set in list(query_section.items()):
|
|
417
507
|
for cache_key in cache_keys:
|
|
418
|
-
key_set
|
|
508
|
+
if cache_key in key_set:
|
|
509
|
+
key_set.remove(cache_key)
|
|
419
510
|
if not key_set:
|
|
420
511
|
del query_section[identifier]
|
|
421
512
|
if not query_section:
|
|
@@ -443,6 +534,25 @@ def invalidate_and_remove_cache_keys(cache_keys: Iterable[str]) -> None:
|
|
|
443
534
|
release_lock()
|
|
444
535
|
|
|
445
536
|
|
|
537
|
+
def _invalidate_request_query_dependencies_locked(
|
|
538
|
+
idx: dependency_index,
|
|
539
|
+
manager_name: str,
|
|
540
|
+
) -> tuple[str, ...]:
|
|
541
|
+
request_queries = cast(
|
|
542
|
+
request_query_manager_section,
|
|
543
|
+
idx.get("request_query", {}).get(manager_name, {}),
|
|
544
|
+
)
|
|
545
|
+
cache_keys = tuple(
|
|
546
|
+
dict.fromkeys(
|
|
547
|
+
cache_key for key_set in request_queries.values() for cache_key in key_set
|
|
548
|
+
)
|
|
549
|
+
)
|
|
550
|
+
for cache_key in cache_keys:
|
|
551
|
+
cache.delete(cache_key)
|
|
552
|
+
_remove_cache_keys_from_index_locked(idx, cache_keys)
|
|
553
|
+
return cache_keys
|
|
554
|
+
|
|
555
|
+
|
|
446
556
|
def invalidate_request_query_dependencies(manager_name: str) -> tuple[str, ...]:
|
|
447
557
|
"""
|
|
448
558
|
Invalidate all request-query cache keys tracked for a manager atomically.
|
|
@@ -453,24 +563,12 @@ def invalidate_request_query_dependencies(manager_name: str) -> tuple[str, ...]:
|
|
|
453
563
|
acquire_lock_with_retry("invalidate_request_query_dependencies")
|
|
454
564
|
try:
|
|
455
565
|
idx = get_full_index()
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
idx.get("request_query", {}).get(manager_name, {}),
|
|
459
|
-
)
|
|
460
|
-
cache_keys = tuple(
|
|
461
|
-
dict.fromkeys(
|
|
462
|
-
cache_key
|
|
463
|
-
for key_set in request_queries.values()
|
|
464
|
-
for cache_key in key_set
|
|
465
|
-
)
|
|
566
|
+
invalidated_keys = _invalidate_request_query_dependencies_locked(
|
|
567
|
+
idx, manager_name
|
|
466
568
|
)
|
|
467
|
-
if
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
cache.delete(cache_key)
|
|
471
|
-
_remove_cache_keys_from_index_locked(idx, cache_keys)
|
|
472
|
-
set_full_index(idx)
|
|
473
|
-
return cache_keys
|
|
569
|
+
if invalidated_keys:
|
|
570
|
+
set_full_index(idx)
|
|
571
|
+
return invalidated_keys
|
|
474
572
|
finally:
|
|
475
573
|
release_lock()
|
|
476
574
|
|
|
@@ -522,25 +620,26 @@ def capture_old_values(
|
|
|
522
620
|
instance._old_values = vals
|
|
523
621
|
|
|
524
622
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
623
|
+
def _generic_cache_invalidation_locked(
|
|
624
|
+
idx: dependency_index,
|
|
625
|
+
manager_name: str,
|
|
528
626
|
instance: GeneralManager,
|
|
529
627
|
old_relevant_values: dict[str, Any],
|
|
530
|
-
**kwargs: object,
|
|
531
628
|
) -> None:
|
|
532
629
|
"""
|
|
533
|
-
Invalidate cache entries
|
|
630
|
+
Invalidate cache entries affected by a change while the dependency lock is held.
|
|
534
631
|
|
|
535
632
|
Uses the dependency index to compare previously captured values against the instance's current values for tracked lookups, evaluates both simple and composite dependency conditions for "filter" and "exclude" actions, and for any dependency that warrants invalidation it deletes the corresponding cache entry and removes its references from the index.
|
|
536
633
|
|
|
537
634
|
Parameters:
|
|
538
|
-
|
|
635
|
+
idx (dependency_index): Dependency index loaded by the public receiver.
|
|
636
|
+
manager_name (str): Name of the manager class that emitted the signal.
|
|
539
637
|
instance (GeneralManager): The manager instance that was changed.
|
|
540
638
|
old_relevant_values (dict[str, Any]): Mapping of lookup paths (joined by "__") to their values as captured before the change; used to compare old vs. new values for invalidation decisions.
|
|
541
639
|
"""
|
|
542
|
-
|
|
543
|
-
|
|
640
|
+
invalidated_request_query_keys = _invalidate_request_query_dependencies_locked(
|
|
641
|
+
idx, manager_name
|
|
642
|
+
)
|
|
544
643
|
for cache_key in invalidated_request_query_keys:
|
|
545
644
|
logger.info(
|
|
546
645
|
"invalidating request query cache key",
|
|
@@ -550,7 +649,6 @@ def generic_cache_invalidation(
|
|
|
550
649
|
"action": REQUEST_QUERY_ACTION,
|
|
551
650
|
},
|
|
552
651
|
)
|
|
553
|
-
idx = get_full_index()
|
|
554
652
|
all_cache_keys = tuple(
|
|
555
653
|
cast(dict[str, set[str]], idx.get("all", {})).get(manager_name, set())
|
|
556
654
|
)
|
|
@@ -563,8 +661,8 @@ def generic_cache_invalidation(
|
|
|
563
661
|
"action": "all",
|
|
564
662
|
},
|
|
565
663
|
)
|
|
566
|
-
|
|
567
|
-
|
|
664
|
+
cache.delete(cache_key)
|
|
665
|
+
_remove_cache_keys_from_index_locked(idx, (cache_key,))
|
|
568
666
|
|
|
569
667
|
def _json_loads_val_key(val_key: Any) -> Any:
|
|
570
668
|
if isinstance(val_key, str):
|
|
@@ -928,10 +1026,12 @@ def generic_cache_invalidation(
|
|
|
928
1026
|
model_section = action_section.get(manager_name)
|
|
929
1027
|
if not isinstance(model_section, dict):
|
|
930
1028
|
continue
|
|
931
|
-
for lookup, lookup_map in model_section.items():
|
|
1029
|
+
for lookup, lookup_map in list(model_section.items()):
|
|
932
1030
|
if lookup.startswith("__"):
|
|
933
1031
|
if lookup == ALL_RECORDS_LOOKUP:
|
|
934
|
-
for cache_keys in
|
|
1032
|
+
for cache_keys in list(
|
|
1033
|
+
cast(lookup_dependency_map, lookup_map).values()
|
|
1034
|
+
):
|
|
935
1035
|
for ck in list(cache_keys):
|
|
936
1036
|
logger.info(
|
|
937
1037
|
"invalidating cache key",
|
|
@@ -943,8 +1043,8 @@ def generic_cache_invalidation(
|
|
|
943
1043
|
"value": ALL_RECORDS_VALUE,
|
|
944
1044
|
},
|
|
945
1045
|
)
|
|
946
|
-
|
|
947
|
-
|
|
1046
|
+
cache.delete(ck)
|
|
1047
|
+
_remove_cache_keys_from_index_locked(idx, (ck,))
|
|
948
1048
|
elif lookup.startswith("__sort__"):
|
|
949
1049
|
sort_lookup = lookup.removeprefix("__sort__")
|
|
950
1050
|
attr_path = sort_lookup.split("__")
|
|
@@ -979,8 +1079,8 @@ def generic_cache_invalidation(
|
|
|
979
1079
|
"value": val_key,
|
|
980
1080
|
},
|
|
981
1081
|
)
|
|
982
|
-
|
|
983
|
-
|
|
1082
|
+
cache.delete(ck)
|
|
1083
|
+
_remove_cache_keys_from_index_locked(idx, (ck,))
|
|
984
1084
|
continue
|
|
985
1085
|
lookup_map = cast(lookup_dependency_map, lookup_map)
|
|
986
1086
|
# 1) get operator and attribute path
|
|
@@ -1039,8 +1139,8 @@ def generic_cache_invalidation(
|
|
|
1039
1139
|
"value": val_key,
|
|
1040
1140
|
},
|
|
1041
1141
|
)
|
|
1042
|
-
|
|
1043
|
-
|
|
1142
|
+
cache.delete(ck)
|
|
1143
|
+
_remove_cache_keys_from_index_locked(idx, (ck,))
|
|
1044
1144
|
|
|
1045
1145
|
else: # action == 'exclude'
|
|
1046
1146
|
# Excludes: invalidate only if matches changed
|
|
@@ -1064,5 +1164,37 @@ def generic_cache_invalidation(
|
|
|
1064
1164
|
"value": val_key,
|
|
1065
1165
|
},
|
|
1066
1166
|
)
|
|
1067
|
-
|
|
1068
|
-
|
|
1167
|
+
cache.delete(ck)
|
|
1168
|
+
_remove_cache_keys_from_index_locked(idx, (ck,))
|
|
1169
|
+
|
|
1170
|
+
|
|
1171
|
+
@receiver(post_data_change)
|
|
1172
|
+
def generic_cache_invalidation(
|
|
1173
|
+
sender: type[GeneralManager],
|
|
1174
|
+
instance: GeneralManager,
|
|
1175
|
+
old_relevant_values: dict[str, Any],
|
|
1176
|
+
**kwargs: object,
|
|
1177
|
+
) -> None:
|
|
1178
|
+
"""
|
|
1179
|
+
Invalidate cache entries whose recorded dependencies are affected by changes to a GeneralManager instance.
|
|
1180
|
+
|
|
1181
|
+
Uses the dependency index to compare previously captured values against the instance's current values for tracked lookups, evaluates both simple and composite dependency conditions for "filter" and "exclude" actions, and for any dependency that warrants invalidation it deletes the corresponding cache entry and removes its references from the index.
|
|
1182
|
+
|
|
1183
|
+
Parameters:
|
|
1184
|
+
sender (type[GeneralManager]): Manager class that emitted the signal.
|
|
1185
|
+
instance (GeneralManager): The manager instance that was changed.
|
|
1186
|
+
old_relevant_values (dict[str, Any]): Mapping of lookup paths (joined by "__") to their values as captured before the change; used to compare old vs. new values for invalidation decisions.
|
|
1187
|
+
"""
|
|
1188
|
+
manager_name = sender.__name__
|
|
1189
|
+
acquire_lock_with_retry("generic_cache_invalidation")
|
|
1190
|
+
try:
|
|
1191
|
+
idx = get_full_index()
|
|
1192
|
+
_generic_cache_invalidation_locked(
|
|
1193
|
+
idx,
|
|
1194
|
+
manager_name,
|
|
1195
|
+
instance,
|
|
1196
|
+
old_relevant_values,
|
|
1197
|
+
)
|
|
1198
|
+
set_full_index(idx)
|
|
1199
|
+
finally:
|
|
1200
|
+
release_lock()
|