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,331 @@
1
+ """多来源 Options input alias 的确定性优先级裁决。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import types
6
+ from collections.abc import Mapping, MutableMapping
7
+ from typing import Annotated, Literal, Union, cast, get_args, get_origin
8
+
9
+ from pydantic import BaseModel
10
+
11
+ from ..configuration.merge import _source_ordinal
12
+ from ..configuration.root import ConfigurationRoot
13
+ from ..configuration.values import canonical_path
14
+ from .models import MISSING, _OptionPatch
15
+ from .schema import (
16
+ _annotation_discriminator,
17
+ _direct_model_types,
18
+ _field_validation_paths,
19
+ _model_annotation,
20
+ )
21
+
22
+
23
+ def _prefer_higher_priority_alias_inputs(
24
+ model_type: type[BaseModel],
25
+ input_value: object,
26
+ *,
27
+ configuration: ConfigurationRoot,
28
+ section: str,
29
+ patches: tuple[_OptionPatch, ...],
30
+ ) -> None:
31
+ if not isinstance(input_value, MutableMapping):
32
+ return
33
+ _prefer_model_alias_inputs(
34
+ model_type,
35
+ cast(MutableMapping[str, object], input_value),
36
+ root_model_type=model_type,
37
+ prefix=(),
38
+ configuration=configuration,
39
+ section=section,
40
+ patches=patches,
41
+ )
42
+
43
+
44
+ def _prefer_model_alias_inputs(
45
+ model_type: type[BaseModel],
46
+ values: MutableMapping[str, object],
47
+ *,
48
+ root_model_type: type[BaseModel],
49
+ prefix: tuple[str, ...],
50
+ configuration: ConfigurationRoot,
51
+ section: str,
52
+ patches: tuple[_OptionPatch, ...],
53
+ ) -> None:
54
+ for field_name, field in model_type.model_fields.items():
55
+ paths = _field_validation_paths(model_type, field_name, field)
56
+ present: list[tuple[int, tuple[str, ...], object]] = []
57
+ for index, path in enumerate(paths):
58
+ value = _lookup_input_path(values, path)
59
+ if value is not MISSING:
60
+ present.append((index, path, value))
61
+ if not present:
62
+ continue
63
+
64
+ winner = max(
65
+ present,
66
+ key=lambda item: (
67
+ *_input_path_rank(
68
+ (*prefix, *item[1]),
69
+ root_model_type=root_model_type,
70
+ configuration=configuration,
71
+ section=section,
72
+ patches=patches,
73
+ ),
74
+ -item[0],
75
+ ),
76
+ )
77
+ for candidate in present:
78
+ if candidate is not winner:
79
+ _delete_input_path(values, candidate[1])
80
+
81
+ _prefer_annotation_alias_inputs(
82
+ field.annotation,
83
+ winner[2],
84
+ discriminator=field.discriminator,
85
+ root_model_type=root_model_type,
86
+ prefix=(*prefix, *winner[1]),
87
+ configuration=configuration,
88
+ section=section,
89
+ patches=patches,
90
+ )
91
+
92
+
93
+ def _prefer_annotation_alias_inputs(
94
+ annotation: object,
95
+ value: object,
96
+ *,
97
+ discriminator: object,
98
+ root_model_type: type[BaseModel],
99
+ prefix: tuple[str, ...],
100
+ configuration: ConfigurationRoot,
101
+ section: str,
102
+ patches: tuple[_OptionPatch, ...],
103
+ ) -> None:
104
+ origin = get_origin(annotation)
105
+ if origin is Annotated:
106
+ arguments = get_args(annotation)
107
+ if arguments:
108
+ _prefer_annotation_alias_inputs(
109
+ arguments[0],
110
+ value,
111
+ discriminator=discriminator or _annotation_discriminator(annotation),
112
+ root_model_type=root_model_type,
113
+ prefix=prefix,
114
+ configuration=configuration,
115
+ section=section,
116
+ patches=patches,
117
+ )
118
+ return
119
+
120
+ model_type = _model_annotation(annotation)
121
+ if model_type is not None:
122
+ if isinstance(value, MutableMapping):
123
+ _prefer_model_alias_inputs(
124
+ model_type,
125
+ cast(MutableMapping[str, object], value),
126
+ root_model_type=root_model_type,
127
+ prefix=prefix,
128
+ configuration=configuration,
129
+ section=section,
130
+ patches=patches,
131
+ )
132
+ return
133
+
134
+ if origin in (Union, types.UnionType):
135
+ _prefer_union_alias_inputs(
136
+ annotation,
137
+ value,
138
+ discriminator=discriminator,
139
+ root_model_type=root_model_type,
140
+ prefix=prefix,
141
+ configuration=configuration,
142
+ section=section,
143
+ patches=patches,
144
+ )
145
+ return
146
+
147
+ _prefer_container_alias_inputs(
148
+ origin,
149
+ get_args(annotation),
150
+ value,
151
+ root_model_type=root_model_type,
152
+ prefix=prefix,
153
+ configuration=configuration,
154
+ section=section,
155
+ patches=patches,
156
+ )
157
+
158
+
159
+ def _prefer_union_alias_inputs(
160
+ annotation: object,
161
+ value: object,
162
+ *,
163
+ discriminator: object,
164
+ root_model_type: type[BaseModel],
165
+ prefix: tuple[str, ...],
166
+ configuration: ConfigurationRoot,
167
+ section: str,
168
+ patches: tuple[_OptionPatch, ...],
169
+ ) -> None:
170
+ model_choices = _direct_model_types(annotation)
171
+ selected: type[BaseModel] | None = None
172
+ if len(model_choices) == 1:
173
+ selected = model_choices[0]
174
+ elif isinstance(discriminator, str) and isinstance(value, Mapping):
175
+ selected = _select_discriminated_model(model_choices, value, discriminator)
176
+ if selected is not None and isinstance(value, MutableMapping):
177
+ _prefer_model_alias_inputs(
178
+ selected,
179
+ cast(MutableMapping[str, object], value),
180
+ root_model_type=root_model_type,
181
+ prefix=prefix,
182
+ configuration=configuration,
183
+ section=section,
184
+ patches=patches,
185
+ )
186
+
187
+
188
+ def _prefer_container_alias_inputs(
189
+ origin: object,
190
+ arguments: tuple[object, ...],
191
+ value: object,
192
+ *,
193
+ root_model_type: type[BaseModel],
194
+ prefix: tuple[str, ...],
195
+ configuration: ConfigurationRoot,
196
+ section: str,
197
+ patches: tuple[_OptionPatch, ...],
198
+ ) -> None:
199
+ if origin is Mapping and len(arguments) == 2 and isinstance(value, Mapping):
200
+ value_annotation = arguments[1]
201
+ for key, item in value.items():
202
+ if isinstance(key, str):
203
+ _prefer_annotation_alias_inputs(
204
+ value_annotation,
205
+ item,
206
+ discriminator=None,
207
+ root_model_type=root_model_type,
208
+ prefix=(*prefix, key),
209
+ configuration=configuration,
210
+ section=section,
211
+ patches=patches,
212
+ )
213
+ return
214
+ if origin in (tuple, frozenset) and isinstance(value, (list, tuple, frozenset)):
215
+ for index, item in enumerate(value):
216
+ item_annotation = _sequence_item_annotation(arguments, index)
217
+ if item_annotation is not None:
218
+ _prefer_annotation_alias_inputs(
219
+ item_annotation,
220
+ item,
221
+ discriminator=None,
222
+ root_model_type=root_model_type,
223
+ prefix=(*prefix, str(index)),
224
+ configuration=configuration,
225
+ section=section,
226
+ patches=patches,
227
+ )
228
+
229
+
230
+ def _select_discriminated_model(
231
+ model_choices: tuple[type[BaseModel], ...],
232
+ values: Mapping[str, object],
233
+ discriminator: str,
234
+ ) -> type[BaseModel] | None:
235
+ for model_type in model_choices:
236
+ field = model_type.model_fields.get(discriminator)
237
+ if field is None:
238
+ continue
239
+ tag = MISSING
240
+ for path in _field_validation_paths(model_type, discriminator, field):
241
+ tag = _lookup_input_path(values, path)
242
+ if tag is not MISSING:
243
+ break
244
+ if tag is not MISSING and tag in _literal_values(field.annotation):
245
+ return model_type
246
+ return None
247
+
248
+
249
+ def _literal_values(annotation: object) -> tuple[object, ...]:
250
+ origin = get_origin(annotation)
251
+ if origin is Annotated:
252
+ arguments = get_args(annotation)
253
+ return _literal_values(arguments[0]) if arguments else ()
254
+ if origin is Literal:
255
+ return get_args(annotation)
256
+ return ()
257
+
258
+
259
+ def _sequence_item_annotation(
260
+ arguments: tuple[object, ...],
261
+ index: int,
262
+ ) -> object | None:
263
+ if not arguments:
264
+ return None
265
+ if len(arguments) == 2 and arguments[1] is Ellipsis:
266
+ return arguments[0]
267
+ if index < len(arguments):
268
+ return arguments[index]
269
+ return None
270
+
271
+
272
+ def _input_path_rank(
273
+ relative_path: tuple[str, ...],
274
+ *,
275
+ root_model_type: type[BaseModel],
276
+ configuration: ConfigurationRoot,
277
+ section: str,
278
+ patches: tuple[_OptionPatch, ...],
279
+ ) -> tuple[int, int]:
280
+ patch_rank = -1
281
+ for index, patch in enumerate(patches):
282
+ if patch.model_type is root_model_type and _patch_affects_path(
283
+ relative_path,
284
+ patch.field_paths,
285
+ ):
286
+ patch_rank = index
287
+ if patch_rank >= 0:
288
+ return (1, patch_rank)
289
+
290
+ full_parts = (*canonical_path(section), *relative_path)
291
+ for length in range(len(full_parts), 0, -1):
292
+ try:
293
+ provenance = configuration._provenance_at(full_parts[:length])
294
+ except (KeyError, ValueError):
295
+ continue
296
+ return (0, _source_ordinal(provenance.effective_source.source_id))
297
+ return (-1, -1)
298
+
299
+
300
+ def _lookup_input_path(
301
+ values: Mapping[str, object],
302
+ path: tuple[str, ...],
303
+ ) -> object:
304
+ current: object = values
305
+ for part in path:
306
+ if not isinstance(current, Mapping) or part not in current:
307
+ return MISSING
308
+ current = current[part]
309
+ return current
310
+
311
+
312
+ def _delete_input_path(
313
+ values: MutableMapping[str, object],
314
+ path: tuple[str, ...],
315
+ ) -> None:
316
+ current: object = values
317
+ for part in path[:-1]:
318
+ if not isinstance(current, MutableMapping) or part not in current:
319
+ return
320
+ current = current[part]
321
+ if isinstance(current, MutableMapping):
322
+ current.pop(path[-1], None)
323
+
324
+
325
+ def _patch_affects_path(path: tuple[str, ...], candidates: frozenset[tuple[str, ...]]) -> bool:
326
+ if not path:
327
+ return bool(candidates)
328
+ return any(
329
+ path[: len(candidate)] == candidate or candidate[: len(path)] == path
330
+ for candidate in candidates
331
+ )
@@ -0,0 +1,62 @@
1
+ """限定到当前 lifecycle invocation 的声明与程序覆盖入口。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping
6
+ from typing import TYPE_CHECKING
7
+
8
+ from ..diagnostics import LifecycleStage
9
+ from ..errors import OptionsContributionClosedError
10
+ from ..modularity import ModuleDescriptor
11
+ from .models import TOptions
12
+
13
+ if TYPE_CHECKING:
14
+ from .registry import _OptionsRegistry
15
+
16
+
17
+ class _OptionsContributor:
18
+ __slots__ = ("_active", "_kind", "_owner", "_registry", "_stage")
19
+
20
+ def __init__(
21
+ self,
22
+ registry: _OptionsRegistry,
23
+ *,
24
+ owner: ModuleDescriptor,
25
+ stage: LifecycleStage,
26
+ kind: str,
27
+ ) -> None:
28
+ self._registry = registry
29
+ self._owner = owner
30
+ self._stage = stage
31
+ self._kind = kind
32
+ self._active = True
33
+
34
+ def __call__(
35
+ self,
36
+ model_type: type[TOptions],
37
+ *,
38
+ section: str | None = None,
39
+ values: Mapping[str, object] | None = None,
40
+ secret_paths: frozenset[str] = frozenset(),
41
+ reason: str | None = None,
42
+ ) -> None:
43
+ if not self._active:
44
+ raise OptionsContributionClosedError(option_kind=self._kind, stage=self._stage.value)
45
+ if section is None and (values is None or secret_paths):
46
+ raise ValueError("Options requires a section declaration or values contribution")
47
+ if section is not None:
48
+ self._registry.declare(
49
+ model_type,
50
+ section,
51
+ secret_paths=secret_paths,
52
+ owner=self._owner,
53
+ stage=self._stage,
54
+ reason=reason,
55
+ )
56
+ if values is not None:
57
+ self._registry.patch(
58
+ model_type, values, owner=self._owner, stage=self._stage, reason=reason
59
+ )
60
+
61
+ def _close(self) -> None:
62
+ self._active = False
@@ -0,0 +1,48 @@
1
+ """Options patch 的独立容器副本;不向贡献者暴露已发布 snapshot 的可变引用。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping
6
+ from typing import cast
7
+
8
+
9
+ def _merge_patch_input(base: object, patch: Mapping[str, object]) -> dict[str, object]:
10
+ merged = {} if not isinstance(base, Mapping) else cast(dict[str, object], _thaw_value(base))
11
+ _merge_mapping_values(merged, patch)
12
+ return merged
13
+
14
+
15
+ def _merge_mapping_values(
16
+ target: dict[str, object],
17
+ incoming: Mapping[str, object],
18
+ ) -> None:
19
+ for key, value in incoming.items():
20
+ current = target.get(key)
21
+ if isinstance(current, dict) and isinstance(value, Mapping):
22
+ _merge_mapping_values(current, value)
23
+ else:
24
+ target[key] = _thaw_value(value)
25
+
26
+
27
+ def _thaw_value(value: object) -> object:
28
+ if isinstance(value, Mapping):
29
+ return {key: _thaw_value(item) for key, item in value.items()}
30
+ if isinstance(value, tuple):
31
+ return tuple(_thaw_value(item) for item in value)
32
+ if isinstance(value, frozenset):
33
+ return frozenset(_thaw_value(item) for item in value)
34
+ return value
35
+
36
+
37
+ def _mapping_paths(
38
+ values: Mapping[str, object],
39
+ prefix: tuple[str, ...] = (),
40
+ ) -> set[tuple[str, ...]]:
41
+ paths: set[tuple[str, ...]] = set()
42
+ for key, value in values.items():
43
+ current = (*prefix, key)
44
+ if isinstance(value, Mapping) and value:
45
+ paths.update(_mapping_paths(value, current))
46
+ else:
47
+ paths.add(current)
48
+ return paths
@@ -0,0 +1,214 @@
1
+ """沿有限输入和正式 typing 解释 Mapping 边界;分支路径不跨 Union 共享。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import types
6
+ from collections.abc import Mapping
7
+ from dataclasses import dataclass
8
+ from typing import Annotated, Union, get_args, get_origin
9
+
10
+ from pydantic import BaseModel
11
+
12
+ from ..configuration.values import MISSING, canonical_segment
13
+ from .aliases import _literal_values, _sequence_item_annotation
14
+ from .schema import _annotation_discriminator, _direct_model_types, _field_validation_paths
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class _InputReference:
19
+ """只读来源层视图;字段按 canonical 查找,数据键按原名查找。"""
20
+
21
+ layers: tuple[object, ...]
22
+
23
+ def child(self, key: str, *, field: bool) -> object:
24
+ found: list[object] = []
25
+ for layer in self.layers:
26
+ if not isinstance(layer, Mapping):
27
+ found.clear()
28
+ continue
29
+ values = _field_view(layer) if field else layer
30
+ if key in values:
31
+ child = values[key]
32
+ if not isinstance(child, Mapping):
33
+ found.clear()
34
+ found.append(child)
35
+ if not found:
36
+ return MISSING
37
+ return _InputReference(tuple(found)) if isinstance(found[-1], Mapping) else found[-1]
38
+
39
+
40
+ def _mapping_input_paths(
41
+ model_type: type[BaseModel], value: object, reference: object, selected: object = None
42
+ ) -> frozenset[tuple[str | int | None, ...]]:
43
+ paths: set[tuple[str | int | None, ...]] = set()
44
+ _visit(model_type, value, reference, (), paths, selected=selected)
45
+ return frozenset(paths)
46
+
47
+
48
+ def _visit(
49
+ annotation: object,
50
+ value: object,
51
+ reference: object,
52
+ prefix: tuple[str | int | None, ...],
53
+ paths: set[tuple[str | int | None, ...]],
54
+ discriminator: object = None,
55
+ selected: object = None,
56
+ ) -> None:
57
+ origin, args = get_origin(annotation), get_args(annotation)
58
+ if origin is Annotated:
59
+ _visit(
60
+ args[0],
61
+ value,
62
+ reference,
63
+ prefix,
64
+ paths,
65
+ discriminator or _annotation_discriminator(annotation),
66
+ selected,
67
+ )
68
+ elif origin in (Union, types.UnionType):
69
+ _visit_union(annotation, value, reference, prefix, paths, discriminator, selected)
70
+ elif origin in (Mapping, dict) and isinstance(value, Mapping):
71
+ paths.add(prefix)
72
+ for key, child in value.items():
73
+ hint = (
74
+ reference.child(key, field=False)
75
+ if isinstance(reference, _InputReference)
76
+ else reference.get(key, child)
77
+ if isinstance(reference, Mapping)
78
+ else child
79
+ )
80
+ choice = selected.get(key) if isinstance(selected, Mapping) else None
81
+ _visit(args[1], child, hint, (*prefix, key), paths, selected=choice)
82
+ elif isinstance(annotation, type) and issubclass(annotation, BaseModel):
83
+ _visit_fields(annotation, value, reference, prefix, paths, selected)
84
+ elif origin in (tuple, frozenset, list, set) and isinstance(value, (tuple, list, frozenset)):
85
+ for index, child in enumerate(value):
86
+ item = _sequence_item_annotation(args, index)
87
+ hint = (
88
+ reference[index]
89
+ if isinstance(reference, (tuple, list)) and index < len(reference)
90
+ else child
91
+ )
92
+ if item is not None:
93
+ choice = (
94
+ selected[index]
95
+ if isinstance(selected, tuple) and index < len(selected)
96
+ else None
97
+ )
98
+ _visit(item, child, hint, (*prefix, index), paths, selected=choice)
99
+
100
+
101
+ def _visit_union(
102
+ annotation: object,
103
+ value: object,
104
+ reference: object,
105
+ prefix: tuple[str | int | None, ...],
106
+ paths: set[tuple[str | int | None, ...]],
107
+ discriminator: object,
108
+ selected: object,
109
+ ) -> None:
110
+ choices = _direct_model_types(annotation)
111
+ if isinstance(discriminator, str) and isinstance(reference, (Mapping, _InputReference)):
112
+ # 只解释标签所属的分支,不在绑定前执行用户 validator 或替代原生 Union 校验。
113
+ branch_type = _select_input_model(choices, reference, discriminator)
114
+ if branch_type is not None:
115
+ _visit(branch_type, value, reference, prefix, paths, selected=selected)
116
+ return
117
+ if isinstance(selected, BaseModel) and type(selected) in choices:
118
+ _visit(type(selected), value, reference, prefix, paths, selected=selected)
119
+ return
120
+ branch_paths = []
121
+ for item in get_args(annotation):
122
+ if item is type(None) or (isinstance(value, Mapping) and item in (str, int, float, bool)):
123
+ continue
124
+ branch: set[tuple[str | int | None, ...]] = set()
125
+ _visit(item, value, reference, prefix, branch)
126
+ branch_paths.append(branch)
127
+ if branch_paths:
128
+ if all(branch == branch_paths[0] for branch in branch_paths):
129
+ paths.update(branch_paths[0])
130
+ else:
131
+ # 未标记 Union 的选择归原生 validator。暂时保留每一层原始输入键,
132
+ # 各候选模型在自己的原生验证入口归一化,成功后按实际选择发布 Configuration。
133
+ _preserve_mapping_paths(value, prefix, paths)
134
+
135
+
136
+ def _preserve_mapping_paths(
137
+ value: object,
138
+ prefix: tuple[str | int | None, ...],
139
+ paths: set[tuple[str | int | None, ...]],
140
+ ) -> None:
141
+ if isinstance(value, Mapping):
142
+ paths.add(prefix)
143
+ for key, child in value.items():
144
+ _preserve_mapping_paths(child, (*prefix, key), paths)
145
+ elif isinstance(value, (tuple, list)):
146
+ for index, child in enumerate(value):
147
+ _preserve_mapping_paths(child, (*prefix, index), paths)
148
+
149
+
150
+ def _select_input_model(
151
+ choices: tuple[type[BaseModel], ...],
152
+ reference: object,
153
+ discriminator: str,
154
+ ) -> type[BaseModel] | None:
155
+ for model in choices:
156
+ field = model.model_fields[discriminator]
157
+ for path in _field_validation_paths(model, discriminator, field):
158
+ tag = _field_at(reference, path)
159
+ if tag is not MISSING:
160
+ if tag in _literal_values(field.annotation):
161
+ return model
162
+ break
163
+ return None
164
+
165
+
166
+ def _field_view(value: Mapping[object, object]) -> dict[str, object]:
167
+ fields: dict[str, object] = {}
168
+ for key, child in value.items():
169
+ try:
170
+ name = canonical_segment(key)
171
+ except (TypeError, ValueError):
172
+ # 此时可能仍在枚举 Union 候选;其他分支的数据键不是当前模型字段。
173
+ # 真正选用模型时由 normalize_mapping 拒绝无效字段,不在探测路径时裁决。
174
+ continue
175
+ fields[name] = child
176
+ return fields
177
+
178
+
179
+ def _field_at(value: object, path: tuple[str, ...]) -> object:
180
+ for segment in path:
181
+ if isinstance(value, _InputReference):
182
+ value = value.child(segment, field=True)
183
+ elif isinstance(value, Mapping):
184
+ value = _field_view(value).get(segment, MISSING)
185
+ else:
186
+ return MISSING
187
+ return value
188
+
189
+
190
+ def _visit_fields(
191
+ model: type[BaseModel],
192
+ value: object,
193
+ reference: object,
194
+ prefix: tuple[str | int | None, ...],
195
+ paths: set[tuple[str | int | None, ...]],
196
+ selected: object,
197
+ ) -> None:
198
+ if not isinstance(value, Mapping):
199
+ return
200
+ # 递归的终点是输入叶子;再次遇到同一模型类型不代表再次遇到同一输入节点。
201
+ for name, field in model.model_fields.items():
202
+ for path in _field_validation_paths(model, name, field):
203
+ child = _field_at(value, path)
204
+ if child is not MISSING:
205
+ hint = _field_at(reference, path)
206
+ _visit(
207
+ field.annotation,
208
+ child,
209
+ child if hint is MISSING else hint,
210
+ (*prefix, *path),
211
+ paths,
212
+ field.discriminator,
213
+ getattr(selected, name) if isinstance(selected, model) else None,
214
+ )