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,55 @@
1
+ """主 Host 拥有的服务生命周期契约;连接恢复由服务实现负责。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ from abc import ABC, abstractmethod
7
+ from enum import Enum
8
+ from typing import TYPE_CHECKING, ClassVar
9
+
10
+ from .errors import HostedServiceDefinitionError
11
+
12
+ if TYPE_CHECKING:
13
+ from ..lifecycle import ShutdownReason
14
+ from .bridge import HostedServiceContext
15
+
16
+
17
+ class HostedService(ABC):
18
+ """APP 单例;start 返回即启动成功,stop 必须等待自身资源退出。"""
19
+
20
+ run_in_background_processes: ClassVar[bool] = False
21
+
22
+ @abstractmethod
23
+ async def start(self, context: HostedServiceContext) -> None:
24
+ raise NotImplementedError
25
+
26
+ @abstractmethod
27
+ async def stop(self, reason: ShutdownReason) -> None:
28
+ raise NotImplementedError
29
+
30
+
31
+ class HostedServiceStatus(str, Enum):
32
+ STARTING = "starting"
33
+ RUNNING = "running"
34
+ STOPPING = "stopping"
35
+ STOPPED = "stopped"
36
+ FAILED = "failed"
37
+
38
+
39
+ def _validate_service_type(candidate: type[HostedService]) -> None:
40
+ if not isinstance(candidate, type) or not issubclass(candidate, HostedService):
41
+ raise HostedServiceDefinitionError(
42
+ service=repr(candidate), reason="hosted_services must contain HostedService types"
43
+ )
44
+ name = f"{candidate.__module__}.{candidate.__qualname__}"
45
+ if not isinstance(candidate.run_in_background_processes, bool):
46
+ raise HostedServiceDefinitionError(
47
+ service=name, reason="run_in_background_processes must be bool"
48
+ )
49
+ if inspect.isabstract(candidate):
50
+ raise HostedServiceDefinitionError(service=name, reason="service type must be concrete")
51
+ for operation in ("start", "stop"):
52
+ if not inspect.iscoroutinefunction(getattr(candidate, operation, None)):
53
+ raise HostedServiceDefinitionError(
54
+ service=name, reason=f"{operation} must use async def"
55
+ )
@@ -0,0 +1,53 @@
1
+ """Hosted Service definition、lifecycle 与 bridge 的脱敏错误边界。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ..errors.base import FrameworkError
6
+
7
+
8
+ class HostedServiceDefinitionError(FrameworkError):
9
+ def __init__(self, *, service: str, reason: str) -> None:
10
+ self.service = service
11
+ self.reason = reason
12
+ super().__init__(f"Invalid Hosted Service {service!r}: {reason}")
13
+
14
+
15
+ class HostedServiceContractError(FrameworkError):
16
+ """只发布 Service identity、操作与稳定错误类型,不保留 provider 异常正文。"""
17
+
18
+ def __init__(self, *, service: str, operation: str, error_type: str) -> None:
19
+ self.service = service
20
+ self.operation = operation
21
+ self.error_type = error_type
22
+ super().__init__(
23
+ f"Hosted Service {service!r} contract rejected {operation} with {error_type}"
24
+ )
25
+
26
+
27
+ class HostedServiceLifecycleError(FrameworkError):
28
+ """Service 第三方异常在此终止传播,避免设备或 SDK 文本进入终态。"""
29
+
30
+ failure_id = None
31
+ stage = None
32
+ dependency_path: tuple[object, ...] = ()
33
+
34
+ def __init__(self, *, service: str, operation: str, error_type: str) -> None:
35
+ self.service = service
36
+ self.operation = operation
37
+ self.error_type = error_type
38
+ super().__init__(f"Hosted Service {service!r} {operation} failed with {error_type}")
39
+
40
+ @property
41
+ def original_error(self) -> BaseException:
42
+ # 与 ApplicationStartError 的统一 primary shape 对齐,但绝不返回第三方异常。
43
+ return self
44
+
45
+
46
+ class HostedServiceInvocationRejectedError(FrameworkError):
47
+ def __init__(self, *, service: str, operation: str, error_type: str) -> None:
48
+ self.service = service
49
+ self.operation = operation
50
+ self.error_type = error_type
51
+ super().__init__(
52
+ f"Hosted Service {service!r} invocation {operation} rejected with {error_type}"
53
+ )
@@ -0,0 +1,17 @@
1
+ """Hosted Service 的 Host-owned 容量与 lifecycle deadline。"""
2
+
3
+ from datetime import timedelta
4
+
5
+ from pydantic import ConfigDict, Field
6
+
7
+ from ..options import BaseOptions
8
+
9
+ HOSTED_SERVICE_OPTIONS_SECTION = "hosted_service"
10
+
11
+
12
+ class HostedServiceOptions(BaseOptions):
13
+ model_config = ConfigDict(extra="forbid")
14
+
15
+ max_pending_invocations: int = Field(default=128, ge=1)
16
+ startup_timeout: timedelta = Field(default=timedelta(seconds=30), gt=timedelta(0))
17
+ shutdown_timeout: timedelta = Field(default=timedelta(seconds=30), gt=timedelta(0))
@@ -0,0 +1,267 @@
1
+ """Hosted Service resolution、bootstrap 与逆序 shutdown owner。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from collections.abc import Awaitable, Callable
7
+ from dataclasses import dataclass
8
+ from typing import cast
9
+
10
+ from dishka import AsyncContainer
11
+
12
+ from ..application_services import ApplicationServiceCatalog
13
+ from ..lifecycle import ApplicationState, ShutdownReason
14
+ from .bridge import HostedServiceContext, _HostedServiceBridge
15
+ from .catalog import HostedServiceCatalog, HostedServiceRegistration
16
+ from .contracts import HostedService, HostedServiceStatus
17
+ from .errors import HostedServiceDefinitionError, HostedServiceLifecycleError
18
+ from .options import HostedServiceOptions
19
+ from .state import ApplicationReadinessSnapshot, _HostedServiceStateOwner
20
+
21
+
22
+ @dataclass(slots=True)
23
+ class _HostedServiceEntry:
24
+ registration: HostedServiceRegistration
25
+ service: HostedService
26
+ context: HostedServiceContext
27
+ bridge: _HostedServiceBridge
28
+
29
+
30
+ class _HostedServiceCoordinator:
31
+ """单 Application 的 Service/bridge/task owner;不强制终止实现拥有的 OS thread。"""
32
+
33
+ __slots__ = (
34
+ "_application_service_catalog",
35
+ "_catalog",
36
+ "_container",
37
+ "_entries",
38
+ "_options",
39
+ "_registrations",
40
+ "_started",
41
+ "_state_getter",
42
+ "_state_owner",
43
+ )
44
+
45
+ def __init__(
46
+ self,
47
+ *,
48
+ catalog: HostedServiceCatalog,
49
+ options: HostedServiceOptions,
50
+ state_getter: Callable[[], ApplicationState],
51
+ ) -> None:
52
+ self._catalog = catalog
53
+ self._options = options
54
+ self._state_getter = state_getter
55
+ self._registrations = catalog.registrations
56
+ self._state_owner = _HostedServiceStateOwner(self._registrations)
57
+ self._container: AsyncContainer | None = None
58
+ self._application_service_catalog: ApplicationServiceCatalog | None = None
59
+ self._entries: list[_HostedServiceEntry] = []
60
+ self._started = False
61
+
62
+ def attach_container(
63
+ self,
64
+ container: AsyncContainer,
65
+ application_service_catalog: ApplicationServiceCatalog,
66
+ ) -> None:
67
+ if self._container is not None:
68
+ raise RuntimeError("Hosted Service coordinator container is already attached")
69
+ self._container = container
70
+ self._application_service_catalog = application_service_catalog
71
+
72
+ def readiness(self) -> ApplicationReadinessSnapshot:
73
+ return self._state_owner.snapshot(self._state_getter())
74
+
75
+ def select_child(self) -> None:
76
+ if self._started:
77
+ raise RuntimeError("Hosted Service process selection must precede start")
78
+ # 先按 build 冻结的声明选择;解析、bridge、readiness 与 stop 共用同一进程集合。
79
+ self._registrations = tuple(
80
+ item for item in self._catalog.registrations if item.run_in_background_processes
81
+ )
82
+ self._state_owner = _HostedServiceStateOwner(self._registrations)
83
+
84
+ async def start(self) -> None:
85
+ if self._started:
86
+ raise RuntimeError("Hosted Service coordinator is already started")
87
+ try:
88
+ self._catalog._assert_unchanged()
89
+ except HostedServiceDefinitionError as original_error:
90
+ raise HostedServiceLifecycleError(
91
+ service=original_error.service,
92
+ operation="metadata",
93
+ error_type=type(original_error).__name__,
94
+ ) from None
95
+ container = self._container
96
+ application_service_catalog = self._application_service_catalog
97
+ if container is None or application_service_catalog is None:
98
+ raise RuntimeError("Hosted Service coordinator container is unavailable")
99
+ self._started = True
100
+ for registration in self._registrations:
101
+ self._state_owner.set_status(registration.name, HostedServiceStatus.STARTING)
102
+ try:
103
+ resolved = await container.get(registration.service_type)
104
+ except BaseException as original_error:
105
+ self._state_owner.set_status(registration.name, HostedServiceStatus.FAILED)
106
+ raise HostedServiceLifecycleError(
107
+ service=registration.name,
108
+ operation="resolve",
109
+ error_type=type(original_error).__name__,
110
+ ) from None
111
+ if not isinstance(resolved, registration.service_type):
112
+ self._state_owner.set_status(registration.name, HostedServiceStatus.FAILED)
113
+ raise HostedServiceLifecycleError(
114
+ service=registration.name,
115
+ operation="resolve",
116
+ error_type="InvalidRuntimeInstance",
117
+ )
118
+ bridge = _HostedServiceBridge(
119
+ name=registration.name,
120
+ loop=asyncio.get_running_loop(),
121
+ container=container,
122
+ catalog=application_service_catalog,
123
+ limit=self._options.max_pending_invocations,
124
+ )
125
+ context = HostedServiceContext(registration.name, bridge)
126
+ entry = _HostedServiceEntry(registration, resolved, context, bridge)
127
+ self._entries.append(entry)
128
+ bridge.open()
129
+ await self._start_entry(entry)
130
+
131
+ def close_admission(self) -> None:
132
+ for entry in self._entries:
133
+ entry.bridge.close()
134
+
135
+ async def stop(
136
+ self,
137
+ reason: ShutdownReason,
138
+ ) -> tuple[HostedServiceLifecycleError, ...]:
139
+ if not self._started and not self._entries:
140
+ return ()
141
+ self.close_admission()
142
+ failures: list[HostedServiceLifecycleError] = []
143
+ for entry in reversed(self._entries):
144
+ name = entry.registration.name
145
+ self._state_owner.set_status(name, HostedServiceStatus.STOPPING)
146
+ failure = await self._stop_entry(entry, reason)
147
+ if failure is not None:
148
+ failures.append(failure)
149
+
150
+ if self._entries:
151
+ await asyncio.gather(
152
+ *(
153
+ entry.bridge.drain(self._options.shutdown_timeout.total_seconds())
154
+ for entry in self._entries
155
+ )
156
+ )
157
+ failed_names = {failure.service for failure in failures}
158
+ for entry in self._entries:
159
+ name = entry.registration.name
160
+ self._state_owner.set_status(
161
+ name,
162
+ HostedServiceStatus.FAILED if name in failed_names else HostedServiceStatus.STOPPED,
163
+ )
164
+ self._started = False
165
+ return tuple(failures)
166
+
167
+ async def _start_entry(self, entry: _HostedServiceEntry) -> None:
168
+ name = entry.registration.name
169
+ callback = cast(
170
+ Callable[[HostedServiceContext], Awaitable[object]],
171
+ entry.service.start,
172
+ )
173
+
174
+ async def invoke_start() -> object:
175
+ return await callback(entry.context)
176
+
177
+ start_task = asyncio.create_task(
178
+ invoke_start(),
179
+ name=f"python-platform-hosted-service-start:{name}",
180
+ )
181
+ try:
182
+ done, _ = await asyncio.wait(
183
+ (start_task,),
184
+ timeout=self._options.startup_timeout.total_seconds(),
185
+ return_when=asyncio.FIRST_COMPLETED,
186
+ )
187
+ except BaseException as original_error:
188
+ # coordinator 创建的 start task 必须由 coordinator 收口;否则取消外层
189
+ # Application.start 会留下仍在运行、却已失去 owner 的 SDK bootstrap。
190
+ start_task.cancel()
191
+ await asyncio.gather(start_task, return_exceptions=True)
192
+ self._state_owner.set_status(name, HostedServiceStatus.FAILED)
193
+ raise HostedServiceLifecycleError(
194
+ service=name,
195
+ operation="start",
196
+ error_type=type(original_error).__name__,
197
+ ) from None
198
+ if start_task not in done:
199
+ start_task.cancel()
200
+ await asyncio.gather(start_task, return_exceptions=True)
201
+ self._state_owner.set_status(name, HostedServiceStatus.FAILED)
202
+ raise HostedServiceLifecycleError(
203
+ service=name, operation="start", error_type="TimeoutError"
204
+ )
205
+ try:
206
+ result = start_task.result()
207
+ except BaseException as original_error:
208
+ self._state_owner.set_status(name, HostedServiceStatus.FAILED)
209
+ raise HostedServiceLifecycleError(
210
+ service=name,
211
+ operation="start",
212
+ error_type=type(original_error).__name__,
213
+ ) from None
214
+ if result is not None:
215
+ self._state_owner.set_status(name, HostedServiceStatus.FAILED)
216
+ raise HostedServiceLifecycleError(
217
+ service=name,
218
+ operation="start",
219
+ error_type="InvalidReturnValue",
220
+ )
221
+ self._state_owner.set_status(name, HostedServiceStatus.RUNNING)
222
+
223
+ async def _stop_entry(
224
+ self,
225
+ entry: _HostedServiceEntry,
226
+ reason: ShutdownReason,
227
+ ) -> HostedServiceLifecycleError | None:
228
+ name = entry.registration.name
229
+ callback = cast(
230
+ Callable[[ShutdownReason], Awaitable[object]],
231
+ entry.service.stop,
232
+ )
233
+
234
+ async def invoke_stop() -> object:
235
+ return await callback(reason)
236
+
237
+ stop_task = asyncio.create_task(
238
+ invoke_stop(), name=f"python-platform-hosted-service-stop:{name}"
239
+ )
240
+ done, _ = await asyncio.wait(
241
+ (stop_task,), timeout=self._options.shutdown_timeout.total_seconds()
242
+ )
243
+ timed_out = not done
244
+ if timed_out:
245
+ # 取消只是一项请求。实现可能在 finally 中异步释放 SDK 资源;完成前不能释放容器。
246
+ stop_task.cancel()
247
+ try:
248
+ result = await stop_task
249
+ except BaseException as original_error:
250
+ return HostedServiceLifecycleError(
251
+ service=name,
252
+ operation="stop",
253
+ error_type="TimeoutError"
254
+ if timed_out and isinstance(original_error, asyncio.CancelledError)
255
+ else type(original_error).__name__,
256
+ )
257
+ if timed_out:
258
+ return HostedServiceLifecycleError(
259
+ service=name, operation="stop", error_type="TimeoutError"
260
+ )
261
+ if result is not None:
262
+ return HostedServiceLifecycleError(
263
+ service=name,
264
+ operation="stop",
265
+ error_type="InvalidReturnValue",
266
+ )
267
+ return None
@@ -0,0 +1,40 @@
1
+ """Application 所属 loop 修改生命周期状态;公开快照不持有可变状态。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+ from ..lifecycle import ApplicationState
8
+ from .catalog import HostedServiceRegistration
9
+ from .contracts import HostedServiceStatus
10
+
11
+
12
+ @dataclass(frozen=True, slots=True)
13
+ class HostedServiceSnapshot:
14
+ name: str
15
+ status: HostedServiceStatus
16
+
17
+
18
+ @dataclass(frozen=True, slots=True)
19
+ class ApplicationReadinessSnapshot:
20
+ ready: bool
21
+ services: tuple[HostedServiceSnapshot, ...]
22
+
23
+
24
+ class _HostedServiceStateOwner:
25
+ def __init__(self, registrations: tuple[HostedServiceRegistration, ...]) -> None:
26
+ self._states = {item.name: HostedServiceStatus.STOPPED for item in registrations}
27
+
28
+ def set_status(self, name: str, status: HostedServiceStatus) -> None:
29
+ self._states[name] = status
30
+
31
+ def snapshot(self, application_state: ApplicationState) -> ApplicationReadinessSnapshot:
32
+ return ApplicationReadinessSnapshot(
33
+ # Module 已完成 runtime hook 时 Application 可能已为 RUNNING;全部服务
34
+ # start 返回之前仍不能报告启动完成,这不是服务自报的连接健康度。
35
+ ready=application_state is ApplicationState.RUNNING
36
+ and all(state is HostedServiceStatus.RUNNING for state in self._states.values()),
37
+ services=tuple(
38
+ HostedServiceSnapshot(name, state) for name, state in self._states.items()
39
+ ),
40
+ )
@@ -0,0 +1,4 @@
1
+ """正式 Host 的进程身份与运行控制;测试 Application 不占用 Host 位置。"""
2
+
3
+ # 控制变量不进入业务配置前缀,避免与严格 Options 字段发生冲突。
4
+ ENVIRONMENT_VARIABLE = "PYTHON_PLATFORM_ENVIRONMENT"
@@ -0,0 +1,40 @@
1
+ """同一实际项目目录和环境的进程位置;占用事实由操作系统锁决定。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ from filelock import BaseFileLock, UnixFileLock, WindowsFileLock
10
+
11
+
12
+ def _instance_lock(project: Path, environment: str, position: str = "host") -> BaseFileLock:
13
+ # 锁放在解析后的项目下,避免相对路径、junction/symlink 或不同用户的临时目录分裂身份。
14
+ # environment/position 只参与摘要,不作为路径片段,且互相独立编码避免拼接碰撞。
15
+ identity = hashlib.sha256(repr((environment, position)).encode()).hexdigest()
16
+ path = project.resolve(strict=True) / ".python-platform" / "locks" / f"{identity}.lock"
17
+ return _native_lock(path)
18
+
19
+
20
+ def _native_lock(path: Path) -> BaseFileLock:
21
+ path = _native_path(path)
22
+ lock_type = WindowsFileLock if sys.platform == "win32" else UnixFileLock
23
+ return lock_type(
24
+ path,
25
+ timeout=0,
26
+ fallback_to_soft=False,
27
+ preserve_lock_file=True,
28
+ )
29
+
30
+
31
+ def _native_path(path: Path) -> Path:
32
+ # Windows Unicode 长路径语法不依赖进程 manifest/注册表;不 resolve 最后的锁文件,
33
+ # 保留 filelock 自身对 symlink/reparse point 的拒绝,实际项目目录已由调用方解析。
34
+ if sys.platform == "win32":
35
+ value = str(path)
36
+ if value.startswith("\\\\?\\"):
37
+ return path
38
+ return Path("\\\\?\\UNC\\" + value[2:] if value.startswith("\\\\") else "\\\\?\\" + value)
39
+ else:
40
+ return path
@@ -0,0 +1,73 @@
1
+ from .contracts import (
2
+ IDENTITY_ROLES_MANAGE,
3
+ IDENTITY_USERS_MANAGE,
4
+ AuthenticationApplicationService,
5
+ CreateRoleCommand,
6
+ CreateUserCommand,
7
+ CurrentUserView,
8
+ IdentityAccessStore,
9
+ IdentityLoginStore,
10
+ IdentityManagementApplicationService,
11
+ IdentityOptions,
12
+ IdentityRepository,
13
+ IdentityRole,
14
+ IdentityUser,
15
+ LoginCommand,
16
+ RefreshRotationResult,
17
+ RefreshRotationStatus,
18
+ RefreshSession,
19
+ RefreshSessionStore,
20
+ RefreshTokenCommand,
21
+ RevokeTokenCommand,
22
+ RoleView,
23
+ SetRolePermissionsCommand,
24
+ SetUserRolesCommand,
25
+ TokenPair,
26
+ UserView,
27
+ )
28
+ from .errors import (
29
+ IdentityConflictError,
30
+ IdentityDefinitionError,
31
+ IdentityResourceNotFoundError,
32
+ InvalidCredentialsError,
33
+ )
34
+ from .module import IdentityModule
35
+ from .passwords import PasswordHasher
36
+ from .services import IdentityAuthenticator
37
+ from .tokens import JwtTokenService
38
+
39
+ __all__ = (
40
+ "IDENTITY_ROLES_MANAGE",
41
+ "IDENTITY_USERS_MANAGE",
42
+ "AuthenticationApplicationService",
43
+ "CreateRoleCommand",
44
+ "CreateUserCommand",
45
+ "CurrentUserView",
46
+ "IdentityAccessStore",
47
+ "IdentityAuthenticator",
48
+ "IdentityConflictError",
49
+ "IdentityDefinitionError",
50
+ "IdentityLoginStore",
51
+ "IdentityManagementApplicationService",
52
+ "IdentityModule",
53
+ "IdentityOptions",
54
+ "IdentityRepository",
55
+ "IdentityResourceNotFoundError",
56
+ "IdentityRole",
57
+ "IdentityUser",
58
+ "InvalidCredentialsError",
59
+ "JwtTokenService",
60
+ "LoginCommand",
61
+ "PasswordHasher",
62
+ "RefreshRotationResult",
63
+ "RefreshRotationStatus",
64
+ "RefreshSession",
65
+ "RefreshSessionStore",
66
+ "RefreshTokenCommand",
67
+ "RevokeTokenCommand",
68
+ "RoleView",
69
+ "SetRolePermissionsCommand",
70
+ "SetUserRolesCommand",
71
+ "TokenPair",
72
+ "UserView",
73
+ )