Compare commits
	
		
			6 Commits 
		
	
	
		
			2248ffb74f
			...
			65b795612c
		
	
	| Author | SHA1 | Date | 
|---|---|---|
| 
							
							
								 | 
						65b795612c | |
| 
							
							
								 | 
						a42c1761a8 | |
| 
							
							
								 | 
						359d732633 | |
| 
							
							
								 | 
						b09e35f3dc | |
| 
							
							
								 | 
						6618b004f4 | |
| 
							
							
								 | 
						fc57a4d639 | 
| 
						 | 
				
			
			@ -22,7 +22,6 @@ from __future__ import annotations
 | 
			
		|||
from functools import partial
 | 
			
		||||
import multiprocessing as mp
 | 
			
		||||
import os
 | 
			
		||||
import textwrap
 | 
			
		||||
from typing import (
 | 
			
		||||
    Any,
 | 
			
		||||
    TYPE_CHECKING,
 | 
			
		||||
| 
						 | 
				
			
			@ -35,7 +34,10 @@ from .log import (
 | 
			
		|||
    get_logger,
 | 
			
		||||
)
 | 
			
		||||
from . import _state
 | 
			
		||||
from .devx import _debug
 | 
			
		||||
from .devx import (
 | 
			
		||||
    _debug,
 | 
			
		||||
    pformat,
 | 
			
		||||
)
 | 
			
		||||
from .to_asyncio import run_as_asyncio_guest
 | 
			
		||||
from ._addr import UnwrappedAddress
 | 
			
		||||
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(
 | 
			
		||||
    actor: Actor,
 | 
			
		||||
    *,
 | 
			
		||||
| 
						 | 
				
			
			@ -234,22 +135,22 @@ def _trio_main(
 | 
			
		|||
            f'  loglevel: {actor.loglevel}\n'
 | 
			
		||||
        )
 | 
			
		||||
        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
 | 
			
		||||
                tree_str=actor_info,
 | 
			
		||||
                back_from_op=2,  # since "complete"
 | 
			
		||||
                text=actor_info,
 | 
			
		||||
                nest_indent=2,  # since "complete"
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
    logmeth = log.info
 | 
			
		||||
    exit_status: str = (
 | 
			
		||||
        'Subactor exited\n'
 | 
			
		||||
        +
 | 
			
		||||
        nest_from_op(
 | 
			
		||||
        pformat.nest_from_op(
 | 
			
		||||
            input_op=')>',  # like a "closed-to-play"-icon from super perspective
 | 
			
		||||
            tree_str=actor_info,
 | 
			
		||||
            back_from_op=1,
 | 
			
		||||
            text=actor_info,
 | 
			
		||||
            nest_indent=1,
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    try:
 | 
			
		||||
| 
						 | 
				
			
			@ -264,9 +165,9 @@ def _trio_main(
 | 
			
		|||
        exit_status: str = (
 | 
			
		||||
            'Actor received KBI (aka an OS-cancel)\n'
 | 
			
		||||
            +
 | 
			
		||||
            nest_from_op(
 | 
			
		||||
            pformat.nest_from_op(
 | 
			
		||||
                input_op='c)>',  # closed due to cancel (see above)
 | 
			
		||||
                tree_str=actor_info,
 | 
			
		||||
                text=actor_info,
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
    except BaseException as err:
 | 
			
		||||
| 
						 | 
				
			
			@ -274,9 +175,9 @@ def _trio_main(
 | 
			
		|||
        exit_status: str = (
 | 
			
		||||
            'Main actor task exited due to crash?\n'
 | 
			
		||||
            +
 | 
			
		||||
            nest_from_op(
 | 
			
		||||
            pformat.nest_from_op(
 | 
			
		||||
                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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,8 +15,10 @@
 | 
			
		|||
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
'''
 | 
			
		||||
Pretty formatters for use throughout the code base.
 | 
			
		||||
Mostly handy for logging and exception message content.
 | 
			
		||||
Pretty formatters for use throughout our internals.
 | 
			
		||||
 | 
			
		||||
Handy for logging and exception message content but also for `repr()`
 | 
			
		||||
in REPL(s).
 | 
			
		||||
 | 
			
		||||
'''
 | 
			
		||||
import sys
 | 
			
		||||
| 
						 | 
				
			
			@ -224,8 +226,8 @@ def pformat_cs(
 | 
			
		|||
    field_prefix: str = ' |_',
 | 
			
		||||
) -> str:
 | 
			
		||||
    '''
 | 
			
		||||
    Pretty format info about a `trio.CancelScope` including most
 | 
			
		||||
    of its public state and `._cancel_status`.
 | 
			
		||||
    Pretty format info about a `trio.CancelScope` including most of
 | 
			
		||||
    its public state and `._cancel_status`.
 | 
			
		||||
 | 
			
		||||
    The output can be modified to show a "var name" for the
 | 
			
		||||
    instance as a field prefix, just a simple str before each
 | 
			
		||||
| 
						 | 
				
			
			@ -247,3 +249,279 @@ def pformat_cs(
 | 
			
		|||
        +
 | 
			
		||||
        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,
 | 
			
		||||
 | 
			
		||||
) -> 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
 | 
			
		||||
| 
						 | 
				
			
			@ -282,7 +284,7 @@ def get_logger(
 | 
			
		|||
        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:
 | 
			
		||||
        # - always avoid duplicate project-package token
 | 
			
		||||
        #   in msg output: i.e. tractor.tractor.ipc._chan.py in header
 | 
			
		||||
| 
						 | 
				
			
			@ -331,7 +333,7 @@ def get_logger(
 | 
			
		|||
 | 
			
		||||
def get_console_log(
 | 
			
		||||
    level: str|None = None,
 | 
			
		||||
    logger: Logger|None = None,
 | 
			
		||||
    logger: Logger|StackLevelAdapter|None = None,
 | 
			
		||||
    **kwargs,
 | 
			
		||||
 | 
			
		||||
) -> LoggerAdapter:
 | 
			
		||||
| 
						 | 
				
			
			@ -344,12 +346,23 @@ def get_console_log(
 | 
			
		|||
    Yeah yeah, i know we can use `logging.config.dictConfig()`. You do it.
 | 
			
		||||
 | 
			
		||||
    '''
 | 
			
		||||
    log = get_logger(
 | 
			
		||||
        logger=logger,
 | 
			
		||||
        **kwargs
 | 
			
		||||
    )  # set a root logger
 | 
			
		||||
    logger: Logger = log.logger
 | 
			
		||||
    # get/create a stack-aware-adapter
 | 
			
		||||
    if (
 | 
			
		||||
        logger
 | 
			
		||||
        and
 | 
			
		||||
        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:
 | 
			
		||||
        return log
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -367,10 +380,7 @@ def get_console_log(
 | 
			
		|||
            None,
 | 
			
		||||
        )
 | 
			
		||||
    ):
 | 
			
		||||
        fmt = LOG_FORMAT
 | 
			
		||||
        # if logger:
 | 
			
		||||
        #     fmt = None
 | 
			
		||||
 | 
			
		||||
        fmt: str = LOG_FORMAT  # always apply our format?
 | 
			
		||||
        handler = StreamHandler()
 | 
			
		||||
        formatter = colorlog.ColoredFormatter(
 | 
			
		||||
            fmt=fmt,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,6 +20,7 @@ Prettified version of `msgspec.Struct` for easier console grokin.
 | 
			
		|||
'''
 | 
			
		||||
from __future__ import annotations
 | 
			
		||||
from collections import UserList
 | 
			
		||||
import textwrap
 | 
			
		||||
from typing import (
 | 
			
		||||
    Any,
 | 
			
		||||
    Iterator,
 | 
			
		||||
| 
						 | 
				
			
			@ -105,27 +106,11 @@ def iter_fields(struct: Struct) -> Iterator[
 | 
			
		|||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def pformat(
 | 
			
		||||
def iter_struct_ppfmt_lines(
 | 
			
		||||
    struct: Struct,
 | 
			
		||||
    field_indent: int = 2,
 | 
			
		||||
    indent: int = 0,
 | 
			
		||||
    field_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
 | 
			
		||||
    k: str
 | 
			
		||||
    v: Any
 | 
			
		||||
| 
						 | 
				
			
			@ -135,15 +120,18 @@ def pformat(
 | 
			
		|||
        # ..]` over .__name__ == `Literal` but still get only the
 | 
			
		||||
        # latter for simple types like `str | int | None` etc..?
 | 
			
		||||
        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
 | 
			
		||||
        if isinstance(v, Struct):
 | 
			
		||||
            val_str: str =  v.pformat(
 | 
			
		||||
                indent=field_indent + indent,
 | 
			
		||||
                field_indent=indent + field_indent,
 | 
			
		||||
            yield from iter_struct_ppfmt_lines(
 | 
			
		||||
                struct=v,
 | 
			
		||||
                field_indent=field_indent+field_indent,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            val_str: str = repr(v)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -161,8 +149,39 @@ def pformat(
 | 
			
		|||
                # raise
 | 
			
		||||
                # return _Struct.__repr__(struct)
 | 
			
		||||
 | 
			
		||||
        # TODO: LOLOL use `textwrap.indent()` instead dawwwwwg!
 | 
			
		||||
        obj_str += (field_ws + f'{k}: {typ_name} = {val_str},\n')
 | 
			
		||||
        yield (
 | 
			
		||||
            ' '*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 (
 | 
			
		||||
        f'{qtn}(\n'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue