diff --git a/PKG-INFO b/PKG-INFO
index 5092500d5f53a013fc6bd73905054de4b00d4af7..cbe1edc28dd9a008db1a0080a770922aa3e37052 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,14 +1,14 @@
 Metadata-Version: 2.1
 Name: swh.model
-Version: 0.0.44
+Version: 0.0.45
 Summary: Software Heritage data model
 Home-page: https://forge.softwareheritage.org/diffusion/DMOD/
 Author: Software Heritage developers
 Author-email: swh-devel@inria.fr
 License: UNKNOWN
+Project-URL: Bug Reports, https://forge.softwareheritage.org/maniphest
 Project-URL: Funding, https://www.softwareheritage.org/donate
 Project-URL: Source, https://forge.softwareheritage.org/source/swh-model
-Project-URL: Bug Reports, https://forge.softwareheritage.org/maniphest
 Description: swh-model
         =========
         
diff --git a/swh.model.egg-info/PKG-INFO b/swh.model.egg-info/PKG-INFO
index 5092500d5f53a013fc6bd73905054de4b00d4af7..cbe1edc28dd9a008db1a0080a770922aa3e37052 100644
--- a/swh.model.egg-info/PKG-INFO
+++ b/swh.model.egg-info/PKG-INFO
@@ -1,14 +1,14 @@
 Metadata-Version: 2.1
 Name: swh.model
-Version: 0.0.44
+Version: 0.0.45
 Summary: Software Heritage data model
 Home-page: https://forge.softwareheritage.org/diffusion/DMOD/
 Author: Software Heritage developers
 Author-email: swh-devel@inria.fr
 License: UNKNOWN
+Project-URL: Bug Reports, https://forge.softwareheritage.org/maniphest
 Project-URL: Funding, https://www.softwareheritage.org/donate
 Project-URL: Source, https://forge.softwareheritage.org/source/swh-model
-Project-URL: Bug Reports, https://forge.softwareheritage.org/maniphest
 Description: swh-model
         =========
         
diff --git a/swh/__init__.py b/swh/__init__.py
index 69e3be50dac40cddced86e9df2f9c2df5a75f004..de9df0686bf3cc8a381e3adf4dd9e12ceecbf000 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 bfd7c7cd026f271be37aecca9d2435b683304403..64a6ef7a824f8da162a7152708caddfaad793861 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 de85857e1294bb82a1e851966878d6887a4b9c7b..f045fb01e48e15320d625d92076b28168c367032 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 62e031b09df10093b7465a8ce32ea6e7bd12c7bf..6a00068145034d088cc3240fb20136df7b227a23 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,17 @@ _object_type_map = {
 }
 
 
-class PersistentId(namedtuple('PersistentId', PID_KEYS)):
+_PersistentId = NamedTuple(
+    'PersistentId', [
+        ('namespace', str),
+        ('scheme_version', int),
+        ('object_type', str),
+        ('object_id', str),
+        ('metadata', Dict[str, Any]),
+    ])
+
+
+class PersistentId(_PersistentId):
     """
     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 c75cc2c2203f771e49e9c3627e0d7ecdd7960541..02c6f2b29d17e5f6d9dc5336fe760bfc68d1617e 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]  # TODO: make this an enum
     """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/model.py b/swh/model/model.py
index badac8c6b119f6f30c448f6093de76499754b50a..70559dbbf685cfdf0240e5302dd40cfc0205c372 100644
--- a/swh/model/model.py
+++ b/swh/model/model.py
@@ -26,7 +26,7 @@ class BaseModel:
     that are suitable for JSON/msgpack-like formats."""
 
     def to_dict(self):
-        """Wrapper of `attr.asdict` that can be overriden by subclasses
+        """Wrapper of `attr.asdict` that can be overridden by subclasses
         that have special handling of some of the fields."""
         return attr.asdict(self)
 
@@ -352,7 +352,7 @@ class Content(BaseModel):
 
     @reason.validator
     def check_reason(self, attribute, value):
-        """Checks the reason is full iff status != absent."""
+        """Checks the reason is full if status != absent."""
         assert self.reason == value
         if self.status == 'absent' and value is None:
             raise ValueError('Must provide a reason if content is absent.')
diff --git a/swh/model/tests/test_from_disk.py b/swh/model/tests/test_from_disk.py
index 30f543d639f570be72c24bb55f7bb604cba5f2cd..7b21d20e3af2ccb31aca3607e635845f50592c7d 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/version.txt b/version.txt
index aa6ed2afee0d1e7b4e4eb957a5001a7f2479d083..5bd5928bc3227ac1293fd77b68d90cf559822854 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-v0.0.44-0-ge77c94d
\ No newline at end of file
+v0.0.45-0-g70e5d50
\ No newline at end of file