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
parent
63c23d6b82
commit
544ff5ab4c
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue