python-filewrap 0.1.4__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 +1079 -105
- {python_filewrap-0.1.4.dist-info → python_filewrap-0.2.dist-info}/METADATA +3 -2
- python_filewrap-0.2.dist-info/RECORD +7 -0
- python_filewrap-0.1.4.dist-info/RECORD +0 -7
- {python_filewrap-0.1.4.dist-info → python_filewrap-0.2.dist-info}/LICENSE +0 -0
- {python_filewrap-0.1.4.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,6 +83,832 @@ 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
913
|
bio: SupportsRead[Buffer] | SupportsReadinto | Callable[[int], Buffer],
|
|
94
914
|
/,
|
|
@@ -485,35 +1305,56 @@ def bytes_iter_to_reader(
|
|
|
485
1305
|
/,
|
|
486
1306
|
) -> SupportsRead[bytearray]:
|
|
487
1307
|
getnext = iter(it).__next__
|
|
1308
|
+
pos = 0
|
|
488
1309
|
at_end = False
|
|
489
1310
|
unconsumed: bytearray = bytearray()
|
|
490
1311
|
lock = Lock()
|
|
491
|
-
def
|
|
492
|
-
|
|
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
|
|
493
1327
|
if at_end or n == 0:
|
|
494
1328
|
return bytearray()
|
|
1329
|
+
if n is None:
|
|
1330
|
+
n = -1
|
|
495
1331
|
with lock:
|
|
496
1332
|
try:
|
|
497
|
-
if n
|
|
1333
|
+
if n < 0:
|
|
498
1334
|
while True:
|
|
499
1335
|
unconsumed += getnext()
|
|
500
1336
|
else:
|
|
501
1337
|
while n > len(unconsumed):
|
|
502
1338
|
unconsumed += getnext()
|
|
503
1339
|
b, unconsumed = unconsumed[:n], unconsumed[n:]
|
|
1340
|
+
pos += len(b)
|
|
504
1341
|
return b
|
|
505
1342
|
except StopIteration:
|
|
506
1343
|
at_end = True
|
|
507
|
-
|
|
1344
|
+
b = unconsumed[:]
|
|
1345
|
+
del unconsumed[:]
|
|
1346
|
+
pos += len(b)
|
|
1347
|
+
return b
|
|
508
1348
|
def readinto(buf, /) -> int:
|
|
509
|
-
nonlocal at_end, unconsumed
|
|
1349
|
+
nonlocal pos, at_end, unconsumed
|
|
510
1350
|
if at_end or not (bufsize := len(buf)):
|
|
511
1351
|
return 0
|
|
512
1352
|
with lock:
|
|
513
|
-
|
|
1353
|
+
n = len(unconsumed)
|
|
1354
|
+
if bufsize <= n:
|
|
514
1355
|
buf[:], unconsumed = unconsumed[:bufsize], unconsumed[bufsize:]
|
|
1356
|
+
pos += bufsize
|
|
515
1357
|
return bufsize
|
|
516
|
-
n = len(unconsumed)
|
|
517
1358
|
buf[:n] = unconsumed
|
|
518
1359
|
del unconsumed[:]
|
|
519
1360
|
try:
|
|
@@ -524,47 +1365,95 @@ def bytes_iter_to_reader(
|
|
|
524
1365
|
m = n + len(b)
|
|
525
1366
|
if m >= bufsize:
|
|
526
1367
|
buf[n:] = b[:bufsize-n]
|
|
527
|
-
unconsumed += b[
|
|
1368
|
+
unconsumed += b[bufsize-n:]
|
|
1369
|
+
pos += bufsize
|
|
528
1370
|
return bufsize
|
|
529
1371
|
else:
|
|
530
1372
|
buf[n:m] = b
|
|
1373
|
+
pos += len(b)
|
|
531
1374
|
n = m
|
|
532
1375
|
except StopIteration:
|
|
533
1376
|
at_end = True
|
|
534
1377
|
return n
|
|
535
|
-
def
|
|
536
|
-
nonlocal unconsumed, at_end
|
|
537
|
-
if at_end:
|
|
538
|
-
|
|
539
|
-
if
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
try:
|
|
546
|
-
while True:
|
|
547
|
-
r = getnext()
|
|
548
|
-
if not r:
|
|
549
|
-
continue
|
|
550
|
-
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:
|
|
551
1388
|
idx += 1
|
|
552
|
-
|
|
553
|
-
|
|
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
|
|
554
1396
|
return b
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
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
|
|
561
1440
|
reprs = f"<reader for {it!r}>"
|
|
562
|
-
return type("reader", (), {
|
|
563
|
-
"
|
|
564
|
-
"
|
|
565
|
-
"__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,
|
|
566
1445
|
"__next__": staticmethod(__next__),
|
|
567
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),
|
|
568
1457
|
})()
|
|
569
1458
|
|
|
570
1459
|
|
|
@@ -577,35 +1466,68 @@ def bytes_iter_to_async_reader(
|
|
|
577
1466
|
getnext = aiter(it).__anext__
|
|
578
1467
|
else:
|
|
579
1468
|
getnext = ensure_async(iter(it).__next__, threaded=threaded)
|
|
1469
|
+
pos = 0
|
|
580
1470
|
at_end = False
|
|
581
1471
|
unconsumed: bytearray = bytearray()
|
|
582
1472
|
lock = AsyncLock()
|
|
583
|
-
|
|
584
|
-
|
|
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
|
|
585
1500
|
if at_end or n == 0:
|
|
586
1501
|
return bytearray()
|
|
1502
|
+
if n is None:
|
|
1503
|
+
n = -1
|
|
587
1504
|
async with lock:
|
|
588
1505
|
try:
|
|
589
|
-
if n
|
|
1506
|
+
if n < 0:
|
|
590
1507
|
while True:
|
|
591
1508
|
unconsumed += await getnext()
|
|
592
1509
|
else:
|
|
593
1510
|
while n > len(unconsumed):
|
|
594
1511
|
unconsumed += await getnext()
|
|
595
1512
|
b, unconsumed = unconsumed[:n], unconsumed[n:]
|
|
1513
|
+
pos += len(b)
|
|
596
1514
|
return b
|
|
597
|
-
except StopAsyncIteration:
|
|
1515
|
+
except (StopIteration, StopAsyncIteration):
|
|
598
1516
|
at_end = True
|
|
599
|
-
|
|
1517
|
+
b = unconsumed[:]
|
|
1518
|
+
del unconsumed[:]
|
|
1519
|
+
pos += len(b)
|
|
1520
|
+
return b
|
|
600
1521
|
async def readinto(buf, /) -> int:
|
|
601
|
-
nonlocal at_end, unconsumed
|
|
1522
|
+
nonlocal pos, at_end, unconsumed
|
|
602
1523
|
if at_end or not (bufsize := len(buf)):
|
|
603
1524
|
return 0
|
|
604
1525
|
async with lock:
|
|
605
|
-
|
|
1526
|
+
n = len(unconsumed)
|
|
1527
|
+
if bufsize <= n:
|
|
606
1528
|
buf[:], unconsumed = unconsumed[:bufsize], unconsumed[bufsize:]
|
|
1529
|
+
pos += bufsize
|
|
607
1530
|
return bufsize
|
|
608
|
-
n = len(unconsumed)
|
|
609
1531
|
buf[:n] = unconsumed
|
|
610
1532
|
del unconsumed[:]
|
|
611
1533
|
try:
|
|
@@ -616,47 +1538,99 @@ def bytes_iter_to_async_reader(
|
|
|
616
1538
|
m = n + len(b)
|
|
617
1539
|
if m >= bufsize:
|
|
618
1540
|
buf[n:] = b[:bufsize-n]
|
|
619
|
-
unconsumed += b[
|
|
1541
|
+
unconsumed += b[bufsize-n:]
|
|
1542
|
+
pos += bufsize
|
|
620
1543
|
return bufsize
|
|
621
1544
|
else:
|
|
622
1545
|
buf[n:m] = b
|
|
1546
|
+
pos += len(b)
|
|
623
1547
|
n = m
|
|
624
|
-
except StopAsyncIteration:
|
|
1548
|
+
except (StopIteration, StopAsyncIteration):
|
|
625
1549
|
at_end = True
|
|
626
1550
|
return n
|
|
627
|
-
async def
|
|
628
|
-
nonlocal unconsumed, at_end
|
|
629
|
-
if at_end:
|
|
630
|
-
|
|
631
|
-
if
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
try:
|
|
638
|
-
while True:
|
|
639
|
-
r = await getnext()
|
|
640
|
-
if not r:
|
|
641
|
-
continue
|
|
642
|
-
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:
|
|
643
1561
|
idx += 1
|
|
644
|
-
|
|
645
|
-
|
|
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
|
|
646
1569
|
return b
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
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),
|
|
655
1627
|
"read": staticmethod(read),
|
|
656
1628
|
"readinto": staticmethod(readinto),
|
|
657
|
-
"
|
|
658
|
-
"
|
|
659
|
-
"
|
|
1629
|
+
"readline": staticmethod(readline),
|
|
1630
|
+
"readlines": staticmethod(readlines),
|
|
1631
|
+
"readable": staticmethod(lambda: True),
|
|
1632
|
+
"seekable": staticmethod(lambda: False),
|
|
1633
|
+
"tell": staticmethod(lambda: pos),
|
|
660
1634
|
})()
|
|
661
1635
|
|
|
662
1636
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-filewrap
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2
|
|
4
4
|
Summary: Python file wrappers.
|
|
5
5
|
Home-page: https://github.com/ChenyangGao/web-mount-packs/tree/main/python-module/python-filewrap
|
|
6
6
|
License: MIT
|
|
@@ -21,7 +21,8 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
21
21
|
Classifier: Topic :: Software Development
|
|
22
22
|
Classifier: Topic :: Software Development :: Libraries
|
|
23
23
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
-
Requires-Dist: python-asynctools
|
|
24
|
+
Requires-Dist: python-asynctools (>=0.0.4)
|
|
25
|
+
Requires-Dist: python-property (>=0.0.2)
|
|
25
26
|
Project-URL: Repository, https://github.com/ChenyangGao/web-mount-packs/tree/main/python-module/python-filewrap
|
|
26
27
|
Description-Content-Type: text/markdown
|
|
27
28
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
|
|
2
|
+
filewrap/__init__.py,sha256=WKOOUCXO47ZmDRyROPcLkKIFiwsb5eDBhvbItCJ8zm4,61007
|
|
3
|
+
filewrap/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
python_filewrap-0.2.dist-info/LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
|
|
5
|
+
python_filewrap-0.2.dist-info/METADATA,sha256=mSMywqyvrqatQcKls-K7-2HQP02yE9ux2jMk6Gxuvu4,1411
|
|
6
|
+
python_filewrap-0.2.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
7
|
+
python_filewrap-0.2.dist-info/RECORD,,
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
|
|
2
|
-
filewrap/__init__.py,sha256=ak01i7DaNqkfh4I7h25A0TgWtJqGbaZMsawdU7s1Ykc,24866
|
|
3
|
-
filewrap/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
python_filewrap-0.1.4.dist-info/LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
|
|
5
|
-
python_filewrap-0.1.4.dist-info/METADATA,sha256=jOzfdCKroUd6WNF6AGG65cZe7IYGmQFNCscBAjcuFsE,1362
|
|
6
|
-
python_filewrap-0.1.4.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
7
|
-
python_filewrap-0.1.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|