Skip to main content

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!" }

Docshttp://localhost:8080/api/docs Healthhttp://localhost:8080/api/health OpenAPI JSONhttp://localhost:8080/api/openapi.json (also /api/openapi.yaml) Metricshttp://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 concerns
  • Input / Output — DTOs with automatic OpenAPI schema generation via Pydantic Field()
  • Output.status_code — custom HTTP status codes per response
  • UseCaseException — 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.
  • Swagger UI at /{basePath}/docs — auto-generated from registered use cases
  • OpenAPI spec at /{basePath}/openapi.json and /{basePath}/openapi.yaml — raw spec download
  • Health check at GET /{basePath}/healthIETF Health Check Response Format
  • Prometheus metrics at GET /{basePath}/metricsPrometheus 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.typed marker (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 yet
  • setup(host) runs during build() in dependency order
  • validate(host) runs after registration freeze and can abort startup
  • shutdown() 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",
                # Optional OpenAPI Operation object — when present, the official
                # OpenApiPlugin merges the route into /openapi.json and /docs (ADR-0003).
                openapi={
                    "summary": "Hello from a plugin route",
                    "responses": {"200": {"description": "OK"}},
                },
                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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

macss_modular_api-0.6.0.tar.gz (74.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

macss_modular_api-0.6.0-py3-none-any.whl (82.8 kB view details)

Uploaded Python 3

File details

Details for the file macss_modular_api-0.6.0.tar.gz.

File metadata

  • Download URL: macss_modular_api-0.6.0.tar.gz
  • Upload date:
  • Size: 74.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for macss_modular_api-0.6.0.tar.gz
Algorithm Hash digest
SHA256 503ba52d5b4694f2bf841b8c4df682a016365a3daf3eee7c05cfd5f4d77ba1ae
MD5 13d820057e332caff95965ec7c7bc04a
BLAKE2b-256 984d222635cc9db1bd8324bebcf3a6f2ce97bc1e0a735086f95d0f3f8b675444

See more details on using hashes here.

File details

Details for the file macss_modular_api-0.6.0-py3-none-any.whl.

File metadata

File hashes

Hashes for macss_modular_api-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0dbc96b037c84f18cbd0480e002c1f86a9654c20e061d73a75f966f54530489f
MD5 663793174d0cac0f72c8d97336a59ee4
BLAKE2b-256 608ce754b1ff1844bab5deef19e8a701c12dadc6376bc7ab2e293f2c1ce84bc9

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page