Skip to main content

crozier

An ultra-fast, memory-efficient OpenAPI-to-SDK code generator written in Rust + minijinja. crozier reproduces Fern's generated SDKs byte-for-byte (with generator-identifying comments aside), driven by nothing but an OpenAPI document and a couple of naming flags — no per-project config, no generators written in the target language.

Python is the only target today; more will follow.

Status: pre-1.0. crozier generates the whole Python SDK — the type layer (pydantic models, enums, unions/aliases), the endpoint clients, the core/ runtime, and the packaging scaffolding — and reproduces Fern's output byte-for-byte across every corpus in its golden suite. See docs/matching.md for how that is verified.

See it in action

One OpenAPI document in, a complete typed Python SDK out — crozier generate writes the whole package in one shot, then you can read the code it produced:

Animated demo: typing crozier generate python --spec petstore.yml --output sdk, crozier reporting how many files it wrote, then cat-ing the generated PetStatus enum as it streams in with syntax highlighting

The output is byte-for-byte Fern's. Here is the Pet model crozier emitted from a handful of lines of schema — a real generated file, not a hand-written sample:

The generated types/pet.py: a pydantic Pet(UniversalBaseModel) with Optional id, name, status and owner fields and the pydantic v1/v2 config block

crozier generate python takes the document and a couple of naming flags and writes the SDK — the models, enums, the per-endpoint client, and Fern's core/ runtime — into --output:

Terminal: crozier generate python --spec petstore.yml --output sdk --package-name petstore, the generated-files summary line, and a tree of the written package showing src/petstore with core, types, client.py, raw_client.py and pyproject.toml

Or drive one — or several — named generators from a crozier.yml instead of flags. crozier config shows the effective settings for every generator and the layer each value resolved from (CLI flag > CROZIER_* env > file > default):

Terminal: crozier config against a crozier.yml with shared defaults and two generators (python and admin), each field shown with its resolved value and source layer — shared, generator, or default

crozier init drops a starter crozier.yml carrying a JSON Schema modeline, so editors complete and validate it against crozier's own config schema:

Terminal: crozier init writing crozier.yml, then cat-ing it — a yaml-language-server $schema modeline, shared spec, and a generators block with the built-in python generator

The full command surface (crozier --help and crozier generate --help)

crozier --help: the top-level usage listing the generate, init, config and schema subcommands

crozier generate --help: every flag — spec, output, package-name, project-name, client-class-name, audience, config, audience-strict, no-config, extra-fields, enum-type, default-max-retries and layout

A malformed document is rejected at the boundary with an actionable message and a non-zero exit — never a panic:

crozier rejecting broken.yml with the message: invalid OpenAPI document broken.yml, missing openapi version field, is this an OpenAPI document?

These are real captures of the CLI, rendered from its actual output by just screenshots and gated by screencomp — change what a command prints and the committed image (and its digest) changes with it.

Install

From PyPI (fastest — a prebuilt binary, no Rust toolchain): crozier ships as platform wheels that wrap the compiled binary and expose it as a console script, so any Python installer puts it on your PATH in seconds:

pip install crozier      # or: pipx install crozier
uvx crozier --help       # run once without installing

Platforms without a prebuilt wheel fall back to the source distribution, which builds from Rust (a toolchain is needed there).

From crates.io:

cargo install crozier --locked

From the install script (prebuilt binary from GitHub Releases, verified):

curl -fsSL https://raw.githubusercontent.com/nickderobertis/crozier/main/scripts/install.sh | sh

It detects your platform, downloads the matching archive, and verifies it against a trust root independent of where it was downloaded — a Sigstore build-provenance attestation when a verifier (cosign, pip install sigstore, or gh) is present, else the canonical SHA-256 checksum. Pin a version or install location with sh -s -- --version v0.1.0 --to ~/.local/bin.

From source (latest main):

cargo install --git https://github.com/nickderobertis/crozier --locked

From a release archive (manual): each release publishes per-platform archives, each with a matching .sha256 and a .sigstore.json provenance bundle. Download the archive for your platform from the Releases page — it lists every target built — verify the checksum (or the attestation), and put the crozier binary on your PATH.

Usage

The fastest path needs no config file — the built-in python generator runs straight from flags:

crozier generate python \
  --spec path/to/openapi.yml \
  --output ./generated \
  --package-name my_api \
  --project-name my-api
  • --spec — the OpenAPI 3.x document (.yml, .yaml, or .json).
  • --output — directory to write the SDK into.
  • --package-name — the Python import package (the directory under src/). Defaults to a snake_case of the API title.
  • --project-name — the distribution name recorded in version.py. Defaults to the package name.
  • --client-class-name — the name of the generated root client class (Fern's client_class_name). Defaults to {PascalCase(package_name)}Api.
  • --audience (repeatable) / --audience-strict — prune generation to x-crozier-audiences.
  • --fern-strict — refuse every document in a class Fern refuses (also CROZIER_FERN_STRICT=true, or fern-strict: true in crozier.yml). A refusal exits 1, writes nothing, and names the refusal class and the offending element. Each class Fern refuses is decided once, in docs/fern-refusals/: refuse, where crozier refuses the document whatever this setting says, or generate, where it writes a valid SDK by default and refuses only under --fern-strict. Every class registered today is refuse, so the flag changes no outcome yet.

crozier exits 0 on success (with a one-line summary on stderr), 1 on an error, printing the exact problem and a suggested fix, and 2 on a usage error (a bad flag or argument). crozier compare adds 3 and 4 for its results (docs/compare.md).

To check crozier against the SDK you generate today, before switching, run crozier compare: for every generator your crozier.yml files declare, it runs a reference command you configure, generates crozier's SDK, compares the two trees with comments set aside and crozier's intended departures — its own identity headers, a corrected Fern defect, and the rest — reported line by line rather than failed, and reports the time each side took. See docs/compare.md. Moving from Fern, the Fern reference recipe produces that reference.

In CI, the GitHub Action runs the same check on every pull request and reports it in the job summary:

      - uses: nickderobertis/crozier@v0

See docs/github-action.md for its inputs, outputs and versioning, and the one workflow a repository migrating from Fern adds.

Configuration

crozier runs one or more named generators. You can drive them purely from flags (above), from a crozier.yml, or any mix — every setting resolves per field as:

CLI flag  >  CROZIER_* env var  >  crozier.yml (generator over shared)  >  built-in default

Run crozier init to drop a starter crozier.yml in the working directory. It leads with a JSON Schema modeline —

# yaml-language-server: $schema=https://raw.githubusercontent.com/nickderobertis/crozier/main/assets/crozier.schema.json

— so editors with the YAML language server give field completion and validation against the published schema (derived from crozier's own config types, so it never drifts). Run crozier config to print the effective settings and the layer each value came from.

A crozier.yml in the working directory is picked up automatically. Top-level keys are shared defaults; each entry under generators: is one SDK to emit:

# Shared across every generator
spec: ./openapi.yml
audiences: [public]

generators:
  python:              # overrides the built-in `python` generator
    output: ./sdks/python
    package-name: my_api
    project-name: my-api
  admin:               # a second generator (also Python for now)
    type: python
    spec: ./admin-openapi.yml
    output: ./sdks/admin
    package-name: admin_api
  • crozier or crozier generate — run every configured generator (or the built-in python when nothing is configured).
  • crozier generate <name> — run one generator by name (python always works, even with no config file).
  • crozier init — write a starter crozier.yml (--output, --force).
  • crozier config [<name>] — show the effective config and each value's source.
  • crozier schema — print the config JSON Schema to stdout.
  • --config <path> (repeatable, later wins) selects config files instead of auto-discovery; --no-config ignores config files entirely; CROZIER_CONFIG names a file via the environment.

Per-generation flags (--spec, --output, …) apply to a single generator, so they are rejected when more than one would run — name one, or set the values in crozier.yml. See docs/configuration.md for the full reference.

Migrating from Fern

A repository can move from Fern's Python generator to crozier one generator at a time, checking each against Fern before it switches. Before step 1, bring the Fern generator to the version crozier certifies; that and the other steps a migration may still need are in docs/migrating-from-fern.md.

1. Add crozier config beside the Fern config, one generator at a time. Save your copy of the Fern reference recipe and name it as reference.command. Set layout to how that generator's Fern output was written: flat for a local-file-system output path (point output where that tree lives), packaged (the default) for fern generate --preview --output:

# crozier.yml, at the repository root beside fern/
generators:
  python:
    spec: ./fern/openapi/openapi.yml
    output: ./sdks/python          # the generator's local-file-system path
    package-name: acme             # fern.config.json's organization
    layout: flat
    reference:
      command: ./scripts/fern-reference.sh

2. Compare until every generator matches, locally and in CI with the GitHub Action:

crozier compare

It checks only generators that have crozier config, so the ones still on Fern do not fail it. crozier does not track what is left; list the Python generators your Fern config still runs:

grep -rn --include=generators.yml 'fernapi/fern-python-sdk' .

docs/compare.md covers the report, exit statuses, JSON report, timings and the reference-command contract.

3. Switch the build from fern generate to crozier:

crozier generate python

4. Remove that generator (its fernapi/fern-python-sdk entry) from generators.yml. TypeScript and other generators stay on Fern.

Development

The command surface is a small set of just recipes:

just bootstrap   # set up from a clean clone (toolchain, the pinned Nx, dev tools)
just check       # the gate over what your change reaches — must pass before any commit or PR
just check --sweep  # the gate over everything (what the release pull request runs)
just test        # affected tests, then the 95% coverage floor
just test-e2e    # drive the compiled binary and byte-compare against fixtures
just format      # format every project in place
just upgrade     # cargo update, then re-run the gate

CI verifies the quality gate and visual regression on pull requests. Use just screenshots-bless after intended CLI output changes and commit the baseline and images together (see screenshots/AGENTS.md).

The tracked .cargo/config.toml is the build contract: every cargo invocation inside a clone builds into that clone's own target/ (two worktrees never share one), and dev/test builds carry no debuginfo (debug = 0), which keeps it off the disk. Nothing in the tree configures sccache; to share compiler results across worktrees, set [build] rustc-wrapper = "sccache" in your user-level ~/.cargo/config.toml, which Cargo merges with the repository's.

See AGENTS.md for the durable contributor guide and docs/matching.md for the byte-matching strategy.

License and attribution

crozier is licensed under Apache-2.0. It is an independent, clean-room implementation — it reproduces Fern's generated output format (the project's explicit goal) and does not copy Fern's generator source.

The SDK goldens under tests/fixtures/ are Fern-generated output, retained under Apache-2.0 with attribution and a statement of changes; see NOTICE and licenses/fern-APACHE-2.0.txt. Their OpenAPI inputs are independently authored or published descriptions of real APIs, admitted under the corpus redistribution policy.

Metadata

Release files for crozier 0.0.116

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for crozier 0.0.116
File Size Uploaded
crozier-0.0.116.tar.gz 714.2 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for crozier 0.0.116
File
crozier-0.0.116-py3-none-win_amd64.whl Python 3 none Windows x86-64 Details
crozier-0.0.116-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl Python 3 none Linux glibc 2.17+ x86-64 Details
crozier-0.0.116-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl Python 3 none Linux glibc 2.17+ ARM64 Details
crozier-0.0.116-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details
crozier-0.0.116-py3-none-macosx_10_12_x86_64.whl Python 3 none macOS 10.12+ x86-64 Details

Total release size: 17.2 MB

Release files / crozier-0.0.116.tar.gz

Download URL crozier-0.0.116.tar.gz
Size 714.2 kB
Tags Source
SHA-256 checksum
How to use checksums
7c5c0d7c380c9437a69cdd1695b8b92ac23f7a44a52980378501cd995e6f46c3
BLAKE2b-256 checksum
How to use checksums
6ca17d079e5a5a9b891eb0584f0aed5785db6750942c380a0f061c7baf5434d3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / crozier-0.0.116-py3-none-win_amd64.whl

Download URL crozier-0.0.116-py3-none-win_amd64.whl
Size 3.3 MB
Tags Python 3 Windows x86-64
SHA-256 checksum
How to use checksums
ae256e787b5fed00385040bff465eba2c563a1cd78203cf1b705715e7fdff7c7
BLAKE2b-256 checksum
How to use checksums
2f7db078053cadfd3c3a6b0781cfb6dbe40afdf67bf47fbb1e7f37c1d8de012d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / crozier-0.0.116-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL crozier-0.0.116-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 3.5 MB
Tags Linux glibc 2.17+ x86-64 Python 3
SHA-256 checksum
How to use checksums
0abbbeb6b0b2d7aeac934a4d8fdbfdeb44ae93768de1a57aed74fa02dbb7154b
BLAKE2b-256 checksum
How to use checksums
dc90f2c90307571c2284554113df1a4a42600b101c156bf8754aafce83f5637c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / crozier-0.0.116-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL crozier-0.0.116-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 3.2 MB
Tags Linux glibc 2.17+ ARM64 Python 3
SHA-256 checksum
How to use checksums
4b31671ad5b8e6800d6053474ebb65f3c00ffbd8eb61f7df41865d2782cdcda5
BLAKE2b-256 checksum
How to use checksums
2ba49e9f0a3cf704ef249c23e7203097a65b084db42fb512cada81403ad88e01
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / crozier-0.0.116-py3-none-macosx_11_0_arm64.whl

Download URL crozier-0.0.116-py3-none-macosx_11_0_arm64.whl
Size 3.0 MB
Tags Python 3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
4b7b2f907e40e7351ce5e96aa6b0b5f96ef30f3f6d77fba16e038ad37babad2c
BLAKE2b-256 checksum
How to use checksums
004b3f91470f9fc234084555487523e26d403a005d77ce5d12d7dbea7ef35eea
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / crozier-0.0.116-py3-none-macosx_10_12_x86_64.whl

Download URL crozier-0.0.116-py3-none-macosx_10_12_x86_64.whl
Size 3.4 MB
Tags Python 3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
ab1bbadfd9b2f9383f2a40dc603a490ca56a7e89ab078bd7de2467fdf67bcc79
BLAKE2b-256 checksum
How to use checksums
ebf61a947ca5014c1b88453298dfcbff93bc8ca8144482b5d13f594a4dadb16e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

This release

0.0.116 This release

6 release files

0.0.85

6 release files

0.0.31

6 release files

0.0.30

6 release files

0.0.29

6 release files

0.0.28

6 release files

0.0.27

6 release files

0.0.26

6 release files

0.0.25

6 release files

0.0.24

6 release files

0.0.23

6 release files

0.0.22

6 release files

0.0.21

6 release files

0.0.20

6 release files

0.0.19

6 release files

0.0.18

6 release files

0.0.17

6 release files

0.0.16

6 release files

0.0.15

6 release files

0.0.14

6 release files

0.0.13

6 release files

0.0.12

6 release files

0.0.11

6 release files

0.0.10

6 release files

0.0.9

6 release files

0.0.8

6 release files

0.0.7

6 release files

0.0.6

6 release files

0.0.5

6 release files

0.0.4

6 release files

0.0.3

6 release files

0.0.2

6 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page