The Django documentation shows that you can provide a dict as input to the choices parameter on the CharField type: https://docs.djangoproject.com/en/6.0/ref/models/fields/#choices. It is, however, not accepted by the type annotations here.
Example file:
from django.db import models
class Foo(models.Model):
STATUSES = {
"new": "New",
"open": "Open",
"closed": "Closed",
"archived": "Archived",
}
status = models.CharField(choices=STATUSES)
Output from mypy:
models.py:12: error: Argument "choices" to "CharField" has incompatible type "dict[str, str]"; expected "Iterable[tuple[Never, str] | tuple[str, Iterable[tuple[Never, str]]]] | type[TextChoices]" [arg-type]
Output from Pyright:
models.py
models.py:12:39 - error: Argument of type "dict[str, str]" cannot be assigned to parameter "choices" of type "Iterable[tuple[_C@CharField, str] | tuple[str, Iterable[tuple[_C@CharField, str]]]] | type[TextChoices]" in function "__new__"
Type "dict[str, str]" is not assignable to type "Iterable[tuple[_C@CharField, str] | tuple[str, Iterable[tuple[_C@CharField, str]]]] | type[TextChoices]"
"dict[str, str]" is not assignable to "Iterable[tuple[_C@CharField, str] | tuple[str, Iterable[tuple[_C@CharField, str]]]]"
Type parameter "_T_co@Iterable" is covariant, but "str" is not a subtype of "tuple[_C@CharField, str] | tuple[str, Iterable[tuple[_C@CharField, str]]]"
Type "str" is not assignable to type "tuple[_C@CharField, str] | tuple[str, Iterable[tuple[_C@CharField, str]]]"
"str" is not assignable to "tuple[_C@CharField, str]"
"str" is not assignable to "tuple[str, Iterable[tuple[_C@CharField, str]]]"
Type "dict[str, str]" is not assignable to type "type[TextChoices]" (reportArgumentType)
1 error, 0 warnings, 0 informations
Ouput from ty:
error[[invalid-argument-type](https://ty.dev/rules#invalid-argument-type)]: Argument to constructor `CharField.__new__` is incorrect
--> models.py:12:31
|
12 | status = models.CharField(choices=STATUSES)
| ^^^^^^^^^^^^^^^^ Expected `Iterable[tuple[Unknown, str] | tuple[str, Iterable[tuple[Unknown, str]]]]`, found `dict[str, str]`
|
info: type `dict[str, str]` is not assignable to protocol `Iterable[tuple[Unknown, str] | tuple[str, Iterable[tuple[Unknown, str]]]]`
info: └── protocol member `__iter__` is incompatible
info: └── incompatible return types: `Iterator[str]` is not assignable to `Iterator[tuple[Unknown, str] | tuple[str, Iterable[tuple[Unknown, str]]]]`
info: └── protocol `Iterator[str]` is not assignable to protocol `Iterator[tuple[Unknown, str] | tuple[str, Iterable[tuple[Unknown, str]]]]`
info: └── protocol member `__next__` is incompatible
info: └── incompatible return types: `str` is not assignable to `tuple[Unknown, str] | tuple[str, Iterable[tuple[Unknown, str]]]`
info: Matching overload defined here
--> venv/lib/python3.14/site-packages/django-stubs/db/models/fields/__init__.pyi:702:9
|
702 | def __new__(
| ^^^^^^^
|
::: venv/lib/python3.14/site-packages/django-stubs/db/models/fields/__init__.pyi:722:9
|
722 | / choices: Iterable[tuple[_C, str] | tuple[str, Iterable[tuple[_C, str]]]]
723 | | | type[TextChoices] = ...,
| |_________________________________- Parameter declared here
|
info: Non-matching overloads for function `__new__`:
info: (cls, verbose_name: str | None = ..., *, name: str | None = ..., primary_key: bool = ..., max_length: int = ..., db_collation: str | None = ..., unique: bool = ..., blank: bool = ..., null: Literal[True], db_index: bool = ..., default: _C@CharField | (() -> _C@CharField) = ..., db_default: _C@CharField | Func = ..., editable: bool = ..., auto_created: bool = ..., serialize: bool = ..., unique_for_date: str | None = ..., unique_for_month: str | None = ..., unique_for_year: str | None = ..., choices: Iterable[tuple[_C@CharField, str] | tuple[str, Iterable[tuple[_C@CharField, str]]]] = ..., help_text: str = ..., db_column: str | None = ..., db_comment: str | None = ..., db_tablespace: str | None = ..., validators: Iterable[(...) -> None] = ..., error_messages: dict[str, Any] | None = ...) -> CharField[str | None]
Found 1 diagnostic
Tested with these versions:
Package Version
----------------- ---------------
Django 6.0.7
django-types 0.24.0
mypy 2.2.0
pyright 1.1.411
ty 0.0.57
I can see there's already a type defined that allows for a dict to be passed to the choices parameter:
|
_FieldChoices: TypeAlias = _LiteralFieldChoices | Callable[[], _LiteralFieldChoices] |
It is, however, not used where the type checkers need it:
|
choices: Iterable[tuple[_C, _StrOrPromise] | tuple[str, Iterable[tuple[_C, _StrOrPromise]]]] |
|
| type[TextChoices] = ..., |
At first glance, it looks like it would be enough to change the latter lines to choices: _FieldChoices = ..., but I wonder if there's something deeper at play here which doesn't make it as straight forward?
The Django documentation shows that you can provide a dict as input to the
choicesparameter on theCharFieldtype: https://docs.djangoproject.com/en/6.0/ref/models/fields/#choices. It is, however, not accepted by the type annotations here.Example file:
Output from mypy:
Output from Pyright:
Ouput from ty:
Tested with these versions:
I can see there's already a type defined that allows for a dict to be passed to the
choicesparameter:django-types/django-stubs/db/models/fields/__init__.pyi
Line 23 in 36159bf
It is, however, not used where the type checkers need it:
django-types/django-stubs/db/models/fields/__init__.pyi
Lines 730 to 731 in 36159bf
At first glance, it looks like it would be enough to change the latter lines to
choices: _FieldChoices = ...,but I wonder if there's something deeper at play here which doesn't make it as straight forward?