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 CHANGED
@@ -2,10 +2,10 @@
2
2
  # encoding: utf-8
3
3
 
4
4
  __author__ = "ChenyangGao <https://chenyanggao.github.io>"
5
- __version__ = (0, 1, 4)
5
+ __version__ = (0, 2)
6
6
  __all__ = [
7
- "Buffer", "SupportsRead", "SupportsReadinto",
8
- "SupportsWrite", "SupportsSeek",
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 abc import ABC, abstractmethod
34
+ from _ctypes import _SimpleCData
32
35
  from array import array
33
36
 
34
- def _check_methods(C, *methods):
35
- mro = C.__mro__
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
- raise NotImplementedError
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 read(n=-1, /) -> bytearray:
492
- nonlocal at_end, unconsumed
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 is None or n < 0:
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
- return unconsumed
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
- if bufsize <= len(unconsumed):
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[m-bufsize:]
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 __next__() -> bytearray:
536
- nonlocal unconsumed, at_end
537
- if at_end:
538
- raise StopIteration
539
- if unconsumed:
540
- # search for b"\n"
541
- if (idx := unconsumed.find(49)) > -1:
542
- idx += 1
543
- b, unconsumed = unconsumed[:idx], unconsumed[idx:]
544
- return b
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
- unconsumed += r[:idx]
553
- b, unconsumed = unconsumed, bytearray(r[idx:])
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
- unconsumed += r
556
- except StopIteration:
557
- at_end = True
558
- if unconsumed:
559
- return unconsumed
560
- raise
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
- "read": staticmethod(read),
564
- "readinto": staticmethod(readinto),
565
- "__iter__": lambda self, /: 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
- async def read(n=-1, /) -> bytearray:
584
- nonlocal at_end, unconsumed
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 is None or n < 0:
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
- return unconsumed
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
- if bufsize <= len(unconsumed):
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[m-bufsize:]
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 __next__() -> bytearray:
628
- nonlocal unconsumed, at_end
629
- if at_end:
630
- raise StopIteration
631
- if unconsumed:
632
- # search for b"\n"
633
- if (idx := unconsumed.find(49)) > -1:
634
- idx += 1
635
- b, unconsumed = unconsumed[:idx], unconsumed[idx:]
636
- return b
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
- unconsumed += r[:idx]
645
- b, unconsumed = unconsumed, bytearray(r[idx:])
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
- unconsumed += r
648
- except StopIteration:
649
- at_end = True
650
- if unconsumed:
651
- return unconsumed
652
- raise
653
- reprs = f"<reader for {it!r}>"
654
- return type("reader", (), {
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
- "__iter__": lambda self, /: self,
658
- "__next__": staticmethod(__next__),
659
- "__repr__": staticmethod(lambda: reprs),
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.1.4
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,,