mirror of https://github.com/tqdm/tqdm.git
contrib.logging: cleanup & docs
This commit is contained in:
parent
e86b9b18a0
commit
7458666698
|
@ -1102,6 +1102,33 @@ A reusable canonical example is given below:
|
|||
# After the `with`, printing is restored
|
||||
print("Done!")
|
||||
|
||||
Redirecting ``logging``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Similar to ``sys.stdout``/``sys.stderr`` as detailed above, console ``logging``
|
||||
may also be redirected to ``tqdm.write()``.
|
||||
|
||||
Warning: if also redirecting ``sys.stdout``/``sys.stderr``, make sure to
|
||||
redirect ``logging`` first if needed.
|
||||
|
||||
Helper methods are available in ``tqdm.contrib.logging``. For example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
import logging
|
||||
from tqdm import trange
|
||||
from tqdm.contrib.logging import logging_redirect_tqdm
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
with logging_redirect_tqdm():
|
||||
for i in trange(9):
|
||||
if i == 4:
|
||||
LOG.info("console logging redirected to `tqdm.write()`")
|
||||
# logging restored
|
||||
|
||||
Monitoring thread, intervals and miniters
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
58
README.rst
58
README.rst
|
@ -1321,58 +1321,32 @@ A reusable canonical example is given below:
|
|||
# After the `with`, printing is restored
|
||||
print("Done!")
|
||||
|
||||
Redirecting console logging to tqdm
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Redirecting ``logging``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Similar to redirecting ``sys.stdout`` directly as detailed in the previous section,
|
||||
you may want to redirect logging that would otherwise go to the
|
||||
console (``sys.stdout`` or ``sys.stderr``) to ``tqdm``.
|
||||
Similar to ``sys.stdout``/``sys.stderr`` as detailed above, console ``logging``
|
||||
may also be redirected to ``tqdm.write()``.
|
||||
|
||||
Note: if you are also replace ``sys.stdout`` and ``sys.stderr`` at the same time,
|
||||
then the logging should be redirected first. Otherwise it won't be able to detect
|
||||
the console logging handler.
|
||||
Warning: if also redirecting ``sys.stdout``/``sys.stderr``, make sure to
|
||||
redirect ``logging`` first if needed.
|
||||
|
||||
For that you may use ``redirect_logging_to_tqdm`` or ``tqdm_with_logging_redirect``
|
||||
from ``tqdm.contrib.logging``. Both methods accept the following optional parameters:
|
||||
|
||||
- ``loggers``: A list of loggers to update. Defaults to ``logging.root``.
|
||||
- ``tqdm``: A ``tqdm`` class. Defaults to ``tqdm.tqdm``.
|
||||
|
||||
An example redirecting the console logging to tqdm:
|
||||
Helper methods are available in ``tqdm.contrib.logging``. For example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
import logging
|
||||
from tqdm.contrib.logging import redirect_logging_to_tqdm
|
||||
from tqdm import trange
|
||||
from tqdm.contrib.logging import logging_redirect_tqdm
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig(level='INFO')
|
||||
with redirect_logging_to_tqdm():
|
||||
# logging to the console is now redirected to tqdm
|
||||
LOGGER.info('some message')
|
||||
# logging is now restored
|
||||
|
||||
An similar example, wrapping tqdm while redirecting console logging:
|
||||
|
||||
.. code:: python
|
||||
|
||||
import logging
|
||||
from tqdm.contrib.logging import tqdm_with_logging_redirect
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig(level='INFO')
|
||||
|
||||
file_list = ['file1', 'file2']
|
||||
with tqdm_with_logging_redirect(total=len(file_list)) as pbar:
|
||||
# logging to the console is now redirected to tqdm
|
||||
for filename in file_list:
|
||||
LOGGER.info('processing file: %s', filename)
|
||||
pbar.update(1)
|
||||
# logging is now restored
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
with logging_redirect_tqdm():
|
||||
for i in trange(9):
|
||||
if i == 4:
|
||||
LOG.info("console logging redirected to `tqdm.write()`")
|
||||
# logging restored
|
||||
|
||||
Monitoring thread, intervals and miniters
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# pylint: disable=missing-module-docstring, missing-class-docstring
|
||||
# pylint: disable=missing-function-docstring, no-self-use
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
|
@ -13,9 +12,9 @@ import pytest
|
|||
from tqdm import tqdm
|
||||
from tqdm.contrib.logging import _get_first_found_console_logging_formatter
|
||||
from tqdm.contrib.logging import _TqdmLoggingHandler as TqdmLoggingHandler
|
||||
from tqdm.contrib.logging import redirect_logging_to_tqdm, tqdm_with_logging_redirect
|
||||
from tqdm.contrib.logging import logging_redirect_tqdm, tqdm_logging_redirect
|
||||
|
||||
from ..tests_tqdm import importorskip
|
||||
from .tests_tqdm import importorskip
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -101,7 +100,7 @@ class TestGetFirstFoundConsoleLoggingFormatter:
|
|||
class TestRedirectLoggingToTqdm:
|
||||
def test_should_add_and_remove_tqdm_handler(self):
|
||||
logger = logging.Logger('test')
|
||||
with redirect_logging_to_tqdm(loggers=[logger]):
|
||||
with logging_redirect_tqdm(loggers=[logger]):
|
||||
assert len(logger.handlers) == 1
|
||||
assert isinstance(logger.handlers[0], TqdmLoggingHandler)
|
||||
assert not logger.handlers
|
||||
|
@ -111,7 +110,7 @@ class TestRedirectLoggingToTqdm:
|
|||
stderr_console_handler = logging.StreamHandler(sys.stderr)
|
||||
stdout_console_handler = logging.StreamHandler(sys.stderr)
|
||||
logger.handlers = [stderr_console_handler, stdout_console_handler]
|
||||
with redirect_logging_to_tqdm(loggers=[logger]):
|
||||
with logging_redirect_tqdm(loggers=[logger]):
|
||||
assert len(logger.handlers) == 1
|
||||
assert isinstance(logger.handlers[0], TqdmLoggingHandler)
|
||||
assert logger.handlers == [stderr_console_handler, stdout_console_handler]
|
||||
|
@ -122,14 +121,14 @@ class TestRedirectLoggingToTqdm:
|
|||
console_handler = logging.StreamHandler(sys.stderr)
|
||||
console_handler.setFormatter(formatter)
|
||||
logger.handlers = [console_handler]
|
||||
with redirect_logging_to_tqdm(loggers=[logger]):
|
||||
with logging_redirect_tqdm(loggers=[logger]):
|
||||
assert logger.handlers[0].formatter == formatter
|
||||
|
||||
def test_should_not_remove_stream_handlers_not_fot_stdout_or_stderr(self):
|
||||
logger = logging.Logger('test')
|
||||
stream_handler = logging.StreamHandler(StringIO())
|
||||
logger.addHandler(stream_handler)
|
||||
with redirect_logging_to_tqdm(loggers=[logger]):
|
||||
with logging_redirect_tqdm(loggers=[logger]):
|
||||
assert len(logger.handlers) == 2
|
||||
assert logger.handlers[0] == stream_handler
|
||||
assert isinstance(logger.handlers[1], TqdmLoggingHandler)
|
||||
|
@ -139,7 +138,7 @@ class TestRedirectLoggingToTqdm:
|
|||
class TestTqdmWithLoggingRedirect:
|
||||
def test_should_add_and_remove_handler_from_root_logger_by_default(self):
|
||||
original_handlers = list(logging.root.handlers)
|
||||
with tqdm_with_logging_redirect(total=1) as pbar:
|
||||
with tqdm_logging_redirect(total=1) as pbar:
|
||||
assert isinstance(logging.root.handlers[-1], TqdmLoggingHandler)
|
||||
LOGGER.info('test')
|
||||
pbar.update(1)
|
||||
|
@ -147,7 +146,7 @@ class TestTqdmWithLoggingRedirect:
|
|||
|
||||
def test_should_add_and_remove_handler_from_custom_logger(self):
|
||||
logger = logging.Logger('test')
|
||||
with tqdm_with_logging_redirect(total=1, loggers=[logger]) as pbar:
|
||||
with tqdm_logging_redirect(total=1, loggers=[logger]) as pbar:
|
||||
assert len(logger.handlers) == 1
|
||||
assert isinstance(logger.handlers[0], TqdmLoggingHandler)
|
||||
logger.info('test')
|
||||
|
@ -157,7 +156,7 @@ class TestTqdmWithLoggingRedirect:
|
|||
def test_should_not_fail_with_logger_without_console_handler(self):
|
||||
logger = logging.Logger('test')
|
||||
logger.handlers = []
|
||||
with tqdm_with_logging_redirect(total=1, loggers=[logger]):
|
||||
with tqdm_logging_redirect(total=1, loggers=[logger]):
|
||||
logger.info('test')
|
||||
assert not logger.handlers
|
||||
|
||||
|
@ -169,14 +168,14 @@ class TestTqdmWithLoggingRedirect:
|
|||
))
|
||||
logger.handlers = [console_handler]
|
||||
CustomTqdm.messages = []
|
||||
with tqdm_with_logging_redirect(loggers=[logger], tqdm=CustomTqdm):
|
||||
with tqdm_logging_redirect(loggers=[logger], tqdm_class=CustomTqdm):
|
||||
logger.info('test')
|
||||
assert CustomTqdm.messages == ['prefix:test']
|
||||
|
||||
def test_use_root_logger_by_default_and_write_to_custom_tqdm(self):
|
||||
logger = logging.root
|
||||
CustomTqdm.messages = []
|
||||
with tqdm_with_logging_redirect(total=1, tqdm=CustomTqdm) as pbar:
|
||||
with tqdm_logging_redirect(total=1, tqdm_class=CustomTqdm) as pbar:
|
||||
assert isinstance(pbar, CustomTqdm)
|
||||
logger.info('test')
|
||||
assert CustomTqdm.messages == ['test']
|
|
@ -1,7 +1,5 @@
|
|||
|
||||
"""
|
||||
Enables multiple commonly used features relating to logging
|
||||
in combination with tqdm.
|
||||
Helper functionality for interoperability with stdlib `logging`.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
@ -12,28 +10,23 @@ from contextlib import contextmanager
|
|||
try:
|
||||
from typing import Iterator, List, Optional, Type # pylint: disable=unused-import
|
||||
except ImportError:
|
||||
# we may ignore type hints
|
||||
pass
|
||||
|
||||
from ..std import tqdm as _tqdm
|
||||
from ..std import tqdm as std_tqdm
|
||||
|
||||
|
||||
class _TqdmLoggingHandler(logging.StreamHandler):
|
||||
def __init__(
|
||||
self,
|
||||
tqdm=None # type: Optional[Type[tqdm.tqdm]]
|
||||
tqdm_class=std_tqdm # type: Type[std_tqdm]
|
||||
):
|
||||
super( # pylint: disable=super-with-arguments
|
||||
_TqdmLoggingHandler, self
|
||||
).__init__()
|
||||
if tqdm is None:
|
||||
tqdm = _tqdm
|
||||
self.tqdm = tqdm
|
||||
super(_TqdmLoggingHandler, self).__init__()
|
||||
self.tqdm_class = tqdm_class
|
||||
|
||||
def emit(self, record):
|
||||
try:
|
||||
msg = self.format(record)
|
||||
self.tqdm.write(msg)
|
||||
self.tqdm_class.write(msg)
|
||||
self.flush()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
|
@ -42,88 +35,69 @@ class _TqdmLoggingHandler(logging.StreamHandler):
|
|||
|
||||
|
||||
def _is_console_logging_handler(handler):
|
||||
return (
|
||||
isinstance(handler, logging.StreamHandler)
|
||||
and handler.stream in {sys.stdout, sys.stderr}
|
||||
)
|
||||
return (isinstance(handler, logging.StreamHandler)
|
||||
and handler.stream in {sys.stdout, sys.stderr})
|
||||
|
||||
|
||||
def _get_first_found_console_logging_formatter(handlers):
|
||||
for handler in handlers:
|
||||
if _is_console_logging_handler(handler):
|
||||
return handler.formatter
|
||||
return None
|
||||
|
||||
|
||||
@contextmanager
|
||||
def redirect_logging_to_tqdm(
|
||||
def logging_redirect_tqdm(
|
||||
loggers=None, # type: Optional[List[logging.Logger]],
|
||||
tqdm=None # type: Optional[Type[tqdm.tqdm]]
|
||||
tqdm_class=std_tqdm # type: Type[std_tqdm]
|
||||
):
|
||||
# type: (...) -> Iterator[None]
|
||||
"""
|
||||
Context manager for redirecting logging console output to tqdm.
|
||||
Logging to other logging handlers, such as a log file,
|
||||
will not be affected.
|
||||
Context manager redirecting console logging to `tqdm.write()`, leaving
|
||||
other logging handlers (e.g. log files) unaffected.
|
||||
|
||||
By default the, the handlers of the root logger will be amended.
|
||||
(for the duration of the context)
|
||||
You may also provide a list of `loggers` instead
|
||||
(e.g. if a particular logger doesn't fallback to the root logger)
|
||||
|
||||
Example:
|
||||
Parameters
|
||||
----------
|
||||
loggers : list, optional
|
||||
Which handlers to redirect (default: [logging.root]).
|
||||
tqdm_class : optional
|
||||
|
||||
Example
|
||||
-------
|
||||
```python
|
||||
import logging
|
||||
from tqdm.contrib.logging import redirect_logging_to_tqdm
|
||||
from tqdm import trange
|
||||
from tqdm.contrib.logging import logging_redirect_tqdm
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig(level='INFO')
|
||||
with redirect_logging_to_tqdm():
|
||||
# logging to the console is now redirected to tqdm
|
||||
LOGGER.info('some message')
|
||||
# logging is now restored
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
with logging_redirect_tqdm():
|
||||
for i in trange(9):
|
||||
if i == 4:
|
||||
LOG.info("console logging redirected to `tqdm.write()`")
|
||||
# logging restored
|
||||
```
|
||||
"""
|
||||
if loggers is None:
|
||||
loggers = [logging.root]
|
||||
original_handlers_list = [
|
||||
logger.handlers for logger in loggers
|
||||
]
|
||||
original_handlers_list = [logger.handlers for logger in loggers]
|
||||
try:
|
||||
for logger in loggers:
|
||||
tqdm_handler = _TqdmLoggingHandler(tqdm)
|
||||
tqdm_handler = _TqdmLoggingHandler(tqdm_class)
|
||||
tqdm_handler.setFormatter(
|
||||
_get_first_found_console_logging_formatter(
|
||||
logger.handlers
|
||||
)
|
||||
)
|
||||
_get_first_found_console_logging_formatter(logger.handlers))
|
||||
logger.handlers = [
|
||||
handler
|
||||
for handler in logger.handlers
|
||||
if not _is_console_logging_handler(handler)
|
||||
] + [tqdm_handler]
|
||||
handler for handler in logger.handlers
|
||||
if not _is_console_logging_handler(handler)] + [tqdm_handler]
|
||||
yield
|
||||
finally:
|
||||
for logger, original_handlers in zip(loggers, original_handlers_list):
|
||||
logger.handlers = original_handlers
|
||||
|
||||
|
||||
def _pop_optional(
|
||||
kwargs, # type: dict
|
||||
key, # type: str
|
||||
default_value=None
|
||||
):
|
||||
try:
|
||||
return kwargs.pop(key)
|
||||
except KeyError:
|
||||
return default_value
|
||||
|
||||
|
||||
@contextmanager
|
||||
def tqdm_with_logging_redirect(
|
||||
def tqdm_logging_redirect(
|
||||
*args,
|
||||
# loggers=None, # type: Optional[List[logging.Logger]]
|
||||
# tqdm=None, # type: Optional[Type[tqdm.tqdm]]
|
||||
|
@ -131,64 +105,22 @@ def tqdm_with_logging_redirect(
|
|||
):
|
||||
# type: (...) -> Iterator[None]
|
||||
"""
|
||||
Similar to `redirect_logging_to_tqdm`,
|
||||
but provides a context manager wrapping tqdm.
|
||||
|
||||
All parameters, except `loggers` and `tqdm`, will get passed on to `tqdm`.
|
||||
|
||||
By default this will wrap `tqdm.tqdm`.
|
||||
You may pass your own `tqdm` class if desired.
|
||||
|
||||
Example:
|
||||
|
||||
Convenience shortcut for:
|
||||
```python
|
||||
import logging
|
||||
from tqdm.contrib.logging import tqdm_with_logging_redirect
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig(level='INFO')
|
||||
|
||||
file_list = ['file1', 'file2']
|
||||
with tqdm_with_logging_redirect(total=len(file_list)) as pbar:
|
||||
# logging to the console is now redirected to tqdm
|
||||
for filename in file_list:
|
||||
LOGGER.info('processing file: %s', filename)
|
||||
pbar.update(1)
|
||||
# logging is now restored
|
||||
```
|
||||
|
||||
A more advanced example with non-default tqdm class and loggers:
|
||||
|
||||
```python
|
||||
import logging
|
||||
from tqdm.auto import tqdm
|
||||
from tqdm.contrib.logging import tqdm_with_logging_redirect
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig(level='INFO')
|
||||
|
||||
file_list = ['file1', 'file2']
|
||||
with tqdm_with_logging_redirect(
|
||||
total=len(file_list),
|
||||
tqdm=tqdm,
|
||||
loggers=[LOGGER]
|
||||
) as pbar:
|
||||
# logging to the console is now redirected to tqdm
|
||||
for filename in file_list:
|
||||
LOGGER.info('processing file: %s', filename)
|
||||
pbar.update(1)
|
||||
# logging is now restored
|
||||
```
|
||||
|
||||
"""
|
||||
loggers = _pop_optional(kwargs, 'loggers')
|
||||
tqdm = _pop_optional(kwargs, 'tqdm')
|
||||
if tqdm is None:
|
||||
tqdm = _tqdm
|
||||
with tqdm(*args, **kwargs) as pbar:
|
||||
with redirect_logging_to_tqdm(loggers=loggers, tqdm=tqdm):
|
||||
with tqdm_class(*args, **tqdm_kwargs) as pbar:
|
||||
with logging_redirect_tqdm(loggers=loggers, tqdm_class=tqdm_class):
|
||||
yield pbar
|
||||
```
|
||||
|
||||
Parameters
|
||||
----------
|
||||
tqdm_class : optional, (default: tqdm.std.tqdm).
|
||||
loggers : optional, list.
|
||||
**tqdm_kwargs : passed to `tqdm_class`.
|
||||
"""
|
||||
tqdm_kwargs = kwargs.copy()
|
||||
loggers = tqdm_kwargs.pop('loggers', None)
|
||||
tqdm_class = tqdm_kwargs.pop('tqdm_class', std_tqdm)
|
||||
with tqdm_class(*args, **tqdm_kwargs) as pbar:
|
||||
with logging_redirect_tqdm(loggers=loggers, tqdm_class=tqdm_class):
|
||||
yield pbar
|
||||
|
|
Loading…
Reference in New Issue