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,201 @@
1
+ """PgQueuer manager readiness、fatal completion 与 task ownership。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from collections.abc import Awaitable, Callable
7
+ from dataclasses import dataclass, field
8
+
9
+ from ...errors.lifecycle import ApplicationRuntimeError
10
+
11
+
12
+ @dataclass(frozen=True, slots=True)
13
+ class _PgQueuerManagerBinding:
14
+ component: str
15
+ task_name: str
16
+ ready: asyncio.Event
17
+ run: Callable[[], Awaitable[None]]
18
+
19
+
20
+ @dataclass(frozen=True, slots=True)
21
+ class _PgQueuerManagerFailure:
22
+ component: str
23
+ error_type: str
24
+ _source: object = field(default_factory=object, repr=False, compare=False)
25
+
26
+
27
+ class _PgQueuerManagerError(ApplicationRuntimeError):
28
+ """保留 provider 启动失败的类型边界;运行期事实使用已有 ApplicationRuntimeError 契约。"""
29
+
30
+
31
+ class _PgQueuerManagerSupervisor:
32
+ """单个 provider runtime 的 manager owner;只发布脱敏失败事实。"""
33
+
34
+ __slots__ = (
35
+ "_failure",
36
+ "_reported_tasks",
37
+ "_startup_complete",
38
+ "_stopping",
39
+ "_task_components",
40
+ "_task_failures",
41
+ "_tasks",
42
+ )
43
+
44
+ def __init__(self) -> None:
45
+ self._failure: asyncio.Future[_PgQueuerManagerFailure] | None = None
46
+ self._startup_complete = False
47
+ self._stopping = False
48
+ self._task_components: dict[asyncio.Task[None], str] = {}
49
+ self._tasks: tuple[asyncio.Task[None], ...] = ()
50
+ self._reported_tasks: set[asyncio.Task[None]] = set()
51
+ self._task_failures: dict[asyncio.Task[None], _PgQueuerManagerFailure] = {}
52
+
53
+ async def start(self, bindings: tuple[_PgQueuerManagerBinding, ...]) -> None:
54
+ if self._failure is not None or self._tasks:
55
+ raise RuntimeError("PgQueuer manager supervisor may only start once")
56
+ if not bindings:
57
+ raise RuntimeError("PgQueuer manager supervisor requires an enabled role")
58
+
59
+ self._failure = asyncio.get_running_loop().create_future()
60
+ tasks_by_binding = {
61
+ asyncio.create_task(self._run(binding), name=binding.task_name): binding
62
+ for binding in bindings
63
+ }
64
+ self._tasks = tuple(tasks_by_binding)
65
+ self._task_components = {
66
+ task: binding.component for task, binding in tasks_by_binding.items()
67
+ }
68
+ readiness = {
69
+ asyncio.create_task(
70
+ binding.ready.wait(),
71
+ name=f"{binding.task_name}-readiness",
72
+ ): binding
73
+ for binding in bindings
74
+ }
75
+ try:
76
+ pending_readiness = set(readiness)
77
+ while pending_readiness:
78
+ done, _ = await asyncio.wait(
79
+ (*pending_readiness, *self._tasks),
80
+ return_when=asyncio.FIRST_COMPLETED,
81
+ )
82
+ failed = next((task for task in self._tasks if task in done), None)
83
+ if failed is not None:
84
+ binding = tasks_by_binding[failed]
85
+ raise self._manager_error(binding.component, failed)
86
+ pending_readiness.difference_update(done)
87
+
88
+ # readiness callback 返回只说明 bootstrap I/O 已成功;再让 manager 前进一步,
89
+ # 防止 callback 后立即失败却被误报为 Application 已启动。
90
+ self._startup_complete = True
91
+ await asyncio.sleep(0)
92
+ failed = next((task for task in self._tasks if task.done()), None)
93
+ if failed is not None:
94
+ self._startup_complete = False
95
+ binding = tasks_by_binding[failed]
96
+ raise self._manager_error(binding.component, failed)
97
+ except BaseException:
98
+ self._startup_complete = False
99
+ self.begin_stop()
100
+ self.cancel()
101
+ await self._collect_outcomes()
102
+ raise
103
+ finally:
104
+ for task in readiness:
105
+ task.cancel()
106
+ if readiness:
107
+ await asyncio.gather(*readiness, return_exceptions=True)
108
+
109
+ def begin_stop(self) -> None:
110
+ self._stopping = True
111
+
112
+ async def wait(self) -> None:
113
+ cancellation: asyncio.CancelledError | None = None
114
+ try:
115
+ outcomes = await self._collect_outcomes()
116
+ except asyncio.CancelledError as error:
117
+ # deadline 取消 gather 时,已失败的 peer 仍须进入终止聚合,不能被取消掩盖。
118
+ cancellation = error
119
+ outcomes = await self._collect_outcomes()
120
+ failures: list[BaseException] = []
121
+ for task, outcome in zip(self._tasks, outcomes, strict=True):
122
+ fact = self._task_failures.get(task)
123
+ if (
124
+ fact is not None
125
+ or (
126
+ isinstance(outcome, BaseException)
127
+ and not isinstance(outcome, asyncio.CancelledError)
128
+ )
129
+ ) and task not in self._reported_tasks:
130
+ manager_error = _PgQueuerManagerError(
131
+ component=self._task_components[task],
132
+ error_type=fact.error_type if fact is not None else type(outcome).__name__,
133
+ )
134
+ if fact is not None:
135
+ manager_error._source = fact._source
136
+ if isinstance(outcome, BaseException):
137
+ manager_error.__cause__ = outcome
138
+ failures.append(manager_error)
139
+ self._reported_tasks.add(task)
140
+ if failures:
141
+ raise BaseExceptionGroup(
142
+ "PgQueuer manager drain failed", tuple(failures)
143
+ ) from cancellation
144
+ if cancellation is not None:
145
+ raise cancellation
146
+
147
+ async def wait_for_failure(self) -> _PgQueuerManagerFailure:
148
+ failure = self._failure
149
+ if failure is None:
150
+ raise RuntimeError("PgQueuer manager supervisor is not started")
151
+ return await asyncio.shield(failure)
152
+
153
+ def cancel(self) -> None:
154
+ for task in self._tasks:
155
+ task.cancel()
156
+
157
+ async def close(self) -> None:
158
+ self.begin_stop()
159
+ # bootstrap 已报告的原始失败不会重复计数;取消 peer 时 finally 的失败仍须可观察。
160
+ await self.wait()
161
+ self._tasks = ()
162
+ self._task_components = {}
163
+ self._task_failures.clear()
164
+ self._reported_tasks.clear()
165
+
166
+ async def _run(self, binding: _PgQueuerManagerBinding) -> None:
167
+ try:
168
+ await binding.run()
169
+ except BaseException as original_error:
170
+ if self._startup_complete and not self._stopping:
171
+ self._publish_failure(binding.component, type(original_error).__name__)
172
+ raise
173
+ if self._startup_complete and not self._stopping:
174
+ self._publish_failure(binding.component, "UnexpectedCompletion")
175
+
176
+ def _publish_failure(self, component: str, error_type: str) -> None:
177
+ task = asyncio.current_task()
178
+ assert task is not None
179
+ fact = _PgQueuerManagerFailure(component=component, error_type=error_type)
180
+ self._task_failures[task] = fact
181
+ failure = self._failure
182
+ if failure is not None and not failure.done():
183
+ failure.set_result(fact)
184
+ # 发布不等于 Application 已接收;同源去重只能由最终终止聚合 owner 完成。
185
+
186
+ async def _collect_outcomes(self) -> tuple[object, ...]:
187
+ if not self._tasks:
188
+ return ()
189
+ return tuple(await asyncio.gather(*self._tasks, return_exceptions=True))
190
+
191
+ def _manager_error(self, component: str, task: asyncio.Task[None]) -> _PgQueuerManagerError:
192
+ self._reported_tasks.add(task)
193
+ if task.cancelled():
194
+ error_type = asyncio.CancelledError.__name__
195
+ else:
196
+ error = task.exception()
197
+ error_type = "UnexpectedCompletion" if error is None else type(error).__name__
198
+ failure = _PgQueuerManagerError(component=component, error_type=error_type)
199
+ if not task.cancelled():
200
+ failure.__cause__ = task.exception()
201
+ return failure
@@ -0,0 +1,22 @@
1
+ """Provider-neutral non-durable background worker capability。"""
2
+
3
+ from .catalog import BackgroundWorkerCatalog
4
+ from .contracts import (
5
+ BackgroundExecutionOptions,
6
+ BackgroundExecutionProfile,
7
+ BackgroundWorker,
8
+ BackgroundWorkerContext,
9
+ BackgroundWorkerDefinition,
10
+ )
11
+ from .errors import BackgroundWorkerDefinitionError, BackgroundWorkerRuntimeError
12
+
13
+ __all__ = (
14
+ "BackgroundExecutionOptions",
15
+ "BackgroundExecutionProfile",
16
+ "BackgroundWorker",
17
+ "BackgroundWorkerCatalog",
18
+ "BackgroundWorkerContext",
19
+ "BackgroundWorkerDefinition",
20
+ "BackgroundWorkerDefinitionError",
21
+ "BackgroundWorkerRuntimeError",
22
+ )
@@ -0,0 +1,96 @@
1
+ """Module-owned worker metadata 的 Application-local immutable catalog。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ from collections.abc import Iterator, Mapping
8
+ from types import MappingProxyType
9
+
10
+ from ..modularity import AppModule, ModuleKey, ModuleRegistry
11
+ from .contracts import BackgroundWorkerDefinition
12
+ from .errors import BackgroundWorkerDefinitionError
13
+
14
+
15
+ class BackgroundWorkerCatalog:
16
+ __slots__ = ("_declarations", "_definitions", "_fingerprint", "_owners")
17
+
18
+ def __init__(
19
+ self,
20
+ definitions: tuple[BackgroundWorkerDefinition, ...],
21
+ *,
22
+ owners: Mapping[str, ModuleKey],
23
+ declarations: tuple[tuple[type[AppModule], tuple[BackgroundWorkerDefinition, ...]], ...],
24
+ ) -> None:
25
+ self._definitions = MappingProxyType({item.name: item for item in definitions})
26
+ self._owners = MappingProxyType(dict(owners))
27
+ self._declarations = declarations
28
+ encoded = json.dumps(
29
+ [
30
+ {
31
+ "name": item.name,
32
+ "owner": str(self._owners[item.name]),
33
+ "interval_seconds": item.interval.total_seconds(),
34
+ "timeout_seconds": item.iteration_timeout.total_seconds(),
35
+ }
36
+ for item in definitions
37
+ ],
38
+ sort_keys=True,
39
+ separators=(",", ":"),
40
+ ).encode()
41
+ self._fingerprint = hashlib.sha256(encoded).hexdigest()
42
+
43
+ @property
44
+ def definitions(self) -> tuple[BackgroundWorkerDefinition, ...]:
45
+ return tuple(self._definitions.values())
46
+
47
+ @property
48
+ def fingerprint(self) -> str:
49
+ return self._fingerprint
50
+
51
+ def __iter__(self) -> Iterator[str]:
52
+ return iter(self._definitions)
53
+
54
+ def get(self, name: str) -> BackgroundWorkerDefinition | None:
55
+ return self._definitions.get(name)
56
+
57
+ def _assert_unchanged(self) -> None:
58
+ for module_type, declared in self._declarations:
59
+ if module_type.__dict__.get("background_worker_definitions", ()) is not declared:
60
+ raise BackgroundWorkerDefinitionError(
61
+ worker=f"{module_type.__module__}.{module_type.__qualname__}",
62
+ reason="Module background worker metadata changed after build",
63
+ )
64
+
65
+
66
+ def _build_background_worker_catalog(registry: ModuleRegistry) -> BackgroundWorkerCatalog:
67
+ definitions: list[BackgroundWorkerDefinition] = []
68
+ owners: dict[str, ModuleKey] = {}
69
+ declarations: list[tuple[type[AppModule], tuple[BackgroundWorkerDefinition, ...]]] = []
70
+ for descriptor in registry.dependency_order:
71
+ declared = descriptor.module_type.__dict__.get("background_worker_definitions", ())
72
+ if not isinstance(declared, tuple):
73
+ raise BackgroundWorkerDefinitionError(
74
+ worker=str(descriptor.key),
75
+ reason="Module background_worker_definitions must be an immutable tuple",
76
+ )
77
+ declarations.append((descriptor.module_type, declared))
78
+ for definition in declared:
79
+ if not isinstance(definition, BackgroundWorkerDefinition):
80
+ raise BackgroundWorkerDefinitionError(
81
+ worker=str(descriptor.key),
82
+ reason="Module background_worker_definitions contains an invalid item",
83
+ )
84
+ previous = owners.get(definition.name)
85
+ if previous is not None:
86
+ raise BackgroundWorkerDefinitionError(
87
+ worker=definition.name,
88
+ reason=f"worker is already owned by {previous}",
89
+ )
90
+ owners[definition.name] = descriptor.key
91
+ definitions.append(definition)
92
+ return BackgroundWorkerCatalog(
93
+ tuple(definitions),
94
+ owners=owners,
95
+ declarations=tuple(declarations),
96
+ )
@@ -0,0 +1,124 @@
1
+ """Non-durable worker contract 与 Host-owned execution options。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import inspect
7
+ from abc import ABC, abstractmethod
8
+ from collections.abc import Awaitable, Callable, Mapping
9
+ from dataclasses import dataclass, field
10
+ from datetime import timedelta
11
+ from enum import Enum
12
+ from typing import cast
13
+
14
+ from pydantic import ConfigDict, Field, field_serializer
15
+
16
+ from ..background_execution import BackgroundTaskOptions
17
+ from ..invocation import BackgroundSystemCaller
18
+ from ..options import BaseOptions
19
+ from .errors import BackgroundWorkerDefinitionError
20
+
21
+
22
+ class BackgroundExecutionProfile(str, Enum):
23
+ """Host 对当前 Application background execution ownership 的显式选择。"""
24
+
25
+ DISABLED = "disabled"
26
+ EMBEDDED = "embedded"
27
+ DEDICATED = "dedicated"
28
+
29
+
30
+ class BackgroundExecutionOptions(BaseOptions):
31
+ """Application 准入和关闭策略;任务声明仍由 Module catalog 唯一拥有。"""
32
+
33
+ model_config = ConfigDict(extra="forbid")
34
+
35
+ profile: BackgroundExecutionProfile = BackgroundExecutionProfile.EMBEDDED
36
+ workers: Mapping[str, BackgroundTaskOptions] = Field(default_factory=dict)
37
+ jobs: Mapping[str, BackgroundTaskOptions] = Field(default_factory=dict)
38
+ max_restarts: int = Field(default=3, ge=0)
39
+ restart_interval: timedelta = Field(default=timedelta(seconds=1), gt=timedelta(0))
40
+ graceful_shutdown_timeout: timedelta = Field(
41
+ default=timedelta(seconds=30),
42
+ gt=timedelta(0),
43
+ )
44
+
45
+ @field_serializer("workers", "jobs")
46
+ def _serialize_tasks(
47
+ self, value: Mapping[str, BackgroundTaskOptions]
48
+ ) -> dict[str, BackgroundTaskOptions]:
49
+ # 发布后的 Mapping 已冻结;原生 serializer 接收普通 dict 后继续处理内部 typed Options。
50
+ return dict(value)
51
+
52
+
53
+ class BackgroundWorker(ABC):
54
+ """一次 iteration 的实现边界;永久 loop 由 Application coordinator 独占。"""
55
+
56
+ @abstractmethod
57
+ async def run_iteration(self, context: BackgroundWorkerContext) -> None:
58
+ raise NotImplementedError
59
+
60
+
61
+ @dataclass(frozen=True, slots=True)
62
+ class BackgroundWorkerContext:
63
+ name: str
64
+ system_caller: BackgroundSystemCaller
65
+ _stop_event: asyncio.Event = field(repr=False, compare=False)
66
+
67
+ @property
68
+ def stop_requested(self) -> bool:
69
+ return self._stop_event.is_set()
70
+
71
+
72
+ @dataclass(frozen=True, slots=True)
73
+ class BackgroundWorkerDefinition:
74
+ name: str
75
+ worker_type: type[BackgroundWorker] | Callable[..., Awaitable[None]]
76
+ interval: timedelta
77
+ iteration_timeout: timedelta
78
+
79
+ def __post_init__(self) -> None:
80
+ _validate_name(self.name)
81
+ is_class = isinstance(self.worker_type, type) and issubclass(
82
+ self.worker_type, BackgroundWorker
83
+ )
84
+ if not is_class and not inspect.iscoroutinefunction(self.worker_type):
85
+ raise BackgroundWorkerDefinitionError(
86
+ worker=self.name,
87
+ reason="worker_type must be a BackgroundWorker class or async function",
88
+ )
89
+ if isinstance(self.worker_type, type) and not inspect.iscoroutinefunction(
90
+ cast(type[BackgroundWorker], self.worker_type).run_iteration
91
+ ):
92
+ raise BackgroundWorkerDefinitionError(
93
+ worker=self.name,
94
+ reason="run_iteration must be async",
95
+ )
96
+ if not isinstance(self.interval, timedelta) or self.interval.total_seconds() <= 0:
97
+ raise BackgroundWorkerDefinitionError(
98
+ worker=self.name,
99
+ reason="interval must be a positive timedelta",
100
+ )
101
+ if (
102
+ not isinstance(self.iteration_timeout, timedelta)
103
+ or self.iteration_timeout.total_seconds() <= 0
104
+ ):
105
+ raise BackgroundWorkerDefinitionError(
106
+ worker=self.name,
107
+ reason="iteration_timeout must be a positive timedelta",
108
+ )
109
+
110
+
111
+ def _validate_name(name: str) -> None:
112
+ if (
113
+ not isinstance(name, str)
114
+ or not name
115
+ or name != name.strip()
116
+ or name.startswith(".")
117
+ or name.endswith(".")
118
+ or ".." in name
119
+ or any(not (character.isalnum() or character in "._-") for character in name)
120
+ ):
121
+ raise BackgroundWorkerDefinitionError(
122
+ worker=name if isinstance(name, str) else repr(name),
123
+ reason="name must be a trimmed stable identifier",
124
+ )
@@ -0,0 +1,20 @@
1
+ """Non-durable background worker definition 与 runtime failures。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ..errors.base import FrameworkError
6
+
7
+
8
+ class BackgroundWorkerDefinitionError(FrameworkError):
9
+ def __init__(self, *, worker: str, reason: str) -> None:
10
+ self.worker = worker
11
+ self.reason = reason
12
+ super().__init__(f"Invalid background worker {worker!r}: {reason}")
13
+
14
+
15
+ class BackgroundWorkerRuntimeError(FrameworkError):
16
+ def __init__(self, *, worker: str, operation: str, error_type: str) -> None:
17
+ self.worker = worker
18
+ self.operation = operation
19
+ self.error_type = error_type
20
+ super().__init__(f"Background worker {worker!r} {operation} failed with {error_type}")
@@ -0,0 +1,133 @@
1
+ """单个 Worker 的 fixed-delay loop 与每次执行的原生作用域。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import logging
7
+ from contextlib import AsyncExitStack
8
+ from uuid import uuid4
9
+
10
+ from dishka import AsyncContainer, Scope
11
+
12
+ from ..application_services.contracts import _DEFAULT_CONNECTION_NAME
13
+ from ..application_services.errors import ApplicationInvocationRejectedError
14
+ from ..application_services.invocation import _InvocationRuntime
15
+ from ..authorization import CurrentUser
16
+ from ..background_execution.lifecycle import _TaskExecution
17
+ from ..invocation import BackgroundSystemCaller
18
+ from .contracts import BackgroundWorkerContext, BackgroundWorkerDefinition
19
+
20
+ _LOGGER = logging.getLogger(__name__)
21
+
22
+
23
+ class _WorkerExecution:
24
+ def __init__(
25
+ self,
26
+ definition: BackgroundWorkerDefinition,
27
+ container: AsyncContainer,
28
+ invocation_runtime: _InvocationRuntime,
29
+ caller: BackgroundSystemCaller,
30
+ ) -> None:
31
+ self._definition = definition
32
+ self._container = container
33
+ self._invocation_runtime = invocation_runtime
34
+ self._caller = caller
35
+ self._stop_event = asyncio.Event()
36
+ self._task: asyncio.Task[None] | None = None
37
+
38
+ @property
39
+ def binding(self) -> _TaskExecution:
40
+ return _TaskExecution(self.start, self._stop_event.set, self.wait, None, self.cancel)
41
+
42
+ async def start(self) -> None:
43
+ self._stop_event.clear()
44
+ self._task = asyncio.create_task(
45
+ self._worker_loop(self._definition),
46
+ name=f"python-platform-worker:{self._definition.name}",
47
+ )
48
+
49
+ async def wait(self) -> None:
50
+ if self._task is not None:
51
+ await self._task
52
+
53
+ def cancel(self) -> None:
54
+ if self._task is not None:
55
+ self._task.cancel()
56
+
57
+ async def _worker_loop(self, definition: BackgroundWorkerDefinition) -> None:
58
+ while not self._stop_event.is_set():
59
+ try:
60
+ await self._run_worker_iteration(definition)
61
+ except asyncio.CancelledError:
62
+ raise
63
+ except Exception as original_error:
64
+ if self._stop_event.is_set():
65
+ raise
66
+ # payload/secret 不进入日志;下一次 fixed-delay iteration 仍按 contract 执行。
67
+ _LOGGER.error(
68
+ "background worker iteration failed",
69
+ exc_info=original_error,
70
+ extra={
71
+ "background_worker": definition.name,
72
+ "background_exception_type": type(original_error).__name__,
73
+ },
74
+ )
75
+ if self._stop_event.is_set():
76
+ break
77
+ try:
78
+ await asyncio.wait_for(
79
+ self._stop_event.wait(),
80
+ timeout=definition.interval.total_seconds(),
81
+ )
82
+ except TimeoutError:
83
+ pass
84
+
85
+ async def _run_worker_iteration(self, definition: BackgroundWorkerDefinition) -> None:
86
+ container = self._container
87
+ context = BackgroundWorkerContext(
88
+ name=definition.name,
89
+ system_caller=self._caller,
90
+ _stop_event=self._stop_event,
91
+ )
92
+
93
+ # 原生 timeout 在当前 Worker Task 内执行、取消并等待清理;finally 失败直接传播。
94
+ # 不创建额外迭代 Task,调用许可、caller 和业务作用域保持同一 owner。
95
+ async with (
96
+ asyncio.timeout(definition.iteration_timeout.total_seconds()),
97
+ AsyncExitStack() as scope,
98
+ ):
99
+ # 关闭可能撤回尚未取得的许可;只处理 enter 的拒绝,不吞业务或 finally 异常。
100
+ try:
101
+ await scope.enter_async_context(self._invocation_runtime.outer_lease())
102
+ except ApplicationInvocationRejectedError:
103
+ return
104
+ token = self._invocation_runtime.bind_caller(CurrentUser.anonymous(), str(uuid4()))
105
+ try:
106
+ try:
107
+ async with container(scope=Scope.REQUEST) as request_container:
108
+ from ..invocation.interception import _EntryInterceptionCatalog
109
+
110
+ entries = await request_container.get(_EntryInterceptionCatalog)
111
+ await entries.invoke(
112
+ self._invocation_runtime,
113
+ request_container,
114
+ definition.worker_type,
115
+ "run_iteration",
116
+ (context,),
117
+ connection_name=_DEFAULT_CONNECTION_NAME,
118
+ )
119
+ except Exception as error:
120
+ if self._stop_event.is_set():
121
+ # 关闭中的异常可能来自被取消业务的 finally,不能按普通迭代失败吞掉。
122
+ raise
123
+ # lease/caller 尚有效;普通迭代失败在 owner 内记录,然后继续下一轮。
124
+ _LOGGER.error(
125
+ "background worker iteration failed",
126
+ exc_info=error,
127
+ extra={
128
+ "background_worker": definition.name,
129
+ "background_exception_type": type(error).__name__,
130
+ },
131
+ )
132
+ finally:
133
+ self._invocation_runtime.reset_caller(token)