python-platform 0.1.1__py3-none-any.whl
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.
- python_platform/__init__.py +445 -0
- python_platform/application/__init__.py +6 -0
- python_platform/application/build_spec.py +37 -0
- python_platform/application/builder.py +260 -0
- python_platform/application/composition.py +219 -0
- python_platform/application/runtime.py +546 -0
- python_platform/application/runtime_hooks.py +327 -0
- python_platform/application/state.py +216 -0
- python_platform/application_services/__init__.py +54 -0
- python_platform/application_services/catalog.py +500 -0
- python_platform/application_services/contracts.py +188 -0
- python_platform/application_services/dispatcher.py +423 -0
- python_platform/application_services/errors.py +89 -0
- python_platform/application_services/execution.py +199 -0
- python_platform/application_services/interceptors.py +44 -0
- python_platform/application_services/invocation.py +427 -0
- python_platform/application_services/policies.py +46 -0
- python_platform/application_services/seeding.py +72 -0
- python_platform/application_services/signature.py +49 -0
- python_platform/application_services/validation.py +84 -0
- python_platform/auditing/__init__.py +12 -0
- python_platform/auditing/contracts.py +39 -0
- python_platform/auditing/control.py +48 -0
- python_platform/auditing/sqlalchemy/__init__.py +6 -0
- python_platform/auditing/sqlalchemy/migrations/0001_platform_auditing.py +45 -0
- python_platform/auditing/sqlalchemy/migrations/__init__.py +1 -0
- python_platform/auditing/sqlalchemy/models.py +31 -0
- python_platform/auditing/sqlalchemy/module.py +32 -0
- python_platform/auditing/sqlalchemy/store.py +63 -0
- python_platform/authorization/__init__.py +38 -0
- python_platform/authorization/catalog.py +63 -0
- python_platform/authorization/contracts.py +241 -0
- python_platform/authorization/definition_discovery.py +81 -0
- python_platform/authorization/definitions.py +43 -0
- python_platform/authorization/errors.py +40 -0
- python_platform/background_execution/__init__.py +15 -0
- python_platform/background_execution/application.py +41 -0
- python_platform/background_execution/child.py +123 -0
- python_platform/background_execution/contracts.py +39 -0
- python_platform/background_execution/lifecycle.py +22 -0
- python_platform/background_execution/local.py +127 -0
- python_platform/background_execution/locks.py +31 -0
- python_platform/background_execution/management.py +17 -0
- python_platform/background_execution/module.py +10 -0
- python_platform/background_execution/permissions.py +14 -0
- python_platform/background_execution/processes.py +331 -0
- python_platform/background_jobs/__init__.py +31 -0
- python_platform/background_jobs/catalog.py +287 -0
- python_platform/background_jobs/contracts.py +202 -0
- python_platform/background_jobs/errors.py +28 -0
- python_platform/background_jobs/execution.py +201 -0
- python_platform/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt +21 -0
- python_platform/background_jobs/pgqueuer/__init__.py +6 -0
- python_platform/background_jobs/pgqueuer/enqueue.py +82 -0
- python_platform/background_jobs/pgqueuer/migrations/0001_pgqueuer_1_3_2.py +63 -0
- python_platform/background_jobs/pgqueuer/migrations/__init__.py +1 -0
- python_platform/background_jobs/pgqueuer/module.py +102 -0
- python_platform/background_jobs/pgqueuer/options.py +70 -0
- python_platform/background_jobs/pgqueuer/runtime.py +479 -0
- python_platform/background_jobs/pgqueuer/sql/pgqueuer_1_3_2_install.sql +118 -0
- python_platform/background_jobs/pgqueuer/supervision.py +201 -0
- python_platform/background_workers/__init__.py +22 -0
- python_platform/background_workers/catalog.py +96 -0
- python_platform/background_workers/contracts.py +124 -0
- python_platform/background_workers/errors.py +20 -0
- python_platform/background_workers/execution.py +133 -0
- python_platform/background_workers/runtime.py +203 -0
- python_platform/caching/__init__.py +11 -0
- python_platform/caching/catalog.py +73 -0
- python_platform/caching/contracts.py +73 -0
- python_platform/caching/errors.py +20 -0
- python_platform/cli/__init__.py +87 -0
- python_platform/cli/errors.py +5 -0
- python_platform/cli/project.py +69 -0
- python_platform/cli/runtime.py +68 -0
- python_platform/configuration/__init__.py +21 -0
- python_platform/configuration/composition.py +65 -0
- python_platform/configuration/contracts.py +68 -0
- python_platform/configuration/dotenv_source.py +51 -0
- python_platform/configuration/environment_source.py +38 -0
- python_platform/configuration/immutability.py +124 -0
- python_platform/configuration/input_shape.py +58 -0
- python_platform/configuration/merge.py +103 -0
- python_platform/configuration/root.py +215 -0
- python_platform/configuration/sources.py +519 -0
- python_platform/configuration/values.py +427 -0
- python_platform/configuration/yaml_source.py +51 -0
- python_platform/developer_kit/__init__.py +1 -0
- python_platform/developer_kit/generation.py +182 -0
- python_platform/developer_kit/project_metadata.py +45 -0
- python_platform/developer_kit/source.py +53 -0
- python_platform/developer_kit/templates/module/cookiecutter.json +1 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/__init__.py.jinja +1 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/application/__init__.py.jinja +1 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/application/cache.py.jinja +10 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/application/events.py.jinja +24 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration.py.jinja +46 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/application/module.py.jinja +29 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/application/options.py.jinja +8 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/application/orders.py.jinja +81 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/application/tasks.py.jinja +86 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/__init__.py.jinja +1 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/module.py.jinja +10 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/orders.py.jinja +36 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/__init__.py.jinja +1 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/module.py.jinja +10 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/orders.py.jinja +57 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repository.py.jinja +12 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding.py.jinja +19 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/settings.py.jinja +16 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/__init__.py.jinja +1 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/definitions.py.jinja +24 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/module.py.jinja +7 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions.py.jinja +15 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/__init__.py.jinja +1 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/files.py.jinja +74 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/module.py.jinja +58 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/realtime.py.jinja +37 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/__init__.py.jinja +1 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/migrations/__init__.py.jinja +1 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models.py.jinja +26 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/module.py.jinja +32 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repository.py.jinja +49 -0
- python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/test_domain.py.jinja +18 -0
- python_platform/developer_kit/templates/project/cookiecutter.json +6 -0
- python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/.dockerignore +8 -0
- python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/.env.jinja +13 -0
- python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/.gitignore +6 -0
- python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/.python-version +1 -0
- python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/Dockerfile +22 -0
- python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/README.md +86 -0
- python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/app.yaml.jinja +16 -0
- python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/compose.dev.yaml.jinja +24 -0
- python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/compose.production.yaml.jinja +13 -0
- python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/pyproject.toml.jinja +28 -0
- python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/src/host/__init__.py.jinja +1 -0
- python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/src/host/main.py.jinja +52 -0
- python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/src/host/module.py.jinja +38 -0
- python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/tests/conftest.py.jinja +8 -0
- python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/tests/host/test_http.py.jinja +101 -0
- python_platform/developer_kit/wiring.py +85 -0
- python_platform/diagnostics/__init__.py +23 -0
- python_platform/diagnostics/journal.py +174 -0
- python_platform/diagnostics/model.py +83 -0
- python_platform/diagnostics/source.py +25 -0
- python_platform/distributed_lock/__init__.py +6 -0
- python_platform/distributed_lock/contracts.py +20 -0
- python_platform/distributed_lock/options.py +19 -0
- python_platform/domain/__init__.py +13 -0
- python_platform/domain/aggregates.py +64 -0
- python_platform/domain/entities.py +33 -0
- python_platform/domain/errors.py +12 -0
- python_platform/domain/value_objects.py +31 -0
- python_platform/errors/__init__.py +75 -0
- python_platform/errors/base.py +13 -0
- python_platform/errors/business.py +53 -0
- python_platform/errors/configuration.py +119 -0
- python_platform/errors/diagnostics.py +12 -0
- python_platform/errors/lifecycle.py +164 -0
- python_platform/errors/modularity.py +100 -0
- python_platform/errors/services.py +100 -0
- python_platform/events/__init__.py +25 -0
- python_platform/events/aggregate.py +39 -0
- python_platform/events/catalog.py +136 -0
- python_platform/events/contracts.py +39 -0
- python_platform/events/contribution.py +128 -0
- python_platform/events/discovery.py +105 -0
- python_platform/events/errors.py +48 -0
- python_platform/events/runtime.py +192 -0
- python_platform/fastapi/__init__.py +49 -0
- python_platform/fastapi/action.py +175 -0
- python_platform/fastapi/adapter.py +381 -0
- python_platform/fastapi/application_services.py +647 -0
- python_platform/fastapi/background.py +42 -0
- python_platform/fastapi/contracts.py +278 -0
- python_platform/fastapi/errors.py +32 -0
- python_platform/fastapi/filters.py +111 -0
- python_platform/fastapi/health.py +44 -0
- python_platform/fastapi/http.py +55 -0
- python_platform/fastapi/http_router.py +212 -0
- python_platform/fastapi/manual_action.py +105 -0
- python_platform/fastapi/middleware.py +93 -0
- python_platform/fastapi/parameters.py +97 -0
- python_platform/fastapi/realtime/__init__.py +13 -0
- python_platform/fastapi/realtime/authentication.py +181 -0
- python_platform/fastapi/realtime/connection.py +166 -0
- python_platform/fastapi/realtime/module.py +41 -0
- python_platform/fastapi/realtime/options.py +50 -0
- python_platform/fastapi/realtime/runtime.py +423 -0
- python_platform/fastapi/request_context.py +244 -0
- python_platform/fastapi/route_integrity.py +88 -0
- python_platform/fastapi/routing.py +410 -0
- python_platform/fastapi/server.py +181 -0
- python_platform/fastapi/settings.py +40 -0
- python_platform/fastapi/tracing.py +62 -0
- python_platform/fastapi/transfer.py +128 -0
- python_platform/fastapi/upload_limits.py +63 -0
- python_platform/fastapi/uploads.py +94 -0
- python_platform/hosted_services/__init__.py +29 -0
- python_platform/hosted_services/bridge.py +420 -0
- python_platform/hosted_services/catalog.py +259 -0
- python_platform/hosted_services/contracts.py +55 -0
- python_platform/hosted_services/errors.py +53 -0
- python_platform/hosted_services/options.py +17 -0
- python_platform/hosted_services/runtime.py +267 -0
- python_platform/hosted_services/state.py +40 -0
- python_platform/hosting/__init__.py +4 -0
- python_platform/hosting/instance.py +40 -0
- python_platform/identity/__init__.py +73 -0
- python_platform/identity/application.py +287 -0
- python_platform/identity/contracts.py +283 -0
- python_platform/identity/errors.py +28 -0
- python_platform/identity/http_api.py +90 -0
- python_platform/identity/module.py +46 -0
- python_platform/identity/passwords.py +34 -0
- python_platform/identity/permissions.py +14 -0
- python_platform/identity/services.py +108 -0
- python_platform/identity/sqlalchemy/__init__.py +5 -0
- python_platform/identity/sqlalchemy/migrations/0001_platform_identity.py +151 -0
- python_platform/identity/sqlalchemy/migrations/0002_physical_delete.py +42 -0
- python_platform/identity/sqlalchemy/migrations/__init__.py +1 -0
- python_platform/identity/sqlalchemy/models.py +85 -0
- python_platform/identity/sqlalchemy/module.py +62 -0
- python_platform/identity/sqlalchemy/stores.py +526 -0
- python_platform/identity/tokens.py +106 -0
- python_platform/invocation/__init__.py +3 -0
- python_platform/invocation/callables.py +171 -0
- python_platform/invocation/contracts.py +33 -0
- python_platform/invocation/entries.py +54 -0
- python_platform/invocation/function_runtime.py +48 -0
- python_platform/invocation/interception.py +184 -0
- python_platform/lifecycle/__init__.py +25 -0
- python_platform/lifecycle/composition.py +35 -0
- python_platform/lifecycle/context.py +31 -0
- python_platform/lifecycle/runtime.py +43 -0
- python_platform/lifecycle/state.py +20 -0
- python_platform/modularity/__init__.py +15 -0
- python_platform/modularity/contracts.py +90 -0
- python_platform/modularity/discovery.py +143 -0
- python_platform/modularity/graph.py +334 -0
- python_platform/modularity/registry.py +100 -0
- python_platform/modularity/selection.py +16 -0
- python_platform/notifications/__init__.py +19 -0
- python_platform/notifications/catalog.py +46 -0
- python_platform/notifications/contracts.py +107 -0
- python_platform/observability/__init__.py +5 -0
- python_platform/observability/context.py +43 -0
- python_platform/observability/export.py +73 -0
- python_platform/observability/formatting.py +89 -0
- python_platform/observability/logging.py +118 -0
- python_platform/observability/options.py +45 -0
- python_platform/observability/tracing.py +80 -0
- python_platform/options/__init__.py +19 -0
- python_platform/options/aliases.py +331 -0
- python_platform/options/contribution.py +62 -0
- python_platform/options/immutability.py +48 -0
- python_platform/options/input_keys.py +214 -0
- python_platform/options/issues.py +335 -0
- python_platform/options/models.py +114 -0
- python_platform/options/registry.py +280 -0
- python_platform/options/schema.py +488 -0
- python_platform/options/validation.py +120 -0
- python_platform/py.typed +0 -0
- python_platform/realtime/__init__.py +12 -0
- python_platform/realtime/contracts.py +28 -0
- python_platform/realtime/diagnostics.py +37 -0
- python_platform/realtime/messages.py +113 -0
- python_platform/redis/__init__.py +6 -0
- python_platform/redis/distributed_lock.py +212 -0
- python_platform/redis/lease_lock.py +36 -0
- python_platform/redis/module.py +50 -0
- python_platform/redis/notification_runtime.py +182 -0
- python_platform/redis/notifications.py +25 -0
- python_platform/redis/options.py +35 -0
- python_platform/redis/runtime.py +116 -0
- python_platform/services/__init__.py +39 -0
- python_platform/services/application_bindings.py +221 -0
- python_platform/services/arbitration.py +381 -0
- python_platform/services/binding.py +102 -0
- python_platform/services/contribution.py +438 -0
- python_platform/services/convention.py +292 -0
- python_platform/services/convention_contracts.py +94 -0
- python_platform/services/exposure.py +23 -0
- python_platform/services/framework_provider.py +178 -0
- python_platform/services/native_graph.py +172 -0
- python_platform/services/provider.py +204 -0
- python_platform/services/registration.py +101 -0
- python_platform/services/runtime.py +298 -0
- python_platform/settings/__init__.py +30 -0
- python_platform/settings/application.py +95 -0
- python_platform/settings/binding.py +33 -0
- python_platform/settings/catalog.py +121 -0
- python_platform/settings/changes.py +14 -0
- python_platform/settings/contracts.py +115 -0
- python_platform/settings/definition_discovery.py +71 -0
- python_platform/settings/definitions.py +41 -0
- python_platform/settings/errors.py +22 -0
- python_platform/settings/handlers.py +43 -0
- python_platform/settings/management.py +57 -0
- python_platform/settings/manager.py +89 -0
- python_platform/settings/module.py +7 -0
- python_platform/settings/notifications.py +39 -0
- python_platform/settings/permissions.py +14 -0
- python_platform/settings/provider.py +74 -0
- python_platform/settings/refresh.py +140 -0
- python_platform/settings/refresh_module.py +45 -0
- python_platform/settings/sqlalchemy/__init__.py +5 -0
- python_platform/settings/sqlalchemy/migrations/0001_platform_settings.py +31 -0
- python_platform/settings/sqlalchemy/migrations/0002_physical_delete.py +32 -0
- python_platform/settings/sqlalchemy/migrations/0003_version_tokens.py +39 -0
- python_platform/settings/sqlalchemy/migrations/__init__.py +1 -0
- python_platform/settings/sqlalchemy/models.py +36 -0
- python_platform/settings/sqlalchemy/module.py +36 -0
- python_platform/settings/sqlalchemy/store.py +86 -0
- python_platform/settings/store.py +16 -0
- python_platform/settings/values.py +39 -0
- python_platform/sqlalchemy/__init__.py +51 -0
- python_platform/sqlalchemy/alembic_runtime/__init__.py +1 -0
- python_platform/sqlalchemy/alembic_runtime/env.py +27 -0
- python_platform/sqlalchemy/alembic_runtime/script.py.mako +14 -0
- python_platform/sqlalchemy/auditing.py +48 -0
- python_platform/sqlalchemy/errors.py +85 -0
- python_platform/sqlalchemy/metadata.py +538 -0
- python_platform/sqlalchemy/migration.py +346 -0
- python_platform/sqlalchemy/module.py +48 -0
- python_platform/sqlalchemy/module_migration.py +148 -0
- python_platform/sqlalchemy/options.py +66 -0
- python_platform/sqlalchemy/repository.py +33 -0
- python_platform/sqlalchemy/runtime.py +85 -0
- python_platform/sqlalchemy/session_provider.py +45 -0
- python_platform/sqlalchemy/unit_of_work.py +97 -0
- python_platform/testing/__init__.py +5 -0
- python_platform/testing/runtime.py +112 -0
- python_platform/unit_of_work/__init__.py +14 -0
- python_platform/unit_of_work/contracts.py +223 -0
- python_platform/unit_of_work/errors.py +27 -0
- python_platform/unit_of_work/manager.py +210 -0
- python_platform/unit_of_work/options.py +92 -0
- python_platform-0.1.1.dist-info/METADATA +122 -0
- python_platform-0.1.1.dist-info/RECORD +344 -0
- python_platform-0.1.1.dist-info/WHEEL +4 -0
- python_platform-0.1.1.dist-info/entry_points.txt +9 -0
- python_platform-0.1.1.dist-info/licenses/LICENSE +7 -0
- python_platform-0.1.1.dist-info/licenses/src/python_platform/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt +21 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
"""Dishka container 构建、native graph 核对与 catalog 发布。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import get_origin
|
|
8
|
+
|
|
9
|
+
from dishka import (
|
|
10
|
+
DEFAULT_COMPONENT,
|
|
11
|
+
STRICT_VALIDATION,
|
|
12
|
+
AsyncContainer,
|
|
13
|
+
DependencyKey,
|
|
14
|
+
make_async_container,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
from ..application_services import ApplicationServiceCatalog
|
|
18
|
+
from ..application_services.catalog import (
|
|
19
|
+
_build_application_service_catalog,
|
|
20
|
+
)
|
|
21
|
+
from ..application_services.invocation import (
|
|
22
|
+
_InvocationRuntime,
|
|
23
|
+
)
|
|
24
|
+
from ..authorization import PermissionCatalog
|
|
25
|
+
from ..background_jobs import BackgroundJobCatalog
|
|
26
|
+
from ..background_workers import BackgroundWorkerCatalog
|
|
27
|
+
from ..background_workers.runtime import _BackgroundCoordinator
|
|
28
|
+
from ..caching import CacheCatalog
|
|
29
|
+
from ..configuration import ConfigurationRoot
|
|
30
|
+
from ..errors import CatalogNativeGraphMismatchError, ContainerValidationError
|
|
31
|
+
from ..events import LocalEventCatalog
|
|
32
|
+
from ..events.catalog import _build_event_catalog, _validate_event_scopes
|
|
33
|
+
from ..events.contribution import EventContributionRegistry
|
|
34
|
+
from ..events.discovery import _discover_event_handlers
|
|
35
|
+
from ..hosted_services import HostedServiceCatalog
|
|
36
|
+
from ..hosted_services.catalog import (
|
|
37
|
+
_validate_hosted_service_drafts,
|
|
38
|
+
_validate_hosted_service_entries,
|
|
39
|
+
)
|
|
40
|
+
from ..hosted_services.runtime import _HostedServiceCoordinator
|
|
41
|
+
from ..invocation.entries import _validate_callable_entries
|
|
42
|
+
from ..invocation.interception import _EntryInterceptionCatalog
|
|
43
|
+
from ..modularity import ModuleKey, ModuleRegistry
|
|
44
|
+
from ..modularity.discovery import _DiscoveredType
|
|
45
|
+
from ..notifications import NotificationCatalog
|
|
46
|
+
from ..options import Options, OptionsSnapshot
|
|
47
|
+
from ..settings import SettingCatalog
|
|
48
|
+
from ..settings.binding import _provide_settings
|
|
49
|
+
from ..settings.store import RuntimeSettingStore
|
|
50
|
+
from ..unit_of_work.contracts import _UnitOfWorkFactory
|
|
51
|
+
from .application_bindings import (
|
|
52
|
+
_bind_application_services,
|
|
53
|
+
_runtime_providers,
|
|
54
|
+
_validate_application_service_dependencies,
|
|
55
|
+
)
|
|
56
|
+
from .arbitration import (
|
|
57
|
+
_RegistrationDraft,
|
|
58
|
+
_ServiceArbitrator,
|
|
59
|
+
)
|
|
60
|
+
from .contribution import ServiceContributionRegistry
|
|
61
|
+
from .convention import _ConventionScanner
|
|
62
|
+
from .convention_contracts import ConventionCandidate
|
|
63
|
+
from .framework_provider import (
|
|
64
|
+
_framework_provider,
|
|
65
|
+
)
|
|
66
|
+
from .native_graph import (
|
|
67
|
+
_accepted_single_service_scopes,
|
|
68
|
+
_NativeServiceGraph,
|
|
69
|
+
_validate_interceptors,
|
|
70
|
+
)
|
|
71
|
+
from .registration import (
|
|
72
|
+
RegistrationDecision,
|
|
73
|
+
RegistrationKind,
|
|
74
|
+
ServiceCatalog,
|
|
75
|
+
ServiceRegistration,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass(frozen=True, slots=True)
|
|
80
|
+
class _ServiceRuntime:
|
|
81
|
+
catalog: ServiceCatalog
|
|
82
|
+
application_service_catalog: ApplicationServiceCatalog
|
|
83
|
+
event_catalog: LocalEventCatalog
|
|
84
|
+
container: AsyncContainer
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class _ServiceRuntimeBuilder:
|
|
88
|
+
"""完成一次 Application-local scan、仲裁、容器构建和诊断快照发布。"""
|
|
89
|
+
|
|
90
|
+
__slots__ = (
|
|
91
|
+
"_background_coordinator",
|
|
92
|
+
"_background_job_catalog",
|
|
93
|
+
"_background_worker_catalog",
|
|
94
|
+
"_cache_catalog",
|
|
95
|
+
"_configuration",
|
|
96
|
+
"_contributions",
|
|
97
|
+
"_discoveries",
|
|
98
|
+
"_event_contributions",
|
|
99
|
+
"_hosted_service_catalog",
|
|
100
|
+
"_hosted_service_coordinator",
|
|
101
|
+
"_invocation_runtime",
|
|
102
|
+
"_notification_catalog",
|
|
103
|
+
"_options",
|
|
104
|
+
"_permission_catalog",
|
|
105
|
+
"_registry",
|
|
106
|
+
"_scan_packages",
|
|
107
|
+
"_setting_catalog",
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def __init__(
|
|
111
|
+
self,
|
|
112
|
+
registry: ModuleRegistry,
|
|
113
|
+
contributions: ServiceContributionRegistry,
|
|
114
|
+
event_contributions: EventContributionRegistry,
|
|
115
|
+
scan_packages: Mapping[ModuleKey, tuple[str, ...]],
|
|
116
|
+
discoveries: tuple[_DiscoveredType, ...],
|
|
117
|
+
configuration: ConfigurationRoot,
|
|
118
|
+
options: OptionsSnapshot,
|
|
119
|
+
invocation_runtime: _InvocationRuntime,
|
|
120
|
+
permission_catalog: PermissionCatalog,
|
|
121
|
+
cache_catalog: CacheCatalog,
|
|
122
|
+
setting_catalog: SettingCatalog,
|
|
123
|
+
notification_catalog: NotificationCatalog,
|
|
124
|
+
background_worker_catalog: BackgroundWorkerCatalog,
|
|
125
|
+
background_job_catalog: BackgroundJobCatalog,
|
|
126
|
+
background_coordinator: _BackgroundCoordinator,
|
|
127
|
+
hosted_service_catalog: HostedServiceCatalog,
|
|
128
|
+
hosted_service_coordinator: _HostedServiceCoordinator,
|
|
129
|
+
) -> None:
|
|
130
|
+
self._registry = registry
|
|
131
|
+
self._contributions = contributions
|
|
132
|
+
self._event_contributions = event_contributions
|
|
133
|
+
self._scan_packages = scan_packages
|
|
134
|
+
self._discoveries = discoveries
|
|
135
|
+
self._configuration = configuration
|
|
136
|
+
self._options = options
|
|
137
|
+
self._invocation_runtime = invocation_runtime
|
|
138
|
+
self._permission_catalog = permission_catalog
|
|
139
|
+
self._cache_catalog = cache_catalog
|
|
140
|
+
self._setting_catalog = setting_catalog
|
|
141
|
+
self._notification_catalog = notification_catalog
|
|
142
|
+
self._background_worker_catalog = background_worker_catalog
|
|
143
|
+
self._background_job_catalog = background_job_catalog
|
|
144
|
+
self._background_coordinator = background_coordinator
|
|
145
|
+
self._hosted_service_catalog = hosted_service_catalog
|
|
146
|
+
self._hosted_service_coordinator = hosted_service_coordinator
|
|
147
|
+
|
|
148
|
+
def build(self) -> _ServiceRuntime:
|
|
149
|
+
event_handler_scopes = _discover_event_handlers(
|
|
150
|
+
self._registry,
|
|
151
|
+
self._discoveries,
|
|
152
|
+
self._event_contributions,
|
|
153
|
+
)
|
|
154
|
+
scanner = _ConventionScanner(
|
|
155
|
+
self._registry,
|
|
156
|
+
self._contributions,
|
|
157
|
+
self._scan_packages,
|
|
158
|
+
self._discoveries,
|
|
159
|
+
event_handler_scopes,
|
|
160
|
+
)
|
|
161
|
+
convention_candidates = scanner.discover()
|
|
162
|
+
entries = self._contributions.freeze()
|
|
163
|
+
_validate_hosted_service_entries(entries, self._hosted_service_catalog)
|
|
164
|
+
arbitration = _ServiceArbitrator(self._registry).arbitrate(entries)
|
|
165
|
+
scanner.validate_lifetimes(arbitration.drafts)
|
|
166
|
+
_validate_hosted_service_drafts(arbitration.drafts, self._hosted_service_catalog)
|
|
167
|
+
bound_application_services = _bind_application_services(
|
|
168
|
+
scanner.application_services,
|
|
169
|
+
arbitration.drafts,
|
|
170
|
+
)
|
|
171
|
+
_validate_application_service_dependencies(
|
|
172
|
+
self._registry, bound_application_services, arbitration.drafts
|
|
173
|
+
)
|
|
174
|
+
application_service_catalog = _build_application_service_catalog(
|
|
175
|
+
tuple(item.binding for item in bound_application_services),
|
|
176
|
+
self._permission_catalog,
|
|
177
|
+
self._contributions.interceptor_rules,
|
|
178
|
+
)
|
|
179
|
+
event_catalog = _build_event_catalog(
|
|
180
|
+
self._registry,
|
|
181
|
+
self._event_contributions,
|
|
182
|
+
)
|
|
183
|
+
entry_interceptors = _EntryInterceptionCatalog(
|
|
184
|
+
self._registry, event_catalog, self._contributions.interceptor_rules
|
|
185
|
+
)
|
|
186
|
+
framework_provider = _framework_provider(
|
|
187
|
+
entry_interceptors=entry_interceptors,
|
|
188
|
+
has_unit_of_work_factory=any(
|
|
189
|
+
draft.key.type_hint is _UnitOfWorkFactory for draft in arbitration.drafts
|
|
190
|
+
),
|
|
191
|
+
registry=self._registry,
|
|
192
|
+
options=self._options,
|
|
193
|
+
invocation_runtime=self._invocation_runtime,
|
|
194
|
+
application_service_catalog=application_service_catalog,
|
|
195
|
+
event_catalog=event_catalog,
|
|
196
|
+
permission_catalog=self._permission_catalog,
|
|
197
|
+
cache_catalog=self._cache_catalog,
|
|
198
|
+
setting_catalog=self._setting_catalog,
|
|
199
|
+
notification_catalog=self._notification_catalog,
|
|
200
|
+
background_worker_catalog=self._background_worker_catalog,
|
|
201
|
+
background_job_catalog=self._background_job_catalog,
|
|
202
|
+
background_coordinator=self._background_coordinator,
|
|
203
|
+
hosted_service_catalog=self._hosted_service_catalog,
|
|
204
|
+
hosted_service_coordinator=self._hosted_service_coordinator,
|
|
205
|
+
configuration=self._configuration,
|
|
206
|
+
)
|
|
207
|
+
_provide_settings(
|
|
208
|
+
framework_provider,
|
|
209
|
+
self._setting_catalog,
|
|
210
|
+
self._configuration,
|
|
211
|
+
# Alias 的 scope 由原生图解析,不能以 draft.scope 是否已知遗漏有效 binding。
|
|
212
|
+
has_store=any(
|
|
213
|
+
draft.key == DependencyKey(RuntimeSettingStore, DEFAULT_COMPONENT)
|
|
214
|
+
and draft.decision is RegistrationDecision.ACCEPTED
|
|
215
|
+
and draft.contribution.kind is not RegistrationKind.MULTI
|
|
216
|
+
and not draft.is_collection
|
|
217
|
+
for draft in arbitration.drafts
|
|
218
|
+
),
|
|
219
|
+
)
|
|
220
|
+
runtime_providers = _runtime_providers(arbitration, bound_application_services)
|
|
221
|
+
try:
|
|
222
|
+
# Options 的唯一 owner 是阶段绑定快照;手工 provider 也不能为未声明类型创建旁路。
|
|
223
|
+
if any(
|
|
224
|
+
draft.key.type_hint is Options or get_origin(draft.key.type_hint) is Options
|
|
225
|
+
for draft in arbitration.drafts
|
|
226
|
+
):
|
|
227
|
+
raise ValueError(
|
|
228
|
+
"Options bindings must come from Module configuration declarations"
|
|
229
|
+
)
|
|
230
|
+
container = make_async_container(
|
|
231
|
+
*runtime_providers,
|
|
232
|
+
framework_provider,
|
|
233
|
+
context={
|
|
234
|
+
Options[model_type]: Options(value) # type: ignore[valid-type]
|
|
235
|
+
for model_type, value in self._options.values.items()
|
|
236
|
+
},
|
|
237
|
+
validation_settings=STRICT_VALIDATION,
|
|
238
|
+
)
|
|
239
|
+
except Exception as original_error:
|
|
240
|
+
error = ContainerValidationError(
|
|
241
|
+
sources=tuple(entry.contribution.source for entry in arbitration.ordered_entries),
|
|
242
|
+
original_error=original_error,
|
|
243
|
+
)
|
|
244
|
+
raise error from original_error
|
|
245
|
+
|
|
246
|
+
native_graph = _NativeServiceGraph(container)
|
|
247
|
+
_validate_event_scopes(
|
|
248
|
+
event_catalog,
|
|
249
|
+
_accepted_single_service_scopes(arbitration.drafts, native_graph),
|
|
250
|
+
)
|
|
251
|
+
_validate_interceptors(native_graph, application_service_catalog, entry_interceptors)
|
|
252
|
+
_validate_callable_entries(
|
|
253
|
+
container,
|
|
254
|
+
self._background_job_catalog,
|
|
255
|
+
self._background_worker_catalog,
|
|
256
|
+
event_catalog,
|
|
257
|
+
event_implementations={
|
|
258
|
+
item.handler_type: native_graph.implementation_for(item.handler_type)
|
|
259
|
+
for item in event_catalog.subscriptions
|
|
260
|
+
if isinstance(item.handler_type, type)
|
|
261
|
+
},
|
|
262
|
+
)
|
|
263
|
+
differences = native_graph.differences(arbitration.drafts)
|
|
264
|
+
if differences:
|
|
265
|
+
raise CatalogNativeGraphMismatchError(differences=differences)
|
|
266
|
+
return _ServiceRuntime(
|
|
267
|
+
catalog=self._freeze_catalog(
|
|
268
|
+
arbitration.drafts,
|
|
269
|
+
native_graph,
|
|
270
|
+
convention_candidates,
|
|
271
|
+
),
|
|
272
|
+
application_service_catalog=application_service_catalog,
|
|
273
|
+
event_catalog=event_catalog,
|
|
274
|
+
container=container,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
@staticmethod
|
|
278
|
+
def _freeze_catalog(
|
|
279
|
+
drafts: tuple[_RegistrationDraft, ...],
|
|
280
|
+
native_graph: _NativeServiceGraph,
|
|
281
|
+
convention_candidates: tuple[ConventionCandidate, ...],
|
|
282
|
+
) -> ServiceCatalog:
|
|
283
|
+
registrations = tuple(
|
|
284
|
+
ServiceRegistration(
|
|
285
|
+
key=draft.key,
|
|
286
|
+
scope=native_graph.scope_for(draft),
|
|
287
|
+
owner=draft.contribution.owner,
|
|
288
|
+
source=draft.contribution.source,
|
|
289
|
+
kind=draft.contribution.kind,
|
|
290
|
+
declaration_index=draft.contribution.declaration_index,
|
|
291
|
+
decision=draft.decision,
|
|
292
|
+
decision_reason=draft.decision_reason,
|
|
293
|
+
replaced_sources=draft.replaced_sources,
|
|
294
|
+
multi_ordinal=draft.multi_ordinal,
|
|
295
|
+
)
|
|
296
|
+
for draft in sorted(drafts, key=lambda item: item.sequence)
|
|
297
|
+
)
|
|
298
|
+
return ServiceCatalog(registrations, convention_candidates)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Global Runtime Settings 的 typed contract 与 Application-local catalog。"""
|
|
2
|
+
|
|
3
|
+
from .catalog import SettingCatalog
|
|
4
|
+
from .contracts import SettingDefinition
|
|
5
|
+
from .definitions import SettingDefinitionContext, SettingDefinitionProvider
|
|
6
|
+
from .errors import RuntimeSettingError, SettingDefinitionError
|
|
7
|
+
from .handlers import SettingApplicationStatus, SettingHandler, SettingHandlerDefinition
|
|
8
|
+
from .manager import SettingManager
|
|
9
|
+
from .provider import SettingProvider
|
|
10
|
+
from .store import RuntimeSettingStore
|
|
11
|
+
from .values import SettingSnapshot, SettingValueSource, SettingWriteOutcome, StoredSetting
|
|
12
|
+
|
|
13
|
+
__all__ = (
|
|
14
|
+
"RuntimeSettingError",
|
|
15
|
+
"RuntimeSettingStore",
|
|
16
|
+
"SettingApplicationStatus",
|
|
17
|
+
"SettingCatalog",
|
|
18
|
+
"SettingDefinition",
|
|
19
|
+
"SettingDefinitionContext",
|
|
20
|
+
"SettingDefinitionError",
|
|
21
|
+
"SettingDefinitionProvider",
|
|
22
|
+
"SettingHandler",
|
|
23
|
+
"SettingHandlerDefinition",
|
|
24
|
+
"SettingManager",
|
|
25
|
+
"SettingProvider",
|
|
26
|
+
"SettingSnapshot",
|
|
27
|
+
"SettingValueSource",
|
|
28
|
+
"SettingWriteOutcome",
|
|
29
|
+
"StoredSetting",
|
|
30
|
+
)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Settings 管理只编排正式 catalog 与 SettingManager,不拥有 provider 或值缓存。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Any, cast
|
|
7
|
+
|
|
8
|
+
from pydantic import JsonValue
|
|
9
|
+
|
|
10
|
+
from ..application_services import ApplicationService, application_service
|
|
11
|
+
from ..authorization import authorize
|
|
12
|
+
from ..configuration.immutability import _serialization_value
|
|
13
|
+
from ..errors import BusinessError, BusinessErrorDefinition, BusinessErrorField
|
|
14
|
+
from ..errors.base import ResourceNotFoundError
|
|
15
|
+
from .catalog import SettingCatalog
|
|
16
|
+
from .contracts import SettingDefinition
|
|
17
|
+
from .management import (
|
|
18
|
+
SETTINGS_READ,
|
|
19
|
+
SETTINGS_WRITE,
|
|
20
|
+
ResetSettingCommand,
|
|
21
|
+
SettingsManagementApplicationService,
|
|
22
|
+
SettingView,
|
|
23
|
+
SettingWriteResult,
|
|
24
|
+
UpdateSettingCommand,
|
|
25
|
+
)
|
|
26
|
+
from .manager import SettingManager
|
|
27
|
+
from .values import SettingSnapshot, SettingValueSource
|
|
28
|
+
|
|
29
|
+
_INVALID_VALUE = BusinessErrorDefinition("settings.invalid_value", "Invalid setting value")
|
|
30
|
+
_VALUE_FIELD = BusinessErrorField(UpdateSettingCommand, "value")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@application_service(provides=SettingsManagementApplicationService)
|
|
34
|
+
class _SettingsManagementApplicationService(ApplicationService):
|
|
35
|
+
def __init__(self, catalog: SettingCatalog, settings: SettingManager) -> None:
|
|
36
|
+
self._catalog = catalog
|
|
37
|
+
self._settings = settings
|
|
38
|
+
|
|
39
|
+
@authorize(SETTINGS_READ)
|
|
40
|
+
async def get_list(self) -> tuple[SettingView, ...]:
|
|
41
|
+
return tuple(
|
|
42
|
+
[
|
|
43
|
+
_view(definition, await self.setting_provider.snapshot(definition))
|
|
44
|
+
for definition in self._catalog.definitions
|
|
45
|
+
]
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
@authorize(SETTINGS_READ)
|
|
49
|
+
async def get(self, name: str) -> SettingView:
|
|
50
|
+
definition = self._definition(name)
|
|
51
|
+
return _view(definition, await self.setting_provider.snapshot(definition))
|
|
52
|
+
|
|
53
|
+
@authorize(SETTINGS_WRITE)
|
|
54
|
+
async def update(self, name: str, command: UpdateSettingCommand) -> SettingWriteResult:
|
|
55
|
+
definition = self._definition(name)
|
|
56
|
+
try:
|
|
57
|
+
# JSON 数组/日期须由 Pydantic 的 JSON 模式解释,不能误用 strict Python 输入语义。
|
|
58
|
+
value = definition._validate_json(json.dumps(command.value, allow_nan=False))
|
|
59
|
+
except (TypeError, ValueError):
|
|
60
|
+
raise BusinessError(_INVALID_VALUE, fields=(_VALUE_FIELD,)) from None
|
|
61
|
+
saved = await self._settings.update(
|
|
62
|
+
definition, value, expected_version=command.expected_version
|
|
63
|
+
)
|
|
64
|
+
return SettingWriteResult(
|
|
65
|
+
setting=_view(definition, saved.snapshot), notification=saved.notification
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
@authorize(SETTINGS_WRITE)
|
|
69
|
+
async def reset(self, name: str, command: ResetSettingCommand) -> SettingWriteResult:
|
|
70
|
+
definition = self._definition(name)
|
|
71
|
+
saved = await self._settings.reset(definition, expected_version=command.expected_version)
|
|
72
|
+
return SettingWriteResult(
|
|
73
|
+
setting=_view(definition, saved.snapshot), notification=saved.notification
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def _definition(self, name: str) -> SettingDefinition[Any]:
|
|
77
|
+
definition = self._catalog.get(name)
|
|
78
|
+
if definition is None:
|
|
79
|
+
raise ResourceNotFoundError("Setting not found")
|
|
80
|
+
return definition
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _view(definition: SettingDefinition[Any], snapshot: SettingSnapshot[Any]) -> SettingView:
|
|
84
|
+
# HTTP DTO 是独立 JSON 副本;不会把内部不可变快照的容器直接交给调用方。
|
|
85
|
+
value = (
|
|
86
|
+
None
|
|
87
|
+
if snapshot.source is SettingValueSource.UNSET
|
|
88
|
+
else cast(
|
|
89
|
+
JsonValue,
|
|
90
|
+
definition._adapter.dump_python(_serialization_value(snapshot.value), mode="json"),
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
return SettingView(
|
|
94
|
+
name=snapshot.name, value=value, source=snapshot.source, version=snapshot.version
|
|
95
|
+
)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""按最终存储 binding 组装原生 Dishka 服务;不以 provider Module 名单推断能力。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dishka import Provider, Scope
|
|
6
|
+
|
|
7
|
+
from ..configuration import ConfigurationRoot
|
|
8
|
+
from .catalog import SettingCatalog
|
|
9
|
+
from .manager import SettingManager
|
|
10
|
+
from .provider import SettingProvider
|
|
11
|
+
from .store import RuntimeSettingStore
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _provide_settings(
|
|
15
|
+
provider: Provider,
|
|
16
|
+
catalog: SettingCatalog,
|
|
17
|
+
configuration: ConfigurationRoot,
|
|
18
|
+
*,
|
|
19
|
+
has_store: bool,
|
|
20
|
+
) -> None:
|
|
21
|
+
if has_store:
|
|
22
|
+
|
|
23
|
+
def reader(store: RuntimeSettingStore) -> SettingProvider:
|
|
24
|
+
return SettingProvider(catalog, configuration, store)
|
|
25
|
+
|
|
26
|
+
provider.provide(reader, scope=Scope.APP)
|
|
27
|
+
provider.provide(SettingManager, scope=Scope.APP)
|
|
28
|
+
else:
|
|
29
|
+
provider.provide(
|
|
30
|
+
lambda: SettingProvider(catalog, configuration),
|
|
31
|
+
provides=SettingProvider,
|
|
32
|
+
scope=Scope.APP,
|
|
33
|
+
)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Module setting metadata 的 Application-local immutable catalog。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterator, Mapping
|
|
6
|
+
from types import MappingProxyType
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
|
+
|
|
9
|
+
from ..diagnostics import SourceLocation
|
|
10
|
+
from ..modularity import ModuleRegistry
|
|
11
|
+
from ..modularity.discovery import _DiscoveredType
|
|
12
|
+
from .contracts import SettingDefinition
|
|
13
|
+
from .definition_discovery import _collect_setting_definitions
|
|
14
|
+
from .errors import SettingDefinitionError
|
|
15
|
+
from .handlers import SettingHandlerDefinition
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from ..services.contribution import ServiceContributionRegistry
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SettingCatalog:
|
|
22
|
+
__slots__ = ("_definitions", "_handlers", "_sources")
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
definitions: tuple[SettingDefinition[Any], ...],
|
|
27
|
+
handlers: tuple[SettingHandlerDefinition[Any], ...] = (),
|
|
28
|
+
*,
|
|
29
|
+
sources: Mapping[str, SourceLocation] | None = None,
|
|
30
|
+
) -> None:
|
|
31
|
+
indexed: dict[str, SettingDefinition[Any]] = {}
|
|
32
|
+
for definition in definitions:
|
|
33
|
+
if not isinstance(definition, SettingDefinition):
|
|
34
|
+
raise SettingDefinitionError(
|
|
35
|
+
setting=repr(definition),
|
|
36
|
+
reason="catalog entries must be SettingDefinition values",
|
|
37
|
+
)
|
|
38
|
+
if definition.name in indexed:
|
|
39
|
+
raise SettingDefinitionError(
|
|
40
|
+
setting=definition.name,
|
|
41
|
+
reason="setting is declared more than once",
|
|
42
|
+
)
|
|
43
|
+
indexed[definition.name] = definition
|
|
44
|
+
self._definitions: Mapping[str, SettingDefinition[Any]] = MappingProxyType(
|
|
45
|
+
dict(sorted(indexed.items()))
|
|
46
|
+
)
|
|
47
|
+
seen: set[tuple[str, type[object]]] = set()
|
|
48
|
+
for handler in handlers:
|
|
49
|
+
if not isinstance(handler, SettingHandlerDefinition):
|
|
50
|
+
raise SettingDefinitionError(setting="handler", reason="invalid handler definition")
|
|
51
|
+
self.require_owned(handler.setting)
|
|
52
|
+
key = (handler.setting.name, handler.handler_type)
|
|
53
|
+
if key in seen:
|
|
54
|
+
raise SettingDefinitionError(setting=key[0], reason="handler is declared twice")
|
|
55
|
+
seen.add(key)
|
|
56
|
+
self._handlers = handlers
|
|
57
|
+
self._sources = MappingProxyType(dict(sources or {}))
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def sources(self) -> Mapping[str, SourceLocation]:
|
|
61
|
+
return self._sources
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def handlers(self) -> tuple[SettingHandlerDefinition[Any], ...]:
|
|
65
|
+
return self._handlers
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def definitions(self) -> tuple[SettingDefinition[Any], ...]:
|
|
69
|
+
return tuple(self._definitions.values())
|
|
70
|
+
|
|
71
|
+
def __iter__(self) -> Iterator[str]:
|
|
72
|
+
return iter(self._definitions)
|
|
73
|
+
|
|
74
|
+
def get(self, name: str) -> SettingDefinition[Any] | None:
|
|
75
|
+
return self._definitions.get(name)
|
|
76
|
+
|
|
77
|
+
def require_owned(self, definition: SettingDefinition[Any]) -> None:
|
|
78
|
+
if self._definitions.get(definition.name) is not definition:
|
|
79
|
+
raise SettingDefinitionError(
|
|
80
|
+
setting=definition.name,
|
|
81
|
+
reason="definition is not owned by this Application",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _build_setting_catalog(
|
|
86
|
+
registry: ModuleRegistry, discoveries: tuple[_DiscoveredType, ...]
|
|
87
|
+
) -> SettingCatalog:
|
|
88
|
+
definitions, sources = _collect_setting_definitions(discoveries)
|
|
89
|
+
handlers: list[SettingHandlerDefinition[Any]] = []
|
|
90
|
+
for descriptor in registry.dependency_order:
|
|
91
|
+
declared_handlers = descriptor.module_type.setting_handler_definitions
|
|
92
|
+
if not isinstance(declared_handlers, tuple):
|
|
93
|
+
raise SettingDefinitionError(
|
|
94
|
+
setting=str(descriptor.key), reason="setting_handler_definitions must be a tuple"
|
|
95
|
+
)
|
|
96
|
+
handlers.extend(declared_handlers)
|
|
97
|
+
return SettingCatalog(definitions, tuple(handlers), sources=sources)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _contribute_setting_handler_defaults(
|
|
101
|
+
registry: ModuleRegistry,
|
|
102
|
+
contributions: ServiceContributionRegistry,
|
|
103
|
+
) -> None:
|
|
104
|
+
from dishka import Provider, Scope
|
|
105
|
+
|
|
106
|
+
# 声明只拥有 handler 与 setting 的关系;实例和依赖仍交给原生 Dishka APP binding。
|
|
107
|
+
registered: set[type[object]] = set()
|
|
108
|
+
for descriptor in registry.dependency_order:
|
|
109
|
+
for definition in descriptor.module_type.setting_handler_definitions:
|
|
110
|
+
handler_type = definition.handler_type
|
|
111
|
+
if handler_type in registered:
|
|
112
|
+
continue
|
|
113
|
+
registered.add(handler_type)
|
|
114
|
+
provider = Provider()
|
|
115
|
+
provider.provide(handler_type, scope=Scope.APP)
|
|
116
|
+
contributions._add_framework_default(
|
|
117
|
+
provider=provider,
|
|
118
|
+
owner=descriptor.key,
|
|
119
|
+
source=descriptor.declaration_source,
|
|
120
|
+
reason=f"Settings handler default for {definition.setting.name}",
|
|
121
|
+
)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Settings 变更只通知标识,数据库仍是有效值的唯一权威。"""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, ConfigDict
|
|
4
|
+
|
|
5
|
+
from ..notifications import NotificationDefinition
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SettingChanged(BaseModel):
|
|
9
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
10
|
+
|
|
11
|
+
name: str
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
SETTINGS_CHANGED = NotificationDefinition("settings.changed", SettingChanged)
|