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,19 @@
1
+ """独立 typed 通知契约;provider 由 Host 选择。"""
2
+
3
+ from .catalog import NotificationCatalog
4
+ from .contracts import (
5
+ NotificationDefinition,
6
+ NotificationDefinitionError,
7
+ NotificationPayloadError,
8
+ NotificationPublishStatus,
9
+ Notifications,
10
+ )
11
+
12
+ __all__ = (
13
+ "NotificationCatalog",
14
+ "NotificationDefinition",
15
+ "NotificationDefinitionError",
16
+ "NotificationPayloadError",
17
+ "NotificationPublishStatus",
18
+ "Notifications",
19
+ )
@@ -0,0 +1,46 @@
1
+ """从已解析 Module 冻结通知声明;不依赖 Redis 或 Settings。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping
6
+ from types import MappingProxyType
7
+ from typing import Any
8
+
9
+ from ..modularity import ModuleRegistry
10
+ from .contracts import NotificationDefinition, NotificationDefinitionError
11
+
12
+
13
+ class NotificationCatalog:
14
+ __slots__ = ("_definitions",)
15
+
16
+ def __init__(self, definitions: tuple[NotificationDefinition[Any], ...]) -> None:
17
+ indexed: dict[str, NotificationDefinition[Any]] = {}
18
+ for definition in definitions:
19
+ if not isinstance(definition, NotificationDefinition) or definition.name in indexed:
20
+ raise NotificationDefinitionError(
21
+ "Notification declarations must have unique valid names"
22
+ )
23
+ indexed[definition.name] = definition
24
+ self._definitions: Mapping[str, NotificationDefinition[Any]] = MappingProxyType(indexed)
25
+
26
+ @property
27
+ def definitions(self) -> tuple[NotificationDefinition[Any], ...]:
28
+ return tuple(self._definitions.values())
29
+
30
+ def require_owned(self, definition: NotificationDefinition[Any]) -> None:
31
+ if self._definitions.get(definition.name) is not definition:
32
+ raise NotificationDefinitionError(
33
+ "Notification definition is not owned by this Application"
34
+ )
35
+
36
+
37
+ def _build_notification_catalog(registry: ModuleRegistry) -> NotificationCatalog:
38
+ definitions: list[NotificationDefinition[Any]] = []
39
+ for descriptor in registry.dependency_order:
40
+ declared = descriptor.module_type.notification_definitions
41
+ if not isinstance(declared, tuple):
42
+ raise NotificationDefinitionError(
43
+ "Module notification_definitions must be an immutable tuple"
44
+ )
45
+ definitions.extend(declared)
46
+ return NotificationCatalog(tuple(definitions))
@@ -0,0 +1,107 @@
1
+ """Typed Pub/Sub 契约;发布结果不代表送达或组件应用确认。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from collections.abc import Awaitable, Callable, Mapping
7
+ from contextlib import AbstractAsyncContextManager
8
+ from enum import Enum
9
+ from typing import Generic, Protocol, TypeVar, cast
10
+
11
+ from pydantic import BaseModel, ConfigDict, Field, TypeAdapter
12
+
13
+ from ..configuration.immutability import (
14
+ _serialization_value,
15
+ freeze_typed_value,
16
+ validate_immutable_annotation,
17
+ )
18
+ from ..errors import FrameworkError
19
+
20
+ _T = TypeVar("_T")
21
+
22
+
23
+ class NotificationDefinitionError(FrameworkError):
24
+ """声明重复、类型不合法或引用不属于当前 Application。"""
25
+
26
+
27
+ class NotificationPayloadError(FrameworkError):
28
+ """只暴露安全错误类型,不携带原始消息。"""
29
+
30
+
31
+ class NotificationPublishStatus(str, Enum):
32
+ NOT_CONFIGURED = "not_configured"
33
+ PUBLISHED = "published"
34
+ UNAVAILABLE = "unavailable"
35
+ REJECTED = "rejected"
36
+
37
+
38
+ class _NotificationEnvelope(BaseModel, Generic[_T]):
39
+ model_config = ConfigDict(frozen=True, extra="forbid")
40
+
41
+ payload: _T
42
+ traceparent: str | None = Field(default=None, max_length=512)
43
+
44
+
45
+ class NotificationDefinition(Generic[_T]):
46
+ __slots__ = ("_adapter", "_name", "_value_type")
47
+
48
+ def __init__(self, name: str, value_type: type[_T]) -> None:
49
+ if (
50
+ not isinstance(name, str)
51
+ or re.fullmatch(r"[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)*", name) is None
52
+ ):
53
+ raise NotificationDefinitionError(
54
+ "Notification name must be a canonical dotted identifier"
55
+ )
56
+ try:
57
+ validate_immutable_annotation(value_type)
58
+ self._adapter = TypeAdapter(_NotificationEnvelope[value_type]) # type: ignore[valid-type]
59
+ except (TypeError, ValueError):
60
+ raise NotificationDefinitionError(
61
+ "Notification payload type must support immutable JSON values"
62
+ ) from None
63
+ self._name = name
64
+ self._value_type = value_type
65
+
66
+ @property
67
+ def name(self) -> str:
68
+ return self._name
69
+
70
+ @property
71
+ def value_type(self) -> type[_T]:
72
+ return self._value_type
73
+
74
+ def _encode(self, payload: _T, carrier: Mapping[str, str]) -> bytes:
75
+ try:
76
+ payload = cast(_T, freeze_typed_value(payload, allow_secrets=True))
77
+ envelope = self._adapter.validate_python(
78
+ {"payload": payload, "traceparent": carrier.get("traceparent")}
79
+ )
80
+ return self._adapter.dump_json(
81
+ cast(_NotificationEnvelope[_T], _serialization_value(envelope))
82
+ )
83
+ except (TypeError, ValueError):
84
+ raise NotificationPayloadError("Invalid notification payload") from None
85
+
86
+ def _decode(self, encoded: bytes) -> tuple[_T, Mapping[str, str]]:
87
+ try:
88
+ envelope = self._adapter.validate_json(encoded)
89
+ value = cast(_T, freeze_typed_value(envelope.payload, allow_secrets=True))
90
+ carrier = {} if envelope.traceparent is None else {"traceparent": envelope.traceparent}
91
+ return value, carrier
92
+ except (TypeError, ValueError):
93
+ raise NotificationPayloadError("Invalid notification envelope") from None
94
+
95
+
96
+ class Notifications(Protocol):
97
+ async def publish(
98
+ self, definition: NotificationDefinition[_T], payload: _T
99
+ ) -> NotificationPublishStatus: ...
100
+
101
+ def subscribe(
102
+ self,
103
+ definition: NotificationDefinition[_T],
104
+ handler: Callable[[_T], Awaitable[None]],
105
+ *,
106
+ on_reconnect: Callable[[], None] | None = None,
107
+ ) -> AbstractAsyncContextManager[None]: ...
@@ -0,0 +1,5 @@
1
+ """Host observability 配置;logging/tracing 集成分别由 optional extras 提供。"""
2
+
3
+ from .options import LoggingOptions, TracingOptions
4
+
5
+ __all__ = ("LoggingOptions", "TracingOptions")
@@ -0,0 +1,43 @@
1
+ """调用追踪边界;基础安装不加载 OpenTelemetry。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterator, Mapping
6
+ from contextlib import contextmanager
7
+ from typing import TYPE_CHECKING
8
+
9
+ if TYPE_CHECKING:
10
+ from opentelemetry.trace import Tracer
11
+
12
+
13
+ @contextmanager
14
+ def invocation_span(
15
+ tracer: Tracer | None, name: str, *, carrier: Mapping[str, str] | None = None
16
+ ) -> Iterator[None]:
17
+ if tracer is None:
18
+ yield
19
+ return
20
+ from opentelemetry.trace import StatusCode
21
+ from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
22
+
23
+ context = None if carrier is None else TraceContextTextMapPropagator().extract(carrier)
24
+ # 异常类型由现有业务/任务边界处理,不复制可能含凭据的 exception.message。
25
+ with tracer.start_as_current_span(
26
+ name, context=context, record_exception=False, set_status_on_exception=False
27
+ ) as span:
28
+ try:
29
+ yield
30
+ except BaseException:
31
+ span.set_status(StatusCode.ERROR)
32
+ raise
33
+
34
+
35
+ def capture_trace_context(tracer: Tracer | None) -> dict[str, str]:
36
+ if tracer is None:
37
+ return {}
38
+ from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
39
+
40
+ carrier: dict[str, str] = {}
41
+ TraceContextTextMapPropagator().inject(carrier)
42
+ # envelope 只传播最小父链路;不传播 baggage、tracestate、身份或任意 header。
43
+ return {key: value for key, value in carrier.items() if key == "traceparent"}
@@ -0,0 +1,73 @@
1
+ """OTLP 之前的安全数据边界,复用原生 exporter、batching 与 HTTP 传输。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from collections.abc import Sequence
7
+
8
+ from opentelemetry.sdk.trace import ReadableSpan
9
+ from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
10
+ from opentelemetry.semconv.trace import SpanAttributes
11
+ from opentelemetry.trace import Link, Status
12
+
13
+ _LOGGER = logging.getLogger(__name__)
14
+ # 这是导出的公开隐私契约;不按业务字段、模块或调用者维护第二份允许清单。
15
+ _SAFE_ATTRIBUTES = frozenset(
16
+ {
17
+ SpanAttributes.HTTP_METHOD,
18
+ SpanAttributes.HTTP_ROUTE,
19
+ SpanAttributes.HTTP_STATUS_CODE,
20
+ "http.request.method",
21
+ "http.response.status_code",
22
+ "error.type",
23
+ }
24
+ )
25
+
26
+
27
+ class _SafeSpanExporter(SpanExporter):
28
+ def __init__(self, exporter: SpanExporter) -> None:
29
+ self._exporter = exporter
30
+
31
+ def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
32
+ try:
33
+ return self._exporter.export(tuple(_safe_span(span) for span in spans))
34
+ except Exception as error:
35
+ _LOGGER.warning("Trace export failed (%s)", type(error).__name__)
36
+ return SpanExportResult.FAILURE
37
+
38
+ def shutdown(self) -> None:
39
+ self._exporter.shutdown()
40
+
41
+ def force_flush(self, timeout_millis: int = 30000) -> bool:
42
+ return self._exporter.force_flush(timeout_millis)
43
+
44
+
45
+ def _safe_span(span: ReadableSpan) -> ReadableSpan:
46
+ # instrumentation 会自动产生 URL、exception event/status description;只转换导出副本,
47
+ # 不修改 SDK 正在使用的 span,也不让原始请求/任务值越过这个输出边界。
48
+ return ReadableSpan(
49
+ name=span.name,
50
+ context=span.context,
51
+ parent=span.parent,
52
+ resource=span.resource,
53
+ attributes={
54
+ key: value for key, value in (span.attributes or {}).items() if key in _SAFE_ATTRIBUTES
55
+ },
56
+ events=(),
57
+ links=tuple(Link(link.context) for link in span.links),
58
+ kind=span.kind,
59
+ status=Status(span.status.status_code),
60
+ start_time=span.start_time,
61
+ end_time=span.end_time,
62
+ instrumentation_scope=span.instrumentation_scope,
63
+ )
64
+
65
+
66
+ class _ExportDiagnosticFilter(logging.Filter):
67
+ def filter(self, record: logging.LogRecord) -> bool:
68
+ # 官方 HTTP exporter 的 reason 来自远端响应/连接异常,不应作为日志正文输出。
69
+ record.msg = "OTLP exporter diagnostic"
70
+ record.args = ()
71
+ record.exc_info = None
72
+ record.exc_text = None
73
+ return True
@@ -0,0 +1,89 @@
1
+ """标准库安全输出边界,启动期与 Application 共用;不加载 logging extra。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import sys
7
+ import time
8
+ import traceback
9
+ from copy import copy
10
+ from types import TracebackType
11
+ from typing import Any
12
+
13
+ _DIAGNOSTIC_FIELDS = (
14
+ "background_worker",
15
+ "background_exception_type",
16
+ "audit_exception_type",
17
+ "audit_contract",
18
+ "audit_method",
19
+ "audit_route_template",
20
+ )
21
+ _HTTP_DIAGNOSTIC_LOGGERS = ("httpx", "httpcore", "uvicorn.access")
22
+
23
+
24
+ def _safe_exception(info: Any) -> str:
25
+ # walk_tb 不取源码行或 locals;异常正文、notes 永远不进入输出。
26
+ if info[1] is None:
27
+ return "No active exception"
28
+ lines: list[str] = []
29
+ seen: set[int] = set()
30
+
31
+ def visit(
32
+ error: BaseException,
33
+ relation: str,
34
+ depth: int,
35
+ trace: TracebackType | None,
36
+ ) -> None:
37
+ prefix = " " * depth + relation
38
+ if depth >= 32:
39
+ lines.append(prefix + "<exception depth limit>")
40
+ return
41
+ if id(error) in seen:
42
+ lines.append(prefix + "<exception reference cycle>")
43
+ return
44
+ seen.add(id(error))
45
+ lines.append(prefix + type(error).__qualname__)
46
+ for frame, lineno in traceback.walk_tb(trace):
47
+ lines.append(
48
+ f"{' ' * (depth + 1)}{frame.f_code.co_filename}:{lineno} in {frame.f_code.co_name}"
49
+ )
50
+ if error.__cause__ is not None:
51
+ visit(error.__cause__, "cause: ", depth + 1, error.__cause__.__traceback__)
52
+ elif error.__context__ is not None and not error.__suppress_context__:
53
+ visit(error.__context__, "context: ", depth + 1, error.__context__.__traceback__)
54
+ if isinstance(error, BaseExceptionGroup):
55
+ for index, child in enumerate(error.exceptions):
56
+ visit(child, f"group[{index}]: ", depth + 1, child.__traceback__)
57
+
58
+ visit(info[1], "", 0, info[2])
59
+ return "\n".join(lines)
60
+
61
+
62
+ class _SafeTextFormatter(logging.Formatter):
63
+ converter = staticmethod(time.gmtime)
64
+ _append_diagnostics = True
65
+
66
+ def formatException(self, ei: Any) -> str:
67
+ return _safe_exception(ei)
68
+
69
+ def format(self, record: logging.LogRecord) -> str:
70
+ safe_record = copy(record)
71
+ # handler 共享 record;清空其他 handler 的缓存及原生含源码的 stack_info。
72
+ safe_record.exc_text = None
73
+ safe_record.stack_info = None
74
+ if any(
75
+ record.name == name or record.name.startswith(name + ".")
76
+ for name in _HTTP_DIAGNOSTIC_LOGGERS
77
+ ):
78
+ safe_record.msg, safe_record.args = "HTTP transport diagnostic", ()
79
+ elif record.name == "uvicorn" or record.name.startswith("uvicorn."):
80
+ # Uvicorn 的 factory/lifespan 错误可能已把异常插入 message,不能格式化原正文。
81
+ safe_record.msg, safe_record.args = "ASGI server diagnostic", ()
82
+ if safe_record.exc_info is None and sys.exc_info()[0] is not None:
83
+ safe_record.exc_info = sys.exc_info()
84
+ rendered = super().format(safe_record)
85
+ if not self._append_diagnostics:
86
+ return rendered
87
+ return rendered + "".join(
88
+ f" {key}={record.__dict__[key]}" for key in _DIAGNOSTIC_FIELDS if key in record.__dict__
89
+ )
@@ -0,0 +1,118 @@
1
+ """Host 配置标准 logging;业务使用 logging.getLogger,不需要新的日志 API。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import Any
7
+
8
+ from pythonjsonlogger.json import JsonFormatter
9
+
10
+ from python_platform import ConfigureContext
11
+
12
+ from ..application_services.invocation import _InvocationRuntime
13
+ from ..lifecycle import PreInitializeContext, ShutdownContext
14
+ from ..modularity import AppModule
15
+ from .formatting import _DIAGNOSTIC_FIELDS, _SafeTextFormatter
16
+ from .options import LoggingOptions
17
+
18
+ _LOG_FIELDS = (
19
+ "asctime",
20
+ "levelname",
21
+ "name",
22
+ "application",
23
+ "environment",
24
+ "correlation_id",
25
+ "trace_id",
26
+ "span_id",
27
+ "message",
28
+ )
29
+
30
+
31
+ class ApplicationLogFilter(logging.Filter):
32
+ _application_logging = True
33
+
34
+ def __init__(self, runtime: _InvocationRuntime, options: LoggingOptions) -> None:
35
+ super().__init__()
36
+ self._runtime = runtime
37
+ self._options = options
38
+
39
+ def filter(self, record: logging.LogRecord) -> bool:
40
+ record.application = self._options.application
41
+ record.environment = self._options.environment
42
+ record.correlation_id = self._runtime.diagnostic_correlation_id()
43
+ record.trace_id = None
44
+ record.span_id = None
45
+ if self._runtime.tracer is not None:
46
+ from opentelemetry.trace import get_current_span
47
+
48
+ context = get_current_span().get_span_context()
49
+ if context.is_valid:
50
+ record.trace_id = format(context.trace_id, "032x")
51
+ record.span_id = format(context.span_id, "016x")
52
+ return True
53
+
54
+
55
+ class _SafeJsonFormatter(_SafeTextFormatter, JsonFormatter):
56
+ _append_diagnostics = False
57
+
58
+ def process_log_record(self, log_data: dict[str, Any]) -> dict[str, Any]:
59
+ # 默认不把任意 extra 字典(可能含 request/job payload)自动写入 JSON。
60
+ return {
61
+ key: value
62
+ for key, value in log_data.items()
63
+ if key in (*_LOG_FIELDS, *_DIAGNOSTIC_FIELDS, "exc_info")
64
+ }
65
+
66
+
67
+ class StructuredLoggingModule(AppModule):
68
+ def configure(self, context: ConfigureContext) -> None:
69
+ context.configure(LoggingOptions, section="logging")
70
+
71
+ def __init__(self) -> None:
72
+ self._handler: logging.StreamHandler[Any] | None = None
73
+ self._previous_level: int | None = None
74
+ self._category_levels: dict[str, int] = {}
75
+
76
+ async def pre_initialize(self, context: PreInitializeContext) -> None:
77
+ root = logging.getLogger()
78
+ if any(
79
+ isinstance(log_filter, ApplicationLogFilter)
80
+ for handler in root.handlers
81
+ for log_filter in handler.filters
82
+ ):
83
+ raise RuntimeError("one Host must own the process logging configuration")
84
+ options = context.options.get(LoggingOptions)
85
+ runtime = await context.container.get(_InvocationRuntime)
86
+ handler: logging.StreamHandler[Any] = logging.StreamHandler()
87
+ handler.addFilter(ApplicationLogFilter(runtime, options))
88
+ fields = " ".join(f"%({field})s" for field in _LOG_FIELDS)
89
+ formatter = (
90
+ _SafeJsonFormatter(fields) if options.format == "json" else _SafeTextFormatter(fields)
91
+ )
92
+ handler.setFormatter(formatter)
93
+ handler.setLevel(
94
+ min(
95
+ logging.getLevelName(level)
96
+ for level in (options.level, *options.logger_levels.values())
97
+ )
98
+ )
99
+ for name, level in options.logger_levels.items():
100
+ logger = logging.getLogger(name)
101
+ self._category_levels[name] = logger.level
102
+ logger.setLevel(level)
103
+ self._previous_level = root.level
104
+ root.setLevel(options.level)
105
+ root.addHandler(handler)
106
+ self._handler = handler
107
+
108
+ async def shutdown(self, context: ShutdownContext) -> None:
109
+ if self._handler is not None:
110
+ root = logging.getLogger()
111
+ root.removeHandler(self._handler)
112
+ self._handler.close()
113
+ self._handler = None
114
+ if self._previous_level is not None:
115
+ root.setLevel(self._previous_level)
116
+ for name, level in self._category_levels.items():
117
+ logging.getLogger(name).setLevel(level)
118
+ self._category_levels.clear()
@@ -0,0 +1,45 @@
1
+ """Host 显式选择日志输出及追踪导出;不从业务代码修改运行配置。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping
6
+ from typing import Literal
7
+ from urllib.parse import urlsplit
8
+
9
+ from pydantic import Field, model_validator
10
+
11
+ from ..options import BaseOptions
12
+
13
+
14
+ class LoggingOptions(BaseOptions):
15
+ application: str = Field(min_length=1)
16
+ environment: str = Field(min_length=1)
17
+ level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO"
18
+ format: Literal["text", "json"] = "text"
19
+ logger_levels: Mapping[str, Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]] = Field(
20
+ default_factory=dict
21
+ )
22
+
23
+ @model_validator(mode="after")
24
+ def _validate_logger_names(self) -> LoggingOptions:
25
+ if any(not name or name.strip() != name or name == "root" for name in self.logger_levels):
26
+ raise ValueError("logger_levels requires nonempty category names; root uses level")
27
+ return self
28
+
29
+
30
+ class TracingOptions(BaseOptions):
31
+ service_name: str = Field(min_length=1)
32
+ export_enabled: bool = False
33
+ endpoint: str = "http://localhost:4318/v1/traces"
34
+ queue_size: int = Field(default=2048, ge=1)
35
+ batch_size: int = Field(default=512, ge=1)
36
+ export_timeout_seconds: float = Field(default=5, gt=0, le=30)
37
+
38
+ @model_validator(mode="after")
39
+ def _validate_export(self) -> TracingOptions:
40
+ target = urlsplit(self.endpoint)
41
+ if target.scheme not in ("http", "https") or not target.hostname or target.username:
42
+ raise ValueError("OTLP endpoint must be an HTTP URL without credentials")
43
+ if self.batch_size > self.queue_size:
44
+ raise ValueError("trace batch size cannot exceed queue size")
45
+ return self
@@ -0,0 +1,80 @@
1
+ """Application-owned OpenTelemetry SDK,Host 按需启用有界 OTLP 导出。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import logging
7
+
8
+ from dishka import Provider, Scope
9
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
10
+ from opentelemetry.sdk.resources import Resource
11
+ from opentelemetry.sdk.trace import TracerProvider
12
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
13
+
14
+ from ..application_services.invocation import _InvocationRuntime
15
+ from ..lifecycle import ConfigureContext, PreInitializeContext, ShutdownContext
16
+ from ..modularity import AppModule
17
+ from ..options import Options
18
+ from .export import _ExportDiagnosticFilter, _SafeSpanExporter
19
+ from .options import TracingOptions
20
+
21
+ _LOGGER = logging.getLogger(__name__)
22
+
23
+
24
+ class TracingModule(AppModule):
25
+ def __init__(self) -> None:
26
+ self._provider: TracerProvider | None = None
27
+ self._export_filter: _ExportDiagnosticFilter | None = None
28
+
29
+ def configure(self, context: ConfigureContext) -> None:
30
+ context.configure(TracingOptions, section="tracing")
31
+ provider = Provider()
32
+ provider.provide(self._prepare, provides=TracerProvider, scope=Scope.APP)
33
+ context.services.contribute(provider, reason="Application-owned OpenTelemetry provider")
34
+
35
+ def _prepare(self, options: Options[TracingOptions]) -> TracerProvider:
36
+ if self._provider is None:
37
+ # Web 安装可先取得 provider;此时没有 exporter/thread/I/O。导出只在 runtime 启动。
38
+ self._provider = TracerProvider(
39
+ resource=Resource({"service.name": options.value.service_name}),
40
+ shutdown_on_exit=False,
41
+ )
42
+ return self._provider
43
+
44
+ async def pre_initialize(self, context: PreInitializeContext) -> None:
45
+ options = context.options.get(TracingOptions)
46
+ provider = await context.container.get(TracerProvider)
47
+ runtime = await context.container.get(_InvocationRuntime)
48
+ runtime.tracer = provider.get_tracer(__package__)
49
+ if options.export_enabled:
50
+ self._export_filter = _ExportDiagnosticFilter()
51
+ logging.getLogger(OTLPSpanExporter.__module__).addFilter(self._export_filter)
52
+ provider.add_span_processor(
53
+ BatchSpanProcessor(
54
+ _SafeSpanExporter(
55
+ OTLPSpanExporter(
56
+ endpoint=options.endpoint, timeout=options.export_timeout_seconds
57
+ )
58
+ ),
59
+ max_queue_size=options.queue_size,
60
+ max_export_batch_size=options.batch_size,
61
+ export_timeout_millis=options.export_timeout_seconds * 1000,
62
+ )
63
+ )
64
+
65
+ async def shutdown(self, context: ShutdownContext) -> None:
66
+ if self._provider is None:
67
+ return
68
+ runtime = await context.container.get(_InvocationRuntime)
69
+ runtime.tracer = None
70
+ try:
71
+ # SDK BatchSpanProcessor 自身限制关闭等待;等待原生关闭结束再撤销输出过滤,
72
+ # 不取消 to_thread 留下仍在导出、却已失去生命周期 owner 的后台工作。
73
+ await asyncio.to_thread(self._provider.shutdown)
74
+ except Exception as error:
75
+ _LOGGER.warning("Trace shutdown failed (%s)", type(error).__name__)
76
+ finally:
77
+ if self._export_filter is not None:
78
+ logging.getLogger(OTLPSpanExporter.__module__).removeFilter(self._export_filter)
79
+ self._export_filter = None
80
+ self._provider = None
@@ -0,0 +1,19 @@
1
+ """Typed Options public contracts and Application-internal binding owner."""
2
+
3
+ from .models import (
4
+ BaseOptions,
5
+ Options,
6
+ OptionsContribution,
7
+ OptionsSnapshot,
8
+ OptionsValidationIssue,
9
+ PreOptionsSnapshot,
10
+ )
11
+
12
+ __all__ = (
13
+ "BaseOptions",
14
+ "Options",
15
+ "OptionsContribution",
16
+ "OptionsSnapshot",
17
+ "OptionsValidationIssue",
18
+ "PreOptionsSnapshot",
19
+ )