Detect value change in custom MutableMapping class with unkown depth - python

I'm implementing a wrapper for a config file (json) with the abstract base class MutableMapping. The code is shown below:
import collections
import json
class ConfigWrapper(collections.MutableMapping):
def __init__(self, file_path):
self._data = None
self.file_path = file_path
self._read()
def __getitem__(self, key):
return self._data[key]
def __setitem__(self, key, value):
if self._data[key] != value:
self._data[key] = value
self._commit()
def __delitem__(self, key):
del(self._data[key])
def __iter__(self):
return iter(self._data)
def __len__(self):
return len(self._data)
def _read(self):
with open(self.file_path) as file:
self._data = json.load(file)
def _commit(self):
with open(self.file_path, "w") as file:
json.dump(self._data, file, indent=4)
I want my wrapper to automatically save changes to the config file if __setitem__ is invoked:
config = ConfigWrapper("afile.json")
config["key"] = "value" # first case
config["section"]["key"] = "value" # second case
The first case works just fine. The second case however doesn't trigger _commit() because the __setitem__ method of the standard dict _data is used.
The json file may contain nested dicts with unknown depth.
Question: How would I solve this problem even if wanted to commit a change in say config["a"]["b"]["c"]["d"] = "value"?
Thanks in advance.

Related

How to change a variable in if condition of base class with super() method?

I have two classes with similar methods but there is a bit of difference in variables, here are the two classes:-
First class
class SerializerOne(object):
def validate(self, data):
instance = self.instance or self.Meta.model(**data)
instance.full_clean()
if data.get('certificate') and data.get('private_key'):
data = get_import_data(instance)
return data
def validate_validity_start(self, value):
if value is None:
value = default_validity_start()
return value
def validate_validity_end(self, value):
if value is None:
value = default_ca_validity_end()
return value
Second class
class SerializerTwo(object):
def validate(self, data):
instance = self.instance or self.Meta.model(**data)
instance.full_clean()
if data.get('certificate') and data.get('private_key'):
data = get_import_data(instance)
data.update({'ca': instance.ca})
return data
def validate_validity_start(self, value):
if value is None:
value = default_validity_start()
return value
def validate_validity_end(self, value):
if value is None:
value = default_cert_validity_end()
return value
Now this is what I have tried to keep the similar methods in a base class:-
Base class
class BaseSerializer(object):
def validate(self, data):
instance = self.instance or self.Meta.model(**data)
instance.full_clean()
if data.get('certificate') and data.get('private_key'):
data = get_import_data(instance)
return data
def validate_validity_start(self, value):
if value is None:
value = default_validity_start()
return value
def validate_validity_end(self, value):
if value is None:
value = default_ca_validity_end()
return value
When I inherit the above BaseSerializer
class SerializerOne(BaseSerializer):
pass
class SerializerTwo(BaseSerializer):
# Now in this class how can I add `data.update({'ca': instance.ca})` in the
# `validate` method, and also there is a bit of change in the
# method `validate_validity_end`,
I found that it can be done with super(), but I couldn't achieve it.
I'd redefine how you factor out the common methods a bit:
class BaseSerializer:
def get_import_data_hook(self, instance):
return get_import_data(instance)
def validate(self, data):
instance = self.instance or self.Meta.model(**data)
instance.full_clean()
if data.get('certificate') and data.get('private_key'):
data = self.get_import_data_hook(instance)
return data
def default_validity_start_hook(self):
return default_validity_start()
def validate_validity_start(self, value):
if value is None:
value = default_validity_start_hook()
return value
def default_validity_end_hook(self):
return default_ca_validity_end()
def validate_validity_end(self, value):
if value is None:
value = self.default_validity_end_hook()
return value
Any time you find yourself reusing all the code except a small tweak, put the tweak in its own hook. You don't have to put "hook" in the name, I just did that for emphasis. Instead, document it clearly.
Now you can do something like
class SerializerOne(BaseSerializer):
pass
and
class SerializerTwo(BaseSerializer):
def get_import_data_hook(self, instance):
data = super().get_import_data_hook(instance)
data.update({'ca': instance.ca})
return data
def default_validity_end_hook(self):
return default_cert_validity_end()
Right now, making a separate hook for default_validity_start_hook seems superfluous, but I added it for consistency. You may also want to look into the functions get_import_data, default_validity_start, and default_c*_validity_end to see if they belong directly in your class structure. It would certainly make the part of the code you show a lot simpler.

How to correctly override methods keys(), items(), __iter__ when overriding a dict?

I am trying to make an enhancement in dict which supports file operation in particular folder in a way it's done in a dict.
Following is an example
if __name__ == "__main__":
d = FolderDict("LALALA", create_new=True)
d["File1"] = "Content of file 1"
d["File2"] = "Content of file 2"
It creates two files in LALALA folder with correct content.
I could handle get,set,contains,del,pop methods correctly but I can't seem to wrap my hand around keys(),values(),iter methods.
I want all the file names to be printed when I call keys(), and when I iter on it I want
k - Name of file
v - content of file
Following my code, Please have a look and help.
import os
class FolderDict(dict):
def get_absolute_path(self, file_name):
return os.path.join(self.folder_path, file_name)
def __init__(self, folder_path, create_new=False):
super().__init__()
self.folder_path = folder_path
if os.path.exists(self.folder_path) and os.path.isdir(self.folder_path):
for file_name in os.listdir(self.folder_path):
with open(self.get_absolute_path(file_name), "r") as reader:
self[file_name] = reader.read()
else:
if create_new:
os.makedirs(folder_path, exist_ok=True)
else:
raise FileNotFoundError("[Errno 2] No such file or directory: '{}'".format(self.folder_path))
def __getitem__(self, item):
with open(self.get_absolute_path(item), "r") as reader:
return reader.read()
def __setitem__(self, key, value):
"""
Be careful,it will overwrite the existing file.
We support w mode not a mode now
"""
with open(self.get_absolute_path(key), "w") as writer:
writer.write(value)
def keys(self):
yield from super().keys()
def __iter__(self):
yield from self.keys()
def __contains__(self, item):
return os.path.exists(self.get_absolute_path(item))
def __missing__(self, key):
raise FileNotFoundError("[Errno 2] No such file or directory: '{}'".format(self.get_absolute_path(key)))
def __delitem__(self, key):
os.remove(self.get_absolute_path(key))
def get(self, k, default=None):
return self[k] if k in self else default
def pop(self, k):
content = self[k]
os.remove(self.get_absolute_path(k))
return content
def __repr__(self):
return "FolderDict for folder {}".format(self.folder_path)
__str__ = __repr__
if __name__ == "__main__":
d = FolderDict("LALALA", create_new=True)
d["File1"] = "Content of file 1"
d["File2"] = "Content of file 2"
d["File3"] = "Content of file 3"
for k,v in d:
print(k)
you didn't even save the information of filenames,so your keys() function doesn't work
Inherit class UserDict instead of inheriting class dict
I can't see what's the meaning of inherit class like dict/UserDict ....
To use FolderDict like real dict ,what you need to to is write __iter__ __next__ __getitem__ __setitem__ __next__ __getattribute__ __delitem__ keys items values ...... in your class.
Code:
import os
from collections import UserDict
class FolderDict(UserDict):
def get_absolute_path(self, file_name):
return os.path.join(self.folder_path, file_name)
def __init__(self, folder_path, create_new=False):
super().__init__()
self._dict = {}
self.folder_path = folder_path
if os.path.exists(self.folder_path) and os.path.isdir(self.folder_path):
for file_name in os.listdir(self.folder_path):
with open(self.get_absolute_path(file_name), "r") as reader:
self[file_name] = reader.read()
else:
if create_new:
os.makedirs(folder_path, exist_ok=True)
else:
raise FileNotFoundError("[Errno 2] No such file or directory: '{}'".format(self.folder_path))
def __getitem__(self, item):
with open(self.get_absolute_path(item), "r") as reader:
return reader.read()
def __setitem__(self, key, value):
"""
Be careful,it will overwrite the existing file.
We support w mode not a mode now
"""
with open(self.get_absolute_path(key), "w") as writer:
writer.write(value)
self._dict[key] = value
def keys(self):
return self._dict.keys()
def __iter__(self):
return iter(self._dict.items())
def __contains__(self, item):
return os.path.exists(self.get_absolute_path(item))
def __missing__(self, key):
raise FileNotFoundError("[Errno 2] No such file or directory: '{}'".format(self.get_absolute_path(key)))
def __delitem__(self, key):
os.remove(self.get_absolute_path(key))
def get(self, k, default=None):
return self[k] if k in self else default
def pop(self, k):
content = self[k]
os.remove(self.get_absolute_path(k))
return content
def __repr__(self):
return "FolderDict for folder {}".format(self.folder_path)
__str__ = __repr__
if __name__ == "__main__":
d = FolderDict("LALALA", create_new=True)
d["File1"] = "Content of file 1"
d["File2"] = "Content of file 2"
d["File3"] = "Content of file 3"
for k,v in d:
print(k,v)
for k in d.keys():
print(k)

Unit testing classes that implement a collection Interface?

I've written a storage interface that can use different storage backends. As a demonstration I've written an implementation that stores objects as a key/value pair in a standard Python dictionary.
class MyStorageInterface(object):
def store(self, key, value):
raise NotImplementedError("Class must be subclassed")
def get(self, key):
raise NotImplementedError("Class must be subclassed")
# Example implementation
class DictStorage(MyStorageInterface):
def __init__(self):
self._cache = dict()
def store(self, key, value):
self._cache[key] = value
def get(self, key):
return self._cache[key]
Now, I want to write some unit tests for my DictStorage implementation. My question is whether or not something like:
storage = DictStorage()
value = 8
key = 'foo'
storage.store(key, value)
actual = storage.get(key)
assert actual == 8, "cache value for %s is %s, expected %s" % (key, str(actual), str(value))
is a suitable unit test for the get method or whether or not there is a Pythonic pattern for testing classes that implement collection type objects.
I don't know if there's anything "collection-specific" but the structure of your test looks good to me. The only difference is that I'd make use of the unittest package and define it as test case:
import unittest
class MyStorageInterface(object):
def store(self, key, value):
raise NotImplementedError("Class must be subclassed")
def get(self, key):
raise NotImplementedError("Class must be subclassed")
class DictStorage(MyStorageInterface):
def __init__(self):
self._cache = dict()
def store(self, key, value):
self._cache[key] = value
def get(self, key):
return self._cache[key]
class DictStorageTests(unittest.TestCase):
def setUp(self):
self._sut = DictStorage()
def test_storing_and_retrieving_value(self):
value_in = 8
key = 'foo'
self._sut.store(key, value_in)
value_out = self._sut.get(key)
self.assertEqual(value_in, value_out)
if __name__ == '__main__':
unittest.main()
Output
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK

How to clear instance data without setattr?

I wanted, to make traversable (by DB, single file or just as string) class in python. I Write this (shorted):
from json import JSONDecoder, JSONEncoder
def json_decode(object): return JSONDecoder().decode(object)
def json_encode(object): return JSONEncoder().encode(object)
class Storage:
__separator__ = 'ANY OF ANYS'
__keys__ = []
__vals__ = []
__slots__ = ('__keys__', '__vals__', '__separator__')
def __getattr__(self, key):
try:
return self.__vals__[self.__keys__.index(key)]
except IndexError:
raise AttributeError
def __setattr__(self, key, val):
self.__keys__.append(key)
self.__vals__.append(val)
def store(self):
return (json_encode(self.__keys__) + self.__separator__ +
json_encode(self.__vals__))
def restore(self, stored):
stored = stored.split(self.__separator__)
for (key, val) in zip(json_decode(stored[0]), json_decode(stored[1])):
setattr(self, key, val)
And yea - that work, but... When i'm making more instances, all of them are like singleton.
So - how to set attribute to instance without _setattr_?
PS. I got idea - make in set/getattr an pass for keys/vals, but it'll make mess.
your __separator__, __keys__, __vals__ and __slots__ are attributes of the object "Storage"(class object). I don't know if it's exactly the same, but I'd call it static variables of the class.
If you want to have different values for each instance of Storage, define each of these variables in your __init__ function:
class Storage(object):
__slots__ = ('__keys__', '__vals__', '__separator__')
def __init__(self):
super(Storage, self).__setattr__('__separator__', "ANY OF ANYS")
super(Storage, self).__setattr__('__keys__', [])
super(Storage, self).__setattr__('__vals__', [])
def __getattr__(self, key):
try:
vals = getattr(self, '__vals__')
keys = getattr(self, '__keys__')
return vals[keys.index(key)]
except IndexError:
raise AttributeError
def __setattr__(self, key, val):
vals = getattr(self, '__vals__')
keys = getattr(self, '__keys__')
vals.append(val)
keys.append(key)
edited so getattr and setattr works
I got that problem 2 days ago. Don't know if that's exactly your problem, but you said that about "its like I have a singleton"
You could make your Storage class a subclass of a special base class like this:
class Singleton(object):
def __new__(cls, *args, **kwargs):
if '_inst_' not in vars(cls):
cls._inst = type.__new__(cls, *args, *kwargs)
return cls._inst
class Storage(Singleton):
....
As long as you don't override __new__() in your subclass, all subsequent calls to create new instances after the first will return the one first created.

How to create a persistant class using pickle in Python

New to python...
I have the following class Key, that extends dict:
class Key( dict ):
def __init__( self ):
self = { some dictionary stuff... }
def __getstate__(self):
state = self.__dict__.copy()
return state
def __setstate__(self, state):
self.__dict__.update( state )
I want to save an instance of the class with its data using pickle.dump and then retrieve the data using pickle.load. I understand that I am supposed to somehow change the getstate and the setstate, however, am not entirely clear on how I am supposed to do that... any help would be greatly appreciated!
I wrote a subclass of dict that does this here it is.
class AttrDict(dict):
"""A dictionary with attribute-style access. It maps attribute access to
the real dictionary. """
def __init__(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs)
def __getstate__(self):
return self.__dict__.items()
def __setstate__(self, items):
for key, val in items:
self.__dict__[key] = val
def __repr__(self):
return "%s(%s)" % (self.__class__.__name__, dict.__repr__(self))
def __setitem__(self, key, value):
return super(AttrDict, self).__setitem__(key, value)
def __getitem__(self, name):
return super(AttrDict, self).__getitem__(name)
def __delitem__(self, name):
return super(AttrDict, self).__delitem__(name)
__getattr__ = __getitem__
__setattr__ = __setitem__
def copy(self):
return AttrDict(self)
It basically converts the state to a basic tuple, and takes that back again to unpickle.
But be aware that you have to have to original source file available to unpickle. The pickling does not actually save the class itself, only the instance state. Python will need the original class definition to re-create from.

Categories

Resources