64
64
# quirk imposed by the base class for users to be aware of.
65
65
# ruff: noqa: E402
66
66
67
- # Most of the bookeeping complexity is induced by the necessity of figuring out if and
68
- # where django's base command line parameters should be attached. To do this commands
69
- # and Typer instances (groups in django-typer parlance) need to be able to access the
70
- # django command class they are attached to. The length of this file is largely a result
71
- # of wrapping Typer.command, Typer.callback and Typer.add_typer in many different contexts
67
+ # Most of the book keeping complexity is induced by the necessity of figuring out if and
68
+ # where django's base command line parameters should be attached and how to bind a method
69
+ # function to a class that may not been created yet. To do this commands and Typer
70
+ # instances (groups in click/django-typer parlance) need to be able to access the django
71
+ # command class they are attached to. The length of this file is largely a result of
72
+ # wrapping Typer.command, Typer.callback and Typer.add_typer in many different contexts
72
73
# to achieve the nice interface we would like and also because we list out each parameter
73
- # for developer experience reasons
74
+ # for developer experience reasons, its only ~600 actual statements
74
75
75
76
import inspect
76
77
import sys
142
143
143
144
__all__ = [
144
145
"TyperCommand" ,
146
+ "CommandNode" ,
147
+ "CommandGroup" ,
148
+ "Typer" ,
149
+ "DjangoTyperMixin" ,
150
+ "DTCommand" ,
151
+ "DTGroup" ,
145
152
"Context" ,
146
153
"initialize" ,
147
154
"command" ,
@@ -454,7 +461,7 @@ def __init__(
454
461
parent .children .append (self )
455
462
456
463
457
- class DjangoTyperCommand (with_typehint (CoreTyperGroup )): # type: ignore[misc]
464
+ class DjangoTyperMixin (with_typehint (CoreTyperGroup )): # type: ignore[misc]
458
465
"""
459
466
A mixin we use to add additional needed contextual awareness to click Commands
460
467
and Groups.
@@ -604,19 +611,47 @@ def call_with_self(*args, **kwargs):
604
611
)
605
612
606
613
607
- class TyperCommandWrapper ( DjangoTyperCommand , CoreTyperCommand ):
614
+ class DTCommand ( DjangoTyperMixin , CoreTyperCommand ):
608
615
"""
609
- This class extends the TyperCommand class to work with the django-typer
610
- interfaces. If you need to add functionality to the command class - which
611
- you should not - you should inherit from this class.
616
+ This class extends the TyperCommand class to work with the django-typer interfaces.
617
+ If you need to add functionality to the command class - you should inherit from
618
+ this class. You can then pass your custom class to the command() decorators
619
+ using the cls parameter.
620
+
621
+ .. code-block:: python
622
+
623
+ from django_typer import TyperCommand, DTCommand, command
624
+
625
+ class CustomCommand(DTCommand):
626
+ ...
627
+
628
+ class Command(TyperCommand):
629
+
630
+ @command(cls=CustomCommand)
631
+ def handle(self):
632
+ ...
612
633
"""
613
634
614
635
615
- class TyperGroupWrapper ( DjangoTyperCommand , CoreTyperGroup ):
636
+ class DTGroup ( DjangoTyperMixin , CoreTyperGroup ):
616
637
"""
617
- This class extends the TyperGroup class to work with the django-typer
618
- interfaces. If you need to add functionality to the group class - which
619
- you should not - you should inherit from this class.
638
+ This class extends the TyperGroup class to work with the django-typer interfaces.
639
+ If you need to add functionality to the group class you should inherit from this
640
+ class. You can then pass your custom class to the command() decorators using the
641
+ cls parameter.
642
+
643
+ .. code-block:: python
644
+
645
+ from django_typer import TyperCommand, DTGroup, group
646
+
647
+ class CustomGroup(DTGroup):
648
+ ...
649
+
650
+ class Command(TyperCommand):
651
+
652
+ @group(cls=CustomGroup)
653
+ def grp(self):
654
+ ...
620
655
"""
621
656
622
657
@@ -633,7 +668,7 @@ def _cache_initializer(
633
668
common_init : bool ,
634
669
name : t .Optional [str ] = Default (None ),
635
670
help : t .Optional [str ] = Default (None ),
636
- cls : t .Type [TyperGroupWrapper ] = TyperGroupWrapper ,
671
+ cls : t .Type [DTGroup ] = DTGroup ,
637
672
** kwargs ,
638
673
):
639
674
if not hasattr (callback , _CACHE_KEY ):
@@ -663,7 +698,7 @@ def _cache_command(
663
698
callback : t .Callable [..., t .Any ],
664
699
name : t .Optional [str ] = None ,
665
700
help : t .Optional [str ] = None ,
666
- cls : t .Type [TyperCommandWrapper ] = TyperCommandWrapper ,
701
+ cls : t .Type [DTCommand ] = DTCommand ,
667
702
** kwargs ,
668
703
):
669
704
if not hasattr (callback , _CACHE_KEY ):
@@ -690,7 +725,7 @@ def __call__(
690
725
app_cls , # pyright: ignore[reportSelfClsParameterName]
691
726
* args ,
692
727
name : t .Optional [str ] = Default (None ),
693
- cls : t .Type [TyperGroupWrapper ] = TyperGroupWrapper ,
728
+ cls : t .Type [DTGroup ] = DTGroup ,
694
729
invoke_without_command : bool = Default (False ),
695
730
no_args_is_help : bool = Default (False ),
696
731
subcommand_metavar : t .Optional [str ] = Default (None ),
@@ -817,7 +852,7 @@ def __init__(
817
852
self ,
818
853
* ,
819
854
name : t .Optional [str ] = Default (None ),
820
- cls : t .Optional [t .Type [TyperGroupWrapper ]] = TyperGroupWrapper ,
855
+ cls : t .Optional [t .Type [DTGroup ]] = DTGroup ,
821
856
invoke_without_command : bool = Default (False ),
822
857
no_args_is_help : bool = Default (False ),
823
858
subcommand_metavar : t .Optional [str ] = Default (None ),
@@ -876,7 +911,7 @@ def callback( # type: ignore
876
911
self ,
877
912
name : t .Optional [str ] = Default (None ),
878
913
* ,
879
- cls : t .Type [TyperGroupWrapper ] = TyperGroupWrapper ,
914
+ cls : t .Type [DTGroup ] = DTGroup ,
880
915
invoke_without_command : bool = Default (False ),
881
916
no_args_is_help : bool = Default (False ),
882
917
subcommand_metavar : t .Optional [str ] = Default (None ),
@@ -967,7 +1002,7 @@ def command( # type: ignore
967
1002
self ,
968
1003
name : t .Optional [str ] = None ,
969
1004
* ,
970
- cls : t .Type [TyperCommandWrapper ] = TyperCommandWrapper ,
1005
+ cls : t .Type [DTCommand ] = DTCommand ,
971
1006
context_settings : t .Optional [t .Dict [t .Any , t .Any ]] = None ,
972
1007
help : t .Optional [str ] = None ,
973
1008
epilog : t .Optional [str ] = None ,
@@ -1036,7 +1071,7 @@ def add_typer( # type: ignore
1036
1071
typer_instance : "CommandGroup" ,
1037
1072
* ,
1038
1073
name : t .Optional [str ] = Default (None ),
1039
- cls : t .Type [TyperGroupWrapper ] = TyperGroupWrapper ,
1074
+ cls : t .Type [DTGroup ] = DTGroup ,
1040
1075
invoke_without_command : bool = Default (False ),
1041
1076
no_args_is_help : bool = Default (False ),
1042
1077
subcommand_metavar : t .Optional [str ] = Default (None ),
@@ -1181,11 +1216,14 @@ def __get__(
1181
1216
@t .overload
1182
1217
def __get__ (
1183
1218
self , obj : "TyperCommand" , owner : t .Any = None
1184
- ) -> t .Union [MethodType , t .Callable [P , R ]]: ...
1219
+ ) -> MethodType : # t.Union[MethodType, t.Callable[P, R]]
1220
+ # todo - we could return the generic callable type here but the problem
1221
+ # is self is included in the ParamSpec and it seems tricky to remove?
1222
+ # MethodType loses the parameters but is preferable to type checking errors
1223
+ # https://github.com/bckohan/django-typer/issues/73
1224
+ ...
1185
1225
1186
- def __get__ (
1187
- self , obj : t .Any , owner : t .Any = None
1188
- ) -> t .Union ["CommandGroup[P, R]" , MethodType , t .Callable [P , R ]]:
1226
+ def __get__ (self , obj , owner = None ):
1189
1227
"""
1190
1228
Our Typer app wrapper also doubles as a descriptor, so when
1191
1229
it is accessed on the instance, we return the wrapped function
@@ -1204,7 +1242,7 @@ def __init__(
1204
1242
self ,
1205
1243
* ,
1206
1244
name : t .Optional [str ] = Default (None ),
1207
- cls : t .Type [TyperGroupWrapper ] = TyperGroupWrapper ,
1245
+ cls : t .Type [DTGroup ] = DTGroup ,
1208
1246
invoke_without_command : bool = Default (False ),
1209
1247
no_args_is_help : bool = Default (False ),
1210
1248
subcommand_metavar : t .Optional [str ] = Default (None ),
@@ -1293,7 +1331,7 @@ def callback( # type: ignore
1293
1331
self ,
1294
1332
name : t .Optional [str ] = Default (None ),
1295
1333
* ,
1296
- cls : t .Type [TyperGroupWrapper ] = TyperGroupWrapper ,
1334
+ cls : t .Type [DTGroup ] = DTGroup ,
1297
1335
invoke_without_command : bool = Default (False ),
1298
1336
no_args_is_help : bool = Default (False ),
1299
1337
subcommand_metavar : t .Optional [str ] = Default (None ),
@@ -1346,7 +1384,7 @@ def command( # type: ignore
1346
1384
self ,
1347
1385
name : t .Optional [str ] = None ,
1348
1386
* ,
1349
- cls : t .Type [TyperCommandWrapper ] = TyperCommandWrapper ,
1387
+ cls : t .Type [DTCommand ] = DTCommand ,
1350
1388
context_settings : t .Optional [t .Dict [t .Any , t .Any ]] = None ,
1351
1389
help : t .Optional [str ] = None , # pylint: disable=redefined-builtin
1352
1390
epilog : t .Optional [str ] = None ,
@@ -1458,7 +1496,7 @@ def make_command(f: t.Callable[P2, R2]) -> t.Callable[P2, R2]:
1458
1496
def group (
1459
1497
self ,
1460
1498
name : t .Optional [str ] = Default (None ),
1461
- cls : t .Type [TyperGroupWrapper ] = TyperGroupWrapper ,
1499
+ cls : t .Type [DTGroup ] = DTGroup ,
1462
1500
invoke_without_command : bool = Default (False ),
1463
1501
no_args_is_help : bool = Default (False ),
1464
1502
subcommand_metavar : t .Optional [str ] = Default (None ),
@@ -1591,7 +1629,7 @@ def create_app(func: t.Callable[P2, R2]) -> CommandGroup[P2, R2]:
1591
1629
def initialize (
1592
1630
name : t .Optional [str ] = Default (None ),
1593
1631
* ,
1594
- cls : t .Type [TyperGroupWrapper ] = TyperGroupWrapper ,
1632
+ cls : t .Type [DTGroup ] = DTGroup ,
1595
1633
invoke_without_command : bool = Default (False ),
1596
1634
no_args_is_help : bool = Default (False ),
1597
1635
subcommand_metavar : t .Optional [str ] = Default (None ),
@@ -1735,7 +1773,7 @@ def make_initializer(func: t.Callable[P2, R2]) -> t.Callable[P2, R2]:
1735
1773
def command ( # pylint: disable=keyword-arg-before-vararg
1736
1774
name : t .Optional [str ] = None ,
1737
1775
* ,
1738
- cls : t .Type [TyperCommandWrapper ] = TyperCommandWrapper ,
1776
+ cls : t .Type [DTCommand ] = DTCommand ,
1739
1777
context_settings : t .Optional [t .Dict [t .Any , t .Any ]] = None ,
1740
1778
help : t .Optional [str ] = None , # pylint: disable=redefined-builtin
1741
1779
epilog : t .Optional [str ] = None ,
@@ -1830,7 +1868,7 @@ def make_command(func: t.Callable[P, R]) -> t.Callable[P, R]:
1830
1868
1831
1869
def group (
1832
1870
name : t .Optional [str ] = Default (None ),
1833
- cls : t .Type [TyperGroupWrapper ] = TyperGroupWrapper ,
1871
+ cls : t .Type [DTGroup ] = DTGroup ,
1834
1872
invoke_without_command : bool = Default (False ),
1835
1873
no_args_is_help : bool = Default (False ),
1836
1874
subcommand_metavar : t .Optional [str ] = Default (None ),
@@ -1955,7 +1993,7 @@ def _add_common_initializer(
1955
1993
cmd .typer_app .callback (
1956
1994
cls = type (
1957
1995
"_Initializer" ,
1958
- (TyperGroupWrapper ,),
1996
+ (DTGroup ,),
1959
1997
{
1960
1998
"django_command" : cmd ,
1961
1999
"_callback_is_method" : False ,
@@ -2065,7 +2103,7 @@ def __new__(
2065
2103
bases ,
2066
2104
attrs ,
2067
2105
name : t .Optional [str ] = Default (None ),
2068
- cls : t .Optional [t .Type [TyperGroupWrapper ]] = TyperGroupWrapper ,
2106
+ cls : t .Optional [t .Type [DTGroup ]] = DTGroup ,
2069
2107
invoke_without_command : bool = Default (False ),
2070
2108
no_args_is_help : bool = Default (False ),
2071
2109
subcommand_metavar : t .Optional [str ] = Default (None ),
@@ -2224,7 +2262,7 @@ class CommandNode:
2224
2262
The name of the group or command that this node represents.
2225
2263
"""
2226
2264
2227
- click_command : DjangoTyperCommand
2265
+ click_command : DjangoTyperMixin
2228
2266
"""
2229
2267
The click command object that this node represents.
2230
2268
"""
@@ -2260,7 +2298,7 @@ def callback(self) -> t.Callable[..., t.Any]:
2260
2298
def __init__ (
2261
2299
self ,
2262
2300
name : str ,
2263
- click_command : DjangoTyperCommand ,
2301
+ click_command : DjangoTyperMixin ,
2264
2302
context : TyperContext ,
2265
2303
django_command : "TyperCommand" ,
2266
2304
parent : t .Optional ["CommandNode" ] = None ,
@@ -2589,7 +2627,7 @@ def initialize(
2589
2627
cmd , # pyright: ignore[reportSelfClsParameterName]
2590
2628
name : t .Optional [str ] = Default (None ),
2591
2629
* ,
2592
- cls : t .Type [TyperGroupWrapper ] = TyperGroupWrapper ,
2630
+ cls : t .Type [DTGroup ] = DTGroup ,
2593
2631
invoke_without_command : bool = Default (False ),
2594
2632
no_args_is_help : bool = Default (False ),
2595
2633
subcommand_metavar : t .Optional [str ] = Default (None ),
@@ -2705,7 +2743,7 @@ def command(
2705
2743
cmd , # pyright: ignore[reportSelfClsParameterName]
2706
2744
name : t .Optional [str ] = None ,
2707
2745
* ,
2708
- cls : t .Type [TyperCommandWrapper ] = TyperCommandWrapper ,
2746
+ cls : t .Type [DTCommand ] = DTCommand ,
2709
2747
context_settings : t .Optional [t .Dict [t .Any , t .Any ]] = None ,
2710
2748
help : t .Optional [str ] = None , # pylint: disable=redefined-builtin
2711
2749
epilog : t .Optional [str ] = None ,
@@ -2799,7 +2837,7 @@ def make_command(func: t.Callable[P, R]) -> t.Callable[P, R]:
2799
2837
def group (
2800
2838
cmd , # pyright: ignore[reportSelfClsParameterName]
2801
2839
name : t .Optional [str ] = Default (None ),
2802
- cls : t .Type [TyperGroupWrapper ] = TyperGroupWrapper ,
2840
+ cls : t .Type [DTGroup ] = DTGroup ,
2803
2841
invoke_without_command : bool = Default (False ),
2804
2842
no_args_is_help : bool = Default (False ),
2805
2843
subcommand_metavar : t .Optional [str ] = Default (None ),
@@ -3061,7 +3099,7 @@ def _build_cmd_tree(
3061
3099
:param node: the parent node or None if this is a root node
3062
3100
"""
3063
3101
assert cmd .name
3064
- assert isinstance (cmd , DjangoTyperCommand )
3102
+ assert isinstance (cmd , DjangoTyperMixin )
3065
3103
ctx = Context (cmd , info_name = info_name , parent = parent , django_command = self )
3066
3104
current = CommandNode (cmd .name , cmd , ctx , self , parent = node )
3067
3105
if node :
0 commit comments