Merge pull request #384 from goodboy/sclang_pformating
A "sclang" pformat-ing, `.msg.pretty_struct.pformat` and `modden.repr` utils bonanzamain
commit
65b795612c
|
@ -22,7 +22,6 @@ from __future__ import annotations
|
||||||
from functools import partial
|
from functools import partial
|
||||||
import multiprocessing as mp
|
import multiprocessing as mp
|
||||||
import os
|
import os
|
||||||
import textwrap
|
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
|
@ -35,7 +34,10 @@ from .log import (
|
||||||
get_logger,
|
get_logger,
|
||||||
)
|
)
|
||||||
from . import _state
|
from . import _state
|
||||||
from .devx import _debug
|
from .devx import (
|
||||||
|
_debug,
|
||||||
|
pformat,
|
||||||
|
)
|
||||||
from .to_asyncio import run_as_asyncio_guest
|
from .to_asyncio import run_as_asyncio_guest
|
||||||
from ._addr import UnwrappedAddress
|
from ._addr import UnwrappedAddress
|
||||||
from ._runtime import (
|
from ._runtime import (
|
||||||
|
@ -103,107 +105,6 @@ def _mp_main(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# TODO: move this func to some kinda `.devx._conc_lang.py` eventually
|
|
||||||
# as we work out our multi-domain state-flow-syntax!
|
|
||||||
def nest_from_op(
|
|
||||||
input_op: str,
|
|
||||||
#
|
|
||||||
# ?TODO? an idea for a syntax to the state of concurrent systems
|
|
||||||
# as a "3-domain" (execution, scope, storage) model and using
|
|
||||||
# a minimal ascii/utf-8 operator-set.
|
|
||||||
#
|
|
||||||
# try not to take any of this seriously yet XD
|
|
||||||
#
|
|
||||||
# > is a "play operator" indicating (CPU bound)
|
|
||||||
# exec/work/ops required at the "lowest level computing"
|
|
||||||
#
|
|
||||||
# execution primititves (tasks, threads, actors..) denote their
|
|
||||||
# lifetime with '(' and ')' since parentheses normally are used
|
|
||||||
# in many langs to denote function calls.
|
|
||||||
#
|
|
||||||
# starting = (
|
|
||||||
# >( opening/starting; beginning of the thread-of-exec (toe?)
|
|
||||||
# (> opened/started, (finished spawning toe)
|
|
||||||
# |_<Task: blah blah..> repr of toe, in py these look like <objs>
|
|
||||||
#
|
|
||||||
# >) closing/exiting/stopping,
|
|
||||||
# )> closed/exited/stopped,
|
|
||||||
# |_<Task: blah blah..>
|
|
||||||
# [OR <), )< ?? ]
|
|
||||||
#
|
|
||||||
# ending = )
|
|
||||||
# >c) cancelling to close/exit
|
|
||||||
# c)> cancelled (caused close), OR?
|
|
||||||
# |_<Actor: ..>
|
|
||||||
# OR maybe "<c)" which better indicates the cancel being
|
|
||||||
# "delivered/returned" / returned" to LHS?
|
|
||||||
#
|
|
||||||
# >x) erroring to eventuall exit
|
|
||||||
# x)> errored and terminated
|
|
||||||
# |_<Actor: ...>
|
|
||||||
#
|
|
||||||
# scopes: supers/nurseries, IPC-ctxs, sessions, perms, etc.
|
|
||||||
# >{ opening
|
|
||||||
# {> opened
|
|
||||||
# }> closed
|
|
||||||
# >} closing
|
|
||||||
#
|
|
||||||
# storage: like queues, shm-buffers, files, etc..
|
|
||||||
# >[ opening
|
|
||||||
# [> opened
|
|
||||||
# |_<FileObj: ..>
|
|
||||||
#
|
|
||||||
# >] closing
|
|
||||||
# ]> closed
|
|
||||||
|
|
||||||
# IPC ops: channels, transports, msging
|
|
||||||
# => req msg
|
|
||||||
# <= resp msg
|
|
||||||
# <=> 2-way streaming (of msgs)
|
|
||||||
# <- recv 1 msg
|
|
||||||
# -> send 1 msg
|
|
||||||
#
|
|
||||||
# TODO: still not sure on R/L-HS approach..?
|
|
||||||
# =>( send-req to exec start (task, actor, thread..)
|
|
||||||
# (<= recv-req to ^
|
|
||||||
#
|
|
||||||
# (<= recv-req ^
|
|
||||||
# <=( recv-resp opened remote exec primitive
|
|
||||||
# <=) recv-resp closed
|
|
||||||
#
|
|
||||||
# )<=c req to stop due to cancel
|
|
||||||
# c=>) req to stop due to cancel
|
|
||||||
#
|
|
||||||
# =>{ recv-req to open
|
|
||||||
# <={ send-status that it closed
|
|
||||||
|
|
||||||
tree_str: str,
|
|
||||||
|
|
||||||
# NOTE: so move back-from-the-left of the `input_op` by
|
|
||||||
# this amount.
|
|
||||||
back_from_op: int = 0,
|
|
||||||
) -> str:
|
|
||||||
'''
|
|
||||||
Depth-increment the input (presumably hierarchy/supervision)
|
|
||||||
input "tree string" below the provided `input_op` execution
|
|
||||||
operator, so injecting a `"\n|_{input_op}\n"`and indenting the
|
|
||||||
`tree_str` to nest content aligned with the ops last char.
|
|
||||||
|
|
||||||
'''
|
|
||||||
return (
|
|
||||||
f'{input_op}\n'
|
|
||||||
+
|
|
||||||
textwrap.indent(
|
|
||||||
tree_str,
|
|
||||||
prefix=(
|
|
||||||
len(input_op)
|
|
||||||
-
|
|
||||||
(back_from_op + 1)
|
|
||||||
) * ' ',
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _trio_main(
|
def _trio_main(
|
||||||
actor: Actor,
|
actor: Actor,
|
||||||
*,
|
*,
|
||||||
|
@ -234,22 +135,22 @@ def _trio_main(
|
||||||
f' loglevel: {actor.loglevel}\n'
|
f' loglevel: {actor.loglevel}\n'
|
||||||
)
|
)
|
||||||
log.info(
|
log.info(
|
||||||
'Starting new `trio` subactor:\n'
|
'Starting new `trio` subactor\n'
|
||||||
+
|
+
|
||||||
nest_from_op(
|
pformat.nest_from_op(
|
||||||
input_op='>(', # see syntax ideas above
|
input_op='>(', # see syntax ideas above
|
||||||
tree_str=actor_info,
|
text=actor_info,
|
||||||
back_from_op=2, # since "complete"
|
nest_indent=2, # since "complete"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
logmeth = log.info
|
logmeth = log.info
|
||||||
exit_status: str = (
|
exit_status: str = (
|
||||||
'Subactor exited\n'
|
'Subactor exited\n'
|
||||||
+
|
+
|
||||||
nest_from_op(
|
pformat.nest_from_op(
|
||||||
input_op=')>', # like a "closed-to-play"-icon from super perspective
|
input_op=')>', # like a "closed-to-play"-icon from super perspective
|
||||||
tree_str=actor_info,
|
text=actor_info,
|
||||||
back_from_op=1,
|
nest_indent=1,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
|
@ -264,9 +165,9 @@ def _trio_main(
|
||||||
exit_status: str = (
|
exit_status: str = (
|
||||||
'Actor received KBI (aka an OS-cancel)\n'
|
'Actor received KBI (aka an OS-cancel)\n'
|
||||||
+
|
+
|
||||||
nest_from_op(
|
pformat.nest_from_op(
|
||||||
input_op='c)>', # closed due to cancel (see above)
|
input_op='c)>', # closed due to cancel (see above)
|
||||||
tree_str=actor_info,
|
text=actor_info,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
except BaseException as err:
|
except BaseException as err:
|
||||||
|
@ -274,9 +175,9 @@ def _trio_main(
|
||||||
exit_status: str = (
|
exit_status: str = (
|
||||||
'Main actor task exited due to crash?\n'
|
'Main actor task exited due to crash?\n'
|
||||||
+
|
+
|
||||||
nest_from_op(
|
pformat.nest_from_op(
|
||||||
input_op='x)>', # closed by error
|
input_op='x)>', # closed by error
|
||||||
tree_str=actor_info,
|
text=actor_info,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
# NOTE since we raise a tb will already be shown on the
|
# NOTE since we raise a tb will already be shown on the
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Pretty formatters for use throughout the code base.
|
Pretty formatters for use throughout our internals.
|
||||||
Mostly handy for logging and exception message content.
|
|
||||||
|
Handy for logging and exception message content but also for `repr()`
|
||||||
|
in REPL(s).
|
||||||
|
|
||||||
'''
|
'''
|
||||||
import sys
|
import sys
|
||||||
|
@ -224,8 +226,8 @@ def pformat_cs(
|
||||||
field_prefix: str = ' |_',
|
field_prefix: str = ' |_',
|
||||||
) -> str:
|
) -> str:
|
||||||
'''
|
'''
|
||||||
Pretty format info about a `trio.CancelScope` including most
|
Pretty format info about a `trio.CancelScope` including most of
|
||||||
of its public state and `._cancel_status`.
|
its public state and `._cancel_status`.
|
||||||
|
|
||||||
The output can be modified to show a "var name" for the
|
The output can be modified to show a "var name" for the
|
||||||
instance as a field prefix, just a simple str before each
|
instance as a field prefix, just a simple str before each
|
||||||
|
@ -247,3 +249,279 @@ def pformat_cs(
|
||||||
+
|
+
|
||||||
fields
|
fields
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def nest_from_op(
|
||||||
|
input_op: str, # TODO, Literal of all op-"symbols" from below?
|
||||||
|
text: str,
|
||||||
|
prefix_op: bool = True, # unset is to suffix the first line
|
||||||
|
# optionally suffix `text`, by def on a newline
|
||||||
|
op_suffix='\n',
|
||||||
|
|
||||||
|
nest_prefix: str = '|_',
|
||||||
|
nest_indent: int|None = None,
|
||||||
|
# XXX indent `next_prefix` "to-the-right-of" `input_op`
|
||||||
|
# by this count of whitespaces (' ').
|
||||||
|
rm_from_first_ln: str|None = None,
|
||||||
|
|
||||||
|
) -> str:
|
||||||
|
'''
|
||||||
|
Depth-increment the input (presumably hierarchy/supervision)
|
||||||
|
input "tree string" below the provided `input_op` execution
|
||||||
|
operator, so injecting a `"\n|_{input_op}\n"`and indenting the
|
||||||
|
`tree_str` to nest content aligned with the ops last char.
|
||||||
|
|
||||||
|
'''
|
||||||
|
# `sclang` "structurred-concurrency-language": an ascii-encoded
|
||||||
|
# symbolic alphabet to describe concurrent systems.
|
||||||
|
#
|
||||||
|
# ?TODO? aa more fomal idea for a syntax to the state of
|
||||||
|
# concurrent systems as a "3-domain" (execution, scope, storage)
|
||||||
|
# model and using a minimal ascii/utf-8 operator-set.
|
||||||
|
#
|
||||||
|
# try not to take any of this seriously yet XD
|
||||||
|
#
|
||||||
|
# > is a "play operator" indicating (CPU bound)
|
||||||
|
# exec/work/ops required at the "lowest level computing"
|
||||||
|
#
|
||||||
|
# execution primititves (tasks, threads, actors..) denote their
|
||||||
|
# lifetime with '(' and ')' since parentheses normally are used
|
||||||
|
# in many langs to denote function calls.
|
||||||
|
#
|
||||||
|
# starting = (
|
||||||
|
# >( opening/starting; beginning of the thread-of-exec (toe?)
|
||||||
|
# (> opened/started, (finished spawning toe)
|
||||||
|
# |_<Task: blah blah..> repr of toe, in py these look like <objs>
|
||||||
|
#
|
||||||
|
# >) closing/exiting/stopping,
|
||||||
|
# )> closed/exited/stopped,
|
||||||
|
# |_<Task: blah blah..>
|
||||||
|
# [OR <), )< ?? ]
|
||||||
|
#
|
||||||
|
# ending = )
|
||||||
|
# >c) cancelling to close/exit
|
||||||
|
# c)> cancelled (caused close), OR?
|
||||||
|
# |_<Actor: ..>
|
||||||
|
# OR maybe "<c)" which better indicates the cancel being
|
||||||
|
# "delivered/returned" / returned" to LHS?
|
||||||
|
#
|
||||||
|
# >x) erroring to eventuall exit
|
||||||
|
# x)> errored and terminated
|
||||||
|
# |_<Actor: ...>
|
||||||
|
#
|
||||||
|
# scopes: supers/nurseries, IPC-ctxs, sessions, perms, etc.
|
||||||
|
# >{ opening
|
||||||
|
# {> opened
|
||||||
|
# }> closed
|
||||||
|
# >} closing
|
||||||
|
#
|
||||||
|
# storage: like queues, shm-buffers, files, etc..
|
||||||
|
# >[ opening
|
||||||
|
# [> opened
|
||||||
|
# |_<FileObj: ..>
|
||||||
|
#
|
||||||
|
# >] closing
|
||||||
|
# ]> closed
|
||||||
|
|
||||||
|
# IPC ops: channels, transports, msging
|
||||||
|
# => req msg
|
||||||
|
# <= resp msg
|
||||||
|
# <=> 2-way streaming (of msgs)
|
||||||
|
# <- recv 1 msg
|
||||||
|
# -> send 1 msg
|
||||||
|
#
|
||||||
|
# TODO: still not sure on R/L-HS approach..?
|
||||||
|
# =>( send-req to exec start (task, actor, thread..)
|
||||||
|
# (<= recv-req to ^
|
||||||
|
#
|
||||||
|
# (<= recv-req ^
|
||||||
|
# <=( recv-resp opened remote exec primitive
|
||||||
|
# <=) recv-resp closed
|
||||||
|
#
|
||||||
|
# )<=c req to stop due to cancel
|
||||||
|
# c=>) req to stop due to cancel
|
||||||
|
#
|
||||||
|
# =>{ recv-req to open
|
||||||
|
# <={ send-status that it closed
|
||||||
|
#
|
||||||
|
if (
|
||||||
|
nest_prefix
|
||||||
|
and
|
||||||
|
nest_indent != 0
|
||||||
|
):
|
||||||
|
if nest_indent is not None:
|
||||||
|
nest_prefix: str = textwrap.indent(
|
||||||
|
nest_prefix,
|
||||||
|
prefix=nest_indent*' ',
|
||||||
|
)
|
||||||
|
nest_indent: int = len(nest_prefix)
|
||||||
|
|
||||||
|
# determine body-text indent either by,
|
||||||
|
# - using wtv explicit indent value is provided,
|
||||||
|
# OR
|
||||||
|
# - auto-calcing the indent to embed `text` under
|
||||||
|
# the `nest_prefix` if provided, **IFF** `nest_indent=None`.
|
||||||
|
tree_str_indent: int = 0
|
||||||
|
if nest_indent not in {0, None}:
|
||||||
|
tree_str_indent = nest_indent
|
||||||
|
elif (
|
||||||
|
nest_prefix
|
||||||
|
and
|
||||||
|
nest_indent != 0
|
||||||
|
):
|
||||||
|
tree_str_indent = len(nest_prefix)
|
||||||
|
|
||||||
|
indented_tree_str: str = text
|
||||||
|
if tree_str_indent:
|
||||||
|
indented_tree_str: str = textwrap.indent(
|
||||||
|
text,
|
||||||
|
prefix=' '*tree_str_indent,
|
||||||
|
)
|
||||||
|
|
||||||
|
# inject any provided nesting-prefix chars
|
||||||
|
# into the head of the first line.
|
||||||
|
if nest_prefix:
|
||||||
|
indented_tree_str: str = (
|
||||||
|
f'{nest_prefix}{indented_tree_str[tree_str_indent:]}'
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
not prefix_op
|
||||||
|
or
|
||||||
|
rm_from_first_ln
|
||||||
|
):
|
||||||
|
tree_lns: list[str] = indented_tree_str.splitlines()
|
||||||
|
first: str = tree_lns[0]
|
||||||
|
if rm_from_first_ln:
|
||||||
|
first = first.strip().replace(
|
||||||
|
rm_from_first_ln,
|
||||||
|
'',
|
||||||
|
)
|
||||||
|
indented_tree_str: str = '\n'.join(tree_lns[1:])
|
||||||
|
|
||||||
|
if prefix_op:
|
||||||
|
indented_tree_str = (
|
||||||
|
f'{first}\n'
|
||||||
|
f'{indented_tree_str}'
|
||||||
|
)
|
||||||
|
|
||||||
|
if prefix_op:
|
||||||
|
return (
|
||||||
|
f'{input_op}{op_suffix}'
|
||||||
|
f'{indented_tree_str}'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return (
|
||||||
|
f'{first}{input_op}{op_suffix}'
|
||||||
|
f'{indented_tree_str}'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ------ modden.repr ------
|
||||||
|
# XXX originally taken verbaatim from `modden.repr`
|
||||||
|
'''
|
||||||
|
More "multi-line" representation then the stdlib's `pprint` equivs.
|
||||||
|
|
||||||
|
'''
|
||||||
|
from inspect import (
|
||||||
|
FrameInfo,
|
||||||
|
stack,
|
||||||
|
)
|
||||||
|
import pprint
|
||||||
|
import reprlib
|
||||||
|
from typing import (
|
||||||
|
Callable,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def mk_repr(
|
||||||
|
**repr_kws,
|
||||||
|
) -> Callable[[str], str]:
|
||||||
|
'''
|
||||||
|
Allocate and deliver a `repr.Repr` instance with provided input
|
||||||
|
settings using the std-lib's `reprlib` mod,
|
||||||
|
* https://docs.python.org/3/library/reprlib.html
|
||||||
|
|
||||||
|
------ Ex. ------
|
||||||
|
An up to 6-layer-nested `dict` as multi-line:
|
||||||
|
- https://stackoverflow.com/a/79102479
|
||||||
|
- https://docs.python.org/3/library/reprlib.html#reprlib.Repr.maxlevel
|
||||||
|
|
||||||
|
'''
|
||||||
|
def_kws: dict[str, int] = dict(
|
||||||
|
indent=3, # indent used for repr of recursive objects
|
||||||
|
maxlevel=616, # recursion levels
|
||||||
|
maxdict=616, # max items shown for `dict`
|
||||||
|
maxlist=616, # max items shown for `dict`
|
||||||
|
maxstring=616, # match editor line-len limit
|
||||||
|
maxtuple=616, # match editor line-len limit
|
||||||
|
maxother=616, # match editor line-len limit
|
||||||
|
)
|
||||||
|
def_kws |= repr_kws
|
||||||
|
reprr = reprlib.Repr(**def_kws)
|
||||||
|
return reprr.repr
|
||||||
|
|
||||||
|
|
||||||
|
def ppfmt(
|
||||||
|
obj: object,
|
||||||
|
do_print: bool = False,
|
||||||
|
) -> str:
|
||||||
|
'''
|
||||||
|
The `pprint.pformat()` version of `pprint.pp()`, namely
|
||||||
|
a default `sort_dicts=False`.. (which i think should be
|
||||||
|
the normal default in the stdlib).
|
||||||
|
|
||||||
|
'''
|
||||||
|
pprepr: Callable = mk_repr()
|
||||||
|
repr_str: str = pprepr(obj)
|
||||||
|
|
||||||
|
if do_print:
|
||||||
|
return pprint.pp(repr_str)
|
||||||
|
|
||||||
|
return repr_str
|
||||||
|
|
||||||
|
|
||||||
|
pformat = ppfmt
|
||||||
|
|
||||||
|
|
||||||
|
def pfmt_frame_info(fi: FrameInfo) -> str:
|
||||||
|
'''
|
||||||
|
Like a std `inspect.FrameInfo.__repr__()` but multi-line..
|
||||||
|
|
||||||
|
'''
|
||||||
|
return (
|
||||||
|
'FrameInfo(\n'
|
||||||
|
' frame={!r},\n'
|
||||||
|
' filename={!r},\n'
|
||||||
|
' lineno={!r},\n'
|
||||||
|
' function={!r},\n'
|
||||||
|
' code_context={!r},\n'
|
||||||
|
' index={!r},\n'
|
||||||
|
' positions={!r})'
|
||||||
|
).format(
|
||||||
|
fi.frame,
|
||||||
|
fi.filename,
|
||||||
|
fi.lineno,
|
||||||
|
fi.function,
|
||||||
|
fi.code_context,
|
||||||
|
fi.index,
|
||||||
|
fi.positions
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def pfmt_callstack(frames: int = 1) -> str:
|
||||||
|
'''
|
||||||
|
Generate a string of nested `inspect.FrameInfo` objects returned
|
||||||
|
from a `inspect.stack()` call such that only the `.frame` field
|
||||||
|
for each layer is pprinted.
|
||||||
|
|
||||||
|
'''
|
||||||
|
caller_frames: list[FrameInfo] = stack()[1:1+frames]
|
||||||
|
frames_str: str = ''
|
||||||
|
for i, frame_info in enumerate(caller_frames):
|
||||||
|
frames_str += textwrap.indent(
|
||||||
|
f'{frame_info.frame!r}\n',
|
||||||
|
prefix=' '*i,
|
||||||
|
|
||||||
|
)
|
||||||
|
return frames_str
|
||||||
|
|
|
@ -270,7 +270,9 @@ def get_logger(
|
||||||
subsys_spec: str|None = None,
|
subsys_spec: str|None = None,
|
||||||
|
|
||||||
) -> StackLevelAdapter:
|
) -> StackLevelAdapter:
|
||||||
'''Return the package log or a sub-logger for ``name`` if provided.
|
'''
|
||||||
|
Return the `tractor`-library root logger or a sub-logger for
|
||||||
|
`name` if provided.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
log: Logger
|
log: Logger
|
||||||
|
@ -282,7 +284,7 @@ def get_logger(
|
||||||
name != _proj_name
|
name != _proj_name
|
||||||
):
|
):
|
||||||
|
|
||||||
# NOTE: for handling for modules that use ``get_logger(__name__)``
|
# NOTE: for handling for modules that use `get_logger(__name__)`
|
||||||
# we make the following stylistic choice:
|
# we make the following stylistic choice:
|
||||||
# - always avoid duplicate project-package token
|
# - always avoid duplicate project-package token
|
||||||
# in msg output: i.e. tractor.tractor.ipc._chan.py in header
|
# in msg output: i.e. tractor.tractor.ipc._chan.py in header
|
||||||
|
@ -331,7 +333,7 @@ def get_logger(
|
||||||
|
|
||||||
def get_console_log(
|
def get_console_log(
|
||||||
level: str|None = None,
|
level: str|None = None,
|
||||||
logger: Logger|None = None,
|
logger: Logger|StackLevelAdapter|None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
|
|
||||||
) -> LoggerAdapter:
|
) -> LoggerAdapter:
|
||||||
|
@ -344,12 +346,23 @@ def get_console_log(
|
||||||
Yeah yeah, i know we can use `logging.config.dictConfig()`. You do it.
|
Yeah yeah, i know we can use `logging.config.dictConfig()`. You do it.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
log = get_logger(
|
# get/create a stack-aware-adapter
|
||||||
logger=logger,
|
if (
|
||||||
**kwargs
|
logger
|
||||||
) # set a root logger
|
and
|
||||||
logger: Logger = log.logger
|
isinstance(logger, StackLevelAdapter)
|
||||||
|
):
|
||||||
|
# XXX, for ex. when passed in by a caller wrapping some
|
||||||
|
# other lib's logger instance with our level-adapter.
|
||||||
|
log = logger
|
||||||
|
|
||||||
|
else:
|
||||||
|
log: StackLevelAdapter = get_logger(
|
||||||
|
logger=logger,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
logger: Logger|StackLevelAdapter = log.logger
|
||||||
if not level:
|
if not level:
|
||||||
return log
|
return log
|
||||||
|
|
||||||
|
@ -367,10 +380,7 @@ def get_console_log(
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
fmt = LOG_FORMAT
|
fmt: str = LOG_FORMAT # always apply our format?
|
||||||
# if logger:
|
|
||||||
# fmt = None
|
|
||||||
|
|
||||||
handler = StreamHandler()
|
handler = StreamHandler()
|
||||||
formatter = colorlog.ColoredFormatter(
|
formatter = colorlog.ColoredFormatter(
|
||||||
fmt=fmt,
|
fmt=fmt,
|
||||||
|
|
|
@ -20,6 +20,7 @@ Prettified version of `msgspec.Struct` for easier console grokin.
|
||||||
'''
|
'''
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from collections import UserList
|
from collections import UserList
|
||||||
|
import textwrap
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
Iterator,
|
Iterator,
|
||||||
|
@ -105,27 +106,11 @@ def iter_fields(struct: Struct) -> Iterator[
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def pformat(
|
def iter_struct_ppfmt_lines(
|
||||||
struct: Struct,
|
struct: Struct,
|
||||||
field_indent: int = 2,
|
field_indent: int = 0,
|
||||||
indent: int = 0,
|
) -> Iterator[tuple[str, str]]:
|
||||||
|
|
||||||
) -> str:
|
|
||||||
'''
|
|
||||||
Recursion-safe `pprint.pformat()` style formatting of
|
|
||||||
a `msgspec.Struct` for sane reading by a human using a REPL.
|
|
||||||
|
|
||||||
'''
|
|
||||||
# global whitespace indent
|
|
||||||
ws: str = ' '*indent
|
|
||||||
|
|
||||||
# field whitespace indent
|
|
||||||
field_ws: str = ' '*(field_indent + indent)
|
|
||||||
|
|
||||||
# qtn: str = ws + struct.__class__.__qualname__
|
|
||||||
qtn: str = struct.__class__.__qualname__
|
|
||||||
|
|
||||||
obj_str: str = '' # accumulator
|
|
||||||
fi: structs.FieldInfo
|
fi: structs.FieldInfo
|
||||||
k: str
|
k: str
|
||||||
v: Any
|
v: Any
|
||||||
|
@ -135,15 +120,18 @@ def pformat(
|
||||||
# ..]` over .__name__ == `Literal` but still get only the
|
# ..]` over .__name__ == `Literal` but still get only the
|
||||||
# latter for simple types like `str | int | None` etc..?
|
# latter for simple types like `str | int | None` etc..?
|
||||||
ft: type = fi.type
|
ft: type = fi.type
|
||||||
typ_name: str = getattr(ft, '__name__', str(ft))
|
typ_name: str = getattr(
|
||||||
|
ft,
|
||||||
|
'__name__',
|
||||||
|
str(ft)
|
||||||
|
).replace(' ', '')
|
||||||
|
|
||||||
# recurse to get sub-struct's `.pformat()` output Bo
|
# recurse to get sub-struct's `.pformat()` output Bo
|
||||||
if isinstance(v, Struct):
|
if isinstance(v, Struct):
|
||||||
val_str: str = v.pformat(
|
yield from iter_struct_ppfmt_lines(
|
||||||
indent=field_indent + indent,
|
struct=v,
|
||||||
field_indent=indent + field_indent,
|
field_indent=field_indent+field_indent,
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
val_str: str = repr(v)
|
val_str: str = repr(v)
|
||||||
|
|
||||||
|
@ -161,8 +149,39 @@ def pformat(
|
||||||
# raise
|
# raise
|
||||||
# return _Struct.__repr__(struct)
|
# return _Struct.__repr__(struct)
|
||||||
|
|
||||||
# TODO: LOLOL use `textwrap.indent()` instead dawwwwwg!
|
yield (
|
||||||
obj_str += (field_ws + f'{k}: {typ_name} = {val_str},\n')
|
' '*field_indent, # indented ws prefix
|
||||||
|
f'{k}: {typ_name} = {val_str},', # field's repr line content
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def pformat(
|
||||||
|
struct: Struct,
|
||||||
|
field_indent: int = 2,
|
||||||
|
indent: int = 0,
|
||||||
|
) -> str:
|
||||||
|
'''
|
||||||
|
Recursion-safe `pprint.pformat()` style formatting of
|
||||||
|
a `msgspec.Struct` for sane reading by a human using a REPL.
|
||||||
|
|
||||||
|
'''
|
||||||
|
obj_str: str = '' # accumulator
|
||||||
|
for prefix, field_repr, in iter_struct_ppfmt_lines(
|
||||||
|
struct,
|
||||||
|
field_indent=field_indent,
|
||||||
|
):
|
||||||
|
obj_str += f'{prefix}{field_repr}\n'
|
||||||
|
|
||||||
|
# global whitespace indent
|
||||||
|
ws: str = ' '*indent
|
||||||
|
if indent:
|
||||||
|
obj_str: str = textwrap.indent(
|
||||||
|
text=obj_str,
|
||||||
|
prefix=ws,
|
||||||
|
)
|
||||||
|
|
||||||
|
# qtn: str = ws + struct.__class__.__qualname__
|
||||||
|
qtn: str = struct.__class__.__qualname__
|
||||||
|
|
||||||
return (
|
return (
|
||||||
f'{qtn}(\n'
|
f'{qtn}(\n'
|
||||||
|
|
Loading…
Reference in New Issue