django-stubs: Missing type parameters for generic type "ModelSerializer"

2.8k views Asked by At

I have

class AnimalSerializer(serializers.ModelSerializer):
    class Meta:
        model = Animal
        fields = "__all__"

Now i run mypy

[mypy]
# The mypy configurations: https://mypy.readthedocs.io/en/latest/config_file.html
python_version = 3.9

check_untyped_defs = True
# disallow_any_explicit = True
disallow_any_generics = True
disallow_untyped_calls = True
disallow_untyped_decorators = True
ignore_errors = False
ignore_missing_imports = True
implicit_reexport = False
strict_optional = True
strict_equality = True
no_implicit_optional = True
warn_unused_ignores = True
warn_redundant_casts = True
warn_unused_configs = True
warn_unreachable = True
warn_no_return = True
mypy_path = /home/simha/app/backend_django/src

plugins =
  mypy_django_plugin.main,
  mypy_drf_plugin.main

[mypy.plugins.django-stubs]
django_settings_module = petsproject.settings
(venv) $ mypy .

I get

error: Missing type parameters for generic type "ModelSerializer"

1

There are 1 answers

0
Alex Waygood On BEST ANSWER

The stub file for serializers.ModelSerializer shows that it inherits from a generic base class BaseSerializer. This means that ModelSerializer is also generic:

# (Imports excluded for the sake of brevity)

_MT = TypeVar("_MT", bound=Model)  # Model Type

# <-- snip -->

class ModelSerializer(Serializer, BaseSerializer[_MT]):
    serializer_field_mapping: Dict[Type[models.Field], Type[Field]] = ...
    serializer_related_field: Type[RelatedField] = ...
    serializer_related_to_field: Type[RelatedField] = ...
    serializer_url_field: Type[RelatedField] = ...
    serializer_choice_field: Type[Field] = ...
    url_field_name: Optional[str] = ...
    instance: Optional[Union[_MT, Sequence[_MT]]]  # type: ignore[override]
    class Meta:
        model: Type[_MT] # type: ignore
        fields: Union[Sequence[str], Literal["__all__"]]
        read_only_fields: Optional[Sequence[str]]
        exclude: Optional[Sequence[str]]
        depth: Optional[int]
        extra_kwargs: Dict[str, Dict[str, Any]]  # type: ignore[override]
    def __init__(
        self,
        instance: Union[None, _MT, Sequence[_MT], QuerySet[_MT], Manager[_MT]] = ...,
        data: Any = ...,
        partial: bool = ...,
        many: bool = ...,
        context: Dict[str, Any] = ...,
        read_only: bool = ...,
        write_only: bool = ...,
        required: bool = ...,
        default: Union[Union[_MT, Sequence[_MT]], Callable[[], Union[_MT, Sequence[_MT]]]] = ...,
        initial: Union[Union[_MT, Sequence[_MT]], Callable[[], Union[_MT, Sequence[_MT]]]] = ...,
        source: str = ...,
        label: str = ...,
        help_text: str = ...,
        style: Dict[str, Any] = ...,
        error_messages: Dict[str, str] = ...,
        validators: Optional[Sequence[Validator[_MT]]] = ...,
        allow_null: bool = ...,
        allow_empty: bool = ...,
    ): ...
    def update(self, instance: _MT, validated_data: Any) -> _MT: ...  # type: ignore[override]
    def create(self, validated_data: Any) -> _MT: ...  # type: ignore[override]
    def save(self, **kwargs: Any) -> _MT: ...  # type: ignore[override]
    def to_representation(self, instance: _MT) -> Any: ...  # type: ignore[override]
    def get_field_names(self, declared_fields: Mapping[str, Field], info: FieldInfo) -> List[str]: ...
    def get_default_field_names(self, declared_fields: Mapping[str, Field], model_info: FieldInfo) -> List[str]: ...
    def build_field(
        self, field_name: str, info: FieldInfo, model_class: _MT, nested_depth: int
    ) -> Tuple[Field, Dict[str, Any]]: ...
    def build_standard_field(
        self, field_name: str, model_field: Type[models.Field]
    ) -> Tuple[Field, Dict[str, Any]]: ...
    def build_relational_field(
        self, field_name: str, relation_info: RelationInfo
    ) -> Tuple[Type[Field], Dict[str, Any]]: ...
    def build_nested_field(
        self, field_name: str, relation_info: RelationInfo, nested_depth: int
    ) -> Tuple[Field, Dict[str, Any]]: ...
    def build_property_field(self, field_name: str, model_class: _MT) -> Tuple[Field, Dict[str, Any]]: ...
    def build_url_field(self, field_name: str, model_class: _MT) -> Tuple[Field, Dict[str, Any]]: ...
    def build_unknown_field(self, field_name: str, model_class: _MT) -> NoReturn: ...
    def include_extra_kwargs(
        self, kwargs: MutableMapping[str, Any], extra_kwargs: MutableMapping[str, Any]
    ) -> MutableMapping[str, Any]: ...
    def get_extra_kwargs(self) -> Dict[str, Any]: ...
    def get_uniqueness_extra_kwargs(
        self, field_names: Iterable[str], declared_fields: Mapping[str, Field], extra_kwargs: Dict[str, Any]
    ) -> Tuple[Dict[str, Any], Dict[str, HiddenField]]: ...
    def _get_model_fields(
        self, field_names: Iterable[str], declared_fields: Mapping[str, Field], extra_kwargs: MutableMapping[str, Any]
    ) -> Dict[str, models.Field]: ...
    def get_unique_together_validators(self) -> List[UniqueTogetherValidator]: ...
    def get_unique_for_date_validators(self) -> List[BaseUniqueForValidator]: ...

When run with the --disallow-any-generics option, MyPy will complain if you inherit from an unparameterised generic, as it is unclear whether you want your inherited class to also be considered generic, or whether you want it to be considered a more concrete version of the base class.

As you have the line model = Animal in the Meta class in your derived class, and the stub file annotates ModelSerializer.Meta.model as being of type Type[_MT], my guess is that you do not want want your AnimalSerializer class to be generic, and instead want it to be specific to an Animal class that is a subclass of Model. As such, you need to rewrite your AnimalSerializer class like this:

class AnimalSerializer(serializers.ModelSerializer[Animal]):
    class Meta:
        model = Animal
        fields = "__all__"