diff --git a/tractor/_entry.py b/tractor/_entry.py index 0f6a91c7..22b1bfee 100644 --- a/tractor/_entry.py +++ b/tractor/_entry.py @@ -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) - # |_ repr of toe, in py these look like - # - # >) closing/exiting/stopping, - # )> closed/exited/stopped, - # |_ - # [OR <), )< ?? ] - # - # ending = ) - # >c) cancelling to close/exit - # c)> cancelled (caused close), OR? - # |_ - # OR maybe "x) erroring to eventuall exit - # x)> errored and terminated - # |_ - # - # scopes: supers/nurseries, IPC-ctxs, sessions, perms, etc. - # >{ opening - # {> opened - # }> closed - # >} closing - # - # storage: like queues, shm-buffers, files, etc.. - # >[ opening - # [> opened - # |_ - # - # >] 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,9 +135,9 @@ 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" @@ -246,7 +147,7 @@ def _trio_main( 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, @@ -264,7 +165,7 @@ 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, ) @@ -274,7 +175,7 @@ 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, ) diff --git a/tractor/devx/pformat.py b/tractor/devx/pformat.py index e04b4fe8..526f7e30 100644 --- a/tractor/devx/pformat.py +++ b/tractor/devx/pformat.py @@ -15,8 +15,10 @@ # along with this program. If not, see . ''' -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,169 @@ 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) + # |_ repr of toe, in py these look like + # + # >) closing/exiting/stopping, + # )> closed/exited/stopped, + # |_ + # [OR <), )< ?? ] + # + # ending = ) + # >c) cancelling to close/exit + # c)> cancelled (caused close), OR? + # |_ + # OR maybe "x) erroring to eventuall exit + # x)> errored and terminated + # |_ + # + # scopes: supers/nurseries, IPC-ctxs, sessions, perms, etc. + # >{ opening + # {> opened + # }> closed + # >} closing + # + # storage: like queues, shm-buffers, files, etc.. + # >[ opening + # [> opened + # |_ + # + # >] 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}' + )