Compare commits
9 Commits
81c33bf550
...
b761524a85
| Author | SHA1 | Date |
|---|---|---|
|
|
b761524a85 | |
|
|
b4dbf5dd86 | |
|
|
39952344cb | |
|
|
15f58495d5 | |
|
|
2be3f93a8f | |
|
|
224e92b468 | |
|
|
ccedee3b87 | |
|
|
7d947d3776 | |
|
|
6b3cc72e5c |
|
|
@ -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
|
||||
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,8 +159,26 @@ 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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue