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,116 @@
|
|
|
1
|
+
"""Application-owned Redis client lifecycle 与 typed JSON cache。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from collections.abc import AsyncIterator
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from pydantic import ValidationError
|
|
10
|
+
from redis.asyncio import Redis
|
|
11
|
+
from redis.exceptions import RedisError
|
|
12
|
+
|
|
13
|
+
from ..caching import CacheCatalog, CacheDefinition, DistributedCacheError
|
|
14
|
+
from ..options import Options
|
|
15
|
+
from .options import RedisOptions
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class _RedisClientOwner:
|
|
19
|
+
__slots__ = ("client",)
|
|
20
|
+
|
|
21
|
+
def __init__(self, client: Redis) -> None:
|
|
22
|
+
self.client = client
|
|
23
|
+
|
|
24
|
+
async def ping(self) -> None:
|
|
25
|
+
try:
|
|
26
|
+
await self.client.ping()
|
|
27
|
+
except RedisError as original_error:
|
|
28
|
+
raise DistributedCacheError(
|
|
29
|
+
operation="startup ping",
|
|
30
|
+
cache="redis",
|
|
31
|
+
error_type=type(original_error).__name__,
|
|
32
|
+
) from None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async def _redis_client(options: Options[RedisOptions]) -> AsyncIterator[_RedisClientOwner]:
|
|
36
|
+
client = Redis.from_url(options.value.url.get_secret_value(), decode_responses=False)
|
|
37
|
+
owner = _RedisClientOwner(client)
|
|
38
|
+
try:
|
|
39
|
+
yield owner
|
|
40
|
+
finally:
|
|
41
|
+
await client.aclose()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class RedisDistributedCache:
|
|
45
|
+
__slots__ = ("_catalog", "_client", "_prefix")
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
client: _RedisClientOwner,
|
|
50
|
+
catalog: CacheCatalog,
|
|
51
|
+
options: Options[RedisOptions],
|
|
52
|
+
) -> None:
|
|
53
|
+
self._client = client.client
|
|
54
|
+
self._catalog = catalog
|
|
55
|
+
self._prefix = options.value.key_prefix
|
|
56
|
+
|
|
57
|
+
async def get(self, definition: CacheDefinition[Any], key: str) -> Any | None:
|
|
58
|
+
redis_key = self._key(definition, key)
|
|
59
|
+
try:
|
|
60
|
+
payload = await self._client.get(redis_key)
|
|
61
|
+
return None if payload is None else definition._deserialize(payload)
|
|
62
|
+
except (RedisError, TypeError, ValueError, ValidationError) as original_error:
|
|
63
|
+
raise DistributedCacheError(
|
|
64
|
+
operation="get",
|
|
65
|
+
cache=definition.name,
|
|
66
|
+
error_type=type(original_error).__name__,
|
|
67
|
+
) from None
|
|
68
|
+
|
|
69
|
+
async def set(
|
|
70
|
+
self,
|
|
71
|
+
definition: CacheDefinition[Any],
|
|
72
|
+
key: str,
|
|
73
|
+
value: Any,
|
|
74
|
+
) -> None:
|
|
75
|
+
redis_key = self._key(definition, key)
|
|
76
|
+
try:
|
|
77
|
+
payload = definition._serialize(value)
|
|
78
|
+
await self._client.set(redis_key, payload, ex=math.ceil(definition.ttl.total_seconds()))
|
|
79
|
+
except (RedisError, TypeError, ValueError, ValidationError) as original_error:
|
|
80
|
+
raise DistributedCacheError(
|
|
81
|
+
operation="set",
|
|
82
|
+
cache=definition.name,
|
|
83
|
+
error_type=type(original_error).__name__,
|
|
84
|
+
) from None
|
|
85
|
+
|
|
86
|
+
async def remove(self, definition: CacheDefinition[Any], key: str) -> None:
|
|
87
|
+
redis_key = self._key(definition, key)
|
|
88
|
+
try:
|
|
89
|
+
await self._client.delete(redis_key)
|
|
90
|
+
except RedisError as original_error:
|
|
91
|
+
raise DistributedCacheError(
|
|
92
|
+
operation="remove",
|
|
93
|
+
cache=definition.name,
|
|
94
|
+
error_type=type(original_error).__name__,
|
|
95
|
+
) from None
|
|
96
|
+
|
|
97
|
+
def _key(self, definition: CacheDefinition[Any], key: str) -> str:
|
|
98
|
+
if not self._catalog.owns(definition):
|
|
99
|
+
raise DistributedCacheError(
|
|
100
|
+
operation="key",
|
|
101
|
+
cache=definition.name,
|
|
102
|
+
error_type="ForeignCacheDefinition",
|
|
103
|
+
)
|
|
104
|
+
if (
|
|
105
|
+
not isinstance(key, str)
|
|
106
|
+
or not key
|
|
107
|
+
or key != key.strip()
|
|
108
|
+
or any(character.isspace() for character in key)
|
|
109
|
+
or len(key) > 512
|
|
110
|
+
):
|
|
111
|
+
raise DistributedCacheError(
|
|
112
|
+
operation="key",
|
|
113
|
+
cache=definition.name,
|
|
114
|
+
error_type="InvalidCacheKey",
|
|
115
|
+
)
|
|
116
|
+
return f"{self._prefix}:{definition.name}:{key}"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Service composition public contracts and Application-internal owners."""
|
|
2
|
+
|
|
3
|
+
from .contribution import ServiceContributor
|
|
4
|
+
from .convention_contracts import (
|
|
5
|
+
ConventionalService,
|
|
6
|
+
ConventionCandidate,
|
|
7
|
+
ConventionCandidateDecision,
|
|
8
|
+
ConventionDiscoveryKind,
|
|
9
|
+
ScopedDependency,
|
|
10
|
+
SingletonDependency,
|
|
11
|
+
TransientDependency,
|
|
12
|
+
service,
|
|
13
|
+
)
|
|
14
|
+
from .registration import (
|
|
15
|
+
ProviderContribution,
|
|
16
|
+
ProviderContributionView,
|
|
17
|
+
RegistrationDecision,
|
|
18
|
+
RegistrationKind,
|
|
19
|
+
ServiceCatalog,
|
|
20
|
+
ServiceRegistration,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = (
|
|
24
|
+
"ConventionCandidate",
|
|
25
|
+
"ConventionCandidateDecision",
|
|
26
|
+
"ConventionDiscoveryKind",
|
|
27
|
+
"ConventionalService",
|
|
28
|
+
"ProviderContribution",
|
|
29
|
+
"ProviderContributionView",
|
|
30
|
+
"RegistrationDecision",
|
|
31
|
+
"RegistrationKind",
|
|
32
|
+
"ScopedDependency",
|
|
33
|
+
"ServiceCatalog",
|
|
34
|
+
"ServiceContributor",
|
|
35
|
+
"ServiceRegistration",
|
|
36
|
+
"SingletonDependency",
|
|
37
|
+
"TransientDependency",
|
|
38
|
+
"service",
|
|
39
|
+
)
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""应用服务绑定与 provider 重定位;依赖既有 DI 仲裁结果,不重复仲裁。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from dishka import (
|
|
8
|
+
DEFAULT_COMPONENT,
|
|
9
|
+
DependencyKey,
|
|
10
|
+
Provider,
|
|
11
|
+
Scope,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from ..application_services import ApplicationService
|
|
15
|
+
from ..application_services.catalog import (
|
|
16
|
+
_APPLICATION_SERVICE_COMPONENT,
|
|
17
|
+
_ApplicationServiceBinding,
|
|
18
|
+
_DiscoveredApplicationService,
|
|
19
|
+
)
|
|
20
|
+
from ..application_services.errors import ApplicationServiceDefinitionError
|
|
21
|
+
from ..diagnostics import SourceLocation
|
|
22
|
+
from ..modularity import ModuleRegistry
|
|
23
|
+
from .arbitration import (
|
|
24
|
+
_ArbitrationResult,
|
|
25
|
+
_provider_with_active_sources,
|
|
26
|
+
_RegistrationDraft,
|
|
27
|
+
)
|
|
28
|
+
from .registration import (
|
|
29
|
+
RegistrationDecision,
|
|
30
|
+
RegistrationKind,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True, slots=True)
|
|
35
|
+
class _BoundApplicationService:
|
|
36
|
+
binding: _ApplicationServiceBinding
|
|
37
|
+
winner: _RegistrationDraft
|
|
38
|
+
related_draft_ids: frozenset[int]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _bind_application_services(
|
|
42
|
+
discoveries: tuple[_DiscoveredApplicationService, ...],
|
|
43
|
+
drafts: tuple[_RegistrationDraft, ...],
|
|
44
|
+
) -> tuple[_BoundApplicationService, ...]:
|
|
45
|
+
"""把扫描声明关联到既有 DI 仲裁 winner,不再维护第二套 override 规则。"""
|
|
46
|
+
|
|
47
|
+
bound: list[_BoundApplicationService] = []
|
|
48
|
+
for discovery in discoveries:
|
|
49
|
+
contract = discovery.definition.contract or discovery.service_type
|
|
50
|
+
public_key = DependencyKey(contract, DEFAULT_COMPONENT)
|
|
51
|
+
declaration = tuple(
|
|
52
|
+
draft
|
|
53
|
+
for draft in drafts
|
|
54
|
+
if draft.factory is not None
|
|
55
|
+
and draft.factory.source is discovery.service_type
|
|
56
|
+
and draft.key == public_key
|
|
57
|
+
and draft.contribution.kind is RegistrationKind.CONVENTION
|
|
58
|
+
)
|
|
59
|
+
if len(declaration) != 1:
|
|
60
|
+
raise ApplicationServiceDefinitionError(
|
|
61
|
+
symbol=(
|
|
62
|
+
f"{discovery.service_type.__module__}.{discovery.service_type.__qualname__}"
|
|
63
|
+
),
|
|
64
|
+
reason="decorated implementation must own one convention binding for its contract",
|
|
65
|
+
)
|
|
66
|
+
# contract key 的全部 draft 都属于同一 provenance chain,Catalog 记录最终事实与被替换来源。
|
|
67
|
+
related = tuple(
|
|
68
|
+
draft for draft in drafts if not draft.is_collection and draft.key == public_key
|
|
69
|
+
)
|
|
70
|
+
winners = tuple(
|
|
71
|
+
draft for draft in related if draft.decision is RegistrationDecision.ACCEPTED
|
|
72
|
+
)
|
|
73
|
+
if len(winners) != 1:
|
|
74
|
+
raise ApplicationServiceDefinitionError(
|
|
75
|
+
symbol=f"{contract.__module__}.{contract.__qualname__}",
|
|
76
|
+
reason="application service contract must have one accepted DI implementation",
|
|
77
|
+
)
|
|
78
|
+
winner = winners[0]
|
|
79
|
+
if winner.scope is not Scope.ACTION or winner.factory is None:
|
|
80
|
+
raise ApplicationServiceDefinitionError(
|
|
81
|
+
symbol=f"{contract.__module__}.{contract.__qualname__}",
|
|
82
|
+
reason="final application service implementation must remain ACTION scoped",
|
|
83
|
+
)
|
|
84
|
+
implementation = winner.factory.source
|
|
85
|
+
if not isinstance(implementation, type) or not issubclass(
|
|
86
|
+
implementation,
|
|
87
|
+
ApplicationService,
|
|
88
|
+
):
|
|
89
|
+
raise ApplicationServiceDefinitionError(
|
|
90
|
+
symbol=f"{contract.__module__}.{contract.__qualname__}",
|
|
91
|
+
reason=(
|
|
92
|
+
"final application service binding must use an ApplicationService class factory"
|
|
93
|
+
),
|
|
94
|
+
)
|
|
95
|
+
replaced_sources: list[SourceLocation] = []
|
|
96
|
+
for draft in related:
|
|
97
|
+
if draft is winner:
|
|
98
|
+
continue
|
|
99
|
+
source = draft.contribution.source
|
|
100
|
+
if source not in replaced_sources:
|
|
101
|
+
replaced_sources.append(source)
|
|
102
|
+
bound.append(
|
|
103
|
+
_BoundApplicationService(
|
|
104
|
+
binding=_ApplicationServiceBinding(
|
|
105
|
+
discovery=discovery,
|
|
106
|
+
implementation=implementation,
|
|
107
|
+
implementation_owner=winner.contribution.owner,
|
|
108
|
+
implementation_source=winner.contribution.source,
|
|
109
|
+
replaced_sources=tuple(replaced_sources),
|
|
110
|
+
),
|
|
111
|
+
winner=winner,
|
|
112
|
+
related_draft_ids=frozenset(id(draft) for draft in related),
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
return tuple(bound)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _validate_application_service_dependencies(
|
|
119
|
+
registry: ModuleRegistry,
|
|
120
|
+
services: tuple[_BoundApplicationService, ...],
|
|
121
|
+
drafts: tuple[_RegistrationDraft, ...],
|
|
122
|
+
) -> None:
|
|
123
|
+
contracts = {
|
|
124
|
+
DependencyKey(
|
|
125
|
+
item.binding.discovery.definition.contract or item.binding.discovery.service_type,
|
|
126
|
+
DEFAULT_COMPONENT,
|
|
127
|
+
): item.binding.discovery.owner
|
|
128
|
+
for item in services
|
|
129
|
+
}
|
|
130
|
+
bound_ids = {draft_id for item in services for draft_id in item.related_draft_ids}
|
|
131
|
+
for draft in drafts:
|
|
132
|
+
if draft.decision is not RegistrationDecision.ACCEPTED or draft.factory is None:
|
|
133
|
+
continue
|
|
134
|
+
factory = draft.factory
|
|
135
|
+
publishes_implementation = any(
|
|
136
|
+
isinstance(candidate, type) and issubclass(candidate, ApplicationService)
|
|
137
|
+
for candidate in (factory.source, draft.key.type_hint)
|
|
138
|
+
)
|
|
139
|
+
if publishes_implementation and id(draft) not in bound_ids:
|
|
140
|
+
raise ApplicationServiceDefinitionError(
|
|
141
|
+
symbol=str(draft.key.type_hint),
|
|
142
|
+
reason="ApplicationService cannot publish an unmanaged implementation binding",
|
|
143
|
+
)
|
|
144
|
+
visible = {draft.contribution.owner}
|
|
145
|
+
pending = [draft.contribution.owner]
|
|
146
|
+
while pending:
|
|
147
|
+
for dependency in registry.descriptors[pending.pop()].dependencies:
|
|
148
|
+
if dependency not in visible:
|
|
149
|
+
visible.add(dependency)
|
|
150
|
+
pending.append(dependency)
|
|
151
|
+
for key in (*factory.dependencies, *factory.kw_dependencies.values()):
|
|
152
|
+
dependency_key = (
|
|
153
|
+
key.with_component(draft.key.component) if key.component is None else key
|
|
154
|
+
)
|
|
155
|
+
owner = contracts.get(dependency_key)
|
|
156
|
+
if owner is not None and owner not in visible:
|
|
157
|
+
raise ApplicationServiceDefinitionError(
|
|
158
|
+
symbol=str(draft.key.type_hint),
|
|
159
|
+
reason=f"service dependency requires a declared Module dependency on {owner}",
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _runtime_providers(
|
|
164
|
+
arbitration: _ArbitrationResult,
|
|
165
|
+
services: tuple[_BoundApplicationService, ...],
|
|
166
|
+
) -> tuple[Provider, ...]:
|
|
167
|
+
"""仅重定位 Application Service factories;其他 Dishka provider 保持原生形状。"""
|
|
168
|
+
|
|
169
|
+
related_draft_ids = frozenset(
|
|
170
|
+
draft_id for service in services for draft_id in service.related_draft_ids
|
|
171
|
+
)
|
|
172
|
+
drafts_by_entry: dict[int, list[_RegistrationDraft]] = {}
|
|
173
|
+
for draft in arbitration.drafts:
|
|
174
|
+
drafts_by_entry.setdefault(draft.entry.entry_id, []).append(draft)
|
|
175
|
+
|
|
176
|
+
providers: list[Provider] = []
|
|
177
|
+
for entry in arbitration.ordered_entries:
|
|
178
|
+
entry_drafts = drafts_by_entry[entry.entry_id]
|
|
179
|
+
active = tuple(
|
|
180
|
+
draft
|
|
181
|
+
for draft in entry_drafts
|
|
182
|
+
if draft.decision is not RegistrationDecision.BACKED_OFF
|
|
183
|
+
and id(draft) not in related_draft_ids
|
|
184
|
+
)
|
|
185
|
+
if not active:
|
|
186
|
+
continue
|
|
187
|
+
if len(active) == len(entry_drafts):
|
|
188
|
+
providers.append(entry.contribution.provider)
|
|
189
|
+
else:
|
|
190
|
+
providers.append(_provider_with_active_sources(entry.contribution.provider, active))
|
|
191
|
+
|
|
192
|
+
# default component 只发布 REQUEST proxy;ACTION implementation 统一藏入框架私有 component。
|
|
193
|
+
implementation_provider = Provider(component=_APPLICATION_SERVICE_COMPONENT)
|
|
194
|
+
for service in services:
|
|
195
|
+
winner = service.winner
|
|
196
|
+
assert winner.factory is not None
|
|
197
|
+
# internal contributor 被 override 时仍以原声明 key 调度;
|
|
198
|
+
# winner class 只是实现事实,不能改写契约 key。
|
|
199
|
+
contract = (
|
|
200
|
+
service.binding.discovery.definition.contract or service.binding.discovery.service_type
|
|
201
|
+
)
|
|
202
|
+
implementation_provider.factories.append(
|
|
203
|
+
winner.factory.replace(
|
|
204
|
+
dependencies=tuple(
|
|
205
|
+
dependency.with_component(DEFAULT_COMPONENT)
|
|
206
|
+
for dependency in winner.factory.dependencies
|
|
207
|
+
),
|
|
208
|
+
kw_dependencies={
|
|
209
|
+
name: dependency.with_component(DEFAULT_COMPONENT)
|
|
210
|
+
for name, dependency in winner.factory.kw_dependencies.items()
|
|
211
|
+
},
|
|
212
|
+
provides=DependencyKey(contract, None),
|
|
213
|
+
when_override=None,
|
|
214
|
+
when_component=DEFAULT_COMPONENT,
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
# ServiceCatalog 的 accepted row 必须描述 relocated native binding,而非旧 default key。
|
|
218
|
+
winner.key = DependencyKey(contract, _APPLICATION_SERVICE_COMPONENT)
|
|
219
|
+
if services:
|
|
220
|
+
providers.append(implementation_provider)
|
|
221
|
+
return tuple(providers)
|