diff --git a/.gitignore b/.gitignore --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ *.sw? .tox version.txt +.mypy_cache/ diff --git a/mypy.ini b/mypy.ini new file mode 100644 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,18 @@ +[mypy] +namespace_packages = True +warn_unused_ignores = True + + +# 3rd party libraries without stubs (yet) + +[mypy-pkg_resources.*] +ignore_missing_imports = True + +[mypy-pyblake2.*] +ignore_missing_imports = True + +[mypy-pytest.*] +ignore_missing_imports = True + +# [mypy-add_your_lib_here.*] +# ignore_missing_imports = True diff --git a/swh/__init__.py b/swh/__init__.py --- a/swh/__init__.py +++ b/swh/__init__.py @@ -1 +1,4 @@ -__path__ = __import__('pkgutil').extend_path(__path__, __name__) +from typing import Iterable + +__path__ = __import__('pkgutil').extend_path(__path__, + __name__) # type: Iterable[str] diff --git a/swh/model/from_disk.py b/swh/model/from_disk.py --- a/swh/model/from_disk.py +++ b/swh/model/from_disk.py @@ -7,6 +7,8 @@ import os import stat +from typing import List + from .hashutil import MultiHash, HASH_BLOCK_SIZE from .merkle import MerkleLeaf, MerkleNode from .identifiers import ( @@ -66,7 +68,7 @@ computation. """ - __slots__ = [] + __slots__ = [] # type: List[str] type = 'content' @classmethod diff --git a/swh/model/hashutil.py b/swh/model/hashutil.py --- a/swh/model/hashutil.py +++ b/swh/model/hashutil.py @@ -57,6 +57,7 @@ import os from io import BytesIO +from typing import Callable, Dict ALGORITHMS = set(['sha1', 'sha256', 'sha1_git', 'blake2s256', 'blake2b512']) """Hashing algorithms supported by this module""" @@ -70,7 +71,7 @@ HASH_BLOCK_SIZE = 32768 """Block size for streaming hash computations made in this module""" -_blake2_hash_cache = {} +_blake2_hash_cache = {} # type: Dict[str, Callable] class MultiHash: diff --git a/swh/model/identifiers.py b/swh/model/identifiers.py --- a/swh/model/identifiers.py +++ b/swh/model/identifiers.py @@ -7,8 +7,8 @@ import datetime import hashlib -from collections import namedtuple from functools import lru_cache +from typing import Any, Dict, NamedTuple from .exceptions import ValidationError from .fields.hashes import validate_sha1 @@ -25,8 +25,6 @@ PID_NAMESPACE = 'swh' PID_VERSION = 1 PID_TYPES = ['ori', 'snp', 'rel', 'rev', 'dir', 'cnt'] -PID_KEYS = ['namespace', 'scheme_version', 'object_type', 'object_id', - 'metadata'] PID_SEP = ':' PID_CTXT_SEP = ';' @@ -640,7 +638,14 @@ } -class PersistentId(namedtuple('PersistentId', PID_KEYS)): +class PersistentId(NamedTuple( + 'PersistentId', [ + ('namespace', str), + ('scheme_version', int), + ('object_type', str), + ('object_id', str), + ('metadata', Dict[str, Any]), + ])): """ Named tuple holding the relevant info associated to a Software Heritage persistent identifier. diff --git a/swh/model/merkle.py b/swh/model/merkle.py --- a/swh/model/merkle.py +++ b/swh/model/merkle.py @@ -8,6 +8,8 @@ import abc import collections +from typing import List, Optional + def deep_update(left, right): """Recursively update the left mapping with deeply nested values from the right @@ -108,7 +110,7 @@ """ __slots__ = ['parents', 'data', '__hash', 'collected'] - type = None + type = None # type: Optional[str] """Type of the current node (used as a classifier for :func:`collect`)""" def __init__(self, data=None): @@ -270,7 +272,7 @@ A Merkle leaf is simply a Merkle node with children disabled. """ - __slots__ = [] + __slots__ = [] # type: List[str] def __setitem__(self, name, child): raise ValueError('%s is a leaf' % self.__class__.__name__) diff --git a/swh/model/py.typed b/swh/model/py.typed new file mode 100644 --- /dev/null +++ b/swh/model/py.typed @@ -0,0 +1 @@ +# Marker file for PEP 561. diff --git a/swh/model/tests/test_from_disk.py b/swh/model/tests/test_from_disk.py --- a/swh/model/tests/test_from_disk.py +++ b/swh/model/tests/test_from_disk.py @@ -4,11 +4,12 @@ # See top-level LICENSE file for more information import os +import pytest import tarfile import tempfile import unittest -import pytest +from typing import ClassVar, Optional from swh.model import from_disk from swh.model.from_disk import Content, DentryPerms, Directory @@ -48,7 +49,7 @@ class DataMixin: - maxDiff = None + maxDiff = None # type: ClassVar[Optional[int]] def setUp(self): self.tmpdir = tempfile.TemporaryDirectory( diff --git a/tox.ini b/tox.ini --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=flake8,py3 +envlist=flake8,py3,mypy [testenv:py3] deps = @@ -14,3 +14,12 @@ flake8 commands = {envpython} -m flake8 + +[testenv:mypy] +skip_install = true +deps = + .[testing] + mypy + django-stubs +commands = + mypy swh