Documentation: https://cyclopts.readthedocs.io
Source Code: https://github.com/BrianPugh/cyclopts
Cyclopts is a modern, easy-to-use command-line interface (CLI) framework that aims to provide an intuitive & efficient developer experience.
Why Cyclopts?
-
Intuitive API: Quickly write CLI applications using a terse, intuitive syntax.
-
Advanced Type Hinting: Full support of all builtin types and even user-specified (yes, including Pydantic, Dataclasses, and Attrs).
-
Rich Help Generation: Automatically generates beautiful help pages from docstrings and other contextual data.
-
Extendable: Easily customize converters, validators, token parsing, and application launching.
Installation
Cyclopts requires Python >=3.10; to install Cyclopts, run:
pip install cyclopts
Quick Start
- Import
cyclopts.run()and give it a function to run.
from cyclopts import run
def foo(loops: int):
for i in range(loops):
print(f"Looping! {i}")
run(foo)
Execute the script from the command line:
$ python start.py 3
Looping! 0
Looping! 1
Looping! 2
When you need more control:
- Create an application using
cyclopts.App. - Register commands with the
commanddecorator. - Register a default function with the
defaultdecorator.
from cyclopts import App
app = App()
@app.command
def foo(loops: int):
for i in range(loops):
print(f"Looping! {i}")
@app.default
def default_action():
print("Hello world! This runs when no command is specified.")
app()
Execute the script from the command line:
$ python demo.py
Hello world! This runs when no command is specified.
$ python demo.py foo 3
Looping! 0
Looping! 1
Looping! 2
With just a few additional lines of code, we have a full-featured CLI app. See the docs for more advanced usage.
Compared to Typer
Cyclopts is what you thought Typer was. Cyclopts includes information from docstrings, supports more complex types (even Unions!), and includes proper validation support. See the documentation for a complete Typer comparison.
Consider the following short 29-line Cyclopts application:
import cyclopts
from typing import Literal
app = cyclopts.App()
@app.command
def deploy(
env: Literal["dev", "staging", "prod"],
replicas: int | Literal["default", "performance"] = "default",
):
"""Deploy code to an environment.
Parameters
----------
env
Environment to deploy to.
replicas
Number of workers to spin up.
"""
if replicas == "default":
replicas = 10
elif replicas == "performance":
replicas = 20
print(f"Deploying to {env} with {replicas} replicas.")
if __name__ == "__main__":
app()
$ my-script deploy --help
Usage: my-script.py deploy [ARGS] [OPTIONS]
Deploy code to an environment.
╭─ Parameters ────────────────────────────────────────────────────────────────────────────────────╮
│ * ENV --env Environment to deploy to. [choices: dev, staging, prod] [required] │
│ REPLICAS --replicas Number of workers to spin up. [choices: default, performance] [default: │
│ default] │
╰─────────────────────────────────────────────────────────────────────────────────────────────────╯
$ my-script deploy staging
Deploying to staging with 10 replicas.
$ my-script deploy staging 7
Deploying to staging with 7 replicas.
$ my-script deploy staging performance
Deploying to staging with 20 replicas.
$ my-script deploy nonexistent-env
╭─ Error ────────────────────────────────────────────────────────────────────────────────────────────╮
│ Error converting value "nonexistent-env" to typing.Literal['dev', 'staging', 'prod'] for "--env". │
╰────────────────────────────────────────────────────────────────────────────────────────────────────╯
$ my-script --version
0.0.0
In its current state, this application would be impossible to implement in Typer; Typer does not support the union type-hint of replicas.
However, let's see how close we can get with Typer (41-lines):
import typer
from typing import Annotated, Literal
app = typer.Typer()
def replica_parser(value: str):
if value == "default":
return 10
elif value == "performance":
return 20
else:
return int(value)
def _version_callback(value: bool):
if value:
print("0.0.0")
raise typer.Exit()
@app.callback()
def callback(
version: Annotated[
bool | None, typer.Option("--version", callback=_version_callback)
] = None,
):
pass
@app.command(help="Deploy code to an environment.")
def deploy(
env: Annotated[Literal["dev", "staging", "prod"], typer.Argument(help="Environment to deploy to.")],
replicas: Annotated[
int,
typer.Argument(
parser=replica_parser,
help="Number of workers to spin up.",
),
] = replica_parser("default"),
):
print(f"Deploying to {env} with {replicas} replicas.")
if __name__ == "__main__":
app()
$ my-script deploy --help
Usage: my-script deploy [OPTIONS] {env}:<dev|staging|prod> [replicas]
Deploy code to an environment.
╭─ Arguments ─────────────────────────────────────────────────────────────────────────────────────╮
│ * env <dev|staging|prod> Environment to deploy to. [required] │
│ replicas <replica_parser> Number of workers to spin up. [default: 10] │
╰─────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─ Options ───────────────────────────────────────────────────────────────────────────────────────╮
│ --help Show this message and exit. │
╰─────────────────────────────────────────────────────────────────────────────────────────────────╯
$ my-script deploy staging
Deploying to staging with 10 replicas.
$ my-script deploy staging 7
Deploying to staging with 7 replicas.
$ my-script deploy staging performance
Deploying to staging with 20 replicas.
$ my-script deploy nonexistent-env
Usage: my-script deploy [OPTIONS] {env}:<dev|staging|prod> [replicas]
Try 'my-script deploy --help' for help.
╭─ Error ─────────────────────────────────────────────────────────────────────────────────────────╮
│ Invalid value for 'env': 'nonexistent-env' is not one of 'dev', 'staging', 'prod'. │
╰─────────────────────────────────────────────────────────────────────────────────────────────────╯
$ my-script --version
0.0.0
The Typer implementation is 41 lines long, while the Cyclopts implementation is just 29 (29% shorter!).
Not only is the Cyclopts implementation significantly shorter, but the code is easier to read.
Since Typer does not support Unions, the choices for replicas could not be displayed on the help page.
Cyclopts is much more terse, much more readable, and much more intuitive to use.
Contributing
Contributions are welcome! See CONTRIBUTING.md for development setup, coding standards, and how to submit a pull request.
Release files for cyclopts 4.24.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| cyclopts-4.24.0.tar.gz | 200.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| cyclopts-4.24.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 440.7 kB
Release files / cyclopts-4.24.0.tar.gz
| Download URL | cyclopts-4.24.0.tar.gz |
|---|---|
| Size | 200.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9bf9da3f09a0ff74aa6621e1644abfdde3b22158b0c09b5aaa6f82bd0c31c526
|
|
BLAKE2b-256 checksum How to use checksums |
e64e9d8d4ac3be590cc6553047bfdd53dc6ed776fe3e180ad77559629b0d1c66
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 3, 2026.
Transparency logRelease files / cyclopts-4.24.0-py3-none-any.whl
| Download URL | cyclopts-4.24.0-py3-none-any.whl |
|---|---|
| Size | 240.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b3e44fd301ea498f0b30578d6192a9d9565fd0098e736bd9c8ca0a33f6989659
|
|
BLAKE2b-256 checksum How to use checksums |
839dbe0c38b950999b73befa59006dacd8f83e5ec5c3d3172b61883beacecc56
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 3, 2026.
Transparency log