Skip to content

Commit 7a979ee

Browse files
author
Bas van Beek
committed
ENH: Add a decorator for applying the effect of warnings.filterwarnings to decorated functions
1 parent 8f095f6 commit 7a979ee

File tree

1 file changed

+74
-2
lines changed

1 file changed

+74
-2
lines changed

nanoutils/utils.py

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import warnings
1919
import importlib
2020
import inspect
21+
import functools
2122
from types import ModuleType
2223
from functools import wraps
2324
from typing import (
@@ -34,9 +35,10 @@
3435
MutableMapping,
3536
Collection,
3637
cast,
37-
overload
38+
overload,
3839
)
3940

41+
from .typing_utils import Literal
4042
from .empty import EMPTY_CONTAINER
4143

4244
__all__ = [
@@ -58,6 +60,7 @@
5860
'positional_only',
5961
'UserMapping',
6062
'MutableUserMapping',
63+
'warning_filter',
6164
]
6265

6366
_T = TypeVar('_T')
@@ -737,6 +740,75 @@ def positional_only(func: _FT) -> _FT:
737740
return func
738741

739742

743+
def warning_filter(
744+
action: Literal["default", "error", "ignore", "always", "module", "once"],
745+
message: str = "",
746+
category: type[Warning] = Warning,
747+
module: str = "",
748+
lineno: int = 0,
749+
append: bool = False,
750+
) -> Callable[[_FT], _FT]:
751+
"""A decorator for wrapping function calls with :func:`warnings.filterwarnings`.
752+
753+
Examples
754+
--------
755+
.. code-block:: python
756+
757+
>>> from nanoutils import warning_filter
758+
>>> import warnings
759+
760+
>>> @warning_filter("error", category=UserWarning)
761+
... def func():
762+
... warnings.warn("test", UserWarning)
763+
764+
>>> func()
765+
Traceback (most recent call last):
766+
...
767+
UserWarning: test
768+
769+
Parameters
770+
----------
771+
action : :class:`str`
772+
One of the following strings:
773+
774+
* ``"default"``: Print the first occurrence of matching warnings for each location (module + line number) where the warning is issued
775+
* ``"error"``: Turn matching warnings into exceptions
776+
* ``"ignore"``: Never print matching warnings
777+
* ``"always"``: Always print matching warnings
778+
* ``"module"``: Print the first occurrence of matching warnings for each module where the warning is issued (regardless of line number)
779+
* ``"once"``: Print only the first occurrence of matching warnings, regardless of location
780+
781+
message : :class:`str`, optional
782+
A string containing a regular expression that the start of the warning message must match.
783+
The expression is compiled to always be case-insensitive.
784+
category : :class:`type[Warning] <type>`
785+
The to-be affected :class:`Warning` (sub-)class.
786+
module : :class:`str`, optional
787+
A string containing a regular expression that the module name must match.
788+
The expression is compiled to be case-sensitive.
789+
lineno : :class:`int`
790+
An integer that the line number where the warning occurred must match,
791+
or 0 to match all line numbers.
792+
append : :class:`bool`
793+
Whether the warning entry is inserted at the end.
794+
795+
See Also
796+
--------
797+
:func:`warnings.filterwarnings` :
798+
Insert a simple entry into the list of warnings filters (at the front).
799+
800+
"""
801+
def decorator(func: _FT) -> _FT:
802+
@functools.wraps(func)
803+
def wrapper(*args, **kwargs):
804+
with warnings.catch_warnings():
805+
warnings.filterwarnings(action, message, category, module, lineno, append)
806+
ret = func(*args, **kwargs)
807+
return ret
808+
return cast(_FT, wrapper)
809+
return decorator
810+
811+
740812
# Move to the end to reduce the risk of circular imports
741813
from ._partial import PartialPrepend
742814
from ._set_attr import SetAttr
@@ -747,5 +819,5 @@ def positional_only(func: _FT) -> _FT:
747819

748820
__doc__ = construct_api_doc(
749821
globals(),
750-
decorators={'set_docstring', 'raise_if', 'ignore_if', 'positional_only'},
822+
decorators={'set_docstring', 'raise_if', 'ignore_if', 'positional_only', 'warning_filter'},
751823
)

0 commit comments

Comments
 (0)