"""
Logging operations.
progress_bar
set_verbosity
timer_decorator
"""
from __future__ import annotations
import logging
import os
import time
from functools import wraps
from typing import Callable, Iterable
from IPython import get_ipython
from IPython.display import display
from ipywidgets import HTML, IntProgress, VBox
from tqdm import tqdm as _tqdm
from pymove.utils.datetime import deltatime_str
LOG_LEVEL = os.getenv('PYMOVE_VERBOSE', 'INFO')
logger = logging.getLogger('pymove')
shell_handler = logging.StreamHandler()
logger.setLevel(LOG_LEVEL)
shell_handler.setLevel(LOG_LEVEL)
logger.addHandler(shell_handler)
[docs]def set_verbosity(level):
"""Change logging level."""
logger.setLevel(level)
shell_handler.setLevel(level)
[docs]def timer_decorator(func: Callable) -> Callable:
"""A decorator that prints how long a function took to run."""
@wraps(func)
def wrapper(*args, **kwargs):
t_start = time.time()
result = func(*args, **kwargs)
t_total = deltatime_str(time.time() - t_start)
message = f'{func.__name__} took {t_total}'
logger.debug('{}\n{}\n{}'.format('*' * len(message), message, '*' * len(message)))
return result
return wrapper
def _log_progress(
sequence: Iterable,
desc: str | None = None,
total: int | None = None,
miniters: int | None = None
):
"""
Make and display a progress bar.
Parameters
----------
sequence : iterable
Represents a sequence of elements.
desc : str, optional
Represents the description of the operation, by default None.
total : int, optional
Represents the total/number elements in sequence, by default None.
miniters : int, optional
Represents the steps in which the bar will be updated, by default None.
"""
if desc is None:
desc = ''
is_iterator = False
if total is None:
try:
total = len(sequence) # type: ignore
except TypeError:
is_iterator = True
if total is not None:
if miniters is None:
if total <= 200:
miniters = 1
else:
miniters = int(total / 200)
else:
if miniters is None:
miniters = 1
if is_iterator:
progress = IntProgress(min=0, max=1, value=1)
progress.bar_style = 'info'
else:
progress = IntProgress(min=0, max=total, value=0)
label = HTML()
box = VBox(children=[label, progress])
display(box)
index = 0
try:
for index, record in enumerate(sequence, 1):
if index == 1 or index % miniters == 0:
if is_iterator:
label.value = f'{desc}: {index} / ?'
else:
progress.value = index
label.value = f'{desc}: {index} / {total}'
yield record
except Exception:
progress.bar_style = 'danger'
raise
else:
progress.bar_style = 'success'
progress.value = index
label.value = '{}: {}'.format(desc, str(index or '?'))
try:
if get_ipython().__class__.__name__ == 'ZMQInteractiveShell':
_log_progress_bar = _log_progress
else:
raise NameError
except NameError:
_log_progress_bar = _tqdm
[docs]def progress_bar(
sequence: Iterable,
desc: str | None = None,
total: int | None = None,
miniters: int | None = None
):
"""
Make and display a progress bar.
Parameters
----------
sequence : iterable
Represents a sequence of elements.
desc : str, optional
Represents the description of the operation, by default None.
total : int, optional
Represents the total/number elements in sequence, by default None.
miniters : int, optional
Represents the steps in which the bar will be updated, by default None.
Return
------
>>> from pymove.utils.log import progress_bar
>>> for i in progress_bar(range(1,101), desc='Print 1 to 100'):
>>> print(i)
# A bar that shows the progress of the iterations
"""
if logger.level > logging.INFO:
return sequence
return _log_progress_bar(sequence, desc, total, miniters)