Skip to content

Commit

Permalink
Merge branch 'master' into master_mateusoliveira
Browse files Browse the repository at this point in the history
  • Loading branch information
svlandeg committed Aug 19, 2024
2 parents 033370e + 29d1d1f commit 7a8c571
Show file tree
Hide file tree
Showing 12 changed files with 139 additions and 31 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,4 @@ jobs:
TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }}
run: python -m build
- name: Publish
uses: pypa/gh-action-pypi-publish@v1.8.11
uses: pypa/gh-action-pypi-publish@v1.9.0
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ dist
.mypy_cache
.idea
site
.coverage
htmlcov
.pytest_cache
coverage.xml
Expand Down
17 changes: 17 additions & 0 deletions docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,27 @@

## Latest Changes

### Internal

* 🙈 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).
* ⬆ Bump pypa/gh-action-pypi-publish from 1.8.11 to 1.9.0. PR [#865](https://github.com/fastapi/typer/pull/865) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Update pytest requirement from <8.0.0,>=4.4.0 to >=4.4.0,<9.0.0. PR [#915](https://github.com/fastapi/typer/pull/915) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ Update pytest-sugar requirement from <0.10.0,>=0.9.4 to >=0.9.4,<1.1.0. PR [#841](https://github.com/fastapi/typer/pull/841) by [@dependabot[bot]](https://github.com/apps/dependabot).

## 0.12.4

### Features

* ✨ Add support for Python 3.12, tests in CI and official marker. PR [#807](https://github.com/tiangolo/typer/pull/807) by [@ivantodorovich](https://github.com/ivantodorovich).

### Fixes

* 🐛 Fix support for `UnionType` (e.g. `str | None`) with Python 3.11. PR [#548](https://github.com/fastapi/typer/pull/548) by [@jonaslb](https://github.com/jonaslb).
* 🐛 Fix `zsh` autocompletion installation. PR [#237](https://github.com/fastapi/typer/pull/237) by [@alexjurkiewicz](https://github.com/alexjurkiewicz).
* 🐛 Fix usage of `Annotated` with future annotations in Python 3.7+. PR [#814](https://github.com/fastapi/typer/pull/814) by [@ivantodorovich](https://github.com/ivantodorovich).
* 🐛 Fix `shell_complete` not working for Arguments. PR [#737](https://github.com/fastapi/typer/pull/737) by [@bckohan](https://github.com/bckohan).

### Docs

* 📝 Update docs links, from tiangolo to new fastapi org. PR [#919](https://github.com/fastapi/typer/pull/919) by [@tiangolo](https://github.com/tiangolo).
Expand Down
6 changes: 3 additions & 3 deletions requirements-tests.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
-e .

pytest >=4.4.0,<8.0.0
pytest-cov >=2.10.0,<5.0.0
pytest >=4.4.0,<9.0.0
pytest-cov >=2.10.0,<6.0.0
coverage[toml] >=6.2,<8.0
pytest-xdist >=1.32.0,<4.0.0
pytest-sugar >=0.9.4,<0.10.0
pytest-sugar >=0.9.4,<1.1.0
mypy ==1.4.1
ruff ==0.2.0
# Needed explicitly by typer-slim
Expand Down
22 changes: 22 additions & 0 deletions tests/assets/completion_argument.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import click
import typer

app = typer.Typer()


def shell_complete(ctx: click.Context, param: click.Parameter, incomplete: str):
typer.echo(f"ctx: {ctx.info_name}", err=True)
typer.echo(f"arg is: {param.name}", err=True)
typer.echo(f"incomplete is: {incomplete}", err=True)
return ["Emma"]


@app.command(context_settings={"auto_envvar_prefix": "TEST"})
def main(name: str = typer.Argument(shell_complete=shell_complete)):
"""
Say hello.
"""


if __name__ == "__main__":
app()
26 changes: 26 additions & 0 deletions tests/test_future_annotations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from __future__ import annotations

import typer
from typer.testing import CliRunner
from typing_extensions import Annotated

runner = CliRunner()


def test_annotated():
app = typer.Typer()

@app.command()
def cmd(force: Annotated[bool, typer.Option("--force")] = False):
if force:
print("Forcing operation")
else:
print("Not forcing")

result = runner.invoke(app)
assert result.exit_code == 0, result.output
assert "Not forcing" in result.output

result = runner.invoke(app, ["--force"])
assert result.exit_code == 0, result.output
assert "Forcing operation" in result.output
19 changes: 19 additions & 0 deletions tests/test_others.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,25 @@ def main(name: str = typer.Option(..., callback=name_callback)):
assert "value is: Camila" in result.stdout


def test_completion_argument():
file_path = Path(__file__).parent / "assets/completion_argument.py"
result = subprocess.run(
[sys.executable, "-m", "coverage", "run", str(file_path), "E"],
capture_output=True,
encoding="utf-8",
env={
**os.environ,
"_COMPLETION_ARGUMENT.PY_COMPLETE": "complete_zsh",
"_TYPER_COMPLETE_ARGS": "completion_argument.py E",
"_TYPER_COMPLETE_TESTING": "True",
},
)
assert "Emma" in result.stdout or "_files" in result.stdout
assert "ctx: completion_argument" in result.stderr
assert "arg is: name" in result.stderr
assert "incomplete is: E" in result.stderr


def test_completion_untyped_parameters():
file_path = Path(__file__).parent / "assets/completion_no_types.py"
result = subprocess.run(
Expand Down
22 changes: 22 additions & 0 deletions tests/test_type_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import typer
from typer.testing import CliRunner

from .utils import needs_py310

runner = CliRunner()


Expand All @@ -29,6 +31,26 @@ def opt(user: Optional[str] = None):
assert "User: Camila" in result.output


@needs_py310
def test_union_type_optional():
app = typer.Typer()

@app.command()
def opt(user: str | None = None):
if user:
print(f"User: {user}")
else:
print("No user")

result = runner.invoke(app)
assert result.exit_code == 0
assert "No user" in result.output

result = runner.invoke(app, ["--user", "Camila"])
assert result.exit_code == 0
assert "User: Camila" in result.output


def test_optional_tuple():
app = typer.Typer()

Expand Down
2 changes: 1 addition & 1 deletion typer/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Typer, build great CLIs. Easy to code. Based on Python type hints."""

__version__ = "0.12.3"
__version__ = "0.12.4"

from shutil import get_terminal_size as get_terminal_size

Expand Down
20 changes: 10 additions & 10 deletions typer/_completion_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,16 +128,16 @@ def install_zsh(*, prog_name: str, complete_var: str, shell: str) -> Path:
zshrc_content = ""
if zshrc_path.is_file():
zshrc_content = zshrc_path.read_text()
completion_init_lines = [
"autoload -Uz compinit",
"compinit",
"zstyle ':completion:*' menu select",
"fpath+=~/.zfunc",
]
for line in completion_init_lines:
if line not in zshrc_content: # pragma: no cover
zshrc_content += f"\n{line}"
zshrc_content += "\n"
completion_line = "fpath+=~/.zfunc; autoload -Uz compinit; compinit"
if completion_line not in zshrc_content:
zshrc_content += f"\n{completion_line}\n"
style_line = "zstyle ':completion:*' menu select"
# TODO: consider setting the style only for the current program
# style_line = f"zstyle ':completion:*:*:{prog_name}:*' menu select"
# Install zstyle completion config only if the user doesn't have a customization
if "zstyle" not in zshrc_content:
zshrc_content += f"\n{style_line}\n"
zshrc_content = f"{zshrc_content.strip()}\n"
zshrc_path.write_text(zshrc_content)
# Install completion under ~/.zfunc/
path_obj = Path.home() / f".zfunc/_{prog_name}"
Expand Down
29 changes: 16 additions & 13 deletions typer/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import click

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 .docstring_automation import get_help_from_docstring, get_param_help_from_docstring
Expand Down Expand Up @@ -830,30 +831,31 @@ def get_click_param(
is_tuple = False
parameter_type: Any = None
is_flag = None
origin = getattr(main_type, "__origin__", None)
origin = get_origin(main_type)

if origin is not None:
# Handle Optional[SomeType]
if origin is Union:
# Handle SomeType | None and Optional[SomeType]
if is_union(origin):
types = []
for type_ in main_type.__args__:
for type_ in get_args(main_type):
if type_ is NoneType:
continue
types.append(type_)
assert len(types) == 1, "Typer Currently doesn't support Union types"
main_type = types[0]
origin = getattr(main_type, "__origin__", None)
origin = get_origin(main_type)
# Handle Tuples and Lists
if lenient_issubclass(origin, List):
main_type = main_type.__args__[0]
assert not getattr(
main_type, "__origin__", None
main_type = get_args(main_type)[0]
assert not get_origin(
main_type
), "List types with complex sub-types are not currently supported"
is_list = True
elif lenient_issubclass(origin, Tuple): # type: ignore
types = []
for type_ in main_type.__args__:
assert not getattr(
type_, "__origin__", None
for type_ in get_args(main_type):
assert not get_origin(
type_
), "Tuple types with complex sub-types are not currently supported"
types.append(
get_click_type(annotation=type_, parameter_info=parameter_info)
Expand All @@ -870,7 +872,7 @@ def get_click_param(
convertor=convertor, default_value=default_value
)
if is_tuple:
convertor = generate_tuple_convertor(main_type.__args__)
convertor = generate_tuple_convertor(get_args(main_type))
if isinstance(parameter_info, OptionInfo):
if main_type is bool and parameter_info.is_flag is not False:
is_flag = True
Expand Down Expand Up @@ -952,6 +954,7 @@ def get_click_param(
expose_value=parameter_info.expose_value,
is_eager=parameter_info.is_eager,
envvar=parameter_info.envvar,
shell_complete=parameter_info.shell_complete,
autocompletion=get_param_completion(parameter_info.autocompletion),
# Rich settings
rich_help_panel=parameter_info.rich_help_panel,
Expand Down Expand Up @@ -1024,7 +1027,7 @@ def get_param_completion(
incomplete_name = None
unassigned_params = list(parameters.values())
for param_sig in unassigned_params[:]:
origin = getattr(param_sig.annotation, "__origin__", None)
origin = get_origin(param_sig.annotation)
if lenient_issubclass(param_sig.annotation, click.Context):
ctx_name = param_sig.name
unassigned_params.remove(param_sig)
Expand Down
4 changes: 2 additions & 2 deletions typer/utils.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import inspect
import sys
from copy import copy
from typing import Any, Callable, Dict, List, Tuple, Type, cast, get_type_hints
from typing import Any, Callable, Dict, List, Tuple, Type, cast

from typing_extensions import Annotated
from typing_extensions import Annotated, get_type_hints

from ._typing import get_args, get_origin
from .models import ArgumentInfo, OptionInfo, ParameterInfo, ParamMeta
Expand Down

0 comments on commit 7a8c571

Please sign in to comment.