Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/werkzeug/datastructures/range.py: 32%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

97 statements  

1from __future__ import annotations 

2 

3 

4class IfRange: 

5 """Very simple object that represents the `If-Range` header in parsed 

6 form. It will either have neither a etag or date or one of either but 

7 never both. 

8 

9 .. versionadded:: 0.7 

10 """ 

11 

12 def __init__(self, etag=None, date=None): 

13 #: The etag parsed and unquoted. Ranges always operate on strong 

14 #: etags so the weakness information is not necessary. 

15 self.etag = etag 

16 #: The date in parsed format or `None`. 

17 self.date = date 

18 

19 def to_header(self): 

20 """Converts the object back into an HTTP header.""" 

21 if self.date is not None: 

22 return http.http_date(self.date) 

23 if self.etag is not None: 

24 return http.quote_etag(self.etag) 

25 return "" 

26 

27 def __str__(self): 

28 return self.to_header() 

29 

30 def __repr__(self): 

31 return f"<{type(self).__name__} {str(self)!r}>" 

32 

33 

34class Range: 

35 """Represents a ``Range`` header. All methods only support only 

36 bytes as the unit. Stores a list of ranges if given, but the methods 

37 only work if only one range is provided. 

38 

39 :raise ValueError: If the ranges provided are invalid. 

40 

41 .. versionchanged:: 0.15 

42 The ranges passed in are validated. 

43 

44 .. versionadded:: 0.7 

45 """ 

46 

47 def __init__(self, units, ranges): 

48 #: The units of this range. Usually "bytes". 

49 self.units = units 

50 #: A list of ``(begin, end)`` tuples for the range header provided. 

51 #: The ranges are non-inclusive. 

52 self.ranges = ranges 

53 

54 for start, end in ranges: 

55 if start is None or (end is not None and (start < 0 or start >= end)): 

56 raise ValueError(f"{(start, end)} is not a valid range.") 

57 

58 def range_for_length(self, length): 

59 """If the range is for bytes, the length is not None and there is 

60 exactly one range and it is satisfiable it returns a ``(start, stop)`` 

61 tuple, otherwise `None`. 

62 """ 

63 if self.units != "bytes" or length is None or len(self.ranges) != 1: 

64 return None 

65 start, end = self.ranges[0] 

66 if end is None: 

67 end = length 

68 if start < 0: 

69 start += length 

70 if http.is_byte_range_valid(start, end, length): 

71 return start, min(end, length) 

72 return None 

73 

74 def make_content_range(self, length): 

75 """Creates a :class:`~werkzeug.datastructures.ContentRange` object 

76 from the current range and given content length. 

77 """ 

78 rng = self.range_for_length(length) 

79 if rng is not None: 

80 return ContentRange(self.units, rng[0], rng[1], length) 

81 return None 

82 

83 def to_header(self): 

84 """Converts the object back into an HTTP header.""" 

85 ranges = [] 

86 for begin, end in self.ranges: 

87 if end is None: 

88 ranges.append(f"{begin}-" if begin >= 0 else str(begin)) 

89 else: 

90 ranges.append(f"{begin}-{end - 1}") 

91 return f"{self.units}={','.join(ranges)}" 

92 

93 def to_content_range_header(self, length): 

94 """Converts the object into `Content-Range` HTTP header, 

95 based on given length 

96 """ 

97 range = self.range_for_length(length) 

98 if range is not None: 

99 return f"{self.units} {range[0]}-{range[1] - 1}/{length}" 

100 return None 

101 

102 def __str__(self): 

103 return self.to_header() 

104 

105 def __repr__(self): 

106 return f"<{type(self).__name__} {str(self)!r}>" 

107 

108 

109def _callback_property(name): 

110 def fget(self): 

111 return getattr(self, name) 

112 

113 def fset(self, value): 

114 setattr(self, name, value) 

115 if self.on_update is not None: 

116 self.on_update(self) 

117 

118 return property(fget, fset) 

119 

120 

121class ContentRange: 

122 """Represents the content range header. 

123 

124 .. versionadded:: 0.7 

125 """ 

126 

127 def __init__(self, units, start, stop, length=None, on_update=None): 

128 assert http.is_byte_range_valid(start, stop, length), "Bad range provided" 

129 self.on_update = on_update 

130 self.set(start, stop, length, units) 

131 

132 #: The units to use, usually "bytes" 

133 units = _callback_property("_units") 

134 #: The start point of the range or `None`. 

135 start = _callback_property("_start") 

136 #: The stop point of the range (non-inclusive) or `None`. Can only be 

137 #: `None` if also start is `None`. 

138 stop = _callback_property("_stop") 

139 #: The length of the range or `None`. 

140 length = _callback_property("_length") 

141 

142 def set(self, start, stop, length=None, units="bytes"): 

143 """Simple method to update the ranges.""" 

144 assert http.is_byte_range_valid(start, stop, length), "Bad range provided" 

145 self._units = units 

146 self._start = start 

147 self._stop = stop 

148 self._length = length 

149 if self.on_update is not None: 

150 self.on_update(self) 

151 

152 def unset(self): 

153 """Sets the units to `None` which indicates that the header should 

154 no longer be used. 

155 """ 

156 self.set(None, None, units=None) 

157 

158 def to_header(self): 

159 if self.units is None: 

160 return "" 

161 if self.length is None: 

162 length = "*" 

163 else: 

164 length = self.length 

165 if self.start is None: 

166 return f"{self.units} */{length}" 

167 return f"{self.units} {self.start}-{self.stop - 1}/{length}" 

168 

169 def __bool__(self): 

170 return self.units is not None 

171 

172 def __str__(self): 

173 return self.to_header() 

174 

175 def __repr__(self): 

176 return f"<{type(self).__name__} {str(self)!r}>" 

177 

178 

179# circular dependencies 

180from .. import http