Open-source project
openapi-generators/openapi-python-client avatar
openapi-generators/openapi-python-client

openapi-python-client and the shape of the client it writes for you

Generate modern Python clients from OpenAPI

2,000 stars299 forksPythonMIT

At a glance

What is it?
A code generator that turns an OpenAPI 3.0 or 3.1 document into a typed, importable Python package with attrs models, httpx transport and per-tag endpoint functions. Its own documentation is candid about the gaps: no OpenAPI 2.x, an unstable template API, and a formatting step that quietly depends on ruff being on your PATH.
Who is it for?
This is the right tool when your team consumes a 3.0 or 3.1 document that other people also have to live with, because it turns one schema file into typed code you can import and commit. It is the wrong tool for a handful of hand-picked endpoints, where calling httpx directly is less code than a regeneration cycle.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 7, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • openapi-generators/openapi-python-client2,000 ★ · +5
Star history
Repository2026-10-152026-11-07
openapi-generators/openapi-python-client2k2k

5 Stars in the last 23 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

What lands on disk after one generate call

The output is a directory with a fixed layout, and the project name comes from the title in your OpenAPI document. A spec titled My API produces my-api-client, with --output-path available when you want that name changed.

Inside, `pyproject.toml` is written for Poetry by default, or with PDM metadata under `--meta=pdm`, or holding only Ruff config. A `setup.py` appears only when you pass `--meta=setup`. Alongside it you get a `README.md` you will have to rewrite, and a module named after the project, for example my_api_client. That module holds four things:

- a client module with a Client class and an AuthenticatedClient class, both needed before you can call anything in the API layer; - an api module with one submodule per OpenAPI tag, plus a default submodule for endpoints that carry no tag, and one function per endpoint inside each; - a models module holding every class defined by the schemas in the document.

The layout is easy to confirm without running anything, because the repository ships the expected result. Under `end_to_end_tests` sit `baseline_openapi_3.0.json` and `baseline_openapi_3.1.yaml`, and the generated client produced from those documents is checked in as a golden record. That pair is the fastest way to see what a 3.0 document and a 3.1 document each turn into.

pipx, include-deps and the ruff formatting step

The preferred install isolates the generator from whatever else is in your environment:

bash
pipx install openapi-python-client --include-deps

The `--include-deps` flag is not optional decoration. It puts `ruff` on your PATH so the generator can clean up the code it writes. Install with plain pip and you get the same tool, but the formatting behaviour then depends on whether ruff happens to be reachable:

bash
pip install openapi-python-client
openapi-python-client --install-completion

There is a sharper version of the same caveat. If you invoke the tool through `pipx run`, the post-generation hooks are not available unless you install them yourself, so a one-off run can produce output that differs from a properly installed one. Since formatting is part of what you get, compare a `pipx run` result against a real install before trusting a diff.

The runtime floor is visible in the packaging metadata: `requires-python = ">=3.11,<4.0"`, with classifiers for 3.11 through 3.14, and the console script points at `openapi_python_client.cli:app`. The dependency list tells you what the generated world will look like too, since httpx, pydantic and attrs are what the emitted models and client are built from.

Config keys that rename classes and replace Enum with Literal

Behaviour changes go in a YAML or JSON file passed to `--config`. Four keys do the real work.

`class_overrides` maps an existing generated class name to a replacement class and module, which is how you shorten a schema key such as `_PrivateInternalLongName` into something a person would type:

yaml
class_overrides:
  _PrivateInternalLongName:
    class_name: ShortName
    module_name: short_name

The practical way to find what needs overriding is to generate once and read the `models` folder, then write the config against what you actually got.

`docstrings_on_attributes: true` moves attribute descriptions out of the class docstring and onto the attributes themselves. Leaving it unset keeps descriptions in the class docstring as a list, which is what you want for a compact API reference but poor for hover help in an editor.

`literal_enums: true` switches enum output from classes inheriting from `Enum` to `Literal` values. That matters when two enum members collide once they are transformed into Python names, a real risk when values differ only by case or contain symbols that have to be rewritten.

yaml
docstrings_on_attributes: true
literal_enums: true

Only the first OpenAPI tag becomes a module by default

Endpoint modules are derived from the tags on each operation, and the default behaviour takes only the first one. An operation labelled inventory and warehouse lands in the inventory module and nowhere else, even though your document says it belongs to both.

yaml
generate_all_tags: true

Turning this on generates a duplicate function under every tag listed on the operation. That is a deliberate trade rather than a bug fix: consumers who browse the client by tag get the operation in both places, at the cost of two symbols to keep in sync. The default keeps generated code smaller, which matters because this output is regenerated rather than hand-edited.

Endpoints with no tag at all always land in the `default` submodule. So the `api` module is never empty even for a document whose operations are untagged, and a tag-first browsing habit will not find those endpoints where you expect them.

Custom templates matched by exact filename on an unstable API

Customisation does not start from a fork. The generator loads templates through Jinja2's ChoiceLoader and FileSystemLoader, so you copy the files you want to change out of the default template directory, keep their names identical, and point the tool at your copy:

bash
openapi-python-client generate \
  --url https://my.api.com/openapi.json \
  --custom-template-path=relative/path/to/mytemplates

File names must match exactly. A renamed template is not merged with the default, it is simply not found, and the built-in version is used.

The warning attached to this feature is more important than the mechanics. It is described as beta level because the API exposed inside the templates is undocumented and unstable, which means a template you write today may need rewriting after a generator upgrade. The repository reflects how central those templates are: the Ruff configuration excludes `openapi_python_client/templates` and the checked-in golden records from linting, and it also excludes `tests/test_templates`. If you build on top of the template API, you are accepting that maintenance cost.

An existing output directory aborts the run without overwrite

Generation refuses to write into a directory that already exists. You get an error and are left to decide between moving the old output or passing `--overwrite`.

bash
openapi-python-client generate --url https://my.api.com/openapi.json

Adding `--overwrite` is what turns that refusal into a replacement of the existing tree, so a CI step that regenerates into a checked-in directory has to supply the flag or fail on every run after the first. The refusal is a data-loss guard rather than an obstacle to route around.

The input can equally be a URL or a file on disk, and pinning the document as a file makes the run reproducible against a committed copy:

bash
openapi-python-client generate --path location/on/disk/openapi.json

The repository layout also shows what sits behind the CLI: `openapi_python_client/` holds the package, `tests/` the unit suite, `integration-tests/` and `fuzz/` the wider exercises, and the dev dependency group pulls in pytest, mypy, syrupy for snapshot testing, and hypothesis, which is what you would expect from a generator whose correctness is judged by matching checked-in output.

No path in for OpenAPI 2.x, and 3.0 versus 3.1 both get checked

Swagger 2.0 documents are out of scope. The stated fix is to convert the document to version 3 first, using one of the available converters, before handing it to this tool. A team with a large inventory of legacy 2.x specs therefore owns an extra step in the pipeline that this generator does not remove.

The project also states plainly that it is unfinished, that it does not support all OpenAPI features, and its packaging metadata classifies it as Development Status :: 4 - Beta. Both supported document versions have baseline fixtures under `end_to_end_tests`, and the last push landed on 2026-09-24, so the gap between a spec you have and the features the generator handles is the first thing to check on any given document.

Read the golden records before you commit to it. They show how 3.0 and 3.1 documents differ in the emitted code, and they are the fastest available check on whether your own document produces something your team can live with.

Poetry output by default while the repository is built with PDM

A close read of the packaging files turns up a mismatch worth knowing before you publish anything you generate. Generated clients default to Poetry metadata, and --meta=pdm and --meta=setup are the documented alternatives. Yet the generator's own repository is not built with Poetry: the top level holds `pdm.lock` and `pdm.minimal.lock` alongside `knope.toml`, so the tool's own dependency workflow runs on PDM. The default output format is a choice about the generated artifact, not a mirror of how the project maintains itself.

The alternative to generating at all is worth naming concretely. httpx is already a direct dependency of the tool that produces the client, and the emitted models lean on pydantic and attrs, so a hand-written module using those three libraries can call a handful of endpoints with full typing and no regeneration step. Generated code wins when the document is wide, the client is shared, and drift from the spec is the real risk. Hand-written code wins when the surface is three endpoints and you would rather own every line.

On licensing, the repository is MIT, so nothing in the generated output carries a copyleft obligation.

Editorial conclusion

This is the right tool when your team consumes a 3.0 or 3.1 document that other people also have to live with, because it turns one schema file into typed code you can import and commit. It is the wrong tool for a handful of hand-picked endpoints, where calling httpx directly is less code than a regeneration cycle. Before the first run, generate from a committed copy of the document with `--path`, read the models folder, and decide whether ruff is on PATH, since the cleanup step depends on it. Pin the generator version your templates were written against.

Frequently asked questions

Does openapi-python-client work with Swagger or OpenAPI 2.x documents?

No. The generator handles OpenAPI 3.0 and 3.1 only, and the stated path for older documents is to convert them to version 3 with an available converter before generating.

What does openapi-python-client generate for a document titled My API?

A directory named my-api-client, containing a pyproject.toml with Poetry metadata by default, a README.md, and a module named my_api_client holding client, api and models submodules. A setup.py file appears only when you pass the setup meta option.

Why does openapi-python-client need ruff installed?

The recommended pipx install with the include-deps flag puts ruff on your PATH so the generator can clean up the code it writes, and the post-generation hooks are not available under pipx run unless you install them yourself.

Can I change the names of generated model classes?

Yes, through the class_overrides key in a YAML or JSON file passed with the config option, which maps a schema key to a new class_name and module_name. The practical method is to generate once and read the models folder for the names you want to change.

How do I get a function for an operation that has several tags?

By default only the first tag is used, and the operation appears in that module alone. Setting generate_all_tags to true in the config generates a duplicate function under every tag listed on the operation.

Official sources

  1. Issues
  2. License: MIT
  3. openapi-generators/openapi-python-client on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/openapi-generators-openapi-python-client.svg)](https://hysenlabs.com/projects/openapi-generators-openapi-python-client)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/openapi-generators-openapi-python-client.svg)](https://hysenlabs.com/projects/openapi-generators-openapi-python-client)

Was this useful?