From 73b969862e47cb3802876633779cd2df3cb8223b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 23 Aug 2024 12:56:40 -0500 Subject: [PATCH 01/47] =?UTF-8?q?=F0=9F=93=9D=20Add=20docs=20for=20virtual?= =?UTF-8?q?=20environments,=20environment=20variables,=20and=20update=20co?= =?UTF-8?q?ntributing=20(#946)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 + docs/contributing.md | 208 +++----- docs/environment-variables.md | 304 +++++++++++ docs/index.md | 2 + docs/tutorial/arguments/envvar.md | 6 + docs/tutorial/index.md | 50 +- docs/tutorial/install.md | 33 ++ docs/virtual-environments.md | 844 ++++++++++++++++++++++++++++++ mkdocs.yml | 3 + 9 files changed, 1262 insertions(+), 190 deletions(-) create mode 100644 docs/environment-variables.md create mode 100644 docs/tutorial/install.md create mode 100644 docs/virtual-environments.md diff --git a/README.md b/README.md index 49b441055f..b1e8c17eb2 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,8 @@ The key features are: ## Installation +Create and activate a virtual environment and then install **Typer**: +
```console diff --git a/docs/contributing.md b/docs/contributing.md index a4a158543a..bcb2a78960 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -4,197 +4,144 @@ First, you might want to see the basic ways to [help Typer and get help](help-ty ## Developing -If you already cloned the repository and you know that you need to deep dive in the code, here are some guidelines to set up your environment. +If you already cloned the typer repository and you want to deep dive in the code, here are some guidelines to set up your environment. -### Virtual environment with `venv` +### Virtual Environment -You can create a virtual environment in a directory using Python's `venv` module: +Follow the instructions to create and activate a [virtual environment](virtual-environments.md){.internal-link target=_blank} for the internal code of `fastapi`. -
- -```console -$ python -m venv env -``` - -
- -That will create a directory `./env/` with the Python binaries and then you will be able to install packages for that isolated environment. - -### Activate the environment +### Install Requirements Using `pip` -Activate the new environment with: - -//// tab | Linux, macOS +After activating the environment, install the required packages:
```console -$ source ./env/bin/activate +$ pip install -r requirements.txt + +---> 100% ```
-//// - -//// tab | Windows PowerShell - -
- -```console -$ .\env\Scripts\Activate.ps1 -``` +It will install all the dependencies and your local Typer in your local environment. -
+### Using your Local Typer -//// +If you create a Python file that imports and uses Typer, and run it with the Python from your local environment, it will use your cloned local Typer source code. -//// tab | Windows Bash +And if you update that local Typer source code when you run that Python file again, it will use the fresh version of Typer you just edited. -Or if you use Bash for Windows (e.g. Git Bash): +That way, you don't have to "install" your local version to be able to test every change. -
+/// note | "Technical Details" -```console -$ source ./env/Scripts/activate -``` +This only happens when you install using this included `requirements.txt` instead of running `pip install typer` directly. -
+That is because inside the `requirements.txt` file, the local version of Typer is marked to be installed in "editable" mode, with the `-e` option. -//// +/// -To check it worked, use: +### Format -//// tab | Linux, macOS, Windows Bash +There is a script that you can run that will format and clean all your code:
```console -$ which pip - -some/directory/typer/env/bin/pip +$ bash scripts/format.sh ```
-//// +It will also auto-sort all your imports. + +## Tests -//// tab | Windows PowerShell +There is a script that you can run locally to test all the code and generate coverage reports in HTML:
```console -$ Get-Command pip - -some/directory/typer/env/bin/pip +$ bash scripts/test-cov-html.sh ```
-//// - -If it shows the `pip` binary at `env/bin/pip` then it worked. ๐ŸŽ‰ - -/// tip - -Every time you install a new package with `pip` under that environment, activate the environment again. - -This makes sure that if you use a terminal program installed by that package (like `flit`), you use the one from your local environment and not any other that could be installed globally. +This command generates a directory `./htmlcov/`, if you open the file `./htmlcov/index.html` in your browser, you can explore interactively the regions of code that are covered by the tests, and notice if there is any region missing. -/// +## Docs -### Flit +First, make sure you set up your environment as described above, that will install all the requirements. -**Typer** uses Flit to build, package and publish the project. +### Docs live -After activating the environment as described above, install `flit`: +During local development, there is a script that builds the site and checks for any changes, live-reloading:
```console -$ pip install flit +$ python ./scripts/docs.py live ----> 100% +[INFO] Serving on http://127.0.0.1:8008 +[INFO] Start watching changes +[INFO] Start detecting changes ```
-Now re-activate the environment to make sure you are using the `flit` you just installed (and not a global one). +It will serve the documentation on `http://127.0.0.1:8008`. -And now use `flit` to install the development dependencies: +That way, you can edit the documentation/source files and see the changes live. -//// tab | Linux, macOS +/// tip -
+Alternatively, you can perform the same steps that scripts does manually. -```console -$ flit install --deps develop --symlink +Go into the docs director at `docs/`: ----> 100% +```console +$ cd docs/ ``` -
- -//// - -//// tab | Windows - -If you are on Windows, use `--pth-file` instead of `--symlink`: - -
+Then run `mkdocs` in that directory: ```console -$ flit install --deps develop --pth-file - ----> 100% +$ mkdocs serve --dev-addr 8008 ``` -
- -//// - -It will install all the dependencies and your local Typer in your local environment. - -#### Using your local Typer +/// -If you create a Python file that imports and uses Typer, and run it with the Python from your local environment, it will use your local Typer source code. +#### Typer CLI (optional) -And if you update that local Typer source code, as it is installed with `--symlink` (or `--pth-file` on Windows), when you run that Python file again, it will use the fresh version of Typer you just edited. +The instructions here show you how to use the script at `./scripts/docs.py` with the `python` program directly. -That way, you don't have to "install" your local version to be able to test every change. +But you can also use Typer CLI, and you will get autocompletion in your terminal for the commands after installing completion. -### Format - -There is a script that you can run that will format and clean all your code: +If you install Typer CLI, you can install completion with:
```console -$ bash scripts/format.sh +$ typer --install-completion + +zsh completion installed in /home/user/.bashrc. +Completion will take effect once you restart the terminal. ```
-It will also auto-sort all your imports. - -For it to sort them correctly, you need to have Typer installed locally in your environment, with the command in the section above using `--symlink` (or `--pth-file` on Windows). - -### Format imports - -There is another script that formats all the imports and makes sure you don't have unused imports: +### Docs Structure -
- -```console -$ bash scripts/format-imports.sh -``` +The documentation uses MkDocs. -
+And there are extra tools/scripts in place in `./scripts/docs.py`. -As it runs one command after the other and modifies and reverts many files, it takes a bit longer to run, so it might be easier to use `scripts/format.sh` frequently and `scripts/format-imports.sh` only before committing. +/// tip -## Docs +You don't need to see the code in `./scripts/docs.py`, you just use it in the command line. -The documentation uses MkDocs. +/// All the documentation is in Markdown format in the directory `./docs`. @@ -206,45 +153,12 @@ In fact, those blocks of code are not written inside the Markdown, they are Pyth And those Python files are included/injected in the documentation when generating the site. -### Docs for tests +### Docs for Tests Most of the tests actually run against the example source files in the documentation. -This helps making sure that: +This helps to make sure that: -* The documentation is up to date. +* The documentation is up-to-date. * The documentation examples can be run as is. * Most of the features are covered by the documentation, ensured by test coverage. - -During local development, there is a script that builds the site and checks for any changes, live-reloading: - -
- -```console -$ bash scripts/docs-live.sh - -[INFO] - Building documentation... -[INFO] - Cleaning site directory -[INFO] - Documentation built in 2.74 seconds -[INFO] - Serving on http://127.0.0.1:8008 -``` - -
- -It will serve the documentation on `http://127.0.0.1:8008`. - -That way, you can edit the documentation/source files and see the changes live. - -## Tests - -There is a script that you can run locally to test all the code and generate coverage reports in HTML: - -
- -```console -$ bash scripts/test-cov-html.sh -``` - -
- -This command generates a directory `./htmlcov/`, if you open the file `./htmlcov/index.html` in your browser, you can explore interactively the regions of code that are covered by the tests, and notice if there is any region missing. diff --git a/docs/environment-variables.md b/docs/environment-variables.md new file mode 100644 index 0000000000..1cc66b928a --- /dev/null +++ b/docs/environment-variables.md @@ -0,0 +1,304 @@ +# Environment Variables + +Before we jump into **Typer** code, let's cover a bit some of the **basics** that we'll need to understand how to work with Python (and programming) in general. Let's check a bit about **environment variables**. + +/// tip + +If you already know what "environment variables" are and how to use them, feel free to skip this. + +/// + +An environment variable (also known as "**env var**") is a variable that lives **outside** of the Python code, in the **operating system**, and could be read by your Python code (or by other programs as well). + +Environment variables could be useful for handling application **settings**, as part of the **installation** of Python, etc. + +## Create and Use Env Vars + +You can **create** and use environment variables in the **shell (terminal)**, without needing Python: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +// You could create an env var MY_NAME with +$ export MY_NAME="Wade Wilson" + +// Then you could use it with other programs, like +$ echo "Hello $MY_NAME" + +Hello Wade Wilson +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +// Create an env var MY_NAME +$ $Env:MY_NAME = "Wade Wilson" + +// Use it with other programs, like +$ echo "Hello $Env:MY_NAME" + +Hello Wade Wilson +``` + +
+ +//// + +## Read env vars in Python + +You could also create environment variables **outside** of Python, in the terminal (or with any other method), and then **read them in Python**. + +For example you could have a file `main.py` with: + +```Python hl_lines="3" +import os + +name = os.getenv("MY_NAME", "World") +print(f"Hello {name} from Python") +``` + +/// tip + +The second argument to `os.getenv()` is the default value to return. + +If not provided, it's `None` by default, here we provide `"World"` as the default value to use. + +/// + +Then you could call that Python program: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +// Here we don't set the env var yet +$ python main.py + +// As we didn't set the env var, we get the default value + +Hello World from Python + +// But if we create an environment variable first +$ export MY_NAME="Wade Wilson" + +// And then call the program again +$ python main.py + +// Now it can read the environment variable + +Hello Wade Wilson from Python +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +// Here we don't set the env var yet +$ python main.py + +// As we didn't set the env var, we get the default value + +Hello World from Python + +// But if we create an environment variable first +$ $Env:MY_NAME = "Wade Wilson" + +// And then call the program again +$ python main.py + +// Now it can read the environment variable + +Hello Wade Wilson from Python +``` + +
+ +//// + +As environment variables can be set outside of the code, but can be read by the code, and don't have to be stored (committed to `git`) with the rest of the files, it's common to use them for configurations or **settings**. + +You can also create an environment variable only for a **specific program invocation**, that is only available to that program, and only for its duration. + +To do that, create it right before the program itself, on the same line: + +
+ +```console +// Create an env var MY_NAME in line for this program call +$ MY_NAME="Wade Wilson" python main.py + +// Now it can read the environment variable + +Hello Wade Wilson from Python + +// The env var no longer exists afterwards +$ python main.py + +Hello World from Python +``` + +
+ +/// tip + +You can read more about it at The Twelve-Factor App: Config. + +/// + +## Types and Validation + +These environment variables can only handle **text strings**, as they are external to Python and have to be compatible with other programs and the rest of the system (and even with different operating systems, as Linux, Windows, macOS). + +That means that **any value** read in Python from an environment variable **will be a `str`**, and any conversion to a different type or any validation has to be done in code. + +You will learn more about using environment variables for your CLI applications later in the section about [CLI Arguments with Environment Variables](./tutorial/arguments/envvar.md){.internal-link target=_blank}. + +## `PATH` Environment Variable + +There is a **special** environment variable called **`PATH`** that is used by the operating systems (Linux, macOS, Windows) to find programs to run. + +The value of the variable `PATH` is a long string that is made of directories separated by a colon `:` on Linux and macOS, and by a semicolon `;` on Windows. + +For example, the `PATH` environment variable could look like this: + +//// tab | Linux, macOS + +```plaintext +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +This means that the system should look for programs in the directories: + +* `/usr/local/bin` +* `/usr/bin` +* `/bin` +* `/usr/sbin` +* `/sbin` + +//// + +//// tab | Windows + +```plaintext +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32 +``` + +This means that the system should look for programs in the directories: + +* `C:\Program Files\Python312\Scripts` +* `C:\Program Files\Python312` +* `C:\Windows\System32` + +//// + +When you type a **command** in the terminal, the operating system **looks for** the program in **each of those directories** listed in the `PATH` environment variable. + +For example, when you type `python` in the terminal, the operating system looks for a program called `python` in the **first directory** in that list. + +If it finds it, then it will **use it**. Otherwise it keeps looking in the **other directories**. + +### Installing Python and Updating the `PATH` + +When you install Python, you might be asked if you want to update the `PATH` environment variable. + +//// tab | Linux, macOS + +Let's say you install Python and it ends up in a directory `/opt/custompython/bin`. + +If you say yes to update the `PATH` environment variable, then the installer will add `/opt/custompython/bin` to the `PATH` environment variable. + +It could look like this: + +```plaintext +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin +``` + +This way, when you type `python` in the terminal, the system will find the Python program in `/opt/custompython/bin` (the last directory) and use that one. + +//// + +//// tab | Windows + +Let's say you install Python and it ends up in a directory `C:\opt\custompython\bin`. + +If you say yes to update the `PATH` environment variable, then the installer will add `C:\opt\custompython\bin` to the `PATH` environment variable. + +```plaintext +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin +``` + +This way, when you type `python` in the terminal, the system will find the Python program in `C:\opt\custompython\bin` (the last directory) and use that one. + +//// + +This way, when you type `python` in the terminal, the system will find the Python program in `/opt/custompython/bin` (the last directory) and use that one. + +So, if you type: + +
+ +```console +$ python +``` + +
+ +//// tab | Linux, macOS + +The system will **find** the `python` program in `/opt/custompython/bin` and run it. + +It would be roughly equivalent to typing: + +
+ +```console +$ /opt/custompython/bin/python +``` + +
+ +//// + +//// tab | Windows + +The system will **find** the `python` program in `C:\opt\custompython\bin\python` and run it. + +It would be roughly equivalent to typing: + +
+ +```console +$ C:\opt\custompython\bin\python +``` + +
+ +//// + +This information will be useful when learning about [Virtual Environments](virtual-environments.md){.internal-link target=_blank}. + +It will also be useful when you **create your own CLI programs** as, for them to be available for your users, they will need to be somewhere in the `PATH` environment variable. + +## Conclusion + +With this you should have a basic understanding of what **environment variables** are and how to use them in Python. + +You can also read more about them in the Wikipedia for Environment Variable. + +In many cases it's not very obvious how environment variables would be useful and applicable right away. But they keep showing up in many different scenarios when you are developing, so it's good to know about them. + +For example, you will need this information in the next section, about [Virtual Environments](virtual-environments.md). diff --git a/docs/index.md b/docs/index.md index 9996f64800..355380729a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -52,6 +52,8 @@ The key features are: ## Installation +Create and activate a virtual environment and then install **Typer**: +
```console diff --git a/docs/tutorial/arguments/envvar.md b/docs/tutorial/arguments/envvar.md index 970d1ee92a..a53d42685e 100644 --- a/docs/tutorial/arguments/envvar.md +++ b/docs/tutorial/arguments/envvar.md @@ -2,6 +2,12 @@ You can also configure a *CLI argument* to read a value from an environment variable if it is not provided in the command line as a *CLI argument*. +/// tip + +You can learn more about environment variables in the [Environment Variables](../../environment-variables.md){.internal-link target=_blank} page. + +/// + To do that, use the `envvar` parameter for `typer.Argument()`: //// tab | Python 3.7+ diff --git a/docs/tutorial/index.md b/docs/tutorial/index.md index c8ea8a1cb5..bad806c88a 100644 --- a/docs/tutorial/index.md +++ b/docs/tutorial/index.md @@ -6,7 +6,7 @@ It covers everything you need to know from the **simplest scripts** to **complex You could consider this a **book**, a **course**, the **official** and recommended way to learn **Typer**. ๐Ÿ˜Ž -## Python types +## Python Types If you need a refresher about how to use Python type hints, check the first part of FastAPI's Python types intro. @@ -31,17 +31,15 @@ These type hints are what give you autocomplete in your editor and several other **Typer** is based on these type hints. -## Intro +## About this Tutorial This tutorial shows you how to use **Typer** with all its features, step by step. Each section gradually builds on the previous ones, but it's structured to separate topics, so that you can go directly to any specific one to solve your specific CLI needs. -It is also built to work as a future reference. +It is also built to work as a future reference so you can come back and see exactly what you need. -So you can come back and see exactly what you need. - -## Run the code +## Run the Code All the code blocks can be copied and used directly (they are tested Python files). @@ -59,42 +57,8 @@ $ python main.py It is **HIGHLY encouraged** that you write or copy the code, edit it and run it locally. -Using it in your editor is what really shows you the benefits of **Typer**, seeing how little code you have to write, all the type checks, autocompletion, etc. - -And running the examples is what will really help you understand what is going on. - -You can learn a lot more by running some examples and playing around with them than by reading all the docs here. - ---- - -## Install **Typer** - -The first step is to install **Typer**: - -
- -```console -$ pip install typer ----> 100% -Successfully installed typer click shellingham rich -``` - -
- -By default, `typer` comes with `rich` and `shellingham`. - -/// note +Using it in your editor is what really shows you the benefits of **Typer**, seeing how little code you have to write, all the **inline errors**, **autocompletion**, etc. -If you are an advanced user and want to opt out of these default extra dependencies, you can instead install `typer-slim`. - -```bash -pip install typer -``` - -...includes the same optional dependencies as: - -```bash -pip install "typer-slim[standard]" -``` +And running the examples is what will really help you **understand** what is going on. -/// +You can learn a lot more by **running some examples** and **playing around** with them than by reading all the docs here. diff --git a/docs/tutorial/install.md b/docs/tutorial/install.md new file mode 100644 index 0000000000..09ee0cfc2c --- /dev/null +++ b/docs/tutorial/install.md @@ -0,0 +1,33 @@ +# Install **Typer** + +The first step is to install **Typer**. + +First, make sure you create your [virtual environment](../virtual-environments.md){.internal-link target=_blank}, activate it, and then install it, for example with: + +
+ +```console +$ pip install typer +---> 100% +Successfully installed typer click shellingham rich +``` + +
+ +By default, `typer` comes with `rich` and `shellingham`. + +/// note + +If you are an advanced user and want to opt out of these default extra dependencies, you can instead install `typer-slim`. + +```bash +pip install typer +``` + +...includes the same optional dependencies as: + +```bash +pip install "typer-slim[standard]" +``` + +/// diff --git a/docs/virtual-environments.md b/docs/virtual-environments.md new file mode 100644 index 0000000000..5a8ffd4013 --- /dev/null +++ b/docs/virtual-environments.md @@ -0,0 +1,844 @@ +# Virtual Environments + +When you work in Python projects you probably should use a **virtual environment** (or a similar mechanism) to isolate the packages you install for each project. + +/// info + +If you already know about virtual environments, how to create them and use them, you might want to skip this section. ๐Ÿค“ + +/// + +/// tip + +A **virtual environment** is different than an **environment variable**. + +An **environment variable** is a variable in the system that can be used by programs. + +A **virtual environment** is a directory with some files in it. + +/// + +/// info + +This page will teach you how to use **virtual environments** and how they work. + +If you are ready to adopt a **tool that manages everything** for you (including installing Python), try uv. + +/// + +## Create a Project + +First, create a directory for your project. + +What I normally do is that I create a directory named `code` inside my home/user directory. + +And inside of that I create one directory per project. + +
+ +```console +// Go to the home directory +$ cd +// Create a directory for all your code projects +$ mkdir code +// Enter into that code directory +$ cd code +// Create a directory for this project +$ mkdir awesome-project +// Enter into that project directory +$ cd awesome-project +``` + +
+ +## Create a Virtual Environment + +When you start working on a Python project **for the first time**, create a virtual environment **inside your project**. + +/// tip + +You only need to do this **once per project**, not every time you work. + +/// + +//// tab | `venv` + +To create a virtual environment, you can use the `venv` module that comes with Python. + +
+ +```console +$ python -m venv .venv +``` + +
+ +/// details | What that command means + +* `python`: use the program called `python` +* `-m`: call a module as a script, we'll tell it which module next +* `venv`: use the module called `venv` that normally comes installed with Python +* `.venv`: create the virtual environment in the new directory `.venv` + +/// + +//// + +//// tab | `uv` + +If you have `uv` installed, you can use it to create a virtual environment. + +
+ +```console +$ uv venv +``` + +
+ +/// tip + +By default, `uv` will create a virtual environment in a directory called `.venv`. + +But you could customize it passing an additional argument with the directory name. + +/// + +//// + +That command creates a new virtual environment in a directory called `.venv`. + +/// details | `.venv` or other name + +You could create the virtual environment in a different directory, but there's a convention of calling it `.venv`. + +/// + +## Activate the Virtual Environment + +Activate the new virtual environment so that any Python command you run or package you install uses it. + +/// tip + +Do this **every time** you start a **new terminal session** to work on the project. + +/// + +//// tab | Linux, macOS + +
+ +```console +$ source .venv/bin/activate +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ .venv\Scripts\Activate.ps1 +``` + +
+ +//// + +//// tab | Windows Bash + +Or if you use Bash for Windows (e.g. Git Bash): + +
+ +```console +$ source .venv/Scripts/activate +``` + +
+ +//// + +/// tip + +Every time you install a **new package** in that environment, **activate** the environment again. + +This makes sure that if you use a **terminal (CLI) program** installed by that package, you use the one from your virtual environment and not any other that could be installed globally, probably with a different version than what you need. + +/// + +## Check the Virtual Environment is Active + +Check that the virtual environment is active (the previous command worked). + +/// tip + +This is **optional**, but it's a good way to **check** that everything is working as expected and you are using the virtual environment you intended. + +/// + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +$ which python + +/home/user/code/awesome-project/.venv/bin/python +``` + +
+ +If it shows the `python` binary at `.venv/bin/python`, inside of your project (in this case `awesome-project`), then it worked. ๐ŸŽ‰ + +//// + +//// tab | Windows PowerShell + +
+ +```console +$ Get-Command python + +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +
+ +If it shows the `python` binary at `.venv\Scripts\python`, inside of your project (in this case `awesome-project`), then it worked. ๐ŸŽ‰ + +//// + +## Upgrade `pip` + +/// tip + +If you use `uv` you would use it to install things instead of `pip`, so you don't need to upgrade `pip`. ๐Ÿ˜Ž + +/// + +If you are using `pip` to install packages (it comes by default with Python), you should **upgrade** it to the latest version. + +Many exotic errors while installing a package are solved by just upgrading `pip` first. + +/// tip + +You would normally do this **once**, right after you create the virtual environment. + +/// + +Make sure the virtual environment is active (with the command above) and then run: + +
+ +```console +$ python -m pip install --upgrade pip + +---> 100% +``` + +
+ +## Add `.gitignore` + +If you are using **Git** (you should), add a `.gitignore` file to exclude everything in your `.venv` from Git. + +/// tip + +If you used `uv` to create the virtual environment, it already did this for you, you can skip this step. ๐Ÿ˜Ž + +/// + +/// tip + +Do this **once**, right after you create the virtual environment. + +/// + +
+ +```console +$ echo "*" > .venv/.gitignore +``` + +
+ +/// details | What that command means + +* `echo "*"`: will "print" the text `*` in the terminal (the next part changes that a bit) +* `>`: anything printed to the terminal by the command to the left of `>` should not be printed but instead written to the file that goes to the right of `>` +* `.gitignore`: the name of the file where the text should be written + +And `*` for Git means "everything". So, it will ignore everything in the `.venv` directory. + +That command will create a file `.gitignore` with the content: + +```gitignore +* +``` + +/// + +## Install Packages + +After activating the environment, you can install packages in it. + +/// tip + +Do this **once** when installing or upgrading the packages your project needs. + +If you need to upgrade a version or add a new package you would **do this again**. + +/// + +### Install Packages Directly + +If you're in a hurry and don't want to use a file to declare your project's package requirements, you can install them directly. + +/// tip + +It's a (very) good idea to put the packages and versions your program needs in a file (for example `requirements.txt` or `pyproject.toml`). + +/// + +//// tab | `pip` + +
+ +```console +$ pip install typer + +---> 100% +``` + +
+ +//// + +//// tab | `uv` + +If you have `uv`: + +
+ +```console +$ uv pip install typer +---> 100% +``` + +
+ +//// + +### Install from `requirements.txt` + +If you have a `requirements.txt`, you can now use it to install its packages. + +//// tab | `pip` + +
+ +```console +$ pip install -r requirements.txt +---> 100% +``` + +
+ +//// + +//// tab | `uv` + +If you have `uv`: + +
+ +```console +$ uv pip install -r requirements.txt +---> 100% +``` + +
+ +//// + +/// details | `requirements.txt` + +A `requirements.txt` with some packages could look like: + +```requirements.txt +typer==0.13.0 +rich==13.7.1 +``` + +/// + +## Run Your Program + +After you activated the virtual environment, you can run your program, and it will use the Python inside of your virtual environment with the packages you installed there. + +
+ +```console +$ python main.py + +Hello World +``` + +
+ +## Configure Your Editor + +You would probably use an editor, make sure you configure it to use the same virtual environment you created (it will probably autodetect it) so that you can get autocompletion and inline errors. + +For example: + +* VS Code +* PyCharm + +/// tip + +You normally have to do this only **once**, when you create the virtual environment. + +/// + +## Deactivate the Virtual Environment + +Once you are done working on your project you can **deactivate** the virtual environment. + +
+ +```console +$ deactivate +``` + +
+ +This way, when you run `python` it won't try to run it from that virtual environment with the packages installed there. + +## Ready to Work + +Now you're ready to start working on your project. + + + +/// tip + +Do you want to understand what's all that above? + +Continue reading. ๐Ÿ‘‡๐Ÿค“ + +/// + +## Why Virtual Environments + +To work with Typer you need to install Python. + +After that, you would need to **install** Typer and any other **packages** you want to use. + +To install packages you would normally use the `pip` command that comes with Python (or similar alternatives). + +Nevertheless, if you just use `pip` directly, the packages would be installed in your **global Python environment** (the global installation of Python). + +### The Problem + +So, what's the problem with installing packages in the global Python environment? + +At some point, you will probably end up writing many different programs that depend on **different packages**. And some of these projects you work on will depend on **different versions** of the same package. ๐Ÿ˜ฑ + +For example, you could create a project called `philosophers-stone`, this program depends on another package called **`harry`, using the version `1`**. So, you need to install `harry`. + +```mermaid +flowchart LR + stone(philosophers-stone) -->|requires| harry-1[harry v1] +``` + +Then, at some point later, you create another project called `prisoner-of-azkaban`, and this project also depends on `harry`, but this project needs **`harry` version `3`**. + +```mermaid +flowchart LR + azkaban(prisoner-of-azkaban) --> |requires| harry-3[harry v3] +``` + +But now the problem is, if you install the packages globally (in the global environment) instead of in a local **virtual environment**, you will have to choose which version of `harry` to install. + +If you want to run `philosophers-stone` you will need to first install `harry` version `1`, for example with: + +
+ +```console +$ pip install "harry==1" +``` + +
+ +And then you would end up with `harry` version `1` installed in your global Python environment. + +```mermaid +flowchart LR + subgraph global[global env] + harry-1[harry v1] + end + subgraph stone-project[philosophers-stone project] + stone(philosophers-stone) -->|requires| harry-1 + end +``` + +But then if you want to run `prisoner-of-azkaban`, you will need to uninstall `harry` version `1` and install `harry` version `3` (or just installing version `3` would automatically uninstall version `1`). + +
+ +```console +$ pip install "harry==3" +``` + +
+ +And then you would end up with `harry` version `3` installed in your global Python environment. + +And if you try to run `philosophers-stone` again, there's a chance it would **not work** because it needs `harry` version `1`. + +```mermaid +flowchart LR + subgraph global[global env] + harry-1[harry v1] + style harry-1 fill:#ccc,stroke-dasharray: 5 5 + harry-3[harry v3] + end + subgraph stone-project[philosophers-stone project] + stone(philosophers-stone) -.-x|โ›”๏ธ| harry-1 + end + subgraph azkaban-project[prisoner-of-azkaban project] + azkaban(prisoner-of-azkaban) --> |requires| harry-3 + end +``` + +/// tip + +It's very common in Python packages to try the best to **avoid breaking changes** in **new versions**, but it's better to be safe, and install newer versions intentionally and when you can run the tests to check everything is working correctly. + +/// + +Now, imagine that with **many** other **packages** that all your **projects depend on**. That's very difficult to manage. And you would probably end up running some projects with some **incompatible versions** of the packages, and not knowing why something isn't working. + +Also, depending on your operating system (e.g. Linux, Windows, macOS), it could have come with Python already installed. And in that case it probably had some packages pre-installed with some specific versions **needed by your system**. If you install packages in the global Python environment, you could end up **breaking** some of the programs that came with your operating system. + +## Where are Packages Installed + +When you install Python, it creates some directories with some files in your computer. + +Some of these directories are the ones in charge of having all the packages you install. + +When you run: + +
+ +```console +// Don't run this now, it's just an example ๐Ÿค“ +$ pip install typer +---> 100% +``` + +
+ +That will download a compressed file with the Typer code, normally from PyPI. + +It will also **download** files for other packages that Typer depends on. + +Then it will **extract** all those files and put them in a directory in your computer. + +By default, it will put those files downloaded and extracted in the directory that comes with your Python installation, that's the **global environment**. + +## What are Virtual Environments + +The solution to the problems of having all the packages in the global environment is to use a **virtual environment for each project** you work on. + +A virtual environment is a **directory**, very similar to the global one, where you can install the packages for a project. + +This way, each project will have it's own virtual environment (`.venv` directory) with its own packages. + +```mermaid +flowchart TB + subgraph stone-project[philosophers-stone project] + stone(philosophers-stone) --->|requires| harry-1 + subgraph venv1[.venv] + harry-1[harry v1] + end + end + subgraph azkaban-project[prisoner-of-azkaban project] + azkaban(prisoner-of-azkaban) --->|requires| harry-3 + subgraph venv2[.venv] + harry-3[harry v3] + end + end + stone-project ~~~ azkaban-project +``` + +## What Does Activating a Virtual Environment Mean + +When you activate a virtual environment, for example with: + +//// tab | Linux, macOS + +
+ +```console +$ source .venv/bin/activate +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ .venv\Scripts\Activate.ps1 +``` + +
+ +//// + +//// tab | Windows Bash + +Or if you use Bash for Windows (e.g. Git Bash): + +
+ +```console +$ source .venv/Scripts/activate +``` + +
+ +//// + +That command will create or modify some [environment variables](environment-variables.md){.internal-link target=_blank} that will be available for the next commands. + +One of those variables is the `PATH` variable. + +/// tip + +You can learn more about the `PATH` environment variable in the [Environment Variables](environment-variables.md#path-environment-variable){.internal-link target=_blank} section. + +/// + +Activating a virtual environment adds its path `.venv/bin` (on Linux and macOS) or `.venv\Scripts` (on Windows) to the `PATH` environment variable. + +Let's say that before activating the environment, the `PATH` variable looked like this: + +//// tab | Linux, macOS + +```plaintext +/usr/bin:/bin:/usr/sbin:/sbin +``` + +That means that the system would look for programs in: + +* `/usr/bin` +* `/bin` +* `/usr/sbin` +* `/sbin` + +//// + +//// tab | Windows + +```plaintext +C:\Windows\System32 +``` + +That means that the system would look for programs in: + +* `C:\Windows\System32` + +//// + +After activating the virtual environment, the `PATH` variable would look something like this: + +//// tab | Linux, macOS + +```plaintext +/home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +That means that the system will now start looking first look for programs in: + +```plaintext +/home/user/code/awesome-project/.venv/bin +``` + +before looking in the other directories. + +So, when you type `python` in the terminal, the system will find the Python program in + +```plaintext +/home/user/code/awesome-project/.venv/bin/python +``` + +and use that one. + +//// + +//// tab | Windows + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32 +``` + +That means that the system will now start looking first look for programs in: + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts +``` + +before looking in the other directories. + +So, when you type `python` in the terminal, the system will find the Python program in + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +and use that one. + +//// + +An important detail is that it will put the virtual environment path at the **beginning** of the `PATH` variable. The system will find it **before** finding any other Python available. This way, when you run `python`, it will use the Python **from the virtual environment** instead of any other `python` (for example, a `python` from a global environment). + +Activating a virtual environment also changes a couple of other things, but this is one of the most important things it does. + +## Checking a Virtual Environment + +When you check if a virtual environment is active, for example with: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +$ which python + +/home/user/code/awesome-project/.venv/bin/python +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ Get-Command python + +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +
+ +//// + +That means that the `python` program that will be used is the one **in the virtual environment**. + +you use `which` in Linux and macOS and `Get-Command` in Windows PowerShell. + +The way that command works is that it will go and check in the `PATH` environment variable, going through **each path in order**, looking for the program called `python`. Once it finds it, it will **show you the path** to that program. + +The most important part is that when you call `python`, that is the exact "`python`" that will be executed. + +So, you can confirm if you are in the correct virtual environment. + +/// tip + +It's easy to activate one virtual environment, get one Python, and then **go to another project**. + +And the second project **wouldn't work** because you are using the **incorrect Python**, from a virtual environment for another project. + +It's useful being able to check what `python` is being used. ๐Ÿค“ + +/// + +## Why Deactivate a Virtual Environment + +For example, you could be working on a project `philosophers-stone`, **activate that virtual environment**, install packages and work with that environment. + +And then you want to work on **another project** `prisoner-of-azkaban`. + +You go to that project: + +
+ +```console +$ cd ~/code/prisoner-of-azkaban +``` + +
+ +If you don't deactivate the virtual environment for `philosophers-stone`, when you run `python` in the terminal, it will try to use the Python from `philosophers-stone`. + +
+ +```console +$ cd ~/code/prisoner-of-azkaban + +$ python main.py + +// Error importing sirius, it's not installed ๐Ÿ˜ฑ +Traceback (most recent call last): + File "main.py", line 1, in + import sirius +``` + +
+ +But if you deactivate the virtual environment and activate the new one for `prisoner-of-askaban` then when you run `python` it will use the Python from the virtual environment in `prisoner-of-azkaban`. + +
+ +```console +$ cd ~/code/prisoner-of-azkaban + +// You don't need to be in the old directory to deactivate, you can do it wherever you are, even after going to the other project ๐Ÿ˜Ž +$ deactivate + +// Activate the virtual environment in prisoner-of-azkaban/.venv ๐Ÿš€ +$ source .venv/bin/activate + +// Now when you run python, it will find the package sirius installed in this virtual environment โœจ +$ python main.py + +I solemnly swear ๐Ÿบ +``` + +
+ +## Alternatives + +This is a simple guide to get you started and teach you how everything works **underneath**. + +There are many **alternatives** to managing virtual environments, package dependencies (requirements), projects. + +Once you are ready and want to use a tool to **manage the entire project**, packages dependencies, virtual environments, etc. I would suggest you try uv. + +`uv` can do a lot of things, it can: + +* **Install Python** for you, including different versions +* Manage the **virtual environment** for your projects +* Install **packages** +* Manage package **dependencies and versions** for your project +* Make sure you have an **exact** set of packages and versions to install, including their dependencies, so that you can be sure that you can run your project in production exactly the same as in your computer while developing, this is called **locking** +* And many other things + +## Conclusion + +If you read and understood all this, now **you know much more** about virtual environments than many developers out there. ๐Ÿค“ + +Knowing these details will most probably be useful in a future time when you are debugging something that seems complex, but you will know **how it all works underneath**. ๐Ÿ˜Ž diff --git a/mkdocs.yml b/mkdocs.yml index cf15b162d9..ead95508a0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -72,6 +72,9 @@ nav: - features.md - Tutorial - User Guide: - tutorial/index.md + - environment-variables.md + - virtual-environments.md + - tutorial/install.md - tutorial/first-steps.md - tutorial/printing.md - tutorial/terminating.md From 20643c31fecdb1dfdf8c0a44e9dbaf987f716e12 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 23 Aug 2024 17:57:14 +0000 Subject: [PATCH 02/47] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index e16bf5488c..2ea245d7f5 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,6 +2,10 @@ ## Latest Changes +### Docs + +* ๐Ÿ“ Add docs for virtual environments, environment variables, and update contributing. PR [#946](https://github.com/fastapi/typer/pull/946) by [@tiangolo](https://github.com/tiangolo). + ### Internal * ๐Ÿ™ˆ Remove extra line in .gitignore. PR [#936](https://github.com/fastapi/typer/pull/936) by [@tiangolo](https://github.com/tiangolo). From cb196cf1027fad0336d67f20f8840da71d8b5cf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 23 Aug 2024 14:10:39 -0500 Subject: [PATCH 03/47] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Fix=20typo=20in=20`d?= =?UTF-8?q?ocs/contributing.md`=20(#947)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/contributing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contributing.md b/docs/contributing.md index bcb2a78960..047fec98a5 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -8,7 +8,7 @@ If you already cloned the +``` + +And you should see the completion working: + +```console +run -- Run the provided Typer app. +utils -- Extra utility commands for Typer apps. +``` + +And the same for the commands in your `demo.py` file: + +```console +$ typer demo.py run + +hello goodbye +``` + +You can also check the configuration file using `nano` or `vim`, for example: + +```bash +nano ~/.zshrc +``` + +It will show some content like: + +```bash +fpath+=~/.zfunc; autoload -Uz compinit; compinit + + +zstyle ':completion:*' menu select +``` + +If you exit from the container, you can start a new one, you will probably have to install the packages again and install completion again. + +Using this process, you can test all the shells, with their completions, being able to start from scratch quickly in a fresh container, and verifying that everything works as expected. + ## Docs First, make sure you set up your environment as described above, that will install all the requirements. diff --git a/scripts/docker/Dockerfile b/scripts/docker/Dockerfile new file mode 100644 index 0000000000..738867a954 --- /dev/null +++ b/scripts/docker/Dockerfile @@ -0,0 +1,22 @@ +FROM python:latest + +# Add Fish +RUN echo 'deb http://download.opensuse.org/repositories/shells:/fish:/release:/3/Debian_12/ /' | tee /etc/apt/sources.list.d/shells:fish:release:3.list +RUN curl -fsSL https://download.opensuse.org/repositories/shells:fish:release:3/Debian_12/Release.key | gpg --dearmor | tee /etc/apt/trusted.gpg.d/shells_fish_release_3.gpg > /dev/null + +# Install packages including Fish, Zsh, PowerShell +RUN apt-get update && apt-get install -y \ + wget \ + apt-transport-https \ + software-properties-common \ + nano \ + vim \ + fish \ + zsh \ + && wget https://github.com/PowerShell/PowerShell/releases/download/v7.4.4/powershell_7.4.4-1.deb_amd64.deb \ + && dpkg -i powershell_7.4.4-1.deb_amd64.deb + +# Install uv +COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv + +ENV UV_SYSTEM_PYTHON=1 diff --git a/scripts/docker/compose.yaml b/scripts/docker/compose.yaml new file mode 100644 index 0000000000..0f9d960d34 --- /dev/null +++ b/scripts/docker/compose.yaml @@ -0,0 +1,7 @@ +services: + typer: + build: . + volumes: + - ../../:/code + working_dir: /code + command: sleep infinity From 3b8199739841ea5f213b4d180d14cf1c8ebb56a4 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 24 Aug 2024 14:38:15 +0000 Subject: [PATCH 10/47] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 8240dfdce9..bfe03d8add 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -4,6 +4,7 @@ ### Docs +* ๐Ÿ“ Add docs and scripts to test completion in different shells. PR [#953](https://github.com/fastapi/typer/pull/953) by [@tiangolo](https://github.com/tiangolo). * โœ๏ธ Fix a typo in `docs/virtual-environments.md`. PR [#952](https://github.com/fastapi/typer/pull/952) by [@tiangolo](https://github.com/tiangolo). * โœ๏ธ Fix typo in `docs/contributing.md`. PR [#947](https://github.com/fastapi/typer/pull/947) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ“ Add docs for virtual environments, environment variables, and update contributing. PR [#946](https://github.com/fastapi/typer/pull/946) by [@tiangolo](https://github.com/tiangolo). From 7e944ac68d157e4d3da9637d3d26b94fb7bdd853 Mon Sep 17 00:00:00 2001 From: Nick Date: Sat, 24 Aug 2024 21:09:20 +0100 Subject: [PATCH 11/47] =?UTF-8?q?=F0=9F=93=9D=20Update=20the=20Progress=20?= =?UTF-8?q?Bar=20tutorial=20with=20correct=20output=20(#199)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: svlandeg --- docs/tutorial/progressbar.md | 2 +- docs_src/progressbar/tutorial006.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/tutorial/progressbar.md b/docs/tutorial/progressbar.md index 250ea49382..c56cf50b29 100644 --- a/docs/tutorial/progressbar.md +++ b/docs/tutorial/progressbar.md @@ -246,5 +246,5 @@ Check it:
python main.py -Processed 100 things in batches. +Processed 1000 things in batches.
diff --git a/docs_src/progressbar/tutorial006.py b/docs_src/progressbar/tutorial006.py index ac94a3ed3e..d83b0da7cb 100644 --- a/docs_src/progressbar/tutorial006.py +++ b/docs_src/progressbar/tutorial006.py @@ -9,6 +9,8 @@ def main(): for batch in range(4): # Fake processing time time.sleep(1) + # Increment by 250 on each loop iteration + # (it will take 4 seconds to reach 1000) progress.update(250) print(f"Processed {total} things in batches.") From b98d0fecb9f8bdba81b1dabd2da2034948f4de8a Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 24 Aug 2024 20:09:40 +0000 Subject: [PATCH 12/47] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index bfe03d8add..e1df216ffd 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -4,6 +4,7 @@ ### Docs +* ๐Ÿ“ Update the Progress Bar tutorial with correct output. PR [#199](https://github.com/fastapi/typer/pull/199) by [@n1ckdm](https://github.com/n1ckdm). * ๐Ÿ“ Add docs and scripts to test completion in different shells. PR [#953](https://github.com/fastapi/typer/pull/953) by [@tiangolo](https://github.com/tiangolo). * โœ๏ธ Fix a typo in `docs/virtual-environments.md`. PR [#952](https://github.com/fastapi/typer/pull/952) by [@tiangolo](https://github.com/tiangolo). * โœ๏ธ Fix typo in `docs/contributing.md`. PR [#947](https://github.com/fastapi/typer/pull/947) by [@tiangolo](https://github.com/tiangolo). From 846dc41ffc4f6835851411ce40be5e3d8489868e Mon Sep 17 00:00:00 2001 From: Chris Burr Date: Sat, 24 Aug 2024 22:10:55 +0200 Subject: [PATCH 13/47] =?UTF-8?q?=F0=9F=9A=B8=20Improve=20assertion=20erro?= =?UTF-8?q?r=20message=20if=20a=20group=20is=20not=20a=20valid=20subclass?= =?UTF-8?q?=20(#425)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastiรกn Ramรญrez Co-authored-by: svlandeg --- typer/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typer/main.py b/typer/main.py index f7d634d8bc..8d915527cd 100644 --- a/typer/main.py +++ b/typer/main.py @@ -505,7 +505,7 @@ def get_group_from_info( context_param_name, ) = get_params_convertors_ctx_param_name_from_function(solved_info.callback) cls = solved_info.cls or TyperGroup - assert issubclass(cls, TyperGroup) + assert issubclass(cls, TyperGroup), f"{cls} should be a subclass of {TyperGroup}" group = cls( name=solved_info.name or "", commands=commands, From a1e80f396fe921b611d8e613e1402ed9d6d5ee7b Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 24 Aug 2024 20:11:41 +0000 Subject: [PATCH 14/47] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index e1df216ffd..cc173956e7 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,6 +2,10 @@ ## Latest Changes +### Features + +* ๐Ÿšธ Improve assertion error message if a group is not a valid subclass. PR [#425](https://github.com/fastapi/typer/pull/425) by [@chrisburr](https://github.com/chrisburr). + ### Docs * ๐Ÿ“ Update the Progress Bar tutorial with correct output. PR [#199](https://github.com/fastapi/typer/pull/199) by [@n1ckdm](https://github.com/n1ckdm). From 1da8e8691e0103b8e411d013371c39e984303a06 Mon Sep 17 00:00:00 2001 From: Kinuax Date: Sat, 24 Aug 2024 22:14:02 +0200 Subject: [PATCH 15/47] =?UTF-8?q?=F0=9F=93=9D=20Update=20package=20docs=20?= =?UTF-8?q?with=20the=20latest=20versions=20of=20Typer=20and=20Poetry=20(#?= =?UTF-8?q?781)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: svlandeg --- docs/tutorial/package.md | 99 +++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 56 deletions(-) diff --git a/docs/tutorial/package.md b/docs/tutorial/package.md index cb04fd6d2c..617c93cd0b 100644 --- a/docs/tutorial/package.md +++ b/docs/tutorial/package.md @@ -49,41 +49,34 @@ cd ./rick-portal-gun ## Dependencies and environment -Add `typer[all]` to your dependencies: +Add `typer` to your dependencies:
```console -$ poetry add "typer[all]" +$ poetry add typer // It creates a virtual environment for your project Creating virtualenv rick-portal-gun-w31dJa0b-py3.10 in /home/rick/.cache/pypoetry/virtualenvs -Using version ^0.1.0 for typer +Using version ^0.12.0 for typer Updating dependencies Resolving dependencies... (1.2s) -Writing lock file - ---> 100% -Package operations: 15 installs, 0 updates, 0 removals - - - Installing zipp (3.1.0) - - Installing importlib-metadata (1.5.0) - - Installing pyparsing (2.4.6) - - Installing six (1.14.0) - - Installing attrs (19.3.0) - - Installing click (7.1.1) - - Installing colorama (0.4.3) - - Installing more-itertools (8.2.0) - - Installing packaging (20.3) - - Installing pluggy (0.13.1) - - Installing py (1.8.1) - - Installing shellingham (1.3.2) - - Installing wcwidth (0.1.8) - - Installing pytest (5.4.1) - - Installing typer (0.0.11) +Package operations: 8 installs, 0 updates, 0 removals + + - Installing mdurl (0.1.2) + - Installing markdown-it-py (3.0.0) + - Installing pygments (2.17.2) + - Installing click (8.1.7) + - Installing rich (13.7.1) + - Installing shellingham (1.5.4) + - Installing typing-extensions (4.11.0) + - Installing typer (0.12.3) + +Writing lock file // Activate that new virtual environment $ poetry shell @@ -106,8 +99,7 @@ You can see that you have a generated project structure that looks like: โ”œโ”€โ”€ rick_portal_gun โ”‚ย ย  โ””โ”€โ”€ __init__.py โ””โ”€โ”€ tests - โ”œโ”€โ”€ __init__.py - โ””โ”€โ”€ test_rick_portal_gun.py + โ””โ”€โ”€ __init__.py ``` ## Create your app @@ -183,14 +175,11 @@ rick-portal-gun = "rick_portal_gun.main:app" [tool.poetry.dependencies] python = "^3.10" -typer = {extras = ["all"], version = "^0.1.0"} - -[tool.poetry.dev-dependencies] -pytest = "^5.2" +typer = "^0.12.0" [build-system] -requires = ["poetry>=0.12"] -build-backend = "poetry.masonry.api" +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" ``` Here's what that line means: @@ -239,7 +228,7 @@ Installing dependencies from lock file No dependencies to install or update - - Installing rick-portal-gun (0.1.0) + - Installing the current project: rick-portal-gun (0.1.0) ```
@@ -258,7 +247,7 @@ $ which rick-portal-gun /home/rick/.cache/pypoetry/virtualenvs/rick-portal-gun-w31dJa0b-py3.10/bin/rick-portal-gun // Try it -$ rick-portal-gun +$ rick-portal-gun --help // You get all the standard help Usage: rick-portal-gun [OPTIONS] COMMAND [ARGS]... @@ -292,7 +281,6 @@ $ poetry build Building rick-portal-gun (0.1.0) - Building sdist - Built rick-portal-gun-0.1.0.tar.gz - - Building wheel - Built rick_portal_gun-0.1.0-py3-none-any.whl ``` @@ -320,7 +308,7 @@ Now you can open another terminal and install that package from the file for you
```console -$ pip install --user /home/rock/code/rick-portal-gun/dist/rick_portal_gun-0.1.0-py3-none-any.whl +$ pip install --user /home/rick/rick-portal-gun/dist/rick_portal_gun-0.1.0-py3-none-any.whl ---> 100% ``` @@ -361,7 +349,7 @@ Having it installed globally (and not in a single environment), you can now inst ```console $ rick-portal-gun --install-completion -zsh completion installed in /home/user/.zshrc. +zsh completion installed in /home/rick/.zshrc. Completion will take effect once you restart the terminal. ``` @@ -439,8 +427,7 @@ The file would live right beside `__init__.py`: โ”‚ โ”œโ”€โ”€ __main__.py โ”‚ โ””โ”€โ”€ main.py โ””โ”€โ”€ tests - โ”œโ”€โ”€ __init__.py - โ””โ”€โ”€ test_rick_portal_gun.py + โ””โ”€โ”€ __init__.py ``` No other file has to import it, you don't have to reference it in your `pyproject.toml` or anything else, it just works by default, as it is standard Python behavior. @@ -457,7 +444,7 @@ Now, after installing your package, if you call it with `python -m` it will work
```console -$ python -m rick_portal_gun +$ python -m rick_portal_gun --help Usage: __main__.py [OPTIONS] COMMAND [ARGS]... @@ -513,7 +500,7 @@ You can pass all the arguments and keyword arguments you could pass to a Click a
```console -$ python -m rick_portal_gun +$ python -m rick_portal_gun --help Usage: rick-portal-gun [OPTIONS] COMMAND [ARGS]... @@ -603,7 +590,6 @@ $ poetry publish --build Building rick-portal-gun (0.1.0) - Building sdist - Built rick-portal-gun-0.1.0.tar.gz - - Building wheel - Built rick_portal_gun-0.1.0-py3-none-any.whl @@ -630,10 +616,10 @@ $ pip uninstall rick-portal-gun Found existing installation: rick-portal-gun 0.1.0 Uninstalling rick-portal-gun-0.1.0: Would remove: - /home/user/.local/bin/rick-portal-gun - /home/user/.local/lib/python3.10/site-packages/rick_portal_gun-0.1.0.dist-info/* - /home/user/.local/lib/python3.10/site-packages/rick_portal_gun/* -# Proceed (y/n)? $ y + /home/rick/.local/bin/rick-portal-gun + /home/rick/.local/lib/python3.10/site-packages/rick_portal_gun-0.1.0.dist-info/* + /home/rick/.local/lib/python3.10/site-packages/rick_portal_gun/* +# Proceed (Y/n)? $ Y Successfully uninstalled rick-portal-gun-0.1.0 ``` @@ -648,11 +634,16 @@ $ pip install --user rick-portal-gun // Notice that it says "Downloading" ๐Ÿš€ Collecting rick-portal-gun - Downloading rick_portal_gun-0.1.0-py3-none-any.whl (1.8 kB) -Requirement already satisfied: typer[all]<0.0.12,>=0.0.11 in ./.local/lib/python3.10/site-packages (from rick-portal-gun) (0.0.11) -Requirement already satisfied: click<7.2.0,>=7.1.1 in ./anaconda3/lib/python3.10/site-packages (from typer[all]<0.0.12,>=0.0.11->rick-portal-gun) (7.1.1) -Requirement already satisfied: colorama; extra == "all" in ./anaconda3/lib/python3.10/site-packages (from typer[all]<0.0.12,>=0.0.11->rick-portal-gun) (0.4.3) -Requirement already satisfied: shellingham; extra == "all" in ./anaconda3/lib/python3.10/site-packages (from typer[all]<0.0.12,>=0.0.11->rick-portal-gun) (1.3.1) + Downloading rick_portal_gun-0.1.0-py3-none-any.whl.metadata (435 bytes) +Requirement already satisfied: typer<0.13.0,>=0.12.3 in ./.local/lib/python3.10/site-packages (from rick-portal-gun==0.1.0) (0.12.3) +Requirement already satisfied: typing-extensions>=3.7.4.3 in ./.local/lib/python3.10/site-packages (from typer<0.13.0,>=0.12.3->rick-portal-gun==0.1.0) (4.11.0) +Requirement already satisfied: click>=8.0.0 in ./.local/lib/python3.10/site-packages (from typer<0.13.0,>=0.12.3->rick-portal-gun==0.1.0) (8.1.7) +Requirement already satisfied: shellingham>=1.3.0 in ./.local/lib/python3.10/site-packages (from typer<0.13.0,>=0.12.3->rick-portal-gun==0.1.0) (1.5.4) +Requirement already satisfied: rich>=10.11.0 in ./.local/lib/python3.10/site-packages (from typer<0.13.0,>=0.12.3->rick-portal-gun==0.1.0) (13.7.1) +Requirement already satisfied: pygments<3.0.0,>=2.13.0 in ./.local/lib/python3.10/site-packages (from rich>=10.11.0->typer<0.13.0,>=0.12.3->rick-portal-gun==0.1.0) (2.17.2) +Requirement already satisfied: markdown-it-py>=2.2.0 in ./.local/lib/python3.10/site-packages (from rich>=10.11.0->typer<0.13.0,>=0.12.3->rick-portal-gun==0.1.0) (3.0.0) +Requirement already satisfied: mdurl~=0.1 in ./.local/lib/python3.10/site-packages (from markdown-it-py>=2.2.0->rich>=10.11.0->typer<0.13.0,>=0.12.3->rick-portal-gun==0.1.0) (0.1.2) +Downloading rick_portal_gun-0.1.0-py3-none-any.whl (1.8 kB) Installing collected packages: rick-portal-gun Successfully installed rick-portal-gun-0.1.0 ``` @@ -715,14 +706,11 @@ rick-portal-gun = "rick_portal_gun.main:app" [tool.poetry.dependencies] python = "^3.10" -typer = {extras = ["all"], version = "^0.1.0"} - -[tool.poetry.dev-dependencies] -pytest = "^5.2" +typer = "^0.12.0" [build-system] -requires = ["poetry>=0.12"] -build-backend = "poetry.masonry.api" +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" ``` And in the file `rick_portal_gun/__init__.py`: @@ -743,7 +731,6 @@ $ poetry publish --build Building rick-portal-gun (0.2.0) - Building sdist - Built rick-portal-gun-0.2.0.tar.gz - - Building wheel - Built rick_portal_gun-0.2.0-py3-none-any.whl From 9c8b5f0663d8423a49853aa9118483e95c2c7438 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 24 Aug 2024 20:14:21 +0000 Subject: [PATCH 16/47] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index cc173956e7..1b30a8633b 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -8,6 +8,7 @@ ### Docs +* ๐Ÿ“ Update package docs with the latest versions of Typer and Poetry. PR [#781](https://github.com/fastapi/typer/pull/781) by [@kinuax](https://github.com/kinuax). * ๐Ÿ“ Update the Progress Bar tutorial with correct output. PR [#199](https://github.com/fastapi/typer/pull/199) by [@n1ckdm](https://github.com/n1ckdm). * ๐Ÿ“ Add docs and scripts to test completion in different shells. PR [#953](https://github.com/fastapi/typer/pull/953) by [@tiangolo](https://github.com/tiangolo). * โœ๏ธ Fix a typo in `docs/virtual-environments.md`. PR [#952](https://github.com/fastapi/typer/pull/952) by [@tiangolo](https://github.com/tiangolo). From 113c13b555937f28b12271bf4dc9dc16b163f52d Mon Sep 17 00:00:00 2001 From: OhioDschungel6 <39698795+OhioDschungel6@users.noreply.github.com> Date: Sat, 24 Aug 2024 22:14:51 +0200 Subject: [PATCH 17/47] =?UTF-8?q?=F0=9F=93=9D=20Fix=20broken=20link=20(#83?= =?UTF-8?q?5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: svlandeg --- docs/alternatives.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/alternatives.md b/docs/alternatives.md index 656a4ef660..1f1b7904de 100644 --- a/docs/alternatives.md +++ b/docs/alternatives.md @@ -22,7 +22,7 @@ Provide a better development experience than just reading *CLI Parameters* by ha /// -### Hug +### Hug Hug is a library to create APIs and CLIs, it uses parameters in functions to declare the required data. From 6ce2feb6e2784d68ec042c3305f6c98279186cdf Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 24 Aug 2024 20:15:57 +0000 Subject: [PATCH 18/47] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 1b30a8633b..3e90ffde83 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -8,6 +8,7 @@ ### Docs +* ๐Ÿ“ Fix broken link. PR [#835](https://github.com/fastapi/typer/pull/835) by [@OhioDschungel6](https://github.com/OhioDschungel6). * ๐Ÿ“ Update package docs with the latest versions of Typer and Poetry. PR [#781](https://github.com/fastapi/typer/pull/781) by [@kinuax](https://github.com/kinuax). * ๐Ÿ“ Update the Progress Bar tutorial with correct output. PR [#199](https://github.com/fastapi/typer/pull/199) by [@n1ckdm](https://github.com/n1ckdm). * ๐Ÿ“ Add docs and scripts to test completion in different shells. PR [#953](https://github.com/fastapi/typer/pull/953) by [@tiangolo](https://github.com/tiangolo). From 8b7814d49d40ba01946ea24e5f278312be416447 Mon Sep 17 00:00:00 2001 From: Sofie Van Landeghem Date: Sat, 24 Aug 2024 22:16:36 +0200 Subject: [PATCH 19/47] =?UTF-8?q?=F0=9F=94=A5=20Clean=20up=20redundant=20c?= =?UTF-8?q?ode=20(#858)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_completion/test_completion_install.py | 5 ----- typer/main.py | 3 +-- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/test_completion/test_completion_install.py b/tests/test_completion/test_completion_install.py index d1a4695bc8..7c9054e250 100644 --- a/tests/test_completion/test_completion_install.py +++ b/tests/test_completion/test_completion_install.py @@ -133,11 +133,6 @@ def test_completion_install_fish(): assert "Completion will take effect once you restart the terminal" in result.stdout -runner = CliRunner() -app = typer.Typer() -app.command()(mod.main) - - def test_completion_install_powershell(): completion_path: Path = ( Path.home() / ".config/powershell/Microsoft.PowerShell_profile.ps1" diff --git a/typer/main.py b/typer/main.py index 8d915527cd..20350e8bcc 100644 --- a/typer/main.py +++ b/typer/main.py @@ -436,7 +436,6 @@ def solve_typer_info_help(typer_info: TyperInfo) -> str: def solve_typer_info_defaults(typer_info: TyperInfo) -> TyperInfo: values: Dict[str, Any] = {} - name = None for name, value in typer_info.__dict__.items(): # Priority 1: Value was set in app.add_typer() if not isinstance(value, DefaultPlaceholder): @@ -816,7 +815,7 @@ def get_click_param( else: default_value = param.default parameter_info = OptionInfo() - annotation: Any = Any + annotation: Any if not param.annotation == param.empty: annotation = param.annotation else: From fe8b82a380d9aaa5cf97faaa399b5738f22cbc37 Mon Sep 17 00:00:00 2001 From: Fabio Ramalho <6180271+fsramalho@users.noreply.github.com> Date: Sat, 24 Aug 2024 21:17:30 +0100 Subject: [PATCH 20/47] =?UTF-8?q?=F0=9F=93=9DAdd=20missing=20`main.py`=20i?= =?UTF-8?q?n=20tutorial=20on=20CLI=20option=20names=20(#868)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: svlandeg --- docs/tutorial/options/name.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/options/name.md b/docs/tutorial/options/name.md index 7ece83f8b5..a724a5b563 100644 --- a/docs/tutorial/options/name.md +++ b/docs/tutorial/options/name.md @@ -81,7 +81,7 @@ Options: --help Show this message and exit. // Try it -$ python --name Camila +$ python main.py --name Camila Hello Camila ``` From 2ef93589d2e7dd679ced6b8c412b608ec9923952 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 24 Aug 2024 20:18:06 +0000 Subject: [PATCH 21/47] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 3e90ffde83..5ea02a5c04 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -6,6 +6,10 @@ * ๐Ÿšธ Improve assertion error message if a group is not a valid subclass. PR [#425](https://github.com/fastapi/typer/pull/425) by [@chrisburr](https://github.com/chrisburr). +### Refactors + +* ๐Ÿ”ฅ Clean up redundant code. PR [#858](https://github.com/fastapi/typer/pull/858) by [@svlandeg](https://github.com/svlandeg). + ### Docs * ๐Ÿ“ Fix broken link. PR [#835](https://github.com/fastapi/typer/pull/835) by [@OhioDschungel6](https://github.com/OhioDschungel6). From f6cbe2545ad4fc563a96ef55532c6f8ef00921ae Mon Sep 17 00:00:00 2001 From: Sofie Van Landeghem Date: Sat, 24 Aug 2024 22:19:19 +0200 Subject: [PATCH 22/47] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Use=20F-strings=20in?= =?UTF-8?q?=20Click=20examples=20in=20docs=20(#891)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use f-string in Click examples --- docs_src/using_click/tutorial001.py | 2 +- docs_src/using_click/tutorial003.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs_src/using_click/tutorial001.py b/docs_src/using_click/tutorial001.py index 6fbf00649a..b3260c6494 100644 --- a/docs_src/using_click/tutorial001.py +++ b/docs_src/using_click/tutorial001.py @@ -7,7 +7,7 @@ def hello(count, name): """Simple program that greets NAME for a total of COUNT times.""" for x in range(count): - click.echo("Hello %s!" % name) + click.echo(f"Hello {name}!") if __name__ == "__main__": diff --git a/docs_src/using_click/tutorial003.py b/docs_src/using_click/tutorial003.py index a8f99e4623..5b3967c015 100644 --- a/docs_src/using_click/tutorial003.py +++ b/docs_src/using_click/tutorial003.py @@ -23,7 +23,7 @@ def callback(): @click.option("--name", prompt="Your name", help="The person to greet.") def hello(name): """Simple program that greets NAME for a total of COUNT times.""" - click.echo("Hello %s!" % name) + click.echo(f"Hello {name}!") typer_click_object = typer.main.get_command(app) From c86da2a7a4d649f8750d5ab36768c4f4499432ff Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 24 Aug 2024 20:20:03 +0000 Subject: [PATCH 23/47] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 5ea02a5c04..82af65b057 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -12,6 +12,7 @@ ### Docs +* ๐Ÿ“Add missing `main.py` in tutorial on CLI option names. PR [#868](https://github.com/fastapi/typer/pull/868) by [@fsramalho](https://github.com/fsramalho). * ๐Ÿ“ Fix broken link. PR [#835](https://github.com/fastapi/typer/pull/835) by [@OhioDschungel6](https://github.com/OhioDschungel6). * ๐Ÿ“ Update package docs with the latest versions of Typer and Poetry. PR [#781](https://github.com/fastapi/typer/pull/781) by [@kinuax](https://github.com/kinuax). * ๐Ÿ“ Update the Progress Bar tutorial with correct output. PR [#199](https://github.com/fastapi/typer/pull/199) by [@n1ckdm](https://github.com/n1ckdm). From a79fbafb17405d2bbdbc085c445d4b6c14189c2f Mon Sep 17 00:00:00 2001 From: Sofie Van Landeghem Date: Sat, 24 Aug 2024 22:20:58 +0200 Subject: [PATCH 24/47] =?UTF-8?q?=F0=9F=94=A5=20Remove=20Python=203.6=20sp?= =?UTF-8?q?ecific=20code=20paths=20(#850)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- typer/_typing.py | 94 +++++++++--------------------------------------- 1 file changed, 17 insertions(+), 77 deletions(-) diff --git a/typer/_typing.py b/typer/_typing.py index b1d2483da0..30cd612de9 100644 --- a/typer/_typing.py +++ b/typer/_typing.py @@ -3,6 +3,7 @@ # mypy: ignore-errors import sys +from collections.abc import Callable as Callable from os import PathLike from typing import ( TYPE_CHECKING, @@ -10,6 +11,7 @@ Any, ClassVar, Dict, + ForwardRef, Generator, List, Mapping, @@ -24,9 +26,13 @@ cast, get_type_hints, ) +from typing import Callable as TypingCallable from typing_extensions import Annotated, Literal +AnyCallable = TypingCallable[..., Any] +NoArgAnyCallable = TypingCallable[[], Any] + try: from typing import _TypingBase as typing_base # type: ignore except ImportError: @@ -45,28 +51,7 @@ TypesUnionType = () -if sys.version_info < (3, 7): - if TYPE_CHECKING: - - class ForwardRef: - def __init__(self, arg: Any): - pass - - def _eval_type(self, globalns: Any, localns: Any) -> Any: - pass - - else: - from typing import _ForwardRef as ForwardRef -else: - from typing import ForwardRef - - -if sys.version_info < (3, 7): - - def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any: - return type_._eval_type(globalns, localns) - -elif sys.version_info < (3, 9): +if sys.version_info < (3, 9): def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any: return type_._evaluate(globalns, localns) @@ -81,7 +66,7 @@ def evaluate_forwardref(type_: ForwardRef, globalns: Any, localns: Any) -> Any: if sys.version_info < (3, 9): # Ensure we always get all the whole `Annotated` hint, not just the annotated type. - # For 3.6 to 3.8, `get_type_hints` doesn't recognize `typing_extensions.Annotated`, + # For 3.7 to 3.8, `get_type_hints` doesn't recognize `typing_extensions.Annotated`, # so it already returns the full annotation get_all_type_hints = get_type_hints @@ -91,19 +76,6 @@ def get_all_type_hints(obj: Any, globalns: Any = None, localns: Any = None) -> A return get_type_hints(obj, globalns, localns, include_extras=True) -if sys.version_info < (3, 7): - from typing import Callable as Callable - - AnyCallable = Callable[..., Any] - NoArgAnyCallable = Callable[[], Any] -else: - from collections.abc import Callable as Callable - from typing import Callable as TypingCallable - - AnyCallable = TypingCallable[..., Any] - NoArgAnyCallable = TypingCallable[[], Any] - - # Annotated[...] is implemented by returning an instance of one of these classes, depending on # python/typing_extensions version. AnnotatedTypeNames = {"AnnotatedMeta", "_AnnotatedAlias"} @@ -133,22 +105,7 @@ def get_origin(tp: Type[Any]) -> Optional[Type[Any]]: return _typing_get_origin(tp) or getattr(tp, "__origin__", None) -if sys.version_info < (3, 7): # noqa: C901 (ignore complexity) - - def get_args(t: Type[Any]) -> Tuple[Any, ...]: - """Simplest get_args compatibility layer possible. - - The Python 3.6 typing module does not have `_GenericAlias` so - this won't work for everything. In particular this will not - support the `generics` module (we don't support generic models in - python 3.6). - - """ - if type(t).__name__ in AnnotatedTypeNames: - return t.__args__ + t.__metadata__ - return getattr(t, "__args__", ()) - -elif sys.version_info < (3, 8): # noqa: C901 +if sys.version_info < (3, 8): # noqa: C901 from typing import _GenericAlias def get_args(t: Type[Any]) -> Tuple[Any, ...]: @@ -352,8 +309,8 @@ def is_union(tp: Optional[Type[Any]]) -> bool: if sys.version_info < (3, 8): - # Even though this implementation is slower, we need it for python 3.6/3.7: - # In python 3.6/3.7 "Literal" is not a builtin type and uses a different + # Even though this implementation is slower, we need it for python 3.7: + # In python 3.7 "Literal" is not a builtin type and uses a different # mechanism. # for this reason `Literal[None] is Literal[None]` evaluates to `False`, # breaking the faster implementation used for the other python versions. @@ -431,10 +388,8 @@ def resolve_annotations( 1, ): value = ForwardRef(value, is_argument=False, is_class=True) - elif sys.version_info >= (3, 7): - value = ForwardRef(value, is_argument=False) else: - value = ForwardRef(value) + value = ForwardRef(value, is_argument=False) try: value = _eval_type(value, base_globals, None) except NameError: @@ -448,25 +403,12 @@ def is_callable_type(type_: Type[Any]) -> bool: return type_ is Callable or get_origin(type_) is Callable -if sys.version_info >= (3, 7): - - def is_literal_type(type_: Type[Any]) -> bool: - return Literal is not None and get_origin(type_) is Literal - - def literal_values(type_: Type[Any]) -> Tuple[Any, ...]: - return get_args(type_) +def is_literal_type(type_: Type[Any]) -> bool: + return Literal is not None and get_origin(type_) is Literal -else: - - def is_literal_type(type_: Type[Any]) -> bool: - return ( - Literal is not None - and hasattr(type_, "__values__") - and type_ == Literal[type_.__values__] - ) - def literal_values(type_: Type[Any]) -> Tuple[Any, ...]: - return type_.__values__ +def literal_values(type_: Type[Any]) -> Tuple[Any, ...]: + return get_args(type_) def all_literal_values(type_: Type[Any]) -> Tuple[Any, ...]: @@ -522,9 +464,7 @@ def _check_classvar(v: Optional[Type[Any]]) -> bool: if v is None: return False - return v.__class__ == ClassVar.__class__ and ( - sys.version_info < (3, 7) or getattr(v, "_name", None) == "ClassVar" - ) + return v.__class__ == ClassVar.__class__ and getattr(v, "_name", None) == "ClassVar" def is_classvar(ann_type: Type[Any]) -> bool: From 8ebcef82ee27fa8cb089b5245bf28e1a2543c49d Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 24 Aug 2024 20:22:32 +0000 Subject: [PATCH 25/47] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 82af65b057..ac07cf50b6 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -12,6 +12,7 @@ ### Docs +* โ™ป๏ธ Use F-strings in Click examples in docs. PR [#891](https://github.com/fastapi/typer/pull/891) by [@svlandeg](https://github.com/svlandeg). * ๐Ÿ“Add missing `main.py` in tutorial on CLI option names. PR [#868](https://github.com/fastapi/typer/pull/868) by [@fsramalho](https://github.com/fsramalho). * ๐Ÿ“ Fix broken link. PR [#835](https://github.com/fastapi/typer/pull/835) by [@OhioDschungel6](https://github.com/OhioDschungel6). * ๐Ÿ“ Update package docs with the latest versions of Typer and Poetry. PR [#781](https://github.com/fastapi/typer/pull/781) by [@kinuax](https://github.com/kinuax). From 7420a15c3f179976dc4a93f46b36830fd4604d07 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 24 Aug 2024 20:24:54 +0000 Subject: [PATCH 26/47] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index ac07cf50b6..24ba7927a5 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -8,6 +8,7 @@ ### Refactors +* ๐Ÿ”ฅ Remove Python 3.6 specific code paths. PR [#850](https://github.com/fastapi/typer/pull/850) by [@svlandeg](https://github.com/svlandeg). * ๐Ÿ”ฅ Clean up redundant code. PR [#858](https://github.com/fastapi/typer/pull/858) by [@svlandeg](https://github.com/svlandeg). ### Docs From 1e327a8cd6491fb60778abbd2fe16d3f5e27420f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 24 Aug 2024 15:26:43 -0500 Subject: [PATCH 27/47] =?UTF-8?q?=E2=AC=86=20Bump=20pillow=20from=2010.3.0?= =?UTF-8?q?=20to=2010.4.0=20(#939)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [pillow](https://github.com/python-pillow/Pillow) from 10.3.0 to 10.4.0. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/10.3.0...10.4.0) --- updated-dependencies: - dependency-name: pillow dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 4ad8149417..e54908e1d2 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -7,7 +7,7 @@ pyyaml >=5.3.1,<7.0.0 # For Material for MkDocs, Chinese search # jieba==0.42.1 # For image processing by Material for MkDocs -pillow==10.3.0 +pillow==10.4.0 # For image processing by Material for MkDocs cairosvg==2.7.1 # mkdocstrings[python]==0.25.1 From 01b180ff2a68f0b696b94ed0756458b37b82b390 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 24 Aug 2024 15:27:02 -0500 Subject: [PATCH 28/47] =?UTF-8?q?=E2=AC=86=20Bump=20mkdocs-material=20from?= =?UTF-8?q?=209.5.18=20to=209.5.33=20(#945)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.18 to 9.5.33. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.18...9.5.33) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index e54908e1d2..5635b9558e 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,6 +1,6 @@ -e . -mkdocs-material==9.5.18 +mkdocs-material==9.5.33 mdx-include >=1.4.1,<2.0.0 mkdocs-redirects>=1.2.1,<1.3.0 pyyaml >=5.3.1,<7.0.0 From bd1ce8a304c194da9f1e9b53e4d8170c35803650 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 24 Aug 2024 20:28:27 +0000 Subject: [PATCH 29/47] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 24ba7927a5..5ebc85d5f2 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -25,6 +25,7 @@ ### Internal +* โฌ† Bump pillow from 10.3.0 to 10.4.0. PR [#939](https://github.com/fastapi/typer/pull/939) by [@dependabot[bot]](https://github.com/apps/dependabot). * ๐Ÿ‘ท Fix issue-manager. PR [#948](https://github.com/fastapi/typer/pull/948) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ™ˆ Remove extra line in .gitignore. PR [#936](https://github.com/fastapi/typer/pull/936) by [@tiangolo](https://github.com/tiangolo). * โฌ† Update pytest-cov requirement from <5.0.0,>=2.10.0 to >=2.10.0,<6.0.0. PR [#844](https://github.com/fastapi/typer/pull/844) by [@dependabot[bot]](https://github.com/apps/dependabot). From 8c0c0032ce25bda9592a1a038144454a209090d1 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 24 Aug 2024 20:28:47 +0000 Subject: [PATCH 30/47] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 5ebc85d5f2..62462267ce 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -25,6 +25,7 @@ ### Internal +* โฌ† Bump mkdocs-material from 9.5.18 to 9.5.33. PR [#945](https://github.com/fastapi/typer/pull/945) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump pillow from 10.3.0 to 10.4.0. PR [#939](https://github.com/fastapi/typer/pull/939) by [@dependabot[bot]](https://github.com/apps/dependabot). * ๐Ÿ‘ท Fix issue-manager. PR [#948](https://github.com/fastapi/typer/pull/948) by [@tiangolo](https://github.com/tiangolo). * ๐Ÿ™ˆ Remove extra line in .gitignore. PR [#936](https://github.com/fastapi/typer/pull/936) by [@tiangolo](https://github.com/tiangolo). From 493dd8e58c34b272dec24291807d98cf87c0ba1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Ra=C4=8Dinsk=C3=BD?= Date: Sat, 24 Aug 2024 22:36:12 +0200 Subject: [PATCH 31/47] =?UTF-8?q?=F0=9F=92=84=20Unify=20the=20width=20of?= =?UTF-8?q?=20the=20Rich=20console=20for=20help=20and=20errors=20(#788)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: svlandeg --- typer/main.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/typer/main.py b/typer/main.py index 20350e8bcc..06a6accbfe 100644 --- a/typer/main.py +++ b/typer/main.py @@ -39,10 +39,11 @@ try: import rich - from rich.console import Console from rich.traceback import Traceback - console_stderr = Console(stderr=True) + from . import rich_utils + + console_stderr = rich_utils._get_rich_console(stderr=True) except ImportError: # pragma: no cover rich = None # type: ignore @@ -70,12 +71,15 @@ def except_hook( supress_internal_dir_names = [typer_path, click_path] exc = exc_value if rich: + from .rich_utils import MAX_WIDTH + rich_tb = Traceback.from_exception( type(exc), exc, exc.__traceback__, show_locals=exception_config.pretty_exceptions_show_locals, suppress=supress_internal_dir_names, + width=MAX_WIDTH, ) console_stderr.print(rich_tb) return From 3f566edbeff698913df6022af69e7985ea17a4e2 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 24 Aug 2024 20:36:29 +0000 Subject: [PATCH 32/47] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 62462267ce..e35ebb7e81 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -4,6 +4,7 @@ ### Features +* ๐Ÿ’„ Unify the width of the Rich console for help and errors. PR [#788](https://github.com/fastapi/typer/pull/788) by [@racinmat](https://github.com/racinmat). * ๐Ÿšธ Improve assertion error message if a group is not a valid subclass. PR [#425](https://github.com/fastapi/typer/pull/425) by [@chrisburr](https://github.com/chrisburr). ### Refactors From 70ffd68cd962e4676e65a4c5cacdca532fbcc025 Mon Sep 17 00:00:00 2001 From: patricksurry Date: Sat, 24 Aug 2024 16:44:57 -0400 Subject: [PATCH 33/47] =?UTF-8?q?=F0=9F=90=9B=20Fix=20PowerShell=20complet?= =?UTF-8?q?ion=20with=20incomplete=20word=20(#360)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastiรกn Ramรญrez Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: svlandeg --- .../test_tutorial003.py | 19 ++++++++++++++++++- .../test_tutorial003_an.py | 19 ++++++++++++++++++- typer/_completion_classes.py | 2 +- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial003.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial003.py index ebd1e066ba..60304e9e55 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial003.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial003.py @@ -9,7 +9,7 @@ runner = CliRunner() -def test_completion(): +def test_completion_zsh(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, " "], capture_output=True, @@ -25,6 +25,23 @@ def test_completion(): assert "Sebastian" in result.stdout +def test_completion_powershell(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, " "], + capture_output=True, + encoding="utf-8", + env={ + **os.environ, + "_TUTORIAL003.PY_COMPLETE": "complete_powershell", + "_TYPER_COMPLETE_ARGS": "tutorial003.py --name Seb", + "_TYPER_COMPLETE_WORD_TO_COMPLETE": "Seb", + }, + ) + assert "Camila" not in result.stdout + assert "Carlos" not in result.stdout + assert "Sebastian" in result.stdout + + def test_1(): result = runner.invoke(mod.app, ["--name", "Camila"]) assert result.exit_code == 0 diff --git a/tests/test_tutorial/test_options_autocompletion/test_tutorial003_an.py b/tests/test_tutorial/test_options_autocompletion/test_tutorial003_an.py index 8f12583e80..7688f108f5 100644 --- a/tests/test_tutorial/test_options_autocompletion/test_tutorial003_an.py +++ b/tests/test_tutorial/test_options_autocompletion/test_tutorial003_an.py @@ -9,7 +9,7 @@ runner = CliRunner() -def test_completion(): +def test_completion_zsh(): result = subprocess.run( [sys.executable, "-m", "coverage", "run", mod.__file__, " "], capture_output=True, @@ -25,6 +25,23 @@ def test_completion(): assert "Sebastian" in result.stdout +def test_completion_powershell(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, " "], + capture_output=True, + encoding="utf-8", + env={ + **os.environ, + "_TUTORIAL003_AN.PY_COMPLETE": "complete_powershell", + "_TYPER_COMPLETE_ARGS": "tutorial003.py --name Seb", + "_TYPER_COMPLETE_WORD_TO_COMPLETE": "Seb", + }, + ) + assert "Camila" not in result.stdout + assert "Carlos" not in result.stdout + assert "Sebastian" in result.stdout + + def test_1(): result = runner.invoke(mod.app, ["--name", "Camila"]) assert result.exit_code == 0 diff --git a/typer/_completion_classes.py b/typer/_completion_classes.py index 6bb25c13bd..71ba031860 100644 --- a/typer/_completion_classes.py +++ b/typer/_completion_classes.py @@ -175,7 +175,7 @@ def get_completion_args(self) -> Tuple[List[str], str]: completion_args = os.getenv("_TYPER_COMPLETE_ARGS", "") incomplete = os.getenv("_TYPER_COMPLETE_WORD_TO_COMPLETE", "") cwords = click.parser.split_arg_string(completion_args) - args = cwords[1:] + args = cwords[1:-1] if incomplete else cwords[1:] return args, incomplete def format_completion(self, item: click.shell_completion.CompletionItem) -> str: From 6b35a70cd128366fe70d019ebaea2556ab50d4f0 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 24 Aug 2024 20:45:13 +0000 Subject: [PATCH 34/47] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index e35ebb7e81..899595b15e 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -7,6 +7,10 @@ * ๐Ÿ’„ Unify the width of the Rich console for help and errors. PR [#788](https://github.com/fastapi/typer/pull/788) by [@racinmat](https://github.com/racinmat). * ๐Ÿšธ Improve assertion error message if a group is not a valid subclass. PR [#425](https://github.com/fastapi/typer/pull/425) by [@chrisburr](https://github.com/chrisburr). +### Fixes + +* ๐Ÿ› Fix PowerShell completion with incomplete word. PR [#360](https://github.com/fastapi/typer/pull/360) by [@patricksurry](https://github.com/patricksurry). + ### Refactors * ๐Ÿ”ฅ Remove Python 3.6 specific code paths. PR [#850](https://github.com/fastapi/typer/pull/850) by [@svlandeg](https://github.com/svlandeg). From afac2b80dca716f65e63f539b5885c37e031a0cf Mon Sep 17 00:00:00 2001 From: Sofie Van Landeghem Date: Sat, 24 Aug 2024 23:02:06 +0200 Subject: [PATCH 35/47] =?UTF-8?q?=F0=9F=90=9B=20=20Fix=20sourcing=20of=20c?= =?UTF-8?q?ompletion=20path=20for=20Git=20Bash=20(#801)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Sebastiรกn Ramรญrez --- typer/_completion_shared.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/typer/_completion_shared.py b/typer/_completion_shared.py index a5ff7b196a..4f40c143c0 100644 --- a/typer/_completion_shared.py +++ b/typer/_completion_shared.py @@ -100,13 +100,13 @@ def install_bash(*, prog_name: str, complete_var: str, shell: str) -> Path: # It seems bash-completion is the official completion system for bash: # Ref: https://www.gnu.org/software/bash/manual/html_node/A-Programmable-Completion-Example.html # But installing in the locations from the docs doesn't seem to have effect - completion_path = Path.home() / f".bash_completions/{prog_name}.sh" + completion_path = Path.home() / ".bash_completions" / f"{prog_name}.sh" rc_path = Path.home() / ".bashrc" rc_path.parent.mkdir(parents=True, exist_ok=True) rc_content = "" if rc_path.is_file(): rc_content = rc_path.read_text() - completion_init_lines = [f"source {completion_path}"] + completion_init_lines = [f"source '{completion_path}'"] for line in completion_init_lines: if line not in rc_content: # pragma: no cover rc_content += f"\n{line}" From f17bb0675a33d23064b24c67754e8fa78e3dfc41 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 24 Aug 2024 21:02:25 +0000 Subject: [PATCH 36/47] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 899595b15e..3386a19686 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -9,6 +9,7 @@ ### Fixes +* ๐Ÿ› Fix sourcing of completion path for Git Bash. PR [#801](https://github.com/fastapi/typer/pull/801) by [@svlandeg](https://github.com/svlandeg). * ๐Ÿ› Fix PowerShell completion with incomplete word. PR [#360](https://github.com/fastapi/typer/pull/360) by [@patricksurry](https://github.com/patricksurry). ### Refactors From fc2c54f0aa9508b4a72ae50cb3bcb3bd0e6c1a21 Mon Sep 17 00:00:00 2001 From: Sofie Van Landeghem Date: Sat, 24 Aug 2024 23:04:45 +0200 Subject: [PATCH 37/47] =?UTF-8?q?=F0=9F=90=9B=20Ensure=20`rich=5Fmarkup=5F?= =?UTF-8?q?mode=3DNone`=20disables=20Rich=20formatting=20(#859)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/tutorial/commands/help.md | 2 +- tests/test_rich_markup_mode.py | 40 ++++++++++++++++++++++++++++++++++ typer/core.py | 22 ++++++++++++------- typer/main.py | 11 ++++++++-- 4 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 tests/test_rich_markup_mode.py diff --git a/docs/tutorial/commands/help.md b/docs/tutorial/commands/help.md index b9501b72ee..5a6668038a 100644 --- a/docs/tutorial/commands/help.md +++ b/docs/tutorial/commands/help.md @@ -208,7 +208,7 @@ Then you can use more formatting in the docstrings and the `help` parameter for /// info -By default, `rich_markup_mode` is `None`, which disables any rich text formatting. +By default, `rich_markup_mode` is `None` if Rich is not installed, and `"rich"` if it is installed. In the latter case, you can set `rich_markup_mode` to `None` to disable rich text formatting. /// diff --git a/tests/test_rich_markup_mode.py b/tests/test_rich_markup_mode.py new file mode 100644 index 0000000000..d9fe5bae4b --- /dev/null +++ b/tests/test_rich_markup_mode.py @@ -0,0 +1,40 @@ +import typer +import typer.completion +from typer.testing import CliRunner + +runner = CliRunner() +rounded = ["โ•ญ", "โ”€", "โ”ฌ", "โ•ฎ", "โ”‚", "โ”œ", "โ”ผ", "โ”ค", "โ•ฐ", "โ”ด", "โ•ฏ"] + + +def test_rich_markup_mode_none(): + app = typer.Typer(rich_markup_mode=None) + + @app.command() + def main(arg: str): + """Main function""" + print(f"Hello {arg}") + + assert app.rich_markup_mode is None + + result = runner.invoke(app, ["World"]) + assert "Hello World" in result.stdout + + result = runner.invoke(app, ["--help"]) + assert all(c not in result.stdout for c in rounded) + + +def test_rich_markup_mode_rich(): + app = typer.Typer(rich_markup_mode="rich") + + @app.command() + def main(arg: str): + """Main function""" + print(f"Hello {arg}") + + assert app.rich_markup_mode == "rich" + + result = runner.invoke(app, ["World"]) + assert "Hello World" in result.stdout + + result = runner.invoke(app, ["--help"]) + assert any(c in result.stdout for c in rounded) diff --git a/typer/core.py b/typer/core.py index 31fece5a76..c5cdbfee8d 100644 --- a/typer/core.py +++ b/typer/core.py @@ -31,15 +31,18 @@ else: from typing_extensions import Literal +MarkupMode = Literal["markdown", "rich", None] + try: import rich from . import rich_utils + DEFAULT_MARKUP_MODE: MarkupMode = "rich" + except ImportError: # pragma: no cover rich = None # type: ignore - -MarkupMode = Literal["markdown", "rich", None] + DEFAULT_MARKUP_MODE = None # Copy from click.parser._split_opt @@ -167,6 +170,7 @@ def _main( complete_var: Optional[str] = None, standalone_mode: bool = True, windows_expand_args: bool = True, + rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE, **extra: Any, ) -> Any: # Typer override, duplicated from click.main() to handle custom rich exceptions @@ -208,7 +212,7 @@ def _main( if not standalone_mode: raise # Typer override - if rich: + if rich and rich_markup_mode is not None: rich_utils.rich_format_error(e) else: e.show() @@ -238,7 +242,7 @@ def _main( if not standalone_mode: raise # Typer override - if rich: + if rich and rich_markup_mode is not None: rich_utils.rich_abort_error() else: click.echo(_("Aborted!"), file=sys.stderr) @@ -614,7 +618,7 @@ def __init__( hidden: bool = False, deprecated: bool = False, # Rich settings - rich_markup_mode: MarkupMode = None, + rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE, rich_help_panel: Union[str, None] = None, ) -> None: super().__init__( @@ -665,11 +669,12 @@ def main( complete_var=complete_var, standalone_mode=standalone_mode, windows_expand_args=windows_expand_args, + rich_markup_mode=self.rich_markup_mode, **extra, ) def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None: - if not rich: + if not rich or self.rich_markup_mode is None: return super().format_help(ctx, formatter) return rich_utils.rich_format_help( obj=self, @@ -687,7 +692,7 @@ def __init__( Union[Dict[str, click.Command], Sequence[click.Command]] ] = None, # Rich settings - rich_markup_mode: MarkupMode = None, + rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE, rich_help_panel: Union[str, None] = None, **attrs: Any, ) -> None: @@ -727,11 +732,12 @@ def main( complete_var=complete_var, standalone_mode=standalone_mode, windows_expand_args=windows_expand_args, + rich_markup_mode=self.rich_markup_mode, **extra, ) def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None: - if not rich: + if not rich or self.rich_markup_mode is None: return super().format_help(ctx, formatter) return rich_utils.rich_format_help( obj=self, diff --git a/typer/main.py b/typer/main.py index 06a6accbfe..013939bf20 100644 --- a/typer/main.py +++ b/typer/main.py @@ -15,7 +15,14 @@ from ._typing import get_args, get_origin, is_union from .completion import get_completion_inspect_parameters -from .core import MarkupMode, TyperArgument, TyperCommand, TyperGroup, TyperOption +from .core import ( + DEFAULT_MARKUP_MODE, + MarkupMode, + TyperArgument, + TyperCommand, + TyperGroup, + TyperOption, +) from .models import ( AnyType, ArgumentInfo, @@ -137,7 +144,7 @@ def __init__( deprecated: bool = Default(False), add_completion: bool = True, # Rich settings - rich_markup_mode: MarkupMode = None, + rich_markup_mode: MarkupMode = Default(DEFAULT_MARKUP_MODE), rich_help_panel: Union[str, None] = Default(None), pretty_exceptions_enable: bool = True, pretty_exceptions_show_locals: bool = True, From 6cc1f9a361414c09db45a0ffd36823fdfd405636 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 24 Aug 2024 21:05:03 +0000 Subject: [PATCH 38/47] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 3386a19686..69319e4eb5 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -9,6 +9,7 @@ ### Fixes +* ๐Ÿ› Ensure `rich_markup_mode=None` disables Rich formatting. PR [#859](https://github.com/fastapi/typer/pull/859) by [@svlandeg](https://github.com/svlandeg). * ๐Ÿ› Fix sourcing of completion path for Git Bash. PR [#801](https://github.com/fastapi/typer/pull/801) by [@svlandeg](https://github.com/svlandeg). * ๐Ÿ› Fix PowerShell completion with incomplete word. PR [#360](https://github.com/fastapi/typer/pull/360) by [@patricksurry](https://github.com/patricksurry). From a5b7557140eda1f494050a791d51469ca6483805 Mon Sep 17 00:00:00 2001 From: Sofie Van Landeghem Date: Sat, 24 Aug 2024 23:08:27 +0200 Subject: [PATCH 39/47] =?UTF-8?q?=E2=9C=85=20Add=20`needs=5Fbash`=20test?= =?UTF-8?q?=20fixture=20(#888)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- tests/test_completion/test_completion.py | 4 +++- tests/utils.py | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/test_completion/test_completion.py b/tests/test_completion/test_completion.py index 703373b226..36581aba09 100644 --- a/tests/test_completion/test_completion.py +++ b/tests/test_completion/test_completion.py @@ -5,9 +5,10 @@ from docs_src.commands.index import tutorial001 as mod -from ..utils import needs_linux +from ..utils import needs_bash, needs_linux +@needs_bash @needs_linux def test_show_completion(): result = subprocess.run( @@ -23,6 +24,7 @@ def test_show_completion(): assert "_TUTORIAL001.PY_COMPLETE=complete_bash" in result.stdout +@needs_bash @needs_linux def test_install_completion(): bash_completion_path: Path = Path.home() / ".bashrc" diff --git a/tests/utils.py b/tests/utils.py index 9b503ff799..8f51332ee2 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -2,6 +2,18 @@ import pytest +try: + import shellingham + from shellingham import ShellDetectionFailure + + shell = shellingham.detect_shell()[0] +except ImportError: # pragma: no cover + shellingham = None + shell = None +except ShellDetectionFailure: # pragma: no cover + shell = None + + needs_py310 = pytest.mark.skipif( sys.version_info < (3, 10), reason="requires python3.10+" ) @@ -9,3 +21,7 @@ needs_linux = pytest.mark.skipif( not sys.platform.startswith("linux"), reason="Test requires Linux" ) + +needs_bash = pytest.mark.skipif( + not shellingham or not shell or "bash" not in shell, reason="Test requires Bash" +) From a3378509cf2cc949473daf3c144ed759051de6c9 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 24 Aug 2024 21:08:45 +0000 Subject: [PATCH 40/47] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 69319e4eb5..3656b71eef 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -32,6 +32,7 @@ ### Internal +* โœ… Add `needs_bash` test fixture. PR [#888](https://github.com/fastapi/typer/pull/888) by [@svlandeg](https://github.com/svlandeg). * โฌ† Bump mkdocs-material from 9.5.18 to 9.5.33. PR [#945](https://github.com/fastapi/typer/pull/945) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump pillow from 10.3.0 to 10.4.0. PR [#939](https://github.com/fastapi/typer/pull/939) by [@dependabot[bot]](https://github.com/apps/dependabot). * ๐Ÿ‘ท Fix issue-manager. PR [#948](https://github.com/fastapi/typer/pull/948) by [@tiangolo](https://github.com/tiangolo). From d93c0ac714386b39df87597e30149bf2c2a7e824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 24 Aug 2024 16:14:02 -0500 Subject: [PATCH 41/47] =?UTF-8?q?=F0=9F=94=A8=20Pre-install=20dependencies?= =?UTF-8?q?=20in=20Docker=20so=20that=20testing=20in=20Docker=20is=20faste?= =?UTF-8?q?r=20(#954)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/docker/Dockerfile | 6 ++++++ scripts/docker/compose.yaml | 5 +++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/scripts/docker/Dockerfile b/scripts/docker/Dockerfile index 738867a954..b158e2db04 100644 --- a/scripts/docker/Dockerfile +++ b/scripts/docker/Dockerfile @@ -20,3 +20,9 @@ RUN apt-get update && apt-get install -y \ COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv ENV UV_SYSTEM_PYTHON=1 + +COPY . /code + +WORKDIR /code + +RUN uv pip install -r requirements.txt diff --git a/scripts/docker/compose.yaml b/scripts/docker/compose.yaml index 0f9d960d34..efe17f9efd 100644 --- a/scripts/docker/compose.yaml +++ b/scripts/docker/compose.yaml @@ -1,7 +1,8 @@ services: typer: - build: . + build: + context: ../../ + dockerfile: scripts/docker/Dockerfile volumes: - ../../:/code - working_dir: /code command: sleep infinity From 3ac3644d480677ae44c50e436920def381aab915 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 24 Aug 2024 21:14:19 +0000 Subject: [PATCH 42/47] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 3656b71eef..0571ef345f 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -32,6 +32,7 @@ ### Internal +* ๐Ÿ”จ Pre-install dependencies in Docker so that testing in Docker is faster. PR [#954](https://github.com/fastapi/typer/pull/954) by [@tiangolo](https://github.com/tiangolo). * โœ… Add `needs_bash` test fixture. PR [#888](https://github.com/fastapi/typer/pull/888) by [@svlandeg](https://github.com/svlandeg). * โฌ† Bump mkdocs-material from 9.5.18 to 9.5.33. PR [#945](https://github.com/fastapi/typer/pull/945) by [@dependabot[bot]](https://github.com/apps/dependabot). * โฌ† Bump pillow from 10.3.0 to 10.4.0. PR [#939](https://github.com/fastapi/typer/pull/939) by [@dependabot[bot]](https://github.com/apps/dependabot). From 88aefd4492696bd1afa7bdb49477dec2c29d7e77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 24 Aug 2024 16:16:36 -0500 Subject: [PATCH 43/47] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.12.5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 2 ++ typer/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 0571ef345f..1938376e72 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,6 +2,8 @@ ## Latest Changes +## 0.12.5 + ### Features * ๐Ÿ’„ Unify the width of the Rich console for help and errors. PR [#788](https://github.com/fastapi/typer/pull/788) by [@racinmat](https://github.com/racinmat). diff --git a/typer/__init__.py b/typer/__init__.py index dd2531263d..b422dd00d3 100644 --- a/typer/__init__.py +++ b/typer/__init__.py @@ -1,6 +1,6 @@ """Typer, build great CLIs. Easy to code. Based on Python type hints.""" -__version__ = "0.12.4" +__version__ = "0.12.5" from shutil import get_terminal_size as get_terminal_size From 14bab0a34db333f2047433ba84ee197ddea4f207 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 25 Aug 2024 21:18:17 -0500 Subject: [PATCH 44/47] =?UTF-8?q?=F0=9F=91=B7=20Update=20`latest-changes`?= =?UTF-8?q?=20GitHub=20Action=20(#955)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/latest-changes.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/latest-changes.yml b/.github/workflows/latest-changes.yml index 31c4ea724b..6a3ac059a5 100644 --- a/.github/workflows/latest-changes.yml +++ b/.github/workflows/latest-changes.yml @@ -34,8 +34,7 @@ jobs: if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }} with: limit-access-to-actor: true - - uses: docker://tiangolo/latest-changes:0.3.0 - # - uses: tiangolo/latest-changes@main + - uses: tiangolo/latest-changes@0.3.1 with: token: ${{ secrets.GITHUB_TOKEN }} latest_changes_file: docs/release-notes.md From 0f9b8fd40fc9f88734452c7ad33d0c6e1722f6fc Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 26 Aug 2024 02:19:33 +0000 Subject: [PATCH 45/47] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 1938376e72..72ee06e795 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,6 +2,10 @@ ## Latest Changes +### Internal + +* ๐Ÿ‘ท Update `latest-changes` GitHub Action. PR [#955](https://github.com/fastapi/typer/pull/955) by [@tiangolo](https://github.com/tiangolo). + ## 0.12.5 ### Features From 1f684d40d42e85525a6040a393e4c81ec637ef6f Mon Sep 17 00:00:00 2001 From: Sofie Van Landeghem Date: Mon, 26 Aug 2024 11:21:33 +0200 Subject: [PATCH 46/47] =?UTF-8?q?=E2=9C=A8=20Show=20help=20items=20in=20or?= =?UTF-8?q?der=20of=20definition=20(#944)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/tutorial/commands/index.md | 33 ++++++++++++++ docs_src/commands/index/tutorial004.py | 17 +++++++ tests/assets/cli/multiapp-docs-title.md | 32 ++++++------- tests/assets/cli/multiapp-docs.md | 32 ++++++------- .../test_index/test_tutorial004.py | 45 +++++++++++++++++++ typer/core.py | 6 +++ 6 files changed, 133 insertions(+), 32 deletions(-) create mode 100644 docs_src/commands/index/tutorial004.py create mode 100644 tests/test_tutorial/test_commands/test_index/test_tutorial004.py diff --git a/docs/tutorial/commands/index.md b/docs/tutorial/commands/index.md index d2693849d7..f531c7cfb3 100644 --- a/docs/tutorial/commands/index.md +++ b/docs/tutorial/commands/index.md @@ -257,6 +257,39 @@ Commands:
+ +## Sorting of the commands + +Note that by design, **Typer** shows the commands in the order they've been declared. + +So, if we take our original example, with `create` and `delete` commands, and reverse the order in the Python file: + +```Python hl_lines="7 12" +{!../docs_src/commands/index/tutorial004.py!} +``` + +Then we will see the `delete` command first in the help output: + +
+ +```console +// Check the help +$ python main.py --help + +Usage: main.py [OPTIONS] COMMAND [ARGS]... + +Options: + --install-completion Install completion for the current shell. + --show-completion Show completion for the current shell, to copy it or customize the installation. + --help Show this message and exit. + +Commands: + delete + create +``` + +
+ ## Click Group If you come from Click, a `typer.Typer` app with subcommands is more or less the equivalent of a Click Group. diff --git a/docs_src/commands/index/tutorial004.py b/docs_src/commands/index/tutorial004.py new file mode 100644 index 0000000000..83419b695f --- /dev/null +++ b/docs_src/commands/index/tutorial004.py @@ -0,0 +1,17 @@ +import typer + +app = typer.Typer() + + +@app.command() +def delete(): + print("Deleting user: Hiro Hamada") + + +@app.command() +def create(): + print("Creating user: Hiro Hamada") + + +if __name__ == "__main__": + app() diff --git a/tests/assets/cli/multiapp-docs-title.md b/tests/assets/cli/multiapp-docs-title.md index e688a9ba91..ffde843736 100644 --- a/tests/assets/cli/multiapp-docs-title.md +++ b/tests/assets/cli/multiapp-docs-title.md @@ -18,41 +18,41 @@ The end **Commands**: -* `sub` * `top`: Top command +* `sub` -## `multiapp sub` +## `multiapp top` + +Top command **Usage**: ```console -$ multiapp sub [OPTIONS] COMMAND [ARGS]... +$ multiapp top [OPTIONS] ``` **Options**: * `--help`: Show this message and exit. -**Commands**: - -* `bye`: Say bye -* `hello`: Say Hello -* `hi`: Say Hi - -### `multiapp sub bye` - -Say bye +## `multiapp sub` **Usage**: ```console -$ multiapp sub bye [OPTIONS] +$ multiapp sub [OPTIONS] COMMAND [ARGS]... ``` **Options**: * `--help`: Show this message and exit. +**Commands**: + +* `hello`: Say Hello +* `hi`: Say Hi +* `bye`: Say bye + ### `multiapp sub hello` Say Hello @@ -87,14 +87,14 @@ $ multiapp sub hi [OPTIONS] [USER] * `--help`: Show this message and exit. -## `multiapp top` +### `multiapp sub bye` -Top command +Say bye **Usage**: ```console -$ multiapp top [OPTIONS] +$ multiapp sub bye [OPTIONS] ``` **Options**: diff --git a/tests/assets/cli/multiapp-docs.md b/tests/assets/cli/multiapp-docs.md index ed4592f5c8..67d02568db 100644 --- a/tests/assets/cli/multiapp-docs.md +++ b/tests/assets/cli/multiapp-docs.md @@ -18,41 +18,41 @@ The end **Commands**: -* `sub` * `top`: Top command +* `sub` -## `multiapp sub` +## `multiapp top` + +Top command **Usage**: ```console -$ multiapp sub [OPTIONS] COMMAND [ARGS]... +$ multiapp top [OPTIONS] ``` **Options**: * `--help`: Show this message and exit. -**Commands**: - -* `bye`: Say bye -* `hello`: Say Hello -* `hi`: Say Hi - -### `multiapp sub bye` - -Say bye +## `multiapp sub` **Usage**: ```console -$ multiapp sub bye [OPTIONS] +$ multiapp sub [OPTIONS] COMMAND [ARGS]... ``` **Options**: * `--help`: Show this message and exit. +**Commands**: + +* `hello`: Say Hello +* `hi`: Say Hi +* `bye`: Say bye + ### `multiapp sub hello` Say Hello @@ -87,14 +87,14 @@ $ multiapp sub hi [OPTIONS] [USER] * `--help`: Show this message and exit. -## `multiapp top` +### `multiapp sub bye` -Top command +Say bye **Usage**: ```console -$ multiapp top [OPTIONS] +$ multiapp sub bye [OPTIONS] ``` **Options**: diff --git a/tests/test_tutorial/test_commands/test_index/test_tutorial004.py b/tests/test_tutorial/test_commands/test_index/test_tutorial004.py new file mode 100644 index 0000000000..ae0139e93a --- /dev/null +++ b/tests/test_tutorial/test_commands/test_index/test_tutorial004.py @@ -0,0 +1,45 @@ +import subprocess +import sys + +from typer.testing import CliRunner + +from docs_src.commands.index import tutorial004 as mod + +app = mod.app + +runner = CliRunner() + + +def test_help(): + result = runner.invoke(app, ["--help"]) + assert result.exit_code == 0 + assert "[OPTIONS] COMMAND [ARGS]..." in result.output + print(result.output) + assert "Commands" in result.output + assert "create" in result.output + assert "delete" in result.output + # Test that the 'delete' command precedes the 'create' command in the help output + create_char = result.output.index("create") + delete_char = result.output.index("delete") + assert delete_char < create_char + + +def test_create(): + result = runner.invoke(app, ["create"]) + assert result.exit_code == 0 + assert "Creating user: Hiro Hamada" in result.output + + +def test_delete(): + result = runner.invoke(app, ["delete"]) + assert result.exit_code == 0 + assert "Deleting user: Hiro Hamada" in result.output + + +def test_script(): + result = subprocess.run( + [sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], + capture_output=True, + encoding="utf-8", + ) + assert "Usage" in result.stdout diff --git a/typer/core.py b/typer/core.py index c5cdbfee8d..00e21da869 100644 --- a/typer/core.py +++ b/typer/core.py @@ -744,3 +744,9 @@ def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> Non ctx=ctx, markup_mode=self.rich_markup_mode, ) + + def list_commands(self, ctx: click.Context) -> List[str]: + """Returns a list of subcommand names. + Note that in Click's Group class, these are sorted. + In Typer, we wish to maintain the original order of creation (cf Issue #933)""" + return [n for n, c in self.commands.items()] From ae82de02adebc6b265d00068f4d2c16420ffbfbf Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 26 Aug 2024 09:22:03 +0000 Subject: [PATCH 47/47] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 72ee06e795..ad028087d5 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,6 +2,10 @@ ## Latest Changes +### Features + +* โœจ Show help items in order of definition. PR [#944](https://github.com/fastapi/typer/pull/944) by [@svlandeg](https://github.com/svlandeg). + ### Internal * ๐Ÿ‘ท Update `latest-changes` GitHub Action. PR [#955](https://github.com/fastapi/typer/pull/955) by [@tiangolo](https://github.com/tiangolo).