python-filewrap 0.0.3.1__py3-none-any.whl → 0.0.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
filewrap/__init__.py CHANGED
@@ -2,21 +2,25 @@
2
2
  # encoding: utf-8
3
3
 
4
4
  __author__ = "ChenyangGao <https://chenyanggao.github.io>"
5
- __version__ = (0, 0, 3)
5
+ __version__ = (0, 0, 4)
6
6
  __all__ = [
7
- "SupportsRead", "SupportsWrite", "bio_chunk_iter", "bio_chunk_async_iter",
8
- "bio_skip_iter", "bio_skip_async_iter", "bio_skip_bytes", "bio_skip_bytes_async",
9
- "bytes_iter_to_reader", "bytes_iter_to_reader_async",
7
+ "SupportsRead", "SupportsWrite",
8
+ "bio_chunk_iter", "bio_chunk_async_iter",
9
+ "bio_skip_iter", "bio_skip_async_iter",
10
+ "bytes_iter_to_reader", "bytes_iter_to_async_reader",
10
11
  ]
11
12
 
12
- from asyncio import to_thread
13
+ from asyncio import to_thread, Lock as AsyncLock
13
14
  from collections.abc import Awaitable, AsyncIterable, Iterable
14
15
  from functools import update_wrapper
15
16
  from inspect import isawaitable, iscoroutinefunction
16
17
  from collections.abc import AsyncIterator, Callable, Iterator
17
18
  from shutil import COPY_BUFSIZE # type: ignore
19
+ from threading import Lock
18
20
  from typing import Any, Protocol, TypeVar
19
21
 
22
+ from asynctools import ensure_async
23
+
20
24
 
21
25
  _T_co = TypeVar("_T_co", covariant=True)
22
26
  _T_contra = TypeVar("_T_contra", contravariant=True)
@@ -30,17 +34,6 @@ class SupportsWrite(Protocol[_T_contra]):
30
34
  def write(self, __s: _T_contra) -> object: ...
31
35
 
32
36
 
33
- def ensure_async(func, /):
34
- if iscoroutinefunction(func):
35
- return func
36
- async def wrapper(*args, **kwds):
37
- ret = to_thread(func, *args, **kwds)
38
- if isawaitable(ret):
39
- ret = await ret
40
- return ret
41
- return update_wrapper(wrapper, func)
42
-
43
-
44
37
  def bio_chunk_iter(
45
38
  bio: SupportsRead[bytes] | Callable[[int], bytes],
46
39
  /,
@@ -242,76 +235,182 @@ async def bio_skip_async_iter(
242
235
  yield length
243
236
 
244
237
 
245
- def bio_skip_bytes(
246
- bio: SupportsRead[bytes] | Callable[[int], bytes],
238
+ def bytes_iter_to_reader(
239
+ it: Iterable[bytes | bytearray],
247
240
  /,
248
- size: int = -1,
249
- chunksize: int = COPY_BUFSIZE,
250
- callback: None | Callable[[int], Any] = None,
251
- ):
252
- for _ in bio_skip_iter(bio, size, chunksize, callback=callback):
253
- pass
254
-
255
-
256
- async def bio_skip_bytes_async(
257
- bio: SupportsRead[bytes] | Callable[[int], bytes | Awaitable[bytes]],
258
- /,
259
- size: int = -1,
260
- chunksize: int = COPY_BUFSIZE,
261
- callback: None | Callable[[int], Any] = None,
262
- ):
263
- async for _ in bio_skip_async_iter(bio, size, chunksize, callback=callback):
264
- pass
265
-
266
-
267
- def bytes_iter_to_reader(it: Iterable[bytes | bytearray], /) -> SupportsRead[bytes]:
268
- get_next = iter(it).__next__
269
- at_eof = False
241
+ ) -> SupportsRead[bytes]:
242
+ getnext = iter(it).__next__
243
+ at_end = False
270
244
  unconsumed: bytearray = bytearray(b"")
271
- def read(n=-1):
272
- nonlocal at_eof, unconsumed
273
- if at_eof or n == 0:
245
+ lock = Lock()
246
+ def read(n=-1, /) -> bytes:
247
+ nonlocal at_end, unconsumed
248
+ if at_end or n == 0:
274
249
  return b""
275
- try:
276
- if n < 0:
250
+ with lock:
251
+ try:
252
+ if n is None or n < 0:
253
+ while True:
254
+ unconsumed += getnext()
255
+ else:
256
+ while n > len(unconsumed):
257
+ unconsumed += getnext()
258
+ b, unconsumed = bytes(unconsumed[:n]), unconsumed[n:]
259
+ return b
260
+ except StopIteration:
261
+ at_end = True
262
+ return bytes(unconsumed)
263
+ def readinto(buf, /) -> int:
264
+ nonlocal at_end, unconsumed
265
+ if at_end or not (bufsize := len(buf)):
266
+ return 0
267
+ with lock:
268
+ if bufsize <= len(unconsumed):
269
+ buf[:], unconsumed = unconsumed[:bufsize], unconsumed[bufsize:]
270
+ return bufsize
271
+ n = len(unconsumed)
272
+ buf[:n] = unconsumed
273
+ del unconsumed[:]
274
+ try:
277
275
  while True:
278
- unconsumed += get_next()
279
- else:
280
- while n > len(unconsumed):
281
- unconsumed += get_next()
282
- b, unconsumed = unconsumed[:n], unconsumed[n:]
283
- return bytes(b)
276
+ b = getnext()
277
+ if not b:
278
+ continue
279
+ m = n + len(b)
280
+ if m >= bufsize:
281
+ buf[n:] = b[:bufsize-n]
282
+ unconsumed += b[m-bufsize:]
283
+ return bufsize
284
+ else:
285
+ buf[n:m] = b
286
+ n = m
287
+ except StopIteration:
288
+ at_end = True
289
+ return n
290
+ def __next__() -> bytes:
291
+ nonlocal unconsumed, at_end
292
+ if at_end:
293
+ raise StopIteration
294
+ if unconsumed:
295
+ # search for b"\n"
296
+ if (idx := unconsumed.find(49)) > -1:
297
+ idx += 1
298
+ b, unconsumed = bytes(unconsumed[:idx]), unconsumed[idx:]
299
+ return b
300
+ try:
301
+ while True:
302
+ b = getnext()
303
+ if not b:
304
+ continue
305
+ if (idx := b.find(49)) > -1:
306
+ idx += 1
307
+ unconsumed += b[:idx]
308
+ b, unconsumed = bytes(unconsumed), bytearray(b[idx:])
309
+ return b
310
+ unconsumed += b
284
311
  except StopIteration:
285
- at_eof = True
286
- return bytes(unconsumed)
312
+ at_end = True
313
+ if unconsumed:
314
+ return bytes(unconsumed)
315
+ raise
287
316
  reprs = f"<reader for {it!r}>"
288
- return type("reader", (), {"read": staticmethod(read), "__repr__": staticmethod(lambda: reprs)})()
317
+ return type("reader", (), {
318
+ "read": staticmethod(read),
319
+ "readinto": staticmethod(readinto),
320
+ "__iter__": lambda self, /: self,
321
+ "__next__": staticmethod(__next__),
322
+ "__repr__": staticmethod(lambda: reprs),
323
+ })()
289
324
 
290
325
 
291
- def bytes_iter_to_reader_async(it: Iterable[bytes | bytearray] | AsyncIterable[bytes | bytearray], /) -> SupportsRead[bytes]:
326
+ def bytes_iter_to_async_reader(
327
+ it: Iterable[bytes | bytearray] | AsyncIterable[bytes | bytearray],
328
+ /,
329
+ threaded: bool = True,
330
+ ) -> SupportsRead[bytes]:
292
331
  if isinstance(it, AsyncIterable):
293
- get_next = aiter(it).__anext__
332
+ getnext = aiter(it).__anext__
294
333
  else:
295
- sync_next = iter(it).__next__
296
- get_next = lambda: to_thread(sync_next)
297
- at_eof = False
334
+ getnext = ensure_async(iter(it).__next__, threaded=threaded)
335
+ at_end = False
298
336
  unconsumed: bytearray = bytearray(b"")
299
- async def read(n=-1):
300
- nonlocal at_eof, unconsumed
301
- if at_eof or n == 0:
337
+ lock = AsyncLock()
338
+ async def read(n=-1, /) -> bytes:
339
+ nonlocal at_end, unconsumed
340
+ if at_end or n == 0:
302
341
  return b""
303
- try:
304
- if n < 0:
342
+ async with lock:
343
+ try:
344
+ if n is None or n < 0:
345
+ while True:
346
+ unconsumed += await getnext()
347
+ else:
348
+ while n > len(unconsumed):
349
+ unconsumed += await getnext()
350
+ b, unconsumed = bytes(unconsumed[:n]), unconsumed[n:]
351
+ return b
352
+ except StopAsyncIteration:
353
+ at_end = True
354
+ return bytes(unconsumed)
355
+ async def readinto(buf, /) -> int:
356
+ nonlocal at_end, unconsumed
357
+ if at_end or not (bufsize := len(buf)):
358
+ return 0
359
+ async with lock:
360
+ if bufsize <= len(unconsumed):
361
+ buf[:], unconsumed = unconsumed[:bufsize], unconsumed[bufsize:]
362
+ return bufsize
363
+ n = len(unconsumed)
364
+ buf[:n] = unconsumed
365
+ del unconsumed[:]
366
+ try:
305
367
  while True:
306
- unconsumed += await get_next()
307
- else:
308
- while n > len(unconsumed):
309
- unconsumed += await get_next()
310
- b, unconsumed = unconsumed[:n], unconsumed[n:]
311
- return bytes(b)
368
+ b = await getnext()
369
+ if not b:
370
+ continue
371
+ m = n + len(b)
372
+ if m >= bufsize:
373
+ buf[n:] = b[:bufsize-n]
374
+ unconsumed += b[m-bufsize:]
375
+ return bufsize
376
+ else:
377
+ buf[n:m] = b
378
+ n = m
379
+ except StopAsyncIteration:
380
+ at_end = True
381
+ return n
382
+ async def __next__() -> bytes:
383
+ nonlocal unconsumed, at_end
384
+ if at_end:
385
+ raise StopIteration
386
+ if unconsumed:
387
+ # search for b"\n"
388
+ if (idx := unconsumed.find(49)) > -1:
389
+ idx += 1
390
+ b, unconsumed = bytes(unconsumed[:idx]), unconsumed[idx:]
391
+ return b
392
+ try:
393
+ while True:
394
+ b = await getnext()
395
+ if not b:
396
+ continue
397
+ if (idx := b.find(49)) > -1:
398
+ idx += 1
399
+ unconsumed += b[:idx]
400
+ b, unconsumed = bytes(unconsumed), bytearray(b[idx:])
401
+ return b
402
+ unconsumed += b
312
403
  except StopIteration:
313
- at_eof = True
314
- return bytes(unconsumed)
404
+ at_end = True
405
+ if unconsumed:
406
+ return bytes(unconsumed)
407
+ raise
315
408
  reprs = f"<reader for {it!r}>"
316
- return type("reader", (), {"read": staticmethod(read), "__repr__": staticmethod(lambda: reprs)})()
409
+ return type("reader", (), {
410
+ "read": staticmethod(read),
411
+ "readinto": staticmethod(readinto),
412
+ "__iter__": lambda self, /: self,
413
+ "__next__": staticmethod(__next__),
414
+ "__repr__": staticmethod(lambda: reprs),
415
+ })()
317
416
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-filewrap
3
- Version: 0.0.3.1
3
+ Version: 0.0.4
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,6 +21,7 @@ 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
25
  Project-URL: Repository, https://github.com/ChenyangGao/web-mount-packs/tree/main/python-module/python-filewrap
25
26
  Description-Content-Type: text/markdown
26
27
 
@@ -0,0 +1,7 @@
1
+ LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
2
+ filewrap/__init__.py,sha256=28PmcNRaPhSDiWpDStAHTGguChOYdxRfAfezwLlH9z8,13273
3
+ filewrap/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ python_filewrap-0.0.4.dist-info/LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
5
+ python_filewrap-0.0.4.dist-info/METADATA,sha256=K12tl1IE20BhHOIhUhJXx0v-l5g1ws3_6S1iC1gLWB8,1362
6
+ python_filewrap-0.0.4.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
7
+ python_filewrap-0.0.4.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
2
- filewrap/__init__.py,sha256=MzZLsQJkjBke4XZzxjw8i5eyL7LyiGjw3M4HVyiMxeA,9893
3
- filewrap/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- python_filewrap-0.0.3.1.dist-info/LICENSE,sha256=o5242_N2TgDsWwFhPn7yr8YJNF7XsJM5NxUMtcT97bc,1100
5
- python_filewrap-0.0.3.1.dist-info/METADATA,sha256=OO_ZWnnc6HBBvRtFXfV2kPM3zBji3dsMTRSruLtGPiI,1331
6
- python_filewrap-0.0.3.1.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
7
- python_filewrap-0.0.3.1.dist-info/RECORD,,