forked from goodboy/tractor
1
0
Fork 0

Change to `RemoteActorError.pformat()`

For more sane manual calls as needed in logging purposes. Obvi remap
the dunder methods to it.

Other:
- drop `hide_tb: bool` from `unpack_error()`, shouldn't need it since
  frame won't ever be part of any tb raised from returned error.
- add a `is_invalid_payload: bool` to `_raise_from_unexpected_msg()` to
  be used from `PldRx` where we don't need to decode the IPC
  msg, just the payload; make the error message reflect this case.
- drop commented `._portal._unwrap_msg()` since we've replaced it with
  `PldRx`'s delegation to newer `._raise_from_unexpected_msg()`.
- hide the `Portal.result()` frame by default, again.
runtime_to_msgspec
Tyler Goodlet 2024-05-06 12:55:16 -04:00
parent 63c23d6b82
commit 544ff5ab4c
2 changed files with 89 additions and 104 deletions

View File

@ -46,7 +46,6 @@ from tractor.msg import (
Error, Error,
MsgType, MsgType,
Stop, Stop,
# Yield,
types as msgtypes, types as msgtypes,
MsgCodec, MsgCodec,
MsgDec, MsgDec,
@ -212,6 +211,8 @@ class RemoteActorError(Exception):
) -> None: ) -> None:
super().__init__(message) super().__init__(message)
# for manual display without having to muck with `Exception.args`
self._message: str = message
# TODO: maybe a better name? # TODO: maybe a better name?
# - .errtype # - .errtype
# - .retype # - .retype
@ -454,32 +455,46 @@ class RemoteActorError(Exception):
_repr _repr
) )
def __repr__(self) -> str: def pformat(self) -> str:
''' '''
Nicely formatted boxed error meta data + traceback. Nicely formatted boxed error meta data + traceback, OR just
the normal message from `.args` (for eg. as you'd want shown
by a locally raised `ContextCancelled`).
''' '''
from tractor.devx._code import pformat_boxed_tb tb_str: str = self.tb_str
fields: str = self._mk_fields_str( if tb_str:
_body_fields fields: str = self._mk_fields_str(
+ _body_fields
self.extra_body_fields, +
) self.extra_body_fields,
body: str = pformat_boxed_tb( )
tb_str=self.tb_str, from tractor.devx import (
fields_str=fields, pformat_boxed_tb,
field_prefix=' |_', )
# ^- is so that it's placed like so, body: str = pformat_boxed_tb(
# just after <Type( tb_str=tb_str,
# |___ .. fields_str=fields,
tb_body_indent=1, field_prefix=' |_',
) # ^- is so that it's placed like so,
# just after <Type(
# |___ ..
tb_body_indent=1,
)
else:
body: str = textwrap.indent(
self._message,
prefix=' ',
) + '\n'
return ( return (
f'<{type(self).__name__}(\n' f'<{type(self).__name__}(\n'
f'{body}' f'{body}'
')>' ')>'
) )
__repr__ = pformat
__str__ = pformat
def unwrap( def unwrap(
self, self,
) -> BaseException: ) -> BaseException:
@ -809,12 +824,9 @@ def pack_error(
def unpack_error( def unpack_error(
msg: Error, msg: Error,
chan: Channel,
chan: Channel|None = None,
box_type: RemoteActorError = RemoteActorError, box_type: RemoteActorError = RemoteActorError,
hide_tb: bool = True,
) -> None|Exception: ) -> None|Exception:
''' '''
Unpack an 'error' message from the wire Unpack an 'error' message from the wire
@ -824,12 +836,10 @@ def unpack_error(
which is the responsibilitiy of the caller. which is the responsibilitiy of the caller.
''' '''
__tracebackhide__: bool = hide_tb
if not isinstance(msg, Error): if not isinstance(msg, Error):
return None return None
# retrieve the remote error's encoded details from fields # retrieve the remote error's msg-encoded details
tb_str: str = msg.tb_str tb_str: str = msg.tb_str
message: str = ( message: str = (
f'{chan.uid}\n' f'{chan.uid}\n'
@ -858,7 +868,6 @@ def unpack_error(
# original source error. # original source error.
elif boxed_type_str == 'RemoteActorError': elif boxed_type_str == 'RemoteActorError':
assert boxed_type is RemoteActorError assert boxed_type is RemoteActorError
# assert len(error_dict['relay_path']) >= 1
assert len(msg.relay_path) >= 1 assert len(msg.relay_path) >= 1
exc = box_type( exc = box_type(
@ -943,8 +952,6 @@ def _raise_from_unexpected_msg(
raise unpack_error( raise unpack_error(
msg, msg,
ctx.chan, ctx.chan,
hide_tb=hide_tb,
) from src_err ) from src_err
# `MsgStream` termination msg. # `MsgStream` termination msg.
@ -1014,6 +1021,7 @@ def _mk_msg_type_err(
src_validation_error: ValidationError|None = None, src_validation_error: ValidationError|None = None,
src_type_error: TypeError|None = None, src_type_error: TypeError|None = None,
is_invalid_payload: bool = False,
) -> MsgTypeError: ) -> MsgTypeError:
''' '''
@ -1028,12 +1036,12 @@ def _mk_msg_type_err(
'`codec` must be a `MsgCodec` for send-side errors?' '`codec` must be a `MsgCodec` for send-side errors?'
) )
from tractor.devx import (
pformat_caller_frame,
)
# no src error from `msgspec.msgpack.Decoder.decode()` so # no src error from `msgspec.msgpack.Decoder.decode()` so
# prolly a manual type-check on our part. # prolly a manual type-check on our part.
if message is None: if message is None:
from tractor.devx._code import (
pformat_caller_frame,
)
tb_fmt: str = pformat_caller_frame(stack_limit=3) tb_fmt: str = pformat_caller_frame(stack_limit=3)
message: str = ( message: str = (
f'invalid msg -> {msg}: {type(msg)}\n\n' f'invalid msg -> {msg}: {type(msg)}\n\n'
@ -1071,47 +1079,57 @@ def _mk_msg_type_err(
# `Channel.recv()` case # `Channel.recv()` case
else: else:
# decode the msg-bytes using the std msgpack if is_invalid_payload:
# interchange-prot (i.e. without any msg_type: str = type(msg)
# `msgspec.Struct` handling) so that we can message: str = (
# determine what `.msg.types.Msg` is the culprit f'invalid `{msg_type.__qualname__}` payload\n\n'
# by reporting the received value. f'<{type(msg).__qualname__}(\n'
msg_dict: dict = msgpack.decode(msg) f' |_pld: {codec.pld_spec_str} = {msg.pld!r}'
msg_type_name: str = msg_dict['msg_type'] f')>\n'
msg_type = getattr(msgtypes, msg_type_name) )
message: str = (
f'invalid `{msg_type_name}` IPC msg\n\n' else:
) # decode the msg-bytes using the std msgpack
# interchange-prot (i.e. without any
# `msgspec.Struct` handling) so that we can
# determine what `.msg.types.Msg` is the culprit
# by reporting the received value.
msg_dict: dict = msgpack.decode(msg)
msg_type_name: str = msg_dict['msg_type']
msg_type = getattr(msgtypes, msg_type_name)
message: str = (
f'invalid `{msg_type_name}` IPC msg\n\n'
)
# XXX be "fancy" and see if we can determine the exact
# invalid field such that we can comprehensively report
# the specific field's type problem.
msgspec_msg: str = src_validation_error.args[0].rstrip('`')
msg, _, maybe_field = msgspec_msg.rpartition('$.')
obj = object()
if (field_val := msg_dict.get(maybe_field, obj)) is not obj:
field_name_expr: str = (
f' |_{maybe_field}: {codec.pld_spec_str} = '
)
fmt_val_lines: list[str] = pformat(field_val).splitlines()
fmt_val: str = (
f'{fmt_val_lines[0]}\n'
+
textwrap.indent(
'\n'.join(fmt_val_lines[1:]),
prefix=' '*len(field_name_expr),
)
)
message += (
f'{msg.rstrip("`")}\n\n'
f'<{msg_type.__qualname__}(\n'
# f'{".".join([msg_type.__module__, msg_type.__qualname__])}\n'
f'{field_name_expr}{fmt_val}\n'
f')>'
)
if verb_header: if verb_header:
message = f'{verb_header} ' + message message = f'{verb_header} ' + message
# XXX see if we can determine the exact invalid field
# such that we can comprehensively report the
# specific field's type problem
msgspec_msg: str = src_validation_error.args[0].rstrip('`')
msg, _, maybe_field = msgspec_msg.rpartition('$.')
obj = object()
if (field_val := msg_dict.get(maybe_field, obj)) is not obj:
field_name_expr: str = (
f' |_{maybe_field}: {codec.pld_spec_str} = '
)
fmt_val_lines: list[str] = pformat(field_val).splitlines()
fmt_val: str = (
f'{fmt_val_lines[0]}\n'
+
textwrap.indent(
'\n'.join(fmt_val_lines[1:]),
prefix=' '*len(field_name_expr),
)
)
message += (
f'{msg.rstrip("`")}\n\n'
f'<{msg_type.__qualname__}(\n'
# f'{".".join([msg_type.__module__, msg_type.__qualname__])}\n'
f'{field_name_expr}{fmt_val}\n'
f')>'
)
msgtyperr = MsgTypeError.from_decode( msgtyperr = MsgTypeError.from_decode(
message=message, message=message,
msgdict=msg_dict, msgdict=msg_dict,

View File

@ -68,40 +68,6 @@ if TYPE_CHECKING:
log = get_logger(__name__) log = get_logger(__name__)
# TODO: remove and/or rework?
# -[ ] rename to `unwrap_result()` and use
# `._raise_from_unexpected_msg()` (after tweak to accept a `chan:
# Channel` arg) in key block??
# -[ ] pretty sure this is entirely covered by
# `_exceptions._raise_from_unexpected_msg()` so REMOVE!
# def _unwrap_msg(
# msg: Return|Error,
# ctx: Context,
# hide_tb: bool = True,
# ) -> Any:
# '''
# Unwrap a final result from a `{return: <Any>}` IPC msg.
# '''
# __tracebackhide__: bool = hide_tb
# try:
# return msg.pld
# except AttributeError as err:
# # internal error should never get here
# # assert msg.get('cid'), (
# assert msg.cid, (
# "Received internal error at portal?"
# )
# raise unpack_error(
# msg,
# ctx.chan,
# ) from err
class Portal: class Portal:
''' '''
A 'portal' to a memory-domain-separated `Actor`. A 'portal' to a memory-domain-separated `Actor`.
@ -173,12 +139,13 @@ class Portal:
portal=self, portal=self,
) )
# @api_frame
async def result(self) -> Any: async def result(self) -> Any:
''' '''
Return the result(s) from the remote actor's "main" task. Return the result(s) from the remote actor's "main" task.
''' '''
# __tracebackhide__ = True __tracebackhide__ = True
# Check for non-rpc errors slapped on the # Check for non-rpc errors slapped on the
# channel for which we always raise # channel for which we always raise
exc = self.channel._exc exc = self.channel._exc