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,546 @@
|
|
|
1
|
+
"""公开 Application 状态机、生命周期与非 DI 调用入口。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from collections.abc import AsyncIterator, Awaitable, Callable, Iterator
|
|
7
|
+
from contextlib import asynccontextmanager, contextmanager
|
|
8
|
+
from typing import Concatenate, ParamSpec, TypeAlias, TypeVar, cast
|
|
9
|
+
from uuid import UUID, uuid4
|
|
10
|
+
|
|
11
|
+
from dishka import AsyncContainer, Scope
|
|
12
|
+
|
|
13
|
+
from ..application_services import (
|
|
14
|
+
ApplicationInvocationRejectedError,
|
|
15
|
+
ApplicationServiceCatalog,
|
|
16
|
+
ApplicationServiceContract,
|
|
17
|
+
ApplicationServiceRegistration,
|
|
18
|
+
)
|
|
19
|
+
from ..application_services.dispatcher import _ApplicationServiceDispatcher
|
|
20
|
+
from ..application_services.invocation import (
|
|
21
|
+
_ScopedApplicationServiceInvoker,
|
|
22
|
+
)
|
|
23
|
+
from ..authorization import CurrentUser, PermissionCatalog
|
|
24
|
+
from ..background_jobs import BackgroundJobCatalog
|
|
25
|
+
from ..background_workers import BackgroundWorkerCatalog
|
|
26
|
+
from ..caching import CacheCatalog
|
|
27
|
+
from ..configuration import ConfigurationRoot
|
|
28
|
+
from ..diagnostics import DiagnosticSnapshot
|
|
29
|
+
from ..errors import (
|
|
30
|
+
ApplicationRuntimeError,
|
|
31
|
+
ApplicationStartError,
|
|
32
|
+
ContainerCleanupError,
|
|
33
|
+
InvalidApplicationStateError,
|
|
34
|
+
LifecycleInvocationError,
|
|
35
|
+
)
|
|
36
|
+
from ..errors.lifecycle import (
|
|
37
|
+
_BackgroundExecutionStartFailure,
|
|
38
|
+
_exclude_runtime_failure,
|
|
39
|
+
)
|
|
40
|
+
from ..events import LocalEventCatalog
|
|
41
|
+
from ..hosted_services import (
|
|
42
|
+
ApplicationReadinessSnapshot,
|
|
43
|
+
HostedServiceCatalog,
|
|
44
|
+
HostedServiceLifecycleError,
|
|
45
|
+
)
|
|
46
|
+
from ..invocation.function_runtime import _invoke_callable
|
|
47
|
+
from ..lifecycle import ApplicationState, ShutdownReason
|
|
48
|
+
from ..modularity import ModuleRegistry
|
|
49
|
+
from ..options import OptionsSnapshot
|
|
50
|
+
from ..services import ServiceCatalog
|
|
51
|
+
from ..settings import SettingCatalog
|
|
52
|
+
from .runtime_hooks import _RuntimeLifecycle
|
|
53
|
+
from .state import _ApplicationStateRoot
|
|
54
|
+
|
|
55
|
+
_P = ParamSpec("_P")
|
|
56
|
+
_R = TypeVar("_R")
|
|
57
|
+
_TContract = TypeVar("_TContract", bound=ApplicationServiceContract)
|
|
58
|
+
_StartCleanupFailure: TypeAlias = (
|
|
59
|
+
LifecycleInvocationError
|
|
60
|
+
| ContainerCleanupError
|
|
61
|
+
| _BackgroundExecutionStartFailure
|
|
62
|
+
| HostedServiceLifecycleError
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class Application:
|
|
67
|
+
"""拥有单次 build 的 Module instances、状态、transition 与 diagnostics。"""
|
|
68
|
+
|
|
69
|
+
__slots__ = ("_root",)
|
|
70
|
+
|
|
71
|
+
def __init__(self, root: _ApplicationStateRoot) -> None:
|
|
72
|
+
self._root = root
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def id(self) -> UUID:
|
|
76
|
+
return self._root.application_id
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def state(self) -> ApplicationState:
|
|
80
|
+
return self._root.state
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def modules(self) -> ModuleRegistry:
|
|
84
|
+
return self._root.registry
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def configuration(self) -> ConfigurationRoot:
|
|
88
|
+
return self._root.configuration
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def options(self) -> OptionsSnapshot:
|
|
92
|
+
return self._root.require_options()
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def service_catalog(self) -> ServiceCatalog:
|
|
96
|
+
return self._root.require_service_catalog()
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def application_service_catalog(self) -> ApplicationServiceCatalog:
|
|
100
|
+
return self._root.require_application_service_catalog()
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def event_catalog(self) -> LocalEventCatalog:
|
|
104
|
+
return self._root.require_event_catalog()
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def permission_catalog(self) -> PermissionCatalog:
|
|
108
|
+
return self._root.require_permission_catalog()
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def cache_catalog(self) -> CacheCatalog:
|
|
112
|
+
return self._root.require_cache_catalog()
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def setting_catalog(self) -> SettingCatalog:
|
|
116
|
+
return self._root.require_setting_catalog()
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def background_worker_catalog(self) -> BackgroundWorkerCatalog:
|
|
120
|
+
return self._root.require_background_worker_catalog()
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def background_job_catalog(self) -> BackgroundJobCatalog:
|
|
124
|
+
return self._root.require_background_job_catalog()
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def hosted_service_catalog(self) -> HostedServiceCatalog:
|
|
128
|
+
return self._root.require_hosted_service_catalog()
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def readiness(self) -> ApplicationReadinessSnapshot:
|
|
132
|
+
return self._root.readiness()
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def diagnostics(self) -> DiagnosticSnapshot:
|
|
136
|
+
return self._root.journal.snapshot(self._root.application_id)
|
|
137
|
+
|
|
138
|
+
def _container_for_integration(self) -> AsyncContainer:
|
|
139
|
+
# 外部 adapter 只能在 composition root 建桥;不把 resolver/container 变成 public API。
|
|
140
|
+
return self._root.require_container()
|
|
141
|
+
|
|
142
|
+
async def start(self) -> None:
|
|
143
|
+
await self._start(run_services=True)
|
|
144
|
+
|
|
145
|
+
async def _start(self, *, run_services: bool) -> None:
|
|
146
|
+
token = self._root.claim_transition(
|
|
147
|
+
operation="start",
|
|
148
|
+
allowed=(ApplicationState.COMPOSED,),
|
|
149
|
+
)
|
|
150
|
+
self._root.state = ApplicationState.STARTING
|
|
151
|
+
if not run_services:
|
|
152
|
+
# 数据库 CLI 只需要 Module 初始化及正式 invocation;不打开后台生产者或外部 SDK。
|
|
153
|
+
self._root.require_background_coordinator().disable_for_database()
|
|
154
|
+
lifecycle = _RuntimeLifecycle(self._root)
|
|
155
|
+
started = False
|
|
156
|
+
try:
|
|
157
|
+
try:
|
|
158
|
+
await lifecycle.start_modules()
|
|
159
|
+
except LifecycleInvocationError as primary:
|
|
160
|
+
self._root.state = ApplicationState.ROLLING_BACK
|
|
161
|
+
cleanup_failures = await lifecycle.shutdown_touched(
|
|
162
|
+
reason=ShutdownReason.START_FAILURE,
|
|
163
|
+
)
|
|
164
|
+
container_failures = await lifecycle.close_container(
|
|
165
|
+
exception=primary.original_error,
|
|
166
|
+
)
|
|
167
|
+
all_cleanup_failures: tuple[
|
|
168
|
+
LifecycleInvocationError | ContainerCleanupError,
|
|
169
|
+
...,
|
|
170
|
+
] = (
|
|
171
|
+
*cleanup_failures,
|
|
172
|
+
*container_failures,
|
|
173
|
+
)
|
|
174
|
+
self._root.journal.complete_failure(
|
|
175
|
+
primary.failure_id,
|
|
176
|
+
cleanup_failure_ids=tuple(
|
|
177
|
+
failure.failure_id for failure in all_cleanup_failures
|
|
178
|
+
),
|
|
179
|
+
)
|
|
180
|
+
self._root.state = ApplicationState.FAILED_START
|
|
181
|
+
raise ApplicationStartError(
|
|
182
|
+
primary=primary,
|
|
183
|
+
cleanup_failures=all_cleanup_failures,
|
|
184
|
+
) from primary
|
|
185
|
+
self._root.state = ApplicationState.RUNNING
|
|
186
|
+
self._root.require_invocation_runtime().start_accepting()
|
|
187
|
+
try:
|
|
188
|
+
await self._root.require_background_coordinator().start()
|
|
189
|
+
except BaseException as original_error:
|
|
190
|
+
background_failure = _BackgroundExecutionStartFailure(original_error=original_error)
|
|
191
|
+
runtime_cleanup_failures = await self._rollback_runtime_start(
|
|
192
|
+
lifecycle,
|
|
193
|
+
primary=background_failure,
|
|
194
|
+
)
|
|
195
|
+
raise ApplicationStartError(
|
|
196
|
+
primary=background_failure,
|
|
197
|
+
cleanup_failures=runtime_cleanup_failures,
|
|
198
|
+
) from background_failure
|
|
199
|
+
try:
|
|
200
|
+
if run_services:
|
|
201
|
+
await self._root.require_hosted_service_coordinator().start()
|
|
202
|
+
except BaseException as original_error:
|
|
203
|
+
hosted_failure = (
|
|
204
|
+
original_error
|
|
205
|
+
if isinstance(original_error, HostedServiceLifecycleError)
|
|
206
|
+
else HostedServiceLifecycleError(
|
|
207
|
+
service="hosted-service-coordinator",
|
|
208
|
+
operation="start",
|
|
209
|
+
error_type=type(original_error).__name__,
|
|
210
|
+
)
|
|
211
|
+
)
|
|
212
|
+
runtime_cleanup_failures = await self._rollback_runtime_start(
|
|
213
|
+
lifecycle,
|
|
214
|
+
primary=hosted_failure,
|
|
215
|
+
)
|
|
216
|
+
raise ApplicationStartError(
|
|
217
|
+
primary=hosted_failure,
|
|
218
|
+
cleanup_failures=runtime_cleanup_failures,
|
|
219
|
+
) from hosted_failure
|
|
220
|
+
started = True
|
|
221
|
+
finally:
|
|
222
|
+
self._root.release_transition(token)
|
|
223
|
+
if started and (self._root.require_background_coordinator().has_runtime_failure_source):
|
|
224
|
+
self._root.runtime_monitor_task = asyncio.create_task(
|
|
225
|
+
self._monitor_runtime_failure(),
|
|
226
|
+
name=f"python-platform-runtime-monitor:{self.id}",
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
async def _rollback_runtime_start(
|
|
230
|
+
self,
|
|
231
|
+
lifecycle: _RuntimeLifecycle,
|
|
232
|
+
*,
|
|
233
|
+
primary: BaseException,
|
|
234
|
+
) -> tuple[_StartCleanupFailure, ...]:
|
|
235
|
+
self._root.state = ApplicationState.ROLLING_BACK
|
|
236
|
+
hosted = self._root.require_hosted_service_coordinator()
|
|
237
|
+
hosted.close_admission()
|
|
238
|
+
cleanup_failures: list[_StartCleanupFailure] = []
|
|
239
|
+
# bootstrap 是严格栈式 ownership:先穷尽已 touch 的 Hosted Service,
|
|
240
|
+
# 再撤销更早启动的 background,不能复用正常 stop 的并行收口策略。
|
|
241
|
+
try:
|
|
242
|
+
hosted_failures = await hosted.stop(ShutdownReason.START_FAILURE)
|
|
243
|
+
except BaseException as hosted_error:
|
|
244
|
+
cleanup_failures.append(
|
|
245
|
+
HostedServiceLifecycleError(
|
|
246
|
+
service="hosted-service-coordinator",
|
|
247
|
+
operation="stop",
|
|
248
|
+
error_type=type(hosted_error).__name__,
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
else:
|
|
252
|
+
cleanup_failures.extend(hosted_failures)
|
|
253
|
+
try:
|
|
254
|
+
await self._root.require_background_coordinator().stop()
|
|
255
|
+
except BaseException as background_error:
|
|
256
|
+
cleanup_failures.append(
|
|
257
|
+
_BackgroundExecutionStartFailure(original_error=background_error)
|
|
258
|
+
)
|
|
259
|
+
await self._root.require_invocation_runtime().stop_accepting_and_wait()
|
|
260
|
+
cleanup_failures.extend(
|
|
261
|
+
await lifecycle.shutdown_touched(reason=ShutdownReason.START_FAILURE)
|
|
262
|
+
)
|
|
263
|
+
cleanup_failures.extend(await lifecycle.close_container(exception=primary))
|
|
264
|
+
self._root.state = ApplicationState.FAILED_START
|
|
265
|
+
return tuple(cleanup_failures)
|
|
266
|
+
|
|
267
|
+
async def stop(self) -> None:
|
|
268
|
+
if self._root.state in (
|
|
269
|
+
ApplicationState.STOPPED,
|
|
270
|
+
ApplicationState.STOPPED_WITH_ERRORS,
|
|
271
|
+
ApplicationState.FAILED_START,
|
|
272
|
+
):
|
|
273
|
+
return
|
|
274
|
+
|
|
275
|
+
token = self._root.claim_transition(
|
|
276
|
+
operation="stop",
|
|
277
|
+
allowed=(ApplicationState.COMPOSED, ApplicationState.RUNNING),
|
|
278
|
+
)
|
|
279
|
+
self._root.state = ApplicationState.STOPPING
|
|
280
|
+
# shield 只保留弱引用;局部 task 同时承担 cleanup 的强 ownership。
|
|
281
|
+
cleanup_task = asyncio.create_task(self._complete_stop())
|
|
282
|
+
cancellation: asyncio.CancelledError | None = None
|
|
283
|
+
cleanup_failure: BaseException | None = None
|
|
284
|
+
try:
|
|
285
|
+
while not cleanup_task.done():
|
|
286
|
+
try:
|
|
287
|
+
await asyncio.shield(cleanup_task)
|
|
288
|
+
except asyncio.CancelledError as original_cancellation:
|
|
289
|
+
if cancellation is None:
|
|
290
|
+
cancellation = original_cancellation
|
|
291
|
+
cleanup_failure = cleanup_task.result()
|
|
292
|
+
finally:
|
|
293
|
+
self._root.release_transition(token)
|
|
294
|
+
|
|
295
|
+
if cancellation is not None:
|
|
296
|
+
if cleanup_failure is not None:
|
|
297
|
+
raise cancellation from cleanup_failure
|
|
298
|
+
raise cancellation
|
|
299
|
+
if cleanup_failure is not None:
|
|
300
|
+
raise cleanup_failure
|
|
301
|
+
|
|
302
|
+
async def _complete_stop(self) -> BaseException | None:
|
|
303
|
+
lifecycle = _RuntimeLifecycle(self._root)
|
|
304
|
+
reason = (
|
|
305
|
+
ShutdownReason.RUNTIME_FAILURE
|
|
306
|
+
if self._root.runtime_failure is not None
|
|
307
|
+
else ShutdownReason.NORMAL
|
|
308
|
+
)
|
|
309
|
+
hosted = self._root.require_hosted_service_coordinator()
|
|
310
|
+
# 先关闭 SDK thread bridge,再让 background 与 Hosted Service 并行停止生产新工作。
|
|
311
|
+
hosted.close_admission()
|
|
312
|
+
background_task = asyncio.create_task(
|
|
313
|
+
self._root.require_background_coordinator().stop(),
|
|
314
|
+
name=f"python-platform-background-stop:{self.id}",
|
|
315
|
+
)
|
|
316
|
+
hosted_task = asyncio.create_task(
|
|
317
|
+
hosted.stop(reason),
|
|
318
|
+
name=f"python-platform-hosted-service-stop:{self.id}",
|
|
319
|
+
)
|
|
320
|
+
background_outcome, hosted_outcome = await asyncio.gather(
|
|
321
|
+
background_task,
|
|
322
|
+
hosted_task,
|
|
323
|
+
return_exceptions=True,
|
|
324
|
+
)
|
|
325
|
+
infrastructure_failures: list[BaseException] = []
|
|
326
|
+
if isinstance(background_outcome, BaseException):
|
|
327
|
+
infrastructure_failures.append(background_outcome)
|
|
328
|
+
if isinstance(hosted_outcome, BaseException):
|
|
329
|
+
infrastructure_failures.append(hosted_outcome)
|
|
330
|
+
else:
|
|
331
|
+
infrastructure_failures.extend(hosted_outcome)
|
|
332
|
+
await self._root.require_invocation_runtime().stop_accepting_and_wait()
|
|
333
|
+
module_failures = await lifecycle.shutdown_touched(
|
|
334
|
+
reason=reason,
|
|
335
|
+
)
|
|
336
|
+
container_failures = await lifecycle.close_container(exception=None)
|
|
337
|
+
runtime_failure = self._root.runtime_failure
|
|
338
|
+
if runtime_failure is not None:
|
|
339
|
+
infrastructure_failures = [
|
|
340
|
+
remaining
|
|
341
|
+
for error in infrastructure_failures
|
|
342
|
+
if (remaining := _exclude_runtime_failure(error, runtime_failure)) is not None
|
|
343
|
+
]
|
|
344
|
+
cleanup_failures: tuple[BaseException, ...] = (
|
|
345
|
+
*infrastructure_failures,
|
|
346
|
+
*module_failures,
|
|
347
|
+
*container_failures,
|
|
348
|
+
)
|
|
349
|
+
terminal_failure: BaseException | None
|
|
350
|
+
if runtime_failure is not None and cleanup_failures:
|
|
351
|
+
terminal_failure = BaseExceptionGroup(
|
|
352
|
+
"Application runtime termination failed",
|
|
353
|
+
(runtime_failure, *cleanup_failures),
|
|
354
|
+
)
|
|
355
|
+
elif runtime_failure is not None:
|
|
356
|
+
terminal_failure = runtime_failure
|
|
357
|
+
elif cleanup_failures:
|
|
358
|
+
terminal_failure = BaseExceptionGroup(
|
|
359
|
+
"Application shutdown failed",
|
|
360
|
+
cleanup_failures,
|
|
361
|
+
)
|
|
362
|
+
else:
|
|
363
|
+
terminal_failure = None
|
|
364
|
+
self._root.state = (
|
|
365
|
+
ApplicationState.STOPPED_WITH_ERRORS
|
|
366
|
+
if terminal_failure is not None
|
|
367
|
+
else ApplicationState.STOPPED
|
|
368
|
+
)
|
|
369
|
+
self._root.termination_failure = terminal_failure
|
|
370
|
+
self._root.termination_event.set()
|
|
371
|
+
monitor = self._root.runtime_monitor_task
|
|
372
|
+
if (
|
|
373
|
+
runtime_failure is None
|
|
374
|
+
and monitor is not None
|
|
375
|
+
and monitor is not asyncio.current_task()
|
|
376
|
+
):
|
|
377
|
+
monitor.cancel()
|
|
378
|
+
await asyncio.gather(monitor, return_exceptions=True)
|
|
379
|
+
self._root.runtime_monitor_task = None
|
|
380
|
+
return terminal_failure
|
|
381
|
+
|
|
382
|
+
async def wait_for_termination(self) -> None:
|
|
383
|
+
"""等待正常 stop 或受监督 runtime fatal 完整清理,不暴露内部 Task/Future。"""
|
|
384
|
+
|
|
385
|
+
allowed = (
|
|
386
|
+
ApplicationState.RUNNING,
|
|
387
|
+
ApplicationState.STOPPING,
|
|
388
|
+
ApplicationState.STOPPED,
|
|
389
|
+
ApplicationState.STOPPED_WITH_ERRORS,
|
|
390
|
+
)
|
|
391
|
+
if self._root.state not in allowed:
|
|
392
|
+
raise InvalidApplicationStateError(
|
|
393
|
+
operation="wait_for_termination",
|
|
394
|
+
current=self._root.state,
|
|
395
|
+
allowed=allowed,
|
|
396
|
+
)
|
|
397
|
+
await self._root.termination_event.wait()
|
|
398
|
+
failure = self._root.termination_failure
|
|
399
|
+
if failure is not None:
|
|
400
|
+
raise failure
|
|
401
|
+
|
|
402
|
+
async def _monitor_runtime_failure(self) -> None:
|
|
403
|
+
current = asyncio.current_task()
|
|
404
|
+
try:
|
|
405
|
+
# HostedService 只拥有 start/stop;持久任务等其他能力仍保留自身故障传播。
|
|
406
|
+
failure = await self._root.require_background_coordinator().wait_for_runtime_failure()
|
|
407
|
+
if self._root.runtime_failure is None:
|
|
408
|
+
self._root.runtime_failure = ApplicationRuntimeError(
|
|
409
|
+
component=failure.component,
|
|
410
|
+
error_type=failure.error_type,
|
|
411
|
+
)
|
|
412
|
+
self._root.runtime_failure._source = failure._source
|
|
413
|
+
try:
|
|
414
|
+
await self.stop()
|
|
415
|
+
except InvalidApplicationStateError:
|
|
416
|
+
# 显式 stop 已拥有 transition 时,由同一 cleanup 路径发布终态。
|
|
417
|
+
await self._root.termination_event.wait()
|
|
418
|
+
except BaseException:
|
|
419
|
+
# stop 已缓存同一个脱敏终态错误;monitor 不制造未观察异常。
|
|
420
|
+
pass
|
|
421
|
+
finally:
|
|
422
|
+
if self._root.runtime_monitor_task is current:
|
|
423
|
+
self._root.runtime_monitor_task = None
|
|
424
|
+
|
|
425
|
+
async def call(
|
|
426
|
+
self,
|
|
427
|
+
function: Callable[..., Awaitable[_R]],
|
|
428
|
+
/,
|
|
429
|
+
*args: object,
|
|
430
|
+
**kwargs: object,
|
|
431
|
+
) -> _R:
|
|
432
|
+
"""调用异步入口,业务实参显式传入,服务参数由最终 Dishka 图补齐。"""
|
|
433
|
+
return await self.call_as(
|
|
434
|
+
self._root.require_invocation_runtime().current_user(),
|
|
435
|
+
function,
|
|
436
|
+
*args,
|
|
437
|
+
**kwargs,
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
async def call_as(
|
|
441
|
+
self,
|
|
442
|
+
current_user: CurrentUser,
|
|
443
|
+
function: Callable[..., Awaitable[_R]],
|
|
444
|
+
/,
|
|
445
|
+
*args: object,
|
|
446
|
+
**kwargs: object,
|
|
447
|
+
) -> _R:
|
|
448
|
+
"""可信身份与调用许可覆盖 REQUEST 创建、业务执行和最终资源释放。"""
|
|
449
|
+
runtime = self._root.require_invocation_runtime()
|
|
450
|
+
return await _invoke_callable(
|
|
451
|
+
self._root.require_container(),
|
|
452
|
+
runtime,
|
|
453
|
+
current_user,
|
|
454
|
+
str(uuid4()),
|
|
455
|
+
function,
|
|
456
|
+
args,
|
|
457
|
+
kwargs,
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
async def invoke(
|
|
461
|
+
self,
|
|
462
|
+
method: Callable[Concatenate[_TContract, _P], Awaitable[_R]],
|
|
463
|
+
/,
|
|
464
|
+
*args: _P.args,
|
|
465
|
+
**kwargs: _P.kwargs,
|
|
466
|
+
) -> _R:
|
|
467
|
+
"""匿名调用公开 Protocol method;可信 caller 需显式使用 invoke_as。"""
|
|
468
|
+
|
|
469
|
+
return await self.invoke_as(CurrentUser.anonymous(), method, *args, **kwargs)
|
|
470
|
+
|
|
471
|
+
async def invoke_as(
|
|
472
|
+
self,
|
|
473
|
+
current_user: CurrentUser,
|
|
474
|
+
method: Callable[Concatenate[_TContract, _P], Awaitable[_R]],
|
|
475
|
+
/,
|
|
476
|
+
*args: _P.args,
|
|
477
|
+
**kwargs: _P.kwargs,
|
|
478
|
+
) -> _R:
|
|
479
|
+
"""由可信 Host/test 传入已验证身份,身份只绑定当前 Application 与 asyncio Task。"""
|
|
480
|
+
|
|
481
|
+
if self._root.state is not ApplicationState.RUNNING:
|
|
482
|
+
raise ApplicationInvocationRejectedError(
|
|
483
|
+
reason=f"Application must be running, got {self._root.state.value}"
|
|
484
|
+
)
|
|
485
|
+
container = self._root.require_container()
|
|
486
|
+
invoker = await container.get(_ScopedApplicationServiceInvoker)
|
|
487
|
+
result = await invoker.invoke(
|
|
488
|
+
container,
|
|
489
|
+
current_user,
|
|
490
|
+
str(uuid4()),
|
|
491
|
+
cast(Callable[..., Awaitable[object]], method),
|
|
492
|
+
cast(tuple[object, ...], args),
|
|
493
|
+
cast(dict[str, object], kwargs),
|
|
494
|
+
)
|
|
495
|
+
return cast(_R, result)
|
|
496
|
+
|
|
497
|
+
@asynccontextmanager
|
|
498
|
+
async def _invocation_lease_for_integration(self) -> AsyncIterator[None]:
|
|
499
|
+
"""Web/internal entry 共用的 Application-local drain lease。"""
|
|
500
|
+
|
|
501
|
+
async with self._root.require_invocation_runtime().outer_lease():
|
|
502
|
+
yield
|
|
503
|
+
|
|
504
|
+
@asynccontextmanager
|
|
505
|
+
async def _http_request_lease_for_integration(self) -> AsyncIterator[None]:
|
|
506
|
+
# Phase 2 health contract 允许 composed/pre-start probe,但该 request 仍参与 stop drain;
|
|
507
|
+
# lease 中的 capability 会让随后进入 dispatcher 的业务 route 继续 fail closed。
|
|
508
|
+
async with self._root.require_invocation_runtime().outer_lease(allow_composed_request=True):
|
|
509
|
+
yield
|
|
510
|
+
|
|
511
|
+
@contextmanager
|
|
512
|
+
def _caller_context_for_integration(
|
|
513
|
+
self,
|
|
514
|
+
current_user: CurrentUser,
|
|
515
|
+
correlation_id: str,
|
|
516
|
+
) -> Iterator[None]:
|
|
517
|
+
"""HTTP bridge 的窄化入口;不公开 mutable principal 或 process-global current app。"""
|
|
518
|
+
|
|
519
|
+
runtime = self._root.require_invocation_runtime()
|
|
520
|
+
token = runtime.bind_caller(current_user, correlation_id)
|
|
521
|
+
try:
|
|
522
|
+
yield
|
|
523
|
+
finally:
|
|
524
|
+
runtime.reset_caller(token)
|
|
525
|
+
|
|
526
|
+
async def _invoke_registration(
|
|
527
|
+
self,
|
|
528
|
+
registration: ApplicationServiceRegistration,
|
|
529
|
+
method_name: str,
|
|
530
|
+
) -> object:
|
|
531
|
+
"""框架内部 contributor 仍走 dispatcher,但不发布 public single Contract。"""
|
|
532
|
+
|
|
533
|
+
if self._root.state is not ApplicationState.RUNNING:
|
|
534
|
+
raise ApplicationInvocationRejectedError(
|
|
535
|
+
reason=f"Application must be running, got {self._root.state.value}"
|
|
536
|
+
)
|
|
537
|
+
service = self.application_service_catalog._for_registration(registration)
|
|
538
|
+
method = service.methods.get(method_name)
|
|
539
|
+
if method is None:
|
|
540
|
+
raise AssertionError("internal contributor method is absent from its catalog entry")
|
|
541
|
+
container = self._root.require_container()
|
|
542
|
+
async with self._invocation_lease_for_integration():
|
|
543
|
+
with self._caller_context_for_integration(CurrentUser.anonymous(), str(uuid4())):
|
|
544
|
+
async with container(scope=Scope.REQUEST) as request_container:
|
|
545
|
+
dispatcher = await request_container.get(_ApplicationServiceDispatcher)
|
|
546
|
+
return await dispatcher.invoke(service, method, (), {})
|