From bdbe0a72d72511b757c5d8a9a1f3692dd30d8cec Mon Sep 17 00:00:00 2001 From: Jonathan Green Date: Thu, 15 Feb 2024 06:01:55 -0400 Subject: [PATCH] Add mypy configuration (#11) --- poetry.lock | 158 +++++++++++++++++- pyproject.toml | 44 +++++ tests/admin/test_library_admin_views.py | 2 +- tests/admin/test_library_card_admin_views.py | 2 +- tests/base.py | 4 +- virtual_library_card/api/dynamic_links.py | 4 +- virtual_library_card/sender.py | 2 +- virtuallibrarycard/business_rules/library.py | 16 +- .../business_rules/library_card.py | 6 +- virtuallibrarycard/forms/forms.py | 2 +- 10 files changed, 220 insertions(+), 20 deletions(-) diff --git a/poetry.lock b/poetry.lock index 48f3da6..90fab2e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "asgiref" @@ -564,6 +564,44 @@ libcloud = ["apache-libcloud"] s3 = ["boto3 (>=1.4.4)"] sftp = ["paramiko (>=1.15)"] +[[package]] +name = "django-stubs" +version = "4.2.7" +description = "Mypy stubs for Django" +optional = false +python-versions = ">=3.8" +files = [ + {file = "django-stubs-4.2.7.tar.gz", hash = "sha256:8ccd2ff4ee5adf22b9e3b7b1a516d2e1c2191e9d94e672c35cc2bc3dd61e0f6b"}, + {file = "django_stubs-4.2.7-py3-none-any.whl", hash = "sha256:4cf4de258fa71adc6f2799e983091b9d46cfc67c6eebc68fe111218c9a62b3b8"}, +] + +[package.dependencies] +django = "*" +django-stubs-ext = ">=4.2.7" +mypy = {version = ">=1.7.0,<1.8.0", optional = true, markers = "extra == \"compatible-mypy\""} +tomli = {version = "*", markers = "python_version < \"3.11\""} +types-pytz = "*" +types-PyYAML = "*" +typing-extensions = "*" + +[package.extras] +compatible-mypy = ["mypy (>=1.7.0,<1.8.0)"] + +[[package]] +name = "django-stubs-ext" +version = "4.2.7" +description = "Monkey-patching and extensions for django-stubs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "django-stubs-ext-4.2.7.tar.gz", hash = "sha256:519342ac0849cda1559746c9a563f03ff99f636b0ebe7c14b75e816a00dfddc3"}, + {file = "django_stubs_ext-4.2.7-py3-none-any.whl", hash = "sha256:45a5d102417a412e3606e3c358adb4744988a92b7b58ccf3fd64bddd5d04d14c"}, +] + +[package.dependencies] +django = "*" +typing-extensions = "*" + [[package]] name = "djangorestframework" version = "3.14.0" @@ -807,6 +845,64 @@ files = [ {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, ] +[[package]] +name = "mypy" +version = "1.7.1" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12cce78e329838d70a204293e7b29af9faa3ab14899aec397798a4b41be7f340"}, + {file = "mypy-1.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1484b8fa2c10adf4474f016e09d7a159602f3239075c7bf9f1627f5acf40ad49"}, + {file = "mypy-1.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31902408f4bf54108bbfb2e35369877c01c95adc6192958684473658c322c8a5"}, + {file = "mypy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f2c2521a8e4d6d769e3234350ba7b65ff5d527137cdcde13ff4d99114b0c8e7d"}, + {file = "mypy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:fcd2572dd4519e8a6642b733cd3a8cfc1ef94bafd0c1ceed9c94fe736cb65b6a"}, + {file = "mypy-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b901927f16224d0d143b925ce9a4e6b3a758010673eeded9b748f250cf4e8f7"}, + {file = "mypy-1.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2f7f6985d05a4e3ce8255396df363046c28bea790e40617654e91ed580ca7c51"}, + {file = "mypy-1.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:944bdc21ebd620eafefc090cdf83158393ec2b1391578359776c00de00e8907a"}, + {file = "mypy-1.7.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9c7ac372232c928fff0645d85f273a726970c014749b924ce5710d7d89763a28"}, + {file = "mypy-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:f6efc9bd72258f89a3816e3a98c09d36f079c223aa345c659622f056b760ab42"}, + {file = "mypy-1.7.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6dbdec441c60699288adf051f51a5d512b0d818526d1dcfff5a41f8cd8b4aaf1"}, + {file = "mypy-1.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4fc3d14ee80cd22367caaaf6e014494415bf440980a3045bf5045b525680ac33"}, + {file = "mypy-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c6e4464ed5f01dc44dc9821caf67b60a4e5c3b04278286a85c067010653a0eb"}, + {file = "mypy-1.7.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d9b338c19fa2412f76e17525c1b4f2c687a55b156320acb588df79f2e6fa9fea"}, + {file = "mypy-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:204e0d6de5fd2317394a4eff62065614c4892d5a4d1a7ee55b765d7a3d9e3f82"}, + {file = "mypy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:84860e06ba363d9c0eeabd45ac0fde4b903ad7aa4f93cd8b648385a888e23200"}, + {file = "mypy-1.7.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8c5091ebd294f7628eb25ea554852a52058ac81472c921150e3a61cdd68f75a7"}, + {file = "mypy-1.7.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40716d1f821b89838589e5b3106ebbc23636ffdef5abc31f7cd0266db936067e"}, + {file = "mypy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cf3f0c5ac72139797953bd50bc6c95ac13075e62dbfcc923571180bebb662e9"}, + {file = "mypy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:78e25b2fd6cbb55ddfb8058417df193f0129cad5f4ee75d1502248e588d9e0d7"}, + {file = "mypy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:75c4d2a6effd015786c87774e04331b6da863fc3fc4e8adfc3b40aa55ab516fe"}, + {file = "mypy-1.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2643d145af5292ee956aa0a83c2ce1038a3bdb26e033dadeb2f7066fb0c9abce"}, + {file = "mypy-1.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75aa828610b67462ffe3057d4d8a4112105ed211596b750b53cbfe182f44777a"}, + {file = "mypy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ee5d62d28b854eb61889cde4e1dbc10fbaa5560cb39780c3995f6737f7e82120"}, + {file = "mypy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:72cf32ce7dd3562373f78bd751f73c96cfb441de147cc2448a92c1a308bd0ca6"}, + {file = "mypy-1.7.1-py3-none-any.whl", hash = "sha256:f7c5d642db47376a0cc130f0de6d055056e010debdaf0707cd2b0fc7e7ef30ea"}, + {file = "mypy-1.7.1.tar.gz", hash = "sha256:fcb6d9afb1b6208b4c712af0dafdc650f518836065df0d4fb1d800f5d6773db2"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.1.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + [[package]] name = "nodeenv" version = "1.6.0" @@ -1553,6 +1649,64 @@ tox = ">=4,<5" [package.extras] testing = ["black", "devpi-process", "flake8 (>=6,<7)", "mypy", "pytest (>=7,<8)", "pytest-cov (>=3,<4)", "pytest-mock (>=3,<4)", "pytest-randomly (>=3)"] +[[package]] +name = "types-pytz" +version = "2024.1.0.20240203" +description = "Typing stubs for pytz" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-pytz-2024.1.0.20240203.tar.gz", hash = "sha256:c93751ee20dfc6e054a0148f8f5227b9a00b79c90a4d3c9f464711a73179c89e"}, + {file = "types_pytz-2024.1.0.20240203-py3-none-any.whl", hash = "sha256:9679eef0365db3af91ef7722c199dbb75ee5c1b67e3c4dd7bfbeb1b8a71c21a3"}, +] + +[[package]] +name = "types-pyyaml" +version = "6.0.12.12" +description = "Typing stubs for PyYAML" +optional = false +python-versions = "*" +files = [ + {file = "types-PyYAML-6.0.12.12.tar.gz", hash = "sha256:334373d392fde0fdf95af5c3f1661885fa10c52167b14593eb856289e1855062"}, + {file = "types_PyYAML-6.0.12.12-py3-none-any.whl", hash = "sha256:c05bc6c158facb0676674b7f11fe3960db4f389718e19e62bd2b84d6205cfd24"}, +] + +[[package]] +name = "types-requests" +version = "2.31.0.6" +description = "Typing stubs for requests" +optional = false +python-versions = ">=3.7" +files = [ + {file = "types-requests-2.31.0.6.tar.gz", hash = "sha256:cd74ce3b53c461f1228a9b783929ac73a666658f223e28ed29753771477b3bd0"}, + {file = "types_requests-2.31.0.6-py3-none-any.whl", hash = "sha256:a2db9cb228a81da8348b49ad6db3f5519452dd20a9c1e1a868c83c5fe88fd1a9"}, +] + +[package.dependencies] +types-urllib3 = "*" + +[[package]] +name = "types-urllib3" +version = "1.26.25.14" +description = "Typing stubs for urllib3" +optional = false +python-versions = "*" +files = [ + {file = "types-urllib3-1.26.25.14.tar.gz", hash = "sha256:229b7f577c951b8c1b92c1bc2b2fdb0b49847bd2af6d1cc2a2e3dd340f3bda8f"}, + {file = "types_urllib3-1.26.25.14-py3-none-any.whl", hash = "sha256:9683bbb7fb72e32bfe9d2be6e04875fbe1b3eeec3cbb4ea231435aa7fd6b4f0e"}, +] + +[[package]] +name = "typing-extensions" +version = "4.9.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, +] + [[package]] name = "tzdata" version = "2023.3" @@ -1630,4 +1784,4 @@ test = ["websockets"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<4" -content-hash = "e838b99ae9a886606f2d24bc471ee1646d2a6edbbc5ff55c49317a6f5455090e" +content-hash = "c13b64cdc210a9768111ef4df2df4202b0ba9b8c40d443d4ce53035130fe0bfc" diff --git a/pyproject.toml b/pyproject.toml index 0f71d3e..861d010 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,9 +15,50 @@ branch = true relative_files = true source = ["virtual_library_card", "virtuallibrarycard"] +[tool.django-stubs] +django_settings_module = "virtual_library_card.settings.dev" + [tool.isort] profile = "black" +[tool.mypy] +# TODO: Enable the the check_untyped_defs option +# This will get rid of the warnings that we get when running mypy +# > note: By default the bodies of untyped functions are not checked +# However this currently causes a number of errors to surface that will +# need to be cleaned up before we can enable the option. +# check_untyped_defs = true +# When we enable this option, we should remove this disable. Its just here +# to silence the noise in the mypy output for now, so its easier to see when +# there are errors in the output. +disable_error_code = "annotation-unchecked" +files = ["."] +plugins = ["mypy_django_plugin.main"] +warn_redundant_casts = true +warn_unreachable = true +warn_unused_configs = true +warn_unused_ignores = true + +[[tool.mypy.overrides]] +# This override silences errors for modules we import that don't currently +# have type hints, or type stubs that cover them. We should go through this +# list periodically and remove modules that have since added type hints. +ignore_missing_imports = true +module = [ + "better_profanity.*", + "crispy_forms.*", + "dal.*", + "datedelta.*", + "localflavor.*", + "parameterized.*", + "rest_framework.*", + "uwsgi.*", + # This is ignored because the file is created when building a container + # so it typically doesn't exist when running mypy, but since it only + # contains a couple version strings it can be safely ignored + "virtual_library_card._version", +] + [tool.poetry] authors = ["The Palace Project "] description = "Virtual Library Card Creator" @@ -77,10 +118,13 @@ tox-docker = "^4.0" tox-gh-actions = "^3.0" [tool.poetry.group.dev.dependencies] +django-stubs = {version = "^4.2.7", extras = ["compatible-mypy"]} +mypy = "^1.7.0" parameterized = "^0.9" pytest = "^8.0.0" pytest-cov = "^4.0.0" pytest-django = "^4.5.2" +types-requests = "^2.28.11" [tool.pytest.ini_options] addopts = [ diff --git a/tests/admin/test_library_admin_views.py b/tests/admin/test_library_admin_views.py index 4c90987..2a7d186 100644 --- a/tests/admin/test_library_admin_views.py +++ b/tests/admin/test_library_admin_views.py @@ -41,7 +41,7 @@ def _get_library_change_data(self, library, **changes): return data def _add_library_states_data( - self, data: dict, places: list[str] = None, library=None + self, data: dict, places: list[str] | None = None, library=None ): """Helper function for change form data, for the inline library form""" places = places or [] diff --git a/tests/admin/test_library_card_admin_views.py b/tests/admin/test_library_card_admin_views.py index 9f7758a..df4d5c7 100644 --- a/tests/admin/test_library_card_admin_views.py +++ b/tests/admin/test_library_card_admin_views.py @@ -14,7 +14,7 @@ class TestLibraryCardAdminViews(BaseAdminUnitTest): MODEL = LibraryCard MODEL_ADMIN = LibraryCardAdmin - def _get_card_data(self, card: LibraryCard = None, **data): + def _get_card_data(self, card: LibraryCard | None = None, **data): initial = { "number": "", "created": "", diff --git a/tests/base.py b/tests/base.py index 6d61740..e0703c7 100644 --- a/tests/base.py +++ b/tests/base.py @@ -134,7 +134,9 @@ def tearDown(self): self._transaction.__exit__(None, None, None) super().tearDown() - def do_library_card_signup_flow(self, client: Client, library: Library = None): + def do_library_card_signup_flow( + self, client: Client, library: Library | None = None + ): """A common flow which is needed multiple times""" if not library: diff --git a/virtual_library_card/api/dynamic_links.py b/virtual_library_card/api/dynamic_links.py index f9122c4..ddc86a7 100644 --- a/virtual_library_card/api/dynamic_links.py +++ b/virtual_library_card/api/dynamic_links.py @@ -39,8 +39,8 @@ def _request( self, method: Literal["get"] | Literal["post"], path: str, - data: Any = None, - json: dict = None, + data: Any | None = None, + json: dict | None = None, ) -> Any: """Make a REST request to the dynamic links API :param method: The HTTP method diff --git a/virtual_library_card/sender.py b/virtual_library_card/sender.py index dc6e537..96b0407 100644 --- a/virtual_library_card/sender.py +++ b/virtual_library_card/sender.py @@ -26,7 +26,7 @@ def text_whitespaces_to_html(string: str) -> str: def send_user_welcome( library: Library, user: CustomUser, - card_number: str = None, + card_number: str | None = None, ): """Send out a welcome email which has two optional parts - User welcome for a new card diff --git a/virtuallibrarycard/business_rules/library.py b/virtuallibrarycard/business_rules/library.py index ee86722..b99c2af 100644 --- a/virtuallibrarycard/business_rules/library.py +++ b/virtuallibrarycard/business_rules/library.py @@ -6,10 +6,10 @@ class LibraryRules: def validate_user_address_fields( cls, library: Library, - city: str = None, - county: str = None, - state: str = None, - country: str = None, + city: str | None = None, + county: str | None = None, + state: str | None = None, + country: str | None = None, ) -> bool: """Validate whether the given address fields are valid for a user that would signup for a given library - Country, State or City, at least one must be within the list of places of the library @@ -31,10 +31,10 @@ def validate_user_address_fields( def _place_hierarchy_match( cls, place: Place, - city: str = None, - county: str = None, - state: str = None, - country: str = None, + city: str | None = None, + county: str | None = None, + state: str | None = None, + country: str | None = None, ) -> bool: """Test from the current place all the way to the last parent available. All levels of the place hierarchy MUST match even if the value isn't provided in the keyword args. diff --git a/virtuallibrarycard/business_rules/library_card.py b/virtuallibrarycard/business_rules/library_card.py index b541015..95c21c8 100644 --- a/virtuallibrarycard/business_rules/library_card.py +++ b/virtuallibrarycard/business_rules/library_card.py @@ -22,7 +22,7 @@ class LibraryCardRules: @classmethod def new_card( - cls, user: CustomUser, library: Library, number: str = None + cls, user: CustomUser, library: Library, number: str | None = None ) -> tuple[LibraryCard, bool]: """Generate a new card only of a card does not exist for this user and library Also send the welcome email""" @@ -50,7 +50,7 @@ def bulk_upload_csv( cls, library: Library, fileio: IO, - admin_user: CustomUser = None, + admin_user: CustomUser | None = None, _async: bool = False, ) -> LibraryCardBulkUpload: """Bulk upload a CSV of library users with information enough to generate cards @@ -63,7 +63,7 @@ def __init__( self, library: Library, fileio: IO, - admin_user: CustomUser = None, + admin_user: CustomUser | None = None, _async: bool = True, ) -> None: self.library = library diff --git a/virtuallibrarycard/forms/forms.py b/virtuallibrarycard/forms/forms.py index 60ee466..a2a0dd5 100644 --- a/virtuallibrarycard/forms/forms.py +++ b/virtuallibrarycard/forms/forms.py @@ -29,7 +29,7 @@ class Meta: exclude = [] labels = {"user": _("User Email")} - def __init__(self, data: dict = None, *args, **kwargs) -> None: + def __init__(self, data: dict | None = None, *args, **kwargs) -> None: super().__init__(data, *args, **kwargs) # If we have the field and also have a new instance (on create)