Skip to content

Commit 2400b5b

Browse files
committed
a few more completers, flush out documentation
1 parent 8641896 commit 2400b5b

File tree

35 files changed

+745
-170
lines changed

35 files changed

+745
-170
lines changed

django_typer/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2585,7 +2585,7 @@ def initialize(
25852585
Override the initializer for this command class after it has been defined.
25862586
25872587
.. note::
2588-
See :ref:`extensions` for details on when you might want to use this extension
2588+
See :ref:`plugins` for details on when you might want to use this extension
25892589
pattern.
25902590
25912591
.. code-block:: python
@@ -2696,7 +2696,7 @@ def command(
26962696
use this decorator to add commands to a root command from other Django apps.
26972697
26982698
.. note::
2699-
See :ref:`extensions` for details on when you might want to use this extension
2699+
See :ref:`plugins` for details on when you might want to use this extension
27002700
pattern.
27012701
27022702
.. code-block:: python
@@ -2794,7 +2794,7 @@ def group(
27942794
use this decorator to add groups to a root command from other Django apps.
27952795
27962796
.. note::
2797-
See :ref:`extensions` for details on when you might want to use this extension
2797+
See :ref:`plugins` for details on when you might want to use this extension
27982798
pattern.
27992799
28002800
.. code-block:: python

django_typer/completers.py

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from click.shell_completion import CompletionItem
3131
from django.apps import apps
3232
from django.conf import settings
33+
from django.core.management import get_commands
3334
from django.db.models import (
3435
CharField,
3536
DecimalField,
@@ -44,6 +45,9 @@
4445
UUIDField,
4546
)
4647

48+
Completer = t.Callable[[Context, Parameter, str], t.List[CompletionItem]]
49+
Strings = t.Union[t.Sequence[str], t.KeysView[str], t.Generator[str, None, None]]
50+
4751

4852
class ModelObjectCompleter:
4953
"""
@@ -515,7 +519,7 @@ def exists(pth: Path) -> bool:
515519

516520

517521
def these_strings(
518-
strings: t.Union[t.Callable[[], t.Sequence[str]], t.Sequence[str]],
522+
strings: t.Union[t.Callable[[], Strings], Strings],
519523
allow_duplicates: bool = False,
520524
):
521525
"""
@@ -545,8 +549,75 @@ def complete(ctx: Context, param: Parameter, incomplete: str):
545549

546550

547551
# use a function that returns a generator because we should not access settings on import
548-
databases = these_strings(lambda: settings.DATABASES.keys())
552+
databases = partial(these_strings, lambda: settings.DATABASES.keys())
553+
"""
554+
A completer that completes Django database aliases configured in settings.DATABASES.
555+
556+
:param allow_duplicates: Whether or not to allow duplicate values. Defaults to False.
557+
:return: A completer function.
558+
"""
559+
560+
commands = partial(these_strings, lambda: get_commands().keys())
549561
"""
550-
A completer that provides completion logic for the Django database aliases
551-
configured in settings.DATABASES.
562+
A completer that completes management command names.
563+
564+
:param allow_duplicates: Whether or not to allow duplicate values. Defaults to False.
565+
:return: A completer function.
552566
"""
567+
568+
569+
def chain(
570+
completer: Completer,
571+
*completers: Completer,
572+
first_match: bool = False,
573+
allow_duplicates: bool = False,
574+
):
575+
"""
576+
Run through the given completers and return the items from the first one, or all
577+
of them if first_match is False.
578+
579+
.. note::
580+
581+
This function is also useful for filtering out previously supplied duplicate
582+
values for completers that do not natively support that:
583+
584+
.. code-block:: python
585+
586+
shell_complete=chain(
587+
complete_import_path,
588+
allow_duplicates=False
589+
)
590+
591+
:param completer: The first completer to use (must be at least one!)
592+
:param completers: The completers to use
593+
:param first_match: If true, return only the matches from the first completer that
594+
finds completions. Default: False
595+
:param allow_duplicates: If False (default) remove completions from previously provided
596+
values.
597+
"""
598+
599+
def complete(ctx: Context, param: Parameter, incomplete: str):
600+
completions = []
601+
present = []
602+
if (
603+
not allow_duplicates
604+
and param.name
605+
and ctx.get_parameter_source(param.name) is not ParameterSource.DEFAULT
606+
):
607+
present = [value for value in (ctx.params.get(param.name) or [])]
608+
for cmpltr in [completer, *completers]:
609+
completions.extend(cmpltr(ctx, param, incomplete))
610+
if first_match and completions:
611+
break
612+
613+
# eliminate duplicates
614+
return list(
615+
{
616+
ci.value: ci
617+
for ci in completions
618+
if ci.value
619+
if ci.value not in present
620+
}.values()
621+
)
622+
623+
return complete

django_typer/tests/apps/adapter0/apps.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
class Adapter1Config(AppConfig):
66
name = "django_typer.tests.apps.adapter0"
7-
label = name.replace(".", "_")
7+
label = "adapter0"
88
verbose_name = "Adapter 0"
99

1010
def ready(self):

django_typer/tests/apps/adapter1/apps.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
class Adapter1Config(AppConfig):
66
name = "django_typer.tests.apps.adapter1"
7-
label = name.replace(".", "_")
7+
label = "adapter1"
88
verbose_name = "Adapter 1"
99

1010
def ready(self):

django_typer/tests/apps/adapter2/apps.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
class Adapter2Config(AppConfig):
66
name = "django_typer.tests.apps.adapter2"
7-
label = name.replace(".", "_")
7+
label = "adapter2"
88
verbose_name = "Adapter 2"
99

1010
def ready(self):

django_typer/tests/apps/howto/management/commands/howto1.py

Lines changed: 0 additions & 27 deletions
This file was deleted.

django_typer/tests/apps/howto/management/commands/howto3.py

Lines changed: 0 additions & 18 deletions
This file was deleted.

django_typer/tests/apps/howto/management/plugins/upstream2_typer.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
from ..commands.upstream2_typer import app
22

3-
# do not create a new app as with inheritance - just
4-
# work directly with the upstream app.
3+
# do not create a new app as with inheritance -
4+
# instead work directly with the upstream app.
55

66

77
# override init
8-
@app.initialize()
8+
@app.callback()
99
def init():
1010
return "plugin:init"
1111

django_typer/tests/apps/test_app/apps.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def test_check(app_configs, **kwargs):
2121

2222
class TestAppConfig(AppConfig):
2323
name = "django_typer.tests.apps.test_app"
24-
label = name.replace(".", "_")
24+
label = "test_app"
2525
verbose_name = "Test App"
2626

2727
def ready(self):

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,28 @@ def handle(
5959
),
6060
),
6161
] = None,
62+
commands: t.Annotated[
63+
t.List[str],
64+
typer.Option(
65+
"--cmd",
66+
help=_("A command by import path or name."),
67+
shell_complete=completers.chain(
68+
completers.complete_import_path, completers.commands()
69+
),
70+
),
71+
] = [],
72+
command_dups: t.Annotated[
73+
t.List[str],
74+
typer.Option(
75+
"--cmd-dup",
76+
help=_("A list of commands by import path or name."),
77+
shell_complete=completers.chain(
78+
completers.complete_import_path,
79+
completers.commands(allow_duplicates=True),
80+
allow_duplicates=True,
81+
),
82+
),
83+
] = [],
6284
):
6385
assert self.__class__ is Command
6486
for app in django_apps:

0 commit comments

Comments
 (0)