Browse Source

fixed type hints; added mypy to pipeline

Fabian Peter Hammerle 2 months ago
parent
commit
40da0b9972

+ 1 - 0
.github/workflows/python.yml

@@ -107,6 +107,7 @@ jobs:
     # > tests/resources/__init__.py:1:0: F0010: error while code parsing: Unable to load file tests/resources/__init__.py:
     # > [Errno 2] No such file or directory: 'tests/resources/__init__.py' (parse-error)
     - run: pipenv run pylint --disable=parse-error tests/*
+    - run: pipenv run mypy "$(cat *.egg-info/top_level.txt)" tests
     # >=1.9.0 to detect branch name
     # https://github.com/coveralls-clients/coveralls-python/pull/207
     # https://github.com/coverallsapp/github-action/issues/4#issuecomment-547036866

+ 2 - 0
CHANGELOG.md

@@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
 ## [Unreleased]
+### Fixed
+- return type hint of `parse_version_string()` & `VolumeFile.find()`
 
 ## [2.1.0] - 2020-07-11
 ### Added

+ 2 - 1
Pipfile

@@ -6,13 +6,14 @@ name = "pypi"
 [packages]
 freesurfer-volume-reader = {editable = true, path = "."}
 
-# python3.5
+# python3.5 compatibility
 numpy = "<1.19.0"
 
 [dev-packages]
 # black requires python>=3.6
 # https://github.com/psf/black/commit/e74117f172e29e8a980e2c9de929ad50d3769150#diff-2eeaed663bd0d25b7e608891384b7298R51
 black = {version = "==20.8b1", markers = "python_version >= '3.6'"}
+mypy = "*"
 pylint = "*"
 pylint-import-requirements = "*"
 #pytest = "*"

+ 29 - 1
Pipfile.lock

@@ -1,7 +1,7 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "715a88fe9437a09761228ab77f85e7b2a5b337d22aecfa16d502f7b5855c5e9d"
+            "sha256": "305e38816b3fcedb62c7360c25db55a7f71274e458aeaed3c47d0f1f70b8a0b9"
         },
         "pipfile-spec": 6,
         "requires": {
@@ -240,6 +240,34 @@
             ],
             "version": "==0.6.1"
         },
+        "mypy": {
+            "hashes": [
+                "sha256:0d0a87c0e7e3a9becdfbe936c981d32e5ee0ccda3e0f07e1ef2c3d1a817cf73e",
+                "sha256:25adde9b862f8f9aac9d2d11971f226bd4c8fbaa89fb76bdadb267ef22d10064",
+                "sha256:28fb5479c494b1bab244620685e2eb3c3f988d71fd5d64cc753195e8ed53df7c",
+                "sha256:2f9b3407c58347a452fc0736861593e105139b905cca7d097e413453a1d650b4",
+                "sha256:33f159443db0829d16f0a8d83d94df3109bb6dd801975fe86bacb9bf71628e97",
+                "sha256:3f2aca7f68580dc2508289c729bd49ee929a436208d2b2b6aab15745a70a57df",
+                "sha256:499c798053cdebcaa916eef8cd733e5584b5909f789de856b482cd7d069bdad8",
+                "sha256:4eec37370483331d13514c3f55f446fc5248d6373e7029a29ecb7b7494851e7a",
+                "sha256:552a815579aa1e995f39fd05dde6cd378e191b063f031f2acfe73ce9fb7f9e56",
+                "sha256:5873888fff1c7cf5b71efbe80e0e73153fe9212fafdf8e44adfe4c20ec9f82d7",
+                "sha256:61a3d5b97955422964be6b3baf05ff2ce7f26f52c85dd88db11d5e03e146a3a6",
+                "sha256:674e822aa665b9fd75130c6c5f5ed9564a38c6cea6a6432ce47eafb68ee578c5",
+                "sha256:7ce3175801d0ae5fdfa79b4f0cfed08807af4d075b402b7e294e6aa72af9aa2a",
+                "sha256:9743c91088d396c1a5a3c9978354b61b0382b4e3c440ce83cf77994a43e8c521",
+                "sha256:9f94aac67a2045ec719ffe6111df543bac7874cee01f41928f6969756e030564",
+                "sha256:a26f8ec704e5a7423c8824d425086705e381b4f1dfdef6e3a1edab7ba174ec49",
+                "sha256:abf7e0c3cf117c44d9285cc6128856106183938c68fd4944763003decdcfeb66",
+                "sha256:b09669bcda124e83708f34a94606e01b614fa71931d356c1f1a5297ba11f110a",
+                "sha256:cd07039aa5df222037005b08fbbfd69b3ab0b0bd7a07d7906de75ae52c4e3119",
+                "sha256:d23e0ea196702d918b60c8288561e722bf437d82cb7ef2edcd98cfa38905d506",
+                "sha256:d65cc1df038ef55a99e617431f0553cd77763869eebdf9042403e16089fe746c",
+                "sha256:d7da2e1d5f558c37d6e8c1246f1aec1e7349e4913d8fb3cb289a35de573fe2eb"
+            ],
+            "index": "pypi",
+            "version": "==0.812"
+        },
         "mypy-extensions": {
             "hashes": [
                 "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",

+ 10 - 4
freesurfer_volume_reader/__init__.py

@@ -28,7 +28,9 @@ except ImportError:  # pragma: no cover
     __version__ = None
 
 
-def parse_version_string(version_string: str) -> typing.Tuple[typing.Union[int, str]]:
+def parse_version_string(
+    version_string: str,
+) -> typing.Tuple[typing.Union[int, str], ...]:
     return tuple(int(p) if p.isdigit() else p for p in version_string.split("."))
 
 
@@ -38,7 +40,11 @@ def remove_group_names_from_regex(regex_pattern: str) -> str:
 
 class VolumeFile(metaclass=abc.ABCMeta):
 
-    FILENAME_REGEX = NotImplemented
+    FILENAME_REGEX = NotImplemented  # type: typing.Pattern[str]
+
+    @abc.abstractmethod
+    def __init__(self, path: str) -> None:
+        pass
 
     @property
     @abc.abstractmethod
@@ -48,8 +54,8 @@ class VolumeFile(metaclass=abc.ABCMeta):
     @classmethod
     def find(
         cls, root_dir_path: str, filename_regex: typing.Optional[typing.Pattern] = None
-    ) -> typing.Iterator["SubfieldVolumeFile"]:
-        if not filename_regex:
+    ) -> typing.Iterator["VolumeFile"]:
+        if filename_regex is None:
             filename_regex = cls.FILENAME_REGEX
         for dirpath, _, filenames in os.walk(root_dir_path):
             for filename in filter(filename_regex.search, filenames):

+ 2 - 0
freesurfer_volume_reader/ashs.py

@@ -36,6 +36,7 @@ class IntracranialVolumeFile(freesurfer_volume_reader.VolumeFile):
         filename_match = self.FILENAME_REGEX.match(os.path.basename(path))
         assert filename_match, self._absolute_path
         self.subject = filename_match.groupdict()["s"]
+        super().__init__(path=path)
 
     @property
     def absolute_path(self):
@@ -72,6 +73,7 @@ class HippocampalSubfieldsVolumeFile(freesurfer_volume_reader.SubfieldVolumeFile
         self.subject = filename_groups["s"]
         self.hemisphere = filename_groups["h"]
         self.correction = filename_groups["c"]
+        super().__init__(path=path)
 
     @property
     def absolute_path(self):

+ 1 - 0
freesurfer_volume_reader/freesurfer.py

@@ -44,6 +44,7 @@ class HippocampalSubfieldsVolumeFile(freesurfer_volume_reader.SubfieldVolumeFile
         self.hemisphere = self.FILENAME_HEMISPHERE_PREFIX_MAP[filename_groups["h"]]
         self.t1_input = filename_groups["T1"] is not None
         self.analysis_id = filename_groups["analysis_id"]
+        super().__init__(path=path)
 
     @property
     def absolute_path(self):

+ 3 - 0
mypy.ini

@@ -0,0 +1,3 @@
+[mypy]
+[mypy-pandas.*]
+ignore_missing_imports = True

+ 3 - 2
setup.py

@@ -11,8 +11,9 @@ setuptools.setup(
     use_scm_version={
         "write_to": os.path.join("freesurfer_volume_reader", "version.py"),
         # `version` triggers pylint C0103
-        "write_to_template": "# pylint: disable=missing-module-docstring\n"
-        + "__version__ = '{version}'\n",
+        # 2 newlines after import & 2 spaces before # for black
+        "write_to_template": "# pylint: disable=missing-module-docstring\nimport typing\n\n"
+        + '__version__ = "{version}"  # type: typing.Optional[str]\n',
     },
     description="Python script & library to read hippocampal subfield volumes"
     "computed by Freesurfer & ASHS",

+ 8 - 2
tests/init_test.py

@@ -56,13 +56,16 @@ class DummyVolumeFile(VolumeFile):
 
     # pylint: disable=useless-super-delegation
 
+    def __init__(self, path: str) -> None:
+        super().__init__(path=path)
+
     @property
     def absolute_path(self):
         return super().absolute_path
 
 
 def test_volume_file_abstractmethod():
-    volume_file = DummyVolumeFile()
+    volume_file = DummyVolumeFile(path="dummy")
     with pytest.raises(NotImplementedError):
         assert volume_file.absolute_path
 
@@ -71,6 +74,9 @@ class DummySubfieldVolumeFile(SubfieldVolumeFile):
 
     # pylint: disable=useless-super-delegation
 
+    def __init__(self, path: str) -> None:
+        super().__init__(path=path)
+
     @property
     def absolute_path(self):
         return super().absolute_path
@@ -83,7 +89,7 @@ class DummySubfieldVolumeFile(SubfieldVolumeFile):
 
 
 def test_subfield_volume_file_abstractmethod():
-    volume_file = DummySubfieldVolumeFile()
+    volume_file = DummySubfieldVolumeFile(path="subfield-dummy")
     with pytest.raises(NotImplementedError):
         assert volume_file.absolute_path
     with pytest.raises(NotImplementedError):