GeneralManager 0.63.2__tar.gz → 0.64.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.63.2/src/GeneralManager.egg-info → generalmanager-0.64.0}/PKG-INFO +1 -1
- {generalmanager-0.63.2 → generalmanager-0.64.0}/pyproject.toml +1 -1
- {generalmanager-0.63.2 → generalmanager-0.64.0/src/GeneralManager.egg-info}/PKG-INFO +1 -1
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/GeneralManager.egg-info/SOURCES.txt +1 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/_types/api.py +2 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/api/graphql.py +214 -70
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/api/graphql_subscriptions.py +50 -0
- generalmanager-0.64.0/src/general_manager/api/notification_batching.py +121 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/api/remote_invalidation.py +51 -16
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/bootstrap.py +1 -1
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/cache/dependency_index.py +1 -1
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/migrations/0007_upload_intent.py +5 -5
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/public_api_registry.py +4 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/search/backends/meilisearch.py +1 -1
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/uploads/finalization.py +4 -4
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/uploads/models.py +5 -5
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/utils/filter_parser.py +1 -1
- {generalmanager-0.63.2 → generalmanager-0.64.0}/LICENSE +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/README.md +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/setup.cfg +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/GeneralManager.egg-info/dependency_links.txt +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/GeneralManager.egg-info/requires.txt +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/GeneralManager.egg-info/top_level.txt +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/_types/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/_types/bucket.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/_types/cache.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/_types/dataframes.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/_types/factory.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/_types/general_manager.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/_types/interface.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/_types/manager.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/_types/measurement.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/_types/permission.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/_types/rule.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/_types/search.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/_types/utils.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/api/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/api/graphql_errors.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/api/graphql_mutations.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/api/graphql_prefetch.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/api/graphql_resolvers.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/api/graphql_search.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/api/graphql_subscription_consumer.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/api/graphql_view.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/api/graphql_warmup.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/api/graphql_warmup_registry.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/api/graphql_warmup_tasks.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/api/mutation.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/api/property.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/api/registry.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/api/remote_api.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/api/remote_invalidation_client.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/apps.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/bucket/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/bucket/base_bucket.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/bucket/calculation_bucket.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/bucket/database_bucket.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/bucket/group_bucket.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/bucket/indexing.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/bucket/request_bucket.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/cache/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/cache/cache_decorator.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/cache/cache_tracker.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/cache/dependency_cache.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/cache/dependency_matching.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/cache/dependency_publish.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/cache/dependency_shards.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/cache/model_dependency_collector.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/cache/run_context.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/cache/signals.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/audit.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/bootstrap.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/checks.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/consumer.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/errors.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/evals/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/evals/__main__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/evals/baseline.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/evals/datasets/basic_queries.yaml +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/evals/datasets/demo_readiness.yaml +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/evals/datasets/edge_cases.yaml +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/evals/datasets/follow_ups.yaml +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/evals/datasets/large_schema.yaml +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/evals/datasets/multi_hop.yaml +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/evals/diagnostics.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/evals/fingerprints.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/evals/fixtures.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/evals/judges/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/evals/judges/answer_quality.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/evals/judges/contract.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/evals/judges/result_accuracy.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/evals/judges/tool_sequence.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/evals/readiness.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/evals/runner.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/evals/traces.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/grounding.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/models.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/providers/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/providers/_shared.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/providers/anthropic.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/providers/base.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/providers/google.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/providers/ollama.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/providers/openai.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/rate_limits.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/routing.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/schema_index.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/settings.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/signals.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/system_prompt.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/tool_metadata.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/tools.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/chat/views.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/conf.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/dataframes/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/dataframes/measurements.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/factory/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/factory/auto_factory.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/factory/factories.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/factory/factory_methods.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/base_interface.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/bundles/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/bundles/calculation.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/bundles/database.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/bundles/remote_manager.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/bundles/request.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/base.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/builtin.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/calculation/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/calculation/_compat.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/calculation/lifecycle.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/configuration.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/core/observability.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/core/utils.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/exceptions.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/existing_model/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/existing_model/_compat.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/existing_model/resolution.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/factory.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/orm/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/orm/_compat.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/orm/history.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/orm/lifecycle.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/orm/mutations.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/orm/support.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/orm_utils/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/orm_utils/django_manager_utils.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/orm_utils/field_descriptors.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/orm_utils/payload_normalizer.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/read_only/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/read_only/_compat.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/read_only/_types.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/read_only/lifecycle.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/read_only/management.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/registry.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/remote_manager.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/capabilities/request/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/infrastructure/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/infrastructure/startup_hooks.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/infrastructure/system_checks.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/interfaces/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/interfaces/calculation.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/interfaces/database.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/interfaces/existing_model.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/interfaces/read_only.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/interfaces/remote_manager.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/interfaces/request.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/manifests/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/manifests/capability_builder.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/manifests/capability_manifest.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/manifests/capability_models.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/orm_interface.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/requests.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/utils/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/utils/database_interface_protocols.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/utils/errors.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/utils/history.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/interface/utils/models.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/logging.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/management/commands/chat_cleanup.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/management/commands/cleanup_upload_intents.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/management/commands/graphql_warmup.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/management/commands/graphql_warmup_refresh_due.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/management/commands/search_index.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/management/commands/search_reconcile.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/management/commands/seed_manager_landscape.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/management/commands/shell.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/management/commands/workflow_drain_outbox.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/management/commands/workflow_replay_dead_letters.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/manager/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/manager/general_manager.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/manager/group_manager.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/manager/input.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/manager/meta.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/measurement/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/measurement/measurement.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/measurement/measurement_field.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/metrics/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/metrics/graphql.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/migrations/0001_initial.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/migrations/0002_workflow_outbox_scaling_indexes.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/migrations/0003_workflow_execution_correlation_constraint.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/migrations/0004_chat_persistence_models.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/migrations/0004_search_index_state.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/migrations/0005_merge_chat_search.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/migrations/0006_chat_pending_confirmation_scoped_ids.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/migrations/0008_upload_finalization_versions.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/migrations/0009_upload_cleanup_state.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/migrations/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/models.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/permission/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/permission/audit.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/permission/base_permission.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/permission/graphql_capabilities.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/permission/manager_based_permission.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/permission/mutation_permission.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/permission/permission_checks.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/permission/permission_data_manager.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/permission/utils.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/py.typed +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/rule/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/rule/handler.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/rule/rule.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/search/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/search/async_tasks.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/search/backend.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/search/backend_registry.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/search/backends/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/search/backends/dev.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/search/backends/opensearch.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/search/backends/typesense.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/search/config.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/search/indexer.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/search/models.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/search/reconciliation.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/search/registry.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/search/tasks.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/search/utils.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/seeding/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/seeding/manager_landscape.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/uploads/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/uploads/adapters.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/uploads/checks.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/uploads/config.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/uploads/errors.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/uploads/graphql.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/uploads/graphql_types.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/uploads/metrics.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/uploads/public.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/uploads/s3.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/uploads/services.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/uploads/tokens.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/uploads/types.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/uploads/urls.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/uploads/views.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/utils/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/utils/args_to_kwargs.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/utils/format_string.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/utils/json_encoder.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/utils/make_cache_key.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/utils/none_to_zero.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/utils/path_mapping.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/utils/public_api.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/utils/testing.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/utils/type_checks.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/workflow/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/workflow/actions.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/workflow/backend_registry.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/workflow/backends/__init__.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/workflow/backends/celery.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/workflow/backends/local.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/workflow/backends/n8n.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/workflow/config.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/workflow/engine.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/workflow/event_registry.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/workflow/events.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/workflow/models.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/workflow/signal_bridge.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/workflow/tasks.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/workflow/telemetry.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/tests/test_settings.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.0}/tests/test_urls.py +0 -0
- {generalmanager-0.63.2 → generalmanager-0.64.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.64.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.64.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.64.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
|
|
@@ -42,6 +42,7 @@ src/general_manager/api/graphql_warmup.py
|
|
|
42
42
|
src/general_manager/api/graphql_warmup_registry.py
|
|
43
43
|
src/general_manager/api/graphql_warmup_tasks.py
|
|
44
44
|
src/general_manager/api/mutation.py
|
|
45
|
+
src/general_manager/api/notification_batching.py
|
|
45
46
|
src/general_manager/api/property.py
|
|
46
47
|
src/general_manager/api/registry.py
|
|
47
48
|
src/general_manager/api/remote_api.py
|
|
@@ -56,6 +56,7 @@ __all__ = [
|
|
|
56
56
|
"UploadTokenInvalidError",
|
|
57
57
|
"UploadTransferConflictError",
|
|
58
58
|
"UploadTransport",
|
|
59
|
+
"bulk_data_change_notifications",
|
|
59
60
|
"graph_ql_mutation",
|
|
60
61
|
"graph_ql_property",
|
|
61
62
|
"register_upload_adapter",
|
|
@@ -115,5 +116,6 @@ from general_manager.uploads.errors import UploadTokenInvalidError
|
|
|
115
116
|
from general_manager.uploads.errors import UploadTransferConflictError
|
|
116
117
|
from general_manager.uploads.types import UploadTransport
|
|
117
118
|
from general_manager.api.mutation import graph_ql_mutation
|
|
119
|
+
from general_manager.api.notification_batching import bulk_data_change_notifications
|
|
118
120
|
from general_manager.api.property import graph_ql_property
|
|
119
121
|
from general_manager.uploads.public import register_upload_adapter
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
-
from contextlib import
|
|
6
|
+
from contextlib import asynccontextmanager
|
|
7
7
|
from copy import deepcopy
|
|
8
8
|
import re
|
|
9
9
|
from types import UnionType
|
|
@@ -113,10 +113,13 @@ from general_manager.api.graphql_search import (
|
|
|
113
113
|
create_filter_options as _create_filter_options_fn,
|
|
114
114
|
normalize_filter_input as _normalize_filter_input_fn,
|
|
115
115
|
)
|
|
116
|
+
from general_manager.api.notification_batching import _queue_notification
|
|
116
117
|
from general_manager.api.graphql_subscriptions import (
|
|
117
118
|
get_channel_layer_safe as _get_channel_layer_fn,
|
|
118
119
|
group_name as _group_name_fn,
|
|
119
120
|
class_group_name as _class_group_name_fn,
|
|
121
|
+
refresh_group_name as _refresh_group_name_fn,
|
|
122
|
+
dispatch_subscription_event as _dispatch_subscription_event_fn,
|
|
120
123
|
channel_listener as _channel_listener_fn,
|
|
121
124
|
channel_message_listener as _channel_message_listener_fn,
|
|
122
125
|
prime_graphql_properties as _prime_graphql_properties_fn,
|
|
@@ -155,6 +158,101 @@ GraphQLFilterNormalizer = Callable[
|
|
|
155
158
|
dict[str, GraphQLFilterMapping],
|
|
156
159
|
]
|
|
157
160
|
GraphQLMutationMap = dict[str, type[graphene.Mutation]]
|
|
161
|
+
_SUBSCRIPTION_CLEANUP_FAILURE_MESSAGE = "subscription cleanup failed"
|
|
162
|
+
_SUBSCRIPTION_ROLLBACK_FAILURE_MESSAGE = "subscription setup and rollback failed"
|
|
163
|
+
_SUBSCRIPTION_STREAM_CLEANUP_FAILURE_MESSAGE = "subscription stream and cleanup failed"
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
async def _cleanup_subscription_resources(
|
|
167
|
+
channel_layer: BaseChannelLayer,
|
|
168
|
+
channel_name: str,
|
|
169
|
+
joined_groups: Iterable[str],
|
|
170
|
+
listener_task: asyncio.Task[None] | None = None,
|
|
171
|
+
) -> None:
|
|
172
|
+
"""Stop a listener and attempt to discard every successfully joined group."""
|
|
173
|
+
cleanup_errors: list[BaseException] = []
|
|
174
|
+
if listener_task is not None:
|
|
175
|
+
listener_task.cancel()
|
|
176
|
+
try:
|
|
177
|
+
await listener_task
|
|
178
|
+
except asyncio.CancelledError:
|
|
179
|
+
pass
|
|
180
|
+
except BaseException as exc: # noqa: BLE001
|
|
181
|
+
cleanup_errors.append(exc)
|
|
182
|
+
|
|
183
|
+
for group in joined_groups:
|
|
184
|
+
try:
|
|
185
|
+
await channel_layer.group_discard(group, channel_name)
|
|
186
|
+
except BaseException as exc: # noqa: BLE001
|
|
187
|
+
cleanup_errors.append(exc)
|
|
188
|
+
|
|
189
|
+
if len(cleanup_errors) == 1:
|
|
190
|
+
raise cleanup_errors[0]
|
|
191
|
+
if cleanup_errors:
|
|
192
|
+
raise BaseExceptionGroup(
|
|
193
|
+
_SUBSCRIPTION_CLEANUP_FAILURE_MESSAGE,
|
|
194
|
+
cleanup_errors,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
async def _join_subscription_groups(
|
|
199
|
+
channel_layer: BaseChannelLayer,
|
|
200
|
+
channel_name: str,
|
|
201
|
+
group_names: Iterable[str],
|
|
202
|
+
) -> list[str]:
|
|
203
|
+
"""Join groups and roll back every successful join if a later join fails."""
|
|
204
|
+
joined_groups: list[str] = []
|
|
205
|
+
try:
|
|
206
|
+
for group in group_names:
|
|
207
|
+
await channel_layer.group_add(group, channel_name)
|
|
208
|
+
joined_groups.append(group)
|
|
209
|
+
except BaseException as setup_error:
|
|
210
|
+
try:
|
|
211
|
+
await _cleanup_subscription_resources(
|
|
212
|
+
channel_layer,
|
|
213
|
+
channel_name,
|
|
214
|
+
joined_groups,
|
|
215
|
+
)
|
|
216
|
+
except BaseException as cleanup_error: # noqa: BLE001
|
|
217
|
+
raise BaseExceptionGroup(
|
|
218
|
+
_SUBSCRIPTION_ROLLBACK_FAILURE_MESSAGE,
|
|
219
|
+
[setup_error, cleanup_error],
|
|
220
|
+
) from None
|
|
221
|
+
raise
|
|
222
|
+
return joined_groups
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@asynccontextmanager
|
|
226
|
+
async def _subscription_cleanup_scope(
|
|
227
|
+
channel_layer: BaseChannelLayer,
|
|
228
|
+
channel_name: str,
|
|
229
|
+
joined_groups: Iterable[str],
|
|
230
|
+
listener_task: asyncio.Task[None],
|
|
231
|
+
) -> AsyncIterator[None]:
|
|
232
|
+
"""Clean subscription resources without masking an active stream failure."""
|
|
233
|
+
try:
|
|
234
|
+
yield
|
|
235
|
+
except BaseException as stream_error:
|
|
236
|
+
try:
|
|
237
|
+
await _cleanup_subscription_resources(
|
|
238
|
+
channel_layer,
|
|
239
|
+
channel_name,
|
|
240
|
+
joined_groups,
|
|
241
|
+
listener_task,
|
|
242
|
+
)
|
|
243
|
+
except BaseException as cleanup_error: # noqa: BLE001
|
|
244
|
+
raise BaseExceptionGroup(
|
|
245
|
+
_SUBSCRIPTION_STREAM_CLEANUP_FAILURE_MESSAGE,
|
|
246
|
+
[stream_error, cleanup_error],
|
|
247
|
+
) from None
|
|
248
|
+
raise
|
|
249
|
+
else:
|
|
250
|
+
await _cleanup_subscription_resources(
|
|
251
|
+
channel_layer,
|
|
252
|
+
channel_name,
|
|
253
|
+
joined_groups,
|
|
254
|
+
listener_task,
|
|
255
|
+
)
|
|
158
256
|
|
|
159
257
|
|
|
160
258
|
class GraphQL:
|
|
@@ -281,6 +379,15 @@ class GraphQL:
|
|
|
281
379
|
"""
|
|
282
380
|
return _class_group_name_fn(manager_class)
|
|
283
381
|
|
|
382
|
+
@staticmethod
|
|
383
|
+
def _refresh_group_name(manager_class: type[GeneralManager]) -> str:
|
|
384
|
+
"""
|
|
385
|
+
Compatibility wrapper for ``graphql_subscriptions.refresh_group_name(manager_class)``.
|
|
386
|
+
|
|
387
|
+
Returns the deterministic manager-wide refresh group name.
|
|
388
|
+
"""
|
|
389
|
+
return _refresh_group_name_fn(manager_class)
|
|
390
|
+
|
|
284
391
|
@staticmethod
|
|
285
392
|
async def _channel_listener(
|
|
286
393
|
channel_layer: BaseChannelLayer,
|
|
@@ -1352,7 +1459,7 @@ class GraphQL:
|
|
|
1352
1459
|
"""
|
|
1353
1460
|
Stream subscription events for a specific manager instance identified by the provided arguments.
|
|
1354
1461
|
|
|
1355
|
-
Yields an initial `SubscriptionEvent` with `action` set to `"snapshot"` containing the current manager instance, then yields `SubscriptionEvent`s for each subsequent action. For
|
|
1462
|
+
Yields an initial `SubscriptionEvent` with `action` set to `"snapshot"` containing the current manager instance, then yields `SubscriptionEvent`s for each subsequent action. For subsequent events, `item` will be the re-instantiated manager instance or `None` if instantiation fails. The subscriber is registered on the manager's channel groups (including dependent managers' groups) and the channel subscriptions and background listener are cleaned up when the iterator is closed or cancelled.
|
|
1356
1463
|
|
|
1357
1464
|
Parameters:
|
|
1358
1465
|
identification: Identification fields required to locate the manager instance (maps to the manager's identification signature).
|
|
@@ -1379,14 +1486,13 @@ class GraphQL:
|
|
|
1379
1486
|
)
|
|
1380
1487
|
except RuntimeError as exc:
|
|
1381
1488
|
raise GraphQLError(str(exc)) from exc
|
|
1382
|
-
channel_name = cast(str, await channel_layer.new_channel())
|
|
1383
|
-
queue: asyncio.Queue[str] = asyncio.Queue[str]()
|
|
1384
1489
|
|
|
1385
1490
|
group_names = {
|
|
1386
1491
|
cls._group_name(
|
|
1387
1492
|
generalManagerClass,
|
|
1388
1493
|
instance.identification,
|
|
1389
|
-
)
|
|
1494
|
+
),
|
|
1495
|
+
cls._refresh_group_name(generalManagerClass),
|
|
1390
1496
|
}
|
|
1391
1497
|
dependencies = cls._resolve_subscription_dependencies(
|
|
1392
1498
|
generalManagerClass,
|
|
@@ -1397,13 +1503,7 @@ class GraphQL:
|
|
|
1397
1503
|
group_names.add(
|
|
1398
1504
|
cls._group_name(dependency_class, dependency_identification)
|
|
1399
1505
|
)
|
|
1400
|
-
|
|
1401
|
-
for group in group_names:
|
|
1402
|
-
await channel_layer.group_add(group, channel_name)
|
|
1403
|
-
|
|
1404
|
-
listener_task = asyncio.create_task(
|
|
1405
|
-
cls._channel_listener(channel_layer, channel_name, queue)
|
|
1406
|
-
)
|
|
1506
|
+
group_names.add(cls._refresh_group_name(dependency_class))
|
|
1407
1507
|
|
|
1408
1508
|
async def event_stream() -> AsyncIterator[SubscriptionEvent]:
|
|
1409
1509
|
"""
|
|
@@ -1415,7 +1515,22 @@ class GraphQL:
|
|
|
1415
1515
|
Notes:
|
|
1416
1516
|
When the iterator is closed or exits, the background listener task is cancelled and the subscription's channel group memberships are discarded.
|
|
1417
1517
|
"""
|
|
1418
|
-
|
|
1518
|
+
channel_name = cast(str, await channel_layer.new_channel())
|
|
1519
|
+
queue: asyncio.Queue[str] = asyncio.Queue[str]()
|
|
1520
|
+
joined_groups = await _join_subscription_groups(
|
|
1521
|
+
channel_layer,
|
|
1522
|
+
channel_name,
|
|
1523
|
+
group_names,
|
|
1524
|
+
)
|
|
1525
|
+
listener_task = asyncio.create_task(
|
|
1526
|
+
cls._channel_listener(channel_layer, channel_name, queue)
|
|
1527
|
+
)
|
|
1528
|
+
async with _subscription_cleanup_scope(
|
|
1529
|
+
channel_layer,
|
|
1530
|
+
channel_name,
|
|
1531
|
+
joined_groups,
|
|
1532
|
+
listener_task,
|
|
1533
|
+
):
|
|
1419
1534
|
clear_capability_context(info)
|
|
1420
1535
|
yield SubscriptionEvent(item=instance, action="snapshot")
|
|
1421
1536
|
while True:
|
|
@@ -1431,12 +1546,6 @@ class GraphQL:
|
|
|
1431
1546
|
item = None
|
|
1432
1547
|
clear_capability_context(info)
|
|
1433
1548
|
yield SubscriptionEvent(item=item, action=action)
|
|
1434
|
-
finally:
|
|
1435
|
-
listener_task.cancel()
|
|
1436
|
-
with suppress(asyncio.CancelledError):
|
|
1437
|
-
await listener_task
|
|
1438
|
-
for group in group_names:
|
|
1439
|
-
await channel_layer.group_discard(group, channel_name)
|
|
1440
1549
|
|
|
1441
1550
|
return event_stream()
|
|
1442
1551
|
|
|
@@ -1466,9 +1575,10 @@ class GraphQL:
|
|
|
1466
1575
|
Stream future authorized change events for any instance of a manager class.
|
|
1467
1576
|
|
|
1468
1577
|
Unlike single-entity subscriptions, class-wide subscriptions do not
|
|
1469
|
-
yield an initial snapshot.
|
|
1470
|
-
|
|
1471
|
-
|
|
1578
|
+
yield an initial snapshot. Aggregate refresh events without an
|
|
1579
|
+
identification carry no item; identified row events, including
|
|
1580
|
+
row-level refresh actions, are hydrated and checked against the
|
|
1581
|
+
request user's object-level read permission before they are yielded.
|
|
1472
1582
|
"""
|
|
1473
1583
|
try:
|
|
1474
1584
|
channel_layer = cast(
|
|
@@ -1476,25 +1586,41 @@ class GraphQL:
|
|
|
1476
1586
|
)
|
|
1477
1587
|
except RuntimeError as exc:
|
|
1478
1588
|
raise GraphQLError(str(exc)) from exc
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
listener_task = asyncio.create_task(
|
|
1484
|
-
cls._channel_message_listener(channel_layer, channel_name, queue)
|
|
1485
|
-
)
|
|
1589
|
+
group_names = {
|
|
1590
|
+
cls._class_group_name(generalManagerClass),
|
|
1591
|
+
cls._refresh_group_name(generalManagerClass),
|
|
1592
|
+
}
|
|
1486
1593
|
|
|
1487
1594
|
async def event_stream() -> AsyncIterator[SubscriptionEvent]:
|
|
1488
|
-
|
|
1595
|
+
channel_name = cast(str, await channel_layer.new_channel())
|
|
1596
|
+
queue: asyncio.Queue[GraphQLFieldMap] = asyncio.Queue()
|
|
1597
|
+
joined_groups = await _join_subscription_groups(
|
|
1598
|
+
channel_layer,
|
|
1599
|
+
channel_name,
|
|
1600
|
+
group_names,
|
|
1601
|
+
)
|
|
1602
|
+
listener_task = asyncio.create_task(
|
|
1603
|
+
cls._channel_message_listener(channel_layer, channel_name, queue)
|
|
1604
|
+
)
|
|
1605
|
+
async with _subscription_cleanup_scope(
|
|
1606
|
+
channel_layer,
|
|
1607
|
+
channel_name,
|
|
1608
|
+
joined_groups,
|
|
1609
|
+
listener_task,
|
|
1610
|
+
):
|
|
1489
1611
|
while True:
|
|
1490
1612
|
message = await queue.get()
|
|
1491
1613
|
if message.get("manager") != generalManagerClass.__name__:
|
|
1492
1614
|
continue
|
|
1493
1615
|
action = message.get("action")
|
|
1616
|
+
if not isinstance(action, str):
|
|
1617
|
+
continue
|
|
1494
1618
|
identification = message.get("identification")
|
|
1495
|
-
if
|
|
1496
|
-
|
|
1497
|
-
|
|
1619
|
+
if action == "refresh" and identification is None:
|
|
1620
|
+
clear_capability_context(info)
|
|
1621
|
+
yield SubscriptionEvent(item=None, action=action)
|
|
1622
|
+
continue
|
|
1623
|
+
if not isinstance(identification, dict):
|
|
1498
1624
|
continue
|
|
1499
1625
|
identification_copy = deepcopy(identification)
|
|
1500
1626
|
try:
|
|
@@ -1509,11 +1635,6 @@ class GraphQL:
|
|
|
1509
1635
|
continue
|
|
1510
1636
|
clear_capability_context(info)
|
|
1511
1637
|
yield SubscriptionEvent(item=item, action=action)
|
|
1512
|
-
finally:
|
|
1513
|
-
listener_task.cancel()
|
|
1514
|
-
with suppress(asyncio.CancelledError):
|
|
1515
|
-
await listener_task
|
|
1516
|
-
await channel_layer.group_discard(group_name, channel_name)
|
|
1517
1638
|
|
|
1518
1639
|
return event_stream()
|
|
1519
1640
|
|
|
@@ -1595,14 +1716,15 @@ class GraphQL:
|
|
|
1595
1716
|
Send a "gm.subscription.event" message to the channel group corresponding to a changed GeneralManager instance.
|
|
1596
1717
|
|
|
1597
1718
|
If the provided instance is a registered GeneralManager and a channel
|
|
1598
|
-
layer is configured,
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1719
|
+
layer is configured, queue one manager-wide refresh while notification
|
|
1720
|
+
batching is active. Outside a batch, publish the row-level message to
|
|
1721
|
+
the instance and class-wide groups through one async bridge.
|
|
1722
|
+
Identification comes from the provided `identification` mapping when
|
|
1723
|
+
supplied, otherwise from the changed instance, and is deep-copied
|
|
1724
|
+
before dispatch. If the instance is `None`, the manager type is not
|
|
1725
|
+
registered, or no channel layer is available, the function returns
|
|
1726
|
+
without dispatching. Ordinary group dispatch failures are logged and
|
|
1727
|
+
do not stop attempts for remaining groups.
|
|
1606
1728
|
|
|
1607
1729
|
Parameters:
|
|
1608
1730
|
sender (type[GeneralManager] | GeneralManager): The signal sender; either a GeneralManager subclass or an instance.
|
|
@@ -1646,35 +1768,57 @@ class GraphQL:
|
|
|
1646
1768
|
)
|
|
1647
1769
|
group_name = cls._group_name(manager_class, event_identification)
|
|
1648
1770
|
class_group_name = cls._class_group_name(manager_class)
|
|
1649
|
-
message = {
|
|
1771
|
+
message: dict[str, object] = {
|
|
1650
1772
|
"type": "gm.subscription.event",
|
|
1651
1773
|
"action": action,
|
|
1652
1774
|
"manager": manager_class.__name__,
|
|
1653
1775
|
"identification": event_identification,
|
|
1654
1776
|
}
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1777
|
+
refresh_group_name = cls._refresh_group_name(manager_class)
|
|
1778
|
+
refresh_message: dict[str, object] = {
|
|
1779
|
+
"type": "gm.subscription.event",
|
|
1780
|
+
"action": "refresh",
|
|
1781
|
+
"manager": manager_class.__name__,
|
|
1782
|
+
}
|
|
1783
|
+
if _queue_notification(
|
|
1784
|
+
key=("graphql", refresh_group_name),
|
|
1785
|
+
group_send=channel_layer.group_send,
|
|
1786
|
+
group=refresh_group_name,
|
|
1787
|
+
message=refresh_message,
|
|
1788
|
+
):
|
|
1789
|
+
return
|
|
1790
|
+
|
|
1791
|
+
target_groups = (group_name, class_group_name)
|
|
1792
|
+
try:
|
|
1793
|
+
dispatched = async_to_sync(_dispatch_subscription_event_fn)(
|
|
1794
|
+
channel_layer,
|
|
1795
|
+
target_groups,
|
|
1796
|
+
message,
|
|
1797
|
+
)
|
|
1798
|
+
except MemoryError:
|
|
1799
|
+
raise
|
|
1800
|
+
except Exception as exc: # noqa: BLE001
|
|
1801
|
+
logger.warning(
|
|
1802
|
+
"failed to dispatch subscription event",
|
|
1803
|
+
context={
|
|
1804
|
+
"manager": manager_class.__name__,
|
|
1805
|
+
"action": action,
|
|
1806
|
+
"target_groups": list(target_groups),
|
|
1807
|
+
"message": message,
|
|
1808
|
+
},
|
|
1809
|
+
exc_info=exc,
|
|
1810
|
+
)
|
|
1811
|
+
return
|
|
1812
|
+
if dispatched > 0:
|
|
1813
|
+
logger.debug(
|
|
1814
|
+
"dispatched subscription event",
|
|
1815
|
+
context={
|
|
1816
|
+
"manager": manager_class.__name__,
|
|
1817
|
+
"action": action,
|
|
1818
|
+
"target_groups": list(target_groups),
|
|
1819
|
+
"successful_group_count": dispatched,
|
|
1820
|
+
},
|
|
1821
|
+
)
|
|
1678
1822
|
|
|
1679
1823
|
|
|
1680
1824
|
post_data_change.connect(GraphQL._handle_data_change, weak=False)
|
{generalmanager-0.63.2 → generalmanager-0.64.0}/src/general_manager/api/graphql_subscriptions.py
RENAMED
|
@@ -110,6 +110,56 @@ def class_group_name(manager_class: type[GeneralManager]) -> str:
|
|
|
110
110
|
return f"gm_subscriptions.{manager_class.__name__}.__class__"
|
|
111
111
|
|
|
112
112
|
|
|
113
|
+
def refresh_group_name(manager_class: type[GeneralManager]) -> str:
|
|
114
|
+
"""
|
|
115
|
+
Build the channel-group name for batched refreshes of a manager class.
|
|
116
|
+
|
|
117
|
+
Parameters:
|
|
118
|
+
manager_class: Manager class used to namespace the refresh group.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
A stable group identifier for aggregate manager refresh events.
|
|
122
|
+
"""
|
|
123
|
+
return f"gm_subscriptions.{manager_class.__name__}.__refresh__"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
async def dispatch_subscription_event(
|
|
127
|
+
channel_layer: BaseChannelLayer,
|
|
128
|
+
group_names: Iterable[str],
|
|
129
|
+
message: SubscriptionMessage,
|
|
130
|
+
) -> int:
|
|
131
|
+
"""
|
|
132
|
+
Send one subscription event sequentially to each requested group.
|
|
133
|
+
|
|
134
|
+
Ordinary dispatch failures are logged for their target and do not prevent
|
|
135
|
+
later sends. ``MemoryError`` propagates immediately because continuing
|
|
136
|
+
dispatch during memory exhaustion is unsafe.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
The number of groups that accepted the event successfully.
|
|
140
|
+
"""
|
|
141
|
+
dispatched = 0
|
|
142
|
+
for target_group_name in group_names:
|
|
143
|
+
try:
|
|
144
|
+
await channel_layer.group_send(target_group_name, message)
|
|
145
|
+
except MemoryError:
|
|
146
|
+
raise
|
|
147
|
+
except Exception as exc: # noqa: BLE001
|
|
148
|
+
logger.warning(
|
|
149
|
+
"failed to dispatch subscription event",
|
|
150
|
+
context={
|
|
151
|
+
"group": target_group_name,
|
|
152
|
+
"event_type": message.get("type"),
|
|
153
|
+
"action": message.get("action"),
|
|
154
|
+
"manager": message.get("manager"),
|
|
155
|
+
},
|
|
156
|
+
exc_info=exc,
|
|
157
|
+
)
|
|
158
|
+
else:
|
|
159
|
+
dispatched += 1
|
|
160
|
+
return dispatched
|
|
161
|
+
|
|
162
|
+
|
|
113
163
|
async def channel_listener(
|
|
114
164
|
channel_layer: BaseChannelLayer,
|
|
115
165
|
channel_name: str,
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Batch data-change notifications across synchronous bulk operations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import threading
|
|
6
|
+
from _thread import LockType
|
|
7
|
+
from collections.abc import Awaitable, Callable, Iterator, Mapping
|
|
8
|
+
from contextlib import contextmanager
|
|
9
|
+
from contextvars import ContextVar
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
|
|
12
|
+
from asgiref.sync import async_to_sync
|
|
13
|
+
|
|
14
|
+
from general_manager.logging import get_logger
|
|
15
|
+
|
|
16
|
+
type NotificationKey = tuple[str, ...]
|
|
17
|
+
type NotificationMessage = Mapping[str, object]
|
|
18
|
+
type GroupSend = Callable[[str, dict[str, object]], Awaitable[None]]
|
|
19
|
+
|
|
20
|
+
logger = get_logger("api.notification_batching")
|
|
21
|
+
_COMBINED_FAILURE_MESSAGE = "bulk data change and notification flush failed"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(slots=True)
|
|
25
|
+
class _PendingNotification:
|
|
26
|
+
key: NotificationKey
|
|
27
|
+
group_send: GroupSend = field(repr=False)
|
|
28
|
+
group: str
|
|
29
|
+
message: dict[str, object]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(slots=True)
|
|
33
|
+
class _BatchState:
|
|
34
|
+
targets: dict[NotificationKey, _PendingNotification] = field(default_factory=dict)
|
|
35
|
+
accepting: bool = True
|
|
36
|
+
lock: LockType = field(default_factory=threading.Lock, repr=False)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
_active_batch: ContextVar[_BatchState | None] = ContextVar(
|
|
40
|
+
"general_manager_notification_batch",
|
|
41
|
+
default=None,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _queue_notification(
|
|
46
|
+
*,
|
|
47
|
+
key: NotificationKey,
|
|
48
|
+
group_send: GroupSend,
|
|
49
|
+
group: str,
|
|
50
|
+
message: NotificationMessage,
|
|
51
|
+
) -> bool:
|
|
52
|
+
"""Queue a target when batching is active."""
|
|
53
|
+
state = _active_batch.get()
|
|
54
|
+
if state is None:
|
|
55
|
+
return False
|
|
56
|
+
with state.lock:
|
|
57
|
+
if not state.accepting:
|
|
58
|
+
return False
|
|
59
|
+
if key in state.targets:
|
|
60
|
+
return True
|
|
61
|
+
state.targets[key] = _PendingNotification(
|
|
62
|
+
key,
|
|
63
|
+
group_send,
|
|
64
|
+
group,
|
|
65
|
+
dict(message),
|
|
66
|
+
)
|
|
67
|
+
return True
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _close_batch(state: _BatchState) -> None:
|
|
71
|
+
with state.lock:
|
|
72
|
+
state.accepting = False
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async def _flush_notifications(state: _BatchState) -> None:
|
|
76
|
+
for key in sorted(state.targets):
|
|
77
|
+
target = state.targets[key]
|
|
78
|
+
try:
|
|
79
|
+
await target.group_send(target.group, target.message)
|
|
80
|
+
except MemoryError:
|
|
81
|
+
raise
|
|
82
|
+
except Exception as exc: # noqa: BLE001
|
|
83
|
+
logger.warning(
|
|
84
|
+
"failed to dispatch batched notification",
|
|
85
|
+
context={"key": key, "group": target.group},
|
|
86
|
+
exc_info=exc,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _flush_sync(state: _BatchState) -> None:
|
|
91
|
+
if state.targets:
|
|
92
|
+
async_to_sync(_flush_notifications)(state)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@contextmanager
|
|
96
|
+
def bulk_data_change_notifications() -> Iterator[None]:
|
|
97
|
+
"""Collect and flush data-change notifications for a bulk operation."""
|
|
98
|
+
current = _active_batch.get()
|
|
99
|
+
if current is not None:
|
|
100
|
+
yield
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
state = _BatchState()
|
|
104
|
+
token = _active_batch.set(state)
|
|
105
|
+
try:
|
|
106
|
+
yield
|
|
107
|
+
except BaseException as body_error:
|
|
108
|
+
_close_batch(state)
|
|
109
|
+
_active_batch.reset(token)
|
|
110
|
+
try:
|
|
111
|
+
_flush_sync(state)
|
|
112
|
+
except BaseException as flush_error: # noqa: BLE001
|
|
113
|
+
raise BaseExceptionGroup(
|
|
114
|
+
_COMBINED_FAILURE_MESSAGE,
|
|
115
|
+
[body_error, flush_error],
|
|
116
|
+
) from None
|
|
117
|
+
raise
|
|
118
|
+
else:
|
|
119
|
+
_close_batch(state)
|
|
120
|
+
_active_batch.reset(token)
|
|
121
|
+
_flush_sync(state)
|