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,210 @@
|
|
|
1
|
+
"""Application-owned ambient UoW 与取消监督;不捕获 Session。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from collections.abc import AsyncIterator
|
|
7
|
+
from contextlib import asynccontextmanager
|
|
8
|
+
from contextvars import ContextVar
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
from uuid import uuid4
|
|
11
|
+
|
|
12
|
+
from dishka import Scope
|
|
13
|
+
|
|
14
|
+
from ..invocation import ApplicationInvocationContext
|
|
15
|
+
from .contracts import UnitOfWork, _UnitOfWorkFactory, _UnitOfWorkParticipant
|
|
16
|
+
from .options import (
|
|
17
|
+
UnitOfWorkDeclaration,
|
|
18
|
+
UnitOfWorkOptions,
|
|
19
|
+
UnitOfWorkTransactionBehavior,
|
|
20
|
+
_ResolvedUnitOfWorkOptions,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from ..application_services.invocation import _InvocationRuntime
|
|
25
|
+
from ..events import LocalEventCatalog
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class UnitOfWorkManager:
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
runtime: _InvocationRuntime,
|
|
32
|
+
factory: _UnitOfWorkFactory,
|
|
33
|
+
options: UnitOfWorkOptions,
|
|
34
|
+
catalog: LocalEventCatalog,
|
|
35
|
+
) -> None:
|
|
36
|
+
self._runtime = runtime
|
|
37
|
+
self._factory = factory
|
|
38
|
+
self._options = options
|
|
39
|
+
self._catalog = catalog
|
|
40
|
+
self._ambient: ContextVar[UnitOfWork | None] = ContextVar(
|
|
41
|
+
f"python_platform_uow_{runtime.application_id}", default=None
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def current(self) -> UnitOfWork | None:
|
|
46
|
+
work = self._ambient.get()
|
|
47
|
+
if work is not None:
|
|
48
|
+
work._require_active("current", allow_completing=True)
|
|
49
|
+
return work
|
|
50
|
+
|
|
51
|
+
def require_current(self, *, transactional: bool = False) -> UnitOfWork:
|
|
52
|
+
work = self.current
|
|
53
|
+
if work is None:
|
|
54
|
+
raise RuntimeError("operation requires an active UnitOfWork")
|
|
55
|
+
if transactional and not work.is_transactional:
|
|
56
|
+
raise RuntimeError("operation requires a transactional UnitOfWork")
|
|
57
|
+
return work
|
|
58
|
+
|
|
59
|
+
def _resolve_options(
|
|
60
|
+
self, connection_name: str | None, declaration: UnitOfWorkDeclaration, method_name: str
|
|
61
|
+
) -> _ResolvedUnitOfWorkOptions:
|
|
62
|
+
from ..application_services.contracts import (
|
|
63
|
+
_DEFAULT_CONNECTION_NAME,
|
|
64
|
+
_validate_connection_name,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
ambient = self.current
|
|
68
|
+
execution = self._runtime.current()
|
|
69
|
+
connection = (
|
|
70
|
+
connection_name
|
|
71
|
+
if connection_name is not None
|
|
72
|
+
else (
|
|
73
|
+
ambient.connection_name
|
|
74
|
+
if ambient is not None
|
|
75
|
+
else (
|
|
76
|
+
execution.context.connection_name
|
|
77
|
+
if execution is not None
|
|
78
|
+
else _DEFAULT_CONNECTION_NAME
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
transactional = declaration.is_transactional
|
|
83
|
+
if transactional is None:
|
|
84
|
+
behavior = self._options.transaction_behavior
|
|
85
|
+
if behavior is UnitOfWorkTransactionBehavior.AUTO:
|
|
86
|
+
verb = self._runtime.http_method()
|
|
87
|
+
transactional = (
|
|
88
|
+
verb != "GET"
|
|
89
|
+
if verb is not None
|
|
90
|
+
else not (method_name == "get" or method_name.startswith("get_"))
|
|
91
|
+
)
|
|
92
|
+
else:
|
|
93
|
+
transactional = behavior is UnitOfWorkTransactionBehavior.ENABLED
|
|
94
|
+
return _ResolvedUnitOfWorkOptions(
|
|
95
|
+
_validate_connection_name(connection),
|
|
96
|
+
transactional,
|
|
97
|
+
declaration.isolation_level or self._options.isolation_level,
|
|
98
|
+
declaration.command_timeout_ms
|
|
99
|
+
if declaration.command_timeout_ms is not None
|
|
100
|
+
else self._options.command_timeout_ms,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
@asynccontextmanager
|
|
104
|
+
async def begin(
|
|
105
|
+
self,
|
|
106
|
+
*,
|
|
107
|
+
requires_new: bool = False,
|
|
108
|
+
connection_name: str | None = None,
|
|
109
|
+
is_transactional: bool | None = None,
|
|
110
|
+
isolation_level: str | None = None,
|
|
111
|
+
command_timeout_ms: int | None = None,
|
|
112
|
+
) -> AsyncIterator[UnitOfWork]:
|
|
113
|
+
declaration = UnitOfWorkDeclaration(
|
|
114
|
+
is_transactional=is_transactional,
|
|
115
|
+
isolation_level=isolation_level,
|
|
116
|
+
command_timeout_ms=command_timeout_ms,
|
|
117
|
+
)
|
|
118
|
+
options = self._resolve_options(connection_name, declaration, "begin")
|
|
119
|
+
async with self._begin(options, requires_new=requires_new) as work:
|
|
120
|
+
yield work
|
|
121
|
+
|
|
122
|
+
def _check_current(self, connection_name: str) -> UnitOfWork | None:
|
|
123
|
+
from ..application_services.errors import NestedConnectionError
|
|
124
|
+
|
|
125
|
+
outer = self.current
|
|
126
|
+
# disabled 只控制自动创建;参与外层仍须满足同 task、有效状态与同连接。
|
|
127
|
+
if outer is not None and outer.connection_name != connection_name:
|
|
128
|
+
raise NestedConnectionError(outer=outer.connection_name, requested=connection_name)
|
|
129
|
+
return outer
|
|
130
|
+
|
|
131
|
+
@asynccontextmanager
|
|
132
|
+
async def _begin(
|
|
133
|
+
self, options: _ResolvedUnitOfWorkOptions, *, requires_new: bool = False
|
|
134
|
+
) -> AsyncIterator[UnitOfWork]:
|
|
135
|
+
outer = self.current if requires_new else self._check_current(options.connection_name)
|
|
136
|
+
if outer is not None and not requires_new:
|
|
137
|
+
participant = _UnitOfWorkParticipant(outer)
|
|
138
|
+
try:
|
|
139
|
+
yield participant
|
|
140
|
+
finally:
|
|
141
|
+
participant._released = True
|
|
142
|
+
return
|
|
143
|
+
async with self._runtime.outer_lease():
|
|
144
|
+
async with self._own_work(options) as work:
|
|
145
|
+
yield work
|
|
146
|
+
|
|
147
|
+
@asynccontextmanager
|
|
148
|
+
async def _own_work(self, options: _ResolvedUnitOfWorkOptions) -> AsyncIterator[UnitOfWork]:
|
|
149
|
+
from ..application_services.errors import ApplicationInvocationCleanupError
|
|
150
|
+
from ..events.runtime import _LocalEventQueue
|
|
151
|
+
|
|
152
|
+
execution = self._runtime.current()
|
|
153
|
+
container = (
|
|
154
|
+
execution.action_container
|
|
155
|
+
if execution is not None
|
|
156
|
+
else self._runtime.current_call_scope()
|
|
157
|
+
)
|
|
158
|
+
if container is None:
|
|
159
|
+
raise RuntimeError("UnitOfWork.begin requires a managed execution scope")
|
|
160
|
+
context = ApplicationInvocationContext(
|
|
161
|
+
application_id=self._runtime.application_id,
|
|
162
|
+
invocation_id=uuid4(),
|
|
163
|
+
parent_invocation_id=execution.context.invocation_id if execution else None,
|
|
164
|
+
connection_name=options.connection_name,
|
|
165
|
+
)
|
|
166
|
+
work = self._factory.create()
|
|
167
|
+
work._options = options
|
|
168
|
+
work._event_queue = _LocalEventQueue(container, context, self._catalog)
|
|
169
|
+
|
|
170
|
+
async def after_commit() -> None:
|
|
171
|
+
# 新服务调用既不能复用旧事务,也不能落入旧 ACTION 的对象缓存。
|
|
172
|
+
token = self._ambient.set(None)
|
|
173
|
+
parent = container.parent_container if container.scope is Scope.ACTION else container
|
|
174
|
+
assert parent is not None
|
|
175
|
+
try:
|
|
176
|
+
with self._runtime.detached_execution(parent):
|
|
177
|
+
assert work._event_queue is not None
|
|
178
|
+
await work._event_queue._dispatch_after_commit()
|
|
179
|
+
finally:
|
|
180
|
+
self._ambient.reset(token)
|
|
181
|
+
|
|
182
|
+
work._after_commit = after_commit
|
|
183
|
+
token = self._ambient.set(work)
|
|
184
|
+
primary: BaseException | None = None
|
|
185
|
+
try:
|
|
186
|
+
yield work
|
|
187
|
+
except BaseException as error:
|
|
188
|
+
primary = error
|
|
189
|
+
finally:
|
|
190
|
+
# 仅原生资源 I/O 移入受监督 task;ContextVar token 始终由原 task 恢复。
|
|
191
|
+
cleanup = asyncio.create_task(work._cleanup())
|
|
192
|
+
try:
|
|
193
|
+
while not cleanup.done():
|
|
194
|
+
try:
|
|
195
|
+
await asyncio.shield(cleanup)
|
|
196
|
+
except asyncio.CancelledError as cancellation:
|
|
197
|
+
if primary is None:
|
|
198
|
+
primary = cancellation
|
|
199
|
+
cleanup.result()
|
|
200
|
+
finally:
|
|
201
|
+
self._ambient.reset(token)
|
|
202
|
+
if work.cleanup_failures:
|
|
203
|
+
failure = ApplicationInvocationCleanupError(
|
|
204
|
+
primary=primary, cleanup_failures=work.cleanup_failures, committed=work.committed
|
|
205
|
+
)
|
|
206
|
+
if isinstance(primary, asyncio.CancelledError):
|
|
207
|
+
raise primary from failure
|
|
208
|
+
raise failure from (primary or work.cleanup_failures[0])
|
|
209
|
+
if primary is not None:
|
|
210
|
+
raise primary
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""UoW 声明整项覆盖;默认值只来自不可变全局 Options。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from typing import TypeVar
|
|
9
|
+
|
|
10
|
+
from pydantic import ConfigDict, Field, field_validator
|
|
11
|
+
|
|
12
|
+
from ..options import BaseOptions
|
|
13
|
+
|
|
14
|
+
_UNIT_OF_WORK_ATTRIBUTE = "__python_platform_unit_of_work__"
|
|
15
|
+
_T = TypeVar("_T")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class UnitOfWorkTransactionBehavior(str, Enum):
|
|
19
|
+
AUTO = "auto"
|
|
20
|
+
ENABLED = "enabled"
|
|
21
|
+
DISABLED = "disabled"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class UnitOfWorkOptions(BaseOptions):
|
|
25
|
+
model_config = ConfigDict(extra="forbid")
|
|
26
|
+
|
|
27
|
+
transaction_behavior: UnitOfWorkTransactionBehavior = UnitOfWorkTransactionBehavior.AUTO
|
|
28
|
+
isolation_level: str | None = None
|
|
29
|
+
command_timeout_ms: int | None = Field(default=None, strict=True, ge=0)
|
|
30
|
+
|
|
31
|
+
@field_validator("isolation_level")
|
|
32
|
+
@classmethod
|
|
33
|
+
def _validate_isolation_level(cls, value: str | None) -> str | None:
|
|
34
|
+
if value is not None and (
|
|
35
|
+
not value or value != value.strip() or value.upper() == "AUTOCOMMIT"
|
|
36
|
+
):
|
|
37
|
+
# 事务隔离沿用原生 provider;DBAPI AUTOCOMMIT 会破坏本框架的批次/回滚语义。
|
|
38
|
+
raise ValueError("isolation_level must name a database transaction isolation level")
|
|
39
|
+
return value
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True, slots=True)
|
|
43
|
+
class UnitOfWorkDeclaration:
|
|
44
|
+
disabled: bool = False
|
|
45
|
+
is_transactional: bool | None = None
|
|
46
|
+
isolation_level: str | None = None
|
|
47
|
+
command_timeout_ms: int | None = None
|
|
48
|
+
|
|
49
|
+
def __post_init__(self) -> None:
|
|
50
|
+
if not isinstance(self.disabled, bool):
|
|
51
|
+
raise TypeError("disabled must be bool")
|
|
52
|
+
if self.is_transactional is not None and not isinstance(self.is_transactional, bool):
|
|
53
|
+
raise TypeError("is_transactional must be bool or None")
|
|
54
|
+
UnitOfWorkOptions(
|
|
55
|
+
isolation_level=self.isolation_level, command_timeout_ms=self.command_timeout_ms
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass(frozen=True, slots=True)
|
|
60
|
+
class _ResolvedUnitOfWorkOptions:
|
|
61
|
+
connection_name: str
|
|
62
|
+
is_transactional: bool
|
|
63
|
+
isolation_level: str | None
|
|
64
|
+
command_timeout_ms: int | None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def unit_of_work(
|
|
68
|
+
*,
|
|
69
|
+
disabled: bool = False,
|
|
70
|
+
is_transactional: bool | None = None,
|
|
71
|
+
isolation_level: str | None = None,
|
|
72
|
+
command_timeout_ms: int | None = None,
|
|
73
|
+
) -> Callable[[_T], _T]:
|
|
74
|
+
declaration = UnitOfWorkDeclaration(
|
|
75
|
+
disabled, is_transactional, isolation_level, command_timeout_ms
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def decorate(target: _T) -> _T:
|
|
79
|
+
setattr(target, _UNIT_OF_WORK_ATTRIBUTE, declaration)
|
|
80
|
+
return target
|
|
81
|
+
|
|
82
|
+
return decorate
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _declaration(implementation: type[object], method: object) -> UnitOfWorkDeclaration:
|
|
86
|
+
# 方法替换整项声明,未填写的字段只补全全局默认,不能泄漏类配置。
|
|
87
|
+
result = getattr(method, _UNIT_OF_WORK_ATTRIBUTE, None)
|
|
88
|
+
if result is None:
|
|
89
|
+
result = getattr(implementation, _UNIT_OF_WORK_ATTRIBUTE, UnitOfWorkDeclaration())
|
|
90
|
+
if not isinstance(result, UnitOfWorkDeclaration):
|
|
91
|
+
raise TypeError("invalid unit_of_work metadata")
|
|
92
|
+
return result
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-platform
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Modular Python application framework with project CLI and bundled templates
|
|
5
|
+
License-Expression: LicenseRef-Proprietary
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
License-File: src/python_platform/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt
|
|
8
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
9
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Requires-Dist: dishka>=1.10.1,<2
|
|
15
|
+
Requires-Dist: filelock==3.32.6
|
|
16
|
+
Requires-Dist: pydantic>=2.13.5,<3
|
|
17
|
+
Requires-Dist: pydantic-settings[yaml]>=2.15.0,<3
|
|
18
|
+
Requires-Dist: croniter>=6.2.4,<7 ; extra == 'background-jobs'
|
|
19
|
+
Requires-Dist: pgqueuer[asyncpg]>=1.3.2,<1.4 ; extra == 'background-jobs'
|
|
20
|
+
Requires-Dist: cookiecutter>=2.7.1,<2.8 ; extra == 'developer-kit'
|
|
21
|
+
Requires-Dist: libcst>=1.9.0,<1.10 ; extra == 'developer-kit'
|
|
22
|
+
Requires-Dist: fastapi>=0.141.1,<0.142 ; extra == 'fastapi'
|
|
23
|
+
Requires-Dist: httpx>=0.28.1,<0.29 ; extra == 'fastapi'
|
|
24
|
+
Requires-Dist: python-multipart==0.0.32 ; extra == 'fastapi'
|
|
25
|
+
Requires-Dist: uvicorn>=0.52.4,<0.53 ; extra == 'fastapi'
|
|
26
|
+
Requires-Dist: wsproto>=1.3.2,<2 ; extra == 'fastapi'
|
|
27
|
+
Requires-Dist: pyjwt>=2.13.0,<3 ; extra == 'identity'
|
|
28
|
+
Requires-Dist: pwdlib[argon2]>=0.3.1,<0.4 ; extra == 'identity'
|
|
29
|
+
Requires-Dist: python-json-logger==4.2.0 ; extra == 'logging'
|
|
30
|
+
Requires-Dist: sqlalchemy[asyncio]>=2.0.52,<2.1 ; extra == 'postgresql'
|
|
31
|
+
Requires-Dist: alembic>=1.19.1,<2 ; extra == 'postgresql'
|
|
32
|
+
Requires-Dist: asyncpg>=0.31,<0.32 ; extra == 'postgresql'
|
|
33
|
+
Requires-Dist: anyio>=4.14.2,<5 ; extra == 'redis'
|
|
34
|
+
Requires-Dist: redis>=8.1.0,<9 ; extra == 'redis'
|
|
35
|
+
Requires-Dist: opentelemetry-api==1.44.0 ; extra == 'tracing'
|
|
36
|
+
Requires-Dist: opentelemetry-sdk==1.44.0 ; extra == 'tracing'
|
|
37
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http==1.44.0 ; extra == 'tracing'
|
|
38
|
+
Requires-Dist: opentelemetry-instrumentation-fastapi==0.65b0 ; extra == 'tracing'
|
|
39
|
+
Requires-Dist: opentelemetry-instrumentation-httpx==0.65b0 ; extra == 'tracing'
|
|
40
|
+
Requires-Python: >=3.12, <3.15
|
|
41
|
+
Provides-Extra: background-jobs
|
|
42
|
+
Provides-Extra: developer-kit
|
|
43
|
+
Provides-Extra: fastapi
|
|
44
|
+
Provides-Extra: identity
|
|
45
|
+
Provides-Extra: logging
|
|
46
|
+
Provides-Extra: postgresql
|
|
47
|
+
Provides-Extra: redis
|
|
48
|
+
Provides-Extra: tracing
|
|
49
|
+
Description-Content-Type: text/markdown
|
|
50
|
+
|
|
51
|
+
# python-platform
|
|
52
|
+
|
|
53
|
+
`python-platform` 是面向模块化业务应用的 Python 框架,提供显式 Module 组合、统一业务调用与事务边界,以及 `pp` CLI 和包内项目模板。Host 选择 Web、数据库和后台 provider,业务模块保持明确的契约与依赖方向。
|
|
54
|
+
|
|
55
|
+
源码仓库私有,交付模型为 PyPI 公开安装包;框架、CLI、模板同包同版本。采用 [hank-repo 专有许可](LICENSE),使用须取得单独书面授权,公开下载不增加使用权;PgQueuer 派生内容保留 [上游许可](src/python_platform/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt)。
|
|
56
|
+
|
|
57
|
+
版本、Python/OS 支持范围和 extras 以 [pyproject.toml](pyproject.toml) 为准,依赖解析以 [uv.lock](uv.lock) 为准。安装前查看 [发布状态与验收证据](docs/status.md#当前发布)。
|
|
58
|
+
|
|
59
|
+
## 开始使用
|
|
60
|
+
|
|
61
|
+
确认目标版本已发布后,安装 CLI 并创建应用:
|
|
62
|
+
|
|
63
|
+
```powershell
|
|
64
|
+
uv tool install "python-platform[developer-kit]"
|
|
65
|
+
pp new my-app
|
|
66
|
+
cd my-app
|
|
67
|
+
uv run pp dev-init
|
|
68
|
+
uv run pp add module orders
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
CLI 将生成项目的运行依赖和 developer-kit 精确固定到自身 distribution 版本;已有项目使用 `uv run pp`。按生成项目 README 完成初次数据库迁移与 Seed,再运行应用;dev-init 只准备开发基础设施,Host 不自动迁移。
|
|
72
|
+
|
|
73
|
+
需要验证未发布制品时使用 `pp new my-app --framework-wheel <同版本wheel路径>`。模块开发、配置、HTTP、数据库和部署步骤见 [开发指南](docs/development.md)。完整命令与参数使用 `pp --help` 及子命令帮助,不另维护命令清单。
|
|
74
|
+
|
|
75
|
+
## 阅读项目
|
|
76
|
+
|
|
77
|
+
- [重构入口](docs/README.md#重构的最短阅读路径):按改动定位 owner 和必要上下文,不重新通读项目。
|
|
78
|
+
- [当前架构与契约](docs/architecture.md):职责、源码入口、生命周期、依赖和失败不变量。
|
|
79
|
+
- [协作规范](AGENTS.md):后续改代码的范围、授权、验证与交接规则。
|
|
80
|
+
- [状态记录](docs/status.md):适用基线、最近有效证据和未完成事项。
|
|
81
|
+
|
|
82
|
+
## 版本与升级
|
|
83
|
+
|
|
84
|
+
本节是版本变化与消费者迁移的唯一正文入口。框架升级只更新依赖,不重新生成业务源码、Host、模板或应用迁移;全局 CLI 升级也不改变已有项目。
|
|
85
|
+
|
|
86
|
+
1. 阅读目标版本说明,在项目运行依赖和开发组的 `python-platform[developer-kit]` 中同时固定目标版本,保留现有 extras。
|
|
87
|
+
2. 旧 Git/wheel 项目首次转 PyPI 时,仅移除 `[tool.uv.sources]` 中 python-platform 的来源覆盖,保留其他依赖来源和 vendor 文件。
|
|
88
|
+
3. 更新锁与环境,完成说明要求的代码适配,再运行应用原有验证:
|
|
89
|
+
|
|
90
|
+
```powershell
|
|
91
|
+
uv lock --upgrade-package python-platform
|
|
92
|
+
uv sync --locked
|
|
93
|
+
uv run pytest
|
|
94
|
+
uv build --no-sources
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
日常代码或文档提交可以保持版本不变;推送 main 仍执行完整 CI,但跳过发布。真正发布不同内容时必须升级版本:0.x 中兼容修复使用 patch,需要消费者适配的变更使用 minor 并说明迁移。首次新建 main 发布当前版本,之后只有整个 push 前后版本升高才发布;Actions 手动运行只验证。失败可重跑原任务,修改后同版本推送不会续发,须再次升级版本。首次 OIDC 配置及重试边界见 [发布操作](docs/development.md#发布操作);尚不存在的下一版本不能作为已验证升级案例。
|
|
98
|
+
|
|
99
|
+
### 0.1.1
|
|
100
|
+
|
|
101
|
+
- 首次公开安装包的目标版本;公开发布完成与否见状态记录。0.1.0 未完成公开发布,本次 patch 升版重新触发发布,不改变公开 API、依赖或数据库契约。new 默认生成 PyPI 精确依赖,保留同版本本地 wheel 验证入口,移除 `--framework-source` 与 Git 来源推断。
|
|
102
|
+
- 从私有预发布迁移时,同步将运行/开发组依赖改为 `==0.1.1`,移除仅属于框架的来源覆盖并重新锁定。已有 Dockerfile 可移除专用于框架下载的 Git/SSH 工具和凭据 mounts;应用自己的其他 Git 依赖由应用维护者判断。本次发布消费变更没有新增框架 Schema 迁移。
|
|
103
|
+
- 新项目使用纯 Host 骨架,业务由 add module 接入。既有应用不自动改目录或重生成;按 [模块与数据库操作](docs/development.md#数据库聚合与迁移) 核对标准 Host/module entry points 和数据库 owner。
|
|
104
|
+
|
|
105
|
+
### 更早私有版本的迁移
|
|
106
|
+
|
|
107
|
+
已经完成以下迁移的应用无需重复执行;这些是仍可能影响旧消费者的要求,不是当前待实施计划。
|
|
108
|
+
|
|
109
|
+
| 旧用法 | 当前迁移要求 |
|
|
110
|
+
| --- | --- |
|
|
111
|
+
| 应用 TOML、OptionDefinition/class metadata、Editor.patch、具体 Options 类型自动注入 | 转 app.yaml;使用阶段 pre_configure/configure 与 values;服务注入 Options[T]。覆盖父配置 hook 时显式 super 或依赖 owner Module;无兼容加载/alias。 |
|
|
112
|
+
| 公开 ApplicationServiceInvoker 或 invoke(ServiceType, method, ...) | 注入公开服务代理,或 invoke(Service.method, ...);策略移到最终实现,Contract 保留签名。IntegrationService 保留独立 Contract。 |
|
|
113
|
+
| Module 的 Settings/权限定义清单、GlobalSettings | 在 scan_packages 内使用同步 SettingDefinitionProvider/PermissionDefinitionProvider;读取用 SettingProvider,写入用 SettingManager,构造期间不使用未绑定的便捷属性。 |
|
|
114
|
+
| HostedRuntime/Definition、ready/not_ready/failure 回调 | 使用 HostedService 类型 tuple 与 async start/stop;组件自己负责运行故障和恢复,确需后台子进程实例时显式声明进程归属。 |
|
|
115
|
+
| SoftDeleteMixin、include_soft_deleted、恢复类接口 | 使用物理删除/外键约束和已发布后续迁移;历史软删除数据须先处理,不能修改旧 revision 或绕过 guard。 |
|
|
116
|
+
| 旧 Settings 整数版本、重置删除版本记录 | 执行已有版本令牌迁移;update/reset 使用查询得到的 UUID/null 预期版本,reset 保留新的版本记录,旧 token 不能复用。 |
|
|
117
|
+
| 手工聚合事件收集与仓储私有 operation | 使用公开 SessionProvider.operation(..., aggregate=...),成功暂存后只收集一次,保留显式 DTO/聚合/ORM 映射。 |
|
|
118
|
+
| 旧约定 HTTP 动作路径 | 按当前 HTTP Module/生成 OpenAPI 核对路径与公开签名,CRUD 省略动作段、静态段优先;无旧 URL alias。 |
|
|
119
|
+
| ReferenceOrderView、公开 fail_after_add 参数 | 改用 ReferenceOrderDto,移除故障注入参数;参考 Orders 的 reference_orders_0002 为存量数据增加 version,更新按预期版本竞争,无旧类型 alias。 |
|
|
120
|
+
| 超出当前 requires-python 的解释器/backport 路径 | 选择 metadata 声明的受支持解释器,重新锁定并执行应用验证,不强装范围外版本。 |
|
|
121
|
+
|
|
122
|
+
持久 Job 的旧 payload version 不能因清理 Python API 而随意删除;须先证明队列 drain 或执行显式数据迁移。数据库迁移、版本并发及已提交数据的边界见 [架构契约](docs/architecture.md#uow持久化与本地事件)。
|