diff --git a/loguru/__init__.pyi b/loguru/__init__.pyi index 3a13ad71..0abe03da 100644 --- a/loguru/__init__.pyi +++ b/loguru/__init__.pyi @@ -25,6 +25,8 @@ from typing import ( overload, ) +import typing_extensions + if sys.version_info >= (3, 6): from typing import Awaitable else: @@ -47,6 +49,7 @@ else: _T = TypeVar("_T") _F = TypeVar("_F", bound=Callable[..., Any]) +_P = typing_extensions.ParamSpec("_P") ExcInfo = Tuple[Optional[Type[BaseException]], Optional[BaseException], Optional[TracebackType]] class _GeneratorContextManager(ContextManager[_T], Generic[_T]): @@ -364,5 +367,7 @@ class Logger: def log(__self, __level: Union[int, str], __message: Any) -> None: ... # noqa: N805 def start(self, *args: Any, **kwargs: Any) -> int: ... def stop(self, *args: Any, **kwargs: Any) -> None: ... + @staticmethod + def lazy(fn: Callable[_P, Any], *args: _P.args, **kwargs: _P.kwargs) -> Any: ... logger: Logger diff --git a/loguru/_logger.py b/loguru/_logger.py index 0167a1eb..bf1aeeae 100644 --- a/loguru/_logger.py +++ b/loguru/_logger.py @@ -117,7 +117,6 @@ else: from pathlib import PurePath as PathLike - Level = namedtuple("Level", ["name", "no", "color", "icon"]) # noqa: PYI024 start_time = aware_now() @@ -234,6 +233,13 @@ class Logger: """ def __init__(self, core, exception, depth, record, lazy, colors, raw, capture, patchers, extra): + if lazy: + warnings.warn( + "lazy mode is deprecated, use `LazyValue` for deferred value", + stacklevel=2, + category=DeprecationWarning, + ) + self._core = core self._options = (exception, depth, record, lazy, colors, raw, capture, patchers, extra) @@ -2137,3 +2143,27 @@ def stop(self, *args, **kwargs): stacklevel=2, ) return self.remove(*args, **kwargs) + + @staticmethod + def lazy(fn, *args, **kwargs): + """Make fn lazy evaluated. + + For example, fn will only be called if debug level is enabled. + + ```python + logger.debug("hello {}", logger.lazy(fn, *...)) + ``` + """ + return LazyValue(fn, *args, **kwargs) + + +class LazyValue: + __slots__ = ("args", "fn", "kwargs") + + def __init__(self, fn, *args, **kwargs): + self.fn = fn + self.args = args + self.kwargs = kwargs + + def __format__(self, format_spec: str): + return format(self.fn(*self.args, **self.kwargs), format_spec) diff --git a/pyproject.toml b/pyproject.toml index 4ea6eb8e..d57d78cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,6 +99,8 @@ filterwarnings = [ 'error', # Mixing threads and "fork()" is deprecated, but we need to test it anyway. 'ignore:.*use of fork\(\) may lead to deadlocks in the child.*:DeprecationWarning', + # deprecated message for users + 'ignore:lazy mode is deprecated.*', ] testpaths = ["tests"] diff --git a/tests/test_lazy.py b/tests/test_lazy.py new file mode 100644 index 00000000..c7b6fb43 --- /dev/null +++ b/tests/test_lazy.py @@ -0,0 +1,9 @@ +from loguru import logger + + +def test_lazy_value(writer): + logger_bound = logger.bind(a=0) + logger_bound.add(writer, format="{message}") + logger_bound.info("hello {}", logger.lazy(str, 1)) + + assert writer.read() == "hello 1\n" diff --git a/tests/test_type_hinting.py b/tests/test_type_hinting.py index 4d452648..cda1981b 100644 --- a/tests/test_type_hinting.py +++ b/tests/test_type_hinting.py @@ -1,8 +1,10 @@ import sys import mypy.api +import pytest +@pytest.mark.skipif(sys.version_info < (3, 6), reason="typing supported is added in py36") def test_mypy_import(): # Check stub file is valid and can be imported by Mypy. # There exist others tests in "typesafety" subfolder but they aren't compatible with Python 3.5.