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,335 @@
1
+ """Options validation issue sanitization、secret 判定与 source-chain 诊断。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import types
6
+ from collections.abc import Mapping
7
+ from typing import Annotated, Any, Union, get_args, get_origin
8
+
9
+ from pydantic import BaseModel, ValidationError
10
+
11
+ from ..configuration.contracts import ConfigurationContribution
12
+ from ..configuration.root import ConfigurationRoot
13
+ from ..configuration.values import REDACTION_MARKER, _looks_secret, canonical_path
14
+ from .aliases import (
15
+ _lookup_input_path,
16
+ _patch_affects_path,
17
+ _sequence_item_annotation,
18
+ )
19
+ from .models import (
20
+ MISSING,
21
+ OptionsContribution,
22
+ OptionsValidationIssue,
23
+ _OptionPatch,
24
+ _OwnedOptionDefinition,
25
+ )
26
+ from .schema import _field_input_paths, _field_validation_paths, _model_annotation
27
+
28
+ DEFINITION_DEFAULT_SOURCE = "definition-default"
29
+ OPTIONS_PATCH_SOURCE = "options-patch"
30
+ NOT_RETAINED_MARKER = "<not-retained>"
31
+
32
+
33
+ class _OptionsIssueBuilder:
34
+ """将含原始 input 的 Pydantic error 收窄为 framework-owned 安全诊断。"""
35
+
36
+ __slots__ = (
37
+ "_configuration",
38
+ "_input_value",
39
+ "_owned",
40
+ "_patches",
41
+ "_secret_paths",
42
+ )
43
+
44
+ def __init__(
45
+ self,
46
+ owned: _OwnedOptionDefinition,
47
+ *,
48
+ configuration: ConfigurationRoot,
49
+ input_value: object,
50
+ patches: tuple[_OptionPatch, ...],
51
+ secret_paths: frozenset[str],
52
+ ) -> None:
53
+ self._owned = owned
54
+ self._configuration = configuration
55
+ self._input_value = input_value
56
+ self._patches = patches
57
+ self._secret_paths = secret_paths
58
+
59
+ def build(self, original_error: ValidationError) -> tuple[OptionsValidationIssue, ...]:
60
+ issues: list[OptionsValidationIssue] = []
61
+ definition = self._owned.definition
62
+ for error_detail in original_error.errors(
63
+ include_url=False,
64
+ include_context=False,
65
+ include_input=False,
66
+ ):
67
+ relative_parts = _error_location(
68
+ error_detail.get("loc", ()),
69
+ input_value=self._input_value,
70
+ )
71
+ full_parts = (*canonical_path(definition.section), *relative_parts)
72
+ full_path = ".".join(full_parts)
73
+ secret = _path_is_secret(full_parts, self._secret_paths)
74
+ issues.append(
75
+ OptionsValidationIssue(
76
+ field_path=full_path,
77
+ error_type=str(error_detail.get("type", "validation_error")),
78
+ input_value=(REDACTION_MARKER if secret else NOT_RETAINED_MARKER),
79
+ redacted=secret,
80
+ source_chain=self._source_chain(relative_parts, full_parts),
81
+ )
82
+ )
83
+ return tuple(issues)
84
+
85
+ def _source_chain(
86
+ self,
87
+ relative_parts: tuple[str, ...],
88
+ full_path: tuple[str, ...],
89
+ ) -> tuple[OptionsContribution, ...]:
90
+ definition = self._owned.definition
91
+ if _definition_default_affects_path(
92
+ definition.model_type,
93
+ relative_parts,
94
+ input_value=self._input_value,
95
+ ):
96
+ return (
97
+ OptionsContribution(
98
+ source_id=DEFINITION_DEFAULT_SOURCE,
99
+ source_location=self._owned.owner.declaration_source,
100
+ stage=self._owned.stage,
101
+ reason=self._owned.reason,
102
+ ),
103
+ )
104
+ chain = list(_nearest_configuration_contributions(self._configuration, full_path))
105
+ for patch in self._patches:
106
+ if patch.model_type is not definition.model_type:
107
+ continue
108
+ if _patch_affects_path(relative_parts, patch.field_paths):
109
+ chain.append(
110
+ OptionsContribution(
111
+ source_id=OPTIONS_PATCH_SOURCE,
112
+ source_location=patch.owner.declaration_source,
113
+ reason=patch.reason,
114
+ stage=patch.stage,
115
+ )
116
+ )
117
+ return tuple(chain)
118
+
119
+
120
+ def _sanitized_validation_error(
121
+ model_type: type[BaseModel],
122
+ issues: tuple[OptionsValidationIssue, ...],
123
+ ) -> ValidationError:
124
+ line_errors: list[Any] = [
125
+ {
126
+ "type": "assertion_error",
127
+ "loc": tuple(issue.field_path.split(".")),
128
+ "input": issue.input_value,
129
+ "ctx": {
130
+ "error": AssertionError(f"validation failed with error type {issue.error_type}")
131
+ },
132
+ }
133
+ for issue in issues
134
+ ]
135
+ model_name = f"{model_type.__module__}.{model_type.__qualname__}"
136
+ return ValidationError.from_exception_data(
137
+ model_name,
138
+ line_errors,
139
+ hide_input=True,
140
+ )
141
+
142
+
143
+ def _error_location(
144
+ location: object,
145
+ *,
146
+ input_value: object,
147
+ ) -> tuple[str, ...]:
148
+ raw_parts = _normalized_error_location_parts(location)
149
+ parts: list[str] = []
150
+ current = input_value
151
+ for index, part in enumerate(raw_parts):
152
+ include, current = _resolve_error_location_part(
153
+ current,
154
+ part,
155
+ remaining=raw_parts[index + 1 :],
156
+ )
157
+ if include:
158
+ parts.append(part)
159
+ return tuple(parts)
160
+
161
+
162
+ def _normalized_error_location_parts(location: object) -> tuple[str, ...]:
163
+ if not isinstance(location, tuple):
164
+ return ()
165
+ raw_parts: list[str] = []
166
+ for item in location:
167
+ if isinstance(item, str):
168
+ raw_parts.append(item)
169
+ elif isinstance(item, int):
170
+ raw_parts.append(str(item))
171
+ return tuple(raw_parts)
172
+
173
+
174
+ def _resolve_error_location_part(
175
+ current: object,
176
+ part: str,
177
+ *,
178
+ remaining: tuple[str, ...],
179
+ ) -> tuple[bool, object]:
180
+ if isinstance(current, Mapping):
181
+ if part in current:
182
+ return True, current[part]
183
+ if part in current.values() or any(later in current for later in remaining):
184
+ return False, current
185
+ return True, MISSING
186
+ if isinstance(current, (list, tuple)) and part.isdecimal():
187
+ item_index = int(part)
188
+ if item_index < len(current):
189
+ return True, current[item_index]
190
+ if current is MISSING:
191
+ return True, current
192
+ return False, current
193
+
194
+
195
+ def _path_is_secret(path: tuple[str, ...], secret_paths: frozenset[str]) -> bool:
196
+ compact_path = tuple(part for part in path if not part.isdecimal())
197
+ for secret in secret_paths:
198
+ secret_path = tuple(secret.split("."))
199
+ if (
200
+ _path_is_prefix(secret_path, compact_path)
201
+ or _path_is_prefix(compact_path, secret_path)
202
+ or _path_is_ordered_subsequence(secret_path, compact_path)
203
+ or _path_is_ordered_subsequence(compact_path, secret_path)
204
+ ):
205
+ return True
206
+ return _looks_secret(path)
207
+
208
+
209
+ def _path_is_prefix(prefix: tuple[str, ...], path: tuple[str, ...]) -> bool:
210
+ return len(prefix) <= len(path) and path[: len(prefix)] == prefix
211
+
212
+
213
+ def _path_is_ordered_subsequence(
214
+ candidate: tuple[str, ...],
215
+ path: tuple[str, ...],
216
+ ) -> bool:
217
+ if not candidate:
218
+ return False
219
+ cursor = iter(path)
220
+ return all(any(part == expected for part in cursor) for expected in candidate)
221
+
222
+
223
+ def _configuration_contribution(
224
+ contribution: ConfigurationContribution,
225
+ ) -> OptionsContribution:
226
+ return OptionsContribution(
227
+ source_id=contribution.source_id.value,
228
+ source_location=contribution.source_location,
229
+ )
230
+
231
+
232
+ def _nearest_configuration_contributions(
233
+ configuration: ConfigurationRoot,
234
+ full_path: tuple[str, ...],
235
+ ) -> tuple[OptionsContribution, ...]:
236
+ parts = full_path
237
+ for length in range(len(parts), 0, -1):
238
+ try:
239
+ provenance = configuration._provenance_at(parts[:length])
240
+ except (KeyError, ValueError):
241
+ continue
242
+ return tuple(_configuration_contribution(item) for item in provenance.chain)
243
+ return ()
244
+
245
+
246
+ def _definition_default_affects_path(
247
+ model_type: type[BaseModel],
248
+ path: tuple[str, ...],
249
+ *,
250
+ input_value: object,
251
+ ) -> bool:
252
+ if not path:
253
+ return False
254
+ for field_name, field in model_type.model_fields.items():
255
+ for input_path in _field_input_paths(field_name, field):
256
+ if path[: len(input_path)] != input_path:
257
+ continue
258
+ supplied = MISSING
259
+ if isinstance(input_value, Mapping):
260
+ for validation_path in _field_validation_paths(
261
+ model_type,
262
+ field_name,
263
+ field,
264
+ ):
265
+ supplied = _lookup_input_path(input_value, validation_path)
266
+ if supplied is not MISSING:
267
+ break
268
+ if supplied is MISSING:
269
+ return not field.is_required()
270
+ remaining = path[len(input_path) :]
271
+ if not remaining:
272
+ return False
273
+ return _annotation_default_affects_path(
274
+ field.annotation,
275
+ remaining,
276
+ input_value=supplied,
277
+ )
278
+ return False
279
+
280
+
281
+ def _annotation_default_affects_path(
282
+ annotation: object,
283
+ path: tuple[str, ...],
284
+ *,
285
+ input_value: object,
286
+ ) -> bool:
287
+ origin = get_origin(annotation)
288
+ if origin is Annotated:
289
+ arguments = get_args(annotation)
290
+ return bool(arguments) and _annotation_default_affects_path(
291
+ arguments[0],
292
+ path,
293
+ input_value=input_value,
294
+ )
295
+
296
+ model_type = _model_annotation(annotation)
297
+ if model_type is not None:
298
+ return _definition_default_affects_path(
299
+ model_type,
300
+ path,
301
+ input_value=input_value,
302
+ )
303
+
304
+ if origin in (Union, types.UnionType):
305
+ return any(
306
+ _annotation_default_affects_path(
307
+ argument,
308
+ path,
309
+ input_value=input_value,
310
+ )
311
+ for argument in get_args(annotation)
312
+ )
313
+
314
+ arguments = get_args(annotation)
315
+ if origin is Mapping and len(arguments) == 2 and path:
316
+ nested_input = MISSING
317
+ if isinstance(input_value, Mapping):
318
+ nested_input = input_value.get(path[0], MISSING)
319
+ return _annotation_default_affects_path(
320
+ arguments[1],
321
+ path[1:],
322
+ input_value=nested_input,
323
+ )
324
+ if origin in (tuple, frozenset) and path and path[0].isdecimal():
325
+ index = int(path[0])
326
+ nested_input = MISSING
327
+ if isinstance(input_value, (list, tuple)) and index < len(input_value):
328
+ nested_input = input_value[index]
329
+ item_annotation = _sequence_item_annotation(arguments, index)
330
+ return item_annotation is not None and _annotation_default_affects_path(
331
+ item_annotation,
332
+ path[1:],
333
+ input_value=nested_input,
334
+ )
335
+ return False
@@ -0,0 +1,114 @@
1
+ """Typed Options definitions、patch facts 与 immutable snapshots。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping
6
+ from dataclasses import dataclass, field
7
+ from types import MappingProxyType
8
+ from typing import Generic, TypeVar, cast
9
+
10
+ from pydantic import BaseModel, ConfigDict
11
+
12
+ from ..diagnostics import LifecycleStage, SourceLocation
13
+ from ..errors import UnknownOptionsError
14
+ from ..modularity import ModuleDescriptor
15
+
16
+ TOptions = TypeVar("TOptions", bound=BaseModel)
17
+ TDeclaredOptions_co = TypeVar(
18
+ "TDeclaredOptions_co",
19
+ bound=BaseModel,
20
+ covariant=True,
21
+ )
22
+ MISSING = object()
23
+
24
+
25
+ class BaseOptions(BaseModel):
26
+ """配置 model 的统一默认契约,字段与 validator 沿用原生 Pydantic。"""
27
+
28
+ model_config = ConfigDict(frozen=True, validate_default=True)
29
+
30
+
31
+ @dataclass(frozen=True, slots=True)
32
+ class Options(Generic[TDeclaredOptions_co]):
33
+ """Application 内共享的最终配置快照,泛型参数也是原生 DI dependency key。"""
34
+
35
+ value: TDeclaredOptions_co = field(repr=False)
36
+
37
+
38
+ @dataclass(frozen=True, slots=True)
39
+ class _OptionDefinition(Generic[TDeclaredOptions_co]):
40
+ model_type: type[TDeclaredOptions_co]
41
+ section: str
42
+ secret_paths: frozenset[str] = frozenset()
43
+
44
+
45
+ @dataclass(frozen=True, slots=True)
46
+ class OptionsContribution:
47
+ source_id: str
48
+ source_location: SourceLocation
49
+ reason: str | None = None
50
+ stage: LifecycleStage | None = None
51
+
52
+
53
+ @dataclass(frozen=True, slots=True)
54
+ class OptionsValidationIssue:
55
+ field_path: str
56
+ error_type: str
57
+ input_value: str
58
+ redacted: bool
59
+ source_chain: tuple[OptionsContribution, ...]
60
+
61
+
62
+ @dataclass(frozen=True, slots=True)
63
+ class _OwnedOptionDefinition:
64
+ definition: _OptionDefinition[BaseModel]
65
+ owner: ModuleDescriptor
66
+ stage: LifecycleStage
67
+ reason: str | None = None
68
+
69
+
70
+ @dataclass(frozen=True, slots=True)
71
+ class _OptionPatch:
72
+ model_type: type[BaseModel]
73
+ values: Mapping[str, object]
74
+ field_paths: frozenset[tuple[str, ...]]
75
+ owner: ModuleDescriptor
76
+ stage: LifecycleStage
77
+ reason: str | None
78
+
79
+
80
+ class _OptionsSnapshotBase:
81
+ __slots__ = ("_kind", "_values")
82
+
83
+ def __init__(
84
+ self,
85
+ values: Mapping[type[BaseModel], BaseModel],
86
+ *,
87
+ kind: str,
88
+ ) -> None:
89
+ self._values = MappingProxyType(dict(values))
90
+ self._kind = kind
91
+
92
+ def __repr__(self) -> str:
93
+ return f"{type(self).__name__}(models={len(self._values)})"
94
+
95
+ def get(self, model_type: type[TOptions]) -> TOptions:
96
+ try:
97
+ value = self._values[model_type]
98
+ except KeyError:
99
+ raise UnknownOptionsError(model_type, option_kind=self._kind) from None
100
+ # model_type 是索引 identity,只在该 key 下保存同一具体 Pydantic type。
101
+ return cast(TOptions, value)
102
+
103
+
104
+ class OptionsSnapshot(_OptionsSnapshotBase):
105
+ """普通 Options 的 Application-local typed immutable lookup。"""
106
+
107
+ @property
108
+ def values(self) -> Mapping[type[BaseModel], BaseModel]:
109
+ """已完成绑定的正式定义;DI 消费此视图,不维护第二份类型清单。"""
110
+ return self._values
111
+
112
+
113
+ class PreOptionsSnapshot(_OptionsSnapshotBase):
114
+ """全局 pre_configure barrier 后发布的 typed immutable lookup。"""