diff --git a/PKG-INFO b/PKG-INFO
index d6afa6e9ae14e5cf27e10ac25b9a506b8ea77373..6357512042a6ec0f50f1399f8afa75d7010f9849 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: swh.model
-Version: 3.1.0
+Version: 3.2.0
 Summary: Software Heritage data model
 Home-page: https://forge.softwareheritage.org/diffusion/DMOD/
 Author: Software Heritage developers
diff --git a/swh.model.egg-info/PKG-INFO b/swh.model.egg-info/PKG-INFO
index d6afa6e9ae14e5cf27e10ac25b9a506b8ea77373..6357512042a6ec0f50f1399f8afa75d7010f9849 100644
--- a/swh.model.egg-info/PKG-INFO
+++ b/swh.model.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: swh.model
-Version: 3.1.0
+Version: 3.2.0
 Summary: Software Heritage data model
 Home-page: https://forge.softwareheritage.org/diffusion/DMOD/
 Author: Software Heritage developers
diff --git a/swh/model/from_disk.py b/swh/model/from_disk.py
index 450bb68ac55471d8588c0ad6dcb470a813527a36..43a9c7157a9d1bf62e769adeb49679e154b1c844 100644
--- a/swh/model/from_disk.py
+++ b/swh/model/from_disk.py
@@ -549,6 +549,13 @@ class Directory(MerkleNode):
             key1, key2 = key.rsplit(b"/", 1)
             del self[key1][key2]
 
+    def __contains__(self, key):
+        if b"/" not in key:
+            return super().__contains__(key)
+        else:
+            key1, key2 = key.split(b"/", 1)
+            return super().__contains__(key1) and self[key1].__contains__(key2)
+
     def __repr__(self):
         return "Directory(id=%s, entries=[%s])" % (
             hash_to_hex(self.hash),
diff --git a/swh/model/hypothesis_strategies.py b/swh/model/hypothesis_strategies.py
index c8644a39134d8199f3431d112e6f7644c52bcafa..513dc93989a16f48c9e5d13f6fcac7c7d0a89f3e 100644
--- a/swh/model/hypothesis_strategies.py
+++ b/swh/model/hypothesis_strategies.py
@@ -1,10 +1,11 @@
-# Copyright (C) 2019-2020 The Software Heritage developers
+# Copyright (C) 2019-2021 The Software Heritage developers
 # See the AUTHORS file at the top-level directory of this distribution
 # License: GNU General Public License version 3, or any later version
 # See top-level LICENSE file for more information
 
 import datetime
 import string
+from typing import Sequence
 
 from hypothesis import assume
 from hypothesis.extra.dateutil import timezones
@@ -75,6 +76,12 @@ def sha1():
     return binary(min_size=20, max_size=20)
 
 
+def binaries_without_bytes(blacklist: Sequence[int]):
+    """Like hypothesis.strategies.binary, but takes a sequence of bytes that
+    should not be included."""
+    return lists(sampled_from([i for i in range(256) if i not in blacklist])).map(bytes)
+
+
 @composite
 def extended_swhids(draw):
     object_type = draw(sampled_from(ExtendedObjectType))
@@ -274,7 +281,7 @@ def revisions():
 def directory_entries_d():
     return builds(
         dict,
-        name=binary(),
+        name=binaries_without_bytes(b"/"),
         target=sha1_git(),
         type=sampled_from(["file", "dir", "rev"]),
         perms=sampled_from([perm.value for perm in DentryPerms]),
diff --git a/swh/model/tests/test_from_disk.py b/swh/model/tests/test_from_disk.py
index 4a7cb385a2935cd41eed7d99cfa01968b0486316..fa3d8b68270526cc9a935bda60b332ec8a991b9d 100644
--- a/swh/model/tests/test_from_disk.py
+++ b/swh/model/tests/test_from_disk.py
@@ -978,3 +978,19 @@ class DirectoryManipulation(DataMixin, unittest.TestCase):
             del d["foo"]
         with self.assertRaisesRegex(ValueError, "bytes Directory entry"):
             del d[42]
+
+    def test_directory_contains(self):
+        d = Directory()
+        d[b"a"] = Directory()
+        d[b"a/b"] = Directory()
+        d[b"a/b/c"] = Directory()
+        d[b"a/b/c/d"] = Content()
+
+        self.assertIn(b"a", d)
+        self.assertIn(b"a/b", d)
+        self.assertIn(b"a/b/c", d)
+        self.assertIn(b"a/b/c/d", d)
+
+        self.assertNotIn(b"b", d)
+        self.assertNotIn(b"b/c", d)
+        self.assertNotIn(b"b/c/d", d)