Skip to content

Commit 1549df7

Browse files
committed
more reference doc work, move command help docs to reference and remove commands page
1 parent 6a77329 commit 1549df7

File tree

8 files changed

+151
-43
lines changed

8 files changed

+151
-43
lines changed

django_typer/completers.py

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
11
"""
2-
A collection of completer classes that can be used to quickly add shell completion
3-
for various kinds of django objects.
2+
Typer_ and click_ provide tab-completion hooks for individual parameters. As with
3+
:mod:`~django_typer.parsers` custom completion logic can be implemented for custom
4+
parameter types and added to the annotation of the parameter. Previous versions of
5+
Typer_ supporting click_ 7 used the autocompletion argument to provide completion
6+
logic, Typer_ still supports this, but passing ``shell_complete`` to the annotation is
7+
the preferred way to do this.
8+
9+
This module provides some completer functions and classes that work with common Django_
10+
types:
11+
12+
- **Model Objects**: Complete model object field strings using :class:`ModelObjectCompleter`.
13+
- **App Labels**: Complete app labels or names using :func:`complete_app_label`.
14+
415
"""
516

17+
# pylint: disable=line-too-long
18+
619
import typing as t
720
from types import MethodType
821
from uuid import UUID
@@ -27,11 +40,59 @@
2740
class ModelObjectCompleter:
2841
"""
2942
A completer for generic Django model objects. This completer will work
30-
for any Django core model field where completion makes sense. For example,
31-
it will work for IntegerField, CharField, TextField, and UUIDField, but
32-
not for ForeignKey, ManyToManyField or BinaryField.
43+
for most Django core model field types where completion makes sense.
44+
45+
This completer currently supports the following field types and their subclasses:
46+
47+
- `IntegerField <https://docs.djangoproject.com/en/stable/ref/models/fields/#integerfield>`_
48+
- `AutoField <https://docs.djangoproject.com/en/stable/ref/models/fields/#autofield>`_
49+
- `BigAutoField <https://docs.djangoproject.com/en/stable/ref/models/fields/#bigautofield>`_
50+
- `BigIntegerField <https://docs.djangoproject.com/en/stable/ref/models/fields/#bigintegerfield>`_
51+
- `SmallIntegerField <https://docs.djangoproject.com/en/stable/ref/models/fields/#smallintegerfield>`_
52+
- `PositiveIntegerField <https://docs.djangoproject.com/en/stable/ref/models/fields/#positiveintegerfield>`_
53+
- `PositiveSmallIntegerField <https://docs.djangoproject.com/en/stable/ref/models/fields/#positivesmallintegerfield>`_
54+
- `SmallAutoField <https://docs.djangoproject.com/en/stable/ref/models/fields/#smallautofield>`_
55+
- `CharField <https://docs.djangoproject.com/en/stable/ref/models/fields/#charfield>`_
56+
- `SlugField <https://docs.djangoproject.com/en/stable/ref/models/fields/#slugfield>`_
57+
- `URLField <https://docs.djangoproject.com/en/stable/ref/models/fields/#urlfield>`_
58+
- `EmailField <https://docs.djangoproject.com/en/stable/ref/models/fields/#emailfield>`_
59+
- `TextField <https://docs.djangoproject.com/en/stable/ref/models/fields/#textfield>`_
60+
- `UUIDField <https://docs.djangoproject.com/en/stable/ref/models/fields/#uuidfield>`_
61+
- `FloatField <https://docs.djangoproject.com/en/stable/ref/models/fields/#floatfield>`_
62+
- `DecimalField <https://docs.djangoproject.com/en/stable/ref/models/fields/#decimalfield>`_
63+
64+
The completer query logic is pluggable, but the defaults cover most use cases. The
65+
limit field is important. It defaults to 50 meaning if more than 50 potential completions
66+
are found only the first 50 will be returned and there will be no indication to the user
67+
that there are more. This is to prevent the shell from becoming unresponsive when offering
68+
completion for large tables.
69+
70+
To use this completer, pass an instance of this class to the `shell_complete`
71+
argument of a typer.Option or typer.Argument:
72+
73+
.. code-block:: python
74+
75+
from django_typer.completers import ModelObjectCompleter
76+
77+
class Command(TyperCommand):
78+
79+
def handle(
80+
self,
81+
model_obj: Annotated[
82+
MyModel,
83+
typer.Argument(
84+
shell_complete=ModelObjectCompleter(MyModel, lookup_field="name"),
85+
help=_("The model object to use.")
86+
)
87+
]
88+
):
89+
...
90+
91+
.. note::
3392
34-
The completer query logic is pluggable, but the defaults cover most use cases.
93+
See also :func:`~django_typer.model_parser_completer` for a convenience
94+
function that returns a configured parser and completer for a model object
95+
and helps reduce boilerplate.
3596
3697
:param model_cls: The Django model class to query.
3798
:param lookup_field: The name of the model field to use for lookup.
@@ -274,7 +335,7 @@ def __call__(
274335

275336
def complete_app_label(ctx: Context, param: Parameter, incomplete: str):
276337
"""
277-
A case-insensitive completer for Django app labels or names. The completer
338+
A case-sensitive completer for Django app labels or names. The completer
278339
prefers labels but names will also work.
279340
280341
.. code-block:: python

django_typer/management/commands/shellcompletion.py

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
11
"""
2-
The shellcompletion command is a Django management command that installs and removes
2+
The shellcompletion command is a Django_ management command that installs and removes
33
shellcompletion scripts for supported shells (bash, fish, zsh, powershell). This
44
command is also the entry point for running the completion logic and can be used to
55
debug completer code.
66
7-
It invokes typer's shell completion installation logic, but does have to patch the
8-
installed scripts. This is because there is only one installation for all django
9-
management commands, not each individual command. The completion logic here will
10-
failover to django's builtin autocomplete if the command in question is not a
11-
TyperCommand. To promote compatibility with other management command libraries or
12-
custom completion logic, a fallback completion function can also be specified. A
13-
needed refactoring here would be to provide root hooks for completion logic in django
14-
that base classes can register for. This would provide a coordinated way for libraries
15-
like django-typer to plug in their own completion logic.
7+
.. typer:: django_typer.management.commands.shellcompletion.Command:typer_app
8+
:prog: manage.py shellcompletion
9+
:width: 80
10+
:convert-png: latex
11+
12+
:func:`~django_typer.management.commands.shellcompletion.Command.install` invokes typer's shell
13+
completion installation logic, but does have to patch the installed scripts. This is because
14+
there is only one installation for all Django_ management commands, not each individual command.
15+
The completion logic here will failover to Django_'s builtin autocomplete if the command in
16+
question is not a :class:`~django_typer.TyperCommand`. To promote compatibility with other
17+
management command libraries or custom completion logic, a fallback completion function can also
18+
be specified.
1619
"""
1720

21+
# A needed refactoring here would be to provide root hooks for completion logic in Django core that
22+
# base classes can register for. This would provide a coordinated way for libraries like
23+
# django-typer to plug in their own completion logic.
24+
1825
import contextlib
1926
import inspect
2027
import io
@@ -62,9 +69,9 @@ class Command(TyperCommand):
6269
This command installs autocompletion for the current shell. This command uses the typer/click
6370
autocompletion scripts to generate the autocompletion items, but monkey patches the scripts
6471
to invoke our bundled shell complete script which fails over to the django autocomplete
65-
function when the command being completed is not a TyperCommand. When the django autocomplete
66-
function is used we also wrap it so that it works for any supported click/typer shell, not just
67-
bash.
72+
function when the command being completed is not a :class:`~django_typer.TyperCommand`. When
73+
the django autocomplete function is used we also wrap it so that it works for any supported
74+
click/typer shell, not just bash.
6875
6976
We also provide a remove command to easily remove the installed script.
7077
@@ -290,7 +297,11 @@ def install(
290297
Install autocompletion for the given shell. If the shell is not specified, it will
291298
try to detect the shell. If the shell is not detected, it will fail.
292299
293-
We run the upstream typer installation routines.
300+
We run the upstream typer installation routines, with some augmentation.
301+
302+
.. typer:: django_typer.management.commands.shellcompletion.Command:typer_app:install
303+
:width: 85
304+
:convert-png: latex
294305
"""
295306
# do not import this private stuff until we need it - avoids tanking the whole
296307
# library if these imports change
@@ -336,6 +347,12 @@ def remove(
336347
337348
Since the installation routine is upstream we first run install to determine where the
338349
completion script is installed and then we remove it.
350+
351+
.. typer:: django_typer.management.commands.shellcompletion.Command:typer_app:remove
352+
:prog: shellcompletion
353+
:width: 80
354+
:convert-png: latex
355+
339356
"""
340357
# do not import this private stuff until we need it - avoids tanking the whole
341358
# library if these imports change
@@ -436,7 +453,22 @@ def complete(
436453
"""
437454
We implement the shell complete generation script as a Django command because the
438455
Django environment needs to be bootstrapped for it to work. This also allows
439-
us to test autocompletions in a platform agnostic way.
456+
us to test completion logic in a platform agnostic way.
457+
458+
.. tip::
459+
460+
This command is super useful for debugging shell_complete logic. For example to
461+
enter into the debugger, we could set a breakpoint in our ``shell_complete`` function
462+
for the option parameter and then run the following command:
463+
464+
.. code-block:: bash
465+
466+
$ ./manage.py shellcompletion complete "./manage.py your_command --option "
467+
468+
.. typer:: django_typer.management.commands.shellcompletion.Command:typer_app:complete
469+
:prog: shellcompletion
470+
:width: 80
471+
:convert-png: latex
440472
"""
441473
os.environ[self.COMPLETE_VAR] = (
442474
f"complete_{shell.value}"

django_typer/parsers.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,44 @@
2525
import typing as t
2626
from uuid import UUID
2727

28-
from click import Parameter, ParamType, Context
28+
from click import Context, Parameter, ParamType
2929
from django.apps import AppConfig, apps
3030
from django.core.exceptions import ObjectDoesNotExist
3131
from django.core.management import CommandError
3232
from django.db.models import Field, Model, UUIDField
3333
from django.utils.translation import gettext as _
3434

35+
from django_typer.completers import ModelObjectCompleter
36+
3537

3638
class ModelObjectParser(ParamType):
3739
"""
3840
A parser that will turn strings into model object instances based on the
3941
configured lookup field and model class.
4042
43+
.. code-block:: python
44+
45+
from django_typer.parsers import ModelObjectParser
46+
47+
class Command(TyperCommand):
48+
def handle(
49+
self,
50+
django_apps: Annotated[
51+
t.List[MyModel],
52+
typer.Argument(
53+
parser=ModelObjectParser(MyModel, lookup_field="name"),
54+
help=_("One or more application labels."),
55+
),
56+
],
57+
):
58+
59+
.. note::
60+
61+
Typer_ does not respect the shell_complete functions on ParamTypes passed as
62+
parsers. To add shell_completion see :class:`~django_typer.completers.ModelObjectCompleter`
63+
or the :func:`~django_typer.model_parser_completer` convenience
64+
function.
65+
4166
:param model_cls: The model class to use for lookup.
4267
:param lookup_field: The field to use for lookup. Defaults to 'pk'.
4368
:param on_error: A callable that will be called if the lookup fails.
@@ -55,6 +80,7 @@ class ModelObjectParser(ParamType):
5580

5681
_lookup = ""
5782
_field: Field
83+
_completer: ModelObjectCompleter
5884

5985
__name__ = "ModelObjectParser" # typer internals expect this
6086

django_typer/patch.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
Stuffing the typer interface into the Django BaseCommand interface is an exercise
2+
Stuffing the typer interface into the Django BaseCommand_ interface is an exercise
33
in fitting a square peg into a round hole. A small amount of upstream patching is
44
required to make this work. All monkey patching is defined in this module to more
55
easily keep track of it.

django_typer/utils.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
"""
2-
A context manager that can be used to determine if we're executing inside of
3-
a Typer command. This is analogous to click's get_current_context but for
4-
command execution.
2+
A collection of useful utilities.
53
"""
64

75
import typing as t
@@ -40,6 +38,9 @@ def get_current_command() -> t.Optional["TyperCommand"]: # type: ignore
4038
4139
This function is thread safe.
4240
41+
This is analogous to click's get_current_context but for
42+
command execution.
43+
4344
:return: The current typer command or None if there is no active command.
4445
"""
4546
try:

doc/source/commands.rst

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

doc/source/index.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,5 @@ for arbitrarily complex command hierarchies.
128128
tutorial
129129
howto
130130
shell_completion
131-
commands
132131
reference
133132
changelog

doc/source/shell_completion.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ for django commands we need to register our completion logic for Django manage s
4040
the shell. This process has two phases:
4141

4242
1. Ensure that your shell is configured to support completions.
43-
2. Use the :ref:`shellcompletion <shellcompletion-command>` command to install the completion
43+
2. Use the :mod:`~django_typer.management.commands.shellcompletion` command to install the completion
4444
hook for your Django manage script.
4545

4646

@@ -89,7 +89,7 @@ had luck with the following:
8989
Install the Completion Hook
9090
---------------------------
9191

92-
django-typer_ comes with a management command called :ref:`shellcompletion <shellcompletion-command>`.
92+
django-typer_ comes with a management command called :mod:`~django_typer.management.commands.shellcompletion`.
9393
To install completions for your Django project simply run the install command:
9494

9595
.. code-block:: bash
@@ -104,7 +104,8 @@ To install completions for your Django project simply run the install command:
104104

105105
The installation script should be able to automatically detect your shell and install the appropriate
106106
scripts. If it is unable to do so you may force it to install for a specific shell by passing the
107-
shell name as an argument. Refer to the :ref:`command help <shellcompletion-command>` for details.
107+
shell name as an argument. Refer to the :mod:`~django_typer.management.commands.shellcompletion`
108+
for details.
108109

109110
**After installation you will probably need to restart your shell or source the appropriate rc file.**
110111

0 commit comments

Comments
 (0)