diff --git a/model_utils/__init__.py b/model_utils/__init__.py index cf1b22c..14b50cc 100644 --- a/model_utils/__init__.py +++ b/model_utils/__init__.py @@ -7,6 +7,6 @@ try: __version__ = importlib.metadata.version('django-model-utils') except importlib.metadata.PackageNotFoundError: # pragma: no cover # package is not installed - __version__ = None + __version__ = None # type: ignore[assignment] __all__ = ("Choices", "FieldTracker", "ModelTracker") diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..918bc71 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,16 @@ +[mypy] +implicit_reexport=False +pretty=True +show_error_codes=True +strict_equality=True +warn_redundant_casts=True +warn_unreachable=True +warn_unused_ignores=True + +mypy_path = $MYPY_CONFIG_FILE_DIR + +plugins = + mypy_django_plugin.main + +[mypy.plugins.django-stubs] +django_settings_module = "tests.settings" diff --git a/requirements-mypy.txt b/requirements-mypy.txt new file mode 100644 index 0000000..c0422a7 --- /dev/null +++ b/requirements-mypy.txt @@ -0,0 +1,2 @@ +mypy==1.9.0 +django-stubs==4.2.7 diff --git a/tests/models.py b/tests/models.py index 9164c10..44f7590 100644 --- a/tests/models.py +++ b/tests/models.py @@ -1,3 +1,7 @@ +from __future__ import annotations + +from typing import ClassVar + from django.db import models from django.db.models import Manager from django.db.models.query_utils import DeferredAttribute @@ -32,7 +36,7 @@ class InheritanceManagerTestParent(models.Model): related_self = models.OneToOneField( "self", related_name="imtests_self", null=True, on_delete=models.CASCADE) - objects = InheritanceManager() + objects: ClassVar[InheritanceManager[InheritanceManagerTestParent]] = InheritanceManager() def __str__(self): return "{}({})".format( @@ -44,7 +48,7 @@ class InheritanceManagerTestParent(models.Model): class InheritanceManagerTestChild1(InheritanceManagerTestParent): non_related_field_using_descriptor_2 = models.FileField(upload_to="test") normal_field_2 = models.TextField() - objects = InheritanceManager() + objects: ClassVar[InheritanceManager[InheritanceManagerTestParent]] = InheritanceManager() class InheritanceManagerTestGrandChild1(InheritanceManagerTestChild1): @@ -172,8 +176,8 @@ class Post(models.Model): order = models.IntegerField() objects = models.Manager() - public = QueryManager(published=True) - public_confirmed = QueryManager( + public: ClassVar[QueryManager[Post]] = QueryManager(published=True) + public_confirmed: ClassVar[QueryManager[Post]] = QueryManager( models.Q(published=True) & models.Q(confirmed=True)) public_reversed = QueryManager(published=True).order_by("-order") @@ -194,7 +198,7 @@ class SplitFieldAbstractParent(models.Model): class AbstractTracked(models.Model): - number = 1 + number: models.IntegerField class Meta: abstract = True @@ -340,13 +344,13 @@ class SoftDeletable(SoftDeletableModel): """ name = models.CharField(max_length=20) - all_objects = models.Manager() + all_objects: ClassVar[Manager[SoftDeletable]] = models.Manager() class CustomSoftDelete(SoftDeletableModel): is_read = models.BooleanField(default=False) - objects = CustomSoftDeleteManager() + objects: ClassVar[CustomSoftDeleteManager[SoftDeletableModel]] = CustomSoftDeleteManager() class StringyDescriptor: @@ -390,7 +394,7 @@ class ModelWithCustomDescriptor(models.Model): class BoxJoinModel(models.Model): name = models.CharField(max_length=32) - objects = JoinManager() + objects: ClassVar[JoinManager[BoxJoinModel]] = JoinManager() class JoinItemForeignKey(models.Model): @@ -400,7 +404,7 @@ class JoinItemForeignKey(models.Model): null=True, on_delete=models.CASCADE ) - objects = JoinManager() + objects: ClassVar[JoinManager[JoinItemForeignKey]] = JoinManager() class CustomUUIDModel(UUIDModel): diff --git a/tests/settings.py b/tests/settings.py index c4de921..4593b4a 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -19,7 +19,7 @@ else: "USER": os.environ.get("POSTGRES_USER", 'postgres'), "PASSWORD": os.environ.get("POSTGRES_PASSWORD", ""), "HOST": os.environ.get("POSTGRES_HOST", "localhost"), - "PORT": os.environ.get("POSTGRES_PORT", 5432) + "PORT": os.environ.get("POSTGRES_PORT", "5432") }, } SECRET_KEY = 'dummy' diff --git a/tests/test_fields/test_field_tracker.py b/tests/test_fields/test_field_tracker.py index b7da2f9..42bd66b 100644 --- a/tests/test_fields/test_field_tracker.py +++ b/tests/test_fields/test_field_tracker.py @@ -1,7 +1,10 @@ +from __future__ import annotations + from unittest import skip from django.core.cache import cache from django.core.exceptions import FieldError +from django.db import models from django.db.models.fields.files import FieldFile from django.test import TestCase @@ -73,7 +76,7 @@ class FieldTrackerCommonTests: class FieldTrackerTests(FieldTrackerTestCase, FieldTrackerCommonTests): - tracked_class = Tracked + tracked_class: type[models.Model] = Tracked def setUp(self): self.instance = self.tracked_class() @@ -280,7 +283,7 @@ class FieldTrackerMultipleInstancesTests(TestCase): class FieldTrackedModelCustomTests(FieldTrackerTestCase, FieldTrackerCommonTests): - tracked_class = TrackedNotDefault + tracked_class: type[models.Model] = TrackedNotDefault def setUp(self): self.instance = self.tracked_class() @@ -411,7 +414,7 @@ class FieldTrackedModelAttributeTests(FieldTrackerTestCase): class FieldTrackedModelMultiTests(FieldTrackerTestCase, FieldTrackerCommonTests): - tracked_class = TrackedMultiple + tracked_class: type[models.Model] = TrackedMultiple def setUp(self): self.instance = self.tracked_class() @@ -502,8 +505,8 @@ class FieldTrackedModelMultiTests(FieldTrackerTestCase, class FieldTrackerForeignKeyTests(FieldTrackerTestCase): - fk_class = Tracked - tracked_class = TrackedFK + fk_class: type[models.Model] = Tracked + tracked_class: type[models.Model] = TrackedFK def setUp(self): self.old_fk = self.fk_class.objects.create(number=8) @@ -729,7 +732,7 @@ class FieldTrackerFileFieldTests(FieldTrackerTestCase): class ModelTrackerTests(FieldTrackerTests): - tracked_class = ModelTracked + tracked_class: type[models.Model] = ModelTracked def test_cache_compatible(self): cache.set('key', self.instance) diff --git a/tox.ini b/tox.ini index fcf087a..f77e732 100644 --- a/tox.ini +++ b/tox.ini @@ -8,11 +8,12 @@ envlist = py{310,311,312}-dj{main} flake8 isort + mypy [gh-actions] python = 3.7: py37 - 3.8: py38, flake8, isort + 3.8: py38, flake8, isort, mypy 3.9: py39 3.10: py310 3.11: py311 @@ -63,3 +64,13 @@ deps = isort commands = isort model_utils tests setup.py --check-only --diff skip_install = True + +[testenv:mypy] +basepython = python3.8 +deps = + time-machine==2.8.2 + -r requirements-mypy.txt +set_env = + SQLITE=1 +commands = + mypy model_utils tests