|
| 1 | +# One File Per Command |
| 2 | + |
| 3 | +When your CLI application grows, you can split it into multiple files and modules. This pattern helps maintain a clean and organized code structure. ✨ |
| 4 | + |
| 5 | +This tutorial will show you how to use `add_typer` to create sub commands and organize your commands in multiple files. |
| 6 | + |
| 7 | +We will create a simple CLI with the following commands: |
| 8 | + |
| 9 | +- `version` |
| 10 | +- `users add NAME` |
| 11 | +- `users delete NAME` |
| 12 | + |
| 13 | +## CLI structure |
| 14 | + |
| 15 | +Here is the structure we'll be working with: |
| 16 | + |
| 17 | +```text |
| 18 | +mycli/ |
| 19 | +├── __init__.py |
| 20 | +├── main.py |
| 21 | +├── users/ |
| 22 | +│ ├── __init__.py |
| 23 | +│ ├── add.py |
| 24 | +│ └── delete.py |
| 25 | +└── version.py |
| 26 | +``` |
| 27 | + |
| 28 | +`mycli` will be our <abbr title="a directory with an __init__.py file, it can be imported">package</abbr>, and it will contain the following modules: |
| 29 | + |
| 30 | +- `main.py`: The main <abbr title="a Python file that can be imported">module</abbr> that will import the `version` and `users` modules. |
| 31 | +- `version.py`: A <abbr title="a Python file that can be imported">module</abbr> that will contain the `version` command. |
| 32 | +- `users/`: A <abbr title="another directory with an __init__.py file, it can also be imported">package</abbr> (inside of our `mycli` package) that will contain the `add` and `delete` commands. |
| 33 | + |
| 34 | +## Implementation |
| 35 | + |
| 36 | +Let's start implementing our CLI! 🚀 |
| 37 | + |
| 38 | +We'll create the `version` module, the `main` module, and the `users` package. |
| 39 | + |
| 40 | +### Version Module (`version.py`) |
| 41 | + |
| 42 | +Let's start by creating the `version` module. This module will contain the `version` command. |
| 43 | + |
| 44 | +{* docs_src/one_file_per_command/version.py *} |
| 45 | + |
| 46 | +In this file we are creating a new Typer app instance for the `version` command. |
| 47 | + |
| 48 | +This is not required in single-file applications, but in the case of multi-file applications it will allow us to include this command in the main application using `app.add_typer()`. |
| 49 | + |
| 50 | +Let's see that next! |
| 51 | + |
| 52 | +### Main Module (`main.py`) |
| 53 | + |
| 54 | +The main module will be the entry point of the application. It will import the version module and the users module. |
| 55 | + |
| 56 | +/// tip |
| 57 | + |
| 58 | +We'll see how to implement the users module in the next section. |
| 59 | + |
| 60 | +/// |
| 61 | + |
| 62 | +{* docs_src/one_file_per_command/main.py hl[8,9] *} |
| 63 | + |
| 64 | +In this module, we import the `version` and `users` modules and add them to the main app using `app.add_typer()`. |
| 65 | + |
| 66 | +For the `users` module, we specify the name as `"users"` to group the commands under the `users` sub-command. |
| 67 | + |
| 68 | +Notice that we didn't add a name for the `version_app` Typer app. Because of this, Typer will add the commands (just one in this case) declared in the `version_app` directly at the top level. So, there will be a top-level `version` sub-command. |
| 69 | + |
| 70 | +But for `users`, we add a name `"users"`, this way those commands will be under the sub-command `users` instead of at the top level. So, there will be a `users add` and `users delete` sub-sub-commands. 😅 |
| 71 | + |
| 72 | +/// tip |
| 73 | + |
| 74 | +If you want a command to group the included commands in a sub-app, add a name. |
| 75 | + |
| 76 | +If you want to include the commands from a sub-app directly at the top level, don't add a name, or set it to `None`. 🤓 |
| 77 | + |
| 78 | +/// |
| 79 | + |
| 80 | +Let's now create the `users` module with the `add` and `delete` commands. |
| 81 | + |
| 82 | +### Users Add Command (`users/add.py`) |
| 83 | + |
| 84 | +{* docs_src/one_file_per_command/users/add.py *} |
| 85 | + |
| 86 | +Like the `version` module, we create a new Typer app instance for the `users/add` command. This allows us to include the `add` command in the users app. |
| 87 | + |
| 88 | +### Users Delete Command (`users/delete.py`) |
| 89 | + |
| 90 | +{* docs_src/one_file_per_command/users/delete.py *} |
| 91 | + |
| 92 | +And once again, we create a new Typer app instance for the `users/delete` command. This allows us to include the `delete` command in the users app. |
| 93 | + |
| 94 | +### Users' app (`users/__init__.py`) |
| 95 | + |
| 96 | +Finally, we need to create an `__init__.py` file in the `users` directory to define the `users` app. |
| 97 | + |
| 98 | +{* docs_src/one_file_per_command/users/__init__.py *} |
| 99 | + |
| 100 | +Similarly to the `version` module, we create a new `Typer` app instance for the `users` module. This allows us to include the `add` and `delete` commands in the users app. |
| 101 | + |
| 102 | +## Running the Application |
| 103 | + |
| 104 | +Now we are ready to run the application! 😎 |
| 105 | + |
| 106 | +To run the application, you can execute it as a Python module: |
| 107 | + |
| 108 | +<div class="termy"> |
| 109 | + |
| 110 | +```console |
| 111 | +$ python -m mycli.main version |
| 112 | + |
| 113 | +My CLI Version 1.0 |
| 114 | + |
| 115 | +$ python -m mycli.main users add Camila |
| 116 | + |
| 117 | +Adding user: Camila |
| 118 | +``` |
| 119 | + |
| 120 | +</div> |
| 121 | + |
| 122 | +And if you built a package and installed your app, you can then use the `mycli` command: |
| 123 | + |
| 124 | +<div class="termy"> |
| 125 | + |
| 126 | +```console |
| 127 | +$ mycli version |
| 128 | + |
| 129 | +My CLI Version 1.0 |
| 130 | + |
| 131 | +$ mycli users add Camila |
| 132 | + |
| 133 | +Adding user: Camila |
| 134 | +``` |
| 135 | + |
| 136 | +</div> |
| 137 | + |
| 138 | +## Callbacks |
| 139 | + |
| 140 | +Have in mind that if you include a sub-app with `app.add_typer()` **without a name**, the commands will be added to the top level, so **only the top level callback** (if there's any) will be used, the one declared in the main app. |
| 141 | + |
| 142 | +If you **want to use a callback** for a sub-app, you need to include the sub-app **with a name**, which creates a sub-command grouping the commands in that sub-app. 🤓 |
| 143 | + |
| 144 | +In the example above, if the `users` sub-app had a callback, it would be used. But if the `version` sub-app had a callback, it would not be used, because the `version` sub-app was included without a name. |
0 commit comments