File: mapped_sequence.py

package info (click to toggle)
python-agate 1.13.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,008 kB
  • sloc: python: 8,578; makefile: 126
file content (163 lines) | stat: -rw-r--r-- 4,303 bytes parent folder | download | duplicates (2)
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())