I made a class handle the preference database of my apps. The problem is that as new features are added, using the get() method on the new keys raise KeyErrors. To make this foolproof, I would like to add a repair() method, which would be called on init if the keys of the current database does not match the default keys. I was wondering if there would be an easy way to do that, and that would be reliable with various nesting levels.
#!/usr/bin/python3
import json
import os
LOCAL_DIR = os.path.dirname(os.path.realpath(__file__)) + '/'
PREFERENCES_FILE = LOCAL_DIR + "preferences.json"
PREFERENCES_DEFAULT = \
{
'Key A':
{
'sub key':
{
'value': 0,
}
},
'Key B':
{
'value': 1
},
'Key C': 2,
}
PREFERENCES_BROKEN = \
{
'Key A':
{
'sub key':
{
}
},
'Key B':
{
},
}
def copyDict(dictionnary):
# from https://writeonly.wordpress.com/2009/05/07/deepcopy-is-a-pig-for-simple-data
out = dict().fromkeys(dictionnary)
for key, value in dictionnary.items():
try:
out[key] = value.copy() # dicts, sets
except AttributeError:
try:
out[key] = value[:] # lists, tuples, strings, unicode
except TypeError:
out[key] = value # ints
return out
class Database():
def __init__(self):
if os.path.isfile(PREFERENCES_FILE) and os.stat(PREFERENCES_FILE).st_size > 0:
self.load()
if not self.verify():
self.repair()
else:
self.db = copyDict(PREFERENCES_DEFAULT)
with open(PREFERENCES_FILE, "w") as f:
f.write(json.dumps(self.db, indent=2, sort_keys=False))
print("Created preferences file")
def get(self, *keys, db=None):
if not db: db = self.db
for key in keys:
db = db[key]
return db
def load(self):
with open(PREFERENCES_FILE, "r") as f:
self.db = json.load(f)
print("Loaded preferences database")
def repair(self):
#self.save()
pass
def save(self):
with open(PREFERENCES_FILE, "w") as f:
f.write(json.dumps(self.db, indent=2, sort_keys=False))
print("Saved preferences database")
def verify(self):
db = [x for x in self.walk(self.db)]
default = [x for x in self.walk(PREFERENCES_DEFAULT)]
return db == default
def walk(self, indict, pre=None):
# From https://stackoverflow.com/a/12507546
pre = pre[:] if pre else []
if isinstance(indict, dict):
for key, value in indict.items():
if isinstance(value, dict):
for d in self.walk(value, [key] + pre):
yield d
elif isinstance(value, list) or isinstance(value, tuple):
for v in value:
for d in self.walk(v, [key] + pre):
yield d
else:
yield pre + [key]
else:
yield indict
# Working database example
if os.path.isfile(PREFERENCES_FILE):
os.remove(PREFERENCES_FILE)
db = Database()
print("# GOOD DATABASE")
print(db.get("Key A", "sub key", "value"))
print(db.get("Key B", "value"))
print(db.get("Key C"))
print()
# Broken database example
with open(PREFERENCES_FILE, "w") as f:
f.write(json.dumps(PREFERENCES_BROKEN, indent=2, sort_keys=False))
db = Database()
print("# BROKEN DATABASE")
print(db.get("Key A", "sub key", "value"))
print(db.get("Key B", "value"))
print(db.get("Key C"))
