Skip to content

Commit dcf24a3

Browse files
committed
add type hints to decorators #33
1 parent ff01b54 commit dcf24a3

File tree

3 files changed

+75
-37
lines changed

3 files changed

+75
-37
lines changed

django_typer/__init__.py

Lines changed: 50 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,12 @@
107107
)
108108
from .utils import _command_context, traceback_config, with_typehint
109109

110+
if sys.version_info < (3, 9):
111+
from typing_extensions import ParamSpec
112+
else:
113+
from typing import ParamSpec
114+
115+
110116
VERSION = (1, 0, 2)
111117

112118
__title__ = "Django Typer"
@@ -126,6 +132,9 @@
126132
"model_parser_completer",
127133
]
128134

135+
P = ParamSpec("P")
136+
R = t.TypeVar("R")
137+
129138

130139
def model_parser_completer(
131140
model_cls: t.Type[Model],
@@ -532,7 +541,9 @@ class GroupFunction(Typer):
532541
django_command_cls: t.Type["TyperCommand"]
533542
_callback: t.Callable[..., t.Any]
534543

535-
def __get__(self, obj, obj_type=None):
544+
def __get__(
545+
self, obj: t.Optional["TyperCommand"], _=None
546+
) -> t.Union["GroupFunction", MethodType]:
536547
"""
537548
Our Typer app wrapper also doubles as a descriptor, so when
538549
it is accessed on the instance, we return the wrapped function
@@ -590,8 +601,8 @@ def command( # type: ignore
590601
deprecated: bool = False,
591602
# Rich settings
592603
rich_help_panel: t.Union[str, None] = Default(None),
593-
**kwargs,
594-
):
604+
**kwargs: t.Dict[str, t.Any],
605+
) -> t.Callable[[t.Callable[P, R]], t.Callable[P, R]]:
595606
"""
596607
A function decorator that creates a new command and attaches it to this group.
597608
This is a passthrough to Typer.command() and the options are the same, except
@@ -637,21 +648,29 @@ def command1(self):
637648
:param rich_help_panel: the rich help panel to use - if rich is installed
638649
this can be used to group commands into panels in the help output.
639650
"""
640-
return super().command(
641-
name=name,
642-
cls=cls,
643-
context_settings=context_settings,
644-
help=help,
645-
epilog=epilog,
646-
short_help=short_help,
647-
options_metavar=options_metavar,
648-
add_help_option=add_help_option,
649-
no_args_is_help=no_args_is_help,
650-
hidden=hidden,
651-
deprecated=deprecated,
652-
rich_help_panel=rich_help_panel,
653-
**kwargs,
654-
)
651+
652+
def decorator(f: t.Callable[P, R]) -> t.Callable[P, R]:
653+
return super( # pylint: disable=super-with-arguments
654+
GroupFunction, self
655+
).command(
656+
name=name,
657+
cls=cls,
658+
context_settings=context_settings,
659+
help=help,
660+
epilog=epilog,
661+
short_help=short_help,
662+
options_metavar=options_metavar,
663+
add_help_option=add_help_option,
664+
no_args_is_help=no_args_is_help,
665+
hidden=hidden,
666+
deprecated=deprecated,
667+
rich_help_panel=rich_help_panel,
668+
**kwargs,
669+
)(
670+
f
671+
)
672+
673+
return decorator
655674

656675
def group(
657676
self,
@@ -673,8 +692,8 @@ def group(
673692
deprecated: bool = Default(False),
674693
# Rich settings
675694
rich_help_panel: t.Union[str, None] = Default(None),
676-
**kwargs,
677-
):
695+
**kwargs: t.Dict[str, t.Any],
696+
) -> t.Callable[[t.Callable[..., t.Any]], "GroupFunction"]:
678697
"""
679698
Create a new subgroup and attach it to this group. This is like creating a new
680699
Typer app and adding it to a parent Typer app. The kwargs are passed through
@@ -721,7 +740,7 @@ def subcommand(self):
721740
this can be used to group commands into panels in the help output.
722741
"""
723742

724-
def create_app(func: t.Callable[..., t.Any]):
743+
def create_app(func: t.Callable[..., t.Any]) -> GroupFunction:
725744
grp = GroupFunction( # type: ignore
726745
name=name,
727746
cls=cls,
@@ -769,8 +788,8 @@ def initialize(
769788
deprecated: bool = Default(False),
770789
# Rich settings
771790
rich_help_panel: t.Union[str, None] = Default(None),
772-
**kwargs,
773-
):
791+
**kwargs: t.Dict[str, t.Any],
792+
) -> t.Callable[[t.Callable[P, R]], t.Callable[P, R]]:
774793
"""
775794
A function decorator that creates a Typer_
776795
`callback <https://typer.tiangolo.com/tutorial/commands/callback/>`_. This
@@ -863,7 +882,7 @@ def divide(
863882
this can be used to group commands into panels in the help output.
864883
"""
865884

866-
def decorator(func: t.Callable[..., t.Any]):
885+
def decorator(func: t.Callable[P, R]) -> t.Callable[P, R]:
867886
setattr(
868887
func,
869888
"_typer_callback_",
@@ -899,7 +918,7 @@ def decorator(func: t.Callable[..., t.Any]):
899918

900919
def command( # pylint: disable=keyword-arg-before-vararg
901920
name: t.Optional[str] = None,
902-
*args,
921+
*,
903922
cls: t.Type[TyperCommandWrapper] = TyperCommandWrapper,
904923
context_settings: t.Optional[t.Dict[t.Any, t.Any]] = None,
905924
help: t.Optional[str] = None, # pylint: disable=redefined-builtin
@@ -912,8 +931,8 @@ def command( # pylint: disable=keyword-arg-before-vararg
912931
deprecated: bool = False,
913932
# Rich settings
914933
rich_help_panel: t.Union[str, None] = Default(None),
915-
**kwargs,
916-
):
934+
**kwargs: t.Dict[str, t.Any],
935+
) -> t.Callable[[t.Callable[P, R]], t.Callable[P, R]]:
917936
"""
918937
A function decorator that creates a new command and attaches it to the root
919938
command group. This is a passthrough to
@@ -970,13 +989,12 @@ def other_command(self):
970989
this can be used to group commands into panels in the help output.
971990
"""
972991

973-
def decorator(func: t.Callable[..., t.Any]):
992+
def decorator(func: t.Callable[P, R]) -> t.Callable[P, R]:
974993
setattr(
975994
func,
976995
"_typer_command_",
977996
lambda cmd, _name=None, _help=None, **extra: cmd.typer_app.command(
978997
name=name or _name,
979-
*args,
980998
cls=type("_AdaptedCommand", (cls,), {"django_command": cmd}),
981999
context_settings=context_settings,
9821000
help=help or _help,
@@ -1017,8 +1035,8 @@ def group(
10171035
deprecated: bool = Default(False),
10181036
# Rich settings
10191037
rich_help_panel: t.Union[str, None] = Default(None),
1020-
**kwargs,
1021-
):
1038+
**kwargs: t.Dict[str, t.Any],
1039+
) -> t.Callable[[t.Callable[..., t.Any]], GroupFunction]:
10221040
"""
10231041
A function decorator that creates a new subgroup and attaches it to the root
10241042
command group. This is like creating a new Typer_ app and adding it to a parent
@@ -1083,7 +1101,7 @@ def subcommand(self):
10831101
this can be used to group commands into panels in the help output.
10841102
"""
10851103

1086-
def create_app(func: t.Callable[..., t.Any]):
1104+
def create_app(func: t.Callable[..., t.Any]) -> GroupFunction:
10871105
grp = GroupFunction( # type: ignore
10881106
name=name,
10891107
cls=cls,

django_typer/management/commands/shellcompletion.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,9 @@ def remove(
380380
# its less brittle to install and use the returned path to uninstall
381381
self.shell = shell # type: ignore
382382
prog_name = str(manage_script or self.manage_script_name)
383-
installed_path = install(shell=self.shell.value, prog_name=prog_name)[1]
383+
installed_path = install(
384+
shell=self.shell.value if self.shell else None, prog_name=prog_name
385+
)[1]
384386
if self.shell in [Shells.pwsh, Shells.powershell]:
385387
# annoyingly, powershell has one profile script for all completion commands
386388
# so we have to find our entry and remove it
@@ -408,11 +410,12 @@ def remove(
408410
else:
409411
installed_path.unlink()
410412
rc_file = {
413+
None: None,
411414
Shells.bash: Path("~/.bashrc").expanduser(),
412415
Shells.zsh: Path("~/.zshrc").expanduser(),
413416
}.get(self.shell, None)
414417
if rc_file and rc_file.is_file():
415-
edited = []
418+
edited: t.List[str] = []
416419
with open(rc_file, "rt", encoding="utf-8") as rc:
417420
for line in rc.readlines():
418421
if (
@@ -432,7 +435,7 @@ def remove(
432435
self.stdout.write(
433436
self.style.WARNING( # pylint: disable=no-member
434437
gettext("Removed autocompletion for {shell}.").format(
435-
shell=self.shell.value
438+
shell=self.shell.value if self.shell else ""
436439
)
437440
)
438441
)

django_typer/tests/test_app/management/commands/groups.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@
1313

1414

1515
class Command(TyperCommand):
16+
1617
help = _("Test multiple groups commands and callbacks")
1718

1819
precision = 2
1920
verbosity = 1
2021

2122
@command()
22-
def echo(self, message: str):
23+
def echo(self, message: str) -> str:
2324
"""
2425
Echo the given message.
2526
"""
@@ -75,7 +76,7 @@ def divide(
7576
help=_("Additional denominators: n1/n2/n3/.../nN."), show_default=False
7677
),
7778
],
78-
):
79+
) -> str:
7980
"""
8081
Divide the given numbers.
8182
"""
@@ -146,3 +147,19 @@ def split(self, sep: str = " "):
146147
"""
147148
assert issubclass(self.__class__, Command)
148149
return " ".join(self.op_string.split(sep))
150+
151+
def test(self, a: int, b: int) -> int:
152+
return a + b
153+
154+
155+
# cmd = Command()
156+
# echoed = cmd.echo("hello")
157+
# cmd.math(3)
158+
# dividend = cmd.divide(10, 2, [2, 5])
159+
160+
# reveal_type(Command.echo)
161+
# reveal_type(cmd.echo)
162+
# reveal_type(cmd.math)
163+
# reveal_type(Command.test)
164+
# reveal_type(cmd.test)
165+
# reveal_type(cmd.divide)

0 commit comments

Comments
 (0)