Add the interim `/tipc/` maddr grammar

`mk_maddr()`/`parse_maddr()` learn,

    /tipc/<stype>/<instance>/<scope>

mirroring how `uds` maps onto the spec-legal `/unix`.

XXX `str`-ONLY for now: there is no registered `/tipc` proto
in the multiaddr table (upstream track gh #483 +
multiformats/py-multiaddr#107) and `Multiaddr()` rejects an
unregistered name outright. `MsgTransport.maddr`s return type
is already `Multiaddr|str` (and `MsgpackUDSStream` already
exercises the `str` branch), so this fits — but it IS why gh

`parse_maddr()` therefore special-cases the `/tipc/` prefix
BEFORE handing anything to `Multiaddr()`.

Also drive the maddr mapping-table tests off `_address_types`
instead of a hardcoded len/dict so the next backend can't
fail them for the wrong reason.

(this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
wkt/pr493_review
Gud Boi 2026-08-14 09:55:01 -04:00
parent e0f66616cd
commit 51d7133f47
3 changed files with 102 additions and 5 deletions

View File

@ -19,7 +19,10 @@ from tractor.discovery._multiaddr import (
_tpt_proto_to_maddr,
_maddr_to_tpt_proto,
)
from tractor.discovery._addr import wrap_address
from tractor.discovery._addr import (
wrap_address,
_address_types,
)
def test_tpt_proto_to_maddr_mapping():
@ -30,7 +33,12 @@ def test_tpt_proto_to_maddr_mapping():
'''
assert _tpt_proto_to_maddr['tcp'] == 'tcp'
assert _tpt_proto_to_maddr['uds'] == 'unix'
assert len(_tpt_proto_to_maddr) == 2
assert _tpt_proto_to_maddr['tipc'] == 'tipc'
# NOTE, drive the expected set off the registration table
# (per the "drive-the-set-from-the-`Literal`" pattern) so
# adding a backend can't fail this for the wrong reason.
assert set(_tpt_proto_to_maddr) == set(_address_types)
def test_mk_maddr_tcp_ipv4():
@ -153,9 +161,12 @@ def test_maddr_to_tpt_proto_mapping():
'''
assert _maddr_to_tpt_proto == {
'tcp': 'tcp',
'unix': 'uds',
maddr_proto: proto_key
for proto_key, maddr_proto in _tpt_proto_to_maddr.items()
}
assert _maddr_to_tpt_proto['tcp'] == 'tcp'
assert _maddr_to_tpt_proto['unix'] == 'uds'
assert _maddr_to_tpt_proto['tipc'] == 'tipc'
def test_parse_maddr_tcp_ipv4():

View File

@ -19,6 +19,12 @@ import pytest
import trio
from tractor.msg.types import Aid
from tractor.discovery import _addr
from tractor.discovery._addr import wrap_address
from tractor.discovery._multiaddr import (
mk_maddr,
parse_maddr,
)
from tractor.ipc import _tipc
from tractor.ipc._tipc import (
AF_TIPC,
@ -206,6 +212,48 @@ def test_get_random_honors_bindspace():
assert addr.bindspace == TIPC_NODE_SCOPE == addr._scope
def test_wrap_address_dispatches_on_the_proto_key():
'''
The proto-keyed unwrapped form must round-trip through the
*global* `wrap_address()` and NOT get stolen by `tcp`s
`(str(), int())` case nor `uds`s `(_, str())` one.
'''
addr: TIPCAddress = TIPCAddress.get_random()
assert wrap_address(addr.unwrap()) == addr
# ..and via the `list` form `msgpack` decodes to
assert wrap_address(list(addr.unwrap())) == addr
assert _addr._address_types['tipc'] is TIPCAddress
assert _addr.get_address_cls('tipc') is TIPCAddress
# the host-singleton registrar default is import-time cheap
# (no kernel module, no I/O) and mirrors the `1616` idiom
assert _addr._default_lo_addrs['tipc'] == (
'tipc', TRACTOR_STYPE, 1616, TIPC_CLUSTER_SCOPE,
)
def test_maddr_roundtrip():
'''
Interim `str`-only `/tipc/` maddr grammar (there's no
registered `/tipc` multiaddr proto yet, gh #483), which
`parse_maddr()` special-cases before `Multiaddr()` ever sees
the string.
'''
addr: TIPCAddress = TIPCAddress.get_random()
maddr: str = mk_maddr(addr)
assert isinstance(maddr, str)
assert maddr == (
f'/tipc/{addr._stype}/{addr._instance}/{addr._scope}'
)
assert parse_maddr(maddr) == addr
# ..and through the generic entrypoint
assert wrap_address(maddr) == addr
def test_eafnosupport_is_actionable_connerr(
monkeypatch: pytest.MonkeyPatch,
):

View File

@ -38,8 +38,23 @@ if TYPE_CHECKING:
_tpt_proto_to_maddr: dict[str, str] = {
'tcp': 'tcp',
'uds': 'unix',
'tipc': 'tipc',
}
# XXX, there is NO `/tipc` in the multiaddr protocol table yet
# (upstream track: gh #483 + multiformats/py-multiaddr#107), and
# `Multiaddr()` rejects an unregistered proto name outright.
#
# So until that lands `tipc` maddrs stay **`str`**-only — which
# `MsgTransport.maddr`s `Multiaddr|str` return type already
# allows and `MsgpackUDSStream.maddr` already exercises — and
# `parse_maddr()` special-cases the prefix BEFORE handing
# anything to `Multiaddr()`.
#
# This is also why gh #443's "always return `Multiaddr`" item
# stays blocked.
_tipc_maddr_prefix: str = '/tipc/'
# reverse mapping: multiaddr protocol name -> tractor proto_key
_maddr_to_tpt_proto: dict[str, str] = {
v: k for k, v in _tpt_proto_to_maddr.items()
@ -49,7 +64,7 @@ _maddr_to_tpt_proto: dict[str, str] = {
def mk_maddr(
addr: 'Address',
) -> Multiaddr:
) -> Multiaddr|str:
'''
Construct a `Multiaddr` from a tractor `Address` instance,
dispatching on the `.proto_key` to build the correct
@ -75,6 +90,18 @@ def mk_maddr(
f'/{net_proto}/{host}/{maddr_proto}/{port}'
)
# NOTE, interim `str`-only grammar (see the
# `_tipc_maddr_prefix` note above),
#
# /tipc/<stype>/<instance>/<scope>
#
# mirroring how `uds` maps onto the spec-legal `/unix`.
case 'tipc':
_, stype, instance, scope = addr.unwrap()
return (
f'/{maddr_proto}/{stype}/{instance}/{scope}'
)
case 'uds':
filedir, filename = addr.unwrap()
filepath = Path(filedir) / filename
@ -100,6 +127,17 @@ def parse_maddr(
# lazy imports to avoid circular deps
from tractor.ipc._tcp import TCPAddress
from tractor.ipc._uds import UDSAddress
from tractor.ipc._tipc import TIPCAddress
# XXX MUST come before `Multiaddr()` which rejects the
# not-yet-registered `/tipc` proto name outright.
if maddr_str.startswith(_tipc_maddr_prefix):
_, _, stype, instance, scope = maddr_str.split('/')
return TIPCAddress(
_stype=int(stype),
_instance=int(instance),
_scope=int(scope),
)
maddr = Multiaddr(maddr_str)
proto_names: list[str] = [