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,423 @@
|
|
|
1
|
+
"""受管理服务代理与业务管线编排;不拥有 Application admission 或 Session。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
import time
|
|
8
|
+
from collections.abc import Awaitable, Callable
|
|
9
|
+
from contextvars import ContextVar
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from datetime import UTC, datetime
|
|
12
|
+
from typing import TYPE_CHECKING, cast
|
|
13
|
+
from uuid import uuid4
|
|
14
|
+
|
|
15
|
+
from dishka import AsyncContainer
|
|
16
|
+
from dishka.exceptions import NoFactoryError
|
|
17
|
+
|
|
18
|
+
from ..auditing import ApplicationServiceAuditRecord, AuditSink
|
|
19
|
+
from ..auditing.control import AuditingControl, AuditingOptions
|
|
20
|
+
from ..authorization import PermissionChecker
|
|
21
|
+
from ..authorization.contracts import _AuthorizationRequirement
|
|
22
|
+
from ..authorization.errors import (
|
|
23
|
+
PermissionCheckerUnavailableError,
|
|
24
|
+
PermissionDeniedError,
|
|
25
|
+
UnauthenticatedError,
|
|
26
|
+
)
|
|
27
|
+
from ..observability.context import invocation_span
|
|
28
|
+
from ..options import Options
|
|
29
|
+
from ..settings.provider import SettingProvider
|
|
30
|
+
from ..unit_of_work import UnitOfWorkManager
|
|
31
|
+
from .catalog import (
|
|
32
|
+
_APPLICATION_SERVICE_COMPONENT,
|
|
33
|
+
ApplicationServiceCatalog,
|
|
34
|
+
ApplicationServiceRegistration,
|
|
35
|
+
_CompiledApplicationService,
|
|
36
|
+
_CompiledMethod,
|
|
37
|
+
)
|
|
38
|
+
from .contracts import ApplicationService
|
|
39
|
+
from .errors import (
|
|
40
|
+
ApplicationServiceValidationError,
|
|
41
|
+
ApplicationServiceValidationIssue,
|
|
42
|
+
CrossTaskInvocationError,
|
|
43
|
+
NestedConnectionError,
|
|
44
|
+
)
|
|
45
|
+
from .execution import _InterceptorPipeline
|
|
46
|
+
from .interceptors import InterceptionStage
|
|
47
|
+
from .invocation import _current_task, _InvocationRuntime
|
|
48
|
+
from .validation import _validate_arguments
|
|
49
|
+
|
|
50
|
+
_LOGGER = logging.getLogger(__name__)
|
|
51
|
+
|
|
52
|
+
if TYPE_CHECKING:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(slots=True)
|
|
57
|
+
class _HttpInvocationState:
|
|
58
|
+
service: _CompiledApplicationService
|
|
59
|
+
method: _CompiledMethod
|
|
60
|
+
owner_task: asyncio.Task[object]
|
|
61
|
+
pipeline: _InterceptorPipeline
|
|
62
|
+
consumed: bool = False
|
|
63
|
+
is_short_circuited: Callable[[], bool] = lambda: False
|
|
64
|
+
defer_audit: Callable[[Callable[[BaseException | None], Awaitable[None]]], None] | None = None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class _ApplicationServiceProxy:
|
|
68
|
+
"""REQUEST-local Protocol object;只把 cataloged method 转交给统一 dispatcher。"""
|
|
69
|
+
|
|
70
|
+
__slots__ = ("_dispatcher", "_service")
|
|
71
|
+
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
dispatcher: _ApplicationServiceDispatcher,
|
|
75
|
+
service: _CompiledApplicationService,
|
|
76
|
+
) -> None:
|
|
77
|
+
self._dispatcher = dispatcher
|
|
78
|
+
self._service = service
|
|
79
|
+
|
|
80
|
+
def __getattr__(self, name: str) -> object:
|
|
81
|
+
method = self._service.methods.get(name)
|
|
82
|
+
if method is None:
|
|
83
|
+
raise AttributeError(name)
|
|
84
|
+
|
|
85
|
+
async def invoke(*args: object, **kwargs: object) -> object:
|
|
86
|
+
return await self._dispatcher.invoke(self._service, method, args, kwargs)
|
|
87
|
+
|
|
88
|
+
return invoke
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class _ApplicationServiceDispatcher:
|
|
92
|
+
"""所有 caller 共用的 validation、ownership、UoW 与 event 顺序 owner。"""
|
|
93
|
+
|
|
94
|
+
__slots__ = ("_catalog", "_container", "_http_boundary", "_runtime")
|
|
95
|
+
|
|
96
|
+
def __init__(
|
|
97
|
+
self,
|
|
98
|
+
container: AsyncContainer,
|
|
99
|
+
runtime: _InvocationRuntime,
|
|
100
|
+
catalog: ApplicationServiceCatalog,
|
|
101
|
+
) -> None:
|
|
102
|
+
self._container = container
|
|
103
|
+
self._runtime = runtime
|
|
104
|
+
self._catalog = catalog
|
|
105
|
+
self._http_boundary: ContextVar[_HttpInvocationState | None] = ContextVar(
|
|
106
|
+
f"python_platform_http_invocation_{runtime.application_id}_{id(self)}",
|
|
107
|
+
default=None,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
async def invoke(
|
|
111
|
+
self,
|
|
112
|
+
service: _CompiledApplicationService,
|
|
113
|
+
method: _CompiledMethod,
|
|
114
|
+
args: tuple[object, ...],
|
|
115
|
+
kwargs: dict[str, object],
|
|
116
|
+
) -> object:
|
|
117
|
+
pipeline = _InterceptorPipeline(method.descriptor, method.interceptor_steps, args, kwargs)
|
|
118
|
+
async with self._runtime.outer_lease():
|
|
119
|
+
return await self._invoke_entry(
|
|
120
|
+
service,
|
|
121
|
+
method,
|
|
122
|
+
pipeline,
|
|
123
|
+
lambda: self._invoke_after_authorization(service, method, pipeline),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
async def _invoke_entry(
|
|
127
|
+
self,
|
|
128
|
+
service: _CompiledApplicationService,
|
|
129
|
+
method: _CompiledMethod,
|
|
130
|
+
pipeline: _InterceptorPipeline,
|
|
131
|
+
callback: Callable[[], Awaitable[object]],
|
|
132
|
+
) -> object:
|
|
133
|
+
async def authorize() -> object:
|
|
134
|
+
if not service.registration.is_integration_service:
|
|
135
|
+
await self._authorize(method.authorization)
|
|
136
|
+
return await callback()
|
|
137
|
+
|
|
138
|
+
async def audit() -> object:
|
|
139
|
+
occurred_at = datetime.now(UTC)
|
|
140
|
+
started_ns = time.perf_counter_ns()
|
|
141
|
+
failure: BaseException | None = None
|
|
142
|
+
try:
|
|
143
|
+
return await pipeline.run(
|
|
144
|
+
InterceptionStage.AUTHORIZATION, self._container, authorize
|
|
145
|
+
)
|
|
146
|
+
except BaseException as error:
|
|
147
|
+
failure = error
|
|
148
|
+
raise
|
|
149
|
+
finally:
|
|
150
|
+
|
|
151
|
+
async def write(final_failure: BaseException | None) -> None:
|
|
152
|
+
await self._write_audit(
|
|
153
|
+
service.registration,
|
|
154
|
+
method,
|
|
155
|
+
occurred_at=occurred_at,
|
|
156
|
+
duration_ns=time.perf_counter_ns() - started_ns,
|
|
157
|
+
failure=final_failure,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
boundary = self._http_boundary.get()
|
|
161
|
+
if (
|
|
162
|
+
boundary is not None
|
|
163
|
+
and boundary.pipeline is pipeline
|
|
164
|
+
and boundary.defer_audit is not None
|
|
165
|
+
):
|
|
166
|
+
boundary.defer_audit(write)
|
|
167
|
+
else:
|
|
168
|
+
await write(failure)
|
|
169
|
+
|
|
170
|
+
async def trace() -> object:
|
|
171
|
+
with invocation_span(self._runtime.tracer, method.contract_method.__qualname__):
|
|
172
|
+
return await pipeline.run(InterceptionStage.AUDITING, self._container, audit)
|
|
173
|
+
|
|
174
|
+
return await pipeline.run(InterceptionStage.TRACING, self._container, trace)
|
|
175
|
+
|
|
176
|
+
async def invoke_http_request(
|
|
177
|
+
self,
|
|
178
|
+
service: _CompiledApplicationService,
|
|
179
|
+
method: _CompiledMethod,
|
|
180
|
+
callback: Callable[[], Awaitable[object]],
|
|
181
|
+
*,
|
|
182
|
+
is_short_circuited: Callable[[], bool] = lambda: False,
|
|
183
|
+
defer_audit: Callable[[Callable[[BaseException | None], Awaitable[None]]], None]
|
|
184
|
+
| None = None,
|
|
185
|
+
) -> object:
|
|
186
|
+
"""原生绑定在授权内执行;同一 HTTP 调用复用编译链且只计一次授权与审计。"""
|
|
187
|
+
async with self._runtime.outer_lease():
|
|
188
|
+
if self._http_boundary.get() is not None:
|
|
189
|
+
raise RuntimeError("HTTP Application Service boundary is already active")
|
|
190
|
+
pipeline = _InterceptorPipeline(method.descriptor, method.interceptor_steps, (), {})
|
|
191
|
+
state = _HttpInvocationState(service, method, _current_task(), pipeline)
|
|
192
|
+
state.is_short_circuited = is_short_circuited
|
|
193
|
+
state.defer_audit = defer_audit
|
|
194
|
+
token = self._http_boundary.set(state)
|
|
195
|
+
|
|
196
|
+
async def invoke_endpoint() -> object:
|
|
197
|
+
result = await callback()
|
|
198
|
+
# 外层 Interceptor 可合法短路;一旦进入原生绑定,必须消费原 endpoint 许可。
|
|
199
|
+
if not state.consumed and not state.is_short_circuited():
|
|
200
|
+
raise RuntimeError("HTTP Application Service endpoint was not invoked")
|
|
201
|
+
return result
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
return await self._invoke_entry(service, method, pipeline, invoke_endpoint)
|
|
205
|
+
finally:
|
|
206
|
+
self._http_boundary.reset(token)
|
|
207
|
+
|
|
208
|
+
async def invoke_http(
|
|
209
|
+
self,
|
|
210
|
+
service: _CompiledApplicationService,
|
|
211
|
+
method: _CompiledMethod,
|
|
212
|
+
kwargs: dict[str, object],
|
|
213
|
+
) -> object:
|
|
214
|
+
boundary = self._http_boundary.get()
|
|
215
|
+
if boundary is None:
|
|
216
|
+
raise RuntimeError("HTTP Application Service boundary is unavailable")
|
|
217
|
+
if boundary.owner_task is not _current_task():
|
|
218
|
+
raise CrossTaskInvocationError
|
|
219
|
+
if boundary.service is not service or boundary.method is not method:
|
|
220
|
+
raise RuntimeError("HTTP Application Service boundary identity changed")
|
|
221
|
+
if boundary.consumed:
|
|
222
|
+
raise RuntimeError("HTTP Application Service boundary was already consumed")
|
|
223
|
+
boundary.consumed = True
|
|
224
|
+
boundary.pipeline.arguments.kwargs = dict(kwargs)
|
|
225
|
+
return await self._invoke_after_authorization(service, method, boundary.pipeline)
|
|
226
|
+
|
|
227
|
+
async def _invoke_after_authorization(
|
|
228
|
+
self,
|
|
229
|
+
service: _CompiledApplicationService,
|
|
230
|
+
method: _CompiledMethod,
|
|
231
|
+
pipeline: _InterceptorPipeline,
|
|
232
|
+
) -> object:
|
|
233
|
+
async def validate() -> object:
|
|
234
|
+
await self._validate_pipeline(service.registration, method, pipeline)
|
|
235
|
+
return await pipeline.run(
|
|
236
|
+
InterceptionStage.UNIT_OF_WORK,
|
|
237
|
+
self._container,
|
|
238
|
+
lambda: self._invoke_work(service.registration, method, pipeline),
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
return await pipeline.run(InterceptionStage.VALIDATION, self._container, validate)
|
|
242
|
+
|
|
243
|
+
@staticmethod
|
|
244
|
+
async def _validate_pipeline(
|
|
245
|
+
registration: ApplicationServiceRegistration,
|
|
246
|
+
method: _CompiledMethod,
|
|
247
|
+
pipeline: _InterceptorPipeline,
|
|
248
|
+
) -> None:
|
|
249
|
+
arguments = pipeline.arguments
|
|
250
|
+
if not arguments.needs_validation:
|
|
251
|
+
return
|
|
252
|
+
if method.descriptor.validation_enabled:
|
|
253
|
+
arguments.args, arguments.kwargs = await _validate_arguments(
|
|
254
|
+
method, registration, arguments.args, arguments.kwargs
|
|
255
|
+
)
|
|
256
|
+
else:
|
|
257
|
+
# 可选类型验证关闭后,必需参数与参数名仍属于公开调用契约。
|
|
258
|
+
try:
|
|
259
|
+
method.signature.bind(None, *arguments.args, **arguments.kwargs)
|
|
260
|
+
except TypeError:
|
|
261
|
+
raise ApplicationServiceValidationError(
|
|
262
|
+
service=registration.contract,
|
|
263
|
+
method_name=method.name,
|
|
264
|
+
issues=(
|
|
265
|
+
ApplicationServiceValidationIssue(
|
|
266
|
+
location=(), error_type="arguments", message="Invalid method arguments"
|
|
267
|
+
),
|
|
268
|
+
),
|
|
269
|
+
) from None
|
|
270
|
+
arguments.mark_validated()
|
|
271
|
+
|
|
272
|
+
async def _invoke_work(
|
|
273
|
+
self,
|
|
274
|
+
registration: ApplicationServiceRegistration,
|
|
275
|
+
method: _CompiledMethod,
|
|
276
|
+
pipeline: _InterceptorPipeline,
|
|
277
|
+
) -> object:
|
|
278
|
+
ambient = self._runtime.current()
|
|
279
|
+
if ambient is not None:
|
|
280
|
+
if _current_task() is not ambient.owner_task:
|
|
281
|
+
raise CrossTaskInvocationError
|
|
282
|
+
if registration.connection_name != ambient.context.connection_name:
|
|
283
|
+
raise NestedConnectionError(
|
|
284
|
+
outer=ambient.context.connection_name, requested=registration.connection_name
|
|
285
|
+
)
|
|
286
|
+
manager = await self._container.get(UnitOfWorkManager)
|
|
287
|
+
|
|
288
|
+
async def nested() -> object:
|
|
289
|
+
return await self._invoke_target(
|
|
290
|
+
ambient.action_container, registration, method, pipeline
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
if method.descriptor.unit_of_work.disabled:
|
|
294
|
+
manager._check_current(registration.connection_name)
|
|
295
|
+
return await nested()
|
|
296
|
+
options = manager._resolve_options(
|
|
297
|
+
registration.connection_name, method.descriptor.unit_of_work, method.name
|
|
298
|
+
)
|
|
299
|
+
async with manager._begin(options) as work:
|
|
300
|
+
result = await nested()
|
|
301
|
+
await work.complete()
|
|
302
|
+
return result
|
|
303
|
+
|
|
304
|
+
async def invoke(action: AsyncContainer) -> object:
|
|
305
|
+
return await self._invoke_target(action, registration, method, pipeline)
|
|
306
|
+
|
|
307
|
+
return await self._runtime.run_action(
|
|
308
|
+
self._container,
|
|
309
|
+
connection_name=registration.connection_name,
|
|
310
|
+
callback=invoke,
|
|
311
|
+
declaration=method.descriptor.unit_of_work,
|
|
312
|
+
method_name=method.name,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
async def _invoke_target(
|
|
316
|
+
self,
|
|
317
|
+
container: AsyncContainer,
|
|
318
|
+
registration: ApplicationServiceRegistration,
|
|
319
|
+
method: _CompiledMethod,
|
|
320
|
+
pipeline: _InterceptorPipeline,
|
|
321
|
+
) -> object:
|
|
322
|
+
async def invoke() -> object:
|
|
323
|
+
# 位于验证之后的扩展可以改参数;目标调用不能使用修改前的验证结果。
|
|
324
|
+
await self._validate_pipeline(registration, method, pipeline)
|
|
325
|
+
implementation = await container.get(
|
|
326
|
+
registration.contract, component=_APPLICATION_SERVICE_COMPONENT
|
|
327
|
+
)
|
|
328
|
+
# 实例已由当前 ACTION 容器取得,绑定只属于本 Application 的读取服务。
|
|
329
|
+
cast(ApplicationService, implementation)._setting_provider = await container.get(
|
|
330
|
+
SettingProvider
|
|
331
|
+
)
|
|
332
|
+
return await method.implementation_method(
|
|
333
|
+
implementation, *pipeline.arguments.args, **pipeline.arguments.kwargs
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
return await pipeline.run(InterceptionStage.METHOD, container, invoke)
|
|
337
|
+
|
|
338
|
+
async def _write_audit(
|
|
339
|
+
self,
|
|
340
|
+
registration: ApplicationServiceRegistration,
|
|
341
|
+
method: _CompiledMethod,
|
|
342
|
+
*,
|
|
343
|
+
occurred_at: datetime,
|
|
344
|
+
duration_ns: int,
|
|
345
|
+
failure: BaseException | None,
|
|
346
|
+
) -> None:
|
|
347
|
+
options = (await self._container.get(Options[AuditingOptions])).value
|
|
348
|
+
control = await self._container.get(AuditingControl)
|
|
349
|
+
if (
|
|
350
|
+
not method.descriptor.auditing_enabled
|
|
351
|
+
or not options.is_enabled
|
|
352
|
+
or not control.is_enabled
|
|
353
|
+
):
|
|
354
|
+
return
|
|
355
|
+
try:
|
|
356
|
+
sink = await self._container.get(AuditSink)
|
|
357
|
+
except NoFactoryError:
|
|
358
|
+
return
|
|
359
|
+
except Exception as audit_error:
|
|
360
|
+
_LOGGER.error(
|
|
361
|
+
"application service audit write failed",
|
|
362
|
+
exc_info=audit_error,
|
|
363
|
+
extra={
|
|
364
|
+
"audit_exception_type": type(audit_error).__name__,
|
|
365
|
+
"audit_contract": registration.contract.__qualname__,
|
|
366
|
+
"audit_method": method.name,
|
|
367
|
+
},
|
|
368
|
+
)
|
|
369
|
+
return
|
|
370
|
+
try:
|
|
371
|
+
current_user = self._runtime.current_user()
|
|
372
|
+
correlation_id = self._runtime.correlation_id() or str(uuid4())
|
|
373
|
+
await sink.write_application_service(
|
|
374
|
+
ApplicationServiceAuditRecord(
|
|
375
|
+
occurred_at=occurred_at,
|
|
376
|
+
user_id=current_user.user_id,
|
|
377
|
+
session_id=current_user.session_id,
|
|
378
|
+
correlation_id=correlation_id,
|
|
379
|
+
contract=(
|
|
380
|
+
f"{registration.contract.__module__}.{registration.contract.__qualname__}"
|
|
381
|
+
),
|
|
382
|
+
method=method.name,
|
|
383
|
+
succeeded=failure is None,
|
|
384
|
+
duration_ns=duration_ns,
|
|
385
|
+
exception_type=None if failure is None else type(failure).__name__,
|
|
386
|
+
)
|
|
387
|
+
)
|
|
388
|
+
except Exception as audit_error:
|
|
389
|
+
# sink resolution/context/write 都属于独立观测边界;失败不能覆盖业务结果。
|
|
390
|
+
_LOGGER.error(
|
|
391
|
+
"application service audit write failed",
|
|
392
|
+
exc_info=audit_error,
|
|
393
|
+
extra={
|
|
394
|
+
"audit_exception_type": type(audit_error).__name__,
|
|
395
|
+
"audit_contract": registration.contract.__qualname__,
|
|
396
|
+
"audit_method": method.name,
|
|
397
|
+
},
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
async def _authorize(self, requirement: _AuthorizationRequirement) -> None:
|
|
401
|
+
if requirement.allows_anonymous or not requirement.requires_authenticated_user:
|
|
402
|
+
return
|
|
403
|
+
current_user = self._runtime.current_user()
|
|
404
|
+
if not current_user.is_authenticated:
|
|
405
|
+
raise UnauthenticatedError
|
|
406
|
+
if not requirement.permissions:
|
|
407
|
+
return
|
|
408
|
+
try:
|
|
409
|
+
checker = await self._container.get(PermissionChecker)
|
|
410
|
+
except NoFactoryError:
|
|
411
|
+
raise PermissionCheckerUnavailableError from None
|
|
412
|
+
for permission in requirement.permissions:
|
|
413
|
+
if not await checker.is_granted(current_user, permission):
|
|
414
|
+
raise PermissionDeniedError(permission=str(permission))
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _proxy_factory(
|
|
418
|
+
service: _CompiledApplicationService,
|
|
419
|
+
) -> Callable[[_ApplicationServiceDispatcher], object]:
|
|
420
|
+
def create(dispatcher: _ApplicationServiceDispatcher) -> object:
|
|
421
|
+
return _ApplicationServiceProxy(dispatcher, service)
|
|
422
|
+
|
|
423
|
+
return create
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Application Service definition、validation 与 invocation failure contracts。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from ..errors.base import FrameworkError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ApplicationServiceDefinitionError(FrameworkError):
|
|
11
|
+
def __init__(self, *, symbol: str, reason: str) -> None:
|
|
12
|
+
self.symbol = symbol
|
|
13
|
+
self.reason = reason
|
|
14
|
+
super().__init__(f"Invalid Application Service {symbol}: {reason}")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ApplicationServiceMethodError(FrameworkError):
|
|
18
|
+
def __init__(self, *, service: type[object], method: object, reason: str) -> None:
|
|
19
|
+
self.service = service
|
|
20
|
+
self.method = method
|
|
21
|
+
self.reason = reason
|
|
22
|
+
method_name = getattr(method, "__qualname__", repr(method))
|
|
23
|
+
super().__init__(
|
|
24
|
+
f"Application Service method {service.__module__}.{service.__qualname__}/{method_name} "
|
|
25
|
+
f"is unavailable: {reason}"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True, slots=True)
|
|
30
|
+
class ApplicationServiceValidationIssue:
|
|
31
|
+
location: tuple[str | int, ...]
|
|
32
|
+
error_type: str
|
|
33
|
+
message: str
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ApplicationServiceValidationError(FrameworkError):
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
*,
|
|
40
|
+
service: type[object],
|
|
41
|
+
method_name: str,
|
|
42
|
+
issues: tuple[ApplicationServiceValidationIssue, ...],
|
|
43
|
+
) -> None:
|
|
44
|
+
self.service = service
|
|
45
|
+
self.method_name = method_name
|
|
46
|
+
self.issues = issues
|
|
47
|
+
super().__init__(
|
|
48
|
+
f"Application Service validation failed for "
|
|
49
|
+
f"{service.__module__}.{service.__qualname__}.{method_name} ({len(issues)} issue(s))"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ApplicationInvocationRejectedError(FrameworkError):
|
|
54
|
+
def __init__(self, *, reason: str) -> None:
|
|
55
|
+
self.reason = reason
|
|
56
|
+
super().__init__(f"Application invocation rejected: {reason}")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class CrossTaskInvocationError(ApplicationInvocationRejectedError):
|
|
60
|
+
def __init__(self) -> None:
|
|
61
|
+
super().__init__(reason="ambient ACTION belongs to a different asyncio task")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class NestedConnectionError(ApplicationInvocationRejectedError):
|
|
65
|
+
def __init__(self, *, outer: str, requested: str) -> None:
|
|
66
|
+
self.outer = outer
|
|
67
|
+
self.requested = requested
|
|
68
|
+
super().__init__(
|
|
69
|
+
reason=f"nested invocation cannot change connection from {outer!r} to {requested!r}"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class ApplicationInvocationCleanupError(FrameworkError):
|
|
74
|
+
def __init__(
|
|
75
|
+
self,
|
|
76
|
+
*,
|
|
77
|
+
primary: BaseException | None,
|
|
78
|
+
cleanup_failures: tuple[BaseException, ...],
|
|
79
|
+
committed: bool,
|
|
80
|
+
) -> None:
|
|
81
|
+
self.primary = primary
|
|
82
|
+
self.cleanup_failures = cleanup_failures
|
|
83
|
+
self.committed = committed
|
|
84
|
+
primary_name = type(primary).__name__ if primary is not None else "none"
|
|
85
|
+
super().__init__(
|
|
86
|
+
"Application invocation cleanup failed "
|
|
87
|
+
f"(primary={primary_name}, cleanup_failures={len(cleanup_failures)}, "
|
|
88
|
+
f"committed={committed})"
|
|
89
|
+
)
|