From 15549f7c2654292be259f4268c7e2abb4915ede4 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Tue, 9 Apr 2024 10:09:05 -0400 Subject: [PATCH] Expose `MsgType` and extend `MsgCodec` API a bit Make a new `MsgType: TypeAlias` for the union of all msg types such that it can be used in annots throughout the code base; just make `.msg.__msg_spec__` delegate to it. Add some new codec methods: - `pld_spec_str`: for the `str`-casted value of the payload spec, generally useful in logging content. - `msg_spec_items()`: to render a `dict` of msg types to their `str()`-casted values with support for singling out a specific `MsgType`, type by input `msg` instance. - `pformat_msg_spec()`: for rendering the (partial) `.msg_spec` as a formatted `str` useful in logging. Oh right, add a `Error._msg_dict: dict` in support of the previous commit (for `MsgTypeError` packing as RAEs) such that our error msg type can house a non-type-spec decoded wire-bytes for error reporting/analysis purposes. --- tractor/msg/__init__.py | 10 ++++----- tractor/msg/_codec.py | 46 +++++++++++++++++++++++++++++++++++++---- tractor/msg/types.py | 25 +++++++++++++++++++++- 3 files changed, 71 insertions(+), 10 deletions(-) diff --git a/tractor/msg/__init__.py b/tractor/msg/__init__.py index fe965e0..443b781 100644 --- a/tractor/msg/__init__.py +++ b/tractor/msg/__init__.py @@ -19,7 +19,6 @@ Built-in messaging patterns, types, APIs and helpers. ''' from typing import ( - Union, TypeAlias, ) from .ptr import ( @@ -56,8 +55,9 @@ from .types import ( # full msg class set from above as list __msg_types__ as __msg_types__, + + # type-alias for union of all msgs + MsgType as MsgType, ) -# TODO: use new type declaration syntax for msg-type-spec -# https://docs.python.org/3/library/typing.html#type-aliases -# https://docs.python.org/3/reference/simple_stmts.html#type -__msg_spec__: TypeAlias = Union[*__msg_types__] + +__msg_spec__: TypeAlias = MsgType diff --git a/tractor/msg/_codec.py b/tractor/msg/_codec.py index 56f24d6..de3316c 100644 --- a/tractor/msg/_codec.py +++ b/tractor/msg/_codec.py @@ -57,7 +57,7 @@ from trio.lowlevel import ( from tractor.msg.pretty_struct import Struct from tractor.msg.types import ( mk_msg_spec, - Msg, + MsgType, ) @@ -87,12 +87,50 @@ class MsgCodec(Struct): pld_spec: Union[Type[Struct]]|None + @property + def pld_spec_str(self) -> str: + spec: Union[Type]|Type = self.pld_spec + + # TODO: could also use match: instead? + if getattr(spec, '__args__', False): + # `typing.Union` case + return str(spec) + else: + return spec.__name__ + # struct type unions # https://jcristharif.com/msgspec/structs.html#tagged-unions @property def msg_spec(self) -> Union[Type[Struct]]: return self._dec.type + def msg_spec_items( + self, + msg: MsgType|None = None, + + ) -> dict[str, MsgType]|str: + + msgt_table: dict[str, MsgType] = { + msgt: str(msgt) + for msgt in self.msg_spec.__args__ + } + if msg: + msgt: MsgType = type(msg) + str_repr: str = msgt_table[msgt] + return {msgt: str_repr} + + return msgt_table + + # TODO: some way to make `pretty_struct.Struct` use this + # wrapped field over the `.msg_spec` one? + def pformat_msg_spec( + self, + msg: MsgType|None = None, + ) -> str: + return '\n'.join( + self.msg_spec_items(msg=msg).values() + ) + lib: ModuleType = msgspec # TODO: a sub-decoder system as well? @@ -108,7 +146,7 @@ class MsgCodec(Struct): # OR # ) = { # # pre-seed decoders for std-py-type-set for use when - # # `Msg.pld == None|Any`. + # # `MsgType.pld == None|Any`. # None: msgpack.Decoder(Any), # Any: msgpack.Decoder(Any), # } @@ -303,7 +341,7 @@ def mk_codec( # by `tag_field: str` value key? # payload_msg_specs: dict[ # str, # tag_field value as sub-decoder key - # Union[Type[Struct]] # `Msg.pld` type spec + # Union[Type[Struct]] # `MsgType.pld` type spec # ]|None = None, libname: str = 'msgspec', @@ -336,7 +374,7 @@ def mk_codec( raise RuntimeError( f'If a payload spec is provided,\n' "the builtin SC-shuttle-protocol's msg set\n" - f'(i.e. `{Msg}`) MUST be used!\n\n' + f'(i.e. a `{MsgType}`) MUST be used!\n\n' f'However both values were passed as => mk_codec(\n' f' ipc_msg_spec={ipc_msg_spec}`\n' f' ipc_pld_spec={ipc_pld_spec}`\n)\n' diff --git a/tractor/msg/types.py b/tractor/msg/types.py index 7355a61..14db09c 100644 --- a/tractor/msg/types.py +++ b/tractor/msg/types.py @@ -31,6 +31,7 @@ from typing import ( Literal, Type, TypeVar, + TypeAlias, Union, ) @@ -400,16 +401,29 @@ class CancelAck( pld: bool +# TODO: unify this with `._exceptions.RemoteActorError` +# such that we can have a msg which is both raisable and +# IPC-wire ready? +# B~o class Error( Struct, tag=True, tag_field='msg_type', + + # TODO may omit defaults? + # https://jcristharif.com/msgspec/structs.html#omitting-default-values + # omit_defaults=True, ): ''' A pkt that wraps `RemoteActorError`s for relay and raising. Fields are 1-to-1 meta-data as needed originally by - `RemoteActorError.msgdata: dict`. + `RemoteActorError.msgdata: dict` but now are defined here. + + Note: this msg shuttles `ContextCancelled` and `StreamOverrun` + as well is used to rewrap any `MsgTypeError` for relay-reponse + to bad `Yield.pld` senders during an IPC ctx's streaming dialog + phase. ''' src_uid: tuple[str, str] @@ -428,6 +442,10 @@ class Error( # `StreamOverrun` sender: tuple[str, str]|None = None + # for the `MsgTypeError` case where the receiver side + # decodes the underlying original `Msg`-subtype + _msg_dict: dict|None = None + # TODO: should be make a msg version of `ContextCancelled?` # and/or with a scope field or a full `ActorCancelled`? @@ -486,6 +504,11 @@ __msg_types__: list[Msg] = ( _payload_msgs ) +# TODO: use new type declaration syntax for msg-type-spec +# https://docs.python.org/3/library/typing.html#type-aliases +# https://docs.python.org/3/reference/simple_stmts.html#type +MsgType: TypeAlias = Union[*__msg_types__] + def mk_msg_spec( payload_type_union: Union[Type] = Any,