• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

earwig / mwparserfromhell / 6080278913

05 Sep 2023 04:44AM UTC coverage: 99.136% (+0.04%) from 99.098%
6080278913

push

github

earwig
Fix pickling SmartLists (fixes #289)

23 of 23 new or added lines in 4 files covered. (100.0%)

2982 of 3008 relevant lines covered (99.14%)

6.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.8
/src/mwparserfromhell/smart_list/list_proxy.py
1
# Copyright (C) 2012-2023 Ben Kurtovic <ben.kurtovic@gmail.com>
2
# Copyright (C) 2019-2020 Yuri Astrakhan <YuriAstrakhan@gmail.com>
3
#
4
# Permission is hereby granted, free of charge, to any person obtaining a copy
5
# of this software and associated documentation files (the "Software"), to deal
6
# in the Software without restriction, including without limitation the rights
7
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
# copies of the Software, and to permit persons to whom the Software is
9
# furnished to do so, subject to the following conditions:
10
#
11
# The above copyright notice and this permission notice shall be included in
12
# all copies or substantial portions of the Software.
13
#
14
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
# SOFTWARE.
21

22
import weakref
7✔
23

24
from .utils import _SliceNormalizerMixIn, inheritdoc
7✔
25

26

27
class ListProxy(_SliceNormalizerMixIn, list):
7✔
28
    """Implement the ``list`` interface by getting elements from a parent.
29

30
    This is created by a :class:`.SmartList` object when slicing. It does not
31
    actually store the list at any time; instead, whenever the list is needed,
32
    it builds it dynamically using the :meth:`_render` method.
33
    """
34

35
    __slots__ = ("__weakref__", "_parent", "_sliceinfo")
7✔
36

37
    def __init__(self, parent, sliceinfo):
7✔
38
        super().__init__()
7✔
39
        self._parent = parent
7✔
40
        self._sliceinfo = sliceinfo
7✔
41

42
    def __reduce_ex__(self, protocol: int) -> tuple:
7✔
43
        return (ListProxy, (self._parent, self._sliceinfo), ())
7✔
44

45
    def __setstate__(self, state: tuple) -> None:
7✔
46
        # Reregister with the parent
47
        child_ref = weakref.ref(self, self._parent._delete_child)
7✔
48
        self._parent._children[id(child_ref)] = (child_ref, self._sliceinfo)
7✔
49

50
    def __repr__(self):
7✔
51
        return repr(self._render())
7✔
52

53
    def __lt__(self, other):
7✔
54
        if isinstance(other, ListProxy):
7✔
55
            return self._render() < list(other)
7✔
56
        return self._render() < other
7✔
57

58
    def __le__(self, other):
7✔
59
        if isinstance(other, ListProxy):
7✔
60
            return self._render() <= list(other)
7✔
61
        return self._render() <= other
7✔
62

63
    def __eq__(self, other):
7✔
64
        if isinstance(other, ListProxy):
7✔
65
            return self._render() == list(other)
7✔
66
        return self._render() == other
7✔
67

68
    def __ne__(self, other):
7✔
69
        if isinstance(other, ListProxy):
7✔
70
            return self._render() != list(other)
7✔
71
        return self._render() != other
7✔
72

73
    def __gt__(self, other):
7✔
74
        if isinstance(other, ListProxy):
7✔
75
            return self._render() > list(other)
×
76
        return self._render() > other
7✔
77

78
    def __ge__(self, other):
7✔
79
        if isinstance(other, ListProxy):
7✔
80
            return self._render() >= list(other)
×
81
        return self._render() >= other
7✔
82

83
    def __bool__(self):
7✔
84
        return bool(self._render())
7✔
85

86
    def __len__(self):
7✔
87
        return max((self._stop - self._start) // self._step, 0)
7✔
88

89
    def __getitem__(self, key):
7✔
90
        if isinstance(key, slice):
7✔
91
            key = self._normalize_slice(key, clamp=True)
7✔
92
            keystart = min(self._start + key.start, self._stop)
7✔
93
            keystop = min(self._start + key.stop, self._stop)
7✔
94
            adjusted = slice(keystart, keystop, key.step)
7✔
95
            return self._parent[adjusted]
7✔
96
        return self._render()[key]
7✔
97

98
    def __setitem__(self, key, item):
7✔
99
        if isinstance(key, slice):
7✔
100
            key = self._normalize_slice(key, clamp=True)
7✔
101
            keystart = min(self._start + key.start, self._stop)
7✔
102
            keystop = min(self._start + key.stop, self._stop)
7✔
103
            adjusted = slice(keystart, keystop, key.step)
7✔
104
            self._parent[adjusted] = item
7✔
105
        else:
106
            length = len(self)
7✔
107
            if key < 0:
7✔
108
                key = length + key
7✔
109
            if key < 0 or key >= length:
7✔
110
                raise IndexError("list assignment index out of range")
7✔
111
            self._parent[self._start + key] = item
7✔
112

113
    def __delitem__(self, key):
7✔
114
        if isinstance(key, slice):
7✔
115
            key = self._normalize_slice(key, clamp=True)
7✔
116
            keystart = min(self._start + key.start, self._stop)
7✔
117
            keystop = min(self._start + key.stop, self._stop)
7✔
118
            adjusted = slice(keystart, keystop, key.step)
7✔
119
            del self._parent[adjusted]
7✔
120
        else:
121
            length = len(self)
7✔
122
            if key < 0:
7✔
123
                key = length + key
7✔
124
            if key < 0 or key >= length:
7✔
125
                raise IndexError("list assignment index out of range")
7✔
126
            del self._parent[self._start + key]
7✔
127

128
    def __iter__(self):
7✔
129
        i = self._start
7✔
130
        while i < self._stop:
7✔
131
            yield self._parent[i]
7✔
132
            i += self._step
7✔
133

134
    def __reversed__(self):
7✔
135
        i = self._stop - 1
7✔
136
        while i >= self._start:
7✔
137
            yield self._parent[i]
7✔
138
            i -= self._step
7✔
139

140
    def __contains__(self, item):
7✔
141
        return item in self._render()
7✔
142

143
    def __add__(self, other):
7✔
144
        return type(self._parent)(list(self) + other)
7✔
145

146
    def __radd__(self, other):
7✔
147
        return type(self._parent)(other + list(self))
7✔
148

149
    def __iadd__(self, other):
7✔
150
        self.extend(other)
7✔
151
        return self
7✔
152

153
    def __mul__(self, other):
7✔
154
        return type(self._parent)(list(self) * other)
7✔
155

156
    def __rmul__(self, other):
7✔
157
        return type(self._parent)(other * list(self))
7✔
158

159
    def __imul__(self, other):
7✔
160
        self.extend(list(self) * (other - 1))
7✔
161
        return self
7✔
162

163
    @property
7✔
164
    def _start(self):
6✔
165
        """The starting index of this list, inclusive."""
166
        return self._sliceinfo[0]
7✔
167

168
    @property
7✔
169
    def _stop(self):
6✔
170
        """The ending index of this list, exclusive."""
171
        if self._sliceinfo[1] is None:
7✔
172
            return len(self._parent)
7✔
173
        return self._sliceinfo[1]
7✔
174

175
    @property
7✔
176
    def _step(self):
6✔
177
        """The number to increase the index by between items."""
178
        return self._sliceinfo[2]
7✔
179

180
    def _render(self):
7✔
181
        """Return the actual list from the stored start/stop/step."""
182
        return list(self._parent)[self._start : self._stop : self._step]
7✔
183

184
    @inheritdoc
7✔
185
    def append(self, item):
6✔
186
        self._parent.insert(self._stop, item)
7✔
187

188
    @inheritdoc
7✔
189
    def count(self, item):
6✔
190
        return self._render().count(item)
7✔
191

192
    @inheritdoc
7✔
193
    def index(self, item, start=None, stop=None):
7✔
194
        if start is not None:
7✔
195
            if stop is not None:
7✔
196
                return self._render().index(item, start, stop)
7✔
197
            return self._render().index(item, start)
7✔
198
        return self._render().index(item)
7✔
199

200
    @inheritdoc
7✔
201
    def extend(self, item):
6✔
202
        self._parent[self._stop : self._stop] = item
7✔
203

204
    @inheritdoc
7✔
205
    def insert(self, index, item):
6✔
206
        if index < 0:
7✔
207
            index = len(self) + index
7✔
208
        self._parent.insert(self._start + index, item)
7✔
209

210
    @inheritdoc
7✔
211
    def pop(self, index=None):
7✔
212
        length = len(self)
7✔
213
        if index is None:
7✔
214
            index = length - 1
7✔
215
        elif index < 0:
7✔
216
            index = length + index
7✔
217
        if index < 0 or index >= length:
7✔
218
            raise IndexError("pop index out of range")
7✔
219
        return self._parent.pop(self._start + index)
7✔
220

221
    @inheritdoc
7✔
222
    def remove(self, item):
6✔
223
        index = self.index(item)
7✔
224
        del self._parent[self._start + index]
7✔
225

226
    @inheritdoc
7✔
227
    def reverse(self):
6✔
228
        item = self._render()
7✔
229
        item.reverse()
7✔
230
        self._parent[self._start : self._stop : self._step] = item
7✔
231

232
    @inheritdoc
7✔
233
    def sort(self, key=None, reverse=None):
7✔
234
        item = self._render()
7✔
235
        kwargs = {}
7✔
236
        if key is not None:
7✔
237
            kwargs["key"] = key
7✔
238
        if reverse is not None:
7✔
239
            kwargs["reverse"] = reverse
7✔
240
        item.sort(**kwargs)
7✔
241
        self._parent[self._start : self._stop : self._step] = item
7✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc