Coverage for tests/tests.py: 97%
524 statements
« prev ^ index » next coverage.py v7.3.4, created at 2024-01-20 18:02 +0000
« prev ^ index » next coverage.py v7.3.4, created at 2024-01-20 18:02 +0000
1import inspect
2import json
3import os
4import re
5import subprocess
6import sys
7from io import StringIO
8from pathlib import Path
9from typing import Any
11import django
12import typer
13from click.exceptions import UsageError
14from django.apps import apps
15from django.core.management import call_command
16from django.test import TestCase, override_settings
17from sklearn.feature_extraction.text import TfidfVectorizer
18from sklearn.metrics.pairwise import cosine_similarity
20from django_typer import TyperCommand, get_command, group
21from django_typer.tests.utils import read_django_parameters
24def similarity(text1, text2):
25 """
26 Compute the cosine similarity between two texts.
27 https://en.wikipedia.org/wiki/Cosine_similarity
29 We use this to lazily evaluate the output of --help to our
30 renderings.
31 """
32 vectorizer = TfidfVectorizer()
33 tfidf_matrix = vectorizer.fit_transform([text1, text2])
34 return cosine_similarity(tfidf_matrix[0:1], tfidf_matrix[1:2])[0][0]
37manage_py = Path(__file__).parent.parent.parent / "manage.py"
38TESTS_DIR = Path(__file__).parent
41def get_named_arguments(function):
42 sig = inspect.signature(function)
43 return [
44 name
45 for name, param in sig.parameters.items()
46 if param.default != inspect.Parameter.empty
47 ]
50def run_command(command, *args, parse_json=True):
51 cwd = os.getcwd()
52 try:
53 os.chdir(manage_py.parent)
54 result = subprocess.run(
55 [sys.executable, f"./{manage_py.name}", command, *args],
56 capture_output=True,
57 text=True,
58 )
60 # Check the return code to ensure the script ran successfully
61 if result.returncode != 0:
62 return result.stderr or result.stdout or ""
64 # Parse the output
65 if result.stdout:
66 if parse_json:
67 try:
68 return json.loads(result.stdout)
69 except json.JSONDecodeError:
70 return result.stdout or result.stderr or ""
71 return result.stdout
72 return result.stderr or ""
73 finally:
74 os.chdir(cwd)
77class BasicTests(TestCase):
78 def test_command_line(self):
79 self.assertEqual(
80 run_command("basic", "a1", "a2"),
81 {"arg1": "a1", "arg2": "a2", "arg3": 0.5, "arg4": 1},
82 )
84 self.assertEqual(
85 run_command("basic", "a1", "a2", "--arg3", "0.75", "--arg4", "2"),
86 {"arg1": "a1", "arg2": "a2", "arg3": 0.75, "arg4": 2},
87 )
89 def test_call_command(self):
90 out = StringIO()
91 returned_options = json.loads(call_command("basic", ["a1", "a2"], stdout=out))
92 self.assertEqual(
93 returned_options, {"arg1": "a1", "arg2": "a2", "arg3": 0.5, "arg4": 1}
94 )
96 def test_call_command_stdout(self):
97 out = StringIO()
98 call_command("basic", ["a1", "a2"], stdout=out)
99 printed_options = json.loads(out.getvalue())
100 self.assertEqual(
101 printed_options, {"arg1": "a1", "arg2": "a2", "arg3": 0.5, "arg4": 1}
102 )
104 def test_get_version(self):
105 self.assertEqual(
106 str(run_command("basic", "--version")).strip(), django.get_version()
107 )
109 def test_call_direct(self):
110 basic = get_command("basic")
111 self.assertEqual(
112 json.loads(basic.handle("a1", "a2")),
113 {"arg1": "a1", "arg2": "a2", "arg3": 0.5, "arg4": 1},
114 )
116 from django_typer.tests.test_app.management.commands.basic import (
117 Command as Basic,
118 )
120 self.assertEqual(
121 json.loads(Basic()("a1", "a2", arg3=0.75, arg4=2)),
122 {"arg1": "a1", "arg2": "a2", "arg3": 0.75, "arg4": 2},
123 )
125 def test_parser(self):
126 basic_cmd = get_command("basic")
127 parser = basic_cmd.create_parser("./manage.py", "basic")
128 with self.assertRaises(NotImplementedError):
129 parser.add_argument()
132class CommandDefinitionTests(TestCase):
133 def test_group_callback_throws(self):
134 class CBTestCommand(TyperCommand):
135 @group()
136 def grp():
137 pass
139 grp.group()
141 def grp2():
142 pass
144 with self.assertRaises(NotImplementedError):
146 class CommandBad(TyperCommand):
147 @group()
148 def grp():
149 pass
151 @grp.callback()
152 def bad_callback():
153 pass
155 with self.assertRaises(NotImplementedError):
157 class CommandBad(CBTestCommand):
158 @CBTestCommand.grp.callback()
159 def bad_callback():
160 pass
163class InterfaceTests(TestCase):
164 """
165 Make sure the django_typer decorator interfaces match the
166 typer decorator interfaces. We don't simply pass variadic arguments
167 to the typer decorator because we want the IDE to offer auto complete
168 suggestions. This is a "developer experience" concession
169 """
171 def test_command_interface_matches(self):
172 from django_typer import command
174 command_params = set(get_named_arguments(command))
175 typer_params = set(get_named_arguments(typer.Typer.command))
177 self.assertFalse(command_params.symmetric_difference(typer_params))
179 def test_initialize_interface_matches(self):
180 from django_typer import initialize
182 initialize_params = set(get_named_arguments(initialize))
183 typer_params = set(get_named_arguments(typer.Typer.callback))
185 self.assertFalse(initialize_params.symmetric_difference(typer_params))
187 def test_typer_command_interface_matches(self):
188 from django_typer import _TyperCommandMeta
190 typer_command_params = set(get_named_arguments(_TyperCommandMeta.__new__))
191 typer_params = set(get_named_arguments(typer.Typer.__init__))
192 typer_params.remove("name")
193 typer_params.remove("add_completion")
194 self.assertFalse(typer_command_params.symmetric_difference(typer_params))
196 def test_group_interface_matches(self):
197 from django_typer import GroupFunction
199 typer_command_params = set(get_named_arguments(GroupFunction.group))
200 typer_params = set(get_named_arguments(typer.Typer.add_typer))
201 typer_params.remove("callback")
202 self.assertFalse(typer_command_params.symmetric_difference(typer_params))
204 def test_group_command_interface_matches(self):
205 from django_typer import GroupFunction
207 typer_command_params = set(get_named_arguments(GroupFunction.command))
208 typer_params = set(get_named_arguments(typer.Typer.command))
209 self.assertFalse(typer_command_params.symmetric_difference(typer_params))
212class MultiTests(TestCase):
213 def test_command_line(self):
214 self.assertEqual(
215 run_command("multi", "cmd1", "/path/one", "/path/two"),
216 {"files": ["/path/one", "/path/two"], "flag1": False},
217 )
219 self.assertEqual(
220 run_command("multi", "cmd1", "/path/four", "/path/three", "--flag1"),
221 {"files": ["/path/four", "/path/three"], "flag1": True},
222 )
224 self.assertEqual(
225 run_command("multi", "sum", "1.2", "3.5", " -12.3"), sum([1.2, 3.5, -12.3])
226 )
228 self.assertEqual(run_command("multi", "cmd3"), {})
230 def test_call_command(self):
231 ret = json.loads(call_command("multi", ["cmd1", "/path/one", "/path/two"]))
232 self.assertEqual(ret, {"files": ["/path/one", "/path/two"], "flag1": False})
234 ret = json.loads(
235 call_command("multi", ["cmd1", "/path/four", "/path/three", "--flag1"])
236 )
237 self.assertEqual(ret, {"files": ["/path/four", "/path/three"], "flag1": True})
239 ret = json.loads(call_command("multi", ["sum", "1.2", "3.5", " -12.3"]))
240 self.assertEqual(ret, sum([1.2, 3.5, -12.3]))
242 ret = json.loads(call_command("multi", ["cmd3"]))
243 self.assertEqual(ret, {})
245 def test_call_command_stdout(self):
246 out = StringIO()
247 call_command("multi", ["cmd1", "/path/one", "/path/two"], stdout=out)
248 self.assertEqual(
249 json.loads(out.getvalue()),
250 {"files": ["/path/one", "/path/two"], "flag1": False},
251 )
253 out = StringIO()
254 call_command(
255 "multi", ["cmd1", "/path/four", "/path/three", "--flag1"], stdout=out
256 )
257 self.assertEqual(
258 json.loads(out.getvalue()),
259 {"files": ["/path/four", "/path/three"], "flag1": True},
260 )
262 out = StringIO()
263 call_command("multi", ["sum", "1.2", "3.5", " -12.3"], stdout=out)
264 self.assertEqual(json.loads(out.getvalue()), sum([1.2, 3.5, -12.3]))
266 out = StringIO()
267 call_command("multi", ["cmd3"], stdout=out)
268 self.assertEqual(json.loads(out.getvalue()), {})
270 def test_get_version(self):
271 self.assertEqual(
272 str(run_command("multi", "--version")).strip(), django.get_version()
273 )
274 self.assertEqual(
275 str(run_command("multi", "--version", "cmd1")).strip(), django.get_version()
276 )
277 self.assertEqual(
278 str(run_command("multi", "--version", "sum")).strip(), django.get_version()
279 )
280 self.assertEqual(
281 str(run_command("multi", "--version", "cmd3")).strip(), django.get_version()
282 )
284 def test_call_direct(self):
285 multi = get_command("multi")
287 self.assertEqual(
288 json.loads(multi.cmd1(["/path/one", "/path/two"])),
289 {"files": ["/path/one", "/path/two"], "flag1": False},
290 )
292 self.assertEqual(
293 json.loads(multi.cmd1(["/path/four", "/path/three"], flag1=True)),
294 {"files": ["/path/four", "/path/three"], "flag1": True},
295 )
297 self.assertEqual(float(multi.sum([1.2, 3.5, -12.3])), sum([1.2, 3.5, -12.3]))
299 self.assertEqual(json.loads(multi.cmd3()), {})
302class TestGetCommand(TestCase):
303 def test_get_command(self):
304 from django_typer.tests.test_app.management.commands.basic import (
305 Command as Basic,
306 )
308 basic = get_command("basic")
309 assert basic.__class__ == Basic
311 from django_typer.tests.test_app.management.commands.multi import (
312 Command as Multi,
313 )
315 multi = get_command("multi")
316 assert multi.__class__ == Multi
317 cmd1 = get_command("multi", "cmd1")
318 assert cmd1.__func__ is multi.cmd1.__func__
319 sum = get_command("multi", "sum")
320 assert sum.__func__ is multi.sum.__func__
321 cmd3 = get_command("multi", "cmd3")
322 assert cmd3.__func__ is multi.cmd3.__func__
324 from django_typer.tests.test_app.management.commands.callback1 import (
325 Command as Callback1,
326 )
328 callback1 = get_command("callback1")
329 assert callback1.__class__ == Callback1
331 # callbacks are not commands
332 with self.assertRaises(ValueError):
333 get_command("callback1", "init")
336class CallbackTests(TestCase):
337 cmd_name = "callback1"
339 def test_helps(self, top_level_only=False):
340 buffer = StringIO()
341 cmd = get_command(self.cmd_name, stdout=buffer, no_color=True)
343 help_output_top = run_command(self.cmd_name, "--help")
344 cmd.print_help("./manage.py", self.cmd_name)
345 self.assertEqual(help_output_top.strip(), buffer.getvalue().strip())
346 self.assertIn(f"Usage: ./manage.py {self.cmd_name} [OPTIONS]", help_output_top)
348 if not top_level_only:
349 buffer.truncate(0)
350 buffer.seek(0)
351 callback_help = run_command(self.cmd_name, "5", self.cmd_name, "--help")
352 cmd.print_help("./manage.py", self.cmd_name, self.cmd_name)
353 self.assertEqual(callback_help.strip(), buffer.getvalue().strip())
354 self.assertIn(
355 f"Usage: ./manage.py {self.cmd_name} P1 {self.cmd_name} [OPTIONS] ARG1 ARG2",
356 callback_help,
357 )
359 def test_command_line(self):
360 self.assertEqual(
361 run_command(self.cmd_name, "5", self.cmd_name, "a1", "a2"),
362 {
363 "p1": 5,
364 "flag1": False,
365 "flag2": True,
366 "arg1": "a1",
367 "arg2": "a2",
368 "arg3": 0.5,
369 "arg4": 1,
370 },
371 )
373 self.assertEqual(
374 run_command(
375 self.cmd_name,
376 "--flag1",
377 "--no-flag2",
378 "6",
379 self.cmd_name,
380 "a1",
381 "a2",
382 "--arg3",
383 "0.75",
384 "--arg4",
385 "2",
386 ),
387 {
388 "p1": 6,
389 "flag1": True,
390 "flag2": False,
391 "arg1": "a1",
392 "arg2": "a2",
393 "arg3": 0.75,
394 "arg4": 2,
395 },
396 )
398 def test_call_command(self, should_raise=True):
399 ret = json.loads(
400 call_command(
401 self.cmd_name,
402 *["5", self.cmd_name, "a1", "a2"],
403 **{"p1": 5, "arg1": "a1", "arg2": "a2"},
404 )
405 )
406 self.assertEqual(
407 ret,
408 {
409 "p1": 5,
410 "flag1": False,
411 "flag2": True,
412 "arg1": "a1",
413 "arg2": "a2",
414 "arg3": 0.5,
415 "arg4": 1,
416 },
417 )
419 ret = json.loads(
420 call_command(
421 self.cmd_name,
422 *[
423 "--flag1",
424 "--no-flag2",
425 "6",
426 self.cmd_name,
427 "a1",
428 "a2",
429 "--arg3",
430 "0.75",
431 "--arg4",
432 "2",
433 ],
434 )
435 )
436 self.assertEqual(
437 ret,
438 {
439 "p1": 6,
440 "flag1": True,
441 "flag2": False,
442 "arg1": "a1",
443 "arg2": "a2",
444 "arg3": 0.75,
445 "arg4": 2,
446 },
447 )
449 # show that order matters args vs options
450 interspersed = [
451 lambda: call_command(
452 self.cmd_name,
453 *[
454 "6",
455 "--flag1",
456 "--no-flag2",
457 self.cmd_name,
458 "n1",
459 "n2",
460 "--arg3",
461 "0.2",
462 "--arg4",
463 "9",
464 ],
465 ),
466 lambda: call_command(
467 self.cmd_name,
468 *[
469 "--no-flag2",
470 "6",
471 "--flag1",
472 self.cmd_name,
473 "--arg4",
474 "9",
475 "n1",
476 "n2",
477 "--arg3",
478 "0.2",
479 ],
480 ),
481 ]
482 expected = {
483 "p1": 6,
484 "flag1": True,
485 "flag2": False,
486 "arg1": "n1",
487 "arg2": "n2",
488 "arg3": 0.2,
489 "arg4": 9,
490 }
491 if should_raise:
492 for call_cmd in interspersed:
493 if should_raise: 493 ↛ 497line 493 didn't jump to line 497, because the condition on line 493 was never false
494 with self.assertRaises(BaseException):
495 call_cmd()
496 else:
497 self.assertEqual(json.loads(call_cmd()), expected)
499 def test_call_command_stdout(self):
500 out = StringIO()
501 call_command(
502 self.cmd_name,
503 [
504 "--flag1",
505 "--no-flag2",
506 "6",
507 self.cmd_name,
508 "a1",
509 "a2",
510 "--arg3",
511 "0.75",
512 "--arg4",
513 "2",
514 ],
515 stdout=out,
516 )
518 self.assertEqual(
519 json.loads(out.getvalue()),
520 {
521 "p1": 6,
522 "flag1": True,
523 "flag2": False,
524 "arg1": "a1",
525 "arg2": "a2",
526 "arg3": 0.75,
527 "arg4": 2,
528 },
529 )
531 def test_get_version(self):
532 self.assertEqual(
533 str(run_command(self.cmd_name, "--version")).strip(), django.get_version()
534 )
535 self.assertEqual(
536 str(run_command(self.cmd_name, "--version", "6", self.cmd_name)).strip(),
537 django.get_version(),
538 )
540 def test_call_direct(self):
541 cmd = get_command(self.cmd_name)
543 self.assertEqual(
544 json.loads(cmd(arg1="a1", arg2="a2", arg3=0.2)),
545 {"arg1": "a1", "arg2": "a2", "arg3": 0.2, "arg4": 1},
546 )
549class Callback2Tests(CallbackTests):
550 cmd_name = "callback2"
552 def test_call_command(self):
553 super().test_call_command(should_raise=False)
555 def test_helps(self, top_level_only=False):
556 # we only run the top level help comparison because when
557 # interspersed args are allowed its impossible to get the
558 # subcommand to print its help
559 super().test_helps(top_level_only=True)
562class TestDjangoParameters(TestCase):
563 commands = [
564 ("dj_params1", []),
565 ("dj_params2", ["cmd1"]),
566 ("dj_params2", ["cmd2"]),
567 ("dj_params3", ["cmd1"]),
568 ("dj_params3", ["cmd2"]),
569 ("dj_params4", []),
570 ]
572 def test_settings(self):
573 for cmd, args in self.commands:
574 run_command(cmd, "--settings", "django_typer.tests.settings2", *args)
575 self.assertEqual(read_django_parameters().get("settings", None), 2)
577 def test_color_params(self):
578 for cmd, args in self.commands:
579 run_command(cmd, "--no-color", *args)
580 self.assertEqual(read_django_parameters().get("no_color", False), True)
581 run_command(cmd, "--force-color", *args)
582 self.assertEqual(read_django_parameters().get("no_color", True), False)
584 result = run_command(cmd, "--force-color", "--no-color", *args)
585 self.assertTrue("CommandError" in result)
586 self.assertTrue("--no-color" in result)
587 self.assertTrue("--force-color" in result)
589 call_command(cmd, args, no_color=True)
590 self.assertEqual(read_django_parameters().get("no_color", False), True)
591 call_command(cmd, args, force_color=True)
592 self.assertEqual(read_django_parameters().get("no_color", True), False)
593 with self.assertRaises(BaseException):
594 call_command(cmd, args, force_color=True, no_color=True)
596 def test_pythonpath(self):
597 added = str(Path(__file__).parent.absolute())
598 self.assertTrue(added not in sys.path)
599 for cmd, args in self.commands:
600 run_command(cmd, "--pythonpath", added, *args)
601 self.assertTrue(added in read_django_parameters().get("python_path", []))
603 def test_skip_checks(self):
604 for cmd, args in self.commands:
605 result = run_command(
606 cmd, "--settings", "django_typer.tests.settings_fail_check", *args
607 )
608 self.assertTrue("SystemCheckError" in result)
609 self.assertTrue("test_app.E001" in result)
611 result = run_command(
612 cmd,
613 "--skip-checks",
614 "--settings",
615 "django_typer.tests.settings_fail_check",
616 *args,
617 )
618 self.assertFalse("SystemCheckError" in result)
619 self.assertFalse("test_app.E001" in result)
621 @override_settings(DJANGO_TYPER_FAIL_CHECK=True)
622 def test_skip_checks_call(self):
623 for cmd, args in self.commands:
624 from django.core.management.base import SystemCheckError
626 with self.assertRaises(SystemCheckError):
627 call_command(cmd, *args, skip_checks=False)
629 # when you call_command and don't supply skip_checks, it will default to True!
630 call_command(cmd, *args, skip_checks=True)
631 call_command(cmd, *args)
633 def test_traceback(self):
634 # traceback does not come into play with call_command
635 for cmd, args in self.commands:
636 result = run_command(cmd, *args, "--throw")
637 if cmd != "dj_params4":
638 self.assertFalse("Traceback" in result)
639 else:
640 self.assertTrue("Traceback" in result)
642 if cmd != "dj_params4":
643 result_tb = run_command(cmd, "--traceback", *args, "--throw")
644 self.assertTrue("Traceback" in result_tb)
645 else:
646 result_tb = run_command(cmd, "--no-traceback", *args, "--throw")
647 self.assertFalse("Traceback" in result_tb)
649 def test_verbosity(self):
650 run_command("dj_params3", "cmd1")
651 self.assertEqual(read_django_parameters().get("verbosity", None), 1)
653 call_command("dj_params3", ["cmd1"])
654 self.assertEqual(read_django_parameters().get("verbosity", None), 1)
656 run_command("dj_params3", "--verbosity", "2", "cmd1")
657 self.assertEqual(read_django_parameters().get("verbosity", None), 2)
659 call_command("dj_params3", ["cmd1"], verbosity=2)
660 self.assertEqual(read_django_parameters().get("verbosity", None), 2)
662 run_command("dj_params3", "--verbosity", "0", "cmd2")
663 self.assertEqual(read_django_parameters().get("verbosity", None), 0)
665 call_command("dj_params3", ["cmd2"], verbosity=0)
666 self.assertEqual(read_django_parameters().get("verbosity", None), 0)
668 run_command("dj_params4")
669 self.assertEqual(read_django_parameters().get("verbosity", None), 1)
671 call_command("dj_params4")
672 self.assertEqual(read_django_parameters().get("verbosity", None), 1)
674 run_command("dj_params4", "--verbosity", "2")
675 self.assertEqual(read_django_parameters().get("verbosity", None), 2)
677 call_command("dj_params4", [], verbosity=2)
678 self.assertEqual(read_django_parameters().get("verbosity", None), 2)
680 run_command("dj_params4", "--verbosity", "0")
681 self.assertEqual(read_django_parameters().get("verbosity", None), 0)
683 call_command("dj_params4", [], verbosity=0)
684 self.assertEqual(read_django_parameters().get("verbosity", None), 0)
687class TestHelpPrecedence(TestCase):
688 def test_help_precedence1(self):
689 buffer = StringIO()
690 cmd = get_command("help_precedence1", stdout=buffer)
691 cmd.print_help("./manage.py", "help_precedence1")
692 self.assertTrue(
693 re.search(
694 r"help_precedence1\s+Test minimal TyperCommand subclass - command method",
695 buffer.getvalue(),
696 )
697 )
698 self.assertIn(
699 "Test minimal TyperCommand subclass - typer param", buffer.getvalue()
700 )
702 def test_help_precedence2(self):
703 buffer = StringIO()
704 cmd = get_command("help_precedence2", stdout=buffer)
705 cmd.print_help("./manage.py", "help_precedence2")
706 self.assertIn(
707 "Test minimal TyperCommand subclass - class member", buffer.getvalue()
708 )
709 self.assertTrue(
710 re.search(
711 r"help_precedence2\s+Test minimal TyperCommand subclass - command method",
712 buffer.getvalue(),
713 )
714 )
716 def test_help_precedence3(self):
717 buffer = StringIO()
718 cmd = get_command("help_precedence3", stdout=buffer)
719 cmd.print_help("./manage.py", "help_precedence3")
720 self.assertTrue(
721 re.search(
722 r"help_precedence3\s+Test minimal TyperCommand subclass - command method",
723 buffer.getvalue(),
724 )
725 )
726 self.assertIn(
727 "Test minimal TyperCommand subclass - callback method", buffer.getvalue()
728 )
730 def test_help_precedence4(self):
731 buffer = StringIO()
732 cmd = get_command("help_precedence4", stdout=buffer)
733 cmd.print_help("./manage.py", "help_precedence4")
734 self.assertIn(
735 "Test minimal TyperCommand subclass - callback docstring", buffer.getvalue()
736 )
737 self.assertTrue(
738 re.search(
739 r"help_precedence4\s+Test minimal TyperCommand subclass - command method",
740 buffer.getvalue(),
741 )
742 )
744 def test_help_precedence5(self):
745 buffer = StringIO()
746 cmd = get_command("help_precedence5", stdout=buffer)
747 cmd.print_help("./manage.py", "help_precedence5")
748 self.assertIn(
749 "Test minimal TyperCommand subclass - command method", buffer.getvalue()
750 )
752 def test_help_precedence6(self):
753 buffer = StringIO()
754 cmd = get_command("help_precedence6", stdout=buffer)
755 cmd.print_help("./manage.py", "help_precedence6")
756 self.assertIn(
757 "Test minimal TyperCommand subclass - docstring", buffer.getvalue()
758 )
761class TestGroups(TestCase):
762 """
763 A collection of tests that test complex grouping commands and also that
764 command inheritance behaves as expected.
765 """
767 def test_group_call(self):
768 with self.assertRaises(NotImplementedError):
769 get_command("groups")()
771 @override_settings(
772 INSTALLED_APPS=[
773 "django_typer.tests.test_app",
774 "django.contrib.admin",
775 "django.contrib.auth",
776 "django.contrib.contenttypes",
777 "django.contrib.sessions",
778 "django.contrib.messages",
779 "django.contrib.staticfiles",
780 ],
781 )
782 def test_helps(self, app="test_app"):
783 for cmds in [
784 ("groups",),
785 ("groups", "echo"),
786 ("groups", "math"),
787 ("groups", "math", "divide"),
788 ("groups", "math", "multiply"),
789 ("groups", "string"),
790 ("groups", "string", "case"),
791 ("groups", "string", "case", "lower"),
792 ("groups", "string", "case", "upper"),
793 ("groups", "string", "strip"),
794 ("groups", "string", "split"),
795 ("groups", "setting"),
796 ("groups", "setting", "print"),
797 ]:
798 if app == "test_app" and cmds[-1] in ["strip", "setting", "print"]: 798 ↛ 799line 798 didn't jump to line 799, because the condition on line 798 was never true
799 with self.assertRaises(ValueError):
800 cmd = get_command(cmds[0], stdout=buffer)
801 cmd.print_help("./manage.py", *cmds)
802 continue
804 buffer = StringIO()
805 cmd = get_command(cmds[0], stdout=buffer)
806 cmd.print_help("./manage.py", *cmds)
807 hlp = buffer.getvalue()
808 self.assertGreater(
809 sim := similarity(
810 hlp, (TESTS_DIR / app / "helps" / f"{cmds[-1]}.txt").read_text()
811 ),
812 0.96, # width inconsistences drive this number < 1
813 )
814 print(f'{app}: {" ".join(cmds)} = {sim:.2f}')
816 @override_settings(
817 INSTALLED_APPS=[
818 "django_typer.tests.test_app2",
819 "django_typer.tests.test_app",
820 "django.contrib.admin",
821 "django.contrib.auth",
822 "django.contrib.contenttypes",
823 "django.contrib.sessions",
824 "django.contrib.messages",
825 "django.contrib.staticfiles",
826 ],
827 )
828 def test_helps_override(self):
829 self.test_helps.__wrapped__(self, app="test_app2")
831 @override_settings(
832 INSTALLED_APPS=[
833 "django_typer.tests.test_app",
834 "django.contrib.admin",
835 "django.contrib.auth",
836 "django.contrib.contenttypes",
837 "django.contrib.sessions",
838 "django.contrib.messages",
839 "django.contrib.staticfiles",
840 ],
841 )
842 def test_command_line(self, settings=None):
843 override = settings is not None
844 settings = ("--settings", settings) if settings else []
846 self.assertEqual(
847 run_command("groups", *settings, "echo", "hey!").strip(),
848 "hey!",
849 )
851 self.assertEqual(
852 call_command("groups", "echo", "hey!").strip(),
853 "hey!",
854 )
855 self.assertEqual(
856 get_command("groups", "echo")("hey!").strip(),
857 "hey!",
858 )
859 self.assertEqual(
860 get_command("groups", "echo")(message="hey!").strip(),
861 "hey!",
862 )
864 self.assertEqual(get_command("groups").echo("hey!").strip(), "hey!")
866 self.assertEqual(get_command("groups").echo(message="hey!").strip(), "hey!")
868 result = run_command("groups", *settings, "echo", "hey!", "5")
869 if override:
870 self.assertEqual(result.strip(), ("hey! " * 5).strip())
871 self.assertEqual(
872 get_command("groups").echo("hey!", 5).strip(), ("hey! " * 5).strip()
873 )
874 self.assertEqual(
875 get_command("groups").echo(message="hey!", echoes=5).strip(),
876 ("hey! " * 5).strip(),
877 )
878 self.assertEqual(
879 call_command("groups", "echo", "hey!", "5").strip(),
880 ("hey! " * 5).strip(),
881 )
882 self.assertEqual(
883 call_command("groups", "echo", "hey!", echoes=5).strip(),
884 ("hey! " * 5).strip(),
885 )
886 self.assertEqual(
887 call_command("groups", "echo", "hey!", echoes="5").strip(),
888 ("hey! " * 5).strip(),
889 )
890 else:
891 self.assertIn("UsageError", result)
892 with self.assertRaises(TypeError):
893 call_command("groups", "echo", "hey!", echoes=5)
894 with self.assertRaises(TypeError):
895 get_command("groups").echo(message="hey!", echoes=5)
897 self.assertEqual(
898 run_command(
899 "groups",
900 *settings,
901 "math",
902 "--precision",
903 "5",
904 "multiply",
905 "1.2",
906 "3.5",
907 " -12.3",
908 parse_json=False,
909 ).strip(),
910 "-51.66000",
911 )
913 grp_cmd = get_command("groups")
914 grp_cmd.math(precision=5)
915 self.assertEqual(grp_cmd.multiply(1.2, 3.5, [-12.3]), "-51.66000")
917 self.assertEqual(
918 call_command(
919 "groups", "math", "multiply", "1.2", "3.5", " -12.3", precision=5
920 ),
921 "-51.66000",
922 )
924 self.assertEqual(
925 call_command(
926 "groups", "math", "multiply", "1.2", "3.5", " -12.3", precision="5"
927 ),
928 "-51.66000",
929 )
931 self.assertEqual(
932 run_command(
933 "groups",
934 *settings,
935 "math",
936 "divide",
937 "1.2",
938 "3.5",
939 " -12.3",
940 parse_json=False,
941 ).strip(),
942 "-0.03",
943 )
945 self.assertEqual(
946 call_command(
947 "groups",
948 "math",
949 "divide",
950 "1.2",
951 "3.5",
952 " -12.3",
953 ),
954 "-0.03",
955 )
957 self.assertEqual(get_command("groups").divide(1.2, 3.5, [-12.3]), "-0.03")
958 self.assertEqual(
959 get_command("groups", "math", "divide")(1.2, 3.5, [-12.3]), "-0.03"
960 )
962 self.assertEqual(
963 run_command(
964 "groups", *settings, "string", "ANNAmontes", "case", "lower"
965 ).strip(),
966 "annamontes",
967 )
969 self.assertEqual(
970 call_command("groups", "string", "ANNAmontes", "case", "lower"),
971 "annamontes",
972 )
974 grp_cmd = get_command("groups")
975 grp_cmd.string("ANNAmontes")
976 self.assertEqual(grp_cmd.lower(), "annamontes")
978 self.assertEqual(
979 run_command(
980 "groups", *settings, "string", "annaMONTES", "case", "upper"
981 ).strip(),
982 "ANNAMONTES",
983 )
985 grp_cmd.string("annaMONTES")
986 self.assertEqual(grp_cmd.upper(), "ANNAMONTES")
988 self.assertEqual(
989 run_command(
990 "groups",
991 *settings,
992 "string",
993 "ANNAMONTES",
994 "case",
995 "lower",
996 "--begin",
997 "4",
998 "--end",
999 "9",
1000 ).strip(),
1001 "ANNAmonteS",
1002 )
1004 self.assertEqual(
1005 call_command(
1006 "groups",
1007 "string",
1008 "ANNAMONTES",
1009 "case",
1010 "lower",
1011 "--begin",
1012 "4",
1013 "--end",
1014 "9",
1015 ).strip(),
1016 "ANNAmonteS",
1017 )
1019 self.assertEqual(
1020 call_command(
1021 "groups", "string", "ANNAMONTES", "case", "lower", begin=4, end=9
1022 ).strip(),
1023 "ANNAmonteS",
1024 )
1026 grp_cmd.string("ANNAMONTES")
1027 self.assertEqual(grp_cmd.lower(begin=4, end=9), "ANNAmonteS")
1028 grp_cmd.string("ANNAMONTES")
1029 self.assertEqual(grp_cmd.lower(4, 9), "ANNAmonteS")
1031 result = run_command(
1032 "groups", *settings, "string", "annamontes", "case", "upper", "4", "9"
1033 ).strip()
1034 if override:
1035 self.assertIn("UsageError", result)
1036 grp_cmd.string("annamontes")
1037 with self.assertRaises(TypeError):
1038 self.assertEqual(grp_cmd.upper(4, 9), "annaMONTEs")
1040 with self.assertRaises(UsageError):
1041 self.assertEqual(
1042 call_command(
1043 "groups", "string", "annamontes", "case", "upper", "4", "9"
1044 ).strip(),
1045 "annaMONTEs",
1046 )
1047 else:
1048 self.assertEqual(result, "annaMONTEs")
1049 grp_cmd.string("annamontes")
1050 self.assertEqual(grp_cmd.upper(4, 9), "annaMONTEs")
1051 self.assertEqual(
1052 call_command(
1053 "groups", "string", "annamontes", "case", "upper", "4", "9"
1054 ).strip(),
1055 "annaMONTEs",
1056 )
1058 result = run_command(
1059 "groups", *settings, "string", " emmatc ", "strip", parse_json=False
1060 )
1061 if override:
1062 self.assertEqual(result, "emmatc\n")
1063 self.assertEqual(
1064 call_command("groups", "string", " emmatc ", "strip"), "emmatc"
1065 )
1066 grp_cmd.string(" emmatc ")
1067 self.assertEqual(grp_cmd.strip(), "emmatc")
1068 else:
1069 self.assertIn("UsageError", result)
1070 with self.assertRaises(UsageError):
1071 self.assertEqual(
1072 call_command("groups", "string", " emmatc ", "strip"), "emmatc"
1073 )
1074 with self.assertRaises(AttributeError):
1075 grp_cmd.string(" emmatc ")
1076 self.assertEqual(grp_cmd.strip(), "emmatc")
1078 self.assertEqual(
1079 run_command(
1080 "groups", *settings, "string", "c,a,i,t,l,y,n", "split", "--sep", ","
1081 ).strip(),
1082 "c a i t l y n",
1083 )
1084 self.assertEqual(
1085 call_command(
1086 "groups", "string", "c,a,i,t,l,y,n", "split", "--sep", ","
1087 ).strip(),
1088 "c a i t l y n",
1089 )
1090 self.assertEqual(
1091 call_command("groups", "string", "c,a,i,t,l,y,n", "split", sep=",").strip(),
1092 "c a i t l y n",
1093 )
1094 grp_cmd.string("c,a,i,t,l,y,n")
1095 self.assertEqual(grp_cmd.split(sep=","), "c a i t l y n")
1096 grp_cmd.string("c,a,i,t,l,y,n")
1097 self.assertEqual(grp_cmd.split(","), "c a i t l y n")
1099 @override_settings(
1100 INSTALLED_APPS=[
1101 "django_typer.tests.test_app2",
1102 "django_typer.tests.test_app",
1103 "django.contrib.admin",
1104 "django.contrib.auth",
1105 "django.contrib.contenttypes",
1106 "django.contrib.sessions",
1107 "django.contrib.messages",
1108 "django.contrib.staticfiles",
1109 ],
1110 )
1111 def test_command_line_override(self):
1112 self.test_command_line.__wrapped__(self, settings="django_typer.tests.override")
1115class TestCallCommandArgs(TestCase):
1116 @override_settings(
1117 INSTALLED_APPS=[
1118 "django_typer.tests.test_app2",
1119 "django_typer.tests.test_app",
1120 "django.contrib.admin",
1121 "django.contrib.auth",
1122 "django.contrib.contenttypes",
1123 "django.contrib.sessions",
1124 "django.contrib.messages",
1125 "django.contrib.staticfiles",
1126 ],
1127 )
1128 def test_completion_args(self):
1129 # call_command converts all args to strings - todo - fix this? or accept it? fixing it
1130 # would require a monkey patch. I think accepting it and trying to allow Arguments to
1131 # be passed in as named parameters would be a good compromise. Users can always invoke
1132 # the typer commands directly using () or the functions directly.
1134 # if autocompletion ends up requiring a monkey patch, consider fixing it
1136 # turns out call_command will also turn options values into strings you've flagged them as required
1137 # and they're passed in as named parameters
1139 test_app = apps.get_app_config("django_typer_tests_test_app")
1140 test_app2 = apps.get_app_config("django_typer_tests_test_app2")
1142 out = StringIO()
1143 call_command(
1144 "completion",
1145 ["django_typer_tests_test_app", "django_typer_tests_test_app2"],
1146 stdout=out,
1147 )
1148 printed_options = json.loads(out.getvalue())
1149 self.assertEqual(
1150 printed_options,
1151 ["django_typer_tests_test_app", "django_typer_tests_test_app2"],
1152 )
1154 out = StringIO()
1155 printed_options = json.loads(get_command("completion")([test_app, test_app2]))
1156 self.assertEqual(
1157 printed_options,
1158 ["django_typer_tests_test_app", "django_typer_tests_test_app2"],
1159 )
1162class TestTracebackConfig(TestCase):
1163 rich_installed = True
1165 def test_default_traceback(self):
1166 result = run_command("test_command1", "delete", "me", "--throw")
1167 self.assertIn("Traceback (most recent call last)", result)
1168 self.assertIn("Exception: This is a test exception", result)
1169 if self.rich_installed:
1170 self.assertIn("────────", result)
1171 # locals should be present
1172 self.assertIn("name = 'me'", result)
1173 self.assertIn("throw = True", result)
1174 # by default we get only the last frame
1175 self.assertEqual(len(re.findall(r"\.py:\d+", result) or []), 1)
1176 else:
1177 self.assertNotIn("────────", result)
1179 def test_tb_command_overrides(self):
1180 result = run_command("test_tb_overrides", "delete", "me", "--throw")
1181 self.assertIn("Traceback (most recent call last)", result)
1182 self.assertIn("Exception: This is a test exception", result)
1183 if self.rich_installed:
1184 self.assertIn("────────", result)
1185 # locals should be present
1186 self.assertNotIn("name = 'me'", result)
1187 self.assertNotIn("throw = True", result)
1188 # should get a full length stack trace
1189 self.assertGreater(len(re.findall(r"\.py:\d+", result) or []), 1)
1190 else:
1191 self.assertNotIn("────────", result)
1193 def test_turn_traceback_off_false(self):
1194 result = run_command(
1195 "test_command1",
1196 "--settings",
1197 "django_typer.tests.settings_tb_false",
1198 "delete",
1199 "me",
1200 "--throw",
1201 )
1202 self.assertNotIn("────────", result)
1203 self.assertIn("Traceback (most recent call last)", result)
1204 self.assertIn("Exception: This is a test exception", result)
1206 def test_turn_traceback_off_none(self):
1207 result = run_command(
1208 "test_command1",
1209 "--settings",
1210 "django_typer.tests.settings_tb_none",
1211 "delete",
1212 "me",
1213 "--throw",
1214 )
1215 self.assertNotIn("────────", result)
1216 self.assertIn("Traceback (most recent call last)", result)
1217 self.assertIn("Exception: This is a test exception", result)
1219 def test_traceback_no_locals_short_false(self):
1220 result = run_command(
1221 "test_command1",
1222 "--settings",
1223 "django_typer.tests.settings_tb_change_defaults",
1224 "delete",
1225 "me",
1226 "--throw",
1227 )
1228 self.assertIn("Traceback (most recent call last)", result)
1229 self.assertIn("Exception: This is a test exception", result)
1230 # locals should not be present
1231 self.assertNotIn("name = 'me'", result)
1232 self.assertNotIn("throw = True", result)
1233 if self.rich_installed:
1234 self.assertIn("────────", result)
1235 self.assertGreater(len(re.findall(r"\.py:\d+", result) or []), 1)
1236 else:
1237 self.assertNotIn("────────", result)
1239 def test_rich_install(self):
1240 if self.rich_installed:
1241 result = run_command(
1242 "test_command1",
1243 "--settings",
1244 "django_typer.tests.settings_throw_init_exception",
1245 "delete",
1246 "me",
1247 )
1248 self.assertIn("Traceback (most recent call last)", result)
1249 self.assertIn("Exception: Test ready exception", result)
1250 self.assertIn("────────", result)
1251 self.assertIn("── locals ──", result)
1253 @override_settings(DJ_RICH_TRACEBACK_CONFIG={"no_install": True})
1254 def test_tb_no_install(self):
1255 if self.rich_installed:
1256 result = run_command(
1257 "test_command1",
1258 "--settings",
1259 "django_typer.tests.settings_tb_no_install",
1260 "delete",
1261 "me",
1262 )
1263 self.assertIn("Traceback (most recent call last)", result)
1264 self.assertIn("Exception: Test ready exception", result)
1265 self.assertNotIn("────────", result)
1266 self.assertNotIn("── locals ──", result)
1269class TestTracebackConfigNoRich(TestTracebackConfig):
1270 rich_installed = False
1272 def setUp(self):
1273 subprocess.run(["pip", "uninstall", "-y", "rich"], check=True)
1275 def tearDown(self):
1276 subprocess.run(["pip", "install", "rich"], check=True)
1279class TestSettingsSystemCheck(TestCase):
1280 def test_warning_thrown(self):
1281 result = run_command(
1282 "noop", "--settings", "django_typer.tests.settings_tb_bad_config"
1283 )
1284 self.assertIn(
1285 "django_typer.tests.settings_tb_bad_config: (django_typer.W001) DT_RICH_TRACEBACK_CONFIG",
1286 result,
1287 )
1288 self.assertIn(
1289 "HINT: Unexpected parameters encountered: unexpected_setting.", result
1290 )