matelab-python-sdk 0.1.0a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
matelab/streaming.py ADDED
@@ -0,0 +1,82 @@
1
+ from collections.abc import AsyncIterator
2
+ from dataclasses import dataclass
3
+ from typing import Self
4
+
5
+ import httpx
6
+
7
+ from matelab.errors import MatelabUsageError
8
+
9
+
10
+ @dataclass(frozen=True, slots=True)
11
+ class ByteRange:
12
+ """One HTTP byte range from the stable subset supported by Matelab."""
13
+
14
+ start: int | None = None
15
+ end: int | None = None
16
+ suffix_length: int | None = None
17
+
18
+ def __post_init__(self) -> None:
19
+ if any(type(value) is not int for value in (self.start, self.end, self.suffix_length) if value is not None):
20
+ raise MatelabUsageError("Byte-range values must be integers.")
21
+ if self.suffix_length is not None:
22
+ if self.start is not None or self.end is not None or self.suffix_length < 1:
23
+ raise MatelabUsageError("A suffix byte range needs a positive length and no start or end.")
24
+ return
25
+ if self.start is None or self.start < 0:
26
+ raise MatelabUsageError("A byte range needs a non-negative start or a positive suffix length.")
27
+ if self.end is not None and (self.end < 1 or self.end < self.start):
28
+ raise MatelabUsageError(
29
+ "An explicit byte-range end must be positive and greater than or equal to the start."
30
+ )
31
+
32
+ @classmethod
33
+ def from_start(cls, start: int) -> Self:
34
+ return cls(start=start)
35
+
36
+ @classmethod
37
+ def between(cls, start: int, end: int) -> Self:
38
+ return cls(start=start, end=end)
39
+
40
+ @classmethod
41
+ def last(cls, length: int) -> Self:
42
+ return cls(suffix_length=length)
43
+
44
+ def to_header(self) -> str:
45
+ if self.suffix_length is not None:
46
+ return f"bytes=-{self.suffix_length}"
47
+ return f"bytes={self.start}-{self.end if self.end is not None else ''}"
48
+
49
+
50
+ class DownloadStream:
51
+ """A Provider response whose bytes remain open until consumed or closed."""
52
+
53
+ def __init__(self, response: httpx.Response, *, content_length: int | None) -> None:
54
+ self._response: httpx.Response = response
55
+ self.status_code: int = response.status_code
56
+ self.content_type: str | None = response.headers.get("Content-Type")
57
+ self.content_length: int | None = content_length
58
+ self.content_range: str | None = response.headers.get("Content-Range")
59
+ self.accept_ranges: str | None = response.headers.get("Accept-Ranges")
60
+ self.content_disposition: str | None = response.headers.get("Content-Disposition")
61
+
62
+ @property
63
+ def is_closed(self) -> bool:
64
+ return self._response.is_closed
65
+
66
+ async def __aenter__(self) -> Self:
67
+ return self
68
+
69
+ async def __aexit__(self, *_: object) -> None:
70
+ await self.aclose()
71
+
72
+ def __aiter__(self) -> AsyncIterator[bytes]:
73
+ return self.aiter_bytes()
74
+
75
+ def aiter_bytes(self, chunk_size: int | None = None) -> AsyncIterator[bytes]:
76
+ return self._response.aiter_bytes(chunk_size)
77
+
78
+ async def read(self) -> bytes:
79
+ return await self._response.aread()
80
+
81
+ async def aclose(self) -> None:
82
+ await self._response.aclose()