From 74e7f7079cb31578fa2578f219d93a33cfeb46f8 Mon Sep 17 00:00:00 2001 From: Angus L'Herrou Date: Fri, 14 May 2021 10:19:23 -0400 Subject: command and group decorator parentheses are optional --- src/click/core.py | 32 +++++++++++++++++++++++---- src/click/decorators.py | 58 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/click/core.py b/src/click/core.py index 95d852b..3d11ab5 100644 --- a/src/click/core.py +++ b/src/click/core.py @@ -1797,7 +1797,7 @@ class Group(MultiCommand): def command( self, *args: t.Any, **kwargs: t.Any - ) -> t.Callable[[t.Callable[..., t.Any]], Command]: + ) -> t.Union[t.Callable[[t.Callable[..., t.Any]], Command], Command]: """A shortcut decorator for declaring and attaching a command to the group. This takes the same arguments as :func:`command` and immediately registers the created command with this group by @@ -1806,6 +1806,9 @@ class Group(MultiCommand): To customize the command class used, set the :attr:`command_class` attribute. + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + .. versionchanged:: 8.0 Added the :attr:`command_class` attribute. """ @@ -1814,16 +1817,25 @@ class Group(MultiCommand): if self.command_class is not None and "cls" not in kwargs: kwargs["cls"] = self.command_class + func: t.Optional[t.Callable] = None + + if args and callable(args[0]): + func = args[0] + args = args[1:] + def decorator(f: t.Callable[..., t.Any]) -> Command: - cmd = command(*args, **kwargs)(f) + cmd: Command = command(*args, **kwargs)(f) self.add_command(cmd) return cmd + if func is not None: + return decorator(func) + return decorator def group( self, *args: t.Any, **kwargs: t.Any - ) -> t.Callable[[t.Callable[..., t.Any]], "Group"]: + ) -> t.Union[t.Callable[[t.Callable[..., t.Any]], "Group"], "Group"]: """A shortcut decorator for declaring and attaching a group to the group. This takes the same arguments as :func:`group` and immediately registers the created group with this group by @@ -1832,11 +1844,20 @@ class Group(MultiCommand): To customize the group class used, set the :attr:`group_class` attribute. + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + .. versionchanged:: 8.0 Added the :attr:`group_class` attribute. """ from .decorators import group + func: t.Optional[t.Callable] = None + + if args and callable(args[0]): + func = args[0] + args = args[1:] + if self.group_class is not None and "cls" not in kwargs: if self.group_class is type: kwargs["cls"] = type(self) @@ -1844,10 +1865,13 @@ class Group(MultiCommand): kwargs["cls"] = self.group_class def decorator(f: t.Callable[..., t.Any]) -> "Group": - cmd = group(*args, **kwargs)(f) + cmd: Group = group(*args, **kwargs)(f) self.add_command(cmd) return cmd + if func is not None: + return decorator(func) + return decorator def get_command(self, ctx: Context, cmd_name: str) -> t.Optional[Command]: diff --git a/src/click/decorators.py b/src/click/decorators.py index 7930a16..a8c6d2d 100644 --- a/src/click/decorators.py +++ b/src/click/decorators.py @@ -153,11 +153,29 @@ def _make_command( ) +@t.overload def command( name: t.Optional[str] = None, cls: t.Optional[t.Type[Command]] = None, **attrs: t.Any, ) -> t.Callable[[F], Command]: + ... + + +@t.overload +def command( + name: t.Callable, + cls: t.Optional[t.Type[Command]] = None, + **attrs: t.Any, +) -> Command: + ... + + +def command( + name: t.Union[str, t.Callable, None] = None, + cls: t.Optional[t.Type[Command]] = None, + **attrs: t.Any, +) -> t.Union[Command, t.Callable[[F], Command]]: r"""Creates a new :class:`Command` and uses the decorated function as callback. This will also automatically attach all decorated :func:`option`\s and :func:`argument`\s as parameters to the command. @@ -176,24 +194,62 @@ def command( name with underscores replaced by dashes. :param cls: the command class to instantiate. This defaults to :class:`Command`. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. """ if cls is None: cls = Command + func: t.Optional[t.Callable] = None + + if callable(name): + func = name + name = None + def decorator(f: t.Callable[..., t.Any]) -> Command: cmd = _make_command(f, name, attrs, cls) # type: ignore cmd.__doc__ = f.__doc__ return cmd + if func is not None: + return decorator(func) + return decorator -def group(name: t.Optional[str] = None, **attrs: t.Any) -> t.Callable[[F], Group]: +@t.overload +def group( + name: t.Optional[str] = None, + **attrs: t.Any, +) -> t.Callable[[F], Group]: + ... + + +@t.overload +def group( + name: t.Callable, + **attrs: t.Any, +) -> Group: + ... + + +def group( + name: t.Union[str, t.Callable, None] = None, **attrs: t.Any +) -> t.Union[Group, t.Callable[[F], Group]]: """Creates a new :class:`Group` with a function as callback. This works otherwise the same as :func:`command` just that the `cls` parameter is set to :class:`Group`. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. """ attrs.setdefault("cls", Group) + + if callable(name): + grp: t.Callable[[F], Group] = t.cast(Group, command(**attrs)) + return grp(name) + return t.cast(Group, command(name, **attrs)) -- cgit v1.2.1