Allow to pickle db models by removing the current connection. (#5641)

## Description

This might be a quick one, depending on how you feel about it... It
allows you to pickle DB model objects. I don't think this is used
directly in Beets, but it might be useful in general. For instance, we
encountered an issue where we wanted to quickly pickle an Item or Album.
This sometimes worked and other times failed, which seemed quite
inconsistent.

Some DB model methods and properties have the side effect of attaching
an SQLite connection to self (._db), which prevents serialization. The
fix is quite straightforward, so I thought we might want to integrate
this into beets directly.

## To Do

- [x] Changelog
- [x] Tests
This commit is contained in:
Sebastian Mohr 2025-04-15 11:43:36 +02:00 committed by GitHub
parent 98014ab02e
commit b4a634a443
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 24 additions and 0 deletions

View file

@ -716,6 +716,15 @@ class Model(ABC, Generic[D]):
"""Set the object's key to a value represented by a string."""
self[key] = self._parse(key, string)
def __getstate__(self):
"""Return the state of the object for pickling.
Remove the database connection as sqlite connections are not
picklable.
"""
state = self.__dict__.copy()
state["_db"] = None
return state
# Database controller and supporting interfaces.

View file

@ -102,6 +102,7 @@ Other changes:
EXTM3U playlists instead of JSON-encoding them.
* :doc:`plugins/ftintitle`: Optimize the plugin by avoiding unnecessary writes
to the database.
* Database models are now serializable with pickle.
2.2.0 (December 02, 2024)
-------------------------

View file

@ -421,6 +421,20 @@ class ModelTest(unittest.TestCase):
with pytest.raises(TypeError, match="must be a string"):
dbcore.Model._parse(None, 42)
def test_pickle_dump(self):
"""Tries to pickle an item. This tests the __getstate__ method
of the Model ABC"""
import pickle
model = ModelFixture1(self.db)
model.add(self.db)
model.field_one = 123
model.store()
assert model._db is not None
pickle.dumps(model)
class FormatTest(unittest.TestCase):
def test_format_fixed_field_integer(self):