python-filewrap 0.1.3__py3-none-any.whl → 0.2__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.
- filewrap/__init__.py +1109 -119
- {python_filewrap-0.1.3.dist-info → python_filewrap-0.2.dist-info}/METADATA +3 -2
- python_filewrap-0.2.dist-info/RECORD +7 -0
- python_filewrap-0.1.3.dist-info/RECORD +0 -7
- {python_filewrap-0.1.3.dist-info → python_filewrap-0.2.dist-info}/LICENSE +0 -0
- {python_filewrap-0.1.3.dist-info → python_filewrap-0.2.dist-info}/WHEEL +0 -0
filewrap/__init__.py
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
# encoding: utf-8
|
|
3
3
|
|
|
4
4
|
__author__ = "ChenyangGao <https://chenyanggao.github.io>"
|
|
5
|
-
__version__ = (0,
|
|
5
|
+
__version__ = (0, 2)
|
|
6
6
|
__all__ = [
|
|
7
|
-
"Buffer", "SupportsRead", "SupportsReadinto",
|
|
8
|
-
"
|
|
7
|
+
"Buffer", "SupportsRead", "SupportsReadinto", "SupportsWrite", "SupportsSeek",
|
|
8
|
+
"AsyncBufferedReader", "AsyncTextIOWrapper",
|
|
9
9
|
"bio_chunk_iter", "bio_chunk_async_iter",
|
|
10
10
|
"bio_skip_iter", "bio_skip_async_iter",
|
|
11
11
|
"bytes_iter", "bytes_async_iter",
|
|
@@ -16,58 +16,52 @@ __all__ = [
|
|
|
16
16
|
"progress_bytes_iter", "progress_bytes_async_iter",
|
|
17
17
|
]
|
|
18
18
|
|
|
19
|
-
from asyncio import to_thread, Lock as AsyncLock
|
|
19
|
+
from asyncio import run as run_async, to_thread, Lock as AsyncLock
|
|
20
20
|
from collections.abc import Awaitable, AsyncIterable, AsyncIterator, Callable, Iterable, Iterator
|
|
21
21
|
from functools import update_wrapper
|
|
22
|
+
from io import BufferedIOBase, BufferedReader, BytesIO, RawIOBase, TextIOWrapper
|
|
22
23
|
from inspect import isawaitable, iscoroutinefunction, isasyncgen, isgenerator
|
|
23
24
|
from itertools import chain
|
|
25
|
+
from os import linesep
|
|
26
|
+
from re import compile as re_compile
|
|
24
27
|
from shutil import COPY_BUFSIZE # type: ignore
|
|
25
28
|
from threading import Lock
|
|
26
|
-
from typing import cast, runtime_checkable, Any, ParamSpec, Protocol, TypeVar
|
|
29
|
+
from typing import cast, runtime_checkable, Any, BinaryIO, ParamSpec, Protocol, Self, TypeVar
|
|
27
30
|
|
|
28
31
|
try:
|
|
29
32
|
from collections.abc import Buffer # type: ignore
|
|
30
33
|
except ImportError:
|
|
31
|
-
from
|
|
34
|
+
from _ctypes import _SimpleCData
|
|
32
35
|
from array import array
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
for method in methods:
|
|
37
|
-
for B in mro:
|
|
38
|
-
if method in B.__dict__:
|
|
39
|
-
if B.__dict__[method] is None:
|
|
40
|
-
return NotImplemented
|
|
41
|
-
break
|
|
42
|
-
else:
|
|
43
|
-
return NotImplemented
|
|
44
|
-
return True
|
|
45
|
-
|
|
46
|
-
class Buffer(ABC): # type: ignore
|
|
47
|
-
__slots__ = ()
|
|
48
|
-
|
|
49
|
-
@abstractmethod
|
|
37
|
+
@runtime_checkable
|
|
38
|
+
class Buffer(Protocol): # type: ignore
|
|
50
39
|
def __buffer__(self, flags: int, /) -> memoryview:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
@classmethod
|
|
54
|
-
def __subclasshook__(cls, C):
|
|
55
|
-
if cls is Buffer:
|
|
56
|
-
return _check_methods(C, "__buffer__")
|
|
57
|
-
return NotImplemented
|
|
40
|
+
pass
|
|
58
41
|
|
|
59
42
|
Buffer.register(bytes)
|
|
60
43
|
Buffer.register(bytearray)
|
|
61
44
|
Buffer.register(memoryview)
|
|
45
|
+
Buffer.register(_SimpleCData)
|
|
62
46
|
Buffer.register(array)
|
|
63
47
|
|
|
64
|
-
from asynctools import async_chain, ensure_async, ensure_aiter
|
|
48
|
+
from asynctools import async_chain, ensure_async, ensure_aiter, ensure_coroutine
|
|
49
|
+
from property import staticproperty
|
|
65
50
|
|
|
66
51
|
|
|
67
52
|
Args = ParamSpec("Args")
|
|
68
53
|
_T_co = TypeVar("_T_co", covariant=True)
|
|
69
54
|
_T_contra = TypeVar("_T_contra", contravariant=True)
|
|
70
55
|
|
|
56
|
+
@BufferedIOBase.register
|
|
57
|
+
class VirtualBufferedReader:
|
|
58
|
+
def __new__(cls, /, *a, **k):
|
|
59
|
+
if cls is __class__: # type: ignore
|
|
60
|
+
raise TypeError("not allowed to create instances")
|
|
61
|
+
return super().__new__(cls, *a, **k)
|
|
62
|
+
|
|
63
|
+
CRE_NOT_UNIX_NEWLINES_sub = re_compile("\r\n|\r").sub
|
|
64
|
+
|
|
71
65
|
|
|
72
66
|
@runtime_checkable
|
|
73
67
|
class SupportsRead(Protocol[_T_co]):
|
|
@@ -89,8 +83,834 @@ class SupportsSeek(Protocol):
|
|
|
89
83
|
def seek(self, /, __offset: int, __whence: int = 0) -> int: ...
|
|
90
84
|
|
|
91
85
|
|
|
86
|
+
class AsyncBufferedReader(BufferedReader):
|
|
87
|
+
|
|
88
|
+
def __init__(
|
|
89
|
+
self,
|
|
90
|
+
/,
|
|
91
|
+
raw: RawIOBase,
|
|
92
|
+
buffer_size: int = 8192,
|
|
93
|
+
):
|
|
94
|
+
super().__init__(raw, min(buffer_size, 1))
|
|
95
|
+
self._buf = bytearray(buffer_size)
|
|
96
|
+
self._buf_view = memoryview(self._buf)
|
|
97
|
+
self._buf_pos = 0
|
|
98
|
+
self._buf_stop = 0
|
|
99
|
+
self._pos = raw.tell()
|
|
100
|
+
|
|
101
|
+
def __del__(self, /):
|
|
102
|
+
try:
|
|
103
|
+
self.close()
|
|
104
|
+
except:
|
|
105
|
+
pass
|
|
106
|
+
|
|
107
|
+
async def __aenter__(self, /) -> Self:
|
|
108
|
+
return self
|
|
109
|
+
|
|
110
|
+
async def __aexit__(self, /, *exc_info):
|
|
111
|
+
await self.aclose()
|
|
112
|
+
|
|
113
|
+
def __aiter__(self, /):
|
|
114
|
+
return self
|
|
115
|
+
|
|
116
|
+
async def __anext__(self, /):
|
|
117
|
+
if line := await self.readline():
|
|
118
|
+
return line
|
|
119
|
+
else:
|
|
120
|
+
raise StopAsyncIteration
|
|
121
|
+
|
|
122
|
+
def __getattr__(self, attr, /):
|
|
123
|
+
return getattr(self.raw, attr)
|
|
124
|
+
|
|
125
|
+
def __len__(self, /) -> int:
|
|
126
|
+
return self.length
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def length(self, /) -> int:
|
|
130
|
+
return getattr(self.raw, "length")
|
|
131
|
+
|
|
132
|
+
def calibrate(self, /, target: int = -1) -> bool:
|
|
133
|
+
pos = self._pos
|
|
134
|
+
if target < 0:
|
|
135
|
+
target = self.raw.tell()
|
|
136
|
+
if pos == target:
|
|
137
|
+
return True
|
|
138
|
+
buf_pos = self._buf_pos
|
|
139
|
+
buf_stop = self._buf_stop
|
|
140
|
+
self._pos = target
|
|
141
|
+
move_left = pos - target
|
|
142
|
+
reusable = 0 <= move_left <= buf_pos
|
|
143
|
+
if reusable:
|
|
144
|
+
width = buf_pos - move_left
|
|
145
|
+
self._buf_view[:width] = self._buf_view[move_left:buf_pos]
|
|
146
|
+
self._buf_pos = self._buf_stop = width
|
|
147
|
+
else:
|
|
148
|
+
self._buf_pos = self._buf_stop = 0
|
|
149
|
+
return reusable
|
|
150
|
+
|
|
151
|
+
async def aclose(self, /):
|
|
152
|
+
raw = self.raw
|
|
153
|
+
try:
|
|
154
|
+
ret = getattr(raw, "aclose")()
|
|
155
|
+
except (AttributeError, TypeError):
|
|
156
|
+
ret = getattr(raw, "close")()
|
|
157
|
+
if isawaitable(ret):
|
|
158
|
+
await ret
|
|
159
|
+
|
|
160
|
+
def close(self, /):
|
|
161
|
+
raw = self.raw
|
|
162
|
+
try:
|
|
163
|
+
ret = getattr(raw, "aclose")()
|
|
164
|
+
except (AttributeError, TypeError):
|
|
165
|
+
ret = getattr(raw, "close")()
|
|
166
|
+
if isawaitable(ret):
|
|
167
|
+
run_async(ensure_coroutine(ret))
|
|
168
|
+
|
|
169
|
+
async def flush(self, /):
|
|
170
|
+
return await ensure_async(self.raw.flush, threaded=True)()
|
|
171
|
+
|
|
172
|
+
def peek(self, size: int = 0, /) -> bytes:
|
|
173
|
+
start, stop = self._buf_pos, self._buf_stop
|
|
174
|
+
if size > 0:
|
|
175
|
+
stop = min(start + size, stop)
|
|
176
|
+
return self._buf_view[start:stop].tobytes()
|
|
177
|
+
|
|
178
|
+
def review(self, size: int = 0, /) -> bytes:
|
|
179
|
+
start, stop = 0, self._buf_pos
|
|
180
|
+
if size > 0:
|
|
181
|
+
start = max(0, stop - size)
|
|
182
|
+
return self._buf_view[start:stop].tobytes()
|
|
183
|
+
|
|
184
|
+
def context(self, /) -> tuple[bytes, int]:
|
|
185
|
+
start, stop = self._buf_pos, self._buf_stop
|
|
186
|
+
return self._buf_view[0:stop].tobytes(), start
|
|
187
|
+
|
|
188
|
+
async def read(self, size: None | int = -1, /) -> bytes: # type: ignore
|
|
189
|
+
if self.closed:
|
|
190
|
+
raise ValueError("I/O operation on closed file.")
|
|
191
|
+
if size == 0:
|
|
192
|
+
return b""
|
|
193
|
+
if size is None:
|
|
194
|
+
size = -1
|
|
195
|
+
buf_view = self._buf_view
|
|
196
|
+
buf_pos = self._buf_pos
|
|
197
|
+
buf_stop = self._buf_stop
|
|
198
|
+
buf_size = buf_stop - buf_pos
|
|
199
|
+
if size > 0:
|
|
200
|
+
if buf_size >= size:
|
|
201
|
+
buf_pos_stop = self._buf_pos = buf_pos + size
|
|
202
|
+
self._pos += size
|
|
203
|
+
return buf_view[buf_pos:buf_pos_stop].tobytes()
|
|
204
|
+
buffer_view = memoryview(bytearray(size))
|
|
205
|
+
buffer_view[:buf_size] = buf_view[buf_pos:buf_stop]
|
|
206
|
+
self._buf_pos = buf_stop
|
|
207
|
+
self._pos += buf_size
|
|
208
|
+
buf_size += await self.readinto(buffer_view[buf_size:])
|
|
209
|
+
return buffer_view[:buf_size].tobytes()
|
|
210
|
+
BUFSIZE = len(buf_view)
|
|
211
|
+
read = ensure_async(self.raw.read, threaded=True)
|
|
212
|
+
buffer = bytearray(buf_view[buf_pos:buf_stop])
|
|
213
|
+
try:
|
|
214
|
+
while data := await read(BUFSIZE):
|
|
215
|
+
buffer += data
|
|
216
|
+
length = len(data)
|
|
217
|
+
self._pos += length
|
|
218
|
+
if BUFSIZE == length:
|
|
219
|
+
buf_view[:] = data
|
|
220
|
+
if buf_pos != BUFSIZE:
|
|
221
|
+
buf_pos = self._buf_pos = self._buf_stop = BUFSIZE
|
|
222
|
+
else:
|
|
223
|
+
buf_pos_stop = buf_stop + length
|
|
224
|
+
if buf_pos_stop <= BUFSIZE:
|
|
225
|
+
buf_view[buf_stop:buf_pos_stop] = data
|
|
226
|
+
self._buf_pos = self._buf_stop = buf_pos_stop
|
|
227
|
+
else:
|
|
228
|
+
index = BUFSIZE - length
|
|
229
|
+
buf_view[:index] = buf_view[-index:]
|
|
230
|
+
buf_view[-length:] = data
|
|
231
|
+
self._buf_pos = self._buf_stop = BUFSIZE
|
|
232
|
+
break
|
|
233
|
+
return bytes(buffer)
|
|
234
|
+
except:
|
|
235
|
+
self.calibrate()
|
|
236
|
+
raise
|
|
237
|
+
|
|
238
|
+
async def read1(self, size: None | int = -1, /) -> bytes: # type: ignore
|
|
239
|
+
if self.closed:
|
|
240
|
+
raise ValueError("I/O operation on closed file.")
|
|
241
|
+
if size == 0:
|
|
242
|
+
return b""
|
|
243
|
+
if size is None:
|
|
244
|
+
size = -1
|
|
245
|
+
buf_view = self._buf_view
|
|
246
|
+
buf_pos = self._buf_pos
|
|
247
|
+
buf_stop = self._buf_stop
|
|
248
|
+
buf_size = buf_stop - buf_pos
|
|
249
|
+
if size > 0:
|
|
250
|
+
if buf_size >= size:
|
|
251
|
+
buf_pos_stop = self._buf_pos = buf_pos + size
|
|
252
|
+
self._pos += size
|
|
253
|
+
return buf_view[buf_pos:buf_pos_stop].tobytes()
|
|
254
|
+
size -= buf_size
|
|
255
|
+
try:
|
|
256
|
+
data = await ensure_async(self.raw.read, threaded=True)(size)
|
|
257
|
+
except:
|
|
258
|
+
self.calibrate()
|
|
259
|
+
raise
|
|
260
|
+
prev_data = buf_view[buf_pos:buf_stop].tobytes()
|
|
261
|
+
if data:
|
|
262
|
+
BUFSIZE = len(buf_view)
|
|
263
|
+
length = len(data)
|
|
264
|
+
self._pos += len(prev_data) + length
|
|
265
|
+
if BUFSIZE <= length:
|
|
266
|
+
buf_view[:] = memoryview(data)[-BUFSIZE:]
|
|
267
|
+
self._buf_pos = self._buf_stop = BUFSIZE
|
|
268
|
+
else:
|
|
269
|
+
buf_pos_stop = buf_stop + length
|
|
270
|
+
if buf_pos_stop <= BUFSIZE:
|
|
271
|
+
buf_view[buf_stop:buf_pos_stop] = data
|
|
272
|
+
self._buf_pos = self._buf_stop = buf_pos_stop
|
|
273
|
+
else:
|
|
274
|
+
index = BUFSIZE - length
|
|
275
|
+
buf_view[:index] = buf_view[-index:]
|
|
276
|
+
buf_view[-length:] = data
|
|
277
|
+
self._buf_pos = self._buf_stop = BUFSIZE
|
|
278
|
+
return prev_data + data
|
|
279
|
+
else:
|
|
280
|
+
return prev_data
|
|
281
|
+
|
|
282
|
+
async def readinto(self, buffer, /) -> int: # type: ignore
|
|
283
|
+
if self.closed:
|
|
284
|
+
raise ValueError("I/O operation on closed file.")
|
|
285
|
+
size = len(buffer)
|
|
286
|
+
if size == 0:
|
|
287
|
+
return 0
|
|
288
|
+
buf_view = self._buf_view
|
|
289
|
+
buf_pos = self._buf_pos
|
|
290
|
+
buf_stop = self._buf_stop
|
|
291
|
+
buf_size = buf_stop - buf_pos
|
|
292
|
+
if buf_size >= size:
|
|
293
|
+
buf_pos_stop = buf_pos + size
|
|
294
|
+
buffer[:] = buf_view[buf_pos:buf_pos_stop]
|
|
295
|
+
self._buf_pos = buf_pos_stop
|
|
296
|
+
self._pos += size
|
|
297
|
+
return size
|
|
298
|
+
try:
|
|
299
|
+
readinto = ensure_async(self.raw.readinto, threaded=True)
|
|
300
|
+
except AttributeError:
|
|
301
|
+
read = ensure_async(self.raw.read, threaded=True)
|
|
302
|
+
async def readinto(buffer, /) -> int:
|
|
303
|
+
data = await read(len(buffer))
|
|
304
|
+
if data:
|
|
305
|
+
size = len(data)
|
|
306
|
+
buffer[:size] = data
|
|
307
|
+
return size
|
|
308
|
+
else:
|
|
309
|
+
return 0
|
|
310
|
+
BUFSIZE = len(buf_view)
|
|
311
|
+
buffer_view = memoryview(buffer)
|
|
312
|
+
buffer_view[:buf_size] = buf_view[buf_pos:buf_stop]
|
|
313
|
+
buf_pos = self._buf_pos = buf_stop
|
|
314
|
+
self._pos += buf_size
|
|
315
|
+
buffer_pos = buf_size
|
|
316
|
+
size -= buf_size
|
|
317
|
+
try:
|
|
318
|
+
running = size > 0
|
|
319
|
+
while running:
|
|
320
|
+
if buf_stop < BUFSIZE:
|
|
321
|
+
length = await readinto(buf_view[buf_stop:])
|
|
322
|
+
if not length:
|
|
323
|
+
break
|
|
324
|
+
buf_stop = self._buf_stop = buf_stop + length
|
|
325
|
+
if buf_stop < BUFSIZE:
|
|
326
|
+
running = False
|
|
327
|
+
else:
|
|
328
|
+
length = await readinto(buf_view)
|
|
329
|
+
if not length:
|
|
330
|
+
break
|
|
331
|
+
if length < BUFSIZE:
|
|
332
|
+
part1, part2 = buf_view[length:].tobytes(), buf_view[:length].tobytes()
|
|
333
|
+
buf_view[:length] = part1
|
|
334
|
+
buf_view[length:] = part2
|
|
335
|
+
running = False
|
|
336
|
+
buf_pos = self._buf_pos = BUFSIZE - length
|
|
337
|
+
else:
|
|
338
|
+
buf_pos = self._buf_pos = 0
|
|
339
|
+
move = min(length, size)
|
|
340
|
+
buffer_view[buffer_pos:buffer_pos+move] = buf_view[buf_pos:buf_pos+move]
|
|
341
|
+
self._buf_pos += move
|
|
342
|
+
self._pos += move
|
|
343
|
+
buffer_pos += move
|
|
344
|
+
if move == size:
|
|
345
|
+
running = False
|
|
346
|
+
else:
|
|
347
|
+
size -= length
|
|
348
|
+
return buffer_pos
|
|
349
|
+
except:
|
|
350
|
+
self.calibrate()
|
|
351
|
+
raise
|
|
352
|
+
|
|
353
|
+
async def readinto1(self, buffer, /) -> int: # type: ignore
|
|
354
|
+
if self.closed:
|
|
355
|
+
raise ValueError("I/O operation on closed file.")
|
|
356
|
+
size = len(buffer)
|
|
357
|
+
if size == 0:
|
|
358
|
+
return 0
|
|
359
|
+
buf_view = self._buf_view
|
|
360
|
+
buf_pos = self._buf_pos
|
|
361
|
+
buf_stop = self._buf_stop
|
|
362
|
+
buf_size = buf_stop - buf_pos
|
|
363
|
+
if buf_size >= size:
|
|
364
|
+
buf_pos_stop = buf_pos + size
|
|
365
|
+
buffer[:] = buf_view[buf_pos:buf_pos_stop]
|
|
366
|
+
self._buf_pos = buf_pos_stop
|
|
367
|
+
self._pos += size
|
|
368
|
+
return size
|
|
369
|
+
try:
|
|
370
|
+
readinto = ensure_async(self.raw.readinto, threaded=True)
|
|
371
|
+
except AttributeError:
|
|
372
|
+
read = ensure_async(self.raw.read, threaded=True)
|
|
373
|
+
async def readinto(buffer, /) -> int:
|
|
374
|
+
data = await read(len(buffer))
|
|
375
|
+
if data:
|
|
376
|
+
size = len(data)
|
|
377
|
+
buffer[:size] = data
|
|
378
|
+
return size
|
|
379
|
+
else:
|
|
380
|
+
return 0
|
|
381
|
+
BUFSIZE = len(buf_view)
|
|
382
|
+
buffer_view = memoryview(buffer)
|
|
383
|
+
buffer_view[:buf_size] = buf_view[buf_pos:buf_stop]
|
|
384
|
+
buf_pos = self._buf_pos = buf_stop
|
|
385
|
+
self._pos += buf_size
|
|
386
|
+
buffer_pos = buf_size
|
|
387
|
+
size -= buf_size
|
|
388
|
+
try:
|
|
389
|
+
length = await readinto(buffer_view[buf_size:])
|
|
390
|
+
except:
|
|
391
|
+
self.calibrate()
|
|
392
|
+
raise
|
|
393
|
+
if length:
|
|
394
|
+
BUFSIZE = len(buf_view)
|
|
395
|
+
buffer_pos += length
|
|
396
|
+
self._pos += length
|
|
397
|
+
if BUFSIZE <= buffer_pos:
|
|
398
|
+
buf_view[:] = buffer_view[-BUFSIZE:]
|
|
399
|
+
self._buf_pos = self._buf_stop = BUFSIZE
|
|
400
|
+
else:
|
|
401
|
+
buf_pos_stop = buf_stop + length
|
|
402
|
+
if buf_pos_stop <= BUFSIZE:
|
|
403
|
+
buf_view[buf_stop:buf_pos_stop] = buffer_view[-length:]
|
|
404
|
+
self._buf_pos = self._buf_stop = buf_pos_stop
|
|
405
|
+
else:
|
|
406
|
+
index = BUFSIZE - length
|
|
407
|
+
buf_view[:index] = buf_view[-index:]
|
|
408
|
+
buf_view[-length:] = buffer_view[-length:]
|
|
409
|
+
self._buf_pos = self._buf_stop = BUFSIZE
|
|
410
|
+
return buffer_pos
|
|
411
|
+
|
|
412
|
+
async def readline(self, size: int | None = -1, /) -> bytes: # type: ignore
|
|
413
|
+
if self.closed:
|
|
414
|
+
raise ValueError("I/O operation on closed file.")
|
|
415
|
+
if size == 0:
|
|
416
|
+
return b""
|
|
417
|
+
if size is None:
|
|
418
|
+
size = -1
|
|
419
|
+
buf = self._buf
|
|
420
|
+
buf_pos = self._buf_pos
|
|
421
|
+
buf_stop = self._buf_stop
|
|
422
|
+
buf_size = buf_stop - buf_pos
|
|
423
|
+
if size > 0 and size <= buf_size:
|
|
424
|
+
stop = buf_pos + size
|
|
425
|
+
index = buf.find(b"\n", buf_pos, stop)
|
|
426
|
+
if index > 0:
|
|
427
|
+
buf_pos_stop = self._buf_pos = index + 1
|
|
428
|
+
self._pos += buf_pos_stop - buf_pos
|
|
429
|
+
else:
|
|
430
|
+
buf_pos_stop = self._buf_pos = stop
|
|
431
|
+
self._pos += size
|
|
432
|
+
return self._buf_view[buf_pos:buf_pos_stop].tobytes()
|
|
433
|
+
index = buf.find(b"\n", buf_pos, buf_stop)
|
|
434
|
+
if index > 0:
|
|
435
|
+
buf_pos_stop = self._buf_pos = index + 1
|
|
436
|
+
self._pos += buf_pos_stop - buf_pos
|
|
437
|
+
return self._buf_view[buf_pos:buf_pos_stop].tobytes()
|
|
438
|
+
try:
|
|
439
|
+
readline = ensure_async(self.raw.readline, threaded=True)
|
|
440
|
+
except AttributeError:
|
|
441
|
+
async def readline(size: None | int = -1, /) -> bytes:
|
|
442
|
+
if size == 0:
|
|
443
|
+
return b""
|
|
444
|
+
if size is None:
|
|
445
|
+
size = -1
|
|
446
|
+
read = ensure_async(self.raw.read, threaded=True)
|
|
447
|
+
cache = bytearray()
|
|
448
|
+
if size > 0:
|
|
449
|
+
while size and (c := await read(1)):
|
|
450
|
+
cache += c
|
|
451
|
+
if c == b"\n":
|
|
452
|
+
break
|
|
453
|
+
size -= 1
|
|
454
|
+
else:
|
|
455
|
+
while c := await read(1):
|
|
456
|
+
cache += c
|
|
457
|
+
if c == b"\n":
|
|
458
|
+
break
|
|
459
|
+
return bytes(cache)
|
|
460
|
+
if size > 0:
|
|
461
|
+
size -= buf_size
|
|
462
|
+
try:
|
|
463
|
+
data = await readline(size)
|
|
464
|
+
except:
|
|
465
|
+
self.calibrate()
|
|
466
|
+
raise
|
|
467
|
+
buf_view = self._buf_view
|
|
468
|
+
BUFSIZE = len(buf_view)
|
|
469
|
+
length = len(data)
|
|
470
|
+
prev_data = buf_view[buf_pos:buf_stop].tobytes()
|
|
471
|
+
self._pos += len(prev_data) + length
|
|
472
|
+
if BUFSIZE <= length:
|
|
473
|
+
buf_view[:] = memoryview(data)[-BUFSIZE:]
|
|
474
|
+
self._buf_pos = self._buf_stop = BUFSIZE
|
|
475
|
+
else:
|
|
476
|
+
buf_pos_stop = buf_stop + length
|
|
477
|
+
if buf_pos_stop <= BUFSIZE:
|
|
478
|
+
buf_view[buf_stop:buf_pos_stop] = data
|
|
479
|
+
self._buf_pos = self._buf_stop = buf_pos_stop
|
|
480
|
+
else:
|
|
481
|
+
index = BUFSIZE - length
|
|
482
|
+
buf_view[:index] = buf_view[-index:]
|
|
483
|
+
buf_view[-length:] = data
|
|
484
|
+
self._buf_pos = self._buf_stop = BUFSIZE
|
|
485
|
+
return prev_data + data
|
|
486
|
+
|
|
487
|
+
async def readlines(self, hint: int = -1, /) -> list[bytes]: # type: ignore
|
|
488
|
+
if self.closed:
|
|
489
|
+
raise ValueError("I/O operation on closed file.")
|
|
490
|
+
readline = self.readline
|
|
491
|
+
lines: list[bytes] = []
|
|
492
|
+
append = lines.append
|
|
493
|
+
if hint <= 0:
|
|
494
|
+
while line := await readline():
|
|
495
|
+
append(line)
|
|
496
|
+
else:
|
|
497
|
+
while hint > 0 and (line := await readline()):
|
|
498
|
+
append(line)
|
|
499
|
+
hint -= len(line)
|
|
500
|
+
return lines
|
|
501
|
+
|
|
502
|
+
async def seek(self, target: int, whence: int = 0, /) -> int: # type: ignore
|
|
503
|
+
pos = self._pos
|
|
504
|
+
if whence == 1:
|
|
505
|
+
target += pos
|
|
506
|
+
elif whence == 2:
|
|
507
|
+
if target > 0:
|
|
508
|
+
raise ValueError("target out of range: overflow")
|
|
509
|
+
target = self._pos = await ensure_async(self.raw.seek, threaded=True)(target, 2)
|
|
510
|
+
if target != pos:
|
|
511
|
+
self.calibrate(target)
|
|
512
|
+
return target
|
|
513
|
+
if target < 0:
|
|
514
|
+
raise ValueError("target out of range: underflow")
|
|
515
|
+
if target != pos:
|
|
516
|
+
buf_pos = target - pos + self._buf_pos
|
|
517
|
+
if 0 <= buf_pos <= self._buf_stop:
|
|
518
|
+
self._buf_pos = buf_pos
|
|
519
|
+
pos = self._pos = target
|
|
520
|
+
else:
|
|
521
|
+
pos = self._pos = await ensure_async(self.raw.seek, threaded=True)(target, 0)
|
|
522
|
+
self._buf_pos = self._buf_stop = 0
|
|
523
|
+
return pos
|
|
524
|
+
|
|
525
|
+
def tell(self, /) -> int:
|
|
526
|
+
return self._pos
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
class AsyncTextIOWrapper(TextIOWrapper):
|
|
530
|
+
|
|
531
|
+
def __init__(
|
|
532
|
+
self,
|
|
533
|
+
/,
|
|
534
|
+
buffer: BinaryIO,
|
|
535
|
+
encoding: None | str = None,
|
|
536
|
+
errors: None | str = None,
|
|
537
|
+
newline: None | str = None,
|
|
538
|
+
line_buffering: bool = False,
|
|
539
|
+
write_through: bool = False,
|
|
540
|
+
):
|
|
541
|
+
super().__init__(
|
|
542
|
+
buffer,
|
|
543
|
+
encoding=encoding,
|
|
544
|
+
errors=errors,
|
|
545
|
+
newline=newline,
|
|
546
|
+
line_buffering=line_buffering,
|
|
547
|
+
write_through=write_through,
|
|
548
|
+
)
|
|
549
|
+
self.newline = newline
|
|
550
|
+
|
|
551
|
+
def __del__(self, /):
|
|
552
|
+
try:
|
|
553
|
+
self.close()
|
|
554
|
+
except:
|
|
555
|
+
pass
|
|
556
|
+
|
|
557
|
+
async def __aenter__(self, /) -> Self:
|
|
558
|
+
return self
|
|
559
|
+
|
|
560
|
+
async def __aexit__(self, /, *exc_info):
|
|
561
|
+
await self.aclose()
|
|
562
|
+
|
|
563
|
+
def __aiter__(self, /):
|
|
564
|
+
return self
|
|
565
|
+
|
|
566
|
+
async def __anext__(self, /):
|
|
567
|
+
if line := await self.readline():
|
|
568
|
+
return line
|
|
569
|
+
else:
|
|
570
|
+
raise StopAsyncIteration
|
|
571
|
+
|
|
572
|
+
def __getattr__(self, attr, /):
|
|
573
|
+
return getattr(self.buffer, attr)
|
|
574
|
+
|
|
575
|
+
async def aclose(self, /):
|
|
576
|
+
buffer = self.buffer
|
|
577
|
+
try:
|
|
578
|
+
ret = getattr(buffer, "aclose")()
|
|
579
|
+
except (AttributeError, TypeError):
|
|
580
|
+
ret = getattr(buffer, "close")()
|
|
581
|
+
if isawaitable(ret):
|
|
582
|
+
await ret
|
|
583
|
+
|
|
584
|
+
def close(self, /):
|
|
585
|
+
buffer = self.buffer
|
|
586
|
+
try:
|
|
587
|
+
ret = getattr(buffer, "aclose")()
|
|
588
|
+
except (AttributeError, TypeError):
|
|
589
|
+
ret = getattr(buffer, "close")()
|
|
590
|
+
if isawaitable(ret):
|
|
591
|
+
run_async(ensure_coroutine(ret))
|
|
592
|
+
|
|
593
|
+
async def flush(self, /):
|
|
594
|
+
return await ensure_async(self.buffer.flush, threaded=True)()
|
|
595
|
+
|
|
596
|
+
async def read(self, size: None | int = -1, /) -> str: # type: ignore
|
|
597
|
+
if self.closed:
|
|
598
|
+
raise ValueError("I/O operation on closed file.")
|
|
599
|
+
if size == 0:
|
|
600
|
+
return ""
|
|
601
|
+
if size is None:
|
|
602
|
+
size = -1
|
|
603
|
+
read = ensure_async(self.buffer.read, threaded=True)
|
|
604
|
+
encoding = self.encoding
|
|
605
|
+
errors = self.errors or "strict"
|
|
606
|
+
newline = self.newline
|
|
607
|
+
if size < 0:
|
|
608
|
+
data = await read(-1)
|
|
609
|
+
else:
|
|
610
|
+
data = await read(size)
|
|
611
|
+
if size < 0 or len(data) < size:
|
|
612
|
+
text = str(data, encoding, errors)
|
|
613
|
+
if newline is None:
|
|
614
|
+
text = CRE_NOT_UNIX_NEWLINES_sub("\n", text)
|
|
615
|
+
return text
|
|
616
|
+
|
|
617
|
+
def process_part(data, errors="strict", /) -> int:
|
|
618
|
+
text = str(data, encoding, errors)
|
|
619
|
+
if newline is None:
|
|
620
|
+
text = CRE_NOT_UNIX_NEWLINES_sub("\n", text)
|
|
621
|
+
add_part(text)
|
|
622
|
+
return len(text)
|
|
623
|
+
|
|
624
|
+
ls_parts: list[str] = []
|
|
625
|
+
add_part = ls_parts.append
|
|
626
|
+
cache = b""
|
|
627
|
+
while data := await read(size):
|
|
628
|
+
cache += data
|
|
629
|
+
while cache:
|
|
630
|
+
try:
|
|
631
|
+
size -= process_part(cache)
|
|
632
|
+
cache = b""
|
|
633
|
+
except UnicodeDecodeError as e:
|
|
634
|
+
start, stop = e.start, e.end
|
|
635
|
+
if start:
|
|
636
|
+
size -= process_part(cache[:start])
|
|
637
|
+
if e.reason == "unexpected end of data" and stop == len(cache):
|
|
638
|
+
cache = cache[start:]
|
|
639
|
+
break
|
|
640
|
+
if errors == "strict":
|
|
641
|
+
raise
|
|
642
|
+
size -= process_part(cache[start:stop], errors)
|
|
643
|
+
cache = cache[stop:]
|
|
644
|
+
if len(data) < size:
|
|
645
|
+
break
|
|
646
|
+
if cache:
|
|
647
|
+
process_part(cache, errors)
|
|
648
|
+
return "".join(ls_parts)
|
|
649
|
+
|
|
650
|
+
async def readline(self, size=-1, /) -> str: # type: ignore
|
|
651
|
+
if self.closed:
|
|
652
|
+
raise ValueError("I/O operation on closed file.")
|
|
653
|
+
if size == 0:
|
|
654
|
+
return ""
|
|
655
|
+
if size is None:
|
|
656
|
+
size = -1
|
|
657
|
+
read = ensure_async(self.buffer.read, threaded=True)
|
|
658
|
+
seek = self.seek
|
|
659
|
+
encoding = self.encoding
|
|
660
|
+
errors = self.errors or "strict"
|
|
661
|
+
newline = self.newline
|
|
662
|
+
peek = getattr(self.buffer, "peek", None)
|
|
663
|
+
if not callable(peek):
|
|
664
|
+
peek = None
|
|
665
|
+
if newline:
|
|
666
|
+
sepb = bytes(newline, encoding)
|
|
667
|
+
else:
|
|
668
|
+
crb = bytes("\r", encoding)
|
|
669
|
+
lfb = bytes("\n", encoding)
|
|
670
|
+
lfb_len = len(lfb)
|
|
671
|
+
buf = bytearray()
|
|
672
|
+
text = ""
|
|
673
|
+
reach_end = False
|
|
674
|
+
if size < 0:
|
|
675
|
+
while True:
|
|
676
|
+
if peek is None:
|
|
677
|
+
while c := await read(1):
|
|
678
|
+
buf += c
|
|
679
|
+
if newline:
|
|
680
|
+
if buf.endswith(sepb):
|
|
681
|
+
break
|
|
682
|
+
elif buf.endswith(lfb):
|
|
683
|
+
break
|
|
684
|
+
elif buf.endswith(crb):
|
|
685
|
+
peek_maybe_lfb = await read(lfb_len)
|
|
686
|
+
if peek_maybe_lfb == lfb:
|
|
687
|
+
buf += lfb
|
|
688
|
+
elif peek_maybe_lfb:
|
|
689
|
+
# TODO: 这是一个提前量,未必需要立即往回 seek,因为转换为 str 后可能尾部不是 \r(因为可以和前面的符号结合),所以这个可能可以被复用,如果需要优化,可以在程序结束时的 finally 部分最终执行 seek(可能最终字符被消耗所以不需要 seek)
|
|
690
|
+
await seek(-len(peek_maybe_lfb), 1)
|
|
691
|
+
if len(peek_maybe_lfb) < lfb_len:
|
|
692
|
+
reach_end = True
|
|
693
|
+
break
|
|
694
|
+
else:
|
|
695
|
+
reach_end = True
|
|
696
|
+
else:
|
|
697
|
+
while True:
|
|
698
|
+
buf_stop = len(buf)
|
|
699
|
+
peek_b = peek()
|
|
700
|
+
if peek_b:
|
|
701
|
+
buf += peek_b
|
|
702
|
+
if newline:
|
|
703
|
+
if (idx := buf.find(sepb)) > -1:
|
|
704
|
+
idx += 1
|
|
705
|
+
await read(idx - buf_stop)
|
|
706
|
+
del buf[idx:]
|
|
707
|
+
break
|
|
708
|
+
elif (idx := buf.find(lfb)) > -1:
|
|
709
|
+
idx += 1
|
|
710
|
+
await read(idx - buf_stop)
|
|
711
|
+
del buf[idx:]
|
|
712
|
+
break
|
|
713
|
+
elif (idx := buf.find(crb)) > -1:
|
|
714
|
+
idx += 1
|
|
715
|
+
await read(idx - buf_stop)
|
|
716
|
+
if buf.startswith(lfb, idx):
|
|
717
|
+
await read(lfb_len)
|
|
718
|
+
del buf[idx+lfb_len:]
|
|
719
|
+
else:
|
|
720
|
+
del buf[idx:]
|
|
721
|
+
break
|
|
722
|
+
c = await read(1)
|
|
723
|
+
if not c:
|
|
724
|
+
reach_end = True
|
|
725
|
+
break
|
|
726
|
+
buf += c
|
|
727
|
+
while buf:
|
|
728
|
+
try:
|
|
729
|
+
text += str(buf, encoding)
|
|
730
|
+
buf.clear()
|
|
731
|
+
except UnicodeEncodeError as e:
|
|
732
|
+
start, stop = e.start, e.end
|
|
733
|
+
if start:
|
|
734
|
+
text += str(buf[:start], encoding)
|
|
735
|
+
if e.reason == "unexpected end of data" and stop == len(buf):
|
|
736
|
+
buf = buf[start:]
|
|
737
|
+
break
|
|
738
|
+
if errors == "strict":
|
|
739
|
+
raise
|
|
740
|
+
text += str(buf[start:stop], encoding, errors)
|
|
741
|
+
buf = buf[stop:]
|
|
742
|
+
else:
|
|
743
|
+
if newline:
|
|
744
|
+
if text.endswith(newline):
|
|
745
|
+
return text[:-len(newline)] + "\n"
|
|
746
|
+
elif newline is None:
|
|
747
|
+
if text.endswith("\r\n"):
|
|
748
|
+
return text[:-2] + "\n"
|
|
749
|
+
elif text.endswith("\r"):
|
|
750
|
+
return text[:-1] + "\n"
|
|
751
|
+
elif text.endswith("\n"):
|
|
752
|
+
return text
|
|
753
|
+
elif text.endswith(("\r\n", "\r", "\n")):
|
|
754
|
+
return text
|
|
755
|
+
if reach_end:
|
|
756
|
+
return text
|
|
757
|
+
else:
|
|
758
|
+
while True:
|
|
759
|
+
rem = size - len(text)
|
|
760
|
+
if peek is None:
|
|
761
|
+
while rem and (c := await read(1)):
|
|
762
|
+
buf += c
|
|
763
|
+
rem -= 1
|
|
764
|
+
if newline:
|
|
765
|
+
if buf.endswith(sepb):
|
|
766
|
+
break
|
|
767
|
+
elif buf.endswith(lfb):
|
|
768
|
+
break
|
|
769
|
+
elif buf.endswith(crb):
|
|
770
|
+
peek_maybe_lfb = await read(lfb_len)
|
|
771
|
+
if peek_maybe_lfb == lfb:
|
|
772
|
+
buf += lfb
|
|
773
|
+
elif peek_maybe_lfb:
|
|
774
|
+
await seek(-len(peek_maybe_lfb), 1)
|
|
775
|
+
if len(peek_maybe_lfb) < lfb_len:
|
|
776
|
+
reach_end = True
|
|
777
|
+
break
|
|
778
|
+
else:
|
|
779
|
+
reach_end = True
|
|
780
|
+
else:
|
|
781
|
+
while rem:
|
|
782
|
+
buf_stop = len(buf)
|
|
783
|
+
peek_b = peek()
|
|
784
|
+
if peek_b:
|
|
785
|
+
if len(peek_b) >= rem:
|
|
786
|
+
buf += peek_b[:rem]
|
|
787
|
+
rem = 0
|
|
788
|
+
else:
|
|
789
|
+
buf += peek_b
|
|
790
|
+
rem -= len(peek_b)
|
|
791
|
+
if newline:
|
|
792
|
+
if (idx := buf.find(sepb)) > -1:
|
|
793
|
+
idx += 1
|
|
794
|
+
await read(idx - buf_stop)
|
|
795
|
+
del buf[idx:]
|
|
796
|
+
break
|
|
797
|
+
elif (idx := buf.find(lfb)) > -1:
|
|
798
|
+
idx += 1
|
|
799
|
+
await read(idx - buf_stop)
|
|
800
|
+
del buf[idx:]
|
|
801
|
+
break
|
|
802
|
+
elif (idx := buf.find(crb)) > -1:
|
|
803
|
+
idx += 1
|
|
804
|
+
await read(idx - buf_stop)
|
|
805
|
+
if buf.startswith(lfb, idx):
|
|
806
|
+
await read(lfb_len)
|
|
807
|
+
del buf[idx+lfb_len:]
|
|
808
|
+
else:
|
|
809
|
+
del buf[idx:]
|
|
810
|
+
break
|
|
811
|
+
if rem:
|
|
812
|
+
c = await read(1)
|
|
813
|
+
if not c:
|
|
814
|
+
reach_end = True
|
|
815
|
+
break
|
|
816
|
+
rem -= 1
|
|
817
|
+
buf += c
|
|
818
|
+
while buf:
|
|
819
|
+
try:
|
|
820
|
+
text += str(buf, encoding)
|
|
821
|
+
buf.clear()
|
|
822
|
+
except UnicodeEncodeError as e:
|
|
823
|
+
start, stop = e.start, e.end
|
|
824
|
+
if start:
|
|
825
|
+
text += str(buf[:start], encoding)
|
|
826
|
+
if e.reason == "unexpected end of data" and stop == len(buf):
|
|
827
|
+
buf = buf[start:]
|
|
828
|
+
break
|
|
829
|
+
if errors == "strict":
|
|
830
|
+
raise
|
|
831
|
+
text += str(buf[start:stop], encoding, errors)
|
|
832
|
+
buf = buf[stop:]
|
|
833
|
+
else:
|
|
834
|
+
if newline:
|
|
835
|
+
if text.endswith(newline):
|
|
836
|
+
return text[:-len(newline)] + "\n"
|
|
837
|
+
elif newline is None:
|
|
838
|
+
if text.endswith("\r\n"):
|
|
839
|
+
return text[:-2] + "\n"
|
|
840
|
+
elif text.endswith("\r"):
|
|
841
|
+
return text[:-1] + "\n"
|
|
842
|
+
elif text.endswith("\n"):
|
|
843
|
+
return text
|
|
844
|
+
elif text.endswith(("\r\n", "\r", "\n")):
|
|
845
|
+
return text
|
|
846
|
+
if reach_end or len(text) == size:
|
|
847
|
+
return text
|
|
848
|
+
|
|
849
|
+
async def readlines(self, hint=-1, /) -> list[str]: # type: ignore
|
|
850
|
+
if self.closed:
|
|
851
|
+
raise ValueError("I/O operation on closed file.")
|
|
852
|
+
readline = self.readline
|
|
853
|
+
lines: list[str] = []
|
|
854
|
+
append = lines.append
|
|
855
|
+
if hint <= 0:
|
|
856
|
+
while line := await readline():
|
|
857
|
+
append(line)
|
|
858
|
+
else:
|
|
859
|
+
while hint > 0 and (line := await readline()):
|
|
860
|
+
append(line)
|
|
861
|
+
hint -= len(line)
|
|
862
|
+
return lines
|
|
863
|
+
|
|
864
|
+
def reconfigure(
|
|
865
|
+
self,
|
|
866
|
+
/,
|
|
867
|
+
encoding: None | str = None,
|
|
868
|
+
errors: None | str = None,
|
|
869
|
+
newline: None | str = None,
|
|
870
|
+
line_buffering: None | bool = None,
|
|
871
|
+
write_through: None | bool = None,
|
|
872
|
+
):
|
|
873
|
+
super().reconfigure(
|
|
874
|
+
encoding=encoding,
|
|
875
|
+
errors=errors,
|
|
876
|
+
newline=newline,
|
|
877
|
+
line_buffering=line_buffering,
|
|
878
|
+
write_through=write_through,
|
|
879
|
+
)
|
|
880
|
+
self.newline = newline
|
|
881
|
+
|
|
882
|
+
async def seek(self, target: int, whence: int = 0, /) -> int: # type: ignore
|
|
883
|
+
return await ensure_async(self.buffer.seek, threaded=True)(target, whence)
|
|
884
|
+
|
|
885
|
+
def tell(self, /) -> int:
|
|
886
|
+
return self.buffer.tell()
|
|
887
|
+
|
|
888
|
+
async def truncate(self, pos: None | int = None, /) -> int: # type: ignore
|
|
889
|
+
return await ensure_async(self.buffer.truncate, threaded=True)(pos)
|
|
890
|
+
|
|
891
|
+
async def write(self, text: str, /) -> int: # type: ignore
|
|
892
|
+
match self.newline:
|
|
893
|
+
case "" | "\n":
|
|
894
|
+
pass
|
|
895
|
+
case None:
|
|
896
|
+
if linesep != "\n":
|
|
897
|
+
text = text.replace("\n", linesep)
|
|
898
|
+
case _:
|
|
899
|
+
text = text.replace("\n", linesep)
|
|
900
|
+
data = bytes(text, self.encoding, self.errors or "strict")
|
|
901
|
+
await ensure_async(self.buffer.write, threaded=True)(data)
|
|
902
|
+
if self.write_through or self.line_buffering and ("\n" in text or "\r" in text):
|
|
903
|
+
await self.flush()
|
|
904
|
+
return len(text)
|
|
905
|
+
|
|
906
|
+
async def writelines(self, lines: Iterable[str], /): # type: ignore
|
|
907
|
+
write = self.write
|
|
908
|
+
for line in lines:
|
|
909
|
+
await write(line)
|
|
910
|
+
|
|
911
|
+
|
|
92
912
|
def bio_chunk_iter(
|
|
93
|
-
bio: SupportsRead[Buffer] | Callable[[int], Buffer],
|
|
913
|
+
bio: SupportsRead[Buffer] | SupportsReadinto | Callable[[int], Buffer],
|
|
94
914
|
/,
|
|
95
915
|
size: int = -1,
|
|
96
916
|
chunksize: int = COPY_BUFSIZE,
|
|
@@ -100,11 +920,19 @@ def bio_chunk_iter(
|
|
|
100
920
|
use_readinto = False
|
|
101
921
|
if callable(bio):
|
|
102
922
|
read = bio
|
|
103
|
-
elif can_buffer and
|
|
923
|
+
elif can_buffer and isinstance(bio, SupportsReadinto):
|
|
104
924
|
readinto = bio.readinto
|
|
105
925
|
use_readinto = True
|
|
106
|
-
|
|
926
|
+
elif isinstance(bio, SupportsRead):
|
|
107
927
|
read = bio.read
|
|
928
|
+
else:
|
|
929
|
+
readinto = bio.readinto
|
|
930
|
+
def read(_):
|
|
931
|
+
buf = bytearray(chunksize)
|
|
932
|
+
length = readinto(buf)
|
|
933
|
+
if length == chunksize:
|
|
934
|
+
return buf
|
|
935
|
+
return buf[:length]
|
|
108
936
|
if not callable(callback):
|
|
109
937
|
callback = None
|
|
110
938
|
if use_readinto:
|
|
@@ -151,7 +979,7 @@ def bio_chunk_iter(
|
|
|
151
979
|
|
|
152
980
|
|
|
153
981
|
async def bio_chunk_async_iter(
|
|
154
|
-
bio: SupportsRead[Buffer] | Callable[[int], Buffer | Awaitable[Buffer]],
|
|
982
|
+
bio: SupportsRead[Buffer] | SupportsReadinto | Callable[[int], Buffer | Awaitable[Buffer]],
|
|
155
983
|
/,
|
|
156
984
|
size: int = -1,
|
|
157
985
|
chunksize: int = COPY_BUFSIZE,
|
|
@@ -160,12 +988,20 @@ async def bio_chunk_async_iter(
|
|
|
160
988
|
) -> AsyncIterator[Buffer]:
|
|
161
989
|
use_readinto = False
|
|
162
990
|
if callable(bio):
|
|
163
|
-
read = ensure_async(bio)
|
|
164
|
-
elif can_buffer and
|
|
165
|
-
readinto = ensure_async(bio.readinto)
|
|
991
|
+
read = ensure_async(bio, threaded=True)
|
|
992
|
+
elif can_buffer and isinstance(bio, SupportsReadinto):
|
|
993
|
+
readinto = ensure_async(bio.readinto, threaded=True)
|
|
166
994
|
use_readinto = True
|
|
995
|
+
elif isinstance(bio, SupportsRead):
|
|
996
|
+
read = ensure_async(bio.read, threaded=True)
|
|
167
997
|
else:
|
|
168
|
-
|
|
998
|
+
readinto = ensure_async(bio.readinto, threaded=True)
|
|
999
|
+
async def read(_):
|
|
1000
|
+
buf = bytearray(chunksize)
|
|
1001
|
+
length = await readinto(buf)
|
|
1002
|
+
if length == chunksize:
|
|
1003
|
+
return buf
|
|
1004
|
+
return buf[:length]
|
|
169
1005
|
callback = ensure_async(callback) if callable(callback) else None
|
|
170
1006
|
if use_readinto:
|
|
171
1007
|
buf = bytearray(chunksize)
|
|
@@ -211,7 +1047,7 @@ async def bio_chunk_async_iter(
|
|
|
211
1047
|
|
|
212
1048
|
|
|
213
1049
|
def bio_skip_iter(
|
|
214
|
-
bio: SupportsRead[Buffer] | Callable[[int], Buffer],
|
|
1050
|
+
bio: SupportsRead[Buffer] | SupportsReadinto | Callable[[int], Buffer],
|
|
215
1051
|
/,
|
|
216
1052
|
size: int = -1,
|
|
217
1053
|
chunksize: int = COPY_BUFSIZE,
|
|
@@ -282,7 +1118,7 @@ def bio_skip_iter(
|
|
|
282
1118
|
|
|
283
1119
|
|
|
284
1120
|
async def bio_skip_async_iter(
|
|
285
|
-
bio: SupportsRead[Buffer] | Callable[[int], Buffer | Awaitable[Buffer]],
|
|
1121
|
+
bio: SupportsRead[Buffer] | SupportsReadinto | Callable[[int], Buffer | Awaitable[Buffer]],
|
|
286
1122
|
/,
|
|
287
1123
|
size: int = -1,
|
|
288
1124
|
chunksize: int = COPY_BUFSIZE,
|
|
@@ -293,7 +1129,7 @@ async def bio_skip_async_iter(
|
|
|
293
1129
|
callback = ensure_async(callback) if callable(callback) else None
|
|
294
1130
|
length: int
|
|
295
1131
|
try:
|
|
296
|
-
seek = ensure_async(getattr(bio, "seek"))
|
|
1132
|
+
seek = ensure_async(getattr(bio, "seek"), threaded=True)
|
|
297
1133
|
curpos = await seek(0, 1)
|
|
298
1134
|
if size > 0:
|
|
299
1135
|
length = (await seek(size, 1)) - curpos
|
|
@@ -303,9 +1139,9 @@ async def bio_skip_async_iter(
|
|
|
303
1139
|
if chunksize <= 0:
|
|
304
1140
|
chunksize = COPY_BUFSIZE
|
|
305
1141
|
if callable(bio):
|
|
306
|
-
read = ensure_async(bio)
|
|
1142
|
+
read = ensure_async(bio, threaded=True)
|
|
307
1143
|
elif hasattr(bio, "readinto"):
|
|
308
|
-
readinto = ensure_async(bio.readinto)
|
|
1144
|
+
readinto = ensure_async(bio.readinto, threaded=True)
|
|
309
1145
|
buf = bytearray(chunksize)
|
|
310
1146
|
if size > 0:
|
|
311
1147
|
while size >= chunksize:
|
|
@@ -329,7 +1165,7 @@ async def bio_skip_async_iter(
|
|
|
329
1165
|
await callback(length)
|
|
330
1166
|
yield length
|
|
331
1167
|
else:
|
|
332
|
-
read = ensure_async(bio.read)
|
|
1168
|
+
read = ensure_async(bio.read, threaded=True)
|
|
333
1169
|
if size > 0:
|
|
334
1170
|
while size:
|
|
335
1171
|
readsize = min(chunksize, size)
|
|
@@ -469,35 +1305,56 @@ def bytes_iter_to_reader(
|
|
|
469
1305
|
/,
|
|
470
1306
|
) -> SupportsRead[bytearray]:
|
|
471
1307
|
getnext = iter(it).__next__
|
|
1308
|
+
pos = 0
|
|
472
1309
|
at_end = False
|
|
473
1310
|
unconsumed: bytearray = bytearray()
|
|
474
1311
|
lock = Lock()
|
|
475
|
-
def
|
|
476
|
-
|
|
1312
|
+
def __del__():
|
|
1313
|
+
try:
|
|
1314
|
+
close()
|
|
1315
|
+
except:
|
|
1316
|
+
pass
|
|
1317
|
+
def close():
|
|
1318
|
+
nonlocal at_end
|
|
1319
|
+
getattr(it, "close")()
|
|
1320
|
+
at_end = True
|
|
1321
|
+
def peek(n: int = 0, /) -> bytearray:
|
|
1322
|
+
if n <= 0:
|
|
1323
|
+
return unconsumed[:]
|
|
1324
|
+
return unconsumed[:n]
|
|
1325
|
+
def read(n: None | int = -1, /) -> bytearray:
|
|
1326
|
+
nonlocal pos, at_end, unconsumed
|
|
477
1327
|
if at_end or n == 0:
|
|
478
1328
|
return bytearray()
|
|
1329
|
+
if n is None:
|
|
1330
|
+
n = -1
|
|
479
1331
|
with lock:
|
|
480
1332
|
try:
|
|
481
|
-
if n
|
|
1333
|
+
if n < 0:
|
|
482
1334
|
while True:
|
|
483
1335
|
unconsumed += getnext()
|
|
484
1336
|
else:
|
|
485
1337
|
while n > len(unconsumed):
|
|
486
1338
|
unconsumed += getnext()
|
|
487
1339
|
b, unconsumed = unconsumed[:n], unconsumed[n:]
|
|
1340
|
+
pos += len(b)
|
|
488
1341
|
return b
|
|
489
1342
|
except StopIteration:
|
|
490
1343
|
at_end = True
|
|
491
|
-
|
|
1344
|
+
b = unconsumed[:]
|
|
1345
|
+
del unconsumed[:]
|
|
1346
|
+
pos += len(b)
|
|
1347
|
+
return b
|
|
492
1348
|
def readinto(buf, /) -> int:
|
|
493
|
-
nonlocal at_end, unconsumed
|
|
1349
|
+
nonlocal pos, at_end, unconsumed
|
|
494
1350
|
if at_end or not (bufsize := len(buf)):
|
|
495
1351
|
return 0
|
|
496
1352
|
with lock:
|
|
497
|
-
|
|
1353
|
+
n = len(unconsumed)
|
|
1354
|
+
if bufsize <= n:
|
|
498
1355
|
buf[:], unconsumed = unconsumed[:bufsize], unconsumed[bufsize:]
|
|
1356
|
+
pos += bufsize
|
|
499
1357
|
return bufsize
|
|
500
|
-
n = len(unconsumed)
|
|
501
1358
|
buf[:n] = unconsumed
|
|
502
1359
|
del unconsumed[:]
|
|
503
1360
|
try:
|
|
@@ -508,47 +1365,95 @@ def bytes_iter_to_reader(
|
|
|
508
1365
|
m = n + len(b)
|
|
509
1366
|
if m >= bufsize:
|
|
510
1367
|
buf[n:] = b[:bufsize-n]
|
|
511
|
-
unconsumed += b[
|
|
1368
|
+
unconsumed += b[bufsize-n:]
|
|
1369
|
+
pos += bufsize
|
|
512
1370
|
return bufsize
|
|
513
1371
|
else:
|
|
514
1372
|
buf[n:m] = b
|
|
1373
|
+
pos += len(b)
|
|
515
1374
|
n = m
|
|
516
1375
|
except StopIteration:
|
|
517
1376
|
at_end = True
|
|
518
1377
|
return n
|
|
519
|
-
def
|
|
520
|
-
nonlocal unconsumed, at_end
|
|
521
|
-
if at_end:
|
|
522
|
-
|
|
523
|
-
if
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
try:
|
|
530
|
-
while True:
|
|
531
|
-
r = getnext()
|
|
532
|
-
if not r:
|
|
533
|
-
continue
|
|
534
|
-
if (idx := r.find(49)) > -1:
|
|
1378
|
+
def readline(n: None | int = -1, /) -> bytearray:
|
|
1379
|
+
nonlocal pos, unconsumed, at_end
|
|
1380
|
+
if at_end or n == 0:
|
|
1381
|
+
return bytearray()
|
|
1382
|
+
if n is None:
|
|
1383
|
+
n = -1
|
|
1384
|
+
with lock:
|
|
1385
|
+
if unconsumed:
|
|
1386
|
+
# search for b"\n"
|
|
1387
|
+
if (idx := unconsumed.find(49)) > -1:
|
|
535
1388
|
idx += 1
|
|
536
|
-
|
|
537
|
-
|
|
1389
|
+
if n < 0 or idx <= n:
|
|
1390
|
+
b, unconsumed = unconsumed[:idx], unconsumed[idx:]
|
|
1391
|
+
pos += idx
|
|
1392
|
+
return b
|
|
1393
|
+
if n > 0 and len(unconsumed) >= n:
|
|
1394
|
+
b, unconsumed = unconsumed[:n], unconsumed[n:]
|
|
1395
|
+
pos += n
|
|
538
1396
|
return b
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
1397
|
+
try:
|
|
1398
|
+
start = len(unconsumed)
|
|
1399
|
+
while True:
|
|
1400
|
+
r = getnext()
|
|
1401
|
+
if not r:
|
|
1402
|
+
continue
|
|
1403
|
+
unconsumed += r
|
|
1404
|
+
if (idx := unconsumed.find(49, start)) > -1:
|
|
1405
|
+
idx += 1
|
|
1406
|
+
if n < 0 or idx <= n:
|
|
1407
|
+
b, unconsumed = unconsumed[:idx], unconsumed[idx:]
|
|
1408
|
+
pos += idx
|
|
1409
|
+
return b
|
|
1410
|
+
start = len(unconsumed)
|
|
1411
|
+
if n > 0 and start >= n:
|
|
1412
|
+
b, unconsumed = unconsumed[:n], unconsumed[n:]
|
|
1413
|
+
pos += n
|
|
1414
|
+
return b
|
|
1415
|
+
except StopIteration:
|
|
1416
|
+
at_end = True
|
|
1417
|
+
if unconsumed:
|
|
1418
|
+
b = unconsumed[:]
|
|
1419
|
+
del unconsumed[:]
|
|
1420
|
+
pos += len(b)
|
|
1421
|
+
return b
|
|
1422
|
+
raise
|
|
1423
|
+
def readlines(hint: int = -1, /) -> list[bytearray]:
|
|
1424
|
+
if at_end:
|
|
1425
|
+
return []
|
|
1426
|
+
lines: list[bytearray] = []
|
|
1427
|
+
append = lines.append
|
|
1428
|
+
if hint <= 0:
|
|
1429
|
+
while line := readline():
|
|
1430
|
+
append(line)
|
|
1431
|
+
else:
|
|
1432
|
+
while hint > 0 and (line := readline()):
|
|
1433
|
+
append(line)
|
|
1434
|
+
hint -= len(line)
|
|
1435
|
+
return lines
|
|
1436
|
+
def __next__() -> bytearray:
|
|
1437
|
+
if at_end or not (b := readline()):
|
|
1438
|
+
raise StopIteration
|
|
1439
|
+
return b
|
|
545
1440
|
reprs = f"<reader for {it!r}>"
|
|
546
|
-
return type("reader", (), {
|
|
547
|
-
"
|
|
548
|
-
"
|
|
549
|
-
"__iter__": lambda self
|
|
1441
|
+
return type("reader", (VirtualBufferedReader,), {
|
|
1442
|
+
"__del__": staticmethod(__del__),
|
|
1443
|
+
"__getattr__": staticmethod(lambda attr, /: getattr(it, attr)),
|
|
1444
|
+
"__iter__": lambda self: self,
|
|
550
1445
|
"__next__": staticmethod(__next__),
|
|
551
1446
|
"__repr__": staticmethod(lambda: reprs),
|
|
1447
|
+
"close": staticmethod(close),
|
|
1448
|
+
"closed": staticproperty(lambda: at_end),
|
|
1449
|
+
"peek": staticmethod(peek),
|
|
1450
|
+
"read": staticmethod(read),
|
|
1451
|
+
"readinto": staticmethod(readinto),
|
|
1452
|
+
"readline": staticmethod(readline),
|
|
1453
|
+
"readlines": staticmethod(readlines),
|
|
1454
|
+
"readable": staticmethod(lambda: True),
|
|
1455
|
+
"seekable": staticmethod(lambda: False),
|
|
1456
|
+
"tell": staticmethod(lambda: pos),
|
|
552
1457
|
})()
|
|
553
1458
|
|
|
554
1459
|
|
|
@@ -561,35 +1466,68 @@ def bytes_iter_to_async_reader(
|
|
|
561
1466
|
getnext = aiter(it).__anext__
|
|
562
1467
|
else:
|
|
563
1468
|
getnext = ensure_async(iter(it).__next__, threaded=threaded)
|
|
1469
|
+
pos = 0
|
|
564
1470
|
at_end = False
|
|
565
1471
|
unconsumed: bytearray = bytearray()
|
|
566
1472
|
lock = AsyncLock()
|
|
567
|
-
|
|
568
|
-
|
|
1473
|
+
def __del__():
|
|
1474
|
+
try:
|
|
1475
|
+
close()
|
|
1476
|
+
except:
|
|
1477
|
+
pass
|
|
1478
|
+
def close():
|
|
1479
|
+
try:
|
|
1480
|
+
method = getattr(it, "aclose")
|
|
1481
|
+
except AttributeError:
|
|
1482
|
+
method = getattr(it, "close")
|
|
1483
|
+
ret = method()
|
|
1484
|
+
if isawaitable(ret):
|
|
1485
|
+
run_async(ensure_coroutine(ret))
|
|
1486
|
+
async def aclose():
|
|
1487
|
+
try:
|
|
1488
|
+
method = getattr(it, "aclose")
|
|
1489
|
+
except AttributeError:
|
|
1490
|
+
method = getattr(it, "close")
|
|
1491
|
+
ret = method()
|
|
1492
|
+
if isawaitable(ret):
|
|
1493
|
+
await ret
|
|
1494
|
+
def peek(n: int = 0, /) -> bytearray:
|
|
1495
|
+
if n <= 0:
|
|
1496
|
+
return unconsumed[:]
|
|
1497
|
+
return unconsumed[:n]
|
|
1498
|
+
async def read(n: None | int = -1, /) -> bytearray:
|
|
1499
|
+
nonlocal pos, at_end, unconsumed
|
|
569
1500
|
if at_end or n == 0:
|
|
570
1501
|
return bytearray()
|
|
1502
|
+
if n is None:
|
|
1503
|
+
n = -1
|
|
571
1504
|
async with lock:
|
|
572
1505
|
try:
|
|
573
|
-
if n
|
|
1506
|
+
if n < 0:
|
|
574
1507
|
while True:
|
|
575
1508
|
unconsumed += await getnext()
|
|
576
1509
|
else:
|
|
577
1510
|
while n > len(unconsumed):
|
|
578
1511
|
unconsumed += await getnext()
|
|
579
1512
|
b, unconsumed = unconsumed[:n], unconsumed[n:]
|
|
1513
|
+
pos += len(b)
|
|
580
1514
|
return b
|
|
581
|
-
except StopAsyncIteration:
|
|
1515
|
+
except (StopIteration, StopAsyncIteration):
|
|
582
1516
|
at_end = True
|
|
583
|
-
|
|
1517
|
+
b = unconsumed[:]
|
|
1518
|
+
del unconsumed[:]
|
|
1519
|
+
pos += len(b)
|
|
1520
|
+
return b
|
|
584
1521
|
async def readinto(buf, /) -> int:
|
|
585
|
-
nonlocal at_end, unconsumed
|
|
1522
|
+
nonlocal pos, at_end, unconsumed
|
|
586
1523
|
if at_end or not (bufsize := len(buf)):
|
|
587
1524
|
return 0
|
|
588
1525
|
async with lock:
|
|
589
|
-
|
|
1526
|
+
n = len(unconsumed)
|
|
1527
|
+
if bufsize <= n:
|
|
590
1528
|
buf[:], unconsumed = unconsumed[:bufsize], unconsumed[bufsize:]
|
|
1529
|
+
pos += bufsize
|
|
591
1530
|
return bufsize
|
|
592
|
-
n = len(unconsumed)
|
|
593
1531
|
buf[:n] = unconsumed
|
|
594
1532
|
del unconsumed[:]
|
|
595
1533
|
try:
|
|
@@ -600,47 +1538,99 @@ def bytes_iter_to_async_reader(
|
|
|
600
1538
|
m = n + len(b)
|
|
601
1539
|
if m >= bufsize:
|
|
602
1540
|
buf[n:] = b[:bufsize-n]
|
|
603
|
-
unconsumed += b[
|
|
1541
|
+
unconsumed += b[bufsize-n:]
|
|
1542
|
+
pos += bufsize
|
|
604
1543
|
return bufsize
|
|
605
1544
|
else:
|
|
606
1545
|
buf[n:m] = b
|
|
1546
|
+
pos += len(b)
|
|
607
1547
|
n = m
|
|
608
|
-
except StopAsyncIteration:
|
|
1548
|
+
except (StopIteration, StopAsyncIteration):
|
|
609
1549
|
at_end = True
|
|
610
1550
|
return n
|
|
611
|
-
async def
|
|
612
|
-
nonlocal unconsumed, at_end
|
|
613
|
-
if at_end:
|
|
614
|
-
|
|
615
|
-
if
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
try:
|
|
622
|
-
while True:
|
|
623
|
-
r = await getnext()
|
|
624
|
-
if not r:
|
|
625
|
-
continue
|
|
626
|
-
if (idx := r.find(49)) > -1:
|
|
1551
|
+
async def readline(n: None | int = -1, /) -> bytearray:
|
|
1552
|
+
nonlocal pos, unconsumed, at_end
|
|
1553
|
+
if at_end or n == 0:
|
|
1554
|
+
return bytearray()
|
|
1555
|
+
if n is None:
|
|
1556
|
+
n = -1
|
|
1557
|
+
async with lock:
|
|
1558
|
+
if unconsumed:
|
|
1559
|
+
# search for b"\n"
|
|
1560
|
+
if (idx := unconsumed.find(49)) > -1:
|
|
627
1561
|
idx += 1
|
|
628
|
-
|
|
629
|
-
|
|
1562
|
+
if n < 0 or idx <= n:
|
|
1563
|
+
b, unconsumed = unconsumed[:idx], unconsumed[idx:]
|
|
1564
|
+
pos += idx
|
|
1565
|
+
return b
|
|
1566
|
+
if n > 0 and len(unconsumed) >= n:
|
|
1567
|
+
b, unconsumed = unconsumed[:n], unconsumed[n:]
|
|
1568
|
+
pos += n
|
|
630
1569
|
return b
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
1570
|
+
try:
|
|
1571
|
+
start = len(unconsumed)
|
|
1572
|
+
while True:
|
|
1573
|
+
r = await getnext()
|
|
1574
|
+
if not r:
|
|
1575
|
+
continue
|
|
1576
|
+
unconsumed += r
|
|
1577
|
+
if (idx := unconsumed.find(49, start)) > -1:
|
|
1578
|
+
idx += 1
|
|
1579
|
+
if n < 0 or idx <= n:
|
|
1580
|
+
b, unconsumed = unconsumed[:idx], unconsumed[idx:]
|
|
1581
|
+
pos += idx
|
|
1582
|
+
return b
|
|
1583
|
+
start = len(unconsumed)
|
|
1584
|
+
if n > 0 and start >= n:
|
|
1585
|
+
b, unconsumed = unconsumed[:n], unconsumed[n:]
|
|
1586
|
+
pos += n
|
|
1587
|
+
return b
|
|
1588
|
+
except (StopIteration, StopAsyncIteration):
|
|
1589
|
+
at_end = True
|
|
1590
|
+
if unconsumed:
|
|
1591
|
+
b = unconsumed[:]
|
|
1592
|
+
del unconsumed[:]
|
|
1593
|
+
pos += len(b)
|
|
1594
|
+
return b
|
|
1595
|
+
raise
|
|
1596
|
+
async def readlines(hint: int = -1, /) -> list[bytearray]:
|
|
1597
|
+
if at_end:
|
|
1598
|
+
return []
|
|
1599
|
+
if hint is None:
|
|
1600
|
+
hint = -1
|
|
1601
|
+
lines: list[bytearray] = []
|
|
1602
|
+
append = lines.append
|
|
1603
|
+
async with lock:
|
|
1604
|
+
if hint <= 0:
|
|
1605
|
+
while line := await readline():
|
|
1606
|
+
append(line)
|
|
1607
|
+
else:
|
|
1608
|
+
while hint > 0 and (line := await readline()):
|
|
1609
|
+
append(line)
|
|
1610
|
+
hint -= len(line)
|
|
1611
|
+
return lines
|
|
1612
|
+
async def __anext__() -> bytearray:
|
|
1613
|
+
if at_end or not (b := await readline()):
|
|
1614
|
+
raise StopAsyncIteration
|
|
1615
|
+
return b
|
|
1616
|
+
reprs = f"<async_reader for {it!r}>"
|
|
1617
|
+
return type("async_reader", (VirtualBufferedReader,), {
|
|
1618
|
+
"__del__": staticmethod(__del__),
|
|
1619
|
+
"__getattr__": staticmethod(lambda attr, /: getattr(it, attr)),
|
|
1620
|
+
"__aiter__": lambda self: self,
|
|
1621
|
+
"__anext__": staticmethod(__anext__),
|
|
1622
|
+
"__repr__": staticmethod(lambda: reprs),
|
|
1623
|
+
"close": staticmethod(close),
|
|
1624
|
+
"aclose": staticmethod(aclose),
|
|
1625
|
+
"closed": staticproperty(lambda: at_end),
|
|
1626
|
+
"peek": staticmethod(peek),
|
|
639
1627
|
"read": staticmethod(read),
|
|
640
1628
|
"readinto": staticmethod(readinto),
|
|
641
|
-
"
|
|
642
|
-
"
|
|
643
|
-
"
|
|
1629
|
+
"readline": staticmethod(readline),
|
|
1630
|
+
"readlines": staticmethod(readlines),
|
|
1631
|
+
"readable": staticmethod(lambda: True),
|
|
1632
|
+
"seekable": staticmethod(lambda: False),
|
|
1633
|
+
"tell": staticmethod(lambda: pos),
|
|
644
1634
|
})()
|
|
645
1635
|
|
|
646
1636
|
|