-
-
Save gwerbin/5483e4b9fa1f65d4cb0918f3d7eb1e37 to your computer and use it in GitHub Desktop.
Wraps an object with a arbitrary str conversion, for logging.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
from collections import UserString | |
from collections.abc import Callable, Mapping | |
from typing import Any, ParamSpec, TypeVar, overload | |
if sys.version_info >= (3, 11): | |
from typing import Self, Unpack | |
else: | |
from typing_extensions import Self, Unpack | |
_LazyStringT = TypeVar("_LazyStringT", bound='LazyString') | |
_P = ParamSpec("_P") | |
class LazyString(UserString): | |
r"""Lazy String class. | |
See :func:`lazystr`, which you should use instead of creating an instance of this | |
class directly. | |
""" | |
func: Callable[..., str] | |
args: tuple[Any, ...] # P.args | |
kwargs: Mapping[str, Any] # P.kwargs | |
def __init__( | |
self, func: Callable[_P, str], *args: _P.args, **kwargs: _P.kwargs | |
) -> None: | |
self.func = func | |
self.args = args | |
self.kwargs = kwargs | |
# Mypy does not (yet) support overriding an attribute with a property. | |
# It will be added in v0.99x, but even then it will only support *writeable* | |
# properties, not read-only ones. | |
# https://github.com/python/mypy/pull/13475#issuecomment-1274714013 | |
@property | |
def data(self) -> str: # type: ignore[override] | |
return self.func(*self.args, **self.kwargs) | |
@overload | |
def lazystr(func_or_str: Callable[_P, str], *args: _P.args, **kwargs: _P.kwargs) -> LazyString: ... | |
@overload | |
def lazystr(func_or_str: str, *args: object, **kwargs: object) -> str: ... | |
def lazystr(func_or_str: Callable[_P, str] | str, *args: Any, **kwargs: Any) -> LazyString | str: | |
r"""Call an arbitrary function whenever :func:`str()` is called on this object. | |
This is intended for logging, to ensure that expensive string-ifying operations are | |
only performed when log messages are actually generated, and are not performed | |
otherwise. | |
:param func: The function to call. Can have any number of arguments, but must return | |
a string. If this argument is a string, the string is returned as-is, and | |
parameters ``args`` and ``kwargs`` are ignored. | |
:param args: Positional arguments passed to ``func``. | |
:param kwargs: Named/keyword arguments passed to ``func``. | |
.. code-block:: | |
:caption: Example: | |
import logging | |
from toolz import compose | |
logger = logging.getLogger() | |
x = np.arange(100).reshape((10, 10)) | |
logger.debug( | |
'This is the array:\n%s', | |
lazystr(lambda: indent_lines(repr(x))), | |
) | |
logger.debug( | |
'This is the array:\n%s', | |
lazystr(compose(indent_lines, repr), x) | |
) | |
logger.debug( | |
'This is the array:\n%s', | |
lazystr(compose(indent_lines, repr), x, spaces='\t') | |
) | |
""" | |
if isinstance(func_or_str, str): | |
return func_or_str | |
else: | |
return LazyString(func_or_str, *args, **kwargs) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import logging | |
import json | |
from typing import TYPE_CHECKING | |
from lazy_string import LazyString | |
logger = logging.getLogger('my.logger') | |
x = [list(range(i*10, i*10 + 10)) for i in range(10)] | |
logger.debug( | |
'This is the data: %r', | |
LazyString(x, repr=lambda o: indent(json.dumps(o, indent=2), 2)) | |
) | |
if TYPE_CHECKING: | |
reveal_type( | |
LazyString(str=lambda o: '< ... there is nothing here ... >') | |
) | |
reveal_type( | |
LazyString(x, repr=lambda o: indent(json.dumps(o, indent=2), 2)) | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment