0

I have a Django model with a field that is a models.CharField with choices:

from django.db import models

class MyChoice(models.TextChoices):
    FOO = "foo", _("foo")
    BAR = "bar", _("bar")
    BAZ = "baz", _("baz")

class MyModel(models.Model):
    my_field = models.CharField(choices=MyChoice.choices, max_length=64)

Then, if I use this choice field in a function that has it as a typed parameter, mypy catches it as an error.

def use_choice(choice: MyChoice) -> None:
    pass

def call_use_choice(model: MyModel) -> None:
    use_choice(model.my_field)

# error: Argument 1 to "use_choice" has incompatible type "str"; expected "MyChoice"  [arg-type]

My configuration is as follows:

# pyproject.toml

[tool.poetry.dependencies]
python = ">=3.10,<3.11"
Django = "^3.2.8"

[tool.poetry.dependencies]
mypy = "^1.1.1"
django-stubs = "^1.16.0"
# mypy.ini

[mypy]
python_version = 3.10
ignore_missing_imports = True
plugins =
    mypy_django_plugin.main

[mypy.plugins.django-stubs]
django_settings_module = "config.settings"

Why is this happening?

tinom9
  • 369
  • 2
  • 12
  • 1
    Hm, is `model.my_field` really a `MyChoice` instance? As far as I remember, it should be plain `str`, as `mypy` points out. [This question](https://stackoverflow.com/questions/18676156/how-to-properly-use-the-choices-field-option-in-django) seems to agree with me. – STerliakov Mar 28 '23 at 22:25
  • Correct, `model.my_field` is indeed plain `str`, which answers my question as to why the typing does not return a `MyChoice` instance. Nonetheless, I was expecting a more constrained handling by Django, but I learned that the field does not guarantee that the choices saved are, in any form, members of `MyChoice`. Should my approach be to use a more permissive typing `MyChoice | str`? If I guaranteed no value of `model.my_field` is ever different from `MyChoice`, how would I programatically type it (`MyChoice | Literal["foo"] | ...`)? – tinom9 Apr 02 '23 at 16:55
  • Well, if you do want to enforce setting to `MyChoice` only, try annotating as `CharField[MyChoice, str]` (or `CharField[MyChoice, Literal[...]]` with manual choices enumeration in `Literal`, if you insist - there's no way to build it dynamically for `mypy`). For meaning of generic parameters, read a docstring of [a `Field` class in `django-stubs`](https://github.com/typeddjango/django-stubs/blob/master/django-stubs/db/models/fields/__init__.pyi#L48). – STerliakov Apr 03 '23 at 01:03

0 Answers0