Compare commits

...

9 Commits

Author SHA1 Message Date
Tyler Goodlet b761524a85 TOSQASH 22e62ed: with-stmt-ws-removal 2025-09-29 20:23:45 -04:00
Tyler Goodlet b4dbf5dd86 WIP, expand pldrx suite for tagged-multi-msgs
Nowhere near ready yet since this generates many test-body-logic
un-handled true-positives which currently fail, but it's a first draft
for the general case set. To start, includes a greater-than-one-`Msg` (a
strict top level tagged-union-of-structs) pld spec alongside
a `AnyFieldMsg`-workaround struct-msg for packing all other builtin
non-`Struct`/`Any` python types alongside the other explicit msgs.
2025-09-29 20:13:58 -04:00
Tyler Goodlet 39952344cb Ext-types test suite clean out
Removing the now masked-for-a-while unit test remnants for
`test_limit_msgspec()` (and its helper `chk_pld_type()`) since these cases
are now covered in the `test_pldrx_limiting` suite at an e2e
IPC-system-spanning level.

Note that the contents of the `chk_pld_type()` might be useful in the
future once we start setting/allowing semantics for various "phases of
IPC with matching msgspecs", but that's a little ways off rn and this
commit can always be looked up, also iirc most of the details were
already somewhat out of date and causing suite failure.
2025-09-29 11:58:15 -04:00
Tyler Goodlet 15f58495d5 Add todo-note for non-strict `msgspec` decode-mode? 2025-09-29 11:41:46 -04:00
Tyler Goodlet 2be3f93a8f Set `hide_tb` at top of `.limit_plds()` body 2025-09-25 22:05:20 -04:00
Tyler Goodlet 224e92b468 Always merge input `spec` with any `ext_types`
That is, in `.msg._codec.mk_dec()` to ensure we actually still respect
the provided `spec: Union[Type[Struct]]|Type|None` alongside any
"custom" extension-types expected to be `dec_hook()` pre-processed.

Notes,
- previously when `dec_hook()` was provided we were merging with
  a `msgspec.Raw` instead of `spec` which **is entirely wrong**; it was
  likely leftover code from the sloppy/naive first draft of extension
  types support.
- notice the `spec: Union[Type[Struct]]|Type|None` type annotation (and
  it appears as though a `test_ext_types_msgspec` suite actually passes
  the value `spec=None` fyi) with a value of `None` to imply merging as
  `Union[ext_types]|None` (or equivalently a `Optional[Union]`), due
  to the incorrect `Raw`-default usage this was actually being ignored..
  -> this case has now been clarified via comment in the fn-signature.
2025-09-25 19:39:18 -04:00
Tyler Goodlet ccedee3b87 Dynamically set `pld_spec` for `test_basic_payload_spec()
Such that we can parametrize the `@context(pld_spec)` endpoint setting
using `pytest` and of course enable testing more then just the lone
`maybe_msg_spec` case. The implementation was a bit tricky because
subactors import any `enable_modules` just after subproc spawn, so
there's no easy way to indicate from the parent should should be passed
to the `@context()` decorator since it's already resolved by the time an
IPC is established. Thus the bulk of this patch is implementing
a pre-ctx which monkey-patches the (test) `child()`-ep-defining-module
before running test logic.

Impl deats,
- drop `maybe_msg_spec` global instead providing the same value via
  a new `pld_spec: Union[Type]` parametrized input to the test suite.
- add a `decorate_child_ep()` helper which (re-)decorates the
  mod-defined `child()` IPC-context endpoint with the provided `pld_spec`.
- add a new "pre IPC context" endpoint: `set_chld_pldspec()` which can
  be opened (from another actor) just prior to opening the `child()` ep
  and it will decorate the latter (using `decorate_child_ep()`)
  presuming a `.msg._exts.enc_type_union()` generated  `pld_spec_strs`
  is provided.
- actually open the `set_chld_pldspec()` as a `deco_ctx` rom the
  root-actor and ensure we cancel it on block teardown in non-raising
  cases.
2025-09-25 19:23:09 -04:00
Tyler Goodlet 7d947d3776 Add `types`-mod to `.msg._exts.dec_type_union()`
Such that decoded output equivalent to `str|None` can actually be
unpacked from a `type_names = ['str', 'NoneType]` without just
ignoring the null-type entry.. Previously, the loop would fall through
silently ignoring the `None` -> `NoneType` string representation mapped
by `.enc_type_union()` and the output union would be incorrect.

Deats,
- include the stdlib's `types` in the lookup loop, obvi changing the
  output var's name to `_types` to not collide.
- add output checking versus input `type_names` such that we raise
  a value-error with a case specific `report: str` when either,
  * the output `_types: list[Type]` is empty,
  * the `len(_types) != len(type_names)`.
2025-09-25 18:23:44 -04:00
Tyler Goodlet 6b3cc72e5c Mv `load_module_from_path()` to a new `._code_load` submod 2025-09-25 12:19:12 -04:00
8 changed files with 284 additions and 282 deletions

View File

@ -454,7 +454,7 @@ async def send_back_values(
with (
maybe_apply_codec(nsp_codec) as codec,
limit_plds(
rent_pld_spec,
spec=rent_pld_spec,
dec_hook=dec_nsp if add_hooks else None,
ext_types=[NamespacePath] if add_hooks else None,
) as pld_dec,
@ -665,7 +665,9 @@ def test_ext_types_over_ipc(
expect_codec=nsp_codec,
enter_value=codec,
)
rent_pld_spec_type_strs: list[str] = _exts.enc_type_union(pld_spec)
rent_pld_spec_type_strs: list[str] = _exts.enc_type_union(
pld_spec
)
# XXX should raise an mte (`MsgTypeError`)
# when `add_hooks == False` bc the input
@ -695,7 +697,7 @@ def test_ext_types_over_ipc(
limit_plds(
pld_spec,
dec_hook=dec_nsp if add_hooks else None,
ext_types=[NamespacePath] if add_hooks else None,
ext_types=[NamespacePath] if add_hooks else None,
) as pld_dec,
):
ctx_pld_dec: MsgDec = ctx._pld_rx._pld_dec
@ -743,204 +745,10 @@ def test_ext_types_over_ipc(
assert exc.boxed_type is TypeError
# def chk_pld_type(
# payload_spec: Type[Struct]|Any,
# pld: Any,
# expect_roundtrip: bool|None = None,
# ) -> bool:
# pld_val_type: Type = type(pld)
# # TODO: verify that the overridden subtypes
# # DO NOT have modified type-annots from original!
# # 'Start', .pld: FuncSpec
# # 'StartAck', .pld: IpcCtxSpec
# # 'Stop', .pld: UNSEt
# # 'Error', .pld: ErrorData
# codec: MsgCodec = mk_codec(
# # NOTE: this ONLY accepts `PayloadMsg.pld` fields of a specified
# # type union.
# ipc_pld_spec=payload_spec,
# )
# # make a one-off dec to compare with our `MsgCodec` instance
# # which does the below `mk_msg_spec()` call internally
# ipc_msg_spec: Union[Type[Struct]]
# msg_types: list[PayloadMsg[payload_spec]]
# (
# ipc_msg_spec,
# msg_types,
# ) = mk_msg_spec(
# payload_type_union=payload_spec,
# )
# _enc = msgpack.Encoder()
# _dec = msgpack.Decoder(
# type=ipc_msg_spec or Any, # like `PayloadMsg[Any]`
# )
# assert (
# payload_spec
# ==
# codec.pld_spec
# )
# # assert codec.dec == dec
# #
# # ^-XXX-^ not sure why these aren't "equal" but when cast
# # to `str` they seem to match ?? .. kk
# assert (
# str(ipc_msg_spec)
# ==
# str(codec.msg_spec)
# ==
# str(_dec.type)
# ==
# str(codec.dec.type)
# )
# # verify the boxed-type for all variable payload-type msgs.
# if not msg_types:
# breakpoint()
# roundtrip: bool|None = None
# pld_spec_msg_names: list[str] = [
# td.__name__ for td in _payload_msgs
# ]
# for typedef in msg_types:
# skip_runtime_msg: bool = typedef.__name__ not in pld_spec_msg_names
# if skip_runtime_msg:
# continue
# pld_field = structs.fields(typedef)[1]
# assert pld_field.type is payload_spec # TODO-^ does this need to work to get all subtypes to adhere?
# kwargs: dict[str, Any] = {
# 'cid': '666',
# 'pld': pld,
# }
# enc_msg: PayloadMsg = typedef(**kwargs)
# _wire_bytes: bytes = _enc.encode(enc_msg)
# wire_bytes: bytes = codec.enc.encode(enc_msg)
# assert _wire_bytes == wire_bytes
# ve: ValidationError|None = None
# try:
# dec_msg = codec.dec.decode(wire_bytes)
# _dec_msg = _dec.decode(wire_bytes)
# # decoded msg and thus payload should be exactly same!
# assert (roundtrip := (
# _dec_msg
# ==
# dec_msg
# ==
# enc_msg
# ))
# if (
# expect_roundtrip is not None
# and expect_roundtrip != roundtrip
# ):
# breakpoint()
# assert (
# pld
# ==
# dec_msg.pld
# ==
# enc_msg.pld
# )
# # assert (roundtrip := (_dec_msg == enc_msg))
# except ValidationError as _ve:
# ve = _ve
# roundtrip: bool = False
# if pld_val_type is payload_spec:
# raise ValueError(
# 'Got `ValidationError` despite type-var match!?\n'
# f'pld_val_type: {pld_val_type}\n'
# f'payload_type: {payload_spec}\n'
# ) from ve
# else:
# # ow we good cuz the pld spec mismatched.
# print(
# 'Got expected `ValidationError` since,\n'
# f'{pld_val_type} is not {payload_spec}\n'
# )
# else:
# if (
# payload_spec is not Any
# and
# pld_val_type is not payload_spec
# ):
# raise ValueError(
# 'DID NOT `ValidationError` despite expected type match!?\n'
# f'pld_val_type: {pld_val_type}\n'
# f'payload_type: {payload_spec}\n'
# )
# # full code decode should always be attempted!
# if roundtrip is None:
# breakpoint()
# return roundtrip
# ?TODO? maybe remove since covered in the newer `test_pldrx_limiting`
# via end-2-end testing of all this?
# -[ ] IOW do we really NEED this lowlevel unit testing?
#
# def test_limit_msgspec(
# debug_mode: bool,
# ):
# '''
# Internals unit testing to verify that type-limiting an IPC ctx's
# msg spec with `Pldrx.limit_plds()` results in various
# encapsulated `msgspec` object settings and state.
# '''
# async def main():
# async with tractor.open_root_actor(
# debug_mode=debug_mode,
# ):
# # ensure we can round-trip a boxing `PayloadMsg`
# assert chk_pld_type(
# payload_spec=Any,
# pld=None,
# expect_roundtrip=True,
# )
# # verify that a mis-typed payload value won't decode
# assert not chk_pld_type(
# payload_spec=int,
# pld='doggy',
# )
# # parametrize the boxed `.pld` type as a custom-struct
# # and ensure that parametrization propagates
# # to all payload-msg-spec-able subtypes!
# class CustomPayload(Struct):
# name: str
# value: Any
# assert not chk_pld_type(
# payload_spec=CustomPayload,
# pld='doggy',
# )
# assert chk_pld_type(
# payload_spec=CustomPayload,
# pld=CustomPayload(name='doggy', value='urmom')
# )
# # yah, we can `.pause_from_sync()` now!
# # breakpoint()
# trio.run(main)
# TODO: further SC-msg-specific verification that the overridden
# subtypes DO NOT have modified type-annots from original!
# 'Start', .pld: FuncSpec
# 'StartAck', .pld: IpcCtxSpec
# 'Stop', .pld: UNSEt
# 'Error', .pld: ErrorData
# def test_per_msg_payload_spec_limits():

View File

@ -2,14 +2,12 @@
`tractor.log`-wrapping unit tests.
'''
import importlib
from pathlib import Path
import shutil
import sys
from types import ModuleType
import pytest
import tractor
from tractor import _code_load
def test_root_pkg_not_duplicated_in_logger_name():
@ -37,31 +35,6 @@ def test_root_pkg_not_duplicated_in_logger_name():
assert 'mod' not in sublog.name
# ?TODO, move this into internal libs?
# -[ ] we already use it in `modden.config._pymod` as well
def load_module_from_path(
path: Path,
module_name: str|None = None,
) -> ModuleType:
'''
Taken from SO,
https://stackoverflow.com/a/67208147
which is based on stdlib docs,
https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
'''
module_name = module_name or path.stem
spec = importlib.util.spec_from_file_location(
module_name,
str(path),
)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
return module
def test_implicit_mod_name_applied_for_child(
testdir: pytest.Pytester,
loglevel: str,
@ -109,7 +82,7 @@ def test_implicit_mod_name_applied_for_child(
# XXX NOTE, once the "top level" pkg mod has been
# imported, we can then use `import` syntax to
# import it's sub-pkgs and modules.
pkgmod = load_module_from_path(
pkgmod = _code_load.load_module_from_path(
Path(pkg / '__init__.py'),
module_name=proj_name,
)

View File

@ -7,7 +7,15 @@ related settings around IPC contexts.
from contextlib import (
asynccontextmanager as acm,
)
import sys
import types
from typing import (
Any,
Union,
Type,
)
import msgspec
from msgspec import (
Struct,
)
@ -22,11 +30,10 @@ from tractor import (
Portal,
)
from tractor.msg import (
_codec,
_ops as msgops,
Return,
)
from tractor.msg import (
_codec,
_exts,
)
from tractor.msg.types import (
log,
@ -41,13 +48,22 @@ class PldMsg(
# case of these details?
#
# https://jcristharif.com/msgspec/structs.html#tagged-unions
# tag=True,
# tag_field='msg_type',
tag=True,
tag_field='msg_type',
):
field: str
maybe_msg_spec = PldMsg|None
class Msg1(PldMsg):
field: str
class Msg2(PldMsg):
field: int
class AnyFieldMsg(PldMsg):
field: Any
@acm
@ -104,9 +120,15 @@ async def maybe_expect_raises(
)
@tractor.context(
pld_spec=maybe_msg_spec,
)
# NOTE, this decorator is applied dynamically by both the root and
# 'sub' actor such that we can dynamically apply various cases from
# a parametrized test.
#
# maybe_msg_spec = PldMsg|None
#
# @tractor.context(
# pld_spec=maybe_msg_spec,
# )
async def child(
ctx: Context,
started_value: int|PldMsg|None,
@ -114,13 +136,13 @@ async def child(
validate_pld_spec: bool,
raise_on_started_mte: bool = True,
pack_any_field: bool = False,
) -> None:
'''
Call ``Context.started()`` more then once (an error).
'''
expect_started_mte: bool = started_value == 10
# sanaity check that child RPC context is the current one
curr_ctx: Context = current_ipc_ctx()
assert ctx is curr_ctx
@ -128,6 +150,7 @@ async def child(
rx: msgops.PldRx = ctx._pld_rx
curr_pldec: _codec.MsgDec = rx.pld_dec
ctx_meta: dict = getattr(
child,
'_tractor_context_meta',
@ -136,10 +159,28 @@ async def child(
if ctx_meta:
assert (
ctx_meta['pld_spec']
is curr_pldec.spec
is curr_pldec.pld_spec
is
curr_pldec.spec
is
curr_pldec.pld_spec
)
pld_types: set[Type] = _codec.unpack_spec_types(
curr_pldec.pld_spec,
)
if (
AnyFieldMsg in pld_types
and
pack_any_field
):
started_value = AnyFieldMsg(field=started_value)
expect_started_mte: bool = (
started_value == 10
and
not pack_any_field
)
# 2 cases: hdndle send-side and recv-only validation
# - when `raise_on_started_mte == True`, send validate
# - else, parent-recv-side only validation
@ -219,16 +260,65 @@ async def child(
# msg-type-error from this RPC task ;)
return return_value
def decorate_child_ep(
pld_spec: Union[Type],
) -> types.ModuleType:
'''
Apply parametrized pld_spec to ctx ep like,
@tractor.context(
pld_spec=maybe_msg_spec,
)(child)
'''
this_mod = sys.modules[__name__]
global child # a mod-fn defined above
assert this_mod.child is child
this_mod.child = tractor.context(
pld_spec=pld_spec,
)(child)
return this_mod
@tractor.context
async def set_chld_pldspec(
ctx: tractor.Context,
pld_spec_strs: list[str],
):
'''
Dynamically apply the `@context(pld_spec=pld_spec)` deco to the
current actor's in-mem instance of this test module.
Allows dynamically applying the "payload-spec" in both a parent
and child actor after spawn.
'''
this_mod = sys.modules[__name__]
pld_spec: list[str] = _exts.dec_type_union(
pld_spec_strs,
mods=[
this_mod,
msgspec.inspect,
],
)
decorate_child_ep(pld_spec)
await ctx.started()
await trio.sleep_forever()
@pytest.mark.parametrize(
'return_value',
[
'yo',
None,
Msg2(field=10),
AnyFieldMsg(field='yo'),
],
ids=[
'return[invalid-"yo"]',
'return[valid-None]',
'return[maybe-valid-None]',
'return[maybe-valid-Msg2]',
'return[maybe-valid-any-packed-yo]',
],
)
@pytest.mark.parametrize(
@ -236,10 +326,14 @@ async def child(
[
10,
PldMsg(field='yo'),
Msg1(field='yo'),
AnyFieldMsg(field=10),
],
ids=[
'Started[invalid-10]',
'Started[valid-PldMsg]',
'Started[maybe-valid-PldMsg]',
'Started[maybe-valid-Msg1]',
'Started[maybe-valid-any-packed-10]',
],
)
@pytest.mark.parametrize(
@ -253,12 +347,31 @@ async def child(
'no-started-pld-validate',
],
)
@pytest.mark.parametrize(
'pld_spec',
[
PldMsg|None,
# demo how to have strict msgs alongside all other supported
# py-types by embedding the any-types inside a shuttle msg.
Msg1|Msg2|AnyFieldMsg,
# XXX, will never work since Struct overrides dict.
# https://jcristharif.com/msgspec/usage.html#typed-decoding
# Msg1|Msg2|msgspec.inspect.AnyType,
],
ids=[
'maybe_PldMsg_spec',
'Msg1_or_Msg2_or_AnyFieldMsg_spec',
]
)
def test_basic_payload_spec(
debug_mode: bool,
loglevel: str,
return_value: str|None,
started_value: int|PldMsg,
pld_check_started_value: bool,
pld_spec: Union[Type],
):
'''
Validate the most basic `PldRx` msg-type-spec semantics around
@ -267,16 +380,33 @@ def test_basic_payload_spec(
pld-spec.
'''
invalid_return: bool = return_value == 'yo'
invalid_started: bool = started_value == 10
pld_types: set[Type] = _codec.unpack_spec_types(pld_spec)
invalid_return: bool = (
return_value == 'yo'
)
invalid_started: bool = (
started_value == 10
)
# dynamically apply ep's pld-spec in 'root'.
decorate_child_ep(pld_spec)
assert (
child._tractor_context_meta['pld_spec'] == pld_spec
)
pld_spec_strs: list[str] = _exts.enc_type_union(
pld_spec,
)
assert len(pld_types) > 1
async def main():
nonlocal pld_spec
async with tractor.open_nursery(
debug_mode=debug_mode,
loglevel=loglevel,
) as an:
p: Portal = await an.start_actor(
'child',
'sub',
enable_modules=[__name__],
)
@ -286,9 +416,11 @@ def test_basic_payload_spec(
if invalid_started:
msg_type_str: str = 'Started'
bad_value: int = 10
elif invalid_return:
msg_type_str: str = 'Return'
bad_value: str = 'yo'
else:
# XXX but should never be used below then..
msg_type_str: str = ''
@ -302,6 +434,7 @@ def test_basic_payload_spec(
invalid_started
) else None
)
async with (
maybe_expect_raises(
raises=should_raise,
@ -315,6 +448,11 @@ def test_basic_payload_spec(
# only for debug
# post_mortem=True,
),
p.open_context(
set_chld_pldspec,
pld_spec_strs=pld_spec_strs,
) as (deco_ctx, _),
p.open_context(
child,
return_value=return_value,
@ -325,12 +463,18 @@ def test_basic_payload_spec(
# now opened with 'child' sub
assert current_ipc_ctx() is ctx
assert type(first) is PldMsg
# assert type(first) is PldMsg
assert isinstance(first, PldMsg)
assert first.field == 'yo'
try:
res: None|PldMsg = await ctx.result(hide_tb=False)
assert res is None
assert res == return_value
if res is None:
await tractor.pause()
if isinstance(res, PldMsg):
assert res.field == 10
except MsgTypeError as mte:
maybe_mte = mte
if not invalid_return:
@ -356,6 +500,9 @@ def test_basic_payload_spec(
ctx.outcome
)
if should_raise is None:
await deco_ctx.cancel()
if should_raise is None:
assert maybe_mte is None

View File

@ -0,0 +1,48 @@
# 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 <https://www.gnu.org/licenses/>.
'''
(Hot) coad (re-)load utils for python.
'''
import importlib
from pathlib import Path
import sys
from types import ModuleType
# ?TODO, move this into internal libs?
# -[ ] we already use it in `modden.config._pymod` as well
def load_module_from_path(
path: Path,
module_name: str|None = None,
) -> ModuleType:
'''
Taken from SO,
https://stackoverflow.com/a/67208147
which is based on stdlib docs,
https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
'''
module_name = module_name or path.stem
spec = importlib.util.spec_from_file_location(
module_name,
str(path),
)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
return module

View File

@ -181,7 +181,11 @@ class MsgDec(Struct):
def mk_dec(
spec: Union[Type[Struct]]|Type|None,
spec: (
Union[Type[Struct]]
|Type # lone type
|None # implying `Union[*ext_types]|None`
),
# NOTE, required for ad-hoc type extensions to the underlying
# serialization proto (which is default `msgpack`),
@ -194,16 +198,18 @@ def mk_dec(
Create an IPC msg decoder, a slightly higher level wrapper around
a `msgspec.msgpack.Decoder` which provides,
- easier introspection of the underlying type spec via
the `.spec` and `.spec_str` attrs,
- easier introspection of the underlying type spec via the
`.spec` and `.spec_str` attrs,
- `.hook` access to the `Decoder.dec_hook()`,
- automatic custom extension-types decode support when
`dec_hook()` is provided such that any `PayloadMsg.pld` tagged
as a type from from `ext_types` (presuming the `MsgCodec.encode()` also used
a `.enc_hook()`) is processed and constructed by a `PldRx` implicitily.
as a type from from `ext_types` (presuming the
`MsgCodec.encode()` also used a `.enc_hook()`) is processed and
constructed by a `PldRx` implicitily.
NOTE, as mentioned a `MsgDec` is normally used for `PayloadMsg.pld: PayloadT` field
decoding inside an IPC-ctx-oriented `PldRx`.
NOTE, as mentioned a `MsgDec` is normally used for
`PayloadMsg.pld: PayloadT` field decoding inside an
IPC-ctx-oriented `PldRx`.
'''
if (
@ -248,12 +254,16 @@ def mk_dec(
# will work? kk B)
#
# maybe_box_struct = mk_boxed_ext_struct(ext_types)
spec = Raw | Union[*ext_types]
spec = spec | Union[*ext_types]
return MsgDec(
_dec=msgpack.Decoder(
type=spec, # like `MsgType[Any]`
type=spec,
dec_hook=dec_hook,
# ?TODO, support it?
# https://jcristharif.com/msgspec/usage.html#strict-vs-lax-mode
# strict=False,
),
)

View File

@ -33,9 +33,7 @@ converters,
|_ https://jcristharif.com/msgspec/changelog.html
'''
from types import (
ModuleType,
)
import types
import typing
from typing import (
Type,
@ -44,35 +42,51 @@ from typing import (
def dec_type_union(
type_names: list[str],
mods: list[ModuleType] = []
mods: list[types.ModuleType] = []
) -> Type|Union[Type]:
'''
Look up types by name, compile into a list and then create and
return a `typing.Union` from the full set.
'''
# import importlib
types: list[Type] = []
_types: list[Type] = []
for type_name in type_names:
for mod in [
typing,
# importlib.import_module(__name__),
types,
] + mods:
if type_ref := getattr(
mod,
type_name,
False,
):
types.append(type_ref)
_types.append(type_ref)
break
# special case handling only..
# ipc_pld_spec: Union[Type] = eval(
# pld_spec_str,
# {}, # globals
# {'typing': typing}, # locals
# )
report: str = ''
if not _types:
report: str = 'No type-instances could be resolved from `type_names` ??\n'
return Union[*types]
elif len(type_names) != len(_types):
report: str = (
f'Some type-instances could not be resolved from `type_names` ??\n'
f'_types: {_types!r}\n'
)
if report:
raise ValueError(
report
+
f'type_names: {type_names!r}\n'
)
if not _types:
raise ValueError(
f'No type-instance could be resolved from `type_names` ??\n'
f'type_names: {type_names!r}\n'
)
return Union[*_types]
def enc_type_union(

View File

@ -119,7 +119,7 @@ class PldRx(Struct):
def limit_plds(
self,
spec: Union[Type[Struct]],
**dec_kwargs,
**mk_dec_kwargs,
) -> MsgDec:
'''
@ -135,7 +135,7 @@ class PldRx(Struct):
orig_dec: MsgDec = self._pld_dec
limit_dec: MsgDec = mk_dec(
spec=spec,
**dec_kwargs,
**mk_dec_kwargs,
)
try:
self._pld_dec = limit_dec
@ -582,6 +582,7 @@ async def drain_to_final_msg(
even after ctx closure and the `.open_context()` block exit.
'''
__tracebackhide__: bool = hide_tb
raise_overrun: bool = not ctx._allow_overruns
parent_never_opened_stream: bool = ctx._stream is None
@ -834,7 +835,8 @@ async def drain_to_final_msg(
f'{ctx.outcome}\n'
)
__tracebackhide__: bool = hide_tb
# ?TODO? why was this here and not above?
# __tracebackhide__: bool = hide_tb
return (
result_msg,
pre_result_drained,

View File

@ -1703,7 +1703,7 @@ def run_as_asyncio_guest(
# asyncio.CancelledError,
# ^^XXX `.shield()` call above prevents this??
)as state_err:
) as state_err:
# XXX be super dupere noisy about abandonment issues!
aio_task: asyncio.Task = asyncio.current_task()