From a64e8748696ad95f2416e22ba74bb1d7aa8f5b2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 25 Dec 2025 10:27:01 +0100 Subject: [PATCH 1/5] =?UTF-8?q?=E2=9E=96=20Drop=20support=20for=20Python?= =?UTF-8?q?=203.8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ae0dd2425d..eb13579db6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ description = "Typer, build great CLIs. Easy to code. Based on Python type hints authors = [ {name = "Sebastián Ramírez", email = "tiangolo@gmail.com"}, ] -requires-python = ">=3.8" +requires-python = ">=3.9" classifiers = [ "Intended Audience :: Information Technology", "Intended Audience :: System Administrators", @@ -26,7 +26,6 @@ classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", From a143e6bc002df08d17b840f815513eeeab4edc65 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 25 Dec 2025 09:27:38 +0000 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20for?= =?UTF-8?q?mat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/assets/cli/rich_formatted_app.py | 3 ++- tests/test_ambiguous_params.py | 3 ++- tests/test_annotated.py | 2 +- tests/test_future_annotations.py | 3 ++- tests/test_launch.py | 23 ++++++++++++++--------- tests/test_others.py | 2 +- typer/completion.py | 3 ++- typer/core.py | 3 +-- typer/main.py | 3 ++- typer/models.py | 2 +- typer/rich_utils.py | 3 ++- typer/testing.py | 3 ++- 12 files changed, 32 insertions(+), 21 deletions(-) diff --git a/tests/assets/cli/rich_formatted_app.py b/tests/assets/cli/rich_formatted_app.py index 54d8a52267..5d57a338e4 100644 --- a/tests/assets/cli/rich_formatted_app.py +++ b/tests/assets/cli/rich_formatted_app.py @@ -1,5 +1,6 @@ +from typing import Annotated + import typer -from typing_extensions import Annotated app = typer.Typer(rich_markup_mode="rich") diff --git a/tests/test_ambiguous_params.py b/tests/test_ambiguous_params.py index 0693c8e9aa..69cdf5f011 100644 --- a/tests/test_ambiguous_params.py +++ b/tests/test_ambiguous_params.py @@ -1,3 +1,5 @@ +from typing import Annotated + import pytest import typer from typer.testing import CliRunner @@ -8,7 +10,6 @@ MultipleTyperAnnotationsError, _split_annotation_from_typer_annotations, ) -from typing_extensions import Annotated runner = CliRunner() diff --git a/tests/test_annotated.py b/tests/test_annotated.py index c1be5b88d7..6c10a9910b 100644 --- a/tests/test_annotated.py +++ b/tests/test_annotated.py @@ -1,9 +1,9 @@ import sys from pathlib import Path +from typing import Annotated import typer from typer.testing import CliRunner -from typing_extensions import Annotated from .utils import needs_py310 diff --git a/tests/test_future_annotations.py b/tests/test_future_annotations.py index e820cd0923..e98040a340 100644 --- a/tests/test_future_annotations.py +++ b/tests/test_future_annotations.py @@ -1,8 +1,9 @@ from __future__ import annotations +from typing import Annotated + import typer from typer.testing import CliRunner -from typing_extensions import Annotated runner = CliRunner() diff --git a/tests/test_launch.py b/tests/test_launch.py index 75aaa2f091..e9b66ab081 100644 --- a/tests/test_launch.py +++ b/tests/test_launch.py @@ -16,9 +16,11 @@ ], ) def test_launch_url_unix(system: str, command: str): - with patch("platform.system", return_value=system), patch( - "shutil.which", return_value=True - ), patch("subprocess.Popen") as mock_popen: + with ( + patch("platform.system", return_value=system), + patch("shutil.which", return_value=True), + patch("subprocess.Popen") as mock_popen, + ): typer.launch(url) mock_popen.assert_called_once_with( @@ -27,18 +29,21 @@ def test_launch_url_unix(system: str, command: str): def test_launch_url_windows(): - with patch("platform.system", return_value="Windows"), patch( - "webbrowser.open" - ) as mock_webbrowser_open: + with ( + patch("platform.system", return_value="Windows"), + patch("webbrowser.open") as mock_webbrowser_open, + ): typer.launch(url) mock_webbrowser_open.assert_called_once_with(url) def test_launch_url_no_xdg_open(): - with patch("platform.system", return_value="Linux"), patch( - "shutil.which", return_value=None - ), patch("webbrowser.open") as mock_webbrowser_open: + with ( + patch("platform.system", return_value="Linux"), + patch("shutil.which", return_value=None), + patch("webbrowser.open") as mock_webbrowser_open, + ): typer.launch(url) mock_webbrowser_open.assert_called_once_with(url) diff --git a/tests/test_others.py b/tests/test_others.py index ca60e1ed81..7617d0b1c9 100644 --- a/tests/test_others.py +++ b/tests/test_others.py @@ -3,6 +3,7 @@ import sys import typing from pathlib import Path +from typing import Annotated from unittest import mock import click @@ -14,7 +15,6 @@ from typer.main import solve_typer_info_defaults, solve_typer_info_help from typer.models import ParameterInfo, TyperInfo from typer.testing import CliRunner -from typing_extensions import Annotated from .utils import requires_completion_permission diff --git a/typer/completion.py b/typer/completion.py index b2080c05b1..d320325f84 100644 --- a/typer/completion.py +++ b/typer/completion.py @@ -1,6 +1,7 @@ import os import sys -from typing import Any, MutableMapping, Tuple +from collections.abc import MutableMapping +from typing import Any, Tuple import click diff --git a/typer/core.py b/typer/core.py index a8cb70e1b0..1e920ac346 100644 --- a/typer/core.py +++ b/typer/core.py @@ -3,6 +3,7 @@ import inspect import os import sys +from collections.abc import MutableMapping, Sequence from difflib import get_close_matches from enum import Enum from gettext import gettext as _ @@ -11,9 +12,7 @@ Callable, Dict, List, - MutableMapping, Optional, - Sequence, TextIO, Tuple, Union, diff --git a/typer/main.py b/typer/main.py index cf45a98d56..eea5d7b1a6 100644 --- a/typer/main.py +++ b/typer/main.py @@ -5,13 +5,14 @@ import subprocess import sys import traceback +from collections.abc import Sequence from datetime import datetime from enum import Enum from functools import update_wrapper from pathlib import Path from traceback import FrameSummary, StackSummary from types import TracebackType -from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, Union +from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union from uuid import UUID import click diff --git a/typer/models.py b/typer/models.py index e0bddb965b..4f6b80d38b 100644 --- a/typer/models.py +++ b/typer/models.py @@ -1,5 +1,6 @@ import inspect import io +from collections.abc import Sequence from typing import ( TYPE_CHECKING, Any, @@ -7,7 +8,6 @@ Dict, List, Optional, - Sequence, Type, TypeVar, Union, diff --git a/typer/rich_utils.py b/typer/rich_utils.py index d6627232de..ae0c82afb8 100644 --- a/typer/rich_utils.py +++ b/typer/rich_utils.py @@ -3,9 +3,10 @@ import inspect import io from collections import defaultdict +from collections.abc import Iterable from gettext import gettext as _ from os import getenv -from typing import Any, DefaultDict, Dict, Iterable, List, Literal, Optional, Union +from typing import Any, DefaultDict, Dict, List, Literal, Optional, Union import click from rich import box diff --git a/typer/testing.py b/typer/testing.py index ebd3e1553a..61f337b789 100644 --- a/typer/testing.py +++ b/typer/testing.py @@ -1,4 +1,5 @@ -from typing import IO, Any, Mapping, Optional, Sequence, Union +from collections.abc import Mapping, Sequence +from typing import IO, Any, Optional, Union from click.testing import CliRunner as ClickCliRunner # noqa from click.testing import Result From 864c55b309d930ba1b67d560897bffd229fb3272 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 25 Dec 2025 10:36:14 +0100 Subject: [PATCH 3/5] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Upgrade=20Python=20syn?= =?UTF-8?q?tax=20to=203.9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pdm_build.py | 16 ++++---- scripts/mkdocs_hooks.py | 8 ++-- tests/test_others.py | 8 ++-- tests/test_rich_markup_mode.py | 12 +++--- tests/test_type_conversion.py | 16 ++++---- typer/_completion_classes.py | 18 ++++----- typer/_completion_shared.py | 4 +- typer/_typing.py | 16 ++++---- typer/cli.py | 4 +- typer/completion.py | 4 +- typer/core.py | 43 ++++++++++----------- typer/main.py | 68 +++++++++++++++++----------------- typer/models.py | 39 +++++++++---------- typer/params.py | 50 ++++++++++++------------- typer/rich_utils.py | 30 +++++++-------- typer/utils.py | 26 ++++++------- 16 files changed, 176 insertions(+), 186 deletions(-) diff --git a/pdm_build.py b/pdm_build.py index 4629801f0e..874ffad95c 100644 --- a/pdm_build.py +++ b/pdm_build.py @@ -1,5 +1,5 @@ import os -from typing import Any, Dict, List +from typing import Any from pdm.backend.hooks import Context @@ -11,34 +11,34 @@ def pdm_build_initialize(context: Context): # Get main version version = metadata["version"] # Get package names to keep in sync with the same main version - sync_dependencies: List[str] = context.config.data["tool"]["tiangolo"][ + sync_dependencies: list[str] = context.config.data["tool"]["tiangolo"][ "_internal-slim-build" ]["sync-dependencies"] # Get custom config for the current package, from the env var - config: Dict[str, Any] = context.config.data["tool"]["tiangolo"][ + config: dict[str, Any] = context.config.data["tool"]["tiangolo"][ "_internal-slim-build" ]["packages"][TIANGOLO_BUILD_PACKAGE] - project_config: Dict[str, Any] = config["project"] + project_config: dict[str, Any] = config["project"] # Get main optional dependencies, extras - optional_dependencies: Dict[str, List[str]] = metadata.get( + optional_dependencies: dict[str, list[str]] = metadata.get( "optional-dependencies", {} ) # Get custom optional dependencies name to always include in this (non-slim) package - include_optional_dependencies: List[str] = config.get( + include_optional_dependencies: list[str] = config.get( "include-optional-dependencies", [] ) # Override main [project] configs with custom configs for this package for key, value in project_config.items(): metadata[key] = value # Get custom build config for the current package - build_config: Dict[str, Any] = ( + build_config: dict[str, Any] = ( config.get("tool", {}).get("pdm", {}).get("build", {}) ) # Override PDM build config with custom build config for this package for key, value in build_config.items(): context.config.build_config[key] = value # Get main dependencies - dependencies: List[str] = metadata.get("dependencies", []) + dependencies: list[str] = metadata.get("dependencies", []) # Add optional dependencies to the default dependencies for this (non-slim) package for include_optional in include_optional_dependencies: optional_dependencies_group = optional_dependencies.get(include_optional, []) diff --git a/scripts/mkdocs_hooks.py b/scripts/mkdocs_hooks.py index f09e9a99df..0e8789461b 100644 --- a/scripts/mkdocs_hooks.py +++ b/scripts/mkdocs_hooks.py @@ -1,4 +1,4 @@ -from typing import Any, List, Union +from typing import Any, Union from mkdocs.config.defaults import MkDocsConfig from mkdocs.structure.files import Files @@ -7,9 +7,9 @@ def generate_renamed_section_items( - items: List[Union[Page, Section, Link]], *, config: MkDocsConfig -) -> List[Union[Page, Section, Link]]: - new_items: List[Union[Page, Section, Link]] = [] + items: list[Union[Page, Section, Link]], *, config: MkDocsConfig +) -> list[Union[Page, Section, Link]]: + new_items: list[Union[Page, Section, Link]] = [] for item in items: if isinstance(item, Section): new_title = item.title diff --git a/tests/test_others.py b/tests/test_others.py index 7617d0b1c9..735cc68e69 100644 --- a/tests/test_others.py +++ b/tests/test_others.py @@ -148,14 +148,14 @@ def main(name: str = typer.Option(..., callback=name_callback)): def test_callback_4_list_none(): app = typer.Typer() - def names_callback(ctx, param, values: typing.Optional[typing.List[str]]): + def names_callback(ctx, param, values: typing.Optional[list[str]]): if values is None: return values return [value.upper() for value in values] @app.command() def main( - names: typing.Optional[typing.List[str]] = typer.Option( + names: typing.Optional[list[str]] = typer.Option( None, "--name", callback=names_callback ), ): @@ -172,14 +172,14 @@ def main( def test_empty_list_default_generator(): - def empty_list() -> typing.List[str]: + def empty_list() -> list[str]: return [] app = typer.Typer() @app.command() def main( - names: Annotated[typing.List[str], typer.Option(default_factory=empty_list)], + names: Annotated[list[str], typer.Option(default_factory=empty_list)], ): print(names) diff --git a/tests/test_rich_markup_mode.py b/tests/test_rich_markup_mode.py index 99f000bac6..b06b7dcbe6 100644 --- a/tests/test_rich_markup_mode.py +++ b/tests/test_rich_markup_mode.py @@ -1,5 +1,3 @@ -from typing import List - import pytest import typer import typer.completion @@ -58,7 +56,7 @@ def main(arg: str): ), ], ) -def test_markup_mode_newline_pr815(mode: str, lines: List[str]): +def test_markup_mode_newline_pr815(mode: str, lines: list[str]): app = typer.Typer(rich_markup_mode=mode) @app.command() @@ -96,7 +94,7 @@ def main(arg: str): pytest.param(None, ["First line", "", "Line 1 Line 2 Line 3", ""]), ], ) -def test_markup_mode_newline_issue447(mode: str, lines: List[str]): +def test_markup_mode_newline_issue447(mode: str, lines: list[str]): app = typer.Typer(rich_markup_mode=mode) @app.command() @@ -169,7 +167,7 @@ def main(arg: str): ), ], ) -def test_markup_mode_newline_mixed(mode: str, lines: List[str]): +def test_markup_mode_newline_mixed(mode: str, lines: list[str]): app = typer.Typer(rich_markup_mode=mode) @app.command() @@ -213,7 +211,7 @@ def main(arg: str): pytest.param(None, ["First line", "", "- 1 - 2 - 3", ""]), ], ) -def test_markup_mode_bullets_single_newline(mode: str, lines: List[str]): +def test_markup_mode_bullets_single_newline(mode: str, lines: list[str]): app = typer.Typer(rich_markup_mode=mode) @app.command() @@ -256,7 +254,7 @@ def main(arg: str): (None, ["First line", "", "- 1", "", "- 2", "", "- 3", ""]), ], ) -def test_markup_mode_bullets_double_newline(mode: str, lines: List[str]): +def test_markup_mode_bullets_double_newline(mode: str, lines: list[str]): app = typer.Typer(rich_markup_mode=mode) @app.command() diff --git a/tests/test_type_conversion.py b/tests/test_type_conversion.py index 904a686d2e..0de0960522 100644 --- a/tests/test_type_conversion.py +++ b/tests/test_type_conversion.py @@ -1,6 +1,6 @@ from enum import Enum from pathlib import Path -from typing import Any, List, Optional, Tuple +from typing import Any, Optional import click import pytest @@ -55,7 +55,7 @@ def test_optional_tuple(): app = typer.Typer() @app.command() - def opt(number: Optional[Tuple[int, int]] = None): + def opt(number: Optional[tuple[int, int]] = None): if number: print(f"Number: {number}") else: @@ -90,7 +90,7 @@ class SomeEnum(Enum): @pytest.mark.parametrize( "type_annotation", - [List[Path], List[SomeEnum], List[str]], + [list[Path], list[SomeEnum], list[str]], ) def test_list_parameters_convert_to_lists(type_annotation): # Lists containing objects that are converted by Click (i.e. not Path or Enum) @@ -111,11 +111,11 @@ def list_conversion(container: type_annotation): @pytest.mark.parametrize( "type_annotation", [ - Tuple[str, str], - Tuple[str, Path], - Tuple[Path, Path], - Tuple[str, SomeEnum], - Tuple[SomeEnum, SomeEnum], + tuple[str, str], + tuple[str, Path], + tuple[Path, Path], + tuple[str, SomeEnum], + tuple[SomeEnum, SomeEnum], ], ) def test_tuple_parameter_elements_are_converted_recursively(type_annotation): diff --git a/typer/_completion_classes.py b/typer/_completion_classes.py index b726865e93..d08c76ac5e 100644 --- a/typer/_completion_classes.py +++ b/typer/_completion_classes.py @@ -2,7 +2,7 @@ import os import re import sys -from typing import Any, Dict, List, Tuple +from typing import Any import click import click.parser @@ -38,14 +38,14 @@ class BashComplete(click.shell_completion.BashComplete): name = Shells.bash.value source_template = COMPLETION_SCRIPT_BASH - def source_vars(self) -> Dict[str, Any]: + def source_vars(self) -> dict[str, Any]: return { "complete_func": self.func_name, "autocomplete_var": self.complete_var, "prog_name": self.prog_name, } - def get_completion_args(self) -> Tuple[List[str], str]: + def get_completion_args(self) -> tuple[list[str], str]: cwords = click_split_arg_string(os.environ["COMP_WORDS"]) cword = int(os.environ["COMP_CWORD"]) args = cwords[1:cword] @@ -74,14 +74,14 @@ class ZshComplete(click.shell_completion.ZshComplete): name = Shells.zsh.value source_template = COMPLETION_SCRIPT_ZSH - def source_vars(self) -> Dict[str, Any]: + def source_vars(self) -> dict[str, Any]: return { "complete_func": self.func_name, "autocomplete_var": self.complete_var, "prog_name": self.prog_name, } - def get_completion_args(self) -> Tuple[List[str], str]: + def get_completion_args(self) -> tuple[list[str], str]: completion_args = os.getenv("_TYPER_COMPLETE_ARGS", "") cwords = click_split_arg_string(completion_args) args = cwords[1:] @@ -125,14 +125,14 @@ class FishComplete(click.shell_completion.FishComplete): name = Shells.fish.value source_template = COMPLETION_SCRIPT_FISH - def source_vars(self) -> Dict[str, Any]: + def source_vars(self) -> dict[str, Any]: return { "complete_func": self.func_name, "autocomplete_var": self.complete_var, "prog_name": self.prog_name, } - def get_completion_args(self) -> Tuple[List[str], str]: + def get_completion_args(self) -> tuple[list[str], str]: completion_args = os.getenv("_TYPER_COMPLETE_ARGS", "") cwords = click_split_arg_string(completion_args) args = cwords[1:] @@ -178,14 +178,14 @@ class PowerShellComplete(click.shell_completion.ShellComplete): name = Shells.powershell.value source_template = COMPLETION_SCRIPT_POWER_SHELL - def source_vars(self) -> Dict[str, Any]: + def source_vars(self) -> dict[str, Any]: return { "complete_func": self.func_name, "autocomplete_var": self.complete_var, "prog_name": self.prog_name, } - def get_completion_args(self) -> Tuple[List[str], str]: + 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_split_arg_string(completion_args) diff --git a/typer/_completion_shared.py b/typer/_completion_shared.py index 21cc98bc5c..4c4d79dda7 100644 --- a/typer/_completion_shared.py +++ b/typer/_completion_shared.py @@ -3,7 +3,7 @@ import subprocess from enum import Enum from pathlib import Path -from typing import Optional, Tuple, Union +from typing import Optional, Union import click from typer.core import HAS_SHELLINGHAM @@ -206,7 +206,7 @@ def install( shell: Optional[str] = None, prog_name: Optional[str] = None, complete_var: Optional[str] = None, -) -> Tuple[str, Path]: +) -> tuple[str, Path]: prog_name = prog_name or click.get_current_context().find_root().info_name assert prog_name if complete_var is None: diff --git a/typer/_typing.py b/typer/_typing.py index 65e6875b0a..15d0e91b65 100644 --- a/typer/_typing.py +++ b/typer/_typing.py @@ -10,8 +10,6 @@ Callable, Literal, Optional, - Tuple, - Type, Union, get_args, get_origin, @@ -20,13 +18,13 @@ if sys.version_info < (3, 10): - def is_union(tp: Optional[Type[Any]]) -> bool: + def is_union(tp: Optional[type[Any]]) -> bool: return tp is Union else: import types - def is_union(tp: Optional[Type[Any]]) -> bool: + def is_union(tp: Optional[type[Any]]) -> bool: return tp is Union or tp is types.UnionType # noqa: E721 @@ -48,7 +46,7 @@ def is_union(tp: Optional[Type[Any]]) -> bool: NoneType = None.__class__ -NONE_TYPES: Tuple[Any, Any, Any] = (None, NoneType, Literal[None]) +NONE_TYPES: tuple[Any, Any, Any] = (None, NoneType, Literal[None]) def is_none_type(type_: Any) -> bool: @@ -58,21 +56,21 @@ def is_none_type(type_: Any) -> bool: return False -def is_callable_type(type_: Type[Any]) -> bool: +def is_callable_type(type_: type[Any]) -> bool: return type_ is Callable or get_origin(type_) is Callable -def is_literal_type(type_: Type[Any]) -> bool: +def is_literal_type(type_: type[Any]) -> bool: import typing_extensions return get_origin(type_) in (Literal, typing_extensions.Literal) -def literal_values(type_: Type[Any]) -> Tuple[Any, ...]: +def literal_values(type_: type[Any]) -> tuple[Any, ...]: return get_args(type_) -def all_literal_values(type_: Type[Any]) -> Tuple[Any, ...]: +def all_literal_values(type_: type[Any]) -> tuple[Any, ...]: """ This method is used to retrieve all Literal values as Literal can be used recursively (see https://www.python.org/dev/peps/pep-0586) diff --git a/typer/cli.py b/typer/cli.py index 16b37c515d..11e9ce0430 100644 --- a/typer/cli.py +++ b/typer/cli.py @@ -2,7 +2,7 @@ import re import sys from pathlib import Path -from typing import Any, List, Optional +from typing import Any, Optional import click import typer @@ -53,7 +53,7 @@ def maybe_update_state(ctx: click.Context) -> None: class TyperCLIGroup(typer.core.TyperGroup): - def list_commands(self, ctx: click.Context) -> List[str]: + def list_commands(self, ctx: click.Context) -> list[str]: self.maybe_add_run(ctx) return super().list_commands(ctx) diff --git a/typer/completion.py b/typer/completion.py index d320325f84..db87f83e3f 100644 --- a/typer/completion.py +++ b/typer/completion.py @@ -1,7 +1,7 @@ import os import sys from collections.abc import MutableMapping -from typing import Any, Tuple +from typing import Any import click @@ -15,7 +15,7 @@ _click_patched = False -def get_completion_inspect_parameters() -> Tuple[ParamMeta, ParamMeta]: +def get_completion_inspect_parameters() -> tuple[ParamMeta, ParamMeta]: completion_init() test_disable_detection = os.getenv("_TYPER_COMPLETE_TEST_DISABLE_SHELL_DETECTION") if HAS_SHELLINGHAM and not test_disable_detection: diff --git a/typer/core.py b/typer/core.py index 1e920ac346..83eb8ec52b 100644 --- a/typer/core.py +++ b/typer/core.py @@ -10,11 +10,8 @@ from typing import ( Any, Callable, - Dict, - List, Optional, TextIO, - Tuple, Union, cast, ) @@ -40,7 +37,7 @@ # Copy from click.parser._split_opt -def _split_opt(opt: str) -> Tuple[str, str]: +def _split_opt(opt: str) -> tuple[str, str]: first = opt[:1] if first.isalnum(): return "", opt @@ -53,7 +50,7 @@ def _typer_param_setup_autocompletion_compat( self: click.Parameter, *, autocompletion: Optional[ - Callable[[click.Context, List[str], str], List[Union[Tuple[str, str], str]]] + Callable[[click.Context, list[str], str], list[Union[tuple[str, str], str]]] ] = None, ) -> None: if self._custom_shell_complete is not None: @@ -70,7 +67,7 @@ def _typer_param_setup_autocompletion_compat( def compat_autocompletion( ctx: click.Context, param: click.core.Parameter, incomplete: str - ) -> List["click.shell_completion.CompletionItem"]: + ) -> list["click.shell_completion.CompletionItem"]: from click.shell_completion import CompletionItem out = [] @@ -95,7 +92,7 @@ def _get_default_string( *, ctx: click.Context, show_default_is_str: bool, - default_value: Union[List[Any], Tuple[Any, ...], str, Callable[..., Any], Any], + default_value: Union[list[Any], tuple[Any, ...], str, Callable[..., Any], Any], ) -> str: # Extracted from click.core.Option.get_help_record() to be reused by # rich_utils avoiding RegEx hacks @@ -256,7 +253,7 @@ def __init__( self, *, # Parameter - param_decls: List[str], + param_decls: list[str], type: Optional[Any] = None, required: Optional[bool] = None, default: Optional[Any] = None, @@ -265,13 +262,13 @@ def __init__( metavar: Optional[str] = None, expose_value: bool = True, is_eager: bool = False, - envvar: Optional[Union[str, List[str]]] = None, + envvar: Optional[Union[str, list[str]]] = None, # Note that shell_complete is not fully supported and will be removed in future versions # TODO: Remove shell_complete in a future version (after 0.16.0) shell_complete: Optional[ Callable[ [click.Context, click.Parameter, str], - Union[List["click.shell_completion.CompletionItem"], List[str]], + Union[list["click.shell_completion.CompletionItem"], list[str]], ] ] = None, autocompletion: Optional[Callable[..., Any]] = None, @@ -311,7 +308,7 @@ def _get_default_string( *, ctx: click.Context, show_default_is_str: bool, - default_value: Union[List[Any], Tuple[Any, ...], str, Callable[..., Any], Any], + default_value: Union[list[Any], tuple[Any, ...], str, Callable[..., Any], Any], ) -> str: return _get_default_string( self, @@ -325,7 +322,7 @@ def _extract_default_help_str( ) -> Optional[Union[Any, Callable[[], Any]]]: return _extract_default_help_str(self, ctx=ctx) - def get_help_record(self, ctx: click.Context) -> Optional[Tuple[str, str]]: + def get_help_record(self, ctx: click.Context) -> Optional[tuple[str, str]]: # Modified version of click.core.Option.get_help_record() # to support Arguments if self.hidden: @@ -414,7 +411,7 @@ def __init__( self, *, # Parameter - param_decls: List[str], + param_decls: list[str], type: Optional[Union[click.types.ParamType, Any]] = None, required: Optional[bool] = None, default: Optional[Any] = None, @@ -423,13 +420,13 @@ def __init__( metavar: Optional[str] = None, expose_value: bool = True, is_eager: bool = False, - envvar: Optional[Union[str, List[str]]] = None, + envvar: Optional[Union[str, list[str]]] = None, # Note that shell_complete is not fully supported and will be removed in future versions # TODO: Remove shell_complete in a future version (after 0.16.0) shell_complete: Optional[ Callable[ [click.Context, click.Parameter, str], - Union[List["click.shell_completion.CompletionItem"], List[str]], + Union[list["click.shell_completion.CompletionItem"], list[str]], ] ] = None, autocompletion: Optional[Callable[..., Any]] = None, @@ -484,7 +481,7 @@ def _get_default_string( *, ctx: click.Context, show_default_is_str: bool, - default_value: Union[List[Any], Tuple[Any, ...], str, Callable[..., Any], Any], + default_value: Union[list[Any], tuple[Any, ...], str, Callable[..., Any], Any], ) -> str: return _get_default_string( self, @@ -506,7 +503,7 @@ def make_metavar(self, ctx: Union[click.Context, None] = None) -> str: # Click < 8.2 return super().make_metavar() # type: ignore[call-arg] - def get_help_record(self, ctx: click.Context) -> Optional[Tuple[str, str]]: + def get_help_record(self, ctx: click.Context) -> Optional[tuple[str, str]]: # Duplicate all of Click's logic only to modify a single line, to allow boolean # flags with only names for False values as it's currently supported by Typer # Ref: https://typer.tiangolo.com/tutorial/parameter-types/bool/#only-names-for-false @@ -663,9 +660,9 @@ def __init__( self, name: Optional[str], *, - context_settings: Optional[Dict[str, Any]] = None, + context_settings: Optional[dict[str, Any]] = None, callback: Optional[Callable[..., Any]] = None, - params: Optional[List[click.Parameter]] = None, + params: Optional[list[click.Parameter]] = None, help: Optional[str] = None, epilog: Optional[str] = None, short_help: Optional[str] = None, @@ -748,7 +745,7 @@ def __init__( *, name: Optional[str] = None, commands: Optional[ - Union[Dict[str, click.Command], Sequence[click.Command]] + Union[dict[str, click.Command], Sequence[click.Command]] ] = None, # Rich settings rich_markup_mode: MarkupMode = DEFAULT_MARKUP_MODE, @@ -778,8 +775,8 @@ def _main_shell_completion( ) def resolve_command( - self, ctx: click.Context, args: List[str] - ) -> Tuple[Optional[str], Optional[click.Command], List[str]]: + self, ctx: click.Context, args: list[str] + ) -> tuple[Optional[str], Optional[click.Command], list[str]]: try: return super().resolve_command(ctx, args) except click.UsageError as e: @@ -825,7 +822,7 @@ def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> Non markup_mode=self.rich_markup_mode, ) - def list_commands(self, ctx: click.Context) -> List[str]: + 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)""" diff --git a/typer/main.py b/typer/main.py index eea5d7b1a6..dbff2b8986 100644 --- a/typer/main.py +++ b/typer/main.py @@ -12,7 +12,7 @@ from pathlib import Path from traceback import FrameSummary, StackSummary from types import TracebackType -from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union +from typing import Any, Callable, Optional, Union from uuid import UUID import click @@ -56,7 +56,7 @@ def except_hook( - exc_type: Type[BaseException], exc_value: BaseException, tb: Optional[TracebackType] + exc_type: type[BaseException], exc_value: BaseException, tb: Optional[TracebackType] ) -> None: exception_config: Union[DeveloperExceptionConfig, None] = getattr( exc_value, _typer_developer_exception_attr_name, None @@ -83,7 +83,7 @@ def except_hook( console_stderr.print(rich_tb) return tb_exc = traceback.TracebackException.from_exception(exc) - stack: List[FrameSummary] = [] + stack: list[FrameSummary] = [] for frame in tb_exc.stack: if any(frame.filename.startswith(path) for path in internal_dir_names): if not exception_config.pretty_exceptions_short: @@ -106,7 +106,7 @@ def except_hook( return -def get_install_completion_arguments() -> Tuple[click.Parameter, click.Parameter]: +def get_install_completion_arguments() -> tuple[click.Parameter, click.Parameter]: install_param, show_param = get_completion_inspect_parameters() click_install_param, _ = get_click_param(install_param) click_show_param, _ = get_click_param(show_param) @@ -118,14 +118,14 @@ def __init__( self, *, name: Optional[str] = Default(None), - cls: Optional[Type[TyperGroup]] = Default(None), + cls: Optional[type[TyperGroup]] = Default(None), invoke_without_command: bool = Default(False), no_args_is_help: bool = Default(False), subcommand_metavar: Optional[str] = Default(None), chain: bool = Default(False), result_callback: Optional[Callable[..., Any]] = Default(None), # Command - context_settings: Optional[Dict[Any, Any]] = Default(None), + context_settings: Optional[dict[Any, Any]] = Default(None), callback: Optional[Callable[..., Any]] = Default(None), help: Optional[str] = Default(None), epilog: Optional[str] = Default(None), @@ -168,21 +168,21 @@ def __init__( hidden=hidden, deprecated=deprecated, ) - self.registered_groups: List[TyperInfo] = [] - self.registered_commands: List[CommandInfo] = [] + self.registered_groups: list[TyperInfo] = [] + self.registered_commands: list[CommandInfo] = [] self.registered_callback: Optional[TyperInfo] = None def callback( self, *, - cls: Optional[Type[TyperGroup]] = Default(None), + cls: Optional[type[TyperGroup]] = Default(None), invoke_without_command: bool = Default(False), no_args_is_help: bool = Default(False), subcommand_metavar: Optional[str] = Default(None), chain: bool = Default(False), result_callback: Optional[Callable[..., Any]] = Default(None), # Command - context_settings: Optional[Dict[Any, Any]] = Default(None), + context_settings: Optional[dict[Any, Any]] = Default(None), help: Optional[str] = Default(None), epilog: Optional[str] = Default(None), short_help: Optional[str] = Default(None), @@ -222,8 +222,8 @@ def command( self, name: Optional[str] = None, *, - cls: Optional[Type[TyperCommand]] = None, - context_settings: Optional[Dict[Any, Any]] = None, + cls: Optional[type[TyperCommand]] = None, + context_settings: Optional[dict[Any, Any]] = None, help: Optional[str] = None, epilog: Optional[str] = None, short_help: Optional[str] = None, @@ -268,14 +268,14 @@ def add_typer( typer_instance: "Typer", *, name: Optional[str] = Default(None), - cls: Optional[Type[TyperGroup]] = Default(None), + cls: Optional[type[TyperGroup]] = Default(None), invoke_without_command: bool = Default(False), no_args_is_help: bool = Default(False), subcommand_metavar: Optional[str] = Default(None), chain: bool = Default(False), result_callback: Optional[Callable[..., Any]] = Default(None), # Command - context_settings: Optional[Dict[Any, Any]] = Default(None), + context_settings: Optional[dict[Any, Any]] = Default(None), callback: Optional[Callable[..., Any]] = Default(None), help: Optional[str] = Default(None), epilog: Optional[str] = Default(None), @@ -436,7 +436,7 @@ def solve_typer_info_help(typer_info: TyperInfo) -> str: def solve_typer_info_defaults(typer_info: TyperInfo) -> TyperInfo: - values: Dict[str, Any] = {} + values: dict[str, Any] = {} for name, value in typer_info.__dict__.items(): # Priority 1: Value was set in app.add_typer() if not isinstance(value, DefaultPlaceholder): @@ -480,7 +480,7 @@ def get_group_from_info( assert group_info.typer_instance, ( "A Typer instance is needed to generate a Click Group" ) - commands: Dict[str, click.Command] = {} + commands: dict[str, click.Command] = {} for command_info in group_info.typer_instance.registered_commands: command = get_command_from_info( command_info=command_info, @@ -554,7 +554,7 @@ def get_command_name(name: str) -> str: def get_params_convertors_ctx_param_name_from_function( callback: Optional[Callable[..., Any]], -) -> Tuple[List[Union[click.Argument, click.Option]], Dict[str, Any], Optional[str]]: +) -> tuple[list[Union[click.Argument, click.Option]], dict[str, Any], Optional[str]]: params = [] convertors = {} context_param_name = None @@ -633,7 +633,7 @@ def param_path_convertor(value: Optional[str] = None) -> Optional[Path]: return None -def generate_enum_convertor(enum: Type[Enum]) -> Callable[[Any], Any]: +def generate_enum_convertor(enum: type[Enum]) -> Callable[[Any], Any]: val_map = {str(val.value): val for val in enum} def convertor(value: Any) -> Any: @@ -648,8 +648,8 @@ def convertor(value: Any) -> Any: def generate_list_convertor( convertor: Optional[Callable[[Any], Any]], default_value: Optional[Any] -) -> Callable[[Optional[Sequence[Any]]], Optional[List[Any]]]: - def internal_convertor(value: Optional[Sequence[Any]]) -> Optional[List[Any]]: +) -> Callable[[Optional[Sequence[Any]]], Optional[list[Any]]]: + def internal_convertor(value: Optional[Sequence[Any]]) -> Optional[list[Any]]: if (value is None) or (default_value is None and len(value) == 0): return None return [convertor(v) if convertor else v for v in value] @@ -659,12 +659,12 @@ def internal_convertor(value: Optional[Sequence[Any]]) -> Optional[List[Any]]: def generate_tuple_convertor( types: Sequence[Any], -) -> Callable[[Optional[Tuple[Any, ...]]], Optional[Tuple[Any, ...]]]: +) -> Callable[[Optional[tuple[Any, ...]]], Optional[tuple[Any, ...]]]: convertors = [determine_type_convertor(type_) for type_ in types] def internal_convertor( - param_args: Optional[Tuple[Any, ...]], - ) -> Optional[Tuple[Any, ...]]: + param_args: Optional[tuple[Any, ...]], + ) -> Optional[tuple[Any, ...]]: if param_args is None: return None return tuple( @@ -679,7 +679,7 @@ def get_callback( *, callback: Optional[Callable[..., Any]] = None, params: Sequence[click.Parameter] = [], - convertors: Optional[Dict[str, Callable[[str], Any]]] = None, + convertors: Optional[dict[str, Callable[[str], Any]]] = None, context_param_name: Optional[str] = None, pretty_exceptions_short: bool, ) -> Optional[Callable[..., Any]]: @@ -687,7 +687,7 @@ def get_callback( if not callback: return None parameters = get_params_from_function(callback) - use_params: Dict[str, Any] = {} + use_params: dict[str, Any] = {} for param_name in parameters: use_params[param_name] = None for param in params: @@ -813,14 +813,14 @@ def get_click_type( def lenient_issubclass( - cls: Any, class_or_tuple: Union[AnyType, Tuple[AnyType, ...]] + cls: Any, class_or_tuple: Union[AnyType, tuple[AnyType, ...]] ) -> bool: return isinstance(cls, type) and issubclass(cls, class_or_tuple) def get_click_param( param: ParamMeta, -) -> Tuple[Union[click.Argument, click.Option], Any]: +) -> tuple[Union[click.Argument, click.Option], Any]: # First, find out what will be: # * ParamInfo (ArgumentInfo or OptionInfo) # * default_value @@ -863,13 +863,13 @@ def get_click_param( main_type = types[0] origin = get_origin(main_type) # Handle Tuples and Lists - if lenient_issubclass(origin, List): + if lenient_issubclass(origin, list): 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 + elif lenient_issubclass(origin, tuple): # type: ignore types = [] for type_ in get_args(main_type): assert not get_origin(type_), ( @@ -992,7 +992,7 @@ def get_param_callback( ctx_name = None click_param_name = None value_name = None - untyped_names: List[str] = [] + untyped_names: list[str] = [] for param_name, param_sig in parameters.items(): if lenient_issubclass(param_sig.annotation, click.Context): ctx_name = param_name @@ -1016,7 +1016,7 @@ def get_param_callback( ) def wrapper(ctx: click.Context, param: click.Parameter, value: Any) -> Any: - use_params: Dict[str, Any] = {} + use_params: dict[str, Any] = {} if ctx_name: use_params[ctx_name] = ctx if click_param_name: @@ -1048,7 +1048,7 @@ def get_param_completion( if lenient_issubclass(param_sig.annotation, click.Context): ctx_name = param_sig.name unassigned_params.remove(param_sig) - elif lenient_issubclass(origin, List): + elif lenient_issubclass(origin, list): args_name = param_sig.name unassigned_params.remove(param_sig) elif lenient_issubclass(param_sig.annotation, str): @@ -1072,8 +1072,8 @@ def get_param_completion( f"Invalid autocompletion callback parameters: {show_params}" ) - def wrapper(ctx: click.Context, args: List[str], incomplete: Optional[str]) -> Any: - use_params: Dict[str, Any] = {} + def wrapper(ctx: click.Context, args: list[str], incomplete: Optional[str]) -> Any: + use_params: dict[str, Any] = {} if ctx_name: use_params[ctx_name] = ctx if args_name: diff --git a/typer/models.py b/typer/models.py index 4f6b80d38b..78d1a5354d 100644 --- a/typer/models.py +++ b/typer/models.py @@ -5,10 +5,7 @@ TYPE_CHECKING, Any, Callable, - Dict, - List, Optional, - Type, TypeVar, Union, ) @@ -23,7 +20,7 @@ NoneType = type(None) -AnyType = Type[Any] +AnyType = type[Any] Required = ... @@ -87,8 +84,8 @@ def __init__( self, name: Optional[str] = None, *, - cls: Optional[Type["TyperCommand"]] = None, - context_settings: Optional[Dict[Any, Any]] = None, + cls: Optional[type["TyperCommand"]] = None, + context_settings: Optional[dict[Any, Any]] = None, callback: Optional[Callable[..., Any]] = None, help: Optional[str] = None, epilog: Optional[str] = None, @@ -123,14 +120,14 @@ def __init__( typer_instance: Optional["Typer"] = Default(None), *, name: Optional[str] = Default(None), - cls: Optional[Type["TyperGroup"]] = Default(None), + cls: Optional[type["TyperGroup"]] = Default(None), invoke_without_command: bool = Default(False), no_args_is_help: bool = Default(False), subcommand_metavar: Optional[str] = Default(None), chain: bool = Default(False), result_callback: Optional[Callable[..., Any]] = Default(None), # Command - context_settings: Optional[Dict[Any, Any]] = Default(None), + context_settings: Optional[dict[Any, Any]] = Default(None), callback: Optional[Callable[..., Any]] = Default(None), help: Optional[str] = Default(None), epilog: Optional[str] = Default(None), @@ -172,13 +169,13 @@ def __init__( metavar: Optional[str] = None, expose_value: bool = True, is_eager: bool = False, - envvar: Optional[Union[str, List[str]]] = None, + envvar: Optional[Union[str, list[str]]] = None, # Note that shell_complete is not fully supported and will be removed in future versions # TODO: Remove shell_complete in a future version (after 0.16.0) shell_complete: Optional[ Callable[ [click.Context, click.Parameter, str], - Union[List["click.shell_completion.CompletionItem"], List[str]], + Union[list["click.shell_completion.CompletionItem"], list[str]], ] ] = None, autocompletion: Optional[Callable[..., Any]] = None, @@ -199,7 +196,7 @@ def __init__( max: Optional[Union[int, float]] = None, clamp: bool = False, # DateTime - formats: Optional[List[str]] = None, + formats: Optional[list[str]] = None, # File mode: Optional[str] = None, encoding: Optional[str] = None, @@ -214,7 +211,7 @@ def __init__( readable: bool = True, resolve_path: bool = False, allow_dash: bool = False, - path_type: Union[None, Type[str], Type[bytes]] = None, + path_type: Union[None, type[str], type[bytes]] = None, # Rich settings rich_help_panel: Union[str, None] = None, ): @@ -282,13 +279,13 @@ def __init__( metavar: Optional[str] = None, expose_value: bool = True, is_eager: bool = False, - envvar: Optional[Union[str, List[str]]] = None, + envvar: Optional[Union[str, list[str]]] = None, # Note that shell_complete is not fully supported and will be removed in future versions # TODO: Remove shell_complete in a future version (after 0.16.0) shell_complete: Optional[ Callable[ [click.Context, click.Parameter, str], - Union[List["click.shell_completion.CompletionItem"], List[str]], + Union[list["click.shell_completion.CompletionItem"], list[str]], ] ] = None, autocompletion: Optional[Callable[..., Any]] = None, @@ -318,7 +315,7 @@ def __init__( max: Optional[Union[int, float]] = None, clamp: bool = False, # DateTime - formats: Optional[List[str]] = None, + formats: Optional[list[str]] = None, # File mode: Optional[str] = None, encoding: Optional[str] = None, @@ -333,7 +330,7 @@ def __init__( readable: bool = True, resolve_path: bool = False, allow_dash: bool = False, - path_type: Union[None, Type[str], Type[bytes]] = None, + path_type: Union[None, type[str], type[bytes]] = None, # Rich settings rich_help_panel: Union[str, None] = None, ): @@ -411,13 +408,13 @@ def __init__( metavar: Optional[str] = None, expose_value: bool = True, is_eager: bool = False, - envvar: Optional[Union[str, List[str]]] = None, + envvar: Optional[Union[str, list[str]]] = None, # Note that shell_complete is not fully supported and will be removed in future versions # TODO: Remove shell_complete in a future version (after 0.16.0) shell_complete: Optional[ Callable[ [click.Context, click.Parameter, str], - Union[List["click.shell_completion.CompletionItem"], List[str]], + Union[list["click.shell_completion.CompletionItem"], list[str]], ] ] = None, autocompletion: Optional[Callable[..., Any]] = None, @@ -438,7 +435,7 @@ def __init__( max: Optional[Union[int, float]] = None, clamp: bool = False, # DateTime - formats: Optional[List[str]] = None, + formats: Optional[list[str]] = None, # File mode: Optional[str] = None, encoding: Optional[str] = None, @@ -453,7 +450,7 @@ def __init__( readable: bool = True, resolve_path: bool = False, allow_dash: bool = False, - path_type: Union[None, Type[str], Type[bytes]] = None, + path_type: Union[None, type[str], type[bytes]] = None, # Rich settings rich_help_panel: Union[str, None] = None, ): @@ -537,7 +534,7 @@ class TyperPath(click.Path): # Overwrite Click's behaviour to be compatible with Typer's autocompletion system def shell_complete( self, ctx: click.Context, param: click.Parameter, incomplete: str - ) -> List[click.shell_completion.CompletionItem]: + ) -> list[click.shell_completion.CompletionItem]: """Return an empty list so that the autocompletion functionality will work properly from the commandline. """ diff --git a/typer/params.py b/typer/params.py index 66c2b32d3e..2a03c03e71 100644 --- a/typer/params.py +++ b/typer/params.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Any, Callable, List, Optional, Type, Union, overload +from typing import TYPE_CHECKING, Any, Callable, Optional, Union, overload import click @@ -18,13 +18,13 @@ def Option( metavar: Optional[str] = None, expose_value: bool = True, is_eager: bool = False, - envvar: Optional[Union[str, List[str]]] = None, + envvar: Optional[Union[str, list[str]]] = None, # Note that shell_complete is not fully supported and will be removed in future versions # TODO: Remove shell_complete in a future version (after 0.16.0) shell_complete: Optional[ Callable[ [click.Context, click.Parameter, str], - Union[List["click.shell_completion.CompletionItem"], List[str]], + Union[list["click.shell_completion.CompletionItem"], list[str]], ] ] = None, autocompletion: Optional[Callable[..., Any]] = None, @@ -53,7 +53,7 @@ def Option( max: Optional[Union[int, float]] = None, clamp: bool = False, # DateTime - formats: Optional[List[str]] = None, + formats: Optional[list[str]] = None, # File mode: Optional[str] = None, encoding: Optional[str] = None, @@ -68,7 +68,7 @@ def Option( readable: bool = True, resolve_path: bool = False, allow_dash: bool = False, - path_type: Union[None, Type[str], Type[bytes]] = None, + path_type: Union[None, type[str], type[bytes]] = None, # Rich settings rich_help_panel: Union[str, None] = None, ) -> Any: ... @@ -84,13 +84,13 @@ def Option( metavar: Optional[str] = None, expose_value: bool = True, is_eager: bool = False, - envvar: Optional[Union[str, List[str]]] = None, + envvar: Optional[Union[str, list[str]]] = None, # Note that shell_complete is not fully supported and will be removed in future versions # TODO: Remove shell_complete in a future version (after 0.16.0) shell_complete: Optional[ Callable[ [click.Context, click.Parameter, str], - Union[List["click.shell_completion.CompletionItem"], List[str]], + Union[list["click.shell_completion.CompletionItem"], list[str]], ] ] = None, autocompletion: Optional[Callable[..., Any]] = None, @@ -119,7 +119,7 @@ def Option( max: Optional[Union[int, float]] = None, clamp: bool = False, # DateTime - formats: Optional[List[str]] = None, + formats: Optional[list[str]] = None, # File mode: Optional[str] = None, encoding: Optional[str] = None, @@ -134,7 +134,7 @@ def Option( readable: bool = True, resolve_path: bool = False, allow_dash: bool = False, - path_type: Union[None, Type[str], Type[bytes]] = None, + path_type: Union[None, type[str], type[bytes]] = None, # Rich settings rich_help_panel: Union[str, None] = None, ) -> Any: ... @@ -148,13 +148,13 @@ def Option( metavar: Optional[str] = None, expose_value: bool = True, is_eager: bool = False, - envvar: Optional[Union[str, List[str]]] = None, + envvar: Optional[Union[str, list[str]]] = None, # Note that shell_complete is not fully supported and will be removed in future versions # TODO: Remove shell_complete in a future version (after 0.16.0) shell_complete: Optional[ Callable[ [click.Context, click.Parameter, str], - Union[List["click.shell_completion.CompletionItem"], List[str]], + Union[list["click.shell_completion.CompletionItem"], list[str]], ] ] = None, autocompletion: Optional[Callable[..., Any]] = None, @@ -184,7 +184,7 @@ def Option( max: Optional[Union[int, float]] = None, clamp: bool = False, # DateTime - formats: Optional[List[str]] = None, + formats: Optional[list[str]] = None, # File mode: Optional[str] = None, encoding: Optional[str] = None, @@ -199,7 +199,7 @@ def Option( readable: bool = True, resolve_path: bool = False, allow_dash: bool = False, - path_type: Union[None, Type[str], Type[bytes]] = None, + path_type: Union[None, type[str], type[bytes]] = None, # Rich settings rich_help_panel: Union[str, None] = None, ) -> Any: @@ -270,13 +270,13 @@ def Argument( metavar: Optional[str] = None, expose_value: bool = True, is_eager: bool = False, - envvar: Optional[Union[str, List[str]]] = None, + envvar: Optional[Union[str, list[str]]] = None, # Note that shell_complete is not fully supported and will be removed in future versions # TODO: Remove shell_complete in a future version (after 0.16.0) shell_complete: Optional[ Callable[ [click.Context, click.Parameter, str], - Union[List["click.shell_completion.CompletionItem"], List[str]], + Union[list["click.shell_completion.CompletionItem"], list[str]], ] ] = None, autocompletion: Optional[Callable[..., Any]] = None, @@ -296,7 +296,7 @@ def Argument( max: Optional[Union[int, float]] = None, clamp: bool = False, # DateTime - formats: Optional[List[str]] = None, + formats: Optional[list[str]] = None, # File mode: Optional[str] = None, encoding: Optional[str] = None, @@ -311,7 +311,7 @@ def Argument( readable: bool = True, resolve_path: bool = False, allow_dash: bool = False, - path_type: Union[None, Type[str], Type[bytes]] = None, + path_type: Union[None, type[str], type[bytes]] = None, # Rich settings rich_help_panel: Union[str, None] = None, ) -> Any: ... @@ -327,13 +327,13 @@ def Argument( metavar: Optional[str] = None, expose_value: bool = True, is_eager: bool = False, - envvar: Optional[Union[str, List[str]]] = None, + envvar: Optional[Union[str, list[str]]] = None, # Note that shell_complete is not fully supported and will be removed in future versions # TODO: Remove shell_complete in a future version (after 0.16.0) shell_complete: Optional[ Callable[ [click.Context, click.Parameter, str], - Union[List["click.shell_completion.CompletionItem"], List[str]], + Union[list["click.shell_completion.CompletionItem"], list[str]], ] ] = None, autocompletion: Optional[Callable[..., Any]] = None, @@ -353,7 +353,7 @@ def Argument( max: Optional[Union[int, float]] = None, clamp: bool = False, # DateTime - formats: Optional[List[str]] = None, + formats: Optional[list[str]] = None, # File mode: Optional[str] = None, encoding: Optional[str] = None, @@ -368,7 +368,7 @@ def Argument( readable: bool = True, resolve_path: bool = False, allow_dash: bool = False, - path_type: Union[None, Type[str], Type[bytes]] = None, + path_type: Union[None, type[str], type[bytes]] = None, # Rich settings rich_help_panel: Union[str, None] = None, ) -> Any: ... @@ -382,13 +382,13 @@ def Argument( metavar: Optional[str] = None, expose_value: bool = True, is_eager: bool = False, - envvar: Optional[Union[str, List[str]]] = None, + envvar: Optional[Union[str, list[str]]] = None, # Note that shell_complete is not fully supported and will be removed in future versions # TODO: Remove shell_complete in a future version (after 0.16.0) shell_complete: Optional[ Callable[ [click.Context, click.Parameter, str], - Union[List["click.shell_completion.CompletionItem"], List[str]], + Union[list["click.shell_completion.CompletionItem"], list[str]], ] ] = None, autocompletion: Optional[Callable[..., Any]] = None, @@ -409,7 +409,7 @@ def Argument( max: Optional[Union[int, float]] = None, clamp: bool = False, # DateTime - formats: Optional[List[str]] = None, + formats: Optional[list[str]] = None, # File mode: Optional[str] = None, encoding: Optional[str] = None, @@ -424,7 +424,7 @@ def Argument( readable: bool = True, resolve_path: bool = False, allow_dash: bool = False, - path_type: Union[None, Type[str], Type[bytes]] = None, + path_type: Union[None, type[str], type[bytes]] = None, # Rich settings rich_help_panel: Union[str, None] = None, ) -> Any: diff --git a/typer/rich_utils.py b/typer/rich_utils.py index ae0c82afb8..ad110cb8d6 100644 --- a/typer/rich_utils.py +++ b/typer/rich_utils.py @@ -6,7 +6,7 @@ from collections.abc import Iterable from gettext import gettext as _ from os import getenv -from typing import Any, DefaultDict, Dict, List, Literal, Optional, Union +from typing import Any, Literal, Optional, Union import click from rich import box @@ -227,7 +227,7 @@ def _get_parameter_help( # import here to avoid cyclic imports from .core import TyperArgument, TyperOption - items: List[Union[Text, Markdown]] = [] + items: list[Union[Text, Markdown]] = [] # Get the environment variable first @@ -330,13 +330,13 @@ def _make_command_help( def _print_options_panel( *, name: str, - params: Union[List[click.Option], List[click.Argument]], + params: Union[list[click.Option], list[click.Argument]], ctx: click.Context, markup_mode: MarkupModeStrict, console: Console, ) -> None: - options_rows: List[List[RenderableType]] = [] - required_rows: List[Union[str, Text]] = [] + options_rows: list[list[RenderableType]] = [] + required_rows: list[Union[str, Text]] = [] for param in params: # Short and long form opt_long_strs = [] @@ -418,14 +418,14 @@ class MetavarHighlighter(RegexHighlighter): ), ] ) - rows_with_required: List[List[RenderableType]] = [] + rows_with_required: list[list[RenderableType]] = [] if any(required_rows): for required, row in zip(required_rows, options_rows): rows_with_required.append([required, *row]) else: rows_with_required = options_rows if options_rows: - t_styles: Dict[str, Any] = { + t_styles: dict[str, Any] = { "show_lines": STYLE_OPTIONS_TABLE_SHOW_LINES, "leading": STYLE_OPTIONS_TABLE_LEADING, "box": STYLE_OPTIONS_TABLE_BOX, @@ -458,12 +458,12 @@ class MetavarHighlighter(RegexHighlighter): def _print_commands_panel( *, name: str, - commands: List[click.Command], + commands: list[click.Command], markup_mode: MarkupModeStrict, console: Console, cmd_len: int, ) -> None: - t_styles: Dict[str, Any] = { + t_styles: dict[str, Any] = { "show_lines": STYLE_COMMANDS_TABLE_SHOW_LINES, "leading": STYLE_COMMANDS_TABLE_LEADING, "box": STYLE_COMMANDS_TABLE_BOX, @@ -493,8 +493,8 @@ def _print_commands_panel( # available instead of allowing the command column to grow and misalign with # other panels. commands_table.add_column("Description", justify="left", no_wrap=False, ratio=10) - rows: List[List[Union[RenderableType, None]]] = [] - deprecated_rows: List[Union[RenderableType, None]] = [] + rows: list[list[Union[RenderableType, None]]] = [] + deprecated_rows: list[Union[RenderableType, None]] = [] for command in commands: helptext = command.short_help or command.help or "" command_name = command.name or "" @@ -567,8 +567,8 @@ def rich_format_help( (0, 1, 1, 1), ) ) - panel_to_arguments: DefaultDict[str, List[click.Argument]] = defaultdict(list) - panel_to_options: DefaultDict[str, List[click.Option]] = defaultdict(list) + panel_to_arguments: defaultdict[str, list[click.Argument]] = defaultdict(list) + panel_to_options: defaultdict[str, list[click.Option]] = defaultdict(list) for param in obj.get_params(ctx): # Skip if option is hidden if getattr(param, "hidden", False): @@ -623,7 +623,7 @@ def rich_format_help( ) if isinstance(obj, click.Group): - panel_to_commands: DefaultDict[str, List[click.Command]] = defaultdict(list) + panel_to_commands: defaultdict[str, list[click.Command]] = defaultdict(list) for command_name in obj.list_commands(ctx): command = obj.get_command(ctx, command_name) if command and not command.hidden: @@ -739,7 +739,7 @@ def rich_render_text(text: str) -> str: def get_traceback( exc: BaseException, exception_config: DeveloperExceptionConfig, - internal_dir_names: List[str], + internal_dir_names: list[str], ) -> Traceback: rich_tb = Traceback.from_exception( type(exc), diff --git a/typer/utils.py b/typer/utils.py index 81dc4dd61d..568639bade 100644 --- a/typer/utils.py +++ b/typer/utils.py @@ -1,13 +1,13 @@ import inspect import sys from copy import copy -from typing import Any, Callable, Dict, List, Tuple, Type, cast +from typing import Any, Callable, cast from ._typing import Annotated, get_args, get_origin, get_type_hints from .models import ArgumentInfo, OptionInfo, ParameterInfo, ParamMeta -def _param_type_to_user_string(param_type: Type[ParameterInfo]) -> str: +def _param_type_to_user_string(param_type: type[ParameterInfo]) -> str: # Render a `ParameterInfo` subclass for use in error messages. # User code doesn't call `*Info` directly, so errors should present the classes how # they were (probably) defined in the user code. @@ -21,9 +21,9 @@ def _param_type_to_user_string(param_type: Type[ParameterInfo]) -> str: class AnnotatedParamWithDefaultValueError(Exception): argument_name: str - param_type: Type[ParameterInfo] + param_type: type[ParameterInfo] - def __init__(self, argument_name: str, param_type: Type[ParameterInfo]): + def __init__(self, argument_name: str, param_type: type[ParameterInfo]): self.argument_name = argument_name self.param_type = param_type @@ -37,14 +37,14 @@ def __str__(self) -> str: class MixedAnnotatedAndDefaultStyleError(Exception): argument_name: str - annotated_param_type: Type[ParameterInfo] - default_param_type: Type[ParameterInfo] + annotated_param_type: type[ParameterInfo] + default_param_type: type[ParameterInfo] def __init__( self, argument_name: str, - annotated_param_type: Type[ParameterInfo], - default_param_type: Type[ParameterInfo], + annotated_param_type: type[ParameterInfo], + default_param_type: type[ParameterInfo], ): self.argument_name = argument_name self.annotated_param_type = annotated_param_type @@ -77,9 +77,9 @@ def __str__(self) -> str: class DefaultFactoryAndDefaultValueError(Exception): argument_name: str - param_type: Type[ParameterInfo] + param_type: type[ParameterInfo] - def __init__(self, argument_name: str, param_type: Type[ParameterInfo]): + def __init__(self, argument_name: str, param_type: type[ParameterInfo]): self.argument_name = argument_name self.param_type = param_type @@ -92,8 +92,8 @@ def __str__(self) -> str: def _split_annotation_from_typer_annotations( - base_annotation: Type[Any], -) -> Tuple[Type[Any], List[ParameterInfo]]: + base_annotation: type[Any], +) -> tuple[type[Any], list[ParameterInfo]]: if get_origin(base_annotation) is not Annotated: return base_annotation, [] base_annotation, *maybe_typer_annotations = get_args(base_annotation) @@ -104,7 +104,7 @@ def _split_annotation_from_typer_annotations( ] -def get_params_from_function(func: Callable[..., Any]) -> Dict[str, ParamMeta]: +def get_params_from_function(func: Callable[..., Any]) -> dict[str, ParamMeta]: if sys.version_info >= (3, 10): signature = inspect.signature(func, eval_str=True) else: From 04d4e1975b6977e798e721058ece047578a2e571 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 25 Dec 2025 10:39:05 +0100 Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=8E=A8=20Format=20to=20handle=20linti?= =?UTF-8?q?ng?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- typer/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typer/main.py b/typer/main.py index dbff2b8986..e8c6b9e429 100644 --- a/typer/main.py +++ b/typer/main.py @@ -869,7 +869,7 @@ def get_click_param( "List types with complex sub-types are not currently supported" ) is_list = True - elif lenient_issubclass(origin, tuple): # type: ignore + elif lenient_issubclass(origin, tuple): types = [] for type_ in get_args(main_type): assert not get_origin(type_), ( From ac8d83ec8bc61b175f49b55cb0df6f8405a6dca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 25 Dec 2025 10:44:37 +0100 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=93=8C=20Update=20uv.lock?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- uv.lock | 30 +++++------------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/uv.lock b/uv.lock index fe0a3772d3..2798324cc5 100644 --- a/uv.lock +++ b/uv.lock @@ -1,10 +1,9 @@ version = 1 revision = 3 -requires-python = ">=3.8" +requires-python = ">=3.9" resolution-markers = [ "python_full_version >= '3.10'", - "python_full_version == '3.9.*'", - "python_full_version < '3.9'", + "python_full_version < '3.10'", ] [[package]] @@ -12,8 +11,7 @@ name = "click" version = "8.1.8" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version == '3.9.*'", - "python_full_version < '3.9'", + "python_full_version < '3.10'", ] dependencies = [ { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, @@ -52,8 +50,7 @@ name = "markdown-it-py" version = "3.0.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version == '3.9.*'", - "python_full_version < '3.9'", + "python_full_version < '3.10'", ] dependencies = [ { name = "mdurl", marker = "python_full_version < '3.10'" }, @@ -127,8 +124,7 @@ dependencies = [ { name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "rich" }, { name = "shellingham" }, - { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "typing-extensions" }, ] [package.metadata] @@ -139,26 +135,10 @@ requires-dist = [ { name = "typing-extensions", specifier = ">=3.7.4.3" }, ] -[[package]] -name = "typing-extensions" -version = "4.13.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, -] - [[package]] name = "typing-extensions" version = "4.15.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", - "python_full_version == '3.9.*'", -] sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },