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
+ """Provider-neutral Identity Module 只注册正式 contract 与 security services。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dishka import Provider, Scope
6
+
7
+ from ..authorization import AccessTokenAuthenticator, PermissionChecker
8
+ from ..lifecycle import ConfigureContext
9
+ from ..modularity import AppModule
10
+ from .contracts import (
11
+ IdentityOptions,
12
+ )
13
+ from .passwords import PasswordHasher
14
+ from .services import (
15
+ IDENTITY_PERMISSION_CACHE,
16
+ IdentityAuthenticator,
17
+ IdentityPermissionChecker,
18
+ )
19
+ from .tokens import JwtTokenService
20
+
21
+
22
+ class IdentityModule(AppModule):
23
+ scan_packages = ("python_platform.identity.application", "python_platform.identity.permissions")
24
+
25
+ cache_definitions = (IDENTITY_PERMISSION_CACHE,)
26
+
27
+ def configure(self, context: ConfigureContext) -> None:
28
+ context.configure(
29
+ IdentityOptions,
30
+ section="identity",
31
+ secret_paths=frozenset({"keys", "seed_admin_password"}),
32
+ )
33
+ provider = Provider()
34
+ provider.provide(PasswordHasher, provides=PasswordHasher, scope=Scope.APP)
35
+ provider.provide(JwtTokenService, provides=JwtTokenService, scope=Scope.APP)
36
+ provider.provide(
37
+ IdentityAuthenticator,
38
+ provides=AccessTokenAuthenticator,
39
+ scope=Scope.APP,
40
+ )
41
+ provider.provide(
42
+ IdentityPermissionChecker,
43
+ provides=PermissionChecker,
44
+ scope=Scope.APP,
45
+ )
46
+ context.services.contribute(provider, reason="Identity security services")
@@ -0,0 +1,34 @@
1
+ """pwdlib Argon2id boundary;昂贵 hash/verify 不阻塞 asyncio event loop。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+
7
+ from pwdlib import PasswordHash
8
+
9
+ _DUMMY_HASH = (
10
+ "$argon2id$v=19$m=65536,t=3,p=4$VyPuPaiooyVPpvuUem+SEA$"
11
+ "FGOxAPGfz6q2biOigVe+t5G92Au3TgjT/eOEKxT6+Mk"
12
+ )
13
+
14
+
15
+ class PasswordHasher:
16
+ __slots__ = ("_password_hash",)
17
+
18
+ def __init__(self) -> None:
19
+ self._password_hash = PasswordHash.recommended()
20
+
21
+ async def hash(self, password: str) -> str:
22
+ return await asyncio.to_thread(self._password_hash.hash, password)
23
+
24
+ async def verify(self, password: str, password_hash: str | None) -> tuple[bool, str | None]:
25
+ selected_hash = password_hash if password_hash is not None else _DUMMY_HASH
26
+ try:
27
+ return await asyncio.to_thread(
28
+ self._password_hash.verify_and_update,
29
+ password,
30
+ selected_hash,
31
+ )
32
+ except Exception:
33
+ # 持久化 hash 损坏与错误密码共享失败语义,且绝不回显 hash/parser 文本。
34
+ return False, None
@@ -0,0 +1,14 @@
1
+ """identity 权限定义;持久化名称继续来自所属正式契约。"""
2
+
3
+ from ..authorization import (
4
+ PermissionDefinition,
5
+ PermissionDefinitionContext,
6
+ PermissionDefinitionProvider,
7
+ )
8
+ from .contracts import IDENTITY_ROLES_MANAGE, IDENTITY_USERS_MANAGE
9
+
10
+
11
+ class IdentityPermissionDefinitionProvider(PermissionDefinitionProvider):
12
+ def define(self, context: PermissionDefinitionContext) -> None:
13
+ context.add(PermissionDefinition(IDENTITY_USERS_MANAGE, "Manage identity users"))
14
+ context.add(PermissionDefinition(IDENTITY_ROLES_MANAGE, "Manage identity roles"))
@@ -0,0 +1,108 @@
1
+ """Access token authentication 与 version-bound permission cache orchestration。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from datetime import UTC, datetime, timedelta
7
+
8
+ from dishka import AsyncContainer
9
+ from dishka.exceptions import NoFactoryError
10
+ from pydantic import BaseModel, ConfigDict
11
+
12
+ from ..authorization import CurrentUser, PermissionCatalog, PermissionChecker, PermissionName
13
+ from ..caching import CacheDefinition, DistributedCache, DistributedCacheError
14
+ from .contracts import IdentityAccessStore
15
+ from .errors import InvalidCredentialsError
16
+ from .tokens import JwtTokenService
17
+
18
+ _LOGGER = logging.getLogger(__name__)
19
+
20
+
21
+ class PermissionCacheEntry(BaseModel):
22
+ model_config = ConfigDict(frozen=True)
23
+
24
+ permissions: tuple[str, ...]
25
+
26
+
27
+ IDENTITY_PERMISSION_CACHE = CacheDefinition(
28
+ "identity.permissions",
29
+ PermissionCacheEntry,
30
+ ttl=timedelta(minutes=5),
31
+ )
32
+
33
+
34
+ class IdentityAuthenticator:
35
+ __slots__ = ("_access", "_tokens")
36
+
37
+ def __init__(self, tokens: JwtTokenService, access: IdentityAccessStore) -> None:
38
+ self._tokens = tokens
39
+ self._access = access
40
+
41
+ async def authenticate_access_token(self, token: str) -> CurrentUser:
42
+ claims = self._tokens.verify(token)
43
+ current_user = await self._access.load_current_user(
44
+ claims.user_id,
45
+ claims.session_id,
46
+ datetime.now(UTC),
47
+ )
48
+ if current_user is None:
49
+ raise InvalidCredentialsError
50
+ return current_user
51
+
52
+
53
+ class IdentityPermissionChecker(PermissionChecker):
54
+ __slots__ = ("_access", "_catalog", "_container")
55
+
56
+ def __init__(
57
+ self,
58
+ access: IdentityAccessStore,
59
+ catalog: PermissionCatalog,
60
+ container: AsyncContainer,
61
+ ) -> None:
62
+ self._access = access
63
+ self._catalog = catalog
64
+ self._container = container
65
+
66
+ async def is_granted(self, user: CurrentUser, permission: PermissionName) -> bool:
67
+ if not user.is_authenticated or permission not in self._catalog:
68
+ return False
69
+ cache = await self._optional_cache()
70
+ cache_key = _permission_cache_key(user, self._catalog.fingerprint)
71
+ if cache is not None:
72
+ try:
73
+ cached = await cache.get(IDENTITY_PERMISSION_CACHE, cache_key)
74
+ except DistributedCacheError:
75
+ cached = None
76
+ _LOGGER.warning(
77
+ "permission cache read failed; falling back to authoritative database",
78
+ extra={"cache": IDENTITY_PERMISSION_CACHE.name},
79
+ )
80
+ if cached is not None:
81
+ return permission.value in cached.permissions
82
+
83
+ permissions = await self._access.permissions_for_roles(user.role_versions)
84
+ if cache is not None:
85
+ try:
86
+ await cache.set(
87
+ IDENTITY_PERMISSION_CACHE,
88
+ cache_key,
89
+ PermissionCacheEntry(permissions=tuple(sorted(permissions))),
90
+ )
91
+ except DistributedCacheError:
92
+ _LOGGER.warning(
93
+ "permission cache write failed; database result remains authoritative",
94
+ extra={"cache": IDENTITY_PERMISSION_CACHE.name},
95
+ )
96
+ return permission.value in permissions
97
+
98
+ async def _optional_cache(self) -> DistributedCache | None:
99
+ try:
100
+ return await self._container.get(DistributedCache)
101
+ except NoFactoryError:
102
+ return None
103
+
104
+
105
+ def _permission_cache_key(user: CurrentUser, catalog_fingerprint: str) -> str:
106
+ role_versions = ",".join(f"{item.role_id.hex}.{item.version}" for item in user.role_versions)
107
+ assert user.user_id is not None
108
+ return f"{catalog_fingerprint}:{user.user_id.hex}:{user.permission_version}:{role_versions}"
@@ -0,0 +1,5 @@
1
+ """Identity 的 SQLAlchemy/PostgreSQL provider。"""
2
+
3
+ from .module import SqlAlchemyIdentityModule
4
+
5
+ __all__ = ("SqlAlchemyIdentityModule",)
@@ -0,0 +1,151 @@
1
+ """Create the independent platform identity migration branch."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ import sqlalchemy as sa
8
+ from alembic import op
9
+
10
+ revision = "platform_identity_0001"
11
+ down_revision = None
12
+ branch_labels = ("platform_identity",)
13
+ depends_on = None
14
+
15
+
16
+ def _audit_columns() -> tuple[sa.Column[Any], ...]:
17
+ return (
18
+ sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
19
+ sa.Column("created_by", sa.Uuid(), nullable=True),
20
+ sa.Column("updated_at", sa.DateTime(timezone=True), nullable=True),
21
+ sa.Column("updated_by", sa.Uuid(), nullable=True),
22
+ )
23
+
24
+
25
+ def _soft_delete_columns() -> tuple[sa.Column[Any], ...]:
26
+ return (
27
+ sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
28
+ sa.Column("deleted_by", sa.Uuid(), nullable=True),
29
+ )
30
+
31
+
32
+ def upgrade() -> None:
33
+ op.create_table(
34
+ "platform_identity_users",
35
+ sa.Column("user_id", sa.Uuid(), nullable=False),
36
+ sa.Column("username", sa.String(length=120), nullable=False),
37
+ sa.Column("normalized_username", sa.String(length=120), nullable=False),
38
+ sa.Column("password_hash", sa.String(length=512), nullable=False),
39
+ sa.Column("is_active", sa.Boolean(), nullable=False),
40
+ sa.Column("failed_access_count", sa.Integer(), nullable=False),
41
+ sa.Column("lockout_end", sa.DateTime(timezone=True), nullable=True),
42
+ sa.Column("permission_version", sa.BigInteger(), nullable=False),
43
+ *_audit_columns(),
44
+ *_soft_delete_columns(),
45
+ sa.PrimaryKeyConstraint("user_id"),
46
+ )
47
+ op.create_index(
48
+ "ix_platform_identity_users_normalized_username",
49
+ "platform_identity_users",
50
+ ["normalized_username"],
51
+ unique=True,
52
+ )
53
+ op.create_table(
54
+ "platform_identity_roles",
55
+ sa.Column("role_id", sa.Uuid(), nullable=False),
56
+ sa.Column("name", sa.String(length=120), nullable=False),
57
+ sa.Column("normalized_name", sa.String(length=120), nullable=False),
58
+ sa.Column("permission_version", sa.BigInteger(), nullable=False),
59
+ *_audit_columns(),
60
+ *_soft_delete_columns(),
61
+ sa.PrimaryKeyConstraint("role_id"),
62
+ )
63
+ op.create_index(
64
+ "ix_platform_identity_roles_normalized_name",
65
+ "platform_identity_roles",
66
+ ["normalized_name"],
67
+ unique=True,
68
+ )
69
+ op.create_table(
70
+ "platform_identity_user_roles",
71
+ sa.Column("user_id", sa.Uuid(), nullable=False),
72
+ sa.Column("role_id", sa.Uuid(), nullable=False),
73
+ *_audit_columns(),
74
+ sa.ForeignKeyConstraint(
75
+ ["role_id"], ["platform_identity_roles.role_id"], ondelete="CASCADE"
76
+ ),
77
+ sa.ForeignKeyConstraint(
78
+ ["user_id"], ["platform_identity_users.user_id"], ondelete="CASCADE"
79
+ ),
80
+ sa.PrimaryKeyConstraint("user_id", "role_id"),
81
+ )
82
+ op.create_table(
83
+ "platform_identity_role_permissions",
84
+ sa.Column("role_id", sa.Uuid(), nullable=False),
85
+ sa.Column("permission_name", sa.String(length=200), nullable=False),
86
+ *_audit_columns(),
87
+ sa.ForeignKeyConstraint(
88
+ ["role_id"], ["platform_identity_roles.role_id"], ondelete="CASCADE"
89
+ ),
90
+ sa.PrimaryKeyConstraint("role_id", "permission_name"),
91
+ )
92
+ op.create_table(
93
+ "platform_identity_refresh_sessions",
94
+ sa.Column("session_id", sa.Uuid(), nullable=False),
95
+ sa.Column("user_id", sa.Uuid(), nullable=False),
96
+ sa.Column("family_id", sa.Uuid(), nullable=False),
97
+ sa.Column("token_hash", sa.String(length=64), nullable=False),
98
+ sa.Column("family_created_at", sa.DateTime(timezone=True), nullable=False),
99
+ sa.Column("last_used_at", sa.DateTime(timezone=True), nullable=False),
100
+ sa.Column("revoked_at", sa.DateTime(timezone=True), nullable=True),
101
+ sa.Column("replaced_by_session_id", sa.Uuid(), nullable=True),
102
+ *_audit_columns(),
103
+ sa.ForeignKeyConstraint(
104
+ ["user_id"], ["platform_identity_users.user_id"], ondelete="CASCADE"
105
+ ),
106
+ sa.PrimaryKeyConstraint("session_id"),
107
+ )
108
+ op.create_index(
109
+ "ix_platform_identity_refresh_sessions_user_id",
110
+ "platform_identity_refresh_sessions",
111
+ ["user_id"],
112
+ )
113
+ op.create_index(
114
+ "ix_platform_identity_refresh_sessions_family_id",
115
+ "platform_identity_refresh_sessions",
116
+ ["family_id"],
117
+ )
118
+ op.create_index(
119
+ "ix_platform_identity_refresh_sessions_token_hash",
120
+ "platform_identity_refresh_sessions",
121
+ ["token_hash"],
122
+ unique=True,
123
+ )
124
+
125
+
126
+ def downgrade() -> None:
127
+ op.drop_index(
128
+ "ix_platform_identity_refresh_sessions_token_hash",
129
+ table_name="platform_identity_refresh_sessions",
130
+ )
131
+ op.drop_index(
132
+ "ix_platform_identity_refresh_sessions_family_id",
133
+ table_name="platform_identity_refresh_sessions",
134
+ )
135
+ op.drop_index(
136
+ "ix_platform_identity_refresh_sessions_user_id",
137
+ table_name="platform_identity_refresh_sessions",
138
+ )
139
+ op.drop_table("platform_identity_refresh_sessions")
140
+ op.drop_table("platform_identity_role_permissions")
141
+ op.drop_table("platform_identity_user_roles")
142
+ op.drop_index(
143
+ "ix_platform_identity_roles_normalized_name",
144
+ table_name="platform_identity_roles",
145
+ )
146
+ op.drop_table("platform_identity_roles")
147
+ op.drop_index(
148
+ "ix_platform_identity_users_normalized_username",
149
+ table_name="platform_identity_users",
150
+ )
151
+ op.drop_table("platform_identity_users")
@@ -0,0 +1,42 @@
1
+ """退出软删除,升级只接受已经人工处理完历史软删除记录的数据库。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sqlalchemy as sa
6
+ from alembic import op
7
+
8
+ from python_platform.sqlalchemy.errors import SoftDeleteMigrationBlockedError
9
+
10
+ revision = "platform_identity_0002"
11
+ down_revision = "platform_identity_0001"
12
+ branch_labels = None
13
+ depends_on = None
14
+
15
+
16
+ def upgrade() -> None:
17
+ connection = op.get_bind()
18
+ tables = ("platform_identity_users", "platform_identity_roles")
19
+ # 检查和 DDL 共享事务锁,旧进程不能在检查后插入新的软删除记录。
20
+ for name in tables:
21
+ connection.execute(sa.text(f"LOCK TABLE {name} IN ACCESS EXCLUSIVE MODE"))
22
+ affected = tuple(
23
+ (
24
+ name,
25
+ int(
26
+ connection.scalar(
27
+ sa.text(f"SELECT count(*) FROM {name} WHERE deleted_at IS NOT NULL")
28
+ )
29
+ or 0
30
+ ),
31
+ )
32
+ for name in tables
33
+ )
34
+ if any(count for _, count in affected):
35
+ raise SoftDeleteMigrationBlockedError(tuple(item for item in affected if item[1]))
36
+ for name in tables:
37
+ op.drop_column(name, "deleted_at")
38
+ op.drop_column(name, "deleted_by")
39
+
40
+
41
+ def downgrade() -> None:
42
+ raise RuntimeError("physical deletion cannot restore historical deleted records")
@@ -0,0 +1 @@
1
+ """Platform identity Alembic branch。"""
@@ -0,0 +1,85 @@
1
+ """Identity rows 与独立 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, ForeignKey, Integer, String, Uuid
9
+ from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
10
+
11
+ from ...sqlalchemy import AuditedEntityMixin
12
+
13
+
14
+ class IdentityBase(DeclarativeBase):
15
+ pass
16
+
17
+
18
+ class UserRow(AuditedEntityMixin, IdentityBase):
19
+ __tablename__ = "platform_identity_users"
20
+
21
+ user_id: Mapped[UUID] = mapped_column(Uuid, primary_key=True)
22
+ username: Mapped[str] = mapped_column(String(120), nullable=False)
23
+ normalized_username: Mapped[str] = mapped_column(
24
+ String(120), nullable=False, unique=True, index=True
25
+ )
26
+ password_hash: Mapped[str] = mapped_column(String(512), nullable=False)
27
+ is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
28
+ failed_access_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
29
+ lockout_end: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
30
+ permission_version: Mapped[int] = mapped_column(BigInteger, nullable=False, default=0)
31
+
32
+
33
+ class RoleRow(AuditedEntityMixin, IdentityBase):
34
+ __tablename__ = "platform_identity_roles"
35
+
36
+ role_id: Mapped[UUID] = mapped_column(Uuid, primary_key=True)
37
+ name: Mapped[str] = mapped_column(String(120), nullable=False)
38
+ normalized_name: Mapped[str] = mapped_column(
39
+ String(120), nullable=False, unique=True, index=True
40
+ )
41
+ permission_version: Mapped[int] = mapped_column(BigInteger, nullable=False, default=0)
42
+
43
+
44
+ class UserRoleRow(AuditedEntityMixin, IdentityBase):
45
+ __tablename__ = "platform_identity_user_roles"
46
+
47
+ user_id: Mapped[UUID] = mapped_column(
48
+ Uuid,
49
+ ForeignKey("platform_identity_users.user_id", ondelete="CASCADE"),
50
+ primary_key=True,
51
+ )
52
+ role_id: Mapped[UUID] = mapped_column(
53
+ Uuid,
54
+ ForeignKey("platform_identity_roles.role_id", ondelete="CASCADE"),
55
+ primary_key=True,
56
+ )
57
+
58
+
59
+ class RolePermissionRow(AuditedEntityMixin, IdentityBase):
60
+ __tablename__ = "platform_identity_role_permissions"
61
+
62
+ role_id: Mapped[UUID] = mapped_column(
63
+ Uuid,
64
+ ForeignKey("platform_identity_roles.role_id", ondelete="CASCADE"),
65
+ primary_key=True,
66
+ )
67
+ permission_name: Mapped[str] = mapped_column(String(200), primary_key=True)
68
+
69
+
70
+ class RefreshSessionRow(AuditedEntityMixin, IdentityBase):
71
+ __tablename__ = "platform_identity_refresh_sessions"
72
+
73
+ session_id: Mapped[UUID] = mapped_column(Uuid, primary_key=True)
74
+ user_id: Mapped[UUID] = mapped_column(
75
+ Uuid,
76
+ ForeignKey("platform_identity_users.user_id", ondelete="CASCADE"),
77
+ nullable=False,
78
+ index=True,
79
+ )
80
+ family_id: Mapped[UUID] = mapped_column(Uuid, nullable=False, index=True)
81
+ token_hash: Mapped[str] = mapped_column(String(64), nullable=False, unique=True, index=True)
82
+ family_created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
83
+ last_used_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
84
+ revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
85
+ replaced_by_session_id: Mapped[UUID | None] = mapped_column(Uuid, nullable=True)
@@ -0,0 +1,62 @@
1
+ """Host 显式选择的 Identity 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 (
15
+ IdentityAccessStore,
16
+ IdentityLoginStore,
17
+ IdentityRepository,
18
+ RefreshSessionStore,
19
+ )
20
+ from ..module import IdentityModule
21
+ from .models import IdentityBase
22
+ from .stores import (
23
+ SqlAlchemyIdentityAccessStore,
24
+ SqlAlchemyIdentityLoginStore,
25
+ SqlAlchemyIdentityRepository,
26
+ SqlAlchemyRefreshSessionStore,
27
+ )
28
+
29
+
30
+ @contributes_sqlalchemy_models(
31
+ SqlAlchemyModelRegistration(
32
+ metadata=IdentityBase.metadata,
33
+ migrations_package="python_platform.identity.sqlalchemy.migrations",
34
+ branch_label="platform_identity",
35
+ )
36
+ )
37
+ class SqlAlchemyIdentityModule(AppModule):
38
+ dependencies = (IdentityModule, SqlAlchemyPersistenceModule)
39
+
40
+ def configure(self, context: ConfigureContext) -> None:
41
+ provider = Provider()
42
+ provider.provide(
43
+ SqlAlchemyIdentityRepository,
44
+ provides=IdentityRepository,
45
+ scope=Scope.REQUEST,
46
+ )
47
+ provider.provide(
48
+ SqlAlchemyIdentityAccessStore,
49
+ provides=IdentityAccessStore,
50
+ scope=Scope.APP,
51
+ )
52
+ provider.provide(
53
+ SqlAlchemyIdentityLoginStore,
54
+ provides=IdentityLoginStore,
55
+ scope=Scope.APP,
56
+ )
57
+ provider.provide(
58
+ SqlAlchemyRefreshSessionStore,
59
+ provides=RefreshSessionStore,
60
+ scope=Scope.APP,
61
+ )
62
+ context.services.contribute(provider, reason="Identity SQLAlchemy providers")