muutils.logger.headerfuncs
1from __future__ import annotations 2 3import json 4from typing import Any, Mapping, Protocol 5 6from muutils.json_serialize import json_serialize 7 8# takes message, level, other data, and outputs message with appropriate header 9# HeaderFunction = Callable[[str, int, Any], str] 10 11 12class HeaderFunction(Protocol): 13 def __call__(self, msg: Any, lvl: int, **kwargs) -> str: ... 14 15 16def md_header_function( 17 msg: Any, 18 lvl: int, 19 stream: str | None = None, 20 indent_lvl: str = " ", 21 extra_indent: str = "", 22 **kwargs, 23) -> str: 24 """standard header function. will output 25 26 - `# {msg}` 27 28 for levels in [0, 9] 29 30 - `## {msg}` 31 32 for levels in [10, 19], and so on 33 34 - `[{stream}] # {msg}` 35 36 for a non-`None` stream, with level headers as before 37 38 - `!WARNING! [{stream}] {msg}` 39 40 for level in [-9, -1] 41 42 - `!!WARNING!! [{stream}] {msg}` 43 44 for level in [-19, -10] and so on 45 46 """ 47 stream_prefix: str = "" 48 if stream is not None: 49 stream_prefix = f"[{stream}] " 50 51 lvl_div_10: int = lvl // 10 52 53 msg_processed: str 54 if isinstance(msg, Mapping): 55 msg_processed = ", ".join([f"{k}: {json_serialize(v)}" for k, v in msg.items()]) 56 else: 57 msg_processed = json.dumps(json_serialize(msg)) 58 59 if lvl >= 0: 60 return f"{extra_indent}{indent_lvl * (lvl_div_10 - 1)}{stream_prefix}#{'#' * lvl_div_10 if lvl else ''} {msg_processed}" 61 else: 62 exclamation_pts: str = "!" * (abs(lvl) // 10) 63 return f"{extra_indent}{exclamation_pts}WARNING{exclamation_pts} {stream_prefix} {msg_processed}" 64 65 66HEADER_FUNCTIONS: dict[str, HeaderFunction] = { 67 "md": md_header_function, 68}
class
HeaderFunction(typing.Protocol):
Base class for protocol classes.
Protocol classes are defined as::
class Proto(Protocol):
def meth(self) -> int:
...
Such classes are primarily used with static type checkers that recognize structural subtyping (static duck-typing).
For example::
class C:
def meth(self) -> int:
return 0
def func(x: Proto) -> int:
return x.meth()
func(C()) # Passes static type check
See PEP 544 for details. Protocol classes decorated with @typing.runtime_checkable act as simple-minded runtime protocols that check only the presence of given attributes, ignoring their type signatures. Protocol classes can be generic, they are defined as::
class GenProto[T](Protocol):
def meth(self) -> T:
...
HeaderFunction(*args, **kwargs)
1710def _no_init_or_replace_init(self, *args, **kwargs): 1711 cls = type(self) 1712 1713 if cls._is_protocol: 1714 raise TypeError('Protocols cannot be instantiated') 1715 1716 # Already using a custom `__init__`. No need to calculate correct 1717 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1718 if cls.__init__ is not _no_init_or_replace_init: 1719 return 1720 1721 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1722 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1723 # searches for a proper new `__init__` in the MRO. The new `__init__` 1724 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1725 # instantiation of the protocol subclass will thus use the new 1726 # `__init__` and no longer call `_no_init_or_replace_init`. 1727 for base in cls.__mro__: 1728 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1729 if init is not _no_init_or_replace_init: 1730 cls.__init__ = init 1731 break 1732 else: 1733 # should not happen 1734 cls.__init__ = object.__init__ 1735 1736 cls.__init__(self, *args, **kwargs)
def
md_header_function( msg: Any, lvl: int, stream: str | None = None, indent_lvl: str = ' ', extra_indent: str = '', **kwargs) -> str:
17def md_header_function( 18 msg: Any, 19 lvl: int, 20 stream: str | None = None, 21 indent_lvl: str = " ", 22 extra_indent: str = "", 23 **kwargs, 24) -> str: 25 """standard header function. will output 26 27 - `# {msg}` 28 29 for levels in [0, 9] 30 31 - `## {msg}` 32 33 for levels in [10, 19], and so on 34 35 - `[{stream}] # {msg}` 36 37 for a non-`None` stream, with level headers as before 38 39 - `!WARNING! [{stream}] {msg}` 40 41 for level in [-9, -1] 42 43 - `!!WARNING!! [{stream}] {msg}` 44 45 for level in [-19, -10] and so on 46 47 """ 48 stream_prefix: str = "" 49 if stream is not None: 50 stream_prefix = f"[{stream}] " 51 52 lvl_div_10: int = lvl // 10 53 54 msg_processed: str 55 if isinstance(msg, Mapping): 56 msg_processed = ", ".join([f"{k}: {json_serialize(v)}" for k, v in msg.items()]) 57 else: 58 msg_processed = json.dumps(json_serialize(msg)) 59 60 if lvl >= 0: 61 return f"{extra_indent}{indent_lvl * (lvl_div_10 - 1)}{stream_prefix}#{'#' * lvl_div_10 if lvl else ''} {msg_processed}" 62 else: 63 exclamation_pts: str = "!" * (abs(lvl) // 10) 64 return f"{extra_indent}{exclamation_pts}WARNING{exclamation_pts} {stream_prefix} {msg_processed}"
standard header function. will output
# {msg}
for levels in [0, 9]
## {msg}
for levels in [10, 19], and so on
[{stream}] # {msg}
for a non-`None` stream, with level headers as before
!WARNING! [{stream}] {msg}
for level in [-9, -1]
!!WARNING!! [{stream}] {msg}
for level in [-19, -10] and so on