cyclopts 4.0.0


pip install cyclopts

  Latest version

Released: Oct 20, 2025


Meta
Author: Brian Pugh
Requires Python: >=3.10

Classifiers

License
  • OSI Approved :: Apache Software License

Operating System
  • OS Independent

Programming Language
  • Python :: 3
  • Python :: 3.10
  • Python :: 3.11
  • Python :: 3.12
  • Python :: 3.13

Python compat PyPI ReadTheDocs codecov


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 command decorator.
  • Register a default function with the default decorator.
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's includes information from docstrings, support more complex types (even Unions and Literals!), and include 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. However, lets see how close we can get with Typer (47-lines):

import typer
from typing import Annotated, Literal
from enum import Enum

app = typer.Typer()

class Environment(str, Enum):
    dev = "dev"
    staging = "staging"
    prod = "prod"

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[Environment, 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.name} 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           ENV:{dev|staging|prod}  Environment to deploy to. [default: None] [required] │
│      replicas      [REPLICAS]              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.py deploy [OPTIONS] ENV:{dev|staging|prod} [REPLICAS]
Try 'my-script.py deploy --help' for help.
╭─ Error ─────────────────────────────────────────────────────────────────────────────────────────╮
│ Invalid value for '[REPLICAS]': nonexistent-env                                                 │
╰─────────────────────────────────────────────────────────────────────────────────────────────────╯

$ my-script --version
0.0.0

The Typer implementation is 47 lines long, while the Cyclopts implementation is just 29 (38% 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 replica could not be displayed on the help page. Cyclopts is much more terse, much more readable, and much more intuitive to use.

4.0.0 Oct 20, 2025
4.0.0b2 Oct 16, 2025
4.0.0b1 Oct 13, 2025
3.24.0 Sep 08, 2025
3.23.1 Aug 30, 2025
3.23.0 Aug 25, 2025
3.22.5 Jul 31, 2025
3.22.4 Jul 30, 2025
3.22.3 Jul 23, 2025
3.22.2 Jul 09, 2025
3.22.1 Jul 03, 2025
3.22.0 Jul 01, 2025
3.21.0 Jun 27, 2025
3.20.0 Jun 24, 2025
3.19.0 Jun 12, 2025
3.18.0 Jun 09, 2025
3.17.0 Jun 05, 2025
3.16.2 May 26, 2025
3.16.1 May 13, 2025
3.16.0 May 09, 2025
3.15.0 May 08, 2025
3.14.2 May 02, 2025
3.14.1 Apr 30, 2025
3.14.0 Apr 28, 2025
3.13.1 Apr 19, 2025
3.13.0 Apr 16, 2025
3.12.0 Apr 03, 2025
3.11.2 Mar 31, 2025
3.11.1 Mar 26, 2025
3.11.0 Mar 24, 2025
3.10.1 Mar 18, 2025
3.10.0 Mar 16, 2025
3.9.3 Mar 12, 2025
3.9.2 Mar 08, 2025
3.9.1 Mar 06, 2025
3.9.0 Feb 17, 2025
3.8.1 Feb 13, 2025
3.8.0 Feb 12, 2025
3.7.0 Feb 08, 2025
3.6.0 Feb 07, 2025
3.5.1 Feb 05, 2025
3.5.0 Feb 04, 2025
3.4.1 Jan 31, 2025
3.4.0 Jan 27, 2025
3.3.1 Jan 21, 2025
3.3.0 Jan 18, 2025
3.2.1 Jan 15, 2025
3.2.0 Jan 14, 2025
3.1.5 Jan 10, 2025
3.1.4 Jan 07, 2025
3.1.3 Dec 30, 2024
3.1.2 Nov 28, 2024
3.1.1 Nov 27, 2024
3.1.0 Nov 23, 2024
3.0.1 Nov 19, 2024
3.0.0 Nov 09, 2024
2.9.9 Aug 27, 2024
2.9.8 Aug 27, 2024
2.9.7 Aug 16, 2024
2.9.6 Aug 14, 2024
2.9.5 Aug 12, 2024
2.9.4 Jul 22, 2024
2.9.3 Jul 04, 2024
2.9.2 Jul 03, 2024
2.9.1 Jul 02, 2024
2.9.0 Jul 02, 2024
2.8.0 Jun 28, 2024
2.7.1 Jun 20, 2024
2.7.0 Jun 10, 2024
2.6.2 May 16, 2024
2.6.1 Apr 16, 2024
2.6.0 Apr 10, 2024
2.5.0 Mar 25, 2024
2.4.2 Mar 10, 2024
2.4.1 Mar 07, 2024
2.4.0 Feb 24, 2024
2.3.2 Feb 21, 2024
2.3.1 Feb 20, 2024
2.3.0 Feb 17, 2024
2.2.0 Jan 27, 2024
2.1.2 Jan 18, 2024
2.1.1 Jan 17, 2024
2.1.0 Jan 15, 2024
2.0.0 Jan 15, 2024
1.3.0 Dec 27, 2023
1.2.0 Dec 20, 2023
1.1.1 Dec 19, 2023
1.1.0 Dec 16, 2023
1.0.2 Dec 13, 2023
1.0.1 Dec 08, 2023
1.0.0 Dec 05, 2023
0.2.0 Dec 04, 2023
0.1.2 Dec 02, 2023
0.1.1 Nov 30, 2023
0.1.0 Nov 26, 2023
0.0.0 Nov 03, 2023

Wheel compatibility matrix

Platform Python 3
any

Files in release

Extras:
Dependencies:
attrs (>=23.1.0)
docstring-parser (<4.0,>=0.15)
rich-rst (<2.0.0,>=1.3.1)
rich (>=13.6.0)
tomli (>=2.0.0)
typing-extensions (>=4.8.0)