python-httpfile 0.0.2.1__py3-none-any.whl → 0.0.4__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.
- httpfile/__init__.py +807 -58
- {python_httpfile-0.0.2.1.dist-info → python_httpfile-0.0.4.dist-info}/METADATA +5 -3
- python_httpfile-0.0.4.dist-info/RECORD +7 -0
- python_httpfile-0.0.2.1.dist-info/RECORD +0 -7
- {python_httpfile-0.0.2.1.dist-info → python_httpfile-0.0.4.dist-info}/LICENSE +0 -0
- {python_httpfile-0.0.2.1.dist-info → python_httpfile-0.0.4.dist-info}/WHEEL +0 -0
httpfile/__init__.py
CHANGED
|
@@ -1,30 +1,40 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# encoding: utf-8
|
|
3
3
|
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
4
6
|
__author__ = "ChenyangGao <https://chenyanggao.github.io>"
|
|
5
|
-
__version__ = (0, 0,
|
|
7
|
+
__version__ = (0, 0, 4)
|
|
6
8
|
__all__ = ["HTTPFileReader", "AsyncHTTPFileReader"]
|
|
7
9
|
|
|
8
10
|
import errno
|
|
9
11
|
|
|
10
|
-
from collections.abc import
|
|
12
|
+
from collections.abc import Awaitable, Callable, Mapping
|
|
11
13
|
from functools import cached_property, partial
|
|
12
|
-
from
|
|
14
|
+
from importlib.util import find_spec
|
|
15
|
+
from inspect import isawaitable
|
|
13
16
|
from io import (
|
|
14
17
|
BufferedReader, RawIOBase, TextIOWrapper, UnsupportedOperation, DEFAULT_BUFFER_SIZE,
|
|
15
18
|
)
|
|
16
19
|
from os import fstat, stat, PathLike
|
|
17
20
|
from shutil import COPY_BUFSIZE # type: ignore
|
|
18
|
-
from
|
|
21
|
+
from sys import exc_info
|
|
22
|
+
from typing import cast, overload, Any, BinaryIO, Literal, Self
|
|
19
23
|
from types import MappingProxyType
|
|
20
24
|
from warnings import warn
|
|
21
25
|
|
|
26
|
+
from asynctools import ensure_async, run_async
|
|
27
|
+
from filewrap import AsyncBufferedReader, AsyncTextIOWrapper
|
|
22
28
|
from http_response import get_filename, get_length, get_range, get_total_length, is_chunked, is_range_request
|
|
23
29
|
from property import funcproperty
|
|
24
30
|
from urlopen import urlopen
|
|
25
31
|
|
|
26
32
|
|
|
27
|
-
def get_filesize(
|
|
33
|
+
def get_filesize(
|
|
34
|
+
file,
|
|
35
|
+
/,
|
|
36
|
+
dont_read: bool = True,
|
|
37
|
+
) -> int:
|
|
28
38
|
if isinstance(file, (bytes, str, PathLike)):
|
|
29
39
|
return stat(file).st_size
|
|
30
40
|
curpos = 0
|
|
@@ -88,12 +98,12 @@ class HTTPFileReader(RawIOBase, BinaryIO):
|
|
|
88
98
|
self,
|
|
89
99
|
/,
|
|
90
100
|
url: str | Callable[[], str],
|
|
91
|
-
headers:
|
|
101
|
+
headers: None | Mapping = None,
|
|
92
102
|
start: int = 0,
|
|
93
103
|
# NOTE: If the offset of the forward seek is not higher than this value,
|
|
94
104
|
# it will be directly read and discarded, default to 1 MB
|
|
95
105
|
seek_threshold: int = 1 << 20,
|
|
96
|
-
urlopen
|
|
106
|
+
urlopen = urlopen,
|
|
97
107
|
):
|
|
98
108
|
if headers:
|
|
99
109
|
headers = {**headers, "Accept-Encoding": "identity"}
|
|
@@ -103,6 +113,8 @@ class HTTPFileReader(RawIOBase, BinaryIO):
|
|
|
103
113
|
headers["Range"] = f"bytes={start}-"
|
|
104
114
|
elif start < 0:
|
|
105
115
|
headers["Range"] = f"bytes={start}"
|
|
116
|
+
if urlopen is None:
|
|
117
|
+
urlopen = globals()["urlopen"]
|
|
106
118
|
if callable(url):
|
|
107
119
|
geturl = url
|
|
108
120
|
def url():
|
|
@@ -146,7 +158,7 @@ class HTTPFileReader(RawIOBase, BinaryIO):
|
|
|
146
158
|
def __exit__(self, /, *exc_info):
|
|
147
159
|
self.close()
|
|
148
160
|
|
|
149
|
-
def __iter__(self, /):
|
|
161
|
+
def __iter__(self, /) -> Self:
|
|
150
162
|
return self
|
|
151
163
|
|
|
152
164
|
def __len__(self, /) -> int:
|
|
@@ -165,23 +177,36 @@ class HTTPFileReader(RawIOBase, BinaryIO):
|
|
|
165
177
|
name = cls.__qualname__
|
|
166
178
|
if module != "__main__":
|
|
167
179
|
name = module + "." + name
|
|
168
|
-
return f"{name}({self.url!r},
|
|
180
|
+
return f"{name}(url={self.url!r}, headers={self.headers!r}, start={self.tell()}, seek_threshold={self.seek_threshold}, urlopen={self.urlopen!r})"
|
|
169
181
|
|
|
170
182
|
def __setattr__(self, attr, val, /):
|
|
171
183
|
raise TypeError("can't set attribute")
|
|
172
184
|
|
|
173
185
|
def _add_start(self, delta: int, /):
|
|
174
|
-
self.
|
|
186
|
+
if not self.file_can_tell:
|
|
187
|
+
self.__dict__["start"] += delta
|
|
188
|
+
|
|
189
|
+
@cached_property
|
|
190
|
+
def file_can_tell(self, /) -> bool:
|
|
191
|
+
try:
|
|
192
|
+
self.file.tell()
|
|
193
|
+
return True
|
|
194
|
+
except (AttributeError, TypeError, OSError):
|
|
195
|
+
return False
|
|
196
|
+
|
|
197
|
+
@funcproperty
|
|
198
|
+
def file_closed(self, /) -> bool:
|
|
199
|
+
return self.file.closed
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def closed(self, /) -> bool:
|
|
203
|
+
return self.__dict__["closed"]
|
|
175
204
|
|
|
176
205
|
def close(self, /):
|
|
177
206
|
if not self.closed:
|
|
178
207
|
self.response.close()
|
|
179
208
|
self.__dict__["closed"] = True
|
|
180
209
|
|
|
181
|
-
@funcproperty
|
|
182
|
-
def closed(self, /):
|
|
183
|
-
return self.__dict__["closed"]
|
|
184
|
-
|
|
185
210
|
@funcproperty
|
|
186
211
|
def file(self, /) -> BinaryIO:
|
|
187
212
|
return self.response
|
|
@@ -208,7 +233,7 @@ class HTTPFileReader(RawIOBase, BinaryIO):
|
|
|
208
233
|
raise ValueError("I/O operation on closed file.")
|
|
209
234
|
if size == 0 or not self.chunked and self.tell() >= self.length:
|
|
210
235
|
return b""
|
|
211
|
-
if self.
|
|
236
|
+
if self.file_closed:
|
|
212
237
|
self.reconnect()
|
|
213
238
|
if size is None or size < 0:
|
|
214
239
|
data = self.file.read()
|
|
@@ -226,19 +251,19 @@ class HTTPFileReader(RawIOBase, BinaryIO):
|
|
|
226
251
|
raise ValueError("I/O operation on closed file.")
|
|
227
252
|
if not buffer or not self.chunked and self.tell() >= self.length:
|
|
228
253
|
return 0
|
|
229
|
-
if self.
|
|
254
|
+
if self.file_closed:
|
|
230
255
|
self.reconnect()
|
|
231
256
|
size = self.file.readinto(buffer)
|
|
232
257
|
if size:
|
|
233
258
|
self._add_start(size)
|
|
234
259
|
return size
|
|
235
260
|
|
|
236
|
-
def readline(self, size:
|
|
261
|
+
def readline(self, size: None | int = -1, /) -> bytes:
|
|
237
262
|
if self.closed:
|
|
238
263
|
raise ValueError("I/O operation on closed file.")
|
|
239
264
|
if size == 0 or not self.chunked and self.tell() >= self.length:
|
|
240
265
|
return b""
|
|
241
|
-
if self.
|
|
266
|
+
if self.file_closed:
|
|
242
267
|
self.reconnect()
|
|
243
268
|
if size is None or size < 0:
|
|
244
269
|
data = self.file.readline()
|
|
@@ -253,14 +278,14 @@ class HTTPFileReader(RawIOBase, BinaryIO):
|
|
|
253
278
|
raise ValueError("I/O operation on closed file.")
|
|
254
279
|
if not self.chunked and self.tell() >= self.length:
|
|
255
280
|
return []
|
|
256
|
-
if self.
|
|
281
|
+
if self.file_closed:
|
|
257
282
|
self.reconnect()
|
|
258
283
|
ls = self.file.readlines(hint)
|
|
259
284
|
if ls:
|
|
260
285
|
self._add_start(sum(map(len, ls)))
|
|
261
286
|
return ls
|
|
262
287
|
|
|
263
|
-
def reconnect(self, /, start:
|
|
288
|
+
def reconnect(self, /, start: None | int = None) -> int:
|
|
264
289
|
if not self._seekable:
|
|
265
290
|
if start is None and self.tell() or start:
|
|
266
291
|
raise OSError(errno.EOPNOTSUPP, "Unsupport for reconnection of non-seekable streams.")
|
|
@@ -327,9 +352,15 @@ class HTTPFileReader(RawIOBase, BinaryIO):
|
|
|
327
352
|
return self._seekable
|
|
328
353
|
|
|
329
354
|
def tell(self, /) -> int:
|
|
330
|
-
|
|
355
|
+
if self.file_can_tell:
|
|
356
|
+
start = self.start
|
|
357
|
+
if start >= self.length:
|
|
358
|
+
return start
|
|
359
|
+
return start + self.file.tell()
|
|
360
|
+
else:
|
|
361
|
+
return self.start
|
|
331
362
|
|
|
332
|
-
def truncate(self, size:
|
|
363
|
+
def truncate(self, size: None | int = None, /):
|
|
333
364
|
raise UnsupportedOperation(errno.ENOTSUP, "truncate")
|
|
334
365
|
|
|
335
366
|
def writable(self, /) -> bool:
|
|
@@ -341,15 +372,109 @@ class HTTPFileReader(RawIOBase, BinaryIO):
|
|
|
341
372
|
def writelines(self, lines, /):
|
|
342
373
|
raise UnsupportedOperation(errno.ENOTSUP, "writelines")
|
|
343
374
|
|
|
375
|
+
@overload
|
|
376
|
+
@classmethod
|
|
377
|
+
def open(
|
|
378
|
+
cls,
|
|
379
|
+
/,
|
|
380
|
+
url: str | Callable[[], str],
|
|
381
|
+
mode: Literal["br", "rb"],
|
|
382
|
+
*,
|
|
383
|
+
buffering: None | int = None,
|
|
384
|
+
encoding: None | str = None,
|
|
385
|
+
errors: None | str = None,
|
|
386
|
+
newline: None | str = None,
|
|
387
|
+
headers: None | Mapping = None,
|
|
388
|
+
start: int = 0,
|
|
389
|
+
seek_threshold: int = 1 << 20,
|
|
390
|
+
urlopen = None,
|
|
391
|
+
) -> Self | BufferedReader:
|
|
392
|
+
...
|
|
393
|
+
@overload
|
|
394
|
+
@classmethod
|
|
395
|
+
def open(
|
|
396
|
+
cls,
|
|
397
|
+
/,
|
|
398
|
+
url: str | Callable[[], str],
|
|
399
|
+
mode: Literal["r", "rt", "tr"] = "r",
|
|
400
|
+
*,
|
|
401
|
+
buffering: None | int = None,
|
|
402
|
+
encoding: None | str = None,
|
|
403
|
+
errors: None | str = None,
|
|
404
|
+
newline: None | str = None,
|
|
405
|
+
headers: None | Mapping = None,
|
|
406
|
+
start: int = 0,
|
|
407
|
+
seek_threshold: int = 1 << 20,
|
|
408
|
+
urlopen = None,
|
|
409
|
+
) -> TextIOWrapper:
|
|
410
|
+
...
|
|
411
|
+
@classmethod
|
|
412
|
+
def open(
|
|
413
|
+
cls,
|
|
414
|
+
/,
|
|
415
|
+
url: str | Callable[[], str],
|
|
416
|
+
mode: Literal["r", "rt", "tr", "br", "rb"] = "r",
|
|
417
|
+
*,
|
|
418
|
+
buffering: None | int = None,
|
|
419
|
+
encoding: None | str = None,
|
|
420
|
+
errors: None | str = None,
|
|
421
|
+
newline: None | str = None,
|
|
422
|
+
headers: None | Mapping = None,
|
|
423
|
+
start: int = 0,
|
|
424
|
+
seek_threshold: int = 1 << 20,
|
|
425
|
+
urlopen = None,
|
|
426
|
+
) -> Self | BufferedReader | TextIOWrapper:
|
|
427
|
+
file = cls(
|
|
428
|
+
url=url,
|
|
429
|
+
headers=headers,
|
|
430
|
+
start=start,
|
|
431
|
+
seek_threshold=seek_threshold,
|
|
432
|
+
urlopen=urlopen,
|
|
433
|
+
)
|
|
434
|
+
if mode not in ("r", "rt", "tr", "rb", "br"):
|
|
435
|
+
raise OSError(errno.EINVAL, f"invalid (or unsupported) mode: {mode!r}")
|
|
436
|
+
return file.wrap(
|
|
437
|
+
text_mode="b" not in mode, # type: ignore
|
|
438
|
+
buffering=buffering,
|
|
439
|
+
encoding=encoding,
|
|
440
|
+
errors=errors,
|
|
441
|
+
newline=newline,
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
@overload
|
|
344
445
|
def wrap(
|
|
345
446
|
self,
|
|
346
447
|
/,
|
|
347
|
-
text_mode:
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
448
|
+
text_mode: Literal[False] = False,
|
|
449
|
+
*,
|
|
450
|
+
buffering: None | int = None,
|
|
451
|
+
encoding: None | str = None,
|
|
452
|
+
errors: None | str = None,
|
|
453
|
+
newline: None | str = None,
|
|
454
|
+
) -> Self | BufferedReader:
|
|
455
|
+
...
|
|
456
|
+
@overload
|
|
457
|
+
def wrap(
|
|
458
|
+
self,
|
|
459
|
+
/,
|
|
460
|
+
text_mode: Literal[True],
|
|
461
|
+
*,
|
|
462
|
+
buffering: None | int = None,
|
|
463
|
+
encoding: None | str = None,
|
|
464
|
+
errors: None | str = None,
|
|
465
|
+
newline: None | str = None,
|
|
466
|
+
) -> TextIOWrapper:
|
|
467
|
+
...
|
|
468
|
+
def wrap(
|
|
469
|
+
self,
|
|
470
|
+
/,
|
|
471
|
+
text_mode: Literal[False, True] = False,
|
|
472
|
+
*,
|
|
473
|
+
buffering: None | int = None,
|
|
474
|
+
encoding: None | str = None,
|
|
475
|
+
errors: None | str = None,
|
|
476
|
+
newline: None | str = None,
|
|
477
|
+
) -> Self | BufferedReader | TextIOWrapper:
|
|
353
478
|
if buffering is None:
|
|
354
479
|
if text_mode:
|
|
355
480
|
buffering = DEFAULT_BUFFER_SIZE
|
|
@@ -385,8 +510,443 @@ class HTTPFileReader(RawIOBase, BinaryIO):
|
|
|
385
510
|
return buffer
|
|
386
511
|
|
|
387
512
|
|
|
388
|
-
|
|
389
|
-
|
|
513
|
+
class AsyncHTTPFileReader(HTTPFileReader):
|
|
514
|
+
url: str | Callable[[], Awaitable[str]] # type: ignore
|
|
515
|
+
|
|
516
|
+
def __init__(
|
|
517
|
+
self,
|
|
518
|
+
/,
|
|
519
|
+
url: str | Callable[[], str] | Callable[[], Awaitable[str]],
|
|
520
|
+
headers: None | Mapping = None,
|
|
521
|
+
start: int = 0,
|
|
522
|
+
seek_threshold: int = 1 << 20,
|
|
523
|
+
urlopen = urlopen,
|
|
524
|
+
):
|
|
525
|
+
run_async(self.__ainit__(
|
|
526
|
+
url=url,
|
|
527
|
+
headers=headers,
|
|
528
|
+
start=start,
|
|
529
|
+
seek_threshold=seek_threshold,
|
|
530
|
+
urlopen=urlopen,
|
|
531
|
+
))
|
|
532
|
+
|
|
533
|
+
async def __aenter__(self, /) -> Self:
|
|
534
|
+
return self
|
|
535
|
+
|
|
536
|
+
async def __aexit__(self, /, *exc_info):
|
|
537
|
+
await self.aclose()
|
|
538
|
+
|
|
539
|
+
def __aiter__(self, /) -> Self:
|
|
540
|
+
return self
|
|
541
|
+
|
|
542
|
+
async def __anext__(self, /) -> bytes:
|
|
543
|
+
if line := await self.readline():
|
|
544
|
+
return line
|
|
545
|
+
else:
|
|
546
|
+
raise StopAsyncIteration
|
|
547
|
+
|
|
548
|
+
async def __ainit__(
|
|
549
|
+
self,
|
|
550
|
+
/,
|
|
551
|
+
url: str | Callable[[], str] | Callable[[], Awaitable[str]],
|
|
552
|
+
headers: None | Mapping = None,
|
|
553
|
+
start: int = 0,
|
|
554
|
+
seek_threshold: int = 1 << 20,
|
|
555
|
+
urlopen = None,
|
|
556
|
+
):
|
|
557
|
+
if headers:
|
|
558
|
+
headers = {**headers, "Accept-Encoding": "identity"}
|
|
559
|
+
else:
|
|
560
|
+
headers = {"Accept-Encoding": "identity"}
|
|
561
|
+
if start > 0:
|
|
562
|
+
headers["Range"] = f"bytes={start}-"
|
|
563
|
+
elif start < 0:
|
|
564
|
+
headers["Range"] = f"bytes={start}"
|
|
565
|
+
if urlopen is None:
|
|
566
|
+
urlopen = globals()["urlopen"]
|
|
567
|
+
if callable(url):
|
|
568
|
+
geturl = url
|
|
569
|
+
async def url() -> str:
|
|
570
|
+
url = geturl()
|
|
571
|
+
if isawaitable(url):
|
|
572
|
+
url = await url
|
|
573
|
+
headers_extra = getattr(url, "headers")
|
|
574
|
+
if headers_extra:
|
|
575
|
+
headers.update(headers_extra)
|
|
576
|
+
return cast(str, url)
|
|
577
|
+
elif hasattr(url, "headers"):
|
|
578
|
+
headers_extra = getattr(url, "headers")
|
|
579
|
+
if headers_extra:
|
|
580
|
+
headers.update(headers_extra)
|
|
581
|
+
urlopen = ensure_async(urlopen, threaded=True)
|
|
582
|
+
response = await urlopen((await url()) if callable(url) else url, headers=headers)
|
|
583
|
+
if start:
|
|
584
|
+
rng = get_range(response)
|
|
585
|
+
if not rng:
|
|
586
|
+
raise OSError(errno.ESPIPE, "non-seekable")
|
|
587
|
+
start = rng[0]
|
|
588
|
+
self.__dict__.update(
|
|
589
|
+
url = url,
|
|
590
|
+
response = response,
|
|
591
|
+
length = get_total_length(response) or 0,
|
|
592
|
+
chunked = is_chunked(response),
|
|
593
|
+
start = start,
|
|
594
|
+
closed = False,
|
|
595
|
+
urlopen = urlopen,
|
|
596
|
+
headers = MappingProxyType(headers),
|
|
597
|
+
seek_threshold = max(seek_threshold, 0),
|
|
598
|
+
_seekable = is_range_request(response),
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
async def aclose(self, /):
|
|
602
|
+
if not self.closed:
|
|
603
|
+
self.__dict__["closed"] = True
|
|
604
|
+
await self.close_response()
|
|
605
|
+
|
|
606
|
+
def close(self, /):
|
|
607
|
+
if not self.closed:
|
|
608
|
+
self.__dict__["closed"] = True
|
|
609
|
+
try:
|
|
610
|
+
ret = self.response.aclose()
|
|
611
|
+
except (AttributeError, TypeError):
|
|
612
|
+
ret = self.response.close()
|
|
613
|
+
if isawaitable(ret):
|
|
614
|
+
run_async(ret)
|
|
615
|
+
|
|
616
|
+
async def close_response(self, /):
|
|
617
|
+
try:
|
|
618
|
+
ret = self.response.aclose()
|
|
619
|
+
except (AttributeError, TypeError):
|
|
620
|
+
ret = self.response.close()
|
|
621
|
+
if isawaitable(ret):
|
|
622
|
+
await ret
|
|
623
|
+
|
|
624
|
+
async def flush(self, /):
|
|
625
|
+
return await ensure_async(self.file.flush, threaded=True)()
|
|
626
|
+
|
|
627
|
+
async def read(self, size: int = -1, /) -> bytes: # type: ignore
|
|
628
|
+
if self.closed:
|
|
629
|
+
raise ValueError("I/O operation on closed file.")
|
|
630
|
+
if size == 0 or not self.chunked and self.tell() >= self.length:
|
|
631
|
+
return b""
|
|
632
|
+
if self.file_closed:
|
|
633
|
+
await self.reconnect()
|
|
634
|
+
if size is None or size < 0:
|
|
635
|
+
data = await ensure_async(self.file.read, threaded=True)()
|
|
636
|
+
else:
|
|
637
|
+
data = await ensure_async(self.file.read, threaded=True)(size)
|
|
638
|
+
if data:
|
|
639
|
+
self._add_start(len(data))
|
|
640
|
+
return data
|
|
641
|
+
|
|
642
|
+
async def readinto(self, buffer, /) -> int: # type: ignore
|
|
643
|
+
if self.closed:
|
|
644
|
+
raise ValueError("I/O operation on closed file.")
|
|
645
|
+
if not buffer or not self.chunked and self.tell() >= self.length:
|
|
646
|
+
return 0
|
|
647
|
+
if self.file_closed:
|
|
648
|
+
await self.reconnect()
|
|
649
|
+
size = await ensure_async(self.file.readinto, threaded=True)(buffer)
|
|
650
|
+
if size:
|
|
651
|
+
self._add_start(size)
|
|
652
|
+
return size
|
|
653
|
+
|
|
654
|
+
async def readline(self, size: None | int = -1, /) -> bytes: # type: ignore
|
|
655
|
+
if self.closed:
|
|
656
|
+
raise ValueError("I/O operation on closed file.")
|
|
657
|
+
if size == 0 or not self.chunked and self.tell() >= self.length:
|
|
658
|
+
return b""
|
|
659
|
+
if self.file_closed:
|
|
660
|
+
await self.reconnect()
|
|
661
|
+
if size is None or size < 0:
|
|
662
|
+
data = await ensure_async(self.file.readline, threaded=True)()
|
|
663
|
+
else:
|
|
664
|
+
data = await ensure_async(self.file.readline, threaded=True)(size)
|
|
665
|
+
if data:
|
|
666
|
+
self._add_start(len(data))
|
|
667
|
+
return data
|
|
668
|
+
|
|
669
|
+
async def readlines(self, hint: int = -1, /) -> list[bytes]: # type: ignore
|
|
670
|
+
if self.closed:
|
|
671
|
+
raise ValueError("I/O operation on closed file.")
|
|
672
|
+
if not self.chunked and self.tell() >= self.length:
|
|
673
|
+
return []
|
|
674
|
+
if self.file_closed:
|
|
675
|
+
await self.reconnect()
|
|
676
|
+
ls = await ensure_async(self.file.readlines, threaded=True)(hint)
|
|
677
|
+
if ls:
|
|
678
|
+
self._add_start(sum(map(len, ls)))
|
|
679
|
+
return ls
|
|
680
|
+
|
|
681
|
+
async def reconnect(self, /, start: None | int = None) -> int: # type: ignore
|
|
682
|
+
if not self._seekable:
|
|
683
|
+
if start is None and self.tell() or start:
|
|
684
|
+
raise OSError(errno.EOPNOTSUPP, "Unsupport for reconnection of non-seekable streams.")
|
|
685
|
+
start = 0
|
|
686
|
+
if start is None:
|
|
687
|
+
start = self.tell()
|
|
688
|
+
elif start < 0:
|
|
689
|
+
start = self.length + start
|
|
690
|
+
if start < 0:
|
|
691
|
+
start = 0
|
|
692
|
+
if start >= self.length:
|
|
693
|
+
self.__dict__.update(start=start)
|
|
694
|
+
return start
|
|
695
|
+
await self.close_response()
|
|
696
|
+
url = self.url
|
|
697
|
+
response = await self.urlopen(
|
|
698
|
+
(await url()) if callable(url) else url,
|
|
699
|
+
headers={**self.headers, "Range": f"bytes={start}-"}
|
|
700
|
+
)
|
|
701
|
+
length_new = get_total_length(response)
|
|
702
|
+
if self.length != length_new:
|
|
703
|
+
raise OSError(errno.EIO, f"file size changed: {self.length} -> {length_new}")
|
|
704
|
+
self.__dict__.update(
|
|
705
|
+
response=response,
|
|
706
|
+
start=start,
|
|
707
|
+
closed=False,
|
|
708
|
+
)
|
|
709
|
+
return start
|
|
710
|
+
|
|
711
|
+
async def seek(self, pos: int, whence: int = 0, /) -> int: # type: ignore
|
|
712
|
+
if self.closed:
|
|
713
|
+
raise ValueError("I/O operation on closed file.")
|
|
714
|
+
if not self._seekable:
|
|
715
|
+
raise OSError(errno.EINVAL, "not a seekable stream")
|
|
716
|
+
if whence == 0:
|
|
717
|
+
if pos < 0:
|
|
718
|
+
raise OSError(errno.EINVAL, f"negative seek start: {pos!r}")
|
|
719
|
+
old_pos = self.tell()
|
|
720
|
+
if old_pos == pos:
|
|
721
|
+
return pos
|
|
722
|
+
if pos > old_pos and (size := pos - old_pos) <= self.seek_threshold:
|
|
723
|
+
if size <= COPY_BUFSIZE:
|
|
724
|
+
await self.read(size)
|
|
725
|
+
else:
|
|
726
|
+
buf = bytearray(COPY_BUFSIZE)
|
|
727
|
+
readinto = self.readinto
|
|
728
|
+
while size > COPY_BUFSIZE:
|
|
729
|
+
await readinto(buf)
|
|
730
|
+
size -= COPY_BUFSIZE
|
|
731
|
+
await self.read(size)
|
|
732
|
+
else:
|
|
733
|
+
await self.reconnect(pos)
|
|
734
|
+
return pos
|
|
735
|
+
elif whence == 1:
|
|
736
|
+
if pos == 0:
|
|
737
|
+
return self.tell()
|
|
738
|
+
return await self.seek(self.tell() + pos)
|
|
739
|
+
elif whence == 2:
|
|
740
|
+
return await self.seek(self.length + pos)
|
|
741
|
+
else:
|
|
742
|
+
raise OSError(errno.EINVAL, f"whence value unsupported: {whence!r}")
|
|
743
|
+
|
|
744
|
+
@classmethod
|
|
745
|
+
async def new(
|
|
746
|
+
cls,
|
|
747
|
+
/,
|
|
748
|
+
url: str | Callable[[], str],
|
|
749
|
+
headers: None | Mapping = None,
|
|
750
|
+
start: int = 0,
|
|
751
|
+
seek_threshold: int = 1 << 20,
|
|
752
|
+
urlopen = None,
|
|
753
|
+
) -> Self: # type: ignore
|
|
754
|
+
self = cls.__new__(cls)
|
|
755
|
+
await self.__ainit__(
|
|
756
|
+
url=url,
|
|
757
|
+
headers=headers,
|
|
758
|
+
start=start,
|
|
759
|
+
seek_threshold=seek_threshold,
|
|
760
|
+
urlopen=urlopen,
|
|
761
|
+
)
|
|
762
|
+
return self
|
|
763
|
+
|
|
764
|
+
@overload # type: ignore
|
|
765
|
+
@classmethod
|
|
766
|
+
async def open(
|
|
767
|
+
cls,
|
|
768
|
+
/,
|
|
769
|
+
url: str | Callable[[], str],
|
|
770
|
+
mode: Literal["br", "rb"],
|
|
771
|
+
*,
|
|
772
|
+
buffering: None | int = None,
|
|
773
|
+
encoding: None | str = None,
|
|
774
|
+
errors: None | str = None,
|
|
775
|
+
newline: None | str = None,
|
|
776
|
+
headers: None | Mapping = None,
|
|
777
|
+
start: int = 0,
|
|
778
|
+
seek_threshold: int = 1 << 20,
|
|
779
|
+
urlopen = None,
|
|
780
|
+
) -> Self | AsyncBufferedReader:
|
|
781
|
+
...
|
|
782
|
+
@overload
|
|
783
|
+
@classmethod
|
|
784
|
+
async def open(
|
|
785
|
+
cls,
|
|
786
|
+
/,
|
|
787
|
+
url: str | Callable[[], str],
|
|
788
|
+
mode: Literal["r", "rt", "tr"] = "r",
|
|
789
|
+
*,
|
|
790
|
+
buffering: None | int = None,
|
|
791
|
+
encoding: None | str = None,
|
|
792
|
+
errors: None | str = None,
|
|
793
|
+
newline: None | str = None,
|
|
794
|
+
headers: None | Mapping = None,
|
|
795
|
+
start: int = 0,
|
|
796
|
+
seek_threshold: int = 1 << 20,
|
|
797
|
+
urlopen = None,
|
|
798
|
+
) -> AsyncTextIOWrapper:
|
|
799
|
+
...
|
|
800
|
+
@classmethod
|
|
801
|
+
async def open(
|
|
802
|
+
cls,
|
|
803
|
+
/,
|
|
804
|
+
url: str | Callable[[], str],
|
|
805
|
+
mode: Literal["r", "rt", "tr", "br", "rb"] = "r",
|
|
806
|
+
buffering: None | int = None,
|
|
807
|
+
encoding: None | str = None,
|
|
808
|
+
errors: None | str = None,
|
|
809
|
+
newline: None | str = None,
|
|
810
|
+
headers: None | Mapping = None,
|
|
811
|
+
start: int = 0,
|
|
812
|
+
seek_threshold: int = 1 << 20,
|
|
813
|
+
urlopen = None,
|
|
814
|
+
) -> Self | AsyncBufferedReader | AsyncTextIOWrapper:
|
|
815
|
+
file = await cls.new(
|
|
816
|
+
url=url,
|
|
817
|
+
headers=headers,
|
|
818
|
+
start=start,
|
|
819
|
+
seek_threshold=seek_threshold,
|
|
820
|
+
urlopen=urlopen,
|
|
821
|
+
)
|
|
822
|
+
if mode not in ("r", "rt", "tr", "rb", "br"):
|
|
823
|
+
raise OSError(errno.EINVAL, f"invalid (or unsupported) mode: {mode!r}")
|
|
824
|
+
return file.wrap(
|
|
825
|
+
text_mode="b" not in mode, # type: ignore
|
|
826
|
+
buffering=buffering,
|
|
827
|
+
encoding=encoding,
|
|
828
|
+
errors=errors,
|
|
829
|
+
newline=newline,
|
|
830
|
+
)
|
|
831
|
+
|
|
832
|
+
@overload
|
|
833
|
+
def wrap(
|
|
834
|
+
self,
|
|
835
|
+
/,
|
|
836
|
+
text_mode: Literal[False] = False,
|
|
837
|
+
*,
|
|
838
|
+
buffering: None | int = None,
|
|
839
|
+
encoding: None | str = None,
|
|
840
|
+
errors: None | str = None,
|
|
841
|
+
newline: None | str = None,
|
|
842
|
+
) -> Self | AsyncBufferedReader:
|
|
843
|
+
...
|
|
844
|
+
@overload
|
|
845
|
+
def wrap(
|
|
846
|
+
self,
|
|
847
|
+
/,
|
|
848
|
+
text_mode: Literal[True],
|
|
849
|
+
*,
|
|
850
|
+
buffering: None | int = None,
|
|
851
|
+
encoding: None | str = None,
|
|
852
|
+
errors: None | str = None,
|
|
853
|
+
newline: None | str = None,
|
|
854
|
+
) -> AsyncTextIOWrapper:
|
|
855
|
+
...
|
|
856
|
+
def wrap(
|
|
857
|
+
self,
|
|
858
|
+
/,
|
|
859
|
+
text_mode: bool = False,
|
|
860
|
+
*,
|
|
861
|
+
buffering: None | int = None,
|
|
862
|
+
encoding: None | str = None,
|
|
863
|
+
errors: None | str = None,
|
|
864
|
+
newline: None | str = None,
|
|
865
|
+
) -> Self | AsyncBufferedReader | AsyncTextIOWrapper:
|
|
866
|
+
if buffering is None:
|
|
867
|
+
if text_mode:
|
|
868
|
+
buffering = DEFAULT_BUFFER_SIZE
|
|
869
|
+
else:
|
|
870
|
+
buffering = 0
|
|
871
|
+
if buffering == 0:
|
|
872
|
+
if text_mode:
|
|
873
|
+
raise OSError(errno.EINVAL, "can't have unbuffered text I/O")
|
|
874
|
+
return self
|
|
875
|
+
line_buffering = False
|
|
876
|
+
buffer_size: int
|
|
877
|
+
if buffering < 0:
|
|
878
|
+
buffer_size = DEFAULT_BUFFER_SIZE
|
|
879
|
+
elif buffering == 1:
|
|
880
|
+
if not text_mode:
|
|
881
|
+
warn("line buffering (buffering=1) isn't supported in binary mode, "
|
|
882
|
+
"the default buffer size will be used", RuntimeWarning)
|
|
883
|
+
buffer_size = DEFAULT_BUFFER_SIZE
|
|
884
|
+
line_buffering = True
|
|
885
|
+
else:
|
|
886
|
+
buffer_size = buffering
|
|
887
|
+
raw = self
|
|
888
|
+
buffer = AsyncBufferedReader(raw, buffer_size)
|
|
889
|
+
if text_mode:
|
|
890
|
+
return AsyncTextIOWrapper(
|
|
891
|
+
buffer,
|
|
892
|
+
encoding=encoding,
|
|
893
|
+
errors=errors,
|
|
894
|
+
newline=newline,
|
|
895
|
+
line_buffering=line_buffering,
|
|
896
|
+
)
|
|
897
|
+
else:
|
|
898
|
+
return buffer
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
if find_spec("urllib3"):
|
|
902
|
+
from urllib.error import HTTPError
|
|
903
|
+
|
|
904
|
+
_urllib3_urlopen = None
|
|
905
|
+
|
|
906
|
+
class Urllib3FileReader(HTTPFileReader):
|
|
907
|
+
|
|
908
|
+
def __init__(
|
|
909
|
+
self,
|
|
910
|
+
/,
|
|
911
|
+
url: str | Callable[[], str],
|
|
912
|
+
headers: None | Mapping = None,
|
|
913
|
+
start: int = 0,
|
|
914
|
+
seek_threshold: int = 1 << 20,
|
|
915
|
+
urlopen = None,
|
|
916
|
+
):
|
|
917
|
+
global _urllib3_urlopen
|
|
918
|
+
from urllib3 import request
|
|
919
|
+
from urllib3.poolmanager import PoolManager
|
|
920
|
+
|
|
921
|
+
if isinstance(urlopen, PoolManager):
|
|
922
|
+
urlopen = partial(urlopen.request, "GET", preload_content=False)
|
|
923
|
+
elif urlopen is None:
|
|
924
|
+
if _urllib3_urlopen is None:
|
|
925
|
+
urlopen = _urllib3_urlopen = partial(cast(Callable, request), "GET", preload_content=False)
|
|
926
|
+
else:
|
|
927
|
+
urlopen = _urllib3_urlopen
|
|
928
|
+
else:
|
|
929
|
+
urlopen = partial(urlopen, preload_content=False)
|
|
930
|
+
|
|
931
|
+
def urlopen_wrapper(url: str, headers: None | Mapping = headers):
|
|
932
|
+
resp = urlopen(url, headers=headers)
|
|
933
|
+
if resp.status >= 400:
|
|
934
|
+
raise HTTPError(resp.url, resp.status, resp.reason, resp.headers, resp)
|
|
935
|
+
return resp
|
|
936
|
+
|
|
937
|
+
super().__init__(
|
|
938
|
+
url,
|
|
939
|
+
headers=headers,
|
|
940
|
+
start=start,
|
|
941
|
+
seek_threshold=seek_threshold,
|
|
942
|
+
urlopen=urlopen_wrapper,
|
|
943
|
+
)
|
|
944
|
+
|
|
945
|
+
__all__.append("Urllib3FileReader")
|
|
946
|
+
|
|
947
|
+
|
|
948
|
+
if find_spec("requests"):
|
|
949
|
+
_requests_urlopen = None
|
|
390
950
|
|
|
391
951
|
class RequestsFileReader(HTTPFileReader):
|
|
392
952
|
|
|
@@ -394,13 +954,26 @@ try:
|
|
|
394
954
|
self,
|
|
395
955
|
/,
|
|
396
956
|
url: str | Callable[[], str],
|
|
397
|
-
headers:
|
|
957
|
+
headers: None | Mapping = None,
|
|
398
958
|
start: int = 0,
|
|
399
959
|
seek_threshold: int = 1 << 20,
|
|
400
|
-
urlopen
|
|
960
|
+
urlopen = None,
|
|
401
961
|
):
|
|
402
|
-
|
|
403
|
-
|
|
962
|
+
global _requests_urlopen
|
|
963
|
+
from requests import request, Session
|
|
964
|
+
if isinstance(urlopen, Session):
|
|
965
|
+
urlopen = partial(urlopen.request, "GET", stream=True)
|
|
966
|
+
elif urlopen is None:
|
|
967
|
+
if _requests_urlopen is None:
|
|
968
|
+
if "__del__" not in Session.__dict__:
|
|
969
|
+
setattr(Session, "__del__", lambda self, /: self.close())
|
|
970
|
+
urlopen = _requests_urlopen = partial(request, "GET", stream=True)
|
|
971
|
+
else:
|
|
972
|
+
urlopen = _requests_urlopen
|
|
973
|
+
else:
|
|
974
|
+
urlopen = partial(urlopen, stream=True)
|
|
975
|
+
def urlopen_wrapper(url: str, headers: None | Mapping = headers):
|
|
976
|
+
resp = urlopen(url, headers=headers)
|
|
404
977
|
resp.raise_for_status()
|
|
405
978
|
return resp
|
|
406
979
|
super().__init__(
|
|
@@ -411,41 +984,217 @@ try:
|
|
|
411
984
|
urlopen=urlopen_wrapper,
|
|
412
985
|
)
|
|
413
986
|
|
|
414
|
-
def _add_start(self, delta: int, /):
|
|
415
|
-
pass
|
|
416
|
-
|
|
417
987
|
@funcproperty
|
|
418
988
|
def file(self, /) -> BinaryIO:
|
|
419
989
|
return self.response.raw
|
|
420
990
|
|
|
421
|
-
def tell(self, /) -> int:
|
|
422
|
-
start = self.start
|
|
423
|
-
if start >= self.length:
|
|
424
|
-
return start
|
|
425
|
-
return start + self.file.tell()
|
|
426
|
-
|
|
427
991
|
__all__.append("RequestsFileReader")
|
|
428
|
-
except ImportError:
|
|
429
|
-
pass
|
|
430
992
|
|
|
431
993
|
|
|
432
|
-
|
|
433
|
-
from
|
|
994
|
+
if find_spec("aiohttp"):
|
|
995
|
+
from types import MethodType
|
|
434
996
|
|
|
435
|
-
|
|
436
|
-
except ImportError:
|
|
437
|
-
pass
|
|
997
|
+
_aiohttp_urlopen = None
|
|
438
998
|
|
|
999
|
+
class AiohttpFileReader(AsyncHTTPFileReader):
|
|
439
1000
|
|
|
440
|
-
|
|
441
|
-
|
|
1001
|
+
async def __ainit__(
|
|
1002
|
+
self,
|
|
1003
|
+
/,
|
|
1004
|
+
url: str | Callable[[], str] | Callable[[], Awaitable[str]],
|
|
1005
|
+
headers: None | Mapping = None,
|
|
1006
|
+
start: int = 0,
|
|
1007
|
+
seek_threshold: int = 1 << 20,
|
|
1008
|
+
urlopen = None,
|
|
1009
|
+
):
|
|
1010
|
+
global _aiohttp_urlopen
|
|
1011
|
+
from aiohttp import request, ClientSession
|
|
442
1012
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
1013
|
+
close_session = True
|
|
1014
|
+
if isinstance(urlopen, ClientSession):
|
|
1015
|
+
urlopen = urlopen.get
|
|
1016
|
+
close_session = False
|
|
1017
|
+
if urlopen is None:
|
|
1018
|
+
if _aiohttp_urlopen is None:
|
|
1019
|
+
urlopen = _aiohttp_urlopen = partial(request, "GET")
|
|
1020
|
+
else:
|
|
1021
|
+
urlopen = _aiohttp_urlopen
|
|
1022
|
+
else:
|
|
1023
|
+
func = urlopen
|
|
1024
|
+
if isinstance(func, partial):
|
|
1025
|
+
func = func.func
|
|
1026
|
+
close_session = not (
|
|
1027
|
+
isinstance(func, MethodType) and
|
|
1028
|
+
isinstance(func.__self__, ClientSession)
|
|
1029
|
+
)
|
|
1030
|
+
async def urlopen_wrapper(url: str, headers: None | Mapping = headers):
|
|
1031
|
+
resp = await urlopen(url, headers=headers).__aenter__()
|
|
1032
|
+
async def aclose():
|
|
1033
|
+
if close_session:
|
|
1034
|
+
try:
|
|
1035
|
+
await resp._session.close()
|
|
1036
|
+
except AttributeError:
|
|
1037
|
+
pass
|
|
1038
|
+
resp.close()
|
|
1039
|
+
resp.aclose = aclose
|
|
1040
|
+
resp.raise_for_status()
|
|
1041
|
+
return resp
|
|
1042
|
+
await super().__ainit__(
|
|
1043
|
+
url=url,
|
|
1044
|
+
headers=headers,
|
|
1045
|
+
start=start,
|
|
1046
|
+
seek_threshold=seek_threshold,
|
|
1047
|
+
urlopen=urlopen_wrapper,
|
|
1048
|
+
)
|
|
446
1049
|
|
|
1050
|
+
@funcproperty
|
|
1051
|
+
def file(self, /) -> BinaryIO:
|
|
1052
|
+
return self.response.content
|
|
447
1053
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
1054
|
+
@funcproperty
|
|
1055
|
+
def file_closed(self, /) -> bool:
|
|
1056
|
+
return self.response.closed
|
|
1057
|
+
|
|
1058
|
+
__all__.append("AiohttpFileReader")
|
|
1059
|
+
|
|
1060
|
+
|
|
1061
|
+
if find_spec("httpx"):
|
|
1062
|
+
from filewrap import bytes_iter_to_reader, bytes_iter_to_async_reader
|
|
1063
|
+
|
|
1064
|
+
_httpx_urlopen = None
|
|
1065
|
+
_httpx_urlopen_async = None
|
|
1066
|
+
|
|
1067
|
+
class HttpxFileReader(HTTPFileReader):
|
|
451
1068
|
|
|
1069
|
+
def __init__(
|
|
1070
|
+
self,
|
|
1071
|
+
/,
|
|
1072
|
+
url: str | Callable[[], str],
|
|
1073
|
+
headers: None | Mapping = None,
|
|
1074
|
+
start: int = 0,
|
|
1075
|
+
seek_threshold: int = 1 << 20,
|
|
1076
|
+
urlopen = None,
|
|
1077
|
+
):
|
|
1078
|
+
global _httpx_urlopen
|
|
1079
|
+
from httpx import stream, Client
|
|
1080
|
+
|
|
1081
|
+
if isinstance(urlopen, Client):
|
|
1082
|
+
urlopen = partial(urlopen.stream, "GET")
|
|
1083
|
+
elif urlopen is None:
|
|
1084
|
+
if _httpx_urlopen is None:
|
|
1085
|
+
if "__del__" not in Client.__dict__:
|
|
1086
|
+
setattr(Client, "__del__", lambda self: self.close())
|
|
1087
|
+
urlopen = _httpx_urlopen = partial(stream, "GET")
|
|
1088
|
+
else:
|
|
1089
|
+
urlopen = _httpx_urlopen
|
|
1090
|
+
|
|
1091
|
+
def urlopen_wrapper(url: str, headers: None | Mapping = headers):
|
|
1092
|
+
context = urlopen(url, headers=headers)
|
|
1093
|
+
resp = context.__enter__()
|
|
1094
|
+
resp.raise_for_status()
|
|
1095
|
+
resp.context = context
|
|
1096
|
+
file = bytes_iter_to_reader(resp.iter_raw())
|
|
1097
|
+
self.__dict__["file"] = file
|
|
1098
|
+
return resp
|
|
1099
|
+
|
|
1100
|
+
super().__init__(
|
|
1101
|
+
url,
|
|
1102
|
+
headers=headers,
|
|
1103
|
+
start=start,
|
|
1104
|
+
seek_threshold=seek_threshold,
|
|
1105
|
+
urlopen=urlopen_wrapper,
|
|
1106
|
+
)
|
|
1107
|
+
|
|
1108
|
+
@funcproperty
|
|
1109
|
+
def file_closed(self, /) -> bool:
|
|
1110
|
+
return self.response.is_closed
|
|
1111
|
+
|
|
1112
|
+
class AsyncHttpxFileReader(AsyncHTTPFileReader):
|
|
1113
|
+
|
|
1114
|
+
async def __ainit__(
|
|
1115
|
+
self,
|
|
1116
|
+
/,
|
|
1117
|
+
url: str | Callable[[], str] | Callable[[], Awaitable[str]],
|
|
1118
|
+
headers: None | Mapping = None,
|
|
1119
|
+
start: int = 0,
|
|
1120
|
+
seek_threshold: int = 1 << 20,
|
|
1121
|
+
urlopen = None,
|
|
1122
|
+
):
|
|
1123
|
+
global _httpx_urlopen_async
|
|
1124
|
+
from httpx import AsyncClient
|
|
1125
|
+
|
|
1126
|
+
if isinstance(urlopen, AsyncClient):
|
|
1127
|
+
urlopen = partial(urlopen.stream, "GET")
|
|
1128
|
+
elif urlopen is None:
|
|
1129
|
+
if _httpx_urlopen_async is None:
|
|
1130
|
+
if "__del__" not in AsyncClient.__dict__:
|
|
1131
|
+
setattr(AsyncClient, "__del__", lambda self: run_async(self.aclose()))
|
|
1132
|
+
def async_stream(
|
|
1133
|
+
method,
|
|
1134
|
+
url,
|
|
1135
|
+
params = None,
|
|
1136
|
+
content = None,
|
|
1137
|
+
data = None,
|
|
1138
|
+
files = None,
|
|
1139
|
+
json = None,
|
|
1140
|
+
headers = None,
|
|
1141
|
+
cookies = None,
|
|
1142
|
+
auth = None,
|
|
1143
|
+
proxy = None,
|
|
1144
|
+
proxies = None,
|
|
1145
|
+
timeout = 5.0,
|
|
1146
|
+
follow_redirects = False,
|
|
1147
|
+
verify = True,
|
|
1148
|
+
cert = None,
|
|
1149
|
+
trust_env = True,
|
|
1150
|
+
):
|
|
1151
|
+
client = AsyncClient(
|
|
1152
|
+
cookies=cookies,
|
|
1153
|
+
proxy=proxy,
|
|
1154
|
+
proxies=proxies,
|
|
1155
|
+
cert=cert,
|
|
1156
|
+
verify=verify,
|
|
1157
|
+
timeout=timeout,
|
|
1158
|
+
trust_env=trust_env,
|
|
1159
|
+
)
|
|
1160
|
+
return client.stream(
|
|
1161
|
+
method=method,
|
|
1162
|
+
url=url,
|
|
1163
|
+
content=content,
|
|
1164
|
+
data=data,
|
|
1165
|
+
files=files,
|
|
1166
|
+
json=json,
|
|
1167
|
+
params=params,
|
|
1168
|
+
headers=headers,
|
|
1169
|
+
auth=auth,
|
|
1170
|
+
follow_redirects=follow_redirects,
|
|
1171
|
+
)
|
|
1172
|
+
urlopen = _httpx_urlopen_async = partial(async_stream, "GET")
|
|
1173
|
+
else:
|
|
1174
|
+
urlopen = _httpx_urlopen_async
|
|
1175
|
+
|
|
1176
|
+
async def urlopen_wrapper(url: str, headers: None | Mapping = headers):
|
|
1177
|
+
context = urlopen(url, headers=headers)
|
|
1178
|
+
resp = await context.__aenter__()
|
|
1179
|
+
resp.raise_for_status()
|
|
1180
|
+
resp.context = context
|
|
1181
|
+
file = bytes_iter_to_async_reader(resp.aiter_raw())
|
|
1182
|
+
self.__dict__["file"] = file
|
|
1183
|
+
return resp
|
|
1184
|
+
|
|
1185
|
+
await super().__ainit__(
|
|
1186
|
+
url=url,
|
|
1187
|
+
headers=headers,
|
|
1188
|
+
start=start,
|
|
1189
|
+
seek_threshold=seek_threshold,
|
|
1190
|
+
urlopen=urlopen_wrapper,
|
|
1191
|
+
)
|
|
1192
|
+
|
|
1193
|
+
@funcproperty
|
|
1194
|
+
def file_closed(self, /) -> bool:
|
|
1195
|
+
return self.response.is_closed
|
|
1196
|
+
|
|
1197
|
+
__all__.append("HttpxFileReader")
|
|
1198
|
+
__all__.append("AsyncHttpxFileReader")
|
|
1199
|
+
|
|
1200
|
+
# TODO: 设计实现一个 HTTPFileWriter,用于实现上传,关闭后视为上传完成
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-httpfile
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary: Python httpfile.
|
|
5
5
|
Home-page: https://github.com/ChenyangGao/web-mount-packs/tree/main/python-module/python-httpfile
|
|
6
6
|
License: MIT
|
|
@@ -22,8 +22,10 @@ Classifier: Topic :: Software Development
|
|
|
22
22
|
Classifier: Topic :: Software Development :: Libraries
|
|
23
23
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
24
|
Requires-Dist: http_response
|
|
25
|
-
Requires-Dist: python-
|
|
26
|
-
Requires-Dist: python-
|
|
25
|
+
Requires-Dist: python-asynctools (>=0.0.5)
|
|
26
|
+
Requires-Dist: python-filewrap (>=0.2.1)
|
|
27
|
+
Requires-Dist: python-property (>=0.0.2)
|
|
28
|
+
Requires-Dist: python-urlopen (>=0.0.7.1)
|
|
27
29
|
Project-URL: Repository, https://github.com/ChenyangGao/web-mount-packs/tree/main/python-module/python-httpfile
|
|
28
30
|
Description-Content-Type: text/markdown
|
|
29
31
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
|
|
2
|
+
httpfile/__init__.py,sha256=PAqvhrEAfTEzVSZ7wbLrPplYX-8G1epkW7MZt2sWWaw,38657
|
|
3
|
+
httpfile/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
python_httpfile-0.0.4.dist-info/LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
|
|
5
|
+
python_httpfile-0.0.4.dist-info/METADATA,sha256=1CtTQ_EoeIP9eVYlevzlEBdA9D26S9pHfZmdTOnXB0Q,1520
|
|
6
|
+
python_httpfile-0.0.4.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
7
|
+
python_httpfile-0.0.4.dist-info/RECORD,,
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
|
|
2
|
-
httpfile/__init__.py,sha256=9qTmQOShcWtnLtIsqL3NbIeH9M2tAWE9NquEjyFs9ZM,13416
|
|
3
|
-
httpfile/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
python_httpfile-0.0.2.1.dist-info/LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
|
|
5
|
-
python_httpfile-0.0.2.1.dist-info/METADATA,sha256=tKxwg73IETWHCmf-F7--YKrQtlgn15r4iUxA7BzDkqQ,1416
|
|
6
|
-
python_httpfile-0.0.2.1.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
7
|
-
python_httpfile-0.0.2.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|