usecaseapi 1.0.0__tar.gz → 1.1.1__tar.gz

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 (109) hide show
  1. {usecaseapi-1.0.0 → usecaseapi-1.1.1}/.gitignore +5 -0
  2. usecaseapi-1.1.1/PKG-INFO +295 -0
  3. usecaseapi-1.1.1/README.md +258 -0
  4. {usecaseapi-1.0.0 → usecaseapi-1.1.1}/RELEASE.md +6 -5
  5. {usecaseapi-1.0.0 → usecaseapi-1.1.1}/docs/api-reference.md +23 -11
  6. usecaseapi-1.1.1/docs/assets/brand/favicon-128.png +0 -0
  7. usecaseapi-1.1.1/docs/assets/brand/favicon-16.png +0 -0
  8. usecaseapi-1.1.1/docs/assets/brand/favicon-180.png +0 -0
  9. usecaseapi-1.1.1/docs/assets/brand/favicon-192.png +0 -0
  10. usecaseapi-1.1.1/docs/assets/brand/favicon-256.png +0 -0
  11. usecaseapi-1.1.1/docs/assets/brand/favicon-32.png +0 -0
  12. usecaseapi-1.1.1/docs/assets/brand/favicon-48.png +0 -0
  13. usecaseapi-1.1.1/docs/assets/brand/favicon-512.png +0 -0
  14. usecaseapi-1.1.1/docs/assets/brand/favicon-64.png +0 -0
  15. usecaseapi-1.1.1/docs/assets/brand/favicon.ico +0 -0
  16. usecaseapi-1.1.1/docs/assets/brand/icon-circle-dark.png +0 -0
  17. usecaseapi-1.1.1/docs/assets/brand/icon-circle-light.png +0 -0
  18. usecaseapi-1.1.1/docs/assets/brand/icon-horizontal-on-dark.png +0 -0
  19. usecaseapi-1.1.1/docs/assets/brand/icon-square-dark.png +0 -0
  20. usecaseapi-1.1.1/docs/assets/brand/icon-square-light.png +0 -0
  21. usecaseapi-1.1.1/docs/assets/brand/logo-on-dark.png +0 -0
  22. usecaseapi-1.1.1/docs/assets/brand/logo.png +0 -0
  23. usecaseapi-1.1.1/docs/assets/brand/mark-circle-light.png +0 -0
  24. usecaseapi-1.1.1/docs/assets/brand/mark-circle.png +0 -0
  25. usecaseapi-1.1.1/docs/assets/brand/mark-square-dark.png +0 -0
  26. usecaseapi-1.1.1/docs/assets/brand/mark-square.png +0 -0
  27. {usecaseapi-1.0.0 → usecaseapi-1.1.1}/docs/design.md +6 -6
  28. {usecaseapi-1.0.0 → usecaseapi-1.1.1}/docs/integrations.md +13 -4
  29. usecaseapi-1.1.1/docs/llm-manifest-prompt.md +183 -0
  30. usecaseapi-1.1.1/docs/manifest.md +290 -0
  31. {usecaseapi-1.0.0 → usecaseapi-1.1.1}/docs/pypi-publishing.md +6 -4
  32. usecaseapi-1.1.1/docs/quickstart.md +102 -0
  33. usecaseapi-1.1.1/docs/scaffold.md +55 -0
  34. usecaseapi-1.1.1/docs/testing.md +43 -0
  35. {usecaseapi-1.0.0 → usecaseapi-1.1.1}/docs/versioning.md +6 -6
  36. usecaseapi-1.1.1/examples/basic/README.md +16 -0
  37. usecaseapi-1.1.1/examples/basic/src/commerce/__init__.py +1 -0
  38. usecaseapi-1.1.1/examples/basic/src/commerce/usecases/__init__.py +1 -0
  39. usecaseapi-1.1.1/examples/basic/src/commerce/usecases/check_availability/__init__.py +1 -0
  40. usecaseapi-1.1.1/examples/basic/src/commerce/usecases/check_availability/v1/__init__.py +1 -0
  41. usecaseapi-1.1.1/examples/basic/src/commerce/usecases/check_availability/v1/check_availability_contract.py +51 -0
  42. usecaseapi-1.0.0/examples/basic/app/usecases/inventory/check_availability.py → usecaseapi-1.1.1/examples/basic/src/commerce/usecases/check_availability/v1/check_availability_usecase.py +12 -8
  43. usecaseapi-1.1.1/examples/basic/src/commerce/usecases/checkout/__init__.py +1 -0
  44. usecaseapi-1.1.1/examples/basic/src/commerce/usecases/checkout/v1/__init__.py +1 -0
  45. usecaseapi-1.0.0/examples/basic/app/contracts/checkout/checkout/v1.py → usecaseapi-1.1.1/examples/basic/src/commerce/usecases/checkout/v1/checkout_contract.py +9 -9
  46. usecaseapi-1.1.1/examples/basic/src/commerce/usecases/checkout/v1/checkout_usecase.py +38 -0
  47. usecaseapi-1.1.1/examples/basic/src/commerce/usecases/place_order/__init__.py +1 -0
  48. usecaseapi-1.1.1/examples/basic/src/commerce/usecases/place_order/v1/__init__.py +1 -0
  49. usecaseapi-1.0.0/examples/basic/app/contracts/orders/place_order/v1.py → usecaseapi-1.1.1/examples/basic/src/commerce/usecases/place_order/v1/place_order_contract.py +11 -11
  50. usecaseapi-1.1.1/examples/basic/src/commerce/usecases/place_order/v1/place_order_usecase.py +29 -0
  51. usecaseapi-1.1.1/examples/basic/src/composition.py +44 -0
  52. usecaseapi-1.1.1/examples/basic/tests/commerce/usecases/check_availability/v1/test_check_availability.py +24 -0
  53. usecaseapi-1.1.1/examples/basic/tests/commerce/usecases/checkout/v1/test_checkout.py +20 -0
  54. usecaseapi-1.1.1/examples/basic/tests/commerce/usecases/place_order/v1/test_place_order.py +19 -0
  55. usecaseapi-1.1.1/examples/basic/tests/conftest.py +9 -0
  56. usecaseapi-1.1.1/examples/basic/usecaseapi.ucase.yaml +185 -0
  57. {usecaseapi-1.0.0 → usecaseapi-1.1.1}/pyproject.toml +28 -19
  58. usecaseapi-1.1.1/schema/usecaseapi.manifest.v1.schema.yaml +190 -0
  59. {usecaseapi-1.0.0 → usecaseapi-1.1.1}/scripts/verify_distribution.py +18 -10
  60. {usecaseapi-1.0.0 → usecaseapi-1.1.1}/src/usecaseapi/__init__.py +34 -9
  61. {usecaseapi-1.0.0 → usecaseapi-1.1.1}/src/usecaseapi/api.py +149 -80
  62. usecaseapi-1.1.1/src/usecaseapi/cli.py +253 -0
  63. usecaseapi-1.1.1/src/usecaseapi/manifest.py +1537 -0
  64. usecaseapi-1.1.1/src/usecaseapi/scaffold.py +407 -0
  65. {usecaseapi-1.0.0 → usecaseapi-1.1.1}/tests/test_call.py +1 -0
  66. {usecaseapi-1.0.0 → usecaseapi-1.1.1}/tests/test_coverage_pr_comment.py +9 -3
  67. {usecaseapi-1.0.0 → usecaseapi-1.1.1}/tests/test_full_service_validation.py +154 -178
  68. usecaseapi-1.1.1/tests/test_manifest.py +954 -0
  69. usecaseapi-1.1.1/tests/test_release_workflow.py +37 -0
  70. usecaseapi-1.1.1/tests/test_snapshot_docs_scaffold.py +159 -0
  71. usecaseapi-1.0.0/PKG-INFO +0 -192
  72. usecaseapi-1.0.0/README.md +0 -158
  73. usecaseapi-1.0.0/docs/agent-tools.md +0 -15
  74. usecaseapi-1.0.0/docs/quickstart.md +0 -91
  75. usecaseapi-1.0.0/docs/scaffold.md +0 -67
  76. usecaseapi-1.0.0/docs/testing.md +0 -37
  77. usecaseapi-1.0.0/docs/v1-scope.md +0 -36
  78. usecaseapi-1.0.0/examples/basic/README.md +0 -9
  79. usecaseapi-1.0.0/examples/basic/app/__init__.py +0 -1
  80. usecaseapi-1.0.0/examples/basic/app/composition.py +0 -38
  81. usecaseapi-1.0.0/examples/basic/app/contracts/__init__.py +0 -1
  82. usecaseapi-1.0.0/examples/basic/app/contracts/checkout/__init__.py +0 -1
  83. usecaseapi-1.0.0/examples/basic/app/contracts/checkout/checkout/__init__.py +0 -1
  84. usecaseapi-1.0.0/examples/basic/app/contracts/inventory/__init__.py +0 -1
  85. usecaseapi-1.0.0/examples/basic/app/contracts/inventory/check_availability/__init__.py +0 -1
  86. usecaseapi-1.0.0/examples/basic/app/contracts/inventory/check_availability/v1.py +0 -41
  87. usecaseapi-1.0.0/examples/basic/app/contracts/orders/__init__.py +0 -1
  88. usecaseapi-1.0.0/examples/basic/app/contracts/orders/place_order/__init__.py +0 -1
  89. usecaseapi-1.0.0/examples/basic/app/usecases/__init__.py +0 -1
  90. usecaseapi-1.0.0/examples/basic/app/usecases/checkout/__init__.py +0 -1
  91. usecaseapi-1.0.0/examples/basic/app/usecases/checkout/checkout.py +0 -38
  92. usecaseapi-1.0.0/examples/basic/app/usecases/inventory/__init__.py +0 -1
  93. usecaseapi-1.0.0/examples/basic/app/usecases/orders/__init__.py +0 -1
  94. usecaseapi-1.0.0/examples/basic/app/usecases/orders/place_order.py +0 -28
  95. usecaseapi-1.0.0/src/usecaseapi/cli.py +0 -182
  96. usecaseapi-1.0.0/src/usecaseapi/docs.py +0 -88
  97. usecaseapi-1.0.0/src/usecaseapi/scaffold.py +0 -298
  98. usecaseapi-1.0.0/src/usecaseapi/snapshot.py +0 -173
  99. usecaseapi-1.0.0/tests/test_snapshot_docs_scaffold.py +0 -176
  100. {usecaseapi-1.0.0 → usecaseapi-1.1.1}/CODE_OF_CONDUCT.md +0 -0
  101. {usecaseapi-1.0.0 → usecaseapi-1.1.1}/CONTRIBUTING.md +0 -0
  102. {usecaseapi-1.0.0 → usecaseapi-1.1.1}/LICENSE +0 -0
  103. {usecaseapi-1.0.0 → usecaseapi-1.1.1}/SECURITY.md +0 -0
  104. {usecaseapi-1.0.0 → usecaseapi-1.1.1}/scripts/write_coverage_pr_comment.py +0 -0
  105. {usecaseapi-1.0.0 → usecaseapi-1.1.1}/scripts/write_coverage_summary.py +0 -0
  106. {usecaseapi-1.0.0 → usecaseapi-1.1.1}/src/usecaseapi/contracts.py +0 -0
  107. {usecaseapi-1.0.0 → usecaseapi-1.1.1}/src/usecaseapi/errors.py +0 -0
  108. {usecaseapi-1.0.0 → usecaseapi-1.1.1}/src/usecaseapi/model.py +0 -0
  109. {usecaseapi-1.0.0 → usecaseapi-1.1.1}/src/usecaseapi/py.typed +0 -0
@@ -10,6 +10,11 @@ coverage.*
10
10
  htmlcov/
11
11
 
12
12
  .venv/
13
+ .codex/*
14
+ !.codex/skills/
15
+ .codex/skills/*
16
+ !.codex/skills/usecaseapi-manifest-builder/
17
+ !.codex/skills/usecaseapi-manifest-builder/**
13
18
  build/
14
19
  dist/
15
20
  *.egg-info/
@@ -0,0 +1,295 @@
1
+ Metadata-Version: 2.4
2
+ Name: usecaseapi
3
+ Version: 1.1.1
4
+ Summary: FastAPI-style contracts for Python application use cases.
5
+ Project-URL: Homepage, https://github.com/Wisteria30/usecaseapi
6
+ Project-URL: Documentation, https://github.com/Wisteria30/usecaseapi/tree/main/docs
7
+ Project-URL: Repository, https://github.com/Wisteria30/usecaseapi
8
+ Project-URL: Issues, https://github.com/Wisteria30/usecaseapi/issues
9
+ Author: UseCaseAPI Contributors
10
+ Maintainer: UseCaseAPI Contributors
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: ai-agents,application-api,contracts,protocol,pydantic,usecase,workflow
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: <3.15,>=3.12
25
+ Requires-Dist: pydantic<3,>=2.12.4
26
+ Requires-Dist: pyyaml<7,>=6.0.2
27
+ Requires-Dist: typer>=0.25.1
28
+ Provides-Extra: dev
29
+ Requires-Dist: build>=1.3.0; extra == 'dev'
30
+ Requires-Dist: coverage>=7.12.0; extra == 'dev'
31
+ Requires-Dist: mypy>=1.18.2; extra == 'dev'
32
+ Requires-Dist: pytest>=9.0.1; extra == 'dev'
33
+ Requires-Dist: ruff>=0.14.6; extra == 'dev'
34
+ Requires-Dist: twine>=6.2.0; extra == 'dev'
35
+ Requires-Dist: types-pyyaml>=6.0.12; extra == 'dev'
36
+ Description-Content-Type: text/markdown
37
+
38
+ # UseCaseAPI
39
+
40
+ <p align="center">
41
+ <img src="https://raw.githubusercontent.com/Wisteria30/usecaseapi/main/docs/assets/brand/logo.png" alt="UseCaseAPI" width="720">
42
+ </p>
43
+
44
+ <p align="center">
45
+ <em>FastAPI-style contracts for same-process Python application use cases.</em>
46
+ </p>
47
+
48
+ <p align="center">
49
+ <a href="https://github.com/Wisteria30/usecaseapi/actions/workflows/ci.yml">
50
+ <img src="https://github.com/Wisteria30/usecaseapi/actions/workflows/ci.yml/badge.svg" alt="CI">
51
+ </a>
52
+ <a href="https://pypi.org/project/usecaseapi/">
53
+ <img src="https://img.shields.io/pypi/v/usecaseapi.svg" alt="PyPI">
54
+ </a>
55
+ <a href="https://pypi.org/project/usecaseapi/">
56
+ <img src="https://img.shields.io/pypi/pyversions/usecaseapi.svg" alt="Python versions">
57
+ </a>
58
+ <a href="https://github.com/Wisteria30/usecaseapi/blob/main/LICENSE">
59
+ <img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License: MIT">
60
+ </a>
61
+ </p>
62
+
63
+ ---
64
+
65
+ **Documentation**: <a href="https://github.com/Wisteria30/usecaseapi/tree/main/docs">github.com/Wisteria30/usecaseapi/tree/main/docs</a>
66
+
67
+ **Source Code**: <a href="https://github.com/Wisteria30/usecaseapi">github.com/Wisteria30/usecaseapi</a>
68
+
69
+ ---
70
+
71
+ UseCaseAPI gives internal application use cases stable, versioned contracts.
72
+
73
+ Define a contract with a Python `Protocol`, Pydantic v2 models, and domain exceptions. Bind that contract to an implementation explicitly. Call it in the same process without turning HTTP, RPC, serialization, dependency injection containers, or transaction managers into runtime requirements.
74
+
75
+ Add it when your application has internal use case nodes that need stable names, versions, input/output schemas, declared dependencies, and a canonical YAML contract catalog. It helps teams and AI coding agents see what can be called, what can fail, and whether a change broke an application-layer contract before that break ships.
76
+
77
+ It is designed for applications with many internal use case nodes: workflow-like systems, AI-agent tool surfaces, CLIs, batch workers, FastAPI/Django backends, and codebases where accidental application-layer breaking changes are costly.
78
+
79
+ ## Key features
80
+
81
+ - **Code-first contracts**: use `Protocol`, `Model`, `UseCase`, `UseCaseRef`, and normal Python exceptions.
82
+ - **Same-process calls**: call implementations directly, with no JSON serialization in the call path.
83
+ - **Explicit bindings**: connect a contract token to an implementation factory in one place.
84
+ - **Declared dependencies**: expose and validate which use cases can call which other use cases.
85
+ - **Versioned identity**: identify contracts as stable names such as `commerce.place_order@v1`.
86
+ - **Manifest artifacts**: generate a YAML catalog, conservative diffs, Markdown docs, and Mermaid graphs.
87
+ - **CLI scaffolding**: create a contract, implementation, and test layout from one command.
88
+ - **Typed distribution**: ships `py.typed` for downstream type checkers.
89
+
90
+ ## Requirements
91
+
92
+ UseCaseAPI requires:
93
+
94
+ - Python `>=3.12,<3.15`
95
+ - Pydantic v2
96
+ - PyYAML
97
+ - Typer
98
+
99
+ ## Installation
100
+
101
+ ```bash
102
+ uv add usecaseapi
103
+ ```
104
+
105
+ ## Example
106
+
107
+ ### Create a contract
108
+
109
+ ```python
110
+ # src/commerce/usecases/place_order/v1/place_order_contract.py
111
+ from __future__ import annotations
112
+
113
+ from typing import ClassVar, Protocol
114
+
115
+ from usecaseapi import Contract, Model, UseCase, UseCaseError, UseCaseRef, define_usecase
116
+
117
+
118
+ class PlaceOrderUseCaseInput(Model):
119
+ user_id: str
120
+ sku_id: str
121
+ quantity: int
122
+
123
+
124
+ class PlaceOrderUseCaseOutput(Model):
125
+ order_id: str
126
+
127
+
128
+ class PlaceOrderError(UseCaseError):
129
+ code: ClassVar[str] = "commerce.place_order"
130
+
131
+
132
+ class PlaceOrder(UseCase[PlaceOrderUseCaseInput, PlaceOrderUseCaseOutput], Protocol):
133
+ async def __call__(self, input: PlaceOrderUseCaseInput, /) -> PlaceOrderUseCaseOutput:
134
+ ...
135
+
136
+
137
+ PLACE_ORDER_USECASE: UseCaseRef[PlaceOrderUseCaseInput, PlaceOrderUseCaseOutput] = define_usecase(
138
+ PlaceOrder,
139
+ Contract(
140
+ name="commerce.place_order",
141
+ version=1,
142
+ input=PlaceOrderUseCaseInput,
143
+ output=PlaceOrderUseCaseOutput,
144
+ raises=(PlaceOrderError,),
145
+ ),
146
+ )
147
+ ```
148
+
149
+ ### Implement it
150
+
151
+ ```python
152
+ # src/commerce/usecases/place_order/v1/place_order_usecase.py
153
+ from commerce.usecases.place_order.v1.place_order_contract import (
154
+ PlaceOrderUseCaseInput,
155
+ PlaceOrderUseCaseOutput,
156
+ )
157
+
158
+
159
+ class PlaceOrderUseCase:
160
+ async def __call__(
161
+ self,
162
+ input: PlaceOrderUseCaseInput,
163
+ /,
164
+ ) -> PlaceOrderUseCaseOutput:
165
+ return PlaceOrderUseCaseOutput(order_id="ord_123")
166
+ ```
167
+
168
+ The implementation class is ordinary Python. Instantiate it in your composition root
169
+ or in tests with the dependencies that project actually uses.
170
+
171
+ ### Bind and call it
172
+
173
+ ```python
174
+ from dataclasses import dataclass
175
+
176
+ from usecaseapi import UseCaseAPI
177
+
178
+ from commerce.usecases.place_order.v1.place_order_contract import (
179
+ PLACE_ORDER_USECASE,
180
+ PlaceOrderUseCaseInput,
181
+ )
182
+ from commerce.usecases.place_order.v1.place_order_usecase import PlaceOrderUseCase
183
+
184
+
185
+ @dataclass(frozen=True)
186
+ class AppContext:
187
+ tenant_id: str
188
+
189
+
190
+ usecases = UseCaseAPI[AppContext]()
191
+ usecases.bind(PLACE_ORDER_USECASE, lambda caller: PlaceOrderUseCase())
192
+
193
+ caller = usecases.caller(AppContext(tenant_id="tenant_a"))
194
+ output = await caller.call(
195
+ PLACE_ORDER_USECASE,
196
+ PlaceOrderUseCaseInput(user_id="u1", sku_id="s1", quantity=1),
197
+ )
198
+ ```
199
+
200
+ The call is same-process and direct. UseCaseAPI does not serialize the input or output.
201
+
202
+ ## CLI scaffolding
203
+
204
+ Create the first contract version:
205
+
206
+ ```bash
207
+ usecaseapi scaffold commerce place_order --output-root src
208
+ ```
209
+
210
+ Create the next major version from the latest existing contract:
211
+
212
+ ```bash
213
+ usecaseapi scaffold commerce place_order --output-root src --next
214
+ ```
215
+
216
+ The first command creates:
217
+
218
+ ```text
219
+ src/commerce/usecases/place_order/v1/place_order_contract.py
220
+ src/commerce/usecases/place_order/v1/place_order_usecase.py
221
+ tests/commerce/usecases/place_order/v1/test_place_order.py
222
+ ```
223
+
224
+ See [docs/scaffold.md](docs/scaffold.md) for the generated layout and options.
225
+
226
+ ## Manifest
227
+
228
+ Export the canonical contract catalog:
229
+
230
+ ```bash
231
+ usecaseapi manifest export composition:usecases --output usecaseapi.ucase.yaml
232
+ ```
233
+
234
+ Validate it and check that code still matches it:
235
+
236
+ ```bash
237
+ usecaseapi manifest validate usecaseapi.ucase.yaml
238
+ usecaseapi manifest check-sync composition:usecases usecaseapi.ucase.yaml
239
+ ```
240
+
241
+ Generate Python contract and implementation skeletons from the catalog:
242
+
243
+ ```bash
244
+ usecaseapi manifest scaffold usecaseapi.ucase.yaml --root .
245
+ ```
246
+
247
+ Render derived outputs:
248
+
249
+ ```bash
250
+ usecaseapi docs usecaseapi.ucase.yaml --output usecaseapi.md
251
+ usecaseapi graph usecaseapi.ucase.yaml --output usecaseapi.mmd
252
+ usecaseapi diff old.ucase.yaml new.ucase.yaml
253
+ ```
254
+
255
+ See [docs/scaffold.md](docs/scaffold.md), [docs/manifest.md](docs/manifest.md), and
256
+ [docs/llm-manifest-prompt.md](docs/llm-manifest-prompt.md) for generated layouts,
257
+ Manifest syntax, and an LLM prompt for converging requirements into `usecaseapi.ucase.yaml`.
258
+
259
+ ## Manifest docs and graph
260
+
261
+ UseCaseAPI can turn a composed application into inspectable artifacts:
262
+
263
+ - a canonical YAML Manifest;
264
+ - a conservative diff between Manifests;
265
+ - Markdown documentation for use cases and dependencies;
266
+ - Mermaid graph output for the dependency graph.
267
+
268
+ See [docs/api-reference.md](docs/api-reference.md), [docs/versioning.md](docs/versioning.md), and [docs/integrations.md](docs/integrations.md).
269
+
270
+ ## What UseCaseAPI is not
271
+
272
+ UseCaseAPI is not a web framework and does not add HTTP, RPC, serialization, service locators, dependency injection containers, or transaction managers to your application runtime.
273
+
274
+ You can write plain classes and functions for small projects. UseCaseAPI becomes useful when an application has enough internal use case nodes that stable names, versions, declared dependencies, documented errors, Manifest catalogs, and generated docs start paying for themselves.
275
+
276
+ ## Development
277
+
278
+ ```bash
279
+ uv sync --extra dev
280
+ uv run pytest
281
+ uv run mypy
282
+ uv run ruff check .
283
+ uv run ruff format --check .
284
+ ```
285
+
286
+ For release validation:
287
+
288
+ ```bash
289
+ uv build
290
+ uv run twine check dist/*
291
+ uv run --isolated --no-project --with dist/*.whl scripts/verify_distribution.py
292
+ uv run --isolated --no-project --with dist/*.tar.gz scripts/verify_distribution.py
293
+ ```
294
+
295
+ See [docs/pypi-publishing.md](docs/pypi-publishing.md) for the publishing workflow.
@@ -0,0 +1,258 @@
1
+ # UseCaseAPI
2
+
3
+ <p align="center">
4
+ <img src="https://raw.githubusercontent.com/Wisteria30/usecaseapi/main/docs/assets/brand/logo.png" alt="UseCaseAPI" width="720">
5
+ </p>
6
+
7
+ <p align="center">
8
+ <em>FastAPI-style contracts for same-process Python application use cases.</em>
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://github.com/Wisteria30/usecaseapi/actions/workflows/ci.yml">
13
+ <img src="https://github.com/Wisteria30/usecaseapi/actions/workflows/ci.yml/badge.svg" alt="CI">
14
+ </a>
15
+ <a href="https://pypi.org/project/usecaseapi/">
16
+ <img src="https://img.shields.io/pypi/v/usecaseapi.svg" alt="PyPI">
17
+ </a>
18
+ <a href="https://pypi.org/project/usecaseapi/">
19
+ <img src="https://img.shields.io/pypi/pyversions/usecaseapi.svg" alt="Python versions">
20
+ </a>
21
+ <a href="https://github.com/Wisteria30/usecaseapi/blob/main/LICENSE">
22
+ <img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License: MIT">
23
+ </a>
24
+ </p>
25
+
26
+ ---
27
+
28
+ **Documentation**: <a href="https://github.com/Wisteria30/usecaseapi/tree/main/docs">github.com/Wisteria30/usecaseapi/tree/main/docs</a>
29
+
30
+ **Source Code**: <a href="https://github.com/Wisteria30/usecaseapi">github.com/Wisteria30/usecaseapi</a>
31
+
32
+ ---
33
+
34
+ UseCaseAPI gives internal application use cases stable, versioned contracts.
35
+
36
+ Define a contract with a Python `Protocol`, Pydantic v2 models, and domain exceptions. Bind that contract to an implementation explicitly. Call it in the same process without turning HTTP, RPC, serialization, dependency injection containers, or transaction managers into runtime requirements.
37
+
38
+ Add it when your application has internal use case nodes that need stable names, versions, input/output schemas, declared dependencies, and a canonical YAML contract catalog. It helps teams and AI coding agents see what can be called, what can fail, and whether a change broke an application-layer contract before that break ships.
39
+
40
+ It is designed for applications with many internal use case nodes: workflow-like systems, AI-agent tool surfaces, CLIs, batch workers, FastAPI/Django backends, and codebases where accidental application-layer breaking changes are costly.
41
+
42
+ ## Key features
43
+
44
+ - **Code-first contracts**: use `Protocol`, `Model`, `UseCase`, `UseCaseRef`, and normal Python exceptions.
45
+ - **Same-process calls**: call implementations directly, with no JSON serialization in the call path.
46
+ - **Explicit bindings**: connect a contract token to an implementation factory in one place.
47
+ - **Declared dependencies**: expose and validate which use cases can call which other use cases.
48
+ - **Versioned identity**: identify contracts as stable names such as `commerce.place_order@v1`.
49
+ - **Manifest artifacts**: generate a YAML catalog, conservative diffs, Markdown docs, and Mermaid graphs.
50
+ - **CLI scaffolding**: create a contract, implementation, and test layout from one command.
51
+ - **Typed distribution**: ships `py.typed` for downstream type checkers.
52
+
53
+ ## Requirements
54
+
55
+ UseCaseAPI requires:
56
+
57
+ - Python `>=3.12,<3.15`
58
+ - Pydantic v2
59
+ - PyYAML
60
+ - Typer
61
+
62
+ ## Installation
63
+
64
+ ```bash
65
+ uv add usecaseapi
66
+ ```
67
+
68
+ ## Example
69
+
70
+ ### Create a contract
71
+
72
+ ```python
73
+ # src/commerce/usecases/place_order/v1/place_order_contract.py
74
+ from __future__ import annotations
75
+
76
+ from typing import ClassVar, Protocol
77
+
78
+ from usecaseapi import Contract, Model, UseCase, UseCaseError, UseCaseRef, define_usecase
79
+
80
+
81
+ class PlaceOrderUseCaseInput(Model):
82
+ user_id: str
83
+ sku_id: str
84
+ quantity: int
85
+
86
+
87
+ class PlaceOrderUseCaseOutput(Model):
88
+ order_id: str
89
+
90
+
91
+ class PlaceOrderError(UseCaseError):
92
+ code: ClassVar[str] = "commerce.place_order"
93
+
94
+
95
+ class PlaceOrder(UseCase[PlaceOrderUseCaseInput, PlaceOrderUseCaseOutput], Protocol):
96
+ async def __call__(self, input: PlaceOrderUseCaseInput, /) -> PlaceOrderUseCaseOutput:
97
+ ...
98
+
99
+
100
+ PLACE_ORDER_USECASE: UseCaseRef[PlaceOrderUseCaseInput, PlaceOrderUseCaseOutput] = define_usecase(
101
+ PlaceOrder,
102
+ Contract(
103
+ name="commerce.place_order",
104
+ version=1,
105
+ input=PlaceOrderUseCaseInput,
106
+ output=PlaceOrderUseCaseOutput,
107
+ raises=(PlaceOrderError,),
108
+ ),
109
+ )
110
+ ```
111
+
112
+ ### Implement it
113
+
114
+ ```python
115
+ # src/commerce/usecases/place_order/v1/place_order_usecase.py
116
+ from commerce.usecases.place_order.v1.place_order_contract import (
117
+ PlaceOrderUseCaseInput,
118
+ PlaceOrderUseCaseOutput,
119
+ )
120
+
121
+
122
+ class PlaceOrderUseCase:
123
+ async def __call__(
124
+ self,
125
+ input: PlaceOrderUseCaseInput,
126
+ /,
127
+ ) -> PlaceOrderUseCaseOutput:
128
+ return PlaceOrderUseCaseOutput(order_id="ord_123")
129
+ ```
130
+
131
+ The implementation class is ordinary Python. Instantiate it in your composition root
132
+ or in tests with the dependencies that project actually uses.
133
+
134
+ ### Bind and call it
135
+
136
+ ```python
137
+ from dataclasses import dataclass
138
+
139
+ from usecaseapi import UseCaseAPI
140
+
141
+ from commerce.usecases.place_order.v1.place_order_contract import (
142
+ PLACE_ORDER_USECASE,
143
+ PlaceOrderUseCaseInput,
144
+ )
145
+ from commerce.usecases.place_order.v1.place_order_usecase import PlaceOrderUseCase
146
+
147
+
148
+ @dataclass(frozen=True)
149
+ class AppContext:
150
+ tenant_id: str
151
+
152
+
153
+ usecases = UseCaseAPI[AppContext]()
154
+ usecases.bind(PLACE_ORDER_USECASE, lambda caller: PlaceOrderUseCase())
155
+
156
+ caller = usecases.caller(AppContext(tenant_id="tenant_a"))
157
+ output = await caller.call(
158
+ PLACE_ORDER_USECASE,
159
+ PlaceOrderUseCaseInput(user_id="u1", sku_id="s1", quantity=1),
160
+ )
161
+ ```
162
+
163
+ The call is same-process and direct. UseCaseAPI does not serialize the input or output.
164
+
165
+ ## CLI scaffolding
166
+
167
+ Create the first contract version:
168
+
169
+ ```bash
170
+ usecaseapi scaffold commerce place_order --output-root src
171
+ ```
172
+
173
+ Create the next major version from the latest existing contract:
174
+
175
+ ```bash
176
+ usecaseapi scaffold commerce place_order --output-root src --next
177
+ ```
178
+
179
+ The first command creates:
180
+
181
+ ```text
182
+ src/commerce/usecases/place_order/v1/place_order_contract.py
183
+ src/commerce/usecases/place_order/v1/place_order_usecase.py
184
+ tests/commerce/usecases/place_order/v1/test_place_order.py
185
+ ```
186
+
187
+ See [docs/scaffold.md](docs/scaffold.md) for the generated layout and options.
188
+
189
+ ## Manifest
190
+
191
+ Export the canonical contract catalog:
192
+
193
+ ```bash
194
+ usecaseapi manifest export composition:usecases --output usecaseapi.ucase.yaml
195
+ ```
196
+
197
+ Validate it and check that code still matches it:
198
+
199
+ ```bash
200
+ usecaseapi manifest validate usecaseapi.ucase.yaml
201
+ usecaseapi manifest check-sync composition:usecases usecaseapi.ucase.yaml
202
+ ```
203
+
204
+ Generate Python contract and implementation skeletons from the catalog:
205
+
206
+ ```bash
207
+ usecaseapi manifest scaffold usecaseapi.ucase.yaml --root .
208
+ ```
209
+
210
+ Render derived outputs:
211
+
212
+ ```bash
213
+ usecaseapi docs usecaseapi.ucase.yaml --output usecaseapi.md
214
+ usecaseapi graph usecaseapi.ucase.yaml --output usecaseapi.mmd
215
+ usecaseapi diff old.ucase.yaml new.ucase.yaml
216
+ ```
217
+
218
+ See [docs/scaffold.md](docs/scaffold.md), [docs/manifest.md](docs/manifest.md), and
219
+ [docs/llm-manifest-prompt.md](docs/llm-manifest-prompt.md) for generated layouts,
220
+ Manifest syntax, and an LLM prompt for converging requirements into `usecaseapi.ucase.yaml`.
221
+
222
+ ## Manifest docs and graph
223
+
224
+ UseCaseAPI can turn a composed application into inspectable artifacts:
225
+
226
+ - a canonical YAML Manifest;
227
+ - a conservative diff between Manifests;
228
+ - Markdown documentation for use cases and dependencies;
229
+ - Mermaid graph output for the dependency graph.
230
+
231
+ See [docs/api-reference.md](docs/api-reference.md), [docs/versioning.md](docs/versioning.md), and [docs/integrations.md](docs/integrations.md).
232
+
233
+ ## What UseCaseAPI is not
234
+
235
+ UseCaseAPI is not a web framework and does not add HTTP, RPC, serialization, service locators, dependency injection containers, or transaction managers to your application runtime.
236
+
237
+ You can write plain classes and functions for small projects. UseCaseAPI becomes useful when an application has enough internal use case nodes that stable names, versions, declared dependencies, documented errors, Manifest catalogs, and generated docs start paying for themselves.
238
+
239
+ ## Development
240
+
241
+ ```bash
242
+ uv sync --extra dev
243
+ uv run pytest
244
+ uv run mypy
245
+ uv run ruff check .
246
+ uv run ruff format --check .
247
+ ```
248
+
249
+ For release validation:
250
+
251
+ ```bash
252
+ uv build
253
+ uv run twine check dist/*
254
+ uv run --isolated --no-project --with dist/*.whl scripts/verify_distribution.py
255
+ uv run --isolated --no-project --with dist/*.tar.gz scripts/verify_distribution.py
256
+ ```
257
+
258
+ See [docs/pypi-publishing.md](docs/pypi-publishing.md) for the publishing workflow.
@@ -34,9 +34,10 @@ The `src/usecaseapi/__init__.py` module intentionally does not define
34
34
  Publishing is handled by `.github/workflows/release.yml`.
35
35
 
36
36
  On `main`, the workflow detects changes to `pyproject.toml` `[project].version`,
37
- validates the package, creates the annotated `v{version}` tag, and publishes to
38
- PyPI. Pushing a matching `v*` tag or running the workflow manually also publishes
39
- the current package version.
37
+ validates the package, creates the annotated `v{version}` tag, creates a GitHub
38
+ Release with generated release notes and distribution artifacts, and publishes
39
+ to PyPI. Pushing a matching `v*` tag or running the workflow manually also
40
+ publishes the current package version.
40
41
 
41
42
  The workflow expects PyPI Trusted Publishing:
42
43
 
@@ -55,5 +56,5 @@ The workflow uses GitHub OIDC and `uv publish`; no PyPI API token should be stor
55
56
  4. Create the `pypi` GitHub environment and require approval if desired.
56
57
  5. Review the generated package metadata and README rendering locally.
57
58
  6. Update `pyproject.toml` `[project].version` and changelog or GitHub release notes.
58
- 7. Merge the version bump to `main`; GitHub Actions creates the annotated tag and publishes.
59
- 8. Confirm the GitHub Actions release workflow completed and the PyPI project page is correct.
59
+ 7. Merge the version bump to `main`; GitHub Actions creates the annotated tag, creates the GitHub Release, and publishes.
60
+ 8. Confirm the GitHub Actions release workflow completed, the GitHub Release is correct, and the PyPI project page is correct.
@@ -10,7 +10,7 @@ Base class for domain errors that are part of public usecase contracts. Subclass
10
10
 
11
11
  ```python
12
12
  class PlaceOrderError(UseCaseError):
13
- code: ClassVar[str] = "orders.place_order"
13
+ code: ClassVar[str] = "commerce.place_order"
14
14
  ```
15
15
 
16
16
  ## `UseCase[InputT, OutputT]`
@@ -18,8 +18,8 @@ class PlaceOrderError(UseCaseError):
18
18
  Structural Protocol for async callable usecase implementations.
19
19
 
20
20
  ```python
21
- class PlaceOrder(UseCase[Input, Output], Protocol):
22
- async def __call__(self, input: Input, /) -> Output:
21
+ class PlaceOrder(UseCase[PlaceOrderUseCaseInput, PlaceOrderUseCaseOutput], Protocol):
22
+ async def __call__(self, input: PlaceOrderUseCaseInput, /) -> PlaceOrderUseCaseOutput:
23
23
  ...
24
24
  ```
25
25
 
@@ -49,8 +49,8 @@ Registry and runtime.
49
49
 
50
50
  ```python
51
51
  api = UseCaseAPI[AppContext]()
52
- api.register(PLACE_ORDER)
53
- api.bind(PLACE_ORDER, lambda caller: PlaceOrderImpl())
52
+ api.register(PLACE_ORDER_USECASE)
53
+ api.bind(PLACE_ORDER_USECASE, lambda caller: PlaceOrderUseCase())
54
54
  api.validate()
55
55
  caller = api.caller(context)
56
56
  ```
@@ -60,20 +60,32 @@ caller = api.caller(context)
60
60
  Context-bound call surface.
61
61
 
62
62
  ```python
63
- output = await caller.call(PLACE_ORDER, input)
63
+ output = await caller.call(PLACE_ORDER_USECASE, input)
64
64
  results = await caller.gather(caller.call(A, a), caller.call(B, b))
65
65
  ```
66
66
 
67
67
  `Caller.gather` uses `asyncio.TaskGroup`, so multiple failures preserve Python `ExceptionGroup` behavior.
68
68
 
69
- ## `snapshot_from_api(api)`
69
+ ## `manifest_from_api(api)`
70
70
 
71
- Creates a JSON-serializable snapshot for CI and docs.
71
+ Creates a YAML-friendly Manifest catalog from a registered `UseCaseAPI` instance.
72
72
 
73
- ## `diff_snapshots(old, new)`
73
+ ## `load_manifest(path)` / `dump_manifest(manifest, path)`
74
+
75
+ Loads and writes validated `.ucase.yaml` Manifest files.
76
+
77
+ ## `validate_manifest(manifest)`
78
+
79
+ Validates Manifest shape, type expression syntax, error boundaries, model references, and usecase keys.
80
+
81
+ ## `scaffold_from_manifest(manifest)`
82
+
83
+ Generates Python contract and implementation skeletons from Manifest entries.
84
+
85
+ ## `diff_manifests(old, new)`
74
86
 
75
87
  Performs conservative breaking-change detection.
76
88
 
77
- ## `render_markdown(api)` / `render_mermaid(api)`
89
+ ## `render_manifest_markdown(manifest)` / `render_manifest_graph(manifest)`
78
90
 
79
- Renders human-readable docs and graph diagrams.
91
+ Renders human-readable docs and graph diagrams from Manifest data.