docs for muutils v0.8.12
View Source on GitHub

muutils.collect_warnings


  1from __future__ import annotations
  2
  3import sys
  4import warnings
  5from collections import Counter
  6from contextlib import AbstractContextManager
  7from types import TracebackType
  8from typing import Any, Literal
  9
 10
 11class CollateWarnings(AbstractContextManager["CollateWarnings"]):
 12    """Capture every warning issued inside a `with` block and print a collated
 13    summary when the block exits.
 14
 15    Internally this wraps `warnings.catch_warnings(record=True)` so that all
 16    warnings raised in the block are recorded.  When the context exits, identical
 17    warnings are grouped and (optionally) printed with a user-defined format.
 18
 19    # Parameters:
 20     - `print_on_exit : bool`
 21       Whether to print the summary when the context exits
 22       (defaults to `True`)
 23     - `fmt : str`
 24       Format string used for printing each line of the summary.
 25       Available fields are:
 26
 27       * `{count}`     : number of occurrences
 28       * `{filename}`  : file where the warning originated
 29       * `{lineno}`    : line number
 30       * `{category}`  : warning class name
 31       * `{message}`   : warning message text
 32
 33       (defaults to `"({count}x) {filename}:{lineno} {category}: {message}"`)
 34
 35    # Returns:
 36     - `CollateWarnings`
 37       The context-manager instance.  After exit, the attribute
 38       `counts` holds a mapping
 39
 40       ```python
 41       {(filename, lineno, category, message): count}
 42       ```
 43
 44    # Usage:
 45    ```python
 46    >>> import warnings
 47    >>> with CollateWarnings() as cw:
 48    ...     warnings.warn("deprecated", DeprecationWarning)
 49    ...     warnings.warn("deprecated", DeprecationWarning)
 50    ...     warnings.warn("other", UserWarning)
 51    (2x) /tmp/example.py:42 DeprecationWarning: deprecated
 52    (1x) /tmp/example.py:43 UserWarning: other
 53    >>> cw.counts
 54    {('/tmp/example.py', 42, 'DeprecationWarning', 'deprecated'): 2,
 55     ('/tmp/example.py', 43, 'UserWarning', 'other'): 1}
 56    ```
 57    """
 58
 59    _active: bool
 60    _catcher: Any
 61    _records: list[warnings.WarningMessage]
 62    counts: Counter[
 63        tuple[
 64            str,  # filename
 65            int,  # lineno
 66            str,  # category name
 67            str,  # message
 68        ]
 69    ]
 70    print_on_exit: bool
 71    fmt: str
 72
 73    def __init__(
 74        self,
 75        print_on_exit: bool = True,
 76        fmt: str = "({count}x) {filename}:{lineno} {category}: {message}",
 77    ) -> None:
 78        self.print_on_exit = print_on_exit
 79        self.fmt = fmt
 80        self._active = False
 81        self._records = []
 82        self.counts = Counter()
 83
 84    def __enter__(self) -> CollateWarnings:
 85        if self._active:
 86            raise RuntimeError("CollateWarnings cannot be re-entered")
 87
 88        self._active = True
 89        self._catcher = warnings.catch_warnings(record=True)
 90        self._records = self._catcher.__enter__()
 91        warnings.simplefilter("always")  # capture every warning
 92        return self
 93
 94    def __exit__(
 95        self,
 96        exc_type: type[BaseException] | None,
 97        exc_val: BaseException | None,
 98        exc_tb: TracebackType | None,
 99    ) -> Literal[False]:
100        if not self._active:
101            raise RuntimeError("CollateWarnings exited twice")
102
103        self._active = False
104        # stop capturing
105        self._catcher.__exit__(exc_type, exc_val, exc_tb)
106
107        # collate
108        self.counts = Counter(
109            (
110                rec.filename,
111                rec.lineno,
112                rec.category.__name__,
113                str(rec.message),
114            )
115            for rec in self._records
116        )
117
118        if self.print_on_exit:
119            for (filename, lineno, category, message), count in self.counts.items():
120                print(
121                    self.fmt.format(
122                        count=count,
123                        filename=filename,
124                        lineno=lineno,
125                        category=category,
126                        message=message,
127                    ),
128                    file=sys.stderr,
129                )
130
131        # propagate any exception from the with-block
132        return False

class CollateWarnings(contextlib.AbstractContextManager['CollateWarnings']):
 12class CollateWarnings(AbstractContextManager["CollateWarnings"]):
 13    """Capture every warning issued inside a `with` block and print a collated
 14    summary when the block exits.
 15
 16    Internally this wraps `warnings.catch_warnings(record=True)` so that all
 17    warnings raised in the block are recorded.  When the context exits, identical
 18    warnings are grouped and (optionally) printed with a user-defined format.
 19
 20    # Parameters:
 21     - `print_on_exit : bool`
 22       Whether to print the summary when the context exits
 23       (defaults to `True`)
 24     - `fmt : str`
 25       Format string used for printing each line of the summary.
 26       Available fields are:
 27
 28       * `{count}`     : number of occurrences
 29       * `{filename}`  : file where the warning originated
 30       * `{lineno}`    : line number
 31       * `{category}`  : warning class name
 32       * `{message}`   : warning message text
 33
 34       (defaults to `"({count}x) {filename}:{lineno} {category}: {message}"`)
 35
 36    # Returns:
 37     - `CollateWarnings`
 38       The context-manager instance.  After exit, the attribute
 39       `counts` holds a mapping
 40
 41       ```python
 42       {(filename, lineno, category, message): count}
 43       ```
 44
 45    # Usage:
 46    ```python
 47    >>> import warnings
 48    >>> with CollateWarnings() as cw:
 49    ...     warnings.warn("deprecated", DeprecationWarning)
 50    ...     warnings.warn("deprecated", DeprecationWarning)
 51    ...     warnings.warn("other", UserWarning)
 52    (2x) /tmp/example.py:42 DeprecationWarning: deprecated
 53    (1x) /tmp/example.py:43 UserWarning: other
 54    >>> cw.counts
 55    {('/tmp/example.py', 42, 'DeprecationWarning', 'deprecated'): 2,
 56     ('/tmp/example.py', 43, 'UserWarning', 'other'): 1}
 57    ```
 58    """
 59
 60    _active: bool
 61    _catcher: Any
 62    _records: list[warnings.WarningMessage]
 63    counts: Counter[
 64        tuple[
 65            str,  # filename
 66            int,  # lineno
 67            str,  # category name
 68            str,  # message
 69        ]
 70    ]
 71    print_on_exit: bool
 72    fmt: str
 73
 74    def __init__(
 75        self,
 76        print_on_exit: bool = True,
 77        fmt: str = "({count}x) {filename}:{lineno} {category}: {message}",
 78    ) -> None:
 79        self.print_on_exit = print_on_exit
 80        self.fmt = fmt
 81        self._active = False
 82        self._records = []
 83        self.counts = Counter()
 84
 85    def __enter__(self) -> CollateWarnings:
 86        if self._active:
 87            raise RuntimeError("CollateWarnings cannot be re-entered")
 88
 89        self._active = True
 90        self._catcher = warnings.catch_warnings(record=True)
 91        self._records = self._catcher.__enter__()
 92        warnings.simplefilter("always")  # capture every warning
 93        return self
 94
 95    def __exit__(
 96        self,
 97        exc_type: type[BaseException] | None,
 98        exc_val: BaseException | None,
 99        exc_tb: TracebackType | None,
100    ) -> Literal[False]:
101        if not self._active:
102            raise RuntimeError("CollateWarnings exited twice")
103
104        self._active = False
105        # stop capturing
106        self._catcher.__exit__(exc_type, exc_val, exc_tb)
107
108        # collate
109        self.counts = Counter(
110            (
111                rec.filename,
112                rec.lineno,
113                rec.category.__name__,
114                str(rec.message),
115            )
116            for rec in self._records
117        )
118
119        if self.print_on_exit:
120            for (filename, lineno, category, message), count in self.counts.items():
121                print(
122                    self.fmt.format(
123                        count=count,
124                        filename=filename,
125                        lineno=lineno,
126                        category=category,
127                        message=message,
128                    ),
129                    file=sys.stderr,
130                )
131
132        # propagate any exception from the with-block
133        return False

Capture every warning issued inside a with block and print a collated summary when the block exits.

Internally this wraps warnings.catch_warnings(record=True) so that all warnings raised in the block are recorded. When the context exits, identical warnings are grouped and (optionally) printed with a user-defined format.

Parameters:

  • print_on_exit : bool Whether to print the summary when the context exits (defaults to True)
  • fmt : str Format string used for printing each line of the summary. Available fields are:

    • {count} : number of occurrences
    • {filename} : file where the warning originated
    • {lineno} : line number
    • {category} : warning class name
    • {message} : warning message text

    (defaults to "({count}x) {filename}:{lineno} {category}: {message}")

Returns:

  • CollateWarnings The context-manager instance. After exit, the attribute counts holds a mapping

    {(filename, lineno, category, message): count}
    

Usage:

>>> import warnings
>>> with CollateWarnings() as cw:
...     warnings.warn("deprecated", DeprecationWarning)
...     warnings.warn("deprecated", DeprecationWarning)
...     warnings.warn("other", UserWarning)
(2x) /tmp/example.py:42 DeprecationWarning: deprecated
(1x) /tmp/example.py:43 UserWarning: other
>>> cw.counts
{('/tmp/example.py', 42, 'DeprecationWarning', 'deprecated'): 2,
 ('/tmp/example.py', 43, 'UserWarning', 'other'): 1}
CollateWarnings( print_on_exit: bool = True, fmt: str = '({count}x) {filename}:{lineno} {category}: {message}')
74    def __init__(
75        self,
76        print_on_exit: bool = True,
77        fmt: str = "({count}x) {filename}:{lineno} {category}: {message}",
78    ) -> None:
79        self.print_on_exit = print_on_exit
80        self.fmt = fmt
81        self._active = False
82        self._records = []
83        self.counts = Counter()
counts: collections.Counter[tuple[str, int, str, str]]
print_on_exit: bool
fmt: str