Skip to content

Latest commit

 

History

History
310 lines (216 loc) · 10.8 KB

tutorial.rst

File metadata and controls

310 lines (216 loc) · 10.8 KB

Tutorial: Building Commands

Using the :class:`~django_typer.TyperCommand` class is very similar to using the BaseCommand_ class. The main difference is that we use Typer_'s decorators, classes and type annotations to define the command's command line interface instead of argparse_ as BaseCommand_ expects.

Upstream Libraries

.. only:: html

    .. image:: /_static/img/django_typer_upstream.svg
        :align: right

.. only:: latex

    .. image:: /_static/img/django_typer_upstream.pdf
        :align: right

django-typer_ merges the Django_ BaseCommand_ interface with the Typer_ interface and Typer_ itself is built on top of click_. This means when using django-typer_ you will encounter interfaces and concepts from all three of these upstream libraries:

  1. Install the latest release off PyPI_ :

    pip install "django-typer[rich]"

    rich_ is a powerful library for rich text and beautiful formatting in the terminal. It is not required, but highly recommended for the best experience:

  2. Add django_typer to your INSTALLED_APPS setting:

    INSTALLED_APPS = [
        ...
        'django_typer',
    ]

    Note

    Adding django_typer to INSTALLED_APPS is not strictly necessary if you do not wish to use shell tab completions or configure rich traceback rendering. Refer to the :ref:`how-to <configure-rich-exception-tracebacks>` if you would like to disable it.

Convert the closepoll command to a :class:`~django_typer.TyperCommand`

Recall our closepoll command from the polls Tutorial in the Django documentation looks like this:

.. literalinclude:: ../../tests/apps/examples/polls/management/commands/closepoll_django.py
    :language: python
    :linenos:
    :replace:
        tests.apps.examples.polls : polls

We first need to change the inheritance to :class:`~django_typer.TyperCommand` and then move the argument and option definitions from add_arguments into the method signature of handle. A minimal conversion may look like this:

.. tabs::

    .. tab:: Django-style

        .. literalinclude:: ../../tests/apps/examples/polls/management/commands/closepoll_t1.py
            :language: python
            :linenos:
            :replace:
                tests.apps.examples.polls.models : polls.models

    .. tab:: Typer-style

        .. literalinclude:: ../../tests/apps/examples/polls/management/commands/closepoll_t1_typer.py
            :language: python
            :linenos:
            :replace:
                tests.apps.examples.polls.models : polls.models

You'll note that we've removed add_arguments entirely and specified the arguments and options as parameters to the handle method. django-typer_ will interpret the parameters on the handle() method as the command line interface for the command. If we have rich_ installed the help for our new closepoll command will look like this:

.. typer:: tests.apps.examples.polls.management.commands.closepoll_t1.Command:typer_app
    :prog: manage.py closepoll
    :width: 80
    :show-nested:
    :convert-png: latex
    :theme: dark


Note

:class:`~django_typer.TyperCommand` adds the standard set of default options to the command line interface, with the exception of verbosity.

Add Helps with Type annotations

Typer_ allows us to use Annotated types to add additional controls to how the command line interface behaves. The most common use case for this is to add help text to the command line interface. We will annotate our parameter type hints with one of two Typer_ parameter types, either Argument or Option. Arguments_ are positional parameters and Options_ are named parameters (i.e. --delete). In our polls example, the poll_ids are arguments and the delete flag is an option. Here is what that would look like:

.. tabs::

    .. tab:: Django-style

        .. literalinclude:: ../../tests/apps/examples/polls/management/commands/closepoll_t2.py
            :language: python
            :lines: 11-23
            :replace:
                tests.apps.examples.polls.models : polls.models

    .. tab:: Typer-style

        .. literalinclude:: ../../tests/apps/examples/polls/management/commands/closepoll_t2_typer.py
            :language: python
            :lines: 13-23
            :replace:
                tests.apps.examples.polls.models : polls.models

See that our help text now shows up in the command line interface. Also note, that lazy translations work for the help strings. Typer_ also allows us to specify our help text in the docstrings of the command function or class, in this case either Command or handle() - but docstrings are not available to the translation system. If translation is not necessary and your help text is extensive or contains markup the docstring may be the more appropriate place to put it.

.. typer:: tests.apps.examples.polls.management.commands.closepoll_t2.Command:typer_app
    :prog: manage.py closepoll
    :width: 80
    :show-nested:
    :convert-png: latex
    :theme: dark


Note

On Python <=3.8 you will need to import Annotated from typing_extensions_ instead of the standard library.

Defining custom and reusable parameter types

We may have other commands that need to operate on Poll objects from given poll ids. We could duplicate our for loop that loads Poll objects from ids, but that wouldn't be very DRY_. Instead, Typer_ allows us to define custom parsers for arbitrary parameter types. Lets see what that would look like if we used the Poll class as our type hint:

.. tabs::

    .. tab:: Django-style

        .. literalinclude:: ../../tests/apps/examples/polls/management/commands/closepoll_t3.py
            :language: python
            :lines: 11-52

    .. tab:: Typer-style

        .. literalinclude:: ../../tests/apps/examples/polls/management/commands/closepoll_t3_typer.py
            :language: python
            :lines: 11-54

.. typer:: tests.apps.examples.polls.management.commands.closepoll_t3.Command:typer_app
    :prog: manage.py closepoll
    :width: 80
    :show-nested:
    :convert-png: latex
    :theme: dark


django-typer_ offers some built-in :mod:`~django_typer.parsers` that can be used for common Django_ types. For example, the :class:`~django_typer.parsers.ModelObjectParser` can be used to fetch a model object from a given field. By default it will use the primary key, so we could rewrite the relevant lines above like so:

from django_typer.parsers import ModelObjectParser

# ...

t.Annotated[
    t.List[Poll],
    Argument(
        parser=ModelObjectParser(Poll),
        help=_("The database IDs of the poll(s) to close.")
    )
]

# ...

Add shell tab-completion suggestions for polls

It's very annoying to have to know the database primary key of the poll to close it. django-typer_ makes it easy to add tab completion suggestions! You can always implement your own completer functions, but as with, :mod:`~django_typer.parsers`, there are some out-of-the-box :mod:`~django_typer.completers` that make this easy. Let's see what the relevant updates to our closepoll command would look like:

from django_typer.parsers import ModelObjectParser
from django_typer.completers import ModelObjectCompleter

# ...

t.Annotated[
    t.List[Poll],
    Argument(
        parser=ModelObjectParser(Poll),
        shell_complete=ModelObjectCompleter(Poll, help_field='question_text'),
        help=_("The database IDs of the poll(s) to close.")
    )
]

# ...

Note

For tab-completions to work you will need to :ref:`install the shell completion scripts for your shell <shellcompletions>`.

Putting it all together

When we're using a :class:`~django_typer.parsers.ModelObjectParser` and :class:`~django_typer.completers.ModelObjectCompleter` we can use the :func:`~django_typer.model_parser_completer` convenience function to reduce the amount of boiler plate. Let's put everything together and see what our full-featured refactored closepoll command looks like:

.. tabs::

    .. tab:: Django-style

        .. literalinclude:: ../../tests/apps/examples/polls/management/commands/closepoll_t6.py
            :language: python
            :linenos:
            :replace:
                tests.apps.examples.polls.models : polls.models

    .. tab:: Typer-style

        .. literalinclude:: ../../tests/apps/examples/polls/management/commands/closepoll_t6_typer.py
            :language: python
            :linenos:
            :replace:
                tests.apps.examples.polls.models : polls.models


.. only:: html

    .. image:: /_static/img/closepoll_example.gif
        :align: center

.. only:: latex

    .. image:: /_static/img/closepoll_example.png
        :align: center