1
1
"""
2
- A collection of parsers that turn strings into useful Django types.
3
- Pass these parsers to the `parser` argument of typer.Option and
4
- typer.Argument.
2
+ Typer_ supports custom parsers for options and arguments. If you would
3
+ like to type a parameter with a type that isn't supported by Typer_ you can
4
+ `implement your own parser <https://typer.tiangolo.com/tutorial/parameter-types/custom-types>`_
5
+ , or `ParamType <https://click.palletsprojects.com/en/8.1.x/api/#click.ParamType>`_
6
+ in click_ parlance.
7
+
8
+ This module contains a collection of parsers that turn strings into useful
9
+ Django types. Pass these parsers to the `parser` argument of typer.Option and
10
+ typer.Argument. Parsers are provided for:
11
+
12
+ - **Model Objects**: Turn a string into a model object instance using :class:`ModelObjectParser`.
13
+ - **App Labels**: Turn a string into an AppConfig instance using :func:`parse_app_label`.
14
+
15
+
16
+ .. warning::
17
+
18
+ If you implement a custom parser, please take care to ensure that it:
19
+ - Handles the case where the value is already the expected type.
20
+ - Returns None if the value is None (already implemented if subclassing ParamType).
21
+ - Raises a CommandError if the value is invalid.
22
+ - Handles the case where the param and context are None.
5
23
"""
6
24
7
25
import typing as t
8
26
from uuid import UUID
9
27
28
+ from click import Parameter , ParamType , Context
10
29
from django .apps import AppConfig , apps
11
30
from django .core .exceptions import ObjectDoesNotExist
12
31
from django .core .management import CommandError
13
32
from django .db .models import Field , Model , UUIDField
14
33
from django .utils .translation import gettext as _
15
34
16
35
17
- class ModelObjectParser :
36
+ class ModelObjectParser ( ParamType ) :
18
37
"""
19
38
A parser that will turn strings into model object instances based on the
20
39
configured lookup field and model class.
@@ -39,6 +58,11 @@ class ModelObjectParser:
39
58
40
59
__name__ = "ModelObjectParser" # typer internals expect this
41
60
61
+ @property
62
+ def name (self ):
63
+ """Descriptive name of the model object."""
64
+ return self .model_cls ._meta .verbose_name
65
+
42
66
def __init__ (
43
67
self ,
44
68
model_cls : t .Type [Model ],
@@ -54,7 +78,9 @@ def __init__(
54
78
if self .case_insensitive and "iexact" in self ._field .get_lookups ():
55
79
self ._lookup = "__iexact"
56
80
57
- def __call__ (self , value : t .Union [str , Model ]) -> Model :
81
+ def convert (
82
+ self , value : t .Any , param : t .Optional [Parameter ], ctx : t .Optional [Context ]
83
+ ):
58
84
"""
59
85
Invoke the parsing action on the given string. If the value is
60
86
already a model instance of the expected type the value will
@@ -63,11 +89,13 @@ def __call__(self, value: t.Union[str, Model]) -> Model:
63
89
handler is invoked if one was provided.
64
90
65
91
:param value: The value to parse.
92
+ :param param: The parameter that the value is associated with.
93
+ :param ctx: The context of the command.
66
94
:raises CommandError: If the lookup fails and no error handler is
67
95
provided.
68
96
"""
69
97
try :
70
- if isinstance (value , self .model_cls ): # pragma: no cover
98
+ if isinstance (value , self .model_cls ):
71
99
return value
72
100
if isinstance (self ._field , UUIDField ):
73
101
uuid = ""
@@ -94,6 +122,27 @@ def parse_app_label(label: t.Union[str, AppConfig]):
94
122
the instance is returned. The label will be tried first, if that fails
95
123
the label will be treated as the app name.
96
124
125
+ .. code-block:: python
126
+
127
+ import typing as t
128
+ import typer
129
+ from django_typer import TyperCommand
130
+ from django_typer.parsers import parse_app_label
131
+
132
+ class Command(TyperCommand):
133
+
134
+ def handle(
135
+ self,
136
+ django_apps: t.Annotated[
137
+ t.List[AppConfig],
138
+ typer.Argument(
139
+ parser=parse_app_label,
140
+ help=_("One or more application labels.")
141
+ )
142
+ ]
143
+ ):
144
+ ...
145
+
97
146
:param label: The label to map to an AppConfig instance.
98
147
:raises CommandError: If no matching app can be found.
99
148
"""
0 commit comments