Skip to content

Commit 47c1a87

Browse files
committed
finish howto, fix help precedence bug, fix doc8 linting
1 parent ce3c7d1 commit 47c1a87

File tree

12 files changed

+171
-76
lines changed

12 files changed

+171
-76
lines changed

.github/workflows/lint.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ jobs:
4242
poetry run black django_typer --check
4343
poetry run pylint django_typer
4444
poetry run mypy django_typer
45-
poetry run doc8 -q doc
4645
poetry check
4746
poetry run pip check
4847
poetry run python -m readme_renderer ./README.rst -o /tmp/README.html
48+
cd ./doc
49+
poetry run doc8 --ignore-path build --max-line-length 100

CONTRIBUTING.rst

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
.. _Sphinx: https://www.sphinx-doc.org/en/master/
88
.. _readthedocs: https://readthedocs.org/
99
.. _me: https://github.com/bckohan
10+
.. _black: https://black.readthedocs.io/en/stable/
1011

1112
Contributing
1213
############
@@ -37,30 +38,31 @@ Documentation
3738

3839
`django-typer` documentation is generated using Sphinx_ with the
3940
readthedocs_ theme. Any new feature PRs must provide updated documentation for
40-
the features added. To build the docs run:
41+
the features added. To build the docs run doc8 to check for formatting issues
42+
then run Sphinx_:
4143

42-
.. code-block::
44+
.. code-block:: bash
4345
4446
cd ./doc
47+
poetry run doc8 --ignore-path build --max-line-length 100
4548
poetry run make html
4649
4750
4851
Static Analysis
4952
---------------
5053

5154
`django-typer` uses Pylint_ for python linting and mypy_ for static type
52-
checking. Header imports are also standardized using isort_. Before any PR is
53-
accepted the following must be run, and static analysis tools should not
54-
produce any errors or warnings. Disabling certain errors or warnings where
55-
justified is acceptable:
55+
checking. Header imports are also standardized using isort_ and formatting is
56+
done with black_. Before any PR is accepted the following must be run, and
57+
static analysis tools should not produce any errors or warnings. Disabling
58+
certain errors or warnings where justified is acceptable:
5659

57-
.. code-block::
60+
.. code-block:: bash
5861
5962
poetry run isort django_typer
6063
poetry run black django_typer
6164
poetry run pylint django_typer
6265
poetry run mypy django_typer
63-
poetry run doc8 -q doc
6466
poetry check
6567
poetry run pip check
6668
poetry run python -m readme_renderer ./README.rst

django_typer/__init__.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -962,12 +962,12 @@ def decorator(func: CommandFunctionType):
962962
setattr(
963963
func,
964964
"_typer_command_",
965-
lambda cmd, _name=None, **extra: cmd.typer_app.command(
965+
lambda cmd, _name=None, _help=None, **extra: cmd.typer_app.command(
966966
name=name or _name,
967967
*args,
968968
cls=type("_AdaptedCommand", (cls,), {"django_command": cmd}),
969969
context_settings=context_settings,
970-
help=help,
970+
help=help or _help,
971971
epilog=epilog,
972972
short_help=short_help,
973973
options_metavar=options_metavar,
@@ -1290,7 +1290,11 @@ def get_ctor(attr: str) -> t.Optional[t.Callable[..., t.Any]]:
12901290
if cmd_cls._handle:
12911291
ctor = get_ctor(cmd_cls._handle)
12921292
if ctor:
1293-
ctor(cls, _name=cls.typer_app.info.name)
1293+
ctor(
1294+
cls,
1295+
_name=cls.typer_app.info.name,
1296+
_help=getattr(cls, "help", None),
1297+
)
12941298
else:
12951299
cls._num_commands += 1
12961300
cls.typer_app.command(
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import json
2+
3+
from django.utils.translation import gettext_lazy as _
4+
5+
from django_typer import TyperCommand, command
6+
7+
8+
class Command(TyperCommand):
9+
10+
help = _("Test minimal TyperCommand subclass - class member")
11+
12+
@command()
13+
def handle(self, arg1: str, arg2: str, arg3: float = 0.5, arg4: int = 1):
14+
"""
15+
Test minimal TyperCommand subclass - docstring
16+
"""
17+
assert self.__class__ == Command
18+
opts = {"arg1": arg1, "arg2": arg2, "arg3": arg3, "arg4": arg4}
19+
return json.dumps(opts)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import json
2+
3+
from django.utils.translation import gettext_lazy as _
4+
5+
from django_typer import TyperCommand, command
6+
7+
8+
class Command(TyperCommand, help=_("Test minimal TyperCommand subclass - typer param")):
9+
10+
help = _("Test minimal TyperCommand subclass - class member")
11+
12+
def handle(self, arg1: str, arg2: str, arg3: float = 0.5, arg4: int = 1):
13+
"""
14+
Test minimal TyperCommand subclass - docstring
15+
"""
16+
assert self.__class__ == Command
17+
opts = {"arg1": arg1, "arg2": arg2, "arg3": arg3, "arg4": arg4}
18+
return json.dumps(opts)

django_typer/tests/tests.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -935,6 +935,22 @@ def test_help_precedence6(self):
935935
"Test minimal TyperCommand subclass - docstring", buffer.getvalue()
936936
)
937937

938+
def test_help_precedence7(self):
939+
buffer = StringIO()
940+
cmd = get_command("help_precedence7", stdout=buffer, no_color=True)
941+
cmd.print_help("./manage.py", "help_precedence7")
942+
self.assertIn(
943+
"Test minimal TyperCommand subclass - class member", buffer.getvalue()
944+
)
945+
946+
def test_help_precedence8(self):
947+
buffer = StringIO()
948+
cmd = get_command("help_precedence8", stdout=buffer, no_color=True)
949+
cmd.print_help("./manage.py", "help_precedence8")
950+
self.assertIn(
951+
"Test minimal TyperCommand subclass - typer param", buffer.getvalue()
952+
)
953+
938954

939955
class TestOverloaded(TestCase):
940956
"""

doc/.readthedocs.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,7 @@ build:
2020
# Build documentation in the docs/ directory with Sphinx
2121
sphinx:
2222
configuration: doc/source/conf.py
23+
24+
# Optionally build your docs in additional formats such as PDF and ePub
25+
formats:
26+
- pdf

doc/source/howto.rst

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ For example to define an integer positional argument we could simply do:
1616
...
1717
1818
You will likely want to add additional meta information to your arguments for Typer_ to render
19-
things like helps and usage strings. You can do this by annotating the type hint with
19+
things like helps and usage strings. You can do this by annotating the type hint with
2020
the `typer.Argument` class:
2121

2222
.. code-block::
@@ -25,7 +25,7 @@ the `typer.Argument` class:
2525
from typer import Argument
2626
2727
# ...
28-
28+
2929
def handle(self, int_arg: t.Annotated[int, Argument(help="An integer argument")]):
3030
...
3131
@@ -75,12 +75,12 @@ To add meta information, we annotate with the `typer.Option` class:
7575
from typer import Option
7676
7777
# ...
78-
78+
7979
def handle(self, name: t.Annotated[str, Option(help="The name of the thing")]):
8080
...
8181
8282
.. tip::
83-
83+
8484
Refer to the Typer_ docs on options_ for more details.
8585

8686

@@ -248,11 +248,11 @@ This is like defining a group at the command root and is an extension of the
248248
Call TyperCommands from Code
249249
----------------------------
250250

251-
There are two options for invoking a :class:`~django_typer.TyperCommand` from code without spawning off
252-
a subprocess. The first is to use Django_'s builtin call_command_ function. This function will work exactly
253-
as it does for normal BaseCommand_ derived commands. django-typer_ however adds another mechanism that
254-
can be more efficient, especially if your options and arguments are already of the correct type and require
255-
no parsing:
251+
There are two options for invoking a :class:`~django_typer.TyperCommand` from code without spawning
252+
off a subprocess. The first is to use Django_'s builtin call_command_ function. This function will
253+
work exactly as it does for normal BaseCommand_ derived commands. django-typer_ however adds
254+
another mechanism that can be more efficient, especially if your options and arguments are already
255+
of the correct type and require no parsing:
256256

257257
Say we have this command, called ``mycommand``:
258258

@@ -287,8 +287,8 @@ Say we have this command, called ``mycommand``:
287287
The rule of them is this:
288288

289289
- Use call_command_ if your options and arguments need parsing.
290-
- Use :func:`~django_typer.get_command` and invoke the command functions directly if your options
291-
and arguments are already of the correct type.
290+
- Use :func:`~django_typer.get_command` and invoke the command functions directly if your
291+
options and arguments are already of the correct type.
292292

293293
.. tip::
294294

@@ -300,13 +300,14 @@ Change Default Django Options
300300
-----------------------------
301301

302302
:class:`~django_typer.TyperCommand` classes preserve all of the functionality of BaseCommand_ derivatives.
303-
This means that you can still use class members like `suppressed_base_arguments
303+
This means that you can still use class members like `suppressed_base_arguments
304304
<https://docs.djangoproject.com/en/5.0/howto/custom-management-commands/#django.core.management.BaseCommand.suppressed_base_arguments>`_
305305
to suppress default options.
306306

307-
By default :class:`~django_typer.TyperCommand` suppresses ``--verbosity``. You can add it back by setting
308-
``suppressed_base_arguments`` to an empty list. If you want to use verbosity you can simply redefine it or
309-
use one of django-typer_'s :ref:`provided type hints <types>` for the default BaseCommand_ options:
307+
By default :class:`~django_typer.TyperCommand` suppresses ``--verbosity``. You can add it back by
308+
setting ``suppressed_base_arguments`` to an empty list. If you want to use verbosity you can
309+
simply redefine it or use one of django-typer_'s :ref:`provided type hints <types>` for the default
310+
BaseCommand_ options:
310311

311312
.. code-block:: python
312313
@@ -326,7 +327,7 @@ Configure the Typer_ Application
326327

327328
Typer_ apps can be configured using a number of parameters. These parameters are usually passed
328329
to the Typer class constructor when the application is created. django-typer_ provides a way to
329-
pass these options upstream to Typer_ by supplying them as keyword arguments to the
330+
pass these options upstream to Typer_ by supplying them as keyword arguments to the
330331
:class:`~django_typer.TyperCommand` class inheritance:
331332

332333
.. code-block:: python
@@ -358,12 +359,12 @@ See the section on :ref:`defining shell completions.<define-shellcompletions>`
358359
Configure rich_ Stack Traces
359360
----------------------------
360361

361-
When rich_ is installed it may be `configured to display rendered stack traces
362+
When rich_ is installed it may be `configured to display rendered stack traces
362363
<https://rich.readthedocs.io/en/stable/traceback.html>`_ for unhandled exceptions.
363-
These stack traces are information dense and can be very helpful for debugging. By default, if rich_
364-
is installed django-typer_ will configure it to render stack traces. You can disable this behavior by
365-
setting the ``DT_RICH_TRACEBACK_CONFIG`` config to ``False``. You may also set
366-
``DT_RICH_TRACEBACK_CONFIG`` to a dictionary holding the parameters to pass to
364+
These stack traces are information dense and can be very helpful for debugging. By default, if
365+
rich_ is installed django-typer_ will configure it to render stack traces. You can disable
366+
this behavior by setting the ``DT_RICH_TRACEBACK_CONFIG`` config to ``False``. You may also
367+
set ``DT_RICH_TRACEBACK_CONFIG`` to a dictionary holding the parameters to pass to
367368
`rich.traceback.install`.
368369

369370
This provides a common hook for configuring rich_ that you can control on a per-deployment basis:
@@ -397,6 +398,30 @@ This provides a common hook for configuring rich_ that you can control on a per-
397398
the Typer_ application. It is best to not set these and allow users to configure tracebacks
398399
via the ``DT_RICH_TRACEBACK_CONFIG`` setting.
399400

401+
Add Help Text to Commands
402+
-------------------------
403+
404+
There are multiple places to add help text to your commands. There is however a precedence order,
405+
and while lazy translation is supported in help texts, if you use docstrings as the helps they will
406+
not be translated.
407+
408+
The precedence order, for a simple command is as follows:
409+
410+
.. code-block:: python
411+
412+
from django_typer import TyperCommand, command
413+
from django.utils.translation import gettext_lazy as _
414+
415+
class Command(TyperCommand, help=_('2')):
416+
417+
help = _("3")
418+
419+
@command(help=_("1"))
420+
def handle(self):
421+
"""
422+
Docstring is last priority and is not subject to translation.
423+
"""
424+
400425
401426
Document Commands w/Sphinx
402427
--------------------------

doc/source/index.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ BaseCommand functionality is preserved, so that TyperCommand can be a drop in re
1717
* Configure the rendering of exception stack traces using rich.
1818
* :ref:`Install shell tab-completion support <shellcompletions>` for TyperCommands and normal
1919
Django_ commands for bash_, zsh_, fish_ and powershell_.
20-
* :ref:`Create custom and portable shell tab-completions for your CLI parameters. <define-shellcompletions>`
20+
* :ref:`Create custom and portable shell tab-completions for your CLI parameters.
21+
<define-shellcompletions>`
2122
* Refactor existing management commands into TyperCommands because TyperCommand is interface
2223
compatible with BaseCommand.
2324

0 commit comments

Comments
 (0)