Repository: beetbox/mediafile

Issue:
List fields don't return None if the tag doesn't exist
If a list tag does not exist, it returns an empty list rather than `None`, but if a non-list tag does not exist, `None` is returned.

I believe non-existent list tags should also return `None` so that this behavior is consistent.

Discussion:
jtpavlock: I'm a bit confused on the tests concerning the wav bitrate. It seems different python versions are returning different results? Inspecting the test wav file in Windows seems to confirm the 705600 bitrate

Patch:
diff --git a/.gitignore b/.gitignore
index 14035f2..29a33a4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -74,6 +74,7 @@ target/
 # virtualenv
 venv/
 ENV/
+.venv/
 
 # Spyder project settings
 .spyderproject
diff --git a/mediafile.py b/mediafile.py
index ca70c94..c834346 100644
--- a/mediafile.py
+++ b/mediafile.py
@@ -600,8 +600,8 @@ class ListStorageStyle(StorageStyle):
     object to each.
 
     Subclasses may overwrite ``fetch`` and ``store``.  ``fetch`` must
-    return a (possibly empty) list and ``store`` receives a serialized
-    list of values as the second argument.
+    return a (possibly empty) list or `None` if the tag does not exist.
+    ``store`` receives a serialized list of values as the second argument.
 
     The `serialize` and `deserialize` methods (from the base
     `StorageStyle`) are still called with individual values. This class
@@ -610,15 +610,23 @@ class ListStorageStyle(StorageStyle):
     def get(self, mutagen_file):
         """Get the first value in the field's value list.
         """
+        values = self.get_list(mutagen_file)
+        if values is None:
+            return None
+
         try:
-            return self.get_list(mutagen_file)[0]
+            return values[0]
         except IndexError:
             return None
 
     def get_list(self, mutagen_file):
         """Get a list of all values for the field using this style.
         """
-        return [self.deserialize(item) for item in self.fetch(mutagen_file)]
+        raw_values = self.fetch(mutagen_file)
+        if raw_values is None:
+            return None
+
+        return [self.deserialize(item) for item in raw_values]
 
     def fetch(self, mutagen_file):
         """Get the list of raw (serialized) values.
@@ -626,19 +634,27 @@ class ListStorageStyle(StorageStyle):
         try:
             return mutagen_file[self.key]
         except KeyError:
-            return []
+            return None
 
     def set(self, mutagen_file, value):
         """Set an individual value as the only value for the field using
         this style.
         """
-        self.set_list(mutagen_file, [value])
+        if value is None:
+            self.store(mutagen_file, None)
+        else:
+            self.set_list(mutagen_file, [value])
 
     def set_list(self, mutagen_file, values):
         """Set all values for the field using this style. `values`
         should be an iterable.
         """
-        self.store(mutagen_file, [self.serialize(value) for value in values])
+        if values is None:
+            self.delete(mutagen_file)
+        else:
+            self.store(
+                mutagen_file, [self.serialize(value) for value in values]
+            )
 
     def store(self, mutagen_file, values):
         """Set the list of all raw (serialized) values for this field.
@@ -1317,7 +1333,7 @@ class ListMediaField(MediaField):
             values = style.get_list(mediafile.mgfile)
             if values:
                 return [_safe_cast(self.out_type, value) for value in values]
-        return []
+        return None
 
     def __set__(self, mediafile, values):
         for style in self.styles(mediafile.mgfile):


Test patch:
diff --git a/test/test_mediafile.py b/test/test_mediafile.py
index 10bbf80..fa474bd 100644
--- a/test/test_mediafile.py
+++ b/test/test_mediafile.py
@@ -160,7 +160,7 @@ class ImageStructureTestMixin(ArtTestMixin):
         mediafile.save()
 
         mediafile = MediaFile(mediafile.filename)
-        self.assertEqual(len(mediafile.images), 0)
+        self.assertIsNone(mediafile.images)
 
     def test_guess_cover(self):
         mediafile = self._mediafile_fixture('image')