I am writing a class that acts as a dictionary but saves its content to a json file each time a modification is made to ensure a synchronous state.
However I stumbled upon a special case that breaks my synchronization : when appending a value to a list inside the dictionary.
Since this uses __getitem__
, how can I make sure that if the item returned is modified, I save it to the JSON file ?
Here is a fully functional code snippet (Python 3.9.2) to illustrate what I mean
import json
class SyncDictJSON(dict):
__instances: dict = {}
@classmethod
def create(cls, filepath: str, **kwargs):
if filepath not in SyncDictJSON.__instances:
SyncDictJSON.__instances[filepath] = cls(filepath, **kwargs)
return SyncDictJSON.__instances[filepath]
def __init__(self, filepath: str, **kwargs):
super().__init__(**kwargs)
self.filepath = filepath
self.update(SyncDictJSON.read_data_from_filename(self.filepath))
def __getitem__(self, item):
print(f"getitem {item}")
return super(SyncDictJSON, self).__getitem__(item)
def __setitem__(self, key, value):
print(f"set item {key},{value}")
super().__setitem__(key, value)
SyncDictJSON.write_data_to_filename(self, self.filepath)
def __delitem__(self, key):
super().__delitem__(key)
SyncDictJSON.write_data_to_filename(self, self.filepath)
@staticmethod
def write_data_to_filename(data, filepath: str):
with open(filepath, "w", encoding="utf-8") as file:
json.dump(data, file, indent=2, ensure_ascii=False)
@staticmethod
def read_data_from_filename(filename: str):
with open(filename, "r", encoding="utf-8") as file:
return json.load(file)
@classmethod
def from_file(cls, filepath):
return cls(filepath)
if __name__ == '__main__':
with open("testing.json", "w") as file:
file.write("{}")
dico = SyncDictJSON.create("testing.json")
dico["a_list"] = []
dico["a_list"].append(5)
print(dico) # {'a_list': [5]} but testing.json will be empty
The SyncDictJSON
object will record changes to the dictionary. However, once
the list for a_list
is created, appending to it will not change the dictionary;
the dictionary will contain a reference to a list [5]
, which is still the same
reference as when that list was empty.
To record appending to the list, too, you could use the following code, which
wraps the class list
into a similar wrapper. It relies on a reference to the
synchronized dictionary being passed down to the list, so it will require a
little extra space. If you're planning to delete list items and/or insert them,
you would need to overwrite those methods in SyncList
, too.
import json
class SyncList(list):
def __init__(self, container, *args, **kwargs):
print('list init', *args, **kwargs)
self._container = container
super().__init__(*args, **kwargs)
def append(self, x):
print('list.append', self, x)
super().append(x)
self._container.write_data_to_filename(
self._container, self._container.filepath)
class SyncDictJSON(dict):
__instances: dict = {}
@classmethod
def create(cls, filepath: str, **kwargs):
if filepath not in SyncDictJSON.__instances:
SyncDictJSON.__instances[filepath] = cls(filepath, **kwargs)
return SyncDictJSON.__instances[filepath]
def __init__(self, filepath: str, **kwargs):
super().__init__(**kwargs)
self.filepath = filepath
self.update(SyncDictJSON.read_data_from_filename(self.filepath))
def __getitem__(self, item):
print(f"getitem {item}")
return super(SyncDictJSON, self).__getitem__(item)
def __setitem__(self, key, value):
print(f"set item {key},{value}")
super().__setitem__(key, value)
SyncDictJSON.write_data_to_filename(self, self.filepath)
def __delitem__(self, key):
super().__delitem__(key)
SyncDictJSON.write_data_to_filename(self, self.filepath)
@staticmethod
def write_data_to_filename(data, filepath: str):
with open(filepath, "w", encoding="utf-8") as file:
json.dump(data, file, indent=2, ensure_ascii=False)
@staticmethod
def read_data_from_filename(filename: str):
with open(filename, "r", encoding="utf-8") as file:
return json.load(file)
@classmethod
def from_file(cls, filepath):
return cls(filepath)
if __name__ == '__main__':
with open("testing.json", "w") as file:
file.write("{}")
dico = SyncDictJSON.create("testing.json")
dico["a_list"] = SyncList(dico, [])
dico["a_list"].append(5)
print(dico) # {'a_list': [5]}; testing.json will have the same
# list init []
# set item a_list,[]
# getitem a_list
# list.append [] 5
# {'a_list': [5]}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With