2019-06-10 09:59:16 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2019-10-29 15:30:01 +00:00
|
|
|
from benedict.utils import dict_util
|
|
|
|
from six import string_types
|
2019-06-10 09:59:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
class KeypathDict(dict):
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
2019-10-29 15:30:01 +00:00
|
|
|
self._keypath_separator = kwargs.pop('keypath_separator', '.')
|
2019-06-10 09:59:16 +00:00
|
|
|
super(KeypathDict, self).__init__(*args, **kwargs)
|
2019-10-29 15:30:01 +00:00
|
|
|
self._check_keypath_separator_in_keys(self)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def keypath_separator(self):
|
|
|
|
return self._keypath_separator
|
|
|
|
|
|
|
|
@keypath_separator.setter
|
|
|
|
def keypath_separator(self, value):
|
|
|
|
self._keypath_separator = value
|
|
|
|
self._check_keypath_separator_in_keys(self)
|
|
|
|
|
|
|
|
def _check_keypath_separator_in_keys(self, d):
|
|
|
|
sep = self._keypath_separator
|
|
|
|
if not isinstance(d, dict) or not sep:
|
|
|
|
return
|
|
|
|
|
|
|
|
def check_key(parent, key, value):
|
|
|
|
if key and isinstance(key, string_types) and sep in key:
|
|
|
|
raise ValueError(
|
|
|
|
'keys should not contain keypath separator '
|
|
|
|
'\'{}\', found: \'{}\'.'.format(sep, key))
|
|
|
|
dict_util.traverse(d, check_key)
|
|
|
|
|
|
|
|
def _goto_keys(self, keys):
|
|
|
|
result = (None, None, None, )
|
|
|
|
parent = self
|
|
|
|
i = 0
|
|
|
|
j = len(keys)
|
|
|
|
while i < j:
|
|
|
|
key = keys[i]
|
|
|
|
try:
|
|
|
|
value = parent[key]
|
|
|
|
result = (parent, key, value, )
|
|
|
|
parent = value
|
|
|
|
i += 1
|
|
|
|
except (KeyError, TypeError, ) as e:
|
|
|
|
result = (None, None, None, )
|
|
|
|
break
|
|
|
|
return result
|
|
|
|
|
|
|
|
def _list_keys(self, key):
|
|
|
|
if isinstance(key, string_types):
|
|
|
|
sep = self._keypath_separator
|
|
|
|
if sep and sep in key:
|
|
|
|
return list(key.split(sep))
|
|
|
|
else:
|
|
|
|
return [key]
|
|
|
|
elif isinstance(key, (list, tuple, )):
|
|
|
|
keys = []
|
|
|
|
for key_item in key:
|
|
|
|
keys += self._list_keys(key_item)
|
|
|
|
return keys
|
|
|
|
else:
|
|
|
|
return [key]
|
2019-06-10 09:59:16 +00:00
|
|
|
|
|
|
|
def __contains__(self, key):
|
2019-10-29 15:30:01 +00:00
|
|
|
keys = self._list_keys(key)
|
2019-06-10 09:59:16 +00:00
|
|
|
if len(keys) > 1:
|
2019-10-29 15:30:01 +00:00
|
|
|
parent, key, value = self._goto_keys(keys)
|
|
|
|
if isinstance(parent, dict) and parent.__contains__(key):
|
|
|
|
return True
|
2019-07-19 08:58:38 +00:00
|
|
|
else:
|
|
|
|
return False
|
2019-06-10 09:59:16 +00:00
|
|
|
else:
|
|
|
|
return super(KeypathDict, self).__contains__(key)
|
|
|
|
|
|
|
|
def __delitem__(self, key):
|
2019-10-29 15:30:01 +00:00
|
|
|
keys = self._list_keys(key)
|
2019-06-10 09:59:16 +00:00
|
|
|
if len(keys) > 1:
|
2019-10-29 15:30:01 +00:00
|
|
|
parent, key, value = self._goto_keys(keys)
|
|
|
|
if isinstance(parent, dict):
|
|
|
|
parent.__delitem__(key)
|
2019-06-10 09:59:16 +00:00
|
|
|
else:
|
|
|
|
raise KeyError
|
|
|
|
else:
|
|
|
|
super(KeypathDict, self).__delitem__(key)
|
|
|
|
|
|
|
|
def __getitem__(self, key):
|
2019-10-29 15:30:01 +00:00
|
|
|
keys = self._list_keys(key)
|
2019-06-10 09:59:16 +00:00
|
|
|
value = None
|
|
|
|
if len(keys) > 1:
|
2019-10-29 15:30:01 +00:00
|
|
|
parent, key, value = self._goto_keys(keys)
|
|
|
|
if isinstance(parent, dict):
|
|
|
|
return parent.__getitem__(key)
|
2019-06-10 09:59:16 +00:00
|
|
|
else:
|
|
|
|
raise KeyError
|
|
|
|
else:
|
|
|
|
value = super(KeypathDict, self).__getitem__(key)
|
|
|
|
return value
|
|
|
|
|
|
|
|
def __setitem__(self, key, value):
|
2019-10-29 15:30:01 +00:00
|
|
|
self._check_keypath_separator_in_keys(value)
|
|
|
|
keys = self._list_keys(key)
|
2019-06-10 09:59:16 +00:00
|
|
|
if len(keys) > 1:
|
2019-07-19 08:58:38 +00:00
|
|
|
i = 0
|
|
|
|
j = len(keys)
|
|
|
|
item = self
|
|
|
|
while i < j:
|
|
|
|
key = keys[i]
|
|
|
|
if i < (j - 1):
|
|
|
|
if item is self:
|
|
|
|
subitem = super(KeypathDict, self).get(key, None)
|
|
|
|
else:
|
|
|
|
subitem = item.get(key, None)
|
|
|
|
if not isinstance(subitem, dict):
|
|
|
|
subitem = item[key] = {}
|
|
|
|
item = subitem
|
|
|
|
else:
|
|
|
|
item[key] = value
|
|
|
|
i += 1
|
2019-06-10 09:59:16 +00:00
|
|
|
else:
|
|
|
|
super(KeypathDict, self).__setitem__(key, value)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def fromkeys(cls, sequence, value=None):
|
|
|
|
d = KeypathDict()
|
|
|
|
for key in sequence:
|
|
|
|
d[key] = value
|
|
|
|
return d
|
|
|
|
|
|
|
|
def get(self, key, default=None):
|
2019-10-29 15:30:01 +00:00
|
|
|
keys = self._list_keys(key)
|
2019-06-10 09:59:16 +00:00
|
|
|
if len(keys) > 1:
|
2019-10-29 15:30:01 +00:00
|
|
|
parent, key, value = self._goto_keys(keys)
|
|
|
|
if isinstance(parent, dict):
|
|
|
|
return parent.get(key, default)
|
2019-06-10 09:59:16 +00:00
|
|
|
else:
|
|
|
|
return default
|
|
|
|
else:
|
|
|
|
return super(KeypathDict, self).get(key, default)
|
|
|
|
|
2019-07-19 08:58:38 +00:00
|
|
|
def pop(self, key, *args, **kwargs):
|
|
|
|
if kwargs and 'default' in kwargs:
|
|
|
|
default_arg = True
|
|
|
|
default = kwargs.get('default', None)
|
|
|
|
elif args:
|
|
|
|
default_arg = True
|
|
|
|
default = args[0]
|
|
|
|
else:
|
|
|
|
default_arg = False
|
|
|
|
default = None
|
2019-10-29 15:30:01 +00:00
|
|
|
keys = self._list_keys(key)
|
2019-06-10 09:59:16 +00:00
|
|
|
if len(keys) > 1:
|
2019-10-29 15:30:01 +00:00
|
|
|
parent, key, value = self._goto_keys(keys)
|
|
|
|
if isinstance(parent, dict):
|
2019-07-19 08:58:38 +00:00
|
|
|
if default_arg:
|
2019-10-29 15:30:01 +00:00
|
|
|
return parent.pop(key, default)
|
2019-06-10 09:59:16 +00:00
|
|
|
else:
|
2019-10-29 15:30:01 +00:00
|
|
|
return parent.pop(key)
|
2019-07-19 08:58:38 +00:00
|
|
|
else:
|
|
|
|
if default_arg:
|
2019-06-10 09:59:16 +00:00
|
|
|
return default
|
2019-07-19 08:58:38 +00:00
|
|
|
else:
|
|
|
|
raise KeyError
|
2019-06-10 09:59:16 +00:00
|
|
|
else:
|
2019-07-19 08:58:38 +00:00
|
|
|
if default_arg:
|
2019-06-10 09:59:16 +00:00
|
|
|
return super(KeypathDict, self).pop(key, default)
|
2019-07-19 08:58:38 +00:00
|
|
|
else:
|
|
|
|
return super(KeypathDict, self).pop(key)
|
2019-06-10 09:59:16 +00:00
|
|
|
|
|
|
|
def set(self, key, value):
|
2019-07-19 08:58:38 +00:00
|
|
|
self.__setitem__(key, value)
|
2019-06-10 09:59:16 +00:00
|
|
|
|
|
|
|
def setdefault(self, key, default=None):
|
|
|
|
if key not in self:
|
2019-07-19 08:58:38 +00:00
|
|
|
self.__setitem__(key, default)
|
2019-06-10 09:59:16 +00:00
|
|
|
return default
|
|
|
|
else:
|
2019-07-19 08:58:38 +00:00
|
|
|
return self.__getitem__(key)
|
2019-07-09 10:55:34 +00:00
|
|
|
|
|
|
|
def update(self, other):
|
2019-10-29 15:30:01 +00:00
|
|
|
self._check_keypath_separator_in_keys(other)
|
2019-09-06 15:37:03 +00:00
|
|
|
super(KeypathDict, self).update(other)
|