Skip to content

Commit

Permalink
fix: reduce number of deprecation warnings
Browse files Browse the repository at this point in the history
There is (arguably) a bug in Python's warnings module, such that every
use of warnings.catch_warnings erases all memory of which warnings
have been displayed.

This breaks the "default" setting of the warnings filter which is
supposed to ensure that each warning is only displayed once per
(module, line).

So here, we disuse catch_warnings.

See https://bugs.python.org/issue29672
  • Loading branch information
dairiki committed Apr 24, 2023
1 parent 7960115 commit 1bc82bc
Showing 1 changed file with 33 additions and 4 deletions.
37 changes: 33 additions & 4 deletions lektor/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import subprocess
import sys
import tempfile
import threading
import unicodedata
import uuid
import warnings
Expand All @@ -19,6 +20,7 @@
from pathlib import PurePosixPath
from typing import Any
from typing import Callable
from typing import ClassVar
from typing import Hashable
from typing import Iterable
from typing import overload
Expand Down Expand Up @@ -743,6 +745,32 @@ def __str__(self) -> str:
return message


class RecursionGuard(threading.local):
"""A context manager to track recursion.
Usage:
guard = RecursionGuard()
def f(callback):
with guard() as call_depth:
if call_depth > 0:
raise RuntimeError("recursion!")
callback()
"""

depth = 0

@contextmanager
def __call__(self):
depth = self.depth
self.depth = depth + 1
try:
yield depth
finally:
self.depth = depth


_F = TypeVar("_F", bound=Callable[..., Any])


Expand All @@ -755,6 +783,8 @@ class _Deprecate:
version: str | None = None
stacklevel: int = 1

_recursion_guard: ClassVar = RecursionGuard()

def __call__(self, wrapped: _F) -> _F:
if not callable(wrapped):
raise TypeError("do not know how to deprecate {wrapped!r}")
Expand All @@ -764,10 +794,9 @@ def __call__(self, wrapped: _F) -> _F:

@wraps(wrapped)
def wrapper(*args: Any, **kwargs: Any) -> Any:
warnings.warn(message, stacklevel=self.stacklevel + 1)
with warnings.catch_warnings():
# ignore any of our own custom warnings generated by wrapped callable
warnings.simplefilter("ignore", category=DeprecatedWarning)
with self._recursion_guard() as call_depth:
if call_depth == 0:
warnings.warn(message, stacklevel=self.stacklevel + 1)
return wrapped(*args, **kwargs)

return wrapper # type: ignore[return-value]
Expand Down

0 comments on commit 1bc82bc

Please sign in to comment.