Skip to content

Commit 723cf94

Browse files
authored
Merge branch 'master' into feat/autocompletion
2 parents 793921e + 5474f0b commit 723cf94

23 files changed

+265
-65
lines changed

.github/workflows/test.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
strategy:
2121
matrix:
2222
os: [ ubuntu-latest, windows-latest, macos-latest ]
23-
python-version: [ "3.12" ]
23+
python-version: [ "3.13" ]
2424
include:
2525
- os: ubuntu-22.04
2626
python-version: "3.7"
@@ -32,6 +32,8 @@ jobs:
3232
python-version: "3.10"
3333
- os: macos-latest
3434
python-version: "3.11"
35+
- os: windows-latest
36+
python-version: "3.12"
3537
fail-fast: false
3638
runs-on: ${{ matrix.os }}
3739
steps:

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ repos:
1414
- id: end-of-file-fixer
1515
- id: trailing-whitespace
1616
- repo: https://github.com/astral-sh/ruff-pre-commit
17-
rev: v0.9.4
17+
rev: v0.9.7
1818
hooks:
1919
- id: ruff
2020
args:

docs/release-notes.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,34 @@
22

33
## Latest Changes
44

5+
## 0.15.2
6+
7+
### Features
8+
9+
* ✨ Allow custom styles for commands in help output. PR [#1103](https://github.com/fastapi/typer/pull/1103) by [@TheTechromancer](https://github.com/TheTechromancer).
10+
* ✨ Avoid the unnecessary import of `typing_extensions` in newer Python versions. PR [#1048](https://github.com/fastapi/typer/pull/1048) by [@horta](https://github.com/horta).
11+
12+
### Fixes
13+
14+
* 🐛 Fix shell completions for the fish shell. PR [#1069](https://github.com/fastapi/typer/pull/1069) by [@goraje](https://github.com/goraje).
15+
516
### Refactors
617

718
* 🚚 Rename test to corner-cases to make it more explicit. PR [#1083](https://github.com/fastapi/typer/pull/1083) by [@tiangolo](https://github.com/tiangolo).
819

20+
### Docs
21+
22+
* ✏️ Fix small typos in the tutorial documentation. PR [#1137](https://github.com/fastapi/typer/pull/1137) by [@svlandeg](https://github.com/svlandeg).
23+
* 📝 Update optional CLI argument section in tutorial with `Annotated`. PR [#983](https://github.com/fastapi/typer/pull/983) by [@gkeuccsr](https://github.com/gkeuccsr).
24+
* 📝 Clarify the need for `mix_stderr` when accessing the output of `stderr` in tests. PR [#1045](https://github.com/fastapi/typer/pull/1045) by [@mrchrisadams](https://github.com/mrchrisadams).
25+
926
### Internal
1027

28+
* 🔧 Add support for Python 3.13, tests in CI and add PyPI trove classifier. PR [#1091](https://github.com/fastapi/typer/pull/1091) by [@edgarrmondragon](https://github.com/edgarrmondragon).
29+
* ⬆ Bump ruff from 0.9.6 to 0.9.7. PR [#1161](https://github.com/fastapi/typer/pull/1161) by [@dependabot[bot]](https://github.com/apps/dependabot).
30+
*[pre-commit.ci] pre-commit autoupdate. PR [#1162](https://github.com/fastapi/typer/pull/1162) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
31+
* ⬆ Bump ruff from 0.9.5 to 0.9.6. PR [#1153](https://github.com/fastapi/typer/pull/1153) by [@dependabot[bot]](https://github.com/apps/dependabot).
32+
*[pre-commit.ci] pre-commit autoupdate. PR [#1151](https://github.com/fastapi/typer/pull/1151) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
1133
* ⬆ Bump ruff from 0.9.4 to 0.9.5. PR [#1146](https://github.com/fastapi/typer/pull/1146) by [@dependabot[bot]](https://github.com/apps/dependabot).
1234
*[pre-commit.ci] pre-commit autoupdate. PR [#1142](https://github.com/fastapi/typer/pull/1142) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
1335
* ⬆ Bump ruff from 0.9.3 to 0.9.4. PR [#1139](https://github.com/fastapi/typer/pull/1139) by [@dependabot[bot]](https://github.com/apps/dependabot).

docs/tutorial/arguments/optional.md

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -109,24 +109,18 @@ name: str
109109

110110
Now, finally what we came for, an optional *CLI argument*.
111111

112-
To make a *CLI argument* optional, use `typer.Argument()` and pass a different "default" as the first parameter to `typer.Argument()`, for example `None`:
112+
To make a *CLI argument* optional, use `typer.Argument()` and make sure to provide a "default" value, for example `"World"`:
113113

114-
{* docs_src/arguments/optional/tutorial002_an.py hl[7] *}
114+
{* docs_src/arguments/optional/tutorial002_an.py hl[5] *}
115115

116116
Now we have:
117117

118118
```Python
119-
name: Annotated[Optional[str], typer.Argument()] = None
119+
name: Annotated[str, typer.Argument()] = "World"
120120
```
121121

122122
Because we are using `typer.Argument()` **Typer** will know that this is a *CLI argument* (no matter if *required* or *optional*).
123123

124-
/// tip
125-
126-
By using `Optional` your editor will be able to know that the value *could* be `None`, and will be able to warn you if you do something assuming it is a `str` that would break if it was `None`.
127-
128-
///
129-
130124
Check the help:
131125

132126
<div class="termy">

docs/tutorial/options-autocompletion.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ But you can also provide auto completion for the **values** of *CLI options* and
1010

1111
Before checking how to provide custom completions, let's check again how it works.
1212

13-
After installing completion for your own Python package (or using the `typer` command), when you use your CLI program and start adding a *CLI option* with `--` an then hit <kbd>TAB</kbd>, your shell will show you the available *CLI options* (the same for *CLI arguments*, etc).
13+
After installing completion for your own Python package (or using the `typer` command), when you use your CLI program and start adding a *CLI option* with `--` and then hit <kbd>TAB</kbd>, your shell will show you the available *CLI options* (the same for *CLI arguments*, etc).
1414

1515
To check it quickly without creating a new Python package, use the `typer` command.
1616

@@ -165,7 +165,7 @@ That simplifies our code a bit and works the same.
165165

166166
/// tip
167167

168-
If all the `yield` part seems complex for you, don't worry, you can just use the version with the `list` above.
168+
If the `yield` part seems complex for you, don't worry, you can just use the version with the `list` above.
169169

170170
In the end, that's just to save us a couple of lines of code.
171171

docs/tutorial/options/callback-and-context.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ There's something to be aware of with callbacks and completion that requires som
4343

4444
But first let's just use completion in your shell (Bash, Zsh, Fish, or PowerShell).
4545

46-
After installing completion (for your own Python package), when you use your CLI program and start adding a *CLI option* with `--` an then hit <kbd>TAB</kbd>, your shell will show you the available *CLI options* (the same for *CLI arguments*, etc).
46+
After installing completion (for your own Python package), when you use your CLI program and start adding a *CLI option* with `--` and then hit <kbd>TAB</kbd>, your shell will show you the available *CLI options* (the same for *CLI arguments*, etc).
4747

4848
To check it quickly with the previous script use the `typer` command:
4949

docs/tutorial/testing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ Then we check that the text printed to "standard output" contains the text that
7171

7272
/// tip
7373

74-
You could also check `result.stderr` for "standard error" independently from "standard output" if your `CliRunner` instance is created with the `mix_stderr=False` argument.
74+
You could also check output sent to "standard error" (`stderr`) instead of "standard output" (`stdout`). To do so, make sure your `CliRunner` instance is created with the `mix_stderr=False` argument. You need this setting to be able to access `result.stderr` in tests.
7575

7676
///
7777

docs_src/arguments/optional/tutorial002.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
1-
from typing import Optional
2-
31
import typer
42

53

6-
def main(name: Optional[str] = typer.Argument(default=None)):
7-
if name is None:
8-
print("Hello World!")
9-
else:
10-
print(f"Hello {name}")
4+
def main(name: str = typer.Argument(default="World")):
5+
print(f"Hello {name}!")
116

127

138
if __name__ == "__main__":

docs_src/arguments/optional/tutorial002_an.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
1-
from typing import Optional
2-
31
import typer
42
from typing_extensions import Annotated
53

64

7-
def main(name: Annotated[Optional[str], typer.Argument()] = None):
8-
if name is None:
9-
print("Hello World!")
10-
else:
11-
print(f"Hello {name}")
5+
def main(name: Annotated[str, typer.Argument()] = "World"):
6+
print(f"Hello {name}!")
127

138

149
if __name__ == "__main__":

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ classifiers = [
3030
"Programming Language :: Python :: 3.10",
3131
"Programming Language :: Python :: 3.11",
3232
"Programming Language :: Python :: 3.12",
33+
"Programming Language :: Python :: 3.13",
3334
"License :: OSI Approved :: MIT License",
3435
]
3536
dependencies = [

requirements-tests.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ coverage[toml] >=6.2,<8.0
66
pytest-xdist >=1.32.0,<4.0.0
77
pytest-sugar >=0.9.4,<1.1.0
88
mypy ==1.4.1
9-
ruff ==0.9.5
9+
ruff ==0.9.7
1010
# Needed explicitly by typer-slim
1111
rich >=10.11.0
1212
shellingham >=1.3.0
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import typer
2+
3+
app = typer.Typer()
4+
5+
6+
@app.command()
7+
def create(username: str):
8+
"""
9+
Create a [green]new[green/] user with USERNAME.
10+
"""
11+
print(f"Creating user: {username}")
12+
13+
14+
@app.command()
15+
def delete(username: str):
16+
"""
17+
Delete a user with [bold]USERNAME[/].
18+
"""
19+
print(f"Deleting user: {username}")
20+
21+
22+
@app.command()
23+
def delete_all():
24+
"""
25+
[red]Delete ALL users[/red] in the database.
26+
"""
27+
print("Deleting all users")
28+
29+
30+
if __name__ == "__main__":
31+
app()

tests/test_completion/test_completion_complete.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def test_completion_complete_subcommand_fish():
7979
},
8080
)
8181
assert (
82-
"delete Delete a user with USERNAME.\ndelete-all Delete ALL users in the database."
82+
"delete\tDelete a user with USERNAME.\ndelete-all\tDelete ALL users in the database."
8383
in result.stdout
8484
)
8585

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import os
2+
import subprocess
3+
import sys
4+
5+
from . import example_rich_tags as mod
6+
7+
8+
def test_script():
9+
result = subprocess.run(
10+
[sys.executable, "-m", "coverage", "run", mod.__file__, "create", "DeadPool"],
11+
capture_output=True,
12+
encoding="utf-8",
13+
)
14+
assert result.returncode == 0
15+
assert "Creating user: DeadPool" in result.stdout
16+
17+
result = subprocess.run(
18+
[sys.executable, "-m", "coverage", "run", mod.__file__, "delete", "DeadPool"],
19+
capture_output=True,
20+
encoding="utf-8",
21+
)
22+
assert result.returncode == 0
23+
assert "Deleting user: DeadPool" in result.stdout
24+
25+
result = subprocess.run(
26+
[sys.executable, "-m", "coverage", "run", mod.__file__, "delete-all"],
27+
capture_output=True,
28+
encoding="utf-8",
29+
)
30+
assert result.returncode == 0
31+
assert "Deleting all users" in result.stdout
32+
33+
34+
def test_completion_complete_subcommand_bash():
35+
result = subprocess.run(
36+
[sys.executable, "-m", "coverage", "run", mod.__file__, " "],
37+
capture_output=True,
38+
encoding="utf-8",
39+
env={
40+
**os.environ,
41+
"_EXAMPLE_RICH_TAGS.PY_COMPLETE": "complete_bash",
42+
"COMP_WORDS": "example_rich_tags.py del",
43+
"COMP_CWORD": "1",
44+
},
45+
)
46+
assert "delete\ndelete-all" in result.stdout
47+
48+
49+
def test_completion_complete_subcommand_zsh():
50+
result = subprocess.run(
51+
[sys.executable, "-m", "coverage", "run", mod.__file__, " "],
52+
capture_output=True,
53+
encoding="utf-8",
54+
env={
55+
**os.environ,
56+
"_EXAMPLE_RICH_TAGS.PY_COMPLETE": "complete_zsh",
57+
"_TYPER_COMPLETE_ARGS": "example_rich_tags.py del",
58+
},
59+
)
60+
assert (
61+
"""_arguments '*: :(("delete":"Delete a user with USERNAME."\n"""
62+
"""\"delete-all":"Delete ALL users in the database."))'"""
63+
) in result.stdout
64+
65+
66+
def test_completion_complete_subcommand_fish():
67+
result = subprocess.run(
68+
[sys.executable, "-m", "coverage", "run", mod.__file__, " "],
69+
capture_output=True,
70+
encoding="utf-8",
71+
env={
72+
**os.environ,
73+
"_EXAMPLE_RICH_TAGS.PY_COMPLETE": "complete_fish",
74+
"_TYPER_COMPLETE_ARGS": "example_rich_tags.py del",
75+
"_TYPER_COMPLETE_FISH_ACTION": "get-args",
76+
},
77+
)
78+
assert (
79+
"delete\tDelete a user with USERNAME.\ndelete-all\tDelete ALL users in the database."
80+
in result.stdout
81+
)
82+
83+
84+
def test_completion_complete_subcommand_powershell():
85+
result = subprocess.run(
86+
[sys.executable, "-m", "coverage", "run", mod.__file__, " "],
87+
capture_output=True,
88+
encoding="utf-8",
89+
env={
90+
**os.environ,
91+
"_EXAMPLE_RICH_TAGS.PY_COMPLETE": "complete_powershell",
92+
"_TYPER_COMPLETE_ARGS": "example_rich_tags.py del",
93+
},
94+
)
95+
assert (
96+
"delete:::Delete a user with USERNAME.\ndelete-all:::Delete ALL users in the database."
97+
) in result.stdout
98+
99+
100+
def test_completion_complete_subcommand_pwsh():
101+
result = subprocess.run(
102+
[sys.executable, "-m", "coverage", "run", mod.__file__, " "],
103+
capture_output=True,
104+
encoding="utf-8",
105+
env={
106+
**os.environ,
107+
"_EXAMPLE_RICH_TAGS.PY_COMPLETE": "complete_pwsh",
108+
"_TYPER_COMPLETE_ARGS": "example_rich_tags.py del",
109+
},
110+
)
111+
assert (
112+
"delete:::Delete a user with USERNAME.\ndelete-all:::Delete ALL users in the database."
113+
) in result.stdout
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from importlib.machinery import ModuleSpec
2+
from typing import Union
3+
from unittest.mock import patch
4+
5+
import pytest
6+
from typer._completion_classes import _sanitize_help_text
7+
8+
9+
@pytest.mark.parametrize(
10+
"find_spec, help_text, expected",
11+
[
12+
(
13+
ModuleSpec("rich", loader=None),
14+
"help text without rich tags",
15+
"help text without rich tags",
16+
),
17+
(
18+
None,
19+
"help text without rich tags",
20+
"help text without rich tags",
21+
),
22+
(
23+
ModuleSpec("rich", loader=None),
24+
"help [bold]with[/] rich tags",
25+
"help with rich tags",
26+
),
27+
(
28+
None,
29+
"help [bold]with[/] rich tags",
30+
"help [bold]with[/] rich tags",
31+
),
32+
],
33+
)
34+
def test_sanitize_help_text(
35+
find_spec: Union[ModuleSpec, None], help_text: str, expected: str
36+
):
37+
with patch("importlib.util.find_spec", return_value=find_spec) as mock_find_spec:
38+
assert _sanitize_help_text(help_text) == expected
39+
mock_find_spec.assert_called_once_with("rich")

typer/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Typer, build great CLIs. Easy to code. Based on Python type hints."""
22

3-
__version__ = "0.15.1"
3+
__version__ = "0.15.2"
44

55
from shutil import get_terminal_size as get_terminal_size
66

0 commit comments

Comments
 (0)