django-stubs icon indicating copy to clipboard operation
django-stubs copied to clipboard

BaseFormSet - TypeError: 'type' object is not subscriptable

Open jamesbeith opened this issue 3 years ago • 5 comments

Bug report

What's wrong

Having recently updated mypy and django-stubs, the following error started occurring:

error: Missing type parameters for generic type "BaseFormSet"  [type-arg]

This was pointing at my formset class, so I added the generic type and, as an example, had this:

class MyForm(forms.Form):
    ...


class MyFormset(forms.BaseFormSet[MyForm]):
    ...

Which appeased the type checker, but unfortunately resulted in the following runtime error:

  File ".../forms.py", line ..., in <module>
    class MyFormset(forms.BaseFormSet[MyForm]):
TypeError: 'type' object is not subscriptable

This runtime error occurs even with the django_stubs_ext helper installed, as well as when using a string:

class MyFormset(forms.BaseFormSet["MyForm"]):
    ...

Note, this is running mypy with --strict, which enables --disallow-any-generics.

How is that should be

After adding the generic type to the formset class the type checker should pass and no runtime error should occur.

System information

  • OS: macOS 12.4
  • python version: 3.10.4
  • django version: 4.0.4
  • mypy version: 0.950
  • django-stubs version: 1.11.0
  • django-stubs-ext version: 0.4.0

jamesbeith avatar May 30 '22 04:05 jamesbeith

It doesn't reproduce with master.

@sobolevn Do we need a minor/patch release for django_stubs_ext or is there something to wait for? This was added in #909 together with making BaseFormSet generic in stubs. There was a release of main package since that.

sterliakov avatar Jun 14 '22 17:06 sterliakov

adding from __future__ import annotations fixed it for me.

maziar-dandc avatar Jun 20 '22 11:06 maziar-dandc

Try [email protected]. Does it work for you? If not, PRs are welcome.

sobolevn avatar Jun 20 '22 13:06 sobolevn

The original mypy error hasn't gone away (baseclass isn't a valid type), this is a test to reproduce the problem I'm dealing with, if you want, I can make a PR for it but I don't know if this is something that's even supposed to be possible or not:

- case: inlineformset_factory_extended
  main: |
    from typing import Any, Type
    from django import forms
    from myapp.models import Article, Category
    ArticleFS: Type[forms.BaseInlineFormSet[Article, Category, Any]] = forms.inlineformset_factory(Category, Article)
    ArticleFS(instance=Article())  # E: Argument "instance" to "BaseInlineFormSet" has incompatible type "Article"; expected "Optional[Category]"
    class CustomArticleFS(ArticleFS): # It will throw a "ArticleFS" is not a valid type error here.
      def __init__(self, *args, **kwargs):
        print('hi')
        super().__init__(*args, **kwargs)
    fs = CustomArticleFS(instance=Category())
    reveal_type(fs.instance)  # N: Revealed type is "myapp.models.Category"
  installed_apps:
    - myapp
  files:
    - path: myapp/__init__.py
    - path: myapp/models.py
      content: |
        from django.db import models

        class Article(models.Model):
            pass
        class Category(models.Model):
            pass

maziar-dandc avatar Jun 20 '22 14:06 maziar-dandc

It is a completely unrelated issue. The initial problem was with generics: BaseFormSet got type arguments after django-stubs upgrade, but it was not reflected in django_stubs_ext PyPI version.

What you're facing now is a known mypy limitation: mypy does not support dynamic base classes (see this wontfix issue). django-stubs has nothing to do with it.

Another option is to declare your formset class ArticleFS(forms.BaseInlineFormSet[...]) first and then pass formset=ArticleFS to inlineformset_factory. It is not supported (return type will be BaseInlineFormSet anyway), but in this case a simple cast will be sufficient. I have one simple solution for this (overload to return type of formset argument if it is given), but it is not 100% safe and may look odd.

So, it would be great if you can open another issue targeting this problem (inlineformset_factory + custom formset class) to make it a) searchable for future readers and b) usable to discuss this problem. This issue seems resolved.

sterliakov avatar Jun 20 '22 18:06 sterliakov