python-filewrap 0.0.3__tar.gz → 0.0.4__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-filewrap
3
- Version: 0.0.3
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
 
@@ -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,72 +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
- unconsumed = bytearray(b"")
270
- def read(n=-1):
271
- nonlocal unconsumed
272
- if n == 0:
241
+ ) -> SupportsRead[bytes]:
242
+ getnext = iter(it).__next__
243
+ at_end = False
244
+ unconsumed: bytearray = bytearray(b"")
245
+ lock = Lock()
246
+ def read(n=-1, /) -> bytes:
247
+ nonlocal at_end, unconsumed
248
+ if at_end or n == 0:
273
249
  return b""
274
- try:
275
- 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:
276
275
  while True:
277
- unconsumed.extend(get_next())
278
- else:
279
- while n > len(unconsumed):
280
- unconsumed.extend(get_next())
281
- b, unconsumed = unconsumed[:n], unconsumed[n:]
282
- 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
283
311
  except StopIteration:
284
- return bytes(unconsumed)
312
+ at_end = True
313
+ if unconsumed:
314
+ return bytes(unconsumed)
315
+ raise
285
316
  reprs = f"<reader for {it!r}>"
286
- 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
+ })()
287
324
 
288
325
 
289
- def bytes_iter_to_reader_async(it: Iterable[bytes | bytearray] | AsyncIterable[bytes | bytearray], /) -> SupportsRead[bytes]:
290
- unconsumed = bytearray(b"")
326
+ def bytes_iter_to_async_reader(
327
+ it: Iterable[bytes | bytearray] | AsyncIterable[bytes | bytearray],
328
+ /,
329
+ threaded: bool = True,
330
+ ) -> SupportsRead[bytes]:
291
331
  if isinstance(it, AsyncIterable):
292
- get_next = aiter(it).__anext__
332
+ getnext = aiter(it).__anext__
293
333
  else:
294
- sync_next = iter(it).__next__
295
- get_next = lambda: to_thread(sync_next)
296
- async def read(n=-1):
297
- nonlocal unconsumed
298
- if n == 0:
334
+ getnext = ensure_async(iter(it).__next__, threaded=threaded)
335
+ at_end = False
336
+ unconsumed: bytearray = bytearray(b"")
337
+ lock = AsyncLock()
338
+ async def read(n=-1, /) -> bytes:
339
+ nonlocal at_end, unconsumed
340
+ if at_end or n == 0:
299
341
  return b""
300
- try:
301
- 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:
302
367
  while True:
303
- unconsumed.extend(await get_next())
304
- else:
305
- while n > len(unconsumed):
306
- unconsumed.extend(await get_next())
307
- b, unconsumed = unconsumed[:n], unconsumed[n:]
308
- 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
309
403
  except StopIteration:
310
- return bytes(unconsumed)
404
+ at_end = True
405
+ if unconsumed:
406
+ return bytes(unconsumed)
407
+ raise
311
408
  reprs = f"<reader for {it!r}>"
312
- 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
+ })()
313
416
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "python-filewrap"
3
- version = "0.0.3"
3
+ version = "0.0.4"
4
4
  description = "Python file wrappers."
5
5
  authors = ["ChenyangGao <wosiwujm@gmail.com>"]
6
6
  license = "MIT"
@@ -27,6 +27,7 @@ include = [
27
27
 
28
28
  [tool.poetry.dependencies]
29
29
  python = "^3.10"
30
+ python-asynctools = "*"
30
31
 
31
32
  [build-system]
32
33
  requires = ["poetry-core"]
File without changes