1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
|
.. _Commands:
========
Commands
========
There are two different ways of registering functions:
1. :meth:`app.default <cyclopts.App.default>` -
Registers an action for when no registered command is provided.
This was previously demonstrated in :ref:`Getting Started`.
A sub-app **cannot** be registered with :meth:`app.default <cyclopts.App.default>`.
If no ``default`` command is registered, Cyclopts will display the help-page.
2. :meth:`app.command <cyclopts.App.command>` - Registers a function or :class:`.App` as a command.
This section will detail how to use the :meth:`@app.command <cyclopts.App.command>` decorator.
---------------------
Registering a Command
---------------------
The :meth:`@app.command <cyclopts.App.command>` decorator adds a **command** to a Cyclopts application.
.. code-block:: python
from cyclopts import App
app = App()
@app.command
def fizz(n: int):
print(f"FIZZ: {n}")
@app.command
def buzz(n: int):
print(f"BUZZ: {n}")
app()
We can now control which command runs from the CLI:
.. code-block:: console
$ my-script fizz 3
FIZZ: 3
$ my-script buzz 4
BUZZ: 4
$ my-script fuzz
╭─ Error ────────────────────────────────────────────────────────────────────╮
│ Unknown command "fuzz". Did you mean "fizz"? │
╰────────────────────────────────────────────────────────────────────────────╯
------------------------
Registering a SubCommand
------------------------
The :meth:`app.command <cyclopts.App.command>` method can also register another Cyclopts :class:`.App` as a command.
.. code-block:: python
from cyclopts import App
app = App()
sub_app = App(name="foo") # "foo" would be a better variable name than "sub_app".
# "sub_app" in this example emphasizes the name comes from name="foo".
app.command(sub_app) # Registers sub_app to command "foo"
# Or, as a one-liner: app.command(sub_app := App(name="foo"))
@sub_app.command
def bar(n: int):
print(f"BAR: {n}")
# Alternatively, access subapps from app like a dictionary.
@app["foo"].command
def baz(n: int):
print(f"BAZ: {n}")
app()
.. code-block:: console
$ my-script foo bar 3
BAR: 3
$ my-script foo baz 4
BAZ: 4
The subcommand may have their own registered ``default`` action.
Cyclopts's command structure is fully recursive.
.. _Command Changing Name:
---------------------
Changing Command Name
---------------------
By default, commands are registered to the python function's name with underscores replaced with hyphens.
Any leading or trailing underscores will be stripped.
For example, the function ``_foo_bar()`` will become the command ``foo-bar``.
This renaming is done because CLI programs generally tend to use hyphens instead of underscores.
The name transform can be configured by :attr:`App.name_transform <cyclopts.App.name_transform>`.
For example, to make CLI command names be identical to their python function name counterparts, we can configure :class:`~cyclopts.App` as follows:
.. code-block:: python
from cyclopts import App
app = App(name_transform=lambda s: s)
@app.command
def foo_bar(): # will now be "foo_bar" instead of "foo-bar"
print("running function foo_bar")
app()
.. code-block:: console
$ my-script foo_bar
running function foo_bar
Alternatively, the name can be **manually** changed in the :meth:`@app.command <cyclopts.App.command>` decorator.
Manually set names are **not** subject to :attr:`App.name_transform <cyclopts.App.name_transform>`.
.. code-block:: python
from cyclopts import App
app = App()
@app.command(name="bar")
def foo(): # function name will NOT be used.
print("Hello World!")
app()
.. code-block:: console
$ my-script bar
Hello World!
-----------
Adding Help
-----------
There are a few ways to add a help string to a command:
1. If the function has a docstring, the **short description** will be used as the help string for the command.
This is generally the preferred method of providing help strings.
2. If the registered command is a sub app, the sub app's :attr:`help <cyclopts.App.help>` field will be used.
.. code-block:: python
sub_app = App(name="foo", help="Help text for foo.")
app.command(sub_app)
3. The :attr:`help <cyclopts.App.help>` field of :meth:`@app.command <cyclopts.App.command>`. If provided, the docstring or subapp help field will **not** be used.
.. code-block:: python
from cyclopts import App
app = App()
@app.command
def foo():
"""Help string for foo."""
pass
@app.command(help="Help string for bar.")
def bar():
"""This got overridden."""
app()
.. code-block:: console
$ my-script --help
╭─ Commands ────────────────────────────────────────────────────────────╮
│ bar Help string for bar. │
│ foo Help string for foo. │
│ --help,-h Display this message and exit. │
│ --version Display application version. │
╰───────────────────────────────────────────────────────────────────────╯
-----
Async
-----
Cyclopts also works with **async** commands:
.. code-block:: python
import asyncio
from cyclopts import App
app = App()
@app.command
async def foo():
await asyncio.sleep(10)
app()
--------------------------
Decorated Function Details
--------------------------
Cyclopts **does not modify the decorated function in any way**.
The returned function is the **exact same function** being decorated and can be used exactly as if it were not decorated by Cyclopts.
|