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,500 @@
|
|
|
1
|
+
"""Protocol/implementation mapping、method compilation 与 immutable public catalog。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
from collections.abc import Awaitable, Callable, Mapping
|
|
7
|
+
from dataclasses import dataclass, field, replace
|
|
8
|
+
from types import MappingProxyType
|
|
9
|
+
from typing import Any, cast, get_type_hints
|
|
10
|
+
|
|
11
|
+
from pydantic import ConfigDict, validate_call
|
|
12
|
+
|
|
13
|
+
from ..auditing.control import _AUDITING_ATTRIBUTE
|
|
14
|
+
from ..authorization import PermissionCatalog, PermissionName
|
|
15
|
+
from ..authorization.contracts import (
|
|
16
|
+
_ALLOW_ANONYMOUS_ATTRIBUTE,
|
|
17
|
+
_AUTHORIZATION_ATTRIBUTE,
|
|
18
|
+
_authorization_requirement,
|
|
19
|
+
_AuthorizationRequirement,
|
|
20
|
+
)
|
|
21
|
+
from ..authorization.errors import AuthorizationDefinitionError
|
|
22
|
+
from ..diagnostics import SourceLocation
|
|
23
|
+
from ..modularity import ModuleKey
|
|
24
|
+
from ..unit_of_work.options import _UNIT_OF_WORK_ATTRIBUTE, UnitOfWorkDeclaration, _declaration
|
|
25
|
+
from .contracts import (
|
|
26
|
+
ApplicationService,
|
|
27
|
+
ApplicationServiceContract,
|
|
28
|
+
IntegrationService,
|
|
29
|
+
_ApplicationServiceDefinition,
|
|
30
|
+
_inherited_contracts,
|
|
31
|
+
_is_internal_contributor,
|
|
32
|
+
)
|
|
33
|
+
from .errors import ApplicationServiceDefinitionError, ApplicationServiceMethodError
|
|
34
|
+
from .interceptors import InterceptionStage, InterceptorRegistration, _InterceptorRule
|
|
35
|
+
from .policies import _VALIDATION_ATTRIBUTE, _effective_switch
|
|
36
|
+
from .signature import _business_signature
|
|
37
|
+
|
|
38
|
+
_APPLICATION_SERVICE_COMPONENT = "__python_platform_application_service_implementation__"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True, slots=True)
|
|
42
|
+
class ApplicationServiceMethod:
|
|
43
|
+
name: str
|
|
44
|
+
source: SourceLocation
|
|
45
|
+
requires_authenticated_user: bool
|
|
46
|
+
permissions: tuple[PermissionName, ...]
|
|
47
|
+
allows_anonymous: bool
|
|
48
|
+
service: type[object]
|
|
49
|
+
implementation: type[object]
|
|
50
|
+
method: Callable[..., Awaitable[object]]
|
|
51
|
+
validation_enabled: bool
|
|
52
|
+
auditing_enabled: bool
|
|
53
|
+
policy_sources: tuple[SourceLocation, ...]
|
|
54
|
+
interceptors: tuple[InterceptorRegistration, ...] = ()
|
|
55
|
+
unit_of_work: UnitOfWorkDeclaration = field(default_factory=UnitOfWorkDeclaration)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass(frozen=True, slots=True)
|
|
59
|
+
class ApplicationServiceRegistration:
|
|
60
|
+
contract: type[object]
|
|
61
|
+
implementation: type[ApplicationService]
|
|
62
|
+
connection_name: str
|
|
63
|
+
owner: ModuleKey
|
|
64
|
+
implementation_owner: ModuleKey
|
|
65
|
+
source: SourceLocation
|
|
66
|
+
implementation_source: SourceLocation
|
|
67
|
+
replaced_sources: tuple[SourceLocation, ...]
|
|
68
|
+
declaration_index: int
|
|
69
|
+
methods: tuple[ApplicationServiceMethod, ...]
|
|
70
|
+
is_public_service: bool
|
|
71
|
+
has_contract: bool
|
|
72
|
+
is_integration_service: bool
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass(frozen=True, slots=True)
|
|
76
|
+
class _DiscoveredApplicationService:
|
|
77
|
+
service_type: type[ApplicationService]
|
|
78
|
+
definition: _ApplicationServiceDefinition
|
|
79
|
+
owner: ModuleKey
|
|
80
|
+
source: SourceLocation
|
|
81
|
+
declaration_index: int
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass(frozen=True, slots=True)
|
|
85
|
+
class _ApplicationServiceBinding:
|
|
86
|
+
discovery: _DiscoveredApplicationService
|
|
87
|
+
implementation: type[ApplicationService]
|
|
88
|
+
implementation_owner: ModuleKey
|
|
89
|
+
implementation_source: SourceLocation
|
|
90
|
+
replaced_sources: tuple[SourceLocation, ...]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
_ValidatedArguments = tuple[tuple[object, ...], dict[str, object]]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass(frozen=True, slots=True)
|
|
97
|
+
class _CompiledMethod:
|
|
98
|
+
name: str
|
|
99
|
+
contract_method: Callable[..., Awaitable[object]]
|
|
100
|
+
implementation_method: Callable[..., Awaitable[object]]
|
|
101
|
+
validator: Callable[..., Awaitable[_ValidatedArguments]]
|
|
102
|
+
parameter_names: frozenset[str]
|
|
103
|
+
authorization: _AuthorizationRequirement
|
|
104
|
+
descriptor: ApplicationServiceMethod
|
|
105
|
+
signature: inspect.Signature
|
|
106
|
+
interceptor_steps: Mapping[InterceptionStage, tuple[InterceptorRegistration, ...]]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass(frozen=True, slots=True)
|
|
110
|
+
class _CompiledApplicationService:
|
|
111
|
+
registration: ApplicationServiceRegistration
|
|
112
|
+
methods: Mapping[str, _CompiledMethod]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class ApplicationServiceCatalog:
|
|
116
|
+
"""唯一发布 contract、最终 implementation、owner、method 与 override provenance。"""
|
|
117
|
+
|
|
118
|
+
__slots__ = ("_by_contract", "_by_method", "_compiled", "_registrations")
|
|
119
|
+
|
|
120
|
+
def __init__(self, services: tuple[_CompiledApplicationService, ...]) -> None:
|
|
121
|
+
self._compiled = services
|
|
122
|
+
self._registrations = tuple(item.registration for item in services)
|
|
123
|
+
public_services = tuple(item for item in services if item.registration.is_public_service)
|
|
124
|
+
self._by_contract: Mapping[type[object], _CompiledApplicationService] = MappingProxyType(
|
|
125
|
+
{item.registration.contract: item for item in public_services}
|
|
126
|
+
)
|
|
127
|
+
methods_by_identity: dict[
|
|
128
|
+
object,
|
|
129
|
+
tuple[_CompiledApplicationService, _CompiledMethod],
|
|
130
|
+
] = {}
|
|
131
|
+
for service in public_services:
|
|
132
|
+
for method in service.methods.values():
|
|
133
|
+
previous = methods_by_identity.get(method.contract_method)
|
|
134
|
+
if previous is not None:
|
|
135
|
+
# method-only invoke 不能靠遍历顺序裁决继承或复用造成的 identity 歧义。
|
|
136
|
+
contract = service.registration.contract
|
|
137
|
+
raise ApplicationServiceDefinitionError(
|
|
138
|
+
symbol=f"{contract.__module__}.{contract.__qualname__}.{method.name}",
|
|
139
|
+
reason="contract method identity must be unique within an Application",
|
|
140
|
+
)
|
|
141
|
+
methods_by_identity[method.contract_method] = (service, method)
|
|
142
|
+
self._by_method: Mapping[
|
|
143
|
+
object,
|
|
144
|
+
tuple[_CompiledApplicationService, _CompiledMethod],
|
|
145
|
+
] = MappingProxyType(methods_by_identity)
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def registrations(self) -> tuple[ApplicationServiceRegistration, ...]:
|
|
149
|
+
return self._registrations
|
|
150
|
+
|
|
151
|
+
def for_contract(
|
|
152
|
+
self,
|
|
153
|
+
contract: type[object],
|
|
154
|
+
) -> ApplicationServiceRegistration | None:
|
|
155
|
+
compiled = self._by_contract.get(contract)
|
|
156
|
+
return compiled.registration if compiled is not None else None
|
|
157
|
+
|
|
158
|
+
def _resolve_method(
|
|
159
|
+
self,
|
|
160
|
+
method: object,
|
|
161
|
+
) -> tuple[_CompiledApplicationService, _CompiledMethod]:
|
|
162
|
+
resolved = self._by_method.get(method)
|
|
163
|
+
if resolved is None:
|
|
164
|
+
raise ApplicationServiceMethodError(
|
|
165
|
+
service=ApplicationServiceContract,
|
|
166
|
+
method=method,
|
|
167
|
+
reason="method is not a cataloged ApplicationServiceContract method",
|
|
168
|
+
)
|
|
169
|
+
return resolved
|
|
170
|
+
|
|
171
|
+
def _for_registration(
|
|
172
|
+
self,
|
|
173
|
+
registration: ApplicationServiceRegistration,
|
|
174
|
+
) -> _CompiledApplicationService:
|
|
175
|
+
for compiled in self._compiled:
|
|
176
|
+
if compiled.registration is registration:
|
|
177
|
+
return compiled
|
|
178
|
+
raise AssertionError("Application Service registration is not owned by this catalog")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _build_application_service_catalog(
|
|
182
|
+
bindings: tuple[_ApplicationServiceBinding, ...],
|
|
183
|
+
permission_catalog: PermissionCatalog,
|
|
184
|
+
interceptor_rules: tuple[_InterceptorRule, ...] = (),
|
|
185
|
+
) -> ApplicationServiceCatalog:
|
|
186
|
+
return ApplicationServiceCatalog(
|
|
187
|
+
tuple(_compile_service(item, permission_catalog, interceptor_rules) for item in bindings)
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _compile_service(
|
|
192
|
+
binding: _ApplicationServiceBinding,
|
|
193
|
+
permission_catalog: PermissionCatalog,
|
|
194
|
+
interceptor_rules: tuple[_InterceptorRule, ...] = (),
|
|
195
|
+
) -> _CompiledApplicationService:
|
|
196
|
+
"""把 contract declaration 与仲裁胜出的 implementation 编译成唯一调用事实。"""
|
|
197
|
+
|
|
198
|
+
discovery = binding.discovery
|
|
199
|
+
implementation = binding.implementation
|
|
200
|
+
contract: type[object] = discovery.definition.contract or discovery.service_type
|
|
201
|
+
has_contract = discovery.definition.contract is not None
|
|
202
|
+
is_public_service = not _is_internal_contributor(implementation)
|
|
203
|
+
is_integration = issubclass(implementation, IntegrationService)
|
|
204
|
+
_validate_policy_locations(contract if has_contract else None, implementation, is_integration)
|
|
205
|
+
if is_integration and not has_contract:
|
|
206
|
+
raise ApplicationServiceDefinitionError(
|
|
207
|
+
symbol=implementation.__qualname__,
|
|
208
|
+
reason="IntegrationService requires an independent Contract",
|
|
209
|
+
)
|
|
210
|
+
implementation_symbol = f"{implementation.__module__}.{implementation.__qualname__}"
|
|
211
|
+
if inspect.isabstract(implementation):
|
|
212
|
+
raise ApplicationServiceDefinitionError(
|
|
213
|
+
symbol=implementation_symbol,
|
|
214
|
+
reason="application service implementation must be concrete",
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
# public surface 以 Protocol 为权威;override 只能替换实现,不能暗中增删业务方法。
|
|
218
|
+
contract_methods = _public_method_names(contract, is_contract=has_contract)
|
|
219
|
+
|
|
220
|
+
compiled_methods: dict[str, _CompiledMethod] = {}
|
|
221
|
+
public_methods: list[ApplicationServiceMethod] = []
|
|
222
|
+
for name in contract_methods:
|
|
223
|
+
contract_method = _require_async_instance_method(contract, name, role="contract")
|
|
224
|
+
implementation_method = _require_async_instance_method(
|
|
225
|
+
implementation,
|
|
226
|
+
name,
|
|
227
|
+
role="implementation",
|
|
228
|
+
)
|
|
229
|
+
contract_signature, annotations = _resolved_signature(contract_method)
|
|
230
|
+
implementation_signature, _ = _resolved_signature(implementation_method)
|
|
231
|
+
if contract_signature.return_annotation is inspect.Signature.empty:
|
|
232
|
+
raise ApplicationServiceDefinitionError(
|
|
233
|
+
symbol=f"{implementation_symbol}.{name}",
|
|
234
|
+
reason="public service methods must declare a return annotation",
|
|
235
|
+
)
|
|
236
|
+
if _business_signature(contract_signature) != _business_signature(implementation_signature):
|
|
237
|
+
raise ApplicationServiceDefinitionError(
|
|
238
|
+
symbol=f"{implementation_symbol}.{name}",
|
|
239
|
+
reason="implementation signature must exactly match its public contract",
|
|
240
|
+
)
|
|
241
|
+
try:
|
|
242
|
+
validator = _compile_argument_validator(
|
|
243
|
+
contract_method,
|
|
244
|
+
contract_signature,
|
|
245
|
+
annotations,
|
|
246
|
+
)
|
|
247
|
+
except Exception as original_error:
|
|
248
|
+
raise ApplicationServiceDefinitionError(
|
|
249
|
+
symbol=f"{contract.__module__}.{contract.__qualname__}.{name}",
|
|
250
|
+
reason=f"Pydantic could not compile the method: {type(original_error).__name__}",
|
|
251
|
+
) from None
|
|
252
|
+
source = _method_source(contract_method, discovery.owner)
|
|
253
|
+
authorization = _authorization_requirement(implementation, implementation_method)
|
|
254
|
+
unknown_permissions = tuple(
|
|
255
|
+
permission
|
|
256
|
+
for permission in authorization.permissions
|
|
257
|
+
if permission not in permission_catalog
|
|
258
|
+
)
|
|
259
|
+
if unknown_permissions:
|
|
260
|
+
raise AuthorizationDefinitionError(
|
|
261
|
+
symbol=f"{contract.__module__}.{contract.__qualname__}.{name}",
|
|
262
|
+
reason=(
|
|
263
|
+
"authorization references unknown permissions: "
|
|
264
|
+
f"{tuple(str(item) for item in unknown_permissions)!r}"
|
|
265
|
+
),
|
|
266
|
+
)
|
|
267
|
+
descriptor = ApplicationServiceMethod(
|
|
268
|
+
name=name,
|
|
269
|
+
source=source,
|
|
270
|
+
requires_authenticated_user=authorization.requires_authenticated_user,
|
|
271
|
+
permissions=authorization.permissions,
|
|
272
|
+
allows_anonymous=authorization.allows_anonymous,
|
|
273
|
+
service=contract,
|
|
274
|
+
implementation=implementation,
|
|
275
|
+
method=implementation_method,
|
|
276
|
+
unit_of_work=_declaration(implementation, implementation_method),
|
|
277
|
+
validation_enabled=_effective_switch(
|
|
278
|
+
implementation, implementation_method, _VALIDATION_ATTRIBUTE, True
|
|
279
|
+
),
|
|
280
|
+
auditing_enabled=_effective_switch(
|
|
281
|
+
implementation, implementation_method, _AUDITING_ATTRIBUTE, not is_integration
|
|
282
|
+
),
|
|
283
|
+
policy_sources=_policy_sources(
|
|
284
|
+
implementation, implementation_method, binding.implementation_owner
|
|
285
|
+
),
|
|
286
|
+
)
|
|
287
|
+
descriptor = replace(
|
|
288
|
+
descriptor, interceptors=_match_interceptors(descriptor, interceptor_rules)
|
|
289
|
+
)
|
|
290
|
+
compiled_methods[name] = _CompiledMethod(
|
|
291
|
+
name=name,
|
|
292
|
+
contract_method=contract_method,
|
|
293
|
+
implementation_method=implementation_method,
|
|
294
|
+
validator=validator,
|
|
295
|
+
parameter_names=frozenset(tuple(contract_signature.parameters)[1:]),
|
|
296
|
+
authorization=authorization,
|
|
297
|
+
descriptor=descriptor,
|
|
298
|
+
signature=contract_signature,
|
|
299
|
+
interceptor_steps=MappingProxyType(
|
|
300
|
+
{
|
|
301
|
+
stage: tuple(item for item in descriptor.interceptors if item.before is stage)
|
|
302
|
+
for stage in InterceptionStage
|
|
303
|
+
}
|
|
304
|
+
),
|
|
305
|
+
)
|
|
306
|
+
public_methods.append(descriptor)
|
|
307
|
+
|
|
308
|
+
if not public_methods:
|
|
309
|
+
raise ApplicationServiceDefinitionError(
|
|
310
|
+
symbol=f"{contract.__module__}.{contract.__qualname__}",
|
|
311
|
+
reason="application service contract must declare at least one public async method",
|
|
312
|
+
)
|
|
313
|
+
registration = ApplicationServiceRegistration(
|
|
314
|
+
contract=contract,
|
|
315
|
+
implementation=implementation,
|
|
316
|
+
connection_name=discovery.definition.connection_name,
|
|
317
|
+
owner=discovery.owner,
|
|
318
|
+
implementation_owner=binding.implementation_owner,
|
|
319
|
+
source=discovery.source,
|
|
320
|
+
implementation_source=binding.implementation_source,
|
|
321
|
+
replaced_sources=binding.replaced_sources,
|
|
322
|
+
declaration_index=discovery.declaration_index,
|
|
323
|
+
methods=tuple(public_methods),
|
|
324
|
+
is_public_service=is_public_service,
|
|
325
|
+
has_contract=has_contract,
|
|
326
|
+
is_integration_service=is_integration,
|
|
327
|
+
)
|
|
328
|
+
return _CompiledApplicationService(
|
|
329
|
+
registration=registration,
|
|
330
|
+
methods=MappingProxyType(compiled_methods),
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
_POLICY_ATTRIBUTES = (
|
|
335
|
+
_UNIT_OF_WORK_ATTRIBUTE,
|
|
336
|
+
_AUTHORIZATION_ATTRIBUTE,
|
|
337
|
+
_ALLOW_ANONYMOUS_ATTRIBUTE,
|
|
338
|
+
_VALIDATION_ATTRIBUTE,
|
|
339
|
+
_AUDITING_ATTRIBUTE,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _validate_policy_locations(
|
|
344
|
+
contract: type[object] | None, implementation: type[object], integration: bool
|
|
345
|
+
) -> None:
|
|
346
|
+
# provides 只仲裁公开身份;最终实现继承的其他业务 Contract 同样不能持有策略。
|
|
347
|
+
contracts = dict.fromkeys((contract, *_inherited_contracts(implementation)))
|
|
348
|
+
for root, attributes in (
|
|
349
|
+
*((candidate, _POLICY_ATTRIBUTES) for candidate in contracts if candidate is not None),
|
|
350
|
+
(implementation if integration else None, _POLICY_ATTRIBUTES[:2]),
|
|
351
|
+
):
|
|
352
|
+
if root is None:
|
|
353
|
+
continue
|
|
354
|
+
for owner in root.__mro__:
|
|
355
|
+
for target in (
|
|
356
|
+
owner,
|
|
357
|
+
*(value for value in vars(owner).values() if inspect.isfunction(value)),
|
|
358
|
+
):
|
|
359
|
+
if any(attribute in vars(target) for attribute in attributes):
|
|
360
|
+
raise ApplicationServiceDefinitionError(
|
|
361
|
+
symbol=getattr(target, "__qualname__", root.__qualname__),
|
|
362
|
+
reason="Contract policies must move to implementation"
|
|
363
|
+
if root in contracts
|
|
364
|
+
else "IntegrationService cannot declare user authorization",
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def _policy_sources(
|
|
369
|
+
implementation: type[object], method: Callable[..., Any], owner: ModuleKey
|
|
370
|
+
) -> tuple[SourceLocation, ...]:
|
|
371
|
+
return tuple(
|
|
372
|
+
_method_source(target, owner)
|
|
373
|
+
for target in (*reversed(implementation.__mro__), method)
|
|
374
|
+
if any(attribute in vars(target) for attribute in _POLICY_ATTRIBUTES)
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def _match_interceptors(
|
|
379
|
+
method: ApplicationServiceMethod, rules: tuple[_InterceptorRule, ...]
|
|
380
|
+
) -> tuple[InterceptorRegistration, ...]:
|
|
381
|
+
matched: list[InterceptorRegistration] = []
|
|
382
|
+
for rule in rules:
|
|
383
|
+
try:
|
|
384
|
+
accepted = rule.match(method)
|
|
385
|
+
if not isinstance(accepted, bool):
|
|
386
|
+
raise TypeError("interceptor match must return bool")
|
|
387
|
+
except Exception as error:
|
|
388
|
+
raise ApplicationServiceDefinitionError(
|
|
389
|
+
symbol=rule.registration.source.symbol
|
|
390
|
+
or rule.registration.interceptor.__qualname__,
|
|
391
|
+
reason=(
|
|
392
|
+
f"interceptor match failed for {rule.registration.owner}: "
|
|
393
|
+
f"{type(error).__name__}"
|
|
394
|
+
),
|
|
395
|
+
) from None
|
|
396
|
+
if accepted:
|
|
397
|
+
matched.append(rule.registration)
|
|
398
|
+
return tuple(sorted(matched, key=lambda item: tuple(InterceptionStage).index(item.before)))
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _public_method_names(candidate: type[object], *, is_contract: bool) -> tuple[str, ...]:
|
|
402
|
+
names: dict[str, None] = {}
|
|
403
|
+
for owner in reversed(candidate.__mro__):
|
|
404
|
+
if owner in (object, ApplicationService, ApplicationServiceContract):
|
|
405
|
+
continue
|
|
406
|
+
if is_contract and owner.__module__ == "typing":
|
|
407
|
+
continue
|
|
408
|
+
for name, value in owner.__dict__.items():
|
|
409
|
+
if name.startswith("_"):
|
|
410
|
+
continue
|
|
411
|
+
if inspect.isfunction(value) or isinstance(value, (staticmethod, classmethod)):
|
|
412
|
+
names[name] = None
|
|
413
|
+
return tuple(names)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def _require_async_instance_method(
|
|
417
|
+
owner: type[object],
|
|
418
|
+
name: str,
|
|
419
|
+
*,
|
|
420
|
+
role: str,
|
|
421
|
+
) -> Callable[..., Awaitable[object]]:
|
|
422
|
+
symbol = f"{owner.__module__}.{owner.__qualname__}.{name}"
|
|
423
|
+
descriptor = inspect.getattr_static(owner, name)
|
|
424
|
+
if isinstance(descriptor, (staticmethod, classmethod)):
|
|
425
|
+
raise ApplicationServiceDefinitionError(
|
|
426
|
+
symbol=symbol,
|
|
427
|
+
reason=f"public {role} methods must be instance methods",
|
|
428
|
+
)
|
|
429
|
+
method = getattr(owner, name)
|
|
430
|
+
if not inspect.isfunction(method) or not inspect.iscoroutinefunction(method):
|
|
431
|
+
raise ApplicationServiceDefinitionError(
|
|
432
|
+
symbol=symbol,
|
|
433
|
+
reason=f"public {role} methods must use async def",
|
|
434
|
+
)
|
|
435
|
+
parameters = tuple(inspect.signature(method).parameters.values())
|
|
436
|
+
if not parameters or parameters[0].kind not in (
|
|
437
|
+
inspect.Parameter.POSITIONAL_ONLY,
|
|
438
|
+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
439
|
+
):
|
|
440
|
+
# Catalog 与 HTTP generator 都会剥离 receiver;缺少首个位置参数时不能伪装成实例方法。
|
|
441
|
+
raise ApplicationServiceDefinitionError(
|
|
442
|
+
symbol=symbol,
|
|
443
|
+
reason=f"public {role} methods must declare an instance receiver",
|
|
444
|
+
)
|
|
445
|
+
return cast(Callable[..., Awaitable[object]], method)
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def _resolved_signature(
|
|
449
|
+
method: Callable[..., Awaitable[object]],
|
|
450
|
+
) -> tuple[inspect.Signature, dict[str, object]]:
|
|
451
|
+
annotations = get_type_hints(method, include_extras=True)
|
|
452
|
+
signature = inspect.signature(method)
|
|
453
|
+
parameters = tuple(
|
|
454
|
+
parameter.replace(annotation=annotations.get(name, parameter.annotation))
|
|
455
|
+
for name, parameter in signature.parameters.items()
|
|
456
|
+
)
|
|
457
|
+
resolved = signature.replace(
|
|
458
|
+
parameters=parameters,
|
|
459
|
+
return_annotation=annotations.get("return", signature.return_annotation),
|
|
460
|
+
)
|
|
461
|
+
return resolved, annotations
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def _compile_argument_validator(
|
|
465
|
+
method: Callable[..., Awaitable[object]],
|
|
466
|
+
signature: inspect.Signature,
|
|
467
|
+
annotations: Mapping[str, object],
|
|
468
|
+
) -> Callable[..., Awaitable[_ValidatedArguments]]:
|
|
469
|
+
async def capture(*args: object, **kwargs: object) -> _ValidatedArguments:
|
|
470
|
+
# 第一个值是 validation-only self sentinel;业务 implementation 尚未解析或执行。
|
|
471
|
+
return tuple(args[1:]), dict(kwargs)
|
|
472
|
+
|
|
473
|
+
capture.__name__ = method.__name__
|
|
474
|
+
capture.__qualname__ = method.__qualname__
|
|
475
|
+
cast(Any, capture).__signature__ = signature
|
|
476
|
+
capture.__annotations__ = dict(annotations)
|
|
477
|
+
validated = validate_call(
|
|
478
|
+
config=ConfigDict(
|
|
479
|
+
arbitrary_types_allowed=True,
|
|
480
|
+
validate_by_name=True,
|
|
481
|
+
hide_input_in_errors=True,
|
|
482
|
+
)
|
|
483
|
+
)(capture)
|
|
484
|
+
return cast(Callable[..., Awaitable[_ValidatedArguments]], validated)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def _method_source(method: Any, owner: ModuleKey) -> SourceLocation:
|
|
488
|
+
file_name: str | None = None
|
|
489
|
+
line: int | None = None
|
|
490
|
+
try:
|
|
491
|
+
file_name = inspect.getsourcefile(method)
|
|
492
|
+
_, line = inspect.getsourcelines(method)
|
|
493
|
+
except (OSError, TypeError):
|
|
494
|
+
pass
|
|
495
|
+
return SourceLocation(
|
|
496
|
+
owner=str(owner),
|
|
497
|
+
file=file_name,
|
|
498
|
+
line=line,
|
|
499
|
+
symbol=f"{method.__module__}.{method.__qualname__}",
|
|
500
|
+
)
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""Application Service 的公开 Protocol 与 concrete implementation metadata。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import TYPE_CHECKING, Protocol, TypeVar
|
|
8
|
+
|
|
9
|
+
from .errors import ApplicationServiceDefinitionError
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from ..settings.provider import SettingProvider
|
|
13
|
+
|
|
14
|
+
_APPLICATION_SERVICE_DEFINITION_ATTRIBUTE = "__python_platform_application_service_definition__"
|
|
15
|
+
_TApplicationService = TypeVar("_TApplicationService", bound="ApplicationService")
|
|
16
|
+
_DEFAULT_CONNECTION_NAME = "default"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ApplicationServiceContract(Protocol):
|
|
20
|
+
"""跨 Module 注入的业务服务必须显式继承此 Protocol marker。"""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ApplicationService:
|
|
24
|
+
"""业务实现;无独立 Contract 时,以自身类型发布受管理代理。"""
|
|
25
|
+
|
|
26
|
+
_setting_provider: SettingProvider | None = None
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def setting_provider(self) -> SettingProvider:
|
|
30
|
+
# 由受管理调用绑定到当前 Application;不要求业务构造函数调用 super。
|
|
31
|
+
if self._setting_provider is None:
|
|
32
|
+
raise RuntimeError("setting_provider is only bound during managed invocation")
|
|
33
|
+
return self._setting_provider
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class IntegrationService(ApplicationService):
|
|
37
|
+
"""仅供应用内协作的服务;必须声明 Contract,不承载用户授权。"""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class _InternalApplicationServiceContributor(ApplicationService):
|
|
41
|
+
"""框架内部多贡献者基类;不发布含糊的 single Protocol。"""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True, slots=True)
|
|
45
|
+
class _ApplicationServiceDefinition:
|
|
46
|
+
contract: type[ApplicationServiceContract] | None
|
|
47
|
+
connection_name: str
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def application_service(
|
|
51
|
+
*,
|
|
52
|
+
provides: type[ApplicationServiceContract] | None = None,
|
|
53
|
+
connection_name: str = _DEFAULT_CONNECTION_NAME,
|
|
54
|
+
) -> Callable[[type[_TApplicationService]], type[_TApplicationService]]:
|
|
55
|
+
"""显式声明契约或连接;唯一名义继承的业务契约可以按约定识别。"""
|
|
56
|
+
|
|
57
|
+
normalized_connection = _validate_connection_name(connection_name)
|
|
58
|
+
|
|
59
|
+
def decorate(
|
|
60
|
+
service_type: type[_TApplicationService],
|
|
61
|
+
) -> type[_TApplicationService]:
|
|
62
|
+
symbol = getattr(service_type, "__qualname__", type(service_type).__qualname__)
|
|
63
|
+
if not isinstance(service_type, type) or not issubclass(service_type, ApplicationService):
|
|
64
|
+
raise ApplicationServiceDefinitionError(
|
|
65
|
+
symbol=symbol,
|
|
66
|
+
reason="application_service decorator requires an ApplicationService subclass",
|
|
67
|
+
)
|
|
68
|
+
if _APPLICATION_SERVICE_DEFINITION_ATTRIBUTE in service_type.__dict__:
|
|
69
|
+
raise ApplicationServiceDefinitionError(
|
|
70
|
+
symbol=f"{service_type.__module__}.{service_type.__qualname__}",
|
|
71
|
+
reason="application service metadata is already declared",
|
|
72
|
+
)
|
|
73
|
+
contract = provides
|
|
74
|
+
if contract is None:
|
|
75
|
+
if not _is_internal_contributor(service_type):
|
|
76
|
+
contract = _infer_contract(service_type)
|
|
77
|
+
else:
|
|
78
|
+
_validate_contract(
|
|
79
|
+
provides,
|
|
80
|
+
symbol=f"{service_type.__module__}.{service_type.__qualname__}",
|
|
81
|
+
)
|
|
82
|
+
setattr(
|
|
83
|
+
service_type,
|
|
84
|
+
_APPLICATION_SERVICE_DEFINITION_ATTRIBUTE,
|
|
85
|
+
_ApplicationServiceDefinition(
|
|
86
|
+
contract=contract,
|
|
87
|
+
connection_name=normalized_connection,
|
|
88
|
+
),
|
|
89
|
+
)
|
|
90
|
+
return service_type
|
|
91
|
+
|
|
92
|
+
return decorate
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _definition_for(
|
|
96
|
+
candidate: type[object],
|
|
97
|
+
) -> _ApplicationServiceDefinition | None:
|
|
98
|
+
if not issubclass(candidate, ApplicationService) or candidate in (
|
|
99
|
+
ApplicationService,
|
|
100
|
+
IntegrationService,
|
|
101
|
+
_InternalApplicationServiceContributor,
|
|
102
|
+
):
|
|
103
|
+
return None
|
|
104
|
+
metadata = candidate.__dict__.get(_APPLICATION_SERVICE_DEFINITION_ATTRIBUTE)
|
|
105
|
+
if metadata is None:
|
|
106
|
+
# 只读取类的继承事实,不把推导结果写回共享类或全局 registry。
|
|
107
|
+
return _ApplicationServiceDefinition(
|
|
108
|
+
contract=_infer_contract(candidate),
|
|
109
|
+
connection_name=_DEFAULT_CONNECTION_NAME,
|
|
110
|
+
)
|
|
111
|
+
if not isinstance(metadata, _ApplicationServiceDefinition):
|
|
112
|
+
raise ApplicationServiceDefinitionError(
|
|
113
|
+
symbol=f"{candidate.__module__}.{candidate.__qualname__}",
|
|
114
|
+
reason="application service decorator metadata has an invalid runtime shape",
|
|
115
|
+
)
|
|
116
|
+
if metadata.contract is None:
|
|
117
|
+
if issubclass(candidate, IntegrationService):
|
|
118
|
+
raise ApplicationServiceDefinitionError(
|
|
119
|
+
symbol=f"{candidate.__module__}.{candidate.__qualname__}",
|
|
120
|
+
reason="IntegrationService requires an independent Contract",
|
|
121
|
+
)
|
|
122
|
+
else:
|
|
123
|
+
_validate_contract(
|
|
124
|
+
metadata.contract,
|
|
125
|
+
symbol=f"{candidate.__module__}.{candidate.__qualname__}",
|
|
126
|
+
)
|
|
127
|
+
_validate_connection_name(metadata.connection_name)
|
|
128
|
+
return metadata
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _inherited_contracts(candidate: type[object]) -> tuple[type[object], ...]:
|
|
132
|
+
return tuple(
|
|
133
|
+
base
|
|
134
|
+
for base in candidate.__mro__[1:]
|
|
135
|
+
if base is not ApplicationServiceContract
|
|
136
|
+
and ApplicationServiceContract in base.__mro__
|
|
137
|
+
and getattr(base, "_is_protocol", False)
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _infer_contract(candidate: type[object]) -> type[ApplicationServiceContract] | None:
|
|
142
|
+
contracts = _inherited_contracts(candidate)
|
|
143
|
+
most_specific = tuple(
|
|
144
|
+
base for base in contracts if not any(base in other.__mro__[1:] for other in contracts)
|
|
145
|
+
)
|
|
146
|
+
if not most_specific and not issubclass(candidate, IntegrationService):
|
|
147
|
+
return None
|
|
148
|
+
if len(most_specific) != 1:
|
|
149
|
+
raise ApplicationServiceDefinitionError(
|
|
150
|
+
symbol=f"{candidate.__module__}.{candidate.__qualname__}",
|
|
151
|
+
reason=(
|
|
152
|
+
"ApplicationService requires one inherited business contract "
|
|
153
|
+
"or explicit provides=Contract"
|
|
154
|
+
),
|
|
155
|
+
)
|
|
156
|
+
return most_specific[0]
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _validate_contract(candidate: object, *, symbol: str) -> None:
|
|
160
|
+
is_protocol = isinstance(candidate, type) and bool(getattr(candidate, "_is_protocol", False))
|
|
161
|
+
inherits_marker = (
|
|
162
|
+
isinstance(candidate, type) and ApplicationServiceContract in candidate.__mro__
|
|
163
|
+
)
|
|
164
|
+
if not is_protocol or not inherits_marker or candidate is ApplicationServiceContract:
|
|
165
|
+
raise ApplicationServiceDefinitionError(
|
|
166
|
+
symbol=symbol,
|
|
167
|
+
reason="provides must be an ApplicationServiceContract Protocol",
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _is_internal_contributor(candidate: type[object]) -> bool:
|
|
172
|
+
return issubclass(candidate, _InternalApplicationServiceContributor)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _validate_connection_name(connection_name: str) -> str:
|
|
176
|
+
if not isinstance(connection_name, str) or not connection_name:
|
|
177
|
+
raise ApplicationServiceDefinitionError(
|
|
178
|
+
symbol="<decorator>",
|
|
179
|
+
reason="connection_name must be a non-empty string",
|
|
180
|
+
)
|
|
181
|
+
if connection_name != connection_name.strip() or any(
|
|
182
|
+
not (character.isalnum() or character in "_-") for character in connection_name
|
|
183
|
+
):
|
|
184
|
+
raise ApplicationServiceDefinitionError(
|
|
185
|
+
symbol="<decorator>",
|
|
186
|
+
reason="connection_name must contain only letters, digits, '_' or '-'",
|
|
187
|
+
)
|
|
188
|
+
return connection_name
|