mrkeyoor.com_
Sun 20 Sept 11:44 UTC
PyPICLI & Toolingupdated 19 Sept 2026

cyclopts review

Cyclopts turns annotated Python functions into command-line programs. Function parameters become positional arguments or options, type hints drive conversion and validation, and recognized docstring formats supply help text. It handles unions, Literal choices, dataclasses, attrs classes, and Pydantic models without routing through Click. Version 4.23.2 is a small typing release that adds type hints to help-copy methods; the measured install below used 4.23.1.

Verdict

Cyclopts is a good fit for Python 3.10+ tools whose real complexity is typed parameters, generated help, and configuration sources. Keep argparse for small scripts, and stay with Click when the surrounding code already depends on its contexts or plugins.

We installed it

Lab card: what happened when we installed cycloptsScreenshot of cyclopts documentation
Install✓ · 0.3s8 packages on disk · 10 MB
Importimport cyclopts in 0.25s · pure Python · py.typed · requires Python >=3.10
Known vulns0(pip-audit)

Answers from our run

Does cyclopts install cleanly?

Yes. In a fresh container with an empty cache, pip install cyclopts finished in 0.3s, leaving 8 packages and 10 MB on disk. pip-audit reported no known vulnerabilities.

What does cyclopts need to run?

Python >=3.10, and nothing compiled: it is pure Python. In our run import cyclopts succeeded in 0.25s, and the package ships py.typed for type checkers.

cyclopts or typer: which should you use?

typer: Use it when Click compatibility and a larger body of examples matter more than Cyclopts union handling. Cyclopts is a good fit for Python 3.10+ tools whose real complexity is typed parameters, generated help, and configuration sources.

When should you not use cyclopts?

The tool must run on Python 3.9 or older; Cyclopts requires Python 3.10 or newer

API stability3/5The core App, command decorator, default command, run shortcut, and Parameter metadata remain recognizable across the 4.x line. The project also releases at a pace that can expose typing or behavior changes quickly, and advanced configuration spans several public objects. Version 4.23.2 only adds type hints to help-copy methods, but production tools should still pin and test upgrades.
Docs5/5The stable documentation covers the first function, multi-command Apps, parameter conversion, unions, docstring formats, validators, grouping, config sources, shell completion, meta Apps, testing, and a direct Typer comparison. Examples show both the short path and the machinery behind advanced cases. Readers can find the generated API reference when a decorator example no longer answers the question.
Maintenance5/5GitHub reports an unarchived repository, 1,235 stars, a push on August 22, 2026, and 23 open issues and pull requests. Release 4.23.2 was published from that same work and followed the measured 4.23.1 build. The short gap between releases shows prompt maintenance, while also supporting the advice to pin versions instead of taking every update automatically.
Ecosystem3/5The supplied activity figure is 20,250,696 weekly downloads, and Cyclopts integrates directly with dataclasses, attrs, Pydantic, Rich help, environment values, and common config formats. It does not inherit Click's extensions or testing APIs, and its 1,235 GitHub stars indicate a much smaller community than Click or Typer. Teams should expect the official docs to be their main reference.

Use it if

  • Your function signatures already describe the CLI and you want docstrings to provide parameter help without repeating each description in decorators
  • Arguments include unions, Literal choices, nested dataclasses, attrs classes, or Pydantic models that should be converted before the command runs
  • One application needs subcommands, grouped validation, environment values, and TOML or YAML configuration through the same parser
  • You want to test a command by passing a token list to an App and asserting its Python return value
Skip it if

Setup reality

Our clean Python 3.12 install of Cyclopts 4.23.1 succeeded in 0.3 seconds. It left eight packages and 10 MB on disk. The package data reported 33 direct dependencies, requires Python 3.10 or newer, contains only Python code, and includes py.typed. Importing cyclopts took 0.25 seconds. pip-audit found no known vulnerabilities, and the installed metadata identifies the license as Apache Software License. The registry now lists 4.23.2.

There are no credentials or required config files. Start with run(function) for one command or an App when you need subcommands. Docstrings are part of the interface: keep parameter names synchronized and use a format the parser recognizes. Underscores become hyphens in option names, keyword-only parameters become options, and boolean options may gain a negative form. Check generated help in tests rather than discovering those rules after release.

Configuration sources are optional and ordered. CLI tokens take priority, while Env, Toml, Json, or Yaml sources supply missing values according to the order given to App. A misspelled optional config path can behave like an empty file unless you require its existence. YAML support needs the package extra. Treat secrets from environment variables as values to consume, not text to print in help or error logs.

The larger learning cost sits in Parameter, Group, and the meta App. Parameter owns names, converters, validators, and environment bindings. Group controls help sections and multi-parameter rules. A meta App handles options that must run before every subcommand. Pin the minor version for production tools and read its release notes before upgrading. Version 4.23.2 changes typing for help-copy methods rather than runtime parsing, but the fast release cadence still deserves controlled updates.

Patterns

Turn one function into a command run-one-function

from cyclopts import run

def resize(path: str, width: int, height: int = 1080):
    """Resize an image.

    Parameters
    ----------
    path
        Source image.
    width
        Output width.
    height
        Output height.
    """
    return path, width, height

run(resize)

run() suits a single command. Move to App when you need subcommands, shared options, or custom help behavior.

Create an application with subcommands register-subcommands

from cyclopts import App

app = App(name='shipper', version='2.0.0')

@app.command
def build(target: str, *, clean: bool = False):
    """Build one target."""

@app.command
def deploy(environment: str):
    """Deploy the build."""

if __name__ == '__main__':
    app()

Function underscores become command-name hyphens. Running an App without a default command displays help.

Mix a number with named choices accept-union-choice

from typing import Literal

@app.command
def serve(
    workers: int | Literal['auto', 'maximum'] = 'auto',
):
    return workers

Union members are attempted in order. A broad str member placed first can consume input intended for a narrower converter.

Add a short alias and remove a negative flag customize-option

from typing import Annotated
from cyclopts import Parameter

@app.command
def sync(
    *,
    verbose: Annotated[bool, Parameter(alias='-v')] = False,
    force: Annotated[bool, Parameter(negative=())] = False,
):
    pass

Boolean parameters normally receive positive and negative spellings. negative=() suppresses the generated negative form.

Reject an invalid path or worker count validate-input

from pathlib import Path
from typing import Annotated
from cyclopts import Parameter, validators

@app.command
def ingest(
    source: Annotated[Path, Parameter(validator=validators.Path(exists=True, dir_okay=False))],
    workers: Annotated[int, Parameter(validator=validators.Number(gte=1, lte=32))] = 4,
):
    pass

Validators run before the function. The user receives a parser error instead of a traceback from command code.

Layer environment and TOML values load-env-and-toml

import cyclopts
from cyclopts import App

app = App(config=[
    cyclopts.config.Env('SHIPPER_'),
    cyclopts.config.Toml(
        'pyproject.toml',
        root_keys=('tool', 'shipper'),
        search_parents=True,
    ),
])

The source list defines precedence for values absent from CLI tokens. Use must_exist=True when a missing config file should stop execution.

Expand a dataclass into options bind-dataclass

from dataclasses import dataclass

@dataclass
class Database:
    host: str = 'localhost'
    port: int = 5432
    tls: bool = False

@app.command
def migrate(database: Database):
    return database

Nested fields become dotted option names. Inspect command help to confirm the spelling callers must use.

Make authentication options exclusive group-exclusive-options

from typing import Annotated
from cyclopts import Group, Parameter, validators

auth = Group('Authentication', validator=validators.MutuallyExclusive())

@app.command
def fetch(
    *,
    token: Annotated[str | None, Parameter(group=auth)] = None,
    use_netrc: Annotated[bool, Parameter(group=auth)] = False,
):
    pass

A Group controls both the help panel and validation across its parameters. It is more than a display heading.

Parse options before every command add-global-options

@app.meta.default
def launch(*tokens: str, verbose: bool = False):
    configure_logging(verbose)
    return app(tokens)

if __name__ == '__main__':
    app.meta()

Call app.meta() as the program entry point. Calling app() directly bypasses these global options.

Test without starting a subprocess test-token-list

def test_build():
    result = app(
        ['build', 'docs', '--clean'],
        exit_on_error=False,
    )
    assert result == expected

exit_on_error=False turns parse failures into exceptions that the test can assert instead of SystemExit.

Alternatives

PackageRegistryPick it when
typerPyPIUse it when Click compatibility and a larger body of examples matter more than Cyclopts union handling
clickPyPIUse it when decorator-defined options, Click contexts, plugins, and CliRunner are established project conventions
argparse-dataclassPyPIUse it when standard argparse behavior is desired and a dataclass is the only extra abstraction needed
python-firePyPIUse it for internal tools that should expose existing Python objects with very little CLI declaration

More cli & tooling guides

commander · chalk · typescript · esbuild · yargs · click · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.