From e234cf958d2416d4b20b53ae850c487fed32fa35 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Tue, 29 Mar 2022 23:02:52 -0400 Subject: [PATCH] fix(types): decorator typing fails --- CHANGES.rst | 3 +++ src/click/core.py | 34 ++++++++++++++++++++++++++++++---- src/click/decorators.py | 34 +++++++++++++--------------------- 3 files changed, 46 insertions(+), 25 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 64525e8d7..c4f464fb8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Version 8.1.1 Unreleased +- Fix an issue with decorator typing that caused type checking to + report that a command was not callable. :issue:`2227` + Version 8.1.0 ------------- diff --git a/src/click/core.py b/src/click/core.py index 18431cd1d..a9a72c5f9 100644 --- a/src/click/core.py +++ b/src/click/core.py @@ -1809,6 +1809,16 @@ def add_command(self, cmd: Command, name: t.Optional[str] = None) -> None: _check_multicommand(self, name, cmd, register=True) self.commands[name] = cmd + @t.overload + def command(self, __func: t.Callable[..., t.Any]) -> Command: + ... + + @t.overload + def command( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], Command]: + ... + def command( self, *args: t.Any, **kwargs: t.Any ) -> t.Union[t.Callable[[t.Callable[..., t.Any]], Command], Command]: @@ -1834,8 +1844,11 @@ def command( func: t.Optional[t.Callable] = None if args and callable(args[0]): - func = args[0] - args = args[1:] + assert ( + len(args) == 1 and not kwargs + ), "Use 'command(**kwargs)(callable)' to provide arguments." + (func,) = args + args = () def decorator(f: t.Callable[..., t.Any]) -> Command: cmd: Command = command(*args, **kwargs)(f) @@ -1847,6 +1860,16 @@ def decorator(f: t.Callable[..., t.Any]) -> Command: return decorator + @t.overload + def group(self, __func: t.Callable[..., t.Any]) -> "Group": + ... + + @t.overload + def group( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], "Group"]: + ... + def group( self, *args: t.Any, **kwargs: t.Any ) -> t.Union[t.Callable[[t.Callable[..., t.Any]], "Group"], "Group"]: @@ -1869,8 +1892,11 @@ def group( func: t.Optional[t.Callable] = None if args and callable(args[0]): - func = args[0] - args = args[1:] + assert ( + len(args) == 1 and not kwargs + ), "Use 'group(**kwargs)(callable)' to provide arguments." + (func,) = args + args = () if self.group_class is not None and kwargs.get("cls") is None: if self.group_class is type: diff --git a/src/click/decorators.py b/src/click/decorators.py index 05d133443..ef1b1a51a 100644 --- a/src/click/decorators.py +++ b/src/click/decorators.py @@ -126,10 +126,8 @@ def new_func(*args, **kwargs): # type: ignore @t.overload def command( - name: t.Optional[str] = None, - cls: t.Type[CmdType] = ..., - **attrs: t.Any, -) -> t.Callable[..., CmdType]: + __func: t.Callable[..., t.Any], +) -> Command: ... @@ -143,18 +141,10 @@ def command( @t.overload def command( - name: t.Callable, + name: t.Optional[str] = None, cls: t.Type[CmdType] = ..., **attrs: t.Any, -) -> CmdType: - ... - - -@t.overload -def command( - name: t.Callable, - **attrs: t.Any, -) -> Command: +) -> t.Callable[..., CmdType]: ... @@ -191,14 +181,17 @@ def command( The ``params`` argument can be used. Decorated params are appended to the end of the list. """ - if cls is None: - cls = Command func: t.Optional[t.Callable] = None if callable(name): func = name name = None + assert cls is None, "Use 'command(cls=cls)(callable)' to specify a class." + assert not attrs, "Use 'command(**kwargs)(callable)' to provide arguments." + + if cls is None: + cls = Command def decorator(f: t.Callable[..., t.Any]) -> Command: if isinstance(f, Command): @@ -235,17 +228,16 @@ def decorator(f: t.Callable[..., t.Any]) -> Command: @t.overload def group( - name: t.Optional[str] = None, - **attrs: t.Any, -) -> t.Callable[[F], Group]: + __func: t.Callable, +) -> Group: ... @t.overload def group( - name: t.Callable, + name: t.Optional[str] = None, **attrs: t.Any, -) -> Group: +) -> t.Callable[[F], Group]: ...