Cyclopts v4 represents a big feature update that makes it a no-compromise CLI python framework.
While Cyclopts v4 has a few breaking changes that generally make applications cleaner/terser/more intuitive, most shouldn't severely impact applications in the wild. This section lists the changes from most impactful to least.
Breaking Features / Breaking Changes / Migration
-
The default help/version formatting has been changed from RestructuredText to Markdown.
- To keep old behavior, set
App(help_format="restructuredtext").
- To keep old behavior, set
-
Default behavior of
App.__call__andApp.run_asyncreturn value has changed. By default, these methods do not return and now perform asys.exit. This ensures that scripts and installed applications have consistent exit code behavior. Previously, a script might have had a different exit code compared to an equivalent installed package. This behavior can be controlled via the new attributeApp.result_action.-
To replicate the old behavior, set
result_action="return_value"in your root app.app = App(result_action="return_value")
-
-
New App-inheritance mechanism/priorities. For most users this will have no impact, but pay attention if you use Meta Apps.
- Previously, meta-apps were treated as mostly independent. They now inherit configurations from their parent App.
- The execution path through the App tree may now be different from the inheritance path. Meta App parents are higher up the inheritance tree than meta-apps. I.e.,
app.metainherits fromapp. - See #497 for discussion.
-
Dropped Python 3.9 support. Python 3.9 EOL is October 31, 2025.
-
On the help page, the root application name falls back to the script name (instead of the registered
@app.defaultcommand name). -
If a dataclass-like parameter is annotated with
Parameter(name="*"), and all of its attributes are optional, but the parameter itself is not optional, aValueErrorwill now be raised. The value in the function signature must have a default value likeNone. See discussion in #519. -
InvalidCommandErrorhas been renamedUnknownCommandErrorfor consistency. -
Errors are now printed to stderr instead of stdout. Uses the new
App.error_console.-
If you want the old behavior (printing errors to stdout), set
error_consoleto a console writing to stdout:from rich.console import Console app = App(error_console=Console())
-
-
All
Parameterarguments exceptname(the only positional parameter) are now keyword-only. -
If only nameless
Groups are assigned to aParameter, the default Argument/Parameter group is still also applied. This is most convenient when applying a validator without impacting the help page.
Features
Pure "value added" features.
-
Lazy loading - Commands can now be registered using import paths (e.g.,
"myapp.commands.users:create"), which defers module imports until the command is executed. This dramatically improves CLI startup time for applications with many commands or heavy dependencies.from cyclopts import App app = App(name="myapp") user_app = App(name="user") # Module only imported when command is executed user_app.command("myapp.commands.users:create") app.command(user_app)
-
Shell Completion (supports bash, zsh, fish). Enable with
app.register_install_completion_command(), then users can install viamyapp --install-completion.- Standalone python scripts can have dynamic shell completion via the new
cyclopts runCLI command.
- Standalone python scripts can have dynamic shell completion via the new
-
Help page customization via
help_formatterparameter onAppandGroup.- Create fully custom formatters by implementing the
HelpFormatterprotocol. - Built-in formatters:
DefaultFormatter(Rich-based with colors/borders) andPlainFormatter(accessibility-focused plain text).
- Create fully custom formatters by implementing the
-
New
cycloptsCLI tool.cyclopts run- Execute a python script with dynamic shell-completion.cyclopts generate-docs- Generates documentation in a variety of formats (markdown, restructuredtext, html).- Example:
cyclopts generate-docs myscript.py -o docs.md
- Example:
-
Sphinx Extension for automatically generating CLI documentation from your Cyclopts App.
- Use the
.. cyclopts:: mypackage.cli:appdirective in your RST files.
- Use the
-
New
Appattributes; many of these were available asApp.__call__parameters, but now they can also be directly set onAppand are inherited as expected:print_error- Whether Cyclopts should print the rich-formatted error when aCycloptsErroris encountered. True by default.exit_on_error- If there is an error parsing/coercing the CLI tokens, invokesys.exit(1). True by default.help_on_error- If there is an error parsing/coercing the CLI tokens, print out the help-page for the parsed application. False by default.verbose- PopulateCycloptsErrorexception strings with more information intended for developers. False by default.
-
flatten subapp's subcommands if named
"*". by @BrianPugh in #611 -
Add support for
enum.Flagandenum.IntFlag. -
Add support for
datetime.datetype by @PerchunPak in #601 -
Add
cyclopts.config.Dictconfig class for in-memory configuration sources. Add new keyword argumentsourceto config objects. by @BrianPugh in #599 -
App.consolenow always resolves to aconsoleand respects the app-hierarchy. If a console is explicitly assigned, that console will always be used. This makes it easier to access aconsoleobject within commands. -
Improved parsing of JSON strings from the CLI. Can now handle
list[CustomClass]if each element is supplied as a JSON string. -
Optimized "happy execution path" performance.
-
If
App.sort_keyorGroup.sort_keyare generators, Cyclopts automatically invokesnexton them immediately.-
This allows for streamlined lexical ordering of commands using
itertools.count:from itertools import count from cyclopts import App, Group counter = count() @app.command(group=Group("Commands", sort_key=counter)) def first(): pass @app.command(group=Group("Commands", sort_key=counter)) def second(): pass
-
Bug Fixes
- Fixed empty iterables handling within Pydantic models.
- Allow for 0 arguments for iterables (e.g.,
list[int]) whenconsume_multiple=True. - Fixed incorrect parsing of very large integers.
- Do not parse
help_flagsafterend_of_options_delimiterby @BrianPugh in #609
Internal / Developer
- Migrated internal project manager from Poetry to uv.