Coverage for src/gitlabracadabra/packages/stream.py: 81%
22 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-14 23:10 +0200
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-14 23:10 +0200
1#
2# Copyright (C) 2019-2025 Mathieu Parent <math.parent@gmail.com>
3#
4# This program is free software: you can redistribute it and/or modify
5# it under the terms of the GNU Lesser General Public License as published by
6# the Free Software Foundation, either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
17from __future__ import annotations
19from typing import TYPE_CHECKING, AnyStr
21if TYPE_CHECKING: 21 ↛ 22line 21 didn't jump to line 22 because the condition on line 21 was never true
22 from collections.abc import Iterator
24 from requests.models import Response
27class Stream:
28 """Stream."""
30 def __init__(self, response: Response, chunksize: int = 65536) -> None:
31 """Initialize Stream.
33 Args:
34 response: Streamed response.
35 chunksize: Chunk size (used when there is no Content-Length header).
36 """
37 self._response = response
38 self._chunksize = chunksize
40 def __bool__(self) -> bool:
41 """Stream as boolean.
43 Needed for Session.request() which uses: data=data or dict().
44 (otherwise, would be considered False when length is 0).
46 Returns:
47 Always True.
48 """
49 return True
51 def __len__(self) -> int:
52 """Get stream length.
54 Returns:
55 The stream length. Zero if there is no Content-Length header.
56 """
57 return int(self._response.headers.get("Content-Length", "0"))
59 def __iter__(self) -> Iterator[bytes]:
60 """Get an iterator of chunks of body.
62 Returns:
63 A bytes iterator.
64 """
65 return self._response.raw.stream(self._chunksize)
67 @property
68 def name(self) -> str:
69 """Return URL.
71 This is needed to have proper file name in multipart upload.
73 Called from requests.utils.guess_filename(),
74 called from requests.models.RequestEncodingMixin._encode_files(),
75 called from requests.models.PreparedRequest.prepare_body().
77 Returns:
78 The response URL.
79 """
80 if self._response.history: 80 ↛ 82line 80 didn't jump to line 82 because the condition on line 80 was never true
81 # Keep original request URL, to avoid too long filename
82 return self._response.history[0].url
83 return self._response.url
85 def read(self, size: int | None = None) -> AnyStr: # type: ignore
86 """Read stream.
88 Args:
89 size: Length to read. Defaulting to None like http.client.HTTPResponse.read().
91 Returns:
92 The read bytes/str.
93 """
94 return self._response.raw.read(size) # type: ignore