Pylance-release: Django generic views are not recognized if imported with "from django.views import generic" statement

Created on 13 Aug 2020  路  12Comments  路  Source: microsoft/pylance-release

When Django's generic views are imported with the statement from django.views import generic and referred to with generic.TemplateView Pylance shows error: "TemplateView" is not a known member of module.

Environment data

  • Language Server version: Pylance language server 2020.8.0 (pyright 4b16a648)
  • OS and version: Ubuntu
  • Python version: Python 3.8.3
  • Django version: 3.0.8

Expected behaviour

Following code not cause an issue:

from django.views import generic

class MyView(generic.TemplateView):
    pass

Actual behaviour

This code causes error by Pylance.

from django.views import generic

class MyView(generic.TemplateView): # at this line error: "TemplateView" is not a known member of module
    pass

Error:

{
    "resource": "project/web/views.py",
    "owner": "_generated_diagnostic_collection_name_#0",
    "severity": 8,
    "message": "\"TemplateView\" is not a known member of module",
    "source": "Pylance (reportGeneralTypeIssues)",
....
}

Code Snippet / Additional information

First of all, line from django.views import generic is not the recommended way of importing generic views in Django.
In current version of the Django documentation it is only mentioned on the Step 4 of the tutorial.
If TemplateView is imported like this from django.views.generic.base import TemplateView there is no error and no problem.

But looking through our 7 year old project (we recently switched to absolutely fabulous and amazing Pylance) we always used from django.views import generic code.
And looking through the GitHub there are 32,008 code results for the string "from django.views import generic".

So the import maybe wrong and not officially recommended but it works and used by a lot of projects.

Is there any possibility that Pylance will be able to parse the code inside django.generic.views to understand where those classes coming from?

bug fixed in next version in backlog

Most helpful comment

This issue has been fixed in version 2021.4.3, which we've just released. You can find the changelog here: https://github.com/microsoft/pylance-release/blob/main/CHANGELOG.md#202143-29-april-2021

All 12 comments

This should be working, because https://github.com/typeddjango/django-stubs/blob/master/django-stubs/views/generic/__init__.pyi declares that TemplateView is re-exported.

Returning to this, from django.views import generic appears to not work because in the django-stub project, https://github.com/typeddjango/django-stubs/blob/master/django-stubs/views/__init__.pyi does not re-export the submodule, but I'm not certain that it needs to do this.

@erictraut Do you know if this file needs to explicitly re-export the generic submodule (in a subfolder), or is this a bug in the import resolver or binder where should be accepting this code?

Maybe this code was valid due to some import side effect, but that'd strike me as strange to allow import django.views.generic but not from django.views import generic.

image

The current stub is doing something very strange. It's implicitly importing (and re-exporting) the submodule ".generic.base", but it's not implicitly or explicitly importing/exporting the submodule ".generic".

I think the correct fix here is to change the stub to:

from .generic.base import View as View
from . import generic as generic

The thing is that generic already exists as a submodule of views; why does it need to be explicitly exported?

import django.views.generic as generic  # works
from django.views import generic        # doesn't?

image

image

why does it need to be explicitly exported?

Type stubs are meant to be very explicit about which symbols are exported from where. By default, imported symbols are not re-exported, and submodules are not implicitly re-exported.

I don't know what's different between this case and say urllib: https://github.com/python/typeshed/tree/master/stdlib/urllib

Its __init__.py is empty (does not reexport its submodules), and yet we understand the form that's broken for django:

image

I don't think this is a case where the submodule needs to explicitly be re-exported; I'm not writing:

import django.views

django.views.generic

That _does_ require a re-export.

In fact, if you remove the contents of view's __init__.py, it does work:

image

That last case to me says there's a bug where it's hiding generic for some reason when from .generic.base import View as View is present.

The statement import a.b allows you to access symbols in the a.b module. If you want to access symbols in the a module, you should include the statement import a.

Likewise, the statement from .a.b import c implicitly causes submodule .a.b to be imported, but it doesn't cause .a to be imported (or re-exported). If you want .a to be imported (or re-exported), you should do so explicitly.

The Python module loader unfortunately has a bunch of side effects that developers sometimes unknowingly rely upon. This is dangerous and fragile, and pyright intentionally honors only the explicit intent of an import statement.

In this case, the stub includes the statement from .generic.base import View as View. If the intent is to import (and re-export) .generic from this module, it needs to be made explicit.

@dimaulupov as a workaround you can add your own stub under your root folder to override our stub

typings\django\views\__init__.pyi

import django.views.generic as generic
from django.views.generic.base import View as View

Update: I think you'll have to copy all of our bundled django stubs to typings\django and then modify it there.

This should be fixed in the next release.

This issue has been fixed in version 2021.4.3, which we've just released. You can find the changelog here: https://github.com/microsoft/pylance-release/blob/main/CHANGELOG.md#202143-29-april-2021

Was this page helpful?
0 / 5 - 0 ratings