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.
Files changed (344) hide show
  1. python_platform/__init__.py +445 -0
  2. python_platform/application/__init__.py +6 -0
  3. python_platform/application/build_spec.py +37 -0
  4. python_platform/application/builder.py +260 -0
  5. python_platform/application/composition.py +219 -0
  6. python_platform/application/runtime.py +546 -0
  7. python_platform/application/runtime_hooks.py +327 -0
  8. python_platform/application/state.py +216 -0
  9. python_platform/application_services/__init__.py +54 -0
  10. python_platform/application_services/catalog.py +500 -0
  11. python_platform/application_services/contracts.py +188 -0
  12. python_platform/application_services/dispatcher.py +423 -0
  13. python_platform/application_services/errors.py +89 -0
  14. python_platform/application_services/execution.py +199 -0
  15. python_platform/application_services/interceptors.py +44 -0
  16. python_platform/application_services/invocation.py +427 -0
  17. python_platform/application_services/policies.py +46 -0
  18. python_platform/application_services/seeding.py +72 -0
  19. python_platform/application_services/signature.py +49 -0
  20. python_platform/application_services/validation.py +84 -0
  21. python_platform/auditing/__init__.py +12 -0
  22. python_platform/auditing/contracts.py +39 -0
  23. python_platform/auditing/control.py +48 -0
  24. python_platform/auditing/sqlalchemy/__init__.py +6 -0
  25. python_platform/auditing/sqlalchemy/migrations/0001_platform_auditing.py +45 -0
  26. python_platform/auditing/sqlalchemy/migrations/__init__.py +1 -0
  27. python_platform/auditing/sqlalchemy/models.py +31 -0
  28. python_platform/auditing/sqlalchemy/module.py +32 -0
  29. python_platform/auditing/sqlalchemy/store.py +63 -0
  30. python_platform/authorization/__init__.py +38 -0
  31. python_platform/authorization/catalog.py +63 -0
  32. python_platform/authorization/contracts.py +241 -0
  33. python_platform/authorization/definition_discovery.py +81 -0
  34. python_platform/authorization/definitions.py +43 -0
  35. python_platform/authorization/errors.py +40 -0
  36. python_platform/background_execution/__init__.py +15 -0
  37. python_platform/background_execution/application.py +41 -0
  38. python_platform/background_execution/child.py +123 -0
  39. python_platform/background_execution/contracts.py +39 -0
  40. python_platform/background_execution/lifecycle.py +22 -0
  41. python_platform/background_execution/local.py +127 -0
  42. python_platform/background_execution/locks.py +31 -0
  43. python_platform/background_execution/management.py +17 -0
  44. python_platform/background_execution/module.py +10 -0
  45. python_platform/background_execution/permissions.py +14 -0
  46. python_platform/background_execution/processes.py +331 -0
  47. python_platform/background_jobs/__init__.py +31 -0
  48. python_platform/background_jobs/catalog.py +287 -0
  49. python_platform/background_jobs/contracts.py +202 -0
  50. python_platform/background_jobs/errors.py +28 -0
  51. python_platform/background_jobs/execution.py +201 -0
  52. python_platform/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt +21 -0
  53. python_platform/background_jobs/pgqueuer/__init__.py +6 -0
  54. python_platform/background_jobs/pgqueuer/enqueue.py +82 -0
  55. python_platform/background_jobs/pgqueuer/migrations/0001_pgqueuer_1_3_2.py +63 -0
  56. python_platform/background_jobs/pgqueuer/migrations/__init__.py +1 -0
  57. python_platform/background_jobs/pgqueuer/module.py +102 -0
  58. python_platform/background_jobs/pgqueuer/options.py +70 -0
  59. python_platform/background_jobs/pgqueuer/runtime.py +479 -0
  60. python_platform/background_jobs/pgqueuer/sql/pgqueuer_1_3_2_install.sql +118 -0
  61. python_platform/background_jobs/pgqueuer/supervision.py +201 -0
  62. python_platform/background_workers/__init__.py +22 -0
  63. python_platform/background_workers/catalog.py +96 -0
  64. python_platform/background_workers/contracts.py +124 -0
  65. python_platform/background_workers/errors.py +20 -0
  66. python_platform/background_workers/execution.py +133 -0
  67. python_platform/background_workers/runtime.py +203 -0
  68. python_platform/caching/__init__.py +11 -0
  69. python_platform/caching/catalog.py +73 -0
  70. python_platform/caching/contracts.py +73 -0
  71. python_platform/caching/errors.py +20 -0
  72. python_platform/cli/__init__.py +87 -0
  73. python_platform/cli/errors.py +5 -0
  74. python_platform/cli/project.py +69 -0
  75. python_platform/cli/runtime.py +68 -0
  76. python_platform/configuration/__init__.py +21 -0
  77. python_platform/configuration/composition.py +65 -0
  78. python_platform/configuration/contracts.py +68 -0
  79. python_platform/configuration/dotenv_source.py +51 -0
  80. python_platform/configuration/environment_source.py +38 -0
  81. python_platform/configuration/immutability.py +124 -0
  82. python_platform/configuration/input_shape.py +58 -0
  83. python_platform/configuration/merge.py +103 -0
  84. python_platform/configuration/root.py +215 -0
  85. python_platform/configuration/sources.py +519 -0
  86. python_platform/configuration/values.py +427 -0
  87. python_platform/configuration/yaml_source.py +51 -0
  88. python_platform/developer_kit/__init__.py +1 -0
  89. python_platform/developer_kit/generation.py +182 -0
  90. python_platform/developer_kit/project_metadata.py +45 -0
  91. python_platform/developer_kit/source.py +53 -0
  92. python_platform/developer_kit/templates/module/cookiecutter.json +1 -0
  93. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/__init__.py.jinja +1 -0
  94. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/application/__init__.py.jinja +1 -0
  95. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/application/cache.py.jinja +10 -0
  96. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/application/events.py.jinja +24 -0
  97. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/application/integration.py.jinja +46 -0
  98. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/application/module.py.jinja +29 -0
  99. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/application/options.py.jinja +8 -0
  100. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/application/orders.py.jinja +81 -0
  101. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/application/tasks.py.jinja +86 -0
  102. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/__init__.py.jinja +1 -0
  103. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/module.py.jinja +10 -0
  104. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/application_contracts/orders.py.jinja +36 -0
  105. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/__init__.py.jinja +1 -0
  106. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/module.py.jinja +10 -0
  107. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/orders.py.jinja +57 -0
  108. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/repository.py.jinja +12 -0
  109. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/seeding.py.jinja +19 -0
  110. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/domain/settings.py.jinja +16 -0
  111. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/__init__.py.jinja +1 -0
  112. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/definitions.py.jinja +24 -0
  113. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/module.py.jinja +7 -0
  114. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/domain_shared/permissions.py.jinja +15 -0
  115. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/__init__.py.jinja +1 -0
  116. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/files.py.jinja +74 -0
  117. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/module.py.jinja +58 -0
  118. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/http_api/realtime.py.jinja +37 -0
  119. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/__init__.py.jinja +1 -0
  120. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/migrations/__init__.py.jinja +1 -0
  121. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/models.py.jinja +26 -0
  122. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/module.py.jinja +32 -0
  123. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/sqlalchemy/repository.py.jinja +49 -0
  124. python_platform/developer_kit/templates/module/{{cookiecutter.module_name}}/tests/test_domain.py.jinja +18 -0
  125. python_platform/developer_kit/templates/project/cookiecutter.json +6 -0
  126. python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/.dockerignore +8 -0
  127. python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/.env.jinja +13 -0
  128. python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/.gitignore +6 -0
  129. python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/.python-version +1 -0
  130. python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/Dockerfile +22 -0
  131. python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/README.md +86 -0
  132. python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/app.yaml.jinja +16 -0
  133. python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/compose.dev.yaml.jinja +24 -0
  134. python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/compose.production.yaml.jinja +13 -0
  135. python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/pyproject.toml.jinja +28 -0
  136. python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/src/host/__init__.py.jinja +1 -0
  137. python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/src/host/main.py.jinja +52 -0
  138. python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/src/host/module.py.jinja +38 -0
  139. python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/tests/conftest.py.jinja +8 -0
  140. python_platform/developer_kit/templates/project/{{cookiecutter.project_name}}/tests/host/test_http.py.jinja +101 -0
  141. python_platform/developer_kit/wiring.py +85 -0
  142. python_platform/diagnostics/__init__.py +23 -0
  143. python_platform/diagnostics/journal.py +174 -0
  144. python_platform/diagnostics/model.py +83 -0
  145. python_platform/diagnostics/source.py +25 -0
  146. python_platform/distributed_lock/__init__.py +6 -0
  147. python_platform/distributed_lock/contracts.py +20 -0
  148. python_platform/distributed_lock/options.py +19 -0
  149. python_platform/domain/__init__.py +13 -0
  150. python_platform/domain/aggregates.py +64 -0
  151. python_platform/domain/entities.py +33 -0
  152. python_platform/domain/errors.py +12 -0
  153. python_platform/domain/value_objects.py +31 -0
  154. python_platform/errors/__init__.py +75 -0
  155. python_platform/errors/base.py +13 -0
  156. python_platform/errors/business.py +53 -0
  157. python_platform/errors/configuration.py +119 -0
  158. python_platform/errors/diagnostics.py +12 -0
  159. python_platform/errors/lifecycle.py +164 -0
  160. python_platform/errors/modularity.py +100 -0
  161. python_platform/errors/services.py +100 -0
  162. python_platform/events/__init__.py +25 -0
  163. python_platform/events/aggregate.py +39 -0
  164. python_platform/events/catalog.py +136 -0
  165. python_platform/events/contracts.py +39 -0
  166. python_platform/events/contribution.py +128 -0
  167. python_platform/events/discovery.py +105 -0
  168. python_platform/events/errors.py +48 -0
  169. python_platform/events/runtime.py +192 -0
  170. python_platform/fastapi/__init__.py +49 -0
  171. python_platform/fastapi/action.py +175 -0
  172. python_platform/fastapi/adapter.py +381 -0
  173. python_platform/fastapi/application_services.py +647 -0
  174. python_platform/fastapi/background.py +42 -0
  175. python_platform/fastapi/contracts.py +278 -0
  176. python_platform/fastapi/errors.py +32 -0
  177. python_platform/fastapi/filters.py +111 -0
  178. python_platform/fastapi/health.py +44 -0
  179. python_platform/fastapi/http.py +55 -0
  180. python_platform/fastapi/http_router.py +212 -0
  181. python_platform/fastapi/manual_action.py +105 -0
  182. python_platform/fastapi/middleware.py +93 -0
  183. python_platform/fastapi/parameters.py +97 -0
  184. python_platform/fastapi/realtime/__init__.py +13 -0
  185. python_platform/fastapi/realtime/authentication.py +181 -0
  186. python_platform/fastapi/realtime/connection.py +166 -0
  187. python_platform/fastapi/realtime/module.py +41 -0
  188. python_platform/fastapi/realtime/options.py +50 -0
  189. python_platform/fastapi/realtime/runtime.py +423 -0
  190. python_platform/fastapi/request_context.py +244 -0
  191. python_platform/fastapi/route_integrity.py +88 -0
  192. python_platform/fastapi/routing.py +410 -0
  193. python_platform/fastapi/server.py +181 -0
  194. python_platform/fastapi/settings.py +40 -0
  195. python_platform/fastapi/tracing.py +62 -0
  196. python_platform/fastapi/transfer.py +128 -0
  197. python_platform/fastapi/upload_limits.py +63 -0
  198. python_platform/fastapi/uploads.py +94 -0
  199. python_platform/hosted_services/__init__.py +29 -0
  200. python_platform/hosted_services/bridge.py +420 -0
  201. python_platform/hosted_services/catalog.py +259 -0
  202. python_platform/hosted_services/contracts.py +55 -0
  203. python_platform/hosted_services/errors.py +53 -0
  204. python_platform/hosted_services/options.py +17 -0
  205. python_platform/hosted_services/runtime.py +267 -0
  206. python_platform/hosted_services/state.py +40 -0
  207. python_platform/hosting/__init__.py +4 -0
  208. python_platform/hosting/instance.py +40 -0
  209. python_platform/identity/__init__.py +73 -0
  210. python_platform/identity/application.py +287 -0
  211. python_platform/identity/contracts.py +283 -0
  212. python_platform/identity/errors.py +28 -0
  213. python_platform/identity/http_api.py +90 -0
  214. python_platform/identity/module.py +46 -0
  215. python_platform/identity/passwords.py +34 -0
  216. python_platform/identity/permissions.py +14 -0
  217. python_platform/identity/services.py +108 -0
  218. python_platform/identity/sqlalchemy/__init__.py +5 -0
  219. python_platform/identity/sqlalchemy/migrations/0001_platform_identity.py +151 -0
  220. python_platform/identity/sqlalchemy/migrations/0002_physical_delete.py +42 -0
  221. python_platform/identity/sqlalchemy/migrations/__init__.py +1 -0
  222. python_platform/identity/sqlalchemy/models.py +85 -0
  223. python_platform/identity/sqlalchemy/module.py +62 -0
  224. python_platform/identity/sqlalchemy/stores.py +526 -0
  225. python_platform/identity/tokens.py +106 -0
  226. python_platform/invocation/__init__.py +3 -0
  227. python_platform/invocation/callables.py +171 -0
  228. python_platform/invocation/contracts.py +33 -0
  229. python_platform/invocation/entries.py +54 -0
  230. python_platform/invocation/function_runtime.py +48 -0
  231. python_platform/invocation/interception.py +184 -0
  232. python_platform/lifecycle/__init__.py +25 -0
  233. python_platform/lifecycle/composition.py +35 -0
  234. python_platform/lifecycle/context.py +31 -0
  235. python_platform/lifecycle/runtime.py +43 -0
  236. python_platform/lifecycle/state.py +20 -0
  237. python_platform/modularity/__init__.py +15 -0
  238. python_platform/modularity/contracts.py +90 -0
  239. python_platform/modularity/discovery.py +143 -0
  240. python_platform/modularity/graph.py +334 -0
  241. python_platform/modularity/registry.py +100 -0
  242. python_platform/modularity/selection.py +16 -0
  243. python_platform/notifications/__init__.py +19 -0
  244. python_platform/notifications/catalog.py +46 -0
  245. python_platform/notifications/contracts.py +107 -0
  246. python_platform/observability/__init__.py +5 -0
  247. python_platform/observability/context.py +43 -0
  248. python_platform/observability/export.py +73 -0
  249. python_platform/observability/formatting.py +89 -0
  250. python_platform/observability/logging.py +118 -0
  251. python_platform/observability/options.py +45 -0
  252. python_platform/observability/tracing.py +80 -0
  253. python_platform/options/__init__.py +19 -0
  254. python_platform/options/aliases.py +331 -0
  255. python_platform/options/contribution.py +62 -0
  256. python_platform/options/immutability.py +48 -0
  257. python_platform/options/input_keys.py +214 -0
  258. python_platform/options/issues.py +335 -0
  259. python_platform/options/models.py +114 -0
  260. python_platform/options/registry.py +280 -0
  261. python_platform/options/schema.py +488 -0
  262. python_platform/options/validation.py +120 -0
  263. python_platform/py.typed +0 -0
  264. python_platform/realtime/__init__.py +12 -0
  265. python_platform/realtime/contracts.py +28 -0
  266. python_platform/realtime/diagnostics.py +37 -0
  267. python_platform/realtime/messages.py +113 -0
  268. python_platform/redis/__init__.py +6 -0
  269. python_platform/redis/distributed_lock.py +212 -0
  270. python_platform/redis/lease_lock.py +36 -0
  271. python_platform/redis/module.py +50 -0
  272. python_platform/redis/notification_runtime.py +182 -0
  273. python_platform/redis/notifications.py +25 -0
  274. python_platform/redis/options.py +35 -0
  275. python_platform/redis/runtime.py +116 -0
  276. python_platform/services/__init__.py +39 -0
  277. python_platform/services/application_bindings.py +221 -0
  278. python_platform/services/arbitration.py +381 -0
  279. python_platform/services/binding.py +102 -0
  280. python_platform/services/contribution.py +438 -0
  281. python_platform/services/convention.py +292 -0
  282. python_platform/services/convention_contracts.py +94 -0
  283. python_platform/services/exposure.py +23 -0
  284. python_platform/services/framework_provider.py +178 -0
  285. python_platform/services/native_graph.py +172 -0
  286. python_platform/services/provider.py +204 -0
  287. python_platform/services/registration.py +101 -0
  288. python_platform/services/runtime.py +298 -0
  289. python_platform/settings/__init__.py +30 -0
  290. python_platform/settings/application.py +95 -0
  291. python_platform/settings/binding.py +33 -0
  292. python_platform/settings/catalog.py +121 -0
  293. python_platform/settings/changes.py +14 -0
  294. python_platform/settings/contracts.py +115 -0
  295. python_platform/settings/definition_discovery.py +71 -0
  296. python_platform/settings/definitions.py +41 -0
  297. python_platform/settings/errors.py +22 -0
  298. python_platform/settings/handlers.py +43 -0
  299. python_platform/settings/management.py +57 -0
  300. python_platform/settings/manager.py +89 -0
  301. python_platform/settings/module.py +7 -0
  302. python_platform/settings/notifications.py +39 -0
  303. python_platform/settings/permissions.py +14 -0
  304. python_platform/settings/provider.py +74 -0
  305. python_platform/settings/refresh.py +140 -0
  306. python_platform/settings/refresh_module.py +45 -0
  307. python_platform/settings/sqlalchemy/__init__.py +5 -0
  308. python_platform/settings/sqlalchemy/migrations/0001_platform_settings.py +31 -0
  309. python_platform/settings/sqlalchemy/migrations/0002_physical_delete.py +32 -0
  310. python_platform/settings/sqlalchemy/migrations/0003_version_tokens.py +39 -0
  311. python_platform/settings/sqlalchemy/migrations/__init__.py +1 -0
  312. python_platform/settings/sqlalchemy/models.py +36 -0
  313. python_platform/settings/sqlalchemy/module.py +36 -0
  314. python_platform/settings/sqlalchemy/store.py +86 -0
  315. python_platform/settings/store.py +16 -0
  316. python_platform/settings/values.py +39 -0
  317. python_platform/sqlalchemy/__init__.py +51 -0
  318. python_platform/sqlalchemy/alembic_runtime/__init__.py +1 -0
  319. python_platform/sqlalchemy/alembic_runtime/env.py +27 -0
  320. python_platform/sqlalchemy/alembic_runtime/script.py.mako +14 -0
  321. python_platform/sqlalchemy/auditing.py +48 -0
  322. python_platform/sqlalchemy/errors.py +85 -0
  323. python_platform/sqlalchemy/metadata.py +538 -0
  324. python_platform/sqlalchemy/migration.py +346 -0
  325. python_platform/sqlalchemy/module.py +48 -0
  326. python_platform/sqlalchemy/module_migration.py +148 -0
  327. python_platform/sqlalchemy/options.py +66 -0
  328. python_platform/sqlalchemy/repository.py +33 -0
  329. python_platform/sqlalchemy/runtime.py +85 -0
  330. python_platform/sqlalchemy/session_provider.py +45 -0
  331. python_platform/sqlalchemy/unit_of_work.py +97 -0
  332. python_platform/testing/__init__.py +5 -0
  333. python_platform/testing/runtime.py +112 -0
  334. python_platform/unit_of_work/__init__.py +14 -0
  335. python_platform/unit_of_work/contracts.py +223 -0
  336. python_platform/unit_of_work/errors.py +27 -0
  337. python_platform/unit_of_work/manager.py +210 -0
  338. python_platform/unit_of_work/options.py +92 -0
  339. python_platform-0.1.1.dist-info/METADATA +122 -0
  340. python_platform-0.1.1.dist-info/RECORD +344 -0
  341. python_platform-0.1.1.dist-info/WHEEL +4 -0
  342. python_platform-0.1.1.dist-info/entry_points.txt +9 -0
  343. python_platform-0.1.1.dist-info/licenses/LICENSE +7 -0
  344. 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)