~ubuntu-branches/debian/sid/pyrlp/sid

« back to all changes in this revision

Viewing changes to rlp/lazy.py

  • Committer: Package Import Robot
  • Author(s): Ben Finney
  • Date: 2017-07-15 05:25:42 UTC
  • Revision ID: package-import@ubuntu.com-20170715052542-a20zzsypt1qfecq1
Tags: upstream-0.5.1
ImportĀ upstreamĀ versionĀ 0.5.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
from collections import Iterable, Sequence
 
2
from .codec import consume_length_prefix, consume_payload
 
3
from .exceptions import DecodingError
 
4
from .utils import Atomic
 
5
 
 
6
 
 
7
def decode_lazy(rlp, sedes=None, **sedes_kwargs):
 
8
    """Decode an RLP encoded object in a lazy fashion.
 
9
 
 
10
    If the encoded object is a bytestring, this function acts similar to
 
11
    :func:`rlp.decode`. If it is a list however, a :class:`LazyList` is
 
12
    returned instead. This object will decode the string lazily, avoiding
 
13
    both horizontal and vertical traversing as much as possible.
 
14
 
 
15
    The way `sedes` is applied depends on the decoded object: If it is a string
 
16
    `sedes` deserializes it as a whole; if it is a list, each element is
 
17
    deserialized individually. In both cases, `sedes_kwargs` are passed on.
 
18
    Note that, if a deserializer is used, only "horizontal" but not
 
19
    "vertical lazyness" can be preserved.
 
20
 
 
21
    :param rlp: the RLP string to decode
 
22
    :param sedes: an object implementing a method ``deserialize(code)`` which
 
23
                  is used as described above, or ``None`` if no
 
24
                  deserialization should be performed
 
25
    :param \*\*sedes_kwargs: additional keyword arguments that will be passed
 
26
                             to the deserializers
 
27
    :returns: either the already decoded and deserialized object (if encoded as
 
28
              a string) or an instance of :class:`rlp.LazyList`
 
29
    """
 
30
    item, end = consume_item_lazy(rlp, 0)
 
31
    if end != len(rlp):
 
32
        raise DecodingError('RLP length prefix announced wrong length', rlp)
 
33
    if isinstance(item, LazyList):
 
34
        item.sedes = sedes
 
35
        item.sedes_kwargs = sedes_kwargs
 
36
        return item
 
37
    elif sedes:
 
38
        return sedes.deserialize(item, **sedes_kwargs)
 
39
    else:
 
40
        return item
 
41
 
 
42
 
 
43
def consume_item_lazy(rlp, start):
 
44
    """Read an item from an RLP string lazily.
 
45
 
 
46
    If the length prefix announces a string, the string is read; if it
 
47
    announces a list, a :class:`LazyList` is created.
 
48
 
 
49
    :param rlp: the rlp string to read from
 
50
    :param start: the position at which to start reading
 
51
    :returns: a tuple ``(item, end)`` where ``item`` is the read string or a
 
52
              :class:`LazyList` and ``end`` is the position of the first
 
53
              unprocessed byte.
 
54
    """
 
55
    t, l, s = consume_length_prefix(rlp, start)
 
56
    if t == str:
 
57
        #item, _ = consume_payload(rlp, s, str, l), s + l
 
58
        return consume_payload(rlp, s, str, l)
 
59
    else:
 
60
        assert t == list
 
61
        return LazyList(rlp, s, s + l), s + l
 
62
 
 
63
 
 
64
class LazyList(Sequence):
 
65
    """A RLP encoded list which decodes itself when necessary.
 
66
 
 
67
    Both indexing with positive indices and iterating are supported.
 
68
    Getting the length with :func:`len` is possible as well but requires full
 
69
    horizontal encoding.
 
70
 
 
71
    :param rlp: the rlp string in which the list is encoded
 
72
    :param start: the position of the first payload byte of the encoded list
 
73
    :param end: the position of the last payload byte of the encoded list
 
74
    :param sedes: a sedes object which deserializes each element of the list,
 
75
                  or ``None`` for no deserialization
 
76
    :param \*\*sedes_kwargs: keyword arguments which will be passed on to the
 
77
                             deserializer
 
78
    """
 
79
 
 
80
    def __init__(self, rlp, start, end, sedes=None, **sedes_kwargs):
 
81
        self.rlp = rlp
 
82
        self.start = start
 
83
        self.end = end
 
84
        self.index = start
 
85
        self._elements = []
 
86
        self._len = None
 
87
        self.sedes = sedes
 
88
        self.sedes_kwargs = sedes_kwargs
 
89
 
 
90
    def next(self):
 
91
        if self.index == self.end:
 
92
            self._len = len(self._elements)
 
93
            raise StopIteration
 
94
        assert self.index < self.end
 
95
        item, end = consume_item_lazy(self.rlp, self.index)
 
96
        self.index = end
 
97
        if self.sedes:
 
98
            item = self.sedes.deserialize(item, **self.sedes_kwargs)
 
99
        self._elements.append(item)
 
100
        return item
 
101
 
 
102
    def __getitem__(self, i):
 
103
        try:
 
104
            while len(self._elements) <= i:
 
105
                self.next()
 
106
        except StopIteration:
 
107
            assert self.index == self.end
 
108
            raise IndexError('Index %d out of range' % i)
 
109
        return self._elements[i]
 
110
 
 
111
    def __len__(self):
 
112
        if not self._len:
 
113
            try:
 
114
                while True:
 
115
                    self.next()
 
116
            except StopIteration:
 
117
                self._len = len(self._elements)
 
118
        return self._len
 
119
 
 
120
 
 
121
def peek(rlp, index, sedes=None):
 
122
    """Get a specific element from an rlp encoded nested list.
 
123
 
 
124
    This function uses :func:`rlp.decode_lazy` and, thus, decodes only the
 
125
    necessary parts of the string.
 
126
 
 
127
    Usage example::
 
128
 
 
129
        >>> rlpdata = rlp.encode([1, 2, [3, [4, 5]]])
 
130
        >>> rlp.peek(rlpdata, 0, rlp.sedes.big_endian_int)
 
131
        1
 
132
        >>> rlp.peek(rlpdata, [2, 0], rlp.sedes.big_endian_int)
 
133
        3
 
134
 
 
135
    :param rlp: the rlp string
 
136
    :param index: the index of the element to peek at (can be a list for
 
137
                  nested data)
 
138
    :param sedes: a sedes used to deserialize the peeked at object, or `None`
 
139
                  if no deserialization should be performed
 
140
    :raises: :exc:`IndexError` if `index` is invalid (out of range or too many
 
141
             levels)
 
142
    """
 
143
    ll = decode_lazy(rlp)
 
144
    if not isinstance(index, Iterable):
 
145
        index = [index]
 
146
    for i in index:
 
147
        if isinstance(ll, Atomic):
 
148
            raise IndexError('Too many indices given')
 
149
        ll = ll[i]
 
150
    if sedes:
 
151
        return sedes.deserialize(ll)
 
152
    else:
 
153
        return ll