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,171 @@
|
|
|
1
|
+
"""调用签名的 DI 分类与原生注入包装;作用域创建仍归各运行入口。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
from collections.abc import Awaitable, Callable, Mapping
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from types import MappingProxyType, SimpleNamespace
|
|
9
|
+
from typing import Annotated, Any, TypeVar, get_args, get_origin, get_type_hints
|
|
10
|
+
|
|
11
|
+
from dishka import AsyncContainer, BaseScope, DependencyKey
|
|
12
|
+
from dishka.integrations.base import default_parse_dependency, wrap_injection
|
|
13
|
+
|
|
14
|
+
_R = TypeVar("_R")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _resolved_parameter_hints(
|
|
18
|
+
function: Callable[..., object],
|
|
19
|
+
annotations: Mapping[str, object],
|
|
20
|
+
) -> dict[str, Any]:
|
|
21
|
+
# 入口只解释自己拥有的参数;业务 payload 的局部引用不属于 DI 参数契约。
|
|
22
|
+
try:
|
|
23
|
+
return get_type_hints(
|
|
24
|
+
SimpleNamespace(__annotations__=dict(annotations)),
|
|
25
|
+
globalns=function.__globals__,
|
|
26
|
+
include_extras=True,
|
|
27
|
+
)
|
|
28
|
+
except (NameError, TypeError) as error:
|
|
29
|
+
raise TypeError(f"{function.__qualname__}: cannot resolve callable annotations") from error
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _dependency_key(
|
|
33
|
+
parameter: inspect.Parameter,
|
|
34
|
+
hint: object,
|
|
35
|
+
container: AsyncContainer,
|
|
36
|
+
scope: BaseScope,
|
|
37
|
+
) -> DependencyKey | None:
|
|
38
|
+
explicit = default_parse_dependency(parameter, hint)
|
|
39
|
+
hint_type = get_args(hint)[0] if get_origin(hint) is Annotated else hint
|
|
40
|
+
key = explicit or DependencyKey(hint_type, "")
|
|
41
|
+
root = container
|
|
42
|
+
while root.parent_container is not None:
|
|
43
|
+
root = root.parent_container
|
|
44
|
+
registry = root.registry
|
|
45
|
+
while True:
|
|
46
|
+
factory = registry.get_factory(key)
|
|
47
|
+
if factory is not None:
|
|
48
|
+
if registry.scope > scope:
|
|
49
|
+
raise TypeError(
|
|
50
|
+
f"{parameter.name}: dependency {key} is unavailable in {scope.name}"
|
|
51
|
+
)
|
|
52
|
+
return key
|
|
53
|
+
if registry.child_registry is None:
|
|
54
|
+
break
|
|
55
|
+
registry = registry.child_registry
|
|
56
|
+
if explicit is not None:
|
|
57
|
+
raise TypeError(f"{parameter.name}: explicitly requested dependency {key} is unregistered")
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass(frozen=True, slots=True)
|
|
62
|
+
class _CallablePlan:
|
|
63
|
+
"""冻结一次签名解释;只存契约事实,不缓存容器或解析出的服务实例。"""
|
|
64
|
+
|
|
65
|
+
signature: inspect.Signature
|
|
66
|
+
dependencies: Mapping[str, DependencyKey]
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def compile(
|
|
70
|
+
cls,
|
|
71
|
+
function: Callable[..., object],
|
|
72
|
+
container: AsyncContainer,
|
|
73
|
+
scope: BaseScope,
|
|
74
|
+
*,
|
|
75
|
+
business_parameters: int = 0,
|
|
76
|
+
) -> _CallablePlan:
|
|
77
|
+
if not inspect.iscoroutinefunction(function):
|
|
78
|
+
raise TypeError("Application callable must be an async function or bound async method")
|
|
79
|
+
signature = inspect.signature(function)
|
|
80
|
+
annotations = {
|
|
81
|
+
name: parameter.annotation
|
|
82
|
+
for index, (name, parameter) in enumerate(signature.parameters.items())
|
|
83
|
+
if index >= business_parameters and parameter.annotation is not inspect.Parameter.empty
|
|
84
|
+
}
|
|
85
|
+
hints = _resolved_parameter_hints(function, annotations)
|
|
86
|
+
dependencies: dict[str, DependencyKey] = {}
|
|
87
|
+
business: list[inspect.Parameter] = []
|
|
88
|
+
for index, parameter in enumerate(signature.parameters.values()):
|
|
89
|
+
if parameter.kind in (parameter.VAR_POSITIONAL, parameter.VAR_KEYWORD):
|
|
90
|
+
raise TypeError(
|
|
91
|
+
f"{function.__qualname__}.{parameter.name}: "
|
|
92
|
+
"variadic entry parameters are unsupported"
|
|
93
|
+
)
|
|
94
|
+
key = (
|
|
95
|
+
None
|
|
96
|
+
if index < business_parameters
|
|
97
|
+
else _dependency_key(
|
|
98
|
+
parameter,
|
|
99
|
+
hints.get(parameter.name, parameter.annotation),
|
|
100
|
+
container,
|
|
101
|
+
scope,
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
if key is None:
|
|
105
|
+
business.append(parameter)
|
|
106
|
+
elif parameter.kind is parameter.POSITIONAL_ONLY:
|
|
107
|
+
raise TypeError(
|
|
108
|
+
f"{function.__qualname__}.{parameter.name}: "
|
|
109
|
+
"DI parameter cannot be positional-only"
|
|
110
|
+
)
|
|
111
|
+
else:
|
|
112
|
+
dependencies[parameter.name] = key
|
|
113
|
+
return cls(signature.replace(parameters=business), MappingProxyType(dependencies))
|
|
114
|
+
|
|
115
|
+
async def invoke(
|
|
116
|
+
self,
|
|
117
|
+
function: Callable[..., Awaitable[_R]],
|
|
118
|
+
container: AsyncContainer,
|
|
119
|
+
args: tuple[object, ...],
|
|
120
|
+
kwargs: dict[str, object],
|
|
121
|
+
) -> _R:
|
|
122
|
+
if self.dependencies.keys() & kwargs.keys():
|
|
123
|
+
raise TypeError("caller cannot override automatically injected parameters")
|
|
124
|
+
# 先验证全部业务实参,再解析任何依赖,避免错误调用触发 resource factory。
|
|
125
|
+
bound = self.signature.bind(*args, **kwargs)
|
|
126
|
+
bound.apply_defaults()
|
|
127
|
+
|
|
128
|
+
def parse(parameter: inspect.Parameter, hint: Any) -> DependencyKey | None:
|
|
129
|
+
return self.dependencies.get(parameter.name)
|
|
130
|
+
|
|
131
|
+
async def target(*args: object, **kwargs: object) -> _R:
|
|
132
|
+
return await function(*args, **kwargs)
|
|
133
|
+
|
|
134
|
+
# 原函数可能被多个 Application 共享;只给当前调用的 native wrapper 提供签名。
|
|
135
|
+
target.__signature__ = inspect.signature(function) # type: ignore[attr-defined]
|
|
136
|
+
target.__annotations__ = {}
|
|
137
|
+
injected = wrap_injection(
|
|
138
|
+
func=target,
|
|
139
|
+
container_getter=lambda args, kwargs: container,
|
|
140
|
+
is_async=True,
|
|
141
|
+
parse_dependency=parse,
|
|
142
|
+
)
|
|
143
|
+
# DI 参数从签名移除后位置会变化;普通业务参数按名称交给原函数。
|
|
144
|
+
positional = tuple(
|
|
145
|
+
bound.arguments[name]
|
|
146
|
+
for name, parameter in self.signature.parameters.items()
|
|
147
|
+
if parameter.kind is parameter.POSITIONAL_ONLY
|
|
148
|
+
)
|
|
149
|
+
named = {
|
|
150
|
+
name: value
|
|
151
|
+
for name, value in bound.arguments.items()
|
|
152
|
+
if self.signature.parameters[name].kind is not inspect.Parameter.POSITIONAL_ONLY
|
|
153
|
+
}
|
|
154
|
+
return await injected(*positional, **named)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
async def _call_in_scope(
|
|
158
|
+
function: Callable[..., Awaitable[_R]],
|
|
159
|
+
container: AsyncContainer,
|
|
160
|
+
*args: object,
|
|
161
|
+
**kwargs: object,
|
|
162
|
+
) -> _R:
|
|
163
|
+
# handler 的既有 payload/context 是入口契约,即使其类型也可解析也不能被 DI 替换。
|
|
164
|
+
from ..application_services.invocation import _InvocationRuntime
|
|
165
|
+
|
|
166
|
+
plan = _CallablePlan.compile(
|
|
167
|
+
function, container, container.scope, business_parameters=len(args)
|
|
168
|
+
)
|
|
169
|
+
runtime = await container.get(_InvocationRuntime)
|
|
170
|
+
with runtime.call_scope(container):
|
|
171
|
+
return await plan.invoke(function, container, args, kwargs)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""跨 Application Service、Event 与 persistence 共享的只读 invocation facts。"""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import TypeGuard
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
_CORRELATION_ID_MAX_LENGTH = 128
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _is_safe_correlation_id(value: object) -> TypeGuard[str]:
|
|
11
|
+
"""所有入口共用同一 correlation 标识安全契约。"""
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
isinstance(value, str)
|
|
15
|
+
and bool(value)
|
|
16
|
+
and value == value.strip()
|
|
17
|
+
and len(value) <= _CORRELATION_ID_MAX_LENGTH
|
|
18
|
+
and value.isascii()
|
|
19
|
+
and value.isprintable()
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True, slots=True)
|
|
24
|
+
class ApplicationInvocationContext:
|
|
25
|
+
application_id: UUID
|
|
26
|
+
invocation_id: UUID
|
|
27
|
+
parent_invocation_id: UUID | None
|
|
28
|
+
connection_name: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True, slots=True)
|
|
32
|
+
class BackgroundSystemCaller:
|
|
33
|
+
"""只以 type 表达 framework system identity,不携带用户授权事实。"""
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""已登记运行入口的构建期签名验证,读取最终原生图而不实例化处理器。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Awaitable, Callable, Mapping
|
|
6
|
+
from typing import cast
|
|
7
|
+
|
|
8
|
+
from dishka import AsyncContainer, BaseScope, Scope
|
|
9
|
+
|
|
10
|
+
from ..background_jobs import BackgroundJobCatalog
|
|
11
|
+
from ..background_workers import BackgroundWorkerCatalog
|
|
12
|
+
from ..events import LocalEventCatalog, LocalEventPhase
|
|
13
|
+
from .callables import _CallablePlan
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _validate_callable_entries(
|
|
17
|
+
container: AsyncContainer,
|
|
18
|
+
jobs: BackgroundJobCatalog,
|
|
19
|
+
workers: BackgroundWorkerCatalog,
|
|
20
|
+
events: LocalEventCatalog,
|
|
21
|
+
*,
|
|
22
|
+
event_implementations: Mapping[type[object], type[object]],
|
|
23
|
+
) -> None:
|
|
24
|
+
for job in jobs.definitions:
|
|
25
|
+
_validate_entry(container, job.handler_type, "execute", 2, Scope.ACTION)
|
|
26
|
+
for worker in workers.definitions:
|
|
27
|
+
_validate_entry(container, worker.worker_type, "run_iteration", 1, Scope.ACTION)
|
|
28
|
+
for subscription in events.subscriptions:
|
|
29
|
+
scope = Scope.ACTION if subscription.phase is LocalEventPhase.DOMAIN else Scope.REQUEST
|
|
30
|
+
target = subscription.handler_type
|
|
31
|
+
if isinstance(target, type):
|
|
32
|
+
target = event_implementations[target]
|
|
33
|
+
_validate_entry(container, target, "handle", 1, scope)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _validate_entry(
|
|
37
|
+
container: AsyncContainer,
|
|
38
|
+
target: type[object] | Callable[..., Awaitable[None]],
|
|
39
|
+
method_name: str,
|
|
40
|
+
business_parameters: int,
|
|
41
|
+
scope: BaseScope,
|
|
42
|
+
) -> None:
|
|
43
|
+
if isinstance(target, type):
|
|
44
|
+
function = cast(Callable[..., Awaitable[None]], getattr(target, method_name))
|
|
45
|
+
business_parameters += 1 # 未绑定方法的 self 也是调用入口负责的参数。
|
|
46
|
+
else:
|
|
47
|
+
function = target
|
|
48
|
+
plan = _CallablePlan.compile(
|
|
49
|
+
function, container, scope, business_parameters=business_parameters
|
|
50
|
+
)
|
|
51
|
+
try:
|
|
52
|
+
plan.signature.bind(*(object() for _ in range(business_parameters)))
|
|
53
|
+
except TypeError as error:
|
|
54
|
+
raise TypeError(f"{function.__qualname__}: unresolved entry parameters: {error}") from error
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Application 与 HostedService 共用的函数入口作用域和调用许可。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Awaitable, Callable
|
|
6
|
+
from typing import TYPE_CHECKING, TypeVar
|
|
7
|
+
|
|
8
|
+
from dishka import AsyncContainer, Scope
|
|
9
|
+
|
|
10
|
+
from ..application_services.errors import ApplicationInvocationRejectedError
|
|
11
|
+
from ..authorization import CurrentUser
|
|
12
|
+
from .callables import _CallablePlan
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from ..application_services.invocation import _InvocationRuntime
|
|
16
|
+
|
|
17
|
+
_R = TypeVar("_R")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def _invoke_callable(
|
|
21
|
+
container: AsyncContainer,
|
|
22
|
+
runtime: _InvocationRuntime,
|
|
23
|
+
current_user: CurrentUser,
|
|
24
|
+
correlation_id: str,
|
|
25
|
+
function: Callable[..., Awaitable[_R]],
|
|
26
|
+
args: tuple[object, ...],
|
|
27
|
+
kwargs: dict[str, object],
|
|
28
|
+
) -> _R:
|
|
29
|
+
async with runtime.outer_lease():
|
|
30
|
+
ambient = runtime.current()
|
|
31
|
+
existing = ambient.action_container if ambient is not None else runtime.current_call_scope()
|
|
32
|
+
if existing is not None:
|
|
33
|
+
# REQUEST 已缓存的 CurrentUser 必须与授权/审计读取的身份一致。
|
|
34
|
+
# 同一调用作用域内不得通过 call_as 改变身份或污染外层缓存。
|
|
35
|
+
if current_user != runtime.current_user():
|
|
36
|
+
raise ApplicationInvocationRejectedError(
|
|
37
|
+
reason="cannot switch caller identity inside an existing invocation scope",
|
|
38
|
+
)
|
|
39
|
+
plan = _CallablePlan.compile(function, existing, existing.scope)
|
|
40
|
+
return await plan.invoke(function, existing, args, kwargs)
|
|
41
|
+
token = runtime.bind_caller(current_user, correlation_id)
|
|
42
|
+
try:
|
|
43
|
+
plan = _CallablePlan.compile(function, container, Scope.REQUEST)
|
|
44
|
+
async with container(scope=Scope.REQUEST) as request:
|
|
45
|
+
with runtime.call_scope(request):
|
|
46
|
+
return await plan.invoke(function, request, args, kwargs)
|
|
47
|
+
finally:
|
|
48
|
+
runtime.reset_caller(token)
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""正式 Job/Worker/Handler 的扩展链;编译规则复用服务入口的同一权威。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Awaitable, Callable, Mapping
|
|
6
|
+
from dataclasses import dataclass, replace
|
|
7
|
+
from types import MappingProxyType
|
|
8
|
+
from typing import cast
|
|
9
|
+
|
|
10
|
+
from dishka import AsyncContainer, Scope
|
|
11
|
+
|
|
12
|
+
from ..application_services.catalog import (
|
|
13
|
+
ApplicationServiceMethod,
|
|
14
|
+
_match_interceptors,
|
|
15
|
+
_method_source,
|
|
16
|
+
)
|
|
17
|
+
from ..application_services.execution import _InterceptorPipeline
|
|
18
|
+
from ..application_services.interceptors import (
|
|
19
|
+
InterceptionStage,
|
|
20
|
+
InterceptorRegistration,
|
|
21
|
+
_InterceptorRule,
|
|
22
|
+
)
|
|
23
|
+
from ..application_services.invocation import _InvocationRuntime
|
|
24
|
+
from ..events.catalog import LocalEventCatalog
|
|
25
|
+
from ..events.contracts import LocalEventPhase
|
|
26
|
+
from ..modularity import ModuleKey, ModuleRegistry
|
|
27
|
+
from ..unit_of_work.options import _UNIT_OF_WORK_ATTRIBUTE, UnitOfWorkDeclaration, _declaration
|
|
28
|
+
from .callables import _call_in_scope
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True, slots=True)
|
|
32
|
+
class _CompiledEntry:
|
|
33
|
+
descriptor: ApplicationServiceMethod
|
|
34
|
+
steps: Mapping[InterceptionStage, tuple[InterceptorRegistration, ...]]
|
|
35
|
+
outer_scope: Scope
|
|
36
|
+
method_scope: Scope
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class _EntryInterceptionCatalog:
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
registry: ModuleRegistry,
|
|
43
|
+
events: LocalEventCatalog,
|
|
44
|
+
rules: tuple[_InterceptorRule, ...],
|
|
45
|
+
) -> None:
|
|
46
|
+
entries: dict[tuple[object, str], _CompiledEntry] = {}
|
|
47
|
+
for module in registry.dependency_order:
|
|
48
|
+
for worker in module.module_type.background_worker_definitions:
|
|
49
|
+
self._add(entries, worker.worker_type, "run_iteration", module.key, rules, True)
|
|
50
|
+
for job in module.module_type.background_job_definitions:
|
|
51
|
+
self._add(entries, job.handler_type, "execute", module.key, rules, False)
|
|
52
|
+
for subscription in events.subscriptions:
|
|
53
|
+
scope = Scope.ACTION if subscription.phase is LocalEventPhase.DOMAIN else Scope.REQUEST
|
|
54
|
+
self._add(
|
|
55
|
+
entries,
|
|
56
|
+
subscription.handler_type,
|
|
57
|
+
"handle",
|
|
58
|
+
subscription.owner,
|
|
59
|
+
rules,
|
|
60
|
+
False,
|
|
61
|
+
outer_scope=scope,
|
|
62
|
+
method_scope=scope,
|
|
63
|
+
)
|
|
64
|
+
self._entries: Mapping[tuple[object, str], _CompiledEntry] = MappingProxyType(entries)
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def interceptor_scopes(self) -> tuple[tuple[InterceptorRegistration, Scope], ...]:
|
|
68
|
+
return tuple(
|
|
69
|
+
(
|
|
70
|
+
registration,
|
|
71
|
+
entry.method_scope
|
|
72
|
+
if registration.before is InterceptionStage.METHOD
|
|
73
|
+
else entry.outer_scope,
|
|
74
|
+
)
|
|
75
|
+
for entry in self._entries.values()
|
|
76
|
+
for registration in entry.descriptor.interceptors
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def _add(
|
|
81
|
+
entries: dict[tuple[object, str], _CompiledEntry],
|
|
82
|
+
target: object,
|
|
83
|
+
name: str,
|
|
84
|
+
owner: ModuleKey,
|
|
85
|
+
rules: tuple[_InterceptorRule, ...],
|
|
86
|
+
worker: bool,
|
|
87
|
+
*,
|
|
88
|
+
outer_scope: Scope = Scope.REQUEST,
|
|
89
|
+
method_scope: Scope = Scope.ACTION,
|
|
90
|
+
) -> None:
|
|
91
|
+
if (target, name) in entries:
|
|
92
|
+
current = entries[target, name]
|
|
93
|
+
# 同一 handler 可订阅两个 phase;构建时使用所有实际入口的最窄解析边界。
|
|
94
|
+
entries[target, name] = replace(
|
|
95
|
+
current,
|
|
96
|
+
outer_scope=min(current.outer_scope, outer_scope),
|
|
97
|
+
method_scope=min(current.method_scope, method_scope),
|
|
98
|
+
)
|
|
99
|
+
return
|
|
100
|
+
function = getattr(target, name) if isinstance(target, type) else target
|
|
101
|
+
implementation = target if isinstance(target, type) else type(target)
|
|
102
|
+
declaration = _declaration(implementation, function)
|
|
103
|
+
if worker and not any(
|
|
104
|
+
hasattr(item, _UNIT_OF_WORK_ATTRIBUTE) for item in (implementation, function)
|
|
105
|
+
):
|
|
106
|
+
declaration = UnitOfWorkDeclaration(disabled=True)
|
|
107
|
+
source = _method_source(function, owner)
|
|
108
|
+
descriptor = ApplicationServiceMethod(
|
|
109
|
+
name=name,
|
|
110
|
+
source=source,
|
|
111
|
+
requires_authenticated_user=False,
|
|
112
|
+
permissions=(),
|
|
113
|
+
allows_anonymous=True,
|
|
114
|
+
service=implementation,
|
|
115
|
+
implementation=implementation,
|
|
116
|
+
method=cast(Callable[..., Awaitable[object]], function),
|
|
117
|
+
validation_enabled=True,
|
|
118
|
+
auditing_enabled=False,
|
|
119
|
+
policy_sources=(source,),
|
|
120
|
+
unit_of_work=declaration,
|
|
121
|
+
)
|
|
122
|
+
descriptor = replace(descriptor, interceptors=_match_interceptors(descriptor, rules))
|
|
123
|
+
entries[target, name] = _CompiledEntry(
|
|
124
|
+
descriptor,
|
|
125
|
+
MappingProxyType(
|
|
126
|
+
{
|
|
127
|
+
stage: tuple(item for item in descriptor.interceptors if item.before is stage)
|
|
128
|
+
for stage in InterceptionStage
|
|
129
|
+
}
|
|
130
|
+
),
|
|
131
|
+
outer_scope,
|
|
132
|
+
method_scope,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
async def invoke(
|
|
136
|
+
self,
|
|
137
|
+
runtime: _InvocationRuntime,
|
|
138
|
+
container: AsyncContainer,
|
|
139
|
+
target: object,
|
|
140
|
+
name: str,
|
|
141
|
+
args: tuple[object, ...],
|
|
142
|
+
*,
|
|
143
|
+
connection_name: str | None = None,
|
|
144
|
+
) -> object:
|
|
145
|
+
entry = self._entries[target, name]
|
|
146
|
+
pipeline = _InterceptorPipeline(entry.descriptor, entry.steps, args, {})
|
|
147
|
+
|
|
148
|
+
async def call(scope: AsyncContainer) -> object:
|
|
149
|
+
instance: object = (
|
|
150
|
+
await scope.get(cast(type[object], target)) if isinstance(target, type) else None
|
|
151
|
+
)
|
|
152
|
+
function = getattr(instance, name) if instance is not None else target
|
|
153
|
+
return await _call_in_scope(
|
|
154
|
+
cast(Callable[..., Awaitable[object]], function),
|
|
155
|
+
scope,
|
|
156
|
+
*pipeline.arguments.args,
|
|
157
|
+
**pipeline.arguments.kwargs,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
async def method(scope: AsyncContainer) -> object:
|
|
161
|
+
return await pipeline.run(InterceptionStage.METHOD, scope, lambda: call(scope))
|
|
162
|
+
|
|
163
|
+
async def boundary() -> object:
|
|
164
|
+
if connection_name is None:
|
|
165
|
+
# Domain 参与原事务;Post-Commit 自身不复活旧 UoW,服务调用仍会建立新边界。
|
|
166
|
+
return await method(container)
|
|
167
|
+
return await runtime.run_action(
|
|
168
|
+
container,
|
|
169
|
+
connection_name=connection_name,
|
|
170
|
+
callback=method,
|
|
171
|
+
declaration=entry.descriptor.unit_of_work,
|
|
172
|
+
method_name=name,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
stages = tuple(
|
|
176
|
+
stage for stage in InterceptionStage if stage is not InterceptionStage.METHOD
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
async def run(index: int) -> object:
|
|
180
|
+
if index == len(stages):
|
|
181
|
+
return await boundary()
|
|
182
|
+
return await pipeline.run(stages[index], container, lambda: run(index + 1))
|
|
183
|
+
|
|
184
|
+
return await run(0)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Lifecycle state 与按 stage 限权的 context capability。"""
|
|
2
|
+
|
|
3
|
+
from .composition import ConfigureContext, PostConfigureContext, PreConfigureContext
|
|
4
|
+
from .context import ApplicationView, LifecycleContext
|
|
5
|
+
from .runtime import (
|
|
6
|
+
InitializeContext,
|
|
7
|
+
PostInitializeContext,
|
|
8
|
+
PreInitializeContext,
|
|
9
|
+
ShutdownContext,
|
|
10
|
+
)
|
|
11
|
+
from .state import ApplicationState, ShutdownReason
|
|
12
|
+
|
|
13
|
+
__all__ = (
|
|
14
|
+
"ApplicationState",
|
|
15
|
+
"ApplicationView",
|
|
16
|
+
"ConfigureContext",
|
|
17
|
+
"InitializeContext",
|
|
18
|
+
"LifecycleContext",
|
|
19
|
+
"PostConfigureContext",
|
|
20
|
+
"PostInitializeContext",
|
|
21
|
+
"PreConfigureContext",
|
|
22
|
+
"PreInitializeContext",
|
|
23
|
+
"ShutdownContext",
|
|
24
|
+
"ShutdownReason",
|
|
25
|
+
)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""同步 composition stages 各自可见的最小 capability context。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from ..configuration import ConfigurationRoot
|
|
8
|
+
from ..events import EventContributor
|
|
9
|
+
from ..options import PreOptionsSnapshot
|
|
10
|
+
from ..options.contribution import _OptionsContributor
|
|
11
|
+
from ..services import ServiceContributor
|
|
12
|
+
from .context import LifecycleContext
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True, slots=True)
|
|
16
|
+
class PreConfigureContext(LifecycleContext):
|
|
17
|
+
configuration: ConfigurationRoot
|
|
18
|
+
pre_configure: _OptionsContributor
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True, slots=True)
|
|
22
|
+
class ConfigureContext(LifecycleContext):
|
|
23
|
+
configuration: ConfigurationRoot
|
|
24
|
+
pre_options: PreOptionsSnapshot
|
|
25
|
+
configure: _OptionsContributor
|
|
26
|
+
services: ServiceContributor
|
|
27
|
+
events: EventContributor
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True, slots=True)
|
|
31
|
+
class PostConfigureContext(LifecycleContext):
|
|
32
|
+
configuration: ConfigurationRoot
|
|
33
|
+
pre_options: PreOptionsSnapshot
|
|
34
|
+
configure: _OptionsContributor
|
|
35
|
+
services: ServiceContributor
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""所有 lifecycle stage 共享的只读 Application view 与 context 根。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from uuid import UUID
|
|
8
|
+
|
|
9
|
+
from ..diagnostics import LifecycleStage
|
|
10
|
+
from ..modularity import ModuleDescriptor, ModuleGraphSnapshot
|
|
11
|
+
from .state import ApplicationState
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True, slots=True)
|
|
15
|
+
class ApplicationView:
|
|
16
|
+
application_id: UUID
|
|
17
|
+
state: ApplicationState | None
|
|
18
|
+
modules: ModuleGraphSnapshot
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True, slots=True)
|
|
22
|
+
class LifecycleContext:
|
|
23
|
+
application_id: UUID
|
|
24
|
+
descriptor: ModuleDescriptor
|
|
25
|
+
stage: LifecycleStage
|
|
26
|
+
application: ApplicationView
|
|
27
|
+
_emit: Callable[[str], None] = field(repr=False, compare=False)
|
|
28
|
+
|
|
29
|
+
def emit(self, code: str) -> None:
|
|
30
|
+
"""写入当前 Module/stage 已限定作用域的稳定诊断 code。"""
|
|
31
|
+
self._emit(code)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""异步 runtime 与 shutdown stages 的 native container context。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from dishka import AsyncContainer
|
|
8
|
+
|
|
9
|
+
from ..configuration import ConfigurationRoot
|
|
10
|
+
from ..diagnostics import LifecycleStage
|
|
11
|
+
from ..options import OptionsSnapshot
|
|
12
|
+
from .context import LifecycleContext
|
|
13
|
+
from .state import ShutdownReason
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True, slots=True)
|
|
17
|
+
class PreInitializeContext(LifecycleContext):
|
|
18
|
+
configuration: ConfigurationRoot
|
|
19
|
+
options: OptionsSnapshot
|
|
20
|
+
container: AsyncContainer
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True, slots=True)
|
|
24
|
+
class InitializeContext(LifecycleContext):
|
|
25
|
+
configuration: ConfigurationRoot
|
|
26
|
+
options: OptionsSnapshot
|
|
27
|
+
container: AsyncContainer
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True, slots=True)
|
|
31
|
+
class PostInitializeContext(LifecycleContext):
|
|
32
|
+
configuration: ConfigurationRoot
|
|
33
|
+
options: OptionsSnapshot
|
|
34
|
+
container: AsyncContainer
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True, slots=True)
|
|
38
|
+
class ShutdownContext(LifecycleContext):
|
|
39
|
+
configuration: ConfigurationRoot
|
|
40
|
+
options: OptionsSnapshot
|
|
41
|
+
reason: ShutdownReason
|
|
42
|
+
completed_stages: tuple[LifecycleStage, ...]
|
|
43
|
+
container: AsyncContainer
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Application lifecycle 的公开状态与 shutdown reason。"""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ApplicationState(str, Enum):
|
|
7
|
+
COMPOSED = "composed"
|
|
8
|
+
STARTING = "starting"
|
|
9
|
+
RUNNING = "running"
|
|
10
|
+
ROLLING_BACK = "rolling_back"
|
|
11
|
+
FAILED_START = "failed_start"
|
|
12
|
+
STOPPING = "stopping"
|
|
13
|
+
STOPPED = "stopped"
|
|
14
|
+
STOPPED_WITH_ERRORS = "stopped_with_errors"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ShutdownReason(str, Enum):
|
|
18
|
+
NORMAL = "normal"
|
|
19
|
+
START_FAILURE = "start_failure"
|
|
20
|
+
RUNTIME_FAILURE = "runtime_failure"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Module contracts、graph builder 与 immutable registry capability。"""
|
|
2
|
+
|
|
3
|
+
from .contracts import AppModule, ModuleDependency, ModuleDependencyEdge, ModuleKey, ModuleRef
|
|
4
|
+
from .registry import ModuleDescriptor, ModuleGraphSnapshot, ModuleRegistry
|
|
5
|
+
|
|
6
|
+
__all__ = (
|
|
7
|
+
"AppModule",
|
|
8
|
+
"ModuleDependency",
|
|
9
|
+
"ModuleDependencyEdge",
|
|
10
|
+
"ModuleDescriptor",
|
|
11
|
+
"ModuleGraphSnapshot",
|
|
12
|
+
"ModuleKey",
|
|
13
|
+
"ModuleRef",
|
|
14
|
+
"ModuleRegistry",
|
|
15
|
+
)
|