Skip to content

Commit 557180e

Browse files
author
Brian Kohan
committed
tests for coverage and many more self tests
1 parent 2ac9538 commit 557180e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+180
-73
lines changed

django_typer/__init__.py

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,12 @@
2525
interface is preserved and the Typer_ interface is added on top of it. This means that
2626
this code base is more robust to changes in the Django management command system - because
2727
most of the base class functionality is preserved but many Typer_ and click_ internals are
28-
used directly to achieve this. We rely on robust CI to catch breaking changes upstream.
28+
used directly to achieve this. We rely on robust CI to catch breaking changes upstream and
29+
keep a tight version lock on Typer.
2930
"""
3031

3132
# During development of django-typer_ I've wrestled with a number of encumbrances in the
32-
# aging django management command design. I detail them here mostly to keep track of them
33+
# old django management command design. I detail them here mostly to keep track of them
3334
# for possible future refactors of core Django.
3435

3536
# 1) BaseCommand::execute() prints results to stdout without attempting to convert them
@@ -74,8 +75,8 @@
7475
# Typer.add_typer in many different contexts to achieve the nice interface we would
7576
# like and also because we list out each parameter for developer experience
7677
#
77-
# The other complexity comes from the fact that we enter pull in methods into the typer
78-
# infrastructure before classes are created so we have to do some booking to make sure
78+
# The other complexity comes from the fact that we pull in methods into the typer
79+
# infrastructure before classes are created so we have to do some bookkeeping to make sure
7980
# methods are bound to the right objects when called
8081

8182
import inspect
@@ -124,11 +125,11 @@
124125
)
125126
from .utils import (
126127
_command_context,
128+
_load_command_extensions,
127129
called_from_command_definition,
128130
called_from_module,
129131
get_usage_script,
130132
is_method,
131-
_load_command_extensions,
132133
traceback_config,
133134
with_typehint,
134135
)
@@ -173,7 +174,7 @@
173174

174175

175176
if sys.version_info < (3, 10):
176-
# todo - remove this when support for <=3.9 is dropped
177+
# todo - remove this when support for <3.10 is dropped
177178
class static_factory(type):
178179
def __call__(self, *args, **kwargs):
179180
assert args
@@ -258,10 +259,6 @@ def handle(
258259
}
259260

260261

261-
class CallableCommand(t.Protocol):
262-
def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any: ... # pragma: no cover
263-
264-
265262
@t.overload # pragma: no cover
266263
def get_command( # type: ignore[overload-overlap]
267264
command_name: str,
@@ -841,7 +838,9 @@ class Command(
841838
):
842839
pass
843840

844-
Command.__module__ = cmd_module.__name__ # spoof it hard
841+
# spoof it so hard
842+
Command.__module__ = cmd_module.__name__
843+
Command.__qualname__ = f"{cmd_module.__name__}.Command"
845844
setattr(cmd_module, "Command", Command)
846845
return Command.typer_app
847846
else:
@@ -901,9 +900,6 @@ def __get__(self, obj, _=None) -> "Typer[P, R]":
901900
return self
902901

903902
def __getattr__(self, name: str) -> t.Any:
904-
if isinstance(attr := getattr(self.__class__, name, None), property):
905-
return t.cast(t.Callable, attr.fget)(self)
906-
907903
for cmd in self.registered_commands:
908904
assert cmd.callback
909905
if name in (cmd.callback.__name__, cmd.name):
@@ -1988,8 +1984,6 @@ def get_ctor(attr: t.Any) -> t.Optional[t.Callable[..., t.Any]]:
19881984
assert name
19891985
to_remove.append(name)
19901986
_defined_groups[name] = attr
1991-
if attr.info.name and name != attr.info.name:
1992-
_defined_groups[attr.info.name] = attr
19931987
elif register := get_ctor(attr):
19941988
to_register.append(register)
19951989

django_typer/tests/apps/adapter0/management/adapters/adapted.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ def new_echo(self, msg1: str, msg2: str):
88
"""
99
Echo both messages.
1010
"""
11-
assert isinstance(self, Adapted)
11+
assert self.__class__ is Adapted
1212
self.echo(f"test_app2: {msg1} {msg2}")
1313

1414

django_typer/tests/apps/adapter0/management/adapters/adapted2.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,24 @@
66
@Adapted2.command()
77
def top1(self, msg1: str, msg2: str):
88
"""Extend with top level command."""
9-
assert isinstance(self, Adapted2)
9+
assert self.__class__ is Adapted2
1010
return f"test_app2::adapted2({self.verbosity})::top1({msg1}, {msg2})"
1111

1212

1313
@Adapted2.grp1.command()
1414
def grp1_adpt1(self):
1515
"""Extend grp1 with command."""
16-
assert isinstance(self, Adapted2)
16+
assert self.__class__ is Adapted2
1717
return f"test_app2::adapted2({self.verbosity})::grp1({self.argg3})::grp1_adpt1()"
1818

1919

2020
@Adapted2.grp2.group(invoke_without_command=True)
2121
def sub_grp2(self):
22-
assert isinstance(self, Adapted2)
22+
assert self.__class__ is Adapted2
2323
return f"test_app2::adapted2({self.verbosity})::grp2({self.flag1})::sub_grp2()"
2424

2525

2626
@Adapted2.subsubgroup.command()
2727
def ssg_cmd(self):
28-
assert isinstance(self, Adapted2)
28+
assert self.__class__ is Adapted2
2929
return f"test_app2::interference::grp1({self.argg3})::subgroup({self.arg5_0})::subsubgroup({self.subsubgroup_called})::ssg_cmd()"

django_typer/tests/apps/adapter0/management/adapters/native_override_init.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from django_typer.tests.apps.test_app.management.commands.native_override_init import (
22
app,
3+
Command,
34
)
45
from django_typer.types import Verbosity
56

@@ -8,6 +9,7 @@
89
def init(self, verbosity: Verbosity = 0, bog: bool = False):
910
self.verbosity = verbosity
1011
self.bog = bog
12+
assert self.__class__ is Command
1113
return {"verbosity": self.verbosity, "bog": self.bog}
1214

1315

@@ -16,6 +18,7 @@ def grp2(self):
1618
"""
1719
test_app2::grp2
1820
"""
21+
assert self.__class__ is Command
1922
self.grp2_called = True
2023

2124

@@ -24,6 +27,7 @@ def grp2_cmd1(self, g2arg1: int):
2427
"""
2528
test_app2::grp2::grp2_cmd1
2629
"""
30+
assert self.__class__ is Command
2731
return {
2832
"verbosity": self.verbosity,
2933
"bog": self.bog,

django_typer/tests/apps/adapter1/management/adapters/adapted2.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,30 @@
66
@Adapted2.command()
77
def top1(self, msg1: str):
88
"""Extend with top level command."""
9-
assert isinstance(self, Adapted2)
9+
assert self.__class__ is Adapted2
1010
return f"adapter1::adapted2({self.verbosity})::top1({msg1})"
1111

1212

1313
@Adapted2.grp1.command()
1414
def grp1_adpt1(self, int_arg1: int):
1515
"""Extend grp1 with command."""
16-
assert isinstance(self, Adapted2)
16+
assert self.__class__ is Adapted2
1717
return f"adapter1::adapted2({self.verbosity})::grp1({self.argg3})::grp1_adpt1({int_arg1})"
1818

1919

2020
@Adapted2.group(invoke_without_command=True)
2121
def grp3(self):
22-
assert isinstance(self, Adapted2)
22+
assert self.__class__ is Adapted2
2323
return f"adapter1::adapted2({self.verbosity})::grp3()"
2424

2525

2626
@Adapted2.grp2.group(invoke_without_command=True)
2727
def sub_grp2(self, int_arg2: int):
28-
assert isinstance(self, Adapted2)
28+
assert self.__class__ is Adapted2
2929
return f"adapter1::adapted2({self.verbosity})::grp2({self.flag1})::sub_grp2({int_arg2})"
3030

3131

3232
@Adapted2.subsubgroup.command()
3333
def ssg_cmd(self, int_arg3: int):
34-
assert isinstance(self, Adapted2)
34+
assert self.__class__ is Adapted2
3535
return f"adapter1::interference::grp1({self.argg3})::subgroup({self.arg5_0})::subsubgroup({self.subsubgroup_called})::ssg_cmd({int_arg3})"

django_typer/tests/apps/adapter2/management/adapters/adapted2.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,20 @@
66
@Adapted2.command()
77
def top1(self):
88
"""Extend with top level command."""
9-
assert isinstance(self, Adapted2)
9+
assert self.__class__ is Adapted2
1010
return f"adapter2::adapted2({self.verbosity})::top1()"
1111

1212

1313
@Adapted2.grp1.command()
1414
def grp1_adpt1(self, int_arg1: int, int_arg1_2: int):
1515
"""Extend grp1 with command."""
16-
assert isinstance(self, Adapted2)
16+
assert self.__class__ is Adapted2
1717
return f"adapter2::adapted2({self.verbosity})::grp1({self.argg3})::grp1_adpt1({int_arg1}, {int_arg1_2})"
1818

1919

2020
@Adapted2.grp2.group(invoke_without_command=True)
2121
def sub_grp2(self, int_arg2: int, int_arg2_2: int):
22-
assert isinstance(self, Adapted2)
22+
assert self.__class__ is Adapted2
2323
return f"adapter2::adapted2({self.verbosity})::grp2({self.flag1})::sub_grp2({int_arg2}, {int_arg2_2})"
2424

2525

@@ -41,5 +41,5 @@ def subsub_grp2():
4141

4242
@Adapted2.subsubgroup.command()
4343
def ssg_cmd(self, int_arg3: int, int_arg3_2: int):
44-
assert isinstance(self, Adapted2)
44+
assert self.__class__ is Adapted2
4545
return f"adapter2::interference::grp1({self.argg3})::subgroup({self.arg5_0})::subsubgroup({self.subsubgroup_called})::ssg_cmd({int_arg3}, {int_arg3_2})"

django_typer/tests/apps/test_app/management/commands/adapted.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66

77
class Command(TyperCommand):
88
def handle(self, message: str):
9+
assert isinstance(self, Command)
910
self.echo(f"test_app: {message}")

django_typer/tests/apps/test_app/management/commands/adapted2.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,22 +32,27 @@ def init(
3232
),
3333
] = verbosity,
3434
):
35+
assert isinstance(self, Command)
3536
self.verbosity = verbosity
3637

3738
@group()
3839
def grp2(self, flag1: bool = False):
3940
"""Group 2, take a flag"""
41+
assert isinstance(self, Command)
4042
self.flag1 = flag1
4143

4244
@grp2.command()
4345
def grp2_cmd1(self, cmd1_arg: str):
46+
assert isinstance(self, Command)
4447
return f"test_app::adapted2({self.verbosity})::grp2({self.flag1})::grp2_cmd1({cmd1_arg})"
4548

4649
@grp2.command()
4750
def grp2_cmd2(self, cmd2_arg: str):
51+
assert isinstance(self, Command)
4852
return f"test_app::adapted2({self.verbosity})::grp2({self.flag1})::grp2_cmd2({cmd2_arg})"
4953

5054
@Interference.grp1.command()
5155
def grp1_ext(self):
5256
"""Inherit/extend grp1 with a cmd"""
57+
assert isinstance(self, Command)
5358
return f"test_app::adapted2({self.verbosity})::grp1({self.argg3})::grp1_ext()"

django_typer/tests/apps/test_app/management/commands/basic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@
66

77
class Command(TyperCommand):
88
def handle(self, arg1: str, arg2: str, arg3: float = 0.5, arg4: int = 1):
9-
assert self.__class__ == Command
9+
assert self.__class__ is Command
1010
opts = {"arg1": arg1, "arg2": arg2, "arg3": arg3, "arg4": arg4}
1111
return json.dumps(opts)

django_typer/tests/apps/test_app/management/commands/callback1.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ def init(self, p1: int, flag1: bool = False, flag2: bool = True):
1313
"""
1414
The callback to initialize the command.
1515
"""
16-
assert self.__class__ == Command
16+
assert self.__class__ is Command
1717
self.parameters = {"p1": p1, "flag1": flag1, "flag2": flag2}
1818

1919
def handle(self, arg1: str, arg2: str, arg3: float = 0.5, arg4: int = 1):
20-
assert self.__class__ == Command
20+
assert self.__class__ is Command
2121
self.parameters.update({"arg1": arg1, "arg2": arg2, "arg3": arg3, "arg4": arg4})
2222
return json.dumps(self.parameters)

0 commit comments

Comments
 (0)