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,115 @@
1
+ """SettingDefinition 的名称、类型校验与不可变默认值。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Generic, TypeVar, cast, get_args, get_origin
6
+
7
+ from pydantic import BaseModel, Secret, SecretBytes, SecretStr, TypeAdapter, ValidationError
8
+
9
+ from ..configuration.immutability import (
10
+ _serialization_value,
11
+ freeze_typed_value,
12
+ validate_immutable_annotation,
13
+ )
14
+ from ..configuration.values import _looks_secret_name, canonical_path
15
+ from .errors import RuntimeSettingError, SettingDefinitionError
16
+
17
+ _T = TypeVar("_T")
18
+ _MISSING = object()
19
+
20
+
21
+ class SettingDefinition(Generic[_T]):
22
+ __slots__ = ("_adapter", "_default", "_name", "_value_type")
23
+
24
+ def __init__(self, name: str, value_type: type[_T], *, default: object = _MISSING) -> None:
25
+ _validate_setting_name(name)
26
+ if not isinstance(value_type, type):
27
+ raise SettingDefinitionError(setting=name, reason="value_type must be a runtime type")
28
+ if _contains_secret_type(value_type):
29
+ raise SettingDefinitionError(
30
+ setting=name, reason="runtime settings cannot contain secrets"
31
+ )
32
+ try:
33
+ validate_immutable_annotation(value_type)
34
+ adapter: TypeAdapter[_T] = TypeAdapter(value_type)
35
+ validated_default = (
36
+ _MISSING
37
+ if default is _MISSING
38
+ else freeze_typed_value(adapter.validate_python(default), allow_secrets=False)
39
+ )
40
+ except (TypeError, ValidationError) as original_error:
41
+ raise SettingDefinitionError(
42
+ setting=name,
43
+ reason=f"default is invalid: {type(original_error).__name__}",
44
+ ) from None
45
+ self._name = name
46
+ self._value_type = value_type
47
+ self._adapter = adapter
48
+ self._default = validated_default
49
+
50
+ @property
51
+ def name(self) -> str:
52
+ return self._name
53
+
54
+ @property
55
+ def value_type(self) -> type[_T]:
56
+ return self._value_type
57
+
58
+ @property
59
+ def has_default(self) -> bool:
60
+ return self._default is not _MISSING
61
+
62
+ def _validate_python(self, value: object) -> _T:
63
+ return cast(
64
+ _T, freeze_typed_value(self._adapter.validate_python(value), allow_secrets=False)
65
+ )
66
+
67
+ def _validate_json(self, value: str) -> _T:
68
+ return cast(_T, freeze_typed_value(self._adapter.validate_json(value), allow_secrets=False))
69
+
70
+ def _dump_json(self, value: object) -> tuple[_T, str]:
71
+ validated = self._validate_python(value)
72
+ # frozen snapshot 中的 MappingProxyType 不是 Pydantic 原生 dict serializer 输入。
73
+ # 只在 I/O 边界复制容器,继续用同一 TypeAdapter 编码,不暴露可变副本。
74
+ serialized = self._adapter.dump_json(cast(_T, _serialization_value(validated))).decode(
75
+ "utf-8"
76
+ )
77
+ return validated, serialized
78
+
79
+ def _default_value(self) -> _T:
80
+ if self._default is _MISSING:
81
+ raise RuntimeSettingError(
82
+ setting=self.name,
83
+ operation="get",
84
+ error_type="MissingSettingValue",
85
+ )
86
+ return cast(_T, self._default)
87
+
88
+
89
+ def _validate_setting_name(name: object) -> None:
90
+ if isinstance(name, str) and _looks_secret_name(name):
91
+ raise SettingDefinitionError(setting=name, reason="runtime settings cannot store secrets")
92
+ try:
93
+ if not isinstance(name, str) or ".".join(canonical_path(name)) != name:
94
+ raise ValueError("setting name must already be canonical")
95
+ except (TypeError, ValueError):
96
+ raise SettingDefinitionError(
97
+ setting=repr(name), reason="name must be a canonical configuration path"
98
+ ) from None
99
+
100
+
101
+ def _contains_secret_type(value_type: object, seen: frozenset[object] = frozenset()) -> bool:
102
+ if value_type in seen:
103
+ return False
104
+ if isinstance(value_type, type) and issubclass(value_type, (Secret, SecretStr, SecretBytes)):
105
+ return True
106
+ if isinstance(value_type, type) and issubclass(value_type, BaseModel):
107
+ next_seen = seen | {value_type}
108
+ return any(
109
+ _contains_secret_type(field.annotation, next_seen)
110
+ for field in value_type.model_fields.values()
111
+ )
112
+ origin = get_origin(value_type)
113
+ return origin is not None and any(
114
+ _contains_secret_type(argument, seen) for argument in get_args(value_type)
115
+ )
@@ -0,0 +1,71 @@
1
+ """把受控类型发现解释为 Settings 定义;校验只发生在用户贡献边界。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ from collections.abc import Callable
7
+ from typing import Any, cast
8
+
9
+ from ..diagnostics import SourceLocation
10
+ from ..modularity.discovery import _DiscoveredType
11
+ from .contracts import SettingDefinition
12
+ from .definitions import SettingDefinitionContext, SettingDefinitionProvider
13
+ from .errors import SettingDefinitionError
14
+
15
+
16
+ def _collect_setting_definitions(
17
+ discoveries: tuple[_DiscoveredType, ...],
18
+ ) -> tuple[tuple[SettingDefinition[Any], ...], dict[str, SourceLocation]]:
19
+ providers: dict[type[object], SourceLocation] = {}
20
+ sources: dict[str, SourceLocation] = {}
21
+ definitions: list[SettingDefinition[Any]] = []
22
+ for discovery in discoveries:
23
+ candidate = discovery.candidate
24
+ if not issubclass(candidate, SettingDefinitionProvider) or inspect.isabstract(candidate):
25
+ continue
26
+ previous = providers.get(candidate)
27
+ if previous is not None:
28
+ if previous.owner != discovery.source.owner:
29
+ raise SettingDefinitionError(
30
+ setting=candidate.__qualname__,
31
+ reason=f"provider is already owned by {previous}",
32
+ source=discovery.source,
33
+ )
34
+ continue
35
+ providers[candidate] = discovery.source
36
+ for definition in _define(candidate, discovery.source):
37
+ if definition.name in sources:
38
+ raise SettingDefinitionError(
39
+ setting=definition.name,
40
+ reason=f"setting is already owned by {sources[definition.name]}",
41
+ source=discovery.source,
42
+ )
43
+ sources[definition.name] = discovery.source
44
+ definitions.append(definition)
45
+ return tuple(definitions), sources
46
+
47
+
48
+ def _define(
49
+ provider_type: type[SettingDefinitionProvider], source: SourceLocation
50
+ ) -> tuple[SettingDefinition[Any], ...]:
51
+ context = SettingDefinitionContext(source)
52
+ try:
53
+ provider = provider_type()
54
+ callback = cast(Callable[[SettingDefinitionContext], object], provider.define)
55
+ if inspect.iscoroutinefunction(callback) or inspect.isasyncgenfunction(callback):
56
+ raise TypeError("define must be synchronous")
57
+ result = callback(context)
58
+ if result is not None:
59
+ # 拒绝同步函数偷渡 coroutine,同时释放尚未运行的原生 coroutine/generator。
60
+ if inspect.iscoroutine(result) or inspect.isgenerator(result):
61
+ result.close()
62
+ raise TypeError("define must return None")
63
+ except Exception as error:
64
+ raise SettingDefinitionError(
65
+ setting=provider_type.__qualname__,
66
+ reason=f"definition provider failed: {type(error).__name__}: {error}",
67
+ source=source,
68
+ ) from error
69
+ finally:
70
+ definitions = context._close()
71
+ return definitions
@@ -0,0 +1,41 @@
1
+ """同步定义贡献的契约与一次调用独占的上下文;不接触运行期容器。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+ from typing import Any
7
+
8
+ from ..diagnostics import SourceLocation
9
+ from .contracts import SettingDefinition
10
+ from .errors import SettingDefinitionError
11
+
12
+
13
+ class SettingDefinitionProvider(ABC):
14
+ @abstractmethod
15
+ def define(self, context: SettingDefinitionContext) -> None:
16
+ """向所属 Application 贡献定义;必须同步完成并返回 None。"""
17
+
18
+
19
+ class SettingDefinitionContext:
20
+ __slots__ = ("_closed", "_definitions", "_source")
21
+
22
+ def __init__(self, source: SourceLocation) -> None:
23
+ self._source = source
24
+ self._definitions: list[SettingDefinition[Any]] = []
25
+ self._closed = False
26
+
27
+ def add(self, definition: SettingDefinition[Any]) -> None:
28
+ if self._closed:
29
+ raise SettingDefinitionError(
30
+ setting="context", reason="definition context is closed", source=self._source
31
+ )
32
+ if not isinstance(definition, SettingDefinition):
33
+ raise SettingDefinitionError(
34
+ setting="context", reason="expected SettingDefinition", source=self._source
35
+ )
36
+ self._definitions.append(definition)
37
+
38
+ def _close(self) -> tuple[SettingDefinition[Any], ...]:
39
+ # 成功或异常都封闭上下文;外部保留的引用不能再贡献定义。
40
+ self._closed = True
41
+ return tuple(self._definitions)
@@ -0,0 +1,22 @@
1
+ """Runtime Settings definition、lookup 与 provider failures。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ..diagnostics import SourceLocation
6
+ from ..errors.base import FrameworkError
7
+
8
+
9
+ class SettingDefinitionError(FrameworkError):
10
+ def __init__(self, *, setting: str, reason: str, source: SourceLocation | None = None) -> None:
11
+ self.setting = setting
12
+ self.reason = reason
13
+ self.source = source
14
+ super().__init__(f"Invalid setting {setting!r}: {reason}; source={source}")
15
+
16
+
17
+ class RuntimeSettingError(FrameworkError):
18
+ def __init__(self, *, setting: str, operation: str, error_type: str) -> None:
19
+ self.setting = setting
20
+ self.operation = operation
21
+ self.error_type = error_type
22
+ super().__init__(f"Runtime setting {operation} failed for {setting!r} with {error_type}")
@@ -0,0 +1,43 @@
1
+ """长期组件的 typed 应用契约;执行上下文由组件自身负责。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ from dataclasses import dataclass
7
+ from typing import Generic, Protocol, TypeVar
8
+
9
+ from .contracts import SettingDefinition
10
+ from .errors import SettingDefinitionError
11
+ from .values import SettingSnapshot
12
+
13
+ _T = TypeVar("_T")
14
+
15
+
16
+ class SettingHandler(Protocol[_T]):
17
+ async def apply(self, snapshot: SettingSnapshot[_T]) -> None: ...
18
+
19
+
20
+ @dataclass(frozen=True, slots=True)
21
+ class SettingHandlerDefinition(Generic[_T]):
22
+ setting: SettingDefinition[_T]
23
+ handler_type: type[SettingHandler[_T]]
24
+
25
+ def __post_init__(self) -> None:
26
+ if not isinstance(self.setting, SettingDefinition):
27
+ raise SettingDefinitionError(setting="handler", reason="setting must be a definition")
28
+ if (
29
+ not isinstance(self.handler_type, type)
30
+ or inspect.isabstract(self.handler_type)
31
+ or not inspect.iscoroutinefunction(getattr(self.handler_type, "apply", None))
32
+ ):
33
+ raise SettingDefinitionError(
34
+ setting=self.setting.name, reason="handler must have an async apply method"
35
+ )
36
+
37
+
38
+ @dataclass(frozen=True, slots=True)
39
+ class SettingApplicationStatus:
40
+ setting: str
41
+ handler: str
42
+ last_applied: SettingSnapshot[object] | None
43
+ error_type: str | None
@@ -0,0 +1,57 @@
1
+ """Settings 管理的权限、输入与 HTTP 可序列化结果契约。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Literal, Protocol
6
+ from uuid import UUID
7
+
8
+ from pydantic import BaseModel, ConfigDict, JsonValue
9
+
10
+ from ..application_services import ApplicationServiceContract
11
+ from ..authorization import PermissionName
12
+ from ..notifications import NotificationPublishStatus
13
+ from .values import SettingValueSource
14
+
15
+ SETTINGS_READ = PermissionName("settings.read")
16
+ SETTINGS_WRITE = PermissionName("settings.write")
17
+
18
+
19
+ class UpdateSettingCommand(BaseModel):
20
+ model_config = ConfigDict(frozen=True, extra="forbid")
21
+
22
+ value: JsonValue
23
+ expected_version: UUID | None
24
+
25
+
26
+ class ResetSettingCommand(BaseModel):
27
+ model_config = ConfigDict(frozen=True, extra="forbid")
28
+
29
+ expected_version: UUID | None
30
+
31
+
32
+ class SettingView(BaseModel):
33
+ model_config = ConfigDict(frozen=True)
34
+
35
+ name: str
36
+ value: JsonValue
37
+ source: SettingValueSource
38
+ version: UUID | None
39
+ effect: str = "New operations read the saved value; running components apply it separately."
40
+
41
+
42
+ class SettingWriteResult(BaseModel):
43
+ model_config = ConfigDict(frozen=True)
44
+
45
+ saved: Literal[True] = True
46
+ setting: SettingView
47
+ notification: NotificationPublishStatus
48
+
49
+
50
+ class SettingsManagementApplicationService(ApplicationServiceContract, Protocol):
51
+ async def get_list(self) -> tuple[SettingView, ...]: ...
52
+
53
+ async def get(self, name: str) -> SettingView: ...
54
+
55
+ async def update(self, name: str, command: UpdateSettingCommand) -> SettingWriteResult: ...
56
+
57
+ async def reset(self, name: str, command: ResetSettingCommand) -> SettingWriteResult: ...
@@ -0,0 +1,89 @@
1
+ """条件写入与提交后通知的 owner;读取解析由 SettingProvider 负责。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import TYPE_CHECKING, Any, TypeVar, cast
7
+ from uuid import UUID
8
+
9
+ from pydantic import ValidationError
10
+ from pydantic_core import PydanticSerializationError
11
+
12
+ from ..notifications import NotificationPayloadError, NotificationPublishStatus, Notifications
13
+ from .catalog import SettingCatalog
14
+ from .changes import SETTINGS_CHANGED, SettingChanged
15
+ from .contracts import SettingDefinition
16
+ from .errors import RuntimeSettingError
17
+ from .provider import SettingProvider
18
+ from .store import RuntimeSettingStore
19
+ from .values import SettingSnapshot, SettingValueSource, SettingWriteOutcome, StoredSetting
20
+
21
+ if TYPE_CHECKING:
22
+ from .refresh import SettingsRefreshRuntime
23
+
24
+ _T = TypeVar("_T")
25
+ _LOGGER = logging.getLogger(__name__)
26
+
27
+
28
+ class SettingManager:
29
+ __slots__ = ("_catalog", "_notifications", "_provider", "_refresh", "_store")
30
+
31
+ def __init__(
32
+ self, catalog: SettingCatalog, provider: SettingProvider, store: RuntimeSettingStore
33
+ ) -> None:
34
+ self._catalog = catalog
35
+ self._provider = provider
36
+ self._store = store
37
+ self._refresh: SettingsRefreshRuntime | None = None
38
+ self._notifications: Notifications | None = None
39
+
40
+ async def set_global(self, definition: SettingDefinition[_T], value: _T) -> _T:
41
+ current = await self._provider.snapshot(definition)
42
+ saved = await self.update(definition, value, expected_version=current.version)
43
+ return cast(_T, saved.snapshot.value)
44
+
45
+ async def reset_global(self, definition: SettingDefinition[Any]) -> None:
46
+ current = await self._provider.snapshot(definition)
47
+ await self.reset(definition, expected_version=current.version)
48
+
49
+ async def update(
50
+ self, definition: SettingDefinition[_T], value: object, *, expected_version: UUID | None
51
+ ) -> SettingWriteOutcome[_T]:
52
+ self._catalog.require_owned(definition)
53
+ try:
54
+ validated, serialized = definition._dump_json(value)
55
+ except (TypeError, ValidationError, PydanticSerializationError):
56
+ raise RuntimeSettingError(
57
+ setting=definition.name, operation="set", error_type="ValidationError"
58
+ ) from None
59
+ saved = await self._store.write(
60
+ definition.name, serialized, expected_version=expected_version
61
+ )
62
+ # 返回本次已经提交的值,不在提交后重读,避免把另一次并发写入误报为本次结果。
63
+ snapshot = SettingSnapshot(
64
+ definition.name, validated, SettingValueSource.RUNTIME, saved.version
65
+ )
66
+ return SettingWriteOutcome(snapshot, await self._committed(definition.name))
67
+
68
+ async def reset(
69
+ self, definition: SettingDefinition[_T], *, expected_version: UUID | None
70
+ ) -> SettingWriteOutcome[_T]:
71
+ self._catalog.require_owned(definition)
72
+ # 默认值先校验,不能在物理删除已经提交之后再抛出配置校验失败。
73
+ fallback = self._provider._resolve(definition, StoredSetting(None, expected_version))
74
+ saved = await self._store.write(definition.name, None, expected_version=expected_version)
75
+ snapshot = SettingSnapshot(definition.name, fallback.value, fallback.source, saved.version)
76
+ return SettingWriteOutcome(snapshot, await self._committed(definition.name))
77
+
78
+ async def _committed(self, name: str) -> NotificationPublishStatus:
79
+ # 本地刷新先入队;Redis 降级不会撤销短事务已经提交的结果。
80
+ if self._refresh is not None:
81
+ self._refresh.request_refresh(name)
82
+ if self._notifications is None:
83
+ return NotificationPublishStatus.NOT_CONFIGURED
84
+ try:
85
+ return await self._notifications.publish(SETTINGS_CHANGED, SettingChanged(name=name))
86
+ except NotificationPayloadError:
87
+ # 发布大小/编码限制是独立结果;不能把已提交的写入重新表现为保存失败。
88
+ _LOGGER.warning("Committed setting notification rejected for %s", name)
89
+ return NotificationPublishStatus.REJECTED
@@ -0,0 +1,7 @@
1
+ """Provider-neutral 的 Settings 管理服务与权限贡献。"""
2
+
3
+ from ..modularity import AppModule
4
+
5
+
6
+ class SettingsManagementModule(AppModule):
7
+ scan_packages = ("python_platform.settings.application", "python_platform.settings.permissions")
@@ -0,0 +1,39 @@
1
+ """Settings 是通用通知的使用方,重连和漏消息的补偿由 Settings 自身拥有。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from contextlib import AsyncExitStack
6
+
7
+ from ..lifecycle import PreInitializeContext, ShutdownContext
8
+ from ..modularity import AppModule
9
+ from ..notifications import Notifications
10
+ from .changes import SETTINGS_CHANGED, SettingChanged
11
+ from .manager import SettingManager
12
+ from .refresh import SettingsRefreshRuntime
13
+ from .refresh_module import SettingsRefreshModule
14
+
15
+
16
+ class SettingsNotificationsModule(AppModule):
17
+ dependencies = (SettingsRefreshModule,)
18
+ notification_definitions = (SETTINGS_CHANGED,)
19
+
20
+ def __init__(self) -> None:
21
+ self._subscriptions = AsyncExitStack()
22
+
23
+ async def pre_initialize(self, context: PreInitializeContext) -> None:
24
+ refresh = await context.container.get(SettingsRefreshRuntime)
25
+ notifications = await context.container.get(Notifications)
26
+ settings = await context.container.get(SettingManager)
27
+
28
+ async def changed(message: SettingChanged) -> None:
29
+ refresh.request_refresh(message.name)
30
+
31
+ await self._subscriptions.enter_async_context(
32
+ notifications.subscribe(SETTINGS_CHANGED, changed, on_reconnect=refresh.request_refresh)
33
+ )
34
+ settings._notifications = notifications
35
+
36
+ async def shutdown(self, context: ShutdownContext) -> None:
37
+ settings = await context.container.get(SettingManager)
38
+ settings._notifications = None
39
+ await self._subscriptions.aclose()
@@ -0,0 +1,14 @@
1
+ """settings 权限定义;持久化名称继续来自所属正式契约。"""
2
+
3
+ from ..authorization import (
4
+ PermissionDefinition,
5
+ PermissionDefinitionContext,
6
+ PermissionDefinitionProvider,
7
+ )
8
+ from .management import SETTINGS_READ, SETTINGS_WRITE
9
+
10
+
11
+ class SettingsPermissionDefinitionProvider(PermissionDefinitionProvider):
12
+ def define(self, context: PermissionDefinitionContext) -> None:
13
+ context.add(PermissionDefinition(SETTINGS_READ, "Read settings"))
14
+ context.add(PermissionDefinition(SETTINGS_WRITE, "Write settings"))
@@ -0,0 +1,74 @@
1
+ """有效值读取:存储覆盖、Application 配置快照、不可变定义默认值。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TypeVar, cast
6
+
7
+ from pydantic import ValidationError
8
+ from pydantic_core import PydanticSerializationError
9
+
10
+ from ..configuration import ConfigurationRoot
11
+ from .catalog import SettingCatalog
12
+ from .contracts import SettingDefinition
13
+ from .errors import RuntimeSettingError
14
+ from .store import RuntimeSettingStore
15
+ from .values import SettingSnapshot, SettingValueSource, StoredSetting
16
+
17
+ _T = TypeVar("_T")
18
+ _MISSING = object()
19
+
20
+
21
+ class SettingProvider:
22
+ __slots__ = ("_catalog", "_configuration", "_store")
23
+
24
+ def __init__(
25
+ self,
26
+ catalog: SettingCatalog,
27
+ configuration: ConfigurationRoot,
28
+ store: RuntimeSettingStore | None = None,
29
+ ) -> None:
30
+ self._catalog = catalog
31
+ self._configuration = configuration
32
+ self._store = store
33
+
34
+ async def get(self, definition: SettingDefinition[_T]) -> _T:
35
+ snapshot = await self.snapshot(definition)
36
+ if snapshot.source is SettingValueSource.UNSET:
37
+ raise RuntimeSettingError(
38
+ setting=definition.name, operation="get", error_type="MissingSettingValue"
39
+ )
40
+ return cast(_T, snapshot.value)
41
+
42
+ async def snapshot(self, definition: SettingDefinition[_T]) -> SettingSnapshot[_T]:
43
+ self._catalog.require_owned(definition)
44
+ stored = (
45
+ await self._store.read(definition.name)
46
+ if self._store is not None
47
+ else StoredSetting(None, None)
48
+ )
49
+ return self._resolve(definition, stored)
50
+
51
+ def _resolve(
52
+ self, definition: SettingDefinition[_T], stored: StoredSetting
53
+ ) -> SettingSnapshot[_T]:
54
+ try:
55
+ if stored.value_json is not None:
56
+ value = definition._validate_json(stored.value_json)
57
+ source = SettingValueSource.RUNTIME
58
+ else:
59
+ configured = self._configuration.get_path(f"settings.{definition.name}", _MISSING)
60
+ if configured is not _MISSING:
61
+ value = definition._validate_python(configured)
62
+ source = SettingValueSource.CONFIGURATION
63
+ elif definition.has_default:
64
+ value = definition._default_value()
65
+ source = SettingValueSource.DEFINITION
66
+ else:
67
+ return SettingSnapshot(
68
+ definition.name, None, SettingValueSource.UNSET, stored.version
69
+ )
70
+ return SettingSnapshot(definition.name, value, source, stored.version)
71
+ except (TypeError, ValidationError, PydanticSerializationError):
72
+ raise RuntimeSettingError(
73
+ setting=definition.name, operation="get", error_type="ValidationError"
74
+ ) from None