Repository: typeddjango/django-stubs

Issue:
Mypy crashes when django.contrib.[auth,contenttypes] is not installed
Hello everyone and thanks for the awesome work you've been doing. :rocket: 

First of all, I also posted on #428, but my comment there might have been lost in the noise, so I thought I'd open a new, more complete bug report. I hope that's okay with you!

## What's wrong

We have a somewhat irregular Django project, where we don't use either `django.contrib.auth` or `django.contrib.contenttypes`. For some reason, everything worked nice, but a week ago or so (I guess after the update to `1.3.0`?) mypy started crashing with the following traceback:

<details>
<summary>Traceback</summary>
<pre><code>version: 0.790
Traceback (most recent call last):
  File "/home/user/repos/project/venv/bin/mypy", line 8, in <module>
    sys.exit(console_entry())
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/__main__.py", line 8, in console_entry
    main(None, sys.stdout, sys.stderr)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/main.py", line 90, in main
    res = build.build(sources, options, None, flush_errors, fscache, stdout, stderr)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/build.py", line 180, in build
    result = _build(
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/build.py", line 254, in _build
    graph = dispatch(sources, manager, stdout)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/build.py", line 2630, in dispatch
    process_graph(graph, manager)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/build.py", line 2953, in process_graph
    process_stale_scc(graph, scc, manager)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/build.py", line 3051, in process_stale_scc
    graph[id].type_check_first_pass()
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/build.py", line 2111, in type_check_first_pass
    self.type_checker().check_first_pass()
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/checker.py", line 294, in check_first_pass
    self.accept(d)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/checker.py", line 401, in accept
    stmt.accept(self)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/nodes.py", line 677, in accept
    return visitor.visit_func_def(self)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/checker.py", line 726, in visit_func_def
    self._visit_func_def(defn)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/checker.py", line 730, in _visit_func_def
    self.check_func_item(defn, name=defn.name)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/checker.py", line 792, in check_func_item
    self.check_func_def(defn, typ, name)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/checker.py", line 975, in check_func_def
    self.accept(item.body)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/checker.py", line 401, in accept
    stmt.accept(self)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/nodes.py", line 1005, in accept
    return visitor.visit_block(self)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/checker.py", line 1973, in visit_block
    self.accept(s)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/checker.py", line 401, in accept
    stmt.accept(self)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/nodes.py", line 1196, in accept
    return visitor.visit_if_stmt(self)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/checker.py", line 3256, in visit_if_stmt
    self.accept(b)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/checker.py", line 401, in accept
    stmt.accept(self)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/nodes.py", line 1005, in accept
    return visitor.visit_block(self)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/checker.py", line 1973, in visit_block
    self.accept(s)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/checker.py", line 401, in accept
    stmt.accept(self)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/nodes.py", line 1020, in accept
    return visitor.visit_expression_stmt(self)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/checker.py", line 3152, in visit_expression_stmt
    self.expr_checker.accept(s.expr, allow_none_return=True, always_allow_any=True)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/checkexpr.py", line 3766, in accept
    typ = self.visit_call_expr(node, allow_none_return=True)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/checkexpr.py", line 263, in visit_call_expr
    return self.visit_call_expr_inner(e, allow_none_return=allow_none_return)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/checkexpr.py", line 312, in visit_call_expr_inner
    callee_type = get_proper_type(self.accept(e.callee, type_context, always_allow_any=True))
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/checkexpr.py", line 3772, in accept
    typ = node.accept(self)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/nodes.py", line 1497, in accept
    return visitor.visit_member_expr(self)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/checkexpr.py", line 1990, in visit_member_expr
    result = self.analyze_ordinary_member_access(e, is_lvalue)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/checkexpr.py", line 2001, in analyze_ordinary_member_access
    original_type = self.accept(e.expr)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/checkexpr.py", line 3772, in accept
    typ = node.accept(self)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/nodes.py", line 1545, in accept
    return visitor.visit_call_expr(self)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/checkexpr.py", line 263, in visit_call_expr
    return self.visit_call_expr_inner(e, allow_none_return=allow_none_return)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/checkexpr.py", line 340, in visit_call_expr_inner
    ret_type = self.check_call_expr_with_callee_type(callee_type, e, fullname,
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/checkexpr.py", line 817, in check_call_expr_with_callee_type
    return self.check_call(callee_type, e.args, e.arg_kinds, e,
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/checkexpr.py", line 876, in check_call
    return self.check_callable_call(callee, args, arg_kinds, context, arg_names,
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/checkexpr.py", line 988, in check_callable_call
    new_ret_type = self.apply_function_plugin(
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy/checkexpr.py", line 716, in apply_function_plugin
    return callback(
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy_django_plugin/transformers/init_create.py", line 62, in redefine_and_typecheck_model_init
    return typecheck_model_method(ctx, django_context, model_cls, '__init__')
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy_django_plugin/transformers/init_create.py", line 36, in typecheck_model_method
    expected_types = django_context.get_expected_types(typechecker_api, model_cls, method=method)
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/mypy_django_plugin/django/context.py", line 154, in get_expected_types
    from django.contrib.contenttypes.fields import GenericForeignKey
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/django/contrib/contenttypes/fields.py", line 6, in <module>
    from django.contrib.contenttypes.models import ContentType
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/django/contrib/contenttypes/models.py", line 133, in <module>
    class ContentType(models.Model):
  File "/home/user/repos/project/venv/lib/python3.9/site-packages/django/db/models/base.py", line 113, in __new__
    raise RuntimeError(
RuntimeError: Model class django.contrib.contenttypes.models.ContentType doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS.
user/domain/usecases.py:52: : note: use --pdb to drop into pdb
</code></pre>
</details>

We'd expect that we would be able to use `django-stubs` without the need for `django.contrib.*` packages. We'd be open to help out with a bug fix PR, but we'd need some guidance as to what an acceptable solution would be.

For example, the following fixes the mypy crash. Would it be okay with you for the upstream project?
```diff
diff --git a/mypy_django_plugin/django/context.py b/mypy_django_plugin/django/context.py
index 0c74b58..09c6e96 100644
--- a/mypy_django_plugin/django/context.py
+++ b/mypy_django_plugin/django/context.py
@@ -148,7 +148,9 @@ class DjangoContext:
         raise ValueError("No primary key defined")
 
     def get_expected_types(self, api: TypeChecker, model_cls: Type[Model], *, method: str) -> Dict[str, MypyType]:
-        from django.contrib.contenttypes.fields import GenericForeignKey
+        contenttypes_in_apps = self.apps_registry.is_installed("django.contrib.contenttypes")
+        if contenttypes_in_apps:
+            from django.contrib.contenttypes.fields import GenericForeignKey
 
         expected_types = {}
         # add pk if not abstract=True
@@ -192,7 +194,7 @@ class DjangoContext:
 
                     expected_types[field_name] = model_set_type
 
-            elif isinstance(field, GenericForeignKey):
+            elif contenttypes_in_apps and isinstance(field, GenericForeignKey):
                 # it's generic, so cannot set specific model
                 field_name = field.name
                 gfk_info = helpers.lookup_class_typeinfo(api, field.__class__)
diff --git a/mypy_django_plugin/transformers/request.py b/mypy_django_plugin/transformers/request.py
index 83899ce..4132796 100644
--- a/mypy_django_plugin/transformers/request.py
+++ b/mypy_django_plugin/transformers/request.py
@@ -8,6 +8,9 @@ from mypy_django_plugin.lib import helpers
 
 
 def set_auth_user_model_as_type_for_request_user(ctx: AttributeContext, django_context: DjangoContext) -> MypyType:
+    if not django_context.apps_registry.is_installed("django.contrib.auth"):
+        return ctx.default_attr_type
+
     # Imported here because django isn't properly loaded yet when module is loaded
     from django.contrib.auth.base_user import AbstractBaseUser
     from django.contrib.auth.models import AnonymousUser
```

## System information

- OS: Ubuntu 20.04
- `python` version: 3.9.0
- `django` version: 3.1.3
- `mypy` version: 0.790
- `django-stubs` version: 1.7.0



Discussion:

Resolving patch:
diff --git a/mypy_django_plugin/django/context.py b/mypy_django_plugin/django/context.py
--- a/mypy_django_plugin/django/context.py
+++ b/mypy_django_plugin/django/context.py
@@ -148,7 +148,9 @@ def get_primary_key_field(self, model_cls: Type[Model]) -> Field:
         raise ValueError("No primary key defined")
 
     def get_expected_types(self, api: TypeChecker, model_cls: Type[Model], *, method: str) -> Dict[str, MypyType]:
-        from django.contrib.contenttypes.fields import GenericForeignKey
+        contenttypes_in_apps = self.apps_registry.is_installed("django.contrib.contenttypes")
+        if contenttypes_in_apps:
+            from django.contrib.contenttypes.fields import GenericForeignKey
 
         expected_types = {}
         # add pk if not abstract=True
@@ -192,7 +194,7 @@ def get_expected_types(self, api: TypeChecker, model_cls: Type[Model], *, method
 
                     expected_types[field_name] = model_set_type
 
-            elif isinstance(field, GenericForeignKey):
+            elif contenttypes_in_apps and isinstance(field, GenericForeignKey):
                 # it's generic, so cannot set specific model
                 field_name = field.name
                 gfk_info = helpers.lookup_class_typeinfo(api, field.__class__)
diff --git a/mypy_django_plugin/transformers/request.py b/mypy_django_plugin/transformers/request.py
--- a/mypy_django_plugin/transformers/request.py
+++ b/mypy_django_plugin/transformers/request.py
@@ -8,6 +8,9 @@
 
 
 def set_auth_user_model_as_type_for_request_user(ctx: AttributeContext, django_context: DjangoContext) -> MypyType:
+    if not django_context.apps_registry.is_installed("django.contrib.auth"):
+        return ctx.default_attr_type
+
     # Imported here because django isn't properly loaded yet when module is loaded
     from django.contrib.auth.base_user import AbstractBaseUser
     from django.contrib.auth.models import AnonymousUser


Test patch:
diff --git a/tests/typecheck/test_request.yml b/tests/typecheck/test_request.yml
--- a/tests/typecheck/test_request.yml
+++ b/tests/typecheck/test_request.yml
@@ -27,6 +27,24 @@
             reveal_type(request.user) # N: Revealed type is 'django.contrib.auth.models.User'
     custom_settings: |
         INSTALLED_APPS = ('django.contrib.contenttypes', 'django.contrib.auth')
+-   case: request_object_user_without_auth_and_contenttypes_apps
+    disable_cache: true
+    main: |
+        from django.http.request import HttpRequest
+        request = HttpRequest()
+        reveal_type(request.user) # N: Revealed type is 'Union[django.contrib.auth.base_user.AbstractBaseUser, django.contrib.auth.models.AnonymousUser]'
+        if request.user.is_authenticated:
+            reveal_type(request.user) # N: Revealed type is 'django.contrib.auth.base_user.AbstractBaseUser'
+-   case: request_object_user_without_auth_but_with_contenttypes_apps
+    disable_cache: true
+    main: |
+        from django.http.request import HttpRequest
+        request = HttpRequest()
+        reveal_type(request.user) # N: Revealed type is 'Union[django.contrib.auth.base_user.AbstractBaseUser, django.contrib.auth.models.AnonymousUser]'
+        if request.user.is_authenticated:
+            reveal_type(request.user) # N: Revealed type is 'django.contrib.auth.base_user.AbstractBaseUser'
+    custom_settings: |
+        INSTALLED_APPS = ('django.contrib.contenttypes',)
 -   case: subclass_request_not_changed_user_type
     disable_cache: true
     main: |