docs for muutils v0.6.14
View Source on GitHub

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):
13class HeaderFunction(Protocol):
14    def __call__(self, msg: Any, lvl: int, **kwargs) -> str: ...

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
    
HEADER_FUNCTIONS: dict[str, HeaderFunction] = {'md': <function md_header_function>}