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,381 @@
|
|
|
1
|
+
"""default/explicit/override/multi 的单一仲裁策略与稳定 provider order。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from dishka import BaseScope, DependencyKey, Provider
|
|
8
|
+
from dishka.dependency_source import Alias, Factory, FactoryUnionMode
|
|
9
|
+
from dishka.entities.factory_type import FactoryType
|
|
10
|
+
|
|
11
|
+
from ..diagnostics import SourceLocation
|
|
12
|
+
from ..errors import RegistrationConflictError
|
|
13
|
+
from ..modularity import ModuleRegistry
|
|
14
|
+
from .contribution import _ContributionEntry
|
|
15
|
+
from .provider import _NativeProviderInspector
|
|
16
|
+
from .registration import (
|
|
17
|
+
ProviderContribution,
|
|
18
|
+
RegistrationDecision,
|
|
19
|
+
RegistrationKind,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(slots=True)
|
|
24
|
+
class _RegistrationDraft:
|
|
25
|
+
sequence: int
|
|
26
|
+
entry: _ContributionEntry
|
|
27
|
+
key: DependencyKey
|
|
28
|
+
scope: BaseScope | None
|
|
29
|
+
factory: Factory | None
|
|
30
|
+
collection: FactoryUnionMode | None
|
|
31
|
+
collection_source_key: DependencyKey | None
|
|
32
|
+
is_collection: bool
|
|
33
|
+
decision: RegistrationDecision = RegistrationDecision.ACCEPTED
|
|
34
|
+
decision_reason: str = "accepted by service composition policy"
|
|
35
|
+
replaced_sources: tuple[SourceLocation, ...] = ()
|
|
36
|
+
multi_ordinal: int | None = None
|
|
37
|
+
alias: Alias | None = None
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def contribution(self) -> ProviderContribution:
|
|
41
|
+
return self.entry.contribution
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True, slots=True)
|
|
45
|
+
class _ArbitrationResult:
|
|
46
|
+
providers: tuple[Provider, ...]
|
|
47
|
+
drafts: tuple[_RegistrationDraft, ...]
|
|
48
|
+
ordered_entries: tuple[_ContributionEntry, ...]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class _ServiceArbitrator:
|
|
52
|
+
"""拥有一次 service policy decision;只修改尚未发布的 registration drafts。"""
|
|
53
|
+
|
|
54
|
+
__slots__ = ("_registry",)
|
|
55
|
+
|
|
56
|
+
def __init__(self, registry: ModuleRegistry) -> None:
|
|
57
|
+
self._registry = registry
|
|
58
|
+
|
|
59
|
+
def arbitrate(self, entries: tuple[_ContributionEntry, ...]) -> _ArbitrationResult:
|
|
60
|
+
ordered_entries = self._order_entries(entries)
|
|
61
|
+
drafts = self._create_drafts(ordered_entries)
|
|
62
|
+
factories = tuple(draft for draft in drafts if not draft.is_collection)
|
|
63
|
+
collections = tuple(draft for draft in drafts if draft.is_collection)
|
|
64
|
+
self._validate_collection_output_keys(factories, collections)
|
|
65
|
+
self._arbitrate_multi(factories, collections)
|
|
66
|
+
self._arbitrate_single(factories)
|
|
67
|
+
retained_providers = self._retained_providers(ordered_entries, drafts)
|
|
68
|
+
return _ArbitrationResult(
|
|
69
|
+
providers=retained_providers,
|
|
70
|
+
drafts=drafts,
|
|
71
|
+
ordered_entries=ordered_entries,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def _order_entries(
|
|
75
|
+
self, entries: tuple[_ContributionEntry, ...]
|
|
76
|
+
) -> tuple[_ContributionEntry, ...]:
|
|
77
|
+
module_positions = {
|
|
78
|
+
descriptor.key: position
|
|
79
|
+
for position, descriptor in enumerate(self._registry.dependency_order)
|
|
80
|
+
}
|
|
81
|
+
return tuple(
|
|
82
|
+
sorted(
|
|
83
|
+
entries,
|
|
84
|
+
key=lambda entry: (
|
|
85
|
+
module_positions[entry.contribution.owner],
|
|
86
|
+
entry.contribution.declaration_index,
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
@staticmethod
|
|
92
|
+
def _create_drafts(
|
|
93
|
+
entries: tuple[_ContributionEntry, ...],
|
|
94
|
+
) -> tuple[_RegistrationDraft, ...]:
|
|
95
|
+
drafts: list[_RegistrationDraft] = []
|
|
96
|
+
for entry in entries:
|
|
97
|
+
contribution = entry.contribution
|
|
98
|
+
validated = _NativeProviderInspector(
|
|
99
|
+
source=contribution.source, kind=contribution.kind
|
|
100
|
+
).inspect(contribution.provider)
|
|
101
|
+
if validated.signature != entry.signature:
|
|
102
|
+
raise RegistrationConflictError(
|
|
103
|
+
key=None,
|
|
104
|
+
sources=(contribution.source,),
|
|
105
|
+
reason="native Provider was mutated after contribution",
|
|
106
|
+
)
|
|
107
|
+
for factory in validated.factories:
|
|
108
|
+
drafts.append(
|
|
109
|
+
_RegistrationDraft(
|
|
110
|
+
sequence=len(drafts),
|
|
111
|
+
entry=entry,
|
|
112
|
+
key=factory.key,
|
|
113
|
+
scope=factory.scope,
|
|
114
|
+
factory=factory.factory,
|
|
115
|
+
collection=None,
|
|
116
|
+
collection_source_key=None,
|
|
117
|
+
is_collection=False,
|
|
118
|
+
alias=factory.alias,
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
for collection in validated.collections:
|
|
122
|
+
drafts.append(
|
|
123
|
+
_RegistrationDraft(
|
|
124
|
+
sequence=len(drafts),
|
|
125
|
+
entry=entry,
|
|
126
|
+
key=collection.key,
|
|
127
|
+
scope=collection.scope,
|
|
128
|
+
factory=None,
|
|
129
|
+
collection=collection.collection,
|
|
130
|
+
collection_source_key=collection.source_key,
|
|
131
|
+
is_collection=True,
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
return tuple(drafts)
|
|
135
|
+
|
|
136
|
+
@classmethod
|
|
137
|
+
def _validate_collection_output_keys(
|
|
138
|
+
cls,
|
|
139
|
+
factories: tuple[_RegistrationDraft, ...],
|
|
140
|
+
collections: tuple[_RegistrationDraft, ...],
|
|
141
|
+
) -> None:
|
|
142
|
+
single_by_key = cls._group_by_key(
|
|
143
|
+
tuple(
|
|
144
|
+
draft
|
|
145
|
+
for draft in factories
|
|
146
|
+
if draft.contribution.kind is not RegistrationKind.MULTI
|
|
147
|
+
)
|
|
148
|
+
)
|
|
149
|
+
for collection in collections:
|
|
150
|
+
conflicts = single_by_key.get(collection.key, ())
|
|
151
|
+
if conflicts:
|
|
152
|
+
raise RegistrationConflictError(
|
|
153
|
+
key=collection.key,
|
|
154
|
+
sources=(
|
|
155
|
+
collection.contribution.source,
|
|
156
|
+
*(item.contribution.source for item in conflicts),
|
|
157
|
+
),
|
|
158
|
+
reason="native collect() output key conflicts with a single binding",
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
@classmethod
|
|
162
|
+
def _arbitrate_multi(
|
|
163
|
+
cls,
|
|
164
|
+
factories: tuple[_RegistrationDraft, ...],
|
|
165
|
+
collections: tuple[_RegistrationDraft, ...],
|
|
166
|
+
) -> None:
|
|
167
|
+
multi_factories = tuple(
|
|
168
|
+
draft for draft in factories if draft.contribution.kind is RegistrationKind.MULTI
|
|
169
|
+
)
|
|
170
|
+
single_by_key = cls._group_by_key(
|
|
171
|
+
tuple(
|
|
172
|
+
draft
|
|
173
|
+
for draft in factories
|
|
174
|
+
if draft.contribution.kind is not RegistrationKind.MULTI
|
|
175
|
+
)
|
|
176
|
+
)
|
|
177
|
+
collectors_by_source: dict[DependencyKey, list[_RegistrationDraft]] = {}
|
|
178
|
+
for collection in collections:
|
|
179
|
+
assert collection.collection_source_key is not None
|
|
180
|
+
collectors_by_source.setdefault(collection.collection_source_key, []).append(collection)
|
|
181
|
+
|
|
182
|
+
for key, items in cls._group_by_key(multi_factories).items():
|
|
183
|
+
if key in single_by_key:
|
|
184
|
+
raise RegistrationConflictError(
|
|
185
|
+
key=key,
|
|
186
|
+
sources=tuple(
|
|
187
|
+
item.contribution.source for item in (*items, *single_by_key[key])
|
|
188
|
+
),
|
|
189
|
+
reason="a dependency key cannot be both single and multi",
|
|
190
|
+
)
|
|
191
|
+
collectors = collectors_by_source.get(key, [])
|
|
192
|
+
if len(collectors) != 1:
|
|
193
|
+
raise RegistrationConflictError(
|
|
194
|
+
key=key,
|
|
195
|
+
sources=tuple(item.contribution.source for item in (*items, *collectors)),
|
|
196
|
+
reason="multi key must have exactly one native collect() definition",
|
|
197
|
+
)
|
|
198
|
+
seen_sources: dict[object, _RegistrationDraft] = {}
|
|
199
|
+
for ordinal, item in enumerate(items):
|
|
200
|
+
assert item.factory is not None
|
|
201
|
+
source_identity = (
|
|
202
|
+
tuple(item.factory.dependencies)
|
|
203
|
+
if item.factory.type is FactoryType.ALIAS
|
|
204
|
+
else _NativeProviderInspector.source_identity(item.factory.source)
|
|
205
|
+
)
|
|
206
|
+
previous = seen_sources.get(source_identity)
|
|
207
|
+
if previous is not None:
|
|
208
|
+
raise RegistrationConflictError(
|
|
209
|
+
key=key,
|
|
210
|
+
sources=(previous.contribution.source, item.contribution.source),
|
|
211
|
+
reason="duplicate multi contribution uses the same native factory source",
|
|
212
|
+
)
|
|
213
|
+
seen_sources[source_identity] = item
|
|
214
|
+
item.multi_ordinal = ordinal
|
|
215
|
+
item.decision_reason = "accepted as ordered native collect() item"
|
|
216
|
+
|
|
217
|
+
for source_key, collectors in collectors_by_source.items():
|
|
218
|
+
if len(collectors) > 1:
|
|
219
|
+
raise RegistrationConflictError(
|
|
220
|
+
key=source_key,
|
|
221
|
+
sources=tuple(item.contribution.source for item in collectors),
|
|
222
|
+
reason="multi key has more than one native collect() definition",
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
def _arbitrate_single(self, factories: tuple[_RegistrationDraft, ...]) -> None:
|
|
226
|
+
singles = tuple(
|
|
227
|
+
draft for draft in factories if draft.contribution.kind is not RegistrationKind.MULTI
|
|
228
|
+
)
|
|
229
|
+
for key, candidates in self._group_by_key(singles).items():
|
|
230
|
+
defaults = tuple(
|
|
231
|
+
candidate
|
|
232
|
+
for candidate in candidates
|
|
233
|
+
if candidate.contribution.kind
|
|
234
|
+
in (RegistrationKind.DEFAULT, RegistrationKind.CONVENTION)
|
|
235
|
+
)
|
|
236
|
+
explicits = tuple(
|
|
237
|
+
candidate
|
|
238
|
+
for candidate in candidates
|
|
239
|
+
if candidate.contribution.kind is RegistrationKind.EXPLICIT
|
|
240
|
+
)
|
|
241
|
+
overrides = tuple(
|
|
242
|
+
candidate
|
|
243
|
+
for candidate in candidates
|
|
244
|
+
if candidate.contribution.kind is RegistrationKind.OVERRIDE
|
|
245
|
+
)
|
|
246
|
+
base = self._select_single_base(key, defaults, explicits)
|
|
247
|
+
self._apply_override_chain(key, base, overrides)
|
|
248
|
+
|
|
249
|
+
@staticmethod
|
|
250
|
+
def _select_single_base(
|
|
251
|
+
key: DependencyKey,
|
|
252
|
+
defaults: tuple[_RegistrationDraft, ...],
|
|
253
|
+
explicits: tuple[_RegistrationDraft, ...],
|
|
254
|
+
) -> _RegistrationDraft | None:
|
|
255
|
+
if len(explicits) > 1:
|
|
256
|
+
raise RegistrationConflictError(
|
|
257
|
+
key=key,
|
|
258
|
+
sources=tuple(item.contribution.source for item in explicits),
|
|
259
|
+
reason="multiple explicit single bindings require an explicit override",
|
|
260
|
+
)
|
|
261
|
+
if explicits:
|
|
262
|
+
explicit = explicits[0]
|
|
263
|
+
for default in defaults:
|
|
264
|
+
default.decision = RegistrationDecision.BACKED_OFF
|
|
265
|
+
default.decision_reason = (
|
|
266
|
+
f"{default.contribution.reason}; backed off because explicit contribution from "
|
|
267
|
+
f"{explicit.contribution.owner} exists"
|
|
268
|
+
)
|
|
269
|
+
explicit.decision_reason = (
|
|
270
|
+
f"{explicit.contribution.reason}; accepted as the unique explicit single binding"
|
|
271
|
+
)
|
|
272
|
+
return explicit
|
|
273
|
+
if len(defaults) > 1:
|
|
274
|
+
raise RegistrationConflictError(
|
|
275
|
+
key=key,
|
|
276
|
+
sources=tuple(item.contribution.source for item in defaults),
|
|
277
|
+
reason="multiple default/convention single bindings are ambiguous",
|
|
278
|
+
)
|
|
279
|
+
if defaults:
|
|
280
|
+
defaults[0].decision_reason = (
|
|
281
|
+
f"{defaults[0].contribution.reason}; accepted because no explicit "
|
|
282
|
+
"single binding exists"
|
|
283
|
+
)
|
|
284
|
+
return defaults[0]
|
|
285
|
+
return None
|
|
286
|
+
|
|
287
|
+
def _apply_override_chain(
|
|
288
|
+
self,
|
|
289
|
+
key: DependencyKey,
|
|
290
|
+
base: _RegistrationDraft | None,
|
|
291
|
+
overrides: tuple[_RegistrationDraft, ...],
|
|
292
|
+
) -> None:
|
|
293
|
+
if not overrides:
|
|
294
|
+
return
|
|
295
|
+
if base is None:
|
|
296
|
+
raise RegistrationConflictError(
|
|
297
|
+
key=key,
|
|
298
|
+
sources=tuple(item.contribution.source for item in overrides),
|
|
299
|
+
reason="override target does not exist",
|
|
300
|
+
)
|
|
301
|
+
current = base
|
|
302
|
+
chain = [base.contribution.source]
|
|
303
|
+
for override in overrides:
|
|
304
|
+
descriptor = self._registry.descriptors[override.contribution.owner]
|
|
305
|
+
if current.contribution.owner not in descriptor.dependencies:
|
|
306
|
+
raise RegistrationConflictError(
|
|
307
|
+
key=key,
|
|
308
|
+
sources=(current.contribution.source, override.contribution.source),
|
|
309
|
+
reason=(
|
|
310
|
+
f"override owner {override.contribution.owner} must directly depend on "
|
|
311
|
+
f"current target owner {current.contribution.owner}"
|
|
312
|
+
),
|
|
313
|
+
)
|
|
314
|
+
current.decision = RegistrationDecision.OVERRIDDEN
|
|
315
|
+
current.decision_reason = f"overridden by {override.contribution.owner}"
|
|
316
|
+
override.replaced_sources = tuple(chain)
|
|
317
|
+
override.decision_reason = f"accepted explicit override of {current.contribution.owner}"
|
|
318
|
+
chain.append(override.contribution.source)
|
|
319
|
+
current = override
|
|
320
|
+
|
|
321
|
+
@staticmethod
|
|
322
|
+
def _retained_providers(
|
|
323
|
+
entries: tuple[_ContributionEntry, ...],
|
|
324
|
+
drafts: tuple[_RegistrationDraft, ...],
|
|
325
|
+
) -> tuple[Provider, ...]:
|
|
326
|
+
drafts_by_entry: dict[int, list[_RegistrationDraft]] = {}
|
|
327
|
+
for draft in drafts:
|
|
328
|
+
drafts_by_entry.setdefault(draft.entry.entry_id, []).append(draft)
|
|
329
|
+
retained: list[Provider] = []
|
|
330
|
+
for entry in entries:
|
|
331
|
+
entry_drafts = drafts_by_entry[entry.entry_id]
|
|
332
|
+
active = tuple(
|
|
333
|
+
draft
|
|
334
|
+
for draft in entry_drafts
|
|
335
|
+
if draft.decision is not RegistrationDecision.BACKED_OFF
|
|
336
|
+
)
|
|
337
|
+
if not active:
|
|
338
|
+
continue
|
|
339
|
+
if len(active) == len(entry_drafts):
|
|
340
|
+
retained.append(entry.contribution.provider)
|
|
341
|
+
continue
|
|
342
|
+
# 仲裁粒度是 dependency source,而 Provider 只是 native 输入批次。
|
|
343
|
+
# 局部 back-off 时仅重组原生 source 集合,不复制 factory model。
|
|
344
|
+
retained.append(_provider_with_active_sources(entry.contribution.provider, active))
|
|
345
|
+
return tuple(retained)
|
|
346
|
+
|
|
347
|
+
@staticmethod
|
|
348
|
+
def _group_by_key(
|
|
349
|
+
drafts: tuple[_RegistrationDraft, ...],
|
|
350
|
+
) -> dict[DependencyKey, tuple[_RegistrationDraft, ...]]:
|
|
351
|
+
grouped: dict[DependencyKey, list[_RegistrationDraft]] = {}
|
|
352
|
+
try:
|
|
353
|
+
for draft in drafts:
|
|
354
|
+
grouped.setdefault(draft.key, []).append(draft)
|
|
355
|
+
except TypeError as original_error:
|
|
356
|
+
error = RegistrationConflictError(
|
|
357
|
+
key=None,
|
|
358
|
+
sources=tuple(draft.contribution.source for draft in drafts),
|
|
359
|
+
reason="native DependencyKey must be hashable",
|
|
360
|
+
)
|
|
361
|
+
raise error from original_error
|
|
362
|
+
return {key: tuple(values) for key, values in grouped.items()}
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _provider_with_active_sources(
|
|
366
|
+
provider: Provider,
|
|
367
|
+
drafts: tuple[_RegistrationDraft, ...],
|
|
368
|
+
) -> Provider:
|
|
369
|
+
retained = Provider(
|
|
370
|
+
scope=provider.scope,
|
|
371
|
+
component=provider.component,
|
|
372
|
+
when=provider.when,
|
|
373
|
+
)
|
|
374
|
+
retained.factories.extend(
|
|
375
|
+
draft.factory for draft in drafts if draft.factory is not None and draft.alias is None
|
|
376
|
+
)
|
|
377
|
+
retained.aliases.extend(draft.alias for draft in drafts if draft.alias is not None)
|
|
378
|
+
retained.factory_union_mode.extend(
|
|
379
|
+
draft.collection for draft in drafts if draft.collection is not None
|
|
380
|
+
)
|
|
381
|
+
return retained
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""通用注册声明到 Dishka factory/alias/collect 的原生映射。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
from dishka import BaseScope, FromComponent, Provider
|
|
8
|
+
|
|
9
|
+
from ..application_services.contracts import ApplicationService
|
|
10
|
+
from ..diagnostics import SourceLocation
|
|
11
|
+
from ..errors import RegistrationConflictError
|
|
12
|
+
from .registration import RegistrationKind
|
|
13
|
+
|
|
14
|
+
_COLLECTION_COMPONENT = "python_platform.services.collection"
|
|
15
|
+
_REGISTRATION_COMPONENT = "python_platform.services.registration"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _registration_providers(
|
|
19
|
+
service: type[object],
|
|
20
|
+
implementation: type[object] | None,
|
|
21
|
+
*,
|
|
22
|
+
scope: BaseScope,
|
|
23
|
+
cache: bool,
|
|
24
|
+
component: str,
|
|
25
|
+
multi: bool,
|
|
26
|
+
default: bool,
|
|
27
|
+
override: bool,
|
|
28
|
+
identity: str,
|
|
29
|
+
source: SourceLocation,
|
|
30
|
+
) -> tuple[tuple[Provider, RegistrationKind], ...]:
|
|
31
|
+
if not isinstance(service, type) or (
|
|
32
|
+
implementation is not None and not isinstance(implementation, type)
|
|
33
|
+
):
|
|
34
|
+
raise RegistrationConflictError(
|
|
35
|
+
key=None,
|
|
36
|
+
sources=(source,),
|
|
37
|
+
reason="registration requires service and implementation classes",
|
|
38
|
+
)
|
|
39
|
+
if not isinstance(component, str) or component.startswith(
|
|
40
|
+
(_COLLECTION_COMPONENT, _REGISTRATION_COMPONENT)
|
|
41
|
+
):
|
|
42
|
+
raise RegistrationConflictError(
|
|
43
|
+
key=None, sources=(source,), reason="component must be a non-reserved string"
|
|
44
|
+
)
|
|
45
|
+
if override and (multi or default):
|
|
46
|
+
raise RegistrationConflictError(
|
|
47
|
+
key=None, sources=(source,), reason="override cannot also declare multi or default"
|
|
48
|
+
)
|
|
49
|
+
kind = RegistrationKind.OVERRIDE if override else RegistrationKind.EXPLICIT
|
|
50
|
+
provider = Provider(scope=scope, component=component)
|
|
51
|
+
concrete = implementation or service
|
|
52
|
+
if issubclass(concrete, ApplicationService):
|
|
53
|
+
raise RegistrationConflictError(
|
|
54
|
+
key=None,
|
|
55
|
+
sources=(source,),
|
|
56
|
+
reason=(
|
|
57
|
+
"ApplicationService registration requires convention discovery "
|
|
58
|
+
"and its contract proxy"
|
|
59
|
+
),
|
|
60
|
+
)
|
|
61
|
+
if not multi:
|
|
62
|
+
provider.provide(concrete, provides=service, cache=cache, override=override)
|
|
63
|
+
if default and component:
|
|
64
|
+
provider.alias(service, provides=Annotated[service, FromComponent("")], cache=False)
|
|
65
|
+
# default=True 是明确选择;旧 DEFAULT contribution 的 fallback 语义不参与这里。
|
|
66
|
+
return ((provider, kind),)
|
|
67
|
+
|
|
68
|
+
# 每项只有一个创建来源。公开指定项、默认项和集合项都通过原生 alias 引用它;
|
|
69
|
+
# alias 不缓存,避免把 transient 意外提升为 scoped。
|
|
70
|
+
source_component = f"{_REGISTRATION_COMPONENT}.{identity}"
|
|
71
|
+
native_source: object = Annotated[service, FromComponent(source_component)]
|
|
72
|
+
provider.provide(concrete, provides=native_source, cache=cache)
|
|
73
|
+
if component:
|
|
74
|
+
provider.alias(service, component=source_component, provides=service, cache=False)
|
|
75
|
+
if default:
|
|
76
|
+
provider.alias(
|
|
77
|
+
service,
|
|
78
|
+
component=source_component,
|
|
79
|
+
provides=Annotated[service, FromComponent("")],
|
|
80
|
+
cache=False,
|
|
81
|
+
)
|
|
82
|
+
member = Provider(scope=scope)
|
|
83
|
+
member.alias(
|
|
84
|
+
service,
|
|
85
|
+
component=source_component,
|
|
86
|
+
provides=Annotated[service, FromComponent(_COLLECTION_COMPONENT)],
|
|
87
|
+
cache=False,
|
|
88
|
+
)
|
|
89
|
+
# collect 收集 factory;用 Alias 自己的转换保留其依赖、实例身份和失败语义。
|
|
90
|
+
member.factories.extend(alias.as_factory(scope, member.component) for alias in member.aliases)
|
|
91
|
+
member.aliases.clear()
|
|
92
|
+
return ((provider, kind), (member, RegistrationKind.MULTI))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _collection_provider(service: type[object]) -> Provider:
|
|
96
|
+
provider = Provider()
|
|
97
|
+
provider.collect(
|
|
98
|
+
Annotated[service, FromComponent(_COLLECTION_COMPONENT)],
|
|
99
|
+
provides=list.__class_getitem__(service),
|
|
100
|
+
cache=False,
|
|
101
|
+
)
|
|
102
|
+
return provider
|