Skip to content

Commit

Permalink
fix #58 and fix #162
Browse files Browse the repository at this point in the history
  • Loading branch information
bckohan committed Feb 4, 2025
1 parent 284963f commit 4daaa2c
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 2 deletions.
5 changes: 5 additions & 0 deletions django_typer/parsers/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@

class ReturnType(Enum):
MODEL_INSTANCE = 0
"""Return the model instance with the matching field value."""

FIELD_VALUE = 1
"""Return the value of the field that was matched."""

QUERY_SET = 2
"""Return a queryset of model instances that match the field value."""


class ModelObjectParser(ParamType):
Expand Down
2 changes: 2 additions & 0 deletions doc/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ v3.0.0 (202X-XX-XX)

* Implemented `Migrate pyproject.toml to poetry 2 and portable project specifiers. <https://github.com/django-commons/django-typer/issues/164>_`
* BREAKING `Split parsers.py and completers.py into submodules. <https://github.com/django-commons/django-typer/issues/163>_`
* Implemented `Model completer/parser should support returning the field value <https://github.com/django-commons/django-typer/issues/162>`_
* Fixed `Model objects with null lookup fields should not be included in model field completion output <https://github.com/django-commons/django-typer/issues/160>`_
* Implemented `Add security scans to CI. <https://github.com/django-commons/django-typer/issues/158>`_
* Implemented `Add a performance regression. <https://github.com/django-commons/django-typer/issues/157>`_
Expand All @@ -26,6 +27,7 @@ v3.0.0 (202X-XX-XX)
* Fixed `Remove management imports in django_typer/__init__.py <https://github.com/django-commons/django-typer/issues/95>`_
* Fixed `ParamSpec includes self for group methods <https://github.com/django-commons/django-typer/issues/73>`_
* Fixed `Installed shellcompletion scripts do not pass values of --settings or --pythonpath <https://github.com/django-commons/django-typer/issues/68>`_
* Implemented `Add support for QuerySet parameter types. <https://github.com/django-commons/django-typer/issues/58>`_
* Fixed `shellcompletion complete should print to the command's stdout. <https://github.com/django-commons/django-typer/issues/19>`_
* Implemented `Add completer/parser for FileField and FilePathField <https://github.com/django-commons/django-typer/issues/17>`_
* Implemented `Add completer/parser for DurationField <https://github.com/django-commons/django-typer/issues/16>`_
Expand Down
31 changes: 31 additions & 0 deletions doc/source/shell_completion.rst
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,37 @@ shells support. Refer to the reference documentation and the
):
...
QuerySets and Field Values
~~~~~~~~~~~~~~~~~~~~~~~~~~

:class:`~django_typer.parsers.model.ModelObjectParser` can be configured to return queryset
types or the primitive field values instead of a model instance, using the ``return_type``
parameter and the :class:`~django_typer.parsers.model.ReturnType` enumeration:

.. code-block:: python
from django_typer.management import TyperCommand, model_parser_completer
from django_typer.parsers.model import ReturnType
from django.db.models import QuerySet
class Command(TyperCommand):
def handle(
self,
query: Annotated[
QuerySet[ModelClass],
typer.Argument(
**model_parser_completer(
ModelClass,
lookup_field="field_name",
return_type=ReturnType.QUERY_SET,
)
),
],
):
...
Completer Chains
----------------
Expand Down
2 changes: 1 addition & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ open-docs:
docs: build-docs-html open-docs

docs-live:
poetry run sphinx-autobuild doc/source doc/build --open-browser --watch django_typer --port 8000
poetry run sphinx-autobuild doc/source doc/build --open-browser --watch django_typer --port 8000 --delay 1

check-docs-links:
-poetry run sphinx-build -b linkcheck -Q -D linkcheck_timeout=10 ./doc/source ./doc/build
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ pluggy = "^1.5.0"
pywinpty = { version = "^2.0.14", markers = "sys_platform == 'win32'" }
pytest-timeout = "^2.3.1"
pre-commit = "^4.0.1"
django-stubs-ext = ">=4.2.7"

[tool.poetry.group.docs]
optional = true
Expand Down
12 changes: 11 additions & 1 deletion tests/apps/test_app/management/commands/field_value.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,31 @@
import typer
from django.utils.translation import gettext_lazy as _
from datetime import timedelta
from django.core.management.base import CommandError

from django_typer.management import TyperCommand, model_parser_completer
from django_typer.parsers.model import ReturnType
from tests.apps.test_app.models import ShellCompleteTester
from django_typer.utils import duration_iso_string


def test_custom_error_message(model_cls, field_value: str, exception: Exception):
raise CommandError(
f"Test custom error message: {model_cls=}, {field_value=}, {exception=}"
)


class Command(TyperCommand, rich_markup_mode="rich"):
def handle(
self,
duration: Annotated[
timedelta,
typer.Argument(
**model_parser_completer(
ShellCompleteTester, return_type=ReturnType.FIELD_VALUE
ShellCompleteTester,
lookup_field="duration_field",
return_type=ReturnType.FIELD_VALUE,
on_error=test_custom_error_message,
)
),
],
Expand Down
31 changes: 31 additions & 0 deletions tests/apps/test_app/management/commands/queryset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from typing import Annotated

import typer
import json

from django_typer.management import TyperCommand, model_parser_completer
from django_typer.parsers.model import ReturnType
from django_typer.utils import duration_iso_string
from tests.apps.test_app.models import ShellCompleteTester
from django.db.models import QuerySet


class Command(TyperCommand, rich_markup_mode="rich"):
def handle(
self,
query: Annotated[
QuerySet[ShellCompleteTester],
typer.Argument(
**model_parser_completer(
ShellCompleteTester,
lookup_field="duration_field",
return_type=ReturnType.QUERY_SET,
)
),
],
):
assert self.__class__ is Command
assert isinstance(query, QuerySet)
return json.dumps(
{obj.id: duration_iso_string(obj.duration_field) for obj in query}
)
3 changes: 3 additions & 0 deletions tests/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@

import os
from pathlib import Path
import django_stubs_ext

django_stubs_ext.monkeypatch()

# Set default terminal width for help outputs - necessary for
# testing help output in CI environments.
Expand Down
17 changes: 17 additions & 0 deletions tests/test_parser_completers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2058,6 +2058,23 @@ def test_empty_complete_and_env_stability(self):
self.assertIn("makemigrations", self.shellcompletion.complete())
self.assertEqual(env, os.environ)

def test_return_field_value(self):
completions = get_values(self.shellcompletion.complete("field_value P54"))
self.assertTrue("P541D" in completions)
self.assertEqual(call_command("field_value", "P541D"), "P541D")
self.assertEqual(run_command("field_value", "P541D")[0].strip(), "P541D")

with self.assertRaisesMessage(CommandError, "Test custom error"):
call_command("field_value", "P541X")

def test_return_queryset(self):
completions = get_values(self.shellcompletion.complete("queryset P54"))
self.assertTrue("P541D" in completions)
objects = ShellCompleteTester.objects.filter(duration_field=timedelta(days=541))
data1 = json.loads(call_command("queryset", "P541D"))
for obj in objects:
self.assertEqual(data1[str(obj.id)], "P541D")


class TestDateTimeParserCompleter(ParserCompleterMixin, TestCase):
tz_info = None
Expand Down

0 comments on commit 4daaa2c

Please sign in to comment.