GeneralManager 0.86.5__tar.gz → 0.86.7__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.86.5/src/GeneralManager.egg-info → generalmanager-0.86.7}/PKG-INFO +1 -1
- {generalmanager-0.86.5 → generalmanager-0.86.7}/pyproject.toml +1 -1
- {generalmanager-0.86.5 → generalmanager-0.86.7/src/GeneralManager.egg-info}/PKG-INFO +1 -1
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/GeneralManager.egg-info/SOURCES.txt +1 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_mutations.py +59 -9
- generalmanager-0.86.7/src/general_manager/api/mutation_controls.py +5 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/remote_api.py +2 -4
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/remote_invalidation.py +210 -103
- {generalmanager-0.86.5 → generalmanager-0.86.7}/LICENSE +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/README.md +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/setup.cfg +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/GeneralManager.egg-info/dependency_links.txt +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/GeneralManager.egg-info/requires.txt +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/GeneralManager.egg-info/top_level.txt +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/_types/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/_types/api.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/_types/bucket.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/_types/cache.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/_types/dataframes.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/_types/factory.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/_types/general_manager.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/_types/interface.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/_types/manager.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/_types/measurement.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/_types/permission.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/_types/rule.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/_types/search.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/_types/utils.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_as_of.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_errors.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_groups.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_identifiers.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_ordering.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_output.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_prefetch.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_relations.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_resolvers.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_search.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_subscription_consumer.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_subscriptions.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_type.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_view.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_warmup.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_warmup_registry.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_warmup_tasks.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/mutation.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/notification_batching.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/property.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/registry.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/remote_invalidation_client.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/apps.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/as_of.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/bootstrap.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/bucket/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/bucket/_materialized_bucket.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/bucket/_ordering.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/bucket/base_bucket.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/bucket/calculation_bucket.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/bucket/database_bucket.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/bucket/excel_bucket.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/bucket/group_bucket.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/bucket/indexing.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/bucket/projection.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/bucket/request_bucket.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/_dependency_graph.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/batch_refresh.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/cache_decorator.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/cache_tracker.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/data_change_context.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/dependency_cache.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/dependency_index.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/dependency_matching.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/dependency_publish.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/dependency_shards.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/model_dependency_collector.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/run_context.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/run_context_lru.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/signals.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/cache/transaction_barrier.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/audit.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/bootstrap.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/checks.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/consumer.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/context.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/errors.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/__main__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/baseline.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/datasets/basic_queries.yaml +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/datasets/demo_readiness.yaml +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/datasets/edge_cases.yaml +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/datasets/follow_ups.yaml +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/datasets/large_schema.yaml +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/datasets/multi_hop.yaml +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/datasets/planned_orchestration.yaml +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/diagnostics.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/fingerprints.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/fixtures.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/judges/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/judges/answer_quality.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/judges/contract.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/judges/result_accuracy.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/judges/tool_sequence.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/readiness.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/runner.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/evals/traces.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/grounding.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/models.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/budget.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/calculations.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/catalog.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/config.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/events.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/evidence.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/models.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/planner.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/provider_calls.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/resolver.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/routing.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/scheduler.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/synthesis.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/planned/validation.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/providers/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/providers/_shared.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/providers/anthropic.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/providers/base.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/providers/google.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/providers/ollama.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/providers/openai.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/rate_limits.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/routing.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/schema_index.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/settings.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/signals.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/system_prompt.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/tool_metadata.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/tools.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/turns.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/chat/views.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/conf.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/dataframes/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/dataframes/measurements.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/factory/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/factory/auto_factory.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/factory/factories.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/factory/factory_methods.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/base_interface.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/bundles/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/bundles/calculation.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/bundles/database.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/bundles/excel.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/bundles/remote_manager.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/bundles/request.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/base.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/builtin.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/calculation/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/calculation/_compat.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/calculation/input_resolution.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/calculation/lifecycle.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/configuration.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/core/observability.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/core/utils.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/excel/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/exceptions.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/existing_model/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/existing_model/_compat.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/existing_model/resolution.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/factory.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/orm/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/orm/_compat.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/orm/bulk.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/orm/create_many.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/orm/history.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/orm/lifecycle.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/orm/mutations.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/orm/support.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/orm_utils/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/orm_utils/django_manager_utils.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/orm_utils/field_descriptors.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/orm_utils/payload_normalizer.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/orm_utils/update_state.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/read_only/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/read_only/_compat.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/read_only/_types.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/read_only/lifecycle.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/read_only/management.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/registry.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/remote_manager.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/capabilities/request/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/excel.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/excel_store.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/excel_workbook.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/infrastructure/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/infrastructure/startup_hooks.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/infrastructure/system_checks.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/interfaces/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/interfaces/calculation.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/interfaces/database.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/interfaces/excel.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/interfaces/existing_model.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/interfaces/read_only.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/interfaces/remote_manager.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/interfaces/request.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/manifests/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/manifests/capability_builder.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/manifests/capability_manifest.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/manifests/capability_models.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/orm_interface.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/requests.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/utils/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/utils/database_interface_protocols.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/utils/errors.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/utils/history.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/interface/utils/models.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/logging.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/management/commands/chat_cleanup.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/management/commands/cleanup_upload_intents.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/management/commands/graphql_warmup.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/management/commands/graphql_warmup_refresh_due.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/management/commands/search_index.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/management/commands/search_reconcile.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/management/commands/seed_manager_landscape.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/management/commands/shell.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/management/commands/sync_read_only.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/management/commands/workflow_drain_outbox.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/management/commands/workflow_replay_dead_letters.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/manager/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/manager/bulk_create.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/manager/general_manager.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/manager/group_manager.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/manager/input.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/manager/meta.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/measurement/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/measurement/measurement.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/measurement/measurement_field.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/metrics/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/metrics/graphql.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/migrations/0001_initial.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/migrations/0002_workflow_outbox_scaling_indexes.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/migrations/0003_workflow_execution_correlation_constraint.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/migrations/0004_chat_persistence_models.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/migrations/0004_search_index_state.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/migrations/0005_merge_chat_search.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/migrations/0006_chat_pending_confirmation_scoped_ids.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/migrations/0007_upload_intent.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/migrations/0008_upload_finalization_versions.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/migrations/0009_upload_cleanup_state.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/migrations/0010_chat_pending_confirmation_portable_uniqueness.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/migrations/0011_search_index_state_dirty_generation.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/migrations/0012_chat_context_watermarks.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/migrations/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/models.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/permission/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/permission/audit.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/permission/base_permission.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/permission/graphql_capabilities.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/permission/manager_based_permission.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/permission/mutation_permission.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/permission/permission_checks.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/permission/permission_data_manager.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/permission/utils.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/public_api_registry.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/py.typed +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/rule/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/rule/handler.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/rule/rule.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/async_tasks.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/backend.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/backend_registry.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/backends/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/backends/dev.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/backends/meilisearch.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/backends/opensearch.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/backends/typesense.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/checks.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/config.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/identity.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/indexer.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/invalidation.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/m2m_invalidation.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/models.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/reconciliation.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/registry.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/tasks.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/search/utils.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/seeding/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/seeding/manager_landscape.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/adapters.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/checks.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/config.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/errors.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/finalization.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/graphql.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/graphql_types.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/metrics.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/models.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/public.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/s3.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/services.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/tokens.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/types.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/urls.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/uploads/views.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/utils/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/utils/_args_to_kwargs.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/utils/_cache_key_encoder.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/utils/_make_cache_key.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/utils/_none_to_zero.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/utils/filter_parser.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/utils/format_string.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/utils/json_encoder.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/utils/path_mapping.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/utils/public_api.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/utils/testing.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/utils/type_checks.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/actions.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/backend_registry.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/backends/__init__.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/backends/celery.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/backends/local.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/backends/n8n.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/config.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/engine.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/event_registry.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/events.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/models.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/signal_bridge.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/tasks.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/workflow/telemetry.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/tests/test_settings.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/tests/test_urls.py +0 -0
- {generalmanager-0.86.5 → generalmanager-0.86.7}/tests/testing_asgi.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: GeneralManager
|
|
3
|
-
Version: 0.86.
|
|
3
|
+
Version: 0.86.7
|
|
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.86.
|
|
7
|
+
version = "0.86.7"
|
|
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.86.
|
|
3
|
+
Version: 0.86.7
|
|
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
|
|
@@ -50,6 +50,7 @@ src/general_manager/api/graphql_warmup.py
|
|
|
50
50
|
src/general_manager/api/graphql_warmup_registry.py
|
|
51
51
|
src/general_manager/api/graphql_warmup_tasks.py
|
|
52
52
|
src/general_manager/api/mutation.py
|
|
53
|
+
src/general_manager/api/mutation_controls.py
|
|
53
54
|
src/general_manager/api/notification_batching.py
|
|
54
55
|
src/general_manager/api/property.py
|
|
55
56
|
src/general_manager/api/registry.py
|
{generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/graphql_mutations.py
RENAMED
|
@@ -21,6 +21,7 @@ from django.db import models
|
|
|
21
21
|
from django.core.exceptions import FieldDoesNotExist
|
|
22
22
|
|
|
23
23
|
from general_manager.interface.base_interface import AttributeTypedDict, InterfaceBase
|
|
24
|
+
from general_manager.api.mutation_controls import MUTATION_CONTROL_KEYS
|
|
24
25
|
from general_manager.api.graphql_relations import (
|
|
25
26
|
get_graphql_manager_registry,
|
|
26
27
|
resolve_general_manager_type,
|
|
@@ -55,6 +56,33 @@ class _UnsetHistoryComment:
|
|
|
55
56
|
_HISTORY_COMMENT_UNSET = _UnsetHistoryComment()
|
|
56
57
|
|
|
57
58
|
|
|
59
|
+
class ReservedMutationControlFieldError(ValueError):
|
|
60
|
+
"""Raised when an editable field collides with manager execution metadata."""
|
|
61
|
+
|
|
62
|
+
def __init__(self, field_name: str) -> None:
|
|
63
|
+
super().__init__(
|
|
64
|
+
f"Editable GraphQL field {field_name!r} collides with a reserved "
|
|
65
|
+
"manager mutation control. Rename the field or make it read-only."
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class ReservedMutationControlInputError(GraphQLError):
|
|
70
|
+
"""Report client input attempting to override trusted authorization metadata."""
|
|
71
|
+
|
|
72
|
+
def __init__(self, key: str) -> None:
|
|
73
|
+
super().__init__(
|
|
74
|
+
f"Mutation input cannot include reserved key {key!r}.",
|
|
75
|
+
extensions={"code": "BAD_USER_INPUT"},
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _reject_mutation_authorization_controls(kwargs: MutationPayload) -> None:
|
|
80
|
+
"""Allow audit comments but reject client-controlled authorization metadata."""
|
|
81
|
+
for key in sorted(MUTATION_CONTROL_KEYS - {"history_comment"}):
|
|
82
|
+
if key in kwargs:
|
|
83
|
+
raise ReservedMutationControlInputError(key)
|
|
84
|
+
|
|
85
|
+
|
|
58
86
|
class _EditableGrapheneField(Protocol):
|
|
59
87
|
"""Graphene field instance carrying GeneralManager's dynamic editability flag."""
|
|
60
88
|
|
|
@@ -168,8 +196,10 @@ def _normalize_mutation_kwargs_for_manager(
|
|
|
168
196
|
ORM mutation capabilities expect ``field_id``/``field_id_list`` for manager
|
|
169
197
|
relations. Structured references pass through their manager constructor so
|
|
170
198
|
all declared inputs are validated. Scalar reference values stay unchanged.
|
|
171
|
-
Unknown keys and already-normalized keys are preserved
|
|
199
|
+
Unknown keys and already-normalized keys are preserved, except reserved
|
|
200
|
+
authorization controls, which are rejected before and after normalization.
|
|
172
201
|
"""
|
|
202
|
+
_reject_mutation_authorization_controls(kwargs)
|
|
173
203
|
interface_cls = getattr(general_manager_class, "Interface", None)
|
|
174
204
|
if interface_cls is None:
|
|
175
205
|
return dict(kwargs)
|
|
@@ -233,6 +263,7 @@ def _normalize_mutation_kwargs_for_manager(
|
|
|
233
263
|
)
|
|
234
264
|
normalized.pop(key, None)
|
|
235
265
|
|
|
266
|
+
_reject_mutation_authorization_controls(normalized)
|
|
236
267
|
return normalized
|
|
237
268
|
|
|
238
269
|
|
|
@@ -351,6 +382,10 @@ def create_write_fields(
|
|
|
351
382
|
Mapping from attribute name to a Graphene input field instance. Returned
|
|
352
383
|
fields carry a dynamic maintainer-facing ``editable`` flag used by the
|
|
353
384
|
generated mutation class builders.
|
|
385
|
+
|
|
386
|
+
Raises:
|
|
387
|
+
ReservedMutationControlFieldError: An editable field or its normalized
|
|
388
|
+
relation alias collides with reserved manager mutation metadata.
|
|
354
389
|
"""
|
|
355
390
|
fields: GrapheneFieldMap = {}
|
|
356
391
|
attribute_types = interface_cls.get_attribute_types()
|
|
@@ -360,6 +395,8 @@ def create_write_fields(
|
|
|
360
395
|
continue
|
|
361
396
|
if info["is_derived"]:
|
|
362
397
|
continue
|
|
398
|
+
if info["is_editable"] and name in MUTATION_CONTROL_KEYS:
|
|
399
|
+
raise ReservedMutationControlFieldError(name)
|
|
363
400
|
if _is_direct_relation_raw_id_alias(
|
|
364
401
|
name,
|
|
365
402
|
attribute_types,
|
|
@@ -375,6 +412,13 @@ def create_write_fields(
|
|
|
375
412
|
if default is NOT_PROVIDED or callable(default):
|
|
376
413
|
default = Undefined
|
|
377
414
|
manager_type = resolve_general_manager_type(typ, manager_registry)
|
|
415
|
+
if (
|
|
416
|
+
info["is_editable"]
|
|
417
|
+
and manager_type is not None
|
|
418
|
+
and not name.endswith(("_id", "_list"))
|
|
419
|
+
and f"{name}_id" in MUTATION_CONTROL_KEYS
|
|
420
|
+
):
|
|
421
|
+
raise ReservedMutationControlFieldError(name)
|
|
378
422
|
|
|
379
423
|
fld: object
|
|
380
424
|
if info.get("orm_field_kind") in {"file", "image"}:
|
|
@@ -477,6 +521,7 @@ def generate_create_mutation_class(
|
|
|
477
521
|
for field_name, value in kwargs.items()
|
|
478
522
|
if value is not NOT_PROVIDED
|
|
479
523
|
}
|
|
524
|
+
_reject_mutation_authorization_controls(kwargs)
|
|
480
525
|
_preflight_mutation_uploads(
|
|
481
526
|
info=info,
|
|
482
527
|
general_manager_class=generalManagerClass,
|
|
@@ -488,12 +533,14 @@ def generate_create_mutation_class(
|
|
|
488
533
|
kwargs = _normalize_mutation_kwargs_for_manager(generalManagerClass, kwargs)
|
|
489
534
|
history_comment = _pop_history_comment(kwargs)
|
|
490
535
|
create = cast(_ManagerCreateMethod, generalManagerClass.create)
|
|
491
|
-
create_kwargs = {"creator_id": info.context.user.id, **kwargs}
|
|
492
536
|
if isinstance(history_comment, _UnsetHistoryComment):
|
|
493
|
-
instance = create(**
|
|
537
|
+
instance = create(creator_id=info.context.user.id, **kwargs)
|
|
494
538
|
else:
|
|
495
|
-
|
|
496
|
-
|
|
539
|
+
instance = create(
|
|
540
|
+
creator_id=info.context.user.id,
|
|
541
|
+
history_comment=history_comment,
|
|
542
|
+
**kwargs,
|
|
543
|
+
)
|
|
497
544
|
except GraphQLError:
|
|
498
545
|
raise
|
|
499
546
|
except Exception as error:
|
|
@@ -587,6 +634,7 @@ def generate_update_mutation_class(
|
|
|
587
634
|
for field_name, value in kwargs.items()
|
|
588
635
|
if value is not NOT_PROVIDED
|
|
589
636
|
}
|
|
637
|
+
_reject_mutation_authorization_controls(kwargs)
|
|
590
638
|
_preflight_mutation_uploads(
|
|
591
639
|
info=info,
|
|
592
640
|
general_manager_class=generalManagerClass,
|
|
@@ -600,12 +648,14 @@ def generate_update_mutation_class(
|
|
|
600
648
|
update = cast(
|
|
601
649
|
_ManagerUpdateMethod, generalManagerClass(**identification).update
|
|
602
650
|
)
|
|
603
|
-
update_kwargs = {"creator_id": info.context.user.id, **kwargs}
|
|
604
651
|
if isinstance(history_comment, _UnsetHistoryComment):
|
|
605
|
-
instance = update(**
|
|
652
|
+
instance = update(creator_id=info.context.user.id, **kwargs)
|
|
606
653
|
else:
|
|
607
|
-
|
|
608
|
-
|
|
654
|
+
instance = update(
|
|
655
|
+
creator_id=info.context.user.id,
|
|
656
|
+
history_comment=history_comment,
|
|
657
|
+
**kwargs,
|
|
658
|
+
)
|
|
609
659
|
except GraphQLError:
|
|
610
660
|
raise
|
|
611
661
|
except Exception as error:
|
|
@@ -16,6 +16,7 @@ from django.http import HttpRequest, HttpResponse, JsonResponse
|
|
|
16
16
|
from django.urls import URLPattern, path
|
|
17
17
|
|
|
18
18
|
from general_manager.logging import get_logger
|
|
19
|
+
from general_manager.api.mutation_controls import MUTATION_CONTROL_KEYS
|
|
19
20
|
from general_manager.bucket._ordering import normalize_ordering
|
|
20
21
|
from general_manager.bucket.request_bucket import RequestBucket
|
|
21
22
|
|
|
@@ -25,9 +26,6 @@ if TYPE_CHECKING:
|
|
|
25
26
|
logger = get_logger("api.remote")
|
|
26
27
|
|
|
27
28
|
_SEGMENT_RE = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
|
|
28
|
-
_MUTATION_CONTROL_KEYS = frozenset(
|
|
29
|
-
{"ignore_permission", "creator_id", "history_comment"}
|
|
30
|
-
)
|
|
31
29
|
_MUTATION_METADATA_KEY = "__metadata__"
|
|
32
30
|
_MUTATION_METADATA_FIELDS = frozenset({"history_comment"})
|
|
33
31
|
RemoteAPIView = Callable[[HttpRequest], HttpResponse]
|
|
@@ -432,7 +430,7 @@ def _parse_json_body(request: HttpRequest) -> RemoteJSONPayload:
|
|
|
432
430
|
def _parse_mutation_body(request: HttpRequest) -> tuple[RemoteJSONPayload, str | None]:
|
|
433
431
|
"""Separate schema values from supported remote mutation metadata."""
|
|
434
432
|
payload = _parse_json_body(request)
|
|
435
|
-
for control_key in
|
|
433
|
+
for control_key in MUTATION_CONTROL_KEYS:
|
|
436
434
|
if control_key in payload:
|
|
437
435
|
raise RemoteAPIRequestError.reserved_mutation_control(control_key)
|
|
438
436
|
|
{generalmanager-0.86.5 → generalmanager-0.86.7}/src/general_manager/api/remote_invalidation.py
RENAMED
|
@@ -5,18 +5,21 @@ from __future__ import annotations
|
|
|
5
5
|
import asyncio
|
|
6
6
|
from copy import deepcopy
|
|
7
7
|
from datetime import date, datetime
|
|
8
|
+
from dataclasses import replace
|
|
8
9
|
import json
|
|
9
10
|
from importlib import import_module
|
|
10
11
|
import re
|
|
11
12
|
from time import perf_counter
|
|
12
13
|
from uuid import UUID, uuid4
|
|
13
14
|
from collections.abc import Callable, Coroutine, Mapping, MutableSequence, Sequence
|
|
14
|
-
from types import ModuleType
|
|
15
|
+
from types import ModuleType, SimpleNamespace
|
|
15
16
|
from typing import TYPE_CHECKING, Protocol, TypeAlias, cast
|
|
16
17
|
from urllib.parse import parse_qs
|
|
17
18
|
|
|
18
19
|
from asgiref.sync import async_to_sync
|
|
20
|
+
from django.contrib.auth import get_user_model
|
|
19
21
|
from django.conf import settings
|
|
22
|
+
from django.core.handlers.asgi import ASGIHandler
|
|
20
23
|
from django.db import DEFAULT_DB_ALIAS, transaction
|
|
21
24
|
from django.urls import re_path
|
|
22
25
|
|
|
@@ -241,6 +244,85 @@ def emit_remote_invalidation(
|
|
|
241
244
|
post_data_change.connect(emit_remote_invalidation, weak=False)
|
|
242
245
|
|
|
243
246
|
|
|
247
|
+
def _fresh_remote_principal(principal: object) -> object | None:
|
|
248
|
+
"""Reload the authenticated account, without reusing cached permission state."""
|
|
249
|
+
if not getattr(principal, "is_authenticated", False):
|
|
250
|
+
return None
|
|
251
|
+
identity = getattr(principal, "pk", None)
|
|
252
|
+
if identity is None:
|
|
253
|
+
return None
|
|
254
|
+
user = get_user_model()._default_manager.filter(pk=identity).first()
|
|
255
|
+
if user is None or not getattr(user, "is_active", True):
|
|
256
|
+
return None
|
|
257
|
+
return user
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _can_receive_remote_event(
|
|
261
|
+
config: RemoteAPIConfig, principal: object, event: Mapping[str, object]
|
|
262
|
+
) -> bool:
|
|
263
|
+
"""Apply the native current-instance read gate; never disclose deleted IDs."""
|
|
264
|
+
if (
|
|
265
|
+
event.get("action") not in {"create", "update"}
|
|
266
|
+
or event.get("protocol_version") != config.protocol_version
|
|
267
|
+
or event.get("base_path") != config.base_path
|
|
268
|
+
or event.get("resource_name") != config.resource_name
|
|
269
|
+
):
|
|
270
|
+
return False
|
|
271
|
+
identification = event.get("identification")
|
|
272
|
+
if not isinstance(identification, Mapping) or not identification:
|
|
273
|
+
return False
|
|
274
|
+
from general_manager.interface.interfaces.request import RequestInterface
|
|
275
|
+
from general_manager.api.graphql_errors import get_read_permission_filter
|
|
276
|
+
from general_manager.api.graphql_resolvers import (
|
|
277
|
+
prepare_read_authorization,
|
|
278
|
+
finalize_read_authorization,
|
|
279
|
+
)
|
|
280
|
+
from general_manager.bucket._materialized_bucket import MaterializedBucket
|
|
281
|
+
from general_manager.bucket.base_bucket import Bucket
|
|
282
|
+
|
|
283
|
+
try:
|
|
284
|
+
user = _fresh_remote_principal(principal)
|
|
285
|
+
if user is None:
|
|
286
|
+
return False
|
|
287
|
+
instance = config.manager_cls(**dict(identification))
|
|
288
|
+
if isinstance(instance._interface, RequestInterface):
|
|
289
|
+
# Request constructors are lazy. Even a user-only read rule must
|
|
290
|
+
# not authorize an ID whose current detail cannot be loaded.
|
|
291
|
+
instance._interface.set_request_payload_cache(None)
|
|
292
|
+
instance._interface.get_data()
|
|
293
|
+
info = SimpleNamespace(context=SimpleNamespace(user=user))
|
|
294
|
+
plan = get_read_permission_filter(config.manager_cls, info)
|
|
295
|
+
source: Bucket[GeneralManager] = MaterializedBucket(
|
|
296
|
+
config.manager_cls, [instance]
|
|
297
|
+
)
|
|
298
|
+
if plan.decision == "conditional" and any(
|
|
299
|
+
constraint.get("filter") or constraint.get("exclude")
|
|
300
|
+
for constraint in plan.filters
|
|
301
|
+
):
|
|
302
|
+
# Keep native ORM filter/exclude semantics for this one candidate.
|
|
303
|
+
source = config.manager_cls.all().with_instances([instance])
|
|
304
|
+
preparation = prepare_read_authorization(
|
|
305
|
+
source,
|
|
306
|
+
config.manager_cls,
|
|
307
|
+
info,
|
|
308
|
+
permission_plan=replace(plan, requires_instance_check=False),
|
|
309
|
+
)
|
|
310
|
+
authorized = finalize_read_authorization(
|
|
311
|
+
preparation, config.manager_cls, info, source="remote_invalidation"
|
|
312
|
+
).queryset
|
|
313
|
+
# Run the native instance gate unconditionally, even for allow-all plans.
|
|
314
|
+
return any(
|
|
315
|
+
item.Permission(item, user).can_read_instance() for item in authorized
|
|
316
|
+
)
|
|
317
|
+
except MemoryError:
|
|
318
|
+
raise
|
|
319
|
+
except Exception: # noqa: BLE001
|
|
320
|
+
# Missing instances and failed authorization lookups have the same
|
|
321
|
+
# externally silent result. Never send a fallback action or identity.
|
|
322
|
+
logger.warning("remote invalidation authorization failed")
|
|
323
|
+
return False
|
|
324
|
+
|
|
325
|
+
|
|
244
326
|
class RemoteInvalidationConsumer:
|
|
245
327
|
"""Minimal ASGI websocket consumer for remote invalidation events."""
|
|
246
328
|
|
|
@@ -269,28 +351,15 @@ class RemoteInvalidationConsumer:
|
|
|
269
351
|
receive: AsgiReceive,
|
|
270
352
|
send: AsgiSend,
|
|
271
353
|
) -> None:
|
|
354
|
+
"""Forward identified changes only under fresh native instance read rights.
|
|
355
|
+
|
|
356
|
+
Admission requires an active authenticated Django account (4401).
|
|
357
|
+
Unknown resources close with 4404, incompatible versions with 4406.
|
|
358
|
+
Deleted, unavailable and identity-free batch events are suppressed.
|
|
359
|
+
Existing client data is not evicted when access is lost.
|
|
272
360
|
"""
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
The consumer closes with 4404 for unknown or disabled resources, 4406
|
|
276
|
-
for protocol-version mismatches, and 1011 when no channel layer is
|
|
277
|
-
available. It accepts matching connections, subscribes to the resource
|
|
278
|
-
group, sends JSON invalidation payloads, and discards the channel in a
|
|
279
|
-
`finally` block. The generated route supplies `scope["url_route"]
|
|
280
|
-
["kwargs"]["base_path"]` and `["resource_name"]`; the optional
|
|
281
|
-
`version` query string parameter is compared with the resource protocol
|
|
282
|
-
version. A missing version is accepted, multiple values use the first
|
|
283
|
-
parsed value, and invalid UTF-8 query strings propagate decoding errors.
|
|
284
|
-
Non-disconnect inbound websocket messages are ignored while the consumer
|
|
285
|
-
keeps listening.
|
|
286
|
-
|
|
287
|
-
Outbound JSON messages include `protocol_version`, `base_path`,
|
|
288
|
-
`resource_name`, `action`, `identification`, and `event_id` in a
|
|
289
|
-
`websocket.send` frame with a JSON `text` payload. Extra channel-layer
|
|
290
|
-
event fields are ignored; missing required event fields raise `KeyError`.
|
|
291
|
-
Client disconnect exits the loop and cleans up without raising. Missing
|
|
292
|
-
route kwargs, channel-layer, receive, send, and JSON errors propagate.
|
|
293
|
-
"""
|
|
361
|
+
from channels.db import database_sync_to_async
|
|
362
|
+
|
|
294
363
|
from general_manager.manager.meta import GeneralManagerMeta
|
|
295
364
|
|
|
296
365
|
url_route = scope.get("url_route", {})
|
|
@@ -314,6 +383,12 @@ class RemoteInvalidationConsumer:
|
|
|
314
383
|
if protocol_version and protocol_version != config.protocol_version:
|
|
315
384
|
await send({"type": "websocket.close", "code": 4406})
|
|
316
385
|
return
|
|
386
|
+
principal = await database_sync_to_async(_fresh_remote_principal)(
|
|
387
|
+
scope.get("user")
|
|
388
|
+
)
|
|
389
|
+
if principal is None:
|
|
390
|
+
await send({"type": "websocket.close", "code": 4401})
|
|
391
|
+
return
|
|
317
392
|
channel_layer = _get_channel_layer_safe()
|
|
318
393
|
if channel_layer is None:
|
|
319
394
|
await send({"type": "websocket.close", "code": 1011})
|
|
@@ -321,13 +396,13 @@ class RemoteInvalidationConsumer:
|
|
|
321
396
|
self._channel_layer = channel_layer
|
|
322
397
|
self._group_name = remote_invalidation_group_name(config)
|
|
323
398
|
self._channel_name = await channel_layer.new_channel()
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
receive_task: asyncio.Task[AsgiMessage] = asyncio.create_task(receive())
|
|
327
|
-
event_task: asyncio.Task[object] = asyncio.create_task(
|
|
328
|
-
channel_layer.receive(self._channel_name)
|
|
329
|
-
)
|
|
399
|
+
receive_task: asyncio.Task[AsgiMessage] | None = None
|
|
400
|
+
event_task: asyncio.Task[object] | None = None
|
|
330
401
|
try:
|
|
402
|
+
await channel_layer.group_add(self._group_name, self._channel_name)
|
|
403
|
+
await send({"type": "websocket.accept"})
|
|
404
|
+
receive_task = asyncio.create_task(receive())
|
|
405
|
+
event_task = asyncio.create_task(channel_layer.receive(self._channel_name))
|
|
331
406
|
while True:
|
|
332
407
|
done, _ = await asyncio.wait(
|
|
333
408
|
{receive_task, event_task},
|
|
@@ -347,6 +422,9 @@ class RemoteInvalidationConsumer:
|
|
|
347
422
|
not disconnect
|
|
348
423
|
and isinstance(event, Mapping)
|
|
349
424
|
and event.get("type") == "gm.remote.invalidation"
|
|
425
|
+
and await database_sync_to_async(_can_receive_remote_event)(
|
|
426
|
+
config, principal, event
|
|
427
|
+
)
|
|
350
428
|
):
|
|
351
429
|
await send(
|
|
352
430
|
{
|
|
@@ -372,9 +450,12 @@ class RemoteInvalidationConsumer:
|
|
|
372
450
|
await asyncio.gather(event_task, return_exceptions=True)
|
|
373
451
|
break
|
|
374
452
|
finally:
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
453
|
+
pending_tasks = [
|
|
454
|
+
task for task in (receive_task, event_task) if task is not None
|
|
455
|
+
]
|
|
456
|
+
for task in pending_tasks:
|
|
457
|
+
task.cancel()
|
|
458
|
+
await asyncio.gather(*pending_tasks, return_exceptions=True)
|
|
378
459
|
if self._group_name is not None and self._channel_name is not None:
|
|
379
460
|
await channel_layer.group_discard(self._group_name, self._channel_name)
|
|
380
461
|
|
|
@@ -396,11 +477,19 @@ def ensure_remote_invalidation_route(
|
|
|
396
477
|
trailing slash. The helper is idempotent for the same `(base_path,
|
|
397
478
|
resource_name)` pair using the resolved config values without additional
|
|
398
479
|
normalization; changed config for an existing key is preserved unchanged.
|
|
399
|
-
|
|
480
|
+
Existing middleware wrappers and the live URLRouter are preserved; opaque
|
|
481
|
+
websocket applications are left unchanged with a warning. Routes append
|
|
482
|
+
after existing websocket patterns. Missing or non-mutable
|
|
400
483
|
`websocket_urlpatterns` are replaced with a new list. The helper supports
|
|
401
484
|
ASGI modules that expose either a Channels `application_mapping` router or a
|
|
402
|
-
|
|
485
|
+
unwrapped Django ASGIHandler attribute named by `ASGI_APPLICATION`. Unknown
|
|
486
|
+
root callables (including closures and custom HTTP wrappers) are left unchanged
|
|
487
|
+
because treating them as HTTP-only could bypass their websocket security.
|
|
403
488
|
"""
|
|
489
|
+
registry = build_remote_api_registry(manager_classes)
|
|
490
|
+
enabled = [config for config in registry.values() if config.websocket_invalidation]
|
|
491
|
+
if not enabled:
|
|
492
|
+
return
|
|
404
493
|
asgi_path = getattr(settings, "ASGI_APPLICATION", None)
|
|
405
494
|
if not asgi_path:
|
|
406
495
|
return
|
|
@@ -419,26 +508,34 @@ def ensure_remote_invalidation_route(
|
|
|
419
508
|
if "populate() isn't reentrant" in str(exc):
|
|
420
509
|
return
|
|
421
510
|
raise
|
|
422
|
-
websocket_patterns = _websocket_patterns(asgi_module)
|
|
423
|
-
if websocket_patterns is None:
|
|
424
|
-
websocket_patterns = []
|
|
425
|
-
cast(_AsgiModuleRoutes, asgi_module).websocket_urlpatterns = websocket_patterns
|
|
426
511
|
application = getattr(asgi_module, attr_name, None)
|
|
427
512
|
if application is None:
|
|
428
513
|
return
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
514
|
+
mapped_application = _protocol_application(application)
|
|
515
|
+
websocket_app = (
|
|
516
|
+
mapped_application.application_mapping.get("websocket")
|
|
517
|
+
if mapped_application is not None
|
|
518
|
+
else None
|
|
519
|
+
)
|
|
520
|
+
router = _websocket_router(websocket_app)
|
|
521
|
+
if (websocket_app is not None and router is None) or (
|
|
522
|
+
mapped_application is None and type(application) is not ASGIHandler
|
|
523
|
+
):
|
|
524
|
+
logger.warning("remote invalidation routes require an accessible URLRouter")
|
|
525
|
+
return
|
|
526
|
+
websocket_patterns = _websocket_patterns(asgi_module)
|
|
527
|
+
patterns = list(websocket_patterns or [])
|
|
528
|
+
if router is not None:
|
|
529
|
+
for route in router.routes:
|
|
530
|
+
if route not in patterns:
|
|
531
|
+
patterns.append(route)
|
|
532
|
+
for config in enabled:
|
|
436
533
|
normalized = f"{config.base_path.strip('/')}/ws/{config.resource_name}"
|
|
437
534
|
pattern = rf"^{re.escape(normalized)}/?$"
|
|
438
535
|
route_exists = any(
|
|
439
536
|
getattr(route, "_general_manager_remote_ws_key", None)
|
|
440
537
|
== (config.base_path, config.resource_name)
|
|
441
|
-
for route in
|
|
538
|
+
for route in patterns
|
|
442
539
|
)
|
|
443
540
|
if route_exists:
|
|
444
541
|
continue
|
|
@@ -459,11 +556,16 @@ def ensure_remote_invalidation_route(
|
|
|
459
556
|
config.base_path,
|
|
460
557
|
config.resource_name,
|
|
461
558
|
)
|
|
462
|
-
|
|
463
|
-
if
|
|
464
|
-
|
|
559
|
+
patterns.append(websocket_route)
|
|
560
|
+
if websocket_patterns is None:
|
|
561
|
+
cast(_AsgiModuleRoutes, asgi_module).websocket_urlpatterns = patterns
|
|
562
|
+
else:
|
|
563
|
+
websocket_patterns[:] = patterns
|
|
564
|
+
if router is not None:
|
|
565
|
+
router.routes = list(patterns)
|
|
566
|
+
elif mapped_application is not None:
|
|
465
567
|
mapped_application.application_mapping["websocket"] = AuthMiddlewareStack(
|
|
466
|
-
URLRouter(list(
|
|
568
|
+
URLRouter(list(patterns))
|
|
467
569
|
)
|
|
468
570
|
else:
|
|
469
571
|
setattr(
|
|
@@ -472,9 +574,7 @@ def ensure_remote_invalidation_route(
|
|
|
472
574
|
ProtocolTypeRouter(
|
|
473
575
|
{
|
|
474
576
|
"http": application,
|
|
475
|
-
"websocket": AuthMiddlewareStack(
|
|
476
|
-
URLRouter(list(websocket_patterns))
|
|
477
|
-
),
|
|
577
|
+
"websocket": AuthMiddlewareStack(URLRouter(list(patterns))),
|
|
478
578
|
}
|
|
479
579
|
),
|
|
480
580
|
)
|
|
@@ -491,8 +591,8 @@ def clear_remote_invalidation_routes() -> None:
|
|
|
491
591
|
routes in the configured ASGI module are removed regardless of path or
|
|
492
592
|
resource shape while preserving the order of remaining routes. Returns
|
|
493
593
|
without changes when ASGI settings, Channels, the ASGI module, or mutable
|
|
494
|
-
sequence websocket patterns are unavailable.
|
|
495
|
-
|
|
594
|
+
sequence websocket patterns are unavailable. The live URLRouter is updated in place through existing middleware wrappers.
|
|
595
|
+
A plain HTTP application is never wrapped merely to clear routes.
|
|
496
596
|
"""
|
|
497
597
|
asgi_path = getattr(settings, "ASGI_APPLICATION", None)
|
|
498
598
|
if not asgi_path:
|
|
@@ -501,48 +601,31 @@ def clear_remote_invalidation_routes() -> None:
|
|
|
501
601
|
module_path, attr_name = asgi_path.rsplit(".", 1)
|
|
502
602
|
except ValueError:
|
|
503
603
|
return
|
|
504
|
-
try:
|
|
505
|
-
from channels.auth import AuthMiddlewareStack
|
|
506
|
-
from channels.routing import ProtocolTypeRouter, URLRouter
|
|
507
|
-
except ImportError: # pragma: no cover - optional dependency
|
|
508
|
-
return
|
|
509
604
|
try:
|
|
510
605
|
asgi_module = import_module(module_path)
|
|
511
606
|
except ImportError:
|
|
512
607
|
return
|
|
513
608
|
websocket_patterns = _websocket_patterns(asgi_module)
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
609
|
+
application = getattr(asgi_module, attr_name, None)
|
|
610
|
+
mapped_application = _protocol_application(application)
|
|
611
|
+
router = _websocket_router(
|
|
612
|
+
mapped_application.application_mapping.get("websocket")
|
|
613
|
+
if mapped_application is not None
|
|
614
|
+
else None
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
def retained(route: object) -> bool:
|
|
618
|
+
return not (
|
|
520
619
|
getattr(route, "_general_manager_remote_ws", False)
|
|
521
620
|
or getattr(route, "_general_manager_remote_ws_key", None) is not None
|
|
522
621
|
)
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
if
|
|
529
|
-
|
|
530
|
-
mapped_application.application_mapping["websocket"] = AuthMiddlewareStack(
|
|
531
|
-
URLRouter(list(filtered_patterns))
|
|
532
|
-
)
|
|
533
|
-
else:
|
|
534
|
-
setattr(
|
|
535
|
-
asgi_module,
|
|
536
|
-
attr_name,
|
|
537
|
-
ProtocolTypeRouter(
|
|
538
|
-
{
|
|
539
|
-
"http": application,
|
|
540
|
-
"websocket": AuthMiddlewareStack(
|
|
541
|
-
URLRouter(list(filtered_patterns))
|
|
542
|
-
),
|
|
543
|
-
}
|
|
544
|
-
),
|
|
545
|
-
)
|
|
622
|
+
|
|
623
|
+
if websocket_patterns is not None:
|
|
624
|
+
websocket_patterns[:] = [
|
|
625
|
+
route for route in websocket_patterns if retained(route)
|
|
626
|
+
]
|
|
627
|
+
if router is not None:
|
|
628
|
+
router.routes = [route for route in router.routes if retained(route)]
|
|
546
629
|
|
|
547
630
|
|
|
548
631
|
def _websocket_patterns(
|
|
@@ -557,19 +640,43 @@ def _websocket_patterns(
|
|
|
557
640
|
return value
|
|
558
641
|
|
|
559
642
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
)
|
|
567
|
-
|
|
643
|
+
class _RouteCollection(Protocol):
|
|
644
|
+
routes: Sequence[object]
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
def _protocol_application(application: object) -> _ApplicationMapping | None:
|
|
648
|
+
"""Find the protocol router without discarding wrappers around it."""
|
|
649
|
+
seen: set[int] = set()
|
|
650
|
+
while application is not None and id(application) not in seen:
|
|
651
|
+
seen.add(id(application))
|
|
652
|
+
if isinstance(getattr(application, "application_mapping", None), dict):
|
|
653
|
+
return cast(_ApplicationMapping, application)
|
|
654
|
+
application = _middleware_inner(application)
|
|
655
|
+
return None
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
def _websocket_router(application: object) -> _RouteCollection | None:
|
|
659
|
+
"""Locate an actual URLRouter through the existing middleware chain."""
|
|
660
|
+
try:
|
|
661
|
+
from channels.routing import URLRouter
|
|
662
|
+
except ImportError: # pragma: no cover - optional dependency
|
|
663
|
+
return None
|
|
664
|
+
|
|
568
665
|
seen: set[int] = set()
|
|
569
|
-
while
|
|
570
|
-
seen.add(id(
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
666
|
+
while application is not None and id(application) not in seen:
|
|
667
|
+
seen.add(id(application))
|
|
668
|
+
if isinstance(application, URLRouter):
|
|
669
|
+
return cast(_RouteCollection, application)
|
|
670
|
+
application = _middleware_inner(application)
|
|
671
|
+
return None
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
def _middleware_inner(application: object) -> object:
|
|
675
|
+
"""Traverse standard inner links and Channels' known origin wrapper."""
|
|
676
|
+
try:
|
|
677
|
+
from channels.security.websocket import OriginValidator
|
|
678
|
+
except ImportError: # pragma: no cover - optional dependency
|
|
679
|
+
return getattr(application, "inner", None)
|
|
680
|
+
if isinstance(application, OriginValidator):
|
|
681
|
+
return application.application
|
|
682
|
+
return getattr(application, "inner", None)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{generalmanager-0.86.5 → generalmanager-0.86.7}/src/GeneralManager.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|