From 97c91a1784a27d7fbf25804175500085f375c37e Mon Sep 17 00:00:00 2001 From: Brian Kohan Date: Wed, 20 Nov 2024 21:58:57 -0800 Subject: [PATCH] fix #19 --- .../management/commands/shellcompletion.py | 15 +++++++++++++-- doc/source/changelog.rst | 1 + tests/shellcompletion/__init__.py | 5 ++--- tests/test_examples.py | 16 ++++++---------- tests/test_parser_completers.py | 18 ++++++++++++++++++ 5 files changed, 40 insertions(+), 15 deletions(-) diff --git a/django_typer/management/commands/shellcompletion.py b/django_typer/management/commands/shellcompletion.py index fce8a91..c6225d1 100644 --- a/django_typer/management/commands/shellcompletion.py +++ b/django_typer/management/commands/shellcompletion.py @@ -599,11 +599,22 @@ def get_completion() -> None: ) call_fallback(fallback) + buffer = io.StringIO() try: - get_completion() + with contextlib.redirect_stdout(buffer): + # typer's internal behavior is to print and exit, we need + # to intercept this workflow to do our post processing and + # honor the configured stdout + get_completion() + + # leave this in, incase the interface changes to not exit + return buffer.getvalue() # pragma: no cover except SystemExit: + completion_str = buffer.getvalue() + if completion_str: + return completion_str if cmd_str: - return + return "" raise def django_fallback(self): diff --git a/doc/source/changelog.rst b/doc/source/changelog.rst index c5cd0b9..03146e3 100644 --- a/doc/source/changelog.rst +++ b/doc/source/changelog.rst @@ -10,6 +10,7 @@ v3.0.0 (202X-XX-XX) * Implemented `Add showcase of commands using django-typer to docs `_ * Implemented `Add a @finalize decorator for functions to collect/operate on subroutine results. `_ * Fixed `Remove management imports in django_typer/__init__.py `_ +* Fixed `shellcompletion complete should print to the command's stdout. `_ v2.4.0 (2024-11-07) =================== diff --git a/tests/shellcompletion/__init__.py b/tests/shellcompletion/__init__.py index 06df954..63a3c89 100644 --- a/tests/shellcompletion/__init__.py +++ b/tests/shellcompletion/__init__.py @@ -195,10 +195,9 @@ def run_command_completion(self): # and the completion on linux in bash specifically self.assertTrue(re.match(r".*complet\s*ion.*", completions)) completions = self.get_completions(self.launch_script, " ") - self.assertIn("makemigrations", completions) - self.assertIn("migrate", completions) + self.assertIn("adapted", completions) + self.assertIn("help_precedence", completions) self.assertIn("closepoll", completions) - self.assertIn("basic", completions) def test_shell_complete(self): with self.assertRaises(AssertionError): diff --git a/tests/test_examples.py b/tests/test_examples.py index adb1d48..9059813 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -475,12 +475,10 @@ def test_app_labels_completer(self): ) shellcompletion = get_command("shellcompletion", ShellCompletion) + completions = shellcompletion.complete( + f"{self.app_labels_cmd} ", shell=Shells.zsh + ) - stdout = StringIO() - with redirect_stdout(stdout): - shellcompletion.complete(f"{self.app_labels_cmd} ", shell=Shells.zsh) - - completions = stdout.getvalue() self.assertTrue('"contenttypes"' in completions) self.assertTrue('"completers"' in completions) self.assertTrue('"django_typer"' in completions) @@ -489,11 +487,9 @@ def test_app_labels_completer(self): self.assertTrue('"sessions"' in completions) self.assertTrue('"messages"' in completions) - stdout.truncate(0) - stdout.seek(0) - - with redirect_stdout(stdout): - shellcompletion.complete(f"{self.app_labels_cmd} a", shell=Shells.zsh) + completions = shellcompletion.complete( + f"{self.app_labels_cmd} a", shell=Shells.zsh + ) self.assertTrue('"admin"' in completions) self.assertTrue('"auth"' in completions) diff --git a/tests/test_parser_completers.py b/tests/test_parser_completers.py index 36e2dee..b956f89 100644 --- a/tests/test_parser_completers.py +++ b/tests/test_parser_completers.py @@ -104,6 +104,24 @@ def test_app_label_parser_idempotency(self): poll_app = apps.get_app_config("tests_apps_examples_polls") self.assertEqual(parse_app_label(poll_app), poll_app) + def test_shellcompletion_stdout(self): + from django_typer.management.commands.shellcompletion import ( + Command as ShellCompletion, + ) + + shellcompletion = get_command("shellcompletion", ShellCompletion) + result = shellcompletion.complete("completion ") + self.assertTrue("test_app" in result) + self.assertTrue("tests_apps_util" in result) + self.assertTrue("django_typer" in result) + + result2 = StringIO() + call_command("shellcompletion", "complete", "completion ", stdout=result2) + stdout_result = result2.getvalue().strip() + self.assertTrue("test_app" in stdout_result) + self.assertTrue("tests_apps_util" in stdout_result) + self.assertTrue("django_typer" in stdout_result) + def test_app_label_parser_completers(self): from django.apps import apps