Python OpenAPI Client Generator made OK
Project description
okapipy
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 fetch-language en
Use
Sanity-check the parse:
okapipy parse openapi.yaml
Generate a full client project. okapipy reads its settings from a small
manifest you commit alongside your consumer code; scaffold it with
okapipy init:
okapipy init openapi.yaml \
--package acme.commerce --client-class CommerceClient
That writes ./okapipy.yml. Then:
okapipy generate --output ./my-client
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.
Every project-level setting (shape, templates_dir, output) and
every per-spec setting (rules, strip_prefix) lives in
okapipy.yml — see the
Quick start
for the full schema. The CLI keeps only --manifest, --output,
--check (CI dry-run that exits non-zero on any drift), and
--quiet.
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.
Point a specs[] entry at the rules file via rules: in okapipy.yml:
specs:
- namespace: ''
source: openapi.yaml
rules: okapipy.rules.yaml
Then run okapipy generate as usual.
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 fetch-language 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
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 okapipy-0.4.0.tar.gz.
File metadata
- Download URL: okapipy-0.4.0.tar.gz
- Upload date:
- Size: 2.1 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
da1f532a60424a90235b7b0890a2623c0baac5366550ab5ce62f2054aff73fc6
|
|
| MD5 |
c16ae54650d05a8d2af2282da09fac19
|
|
| BLAKE2b-256 |
a12d74adc8aa1daf98d675f30578e9ec2aedde9ac450e15731542b77a4a1470b
|
Provenance
The following attestation bundles were made for okapipy-0.4.0.tar.gz:
Publisher:
release.yml on ffaraone/okapipy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
okapipy-0.4.0.tar.gz -
Subject digest:
da1f532a60424a90235b7b0890a2623c0baac5366550ab5ce62f2054aff73fc6 - Sigstore transparency entry: 1754816367
- Sigstore integration time:
-
Permalink:
ffaraone/okapipy@9bcaae165d0f75ce6fa765c740ae229263f0d0d4 -
Branch / Tag:
refs/tags/0.4.0 - Owner: https://github.com/ffaraone
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9bcaae165d0f75ce6fa765c740ae229263f0d0d4 -
Trigger Event:
release
-
Statement type:
File details
Details for the file okapipy-0.4.0-py3-none-any.whl.
File metadata
- Download URL: okapipy-0.4.0-py3-none-any.whl
- Upload date:
- Size: 169.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5c3305ff7066076e5989a3241d3d927ff3cec3952c45b90dbe103f990ae451ed
|
|
| MD5 |
56a533f9736891b6439d56a6b19b1206
|
|
| BLAKE2b-256 |
bb3b3d73cb0b1d9d2c9651bed55d8f65fd9417f57835b769721c067d02371306
|
Provenance
The following attestation bundles were made for okapipy-0.4.0-py3-none-any.whl:
Publisher:
release.yml on ffaraone/okapipy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
okapipy-0.4.0-py3-none-any.whl -
Subject digest:
5c3305ff7066076e5989a3241d3d927ff3cec3952c45b90dbe103f990ae451ed - Sigstore transparency entry: 1754816379
- Sigstore integration time:
-
Permalink:
ffaraone/okapipy@9bcaae165d0f75ce6fa765c740ae229263f0d0d4 -
Branch / Tag:
refs/tags/0.4.0 - Owner: https://github.com/ffaraone
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9bcaae165d0f75ce6fa765c740ae229263f0d0d4 -
Trigger Event:
release
-
Statement type: