PythonSDK generation

Generate a Python SDK from your OpenAPI spec

Octri reads your OpenAPI document and writes an async-first Python client with typed models, publishes it to PyPI under your own package name, and rebuilds it every time the spec changes.

install · PyPI
pip install acme-api

The client they actually get

What your users write once the Python package is installed.

Two call-site shapes come out of the same spec. class-namespaced is the default: a client object whose operations are grouped by resource, which is the surface every widely used SDK ships and the one your own reference docs already assume. functions is the override, and it is the one to reach for when your users care about tree-shaking. It is a single setting, per language, and nothing else about the client changes with it.

Async first either way, with blocking variants generated beside the coroutines when you turn them on.

Published under your own PyPI account. The distribution name is what users type after pip install, and it is normalised, so acme_api and acme-api are the same name to the index.

python · class-namespaced (default)
from acme_api import Acme, ClientAuthConfig, ClientConfig

acme = Acme(ClientConfig(
    base_url="https://api.acme.com/v1",
    auth=ClientAuthConfig(bearer_auth=os.environ["ACME_TOKEN"]),
))

# Every paginated operation gets a _paginated companion that yields items
# rather than page envelopes.
async for user in acme.users.list_paginated(limit=100):
    print(user.id)

invoice = await acme.invoices.create(customer_id="cus_123", amount_cents=4900)
python · functions
from acme_api import (
    ClientAuthConfig,
    ClientConfig,
    configure_client,
    create_invoice,
    list_users_paginated,
)

configure_client(ClientConfig(
    base_url="https://api.acme.com/v1",
    auth=ClientAuthConfig(bearer_auth=os.environ["ACME_TOKEN"]),
))

async for user in list_users_paginated(limit=100):
    print(user.id)

What you decide for Python

Set project-wide, or per language. The data model is the one Python users feel.

  • Data model

    pydantic (default), dataclass, typeddict

    pydantic is the default because it is the typed-Python ecosystem’s response model: it validates and coerces at the boundary, so a response that violates your spec fails loudly rather than three frames deep. dataclass drops the dependency; typeddict is plain dicts with hints and no runtime cost.

  • Client style

    class-namespaced (default), class, namespaced, functions

    A client object grouped by resource is the default. Free functions are the override.

  • Argument style

    positional (default), object

    Plain named parameters, which is how Python is actually called. A wide operation is promoted to a request object automatically, so you only pay for the ceremony where it buys something.

  • Sync methods

    omit (default), include

    Adds a blocking companion beside each coroutine, for callers who are not running an event loop.

  • Method naming

    short (default), full

    Short strips the namespace word, so get_balance under balance becomes balance.get().

Decide this before the first release

pydantic is the default, and it is a dependency

Runtime validation catches spec drift at the boundary instead of three frames deep, which is worth a lot when your API and your SDK ship on different days, and it is why pydantic is the default: it is what the typed-Python ecosystem already reaches for. It is also a dependency and a per-response cost your consumers did not choose. If your users are a FastAPI shop it is free. If you are shipping to people who will not thank you for a transitive dependency, switch the data model to dataclass before the first release rather than after.

In every generated client, not just this one

The parts nobody wants to hand-write, and the parts a hand-written client usually skips.

  • Retries with exponential backoff and jitter, on the statuses that mean "try again", with a per-attempt timeout.
  • Idempotency keys on the methods that need them, so a retried write does not become two.
  • Auth wired in from your spec’s security scheme, read from wherever your users keep secrets.
  • Typed errors split by cause: a failed constraint, an HTTP status, a network failure and a timeout are four different things.
  • Pagination that follows whichever contract your spec declares, cursor, offset, page number or next URL, without prefetching the collection.
  • Server-sent events as a native stream, for the operations that stream.
  • Error reporting to Octri’s monitoring, pre-wired and switched off until a consumer opts in.
  1. Point it at your spec

    Upload an OpenAPI or Swagger document, paste a URL, or connect the repository it lives in so a merge to main updates it.

  2. Read the audit

    The spec is scored out of 10 on what a generator can do with it, with every missing schema and colliding operationId named. 9 of the fourteen rules carry a button that writes the fix.

  3. Shape the client

    Client style, argument style, namespacing, auth, retries and pagination, set once for the project and overridden per language or per endpoint.

  4. Generate and read it

    The package is generated, compiled and handed to you as source you can read before anyone installs it.

  5. Publish, then keep publishing

    Release to the registry under your own account. Every later spec change regenerates the client, and the version comes from your document rather than from us.

Or in another language

One spec, ten languages. These three are generated from the same document, with the same settings, at the same time.

Questions

Async first. Turn sync methods on and a blocking companion is generated beside each coroutine, so a caller who is not running an event loop still has a method to call.