Module shelve is great but it only save objects when they are explicitly assigned to a shelf. This may be error prone as for instance in:

>>> db = shelve.open("shelf.db")
>>> db["foo"] = []
>>> db["foo"].append("hello")
>>> db["foo"]
[]

Calling append on the object does not lead to modify the stored object because it has not been assigned to the shelf. Instead, of a simple call to append we should have three instructions, which is not really convenient:

>>> l = db["foo"]
>>> l.append("hello")
>>> db["foo"] = l

I show below a simple wrapper around shelve to deal with this comfortably.

Using this module, the usage becomes:

>>> store = Store("shelf.db")
>>> store["foo"] = []
>>> with store.load("foo") as foo :
...     foo.append("hello")
...     foo.append("world!")
>>> print(" ".join(store["foo"]))
hello world!

Method load also accepts a default argument to work like get:

>>> with store.load("bar", []) as bar :
...     bar.extend(["bye-bye", "world!"])
>>> print(" ".join(store["bar"]))
bye-bye world!

Let’s create module store.

We first import shelve and create a unique object to correctly detect when no default argument is given to store.load (using None is not possible because we may want to use None as a default argument).

import shelve

Empty = object()

Then we define the context manager that takes care of writing back the object to the shelf:

Note that we have chosen that, when an exception occurs, the object is not stored, which implements the idea that the object may be in an inconsistent state. A with context is thus seen as a kind of transaction: modified objects are saved back only if nothing goes wrong.

class StoredObject (object) :
    def __init__ (self, shelf, key, default=Empty) :
        self.shelf = shelf
        self.key = key
        if default is Empty :
            self.obj = shelf[key]
        else :
            self.obj = shelf.get(key, default)
    def __enter__ (self) :
        return self.obj
    def __exit__ (self, exc_type, exc_val, exc_tb) :
        if exc_val is None :
            self.shelf[self.key] = self.obj

Finally, we write the wrapper around a shelf:

class Store (shelve.DbfilenameShelf) :
    def load (self, key, default=Empty) :
        return StoredObject(self, key, default)
    def __del__ (self) :
        self.db.close()

DbfilenameShelf is the class used by shelve.open, so we use the same one. We just add two methods: load that returns the context manager defined above, and __del__ to ensure that a shelf is always correctly closed (which, surprisingly, is not made in the default implementation).