From 2eaef26547863ac5b236198364e4ff9092c04de1 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Wed, 6 Jul 2022 17:35:09 -0400 Subject: [PATCH] WIP tagged union message type API XXX NOTE XXX: this is a heavily modified commit from the original (ec226463) which was super out of date when rebased onto the current branch. I went through a manual conflict rework and removed all the legacy segments as well as rename-moved this original mod `tractor.msg.py` -> `tractor.msg/_old_msg.py`. Further the `NamespacePath` type def was discarded from this mod since it was from a super old version which was already moved to a `.msg.ptr` submod. As per original questions and discussion with `msgspec` author: - https://github.com/jcrist/msgspec/issues/25 - https://github.com/jcrist/msgspec/issues/140 this prototypes a new (but very naive) `msgspec.Struct` codec implementation which will be more filled out in the next commit. --- tractor/_ipc.py | 4 +- tractor/msg/_old_msg.py | 121 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 tractor/msg/_old_msg.py diff --git a/tractor/_ipc.py b/tractor/_ipc.py index 5aafda3..b1c2ccd 100644 --- a/tractor/_ipc.py +++ b/tractor/_ipc.py @@ -165,7 +165,9 @@ class MsgpackTCPStream(MsgTransport): self.codec: MsgCodec = codec or MsgCodec() async def _iter_packets(self) -> AsyncGenerator[dict, None]: - '''Yield packets from the underlying stream. + ''' + Yield `bytes`-blob decoded packets from the underlying TCP + stream using the current task's `MsgCodec`. ''' import msgspec # noqa diff --git a/tractor/msg/_old_msg.py b/tractor/msg/_old_msg.py new file mode 100644 index 0000000..823228a --- /dev/null +++ b/tractor/msg/_old_msg.py @@ -0,0 +1,121 @@ +# tractor: structured concurrent "actors". +# Copyright 2018-eternity Tyler Goodlet. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +''' +Capability-based messaging specifications: or colloquially as "msgspecs". + +Includes our SCIPP (structured-con-inter-process-protocol) message type defs +and APIs for applying custom msgspec-sets for implementing un-protocol state machines. + +''' + +# TODO: integration with our ``enable_modules: list[str]`` caps sys. + +# ``pkgutil.resolve_name()`` internally uses +# ``importlib.import_module()`` which can be filtered by inserting +# a ``MetaPathFinder`` into ``sys.meta_path`` (which we could do before +# entering the ``Actor._process_messages()`` loop). +# https://github.com/python/cpython/blob/main/Lib/pkgutil.py#L645 +# https://stackoverflow.com/questions/1350466/preventing-python-code-from-importing-certain-modules +# - https://stackoverflow.com/a/63320902 +# - https://docs.python.org/3/library/sys.html#sys.meta_path + +# the new "Implicit Namespace Packages" might be relevant? +# - https://www.python.org/dev/peps/pep-0420/ + +# add implicit serialized message type support so that paths can be +# handed directly to IPC primitives such as streams and `Portal.run()` +# calls: +# - via ``msgspec``: +# - https://jcristharif.com/msgspec/api.html#struct +# - https://jcristharif.com/msgspec/extending.html +# via ``msgpack-python``: +# https://github.com/msgpack/msgpack-python#packingunpacking-of-custom-data-type + +from __future__ import annotations +from contextlib import contextmanager as cm +from typing import ( + Union, + Any, +) + +from msgspec import Struct +from msgspec.msgpack import ( + Encoder, + Decoder, +) + + +# LIFO codec stack that is appended when the user opens the +# ``configure_native_msgs()`` cm below to configure a new codec set +# which will be applied to all new (msgspec relevant) IPC transports +# that are spawned **after** the configure call is made. +_lifo_codecs: list[ + tuple[ + Encoder, + Decoder, + ], +] = [(Encoder(), Decoder())] + + +def get_msg_codecs() -> tuple[ + Encoder, + Decoder, +]: + ''' + Return the currently configured ``msgspec`` codec set. + + The defaults are defined above. + + ''' + global _lifo_codecs + return _lifo_codecs[-1] + + +@cm +def configure_native_msgs( + tagged_structs: list[Struct], +): + ''' + Push a codec set that will natively decode + tagged structs provied in ``tagged_structs`` + in all IPC transports and pop the codec on exit. + + ''' + global _lifo_codecs + + # See "tagged unions" docs: + # https://jcristharif.com/msgspec/structs.html#tagged-unions + + # "The quickest way to enable tagged unions is to set tag=True when + # defining every struct type in the union. In this case tag_field + # defaults to "type", and tag defaults to the struct class name + # (e.g. "Get")." + enc = Encoder() + + types_union = Union[tagged_structs[0]] | Any + for struct in tagged_structs[1:]: + types_union |= struct + + dec = Decoder(types_union) + + _lifo_codecs.append((enc, dec)) + try: + print("YOYOYOOYOYOYOY") + yield enc, dec + finally: + print("NONONONONON") + _lifo_codecs.pop()