Skip to content

Instantly share code, notes, and snippets.

@gwerbin
Forked from mypy-play/main.py
Last active November 7, 2022 21:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gwerbin/5483e4b9fa1f65d4cb0918f3d7eb1e37 to your computer and use it in GitHub Desktop.
Save gwerbin/5483e4b9fa1f65d4cb0918f3d7eb1e37 to your computer and use it in GitHub Desktop.
Wraps an object with a arbitrary str conversion, for logging.
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)
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