diff --git a/conftest.py b/conftest.py deleted file mode 100644 index 04cba73..0000000 --- a/conftest.py +++ /dev/null @@ -1,3 +0,0 @@ -pytest_plugins = [ - 'test.pytest_plugin' -] \ No newline at end of file diff --git a/dev-requirements.txt b/dev-requirements.txt index 382bdf7..de525f4 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,4 +1,5 @@ -r external/mypy/test-requirements.txt -e external/mypy -e . -decorator \ No newline at end of file +decorator +pip install -e git+https://github.com/mkurnikov/pytest-mypy-plugins.git#egg=pytest-mypy-plugins \ No newline at end of file diff --git a/mypy_django_plugin/plugins/related_fields.py b/mypy_django_plugin/plugins/related_fields.py index 4aab943..7458c2b 100644 --- a/mypy_django_plugin/plugins/related_fields.py +++ b/mypy_django_plugin/plugins/related_fields.py @@ -31,8 +31,7 @@ def get_valid_to_value_or_none(ctx: FunctionContext) -> Optional[Instance]: arg_type = ctx.arg_types[ctx.arg_names.index('to')][0] if not isinstance(arg_type, CallableType): - ctx.api.msg.warn(f'to= parameter type {arg_type.__class__.__name__} is not supported', - context=ctx.context) + # to= defined as string is not supported return None referred_to_type = arg_type.ret_type diff --git a/pytest.ini b/pytest.ini index 2772864..3f271e9 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,8 +1,10 @@ [pytest] -testpaths = test +testpaths = ./test python_files = test*.py addopts = --tb=native -v - -s \ No newline at end of file + -s + --ignore=./external + --mypy-ini-file=./test/plugins.ini diff --git a/test/pytest_tests/__init__.py b/test/pytest_tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/pytest_tests/base.py b/test/pytest_tests/base.py deleted file mode 100644 index f7e3020..0000000 --- a/test/pytest_tests/base.py +++ /dev/null @@ -1,9 +0,0 @@ -from test.pytest_plugin import MypyTypecheckTestCase - - -class BaseDjangoPluginTestCase(MypyTypecheckTestCase): - def ini_file(self): - return """ -[mypy] -plugins = mypy_django_plugin.main - """ diff --git a/test/pytest_tests/test_model_fields.py b/test/pytest_tests/test_model_fields.py deleted file mode 100644 index 6d8f622..0000000 --- a/test/pytest_tests/test_model_fields.py +++ /dev/null @@ -1,21 +0,0 @@ -from test.pytest_plugin import reveal_type -from test.pytest_tests.base import BaseDjangoPluginTestCase - - -class TestBasicModelFields(BaseDjangoPluginTestCase): - def test_model_field_classes_present_as_primitives(self): - from django.db import models - - class User(models.Model): - id = models.AutoField(primary_key=True) - small_int = models.SmallIntegerField() - name = models.CharField(max_length=255) - slug = models.SlugField(max_length=255) - text = models.TextField() - - user = User() - reveal_type(user.id) # E: Revealed type is 'builtins.int' - reveal_type(user.small_int) # E: Revealed type is 'builtins.int' - reveal_type(user.name) # E: Revealed type is 'builtins.str' - reveal_type(user.slug) # E: Revealed type is 'builtins.str' - reveal_type(user.text) # E: Revealed type is 'builtins.str' diff --git a/test/pytest_tests/test_model_relations.py b/test/pytest_tests/test_model_relations.py deleted file mode 100644 index 7900e2e..0000000 --- a/test/pytest_tests/test_model_relations.py +++ /dev/null @@ -1,103 +0,0 @@ -from test.pytest_plugin import reveal_type, output -from test.pytest_tests.base import BaseDjangoPluginTestCase - - -class TestForeignKey(BaseDjangoPluginTestCase): - def test_foreign_key_field(self): - from django.db import models - - class Publisher(models.Model): - pass - - class Book(models.Model): - publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE, - related_name='books') - - book = Book() - reveal_type(book.publisher) # E: Revealed type is 'main.Publisher*' - - publisher = Publisher() - reveal_type(publisher.books) # E: Revealed type is 'django.db.models.query.QuerySet[main.Book]' - - def test_every_foreign_key_creates_field_name_with_appended_id(self): - from django.db import models - - class Publisher(models.Model): - pass - - class Book(models.Model): - publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE, - related_name='books') - - book = Book() - reveal_type(book.publisher_id) # E: Revealed type is 'builtins.int' - - def test_foreign_key_different_order_of_params(self): - from django.db import models - - class Publisher(models.Model): - pass - - class Book(models.Model): - publisher = models.ForeignKey(on_delete=models.CASCADE, to=Publisher, - related_name='books') - - book = Book() - reveal_type(book.publisher) # E: Revealed type is 'main.Publisher*' - - publisher = Publisher() - reveal_type(publisher.books) # E: Revealed type is 'django.db.models.query.QuerySet[main.Book]' - - @output( - """ - main:4: warning: to= parameter type Instance is not supported -""" - ) - def test_to_parameter_as_string_fallbacks_to_any(self): - from django.db import models - - class Book(models.Model): - publisher = models.ForeignKey(to='Publisher', on_delete=models.CASCADE) - - book = Book() - reveal_type(book.publisher) # E: Revealed type is 'Any' - - -class TestOneToOneField(BaseDjangoPluginTestCase): - def test_onetoone_field(self): - from django.db import models - - class User(models.Model): - pass - - class Profile(models.Model): - user = models.OneToOneField(to=User, on_delete=models.CASCADE, related_name='profile') - - profile = Profile() - reveal_type(profile.user) # E: Revealed type is 'main.User*' - - user = User() - reveal_type(user.profile) # E: Revealed type is 'main.Profile' - - def test_onetoone_field_with_underscore_id(self): - from django.db import models - - class User(models.Model): - pass - - class Profile(models.Model): - user = models.OneToOneField(to=User, on_delete=models.CASCADE, related_name='profile') - - profile = Profile() - reveal_type(profile.user_id) # E: Revealed type is 'builtins.int' - - def test_parameter_to_keyword_may_be_absent(self): - from django.db import models - - class User(models.Model): - pass - - class Profile(models.Model): - user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile') - - reveal_type(User().profile) # E: Revealed type is 'main.Profile' diff --git a/test/pytest_tests/test_objects_queryset.py b/test/pytest_tests/test_objects_queryset.py deleted file mode 100644 index 3acf201..0000000 --- a/test/pytest_tests/test_objects_queryset.py +++ /dev/null @@ -1,13 +0,0 @@ -from test.pytest_plugin import reveal_type, output -from test.pytest_tests.base import BaseDjangoPluginTestCase - - -class TestObjectsQueryset(BaseDjangoPluginTestCase): - def test_every_model_has_objects_queryset_available(self): - from django.db import models - - class User(models.Model): - pass - - reveal_type(User.objects) # E: Revealed type is 'django.db.models.query.QuerySet[main.User]' - reveal_type(User.objects.get()) # E: Revealed type is 'main.User*' diff --git a/test/pytest_tests/test_parse_settings.py b/test/pytest_tests/test_parse_settings.py deleted file mode 100644 index e333857..0000000 --- a/test/pytest_tests/test_parse_settings.py +++ /dev/null @@ -1,37 +0,0 @@ -from test.pytest_plugin import reveal_type, file, env -from test.pytest_tests.base import BaseDjangoPluginTestCase - - -class TestParseSettingsFromFile(BaseDjangoPluginTestCase): - @env(DJANGO_SETTINGS_MODULE='mysettings') - def test_case(self): - from django.conf import settings - - reveal_type(settings.ROOT_DIR) # E: Revealed type is 'builtins.str' - reveal_type(settings.OBJ) # E: Revealed type is 'django.utils.functional.LazyObject' - reveal_type(settings.NUMBERS) # E: Revealed type is 'builtins.list[builtins.str]' - reveal_type(settings.DICT) # E: Revealed type is 'builtins.dict[Any, Any]' - - @file('mysettings.py') - def mysettings_py_file(self): - SECRET_KEY = 112233 - ROOT_DIR = '/etc' - NUMBERS = ['one', 'two'] - DICT = {} # type: ignore - - from django.utils.functional import LazyObject - - OBJ = LazyObject() - - -class TestSettingInitializableToNone(BaseDjangoPluginTestCase): - @env(DJANGO_SETTINGS_MODULE='mysettings') - def test_case(self): - from django.conf import settings - - reveal_type(settings.NONE_SETTING) # E: Revealed type is 'builtins.object' - - @file('mysettings.py') - def mysettings_py_file(self): - SECRET_KEY = 112233 - NONE_SETTING: object = None diff --git a/test/pytest_tests/test_postgres_fields.py b/test/pytest_tests/test_postgres_fields.py deleted file mode 100644 index bc3c1dc..0000000 --- a/test/pytest_tests/test_postgres_fields.py +++ /dev/null @@ -1,27 +0,0 @@ -from test.pytest_plugin import reveal_type -from test.pytest_tests.base import BaseDjangoPluginTestCase - - -class TestArrayField(BaseDjangoPluginTestCase): - def test_descriptor_access(self): - from django.db import models - from django.contrib.postgres.fields import ArrayField - - class User(models.Model): - array = ArrayField(base_field=models.Field()) - - user = User() - reveal_type(user.array) # E: Revealed type is 'builtins.list[Any]' - - def test_base_field_parsed_into_generic_attribute(self): - from django.db import models - from django.contrib.postgres.fields import ArrayField - - class User(models.Model): - members = ArrayField(base_field=models.IntegerField()) - members_as_text = ArrayField(base_field=models.CharField(max_length=255)) - - user = User() - reveal_type(user.members) # E: Revealed type is 'builtins.list[builtins.int*]' - reveal_type(user.members_as_text) # E: Revealed type is 'builtins.list[builtins.str*]' - diff --git a/test/test-data/model-fields.test b/test/test-data/model-fields.test new file mode 100644 index 0000000..6384a60 --- /dev/null +++ b/test/test-data/model-fields.test @@ -0,0 +1,16 @@ +[CASE test_model_fields_classes_present_as_primitives] +from django.db import models + +class User(models.Model): + id = models.AutoField(primary_key=True) + small_int = models.SmallIntegerField() + name = models.CharField(max_length=255) + slug = models.SlugField(max_length=255) + text = models.TextField() + +user = User() +reveal_type(user.id) # E: Revealed type is 'builtins.int' +reveal_type(user.small_int) # E: Revealed type is 'builtins.int' +reveal_type(user.name) # E: Revealed type is 'builtins.str' +reveal_type(user.slug) # E: Revealed type is 'builtins.str' +reveal_type(user.text) # E: Revealed type is 'builtins.str' diff --git a/test/test-data/model-relations.test b/test/test-data/model-relations.test new file mode 100644 index 0000000..40c0ced --- /dev/null +++ b/test/test-data/model-relations.test @@ -0,0 +1,53 @@ +[CASE test_foreign_key_field_with_related_name] +from django.db import models + +class Publisher(models.Model): + pass + +class Book(models.Model): + publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE, + related_name='books') + +book = Book() +reveal_type(book.publisher) # E: Revealed type is 'main.Publisher*' + +publisher = Publisher() +reveal_type(publisher.books) # E: Revealed type is 'django.db.models.query.QuerySet[main.Book]' + +[CASE test_foreign_key_field_creates_attribute_with_underscore_id] +from django.db import models + +class Publisher(models.Model): + pass + +class Book(models.Model): + publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE, + related_name='books') + +book = Book() +reveal_type(book.publisher_id) # E: Revealed type is 'builtins.int' + +[CASE test_foreign_key_field_different_order_of_params] +from django.db import models + +class Publisher(models.Model): + pass + +class Book(models.Model): + publisher = models.ForeignKey(on_delete=models.CASCADE, to=Publisher, + related_name='books') + +book = Book() +reveal_type(book.publisher) # E: Revealed type is 'main.Publisher*' + +publisher = Publisher() +reveal_type(publisher.books) # E: Revealed type is 'django.db.models.query.QuerySet[main.Book]' + +[CASE test_to_parameter_as_string_fallbacks_to_any] +from django.db import models + +class Book(models.Model): + publisher = models.ForeignKey(to='Publisher', on_delete=models.CASCADE) + +book = Book() +reveal_type(book.publisher) # E: Revealed type is 'Any' diff --git a/test/test-data/objects-queryset.test b/test/test-data/objects-queryset.test new file mode 100644 index 0000000..bf09319 --- /dev/null +++ b/test/test-data/objects-queryset.test @@ -0,0 +1,8 @@ +[CASE test_every_model_has_objects_queryset_available] +from django.db import models + +class User(models.Model): + pass + +reveal_type(User.objects) # E: Revealed type is 'django.db.models.query.QuerySet[main.User]' +reveal_type(User.objects.get()) # E: Revealed type is 'main.User*' \ No newline at end of file diff --git a/test/test-data/parse-settings.test b/test/test-data/parse-settings.test new file mode 100644 index 0000000..0e0b54b --- /dev/null +++ b/test/test-data/parse-settings.test @@ -0,0 +1,29 @@ +[CASE test_settings_are_parsed_into_django_conf_settings] +from django.conf import settings + +reveal_type(settings.ROOT_DIR) # E: Revealed type is 'builtins.str' +reveal_type(settings.OBJ) # E: Revealed type is 'django.utils.functional.LazyObject' +reveal_type(settings.NUMBERS) # E: Revealed type is 'builtins.list[builtins.str]' +reveal_type(settings.DICT) # E: Revealed type is 'builtins.dict[Any, Any]' +[env DJANGO_SETTINGS_MODULE=mysettings] +[file mysettings.py] +SECRET_KEY = 112233 +ROOT_DIR = '/etc' +NUMBERS = ['one', 'two'] +DICT = {} # type: ignore + +from django.utils.functional import LazyObject + +OBJ = LazyObject() + +[CASE test_settings_could_be_defined_in_different_module_and_imported_with_star] +from django.conf import settings + +reveal_type(settings.BASE) # E: Revealed type is 'builtins.int' +reveal_type(settings.NESTED) # E: Revealed type is 'builtins.str' +[env DJANGO_SETTINGS_MODULE=mysettings] +[file mysettings.py] +from base import * +NESTED = '1122' +[file base.py] +BASE = 1 diff --git a/test/test-data/postgres-fields.test b/test/test-data/postgres-fields.test new file mode 100644 index 0000000..b2aeeee --- /dev/null +++ b/test/test-data/postgres-fields.test @@ -0,0 +1,21 @@ +[CASE array_field_descriptor_access] +from django.db import models +from django.contrib.postgres.fields import ArrayField + +class User(models.Model): + array = ArrayField(base_field=models.Field()) + +user = User() +reveal_type(user.array) # E: Revealed type is 'builtins.list[Any]' + +[CASE array_field_base_field_parsed_into_generic_typevar] +from django.db import models +from django.contrib.postgres.fields import ArrayField + +class User(models.Model): + members = ArrayField(base_field=models.IntegerField()) + members_as_text = ArrayField(base_field=models.CharField(max_length=255)) + +user = User() +reveal_type(user.members) # E: Revealed type is 'builtins.list[builtins.int*]' +reveal_type(user.members_as_text) # E: Revealed type is 'builtins.list[builtins.str*]'