Skip to content

Commit 284963f

Browse files
committed
start work on #58 and #162
1 parent ad5c42c commit 284963f

File tree

3 files changed

+52
-2
lines changed

3 files changed

+52
-2
lines changed

django_typer/management/__init__.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434

3535
from ..completers.model import ModelObjectCompleter # noqa: E402
3636
from ..config import traceback_config # noqa: E402
37-
from ..parsers.model import ModelObjectParser # noqa: E402
37+
from ..parsers.model import ModelObjectParser, ReturnType # noqa: E402
3838
from ..types import ( # noqa: E402
3939
ForceColor,
4040
NoColor,
@@ -126,6 +126,7 @@ def model_parser_completer(
126126
distinct: bool = ModelObjectCompleter.distinct,
127127
on_error: t.Optional[ModelObjectParser.error_handler] = ModelObjectParser.on_error,
128128
order_by: t.Optional[t.Union[str, t.Sequence[str]]] = None,
129+
return_type: ReturnType = ModelObjectParser.return_type,
129130
) -> t.Dict[str, t.Any]:
130131
"""
131132
A factory function that returns a dictionary that can be used to specify
@@ -161,13 +162,16 @@ def handle(
161162
completion suggestions, True by default
162163
:param on_error: a callable that will be called if the parser lookup fails
163164
to produce a matching object - by default a CommandError will be raised
165+
:param return_type: An enumeration switch to return either a model instance,
166+
queryset or model field value type.
164167
"""
165168
return {
166169
"parser": ModelObjectParser(
167170
model_or_qry if inspect.isclass(model_or_qry) else model_or_qry.model, # type: ignore
168171
lookup_field,
169172
case_insensitive=case_insensitive,
170173
on_error=on_error,
174+
return_type=return_type,
171175
),
172176
"shell_complete": ModelObjectCompleter(
173177
model_or_qry,

django_typer/parsers/model.py

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import typing as t
22
from datetime import date, datetime, time
3+
from enum import Enum
34
from uuid import UUID
45

56
from click import Context, Parameter, ParamType
@@ -10,6 +11,12 @@
1011
from django_typer.completers.model import ModelObjectCompleter
1112

1213

14+
class ReturnType(Enum):
15+
MODEL_INSTANCE = 0
16+
FIELD_VALUE = 1
17+
QUERY_SET = 2
18+
19+
1320
class ModelObjectParser(ParamType):
1421
"""
1522
A parser that will turn strings into model object instances based on the
@@ -44,6 +51,9 @@ def handle(
4451
The callable should accept three arguments: the model class, the
4552
value that failed to lookup, and the exception that was raised.
4653
If not provided, a CommandError will be raised.
54+
:param return_type: The model object parser can return types other than the model
55+
instance (default) - use the ReturnType enumeration to return other types
56+
from the parser including QuerySets or the primitive values of the model fields.
4757
"""
4858

4959
error_handler = t.Callable[[t.Type[models.Model], str, Exception], None]
@@ -52,6 +62,7 @@ def handle(
5262
lookup_field: str
5363
case_insensitive: bool = False
5464
on_error: t.Optional[error_handler] = None
65+
return_type: ReturnType = ReturnType.MODEL_INSTANCE
5566

5667
_lookup: str = ""
5768
_field: models.Field
@@ -90,6 +101,7 @@ def __init__(
90101
lookup_field: t.Optional[str] = None,
91102
case_insensitive: bool = case_insensitive,
92103
on_error: t.Optional[error_handler] = on_error,
104+
return_type: ReturnType = return_type,
93105
):
94106
from django.contrib.contenttypes.fields import GenericForeignKey
95107

@@ -98,6 +110,7 @@ def __init__(
98110
lookup_field or getattr(self.model_cls._meta.pk, "name", "id")
99111
)
100112
self.on_error = on_error
113+
self.return_type = return_type
101114
self.case_insensitive = case_insensitive
102115
field = self.model_cls._meta.get_field(self.lookup_field)
103116
assert not isinstance(field, (models.ForeignObjectRel, GenericForeignKey)), _(
@@ -126,7 +139,7 @@ def convert(
126139
"""
127140
original = value
128141
try:
129-
if isinstance(value, self.model_cls):
142+
if not isinstance(value, str):
130143
return value
131144
elif isinstance(self._field, models.UUIDField):
132145
uuid = ""
@@ -147,6 +160,12 @@ def convert(
147160
if ambiguous:
148161
raise ValueError(f"Invalid duration: {value}")
149162
value = parsed
163+
if self.return_type is ReturnType.QUERY_SET:
164+
return self.model_cls.objects.filter(
165+
**{f"{self.lookup_field}{self._lookup}": value}
166+
)
167+
elif self.return_type is ReturnType.FIELD_VALUE:
168+
return value
150169
return self.model_cls.objects.get(
151170
**{f"{self.lookup_field}{self._lookup}": value}
152171
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from typing import Annotated
2+
3+
import typer
4+
from django.utils.translation import gettext_lazy as _
5+
from datetime import timedelta
6+
7+
from django_typer.management import TyperCommand, model_parser_completer
8+
from django_typer.parsers.model import ReturnType
9+
from tests.apps.test_app.models import ShellCompleteTester
10+
from django_typer.utils import duration_iso_string
11+
12+
13+
class Command(TyperCommand, rich_markup_mode="rich"):
14+
def handle(
15+
self,
16+
duration: Annotated[
17+
timedelta,
18+
typer.Argument(
19+
**model_parser_completer(
20+
ShellCompleteTester, return_type=ReturnType.FIELD_VALUE
21+
)
22+
),
23+
],
24+
):
25+
assert self.__class__ is Command
26+
assert isinstance(duration, timedelta)
27+
return duration_iso_string(duration)

0 commit comments

Comments
 (0)