Skip to main content

Python OpenAPI Client Generator made OK

Project description

okapipy

okapipy

CI PyPI Python versions License Docs

Quality Gate Coverage Maintainability prek Checked with mypy Ruff

okapipy turns an OpenAPI 3.x document into a typed, hierarchical Python client — sync and async, sharing one tree. It lifts flat paths into Namespaces, Collections, Resources, Singletons, and Actions, then emits a project where regenerated base/ code carries the wiring and a one-shot user layer carries your customizations. Re-run the generator after a spec change and your code is left strictly alone.

The generated client is built to be navigated by hover: every class docstring opens with a short summary and lists the children you can reach from it — so client. in your IDE shows the whole tree without opening a single file.

📚 Full documentation: https://ffaraone.github.io/okapipy/

Install

okapipy needs Python 3.12+.

pip install okapipy            # or: uv add okapipy

The first NLP-dependent run downloads the spaCy en_core_web_sm model (~12 MB) into ./.spacy/. To pre-warm it (recommended in CI):

okapipy nlp fetch en

Use

Sanity-check the parse:

okapipy spec parse openapi.yaml

Generate a full client project:

okapipy spec generate openapi.yaml \
    --output ./my-client \
    --package acme.commerce \
    --client-class CommerceClient

This writes a runnable Python project under ./my-client: a regenerated src/acme/commerce/base/ tree (transport, models, vendored runtime, per-node base classes) plus one-shot subclass stubs you can customize. Re-running the command refreshes base/ and leaves your edits alone.

Useful flags: --rules path/to/rules.yaml for project-local overrides, --strip-prefix /api/v1 to drop a base prefix, --shape {models|dicts} to lock the client to a single response shape (omit for dual-shape with with_shape(); --shape dicts also skips emitting base/models.py), --check for a CI dry-run that exits non-zero on any drift.

Customize

okapipy decides what each path segment is using POS tagging plus a small heuristic registry. When the heuristics get it wrong — or when you need to fence off pieces of the spec — you decorate the spec with x-okapipy-* extensions, or carry the same overrides in a project-local rules file (useful when you don't own the OpenAPI document). Rules-file values win on every conflict.

Pass a rules file with --rules:

okapipy spec generate openapi.yaml --rules okapipy.rules.yaml \
    --output ./my-client --package acme.commerce --client-class CommerceClient

OpenAPI extensions

openapi: 3.0.0
info: { title: Commerce API, version: 1.0.0 }

# Declare top-level folders so single-noun segments aren't misclassified.
x-okapipy-ns:
  - commerce
  - commerce/internal
  - settings

paths:
  /commerce/orders:                      # Plural → Collection
    get: ...
    post: ...

  /commerce/orders/{id}/submit:          # Verb → Action (NLP catches it)
    post: ...

  /me:
    x-okapipy-kind: singleton            # Force singleton; NLP can't tell apart from a namespace
    get: ...
    patch: ...

  /staff:
    x-okapipy-kind: collection           # spaCy thinks "staff" is singular — override

  /currencies:
    x-okapipy-paginated: false           # Override pagination heuristic
    get: ...

  /internal/debug:
    x-okapipy-exclude: "*"               # Drop every method on this path

  /orders/{id}:
    x-okapipy-exclude: [DELETE]          # Or just selected methods
    get: ...
    delete: ...

Allowed x-okapipy-kind values: namespace, collection, singleton, action. Path-item hints classify the segment and propagate to other paths walking through the same prefix. Operation-level x-okapipy-kind: action is narrower — it routes a single method to a synthetic Action without changing the segment classification.

Rules file

The rules file mirrors the spec extensions and is local-only (no URLs). JSON or YAML, same shape:

x-okapipy-ns:
  - commerce
  - settings

paths:
  /staff:
    x-okapipy-kind: collection
  /me:
    x-okapipy-kind: singleton
  /orders/{id}/submit:
    post:
      x-okapipy-kind: action
  /currencies:
    x-okapipy-paginated: false
  /internal/debug:
    x-okapipy-exclude: "*"
  /orders/{id}:
    x-okapipy-exclude: [DELETE]

For more — code customization, custom strategies, template overrides, client construction options — see https://ffaraone.github.io/okapipy/.

Using the generated client

from acme.commerce import CommerceClient
import httpx

with CommerceClient(
    base_url="https://api.example.com",
    auth=httpx.BearerAuth("..."),
) as client:
    # Iterate a collection — pagination is automatic.
    for order in client.commerce.orders:
        print(order.id, order.total)

    # Filter, sort, page-size — fluent and order-independent.
    for order in (
        client.commerce.orders
        .filter(status="open")
        .order_by("-created_at")
        .page_size(50)
    ):
        ...

    # Resource lookup uses [id], not (id). Indexing is request-free;
    # .retrieve() issues the GET.
    order = client.commerce.orders["ord_42"].retrieve()

    # Sub-collections walk the tree naturally.
    line = client.commerce.orders["ord_42"].lines.create(
        body={"sku": "SKU-1", "qty": 2},
    )

    # Actions return an action object whose method is `.run()`.
    client.commerce.orders["ord_42"].submit.run()

    # Singletons (e.g. /me, /health) are direct attributes.
    me = client.me.retrieve()

    # Count without a full walk.
    total = client.commerce.orders.filter(status="open").count()

Async is the same shape, Async-prefixed:

from acme.commerce import AsyncCommerceClient

async with AsyncCommerceClient(base_url="https://api.example.com") as client:
    async for order in client.commerce.orders:
        ...
    me = await client.me.retrieve()

Contributing

Bug reports, feature requests, and pull requests are welcome via GitHub issues and pull requests.

To work on okapipy locally:

git clone https://github.com/ffaraone/okapipy.git
cd okapipy
uv sync
uv run okapipy nlp fetch en              # one-time spaCy model download
uv run pytest                            # full suite + coverage
uv run mypy src/okapipy/parser           # strict type-check (parser)
uv run ruff check src tests              # lint

Before opening a PR, please ensure tests, type-check, and lint all pass. The internal design notes live under design/ — read the relevant spec and plan before changing parser, generator, or customization behavior. Coding and documentation conventions are in CLAUDE.md.

License

okapipy is released under the Apache License 2.0. You're free to use it commercially, modify it, and redistribute it; please keep the copyright notice and the license text intact.

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

okapipy-0.3.0.tar.gz (2.0 MB view details)

Uploaded Source

Built Distribution

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

okapipy-0.3.0-py3-none-any.whl (150.5 kB view details)

Uploaded Python 3

File details

Details for the file okapipy-0.3.0.tar.gz.

File metadata

  • Download URL: okapipy-0.3.0.tar.gz
  • Upload date:
  • Size: 2.0 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for okapipy-0.3.0.tar.gz
Algorithm Hash digest
SHA256 cc0c6ec254dca87a2b647b3dca8f124ec759342a318d00ae623796721681434d
MD5 b6222b450faa61cdbe8891f9c77cafbc
BLAKE2b-256 68bf651be1c0363fc7144890ac8c4a35e6f8e756db7c5f3fb13717007e685429

See more details on using hashes here.

Provenance

The following attestation bundles were made for okapipy-0.3.0.tar.gz:

Publisher: release.yml on ffaraone/okapipy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file okapipy-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: okapipy-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 150.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for okapipy-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1e4fde9f40646451889ecb07c2d59e9fcefd72df973c4b50e637d1ab03c7cc5e
MD5 b3aaaf528317385d1f237c4941800c67
BLAKE2b-256 e5ca70d75acc11e16c8d0e197373eb81995ea7ab999884cc2e31cc8fe06dbeca

See more details on using hashes here.

Provenance

The following attestation bundles were made for okapipy-0.3.0-py3-none-any.whl:

Publisher: release.yml on ffaraone/okapipy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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