"""FlatDict is a dict object that allows for single level, delimited
key/value pair mapping of nested dictionaries.
"""
__version__ = '1.2.0'
[docs]class FlatDict(dict):
""":py:class:`~flatdict.FlatDict` is a dictionary object that allows for
single level, delimited key/value pair mapping of nested dictionaries.
The default delimiter value is ``:`` but can be changed in the constructor
or by calling
:py:class:`FlatDict.set_delimiter <flatdict.FlatDict.set_delimiter>`.
"""
# The default delimiter value
DELIMITER = ':'
def __init__(self, value=None, delimiter=None, former_type=dict):
super(FlatDict, self).__init__()
self._values = {}
self._delimiter = delimiter or self.DELIMITER
self.former_type = former_type
if isinstance(value, dict):
for key in value.keys():
self.__setitem__(key, value[key])
def __contains__(self, key):
if self._delimiter not in key:
return key in self._values
parent, child = key.split(self._delimiter, 1)
return parent in self._values and child in self._values[parent]
def __delitem__(self, key):
if self._delimiter not in key:
del self._values[key]
else:
parent, child = key.split(self._delimiter, 1)
if (parent in self._values and
child in self._values[parent]):
del self._values[parent][child]
if not self._values[parent]:
del self._values[parent]
def __getitem__(self, key):
if self._delimiter not in key:
return self._values[key]
parent, child = key.split(self._delimiter, 1)
if parent in self._values and child in self._values[parent]:
return self._values[parent][child]
else:
raise KeyError(key)
def __iter__(self):
for key in self.keys():
yield key
def __len__(self):
return len(self.keys())
def __repr__(self):
values = {}
for key in self.keys():
values[key] = self.__getitem__(key)
return values.__repr__()
def __setitem__(self, key, value):
former_type = type(value)
if isinstance(value, (list, tuple)):
value = dict((str(i), v) for (i, v) in enumerate(value))
if isinstance(value, dict) and not isinstance(value, FlatDict):
value = FlatDict(value, self._delimiter, former_type=former_type)
if self._delimiter in key:
parent_key, child_key = key.split(self._delimiter, 1)
if parent_key not in self._values:
self._values[parent_key] = FlatDict(delimiter=self._delimiter)
parent = self._values.get(parent_key)
if not isinstance(parent, FlatDict):
raise TypeError(
'Top level node is not a FlatDict: {0}'.format(
parent_key, type(self._values[parent_key])))
self._values[parent_key][child_key] = value
else:
self._values[key] = value
def __str__(self):
values = {}
for key in self.keys():
values[key] = self.__getitem__(key)
return values.__str__()
def _key(self, parent, child):
return self._delimiter.join([parent, child])
[docs] def as_dict(self):
"""Return the flat dictionary as a dictionary.
:rtype: dict
"""
dict_out = {}
for key in self._values.keys():
value = self._values[key]
if isinstance(value, FlatDict):
if value.former_type == list:
dict_out[key] = [v for k, v in sorted(value.items())]
pass
elif value.former_type == tuple:
dict_out[key] = tuple(v for k, v in sorted(value.items()))
pass
elif value.former_type == dict:
dict_out[key] = value.as_dict()
else:
dict_out[key] = value
return dict_out
[docs] def clear(self):
"""Remove all items from the flat dictionary."""
self._values.clear()
[docs] def copy(self):
"""Return a shallow copy of the flat dictionary.
:rtype: flatdict.FlatDict
"""
values = {}
for key in self.keys():
values[key] = self.__getitem__(key)
return values
[docs] def get(self, key, d=None):
"""Return the value for key if key is in the flat dictionary, else
default. If default is not given, it defaults to ``None``, so that this
method never raises a ``KeyError``.
:param mixed key: The key to get
:param mixed d: The default value
:rtype: mixed
"""
if key not in self.keys():
return self._values.get(key, d)
return self.__getitem__(key)
[docs] def has_key(self, key):
"""Check to see if the flat dictionary has a specific key.
:param mixed key: The key to check for
:rtype: bool
"""
return key in self.keys()
[docs] def items(self):
"""Return a copy of the flat dictionary's list of ``(key, value)``
pairs.
.. note:: CPython implementation detail: Keys and values are listed in \
an arbitrary order which is non-random, varies across Python \
implementations, and depends on the flat dictionary's history of \
insertions and deletions.
:rtype: list
"""
items = list()
for key in self.keys():
items.append((key, self.__getitem__(key)))
return items
[docs] def iteritems(self):
"""Return an iterator over the flat dictionary's (key, value) pairs.
See the note for :py:class:`FlatDict.items() <flatdict.FlatDict.items>`.
Using ``iteritems()`` while adding or deleting entries in the flat
dictionary may raise a ``RuntimeError`` or fail to iterate over all
entries.
:rtype: Iterator
:raises: RuntimeError
"""
for item in self.items():
yield item
[docs] def iterkeys(self):
"""Return an iterator over the flat dictionary's keys. See the note for
:py:class:`FlatDict.items() <flatdict.FlatDict.items>`.
Using ``iterkeys()`` while adding or deleting entries in the flat
dictionary may raise a ``RuntimeError`` or fail to iterate over all
entries.
:rtype: Iterator
:raises: RuntimeError
"""
for key in self.keys():
yield key
[docs] def itervalues(self):
"""Return an iterator over the flat dictionary's values. See the note
for :py:class:`FlatDict.items() <flatdict.FlatDict.items>`.
Using ``itervalues()`` while adding or deleting entries in the flat
dictionary may raise a ``RuntimeError`` or fail to iterate over all
entries.
:rtype: Iterator
:raises: RuntimeError
"""
for key in self.keys():
yield self.__getitem__(key)
[docs] def keys(self):
"""Return a copy of the flat dictionary's list of keys. See the note for
:py:class:`FlatDict.items() <flatdict.FlatDict.items>`.
:rtype: list
"""
keys = list()
for key in self._values.keys():
if isinstance(self._values[key], FlatDict):
child_keys = self._values[key].keys()
for child in child_keys:
keys.append(self._key(key, child))
else:
keys.append(key)
return keys
[docs] def pop(self, key, default=None):
"""If key is in the flat dictionary, remove it and return its value,
else return default. If default is not given and key is not in the
dictionary, a ``KeyError`` is raised.
:param mixed key: The key name
:param mixed default: The default value
:rtype: mixed
"""
if key not in self.keys() and key not in self._values:
return default
if key in self._values:
return self._values.pop(key, default)
value = self.__getitem__(key)
self.__delitem__(key)
return value
[docs] def setdefault(self, key, default):
""" If key is in the flat dictionary, return its value. If not,
insert key with a value of default and return default.
default defaults to ``None``.
:param mixed key: The key name
:param mixed default: The default value
:rtype: mixed
"""
if key not in self:
self.__setitem__(key, default)
return self.__getitem__(key)
[docs] def set_delimiter(self, delimiter):
"""Override the default or passed in delimiter with a new value.
:param str delimiter: The delimiter to use
"""
self._delimiter = delimiter
for key in self._values.keys():
if isinstance(self._values[key], FlatDict):
self._values[key].set_delimiter(delimiter)
[docs] def update(self, other=None, **kwargs):
"""Update the flat dictionary with the key/value pairs from other,
overwriting existing keys.
``update()`` accepts either another flat dictionary object or an
iterable of key/value pairs (as tuples or other iterables of length
two). If keyword arguments are specified, the flat dictionary is then
updated with those key/value pairs: ``d.update(red=1, blue=2)``.
:rtype: None
"""
values = other or kwargs
if values:
for key in values:
self.__setitem__(key, values[key])
[docs] def values(self):
"""Return a copy of the flat dictionary's list of values. See the note
for :py:class:`FlatDict.items() <flatdict.FlatDict.items>`.
:rtype: list
"""
values = list()
for key in self.keys():
values.append(self.__getitem__(key))
return values