Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/webob/byterange.py : 20%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import re
3__all__ = ['Range', 'ContentRange']
4_rx_range = re.compile(r'bytes *= *(\d*) *- *(\d*)', flags=re.I)
5_rx_content_range = re.compile(r'bytes (?:(\d+)-(\d+)|[*])/(?:(\d+)|[*])')
7class Range(object):
8 """
9 Represents the Range header.
10 """
12 def __init__(self, start, end):
13 assert end is None or end >= 0, "Bad range end: %r" % end
14 self.start = start
15 self.end = end # non-inclusive
17 def range_for_length(self, length):
18 """
19 *If* there is only one range, and *if* it is satisfiable by
20 the given length, then return a (start, end) non-inclusive range
21 of bytes to serve. Otherwise return None
22 """
23 if length is None:
24 return None
25 start, end = self.start, self.end
26 if end is None:
27 end = length
28 if start < 0:
29 start += length
30 if _is_content_range_valid(start, end, length):
31 stop = min(end, length)
32 return (start, stop)
33 else:
34 return None
36 def content_range(self, length):
37 """
38 Works like range_for_length; returns None or a ContentRange object
40 You can use it like::
42 response.content_range = req.range.content_range(response.content_length)
44 Though it's still up to you to actually serve that content range!
45 """
46 range = self.range_for_length(length)
47 if range is None:
48 return None
49 return ContentRange(range[0], range[1], length)
51 def __str__(self):
52 s,e = self.start, self.end
53 if e is None:
54 r = 'bytes=%s' % s
55 if s >= 0:
56 r += '-'
57 return r
58 return 'bytes=%s-%s' % (s, e-1)
60 def __repr__(self):
61 return '<%s bytes %r-%r>' % (
62 self.__class__.__name__,
63 self.start, self.end)
65 def __iter__(self):
66 return iter((self.start, self.end))
68 @classmethod
69 def parse(cls, header):
70 """
71 Parse the header; may return None if header is invalid
72 """
73 m = _rx_range.match(header or '')
74 if not m:
75 return None
76 start, end = m.groups()
77 if not start:
78 return cls(-int(end), None)
79 start = int(start)
80 if not end:
81 return cls(start, None)
82 end = int(end) + 1 # return val is non-inclusive
83 if start >= end:
84 return None
85 return cls(start, end)
88class ContentRange(object):
90 """
91 Represents the Content-Range header
93 This header is ``start-stop/length``, where start-stop and length
94 can be ``*`` (represented as None in the attributes).
95 """
97 def __init__(self, start, stop, length):
98 if not _is_content_range_valid(start, stop, length):
99 raise ValueError(
100 "Bad start:stop/length: %r-%r/%r" % (start, stop, length))
101 self.start = start
102 self.stop = stop # this is python-style range end (non-inclusive)
103 self.length = length
105 def __repr__(self):
106 return '<%s %s>' % (self.__class__.__name__, self)
108 def __str__(self):
109 if self.length is None:
110 length = '*'
111 else:
112 length = self.length
113 if self.start is None:
114 assert self.stop is None
115 return 'bytes */%s' % length
116 stop = self.stop - 1 # from non-inclusive to HTTP-style
117 return 'bytes %s-%s/%s' % (self.start, stop, length)
119 def __iter__(self):
120 """
121 Mostly so you can unpack this, like:
123 start, stop, length = res.content_range
124 """
125 return iter([self.start, self.stop, self.length])
127 @classmethod
128 def parse(cls, value):
129 """
130 Parse the header. May return None if it cannot parse.
131 """
132 m = _rx_content_range.match(value or '')
133 if not m:
134 return None
135 s, e, l = m.groups()
136 if s:
137 s = int(s)
138 e = int(e) + 1
139 l = l and int(l)
140 if not _is_content_range_valid(s, e, l, response=True):
141 return None
142 return cls(s, e, l)
145def _is_content_range_valid(start, stop, length, response=False):
146 if (start is None) != (stop is None):
147 return False
148 elif start is None:
149 return length is None or length >= 0
150 elif length is None:
151 return 0 <= start < stop
152 elif start >= stop:
153 return False
154 elif response and stop > length:
155 # "content-range: bytes 0-50/10" is invalid for a response
156 # "range: bytes 0-50" is valid for a request to a 10-bytes entity
157 return False
158 else:
159 return 0 <= start < length