glow 0.15.5__tar.gz → 0.15.7__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.
Files changed (60) hide show
  1. {glow-0.15.5 → glow-0.15.7}/PKG-INFO +2 -1
  2. {glow-0.15.5 → glow-0.15.7}/pyproject.toml +2 -1
  3. {glow-0.15.5 → glow-0.15.7}/src/glow/__init__.py +4 -1
  4. {glow-0.15.5 → glow-0.15.7}/src/glow/_async.py +53 -20
  5. {glow-0.15.5 → glow-0.15.7}/src/glow/_async.pyi +8 -1
  6. {glow-0.15.5 → glow-0.15.7}/src/glow/_cache.py +8 -9
  7. {glow-0.15.5 → glow-0.15.7}/src/glow/_cache.pyi +2 -1
  8. {glow-0.15.5 → glow-0.15.7}/src/glow/_concurrency.py +7 -9
  9. {glow-0.15.5 → glow-0.15.7}/src/glow/_concurrency.pyi +5 -4
  10. {glow-0.15.5 → glow-0.15.7}/src/glow/_coro.py +1 -1
  11. {glow-0.15.5 → glow-0.15.7}/src/glow/_debug.py +13 -6
  12. {glow-0.15.5 → glow-0.15.7}/src/glow/_futures.py +22 -2
  13. {glow-0.15.5 → glow-0.15.7}/src/glow/_import_hook.py +4 -4
  14. {glow-0.15.5 → glow-0.15.7}/src/glow/_parallel.py +101 -68
  15. {glow-0.15.5 → glow-0.15.7}/src/glow/_profile.py +8 -8
  16. {glow-0.15.5 → glow-0.15.7}/src/glow/_profile.pyi +7 -7
  17. {glow-0.15.5 → glow-0.15.7}/src/glow/_reduction.py +2 -6
  18. {glow-0.15.5 → glow-0.15.7}/src/glow/_streams.py +4 -3
  19. glow-0.15.7/src/glow/_types.py +31 -0
  20. {glow-0.15.5 → glow-0.15.7}/src/glow/_uuid.py +7 -1
  21. {glow-0.15.5 → glow-0.15.7}/src/glow/_wrap.py +3 -1
  22. {glow-0.15.5 → glow-0.15.7}/src/glow/cli.py +56 -25
  23. {glow-0.15.5 → glow-0.15.7}/src/glow/cli.pyi +9 -1
  24. {glow-0.15.5 → glow-0.15.7}/src/glow/io/_sound.py +20 -8
  25. {glow-0.15.5 → glow-0.15.7}/test/test_cli.py +28 -2
  26. glow-0.15.5/src/glow/_types.py +0 -53
  27. {glow-0.15.5 → glow-0.15.7}/.gitignore +0 -0
  28. {glow-0.15.5 → glow-0.15.7}/LICENSE +0 -0
  29. {glow-0.15.5 → glow-0.15.7}/README.md +0 -0
  30. {glow-0.15.5 → glow-0.15.7}/src/glow/_array.py +0 -0
  31. {glow-0.15.5 → glow-0.15.7}/src/glow/_dev.py +0 -0
  32. {glow-0.15.5 → glow-0.15.7}/src/glow/_ic.py +0 -0
  33. {glow-0.15.5 → glow-0.15.7}/src/glow/_imutil.py +0 -0
  34. {glow-0.15.5 → glow-0.15.7}/src/glow/_keys.py +0 -0
  35. {glow-0.15.5 → glow-0.15.7}/src/glow/_logging.py +0 -0
  36. {glow-0.15.5 → glow-0.15.7}/src/glow/_more.py +0 -0
  37. {glow-0.15.5 → glow-0.15.7}/src/glow/_parallel.pyi +0 -0
  38. {glow-0.15.5 → glow-0.15.7}/src/glow/_patch_len.py +0 -0
  39. {glow-0.15.5 → glow-0.15.7}/src/glow/_patch_print.py +0 -0
  40. {glow-0.15.5 → glow-0.15.7}/src/glow/_patch_scipy.py +0 -0
  41. {glow-0.15.5 → glow-0.15.7}/src/glow/_repr.py +0 -0
  42. {glow-0.15.5 → glow-0.15.7}/src/glow/_reusable.py +0 -0
  43. {glow-0.15.5 → glow-0.15.7}/src/glow/_sizeof.py +0 -0
  44. {glow-0.15.5 → glow-0.15.7}/src/glow/_thread_quota.py +0 -0
  45. {glow-0.15.5 → glow-0.15.7}/src/glow/api/__init__.py +0 -0
  46. {glow-0.15.5 → glow-0.15.7}/src/glow/api/config.py +0 -0
  47. {glow-0.15.5 → glow-0.15.7}/src/glow/api/exporting.py +0 -0
  48. {glow-0.15.5 → glow-0.15.7}/src/glow/io/__init__.py +0 -0
  49. {glow-0.15.5 → glow-0.15.7}/src/glow/io/_svg.py +0 -0
  50. {glow-0.15.5 → glow-0.15.7}/src/glow/py.typed +0 -0
  51. {glow-0.15.5 → glow-0.15.7}/test/__init__.py +0 -0
  52. {glow-0.15.5 → glow-0.15.7}/test/test_api.py +0 -0
  53. {glow-0.15.5 → glow-0.15.7}/test/test_batch.py +0 -0
  54. {glow-0.15.5 → glow-0.15.7}/test/test_buffered.py +0 -0
  55. {glow-0.15.5 → glow-0.15.7}/test/test_iter.py +0 -0
  56. {glow-0.15.5 → glow-0.15.7}/test/test_shm.py +0 -0
  57. {glow-0.15.5 → glow-0.15.7}/test/test_thread_pool.py +0 -0
  58. {glow-0.15.5 → glow-0.15.7}/test/test_timed.py +0 -0
  59. {glow-0.15.5 → glow-0.15.7}/test/test_timer.py +0 -0
  60. {glow-0.15.5 → glow-0.15.7}/test/test_uuid.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: glow
3
- Version: 0.15.5
3
+ Version: 0.15.7
4
4
  Summary: Functional Python tools
5
5
  Project-URL: homepage, https://github.com/arquolo/glow
6
6
  Author-email: Paul Maevskikh <arquolo@gmail.com>
@@ -40,6 +40,7 @@ Requires-Dist: lxml
40
40
  Requires-Dist: numpy<3,>=1.21
41
41
  Requires-Dist: tqdm
42
42
  Requires-Dist: typing-extensions~=4.1; python_version < '3.11'
43
+ Requires-Dist: typing-inspection~=0.4.1
43
44
  Requires-Dist: wrapt~=1.15
44
45
  Provides-Extra: all
45
46
  Requires-Dist: asttokens; extra == 'all'
@@ -7,7 +7,7 @@ only-packages = true
7
7
 
8
8
  [project]
9
9
  name = "glow"
10
- version = "0.15.5"
10
+ version = "0.15.7"
11
11
  description = "Functional Python tools"
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.12"
@@ -33,6 +33,7 @@ dependencies = [
33
33
  "lxml",
34
34
  "numpy >=1.21, <3",
35
35
  "typing-extensions~=4.1; python_version < '3.11'",
36
+ "typing-inspection~=0.4.1",
36
37
  "tqdm",
37
38
  "wrapt~=1.15",
38
39
  ]
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING
6
6
 
7
7
  from . import _patch_len, _patch_print, _patch_scipy
8
8
  from ._array import aceil, afloor, apack, around, pascal
9
- from ._async import amap, amap_dict, astarmap, astreaming, azip
9
+ from ._async import RwLock, amap, amap_dict, astarmap, astreaming, azip
10
10
  from ._cache import cache_status, memoize
11
11
  from ._concurrency import (
12
12
  call_once,
@@ -17,6 +17,7 @@ from ._concurrency import (
17
17
  )
18
18
  from ._coro import as_actor, coroutine, summary
19
19
  from ._debug import lock_seed, trace, trace_module, whereami
20
+ from ._dev import hide_frame
20
21
  from ._import_hook import register_post_import_hook, when_imported
21
22
  from ._logging import init_loguru
22
23
  from ._more import (
@@ -69,6 +70,7 @@ else:
69
70
 
70
71
  __all__ = [
71
72
  'Reusable',
73
+ 'RwLock',
72
74
  'Uid',
73
75
  'aceil',
74
76
  'afloor',
@@ -91,6 +93,7 @@ __all__ = [
91
93
  'eat',
92
94
  'get_executor',
93
95
  'groupby',
96
+ 'hide_frame',
94
97
  'ic',
95
98
  'ic_repr',
96
99
  'ichunked',
@@ -1,7 +1,7 @@
1
- __all__ = ['amap', 'amap_dict', 'astarmap', 'azip']
1
+ __all__ = ['RwLock', 'amap', 'amap_dict', 'astarmap', 'azip']
2
2
 
3
3
  import asyncio
4
- from asyncio import Queue, Task
4
+ from asyncio import Event, Future, Lock, Queue, Task, TaskGroup
5
5
  from collections import deque
6
6
  from collections.abc import (
7
7
  AsyncIterator,
@@ -12,22 +12,13 @@ from collections.abc import (
12
12
  Mapping,
13
13
  Sequence,
14
14
  )
15
- from contextlib import suppress
15
+ from contextlib import asynccontextmanager, suppress
16
16
  from functools import partial
17
17
  from typing import TypeGuard, cast, overload
18
18
 
19
19
  from ._dev import hide_frame
20
- from ._futures import adispatch
21
- from ._types import (
22
- ABatchDecorator,
23
- ABatchFn,
24
- AnyFuture,
25
- AnyIterable,
26
- AnyIterator,
27
- Coro,
28
- )
29
-
30
- type _Job[T, R] = tuple[T, AnyFuture[R]]
20
+ from ._futures import ABatchDecorator, ABatchFn, Job, adispatch
21
+ from ._types import AnyIterable, AnyIterator, Coro
31
22
 
32
23
 
33
24
  async def amap_dict[K, T1, T2](
@@ -93,7 +84,7 @@ async def astarmap[*Ts, R](
93
84
  yield await func(*args)
94
85
  return
95
86
 
96
- async with asyncio.TaskGroup() as tg:
87
+ async with TaskGroup() as tg:
97
88
  ts = (
98
89
  (tg.create_task(func(*args)) for args in iterable)
99
90
  if isinstance(iterable, Iterable)
@@ -263,10 +254,10 @@ def astreaming[T, R](
263
254
  assert batch_size is None or batch_size >= 1
264
255
  assert timeout > 0
265
256
 
266
- buf: list[_Job[T, R]] = []
257
+ buf: list[Job[T, R]] = []
267
258
  deadline = float('-inf')
268
- not_last = asyncio.Event()
269
- lock = asyncio.Lock()
259
+ not_last = Event()
260
+ lock = Lock()
270
261
  ncalls = 0
271
262
 
272
263
  async def wrapper(items: Sequence[T]) -> list[R]:
@@ -279,10 +270,10 @@ def astreaming[T, R](
279
270
  not_last.set()
280
271
 
281
272
  ncalls += 1
282
- fs: list[asyncio.Future[R]] = []
273
+ fs: list[Future[R]] = []
283
274
  try:
284
275
  for x in items:
285
- f = asyncio.Future[R]()
276
+ f = Future[R]()
286
277
  fs.append(f)
287
278
  buf.append((x, f))
288
279
 
@@ -314,3 +305,45 @@ def astreaming[T, R](
314
305
  return await asyncio.gather(*fs)
315
306
 
316
307
  return wrapper
308
+
309
+
310
+ # ----------------------------- read/write guard -----------------------------
311
+
312
+
313
+ class RwLock:
314
+ """Guard code from concurrent writes.
315
+
316
+ Reads are not limited.
317
+ When write is issued, new reads are delayed until write is finished.
318
+ """
319
+
320
+ def __init__(self) -> None:
321
+ self._num_reads = 0
322
+ self._readable = Event()
323
+ self._readable.set()
324
+ self._writable = Event()
325
+ self._writable.set()
326
+
327
+ @asynccontextmanager
328
+ async def read(self) -> AsyncIterator[None]:
329
+ await self._readable.wait()
330
+ self._writable.clear()
331
+ try:
332
+ yield
333
+ finally:
334
+ self._num_reads -= 1
335
+ if self._num_reads == 0:
336
+ self._writable.set()
337
+
338
+ @asynccontextmanager
339
+ async def write(self) -> AsyncIterator[None]:
340
+ self._readable.clear() # Stop new READs
341
+ try:
342
+ await self._writable.wait() # Wait for all READs or single WRITE
343
+ self._writable.clear() # Only single WRITE is allowed
344
+ try:
345
+ yield
346
+ finally:
347
+ self._writable.set()
348
+ finally:
349
+ self._readable.set()
@@ -1,7 +1,9 @@
1
1
  from collections.abc import AsyncIterator, Callable, Mapping
2
+ from contextlib import AbstractAsyncContextManager
2
3
  from typing import Any, Required, TypedDict, Unpack, overload
3
4
 
4
- from ._types import ABatchDecorator, ABatchFn, AnyIterable, Coro
5
+ from ._futures import ABatchDecorator, ABatchFn
6
+ from ._types import AnyIterable, Coro
5
7
 
6
8
  class _AmapKwargs(TypedDict, total=False):
7
9
  limit: Required[int]
@@ -106,3 +108,8 @@ def astreaming[T, R](
106
108
  batch_size: int | None = ...,
107
109
  timeout: float = ...,
108
110
  ) -> ABatchFn[T, R]: ...
111
+
112
+ class RwLock:
113
+ def __init__(self) -> None: ...
114
+ def read(self) -> AbstractAsyncContextManager: ...
115
+ def write(self) -> AbstractAsyncContextManager: ...
@@ -21,20 +21,19 @@ from typing import Any, Final, Protocol, SupportsInt, cast
21
21
  from weakref import WeakValueDictionary
22
22
 
23
23
  from ._dev import clone_exc, hide_frame
24
- from ._futures import adispatch, dispatch, gather_fs
25
- from ._keys import make_key
26
- from ._repr import si_bin
27
- from ._sizeof import sizeof
28
- from ._types import (
24
+ from ._futures import (
29
25
  ABatchFn,
30
26
  AnyFuture,
31
27
  BatchFn,
32
- CachePolicy,
33
- Decorator,
34
28
  Job,
35
- KeyFn,
36
- Some,
29
+ adispatch,
30
+ dispatch,
31
+ gather_fs,
37
32
  )
33
+ from ._keys import make_key
34
+ from ._repr import si_bin
35
+ from ._sizeof import sizeof
36
+ from ._types import CachePolicy, Decorator, KeyFn, Some
38
37
 
39
38
 
40
39
  class _Empty(enum.Enum):
@@ -1,6 +1,7 @@
1
1
  from typing import Literal, overload
2
2
 
3
- from ._types import AnyBatchDecorator, CachePolicy, Decorator, KeyFn
3
+ from ._futures import AnyBatchDecorator
4
+ from ._types import CachePolicy, Decorator, KeyFn
4
5
 
5
6
  def cache_status() -> str: ...
6
7
 
@@ -19,15 +19,15 @@ from warnings import warn
19
19
 
20
20
  from ._cache import memoize
21
21
  from ._dev import hide_frame
22
- from ._futures import dispatch, gather_fs
23
- from ._types import BatchDecorator, BatchFn
22
+ from ._futures import BatchDecorator, BatchFn, Job, dispatch, gather_fs
23
+ from ._types import Get
24
24
 
25
25
  _PATIENCE = 0.01
26
26
 
27
27
 
28
- def threadlocal[T](
29
- fn: Callable[..., T], /, *args: object, **kwargs: object
30
- ) -> Callable[[], T]:
28
+ def threadlocal[**P, T](
29
+ fn: Callable[P, T], /, *args: P.args, **kwargs: P.kwargs
30
+ ) -> Get[T]:
31
31
  """Create thread-local singleton factory function (functools.partial)."""
32
32
  local_ = threading.local()
33
33
 
@@ -41,7 +41,7 @@ def threadlocal[T](
41
41
  return update_wrapper(wrapper, fn)
42
42
 
43
43
 
44
- def call_once[T](fn: Callable[[], T], /) -> Callable[[], T]:
44
+ def call_once[T](fn: Get[T], /) -> Get[T]:
45
45
  """Make callable a singleton.
46
46
 
47
47
  Supports async-def functions (but not async-gen functions).
@@ -81,8 +81,6 @@ def weak_memoize[**P, R](fn: Callable[P, R], /) -> Callable[P, R]:
81
81
 
82
82
  # ----------------------------- batch collation ------------------------------
83
83
 
84
- type _Job[T, R] = tuple[T, Future[R]]
85
-
86
84
 
87
85
  def _fetch_batch[T](
88
86
  q: SimpleQueue[T], batch_size: int | None, timeout: float
@@ -120,7 +118,7 @@ def _start_fetch_compute[T, R](
120
118
  workers: int,
121
119
  batch_size: int | None,
122
120
  timeout: float,
123
- ) -> SimpleQueue[_Job[T, R]]:
121
+ ) -> SimpleQueue[Job[T, R]]:
124
122
  q = SimpleQueue() # type: ignore[var-annotated]
125
123
  lock = Lock()
126
124
 
@@ -2,13 +2,14 @@ from collections.abc import Callable
2
2
  from contextlib import AbstractContextManager
3
3
  from typing import overload
4
4
 
5
- from ._types import BatchDecorator, BatchFn
5
+ from ._futures import BatchDecorator, BatchFn
6
+ from ._types import Get
6
7
 
7
8
  def threadlocal[T, **P](
8
- fn: Callable[P, T], *args: P.args, **kwargs: P.kwargs
9
- ) -> Callable[[], T]: ...
9
+ fn: Callable[P, T], /, *args: P.args, **kwargs: P.kwargs
10
+ ) -> Get[T]: ...
10
11
  def interpreter_lock(timeout: float = ...) -> AbstractContextManager[None]: ...
11
- def call_once[T](fn: Callable[[], T], /) -> Callable[[], T]: ...
12
+ def call_once[T](fn: Get[T], /) -> Get[T]: ...
12
13
  def shared_call[**P, R](fn: Callable[P, R], /) -> Callable[P, R]: ...
13
14
  def weak_memoize[**P, R](fn: Callable[P, R], /) -> Callable[P, R]: ...
14
15
  @overload
@@ -29,7 +29,7 @@ class _Sync[Y, S, R](wrapt.ObjectProxy): # type: ignore[misc]
29
29
  self._self_lock = Lock()
30
30
 
31
31
  def _call[**P, T](
32
- self, op: Callable[P, T], *args: P.args, **kwargs: P.kwargs
32
+ self, op: Callable[P, T], /, *args: P.args, **kwargs: P.kwargs
33
33
  ) -> T:
34
34
  with self._self_lock:
35
35
  return op(*args, **kwargs)
@@ -3,12 +3,11 @@ __all__ = ['lock_seed', 'trace', 'trace_module', 'whereami']
3
3
  import gc
4
4
  import os
5
5
  import random
6
- import types
7
- from collections.abc import Iterator
6
+ from collections.abc import Callable, Iterator
8
7
  from contextlib import suppress
9
8
  from inspect import currentframe, getmodule, isfunction
10
9
  from itertools import islice
11
- from types import FrameType
10
+ from types import FrameType, ModuleType
12
11
 
13
12
  import numpy as np
14
13
  import wrapt
@@ -74,12 +73,19 @@ def trace(fn, _, args, kwargs):
74
73
  return fn(*args, **kwargs)
75
74
 
76
75
 
77
- def _set_trace(obj, seen=None, prefix=None, module=None):
76
+ def _set_trace(
77
+ obj: ModuleType | Callable,
78
+ *,
79
+ seen: set[str] | None = None,
80
+ prefix: str | None = None,
81
+ module: ModuleType | None = None,
82
+ ) -> None:
78
83
  # TODO: rewrite using unittest.mock
79
- if isinstance(obj, types.ModuleType):
84
+ if isinstance(obj, ModuleType):
80
85
  if seen is None:
81
86
  seen = set()
82
87
  prefix = obj.__name__
88
+ assert isinstance(prefix, str)
83
89
  if not obj.__name__.startswith(prefix) or obj.__name__ in seen:
84
90
  return
85
91
  seen.add(obj.__name__)
@@ -91,6 +97,7 @@ def _set_trace(obj, seen=None, prefix=None, module=None):
91
97
  if not callable(obj):
92
98
  return
93
99
 
100
+ assert isinstance(module, ModuleType)
94
101
  if not hasattr(obj, '__dict__'):
95
102
  setattr(module, obj.__qualname__, trace(obj))
96
103
  print(f'wraps "{module.__name__}:{obj.__qualname__}"')
@@ -113,7 +120,7 @@ def _set_trace(obj, seen=None, prefix=None, module=None):
113
120
  print(f'wraps "{module.__name__}:{obj.__qualname__}.{name}"')
114
121
 
115
122
 
116
- def trace_module(name):
123
+ def trace_module(name: str) -> None:
117
124
  """Enable call logging for each callable inside module name."""
118
125
  register_post_import_hook(_set_trace, name)
119
126
 
@@ -1,9 +1,29 @@
1
1
  import asyncio
2
2
  import concurrent.futures as cf
3
- from collections.abc import Hashable, Iterable, Sequence
3
+ from collections.abc import Callable, Hashable, Iterable, Sequence
4
+ from typing import Protocol, overload
4
5
 
5
6
  from ._dev import hide_frame
6
- from ._types import ABatchFn, AnyFuture, BatchFn, Job, Some
7
+ from ._types import Coro, Some
8
+
9
+ type AnyFuture[R] = cf.Future[R] | asyncio.Future[R]
10
+ type Job[T, R] = tuple[T, AnyFuture[R]]
11
+
12
+ type BatchFn[T, R] = Callable[[Sequence[T]], Sequence[R]]
13
+ type ABatchFn[T, R] = Callable[[Sequence[T]], Coro[Sequence[R]]]
14
+
15
+
16
+ class BatchDecorator(Protocol):
17
+ def __call__[T, R](self, fn: BatchFn[T, R], /) -> BatchFn[T, R]: ...
18
+ class ABatchDecorator(Protocol):
19
+ def __call__[T, R](self, fn: ABatchFn[T, R], /) -> ABatchFn[T, R]: ...
20
+
21
+
22
+ class AnyBatchDecorator(Protocol):
23
+ @overload
24
+ def __call__[T, R](self, fn: BatchFn[T, R], /) -> BatchFn[T, R]: ...
25
+ @overload
26
+ def __call__[T, R](self, fn: ABatchFn[T, R], /) -> ABatchFn[T, R]: ...
7
27
 
8
28
 
9
29
  def dispatch[T, R](fn: BatchFn[T, R], *xs: Job[T, R]) -> None:
@@ -7,11 +7,11 @@ from importlib.machinery import ModuleSpec
7
7
  from threading import RLock
8
8
  from types import ModuleType
9
9
 
10
- type _Hook = Callable[[ModuleType], object]
10
+ from ._types import Callback
11
11
 
12
12
  _INITIALIZED = False
13
13
  _LOCK = RLock()
14
- _HOOKS: dict[str, list[_Hook]] = {}
14
+ _HOOKS: dict[str, list[Callback[ModuleType]]] = {}
15
15
 
16
16
 
17
17
  class _ImportHookChainedLoader(abc.Loader):
@@ -71,7 +71,7 @@ class _ImportHookFinder(abc.MetaPathFinder, set[str]):
71
71
  return None
72
72
 
73
73
 
74
- def register_post_import_hook(hook: _Hook, name: str) -> None:
74
+ def register_post_import_hook(hook: Callback[ModuleType], name: str) -> None:
75
75
  """Register a new post import hook for the target module name.
76
76
 
77
77
  This will result in a proxy callback being registered which will defer
@@ -92,7 +92,7 @@ def register_post_import_hook(hook: _Hook, name: str) -> None:
92
92
  hook(module)
93
93
 
94
94
 
95
- def when_imported[H: _Hook](name: str) -> Callable[[H], H]:
95
+ def when_imported[H: Callback[ModuleType]](name: str) -> Callable[[H], H]:
96
96
  """Create decorator making a function a post import hook for a module.
97
97
 
98
98
  Simplified version of wrapt.when_imported.