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,500 @@
1
+ """Protocol/implementation mapping、method compilation 与 immutable public catalog。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ from collections.abc import Awaitable, Callable, Mapping
7
+ from dataclasses import dataclass, field, replace
8
+ from types import MappingProxyType
9
+ from typing import Any, cast, get_type_hints
10
+
11
+ from pydantic import ConfigDict, validate_call
12
+
13
+ from ..auditing.control import _AUDITING_ATTRIBUTE
14
+ from ..authorization import PermissionCatalog, PermissionName
15
+ from ..authorization.contracts import (
16
+ _ALLOW_ANONYMOUS_ATTRIBUTE,
17
+ _AUTHORIZATION_ATTRIBUTE,
18
+ _authorization_requirement,
19
+ _AuthorizationRequirement,
20
+ )
21
+ from ..authorization.errors import AuthorizationDefinitionError
22
+ from ..diagnostics import SourceLocation
23
+ from ..modularity import ModuleKey
24
+ from ..unit_of_work.options import _UNIT_OF_WORK_ATTRIBUTE, UnitOfWorkDeclaration, _declaration
25
+ from .contracts import (
26
+ ApplicationService,
27
+ ApplicationServiceContract,
28
+ IntegrationService,
29
+ _ApplicationServiceDefinition,
30
+ _inherited_contracts,
31
+ _is_internal_contributor,
32
+ )
33
+ from .errors import ApplicationServiceDefinitionError, ApplicationServiceMethodError
34
+ from .interceptors import InterceptionStage, InterceptorRegistration, _InterceptorRule
35
+ from .policies import _VALIDATION_ATTRIBUTE, _effective_switch
36
+ from .signature import _business_signature
37
+
38
+ _APPLICATION_SERVICE_COMPONENT = "__python_platform_application_service_implementation__"
39
+
40
+
41
+ @dataclass(frozen=True, slots=True)
42
+ class ApplicationServiceMethod:
43
+ name: str
44
+ source: SourceLocation
45
+ requires_authenticated_user: bool
46
+ permissions: tuple[PermissionName, ...]
47
+ allows_anonymous: bool
48
+ service: type[object]
49
+ implementation: type[object]
50
+ method: Callable[..., Awaitable[object]]
51
+ validation_enabled: bool
52
+ auditing_enabled: bool
53
+ policy_sources: tuple[SourceLocation, ...]
54
+ interceptors: tuple[InterceptorRegistration, ...] = ()
55
+ unit_of_work: UnitOfWorkDeclaration = field(default_factory=UnitOfWorkDeclaration)
56
+
57
+
58
+ @dataclass(frozen=True, slots=True)
59
+ class ApplicationServiceRegistration:
60
+ contract: type[object]
61
+ implementation: type[ApplicationService]
62
+ connection_name: str
63
+ owner: ModuleKey
64
+ implementation_owner: ModuleKey
65
+ source: SourceLocation
66
+ implementation_source: SourceLocation
67
+ replaced_sources: tuple[SourceLocation, ...]
68
+ declaration_index: int
69
+ methods: tuple[ApplicationServiceMethod, ...]
70
+ is_public_service: bool
71
+ has_contract: bool
72
+ is_integration_service: bool
73
+
74
+
75
+ @dataclass(frozen=True, slots=True)
76
+ class _DiscoveredApplicationService:
77
+ service_type: type[ApplicationService]
78
+ definition: _ApplicationServiceDefinition
79
+ owner: ModuleKey
80
+ source: SourceLocation
81
+ declaration_index: int
82
+
83
+
84
+ @dataclass(frozen=True, slots=True)
85
+ class _ApplicationServiceBinding:
86
+ discovery: _DiscoveredApplicationService
87
+ implementation: type[ApplicationService]
88
+ implementation_owner: ModuleKey
89
+ implementation_source: SourceLocation
90
+ replaced_sources: tuple[SourceLocation, ...]
91
+
92
+
93
+ _ValidatedArguments = tuple[tuple[object, ...], dict[str, object]]
94
+
95
+
96
+ @dataclass(frozen=True, slots=True)
97
+ class _CompiledMethod:
98
+ name: str
99
+ contract_method: Callable[..., Awaitable[object]]
100
+ implementation_method: Callable[..., Awaitable[object]]
101
+ validator: Callable[..., Awaitable[_ValidatedArguments]]
102
+ parameter_names: frozenset[str]
103
+ authorization: _AuthorizationRequirement
104
+ descriptor: ApplicationServiceMethod
105
+ signature: inspect.Signature
106
+ interceptor_steps: Mapping[InterceptionStage, tuple[InterceptorRegistration, ...]]
107
+
108
+
109
+ @dataclass(frozen=True, slots=True)
110
+ class _CompiledApplicationService:
111
+ registration: ApplicationServiceRegistration
112
+ methods: Mapping[str, _CompiledMethod]
113
+
114
+
115
+ class ApplicationServiceCatalog:
116
+ """唯一发布 contract、最终 implementation、owner、method 与 override provenance。"""
117
+
118
+ __slots__ = ("_by_contract", "_by_method", "_compiled", "_registrations")
119
+
120
+ def __init__(self, services: tuple[_CompiledApplicationService, ...]) -> None:
121
+ self._compiled = services
122
+ self._registrations = tuple(item.registration for item in services)
123
+ public_services = tuple(item for item in services if item.registration.is_public_service)
124
+ self._by_contract: Mapping[type[object], _CompiledApplicationService] = MappingProxyType(
125
+ {item.registration.contract: item for item in public_services}
126
+ )
127
+ methods_by_identity: dict[
128
+ object,
129
+ tuple[_CompiledApplicationService, _CompiledMethod],
130
+ ] = {}
131
+ for service in public_services:
132
+ for method in service.methods.values():
133
+ previous = methods_by_identity.get(method.contract_method)
134
+ if previous is not None:
135
+ # method-only invoke 不能靠遍历顺序裁决继承或复用造成的 identity 歧义。
136
+ contract = service.registration.contract
137
+ raise ApplicationServiceDefinitionError(
138
+ symbol=f"{contract.__module__}.{contract.__qualname__}.{method.name}",
139
+ reason="contract method identity must be unique within an Application",
140
+ )
141
+ methods_by_identity[method.contract_method] = (service, method)
142
+ self._by_method: Mapping[
143
+ object,
144
+ tuple[_CompiledApplicationService, _CompiledMethod],
145
+ ] = MappingProxyType(methods_by_identity)
146
+
147
+ @property
148
+ def registrations(self) -> tuple[ApplicationServiceRegistration, ...]:
149
+ return self._registrations
150
+
151
+ def for_contract(
152
+ self,
153
+ contract: type[object],
154
+ ) -> ApplicationServiceRegistration | None:
155
+ compiled = self._by_contract.get(contract)
156
+ return compiled.registration if compiled is not None else None
157
+
158
+ def _resolve_method(
159
+ self,
160
+ method: object,
161
+ ) -> tuple[_CompiledApplicationService, _CompiledMethod]:
162
+ resolved = self._by_method.get(method)
163
+ if resolved is None:
164
+ raise ApplicationServiceMethodError(
165
+ service=ApplicationServiceContract,
166
+ method=method,
167
+ reason="method is not a cataloged ApplicationServiceContract method",
168
+ )
169
+ return resolved
170
+
171
+ def _for_registration(
172
+ self,
173
+ registration: ApplicationServiceRegistration,
174
+ ) -> _CompiledApplicationService:
175
+ for compiled in self._compiled:
176
+ if compiled.registration is registration:
177
+ return compiled
178
+ raise AssertionError("Application Service registration is not owned by this catalog")
179
+
180
+
181
+ def _build_application_service_catalog(
182
+ bindings: tuple[_ApplicationServiceBinding, ...],
183
+ permission_catalog: PermissionCatalog,
184
+ interceptor_rules: tuple[_InterceptorRule, ...] = (),
185
+ ) -> ApplicationServiceCatalog:
186
+ return ApplicationServiceCatalog(
187
+ tuple(_compile_service(item, permission_catalog, interceptor_rules) for item in bindings)
188
+ )
189
+
190
+
191
+ def _compile_service(
192
+ binding: _ApplicationServiceBinding,
193
+ permission_catalog: PermissionCatalog,
194
+ interceptor_rules: tuple[_InterceptorRule, ...] = (),
195
+ ) -> _CompiledApplicationService:
196
+ """把 contract declaration 与仲裁胜出的 implementation 编译成唯一调用事实。"""
197
+
198
+ discovery = binding.discovery
199
+ implementation = binding.implementation
200
+ contract: type[object] = discovery.definition.contract or discovery.service_type
201
+ has_contract = discovery.definition.contract is not None
202
+ is_public_service = not _is_internal_contributor(implementation)
203
+ is_integration = issubclass(implementation, IntegrationService)
204
+ _validate_policy_locations(contract if has_contract else None, implementation, is_integration)
205
+ if is_integration and not has_contract:
206
+ raise ApplicationServiceDefinitionError(
207
+ symbol=implementation.__qualname__,
208
+ reason="IntegrationService requires an independent Contract",
209
+ )
210
+ implementation_symbol = f"{implementation.__module__}.{implementation.__qualname__}"
211
+ if inspect.isabstract(implementation):
212
+ raise ApplicationServiceDefinitionError(
213
+ symbol=implementation_symbol,
214
+ reason="application service implementation must be concrete",
215
+ )
216
+
217
+ # public surface 以 Protocol 为权威;override 只能替换实现,不能暗中增删业务方法。
218
+ contract_methods = _public_method_names(contract, is_contract=has_contract)
219
+
220
+ compiled_methods: dict[str, _CompiledMethod] = {}
221
+ public_methods: list[ApplicationServiceMethod] = []
222
+ for name in contract_methods:
223
+ contract_method = _require_async_instance_method(contract, name, role="contract")
224
+ implementation_method = _require_async_instance_method(
225
+ implementation,
226
+ name,
227
+ role="implementation",
228
+ )
229
+ contract_signature, annotations = _resolved_signature(contract_method)
230
+ implementation_signature, _ = _resolved_signature(implementation_method)
231
+ if contract_signature.return_annotation is inspect.Signature.empty:
232
+ raise ApplicationServiceDefinitionError(
233
+ symbol=f"{implementation_symbol}.{name}",
234
+ reason="public service methods must declare a return annotation",
235
+ )
236
+ if _business_signature(contract_signature) != _business_signature(implementation_signature):
237
+ raise ApplicationServiceDefinitionError(
238
+ symbol=f"{implementation_symbol}.{name}",
239
+ reason="implementation signature must exactly match its public contract",
240
+ )
241
+ try:
242
+ validator = _compile_argument_validator(
243
+ contract_method,
244
+ contract_signature,
245
+ annotations,
246
+ )
247
+ except Exception as original_error:
248
+ raise ApplicationServiceDefinitionError(
249
+ symbol=f"{contract.__module__}.{contract.__qualname__}.{name}",
250
+ reason=f"Pydantic could not compile the method: {type(original_error).__name__}",
251
+ ) from None
252
+ source = _method_source(contract_method, discovery.owner)
253
+ authorization = _authorization_requirement(implementation, implementation_method)
254
+ unknown_permissions = tuple(
255
+ permission
256
+ for permission in authorization.permissions
257
+ if permission not in permission_catalog
258
+ )
259
+ if unknown_permissions:
260
+ raise AuthorizationDefinitionError(
261
+ symbol=f"{contract.__module__}.{contract.__qualname__}.{name}",
262
+ reason=(
263
+ "authorization references unknown permissions: "
264
+ f"{tuple(str(item) for item in unknown_permissions)!r}"
265
+ ),
266
+ )
267
+ descriptor = ApplicationServiceMethod(
268
+ name=name,
269
+ source=source,
270
+ requires_authenticated_user=authorization.requires_authenticated_user,
271
+ permissions=authorization.permissions,
272
+ allows_anonymous=authorization.allows_anonymous,
273
+ service=contract,
274
+ implementation=implementation,
275
+ method=implementation_method,
276
+ unit_of_work=_declaration(implementation, implementation_method),
277
+ validation_enabled=_effective_switch(
278
+ implementation, implementation_method, _VALIDATION_ATTRIBUTE, True
279
+ ),
280
+ auditing_enabled=_effective_switch(
281
+ implementation, implementation_method, _AUDITING_ATTRIBUTE, not is_integration
282
+ ),
283
+ policy_sources=_policy_sources(
284
+ implementation, implementation_method, binding.implementation_owner
285
+ ),
286
+ )
287
+ descriptor = replace(
288
+ descriptor, interceptors=_match_interceptors(descriptor, interceptor_rules)
289
+ )
290
+ compiled_methods[name] = _CompiledMethod(
291
+ name=name,
292
+ contract_method=contract_method,
293
+ implementation_method=implementation_method,
294
+ validator=validator,
295
+ parameter_names=frozenset(tuple(contract_signature.parameters)[1:]),
296
+ authorization=authorization,
297
+ descriptor=descriptor,
298
+ signature=contract_signature,
299
+ interceptor_steps=MappingProxyType(
300
+ {
301
+ stage: tuple(item for item in descriptor.interceptors if item.before is stage)
302
+ for stage in InterceptionStage
303
+ }
304
+ ),
305
+ )
306
+ public_methods.append(descriptor)
307
+
308
+ if not public_methods:
309
+ raise ApplicationServiceDefinitionError(
310
+ symbol=f"{contract.__module__}.{contract.__qualname__}",
311
+ reason="application service contract must declare at least one public async method",
312
+ )
313
+ registration = ApplicationServiceRegistration(
314
+ contract=contract,
315
+ implementation=implementation,
316
+ connection_name=discovery.definition.connection_name,
317
+ owner=discovery.owner,
318
+ implementation_owner=binding.implementation_owner,
319
+ source=discovery.source,
320
+ implementation_source=binding.implementation_source,
321
+ replaced_sources=binding.replaced_sources,
322
+ declaration_index=discovery.declaration_index,
323
+ methods=tuple(public_methods),
324
+ is_public_service=is_public_service,
325
+ has_contract=has_contract,
326
+ is_integration_service=is_integration,
327
+ )
328
+ return _CompiledApplicationService(
329
+ registration=registration,
330
+ methods=MappingProxyType(compiled_methods),
331
+ )
332
+
333
+
334
+ _POLICY_ATTRIBUTES = (
335
+ _UNIT_OF_WORK_ATTRIBUTE,
336
+ _AUTHORIZATION_ATTRIBUTE,
337
+ _ALLOW_ANONYMOUS_ATTRIBUTE,
338
+ _VALIDATION_ATTRIBUTE,
339
+ _AUDITING_ATTRIBUTE,
340
+ )
341
+
342
+
343
+ def _validate_policy_locations(
344
+ contract: type[object] | None, implementation: type[object], integration: bool
345
+ ) -> None:
346
+ # provides 只仲裁公开身份;最终实现继承的其他业务 Contract 同样不能持有策略。
347
+ contracts = dict.fromkeys((contract, *_inherited_contracts(implementation)))
348
+ for root, attributes in (
349
+ *((candidate, _POLICY_ATTRIBUTES) for candidate in contracts if candidate is not None),
350
+ (implementation if integration else None, _POLICY_ATTRIBUTES[:2]),
351
+ ):
352
+ if root is None:
353
+ continue
354
+ for owner in root.__mro__:
355
+ for target in (
356
+ owner,
357
+ *(value for value in vars(owner).values() if inspect.isfunction(value)),
358
+ ):
359
+ if any(attribute in vars(target) for attribute in attributes):
360
+ raise ApplicationServiceDefinitionError(
361
+ symbol=getattr(target, "__qualname__", root.__qualname__),
362
+ reason="Contract policies must move to implementation"
363
+ if root in contracts
364
+ else "IntegrationService cannot declare user authorization",
365
+ )
366
+
367
+
368
+ def _policy_sources(
369
+ implementation: type[object], method: Callable[..., Any], owner: ModuleKey
370
+ ) -> tuple[SourceLocation, ...]:
371
+ return tuple(
372
+ _method_source(target, owner)
373
+ for target in (*reversed(implementation.__mro__), method)
374
+ if any(attribute in vars(target) for attribute in _POLICY_ATTRIBUTES)
375
+ )
376
+
377
+
378
+ def _match_interceptors(
379
+ method: ApplicationServiceMethod, rules: tuple[_InterceptorRule, ...]
380
+ ) -> tuple[InterceptorRegistration, ...]:
381
+ matched: list[InterceptorRegistration] = []
382
+ for rule in rules:
383
+ try:
384
+ accepted = rule.match(method)
385
+ if not isinstance(accepted, bool):
386
+ raise TypeError("interceptor match must return bool")
387
+ except Exception as error:
388
+ raise ApplicationServiceDefinitionError(
389
+ symbol=rule.registration.source.symbol
390
+ or rule.registration.interceptor.__qualname__,
391
+ reason=(
392
+ f"interceptor match failed for {rule.registration.owner}: "
393
+ f"{type(error).__name__}"
394
+ ),
395
+ ) from None
396
+ if accepted:
397
+ matched.append(rule.registration)
398
+ return tuple(sorted(matched, key=lambda item: tuple(InterceptionStage).index(item.before)))
399
+
400
+
401
+ def _public_method_names(candidate: type[object], *, is_contract: bool) -> tuple[str, ...]:
402
+ names: dict[str, None] = {}
403
+ for owner in reversed(candidate.__mro__):
404
+ if owner in (object, ApplicationService, ApplicationServiceContract):
405
+ continue
406
+ if is_contract and owner.__module__ == "typing":
407
+ continue
408
+ for name, value in owner.__dict__.items():
409
+ if name.startswith("_"):
410
+ continue
411
+ if inspect.isfunction(value) or isinstance(value, (staticmethod, classmethod)):
412
+ names[name] = None
413
+ return tuple(names)
414
+
415
+
416
+ def _require_async_instance_method(
417
+ owner: type[object],
418
+ name: str,
419
+ *,
420
+ role: str,
421
+ ) -> Callable[..., Awaitable[object]]:
422
+ symbol = f"{owner.__module__}.{owner.__qualname__}.{name}"
423
+ descriptor = inspect.getattr_static(owner, name)
424
+ if isinstance(descriptor, (staticmethod, classmethod)):
425
+ raise ApplicationServiceDefinitionError(
426
+ symbol=symbol,
427
+ reason=f"public {role} methods must be instance methods",
428
+ )
429
+ method = getattr(owner, name)
430
+ if not inspect.isfunction(method) or not inspect.iscoroutinefunction(method):
431
+ raise ApplicationServiceDefinitionError(
432
+ symbol=symbol,
433
+ reason=f"public {role} methods must use async def",
434
+ )
435
+ parameters = tuple(inspect.signature(method).parameters.values())
436
+ if not parameters or parameters[0].kind not in (
437
+ inspect.Parameter.POSITIONAL_ONLY,
438
+ inspect.Parameter.POSITIONAL_OR_KEYWORD,
439
+ ):
440
+ # Catalog 与 HTTP generator 都会剥离 receiver;缺少首个位置参数时不能伪装成实例方法。
441
+ raise ApplicationServiceDefinitionError(
442
+ symbol=symbol,
443
+ reason=f"public {role} methods must declare an instance receiver",
444
+ )
445
+ return cast(Callable[..., Awaitable[object]], method)
446
+
447
+
448
+ def _resolved_signature(
449
+ method: Callable[..., Awaitable[object]],
450
+ ) -> tuple[inspect.Signature, dict[str, object]]:
451
+ annotations = get_type_hints(method, include_extras=True)
452
+ signature = inspect.signature(method)
453
+ parameters = tuple(
454
+ parameter.replace(annotation=annotations.get(name, parameter.annotation))
455
+ for name, parameter in signature.parameters.items()
456
+ )
457
+ resolved = signature.replace(
458
+ parameters=parameters,
459
+ return_annotation=annotations.get("return", signature.return_annotation),
460
+ )
461
+ return resolved, annotations
462
+
463
+
464
+ def _compile_argument_validator(
465
+ method: Callable[..., Awaitable[object]],
466
+ signature: inspect.Signature,
467
+ annotations: Mapping[str, object],
468
+ ) -> Callable[..., Awaitable[_ValidatedArguments]]:
469
+ async def capture(*args: object, **kwargs: object) -> _ValidatedArguments:
470
+ # 第一个值是 validation-only self sentinel;业务 implementation 尚未解析或执行。
471
+ return tuple(args[1:]), dict(kwargs)
472
+
473
+ capture.__name__ = method.__name__
474
+ capture.__qualname__ = method.__qualname__
475
+ cast(Any, capture).__signature__ = signature
476
+ capture.__annotations__ = dict(annotations)
477
+ validated = validate_call(
478
+ config=ConfigDict(
479
+ arbitrary_types_allowed=True,
480
+ validate_by_name=True,
481
+ hide_input_in_errors=True,
482
+ )
483
+ )(capture)
484
+ return cast(Callable[..., Awaitable[_ValidatedArguments]], validated)
485
+
486
+
487
+ def _method_source(method: Any, owner: ModuleKey) -> SourceLocation:
488
+ file_name: str | None = None
489
+ line: int | None = None
490
+ try:
491
+ file_name = inspect.getsourcefile(method)
492
+ _, line = inspect.getsourcelines(method)
493
+ except (OSError, TypeError):
494
+ pass
495
+ return SourceLocation(
496
+ owner=str(owner),
497
+ file=file_name,
498
+ line=line,
499
+ symbol=f"{method.__module__}.{method.__qualname__}",
500
+ )
@@ -0,0 +1,188 @@
1
+ """Application Service 的公开 Protocol 与 concrete implementation metadata。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from dataclasses import dataclass
7
+ from typing import TYPE_CHECKING, Protocol, TypeVar
8
+
9
+ from .errors import ApplicationServiceDefinitionError
10
+
11
+ if TYPE_CHECKING:
12
+ from ..settings.provider import SettingProvider
13
+
14
+ _APPLICATION_SERVICE_DEFINITION_ATTRIBUTE = "__python_platform_application_service_definition__"
15
+ _TApplicationService = TypeVar("_TApplicationService", bound="ApplicationService")
16
+ _DEFAULT_CONNECTION_NAME = "default"
17
+
18
+
19
+ class ApplicationServiceContract(Protocol):
20
+ """跨 Module 注入的业务服务必须显式继承此 Protocol marker。"""
21
+
22
+
23
+ class ApplicationService:
24
+ """业务实现;无独立 Contract 时,以自身类型发布受管理代理。"""
25
+
26
+ _setting_provider: SettingProvider | None = None
27
+
28
+ @property
29
+ def setting_provider(self) -> SettingProvider:
30
+ # 由受管理调用绑定到当前 Application;不要求业务构造函数调用 super。
31
+ if self._setting_provider is None:
32
+ raise RuntimeError("setting_provider is only bound during managed invocation")
33
+ return self._setting_provider
34
+
35
+
36
+ class IntegrationService(ApplicationService):
37
+ """仅供应用内协作的服务;必须声明 Contract,不承载用户授权。"""
38
+
39
+
40
+ class _InternalApplicationServiceContributor(ApplicationService):
41
+ """框架内部多贡献者基类;不发布含糊的 single Protocol。"""
42
+
43
+
44
+ @dataclass(frozen=True, slots=True)
45
+ class _ApplicationServiceDefinition:
46
+ contract: type[ApplicationServiceContract] | None
47
+ connection_name: str
48
+
49
+
50
+ def application_service(
51
+ *,
52
+ provides: type[ApplicationServiceContract] | None = None,
53
+ connection_name: str = _DEFAULT_CONNECTION_NAME,
54
+ ) -> Callable[[type[_TApplicationService]], type[_TApplicationService]]:
55
+ """显式声明契约或连接;唯一名义继承的业务契约可以按约定识别。"""
56
+
57
+ normalized_connection = _validate_connection_name(connection_name)
58
+
59
+ def decorate(
60
+ service_type: type[_TApplicationService],
61
+ ) -> type[_TApplicationService]:
62
+ symbol = getattr(service_type, "__qualname__", type(service_type).__qualname__)
63
+ if not isinstance(service_type, type) or not issubclass(service_type, ApplicationService):
64
+ raise ApplicationServiceDefinitionError(
65
+ symbol=symbol,
66
+ reason="application_service decorator requires an ApplicationService subclass",
67
+ )
68
+ if _APPLICATION_SERVICE_DEFINITION_ATTRIBUTE in service_type.__dict__:
69
+ raise ApplicationServiceDefinitionError(
70
+ symbol=f"{service_type.__module__}.{service_type.__qualname__}",
71
+ reason="application service metadata is already declared",
72
+ )
73
+ contract = provides
74
+ if contract is None:
75
+ if not _is_internal_contributor(service_type):
76
+ contract = _infer_contract(service_type)
77
+ else:
78
+ _validate_contract(
79
+ provides,
80
+ symbol=f"{service_type.__module__}.{service_type.__qualname__}",
81
+ )
82
+ setattr(
83
+ service_type,
84
+ _APPLICATION_SERVICE_DEFINITION_ATTRIBUTE,
85
+ _ApplicationServiceDefinition(
86
+ contract=contract,
87
+ connection_name=normalized_connection,
88
+ ),
89
+ )
90
+ return service_type
91
+
92
+ return decorate
93
+
94
+
95
+ def _definition_for(
96
+ candidate: type[object],
97
+ ) -> _ApplicationServiceDefinition | None:
98
+ if not issubclass(candidate, ApplicationService) or candidate in (
99
+ ApplicationService,
100
+ IntegrationService,
101
+ _InternalApplicationServiceContributor,
102
+ ):
103
+ return None
104
+ metadata = candidate.__dict__.get(_APPLICATION_SERVICE_DEFINITION_ATTRIBUTE)
105
+ if metadata is None:
106
+ # 只读取类的继承事实,不把推导结果写回共享类或全局 registry。
107
+ return _ApplicationServiceDefinition(
108
+ contract=_infer_contract(candidate),
109
+ connection_name=_DEFAULT_CONNECTION_NAME,
110
+ )
111
+ if not isinstance(metadata, _ApplicationServiceDefinition):
112
+ raise ApplicationServiceDefinitionError(
113
+ symbol=f"{candidate.__module__}.{candidate.__qualname__}",
114
+ reason="application service decorator metadata has an invalid runtime shape",
115
+ )
116
+ if metadata.contract is None:
117
+ if issubclass(candidate, IntegrationService):
118
+ raise ApplicationServiceDefinitionError(
119
+ symbol=f"{candidate.__module__}.{candidate.__qualname__}",
120
+ reason="IntegrationService requires an independent Contract",
121
+ )
122
+ else:
123
+ _validate_contract(
124
+ metadata.contract,
125
+ symbol=f"{candidate.__module__}.{candidate.__qualname__}",
126
+ )
127
+ _validate_connection_name(metadata.connection_name)
128
+ return metadata
129
+
130
+
131
+ def _inherited_contracts(candidate: type[object]) -> tuple[type[object], ...]:
132
+ return tuple(
133
+ base
134
+ for base in candidate.__mro__[1:]
135
+ if base is not ApplicationServiceContract
136
+ and ApplicationServiceContract in base.__mro__
137
+ and getattr(base, "_is_protocol", False)
138
+ )
139
+
140
+
141
+ def _infer_contract(candidate: type[object]) -> type[ApplicationServiceContract] | None:
142
+ contracts = _inherited_contracts(candidate)
143
+ most_specific = tuple(
144
+ base for base in contracts if not any(base in other.__mro__[1:] for other in contracts)
145
+ )
146
+ if not most_specific and not issubclass(candidate, IntegrationService):
147
+ return None
148
+ if len(most_specific) != 1:
149
+ raise ApplicationServiceDefinitionError(
150
+ symbol=f"{candidate.__module__}.{candidate.__qualname__}",
151
+ reason=(
152
+ "ApplicationService requires one inherited business contract "
153
+ "or explicit provides=Contract"
154
+ ),
155
+ )
156
+ return most_specific[0]
157
+
158
+
159
+ def _validate_contract(candidate: object, *, symbol: str) -> None:
160
+ is_protocol = isinstance(candidate, type) and bool(getattr(candidate, "_is_protocol", False))
161
+ inherits_marker = (
162
+ isinstance(candidate, type) and ApplicationServiceContract in candidate.__mro__
163
+ )
164
+ if not is_protocol or not inherits_marker or candidate is ApplicationServiceContract:
165
+ raise ApplicationServiceDefinitionError(
166
+ symbol=symbol,
167
+ reason="provides must be an ApplicationServiceContract Protocol",
168
+ )
169
+
170
+
171
+ def _is_internal_contributor(candidate: type[object]) -> bool:
172
+ return issubclass(candidate, _InternalApplicationServiceContributor)
173
+
174
+
175
+ def _validate_connection_name(connection_name: str) -> str:
176
+ if not isinstance(connection_name, str) or not connection_name:
177
+ raise ApplicationServiceDefinitionError(
178
+ symbol="<decorator>",
179
+ reason="connection_name must be a non-empty string",
180
+ )
181
+ if connection_name != connection_name.strip() or any(
182
+ not (character.isalnum() or character in "_-") for character in connection_name
183
+ ):
184
+ raise ApplicationServiceDefinitionError(
185
+ symbol="<decorator>",
186
+ reason="connection_name must contain only letters, digits, '_' or '-'",
187
+ )
188
+ return connection_name