diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt
new file mode 100644
index 00000000..f3d5b95c
--- /dev/null
+++ b/ReleaseNotes.txt
@@ -0,0 +1,8 @@
+## pending/current ##
+
+- Serialization / Storage:
+
+    - Added functionality to easily access available content/identifiers in `PulseStorage` and `StorageBackend`.
+    - DEPRECATED `list_contents()`` of `StorageBackend` (use `contents property` instead).
+
+## 0.1.2 ##
diff --git a/qupulse/serialization.py b/qupulse/serialization.py
index 5148b6ae..b0be3542 100644
--- a/qupulse/serialization.py
+++ b/qupulse/serialization.py
@@ -20,7 +20,7 @@ Functions:
 """
 
 from abc import ABCMeta, abstractmethod
-from typing import Dict, Any, Optional, NamedTuple, Union, Mapping, MutableMapping, Set, Callable
+from typing import Dict, Any, Optional, NamedTuple, Union, Mapping, MutableMapping, Set, Callable, Iterator, Iterable
 import os
 import zipfile
 import tempfile
@@ -29,6 +29,7 @@ import weakref
 import warnings
 import gc
 import importlib
+import warnings
 from contextlib import contextmanager
 
 from qupulse.utils.types import DocStringABCMeta
@@ -104,13 +105,29 @@ class StorageBackend(metaclass=ABCMeta):
     def __delitem__(self, identifier: str) -> None:
         self.delete(identifier)
 
-    @abstractmethod
-    def list_contents(self) -> Set[str]:
+    def list_contents(self) -> Iterable[str]:
         """Returns a listing of all available identifiers.
 
+        DEPRECATED (2018-09-20): Use property contents instead.
+
         Returns:
             List of all available identifiers.
         """
+        warnings.warn("list_contents is deprecated. Use the property contents instead", DeprecationWarning)
+        return self.contents
+
+    @property
+    def contents(self) -> Iterable[str]:
+        """The identifiers of all Serializables currently stored in this StorageBackend."""
+        return set(iter(self))
+
+    @abstractmethod
+    def __iter__(self) -> Iterator[str]:
+        """Iterator over all identifiers of Serializables currently stored in this StorageBackend."""
+        pass
+
+    def __len__(self) -> int:
+        return len(self.contents)
 
 
 class FilesystemBackend(StorageBackend):
@@ -166,20 +183,9 @@ class FilesystemBackend(StorageBackend):
         except FileNotFoundError as fnf:
             raise KeyError(identifier) from fnf
 
-    def list_contents(self) -> Set[str]:
-        contents = set()
+    def __iter__(self) -> Iterator[str]:
         for dirpath, dirs, files in os.walk(self._root):
-            contents = contents | {filename
-                                   for filename, ext in (os.path.splitext(file) for file in files)
-                                   if ext == '.json'}
-            break # abort after first iteration; FileSystemBackend doesn't allow for subdirectories anyway right now, so this is a safeguard
-
-            # pref = os.path.commonprefix((dirpath, self._root))
-            # dir_rel_path = dirpath[len(pref):]
-            # contents = contents | {os.path.join(dir_rel_path, filename)
-            #                        for filename, ext in (os.path.splitext(file) for file in files)
-            #                        if ext == '.json'}
-        return contents
+            return (filename for filename, ext in (os.path.splitext(file) for file in files) if ext == '.json')
 
 
 class ZipFileBackend(StorageBackend):
@@ -193,29 +199,38 @@ class ZipFileBackend(StorageBackend):
     network devices, but takes longer to update because every write causes a
     complete recompression (it's not too bad)."""
 
-    def __init__(self, root: str='./storage.zip') -> None:
+    def __init__(self, root: str='./storage.zip', compression_method: int=zipfile.ZIP_DEFLATED) -> None:
         """Creates a new FilesystemBackend.
 
         Args:
-            root (str): The path of the zip file in which all data files are stored. (default: "./storage.zip",
+            root: The path of the zip file in which all data files are stored. (default: "./storage.zip",
                 i.e. the current directory)
+            compression_method: The compression method/algorithm used to compress data in the zipfile. Accepts
+                all values handled by the zipfile module (ZIP_STORED, ZIP_DEFLATED, ZIP_BZIP2, ZIP_LZMA). Please refer
+                to the `zipfile docs <https://docs.python.org/3/library/zipfile.html#zipfile.ZIP_STORED>` for more
+                information. (default: zipfile.ZIP_DEFLATED)
         Raises:
             NotADirectoryError if root is not a valid path.
         """
         parent, fname = os.path.split(root)
-        if not os.path.isdir(parent):
-            raise NotADirectoryError()
         if not os.path.isfile(root):
+            if not os.path.isdir(parent):
+                raise NotADirectoryError(
+                    "Cannot create a ZipStorageBackend. The parent path {} is not valid.".format(parent)
+                )
             z = zipfile.ZipFile(root, "w")
             z.close()
+        elif not zipfile.is_zipfile(root):
+            raise FileExistsError("Cannot open a ZipStorageBackend. The file {} is not a zip archive.".format(root))
         self._root = root
+        self._compression_method = compression_method
 
     def _path(self, identifier) -> str:
         return os.path.join(identifier + '.json')
 
     def put(self, identifier: str, data: str, overwrite: bool=False) -> None:
         if not self.exists(identifier):
-            with zipfile.ZipFile(self._root, mode='a', compression=zipfile.ZIP_DEFLATED) as myzip:
+            with zipfile.ZipFile(self._root, mode='a', compression=self._compression_method) as myzip:
                 path = self._path(identifier)
                 myzip.writestr(path, data)
         else:
@@ -262,14 +277,14 @@ class ZipFileBackend(StorageBackend):
 
         # now add filename with its new data
         if data is not None:
-            with zipfile.ZipFile(self._root, mode='a', compression=zipfile.ZIP_DEFLATED) as zf:
+            with zipfile.ZipFile(self._root, mode='a', compression=self._compression_method) as zf:
                 zf.writestr(filename, data)
 
-    def list_contents(self) -> Set[str]:
+    def __iter__(self) -> Iterator[str]:
         with zipfile.ZipFile(self._root, 'r') as myzip:
-            return set(filename
-                       for filename, ext in (os.path.splitext(file) for file in myzip.namelist())
-                       if ext == '.json')
+            return (filename
+                    for filename, ext in (os.path.splitext(file) for file in myzip.namelist())
+                    if ext == '.json')
 
 
 class CachingBackend(StorageBackend):
@@ -278,9 +293,10 @@ class CachingBackend(StorageBackend):
     CachingBackend relies on another StorageBackend to provide real data IO functionality which
     it extends by caching already opened files in memory for faster subsequent access.
 
-    Note that it does not flush the cache at any time and may thus not be suitable for long-time
-    usage as it may consume increasing amounts of memory.
+    Note that it does not automatically clear the cache at any time and thus will consume increasing amounts of memory
+    over time. Use the :meth:`clear_cache` method to clear the cache manually.
     """
+    #todo (2018-09-20): PulseStorage now provides caching. Does this make CachingBackend obsolete? if so -> deprecate and remove
 
     def __init__(self, backend: StorageBackend) -> None:
         """Creates a new CachingBackend.
@@ -311,12 +327,18 @@ class CachingBackend(StorageBackend):
         if identifier in self._cache:
             del self._cache[identifier]
 
-    def list_contents(self) -> Set[str]:
-        return self._backend.list_contents()
+    def __iter__(self) -> Iterator[str]:
+        return iter(self._backend)
+
+    def clear_cache(self) -> None:
+        self._cache = dict()
 
 
 class DictBackend(StorageBackend):
-    """DictBackend uses a dictionary to store the data for convenience serialization."""
+    """DictBackend uses a dictionary to store Serializables in memory.
+
+    Doing so, it does not provide any persistent storage functionality.
+    """
     def __init__(self) -> None:
         self._cache = {}
 
@@ -338,8 +360,8 @@ class DictBackend(StorageBackend):
     def delete(self, identifier: str) -> None:
         del self._cache[identifier]
 
-    def list_contents(self) -> Set[str]:
-        return set(self._cache.keys())
+    def __iter__(self) -> Iterator[str]:
+        return iter(self._cache)
 
 
 class DeserializationCallbackFinder:
@@ -871,15 +893,15 @@ class PulseStorage(MutableMapping[str, Serializable]):
         except KeyError:
             pass
 
-    def __len__(self) -> None:
-        raise NotImplementedError("len(PulseStorage) has currently no meaningful semantics and is not implemented."
-                                  " If you require this feature, please create a feature request at \n"
-                                  "https://github.com/qutech/qupulse/issues/new")
+    @property
+    def contents(self) -> Iterable[str]:
+        return self._storage_backend.list_contents()
+
+    def __len__(self) -> int:
+        return len(self._storage_backend)
 
-    def __iter__(self) -> None:
-        raise NotImplementedError("iter(PulseStorage) has currently no meaningful semantics and is not implemented."
-                                  " If you require this feature, please create a feature request at \n"
-                                  "https://github.com/qutech/qupulse/issues/new")
+    def __iter__(self) -> Iterator[str]:
+        return iter(self._storage_backend)
 
     def overwrite(self, identifier: str, serializable: Serializable) -> None:
         """Explicitly overwrites a pulse.

List content of PulseStorage
The user probably wants to get an overview of what pulses are actually stored in a `PulseStorage` without accessing the backend directly.

I suggest:
```python
def list_contents(self):
    return self._storage_backend.list_contents()

def __len__(self):
    return len(self.list_contents())

def __iter__(self):
    return iter(self.list_contents())
```


diff --git a/tests/serialization_dummies.py b/tests/serialization_dummies.py
index 6387e397..3c811f33 100644
--- a/tests/serialization_dummies.py
+++ b/tests/serialization_dummies.py
@@ -1,4 +1,4 @@
-from typing import Union, Dict, Any, Callable, Set
+from typing import Union, Dict, Any, Callable, Iterator
 
 from qupulse.serialization import Serializer, Serializable, StorageBackend
 
@@ -30,8 +30,8 @@ class DummyStorageBackend(StorageBackend):
     def delete(self, identifier: str) -> None:
         del self.stored_items[identifier]
 
-    def list_contents(self) -> Set[str]:
-        return set(self.stored_items.keys())
+    def __iter__(self) -> Iterator[str]:
+        return iter(self.stored_items)
 
 
 class DummySerializer(Serializer):
diff --git a/tests/serialization_tests.py b/tests/serialization_tests.py
index df0c9458..6c9ad312 100644
--- a/tests/serialization_tests.py
+++ b/tests/serialization_tests.py
@@ -8,13 +8,13 @@ import json
 from unittest import mock
 from abc import ABCMeta, abstractmethod
 
-from tempfile import TemporaryDirectory, TemporaryFile
-from typing import Optional, Any, Dict, Tuple
+from tempfile import TemporaryDirectory, NamedTemporaryFile, TemporaryFile
+from typing import Optional, Any, Tuple
 
 from qupulse.serialization import FilesystemBackend, CachingBackend, Serializable, JSONSerializableEncoder,\
     ZipFileBackend, AnonymousSerializable, DictBackend, PulseStorage, JSONSerializableDecoder, Serializer,\
     get_default_pulse_registry, set_default_pulse_registry, new_default_pulse_registry, SerializableMeta, \
-    PulseRegistryType, DeserializationCallbackFinder
+    PulseRegistryType, DeserializationCallbackFinder, StorageBackend
 
 from qupulse.expressions import ExpressionScalar
 
@@ -120,7 +120,6 @@ class SerializableTests(metaclass=ABCMeta):
             self.assert_equal_instance(expected, instance)
 
     def test_serialization_and_deserialization(self):
-        # TODO PulseStorage registry specification
         registry = dict()
 
         instance = self.make_instance('blub', registry=registry)
@@ -261,6 +260,84 @@ class DummyPulseTemplateSerializationTests(SerializableTests, unittest.TestCase)
         self.assertEqual(lhs.compare_key, rhs.compare_key)
 
 
+@mock.patch.multiple(StorageBackend, __abstractmethods__=set())
+class StorageBackendTest(unittest.TestCase):
+    """Testing common methods implemented in StorageBackend base class based on the abstract methods implemented
+    by subclasses."""
+
+    def test_setitem(self) -> None:
+        with mock.patch.object(StorageBackend, 'put') as put_mock:
+            storage = StorageBackend()
+            storage["foo"] = "bar"
+            self.assertEqual(mock.call('foo', 'bar'), put_mock.call_args)
+
+        with mock.patch.object(StorageBackend, 'put', side_effect=FileExistsError()) as put_mock:
+            storage = StorageBackend()
+            with self.assertRaises(FileExistsError):
+                storage["foo"] = "bar"
+            self.assertEqual(mock.call('foo', 'bar'), put_mock.call_args)
+
+    def test_getitem(self) -> None:
+        expected = "bar"
+        with mock.patch.object(StorageBackend, 'get', return_value=expected) as get_mock:
+            storage = StorageBackend()
+            foo = storage["foo"]
+            self.assertEqual(expected, foo)
+            self.assertEqual(mock.call("foo"), get_mock.call_args)
+
+        with mock.patch.object(StorageBackend, 'get', side_effect=KeyError()) as get_mock:
+            storage = StorageBackend()
+            with self.assertRaises(KeyError):
+                storage['foo']
+            self.assertEqual(mock.call('foo'), get_mock.call_args)
+
+    def test_contains(self) -> None:
+        with mock.patch.object(StorageBackend, 'exists', return_value=True) as exists_mock:
+            storage = StorageBackend()
+            self.assertTrue('foo' in storage)
+            self.assertEqual(mock.call('foo'), exists_mock.call_args)
+
+        with mock.patch.object(StorageBackend, 'exists', return_value=False) as exists_mock:
+            storage = StorageBackend()
+            self.assertFalse('foo' in storage)
+            self.assertEqual(mock.call('foo'), exists_mock.call_args)
+
+    def test_delitem(self) -> None:
+        with mock.patch.object(StorageBackend, 'delete') as delete_mock:
+            storage = StorageBackend()
+            del(storage['foo'])
+            self.assertEqual(mock.call('foo'), delete_mock.call_args)
+
+        with mock.patch.object(StorageBackend, 'delete', side_effect=KeyError()) as delete_mock:
+            storage = StorageBackend()
+            with self.assertRaises(KeyError):
+                del(storage['foo'])
+            self.assertEqual(mock.call('foo'), delete_mock.call_args)
+
+    def test_list_contents(self) -> None:
+        expected = {'hugo', 'ilse', 'foo.bar'}
+        with warnings.catch_warnings():
+            warnings.simplefilter('ignore', DeprecationWarning)
+            with mock.patch.object(StorageBackend, '__iter__', return_value=iter(expected)) as iter_mock:
+                storage = StorageBackend()
+                self.assertEqual(expected, storage.list_contents())
+                self.assertEqual(1, iter_mock.call_count)
+
+    def test_contents(self) -> None:
+        expected = {'hugo', 'ilse', 'foo.bar'}
+        with mock.patch.object(StorageBackend, '__iter__', return_value=iter(expected)) as iter_mock:
+            storage = StorageBackend()
+            self.assertEqual(expected, storage.contents)
+            self.assertEqual(1, iter_mock.call_count)
+
+    def test_len(self) -> None:
+        expected = {'hugo', 'ilse', 'foo.bar'}
+        with mock.patch.object(StorageBackend, '__iter__', return_value=iter(expected)) as iter_mock:
+            storage = StorageBackend()
+            self.assertEqual(3, len(storage))
+            self.assertEqual(1, iter_mock.call_count)
+
+
 class FileSystemBackendTest(unittest.TestCase):
     def setUp(self) -> None:
         self.tmp_dir = TemporaryDirectory()
@@ -335,148 +412,126 @@ class FileSystemBackendTest(unittest.TestCase):
         self.assertFalse(self.backend.exists(name))
         self.assertFalse(os.listdir(self.tmp_dir.name))
 
-    def test_get_contents(self) -> None:
+    def test_get_contents_iter_len(self) -> None:
         expected = {'foo', 'bar', 'hugo.test'}
         for name in expected:
             self.backend.put(name, self.test_data)
-        contents = self.backend.list_contents()
 
-        self.assertEqual(expected, contents)
+        self.assertEqual(expected, self.backend.list_contents(), msg="list_contents() faulty")
+        self.assertEqual(expected, set(iter(self.backend)), msg="__iter__() faulty")
+        self.assertEqual(3, len(self.backend), msg="__len__() faulty")
 
-    def test_get_contents_empty(self) -> None:
-        contents = self.backend.list_contents()
-        self.assertEqual(0, len(contents))
+    def test_iter_empty(self) -> None:
+        self.assertEqual(set(), set(iter(self.backend)))
 
 
 class ZipFileBackendTests(unittest.TestCase):
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
 
-    def test_init(self):
-        with TemporaryDirectory() as tmp_dir:
-
-            with self.assertRaises(NotADirectoryError):
-                ZipFileBackend(os.path.join(tmp_dir, 'fantasie', 'mehr_phantasie'))
+    def setUp(self) -> None:
+        self.tmp_dir = TemporaryDirectory()
+        self.path = os.path.join(self.tmp_dir.name, 'backend.zip')
+        self.backend = ZipFileBackend(self.path)
+        self.assertTrue(zipfile.is_zipfile(self.path))
 
-            root = os.path.join(tmp_dir, 'root.zip')
+    def tearDown(self) -> None:
+        self.tmp_dir.cleanup()
 
-            ZipFileBackend(root)
+    def test_init_invalid_path(self):
+        invalid_path = os.path.join(self.tmp_dir.name, "asdfasdf", "backend.zip")
+        with self.assertRaises(NotADirectoryError):
+            ZipFileBackend(invalid_path)
 
-            self.assertTrue(zipfile.is_zipfile(root))
+    def test_init_file_exists_not_zip(self):
+        with NamedTemporaryFile() as tmp_file:
+            with self.assertRaises(FileExistsError):
+                ZipFileBackend(tmp_file.name)
 
     def test_init_keeps_data(self):
-        with TemporaryDirectory() as tmp_dir:
-            root = os.path.join(tmp_dir, 'root.zip')
-            with zipfile.ZipFile(root, mode='w', compression=zipfile.ZIP_DEFLATED) as zip_file:
-                zip_file.writestr('test_file.txt', 'chichichi')
-
-            ZipFileBackend(root)
+        path = os.path.join(self.tmp_dir.name, 'test.zip')
+        with zipfile.ZipFile(path, mode='w', compression=zipfile.ZIP_DEFLATED) as zip_file:
+            zip_file.writestr('test_file.txt', 'chichichi')
 
-            with zipfile.ZipFile(root, 'r') as zip_file:
-                ma_string = zip_file.read('test_file.txt')
-                self.assertEqual(b'chichichi', ma_string)
+        ZipFileBackend(path)
 
-    def test_path(self):
-        with TemporaryDirectory() as tmp_dir:
-            root = os.path.join(tmp_dir, 'root.zip')
-            be = ZipFileBackend(root)
-            self.assertEqual(be._path('foo'), 'foo.json')
+        with zipfile.ZipFile(path, 'r') as zip_file:
+            ma_string = zip_file.read('test_file.txt')
+            self.assertEqual(b'chichichi', ma_string)
 
     def test_exists(self):
-        with TemporaryDirectory() as tmp_dir:
-            root = os.path.join(tmp_dir, 'root.zip')
-            be = ZipFileBackend(root)
-
-            self.assertFalse(be.exists('foo'))
+        self.assertFalse(self.backend.exists('foo'))
 
-            with zipfile.ZipFile(root, mode='w', compression=zipfile.ZIP_DEFLATED) as zip_file:
-                zip_file.writestr('foo.json', 'chichichi')
+        with zipfile.ZipFile(self.path, mode='w', compression=zipfile.ZIP_DEFLATED) as zip_file:
+            zip_file.writestr('foo.json', 'chichichi')
 
-            self.assertTrue(be.exists('foo'))
+        self.assertTrue(self.backend.exists('foo'))
 
     def test_put(self):
-        with TemporaryDirectory() as tmp_dir:
-            root = os.path.join(tmp_dir, 'root.zip')
+        self.backend.put('foo', 'foo_data')
 
-            be = ZipFileBackend(root)
+        with zipfile.ZipFile(self.path, 'r') as zip_file:
+            ma_string = zip_file.read('foo.json')
+            self.assertEqual(b'foo_data', ma_string)
 
-            be.put('foo', 'foo_data')
-
-            with zipfile.ZipFile(root, 'r') as zip_file:
-                ma_string = zip_file.read('foo.json')
-                self.assertEqual(b'foo_data', ma_string)
-
-            with self.assertRaises(FileExistsError):
-                be.put('foo', 'bar_data')
-            with zipfile.ZipFile(root, 'r') as zip_file:
-                ma_string = zip_file.read('foo.json')
-                self.assertEqual(b'foo_data', ma_string)
+        with self.assertRaises(FileExistsError):
+            self.backend.put('foo', 'bar_data')
+        with zipfile.ZipFile(self.path, 'r') as zip_file:
+            ma_string = zip_file.read('foo.json')
+            self.assertEqual(b'foo_data', ma_string)
 
-            be.put('foo', 'foo_bar_data', overwrite=True)
-            with zipfile.ZipFile(root, 'r') as zip_file:
-                ma_string = zip_file.read('foo.json')
-                self.assertEqual(b'foo_bar_data', ma_string)
+        self.backend.put('foo', 'foo_bar_data', overwrite=True)
+        with zipfile.ZipFile(self.path, 'r') as zip_file:
+            ma_string = zip_file.read('foo.json')
+            self.assertEqual(b'foo_bar_data', ma_string)
 
     def test_get(self):
-        with TemporaryDirectory() as tmp_dir:
-            root = os.path.join(tmp_dir, 'root.zip')
-            be = ZipFileBackend(root)
-
-            with self.assertRaises(KeyError):
-                be.get('foo')
+        with self.assertRaises(KeyError):
+            self.backend.get('foo')
 
-            data = 'foo_data'
-            with zipfile.ZipFile(root, 'a') as zip_file:
-                zip_file.writestr('foo.json', data)
+        data = 'foo_data'
+        with zipfile.ZipFile(self.path, 'a') as zip_file:
+            zip_file.writestr('foo.json', data)
 
-            self.assertEqual(be.get('foo'), data)
+        self.assertEqual(self.backend.get('foo'), data)
 
-            os.remove(root)
-            with self.assertRaises(KeyError):
-                be.get('foo')
+        os.remove(self.path)
+        with self.assertRaises(KeyError):
+            self.backend.get('foo')
 
     def test_update(self):
-        with TemporaryDirectory() as tmp_dir:
-            root = os.path.join(tmp_dir, 'root.zip')
-            be = ZipFileBackend(root)
-
-            be.put('foo', 'foo_data')
-            be.put('bar', 'bar_data')
+        self.backend.put('foo', 'foo_data')
+        self.backend.put('bar', 'bar_data')
 
-            be._update('foo.json', 'foo_bar_data')
+        self.backend._update('foo.json', 'foo_bar_data')
 
-            self.assertEqual(be.get('foo'), 'foo_bar_data')
-            self.assertEqual(be.get('bar'), 'bar_data')
+        self.assertEqual(self.backend.get('foo'), 'foo_bar_data')
+        self.assertEqual(self.backend.get('bar'), 'bar_data')
 
-            be._update('foo.json', None)
-            self.assertFalse(be.exists('foo'))
+        self.backend._update('foo.json', None)
+        self.assertFalse(self.backend.exists('foo'))
 
     def test_delete(self):
-        with TemporaryDirectory() as tmp_dir:
-            root = os.path.join(tmp_dir, 'root.zip')
-            backend = ZipFileBackend(root)
+        with self.assertRaisesRegex(KeyError, 'foo'):
+            self.backend.delete('foo')
 
-            with self.assertRaisesRegex(KeyError, 'foo'):
-                backend.delete('foo')
+        self.backend.put('foo', 'foo_data')
+        self.assertTrue(self.backend.exists('foo'))
+        self.backend.delete('foo')
+        self.assertFalse(self.backend.exists('foo'))
 
-            backend.put('foo', 'foo_data')
-            self.assertTrue(backend.exists('foo'))
-            backend.delete('foo')
-            self.assertFalse(backend.exists('foo'))
+        with zipfile.ZipFile(self.path, 'r') as file:
+            self.assertNotIn('foo', file.namelist())
 
-            with zipfile.ZipFile(root, 'r') as file:
-                self.assertNotIn('foo', file.namelist())
+    def test_get_contents_iter_len(self) -> None:
+        expected = {'foo', 'bar', 'hugo.test'}
+        for name in expected:
+            self.backend.put(name, "asdfasdfas")
 
-    def test_get_contents(self) -> None:
-        with TemporaryDirectory() as tmp_dir:
-            root = os.path.join(tmp_dir, 'root.zip')
-            backend = ZipFileBackend(root)
-            expected = {'foo', 'bar', 'hugo.test'}
-            for name in expected:
-                backend.put(name, 'foo_data')
-            contents = backend.list_contents()
+        self.assertEqual(expected, self.backend.list_contents(), msg="list_contents() faulty")
+        self.assertEqual(expected, set(iter(self.backend)), msg="__iter__() faulty")
+        self.assertEqual(3, len(self.backend), msg="__len__() faulty")
 
-            self.assertEqual(expected, contents)
+    def test_iter_empty(self) -> None:
+        self.assertEqual(set(), set(iter(self.backend)))
 
 
 class CachingBackendTests(unittest.TestCase):
@@ -566,13 +621,17 @@ class CachingBackendTests(unittest.TestCase):
         self.assertNotIn('foo', self.caching_backend)
         self.assertNotIn('foo', self.dummy_backend)
 
-    def test_get_contents(self) -> None:
+    def test_get_contents_iter_len(self) -> None:
         expected = {'foo', 'bar', 'hugo.test'}
         for name in expected:
-            self.caching_backend.put(name, self.testdata)
-        contents = self.caching_backend.list_contents()
+            self.dummy_backend.put(name, "asdfasdfas")
+
+        self.assertEqual(expected, self.caching_backend.list_contents(), msg="list_contents() faulty")
+        self.assertEqual(expected, set(iter(self.caching_backend)), msg="__iter__() faulty")
+        self.assertEqual(3, len(self.caching_backend), msg="__len__() faulty")
 
-        self.assertEqual(expected, contents)
+    def test_iter_empty(self) -> None:
+        self.assertEqual(set(), set(iter(self.caching_backend)))
 
 
 class DictBackendTests(unittest.TestCase):
@@ -610,15 +669,19 @@ class DictBackendTests(unittest.TestCase):
 
         self.backend.delete('a')
         self.assertFalse(self.backend.storage)
-
-    def test_get_contents(self) -> None:
+        
+    def test_get_contents_iter_len(self) -> None:
         expected = {'foo', 'bar', 'hugo.test'}
         for name in expected:
-            self.backend.put(name, 'foo_data')
-        contents = self.backend.list_contents()
+            self.backend.put(name, "asdfasdfas")
 
-        self.assertEqual(expected, contents)
+        self.assertEqual(expected, self.backend.list_contents(), msg="list_contents() faulty")
+        self.assertEqual(expected, set(iter(self.backend)), msg="__iter__() faulty")
+        self.assertEqual(3, len(self.backend), msg="__len__() faulty")
 
+    def test_iter_empty(self) -> None:
+        self.assertEqual(set(), set(iter(self.backend)))
+        
 
 class DeserializationCallbackFinderTests(unittest.TestCase):
     def test_set_item(self):
@@ -908,15 +971,28 @@ class PulseStorageTests(unittest.TestCase):
         self.assertEqual({}, backend.stored_items)
         self.assertEqual(pulse_storage.temporary_storage, {})
 
+    @mock.patch.multiple(StorageBackend, __abstractmethods__=set())
     def test_len(self) -> None:
-        pulse_storage = PulseStorage(DummyStorageBackend())
-        with self.assertRaisesRegex(NotImplementedError, "request"):
-            len(pulse_storage)
+        with mock.patch.object(StorageBackend, '__len__', return_value=5) as len_mock:
+            pulse_storage = PulseStorage(StorageBackend())
+            self.assertEqual(5, len(pulse_storage))
+            self.assertEqual(1, len_mock.call_count)
 
+    @mock.patch.multiple(StorageBackend, __abstractmethods__=set())
     def test_iter(self) -> None:
-        pulse_storage = PulseStorage(DummyStorageBackend())
-        with self.assertRaisesRegex(NotImplementedError, "request"):
-            iter(pulse_storage)
+        data = ['hugo', 'ilse', 'foo.bar']
+        with mock.patch.object(StorageBackend, '__iter__', return_value=iter(data)) as iter_mock:
+            pulse_storage = PulseStorage(StorageBackend())
+            self.assertEqual(set(data), set(iter(pulse_storage)))
+            self.assertEqual(1, iter_mock.call_count)
+
+    @mock.patch.multiple(StorageBackend, __abstractmethods__=set())
+    def test_contents(self) -> None:
+        data = ['hugo', 'ilse', 'foo.bar']
+        with mock.patch.object(StorageBackend, '__iter__', return_value=iter(data)) as iter_mock:
+            pulse_storage = PulseStorage(StorageBackend())
+            self.assertEqual(set(data), set(iter(pulse_storage)))
+            self.assertEqual(1, iter_mock.call_count)
 
     def test_deserialize_storage_is_default_registry(self) -> None:
         backend = DummyStorageBackend()

pip3 install .[plotting,zurich-instruments,tektronix,Faster-fractions]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-xdist pytest-mock pytest-asyncio
apt-get update apt-get install -y libgmp-dev libmpfr-dev libmpc-dev
3.6
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
cached-property==1.5.2
certifi==2021.5.30
coverage==6.2
cycler==0.11.0
execnet==1.9.0
importlib-metadata==4.8.3
iniconfig==1.1.1
kiwisolver==1.3.1
matplotlib==3.3.4
mpmath==1.3.0
numpy==1.19.5
packaging==21.3
Pillow==8.4.0
pluggy==1.0.0
py==1.11.0
pyparsing==3.1.4
pytest==7.0.1
pytest-asyncio==0.16.0
pytest-cov==4.0.0
pytest-mock==3.6.1
pytest-xdist==3.0.2
python-dateutil==2.9.0.post0
qupulse @ file:///qupulse
six==1.17.0
sympy==1.2
tomli==1.2.3
typing_extensions==4.1.1
zipp==3.6.0

name: qupulse
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - cached-property==1.5.2
      - coverage==6.2
      - cycler==0.11.0
      - execnet==1.9.0
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - kiwisolver==1.3.1
      - matplotlib==3.3.4
      - mpmath==1.3.0
      - numpy==1.19.5
      - packaging==21.3
      - pillow==8.4.0
      - pluggy==1.0.0
      - py==1.11.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - pytest-asyncio==0.16.0
      - pytest-cov==4.0.0
      - pytest-mock==3.6.1
      - pytest-xdist==3.0.2
      - python-dateutil==2.9.0.post0
      - qupulse==0.1.2
      - six==1.17.0
      - sympy==1.2
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - zipp==3.6.0
prefix: /opt/conda/envs/qupulse

tests/serialization_tests.py::DummySerializableTests::test_conversion tests/serialization_tests.py::DummySerializableTests::test_serialization_and_deserialization tests/serialization_tests.py::DummyPulseTemplateSerializationTests::test_conversion tests/serialization_tests.py::DummyPulseTemplateSerializationTests::test_serialization_and_deserialization tests/serialization_tests.py::StorageBackendTest::test_contents tests/serialization_tests.py::StorageBackendTest::test_len tests/serialization_tests.py::StorageBackendTest::test_list_contents tests/serialization_tests.py::FileSystemBackendTest::test_get_contents_iter_len tests/serialization_tests.py::FileSystemBackendTest::test_iter_empty tests/serialization_tests.py::ZipFileBackendTests::test_get_contents_iter_len tests/serialization_tests.py::ZipFileBackendTests::test_init_file_exists_not_zip tests/serialization_tests.py::ZipFileBackendTests::test_iter_empty tests/serialization_tests.py::CachingBackendTests::test_delete tests/serialization_tests.py::CachingBackendTests::test_exists_cached tests/serialization_tests.py::CachingBackendTests::test_exists_not tests/serialization_tests.py::CachingBackendTests::test_exists_not_cached tests/serialization_tests.py::CachingBackendTests::test_get_contents_iter_len tests/serialization_tests.py::CachingBackendTests::test_get_not_existing tests/serialization_tests.py::CachingBackendTests::test_iter_empty tests/serialization_tests.py::CachingBackendTests::test_put_and_get_normal tests/serialization_tests.py::CachingBackendTests::test_put_cached_existing_no_overwrite tests/serialization_tests.py::CachingBackendTests::test_put_cached_existing_overwrite tests/serialization_tests.py::CachingBackendTests::test_put_not_cached_existing_no_overwrite tests/serialization_tests.py::CachingBackendTests::test_put_not_cached_existing_overwrite tests/serialization_tests.py::DictBackendTests::test_get_contents_iter_len tests/serialization_tests.py::DictBackendTests::test_iter_empty tests/serialization_tests.py::PulseStorageTests::test_as_default_registry tests/serialization_tests.py::PulseStorageTests::test_beautified_json tests/serialization_tests.py::PulseStorageTests::test_clear tests/serialization_tests.py::PulseStorageTests::test_consistent_over_instances tests/serialization_tests.py::PulseStorageTests::test_contains tests/serialization_tests.py::PulseStorageTests::test_contents tests/serialization_tests.py::PulseStorageTests::test_delitem tests/serialization_tests.py::PulseStorageTests::test_deserialize tests/serialization_tests.py::PulseStorageTests::test_deserialize_storage_is_default_registry tests/serialization_tests.py::PulseStorageTests::test_deserialize_storage_is_not_default_registry_id_free tests/serialization_tests.py::PulseStorageTests::test_deserialize_storage_is_not_default_registry_id_occupied tests/serialization_tests.py::PulseStorageTests::test_deserialize_twice_same_object_storage_is_default_registry tests/serialization_tests.py::PulseStorageTests::test_failed_overwrite_does_not_leave_subpulses tests/serialization_tests.py::PulseStorageTests::test_getitem tests/serialization_tests.py::PulseStorageTests::test_iter tests/serialization_tests.py::PulseStorageTests::test_len tests/serialization_tests.py::PulseStorageTests::test_overwrite tests/serialization_tests.py::PulseStorageTests::test_set_to_default_registry tests/serialization_tests.py::PulseStorageTests::test_setitem tests/serialization_tests.py::PulseStorageTests::test_setitem_different_id tests/serialization_tests.py::PulseStorageTests::test_setitem_duplicate_only_in_backend tests/serialization_tests.py::PulseStorageTests::test_write_through tests/serialization_tests.py::PulseStorageTests::test_write_through_does_not_overwrite_subpulses tests/serialization_tests.py::SerializerTests::test_collection_dictionaries_identifier tests/serialization_tests.py::SerializerTests::test_collection_dictionaries_identifier_one_nesting_identifier tests/serialization_tests.py::SerializerTests::test_collection_dictionaries_identifier_one_nesting_no_identifier tests/serialization_tests.py::SerializerTests::test_collection_dictionaries_no_identifier tests/serialization_tests.py::SerializerTests::test_collection_dictionaries_no_identifier_one_nesting_identifier tests/serialization_tests.py::SerializerTests::test_deserialize_dict tests/serialization_tests.py::SerializerTests::test_deserialize_identifier tests/serialization_tests.py::SerializerTests::test_dicitify_no_identifier_one_nesting_no_identifier tests/serialization_tests.py::SerializerTests::test_serialization_and_deserialization_combined tests/serialization_tests.py::SerializerTests::test_serialize_identifier tests/serialization_tests.py::SerializerTests::test_serialize_identifier_one_nesting_identifier tests/serialization_tests.py::SerializerTests::test_serialize_identifier_one_nesting_no_identifier tests/serialization_tests.py::SerializerTests::test_serialize_no_identifier tests/serialization_tests.py::SerializerTests::test_serialize_no_identifier_one_nesting_identifier tests/serialization_tests.py::SerializerTests::test_serialize_no_identifier_one_nesting_no_identifier tests/serialization_tests.py::SerializerTests::test_serialize_subpulse_duplicate_identifier tests/serialization_tests.py::SerializerTests::test_serialize_subpulse_identifier tests/serialization_tests.py::SerializerTests::test_serialize_subpulse_no_identifier tests/serialization_tests.py::ConversionTests::test_convert_stored_pulse_in_storage tests/serialization_tests.py::ConversionTests::test_convert_stored_pulse_in_storage_dest_not_empty_id_overlap tests/serialization_tests.py::ConversionTests::test_convert_stored_pulse_in_storage_dest_not_empty_no_id_overlap tests/serialization_tests.py::ConversionTests::test_convert_stored_pulses tests/serialization_tests.py::ConversionTests::test_convert_stored_pulses_dest_not_empty_id_overlap tests/serialization_tests.py::ConversionTests::test_convert_stored_pulses_dest_not_empty_no_id_overlap
tests/serialization_tests.py::DummySerializableTests::test_deserialization tests/serialization_tests.py::DummySerializableTests::test_duplication_error tests/serialization_tests.py::DummySerializableTests::test_get_type_identifier tests/serialization_tests.py::DummySerializableTests::test_identifier tests/serialization_tests.py::DummySerializableTests::test_manual_garbage_collect tests/serialization_tests.py::DummySerializableTests::test_no_registration_before_correct_serialization tests/serialization_tests.py::DummySerializableTests::test_renamed tests/serialization_tests.py::DummySerializableTests::test_renamed_of_anonymous tests/serialization_tests.py::DummySerializableTests::test_serialization tests/serialization_tests.py::DummyPulseTemplateSerializationTests::test_deserialization tests/serialization_tests.py::DummyPulseTemplateSerializationTests::test_duplication_error tests/serialization_tests.py::DummyPulseTemplateSerializationTests::test_get_type_identifier tests/serialization_tests.py::DummyPulseTemplateSerializationTests::test_identifier tests/serialization_tests.py::DummyPulseTemplateSerializationTests::test_manual_garbage_collect tests/serialization_tests.py::DummyPulseTemplateSerializationTests::test_no_registration_before_correct_serialization tests/serialization_tests.py::DummyPulseTemplateSerializationTests::test_renamed tests/serialization_tests.py::DummyPulseTemplateSerializationTests::test_renamed_of_anonymous tests/serialization_tests.py::DummyPulseTemplateSerializationTests::test_serialization tests/serialization_tests.py::StorageBackendTest::test_contains tests/serialization_tests.py::StorageBackendTest::test_delitem tests/serialization_tests.py::StorageBackendTest::test_getitem tests/serialization_tests.py::StorageBackendTest::test_setitem tests/serialization_tests.py::FileSystemBackendTest::test_delete tests/serialization_tests.py::FileSystemBackendTest::test_exists tests/serialization_tests.py::FileSystemBackendTest::test_get_not_existing tests/serialization_tests.py::FileSystemBackendTest::test_init_create_dir tests/serialization_tests.py::FileSystemBackendTest::test_init_file_path tests/serialization_tests.py::FileSystemBackendTest::test_instantiation_fail tests/serialization_tests.py::FileSystemBackendTest::test_put_and_get_normal tests/serialization_tests.py::FileSystemBackendTest::test_put_file_exists_no_overwrite tests/serialization_tests.py::FileSystemBackendTest::test_put_file_exists_overwrite tests/serialization_tests.py::ZipFileBackendTests::test_delete tests/serialization_tests.py::ZipFileBackendTests::test_exists tests/serialization_tests.py::ZipFileBackendTests::test_get tests/serialization_tests.py::ZipFileBackendTests::test_init_invalid_path tests/serialization_tests.py::ZipFileBackendTests::test_init_keeps_data tests/serialization_tests.py::ZipFileBackendTests::test_put tests/serialization_tests.py::ZipFileBackendTests::test_update tests/serialization_tests.py::DictBackendTests::test_delete tests/serialization_tests.py::DictBackendTests::test_exists tests/serialization_tests.py::DictBackendTests::test_get tests/serialization_tests.py::DictBackendTests::test_put tests/serialization_tests.py::DeserializationCallbackFinderTests::test_auto_import tests/serialization_tests.py::DeserializationCallbackFinderTests::test_qctoolkit_import tests/serialization_tests.py::DeserializationCallbackFinderTests::test_set_item tests/serialization_tests.py::SerializableMetaTests::test_native_deserializable tests/serialization_tests.py::DefaultPulseRegistryManipulationTests::test_get_set_default_pulse_registry tests/serialization_tests.py::DefaultPulseRegistryManipulationTests::test_new_default_pulse_registry tests/serialization_tests.py::JSONSerializableDecoderTests::test_decode tests/serialization_tests.py::JSONSerializableDecoderTests::test_filter_serializables tests/serialization_tests.py::JSONSerializableEncoderTest::test_default tests/serialization_tests.py::JSONSerializableEncoderTest::test_default_else_branch tests/serialization_tests.py::JSONSerializableEncoderTest::test_encoding tests/serialization_tests.py::JSONSerializableEncoderTest::test_encoding_duplicated_id tests/serialization_tests.py::TriviallyRepresentableEncoderTest::test_encoding
ReleaseNotes.txt qupulse/serialization.py
ReleaseNotes.txt qupulse/serialization.py