tldm 1.0.0__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.
- tldm/__init__.py +11 -0
- tldm/_monitor.py +115 -0
- tldm/aliases.py +86 -0
- tldm/extensions/__init__.py +1 -0
- tldm/extensions/asyncio.py +100 -0
- tldm/extensions/concurrent.py +125 -0
- tldm/extensions/pandas.py +179 -0
- tldm/extensions/rich.py +312 -0
- tldm/notebook.py +348 -0
- tldm/py.typed +0 -0
- tldm/std.py +1089 -0
- tldm/utils.py +832 -0
- tldm-1.0.0.dist-info/METADATA +1246 -0
- tldm-1.0.0.dist-info/RECORD +16 -0
- tldm-1.0.0.dist-info/WHEEL +4 -0
- tldm-1.0.0.dist-info/licenses/LICENCE +382 -0
tldm/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from importlib.metadata import version
|
|
2
|
+
|
|
3
|
+
from ._monitor import TMonitor as TMonitor
|
|
4
|
+
from .aliases import tenumerate as tenumerate
|
|
5
|
+
from .aliases import tmap as tmap
|
|
6
|
+
from .aliases import tproduct as tproduct
|
|
7
|
+
from .aliases import trange as trange
|
|
8
|
+
from .aliases import tzip as tzip
|
|
9
|
+
from .std import tldm as tldm
|
|
10
|
+
|
|
11
|
+
__version__ = version("tldm")
|
tldm/_monitor.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import atexit
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
from threading import Event, Thread, current_thread
|
|
4
|
+
from time import time
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
from warnings import warn
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from tldm.std import tldm
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TldmSynchronisationWarning(RuntimeWarning):
|
|
13
|
+
"""tldm multi-thread/-process errors which may cause incorrect nesting
|
|
14
|
+
but otherwise no adverse effects"""
|
|
15
|
+
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TMonitor(Thread):
|
|
20
|
+
"""
|
|
21
|
+
Monitoring thread for tldm bars.
|
|
22
|
+
Monitors if tldm bars are taking too much time to display
|
|
23
|
+
and readjusts miniters automatically if necessary.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
tldm_cls : class
|
|
28
|
+
tldm class to use (can be core tldm or a submodule).
|
|
29
|
+
sleep_interval : float
|
|
30
|
+
Time to sleep between monitoring checks.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
_test: dict[str, Event] = {} # internal vars for unit testing
|
|
34
|
+
|
|
35
|
+
name: str
|
|
36
|
+
daemon: bool
|
|
37
|
+
woken: float
|
|
38
|
+
tldm_cls: type["tldm"]
|
|
39
|
+
sleep_interval: float
|
|
40
|
+
_time: Callable[[], float]
|
|
41
|
+
was_killed: Event
|
|
42
|
+
|
|
43
|
+
def __init__(self, tldm_cls: type["tldm"], sleep_interval: float) -> None:
|
|
44
|
+
Thread.__init__(self)
|
|
45
|
+
self.name = "tldm_monitor"
|
|
46
|
+
self.daemon = True # kill thread when main killed (KeyboardInterrupt)
|
|
47
|
+
self.woken = 0 # last time woken up, to sync with monitor
|
|
48
|
+
self.tldm_cls = tldm_cls
|
|
49
|
+
self.sleep_interval = sleep_interval
|
|
50
|
+
self._time = self._test.get("time", time) # type: ignore[assignment]
|
|
51
|
+
self.was_killed = self._test.get("Event", Event)() # type: ignore[operator]
|
|
52
|
+
atexit.register(self.exit)
|
|
53
|
+
self.start()
|
|
54
|
+
|
|
55
|
+
def exit(self) -> bool:
|
|
56
|
+
self.was_killed.set()
|
|
57
|
+
if self is not current_thread():
|
|
58
|
+
self.join()
|
|
59
|
+
return self.report()
|
|
60
|
+
|
|
61
|
+
def get_instances(self) -> list["tldm"]:
|
|
62
|
+
# returns a copy of started `tldm_cls` instances
|
|
63
|
+
return [
|
|
64
|
+
i
|
|
65
|
+
for i in self.tldm_cls._instances.copy()
|
|
66
|
+
# Avoid race by checking that the instance started
|
|
67
|
+
if hasattr(i, "start_t")
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
def run(self) -> None:
|
|
71
|
+
cur_t = self._time()
|
|
72
|
+
while True:
|
|
73
|
+
# After processing and before sleeping, notify that we woke
|
|
74
|
+
# Need to be done just before sleeping
|
|
75
|
+
self.woken = cur_t
|
|
76
|
+
# Sleep some time...
|
|
77
|
+
self.was_killed.wait(self.sleep_interval)
|
|
78
|
+
# Quit if killed
|
|
79
|
+
if self.was_killed.is_set():
|
|
80
|
+
return
|
|
81
|
+
# Then monitor!
|
|
82
|
+
# Acquire lock (to access _instances)
|
|
83
|
+
with self.tldm_cls.get_lock():
|
|
84
|
+
cur_t = self._time()
|
|
85
|
+
# Check tldm instances are waiting too long to print
|
|
86
|
+
instances = self.get_instances()
|
|
87
|
+
for instance in instances:
|
|
88
|
+
# Check event in loop to reduce blocking time on exit
|
|
89
|
+
if self.was_killed.is_set():
|
|
90
|
+
return
|
|
91
|
+
# Only if mininterval > 1 (else iterations are just slow)
|
|
92
|
+
# and last refresh exceeded maxinterval
|
|
93
|
+
if (
|
|
94
|
+
instance.miniters > 1
|
|
95
|
+
and (cur_t - instance.last_print_t) >= instance.maxinterval
|
|
96
|
+
):
|
|
97
|
+
# force bypassing miniters on next iteration
|
|
98
|
+
# (dynamic_miniters adjusts mininterval automatically)
|
|
99
|
+
instance.miniters = 1
|
|
100
|
+
# Refresh now! (works only for manual tldm)
|
|
101
|
+
instance.refresh(nolock=True)
|
|
102
|
+
# Remove accidental long-lived strong reference
|
|
103
|
+
del instance
|
|
104
|
+
if instances != self.get_instances(): # pragma: nocover
|
|
105
|
+
warn(
|
|
106
|
+
"Set changed size during iteration"
|
|
107
|
+
+ " (see https://github.com/tldm/tldm/issues/481)",
|
|
108
|
+
TldmSynchronisationWarning,
|
|
109
|
+
stacklevel=2,
|
|
110
|
+
)
|
|
111
|
+
# Remove accidental long-lived strong references
|
|
112
|
+
del instances
|
|
113
|
+
|
|
114
|
+
def report(self) -> bool:
|
|
115
|
+
return not self.was_killed.is_set()
|
tldm/aliases.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
from collections.abc import Callable, Iterable, Iterator
|
|
3
|
+
from operator import length_hint
|
|
4
|
+
from typing import Any, TypeVar
|
|
5
|
+
|
|
6
|
+
from .std import tldm
|
|
7
|
+
|
|
8
|
+
T = TypeVar("T")
|
|
9
|
+
R = TypeVar("R")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def tenumerate(
|
|
13
|
+
iterable: Iterable[T],
|
|
14
|
+
start: int = 0,
|
|
15
|
+
total: int | float | None = None,
|
|
16
|
+
tldm_class: type[tldm] = tldm,
|
|
17
|
+
**tldm_kwargs: Any,
|
|
18
|
+
) -> Iterator[tuple[int, T]]:
|
|
19
|
+
"""
|
|
20
|
+
Equivalent of builtin `enumerate`.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
tldm_class : [default: tldm.std.tldm].
|
|
25
|
+
"""
|
|
26
|
+
return enumerate(tldm_class(iterable, total=total, **tldm_kwargs), start)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def tzip(
|
|
30
|
+
iter1: Iterable[T], *iter2plus: Iterable[Any], **tldm_kwargs: Any
|
|
31
|
+
) -> Iterator[tuple[T, ...]]:
|
|
32
|
+
"""
|
|
33
|
+
Equivalent of builtin `zip`.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
tldm_class : [default: tldm.std.tldm].
|
|
38
|
+
"""
|
|
39
|
+
kwargs = tldm_kwargs.copy()
|
|
40
|
+
tldm_class = kwargs.pop("tldm_class", tldm)
|
|
41
|
+
yield from zip(tldm_class(iter1, **kwargs), *iter2plus)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def tmap(function: Callable[..., R], *sequences: Iterable[Any], **tldm_kwargs: Any) -> Iterator[R]:
|
|
45
|
+
"""
|
|
46
|
+
Equivalent of builtin `map`.
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
tldm_class : [default: tldm.std.tldm].
|
|
51
|
+
"""
|
|
52
|
+
for i in tzip(*sequences, **tldm_kwargs):
|
|
53
|
+
yield function(*i)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def tproduct(*iterables: Iterable[T], **tldm_kwargs: Any) -> Iterator[tuple[T, ...]]:
|
|
57
|
+
"""
|
|
58
|
+
Equivalent of `itertools.product`.
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
tldm_class : [default: tldm.std.tldm].
|
|
63
|
+
"""
|
|
64
|
+
kwargs = tldm_kwargs.copy()
|
|
65
|
+
repeat = kwargs.pop("repeat", 1)
|
|
66
|
+
tldm_class = kwargs.pop("tldm_class", tldm)
|
|
67
|
+
try:
|
|
68
|
+
lens = list(map(length_hint, iterables))
|
|
69
|
+
except TypeError:
|
|
70
|
+
total = None
|
|
71
|
+
else:
|
|
72
|
+
total = 1
|
|
73
|
+
for i in lens:
|
|
74
|
+
total *= i
|
|
75
|
+
total = total**repeat
|
|
76
|
+
kwargs.setdefault("total", total)
|
|
77
|
+
with tldm_class(**kwargs) as t:
|
|
78
|
+
it = itertools.product(*iterables, repeat=repeat)
|
|
79
|
+
for val in it:
|
|
80
|
+
yield val
|
|
81
|
+
t.update()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def trange(*args: int, **kwargs: Any) -> tldm:
|
|
85
|
+
"""Shortcut for tldm(range(*args), **kwargs)."""
|
|
86
|
+
return tldm(range(*args), **kwargs)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .pandas import tldm_pandas as tldm_pandas
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Asynchronous progressbar decorator for iterators.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
|
|
7
|
+
from ..std import tldm as std_tldm
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class tldm_asyncio(std_tldm):
|
|
11
|
+
"""
|
|
12
|
+
Asynchronous-friendly version of tldm.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, iterable=None, *args, **kwargs):
|
|
16
|
+
super().__init__(iterable, *args, **kwargs)
|
|
17
|
+
self.iterable_awaitable = False
|
|
18
|
+
if iterable is not None:
|
|
19
|
+
if hasattr(iterable, "__anext__"):
|
|
20
|
+
self.iterable_next = iterable.__anext__
|
|
21
|
+
self.iterable_awaitable = True
|
|
22
|
+
elif hasattr(iterable, "__next__"):
|
|
23
|
+
self.iterable_next = iterable.__next__
|
|
24
|
+
|
|
25
|
+
def __aiter__(self):
|
|
26
|
+
return self
|
|
27
|
+
|
|
28
|
+
# def __del__(self):
|
|
29
|
+
# self.close()
|
|
30
|
+
# if len(tldm_asyncio._instances) == 0:
|
|
31
|
+
# if hasattr(tldm_asyncio, "_lock"):
|
|
32
|
+
# del tldm_asyncio._lock
|
|
33
|
+
# if hasattr(tldm_asyncio, "monitor") and tldm_asyncio.monitor is not None:
|
|
34
|
+
# tldm_asyncio.monitor.exit()
|
|
35
|
+
|
|
36
|
+
async def __anext__(self):
|
|
37
|
+
try:
|
|
38
|
+
if self.iterable_awaitable:
|
|
39
|
+
res = await self.iterable_next()
|
|
40
|
+
else:
|
|
41
|
+
if not hasattr(self, "iterable_iterator"):
|
|
42
|
+
self.iterable_iterator = iter(self.iterable)
|
|
43
|
+
self.iterable_next = self.iterable_iterator.__next__
|
|
44
|
+
res = self.iterable_next()
|
|
45
|
+
self.update()
|
|
46
|
+
return res
|
|
47
|
+
except StopIteration:
|
|
48
|
+
self.close()
|
|
49
|
+
raise StopAsyncIteration from None
|
|
50
|
+
except BaseException:
|
|
51
|
+
self.close()
|
|
52
|
+
raise
|
|
53
|
+
|
|
54
|
+
def send(self, *args, **kwargs):
|
|
55
|
+
return self.iterable.send(*args, **kwargs)
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def as_completed(cls, fs, *, loop=None, timeout=None, total=None, **tldm_kwargs):
|
|
59
|
+
"""
|
|
60
|
+
Wrapper for `asyncio.as_completed`.
|
|
61
|
+
"""
|
|
62
|
+
if total is None:
|
|
63
|
+
total = len(fs)
|
|
64
|
+
yield from cls(
|
|
65
|
+
asyncio.as_completed(fs, timeout=timeout),
|
|
66
|
+
total=total,
|
|
67
|
+
**tldm_kwargs,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
async def gather(
|
|
72
|
+
cls,
|
|
73
|
+
*fs,
|
|
74
|
+
loop=None,
|
|
75
|
+
timeout=None,
|
|
76
|
+
total=None,
|
|
77
|
+
return_exceptions=False,
|
|
78
|
+
**tldm_kwargs,
|
|
79
|
+
):
|
|
80
|
+
"""
|
|
81
|
+
Wrapper for `asyncio.gather`.
|
|
82
|
+
"""
|
|
83
|
+
if total is None:
|
|
84
|
+
total = len(fs)
|
|
85
|
+
|
|
86
|
+
async def wrap_awaitable(i, f):
|
|
87
|
+
try:
|
|
88
|
+
return i, await f
|
|
89
|
+
except Exception as e:
|
|
90
|
+
if return_exceptions:
|
|
91
|
+
return i, e
|
|
92
|
+
raise
|
|
93
|
+
|
|
94
|
+
async def aiter_as_completed():
|
|
95
|
+
ifs = [wrap_awaitable(i, f) for i, f in enumerate(fs)]
|
|
96
|
+
for r in asyncio.as_completed(ifs, timeout=timeout):
|
|
97
|
+
yield await r
|
|
98
|
+
|
|
99
|
+
res = [f async for f in cls(aiter_as_completed(), total=total, **tldm_kwargs)]
|
|
100
|
+
return [i for _, i in sorted(res)]
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Thin wrappers around `concurrent.futures`.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from contextlib import contextmanager
|
|
6
|
+
from operator import length_hint
|
|
7
|
+
from os import cpu_count
|
|
8
|
+
|
|
9
|
+
from ..std import tldm as std_tldm
|
|
10
|
+
from ..utils import TldmWarning
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@contextmanager
|
|
14
|
+
def ensure_lock(tldm_class, lock_name=""):
|
|
15
|
+
"""get (create if necessary) and then restore `tldm_class`'s lock"""
|
|
16
|
+
old_lock = getattr(tldm_class, "_lock", None) # don't create a new lock
|
|
17
|
+
lock = old_lock or tldm_class.get_lock() # maybe create a new lock
|
|
18
|
+
lock = getattr(lock, lock_name, lock) # maybe subtype
|
|
19
|
+
tldm_class.set_lock(lock)
|
|
20
|
+
yield lock
|
|
21
|
+
if old_lock is None:
|
|
22
|
+
del tldm_class._lock
|
|
23
|
+
else:
|
|
24
|
+
tldm_class.set_lock(old_lock)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _executor_map(PoolExecutor, fn, *iterables, **tldm_kwargs):
|
|
28
|
+
"""
|
|
29
|
+
Implementation of `thread_map` and `process_map`.
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
tldm_class : [default: tldm.auto.tldm].
|
|
34
|
+
max_workers : [default: min(32, cpu_count() + 4)].
|
|
35
|
+
timeout : [default: None].
|
|
36
|
+
chunksize : [default: 1].
|
|
37
|
+
lock_name : [default: "":str].
|
|
38
|
+
"""
|
|
39
|
+
kwargs = tldm_kwargs.copy()
|
|
40
|
+
if "total" not in kwargs:
|
|
41
|
+
kwargs["total"] = length_hint(iterables[0])
|
|
42
|
+
tldm_class = kwargs.pop("tldm_class", std_tldm)
|
|
43
|
+
max_workers = kwargs.pop("max_workers", min(32, cpu_count() + 4))
|
|
44
|
+
timeout = kwargs.pop("timeout", None)
|
|
45
|
+
chunksize = kwargs.pop("chunksize", 1)
|
|
46
|
+
lock_name = kwargs.pop("lock_name", "")
|
|
47
|
+
with ensure_lock(tldm_class, lock_name=lock_name) as lk:
|
|
48
|
+
# share lock in case workers are already using `tldm`
|
|
49
|
+
with PoolExecutor(
|
|
50
|
+
max_workers=max_workers, initializer=tldm_class.set_lock, initargs=(lk,)
|
|
51
|
+
) as ex:
|
|
52
|
+
return list(
|
|
53
|
+
tldm_class(
|
|
54
|
+
ex.map(fn, *iterables, timeout=timeout, chunksize=chunksize),
|
|
55
|
+
**kwargs,
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def thread_map(fn, *iterables, **tldm_kwargs):
|
|
61
|
+
"""
|
|
62
|
+
Equivalent of `list(map(fn, *iterables))`
|
|
63
|
+
driven by `concurrent.futures.ThreadPoolExecutor`.
|
|
64
|
+
|
|
65
|
+
Parameters
|
|
66
|
+
----------
|
|
67
|
+
tldm_class : optional
|
|
68
|
+
`tldm` class to use for bars [default: tldm.auto.tldm].
|
|
69
|
+
max_workers : int, optional
|
|
70
|
+
Maximum number of workers to spawn; passed to
|
|
71
|
+
`concurrent.futures.ThreadPoolExecutor.__init__`.
|
|
72
|
+
[default: min(32, cpu_count() + 4)].
|
|
73
|
+
timeout : int or float, optional
|
|
74
|
+
The iterator raises a TimeoutError if __next()__ is called and the
|
|
75
|
+
result isn't available within the timeout specified from the
|
|
76
|
+
original call to thread_map. [default: None].
|
|
77
|
+
"""
|
|
78
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
79
|
+
|
|
80
|
+
return _executor_map(ThreadPoolExecutor, fn, *iterables, **tldm_kwargs)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def process_map(fn, *iterables, **tldm_kwargs):
|
|
84
|
+
"""
|
|
85
|
+
Equivalent of `list(map(fn, *iterables))`
|
|
86
|
+
driven by `concurrent.futures.ProcessPoolExecutor`.
|
|
87
|
+
|
|
88
|
+
Parameters
|
|
89
|
+
----------
|
|
90
|
+
tldm_class : optional
|
|
91
|
+
`tldm` class to use for bars [default: tldm.auto.tldm].
|
|
92
|
+
max_workers : int, optional
|
|
93
|
+
Maximum number of workers to spawn; passed to
|
|
94
|
+
`concurrent.futures.ProcessPoolExecutor.__init__`.
|
|
95
|
+
[default: min(32, cpu_count() + 4)].
|
|
96
|
+
timeout : int or float, optional
|
|
97
|
+
The iterator raises a TimeoutError if __next()__ is called and the
|
|
98
|
+
result isn't available within the timeout specified from the
|
|
99
|
+
original call to process_map. [default: None].
|
|
100
|
+
chunksize : int, optional
|
|
101
|
+
Size of chunks sent to worker processes; passed to
|
|
102
|
+
`concurrent.futures.ProcessPoolExecutor.map`. [default: 1].
|
|
103
|
+
lock_name : str, optional
|
|
104
|
+
Member of `tldm_class.get_lock()` to use [default: mp_lock].
|
|
105
|
+
"""
|
|
106
|
+
from concurrent.futures import ProcessPoolExecutor
|
|
107
|
+
|
|
108
|
+
if iterables and "chunksize" not in tldm_kwargs:
|
|
109
|
+
# default `chunksize=1` has poor performance for large iterables
|
|
110
|
+
# (most time spent dispatching items to workers).
|
|
111
|
+
longest_iterable_len = max(map(length_hint, iterables))
|
|
112
|
+
if longest_iterable_len > 1000:
|
|
113
|
+
from warnings import warn
|
|
114
|
+
|
|
115
|
+
warn(
|
|
116
|
+
"Iterable length %d > 1000 but `chunksize` is not set."
|
|
117
|
+
" This may seriously degrade multiprocess performance."
|
|
118
|
+
" Set `chunksize=1` or more." % longest_iterable_len,
|
|
119
|
+
TldmWarning,
|
|
120
|
+
stacklevel=2,
|
|
121
|
+
)
|
|
122
|
+
if "lock_name" not in tldm_kwargs:
|
|
123
|
+
tldm_kwargs = tldm_kwargs.copy()
|
|
124
|
+
tldm_kwargs["lock_name"] = "mp_lock"
|
|
125
|
+
return _executor_map(ProcessPoolExecutor, fn, *iterables, **tldm_kwargs)
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Registration for `tldm` to provide `pandas` progress indicators.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import contextlib
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ..std import tldm as std_tldm
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def tldm_pandas(**tldm_kwargs: dict[str, Any]) -> None:
|
|
12
|
+
"""
|
|
13
|
+
Registers the current `tldm` class with
|
|
14
|
+
pandas.core.
|
|
15
|
+
( frame.DataFrame
|
|
16
|
+
| series.Series
|
|
17
|
+
| groupby.(generic.)DataFrameGroupBy
|
|
18
|
+
| groupby.(generic.)SeriesGroupBy
|
|
19
|
+
).progress_apply
|
|
20
|
+
|
|
21
|
+
A new instance will be created every time `progress_apply` is called,
|
|
22
|
+
and each instance will automatically `close()` upon completion.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
tldm_kwargs : arguments for the tldm instance
|
|
27
|
+
|
|
28
|
+
Examples
|
|
29
|
+
--------
|
|
30
|
+
>>> import pandas as pd
|
|
31
|
+
>>> import numpy as np
|
|
32
|
+
>>> from tldm import tldm, tldm_pandas
|
|
33
|
+
>>> from tldm.gui import tldm as tldm_gui
|
|
34
|
+
>>>
|
|
35
|
+
>>> df = pd.DataFrame(np.random.randint(0, 100, (100000, 6)))
|
|
36
|
+
>>> tldm_pandas(ncols=50) # can use tldm_gui, optional kwargs, etc
|
|
37
|
+
>>> # Now you can use `progress_apply` instead of `apply`
|
|
38
|
+
>>> df.groupby(0).progress_apply(lambda x: x**2)
|
|
39
|
+
|
|
40
|
+
References
|
|
41
|
+
----------
|
|
42
|
+
<https://stackoverflow.com/questions/18603270/\
|
|
43
|
+
progress-indicator-during-pandas-operations-python>
|
|
44
|
+
"""
|
|
45
|
+
from warnings import catch_warnings, simplefilter
|
|
46
|
+
|
|
47
|
+
from pandas.core.frame import DataFrame
|
|
48
|
+
from pandas.core.series import Series
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
with catch_warnings():
|
|
52
|
+
simplefilter("ignore", category=FutureWarning)
|
|
53
|
+
from pandas import Panel
|
|
54
|
+
except ImportError: # pandas>=1.2.0
|
|
55
|
+
Panel = None
|
|
56
|
+
Rolling, Expanding = None, None
|
|
57
|
+
try: # pandas>=1.0.0
|
|
58
|
+
from pandas.core.window.rolling import _Rolling_and_Expanding
|
|
59
|
+
except ImportError:
|
|
60
|
+
try: # pandas>=0.18.0
|
|
61
|
+
from pandas.core.window import _Rolling_and_Expanding
|
|
62
|
+
except ImportError: # pandas>=1.2.0
|
|
63
|
+
try: # pandas>=1.2.0
|
|
64
|
+
from pandas.core.window.expanding import Expanding
|
|
65
|
+
from pandas.core.window.rolling import Rolling
|
|
66
|
+
|
|
67
|
+
_Rolling_and_Expanding = Rolling, Expanding
|
|
68
|
+
except ImportError: # pragma: no cover
|
|
69
|
+
_Rolling_and_Expanding = None
|
|
70
|
+
try: # pandas>=0.25.0
|
|
71
|
+
from pandas.core.groupby.generic import (
|
|
72
|
+
DataFrameGroupBy,
|
|
73
|
+
SeriesGroupBy, # , NDFrameGroupBy
|
|
74
|
+
)
|
|
75
|
+
except ImportError: # pragma: no cover
|
|
76
|
+
try: # pandas>=0.23.0
|
|
77
|
+
from pandas.core.groupby.groupby import DataFrameGroupBy, SeriesGroupBy
|
|
78
|
+
except ImportError:
|
|
79
|
+
from pandas.core.groupby import DataFrameGroupBy, SeriesGroupBy
|
|
80
|
+
try: # pandas>=0.23.0
|
|
81
|
+
from pandas.core.groupby.groupby import GroupBy
|
|
82
|
+
except ImportError: # pragma: no cover
|
|
83
|
+
from pandas.core.groupby import GroupBy
|
|
84
|
+
|
|
85
|
+
try: # pandas>=0.23.0
|
|
86
|
+
from pandas.core.groupby.groupby import PanelGroupBy
|
|
87
|
+
except ImportError:
|
|
88
|
+
try:
|
|
89
|
+
from pandas.core.groupby import PanelGroupBy
|
|
90
|
+
except ImportError: # pandas>=0.25.0
|
|
91
|
+
PanelGroupBy = None
|
|
92
|
+
|
|
93
|
+
tldm_kwargs = tldm_kwargs.copy()
|
|
94
|
+
|
|
95
|
+
def inner_generator(df_function="apply"):
|
|
96
|
+
def inner(df, func, **kwargs):
|
|
97
|
+
"""
|
|
98
|
+
Parameters
|
|
99
|
+
----------
|
|
100
|
+
df : (DataFrame|Series)[GroupBy]
|
|
101
|
+
Data (may be grouped).
|
|
102
|
+
func : function
|
|
103
|
+
To be applied on the (grouped) data.
|
|
104
|
+
**kwargs : optional
|
|
105
|
+
Transmitted to `df.apply()`.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
# Precompute total iterations
|
|
109
|
+
total = tldm_kwargs.pop("total", getattr(df, "ngroups", None))
|
|
110
|
+
if total is None: # not grouped
|
|
111
|
+
if df_function == "applymap":
|
|
112
|
+
total = df.size
|
|
113
|
+
elif isinstance(df, Series):
|
|
114
|
+
total = len(df)
|
|
115
|
+
elif _Rolling_and_Expanding is None or not isinstance(df, _Rolling_and_Expanding):
|
|
116
|
+
# DataFrame or Panel
|
|
117
|
+
axis = kwargs.get("axis", 0)
|
|
118
|
+
if axis == "index":
|
|
119
|
+
axis = 0
|
|
120
|
+
elif axis == "columns":
|
|
121
|
+
axis = 1
|
|
122
|
+
# when axis=0, total is shape[axis1]
|
|
123
|
+
total = df.size // df.shape[axis]
|
|
124
|
+
|
|
125
|
+
# Init bar
|
|
126
|
+
t = std_tldm(total=total, **tldm_kwargs)
|
|
127
|
+
|
|
128
|
+
try: # pandas>=1.3.0
|
|
129
|
+
from pandas.core.common import is_builtin_func
|
|
130
|
+
except ImportError:
|
|
131
|
+
is_builtin_func = df._is_builtin_func
|
|
132
|
+
with contextlib.suppress(TypeError):
|
|
133
|
+
func = is_builtin_func(func)
|
|
134
|
+
|
|
135
|
+
# Define bar updating wrapper
|
|
136
|
+
def wrapper(*args, **kwargs):
|
|
137
|
+
# update tbar correctly
|
|
138
|
+
# it seems `pandas apply` calls `func` twice
|
|
139
|
+
# on the first column/row to decide whether it can
|
|
140
|
+
# take a fast or slow code path; so stop when t.total==t.n
|
|
141
|
+
t.update(n=1 if not t.total or t.n < t.total else 0)
|
|
142
|
+
return func(*args, **kwargs)
|
|
143
|
+
|
|
144
|
+
# Apply the provided function (in **kwargs)
|
|
145
|
+
# on the df using our wrapper (which provides bar updating)
|
|
146
|
+
try:
|
|
147
|
+
return getattr(df, df_function)(wrapper, **kwargs)
|
|
148
|
+
finally:
|
|
149
|
+
t.close()
|
|
150
|
+
|
|
151
|
+
return inner
|
|
152
|
+
|
|
153
|
+
# Monkeypatch pandas to provide easy methods
|
|
154
|
+
# Enable custom tldm progress in pandas!
|
|
155
|
+
Series.progress_apply = inner_generator()
|
|
156
|
+
SeriesGroupBy.progress_apply = inner_generator()
|
|
157
|
+
Series.progress_map = inner_generator("map")
|
|
158
|
+
SeriesGroupBy.progress_map = inner_generator("map")
|
|
159
|
+
|
|
160
|
+
DataFrame.progress_apply = inner_generator()
|
|
161
|
+
DataFrameGroupBy.progress_apply = inner_generator()
|
|
162
|
+
DataFrame.progress_applymap = inner_generator("applymap")
|
|
163
|
+
DataFrame.progress_map = inner_generator("map")
|
|
164
|
+
DataFrameGroupBy.progress_map = inner_generator("map")
|
|
165
|
+
|
|
166
|
+
if Panel is not None:
|
|
167
|
+
Panel.progress_apply = inner_generator()
|
|
168
|
+
if PanelGroupBy is not None:
|
|
169
|
+
PanelGroupBy.progress_apply = inner_generator()
|
|
170
|
+
|
|
171
|
+
GroupBy.progress_apply = inner_generator()
|
|
172
|
+
GroupBy.progress_aggregate = inner_generator("aggregate")
|
|
173
|
+
GroupBy.progress_transform = inner_generator("transform")
|
|
174
|
+
|
|
175
|
+
if Rolling is not None and Expanding is not None:
|
|
176
|
+
Rolling.progress_apply = inner_generator()
|
|
177
|
+
Expanding.progress_apply = inner_generator()
|
|
178
|
+
elif _Rolling_and_Expanding is not None:
|
|
179
|
+
_Rolling_and_Expanding.progress_apply = inner_generator()
|