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,46 @@
1
+ """实现声明的验证开关;静态 metadata 不保存调用状态。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TypeVar
6
+
7
+ from .errors import ApplicationServiceDefinitionError
8
+
9
+ _T = TypeVar("_T")
10
+ _VALIDATION_ATTRIBUTE = "__python_platform_validation_enabled__"
11
+
12
+
13
+ def _declare_switch(target: _T, attribute: str, enabled: bool) -> _T:
14
+ if not isinstance(target, type) and not callable(target):
15
+ raise ApplicationServiceDefinitionError(
16
+ symbol=str(type(target)), reason="policy requires a class or method"
17
+ )
18
+ if attribute in vars(target):
19
+ raise ApplicationServiceDefinitionError(
20
+ symbol=getattr(target, "__qualname__", "<policy>"), reason="policy is already declared"
21
+ )
22
+ setattr(target, attribute, enabled)
23
+ return target
24
+
25
+
26
+ def disable_validation(target: _T) -> _T:
27
+ return _declare_switch(target, _VALIDATION_ATTRIBUTE, False)
28
+
29
+
30
+ def enable_validation(target: _T) -> _T:
31
+ return _declare_switch(target, _VALIDATION_ATTRIBUTE, True)
32
+
33
+
34
+ def _effective_switch(
35
+ implementation: type[object], method: object, attribute: str, default: bool
36
+ ) -> bool:
37
+ # 方法优先,再取 MRO 中最近的类声明;不把基类的禁用累加成无法恢复的开关。
38
+ for target in (method, *implementation.__mro__):
39
+ if attribute in vars(target):
40
+ value = vars(target)[attribute]
41
+ if not isinstance(value, bool):
42
+ raise ApplicationServiceDefinitionError(
43
+ symbol=implementation.__qualname__, reason="policy metadata must be bool"
44
+ )
45
+ return value
46
+ return default
@@ -0,0 +1,72 @@
1
+ """显式 Migration Host 使用的 ordered、per-contributor UoW seeding。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import abstractmethod
6
+ from typing import TYPE_CHECKING, cast
7
+
8
+ from ..errors.base import FrameworkError
9
+ from ..modularity import AppModule
10
+ from ..modularity.selection import _dependency_keys
11
+ from .contracts import _InternalApplicationServiceContributor
12
+
13
+ if TYPE_CHECKING:
14
+ from ..application import Application
15
+
16
+
17
+ class DataSeedContributor(_InternalApplicationServiceContributor):
18
+ """幂等性由具体 contributor 保证;框架只拥有顺序和独立 invocation。"""
19
+
20
+ @abstractmethod
21
+ async def seed(self) -> None:
22
+ raise NotImplementedError
23
+
24
+
25
+ class DataSeedError(FrameworkError):
26
+ def __init__(
27
+ self,
28
+ *,
29
+ completed: tuple[type[DataSeedContributor], ...],
30
+ failed: type[DataSeedContributor],
31
+ original_error: BaseException,
32
+ ) -> None:
33
+ self.completed = completed
34
+ self.failed = failed
35
+ self.original_error = original_error
36
+ super().__init__(
37
+ f"Data seed contributor {failed.__module__}.{failed.__qualname__} failed after "
38
+ f"{len(completed)} completed contributor(s): {type(original_error).__name__}"
39
+ )
40
+
41
+
42
+ class DataSeeder:
43
+ __slots__ = ("_application",)
44
+
45
+ def __init__(self, application: Application) -> None:
46
+ self._application = application
47
+
48
+ async def seed_all(
49
+ self, *, module: type[AppModule] | None = None
50
+ ) -> tuple[type[DataSeedContributor], ...]:
51
+ owners = _dependency_keys(self._application.modules, module) if module is not None else None
52
+ registrations = tuple(
53
+ registration
54
+ for registration in self._application.application_service_catalog.registrations
55
+ if issubclass(registration.implementation, DataSeedContributor)
56
+ and (owners is None or registration.implementation_owner in owners)
57
+ )
58
+ completed: list[type[DataSeedContributor]] = []
59
+ for registration in registrations:
60
+ contributor = cast(type[DataSeedContributor], registration.implementation)
61
+ # contributor 不发布 single Protocol,但仍复用同一 validation/UoW/event dispatcher。
62
+ try:
63
+ await self._application._invoke_registration(registration, "seed")
64
+ except Exception as original_error:
65
+ error = DataSeedError(
66
+ completed=tuple(completed),
67
+ failed=contributor,
68
+ original_error=original_error,
69
+ )
70
+ raise error from original_error
71
+ completed.append(contributor)
72
+ return tuple(completed)
@@ -0,0 +1,49 @@
1
+ """业务签名比较保留 Pydantic 约束,把 FieldInfo 的 wire projection 与业务值分开。"""
2
+
3
+ import inspect
4
+ from copy import copy
5
+ from typing import Annotated, Any, get_args, get_origin
6
+
7
+ from pydantic.fields import FieldInfo
8
+ from pydantic_core import PydanticUndefined
9
+
10
+
11
+ def _business_annotation(annotation: Any) -> Any:
12
+ if get_origin(annotation) is not Annotated:
13
+ return annotation
14
+ base, *metadata = get_args(annotation)
15
+ constraints: list[object] = []
16
+ for item in metadata:
17
+ if isinstance(item, FieldInfo):
18
+ # Path(gt=...) 仍携带业务约束,不能整体丢掉 Annotated 或 FieldInfo。
19
+ constraints.extend(item.metadata)
20
+ for attribute in ("default_factory", "discriminator", "validate_default", "frozen"):
21
+ value = getattr(item, attribute)
22
+ if value is not None:
23
+ constraints.append((attribute, value))
24
+ if item.default is not PydanticUndefined:
25
+ constraints.append(("default", item.default))
26
+ else:
27
+ constraints.append(item)
28
+ return (base, tuple(constraints)) if constraints else base
29
+
30
+
31
+ def _business_signature(signature: inspect.Signature) -> inspect.Signature:
32
+ parameters = []
33
+ for parameter in signature.parameters.values():
34
+ default = parameter.default
35
+ annotation = parameter.annotation
36
+ if isinstance(default, FieldInfo):
37
+ binding = copy(default)
38
+ # 参数默认值由 Signature 比较,不能因 wire 声明的载体不同再比较一次。
39
+ binding.default = PydanticUndefined
40
+ annotation = Annotated[annotation, binding]
41
+ default = default.default
42
+ if default is PydanticUndefined:
43
+ default = inspect.Parameter.empty
44
+ parameters.append(
45
+ parameter.replace(annotation=_business_annotation(annotation), default=default)
46
+ )
47
+ return signature.replace(
48
+ parameters=parameters, return_annotation=_business_annotation(signature.return_annotation)
49
+ )
@@ -0,0 +1,84 @@
1
+ """Pydantic 调用参数边界与诊断脱敏;只保留静态签名允许的信息。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping
6
+
7
+ from pydantic import ValidationError
8
+
9
+ from .catalog import ApplicationServiceRegistration, _CompiledMethod
10
+ from .errors import ApplicationServiceValidationError, ApplicationServiceValidationIssue
11
+
12
+
13
+ async def _validate_arguments(
14
+ method: _CompiledMethod,
15
+ registration: ApplicationServiceRegistration,
16
+ args: tuple[object, ...],
17
+ kwargs: dict[str, object],
18
+ ) -> tuple[tuple[object, ...], dict[str, object]]:
19
+ validation_error: ApplicationServiceValidationError | None = None
20
+ try:
21
+ return await method.validator(None, *args, **kwargs)
22
+ except ValidationError as original_error:
23
+ validation_error = _sanitize_validation_error(
24
+ original_error,
25
+ registration=registration,
26
+ method_name=method.name,
27
+ parameter_names=method.parameter_names,
28
+ )
29
+ # 在 Pydantic except context 外抛出,避免 exception.__context__ 留住 raw input。
30
+ raise validation_error from None
31
+
32
+
33
+ def _sanitize_validation_error(
34
+ error: ValidationError,
35
+ *,
36
+ registration: ApplicationServiceRegistration,
37
+ method_name: str,
38
+ parameter_names: frozenset[str],
39
+ ) -> ApplicationServiceValidationError:
40
+ issues = tuple(
41
+ ApplicationServiceValidationIssue(
42
+ location=_sanitize_validation_location(detail.get("loc"), parameter_names),
43
+ error_type=_sanitize_validation_error_type(detail),
44
+ # 自定义 Pydantic validator 的 msg/type 可能自行拼入原值;公开错误只保留静态事实。
45
+ message="Input validation failed",
46
+ )
47
+ for detail in error.errors(
48
+ include_url=True,
49
+ include_context=False,
50
+ include_input=False,
51
+ )
52
+ )
53
+ return ApplicationServiceValidationError(
54
+ service=registration.contract,
55
+ method_name=method_name,
56
+ issues=issues,
57
+ )
58
+
59
+
60
+ def _sanitize_validation_location(
61
+ location: object,
62
+ parameter_names: frozenset[str],
63
+ ) -> tuple[str | int, ...]:
64
+ if not isinstance(location, tuple) or not location:
65
+ return ()
66
+ first = location[0]
67
+ if isinstance(first, str) and first in parameter_names:
68
+ return (first,)
69
+ if isinstance(first, int) and not isinstance(first, bool) and first >= 0:
70
+ return (first,)
71
+ return ()
72
+
73
+
74
+ def _sanitize_validation_error_type(detail: Mapping[str, object]) -> str:
75
+ error_type = detail.get("type")
76
+ url = detail.get("url")
77
+ if (
78
+ isinstance(error_type, str)
79
+ and isinstance(url, str)
80
+ and url.startswith("https://errors.pydantic.dev/")
81
+ and url.endswith(f"/v/{error_type}")
82
+ ):
83
+ return error_type
84
+ return "value_error"
@@ -0,0 +1,12 @@
1
+ from .contracts import ApplicationServiceAuditRecord, AuditSink, RequestAuditRecord
2
+
3
+ __all__ = (
4
+ "ApplicationServiceAuditRecord",
5
+ "AuditSink",
6
+ "AuditingControl",
7
+ "AuditingOptions",
8
+ "RequestAuditRecord",
9
+ "audited",
10
+ "disable_auditing",
11
+ )
12
+ from .control import AuditingControl, AuditingOptions, audited, disable_auditing
@@ -0,0 +1,39 @@
1
+ """请求与 Application Service 审计只记录允许的静态事实。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from datetime import datetime
7
+ from typing import Protocol
8
+ from uuid import UUID
9
+
10
+
11
+ @dataclass(frozen=True, slots=True)
12
+ class RequestAuditRecord:
13
+ occurred_at: datetime
14
+ user_id: UUID | None
15
+ session_id: UUID | None
16
+ correlation_id: str
17
+ route_template: str
18
+ status_code: int
19
+ duration_ns: int
20
+ exception_type: str | None
21
+
22
+
23
+ @dataclass(frozen=True, slots=True)
24
+ class ApplicationServiceAuditRecord:
25
+ occurred_at: datetime
26
+ user_id: UUID | None
27
+ session_id: UUID | None
28
+ correlation_id: str
29
+ contract: str
30
+ method: str
31
+ succeeded: bool
32
+ duration_ns: int
33
+ exception_type: str | None
34
+
35
+
36
+ class AuditSink(Protocol):
37
+ async def write_request(self, record: RequestAuditRecord) -> None: ...
38
+
39
+ async def write_application_service(self, record: ApplicationServiceAuditRecord) -> None: ...
@@ -0,0 +1,48 @@
1
+ """审计策略和 Application-local 运行时抑制;不改变外层请求审计。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterator
6
+ from contextlib import contextmanager
7
+ from contextvars import ContextVar
8
+ from typing import TypeVar
9
+
10
+ from pydantic import ConfigDict
11
+
12
+ from ..application_services.policies import _declare_switch
13
+ from ..options import BaseOptions
14
+
15
+ _T = TypeVar("_T")
16
+ _AUDITING_ATTRIBUTE = "__python_platform_auditing_enabled__"
17
+
18
+
19
+ def disable_auditing(target: _T) -> _T:
20
+ return _declare_switch(target, _AUDITING_ATTRIBUTE, False)
21
+
22
+
23
+ def audited(target: _T) -> _T:
24
+ return _declare_switch(target, _AUDITING_ATTRIBUTE, True)
25
+
26
+
27
+ class AuditingOptions(BaseOptions):
28
+ model_config = ConfigDict(extra="forbid")
29
+ is_enabled: bool = True
30
+
31
+
32
+ class AuditingControl:
33
+ """每个 Application 独立持有;只抑制当前异步上下文的方法审计。"""
34
+
35
+ def __init__(self) -> None:
36
+ self._disabled: ContextVar[bool] = ContextVar("method_auditing_disabled", default=False)
37
+
38
+ @property
39
+ def is_enabled(self) -> bool:
40
+ return not self._disabled.get()
41
+
42
+ @contextmanager
43
+ def disable(self) -> Iterator[None]:
44
+ token = self._disabled.set(True)
45
+ try:
46
+ yield
47
+ finally:
48
+ self._disabled.reset(token)
@@ -0,0 +1,6 @@
1
+ """SQLAlchemy 审计持久化 provider。"""
2
+
3
+ from .module import SqlAlchemyAuditingModule
4
+ from .store import SqlAlchemyAuditSink
5
+
6
+ __all__ = ("SqlAlchemyAuditSink", "SqlAlchemyAuditingModule")
@@ -0,0 +1,45 @@
1
+ """Create the independent platform auditing migration branch."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sqlalchemy as sa
6
+ from alembic import op
7
+
8
+ revision = "platform_auditing_0001"
9
+ down_revision = None
10
+ branch_labels = ("platform_auditing",)
11
+ depends_on = None
12
+
13
+
14
+ def upgrade() -> None:
15
+ op.create_table(
16
+ "platform_audit_logs",
17
+ sa.Column("audit_id", sa.Uuid(), nullable=False),
18
+ sa.Column("occurred_at", sa.DateTime(timezone=True), nullable=False),
19
+ sa.Column("kind", sa.String(length=32), nullable=False),
20
+ sa.Column("user_id", sa.Uuid(), nullable=True),
21
+ sa.Column("session_id", sa.Uuid(), nullable=True),
22
+ sa.Column("correlation_id", sa.String(length=128), nullable=False),
23
+ sa.Column("route_template", sa.String(length=512), nullable=True),
24
+ sa.Column("contract", sa.String(length=512), nullable=True),
25
+ sa.Column("method", sa.String(length=128), nullable=True),
26
+ sa.Column("status_code", sa.Integer(), nullable=True),
27
+ sa.Column("succeeded", sa.Boolean(), nullable=True),
28
+ sa.Column("duration_ns", sa.BigInteger(), nullable=False),
29
+ sa.Column("exception_type", sa.String(length=256), nullable=True),
30
+ sa.PrimaryKeyConstraint("audit_id"),
31
+ )
32
+ op.create_index("ix_platform_audit_logs_kind", "platform_audit_logs", ["kind"])
33
+ op.create_index("ix_platform_audit_logs_user_id", "platform_audit_logs", ["user_id"])
34
+ op.create_index(
35
+ "ix_platform_audit_logs_correlation_id",
36
+ "platform_audit_logs",
37
+ ["correlation_id"],
38
+ )
39
+
40
+
41
+ def downgrade() -> None:
42
+ op.drop_index("ix_platform_audit_logs_correlation_id", table_name="platform_audit_logs")
43
+ op.drop_index("ix_platform_audit_logs_user_id", table_name="platform_audit_logs")
44
+ op.drop_index("ix_platform_audit_logs_kind", table_name="platform_audit_logs")
45
+ op.drop_table("platform_audit_logs")
@@ -0,0 +1 @@
1
+ """Platform auditing Alembic branch。"""
@@ -0,0 +1,31 @@
1
+ """审计日志的独立 metadata owner。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime
6
+ from uuid import UUID
7
+
8
+ from sqlalchemy import BigInteger, Boolean, DateTime, Integer, String, Uuid
9
+ from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
10
+
11
+
12
+ class AuditingBase(DeclarativeBase):
13
+ pass
14
+
15
+
16
+ class AuditLogRow(AuditingBase):
17
+ __tablename__ = "platform_audit_logs"
18
+
19
+ audit_id: Mapped[UUID] = mapped_column(Uuid, primary_key=True)
20
+ occurred_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
21
+ kind: Mapped[str] = mapped_column(String(32), nullable=False, index=True)
22
+ user_id: Mapped[UUID | None] = mapped_column(Uuid, nullable=True, index=True)
23
+ session_id: Mapped[UUID | None] = mapped_column(Uuid, nullable=True)
24
+ correlation_id: Mapped[str] = mapped_column(String(128), nullable=False, index=True)
25
+ route_template: Mapped[str | None] = mapped_column(String(512), nullable=True)
26
+ contract: Mapped[str | None] = mapped_column(String(512), nullable=True)
27
+ method: Mapped[str | None] = mapped_column(String(128), nullable=True)
28
+ status_code: Mapped[int | None] = mapped_column(Integer, nullable=True)
29
+ succeeded: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
30
+ duration_ns: Mapped[int] = mapped_column(BigInteger, nullable=False)
31
+ exception_type: Mapped[str | None] = mapped_column(String(256), nullable=True)
@@ -0,0 +1,32 @@
1
+ """Host 显式选择的 SQLAlchemy 审计 provider Module。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dishka import Provider, Scope
6
+
7
+ from ...lifecycle import ConfigureContext
8
+ from ...modularity import AppModule
9
+ from ...sqlalchemy import (
10
+ SqlAlchemyModelRegistration,
11
+ SqlAlchemyPersistenceModule,
12
+ contributes_sqlalchemy_models,
13
+ )
14
+ from ..contracts import AuditSink
15
+ from .models import AuditingBase
16
+ from .store import SqlAlchemyAuditSink
17
+
18
+
19
+ @contributes_sqlalchemy_models(
20
+ SqlAlchemyModelRegistration(
21
+ metadata=AuditingBase.metadata,
22
+ migrations_package="python_platform.auditing.sqlalchemy.migrations",
23
+ branch_label="platform_auditing",
24
+ )
25
+ )
26
+ class SqlAlchemyAuditingModule(AppModule):
27
+ dependencies = (SqlAlchemyPersistenceModule,)
28
+
29
+ def configure(self, context: ConfigureContext) -> None:
30
+ provider = Provider()
31
+ provider.provide(SqlAlchemyAuditSink, provides=AuditSink, scope=Scope.APP)
32
+ context.services.contribute(provider, reason="SQLAlchemy independent audit sink")
@@ -0,0 +1,63 @@
1
+ """每条审计记录使用独立短事务,不加入业务 Unit of Work。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from uuid import uuid4
6
+
7
+ from ...sqlalchemy.auditing import PlatformAsyncSession
8
+ from ...sqlalchemy.runtime import _SqlAlchemyEngineRegistry
9
+ from ..contracts import ApplicationServiceAuditRecord, RequestAuditRecord
10
+ from .models import AuditLogRow
11
+
12
+
13
+ class SqlAlchemyAuditSink:
14
+ __slots__ = ("_engines",)
15
+
16
+ def __init__(self, engines: _SqlAlchemyEngineRegistry) -> None:
17
+ self._engines = engines
18
+
19
+ async def write_request(self, record: RequestAuditRecord) -> None:
20
+ await self._write(
21
+ AuditLogRow(
22
+ audit_id=uuid4(),
23
+ occurred_at=record.occurred_at,
24
+ kind="request",
25
+ user_id=record.user_id,
26
+ session_id=record.session_id,
27
+ correlation_id=record.correlation_id,
28
+ route_template=record.route_template,
29
+ contract=None,
30
+ method=None,
31
+ status_code=record.status_code,
32
+ succeeded=record.exception_type is None and record.status_code < 500,
33
+ duration_ns=record.duration_ns,
34
+ exception_type=record.exception_type,
35
+ )
36
+ )
37
+
38
+ async def write_application_service(self, record: ApplicationServiceAuditRecord) -> None:
39
+ await self._write(
40
+ AuditLogRow(
41
+ audit_id=uuid4(),
42
+ occurred_at=record.occurred_at,
43
+ kind="application_service",
44
+ user_id=record.user_id,
45
+ session_id=record.session_id,
46
+ correlation_id=record.correlation_id,
47
+ route_template=None,
48
+ contract=record.contract,
49
+ method=record.method,
50
+ status_code=None,
51
+ succeeded=record.succeeded,
52
+ duration_ns=record.duration_ns,
53
+ exception_type=record.exception_type,
54
+ )
55
+ )
56
+
57
+ async def _write(self, row: AuditLogRow) -> None:
58
+ async with PlatformAsyncSession(
59
+ self._engines.get("default"),
60
+ expire_on_commit=False,
61
+ ) as session:
62
+ async with session.begin():
63
+ session.add(row)
@@ -0,0 +1,38 @@
1
+ from .catalog import PermissionCatalog
2
+ from .contracts import (
3
+ AccessTokenAuthenticator,
4
+ CurrentUser,
5
+ PermissionChecker,
6
+ PermissionDefinition,
7
+ PermissionName,
8
+ RolePermissionVersion,
9
+ allow_anonymous,
10
+ authorize,
11
+ )
12
+ from .definitions import PermissionDefinitionContext, PermissionDefinitionProvider
13
+ from .errors import (
14
+ AuthorizationDefinitionError,
15
+ PermissionCheckerUnavailableError,
16
+ PermissionDefinitionError,
17
+ PermissionDeniedError,
18
+ UnauthenticatedError,
19
+ )
20
+
21
+ __all__ = (
22
+ "AccessTokenAuthenticator",
23
+ "AuthorizationDefinitionError",
24
+ "CurrentUser",
25
+ "PermissionCatalog",
26
+ "PermissionChecker",
27
+ "PermissionCheckerUnavailableError",
28
+ "PermissionDefinition",
29
+ "PermissionDefinitionContext",
30
+ "PermissionDefinitionError",
31
+ "PermissionDefinitionProvider",
32
+ "PermissionDeniedError",
33
+ "PermissionName",
34
+ "RolePermissionVersion",
35
+ "UnauthenticatedError",
36
+ "allow_anonymous",
37
+ "authorize",
38
+ )
@@ -0,0 +1,63 @@
1
+ """从 live Module metadata 聚合并冻结 Permission Catalog。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ from collections.abc import Iterator, Mapping
8
+ from types import MappingProxyType
9
+
10
+ from ..modularity import ModuleRegistry
11
+ from ..modularity.discovery import _DiscoveredType
12
+ from .contracts import PermissionDefinition, PermissionName
13
+ from .definition_discovery import _collect_permission_definitions
14
+ from .errors import PermissionDefinitionError
15
+
16
+
17
+ class PermissionCatalog:
18
+ __slots__ = ("_definitions", "_fingerprint")
19
+
20
+ def __init__(self, definitions: tuple[PermissionDefinition, ...]) -> None:
21
+ by_name: dict[PermissionName, PermissionDefinition] = {}
22
+ for definition in definitions:
23
+ if not isinstance(definition, PermissionDefinition):
24
+ raise PermissionDefinitionError(
25
+ permission=repr(definition),
26
+ reason="catalog entries must be PermissionDefinition values",
27
+ )
28
+ if definition.name in by_name:
29
+ raise PermissionDefinitionError(
30
+ permission=str(definition.name),
31
+ reason="permission is declared more than once",
32
+ )
33
+ by_name[definition.name] = definition
34
+ ordered = dict(sorted(by_name.items(), key=lambda item: item[0].value))
35
+ self._definitions: Mapping[PermissionName, PermissionDefinition] = MappingProxyType(ordered)
36
+ payload = tuple((item.name.value, item.display_name) for item in ordered.values())
37
+ self._fingerprint = hashlib.sha256(
38
+ json.dumps(payload, ensure_ascii=True, separators=(",", ":")).encode("utf-8")
39
+ ).hexdigest()
40
+
41
+ @property
42
+ def definitions(self) -> tuple[PermissionDefinition, ...]:
43
+ return tuple(self._definitions.values())
44
+
45
+ @property
46
+ def fingerprint(self) -> str:
47
+ return self._fingerprint
48
+
49
+ def __contains__(self, name: object) -> bool:
50
+ return name in self._definitions
51
+
52
+ def __iter__(self) -> Iterator[PermissionName]:
53
+ return iter(self._definitions)
54
+
55
+ def get(self, name: PermissionName) -> PermissionDefinition | None:
56
+ return self._definitions.get(name)
57
+
58
+
59
+ def _build_permission_catalog(
60
+ registry: ModuleRegistry,
61
+ discoveries: tuple[_DiscoveredType, ...],
62
+ ) -> PermissionCatalog:
63
+ return PermissionCatalog(_collect_permission_definitions(registry, discoveries))