From 491dcc599dbf684227cd5d241fb945b1315118b0 Mon Sep 17 00:00:00 2001
From: Stefano Zacchiroli <zack@upsilon.cc>
Date: Thu, 12 Sep 2019 14:46:05 +0200
Subject: [PATCH] typing: minimal changes to make a no-op mypy run pass

---
 .gitignore                        |  1 +
 mypy.ini                          | 18 ++++++++++++++++++
 swh/__init__.py                   |  5 ++++-
 swh/model/from_disk.py            |  4 +++-
 swh/model/hashutil.py             |  3 ++-
 swh/model/identifiers.py          | 13 +++++++++----
 swh/model/merkle.py               |  6 ++++--
 swh/model/py.typed                |  1 +
 swh/model/tests/test_from_disk.py |  5 +++--
 tox.ini                           | 11 ++++++++++-
 10 files changed, 55 insertions(+), 12 deletions(-)
 create mode 100644 mypy.ini
 create mode 100644 swh/model/py.typed

diff --git a/.gitignore b/.gitignore
index 0bafd006..68708728 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,4 @@ __pycache__
 *.sw?
 .tox
 version.txt
+.mypy_cache/
diff --git a/mypy.ini b/mypy.ini
new file mode 100644
index 00000000..2c020e0b
--- /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
index 69e3be50..de9df068 100644
--- 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
index bfd7c7cd..64a6ef7a 100644
--- a/swh/model/from_disk.py
+++ b/swh/model/from_disk.py
@@ -7,6 +7,8 @@ import enum
 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 @@ class Content(MerkleLeaf):
     computation.
 
     """
-    __slots__ = []
+    __slots__ = []  # type: List[str]
     type = 'content'
 
     @classmethod
diff --git a/swh/model/hashutil.py b/swh/model/hashutil.py
index de85857e..f045fb01 100644
--- a/swh/model/hashutil.py
+++ b/swh/model/hashutil.py
@@ -57,6 +57,7 @@ import hashlib
 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 @@ Subset of :const:`ALGORITHMS`.
 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
index 62e031b0..0254a89c 100644
--- a/swh/model/identifiers.py
+++ b/swh/model/identifiers.py
@@ -7,8 +7,8 @@ import binascii
 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 @@ CONTENT = 'content'
 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 @@ _object_type_map = {
 }
 
 
-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
index c75cc2c2..15c5db62 100644
--- 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 @@ class MerkleNode(dict, metaclass=abc.ABCMeta):
     """
     __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 @@ class MerkleLeaf(MerkleNode):
 
     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
index 00000000..1242d432
--- /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
index 30f543d6..7b21d20e 100644
--- 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 ModeToPerms(unittest.TestCase):
 
 
 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
index 0fb07c66..39200eda 100644
--- 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 @@ deps =
   flake8
 commands =
   {envpython} -m flake8
+
+[testenv:mypy]
+skip_install = true
+deps =
+  .[testing]
+  mypy
+  django-stubs
+commands =
+  mypy swh
-- 
GitLab