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,212 @@
1
+ """延迟编译 HTTP 声明;当前 Application 的最终服务图决定纯类型注入。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ from collections.abc import Callable, Mapping, Sequence
7
+ from copy import copy
8
+ from dataclasses import dataclass
9
+ from functools import wraps
10
+ from types import MappingProxyType
11
+ from typing import Annotated, Any, get_args, get_origin, get_type_hints
12
+
13
+ from dishka import AsyncContainer, FromComponent, Scope
14
+ from dishka.integrations.base import default_parse_dependency
15
+ from fastapi import APIRouter
16
+ from fastapi.dependencies.models import Dependant
17
+ from fastapi.dependencies.utils import add_non_field_param_to_dependency
18
+ from fastapi.exceptions import FastAPIError
19
+ from fastapi.params import Depends
20
+ from pydantic.fields import FieldInfo
21
+ from starlette.routing import compile_path
22
+
23
+ from ..authorization import PermissionName
24
+ from ..authorization.contracts import _authorization_requirement, _AuthorizationRequirement
25
+ from ..invocation.callables import _dependency_key
26
+ from ..unit_of_work.options import _declaration
27
+ from .errors import FastApiAdapterError
28
+ from .filters import ServiceFilter, _filters_for, _ordered_filters, _validate_filters
29
+ from .manual_action import _action_route_type, _ActionDefinition, _ActionRoute, _wrap_action
30
+
31
+
32
+ @dataclass(frozen=True, slots=True)
33
+ class _HttpDeclaration:
34
+ path: str
35
+ endpoint: Callable[..., Any]
36
+ options: Mapping[str, Any]
37
+
38
+
39
+ @dataclass(frozen=True, slots=True)
40
+ class _RouterInclusion:
41
+ router: APIRouter
42
+ options: Mapping[str, Any]
43
+
44
+
45
+ class HttpRouter(APIRouter):
46
+ """沿用 FastAPI 路由声明 API;安装前只保存声明,不提前解析服务参数。"""
47
+
48
+ def __init__(
49
+ self,
50
+ *args: Any,
51
+ permissions: Sequence[PermissionName] | None = None,
52
+ filters: Sequence[ServiceFilter] = (),
53
+ **kwargs: Any,
54
+ ) -> None:
55
+ self._permissions = None if permissions is None else tuple(permissions)
56
+ self._filters = tuple(filters)
57
+ kwargs.setdefault("route_class", _ActionRoute)
58
+ super().__init__(*args, **kwargs)
59
+ self._compiled = False
60
+ self._declarations: list[_HttpDeclaration | _RouterInclusion] = []
61
+
62
+ def add_api_route(self, path: str, endpoint: Callable[..., Any], **kwargs: Any) -> None:
63
+ if self._compiled:
64
+ return super().add_api_route(path, endpoint, **kwargs)
65
+ self._declarations.append(_HttpDeclaration(path, endpoint, MappingProxyType(dict(kwargs))))
66
+
67
+ def include_router(self, router: APIRouter, **kwargs: Any) -> None:
68
+ if self._compiled:
69
+ return super().include_router(router, **kwargs)
70
+ self._declarations.append(_RouterInclusion(router, MappingProxyType(dict(kwargs))))
71
+
72
+ def _compile(
73
+ self,
74
+ container: AsyncContainer,
75
+ prefix: str = "",
76
+ inherited_permissions: tuple[PermissionName, ...] | None = None,
77
+ inherited_filters: tuple[ServiceFilter, ...] = (),
78
+ ) -> APIRouter:
79
+ # 复制原生 router 配置,避免重新维护 FastAPI 默认值/配置字段第二清单。
80
+ # 每个 Application 只修改自己的 routes 和 endpoint wrapper。
81
+ permissions = (
82
+ None
83
+ if self._permissions is None and inherited_permissions is None
84
+ else (*(inherited_permissions or ()), *(self._permissions or ()))
85
+ )
86
+ filters = (*inherited_filters, *self._filters)
87
+ compiled = copy(self)
88
+ compiled._compiled = True
89
+ compiled.route_class = _action_route_type(self.route_class)
90
+ compiled.routes = list(self.routes)
91
+ compiled._declarations = []
92
+ for declaration in tuple(self._declarations):
93
+ if isinstance(declaration, _RouterInclusion):
94
+ child = declaration.router
95
+ if isinstance(child, HttpRouter):
96
+ child = child._compile(
97
+ container,
98
+ prefix + self.prefix + declaration.options.get("prefix", ""),
99
+ permissions,
100
+ filters,
101
+ )
102
+ elif permissions is not None or filters:
103
+ raise FastApiAdapterError(
104
+ f"HttpRouter at {prefix + self.prefix or '/'} cannot apply inherited "
105
+ f"permissions/filters to native {type(child).__qualname__} at "
106
+ f"{child.prefix or '/'}; use a nested HttpRouter"
107
+ )
108
+ APIRouter.include_router(compiled, child, **declaration.options)
109
+ else:
110
+ path = prefix + self.prefix + declaration.path
111
+ endpoint = _compile_endpoint(declaration.endpoint, path, container)
112
+ authorization = _authorization_requirement(object, declaration.endpoint)
113
+ if permissions is not None and not authorization.allows_anonymous:
114
+ authorization = _AuthorizationRequirement(
115
+ True,
116
+ tuple(dict.fromkeys((*permissions, *authorization.permissions))),
117
+ False,
118
+ )
119
+ definition = _ActionDefinition(
120
+ declaration.endpoint,
121
+ authorization,
122
+ _declaration(type(self), declaration.endpoint),
123
+ _ordered_filters(filters, _filters_for(declaration.endpoint)),
124
+ )
125
+ _validate_filters(definition.filters, container)
126
+ endpoint = _wrap_action(endpoint, definition)
127
+ options = dict(declaration.options)
128
+ if options.get("route_class_override") is not None:
129
+ options["route_class_override"] = _action_route_type(
130
+ options["route_class_override"]
131
+ )
132
+ try:
133
+ APIRouter.add_api_route(
134
+ compiled,
135
+ declaration.path,
136
+ endpoint,
137
+ **options,
138
+ )
139
+ except (TypeError, ValueError, FastAPIError) as error:
140
+ raise FastApiAdapterError(
141
+ f"{endpoint.__qualname__} at {path}: {error}"
142
+ ) from error
143
+ return compiled
144
+
145
+
146
+ def _compile_endpoint(
147
+ endpoint: Callable[..., Any],
148
+ path: str,
149
+ container: AsyncContainer,
150
+ ) -> Callable[..., Any]:
151
+ try:
152
+ hints = get_type_hints(endpoint, include_extras=True)
153
+ signature = inspect.signature(endpoint)
154
+ _, _, path_parameters = compile_path(path)
155
+ parameters = []
156
+ for parameter in signature.parameters.values():
157
+ hint = hints.get(parameter.name, parameter.annotation)
158
+ parameters.append(_compile_parameter(parameter, hint, path_parameters, container))
159
+ except (TypeError, NameError) as error:
160
+ raise FastApiAdapterError(f"{endpoint.__qualname__} at {path}: {error}") from error
161
+
162
+ if inspect.iscoroutinefunction(endpoint):
163
+
164
+ @wraps(endpoint)
165
+ async def target(*args: Any, **kwargs: Any) -> Any:
166
+ return await endpoint(*args, **kwargs)
167
+ else:
168
+
169
+ @wraps(endpoint)
170
+ def target(*args: Any, **kwargs: Any) -> Any:
171
+ return endpoint(*args, **kwargs)
172
+
173
+ target.__signature__ = signature.replace( # type: ignore[attr-defined]
174
+ parameters=parameters,
175
+ return_annotation=hints.get("return", signature.return_annotation),
176
+ )
177
+ target.__annotations__ = {parameter.name: parameter.annotation for parameter in parameters}
178
+ if "return" in hints:
179
+ target.__annotations__["return"] = hints["return"]
180
+ return target
181
+
182
+
183
+ def _compile_parameter(
184
+ parameter: inspect.Parameter,
185
+ hint: object,
186
+ path_parameters: Mapping[str, object],
187
+ container: AsyncContainer,
188
+ *,
189
+ infer_services: bool = True,
190
+ ) -> inspect.Parameter:
191
+ metadata = get_args(hint)[1:] if get_origin(hint) is Annotated else ()
192
+ hint_type = get_args(hint)[0] if metadata else hint
193
+ explicit_di = default_parse_dependency(parameter, hint)
194
+ http_bound = parameter.name in path_parameters or any(
195
+ isinstance(value, (FieldInfo, Depends)) for value in (*metadata, parameter.default)
196
+ )
197
+ # 特殊参数由 FastAPI 的版本匹配原生规则解释,不复制 Request/Response 等类型列表。
198
+ native_special = add_non_field_param_to_dependency(
199
+ param_name=parameter.name,
200
+ type_annotation=hint_type,
201
+ dependant=Dependant(),
202
+ )
203
+ if http_bound or native_special:
204
+ if explicit_di is not None:
205
+ raise TypeError(f"{parameter.name}: explicit HTTP source conflicts with FromComponent")
206
+ return parameter.replace(annotation=hint)
207
+ if not infer_services and explicit_di is None:
208
+ return parameter.replace(annotation=hint)
209
+ key = _dependency_key(parameter, hint, container, Scope.REQUEST)
210
+ if key is not None:
211
+ hint = Annotated[key.type_hint, FromComponent(key.component or "")]
212
+ return parameter.replace(annotation=hint)
@@ -0,0 +1,105 @@
1
+ """手写 Action 的绑定后执行与绑定前策略,保留原生 FastAPI 依赖和线程池。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ from collections.abc import Callable
7
+ from dataclasses import dataclass
8
+ from functools import wraps
9
+ from typing import Any, cast
10
+
11
+ from dishka.integrations.fastapi import DishkaRoute
12
+ from fastapi.routing import APIRoute
13
+ from starlette.concurrency import run_in_threadpool
14
+ from starlette.requests import Request
15
+ from starlette.responses import Response
16
+
17
+ from ..application_services.dispatcher import _ApplicationServiceDispatcher
18
+ from ..authorization.contracts import _AuthorizationRequirement
19
+ from ..unit_of_work.options import UnitOfWorkDeclaration
20
+ from .action import _HttpActionOwner, _request_parameter
21
+ from .filters import ServiceFilter, _ordered_filters
22
+ from .parameters import _HttpParameterValidator
23
+ from .transfer import _manage_stream
24
+ from .uploads import _prepare_upload
25
+
26
+ _ACTION = "__python_platform_action_definition__"
27
+
28
+
29
+ @dataclass(frozen=True, slots=True)
30
+ class _ActionDefinition:
31
+ endpoint: Callable[..., Any]
32
+ authorization: _AuthorizationRequirement
33
+ work: UnitOfWorkDeclaration
34
+ filters: tuple[ServiceFilter, ...]
35
+
36
+
37
+ class _ActionRoute(DishkaRoute):
38
+ def __init__(self, path: str, endpoint: Callable[..., Any], **kwargs: Any) -> None:
39
+ self._definition: _ActionDefinition = getattr(endpoint, _ACTION)
40
+ super().__init__(path, endpoint, **kwargs)
41
+ setattr(self.endpoint, _ACTION, self._definition)
42
+
43
+ def get_route_handler(self) -> Callable[[Request], Any]:
44
+ native = super().get_route_handler()
45
+ definition = self._definition
46
+
47
+ async def handle(request: Request) -> Response:
48
+ owner = await _HttpActionOwner.install(request)
49
+ try:
50
+ dispatcher = await owner.container.get(_ApplicationServiceDispatcher)
51
+ await dispatcher._authorize(definition.authorization)
52
+ await _prepare_upload(request, definition.endpoint, self)
53
+ result = await native(request)
54
+ _manage_stream(request, result)
55
+ owner.response_ready = True
56
+ return result
57
+ except BaseException as failure:
58
+ owner.failure = failure
59
+ raise
60
+
61
+ return handle
62
+
63
+
64
+ def _wrap_action(endpoint: Callable[..., Any], definition: _ActionDefinition) -> Callable[..., Any]:
65
+ signature = inspect.signature(endpoint)
66
+ validator = _HttpParameterValidator(signature)
67
+ parameters = tuple(signature.parameters.values())
68
+ request_parameter, injected_request = _request_parameter(parameters)
69
+ request_name = request_parameter.name
70
+ if injected_request:
71
+ parameters += (request_parameter,)
72
+
73
+ @wraps(endpoint)
74
+ async def target(**values: Any) -> Any:
75
+ request = cast(
76
+ Request, values.pop(request_name) if injected_request else values[request_name]
77
+ )
78
+ owner: _HttpActionOwner = request.state.python_platform_action
79
+ await owner.activate(definition.work, definition.endpoint.__name__)
80
+ snapshots = validator.capture(values)
81
+
82
+ async def invoke(actual: dict[str, object]) -> object:
83
+ if filters:
84
+ validator.validate_changes(actual, snapshots)
85
+ if inspect.iscoroutinefunction(endpoint):
86
+ return await endpoint(**actual)
87
+ return await run_in_threadpool(endpoint, **actual)
88
+
89
+ filters = _ordered_filters(
90
+ getattr(request.app.state, "python_platform_filters", ()), definition.filters
91
+ )
92
+ return await owner.invoke(definition.endpoint, values, filters, invoke)
93
+
94
+ target.__signature__ = signature.replace(parameters=parameters) # type: ignore[attr-defined]
95
+ target.__annotations__ = {p.name: p.annotation for p in parameters}
96
+ target.__annotations__["return"] = signature.return_annotation
97
+ setattr(target, _ACTION, definition)
98
+ return target
99
+
100
+
101
+ def _action_route_type(native: type[APIRoute]) -> type[APIRoute]:
102
+ if issubclass(native, _ActionRoute):
103
+ return native
104
+ # 原生自定义 APIRoute 继续拥有它的 handler 扩展;平台只增加同一 Action 边界。
105
+ return type(f"Managed{native.__name__}", (_ActionRoute, native), {})
@@ -0,0 +1,93 @@
1
+ """Host 有序 middleware 声明编译;原生实例共享,DI 处理对象按请求解析。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ from collections.abc import Awaitable, Callable, Sequence
7
+ from dataclasses import dataclass
8
+ from typing import Any
9
+
10
+ from dishka import AsyncContainer, Scope
11
+ from starlette.middleware import Middleware
12
+ from starlette.requests import Request
13
+ from starlette.types import ASGIApp, Receive, Send
14
+ from starlette.types import Scope as AsgiScope
15
+
16
+ from ..invocation.callables import _CallablePlan, _dependency_key
17
+ from .errors import FastApiAdapterError
18
+ from .request_context import _RequestTaskMiddleware
19
+
20
+
21
+ @dataclass(frozen=True, slots=True)
22
+ class DiMiddleware:
23
+ """原生 ASGI:async invoke(scope, receive, send, call_next, ...DI参数)。"""
24
+
25
+ handler: type | Callable[..., Awaitable[None]]
26
+
27
+
28
+ class _DiMiddleware:
29
+ def __init__(self, app: ASGIApp, *, declaration: DiMiddleware, plan: _CallablePlan) -> None:
30
+ self._app = app
31
+ self._declaration = declaration
32
+ self._plan = plan
33
+
34
+ async def __call__(self, scope: AsgiScope, receive: Receive, send: Send) -> None:
35
+ if scope["type"] != "http":
36
+ await self._app(scope, receive, send)
37
+ return
38
+ # 不创建转发 task;REQUEST generator 与 ContextVar 在解析它们的 task 中退出。
39
+ container = Request(scope).state.dishka_container
40
+ handler = self._declaration.handler
41
+ if isinstance(handler, type):
42
+ instance = await container.get(handler)
43
+ handler = instance.invoke
44
+ await self._plan.invoke(handler, container, (scope, receive, send, self._app), {})
45
+
46
+
47
+ class _NativeMiddleware:
48
+ def __init__(self, app: ASGIApp, *, middleware: Middleware) -> None:
49
+ cls, args, kwargs = middleware
50
+ # 每个下游入口检查前置短 UoW;BaseHTTPMiddleware 切 task 时也走同一入口。
51
+ self._app = _RequestTaskMiddleware(cls(_RequestTaskMiddleware(app), *args, **kwargs))
52
+
53
+ async def __call__(self, *args: Any) -> None:
54
+ await self._app(*args)
55
+
56
+
57
+ def _compile_middlewares(
58
+ items: Sequence[Middleware | DiMiddleware], container: AsyncContainer
59
+ ) -> tuple[Middleware, ...]:
60
+ compiled: list[Middleware] = []
61
+ for item in items:
62
+ if isinstance(item, DiMiddleware):
63
+ handler = item.handler
64
+ if isinstance(handler, type):
65
+ parameter = inspect.Parameter("middleware", inspect.Parameter.KEYWORD_ONLY)
66
+ if _dependency_key(parameter, handler, container, Scope.REQUEST) is None:
67
+ raise FastApiAdapterError("DI middleware type is not registered")
68
+ function = getattr(handler, "invoke", None)
69
+ if function is None:
70
+ raise FastApiAdapterError("DI middleware requires async invoke")
71
+ unbound = _CallablePlan.compile(
72
+ function, container, Scope.REQUEST, business_parameters=5
73
+ )
74
+ plan = _CallablePlan(
75
+ unbound.signature.replace(
76
+ parameters=tuple(unbound.signature.parameters.values())[1:]
77
+ ),
78
+ unbound.dependencies,
79
+ )
80
+ else:
81
+ plan = _CallablePlan.compile(
82
+ handler, container, Scope.REQUEST, business_parameters=4
83
+ )
84
+ if len(plan.signature.parameters) != 4:
85
+ raise FastApiAdapterError(
86
+ "DI middleware must accept scope, receive, send and call_next"
87
+ )
88
+ plan.signature.bind(None, None, None, None)
89
+ item = Middleware(_DiMiddleware, declaration=item, plan=plan)
90
+ elif not isinstance(item, Middleware):
91
+ raise FastApiAdapterError("Host middleware must be Middleware or DiMiddleware")
92
+ compiled.append(Middleware(_NativeMiddleware, middleware=item))
93
+ return tuple(compiled)
@@ -0,0 +1,97 @@
1
+ """HTTP Filter 改参后的验证边界;validator 由当前应用的路由编译结果拥有。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ from typing import Annotated, Any, cast, get_args, get_origin
7
+
8
+ from dishka.integrations.base import default_parse_dependency
9
+ from fastapi.dependencies.models import Dependant
10
+ from fastapi.dependencies.utils import add_non_field_param_to_dependency
11
+ from fastapi.exceptions import RequestValidationError
12
+ from pydantic import ConfigDict, Field, TypeAdapter, ValidationError
13
+ from pydantic.fields import FieldInfo
14
+ from pydantic_core import CoreSchema, SchemaValidator
15
+
16
+ from ..application_services.execution import _InvocationArguments
17
+
18
+
19
+ def _revalidation_schema(value: Any) -> Any:
20
+ # 只复制 schema 的容器,不复制模型 class、validator 或默认业务对象。
21
+ # 显式覆盖每个嵌套模型/数据类,不能改变业务模型的全局配置。
22
+ if isinstance(value, dict):
23
+ result = {
24
+ key: item if key in ("default", "metadata", "config") else _revalidation_schema(item)
25
+ for key, item in value.items()
26
+ }
27
+ if result.get("type") in ("model", "dataclass"):
28
+ result["revalidate_instances"] = "always"
29
+ return result
30
+ if isinstance(value, list):
31
+ return [_revalidation_schema(item) for item in value]
32
+ return value
33
+
34
+
35
+ class _HttpParameterValidator:
36
+ def __init__(self, signature: inspect.Signature) -> None:
37
+ self._signature = signature
38
+ self._validators: dict[str, SchemaValidator] = {}
39
+ for name, parameter in signature.parameters.items():
40
+ annotation = parameter.annotation
41
+ if isinstance(parameter.default, FieldInfo):
42
+ annotation = Annotated[annotation, parameter.default]
43
+ base = get_args(annotation)[0] if get_origin(annotation) is Annotated else annotation
44
+ if (
45
+ annotation is inspect.Parameter.empty
46
+ or default_parse_dependency(parameter, annotation) is not None
47
+ or add_non_field_param_to_dependency(
48
+ param_name=name, type_annotation=base, dependant=Dependant()
49
+ )
50
+ ):
51
+ continue
52
+ if parameter.default is not inspect.Parameter.empty and not isinstance(
53
+ parameter.default, FieldInfo
54
+ ):
55
+ annotation = Annotated[annotation, Field(default=parameter.default)]
56
+ adapter = TypeAdapter(
57
+ tuple[annotation], # type: ignore[valid-type]
58
+ config=ConfigDict(arbitrary_types_allowed=True),
59
+ )
60
+ self._validators[name] = SchemaValidator(
61
+ cast(CoreSchema, _revalidation_schema(adapter.core_schema)), _use_prebuilt=False
62
+ )
63
+
64
+ def capture(self, actual: dict[str, object]) -> dict[str, _InvocationArguments]:
65
+ # 只追踪 HTTP 可验证参数;Request/DI 对象不参与 DTO 的变更判断。
66
+ snapshots = {}
67
+ for name in self._validators:
68
+ if name in actual:
69
+ arguments = _InvocationArguments((actual[name],), {})
70
+ arguments.mark_validated()
71
+ arguments.exposed = True
72
+ snapshots[name] = arguments
73
+ return snapshots
74
+
75
+ def validate_changes(
76
+ self, actual: dict[str, object], snapshots: dict[str, _InvocationArguments]
77
+ ) -> None:
78
+ try:
79
+ self._signature.bind(**actual)
80
+ except TypeError:
81
+ raise RequestValidationError(
82
+ [{"loc": (), "msg": "Invalid action arguments", "type": "arguments"}]
83
+ ) from None
84
+ try:
85
+ for name, validator in self._validators.items():
86
+ values = (actual[name],) if name in actual else ()
87
+ previous = snapshots.get(name)
88
+ if previous is not None:
89
+ if not values or previous.args[0] is not values[0]:
90
+ previous.args = values
91
+ if not previous.needs_validation:
92
+ continue
93
+ # 独立参数各自重验;改动 scalar 不能重复执行未变 DTO 的非幂等 validator。
94
+ # 已绑定实例内部使用字段名;Filter 替换输入仍接受原 schema 的别名。
95
+ actual[name] = validator.validate_python(values, by_name=True)[0]
96
+ except ValidationError as error:
97
+ raise RequestValidationError(error.errors()) from None
@@ -0,0 +1,13 @@
1
+ """FastAPI-only managed WebSocket capability。"""
2
+
3
+ from .authentication import websocket_connection
4
+ from .connection import WebSocketConnection
5
+ from .module import FastApiRealtimeModule
6
+ from .options import FastApiRealtimeOptions
7
+
8
+ __all__ = (
9
+ "FastApiRealtimeModule",
10
+ "FastApiRealtimeOptions",
11
+ "WebSocketConnection",
12
+ "websocket_connection",
13
+ )