Use-case-centric toolkit for building modular APIs with Starlette. Define UseCase classes (input → validate → execute → output), connect them to HTTP routes, and expose OpenAPI documentation automatically.
Project description
modular-api
Use-case-centric toolkit for building modular APIs with Starlette.
Define UseCase classes (input → validate → execute → output), connect them to HTTP routes, and get automatic OpenAPI documentation.
Also available in Dart: modular_api · TypeScript: @macss/modular-api
Quick start
from modular_api import ModularApi, ModuleBuilder
# ─── Module builder (separate file in real projects) ──────────
def build_greetings_module(m: ModuleBuilder) -> None:
m.usecase("hello", HelloWorld)
# ─── Server ───────────────────────────────────────────────────
api = ModularApi(base_path="/api")
api.module("greetings", build_greetings_module)
api.serve(port=8080)
curl -X POST http://localhost:8080/api/greetings/hello \
-H "Content-Type: application/json" \
-d '{"name":"World"}'
{ "message": "Hello, World!" }
Docs → http://localhost:8080/api/docs
Health → http://localhost:8080/api/health
OpenAPI JSON → http://localhost:8080/api/openapi.json (also /api/openapi.yaml)
Metrics → http://localhost:8080/api/metrics (opt-in)
See example/example.py for the full implementation including Input, Output, UseCase with validate(), and the builder.
Features
UseCase[I, O]— pure business logic, no HTTP concernsInput/Output— DTOs with automatic OpenAPI schema generation via PydanticField()Output.status_code— custom HTTP status codes per responseUseCaseException— structured error handling (status_code, message, error_code, details)ModularApi+ModuleBuilder— module registration and routing- Constructor-based unit testing with fake dependency injection
cors_middleware— built-in CORS support- All public endpoints resolve under the configured
base_path. - Scalar docs at
/{basePath}/docs— auto-generated from registered use cases - OpenAPI spec at
/{basePath}/openapi.jsonand/{basePath}/openapi.yaml— raw spec download - Health check at
GET /{basePath}/health— IETF Health Check Response Format - Prometheus metrics at
GET /{basePath}/metrics— Prometheus exposition format - Structured JSON logging — Loki/Grafana compatible, request-scoped with trace_id
- All endpoints default to
POST(configurable per use case) - Full type annotations with
py.typedmarker (PEP 561)
Plugin host
The public plugin contract is available from the package exports and is already used by the official health, metrics, OpenAPI, and docs plugins.
Current lifecycle behavior:
api.plugin(...)registers a plugin instance without running setup yetsetup(host)runs duringbuild()in dependency ordervalidate(host)runs after registration freeze and can abort startupshutdown()runs in reverse setup order on normal shutdown and on partial startup rollback- plugin routes always resolve under the configured
base_path - all three public middleware slots are active with deterministic ordering
from modular_api import ModularApi, Plugin, PluginHost, PluginManifest, PluginRoute
class HelloPlugin(Plugin):
manifest = PluginManifest(
id="acme.hello",
display_name="Hello Plugin",
version="0.1.0",
host_api_version=">=0.1.0 <0.2.0",
)
def setup(self, host: PluginHost) -> None:
host.register_route(
PluginRoute(
id="hello-plugin",
method="GET",
path="/hello-plugin",
visibility="custom",
handler=lambda _: {
"status": 200,
"body": {"ok": True, "basePath": host.metadata().base_path},
},
)
)
def validate(self, host: PluginHost):
return []
api = ModularApi(base_path="/api")
api.plugin(HelloPlugin())
app = api.build()
Installation
pip install macss-modular-api
With Uvicorn for api.serve():
pip install macss-modular-api[serve]
Error handling
async def execute(self) -> FoundUserOutput:
user = await repository.find_by_id(self.input.user_id)
if not user:
raise UseCaseException(
status_code=404,
message="User not found",
error_code="USER_NOT_FOUND",
)
return FoundUserOutput(name=user.name)
Testing
async def test_hello_world():
usecase = HelloWorld(HelloInput(name="World"))
error = usecase.validate()
assert error is None
output = await usecase.execute()
assert output.message == "Hello, World!"
See doc/testing_guide.md for the full testing guide.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file macss_modular_api-0.4.8.tar.gz.
File metadata
- Download URL: macss_modular_api-0.4.8.tar.gz
- Upload date:
- Size: 72.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
51a0a2b5d9787425751482c9ed8f534e1a8e6d4bea24798c39f8e22ca6f793ba
|
|
| MD5 |
1ed4314fb1ddd042ead34501a1858fa6
|
|
| BLAKE2b-256 |
305c4eb81df2d200de420b9591dba9135455e87788d899d96f4c61e09700ecf2
|
File details
Details for the file macss_modular_api-0.4.8-py3-none-any.whl.
File metadata
- Download URL: macss_modular_api-0.4.8-py3-none-any.whl
- Upload date:
- Size: 81.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5534295b1ec2274de7e19396b1facd3d33aaba2258454694b599a9e3e2364d28
|
|
| MD5 |
74212d6e83f375bf763e8c5286ed966b
|
|
| BLAKE2b-256 |
ce8b5baf97ccd0c2b5e4a8398f2909500382ae9759ff87d529182f05380cdb38
|