codekingpro/portable-devtools
114k
1from __future__ import annotations2 3 4class IfRange:5 """Very simple object that represents the `If-Range` header in parsed6 form. It will either have neither a etag or date or one of either but7 never both.8 9 .. versionadded:: 0.710 """11 12 def __init__(self, etag=None, date=None):13 #: The etag parsed and unquoted. Ranges always operate on strong14 #: etags so the weakness information is not necessary.15 self.etag = etag16 #: The date in parsed format or `None`.17 self.date = date18 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 only36 bytes as the unit. Stores a list of ranges if given, but the methods37 only work if only one range is provided.38 39 :raise ValueError: If the ranges provided are invalid.40 41 .. versionchanged:: 0.1542 The ranges passed in are validated.43 44 .. versionadded:: 0.745 """46 47 def __init__(self, units, ranges):48 #: The units of this range. Usually "bytes".49 self.units = units50 #: A list of ``(begin, end)`` tuples for the range header provided.51 #: The ranges are non-inclusive.52 self.ranges = ranges53 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 is60 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 None65 start, end = self.ranges[0]66 if end is None:67 end = length68 if start < 0:69 start += length70 if http.is_byte_range_valid(start, end, length):71 return start, min(end, length)72 return None73 74 def make_content_range(self, length):75 """Creates a :class:`~werkzeug.datastructures.ContentRange` object76 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 None82 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 length96 """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 None101 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.7125 """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_update130 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 be137 #: `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 = units146 self._start = start147 self._stop = stop148 self._length = length149 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 should154 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.length165 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 None171 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 dependencies180from .. import http181 