Fix manager types scope (#991)

* Fix manager types scope

* Restore incremental mode and mention in developer docs

* Separate dev mypy config and regular one

* Document config files usage
This commit is contained in:
sterliakov
2022-06-14 21:30:13 +04:00
committed by GitHub
parent ae5b1a4edf
commit 32e13c37a6
8 changed files with 73 additions and 8 deletions

View File

@@ -46,7 +46,7 @@ jobs:
pip install -r ./requirements.txt pip install -r ./requirements.txt
- name: Run tests - name: Run tests
run: pytest run: pytest --mypy-ini-file=mypy.ini
typecheck: typecheck:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@@ -70,6 +70,12 @@ To execute the unit tests, simply run:
pytest pytest
``` ```
If you get some unexpected results or want to be sure that tests run is not affected by previous one, remove `mypy` cache:
```bash
rm -r .mypy_cache
```
We also test the stubs against Django's own test suite. This is done in CI but you can also do this locally. We also test the stubs against Django's own test suite. This is done in CI but you can also do this locally.
To execute the script run: To execute the script run:

View File

@@ -1,3 +1,10 @@
# Regular configuration file (can be used as base in other projects, runs in CI)
# NOTE: this config file is not used by pytest locally.
# See comment in mypy.ini.dev for explanation.
# WARNING: when changing this file, consider doing the same with mypy.ini.dev
[mypy] [mypy]
allow_redefinition = True allow_redefinition = True
check_untyped_defs = True check_untyped_defs = True

30
mypy.ini.dev Normal file
View File

@@ -0,0 +1,30 @@
# Configuration file to use during development
# Changes of plugin code are not detected by mypy, so
# incremental mode can be harmful during development
# (mypy cache is not always invalidated).
# However, incremental mode has to be supported by django-stubs,
# thus another config (mypy.ini) is used in CI.
# Incremental mode is recommended for regular usage.
# WARNING: when changing this file, consider doing the same with mypy.ini
[mypy]
allow_redefinition = True
check_untyped_defs = True
ignore_missing_imports = True
# Avoid caching between test runs
incremental = False
strict_optional = True
show_traceback = True
warn_no_return = False
warn_unused_ignores = True
warn_redundant_casts = True
warn_unused_configs = True
warn_unreachable = True
plugins =
mypy_django_plugin.main
[mypy.plugins.django-stubs]
django_settings_module = scripts.django_tests_settings

View File

@@ -369,11 +369,10 @@ def bind_or_analyze_type(t: MypyType, api: SemanticAnalyzer, module_name: Option
That should hopefully give a bound type.""" That should hopefully give a bound type."""
if isinstance(t, UnboundType) and module_name is not None: if isinstance(t, UnboundType) and module_name is not None:
node = api.lookup_fully_qualified_or_none(module_name + "." + t.name) node = api.lookup_fully_qualified_or_none(module_name + "." + t.name)
if node is None: if node is not None and node.type is not None:
return None return node.type
return node.type
else: return api.anal_type(t)
return api.anal_type(t)
def copy_method_to_another_class( def copy_method_to_another_class(

View File

@@ -212,7 +212,11 @@ class AddManagers(ModelClassInitializer):
# replace self type with new class, if copying method # replace self type with new class, if copying method
if isinstance(sym.node, FuncDef): if isinstance(sym.node, FuncDef):
helpers.copy_method_to_another_class( helpers.copy_method_to_another_class(
new_cls_def_context, self_type=custom_manager_type, new_method_name=name, method_node=sym.node new_cls_def_context,
self_type=custom_manager_type,
new_method_name=name,
method_node=sym.node,
original_module_name=base_manager_info.module_name,
) )
continue continue

View File

@@ -7,5 +7,5 @@ addopts =
-s -s
-v -v
--cache-clear --cache-clear
--mypy-ini-file=./mypy.ini --mypy-ini-file=./mypy.ini.dev
--mypy-extension-hook=scripts.tests_extension_hook.django_plugin_hook --mypy-extension-hook=scripts.tests_extension_hook.django_plugin_hook

View File

@@ -423,3 +423,22 @@
class MyModel(models.Model): class MyModel(models.Model):
objects = MyModelManager() objects = MyModelManager()
- case: regression_manager_scope_foreign
main: |
from myapp.models import MyModel
reveal_type(MyModel.on_site) # N: Revealed type is "myapp.models.MyModel_CurrentSiteManager[myapp.models.MyModel]"
installed_apps:
- myapp
- django.contrib.sites
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
from django.contrib.sites.models import Site
from django.contrib.sites.managers import CurrentSiteManager
class MyModel(models.Model):
site = models.ForeignKey(Site, on_delete=models.CASCADE)
on_site = CurrentSiteManager()