Migrating From Typer

Much of Cyclopts's syntax is Typer-inspired. Migrating from Typer should be pretty straightforward; it is recommended to first read the Getting Started and Commands sections. The below table offers a jumping off point for translating the various portions of the APIs. The Typer Comparison page also provides many examples comparing the APIs.

Typer-to-Cyclopts API Reference

Typer

Cyclopts

Notes

typer.Typer()

cyclopts.App()

Same/similar fields:
  • App.name - Optional name of application or sub-command.

Cyclopts has more user-friendly default features:
  • Equivalent no_args_is_help=True.

  • Equivalent pretty_exceptions_enable=False.

@app.command()

@app.command()

In Cyclopts, @app.command always results in a command. To define an action when no command is provided, see @app.default.

@app.add_typer()

@app.command()

Sub applications and commands are registered the same way in Cyclopts.

@app.callback()

@app.default()

@app.meta.default()

Typer's callback always executes before executing an app. If used to provide functionality when no command was specified from the CLI, then use @app.default(). Otherwise, checkout Cyclopt's Meta App.

Annotated[..., typer.Argument(...)]

Annotated[..., typer.Option(...)]

Annotated[..., cyclopts.Parameter(...)]

In Cyclopts, Positional/Keyword arguments are determined from the function signature. Some of Typer's validation fields, like exists for Path types are handled in Cyclopts by explicit validators.

Cyclopts and Typer mostly handle type-hints the same way, but there are a few notable exceptions:

Typer-to-Cyclopts Type-Hints

Type Annotation

Notes

Enum

Compared to Typer, Cyclopts handles Enum lookups in the reverse direction. Frequently, Literal offers a more terse, intuitive choice option.

Union

Typer does not support type unions. Cyclopts does.

Optional[List] = None

When no CLI argument is specified, Typer passes in an empty list []. Cyclopts will not bind an argument, resulting in the default None.

General Steps

  1. Add the following import: from cyclopts import App, Parameter.

  2. Change app = Typer(...) to just app = App(). Revisit more advanced configuration later.

  3. Remove all @app.callback stuff. Cyclopts already provides a good --version handler for you.

  4. Replace all Annotated[..., Argument/Option] type-hints with Annotated[..., Parameter()]. If only supplying a help string, it's better to supply it via docstring.

  5. Cyclopts has similar boolean-flag handling as Typer, but has different configuration parameters.

    #########
    # Typer #
    #########
    # Overriding the name results in no "False" flag generation.
    my_flag: Annotated[bool, Option("--my-custom-flag")]
    # However, it can be custom specified:
    my_flag: Annotated[bool, Option("--my-custom-flag/--disable-my-custom-flag")]
    
    ############
    # Cyclopts #
    ############
    # Overriding the name still results in "False" flag generation:
    #    --my-custom-flag --no-my-custom-flag
    my_flag: Annotated[bool, Parameter("--my-custom-flag")]
    # Negative flag generation can be disabled:
    #    --my-custom-flag
    my_flag: Annotated[bool, Parameter("--my-custom-flag", negative="")]
    # Or the prefix can be changed:
    #    --my-custom-flag --disable-my-custom-flag
    my_flag: Annotated[bool, Parameter("--my-custom-flag", negative_bool="--disable-")]
    

After the basic migration is done, it is recommended to read through the rest of Cyclopts's documentation to learn about some of the better functionality it has, which could result in cleaner, terser code.