From b52fc16bbae33abc52b3839d2fd440cf1e886eb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 24 Dec 2019 17:16:58 +0100 Subject: [PATCH] :art: Fix types and format --- docs/index.md | 4 +- .../test_first_steps/test_tutorial001.py | 2 +- typer/__init__.py | 68 ++++++++------- typer/main.py | 83 ++++++++++--------- typer/models.py | 10 ++- typer/params.py | 10 ++- typer/testing.py | 29 ++++--- 7 files changed, 110 insertions(+), 96 deletions(-) diff --git a/docs/index.md b/docs/index.md index 45f0cec..9f062ee 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,8 +5,8 @@ Typer: CLIs with autocompletion. While developing and using.

- - Build Status + + Build Status Coverage diff --git a/tests/test_tutorial/test_first_steps/test_tutorial001.py b/tests/test_tutorial/test_first_steps/test_tutorial001.py index 3c7c78d..cd63835 100644 --- a/tests/test_tutorial/test_first_steps/test_tutorial001.py +++ b/tests/test_tutorial/test_first_steps/test_tutorial001.py @@ -1,5 +1,5 @@ -from typer.testing import CliRunner import typer +from typer.testing import CliRunner from first_steps.tutorial001 import main diff --git a/typer/__init__.py b/typer/__init__.py index 4f36c76..1ccb321 100644 --- a/typer/__init__.py +++ b/typer/__init__.py @@ -2,46 +2,44 @@ __version__ = "0.0.1" -from .main import Typer, run # noqa -from .params import Option, Argument # noqa -from .models import Context, TextFile, BinaryFileRead, BinaryFileWrite # noqa - +from click.exceptions import ( # noqa + Abort, + BadArgumentUsage, + BadOptionUsage, + BadParameter, + ClickException, + FileError, + MissingParameter, + NoSuchOption, + UsageError, +) +# Terminal functions +from click.termui import ( # noqa + clear, + confirm, + echo_via_pager, + edit, + get_terminal_size, + getchar, + launch, + pause, + progressbar, + prompt, + secho, + style, + unstyle, +) # Utilities from click.utils import ( # noqa echo, - get_binary_stream, - get_text_stream, - open_file, format_filename, get_app_dir, + get_binary_stream, get_os_args, + get_text_stream, + open_file, ) -# Terminal functions -from click.termui import ( # noqa - prompt, - confirm, - get_terminal_size, - echo_via_pager, - progressbar, - clear, - style, - unstyle, - secho, - edit, - launch, - getchar, - pause, -) - -from click.exceptions import ( # noqa - ClickException, - UsageError, - BadParameter, - FileError, - Abort, - NoSuchOption, - BadOptionUsage, - BadArgumentUsage, - MissingParameter, -) +from .main import Typer, run # noqa +from .models import BinaryFileRead, BinaryFileWrite, Context, TextFile # noqa +from .params import Argument, Option # noqa diff --git a/typer/main.py b/typer/main.py index 1f3e037..0b19f62 100644 --- a/typer/main.py +++ b/typer/main.py @@ -3,25 +3,26 @@ from datetime import datetime from enum import Enum from functools import update_wrapper from pathlib import Path -from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, Union from uuid import UUID import click from .models import ( + AnyType, ArgumentInfo, BinaryFileRead, BinaryFileWrite, + CommandFunctionType, CommandInfo, + Default, + DefaultPlaceholder, NoneType, OptionInfo, ParameterInfo, Required, - TyperInfo, TextFile, - AnyType, - Default, - DefaultPlaceholder, + TyperInfo, ) @@ -38,7 +39,7 @@ class Typer: result_callback: Optional[Callable] = Default(None), # Command context_settings: Optional[Dict[Any, Any]] = Default(None), - callback: Optional[Callable[..., Any]] = Default(None), + callback: Optional[Callable] = Default(None), help: Optional[str] = Default(None), epilog: Optional[str] = Default(None), short_help: Optional[str] = Default(None), @@ -88,8 +89,8 @@ class Typer: add_help_option: bool = Default(True), hidden: bool = Default(False), deprecated: bool = Default(False), - ) -> Callable[[Callable[..., Any]], Callable[..., Any]]: - def decorator(f: Callable[..., Any]): + ) -> Callable[[CommandFunctionType], CommandFunctionType]: + def decorator(f: CommandFunctionType) -> CommandFunctionType: self.registered_callback = TyperInfo( name=name, cls=cls, @@ -125,11 +126,11 @@ class Typer: add_help_option: bool = True, hidden: bool = False, deprecated: bool = False, - ) -> Callable[[Callable[..., Any]], Callable[..., Any]]: + ) -> Callable[[CommandFunctionType], CommandFunctionType]: if cls is None: cls = click.Command - def decorator(f): + def decorator(f: CommandFunctionType) -> CommandFunctionType: self.registered_commands.append( CommandInfo( name=name, @@ -162,7 +163,7 @@ class Typer: result_callback: Optional[Callable] = Default(None), # Command context_settings: Optional[Dict[Any, Any]] = Default(None), - callback: Optional[Callable[..., Any]] = Default(None), + callback: Optional[Callable] = Default(None), help: Optional[str] = Default(None), epilog: Optional[str] = Default(None), short_help: Optional[str] = Default(None), @@ -170,7 +171,7 @@ class Typer: add_help_option: bool = Default(True), hidden: bool = Default(False), deprecated: bool = Default(False), - ): + ) -> None: self.registered_groups.append( TyperInfo( typer_instance, @@ -193,16 +194,16 @@ class Typer: ) ) - def __call__(self): + def __call__(self) -> Any: return get_command(self)() -def get_group(typer_instance: Typer) -> click.Group: +def get_group(typer_instance: Typer) -> click.Command: group = get_group_from_info(TyperInfo(typer_instance)) return group -def get_command(typer_instance: Typer): +def get_command(typer_instance: Typer) -> click.Command: if ( typer_instance.registered_callback or typer_instance.registered_groups @@ -211,9 +212,10 @@ def get_command(typer_instance: Typer): return get_group(typer_instance) elif len(typer_instance.registered_commands) == 1: return get_command_from_info(typer_instance.registered_commands[0]) + assert False, "Could not get a command for this Typer instance" -def get_group_name(typer_info: TyperInfo): +def get_group_name(typer_info: TyperInfo) -> str: if typer_info.callback: # Priority 1: Callback passed in app.add_typer() return get_command_name(typer_info.callback.__name__) @@ -225,9 +227,10 @@ def get_group_name(typer_info: TyperInfo): return get_command_name(registered_callback.callback.__name__) if typer_info.typer_instance.info.callback: return get_command_name(typer_info.typer_instance.info.callback.__name__) + assert False, "A Group name could not be created" -def solve_typer_info_defaults(typer_info: TyperInfo): +def solve_typer_info_defaults(typer_info: TyperInfo) -> TyperInfo: values = {} name = None for name, value in typer_info.__dict__.items(): @@ -256,7 +259,7 @@ def solve_typer_info_defaults(typer_info: TyperInfo): return TyperInfo(**values) -def get_group_from_info(group_info: TyperInfo): +def get_group_from_info(group_info: TyperInfo) -> click.Command: assert ( group_info.typer_instance ), "A Typer instance is needed to generate a Click Group" @@ -274,8 +277,8 @@ def get_group_from_info(group_info: TyperInfo): context_param_name, ) = get_params_convertors_ctx_param_name_from_function(solved_info.callback) cls = solved_info.cls or click.Group - group = cls( - name=solved_info.name, + group = cls( # type: ignore + name=solved_info.name or "", commands=commands, invoke_without_command=solved_info.invoke_without_command, no_args_is_help=solved_info.no_args_is_help, @@ -289,7 +292,7 @@ def get_group_from_info(group_info: TyperInfo): convertors=convertors, context_param_name=context_param_name, ), - params=params, + params=params, # type: ignore help=solved_info.help, epilog=solved_info.epilog, short_help=solved_info.short_help, @@ -301,13 +304,13 @@ def get_group_from_info(group_info: TyperInfo): return group -def get_command_name(name: str): +def get_command_name(name: str) -> str: return name.lower().replace("_", "-") 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]]: params = [] convertors = {} context_param_name = None @@ -324,7 +327,7 @@ def get_params_convertors_ctx_param_name_from_function( return params, convertors, context_param_name -def get_command_from_info(command_info: CommandInfo): +def get_command_from_info(command_info: CommandInfo) -> click.Command: assert command_info.callback, "A command must have a callback function" name = command_info.name or get_command_name(command_info.callback.__name__) ( @@ -342,7 +345,7 @@ def get_command_from_info(command_info: CommandInfo): convertors=convertors, context_param_name=context_param_name, ), - params=params, + params=params, # type: ignore help=command_info.help, epilog=command_info.epilog, short_help=command_info.short_help, @@ -354,15 +357,16 @@ def get_command_from_info(command_info: CommandInfo): return command -def param_path_convertor(value: Optional[str] = None): +def param_path_convertor(value: Optional[str] = None) -> Optional[Path]: if value is not None: return Path(value) + return None -def generate_enum_convertor(enum: Type[Enum]): +def generate_enum_convertor(enum: Type[Enum]) -> Callable: lower_val_map = {str(val.value).lower(): val for val in enum} - def convertor(value: Any): + def convertor(value: Any) -> Any: if value is not None: low = str(value).lower() if low in lower_val_map: @@ -374,11 +378,11 @@ def generate_enum_convertor(enum: Type[Enum]): def get_callback( *, - callback: Optional[Callable[..., Any]] = None, - params: List[click.Parameter] = [], + callback: Optional[Callable] = None, + params: Sequence[click.Parameter] = [], convertors: Dict[str, Callable[[str], Any]] = {}, context_param_name: str = None, -) -> Optional[Callable[..., Any]]: +) -> Optional[Callable]: if not callback: return None signature = inspect.signature(callback) @@ -388,7 +392,7 @@ def get_callback( for param in params: use_params[param.name] = param.default - def wrapper(**kwargs): + def wrapper(**kwargs: Any) -> Any: for k, v in kwargs.items(): if k in convertors: use_params[k] = convertors[k](v) @@ -396,13 +400,15 @@ def get_callback( use_params[k] = v if context_param_name: use_params[context_param_name] = click.get_current_context() - return callback(**use_params) + return callback(**use_params) # type: ignore update_wrapper(wrapper, callback) return wrapper -def get_click_type(*, annotation: Any, parameter_info: ParameterInfo): +def get_click_type( + *, annotation: Any, parameter_info: ParameterInfo +) -> click.ParamType: if annotation == str: return click.STRING elif annotation == int: @@ -485,7 +491,9 @@ def lenient_issubclass( return isinstance(cls, type) and issubclass(cls, class_or_tuple) -def get_click_param(param: inspect.Parameter): +def get_click_param( + param: inspect.Parameter, +) -> Tuple[Union[click.Argument, click.Option], Any]: # First, find out what will be: # * ParamInfo (ArgumentInfo or OptionInfo) # * default_value @@ -511,7 +519,7 @@ def get_click_param(param: inspect.Parameter): annotation = str main_type = annotation is_list = False - parameter_type = None + parameter_type: Any = None is_flag = None origin = getattr(main_type, "__origin__", None) if origin is not None: @@ -624,9 +632,10 @@ def get_click_param(param: inspect.Parameter): ), convertor, ) + assert False, "A click.Parameter should be returned" -def run(function: Callable): +def run(function: Callable) -> Any: app = Typer() app.command()(function) app() diff --git a/typer/models.py b/typer/models.py index b1be1a2..a91a6de 100644 --- a/typer/models.py +++ b/typer/models.py @@ -1,3 +1,4 @@ +import io from typing import ( TYPE_CHECKING, Any, @@ -7,12 +8,10 @@ from typing import ( Optional, Sequence, Type, - Union, TypeVar, + Union, ) -import io - import click if TYPE_CHECKING: @@ -49,15 +48,18 @@ class DefaultPlaceholder: It's used internally to recognize when a default value has been overwritten, even if the new value is `None`. """ + def __init__(self, value: Any): self.value = value - def __bool__(self): + def __bool__(self) -> bool: return bool(self.value) DefaultType = TypeVar("DefaultType") +CommandFunctionType = TypeVar("CommandFunctionType", bound=Callable[..., Any]) + def Default(value: DefaultType) -> DefaultType: """ diff --git a/typer/params.py b/typer/params.py index 555bf25..820a0c5 100644 --- a/typer/params.py +++ b/typer/params.py @@ -1,7 +1,9 @@ -from typing import Optional, Any, List, Callable, Union, Type -from .models import OptionInfo, ArgumentInfo +from typing import Any, Callable, List, Optional, Type, Union + import click +from .models import ArgumentInfo, OptionInfo + def Option( # Parameter @@ -51,7 +53,7 @@ def Option( resolve_path: bool = False, allow_dash: bool = False, path_type: Union[None, Type[str], Type[bytes]] = None, -): +) -> Any: return OptionInfo( # Parameter default=default, @@ -136,7 +138,7 @@ def Argument( resolve_path: bool = False, allow_dash: bool = False, path_type: Union[None, Type[str], Type[bytes]] = None, -): +) -> Any: return ArgumentInfo( # Parameter default=default, diff --git a/typer/testing.py b/typer/testing.py index 3ef28f0..1a07c43 100644 --- a/typer/testing.py +++ b/typer/testing.py @@ -1,20 +1,23 @@ -from click.testing import Result, CliRunner as ClickCliRunner # noqa -from typer.main import get_command as _get_command +from typing import IO, Any, Iterable, Mapping, Optional, Text, Union + +from click.testing import CliRunner as ClickCliRunner, Result # noqa +from typer.main import Typer, get_command as _get_command class CliRunner(ClickCliRunner): - def invoke( + def invoke( # type: ignore self, - cli, - args=None, - input=None, - env=None, - catch_exceptions=True, - color=False, - mix_stderr=False, - **extra + app: Typer, + cli: Typer, + args: Optional[Union[str, Iterable[str]]] = None, + input: Optional[Union[bytes, Text, IO[Any]]] = None, + env: Optional[Mapping[str, str]] = None, + catch_exceptions: bool = True, + color: bool = False, + mix_stderr: bool = False, + **extra: Any, ) -> Result: - use_cli = _get_command(cli) + use_cli = _get_command(app) return super().invoke( use_cli, args=args, @@ -23,5 +26,5 @@ class CliRunner(ClickCliRunner): catch_exceptions=catch_exceptions, color=color, mix_stderr=mix_stderr, - **extra + **extra, )