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,31 @@
|
|
|
1
|
+
"""Pydantic-backed immutable domain values."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, ConfigDict
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ValueObject(BaseModel):
|
|
11
|
+
"""按具体类型与字段值比较的领域值;它不是 transport DTO。"""
|
|
12
|
+
|
|
13
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
def __pydantic_init_subclass__(cls, **kwargs: Any) -> None:
|
|
17
|
+
super().__pydantic_init_subclass__(**kwargs)
|
|
18
|
+
if cls.model_config.get("frozen") is not True or cls.model_config.get("extra") != "forbid":
|
|
19
|
+
raise TypeError("ValueObject subclasses must remain frozen and forbid extra fields")
|
|
20
|
+
|
|
21
|
+
def __eq__(self, other: object) -> bool:
|
|
22
|
+
if type(self) is not type(other):
|
|
23
|
+
return False
|
|
24
|
+
return self._field_values() == other._field_values()
|
|
25
|
+
|
|
26
|
+
def __hash__(self) -> int:
|
|
27
|
+
# 字段中含 list/dict 等 mutable value 时由 Python 原生 hash fail closed。
|
|
28
|
+
return hash((type(self), self._field_values()))
|
|
29
|
+
|
|
30
|
+
def _field_values(self) -> tuple[object, ...]:
|
|
31
|
+
return tuple(getattr(self, name) for name in type(self).model_fields)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""按 capability 组织的 Python Platform 结构化错误公开边界。"""
|
|
2
|
+
|
|
3
|
+
from .base import FrameworkError, ResourceConflictError, ResourceNotFoundError
|
|
4
|
+
from .business import BusinessError, BusinessErrorDefinition, BusinessErrorField
|
|
5
|
+
from .configuration import (
|
|
6
|
+
ConfigurationSourceError,
|
|
7
|
+
DuplicateOptionDefinitionError,
|
|
8
|
+
InvalidOptionDefinitionError,
|
|
9
|
+
OptionsContributionClosedError,
|
|
10
|
+
OptionsValidationError,
|
|
11
|
+
UnknownOptionsError,
|
|
12
|
+
)
|
|
13
|
+
from .diagnostics import InvalidDiagnosticCodeError
|
|
14
|
+
from .lifecycle import (
|
|
15
|
+
ApplicationRuntimeError,
|
|
16
|
+
ApplicationStartError,
|
|
17
|
+
CompositionResourceError,
|
|
18
|
+
InvalidApplicationStateError,
|
|
19
|
+
LifecycleInvocationError,
|
|
20
|
+
LifecycleReturnValueError,
|
|
21
|
+
)
|
|
22
|
+
from .modularity import (
|
|
23
|
+
DuplicateModuleIdentityError,
|
|
24
|
+
GraphInvariantError,
|
|
25
|
+
InvalidModuleReferenceError,
|
|
26
|
+
ModuleCycleError,
|
|
27
|
+
ModuleInstantiationError,
|
|
28
|
+
ModuleResolutionError,
|
|
29
|
+
UnknownModuleError,
|
|
30
|
+
)
|
|
31
|
+
from .services import (
|
|
32
|
+
CatalogNativeGraphMismatchError,
|
|
33
|
+
ContainerCleanupError,
|
|
34
|
+
ContainerValidationError,
|
|
35
|
+
ConventionScanError,
|
|
36
|
+
InvalidConventionDefinitionError,
|
|
37
|
+
RegistrationConflictError,
|
|
38
|
+
ServiceContributorClosedError,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
__all__ = (
|
|
42
|
+
"ApplicationRuntimeError",
|
|
43
|
+
"ApplicationStartError",
|
|
44
|
+
"BusinessError",
|
|
45
|
+
"BusinessErrorDefinition",
|
|
46
|
+
"BusinessErrorField",
|
|
47
|
+
"CatalogNativeGraphMismatchError",
|
|
48
|
+
"CompositionResourceError",
|
|
49
|
+
"ConfigurationSourceError",
|
|
50
|
+
"ContainerCleanupError",
|
|
51
|
+
"ContainerValidationError",
|
|
52
|
+
"ConventionScanError",
|
|
53
|
+
"DuplicateModuleIdentityError",
|
|
54
|
+
"DuplicateOptionDefinitionError",
|
|
55
|
+
"FrameworkError",
|
|
56
|
+
"GraphInvariantError",
|
|
57
|
+
"InvalidApplicationStateError",
|
|
58
|
+
"InvalidConventionDefinitionError",
|
|
59
|
+
"InvalidDiagnosticCodeError",
|
|
60
|
+
"InvalidModuleReferenceError",
|
|
61
|
+
"InvalidOptionDefinitionError",
|
|
62
|
+
"LifecycleInvocationError",
|
|
63
|
+
"LifecycleReturnValueError",
|
|
64
|
+
"ModuleCycleError",
|
|
65
|
+
"ModuleInstantiationError",
|
|
66
|
+
"ModuleResolutionError",
|
|
67
|
+
"OptionsContributionClosedError",
|
|
68
|
+
"OptionsValidationError",
|
|
69
|
+
"RegistrationConflictError",
|
|
70
|
+
"ResourceConflictError",
|
|
71
|
+
"ResourceNotFoundError",
|
|
72
|
+
"ServiceContributorClosedError",
|
|
73
|
+
"UnknownModuleError",
|
|
74
|
+
"UnknownOptionsError",
|
|
75
|
+
)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""所有 capability error 共享的最小命名根。"""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class FrameworkError(Exception):
|
|
5
|
+
"""所有 Python Platform 结构化错误的命名基类。"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ResourceConflictError(FrameworkError):
|
|
9
|
+
"""可信业务边界可映射为 HTTP 409 的静态冲突。"""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ResourceNotFoundError(FrameworkError):
|
|
13
|
+
"""可信业务边界可映射为 HTTP 404 的静态缺失。"""
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""业务模块集中定义的公开安全错误,不把异常正文或动态输入当作响应。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
from .base import FrameworkError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True, slots=True)
|
|
13
|
+
class BusinessErrorDefinition:
|
|
14
|
+
code: str
|
|
15
|
+
message: str
|
|
16
|
+
|
|
17
|
+
def __post_init__(self) -> None:
|
|
18
|
+
if (
|
|
19
|
+
not isinstance(self.code, str)
|
|
20
|
+
or not self.code
|
|
21
|
+
or any(not (part.isascii() and (part.isalnum() or part in "._")) for part in self.code)
|
|
22
|
+
):
|
|
23
|
+
raise ValueError("business error code must be a stable non-empty identifier")
|
|
24
|
+
if not isinstance(self.message, str) or not self.message.strip():
|
|
25
|
+
raise ValueError("business error message must be a static safe message")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True, slots=True)
|
|
29
|
+
class BusinessErrorField:
|
|
30
|
+
model_type: type[BaseModel]
|
|
31
|
+
name: str
|
|
32
|
+
|
|
33
|
+
def __post_init__(self) -> None:
|
|
34
|
+
# 字段必须来自静态 DTO contract,不能把运行时字典键或用户输入混入定位信息。
|
|
35
|
+
if not isinstance(self.model_type, type) or not issubclass(self.model_type, BaseModel):
|
|
36
|
+
raise TypeError("business error field requires a Pydantic model type")
|
|
37
|
+
if self.name not in self.model_type.model_fields:
|
|
38
|
+
raise ValueError("business error field must name a declared model field")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class BusinessError(FrameworkError):
|
|
42
|
+
def __init__(
|
|
43
|
+
self, definition: BusinessErrorDefinition, *, fields: tuple[BusinessErrorField, ...] = ()
|
|
44
|
+
) -> None:
|
|
45
|
+
if not isinstance(definition, BusinessErrorDefinition):
|
|
46
|
+
raise TypeError("business error requires a static definition")
|
|
47
|
+
if not isinstance(fields, tuple) or any(
|
|
48
|
+
not isinstance(field, BusinessErrorField) for field in fields
|
|
49
|
+
):
|
|
50
|
+
raise TypeError("business error fields must be an immutable tuple")
|
|
51
|
+
self.definition = definition
|
|
52
|
+
self.fields = fields
|
|
53
|
+
super().__init__(definition.code)
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Configuration 与 typed Options 的结构化失败。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from .base import FrameworkError
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from pydantic import BaseModel, ValidationError
|
|
11
|
+
|
|
12
|
+
from ..diagnostics import SourceLocation
|
|
13
|
+
from ..modularity import ModuleKey
|
|
14
|
+
from ..options import OptionsValidationIssue
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ConfigurationSourceError(FrameworkError):
|
|
18
|
+
"""配置来源加载或规范化失败;消息不复制可能含敏感值的底层异常文本。"""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
*,
|
|
23
|
+
source_id: str,
|
|
24
|
+
location: SourceLocation,
|
|
25
|
+
original_error: BaseException,
|
|
26
|
+
) -> None:
|
|
27
|
+
self.source_id = source_id
|
|
28
|
+
self.location = location
|
|
29
|
+
self.original_error = original_error
|
|
30
|
+
self.__cause__ = original_error
|
|
31
|
+
self.__suppress_context__ = True
|
|
32
|
+
super().__init__(f"Unable to load configuration source {source_id!r}")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class InvalidOptionDefinitionError(FrameworkError):
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
*,
|
|
39
|
+
owner: ModuleKey,
|
|
40
|
+
model_type: type[BaseModel] | None,
|
|
41
|
+
option_kind: str,
|
|
42
|
+
reason: str,
|
|
43
|
+
) -> None:
|
|
44
|
+
self.owner = owner
|
|
45
|
+
self.model_type = model_type
|
|
46
|
+
self.option_kind = option_kind
|
|
47
|
+
self.reason = reason
|
|
48
|
+
model_name = (
|
|
49
|
+
f"{model_type.__module__}.{model_type.__qualname__}"
|
|
50
|
+
if model_type is not None
|
|
51
|
+
else "<invalid declaration>"
|
|
52
|
+
)
|
|
53
|
+
super().__init__(
|
|
54
|
+
f"Invalid {option_kind} definition {model_name} declared by {owner}: {reason}"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class DuplicateOptionDefinitionError(FrameworkError):
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
*,
|
|
62
|
+
model_type: type[BaseModel],
|
|
63
|
+
option_kind: str,
|
|
64
|
+
first_owner: ModuleKey,
|
|
65
|
+
second_owner: ModuleKey,
|
|
66
|
+
) -> None:
|
|
67
|
+
self.model_type = model_type
|
|
68
|
+
self.option_kind = option_kind
|
|
69
|
+
self.first_owner = first_owner
|
|
70
|
+
self.second_owner = second_owner
|
|
71
|
+
model_name = f"{model_type.__module__}.{model_type.__qualname__}"
|
|
72
|
+
super().__init__(
|
|
73
|
+
f"Duplicate {option_kind} definition for {model_name}: {first_owner} and {second_owner}"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class UnknownOptionsError(FrameworkError, KeyError):
|
|
78
|
+
def __init__(self, model_type: type[BaseModel], *, option_kind: str) -> None:
|
|
79
|
+
self.model_type = model_type
|
|
80
|
+
self.option_kind = option_kind
|
|
81
|
+
model_name = f"{model_type.__module__}.{model_type.__qualname__}"
|
|
82
|
+
super().__init__(f"Unknown {option_kind} model {model_name}")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class OptionsContributionClosedError(FrameworkError):
|
|
86
|
+
def __init__(self, *, option_kind: str, stage: str) -> None:
|
|
87
|
+
self.option_kind = option_kind
|
|
88
|
+
self.stage = stage
|
|
89
|
+
super().__init__(
|
|
90
|
+
f"{option_kind} configuration contribution is no longer active after {stage}"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class OptionsValidationError(FrameworkError):
|
|
95
|
+
"""对 Pydantic 错误增加 owner/source 语义,只保留已脱敏的 Pydantic cause。"""
|
|
96
|
+
|
|
97
|
+
def __init__(
|
|
98
|
+
self,
|
|
99
|
+
*,
|
|
100
|
+
model_type: type[BaseModel],
|
|
101
|
+
section: str,
|
|
102
|
+
owner: ModuleKey,
|
|
103
|
+
option_kind: str,
|
|
104
|
+
issues: tuple[OptionsValidationIssue, ...],
|
|
105
|
+
validation_error: ValidationError,
|
|
106
|
+
) -> None:
|
|
107
|
+
self.model_type = model_type
|
|
108
|
+
self.section = section
|
|
109
|
+
self.owner = owner
|
|
110
|
+
self.option_kind = option_kind
|
|
111
|
+
self.issues = issues
|
|
112
|
+
self.validation_error = validation_error
|
|
113
|
+
self.__cause__ = validation_error
|
|
114
|
+
self.__suppress_context__ = True
|
|
115
|
+
model_name = f"{model_type.__module__}.{model_type.__qualname__}"
|
|
116
|
+
paths = ", ".join(issue.field_path for issue in issues) or "<model>"
|
|
117
|
+
super().__init__(
|
|
118
|
+
f"Invalid {option_kind} {model_name} at section {section!r}; fields: {paths}"
|
|
119
|
+
)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Diagnostic contract 自身的边界错误。"""
|
|
2
|
+
|
|
3
|
+
from .base import FrameworkError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class InvalidDiagnosticCodeError(FrameworkError):
|
|
7
|
+
def __init__(self, code: str, *, max_length: int) -> None:
|
|
8
|
+
self.code = code
|
|
9
|
+
self.max_length = max_length
|
|
10
|
+
super().__init__(
|
|
11
|
+
f"Diagnostic code must be a stable identifier of at most {max_length} characters"
|
|
12
|
+
)
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""Application state、lifecycle invocation 与运行失败归属。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
from .base import FrameworkError
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from ..diagnostics import LifecyclePhase, LifecycleStage, SourceLocation
|
|
12
|
+
from ..hosted_services.errors import HostedServiceLifecycleError
|
|
13
|
+
from ..lifecycle import ApplicationState
|
|
14
|
+
from ..modularity import ModuleKey
|
|
15
|
+
from .services import ContainerCleanupError
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class InvalidApplicationStateError(FrameworkError):
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
*,
|
|
22
|
+
operation: str,
|
|
23
|
+
current: ApplicationState,
|
|
24
|
+
allowed: tuple[ApplicationState, ...],
|
|
25
|
+
) -> None:
|
|
26
|
+
self.operation = operation
|
|
27
|
+
self.current = current
|
|
28
|
+
self.allowed = allowed
|
|
29
|
+
allowed_values = ", ".join(state.value for state in allowed)
|
|
30
|
+
super().__init__(
|
|
31
|
+
f"Operation {operation!r} is invalid in state {current.value}; "
|
|
32
|
+
f"allowed: {allowed_values}"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class CompositionResourceError(FrameworkError):
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
*,
|
|
40
|
+
module: ModuleKey,
|
|
41
|
+
stage: LifecycleStage,
|
|
42
|
+
resource_kind: str,
|
|
43
|
+
) -> None:
|
|
44
|
+
self.module = module
|
|
45
|
+
self.stage = stage
|
|
46
|
+
self.resource_kind = resource_kind
|
|
47
|
+
super().__init__(
|
|
48
|
+
f"Composition hook {module}/{stage.value} returned forbidden {resource_kind}"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class LifecycleReturnValueError(FrameworkError):
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
*,
|
|
56
|
+
module: ModuleKey,
|
|
57
|
+
phase: LifecyclePhase,
|
|
58
|
+
stage: LifecycleStage,
|
|
59
|
+
returned_type: str,
|
|
60
|
+
) -> None:
|
|
61
|
+
self.module = module
|
|
62
|
+
self.phase = phase
|
|
63
|
+
self.stage = stage
|
|
64
|
+
self.returned_type = returned_type
|
|
65
|
+
super().__init__(
|
|
66
|
+
f"Lifecycle hook {module}/{stage.value} must return None, got {returned_type}"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class LifecycleInvocationError(FrameworkError):
|
|
71
|
+
def __init__(
|
|
72
|
+
self,
|
|
73
|
+
*,
|
|
74
|
+
failure_id: UUID,
|
|
75
|
+
module: ModuleKey,
|
|
76
|
+
phase: LifecyclePhase,
|
|
77
|
+
stage: LifecycleStage,
|
|
78
|
+
dependency_path: tuple[ModuleKey, ...],
|
|
79
|
+
duration_ns: int,
|
|
80
|
+
source: SourceLocation,
|
|
81
|
+
original_error: BaseException,
|
|
82
|
+
) -> None:
|
|
83
|
+
self.failure_id = failure_id
|
|
84
|
+
self.module = module
|
|
85
|
+
self.phase = phase
|
|
86
|
+
self.stage = stage
|
|
87
|
+
self.dependency_path = dependency_path
|
|
88
|
+
self.duration_ns = duration_ns
|
|
89
|
+
self.source = source
|
|
90
|
+
# cleanup 为 best-effort 会收集 wrapper,因此构造时显式建立因果链。
|
|
91
|
+
self.__cause__ = original_error
|
|
92
|
+
self.__suppress_context__ = True
|
|
93
|
+
self.original_error = original_error
|
|
94
|
+
super().__init__(
|
|
95
|
+
f"Lifecycle invocation failed for {module}/{stage.value} "
|
|
96
|
+
f"with {type(original_error).__name__}"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class _BackgroundExecutionStartFailure(FrameworkError):
|
|
101
|
+
"""保留原始 background start failure,同时不伪造 Module lifecycle 位置。"""
|
|
102
|
+
|
|
103
|
+
failure_id = None
|
|
104
|
+
stage = None
|
|
105
|
+
dependency_path: tuple[ModuleKey, ...] = ()
|
|
106
|
+
|
|
107
|
+
def __init__(self, *, original_error: BaseException) -> None:
|
|
108
|
+
self.original_error = original_error
|
|
109
|
+
self.__cause__ = original_error
|
|
110
|
+
self.__suppress_context__ = True
|
|
111
|
+
super().__init__(f"Background execution start failed with {type(original_error).__name__}")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class ApplicationRuntimeError(FrameworkError):
|
|
115
|
+
"""运行期基础设施 fatal fact;不携带第三方异常正文或原始 cause。"""
|
|
116
|
+
|
|
117
|
+
def __init__(self, *, component: str, error_type: str) -> None:
|
|
118
|
+
self.component = component
|
|
119
|
+
self.error_type = error_type
|
|
120
|
+
# 标识一次失败事实,不持有原始异常、task 或 provider;同名失败仍是独立事实。
|
|
121
|
+
self._source = object()
|
|
122
|
+
super().__init__(f"Application runtime {component!r} failed with {error_type}")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class ApplicationStartError(FrameworkError):
|
|
126
|
+
def __init__(
|
|
127
|
+
self,
|
|
128
|
+
*,
|
|
129
|
+
primary: (
|
|
130
|
+
LifecycleInvocationError
|
|
131
|
+
| _BackgroundExecutionStartFailure
|
|
132
|
+
| HostedServiceLifecycleError
|
|
133
|
+
),
|
|
134
|
+
cleanup_failures: tuple[
|
|
135
|
+
LifecycleInvocationError
|
|
136
|
+
| ContainerCleanupError
|
|
137
|
+
| _BackgroundExecutionStartFailure
|
|
138
|
+
| HostedServiceLifecycleError,
|
|
139
|
+
...,
|
|
140
|
+
],
|
|
141
|
+
) -> None:
|
|
142
|
+
self.primary = primary
|
|
143
|
+
self.cleanup_failures = cleanup_failures
|
|
144
|
+
if isinstance(primary, LifecycleInvocationError):
|
|
145
|
+
location = f"{primary.module}/{primary.stage.value}"
|
|
146
|
+
elif isinstance(primary, _BackgroundExecutionStartFailure):
|
|
147
|
+
location = "background-execution"
|
|
148
|
+
else:
|
|
149
|
+
location = f"hosted-service:{primary.service}/{primary.operation}"
|
|
150
|
+
super().__init__(
|
|
151
|
+
f"Application start failed at {location}; cleanup failures: {len(cleanup_failures)}"
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _exclude_runtime_failure(
|
|
156
|
+
error: BaseException, primary: ApplicationRuntimeError
|
|
157
|
+
) -> BaseException | None:
|
|
158
|
+
# 发布 fatal 与观察 drain 可能竞争;终止 owner 才能确认哪个事实已成为主失败。
|
|
159
|
+
def same_source(item: BaseException) -> bool:
|
|
160
|
+
return isinstance(item, ApplicationRuntimeError) and item._source is primary._source
|
|
161
|
+
|
|
162
|
+
if isinstance(error, BaseExceptionGroup):
|
|
163
|
+
return error.split(same_source)[1]
|
|
164
|
+
return None if same_source(error) else error
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Module reference、graph 与 registry ownership 的结构化失败。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from .base import FrameworkError
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from ..diagnostics import SourceLocation
|
|
11
|
+
from ..modularity import AppModule, ModuleDependencyEdge, ModuleKey
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class InvalidModuleReferenceError(FrameworkError):
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
*,
|
|
18
|
+
owner: ModuleKey | None,
|
|
19
|
+
reference: str,
|
|
20
|
+
reason: str,
|
|
21
|
+
source: SourceLocation | None,
|
|
22
|
+
) -> None:
|
|
23
|
+
self.owner = owner
|
|
24
|
+
self.reference = reference
|
|
25
|
+
self.reason = reason
|
|
26
|
+
self.source = source
|
|
27
|
+
super().__init__(f"Invalid module reference {reference!r}: {reason}")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ModuleResolutionError(FrameworkError):
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
*,
|
|
34
|
+
owner: ModuleKey | None,
|
|
35
|
+
reference: str,
|
|
36
|
+
resolution_chain: tuple[str, ...],
|
|
37
|
+
source: SourceLocation | None,
|
|
38
|
+
original_error: BaseException,
|
|
39
|
+
) -> None:
|
|
40
|
+
self.owner = owner
|
|
41
|
+
self.reference = reference
|
|
42
|
+
self.resolution_chain = resolution_chain
|
|
43
|
+
self.source = source
|
|
44
|
+
self.original_error = original_error
|
|
45
|
+
super().__init__(f"Unable to resolve module reference {reference!r}")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ModuleCycleError(FrameworkError):
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
*,
|
|
52
|
+
startup: ModuleKey,
|
|
53
|
+
path: tuple[ModuleKey, ...],
|
|
54
|
+
edges: tuple[ModuleDependencyEdge, ...],
|
|
55
|
+
) -> None:
|
|
56
|
+
self.startup = startup
|
|
57
|
+
self.path = path
|
|
58
|
+
self.edges = edges
|
|
59
|
+
rendered_path = " -> ".join(str(key) for key in path)
|
|
60
|
+
super().__init__(f"Module dependency cycle: {rendered_path}")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class GraphInvariantError(FrameworkError):
|
|
64
|
+
def __init__(self, detail: str) -> None:
|
|
65
|
+
self.detail = detail
|
|
66
|
+
super().__init__(f"Module graph invariant failed: {detail}")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class DuplicateModuleIdentityError(FrameworkError):
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
*,
|
|
73
|
+
key: ModuleKey,
|
|
74
|
+
first_type: type[AppModule],
|
|
75
|
+
second_type: type[AppModule],
|
|
76
|
+
) -> None:
|
|
77
|
+
self.key = key
|
|
78
|
+
self.first_type = first_type
|
|
79
|
+
self.second_type = second_type
|
|
80
|
+
super().__init__(f"Different module classes resolved to the same key {key}")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class ModuleInstantiationError(FrameworkError):
|
|
84
|
+
def __init__(
|
|
85
|
+
self,
|
|
86
|
+
*,
|
|
87
|
+
module: ModuleKey,
|
|
88
|
+
source: SourceLocation,
|
|
89
|
+
original_error: BaseException,
|
|
90
|
+
) -> None:
|
|
91
|
+
self.module = module
|
|
92
|
+
self.source = source
|
|
93
|
+
self.original_error = original_error
|
|
94
|
+
super().__init__(f"Unable to instantiate module {module}")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class UnknownModuleError(FrameworkError, KeyError):
|
|
98
|
+
def __init__(self, module: ModuleKey) -> None:
|
|
99
|
+
self.module = module
|
|
100
|
+
super().__init__(f"Unknown module {module}")
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Service composition、Convention 与 native container 的结构化失败。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
from .base import FrameworkError
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from dishka import DependencyKey
|
|
12
|
+
|
|
13
|
+
from ..diagnostics import SourceLocation
|
|
14
|
+
from ..modularity import ModuleKey
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class InvalidConventionDefinitionError(FrameworkError):
|
|
18
|
+
def __init__(self, *, symbol: str, reason: str) -> None:
|
|
19
|
+
self.symbol = symbol
|
|
20
|
+
self.reason = reason
|
|
21
|
+
super().__init__(f"Invalid convention definition {symbol}: {reason}")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ConventionScanError(FrameworkError):
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
*,
|
|
28
|
+
owner: ModuleKey,
|
|
29
|
+
package: str | None,
|
|
30
|
+
source: SourceLocation,
|
|
31
|
+
reason: str,
|
|
32
|
+
original_error: BaseException | None,
|
|
33
|
+
) -> None:
|
|
34
|
+
self.owner = owner
|
|
35
|
+
self.package = package
|
|
36
|
+
self.source = source
|
|
37
|
+
self.reason = reason
|
|
38
|
+
self.original_error = original_error
|
|
39
|
+
if original_error is not None:
|
|
40
|
+
self.__cause__ = original_error
|
|
41
|
+
self.__suppress_context__ = True
|
|
42
|
+
target = package if package is not None else "<invalid metadata>"
|
|
43
|
+
super().__init__(f"Convention scan failed for {owner}/{target}: {reason}")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ServiceContributorClosedError(FrameworkError):
|
|
47
|
+
def __init__(self, *, stage: str) -> None:
|
|
48
|
+
self.stage = stage
|
|
49
|
+
super().__init__(f"Service contributor is no longer active after {stage}")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class RegistrationConflictError(FrameworkError):
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
*,
|
|
56
|
+
key: DependencyKey | None,
|
|
57
|
+
sources: tuple[SourceLocation, ...],
|
|
58
|
+
reason: str,
|
|
59
|
+
) -> None:
|
|
60
|
+
self.key = key
|
|
61
|
+
self.sources = sources
|
|
62
|
+
self.reason = reason
|
|
63
|
+
rendered_key = str(key) if key is not None else "<provider>"
|
|
64
|
+
super().__init__(f"Service registration conflict for {rendered_key}: {reason}")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class ContainerValidationError(FrameworkError):
|
|
68
|
+
def __init__(
|
|
69
|
+
self,
|
|
70
|
+
*,
|
|
71
|
+
sources: tuple[SourceLocation, ...],
|
|
72
|
+
original_error: Exception,
|
|
73
|
+
) -> None:
|
|
74
|
+
self.sources = sources
|
|
75
|
+
self.original_error = original_error
|
|
76
|
+
self.__cause__ = original_error
|
|
77
|
+
self.__suppress_context__ = True
|
|
78
|
+
super().__init__(
|
|
79
|
+
f"Dishka strict container validation failed with {type(original_error).__name__}"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class CatalogNativeGraphMismatchError(FrameworkError):
|
|
84
|
+
def __init__(self, *, differences: tuple[str, ...]) -> None:
|
|
85
|
+
self.differences = differences
|
|
86
|
+
super().__init__(f"Service catalog/native graph mismatch: {len(differences)} difference(s)")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class ContainerCleanupError(FrameworkError):
|
|
90
|
+
def __init__(
|
|
91
|
+
self,
|
|
92
|
+
*,
|
|
93
|
+
failure_id: UUID,
|
|
94
|
+
original_error: BaseException,
|
|
95
|
+
) -> None:
|
|
96
|
+
self.failure_id = failure_id
|
|
97
|
+
self.original_error = original_error
|
|
98
|
+
self.__cause__ = original_error
|
|
99
|
+
self.__suppress_context__ = True
|
|
100
|
+
super().__init__(f"Dishka container cleanup failed with {type(original_error).__name__}")
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from .aggregate import AggregateEventCollector
|
|
2
|
+
from .catalog import LocalEventCatalog, LocalEventSubscription
|
|
3
|
+
from .contracts import LocalEventPhase, local_event_handler
|
|
4
|
+
from .contribution import EventContributor
|
|
5
|
+
from .errors import (
|
|
6
|
+
EventContributionError,
|
|
7
|
+
EventCycleError,
|
|
8
|
+
LocalEventHandlerError,
|
|
9
|
+
PostCommitEventError,
|
|
10
|
+
)
|
|
11
|
+
from .runtime import LocalEventBus
|
|
12
|
+
|
|
13
|
+
__all__ = (
|
|
14
|
+
"AggregateEventCollector",
|
|
15
|
+
"EventContributionError",
|
|
16
|
+
"EventContributor",
|
|
17
|
+
"EventCycleError",
|
|
18
|
+
"LocalEventBus",
|
|
19
|
+
"LocalEventCatalog",
|
|
20
|
+
"LocalEventHandlerError",
|
|
21
|
+
"LocalEventPhase",
|
|
22
|
+
"LocalEventSubscription",
|
|
23
|
+
"PostCommitEventError",
|
|
24
|
+
"local_event_handler",
|
|
25
|
+
)
|