Saar Drimer
Saar Drimer

Reputation: 1191

Save a dictionary when it changes

I keep a dictionary that's being modified through a user-interface. Instead of having a user-controlled 'save' functionality, I'd like to have the dictionary written into a JSON file whenever it changes. I know the mechanics of saving, but not quite how to trigger the save on a change in the dictionary. Is this possible, and if so, how?

Upvotes: 2

Views: 103

Answers (1)

Martijn Pieters
Martijn Pieters

Reputation: 1121884

You cannot do it with the default dict type, but you can use a custom dictionary subclass or a subclass of the collections.MutableMapping abstract base class and intercept methods that alter the dictionary so you can trigger a save.

It's easiest with the latter; it maps all modifying methods to __setitem__. You need to provide all methods in the Abstract Methods column of the overview table.

Here is my quick sketch of such a class:

import json
from collections import MutableMapping

class JSONBackedMapping(MutableMapping):
    def __init__(self, filename, initial=None, **kw):
        self._filename = filename
        try:
            # Try and load the file
            self.load()
        except (ValueError, IOError):
            # failure, fall back to the initial object
            self._data = initial or {}
        self._data.update(**kw)

    def load(self):
        with open(self._filename, 'r') as inf:
            self._data = json.load(inf)

    def save(self):
        with open(self._filename, 'w') as outf:
            json.dump(self._data, outf)

    def __repr__(self):
        return '<{}({!r}, {})>'.format(
            type(self).__name__,
            self._filename, self._data)

    def __len__(self):           return len(self._data)
    def __iter__(self):          return iter(self._data)
    def __getitem__(self, item): return self._data[item]

    def __delitem__(self, item):
        del self._data[item]
        self.save()

    def __setitem__(self, item, value):
        self._data[item] = value
        self.save()

This will load from the given filename, or if that fails start with an initial dictionary. You can add additional key-value pairs as keyword arguments. Any change made to it will automatically be saved as JSON to the given filename:

>>> data = JSONBackedMapping('data.json')
>>> data
<JSONBackedMapping('data.json', {})>
>>> data['foo'] = 'bar'
>>> data['spam'] = ['eggs', 'ham']
>>> print open('data.json').read()
{"foo": "bar", "spam": ["eggs", "ham"]}
>>> del data
>>> data = JSONBackedMapping('data.json')
>>> data
<JSONBackedMapping('data.json', {u'foo': u'bar', u'spam': [u'eggs', u'ham']})>

Upvotes: 4

Related Questions