Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BaseFormSet - TypeError: 'type' object is not subscriptable #976

Closed
jamesbeith opened this issue May 30, 2022 · 6 comments
Closed

BaseFormSet - TypeError: 'type' object is not subscriptable #976

jamesbeith opened this issue May 30, 2022 · 6 comments
Labels
bug Something isn't working

Comments

@jamesbeith
Copy link

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 jamesbeith added the bug Something isn't working label May 30, 2022
@sterliakov
Copy link
Contributor

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.

@maziar-dandc
Copy link

adding from __future__ import annotations fixed it for me.

@sobolevn
Copy link
Member

Try django_stubs_ext@0.5.0. Does it work for you?
If not, PRs are welcome.

@maziar-dandc
Copy link

maziar-dandc commented Jun 20, 2022

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

@sterliakov
Copy link
Contributor

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.

@flaeppe
Copy link
Member

flaeppe commented Jul 29, 2024

I can see BaseFormSet in our patched list of classes that should support being generic:

# certain django classes need to be generic, but lack the __class_getitem__ dunder needed to
# annotate them: https://github.com/typeddjango/django-stubs/issues/507
# this list stores them so `monkeypatch` can fix them when called
_need_generic: List[MPGeneric[Any]] = [
MPGeneric(ModelAdmin),
MPGeneric(SingleObjectMixin),
MPGeneric(FormMixin),
MPGeneric(DeletionMixin),
MPGeneric(MultipleObjectMixin),
MPGeneric(BaseModelAdmin),
MPGeneric(Field),
MPGeneric(Paginator),
MPGeneric(BaseFormSet),
MPGeneric(BaseModelForm),
MPGeneric(BaseModelFormSet),
MPGeneric(ModelChoiceField),
MPGeneric(Feed),
MPGeneric(Sitemap),
MPGeneric(SuccessMessageMixin),
MPGeneric(FileProxyMixin),
MPGeneric(Lookup),
MPGeneric(BaseConnectionHandler),
MPGeneric(ExpressionWrapper),
MPGeneric(ReverseManyToOneDescriptor),
# These types do have native `__class_getitem__` method since django 3.1:
MPGeneric(QuerySet, (3, 1)),
MPGeneric(BaseManager, (3, 1)),
# These types do have native `__class_getitem__` method since django 4.1:
MPGeneric(ForeignKey, (4, 1)),
]

Which means that it can be patched with django-stubs-ext, check out the django-stubs-ext readme for more info: https://github.com/typeddjango/django-stubs/tree/master/ext#extensions-and-monkey-patching-for-django-stubs

The "I cannot use QuerySet or Manager with type annotations" section in the django-stubs readme also has some additional description that could be informative.

Closed via #909

@flaeppe flaeppe closed this as completed Jul 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Development

No branches or pull requests

5 participants