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,260 @@
|
|
|
1
|
+
"""Startup Module 到 isolated Application object graph 的 composition root。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import copy
|
|
6
|
+
from collections.abc import Mapping
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from uuid import uuid4
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
|
|
12
|
+
from ..application_services.invocation import _InvocationRuntime
|
|
13
|
+
from ..authorization.catalog import _build_permission_catalog
|
|
14
|
+
from ..background_jobs.catalog import _build_background_job_catalog
|
|
15
|
+
from ..background_workers import BackgroundExecutionOptions
|
|
16
|
+
from ..background_workers.catalog import _build_background_worker_catalog
|
|
17
|
+
from ..background_workers.runtime import _BackgroundCoordinator
|
|
18
|
+
from ..caching.catalog import _build_cache_catalog
|
|
19
|
+
from ..configuration.composition import _ConfigurationComposer
|
|
20
|
+
from ..configuration.contracts import _ConfigurationDefaultContribution
|
|
21
|
+
from ..diagnostics.journal import _DiagnosticJournal
|
|
22
|
+
from ..events.contribution import EventContributionRegistry
|
|
23
|
+
from ..hosted_services import HostedServiceOptions
|
|
24
|
+
from ..hosted_services.catalog import (
|
|
25
|
+
_build_hosted_service_catalog,
|
|
26
|
+
_contribute_hosted_service_defaults,
|
|
27
|
+
)
|
|
28
|
+
from ..hosted_services.runtime import _HostedServiceCoordinator
|
|
29
|
+
from ..modularity import AppModule, ModuleDescriptor, ModuleRef
|
|
30
|
+
from ..modularity.discovery import _discover_types, _snapshot_scan_packages
|
|
31
|
+
from ..modularity.graph import _ModuleGraphBuilder
|
|
32
|
+
from ..notifications.catalog import _build_notification_catalog
|
|
33
|
+
from ..options.registry import _OptionsRegistry
|
|
34
|
+
from ..services.contribution import ServiceContributionRegistry
|
|
35
|
+
from ..services.runtime import _ServiceRuntimeBuilder
|
|
36
|
+
from ..settings.catalog import _build_setting_catalog, _contribute_setting_handler_defaults
|
|
37
|
+
from .build_spec import _ApplicationBuildSpec
|
|
38
|
+
from .composition import _CompositionLifecycle
|
|
39
|
+
from .runtime import Application
|
|
40
|
+
from .state import _ApplicationStateRoot
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ApplicationBuilder:
|
|
44
|
+
"""以 startup Module 为唯一入口建立完整、隔离的 Application object graph。"""
|
|
45
|
+
|
|
46
|
+
__slots__ = (
|
|
47
|
+
"_base_path",
|
|
48
|
+
"_cli_args",
|
|
49
|
+
"_dotenv_path",
|
|
50
|
+
"_env_prefix",
|
|
51
|
+
"_environment",
|
|
52
|
+
"_explicit_overrides",
|
|
53
|
+
"_startup_module",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
startup_module: type[AppModule] | ModuleRef,
|
|
59
|
+
*,
|
|
60
|
+
environment: str,
|
|
61
|
+
base_path: Path,
|
|
62
|
+
env_prefix: str,
|
|
63
|
+
cli_args: tuple[str, ...] | list[str] | None = None,
|
|
64
|
+
dotenv_path: Path | None = None,
|
|
65
|
+
explicit_overrides: Mapping[str, object] | None = None,
|
|
66
|
+
) -> None:
|
|
67
|
+
if cli_args is not None and (
|
|
68
|
+
not isinstance(cli_args, (tuple, list))
|
|
69
|
+
or any(not isinstance(arg, str) for arg in cli_args)
|
|
70
|
+
):
|
|
71
|
+
raise TypeError("cli_args must be a sequence of strings")
|
|
72
|
+
self._cli_args = tuple(cli_args or ())
|
|
73
|
+
self._startup_module = startup_module
|
|
74
|
+
self._environment = _validate_environment(environment)
|
|
75
|
+
self._base_path = _validate_base_path(base_path)
|
|
76
|
+
self._env_prefix = _validate_env_prefix(env_prefix)
|
|
77
|
+
self._dotenv_path = _resolve_dotenv_path(
|
|
78
|
+
dotenv_path,
|
|
79
|
+
base_path=self._base_path,
|
|
80
|
+
)
|
|
81
|
+
self._explicit_overrides = _copy_explicit_overrides(explicit_overrides)
|
|
82
|
+
|
|
83
|
+
def build(self) -> Application:
|
|
84
|
+
registry = _ModuleGraphBuilder(self._startup_module).build()
|
|
85
|
+
scan_packages = _snapshot_scan_packages(registry)
|
|
86
|
+
discoveries = _discover_types(registry, scan_packages)
|
|
87
|
+
claims: dict[type[BaseModel], tuple[str, ModuleDescriptor]] = {}
|
|
88
|
+
pre_options_registry = _OptionsRegistry(kind="PreOptions", claims=claims)
|
|
89
|
+
options_registry = _OptionsRegistry(kind="Options", claims=claims)
|
|
90
|
+
permission_catalog = _build_permission_catalog(registry, discoveries)
|
|
91
|
+
cache_catalog = _build_cache_catalog(registry)
|
|
92
|
+
setting_catalog = _build_setting_catalog(registry, discoveries)
|
|
93
|
+
notification_catalog = _build_notification_catalog(registry)
|
|
94
|
+
background_worker_catalog = _build_background_worker_catalog(registry)
|
|
95
|
+
background_job_catalog = _build_background_job_catalog(registry)
|
|
96
|
+
hosted_service_catalog = _build_hosted_service_catalog(registry)
|
|
97
|
+
configuration = _ConfigurationComposer(
|
|
98
|
+
environment=self._environment,
|
|
99
|
+
base_path=self._base_path,
|
|
100
|
+
env_prefix=self._env_prefix,
|
|
101
|
+
dotenv_path=self._dotenv_path,
|
|
102
|
+
explicit_overrides=self._explicit_overrides,
|
|
103
|
+
cli_args=self._cli_args,
|
|
104
|
+
).build(
|
|
105
|
+
tuple(
|
|
106
|
+
_ConfigurationDefaultContribution(
|
|
107
|
+
source_location=descriptor.declaration_source,
|
|
108
|
+
values=descriptor.module_type.configuration_defaults,
|
|
109
|
+
)
|
|
110
|
+
for descriptor in registry.dependency_order
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
root = _ApplicationStateRoot(
|
|
114
|
+
application_id=uuid4(),
|
|
115
|
+
registry=registry,
|
|
116
|
+
configuration=configuration,
|
|
117
|
+
pre_options_registry=pre_options_registry,
|
|
118
|
+
options_registry=options_registry,
|
|
119
|
+
service_registry=ServiceContributionRegistry(),
|
|
120
|
+
event_registry=EventContributionRegistry(),
|
|
121
|
+
permission_catalog=permission_catalog,
|
|
122
|
+
cache_catalog=cache_catalog,
|
|
123
|
+
setting_catalog=setting_catalog,
|
|
124
|
+
background_worker_catalog=background_worker_catalog,
|
|
125
|
+
background_job_catalog=background_job_catalog,
|
|
126
|
+
hosted_service_catalog=hosted_service_catalog,
|
|
127
|
+
journal=_DiagnosticJournal(),
|
|
128
|
+
completed_stages={descriptor.key: [] for descriptor in registry.dependency_order},
|
|
129
|
+
)
|
|
130
|
+
_CompositionLifecycle(root).run()
|
|
131
|
+
_contribute_hosted_service_defaults(hosted_service_catalog, root.service_registry)
|
|
132
|
+
_contribute_setting_handler_defaults(registry, root.service_registry)
|
|
133
|
+
root.invocation_runtime = _InvocationRuntime(
|
|
134
|
+
application_id=root.application_id,
|
|
135
|
+
state_getter=lambda: root.state,
|
|
136
|
+
)
|
|
137
|
+
root.background_coordinator = _BackgroundCoordinator(
|
|
138
|
+
worker_catalog=background_worker_catalog,
|
|
139
|
+
job_catalog=background_job_catalog,
|
|
140
|
+
options=root.require_options().get(BackgroundExecutionOptions),
|
|
141
|
+
invocation_runtime=root.require_invocation_runtime(),
|
|
142
|
+
build_spec=_ApplicationBuildSpec(
|
|
143
|
+
self._startup_module,
|
|
144
|
+
self._environment,
|
|
145
|
+
self._base_path,
|
|
146
|
+
self._env_prefix,
|
|
147
|
+
self._cli_args,
|
|
148
|
+
self._dotenv_path,
|
|
149
|
+
dict(self._explicit_overrides),
|
|
150
|
+
Path.cwd().resolve(),
|
|
151
|
+
),
|
|
152
|
+
)
|
|
153
|
+
root.hosted_service_coordinator = _HostedServiceCoordinator(
|
|
154
|
+
catalog=hosted_service_catalog,
|
|
155
|
+
options=root.require_options().get(HostedServiceOptions),
|
|
156
|
+
state_getter=lambda: root.state,
|
|
157
|
+
)
|
|
158
|
+
service_runtime = _ServiceRuntimeBuilder(
|
|
159
|
+
registry,
|
|
160
|
+
root.service_registry,
|
|
161
|
+
root.event_registry,
|
|
162
|
+
scan_packages,
|
|
163
|
+
discoveries,
|
|
164
|
+
root.configuration,
|
|
165
|
+
root.require_options(),
|
|
166
|
+
root.require_invocation_runtime(),
|
|
167
|
+
permission_catalog,
|
|
168
|
+
cache_catalog,
|
|
169
|
+
setting_catalog,
|
|
170
|
+
notification_catalog,
|
|
171
|
+
background_worker_catalog,
|
|
172
|
+
background_job_catalog,
|
|
173
|
+
root.require_background_coordinator(),
|
|
174
|
+
hosted_service_catalog,
|
|
175
|
+
root.require_hosted_service_coordinator(),
|
|
176
|
+
).build()
|
|
177
|
+
root.service_catalog = service_runtime.catalog
|
|
178
|
+
root.application_service_catalog = service_runtime.application_service_catalog
|
|
179
|
+
root.event_catalog = service_runtime.event_catalog
|
|
180
|
+
root.container = service_runtime.container
|
|
181
|
+
root.require_background_coordinator().attach_container(service_runtime.container)
|
|
182
|
+
root.require_hosted_service_coordinator().attach_container(
|
|
183
|
+
service_runtime.container,
|
|
184
|
+
service_runtime.application_service_catalog,
|
|
185
|
+
)
|
|
186
|
+
return Application(root)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _validate_environment(environment: str) -> str:
|
|
190
|
+
if not isinstance(environment, str) or not environment:
|
|
191
|
+
raise TypeError("environment must be a non-empty string")
|
|
192
|
+
if environment != environment.strip() or any(
|
|
193
|
+
not (character.isalnum() or character in "_-") for character in environment
|
|
194
|
+
):
|
|
195
|
+
raise ValueError("environment must contain only letters, digits, '_' or '-'")
|
|
196
|
+
return environment
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _validate_base_path(base_path: Path) -> Path:
|
|
200
|
+
if not isinstance(base_path, Path):
|
|
201
|
+
raise TypeError("base_path must be pathlib.Path")
|
|
202
|
+
resolved = base_path.resolve()
|
|
203
|
+
if not resolved.is_dir():
|
|
204
|
+
raise ValueError("base_path must be an existing directory")
|
|
205
|
+
return resolved
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _validate_env_prefix(env_prefix: str) -> str:
|
|
209
|
+
if not isinstance(env_prefix, str) or not env_prefix:
|
|
210
|
+
raise TypeError("env_prefix must be a non-empty string")
|
|
211
|
+
if not (env_prefix[0].isalpha() or env_prefix[0] == "_") or any(
|
|
212
|
+
not (character.isalnum() or character == "_") for character in env_prefix
|
|
213
|
+
):
|
|
214
|
+
raise ValueError("env_prefix must be a valid environment-variable prefix")
|
|
215
|
+
return env_prefix
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _resolve_dotenv_path(
|
|
219
|
+
dotenv_path: Path | None,
|
|
220
|
+
*,
|
|
221
|
+
base_path: Path,
|
|
222
|
+
) -> Path | None:
|
|
223
|
+
if dotenv_path is None:
|
|
224
|
+
return None
|
|
225
|
+
if not isinstance(dotenv_path, Path):
|
|
226
|
+
raise TypeError("dotenv_path must be pathlib.Path or None")
|
|
227
|
+
candidate = dotenv_path if dotenv_path.is_absolute() else base_path / dotenv_path
|
|
228
|
+
return candidate.resolve()
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _copy_explicit_overrides(
|
|
232
|
+
explicit_overrides: Mapping[str, object] | None,
|
|
233
|
+
) -> dict[str, object]:
|
|
234
|
+
if explicit_overrides is None:
|
|
235
|
+
return {}
|
|
236
|
+
if not isinstance(explicit_overrides, Mapping):
|
|
237
|
+
raise TypeError("explicit_overrides must be a mapping")
|
|
238
|
+
try:
|
|
239
|
+
return {key: _copy_explicit_value(value) for key, value in explicit_overrides.items()}
|
|
240
|
+
except Exception as original_error:
|
|
241
|
+
raise TypeError("explicit_overrides must be defensively copyable") from original_error
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _copy_explicit_value(value: object) -> object:
|
|
245
|
+
# deepcopy 不支持 MappingProxyType;容器先按配置语义复制,再交给 source boundary 校验。
|
|
246
|
+
if isinstance(value, Mapping):
|
|
247
|
+
copied: object = {key: _copy_explicit_value(item) for key, item in value.items()}
|
|
248
|
+
elif isinstance(value, list):
|
|
249
|
+
copied = [_copy_explicit_value(item) for item in value]
|
|
250
|
+
elif isinstance(value, tuple):
|
|
251
|
+
copied = tuple(_copy_explicit_value(item) for item in value)
|
|
252
|
+
elif isinstance(value, set):
|
|
253
|
+
copied = {_copy_explicit_value(item) for item in value}
|
|
254
|
+
elif isinstance(value, frozenset):
|
|
255
|
+
copied = frozenset(_copy_explicit_value(item) for item in value)
|
|
256
|
+
elif isinstance(value, bytearray):
|
|
257
|
+
copied = bytearray(value)
|
|
258
|
+
else:
|
|
259
|
+
copied = copy.deepcopy(value)
|
|
260
|
+
return copied
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""Synchronous Module composition stages 与 PreOptions/Options barriers。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from typing import cast
|
|
8
|
+
|
|
9
|
+
from ..auditing import AuditingOptions
|
|
10
|
+
from ..background_workers import BackgroundExecutionOptions
|
|
11
|
+
from ..diagnostics import EventStatus, LifecyclePhase, LifecycleStage
|
|
12
|
+
from ..errors import (
|
|
13
|
+
CompositionResourceError,
|
|
14
|
+
LifecycleInvocationError,
|
|
15
|
+
)
|
|
16
|
+
from ..hosted_services import HostedServiceOptions
|
|
17
|
+
from ..hosted_services.options import HOSTED_SERVICE_OPTIONS_SECTION
|
|
18
|
+
from ..lifecycle import ConfigureContext, PostConfigureContext, PreConfigureContext
|
|
19
|
+
from ..modularity import ModuleDescriptor
|
|
20
|
+
from ..unit_of_work import UnitOfWorkOptions
|
|
21
|
+
from .state import (
|
|
22
|
+
LIFECYCLE_ERROR_TYPE,
|
|
23
|
+
_ApplicationStateRoot,
|
|
24
|
+
_lifecycle_failure_message,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class _CompositionLifecycle:
|
|
29
|
+
"""拥有一次全局 stage barrier 编排,不保存任何跨 Application 状态。"""
|
|
30
|
+
|
|
31
|
+
__slots__ = ("_root",)
|
|
32
|
+
|
|
33
|
+
def __init__(self, root: _ApplicationStateRoot) -> None:
|
|
34
|
+
self._root = root
|
|
35
|
+
|
|
36
|
+
def run(self) -> None:
|
|
37
|
+
# typed 调用是 lifecycle contract;不通过 getattr/string table 建第二份阶段权威。
|
|
38
|
+
for descriptor in self._root.registry.dependency_order:
|
|
39
|
+
self._invoke_pre_configure(descriptor)
|
|
40
|
+
self._root.pre_options = self._root.pre_options_registry.bind_pre_options(
|
|
41
|
+
self._root.configuration
|
|
42
|
+
)
|
|
43
|
+
for descriptor in self._root.registry.dependency_order:
|
|
44
|
+
self._invoke_configure(descriptor)
|
|
45
|
+
for descriptor in self._root.registry.dependency_order:
|
|
46
|
+
self._invoke_post_configure(descriptor)
|
|
47
|
+
options = self._root.options_registry
|
|
48
|
+
owner = self._root.registry.startup
|
|
49
|
+
# 常驻框架能力的默认声明只有此处负责;显式 Module 声明优先,默认值仍由 model 验证。
|
|
50
|
+
options.declare_default(BackgroundExecutionOptions, "background_execution", owner=owner)
|
|
51
|
+
options.declare_default(HostedServiceOptions, HOSTED_SERVICE_OPTIONS_SECTION, owner=owner)
|
|
52
|
+
options.declare_default(AuditingOptions, "auditing", owner=owner)
|
|
53
|
+
options.declare_default(UnitOfWorkOptions, "unit_of_work", owner=owner)
|
|
54
|
+
self._root.options = options.bind_options(self._root.configuration)
|
|
55
|
+
self._root.configuration = self._root.configuration._finalize(
|
|
56
|
+
sections=(
|
|
57
|
+
options.sections
|
|
58
|
+
| self._root.pre_options_registry.sections
|
|
59
|
+
| ({"settings"} if self._root.setting_catalog.definitions else set())
|
|
60
|
+
),
|
|
61
|
+
secret_paths=options.secret_paths | self._root.pre_options_registry.secret_paths,
|
|
62
|
+
mapping_paths=lambda values, reference: (
|
|
63
|
+
options.mapping_paths(values, reference, self._root.options)
|
|
64
|
+
| self._root.pre_options_registry.mapping_paths(
|
|
65
|
+
values, reference, self._root.pre_options
|
|
66
|
+
)
|
|
67
|
+
),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def _invoke_pre_configure(self, descriptor: ModuleDescriptor) -> None:
|
|
71
|
+
stage = LifecycleStage.PRE_CONFIGURE
|
|
72
|
+
editor = self._root.pre_options_registry.contributor(
|
|
73
|
+
owner=descriptor,
|
|
74
|
+
stage=stage,
|
|
75
|
+
)
|
|
76
|
+
context = PreConfigureContext(
|
|
77
|
+
application_id=self._root.application_id,
|
|
78
|
+
descriptor=descriptor,
|
|
79
|
+
stage=stage,
|
|
80
|
+
application=self._root.view(None),
|
|
81
|
+
_emit=self._root.diagnostic_emitter(descriptor, stage),
|
|
82
|
+
configuration=self._root.configuration,
|
|
83
|
+
pre_configure=editor,
|
|
84
|
+
)
|
|
85
|
+
callback = cast(
|
|
86
|
+
Callable[[PreConfigureContext], object],
|
|
87
|
+
descriptor.instance.pre_configure,
|
|
88
|
+
)
|
|
89
|
+
try:
|
|
90
|
+
self._invoke(descriptor, stage, lambda: callback(context))
|
|
91
|
+
finally:
|
|
92
|
+
editor._close()
|
|
93
|
+
|
|
94
|
+
def _invoke_configure(self, descriptor: ModuleDescriptor) -> None:
|
|
95
|
+
stage = LifecycleStage.CONFIGURE
|
|
96
|
+
editor = self._root.options_registry.contributor(
|
|
97
|
+
owner=descriptor,
|
|
98
|
+
stage=stage,
|
|
99
|
+
)
|
|
100
|
+
services = self._root.service_registry.contributor(
|
|
101
|
+
descriptor=descriptor,
|
|
102
|
+
stage=stage,
|
|
103
|
+
)
|
|
104
|
+
events = self._root.event_registry.contributor(
|
|
105
|
+
descriptor=descriptor,
|
|
106
|
+
stage=stage,
|
|
107
|
+
)
|
|
108
|
+
context = ConfigureContext(
|
|
109
|
+
application_id=self._root.application_id,
|
|
110
|
+
descriptor=descriptor,
|
|
111
|
+
stage=stage,
|
|
112
|
+
application=self._root.view(None),
|
|
113
|
+
_emit=self._root.diagnostic_emitter(descriptor, stage),
|
|
114
|
+
configuration=self._root.configuration,
|
|
115
|
+
pre_options=self._root.require_pre_options(),
|
|
116
|
+
configure=editor,
|
|
117
|
+
services=services,
|
|
118
|
+
events=events,
|
|
119
|
+
)
|
|
120
|
+
callback = cast(
|
|
121
|
+
Callable[[ConfigureContext], object],
|
|
122
|
+
descriptor.instance.configure,
|
|
123
|
+
)
|
|
124
|
+
try:
|
|
125
|
+
self._invoke(descriptor, stage, lambda: callback(context))
|
|
126
|
+
finally:
|
|
127
|
+
editor._close()
|
|
128
|
+
services._close()
|
|
129
|
+
events._close()
|
|
130
|
+
|
|
131
|
+
def _invoke_post_configure(self, descriptor: ModuleDescriptor) -> None:
|
|
132
|
+
stage = LifecycleStage.POST_CONFIGURE
|
|
133
|
+
editor = self._root.options_registry.contributor(
|
|
134
|
+
owner=descriptor,
|
|
135
|
+
stage=stage,
|
|
136
|
+
)
|
|
137
|
+
services = self._root.service_registry.contributor(
|
|
138
|
+
descriptor=descriptor,
|
|
139
|
+
stage=stage,
|
|
140
|
+
)
|
|
141
|
+
context = PostConfigureContext(
|
|
142
|
+
application_id=self._root.application_id,
|
|
143
|
+
descriptor=descriptor,
|
|
144
|
+
stage=stage,
|
|
145
|
+
application=self._root.view(None),
|
|
146
|
+
_emit=self._root.diagnostic_emitter(descriptor, stage),
|
|
147
|
+
configuration=self._root.configuration,
|
|
148
|
+
pre_options=self._root.require_pre_options(),
|
|
149
|
+
configure=editor,
|
|
150
|
+
services=services,
|
|
151
|
+
)
|
|
152
|
+
callback = cast(
|
|
153
|
+
Callable[[PostConfigureContext], object],
|
|
154
|
+
descriptor.instance.post_configure,
|
|
155
|
+
)
|
|
156
|
+
try:
|
|
157
|
+
self._invoke(descriptor, stage, lambda: callback(context))
|
|
158
|
+
finally:
|
|
159
|
+
editor._close()
|
|
160
|
+
services._close()
|
|
161
|
+
|
|
162
|
+
def _invoke(
|
|
163
|
+
self,
|
|
164
|
+
descriptor: ModuleDescriptor,
|
|
165
|
+
stage: LifecycleStage,
|
|
166
|
+
callback: Callable[[], object],
|
|
167
|
+
) -> None:
|
|
168
|
+
subject = str(descriptor.key)
|
|
169
|
+
active_event = self._root.journal.start_event(
|
|
170
|
+
phase=LifecyclePhase.COMPOSITION,
|
|
171
|
+
stage=stage,
|
|
172
|
+
subject=subject,
|
|
173
|
+
source=descriptor.declaration_source,
|
|
174
|
+
)
|
|
175
|
+
try:
|
|
176
|
+
result = callback()
|
|
177
|
+
is_awaitable = inspect.isawaitable(result)
|
|
178
|
+
if inspect.iscoroutine(result):
|
|
179
|
+
result.close()
|
|
180
|
+
if result is not None:
|
|
181
|
+
raise CompositionResourceError(
|
|
182
|
+
module=descriptor.key,
|
|
183
|
+
stage=stage,
|
|
184
|
+
resource_kind=("awaitable" if is_awaitable else "non-None value"),
|
|
185
|
+
)
|
|
186
|
+
except Exception as original_error:
|
|
187
|
+
failure_id = self._root.journal.begin_failure(
|
|
188
|
+
error_type=LIFECYCLE_ERROR_TYPE,
|
|
189
|
+
phase=LifecyclePhase.COMPOSITION,
|
|
190
|
+
stage=stage,
|
|
191
|
+
subject=subject,
|
|
192
|
+
message=_lifecycle_failure_message(subject, stage, original_error),
|
|
193
|
+
source=descriptor.declaration_source,
|
|
194
|
+
dependency_path=tuple(str(key) for key in descriptor.dependency_path),
|
|
195
|
+
)
|
|
196
|
+
duration_ns = self._root.journal.finish_event(
|
|
197
|
+
active_event,
|
|
198
|
+
status=EventStatus.FAILED,
|
|
199
|
+
failure_id=failure_id,
|
|
200
|
+
)
|
|
201
|
+
self._root.journal.complete_failure(failure_id)
|
|
202
|
+
error = LifecycleInvocationError(
|
|
203
|
+
failure_id=failure_id,
|
|
204
|
+
module=descriptor.key,
|
|
205
|
+
phase=LifecyclePhase.COMPOSITION,
|
|
206
|
+
stage=stage,
|
|
207
|
+
dependency_path=descriptor.dependency_path,
|
|
208
|
+
duration_ns=duration_ns,
|
|
209
|
+
source=descriptor.declaration_source,
|
|
210
|
+
original_error=original_error,
|
|
211
|
+
)
|
|
212
|
+
raise error from original_error
|
|
213
|
+
|
|
214
|
+
self._root.journal.finish_event(
|
|
215
|
+
active_event,
|
|
216
|
+
status=EventStatus.SUCCEEDED,
|
|
217
|
+
failure_id=None,
|
|
218
|
+
)
|
|
219
|
+
self._root.completed_stages[descriptor.key].append(stage)
|