Python OpenAPI Client Generator made OK
Project description
okapipy
A Python OpenAPI client generator that lifts the flat list of paths in an OpenAPI 3.x document into a hierarchical tree of Namespaces, Collections, Resources, Singletons, and Actions, and emits a strongly-typed, async/sync Pydantic v2 client from it.
📚 Full documentation: https://ffaraone.github.io/okapipy/ — installation, quick start, client usage, rules and extensions, strategies, code customization, template overrides, and a full API reference.
Installation
okapipy requires Python 3.12+ and uses uv for dependency management.
uv add okapipy # add to an existing project
# or, for one-off use:
uvx okapipy --help
The first NLP-dependent run downloads the spaCy en_core_web_sm model
(~12 MB) into ./.spacy/. To pre-warm it:
uv run okapipy nlp fetch en
Usage
Parse a spec into its structural tree (path or http(s) URL accepted):
uv run okapipy spec parse openapi.yaml --output tree.yaml
Generate a full client project:
uv run okapipy spec generate openapi.yaml \
--output ./my-client \
--package acme.commerce \
--client-class CommerceClient
This writes a complete Python project under ./my-client with a
regeneratable base layer (src/acme/commerce/base/...) and a one-shot user
layer of subclass stubs you can safely customize. Re-running the command
refreshes the base layer while preserving your edits in the user layer.
Useful flags:
--rules path/to/rules.yaml— project-local overrides for namespace assignment, segment kind, and operation exclusion (mirrors thex-okapipy-*extensions; rules-file values win on conflict).--strip-prefix /api/v1— drop a base prefix from every path before classification.--no-models(alias--without-models) — skip emittingbase/models.pyand drop every model import from the generated client. Operations end up untyped (raw dicts in / out). Useful whendatamodel-code-generatorcan't process the spec's schemas, or when the consumer prefers to bring their own types.--check— CI dry-run: report drift and stale files, exit non-zero on any change.
How paths become a tree
okapipy walks each OpenAPI path one segment at a time and assigns each segment one of five kinds:
| Kind | What it represents | Example path |
|---|---|---|
| Namespace | A folder-style grouping (no operations of its own) | /commerce/..., /auth/... |
| Collection | A plural endpoint that lists/creates | /orders, /users |
| Resource | A single item within a collection (after {id}) |
/orders/{id} |
| Singleton | A resource with no enclosing collection | /me, /health, /users/{id}/avatar |
| Action | A non-CRUD verb endpoint | /login, /orders/{id}/submit |
Classification runs in this order: {id}-shaped segments are resources,
explicit x-okapipy-kind hints win next, then the namespace registry, then
spaCy POS/morphology. When everything else is silent the segment defaults to
a collection.
HTTP methods are routed to fixed slots:
| Terminal kind | GET | POST | PUT | PATCH | DELETE |
|---|---|---|---|---|---|
| Collection | fetch |
create |
dropped | dropped | dropped |
| Resource | retrieve |
dropped | update |
partial_update |
delete |
| Singleton | retrieve |
dropped | update |
partial_update |
delete |
| Action | appended to Action.operations (one Action holds every method on its path) |
Operations that don't fit (e.g. POST /orders/{id} without an action hint)
are dropped with a warning rather than coerced into something synthetic — opt
them in with x-okapipy-kind: action if you want them.
OpenAPI extensions
You decorate the spec with x-okapipy-* keys to override the heuristics. All
extensions are optional; small/well-named specs often need none.
x-okapipy-ns (root level)
Declares which top-level path segments are folders. Without this, a singular
noun like commerce or auth would be classified as a namespace only when
the heuristic guesses correctly — the registry makes it deterministic.
openapi: 3.0.0
info:
title: Commerce API
version: 1.0.0
x-okapipy-ns:
- commerce
- commerce/internal
- settings
paths:
/commerce/orders: # → Namespace(commerce) → Collection(Orders)
get: ...
/commerce/internal/audit: # nested namespaces are fine
get: ...
Leading slashes are tolerated (/commerce and commerce are equivalent).
x-okapipy-kind (path-item or operation level)
Forces a segment's classification when NLP can't disambiguate. Allowed values:
namespace, collection, singleton, action.
paths:
/me:
x-okapipy-kind: singleton # /me is a Singleton, not a Namespace
get:
summary: Return the current user
patch:
summary: Update the current user
/orders/{id}/submit:
post:
x-okapipy-kind: action # operation-level: route this POST to a synthetic Action
/staff: # spaCy treats "staff" as singular → namespace by default
x-okapipy-kind: collection
get: ...
Path-item-level hints classify the segment (and propagate to other paths
that walk through the same prefix — declaring /me as a singleton once is
enough for /me/notifications, /me/refresh, etc., to see it). Operation-level
x-okapipy-kind: action is narrower: it routes a single method to a
synthetic Action without changing the segment's classification.
x-okapipy-exclude (path-item level)
Skips operations during parsing — useful for endpoints you don't want in the generated client (admin-only, deprecated migration endpoints, internal debugging routes).
paths:
/internal/debug:
x-okapipy-exclude: "*" # drop every method on this path
/orders/{id}:
x-okapipy-exclude: [DELETE] # drop just DELETE; keep GET / PUT / PATCH
get: ...
delete: ...
Method names are case-insensitive.
x-okapipy-paginated (path-item or operation level)
Overrides whether a list-shaped operation is treated as paginated by the
generator. Defaults to true. Set it to false for endpoints that return a
bounded list (e.g. enum-like reference data).
paths:
/currencies:
x-okapipy-paginated: false # the full list is short and fixed
get:
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Currency'
Singletons
A Singleton is a resourceful endpoint with no enclosing collection: there is exactly one of it, and CRUD verbs apply directly to it. Common cases:
/me,/self— the authenticated user/health,/status,/version— service introspection/users/{id}/avatar— a sub-singleton owned by a parent resource
Singletons must opt in with x-okapipy-kind: singleton. Without the hint, a
single-noun segment like me would be classified as a Namespace because
spaCy can't tell the two apart from the segment alone. Once declared, the
Singleton accepts the same CRUD verbs as a Resource (GET → retrieve,
PUT → update, PATCH → partial_update, DELETE → delete) and may host
nested collections, sub-singletons, or actions.
paths:
/me:
x-okapipy-kind: singleton
get: { summary: Return the current user, responses: { '200': ... } }
patch: { summary: Update the current user, responses: { '200': ... } }
/users/{id}/avatar:
x-okapipy-kind: singleton # sub-singleton under the User resource
parameters: [{ name: id, in: path, required: true, schema: { type: string } }]
get: ...
put: ...
delete: ...
The generated client surfaces these as direct attributes — client.me.retrieve(),
client.users(id).avatar.update(...) — rather than going through a
collection.
Root and namespace-level actions
Verb endpoints can attach at the root of the API or directly under a
namespace. Plenty of real-world specs do this (/login, /logout,
/password-reset, /auth/refresh), and okapipy does not require a wrapper
collection for them.
paths:
/login:
post: ... # → APIModel.actions[Login]
/password-reset:
post: ... # multi-token verb-phrase → Action
/auth/refresh:
post: ... # → Namespace(auth).actions[Refresh]
Detection notes:
- Multi-token kebab segments with a non-plural head (e.g.
password-reset,force-reimport) are detected as verb-phrases by spaCy directly. - Single-token API verbs that small spaCy models mistag as nouns (
login,logout,refresh,revoke,verify,subscribe,unsubscribe,activate,deactivate,enable,disable,archive,publish,ping, …) are caught by an English-language registry. - Anything outside that set should use
x-okapipy-kind: actionto be safe.
Naming follows the breadcrumb of singular collection names — namespaces don't
contribute. So /login becomes Login, /auth/refresh becomes Refresh,
and /users/{id}/avatar becomes UserAvatar. Cross-namespace name
collisions (e.g. /web/login and /admin/login would both be Login) are
left to the generator to disambiguate.
Rules file
The rules file is a project-local override layer. Use it when you don't own the OpenAPI document but still need to fix classifications, exclude endpoints, or declare namespaces. It mirrors the spec extensions; rules-file values win on every conflict.
Pass it via --rules:
uv run okapipy spec parse openapi.yaml --rules okapipy.rules.yaml
The file is local-only (no URLs) and accepts JSON or YAML:
# Same shape as x-okapipy-ns at the root of the spec.
x-okapipy-ns:
- commerce
- commerce/internal
- settings
paths:
# Path-item-level kind override (mirrors x-okapipy-kind on a path).
/staff:
x-okapipy-kind: collection
# Force /me to be a singleton even though the upstream spec is silent.
/me:
x-okapipy-kind: singleton
# Per-method override (mirrors x-okapipy-kind on an operation).
/orders/{id}/submit:
post:
x-okapipy-kind: action
# Per-method pagination override.
/currencies:
x-okapipy-paginated: false
# Drop every method on a path…
/internal/debug:
x-okapipy-exclude: "*"
# …or just selected methods.
/orders/{id}:
x-okapipy-exclude: [DELETE]
Allowed x-okapipy-kind values: namespace, collection, action,
singleton. Unknown values are rejected at load time with the path that
contains them.
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
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.2.0.tar.gz.
File metadata
- Download URL: okapipy-0.2.0.tar.gz
- Upload date:
- Size: 983.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
82327a7b7b62c7203025d72aef93dc2ccdcff5cca52935b9be4f2154e610cfff
|
|
| MD5 |
1792693bd40050894d7a840829d468e2
|
|
| BLAKE2b-256 |
46c8f5ce70ee0d8d93f050801792f3d9bafb8e40efb23d746d0edd64aae864d1
|
Provenance
The following attestation bundles were made for okapipy-0.2.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.2.0.tar.gz -
Subject digest:
82327a7b7b62c7203025d72aef93dc2ccdcff5cca52935b9be4f2154e610cfff - Sigstore transparency entry: 1460370772
- Sigstore integration time:
-
Permalink:
ffaraone/okapipy@9f8c7a3659d84dbff3722052857603f1703037cc -
Branch / Tag:
refs/tags/0.2.0 - Owner: https://github.com/ffaraone
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9f8c7a3659d84dbff3722052857603f1703037cc -
Trigger Event:
release
-
Statement type:
File details
Details for the file okapipy-0.2.0-py3-none-any.whl.
File metadata
- Download URL: okapipy-0.2.0-py3-none-any.whl
- Upload date:
- Size: 119.3 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 |
4f408d5dc50712f5513aadd80c1c3145ecd280ef646399e3a60bda57ff5dc48f
|
|
| MD5 |
a81fb132e656c38aec1b98eaa849e220
|
|
| BLAKE2b-256 |
cdbaa5012ec091d92953936a6e283653e67d1684e18e69eb574c83ab23267799
|
Provenance
The following attestation bundles were made for okapipy-0.2.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.2.0-py3-none-any.whl -
Subject digest:
4f408d5dc50712f5513aadd80c1c3145ecd280ef646399e3a60bda57ff5dc48f - Sigstore transparency entry: 1460370906
- Sigstore integration time:
-
Permalink:
ffaraone/okapipy@9f8c7a3659d84dbff3722052857603f1703037cc -
Branch / Tag:
refs/tags/0.2.0 - Owner: https://github.com/ffaraone
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9f8c7a3659d84dbff3722052857603f1703037cc -
Trigger Event:
release
-
Statement type: