1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
|
######################################################################
#
# File: b2sdk/_internal/utils/range_.py
#
# Copyright 2020 Backblaze Inc. All Rights Reserved.
#
# License https://www.backblaze.com/using_b2_code.html
#
######################################################################
from __future__ import annotations
import dataclasses
import re
_RANGE_HEADER_RE = re.compile(
r'^(?:bytes[ =])?(?P<start>\d+)-(?P<end>\d+)(?:/(?:(?P<complete_length>\d+)|\*))?$'
)
@dataclasses.dataclass(eq=True, order=True, frozen=True)
class Range:
"""
HTTP ranges use an *inclusive* index at the end.
"""
__slots__ = ['start', 'end']
start: int
end: int
def __post_init__(self):
assert 0 <= self.start <= self.end or (
self.start == 1 and self.end == 0
), f'Invalid range: {self}'
@classmethod
def from_header(cls, raw_range_header: str) -> Range:
"""
Factory method which returns an object constructed from Range http header.
raw_range_header example: 'bytes=0-11'
"""
return cls.from_header_with_size(raw_range_header)[0]
@classmethod
def from_header_with_size(cls, raw_range_header: str) -> tuple[Range, int | None]:
"""
Factory method which returns an object constructed from Range http header.
raw_range_header example: 'bytes=0-11'
"""
match = _RANGE_HEADER_RE.match(raw_range_header)
if not match:
raise ValueError(f'Invalid range header: {raw_range_header}')
start = int(match.group('start'))
end = int(match.group('end'))
complete_length = match.group('complete_length')
complete_length = int(complete_length) if complete_length else None
return cls(start, end), complete_length
def size(self) -> int:
return self.end - self.start + 1
def subrange(self, sub_start, sub_end) -> Range:
"""
Return a range that is part of this range.
:param sub_start: index relative to the start of this range.
:param sub_end: (Inclusive!) index relative to the start of this range.
:return: a new Range
"""
assert 0 <= sub_start <= sub_end < self.size()
return self.__class__(self.start + sub_start, self.start + sub_end)
def as_tuple(self) -> tuple[int, int]:
return self.start, self.end
def __repr__(self) -> str:
return f'{self.__class__.__name__}({self.start}, {self.end})'
EMPTY_RANGE = Range(1, 0)
|