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,423 @@
1
+ """受管理服务代理与业务管线编排;不拥有 Application admission 或 Session。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import logging
7
+ import time
8
+ from collections.abc import Awaitable, Callable
9
+ from contextvars import ContextVar
10
+ from dataclasses import dataclass
11
+ from datetime import UTC, datetime
12
+ from typing import TYPE_CHECKING, cast
13
+ from uuid import uuid4
14
+
15
+ from dishka import AsyncContainer
16
+ from dishka.exceptions import NoFactoryError
17
+
18
+ from ..auditing import ApplicationServiceAuditRecord, AuditSink
19
+ from ..auditing.control import AuditingControl, AuditingOptions
20
+ from ..authorization import PermissionChecker
21
+ from ..authorization.contracts import _AuthorizationRequirement
22
+ from ..authorization.errors import (
23
+ PermissionCheckerUnavailableError,
24
+ PermissionDeniedError,
25
+ UnauthenticatedError,
26
+ )
27
+ from ..observability.context import invocation_span
28
+ from ..options import Options
29
+ from ..settings.provider import SettingProvider
30
+ from ..unit_of_work import UnitOfWorkManager
31
+ from .catalog import (
32
+ _APPLICATION_SERVICE_COMPONENT,
33
+ ApplicationServiceCatalog,
34
+ ApplicationServiceRegistration,
35
+ _CompiledApplicationService,
36
+ _CompiledMethod,
37
+ )
38
+ from .contracts import ApplicationService
39
+ from .errors import (
40
+ ApplicationServiceValidationError,
41
+ ApplicationServiceValidationIssue,
42
+ CrossTaskInvocationError,
43
+ NestedConnectionError,
44
+ )
45
+ from .execution import _InterceptorPipeline
46
+ from .interceptors import InterceptionStage
47
+ from .invocation import _current_task, _InvocationRuntime
48
+ from .validation import _validate_arguments
49
+
50
+ _LOGGER = logging.getLogger(__name__)
51
+
52
+ if TYPE_CHECKING:
53
+ pass
54
+
55
+
56
+ @dataclass(slots=True)
57
+ class _HttpInvocationState:
58
+ service: _CompiledApplicationService
59
+ method: _CompiledMethod
60
+ owner_task: asyncio.Task[object]
61
+ pipeline: _InterceptorPipeline
62
+ consumed: bool = False
63
+ is_short_circuited: Callable[[], bool] = lambda: False
64
+ defer_audit: Callable[[Callable[[BaseException | None], Awaitable[None]]], None] | None = None
65
+
66
+
67
+ class _ApplicationServiceProxy:
68
+ """REQUEST-local Protocol object;只把 cataloged method 转交给统一 dispatcher。"""
69
+
70
+ __slots__ = ("_dispatcher", "_service")
71
+
72
+ def __init__(
73
+ self,
74
+ dispatcher: _ApplicationServiceDispatcher,
75
+ service: _CompiledApplicationService,
76
+ ) -> None:
77
+ self._dispatcher = dispatcher
78
+ self._service = service
79
+
80
+ def __getattr__(self, name: str) -> object:
81
+ method = self._service.methods.get(name)
82
+ if method is None:
83
+ raise AttributeError(name)
84
+
85
+ async def invoke(*args: object, **kwargs: object) -> object:
86
+ return await self._dispatcher.invoke(self._service, method, args, kwargs)
87
+
88
+ return invoke
89
+
90
+
91
+ class _ApplicationServiceDispatcher:
92
+ """所有 caller 共用的 validation、ownership、UoW 与 event 顺序 owner。"""
93
+
94
+ __slots__ = ("_catalog", "_container", "_http_boundary", "_runtime")
95
+
96
+ def __init__(
97
+ self,
98
+ container: AsyncContainer,
99
+ runtime: _InvocationRuntime,
100
+ catalog: ApplicationServiceCatalog,
101
+ ) -> None:
102
+ self._container = container
103
+ self._runtime = runtime
104
+ self._catalog = catalog
105
+ self._http_boundary: ContextVar[_HttpInvocationState | None] = ContextVar(
106
+ f"python_platform_http_invocation_{runtime.application_id}_{id(self)}",
107
+ default=None,
108
+ )
109
+
110
+ async def invoke(
111
+ self,
112
+ service: _CompiledApplicationService,
113
+ method: _CompiledMethod,
114
+ args: tuple[object, ...],
115
+ kwargs: dict[str, object],
116
+ ) -> object:
117
+ pipeline = _InterceptorPipeline(method.descriptor, method.interceptor_steps, args, kwargs)
118
+ async with self._runtime.outer_lease():
119
+ return await self._invoke_entry(
120
+ service,
121
+ method,
122
+ pipeline,
123
+ lambda: self._invoke_after_authorization(service, method, pipeline),
124
+ )
125
+
126
+ async def _invoke_entry(
127
+ self,
128
+ service: _CompiledApplicationService,
129
+ method: _CompiledMethod,
130
+ pipeline: _InterceptorPipeline,
131
+ callback: Callable[[], Awaitable[object]],
132
+ ) -> object:
133
+ async def authorize() -> object:
134
+ if not service.registration.is_integration_service:
135
+ await self._authorize(method.authorization)
136
+ return await callback()
137
+
138
+ async def audit() -> object:
139
+ occurred_at = datetime.now(UTC)
140
+ started_ns = time.perf_counter_ns()
141
+ failure: BaseException | None = None
142
+ try:
143
+ return await pipeline.run(
144
+ InterceptionStage.AUTHORIZATION, self._container, authorize
145
+ )
146
+ except BaseException as error:
147
+ failure = error
148
+ raise
149
+ finally:
150
+
151
+ async def write(final_failure: BaseException | None) -> None:
152
+ await self._write_audit(
153
+ service.registration,
154
+ method,
155
+ occurred_at=occurred_at,
156
+ duration_ns=time.perf_counter_ns() - started_ns,
157
+ failure=final_failure,
158
+ )
159
+
160
+ boundary = self._http_boundary.get()
161
+ if (
162
+ boundary is not None
163
+ and boundary.pipeline is pipeline
164
+ and boundary.defer_audit is not None
165
+ ):
166
+ boundary.defer_audit(write)
167
+ else:
168
+ await write(failure)
169
+
170
+ async def trace() -> object:
171
+ with invocation_span(self._runtime.tracer, method.contract_method.__qualname__):
172
+ return await pipeline.run(InterceptionStage.AUDITING, self._container, audit)
173
+
174
+ return await pipeline.run(InterceptionStage.TRACING, self._container, trace)
175
+
176
+ async def invoke_http_request(
177
+ self,
178
+ service: _CompiledApplicationService,
179
+ method: _CompiledMethod,
180
+ callback: Callable[[], Awaitable[object]],
181
+ *,
182
+ is_short_circuited: Callable[[], bool] = lambda: False,
183
+ defer_audit: Callable[[Callable[[BaseException | None], Awaitable[None]]], None]
184
+ | None = None,
185
+ ) -> object:
186
+ """原生绑定在授权内执行;同一 HTTP 调用复用编译链且只计一次授权与审计。"""
187
+ async with self._runtime.outer_lease():
188
+ if self._http_boundary.get() is not None:
189
+ raise RuntimeError("HTTP Application Service boundary is already active")
190
+ pipeline = _InterceptorPipeline(method.descriptor, method.interceptor_steps, (), {})
191
+ state = _HttpInvocationState(service, method, _current_task(), pipeline)
192
+ state.is_short_circuited = is_short_circuited
193
+ state.defer_audit = defer_audit
194
+ token = self._http_boundary.set(state)
195
+
196
+ async def invoke_endpoint() -> object:
197
+ result = await callback()
198
+ # 外层 Interceptor 可合法短路;一旦进入原生绑定,必须消费原 endpoint 许可。
199
+ if not state.consumed and not state.is_short_circuited():
200
+ raise RuntimeError("HTTP Application Service endpoint was not invoked")
201
+ return result
202
+
203
+ try:
204
+ return await self._invoke_entry(service, method, pipeline, invoke_endpoint)
205
+ finally:
206
+ self._http_boundary.reset(token)
207
+
208
+ async def invoke_http(
209
+ self,
210
+ service: _CompiledApplicationService,
211
+ method: _CompiledMethod,
212
+ kwargs: dict[str, object],
213
+ ) -> object:
214
+ boundary = self._http_boundary.get()
215
+ if boundary is None:
216
+ raise RuntimeError("HTTP Application Service boundary is unavailable")
217
+ if boundary.owner_task is not _current_task():
218
+ raise CrossTaskInvocationError
219
+ if boundary.service is not service or boundary.method is not method:
220
+ raise RuntimeError("HTTP Application Service boundary identity changed")
221
+ if boundary.consumed:
222
+ raise RuntimeError("HTTP Application Service boundary was already consumed")
223
+ boundary.consumed = True
224
+ boundary.pipeline.arguments.kwargs = dict(kwargs)
225
+ return await self._invoke_after_authorization(service, method, boundary.pipeline)
226
+
227
+ async def _invoke_after_authorization(
228
+ self,
229
+ service: _CompiledApplicationService,
230
+ method: _CompiledMethod,
231
+ pipeline: _InterceptorPipeline,
232
+ ) -> object:
233
+ async def validate() -> object:
234
+ await self._validate_pipeline(service.registration, method, pipeline)
235
+ return await pipeline.run(
236
+ InterceptionStage.UNIT_OF_WORK,
237
+ self._container,
238
+ lambda: self._invoke_work(service.registration, method, pipeline),
239
+ )
240
+
241
+ return await pipeline.run(InterceptionStage.VALIDATION, self._container, validate)
242
+
243
+ @staticmethod
244
+ async def _validate_pipeline(
245
+ registration: ApplicationServiceRegistration,
246
+ method: _CompiledMethod,
247
+ pipeline: _InterceptorPipeline,
248
+ ) -> None:
249
+ arguments = pipeline.arguments
250
+ if not arguments.needs_validation:
251
+ return
252
+ if method.descriptor.validation_enabled:
253
+ arguments.args, arguments.kwargs = await _validate_arguments(
254
+ method, registration, arguments.args, arguments.kwargs
255
+ )
256
+ else:
257
+ # 可选类型验证关闭后,必需参数与参数名仍属于公开调用契约。
258
+ try:
259
+ method.signature.bind(None, *arguments.args, **arguments.kwargs)
260
+ except TypeError:
261
+ raise ApplicationServiceValidationError(
262
+ service=registration.contract,
263
+ method_name=method.name,
264
+ issues=(
265
+ ApplicationServiceValidationIssue(
266
+ location=(), error_type="arguments", message="Invalid method arguments"
267
+ ),
268
+ ),
269
+ ) from None
270
+ arguments.mark_validated()
271
+
272
+ async def _invoke_work(
273
+ self,
274
+ registration: ApplicationServiceRegistration,
275
+ method: _CompiledMethod,
276
+ pipeline: _InterceptorPipeline,
277
+ ) -> object:
278
+ ambient = self._runtime.current()
279
+ if ambient is not None:
280
+ if _current_task() is not ambient.owner_task:
281
+ raise CrossTaskInvocationError
282
+ if registration.connection_name != ambient.context.connection_name:
283
+ raise NestedConnectionError(
284
+ outer=ambient.context.connection_name, requested=registration.connection_name
285
+ )
286
+ manager = await self._container.get(UnitOfWorkManager)
287
+
288
+ async def nested() -> object:
289
+ return await self._invoke_target(
290
+ ambient.action_container, registration, method, pipeline
291
+ )
292
+
293
+ if method.descriptor.unit_of_work.disabled:
294
+ manager._check_current(registration.connection_name)
295
+ return await nested()
296
+ options = manager._resolve_options(
297
+ registration.connection_name, method.descriptor.unit_of_work, method.name
298
+ )
299
+ async with manager._begin(options) as work:
300
+ result = await nested()
301
+ await work.complete()
302
+ return result
303
+
304
+ async def invoke(action: AsyncContainer) -> object:
305
+ return await self._invoke_target(action, registration, method, pipeline)
306
+
307
+ return await self._runtime.run_action(
308
+ self._container,
309
+ connection_name=registration.connection_name,
310
+ callback=invoke,
311
+ declaration=method.descriptor.unit_of_work,
312
+ method_name=method.name,
313
+ )
314
+
315
+ async def _invoke_target(
316
+ self,
317
+ container: AsyncContainer,
318
+ registration: ApplicationServiceRegistration,
319
+ method: _CompiledMethod,
320
+ pipeline: _InterceptorPipeline,
321
+ ) -> object:
322
+ async def invoke() -> object:
323
+ # 位于验证之后的扩展可以改参数;目标调用不能使用修改前的验证结果。
324
+ await self._validate_pipeline(registration, method, pipeline)
325
+ implementation = await container.get(
326
+ registration.contract, component=_APPLICATION_SERVICE_COMPONENT
327
+ )
328
+ # 实例已由当前 ACTION 容器取得,绑定只属于本 Application 的读取服务。
329
+ cast(ApplicationService, implementation)._setting_provider = await container.get(
330
+ SettingProvider
331
+ )
332
+ return await method.implementation_method(
333
+ implementation, *pipeline.arguments.args, **pipeline.arguments.kwargs
334
+ )
335
+
336
+ return await pipeline.run(InterceptionStage.METHOD, container, invoke)
337
+
338
+ async def _write_audit(
339
+ self,
340
+ registration: ApplicationServiceRegistration,
341
+ method: _CompiledMethod,
342
+ *,
343
+ occurred_at: datetime,
344
+ duration_ns: int,
345
+ failure: BaseException | None,
346
+ ) -> None:
347
+ options = (await self._container.get(Options[AuditingOptions])).value
348
+ control = await self._container.get(AuditingControl)
349
+ if (
350
+ not method.descriptor.auditing_enabled
351
+ or not options.is_enabled
352
+ or not control.is_enabled
353
+ ):
354
+ return
355
+ try:
356
+ sink = await self._container.get(AuditSink)
357
+ except NoFactoryError:
358
+ return
359
+ except Exception as audit_error:
360
+ _LOGGER.error(
361
+ "application service audit write failed",
362
+ exc_info=audit_error,
363
+ extra={
364
+ "audit_exception_type": type(audit_error).__name__,
365
+ "audit_contract": registration.contract.__qualname__,
366
+ "audit_method": method.name,
367
+ },
368
+ )
369
+ return
370
+ try:
371
+ current_user = self._runtime.current_user()
372
+ correlation_id = self._runtime.correlation_id() or str(uuid4())
373
+ await sink.write_application_service(
374
+ ApplicationServiceAuditRecord(
375
+ occurred_at=occurred_at,
376
+ user_id=current_user.user_id,
377
+ session_id=current_user.session_id,
378
+ correlation_id=correlation_id,
379
+ contract=(
380
+ f"{registration.contract.__module__}.{registration.contract.__qualname__}"
381
+ ),
382
+ method=method.name,
383
+ succeeded=failure is None,
384
+ duration_ns=duration_ns,
385
+ exception_type=None if failure is None else type(failure).__name__,
386
+ )
387
+ )
388
+ except Exception as audit_error:
389
+ # sink resolution/context/write 都属于独立观测边界;失败不能覆盖业务结果。
390
+ _LOGGER.error(
391
+ "application service audit write failed",
392
+ exc_info=audit_error,
393
+ extra={
394
+ "audit_exception_type": type(audit_error).__name__,
395
+ "audit_contract": registration.contract.__qualname__,
396
+ "audit_method": method.name,
397
+ },
398
+ )
399
+
400
+ async def _authorize(self, requirement: _AuthorizationRequirement) -> None:
401
+ if requirement.allows_anonymous or not requirement.requires_authenticated_user:
402
+ return
403
+ current_user = self._runtime.current_user()
404
+ if not current_user.is_authenticated:
405
+ raise UnauthenticatedError
406
+ if not requirement.permissions:
407
+ return
408
+ try:
409
+ checker = await self._container.get(PermissionChecker)
410
+ except NoFactoryError:
411
+ raise PermissionCheckerUnavailableError from None
412
+ for permission in requirement.permissions:
413
+ if not await checker.is_granted(current_user, permission):
414
+ raise PermissionDeniedError(permission=str(permission))
415
+
416
+
417
+ def _proxy_factory(
418
+ service: _CompiledApplicationService,
419
+ ) -> Callable[[_ApplicationServiceDispatcher], object]:
420
+ def create(dispatcher: _ApplicationServiceDispatcher) -> object:
421
+ return _ApplicationServiceProxy(dispatcher, service)
422
+
423
+ return create
@@ -0,0 +1,89 @@
1
+ """Application Service definition、validation 与 invocation failure contracts。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+ from ..errors.base import FrameworkError
8
+
9
+
10
+ class ApplicationServiceDefinitionError(FrameworkError):
11
+ def __init__(self, *, symbol: str, reason: str) -> None:
12
+ self.symbol = symbol
13
+ self.reason = reason
14
+ super().__init__(f"Invalid Application Service {symbol}: {reason}")
15
+
16
+
17
+ class ApplicationServiceMethodError(FrameworkError):
18
+ def __init__(self, *, service: type[object], method: object, reason: str) -> None:
19
+ self.service = service
20
+ self.method = method
21
+ self.reason = reason
22
+ method_name = getattr(method, "__qualname__", repr(method))
23
+ super().__init__(
24
+ f"Application Service method {service.__module__}.{service.__qualname__}/{method_name} "
25
+ f"is unavailable: {reason}"
26
+ )
27
+
28
+
29
+ @dataclass(frozen=True, slots=True)
30
+ class ApplicationServiceValidationIssue:
31
+ location: tuple[str | int, ...]
32
+ error_type: str
33
+ message: str
34
+
35
+
36
+ class ApplicationServiceValidationError(FrameworkError):
37
+ def __init__(
38
+ self,
39
+ *,
40
+ service: type[object],
41
+ method_name: str,
42
+ issues: tuple[ApplicationServiceValidationIssue, ...],
43
+ ) -> None:
44
+ self.service = service
45
+ self.method_name = method_name
46
+ self.issues = issues
47
+ super().__init__(
48
+ f"Application Service validation failed for "
49
+ f"{service.__module__}.{service.__qualname__}.{method_name} ({len(issues)} issue(s))"
50
+ )
51
+
52
+
53
+ class ApplicationInvocationRejectedError(FrameworkError):
54
+ def __init__(self, *, reason: str) -> None:
55
+ self.reason = reason
56
+ super().__init__(f"Application invocation rejected: {reason}")
57
+
58
+
59
+ class CrossTaskInvocationError(ApplicationInvocationRejectedError):
60
+ def __init__(self) -> None:
61
+ super().__init__(reason="ambient ACTION belongs to a different asyncio task")
62
+
63
+
64
+ class NestedConnectionError(ApplicationInvocationRejectedError):
65
+ def __init__(self, *, outer: str, requested: str) -> None:
66
+ self.outer = outer
67
+ self.requested = requested
68
+ super().__init__(
69
+ reason=f"nested invocation cannot change connection from {outer!r} to {requested!r}"
70
+ )
71
+
72
+
73
+ class ApplicationInvocationCleanupError(FrameworkError):
74
+ def __init__(
75
+ self,
76
+ *,
77
+ primary: BaseException | None,
78
+ cleanup_failures: tuple[BaseException, ...],
79
+ committed: bool,
80
+ ) -> None:
81
+ self.primary = primary
82
+ self.cleanup_failures = cleanup_failures
83
+ self.committed = committed
84
+ primary_name = type(primary).__name__ if primary is not None else "none"
85
+ super().__init__(
86
+ "Application invocation cleanup failed "
87
+ f"(primary={primary_name}, cleanup_failures={len(cleanup_failures)}, "
88
+ f"committed={committed})"
89
+ )