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,241 @@
1
+ """不可变身份、Permission contract 与静态授权 metadata。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from dataclasses import dataclass
7
+ from typing import Protocol, TypeVar, runtime_checkable
8
+ from uuid import UUID
9
+
10
+ from .errors import AuthorizationDefinitionError, PermissionDefinitionError
11
+
12
+ _AUTHORIZATION_ATTRIBUTE = "__python_platform_authorization__"
13
+ _ALLOW_ANONYMOUS_ATTRIBUTE = "__python_platform_allow_anonymous__"
14
+ _TDecorated = TypeVar("_TDecorated")
15
+
16
+
17
+ @dataclass(frozen=True, order=True, slots=True)
18
+ class PermissionName:
19
+ value: str
20
+
21
+ def __post_init__(self) -> None:
22
+ if (
23
+ not isinstance(self.value, str)
24
+ or not self.value
25
+ or self.value != self.value.strip()
26
+ or self.value.startswith(".")
27
+ or self.value.endswith(".")
28
+ or ".." in self.value
29
+ or any(not (item.isalnum() or item in "._-") for item in self.value)
30
+ ):
31
+ raise PermissionDefinitionError(
32
+ permission=self.value if isinstance(self.value, str) else repr(self.value),
33
+ reason="name must be a trimmed dotted identifier",
34
+ )
35
+
36
+ def __str__(self) -> str:
37
+ return self.value
38
+
39
+
40
+ @dataclass(frozen=True, slots=True)
41
+ class PermissionDefinition:
42
+ name: PermissionName
43
+ display_name: str
44
+
45
+ def __post_init__(self) -> None:
46
+ if not isinstance(self.name, PermissionName):
47
+ raise PermissionDefinitionError(
48
+ permission=repr(self.name),
49
+ reason="name must be PermissionName",
50
+ )
51
+ if (
52
+ not isinstance(self.display_name, str)
53
+ or not self.display_name
54
+ or self.display_name != self.display_name.strip()
55
+ ):
56
+ raise PermissionDefinitionError(
57
+ permission=str(self.name),
58
+ reason="display_name must be a non-empty trimmed string",
59
+ )
60
+
61
+
62
+ @dataclass(frozen=True, order=True, slots=True)
63
+ class RolePermissionVersion:
64
+ role_id: UUID
65
+ version: int
66
+
67
+ def __post_init__(self) -> None:
68
+ if not isinstance(self.role_id, UUID):
69
+ raise TypeError("role_id must be UUID")
70
+ if not isinstance(self.version, int) or isinstance(self.version, bool) or self.version < 0:
71
+ raise ValueError("role permission version must be a non-negative integer")
72
+
73
+
74
+ @dataclass(frozen=True, slots=True)
75
+ class CurrentUser:
76
+ """一次可信入口已验证的身份快照;不携带权限集合。"""
77
+
78
+ user_id: UUID | None = None
79
+ session_id: UUID | None = None
80
+ username: str | None = None
81
+ permission_version: int = 0
82
+ role_versions: tuple[RolePermissionVersion, ...] = ()
83
+
84
+ def __post_init__(self) -> None:
85
+ identity_values = (self.user_id, self.session_id, self.username)
86
+ is_anonymous = all(value is None for value in identity_values)
87
+ if not is_anonymous and (
88
+ not isinstance(self.user_id, UUID)
89
+ or not isinstance(self.session_id, UUID)
90
+ or not isinstance(self.username, str)
91
+ or not self.username
92
+ or self.username != self.username.strip()
93
+ ):
94
+ raise ValueError("authenticated CurrentUser requires user_id, session_id and username")
95
+ if is_anonymous and (self.permission_version != 0 or self.role_versions):
96
+ raise ValueError("anonymous CurrentUser cannot carry authorization versions")
97
+ if not isinstance(self.permission_version, int) or isinstance(
98
+ self.permission_version, bool
99
+ ):
100
+ raise TypeError("permission_version must be int")
101
+ if self.permission_version < 0:
102
+ raise ValueError("permission_version must be non-negative")
103
+ if not isinstance(self.role_versions, tuple) or any(
104
+ not isinstance(item, RolePermissionVersion) for item in self.role_versions
105
+ ):
106
+ raise TypeError("role_versions must be an immutable RolePermissionVersion tuple")
107
+ if tuple(sorted(self.role_versions)) != self.role_versions:
108
+ raise ValueError("role_versions must be sorted by role_id and version")
109
+ if len({item.role_id for item in self.role_versions}) != len(self.role_versions):
110
+ raise ValueError("role_versions cannot contain duplicate role ids")
111
+
112
+ @property
113
+ def is_authenticated(self) -> bool:
114
+ return self.user_id is not None
115
+
116
+ @classmethod
117
+ def anonymous(cls) -> CurrentUser:
118
+ return cls()
119
+
120
+
121
+ @runtime_checkable
122
+ class PermissionChecker(Protocol):
123
+ async def is_granted(self, user: CurrentUser, permission: PermissionName) -> bool: ...
124
+
125
+
126
+ @runtime_checkable
127
+ class AccessTokenAuthenticator(Protocol):
128
+ async def authenticate_access_token(self, token: str) -> CurrentUser: ...
129
+
130
+
131
+ @dataclass(frozen=True, slots=True)
132
+ class _AuthorizationMetadata:
133
+ permissions: tuple[PermissionName, ...]
134
+
135
+
136
+ @dataclass(frozen=True, slots=True)
137
+ class _AuthorizationRequirement:
138
+ requires_authenticated_user: bool
139
+ permissions: tuple[PermissionName, ...]
140
+ allows_anonymous: bool
141
+
142
+
143
+ def authorize(*permissions: PermissionName) -> Callable[[_TDecorated], _TDecorated]:
144
+ """声明登录或 all-of Permission 要求;只接受集中定义的 PermissionName。"""
145
+
146
+ if any(not isinstance(permission, PermissionName) for permission in permissions):
147
+ raise AuthorizationDefinitionError(
148
+ symbol="<decorator>",
149
+ reason="authorize permissions must be PermissionName values",
150
+ )
151
+ if len(set(permissions)) != len(permissions):
152
+ raise AuthorizationDefinitionError(
153
+ symbol="<decorator>",
154
+ reason="authorize permissions cannot contain duplicates",
155
+ )
156
+ metadata = _AuthorizationMetadata(permissions=permissions)
157
+
158
+ def decorate(target: _TDecorated) -> _TDecorated:
159
+ _validate_authorization_target(target)
160
+ namespace = vars(target)
161
+ symbol = _symbol(target)
162
+ if _AUTHORIZATION_ATTRIBUTE in namespace or _ALLOW_ANONYMOUS_ATTRIBUTE in namespace:
163
+ raise AuthorizationDefinitionError(
164
+ symbol=symbol,
165
+ reason="authorization metadata may only be declared once",
166
+ )
167
+ setattr(target, _AUTHORIZATION_ATTRIBUTE, metadata)
168
+ return target
169
+
170
+ return decorate
171
+
172
+
173
+ def allow_anonymous(target: _TDecorated) -> _TDecorated:
174
+ """Method metadata 明确覆盖 class 级授权;不靠命名或默认回退推断。"""
175
+
176
+ _validate_authorization_target(target)
177
+ namespace = vars(target)
178
+ symbol = _symbol(target)
179
+ if _AUTHORIZATION_ATTRIBUTE in namespace or _ALLOW_ANONYMOUS_ATTRIBUTE in namespace:
180
+ raise AuthorizationDefinitionError(
181
+ symbol=symbol,
182
+ reason="authorization metadata may only be declared once",
183
+ )
184
+ setattr(target, _ALLOW_ANONYMOUS_ATTRIBUTE, True)
185
+ return target
186
+
187
+
188
+ def _authorization_requirement(
189
+ implementation: type[object],
190
+ method: object,
191
+ ) -> _AuthorizationRequirement:
192
+ # 只从仲裁胜出的实现继承链合并类声明;Contract 不再是策略来源。
193
+ class_metadata = tuple(
194
+ metadata
195
+ for owner in reversed(implementation.__mro__)
196
+ if (metadata := _metadata_for(owner)) is not None
197
+ )
198
+ method_metadata = _metadata_for(method)
199
+ method_allows_anonymous = vars(method).get(_ALLOW_ANONYMOUS_ATTRIBUTE, False)
200
+ if method_allows_anonymous:
201
+ return _AuthorizationRequirement(False, (), True)
202
+ permissions: list[PermissionName] = []
203
+ for metadata in (*class_metadata, method_metadata):
204
+ if metadata is None:
205
+ continue
206
+ for permission in metadata.permissions:
207
+ if permission not in permissions:
208
+ permissions.append(permission)
209
+ requires_user = bool(class_metadata) or method_metadata is not None
210
+ return _AuthorizationRequirement(requires_user, tuple(permissions), False)
211
+
212
+
213
+ def _metadata_for(target: object) -> _AuthorizationMetadata | None:
214
+ namespace = vars(target)
215
+ metadata = namespace.get(_AUTHORIZATION_ATTRIBUTE)
216
+ anonymous = namespace.get(_ALLOW_ANONYMOUS_ATTRIBUTE, False)
217
+ if metadata is not None and not isinstance(metadata, _AuthorizationMetadata):
218
+ raise AuthorizationDefinitionError(
219
+ symbol=_symbol(target),
220
+ reason="authorization metadata has an invalid runtime shape",
221
+ )
222
+ if not isinstance(anonymous, bool):
223
+ raise AuthorizationDefinitionError(
224
+ symbol=_symbol(target),
225
+ reason="allow_anonymous metadata has an invalid runtime shape",
226
+ )
227
+ return metadata
228
+
229
+
230
+ def _validate_authorization_target(target: object) -> None:
231
+ if not isinstance(target, type) and not callable(target):
232
+ raise AuthorizationDefinitionError(
233
+ symbol=repr(target),
234
+ reason="authorization decorator target must be a class or callable",
235
+ )
236
+
237
+
238
+ def _symbol(target: object) -> str:
239
+ module = getattr(target, "__module__", type(target).__module__)
240
+ qualname = getattr(target, "__qualname__", type(target).__qualname__)
241
+ return f"{module}.{qualname}"
@@ -0,0 +1,81 @@
1
+ """从冻结类型结果解释权限定义,拥有贡献顺序、来源与冲突检查。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ from collections.abc import Callable
7
+ from typing import cast
8
+
9
+ from ..diagnostics import SourceLocation
10
+ from ..modularity import ModuleRegistry
11
+ from ..modularity.discovery import _DiscoveredType
12
+ from .contracts import PermissionDefinition, PermissionName
13
+ from .definitions import PermissionDefinitionContext, PermissionDefinitionProvider
14
+ from .errors import PermissionDefinitionError
15
+
16
+
17
+ def _collect_permission_definitions(
18
+ registry: ModuleRegistry,
19
+ discoveries: tuple[_DiscoveredType, ...],
20
+ ) -> tuple[PermissionDefinition, ...]:
21
+ positions = {item.key: index for index, item in enumerate(registry.dependency_order)}
22
+ providers: dict[type[object], SourceLocation] = {}
23
+ sources: dict[PermissionName, SourceLocation] = {}
24
+ definitions: list[PermissionDefinition] = []
25
+ for discovery in sorted(
26
+ discoveries,
27
+ key=lambda item: (
28
+ positions[item.owner],
29
+ f"{item.candidate.__module__}.{item.candidate.__qualname__}",
30
+ ),
31
+ ):
32
+ candidate = discovery.candidate
33
+ if not issubclass(candidate, PermissionDefinitionProvider) or inspect.isabstract(candidate):
34
+ continue
35
+ previous = providers.get(candidate)
36
+ if previous is not None:
37
+ if previous.owner != discovery.source.owner:
38
+ raise PermissionDefinitionError(
39
+ permission=candidate.__qualname__,
40
+ reason=f"provider is already owned by {previous}",
41
+ source=discovery.source,
42
+ )
43
+ continue
44
+ providers[candidate] = discovery.source
45
+ for definition in _define(candidate, discovery.source):
46
+ if definition.name in sources:
47
+ raise PermissionDefinitionError(
48
+ permission=str(definition.name),
49
+ reason=f"permission is already owned by {sources[definition.name]}",
50
+ source=discovery.source,
51
+ )
52
+ sources[definition.name] = discovery.source
53
+ definitions.append(definition)
54
+ return tuple(definitions)
55
+
56
+
57
+ def _define(
58
+ provider_type: type[PermissionDefinitionProvider],
59
+ source: SourceLocation,
60
+ ) -> tuple[PermissionDefinition, ...]:
61
+ context = PermissionDefinitionContext(source)
62
+ try:
63
+ provider = provider_type()
64
+ callback = cast(Callable[[PermissionDefinitionContext], object], provider.define)
65
+ if inspect.iscoroutinefunction(callback) or inspect.isasyncgenfunction(callback):
66
+ raise TypeError("define must be synchronous")
67
+ result = callback(context)
68
+ if result is not None:
69
+ if inspect.iscoroutine(result) or inspect.isgenerator(result):
70
+ result.close()
71
+ raise TypeError("define must return None")
72
+ except Exception as error:
73
+ raise PermissionDefinitionError(
74
+ permission=provider_type.__qualname__,
75
+ reason=f"definition provider failed: {type(error).__name__}: {error}",
76
+ source=source,
77
+ ) from error
78
+ finally:
79
+ # 即使构造或贡献失败,已逸出的上下文也不可再写入。
80
+ definitions = context._close()
81
+ return definitions
@@ -0,0 +1,43 @@
1
+ """权限定义的同步贡献契约;上下文由单次 Application build 独占。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+
7
+ from ..diagnostics import SourceLocation
8
+ from .contracts import PermissionDefinition
9
+ from .errors import PermissionDefinitionError
10
+
11
+
12
+ class PermissionDefinitionProvider(ABC):
13
+ @abstractmethod
14
+ def define(self, context: PermissionDefinitionContext) -> None:
15
+ """同步贡献静态权限定义;无构造注入、I/O 或运行期服务解析。"""
16
+
17
+
18
+ class PermissionDefinitionContext:
19
+ __slots__ = ("_closed", "_definitions", "_source")
20
+
21
+ def __init__(self, source: SourceLocation) -> None:
22
+ self._source = source
23
+ self._definitions: list[PermissionDefinition] = []
24
+ self._closed = False
25
+
26
+ def add(self, definition: PermissionDefinition) -> None:
27
+ if self._closed:
28
+ raise PermissionDefinitionError(
29
+ permission="context",
30
+ reason="definition context is closed",
31
+ source=self._source,
32
+ )
33
+ if not isinstance(definition, PermissionDefinition):
34
+ raise PermissionDefinitionError(
35
+ permission="context",
36
+ reason="expected PermissionDefinition",
37
+ source=self._source,
38
+ )
39
+ self._definitions.append(definition)
40
+
41
+ def _close(self) -> tuple[PermissionDefinition, ...]:
42
+ self._closed = True
43
+ return tuple(self._definitions)
@@ -0,0 +1,40 @@
1
+ """Authorization metadata、身份与权限判定的公开失败语义。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ..diagnostics import SourceLocation
6
+ from ..errors.base import FrameworkError
7
+
8
+
9
+ class PermissionDefinitionError(FrameworkError):
10
+ def __init__(
11
+ self, *, permission: str, reason: str, source: SourceLocation | None = None
12
+ ) -> None:
13
+ self.permission = permission
14
+ self.reason = reason
15
+ self.source = source
16
+ location = "" if source is None else f" at {source}"
17
+ super().__init__(f"Invalid permission {permission!r}{location}: {reason}")
18
+
19
+
20
+ class AuthorizationDefinitionError(FrameworkError):
21
+ def __init__(self, *, symbol: str, reason: str) -> None:
22
+ self.symbol = symbol
23
+ self.reason = reason
24
+ super().__init__(f"Invalid authorization metadata for {symbol}: {reason}")
25
+
26
+
27
+ class UnauthenticatedError(FrameworkError):
28
+ def __init__(self) -> None:
29
+ super().__init__("Authentication is required")
30
+
31
+
32
+ class PermissionDeniedError(FrameworkError):
33
+ def __init__(self, *, permission: str) -> None:
34
+ self.permission = permission
35
+ super().__init__(f"Permission {permission!r} is required")
36
+
37
+
38
+ class PermissionCheckerUnavailableError(FrameworkError):
39
+ def __init__(self) -> None:
40
+ super().__init__("A protected Application Service requires a PermissionChecker")
@@ -0,0 +1,15 @@
1
+ """任务执行位置、状态与管理的稳定契约。"""
2
+
3
+ from .contracts import (
4
+ BackgroundExecutionMode,
5
+ BackgroundTaskOptions,
6
+ BackgroundTaskSnapshot,
7
+ BackgroundTaskState,
8
+ )
9
+
10
+ __all__ = (
11
+ "BackgroundExecutionMode",
12
+ "BackgroundTaskOptions",
13
+ "BackgroundTaskSnapshot",
14
+ "BackgroundTaskState",
15
+ )
@@ -0,0 +1,41 @@
1
+ """授权后将操作交给 Application owner;不等待任务业务,也不保留 REQUEST 资源。"""
2
+
3
+ from ..application_services import ApplicationService, application_service
4
+ from ..authorization import authorize
5
+ from ..background_workers.runtime import _BackgroundCoordinator
6
+ from ..errors import ResourceConflictError, ResourceNotFoundError
7
+ from .contracts import BackgroundTaskSnapshot
8
+ from .management import BACKGROUND_MANAGE, BACKGROUND_VIEW, BackgroundManagementApplicationService
9
+
10
+
11
+ @application_service(provides=BackgroundManagementApplicationService)
12
+ class _BackgroundManagementApplicationService(ApplicationService):
13
+ def __init__(self, coordinator: _BackgroundCoordinator) -> None:
14
+ self._coordinator = coordinator
15
+
16
+ @authorize(BACKGROUND_VIEW)
17
+ async def get_list(self) -> tuple[BackgroundTaskSnapshot, ...]:
18
+ return self._coordinator.snapshots()
19
+
20
+ @authorize(BACKGROUND_VIEW)
21
+ async def get(self, task_id: str) -> BackgroundTaskSnapshot:
22
+ for snapshot in self._coordinator.snapshots():
23
+ if snapshot.id == task_id:
24
+ return snapshot
25
+ raise ResourceNotFoundError("Background task does not exist")
26
+
27
+ @authorize(BACKGROUND_MANAGE)
28
+ async def start(self, task_id: str) -> BackgroundTaskSnapshot:
29
+ return self._request(task_id, start=True)
30
+
31
+ @authorize(BACKGROUND_MANAGE)
32
+ async def stop(self, task_id: str) -> BackgroundTaskSnapshot:
33
+ return self._request(task_id, start=False)
34
+
35
+ def _request(self, task_id: str, *, start: bool) -> BackgroundTaskSnapshot:
36
+ try:
37
+ return self._coordinator.request(task_id, start=start)
38
+ except KeyError:
39
+ raise ResourceNotFoundError("Background task does not exist") from None
40
+ except ValueError as error:
41
+ raise ResourceConflictError(str(error)) from None
@@ -0,0 +1,123 @@
1
+ """spawn 子进程入口;管道是父进程许可与存活边界,Application 独立重建。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from typing import TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ from multiprocessing.connection import _ConnectionBase as Connection
10
+
11
+ from ..application import Application
12
+ from ..application.build_spec import _ApplicationBuildSpec
13
+ from ..errors import ApplicationStartError
14
+ from ..lifecycle import ApplicationState
15
+ from .contracts import BackgroundTaskOptions
16
+ from .locks import _position_lock
17
+ from .processes import _POLL_INTERVAL, _Message
18
+
19
+
20
+ def _run_child(
21
+ spec: _ApplicationBuildSpec,
22
+ position: str,
23
+ tasks: dict[str, BackgroundTaskOptions],
24
+ initial: tuple[str, ...],
25
+ pipe: Connection,
26
+ ) -> None:
27
+ try:
28
+ with _position_lock(spec.project, spec.environment, position):
29
+ pipe.send((_Message.LOCKED,))
30
+ if pipe.recv() != (_Message.GO,):
31
+ raise RuntimeError("Background process has no parent permission")
32
+ asyncio.run(_serve_child(spec, tasks, initial, pipe))
33
+ except (EOFError, BrokenPipeError):
34
+ # 父进程已退出,执行许可已撤销;异步入口的 finally 仍先完成 Application 清理。
35
+ pass
36
+ except BaseException as error:
37
+ try:
38
+ if isinstance(error, ApplicationStartError) and error.cleanup_failures:
39
+ pipe.send((_Message.CLEANUP_FAILED, type(error).__name__))
40
+ pipe.send((_Message.FAILED, type(error).__name__))
41
+ except (EOFError, OSError):
42
+ pass
43
+ finally:
44
+ pipe.close()
45
+
46
+
47
+ async def _serve_child(
48
+ spec: _ApplicationBuildSpec,
49
+ tasks: dict[str, BackgroundTaskOptions],
50
+ initial: tuple[str, ...],
51
+ pipe: Connection,
52
+ ) -> None:
53
+ application = spec.build()
54
+ application._root.require_hosted_service_coordinator().select_child()
55
+ coordinator = application._root.require_background_coordinator()
56
+ coordinator.select_child(tasks, initial)
57
+ try:
58
+ await _start_while_parent_present(application, pipe)
59
+ previous = coordinator.snapshots()
60
+ while True:
61
+ if pipe.poll():
62
+ command = pipe.recv()
63
+ if command[0] == _Message.SHUTDOWN:
64
+ break
65
+ action, serial, key = command
66
+ if action not in (_Message.START, _Message.STOP):
67
+ raise RuntimeError("Unknown background process command")
68
+ try:
69
+ snapshot = coordinator.request(key, start=action == _Message.START)
70
+ except (KeyError, ValueError) as error:
71
+ pipe.send((_Message.REJECTED, serial, type(error).__name__))
72
+ else:
73
+ pipe.send((_Message.RESULT, serial, snapshot))
74
+ snapshots = coordinator.snapshots()
75
+ if snapshots != previous:
76
+ pipe.send((_Message.SNAPSHOTS, snapshots))
77
+ previous = snapshots
78
+ if application.state in (
79
+ ApplicationState.STOPPED,
80
+ ApplicationState.STOPPED_WITH_ERRORS,
81
+ ):
82
+ await application.wait_for_termination()
83
+ break
84
+ await asyncio.sleep(_POLL_INTERVAL)
85
+ finally:
86
+ await _stop_child(application, pipe)
87
+
88
+
89
+ async def _start_while_parent_present(application: Application, pipe: Connection) -> None:
90
+ async def start() -> None:
91
+ await application.start()
92
+ # 启动确认与 start 返回处于同一 task 连续段;首轮业务崩溃不能被误判为初始化失败。
93
+ pipe.send(
94
+ (_Message.SNAPSHOTS, application._root.require_background_coordinator().snapshots())
95
+ )
96
+ pipe.send((_Message.READY,))
97
+
98
+ starting = asyncio.create_task(start())
99
+ try:
100
+ while not starting.done():
101
+ if pipe.poll():
102
+ if pipe.recv() != (_Message.SHUTDOWN,):
103
+ raise RuntimeError("Unexpected command during child initialization")
104
+ starting.cancel()
105
+ break
106
+ await asyncio.sleep(_POLL_INTERVAL)
107
+ await starting
108
+ finally:
109
+ if not starting.done():
110
+ # EOF/父进程 shutdown 只请求一次取消,Application 仍独占 partial-start 清理。
111
+ starting.cancel()
112
+ await starting
113
+
114
+
115
+ async def _stop_child(application: Application, pipe: Connection) -> None:
116
+ try:
117
+ await application.stop()
118
+ except BaseException as error:
119
+ try:
120
+ pipe.send((_Message.CLEANUP_FAILED, type(error).__name__))
121
+ except (EOFError, OSError):
122
+ pass
123
+ raise
@@ -0,0 +1,39 @@
1
+ """执行模式与只读状态;不依赖 provider、进程或编排实现。"""
2
+
3
+ from enum import Enum
4
+
5
+ from pydantic import BaseModel, ConfigDict
6
+
7
+ from ..options import BaseOptions
8
+
9
+
10
+ class BackgroundExecutionMode(str, Enum):
11
+ HOST = "host"
12
+ SHARED = "shared"
13
+ EXCLUSIVE = "exclusive"
14
+
15
+
16
+ class BackgroundTaskOptions(BaseOptions):
17
+ enabled: bool = True
18
+ mode: BackgroundExecutionMode = BackgroundExecutionMode.HOST
19
+
20
+
21
+ class BackgroundTaskState(str, Enum):
22
+ DISABLED = "disabled"
23
+ STARTING = "starting"
24
+ RUNNING = "running"
25
+ STOPPING = "stopping"
26
+ STOPPED = "stopped"
27
+ FAILED = "failed"
28
+
29
+
30
+ class BackgroundTaskSnapshot(BaseModel):
31
+ model_config = ConfigDict(frozen=True)
32
+
33
+ id: str
34
+ enabled: bool
35
+ mode: BackgroundExecutionMode
36
+ state: BackgroundTaskState
37
+ process_id: int | None = None
38
+ restart_count: int = 0
39
+ error_type: str | None = None
@@ -0,0 +1,22 @@
1
+ """本进程执行单元的生命周期回调及失败事实;由 Worker/持久队列提供。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Awaitable, Callable
6
+ from dataclasses import dataclass, field
7
+
8
+
9
+ @dataclass(frozen=True, slots=True)
10
+ class _BackgroundRuntimeFailure:
11
+ component: str
12
+ error_type: str
13
+ _source: object = field(default_factory=object, repr=False, compare=False)
14
+
15
+
16
+ @dataclass(frozen=True, slots=True)
17
+ class _TaskExecution:
18
+ start: Callable[[], Awaitable[None]]
19
+ stop_claiming: Callable[[], None]
20
+ wait: Callable[[], Awaitable[None]]
21
+ wait_for_failure: Callable[[], Awaitable[_BackgroundRuntimeFailure]] | None
22
+ cancel_for_deadline: Callable[[], None]