mirror of
https://github.com/django-components/django-components.git
synced 2025-08-30 02:44:06 +00:00
feat: allow extensions to add commands (#1017)
* feat: allow extensions to add commands * refactor: fix tests * refactor: more test fix * refactor: more test fixes * refactor: more linter fixes
This commit is contained in:
parent
3a139127cd
commit
d3d2d0ab08
28 changed files with 2320 additions and 397 deletions
241
tests/test_command_ext.py
Normal file
241
tests/test_command_ext.py
Normal file
|
@ -0,0 +1,241 @@
|
|||
import sys
|
||||
from io import StringIO
|
||||
from textwrap import dedent
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.core.management import call_command
|
||||
from django_components import CommandArg, CommandArgGroup, ComponentCommand, ComponentExtension
|
||||
from django_components.testing import djc_test
|
||||
from .testutils import setup_test_config
|
||||
|
||||
setup_test_config({"autodiscover": False})
|
||||
|
||||
# NOTE: Argparse changed how the optional args are displayed in Python 3.11+
|
||||
if sys.version_info >= (3, 10):
|
||||
OPTIONS_TITLE = "options"
|
||||
else:
|
||||
OPTIONS_TITLE = "optional arguments"
|
||||
|
||||
|
||||
class EmptyExtension(ComponentExtension):
|
||||
name = "empty"
|
||||
|
||||
|
||||
class DummyCommand(ComponentCommand):
|
||||
name = "dummy_cmd"
|
||||
help = "Dummy command description."
|
||||
|
||||
arguments = [
|
||||
CommandArg(
|
||||
name_or_flags="--foo",
|
||||
help="Foo description.",
|
||||
),
|
||||
CommandArgGroup(
|
||||
title="group bar",
|
||||
description="Group description.",
|
||||
arguments=[
|
||||
CommandArg(
|
||||
name_or_flags="--bar",
|
||||
help="Bar description.",
|
||||
),
|
||||
CommandArg(
|
||||
name_or_flags="--baz",
|
||||
help="Baz description.",
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
kwargs.pop("_command")
|
||||
kwargs.pop("_parser")
|
||||
sorted_kwargs = dict(sorted(kwargs.items()))
|
||||
print(f"DummyCommand.handle: args={args}, kwargs={sorted_kwargs}")
|
||||
|
||||
|
||||
class DummyExtension(ComponentExtension):
|
||||
name = "dummy"
|
||||
|
||||
commands = [
|
||||
DummyCommand,
|
||||
]
|
||||
|
||||
|
||||
@djc_test
|
||||
class TestExtensionsCommand:
|
||||
def test_root_command(self):
|
||||
out = StringIO()
|
||||
with patch("sys.stdout", new=out):
|
||||
call_command("components", "ext")
|
||||
output = out.getvalue()
|
||||
|
||||
assert (
|
||||
output
|
||||
== dedent(
|
||||
f"""
|
||||
usage: components ext [-h] {{list,run}} ...
|
||||
|
||||
Run extension commands.
|
||||
|
||||
{OPTIONS_TITLE}:
|
||||
-h, --help show this help message and exit
|
||||
|
||||
subcommands:
|
||||
{{list,run}}
|
||||
list List all extensions.
|
||||
run Run a command added by an extension.
|
||||
"""
|
||||
).lstrip()
|
||||
)
|
||||
|
||||
|
||||
@djc_test
|
||||
class TestExtensionsListCommand:
|
||||
def test_list_command_default(self):
|
||||
out = StringIO()
|
||||
with patch("sys.stdout", new=out):
|
||||
call_command("components", "ext", "list")
|
||||
output = out.getvalue()
|
||||
|
||||
assert "Installed extensions:\nview\n" == output
|
||||
|
||||
# Check that it omits the title when verbose is 0
|
||||
out = StringIO()
|
||||
with patch("sys.stdout", new=out):
|
||||
call_command("components", "ext", "list", "-v", "0")
|
||||
output = out.getvalue()
|
||||
|
||||
assert "view\n" == output
|
||||
|
||||
@djc_test(
|
||||
components_settings={"extensions": [EmptyExtension, DummyExtension]},
|
||||
)
|
||||
def test_list_command_extra(self):
|
||||
out = StringIO()
|
||||
with patch("sys.stdout", new=out):
|
||||
call_command("components", "ext", "list")
|
||||
output = out.getvalue()
|
||||
|
||||
assert "Installed extensions:\nview\nempty\ndummy\n" == output
|
||||
|
||||
# Check that it omits the title when verbose is 0
|
||||
out = StringIO()
|
||||
with patch("sys.stdout", new=out):
|
||||
call_command("components", "ext", "list", "-v", "0")
|
||||
output = out.getvalue()
|
||||
|
||||
assert "view\nempty\ndummy\n" == output
|
||||
|
||||
|
||||
@djc_test
|
||||
class TestExtensionsRunCommand:
|
||||
@djc_test(
|
||||
components_settings={"extensions": [EmptyExtension, DummyExtension]},
|
||||
)
|
||||
def test_run_command_root(self):
|
||||
out = StringIO()
|
||||
with patch("sys.stdout", new=out):
|
||||
call_command("components", "ext", "run")
|
||||
output = out.getvalue()
|
||||
|
||||
assert (
|
||||
output
|
||||
== dedent(
|
||||
f"""
|
||||
usage: components ext run [-h] {{view,empty,dummy}} ...
|
||||
|
||||
Run a command added by an extension.
|
||||
|
||||
{OPTIONS_TITLE}:
|
||||
-h, --help show this help message and exit
|
||||
|
||||
subcommands:
|
||||
{{view,empty,dummy}}
|
||||
view Run commands added by the 'view' extension.
|
||||
empty Run commands added by the 'empty' extension.
|
||||
dummy Run commands added by the 'dummy' extension.
|
||||
"""
|
||||
).lstrip()
|
||||
)
|
||||
|
||||
@djc_test(
|
||||
components_settings={"extensions": [EmptyExtension, DummyExtension]},
|
||||
)
|
||||
def test_run_command_ext_empty(self):
|
||||
out = StringIO()
|
||||
with patch("sys.stdout", new=out):
|
||||
call_command("components", "ext", "run", "empty")
|
||||
output = out.getvalue()
|
||||
|
||||
assert (
|
||||
output
|
||||
== dedent(
|
||||
f"""
|
||||
usage: components ext run empty [-h]
|
||||
|
||||
Run commands added by the 'empty' extension.
|
||||
|
||||
{OPTIONS_TITLE}:
|
||||
-h, --help show this help message and exit
|
||||
"""
|
||||
).lstrip()
|
||||
)
|
||||
|
||||
@djc_test(
|
||||
components_settings={"extensions": [EmptyExtension, DummyExtension]},
|
||||
)
|
||||
def test_run_command_ext_with_commands(self):
|
||||
out = StringIO()
|
||||
with patch("sys.stdout", new=out):
|
||||
call_command("components", "ext", "run", "dummy")
|
||||
output = out.getvalue()
|
||||
|
||||
assert (
|
||||
output
|
||||
== dedent(
|
||||
f"""
|
||||
usage: components ext run dummy [-h] {{dummy_cmd}} ...
|
||||
|
||||
Run commands added by the 'dummy' extension.
|
||||
|
||||
{OPTIONS_TITLE}:
|
||||
-h, --help show this help message and exit
|
||||
|
||||
subcommands:
|
||||
{{dummy_cmd}}
|
||||
dummy_cmd Dummy command description.
|
||||
"""
|
||||
).lstrip()
|
||||
)
|
||||
|
||||
@djc_test(
|
||||
components_settings={"extensions": [EmptyExtension, DummyExtension]},
|
||||
)
|
||||
def test_run_command_ext_command(self):
|
||||
out = StringIO()
|
||||
with patch("sys.stdout", new=out):
|
||||
call_command("components", "ext", "run", "dummy", "dummy_cmd")
|
||||
output = out.getvalue()
|
||||
|
||||
# NOTE: The dummy command prints out the kwargs, which is what we check for here
|
||||
assert (
|
||||
output
|
||||
== dedent(
|
||||
"""
|
||||
DummyCommand.handle: args=(), kwargs={'bar': None, 'baz': None, 'foo': None, 'force_color': False, 'no_color': False, 'pythonpath': None, 'settings': None, 'skip_checks': True, 'traceback': False, 'verbosity': 1}
|
||||
""" # noqa: E501
|
||||
).lstrip()
|
||||
)
|
||||
|
||||
@djc_test(
|
||||
components_settings={"extensions": [EmptyExtension, DummyExtension]},
|
||||
)
|
||||
def test_prints_error_if_command_not_found(self):
|
||||
out = StringIO()
|
||||
with patch("sys.stderr", new=out):
|
||||
try:
|
||||
call_command("components", "ext", "run", "dummy", "dummy_cmd_not_found")
|
||||
except SystemExit:
|
||||
output = out.getvalue()
|
||||
|
||||
assert "invalid choice: 'dummy_cmd_not_found'" in output
|
Loading…
Add table
Add a link
Reference in a new issue