1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
|
"""
This module contains the :class:`MappedSequence` class that forms the foundation
for agate's :class:`.Row` and :class:`.Column` as well as for named sequences of
rows and columns.
"""
from collections import OrderedDict
from collections.abc import Sequence
from agate.utils import memoize
class MappedSequence(Sequence):
"""
A generic container for immutable data that can be accessed either by
numeric index or by key. This is similar to an
:class:`collections.OrderedDict` except that the keys are optional and
iteration over it returns the values instead of keys.
This is the base class for both :class:`.Column` and :class:`.Row`.
:param values:
A sequence of values.
:param keys:
A sequence of keys.
"""
__slots__ = ['_values', '_keys']
def __init__(self, values, keys=None):
self._values = tuple(values)
if keys is not None:
self._keys = keys
else:
self._keys = None
def __getstate__(self):
"""
Return state values to be pickled.
This is necessary on Python2.7 when using :code:`__slots__`.
"""
return {
'_values': self._values,
'_keys': self._keys
}
def __setstate__(self, data):
"""
Restore pickled state.
This is necessary on Python2.7 when using :code:`__slots__`.
"""
self._values = data['_values']
self._keys = data['_keys']
def __unicode__(self):
"""
Print a unicode sample of the contents of this sequence.
"""
sample = ', '.join(repr(d) for d in self.values()[:5])
if len(self) > 5:
sample = '%s, ...' % sample
return f'<agate.{type(self).__name__}: ({sample})>'
def __str__(self):
"""
Print an ascii sample of the contents of this sequence.
"""
return str(self.__unicode__())
def __repr__(self):
return self.__str__()
def __getitem__(self, key):
"""
Retrieve values from this array by index, slice or key.
"""
if isinstance(key, slice):
indices = range(*key.indices(len(self)))
values = self.values()
return tuple(values[i] for i in indices)
# Note: can't use isinstance because bool is a subclass of int
elif type(key) is int:
return self.values()[key]
return self.dict()[key]
def __setitem__(self, key, value):
"""
Set values by index, which we want to fail loudly.
"""
raise TypeError('Rows and columns can not be modified directly. You probably need to compute a new column.')
def __iter__(self):
"""
Iterate over values.
"""
return iter(self.values())
@memoize
def __len__(self):
return len(self.values())
def __eq__(self, other):
"""
Equality test with other sequences.
"""
if not isinstance(other, Sequence):
return False
return self.values() == tuple(other)
def __ne__(self, other):
"""
Inequality test with other sequences.
"""
return not self.__eq__(other)
def __contains__(self, value):
return self.values().__contains__(value)
def keys(self):
"""
Equivalent to :meth:`collections.OrderedDict.keys`.
"""
return self._keys
def values(self):
"""
Equivalent to :meth:`collections.OrderedDict.values`.
"""
return self._values
@memoize
def items(self):
"""
Equivalent to :meth:`collections.OrderedDict.items`.
"""
return tuple(zip(self.keys(), self.values()))
def get(self, key, default=None):
"""
Equivalent to :meth:`collections.OrderedDict.get`.
"""
try:
return self.dict()[key]
except KeyError:
if default:
return default
return None
@memoize
def dict(self):
"""
Retrieve the contents of this sequence as an
:class:`collections.OrderedDict`.
"""
if self.keys() is None:
raise KeyError
return OrderedDict(self.items())
|