Emit canonical tagged addresses
- Make `TCPAddress.unwrap()` emit `('tcp', host, port)` and
`UDSAddress.unwrap()` emit `('unix', path)` while retaining the
compatibility readers from the preceding change.
- Pass concrete TCP fields to Trio, compose multiaddrs from tagged
values, and let `SpawnSpec` carry protocol-specific tuple shapes
for validation by `wrap_address()`.
- Compare runtime, registry, bind, and tunnel addresses through
canonical serialized forms and cover both TCP and UDS operation.
Prompt-IO: ai/prompt-io/opencode/20260820T033108Z_ba07e09d_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
wkt/addr_unpacking
parent
284a967936
commit
5d92595fb4
|
|
@ -0,0 +1,45 @@
|
||||||
|
---
|
||||||
|
model: gpt-5.6-sol
|
||||||
|
service: opencode
|
||||||
|
session: tractor-addr-unpacking
|
||||||
|
timestamp: 2026-08-20T03:31:08Z
|
||||||
|
git_ref: ba07e09d
|
||||||
|
scope: code
|
||||||
|
substantive: true
|
||||||
|
raw_file: 20260820T033108Z_ba07e09d_prompt_io.raw.md
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prompt
|
||||||
|
|
||||||
|
The human asked the agent to continue after adding tagged readers,
|
||||||
|
complete canonical address emission without dropping legacy input
|
||||||
|
compatibility, verify the migration, and prepare a complete multi-commit
|
||||||
|
package when the turn was done.
|
||||||
|
|
||||||
|
## Response summary
|
||||||
|
|
||||||
|
Changed `.unwrap()` to emit tagged TCP and Unix addresses, updated direct
|
||||||
|
tuple consumers and spawn payload declarations, and aligned multiaddr,
|
||||||
|
runtime, IPC, and discovery tests with canonical serialized equality.
|
||||||
|
Kept untagged tuples and the `uds` spelling readable at input boundaries.
|
||||||
|
|
||||||
|
## Files changed
|
||||||
|
|
||||||
|
- `tractor/discovery/_addr.py` - canonical output alias.
|
||||||
|
- `tractor/discovery/_multiaddr.py` - tagged address composition.
|
||||||
|
- `tractor/ipc/_tcp.py` - tagged emission and direct socket dialing.
|
||||||
|
- `tractor/ipc/_uds.py` - tagged full-path emission.
|
||||||
|
- `tractor/msg/types.py` - protocol-neutral spawn tuple containers.
|
||||||
|
- `tests/discovery/test_address_serialization.py` - writer assertions.
|
||||||
|
- `tests/discovery/test_multiaddr.py` - canonical round-trip assertions.
|
||||||
|
- `tests/discovery/test_tpt_bind_addrs.py` - tagged bind assertions.
|
||||||
|
- `tests/ipc/test_each_tpt.py` - canonical runtime address assertions.
|
||||||
|
- `tests/ipc/test_server_tunnel_boundary.py` - tagged TCP destructuring.
|
||||||
|
- `tests/test_local.py` - canonical registry comparison.
|
||||||
|
|
||||||
|
## Human edits
|
||||||
|
|
||||||
|
The human established the reader-before-writer sequencing, canonical tag
|
||||||
|
spellings, retained compatibility expectations, and requested final
|
||||||
|
multi-commit packaging. The agent implemented and tested those choices;
|
||||||
|
no direct manual source edits were observed.
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
---
|
||||||
|
model: gpt-5.6-sol
|
||||||
|
service: opencode
|
||||||
|
timestamp: 2026-08-20T03:31:08Z
|
||||||
|
git_ref: ba07e09d
|
||||||
|
diff_cmd: git diff HEAD~1..HEAD
|
||||||
|
---
|
||||||
|
|
||||||
|
# Raw output - canonical tagged address writers
|
||||||
|
|
||||||
|
After the reader compatibility boundary, the human asked the agent to
|
||||||
|
continue the migration and package the completed work as dependency-
|
||||||
|
ordered commits.
|
||||||
|
|
||||||
|
> `git diff HEAD~1..HEAD -- tractor/discovery/_addr.py tractor/discovery/_multiaddr.py tractor/ipc/_tcp.py tractor/ipc/_uds.py tractor/msg/types.py tests/discovery/test_address_serialization.py tests/discovery/test_multiaddr.py tests/discovery/test_tpt_bind_addrs.py tests/ipc/test_each_tpt.py tests/ipc/test_server_tunnel_boundary.py tests/test_local.py`
|
||||||
|
|
||||||
|
Switched TCP and Unix `.unwrap()` output to canonical tagged tuples,
|
||||||
|
updated direct transport and multiaddr consumers, widened spawn message
|
||||||
|
tuple containers for protocol-specific shapes, and migrated runtime and
|
||||||
|
test comparisons to serialized address equality. Legacy inputs remain
|
||||||
|
accepted at `wrap_address()` and backend reader boundaries.
|
||||||
|
|
||||||
|
Ruff and focused tests passed. The complete non-debugger TCP suite passed
|
||||||
|
with 412 tests; the UDS suite reached 80% without failure before the
|
||||||
|
harness timeout, then all 97 remaining tests passed on resume. Debugger
|
||||||
|
PTY coverage was excluded after an unrelated timeout.
|
||||||
|
|
@ -23,13 +23,13 @@ def test_decode_tagged_tcp_address(value):
|
||||||
Shape-only decoding cannot distinguish future transport address
|
Shape-only decoding cannot distinguish future transport address
|
||||||
forms. Feed canonical tuple and msgpack-style list values through
|
forms. Feed canonical tuple and msgpack-style list values through
|
||||||
the compatibility boundary and prove the explicit `tcp` tag
|
the compatibility boundary and prove the explicit `tcp` tag
|
||||||
selects the TCP backend without changing legacy emission yet.
|
selects the TCP backend and emits the canonical tagged form.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
addr = wrap_address(value)
|
addr = wrap_address(value)
|
||||||
|
|
||||||
assert type(addr) is TCPAddress
|
assert type(addr) is TCPAddress
|
||||||
assert addr.unwrap() == ('127.0.0.1', 1616)
|
assert addr.unwrap() == ('tcp', '127.0.0.1', 1616)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
@ -44,8 +44,8 @@ def test_decode_tagged_unix_address(
|
||||||
'''
|
'''
|
||||||
Multiaddr calls the protocol `unix` while tractor's transport key
|
Multiaddr calls the protocol `unix` while tractor's transport key
|
||||||
remains `uds`. Decode both spellings from tuple/list containers,
|
remains `uds`. Decode both spellings from tuple/list containers,
|
||||||
normalize them to one `UDSAddress`, and retain legacy pair
|
normalize them to one `UDSAddress`, and emit the canonical `unix`
|
||||||
emission until the writer migration lands.
|
spelling.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
value = container((tag, '/tmp/tractor/registry.sock'))
|
value = container((tag, '/tmp/tractor/registry.sock'))
|
||||||
|
|
@ -54,8 +54,8 @@ def test_decode_tagged_unix_address(
|
||||||
assert type(addr) is UDSAddress
|
assert type(addr) is UDSAddress
|
||||||
assert addr.sockpath == Path('/tmp/tractor/registry.sock')
|
assert addr.sockpath == Path('/tmp/tractor/registry.sock')
|
||||||
assert addr.unwrap() == (
|
assert addr.unwrap() == (
|
||||||
'/tmp/tractor',
|
'unix',
|
||||||
'registry.sock',
|
'/tmp/tractor/registry.sock',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -75,11 +75,14 @@ def test_decode_legacy_address_forms(
|
||||||
'''
|
'''
|
||||||
Existing callers, config, and older msgpack payloads still
|
Existing callers, config, and older msgpack payloads still
|
||||||
provide untagged pairs. Keep tuple/list forms readable while
|
provide untagged pairs. Keep tuple/list forms readable while
|
||||||
canonical tagged decoding is introduced, proving this
|
canonical tagged emission is introduced, proving the writer
|
||||||
reader-first commit does not break shipped input behavior.
|
migration does not break shipped input behavior.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
assert type(wrap_address(value)) is expected_type
|
addr = wrap_address(value)
|
||||||
|
|
||||||
|
assert type(addr) is expected_type
|
||||||
|
assert addr.unwrap()[0] in {'tcp', 'unix'}
|
||||||
|
|
||||||
|
|
||||||
def test_tcp_from_native_ipv6_sockname():
|
def test_tcp_from_native_ipv6_sockname():
|
||||||
|
|
@ -94,4 +97,4 @@ def test_tcp_from_native_ipv6_sockname():
|
||||||
('::1', 1616, 0, 0)
|
('::1', 1616, 0, 0)
|
||||||
)
|
)
|
||||||
|
|
||||||
assert addr.unwrap() == ('::1', 1616)
|
assert addr.unwrap() == ('tcp', '::1', 1616)
|
||||||
|
|
|
||||||
|
|
@ -188,7 +188,7 @@ def test_parse_maddr_tcp_ipv4():
|
||||||
result = parse_maddr('/ip4/127.0.0.1/tcp/1234')
|
result = parse_maddr('/ip4/127.0.0.1/tcp/1234')
|
||||||
|
|
||||||
assert isinstance(result, TCPAddress)
|
assert isinstance(result, TCPAddress)
|
||||||
assert result.unwrap() == ('127.0.0.1', 1234)
|
assert result.unwrap() == ('tcp', '127.0.0.1', 1234)
|
||||||
|
|
||||||
|
|
||||||
def test_parse_maddr_tcp_ipv6():
|
def test_parse_maddr_tcp_ipv6():
|
||||||
|
|
@ -200,7 +200,7 @@ def test_parse_maddr_tcp_ipv6():
|
||||||
result = parse_maddr('/ip6/::1/tcp/5678')
|
result = parse_maddr('/ip6/::1/tcp/5678')
|
||||||
|
|
||||||
assert isinstance(result, TCPAddress)
|
assert isinstance(result, TCPAddress)
|
||||||
assert result.unwrap() == ('::1', 5678)
|
assert result.unwrap() == ('tcp', '::1', 5678)
|
||||||
|
|
||||||
|
|
||||||
def test_parse_maddr_uds():
|
def test_parse_maddr_uds():
|
||||||
|
|
@ -213,9 +213,10 @@ def test_parse_maddr_uds():
|
||||||
result = parse_maddr('/unix/tmp/tractor_test/test.sock')
|
result = parse_maddr('/unix/tmp/tractor_test/test.sock')
|
||||||
|
|
||||||
assert isinstance(result, UDSAddress)
|
assert isinstance(result, UDSAddress)
|
||||||
filedir, filename = result.unwrap()
|
assert result.unwrap() == (
|
||||||
assert filename == 'test.sock'
|
'unix',
|
||||||
assert str(filedir) == '/tmp/tractor_test'
|
'/tmp/tractor_test/test.sock',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_parse_maddr_unsupported():
|
def test_parse_maddr_unsupported():
|
||||||
|
|
@ -249,7 +250,7 @@ def test_parse_wg_maddr():
|
||||||
bearer=('192.168.1.50', 51820),
|
bearer=('192.168.1.50', 51820),
|
||||||
)
|
)
|
||||||
assert isinstance(parsed.overlay, TCPAddress)
|
assert isinstance(parsed.overlay, TCPAddress)
|
||||||
assert parsed.overlay.unwrap() == ('10.0.11.1', 1616)
|
assert parsed.overlay.unwrap() == ('tcp', '10.0.11.1', 1616)
|
||||||
|
|
||||||
|
|
||||||
def test_mk_wg_maddr_roundtrip():
|
def test_mk_wg_maddr_roundtrip():
|
||||||
|
|
@ -445,7 +446,7 @@ def test_wrap_address_maddr_str():
|
||||||
result = wrap_address('/ip4/127.0.0.1/tcp/9999')
|
result = wrap_address('/ip4/127.0.0.1/tcp/9999')
|
||||||
|
|
||||||
assert isinstance(result, TCPAddress)
|
assert isinstance(result, TCPAddress)
|
||||||
assert result.unwrap() == ('127.0.0.1', 9999)
|
assert result.unwrap() == ('tcp', '127.0.0.1', 9999)
|
||||||
|
|
||||||
|
|
||||||
def test_wrap_address_wg_maddr_str():
|
def test_wrap_address_wg_maddr_str():
|
||||||
|
|
@ -460,7 +461,7 @@ def test_wrap_address_wg_maddr_str():
|
||||||
|
|
||||||
assert isinstance(result, TunnelledAddress)
|
assert isinstance(result, TunnelledAddress)
|
||||||
assert result.tunnel.peer_pubkey == _WG_PUBKEY
|
assert result.tunnel.peer_pubkey == _WG_PUBKEY
|
||||||
assert result.overlay.unwrap() == ('10.0.11.1', 1616)
|
assert result.overlay.unwrap() == ('tcp', '10.0.11.1', 1616)
|
||||||
|
|
||||||
|
|
||||||
# ------ parse_endpoints() tests ------
|
# ------ parse_endpoints() tests ------
|
||||||
|
|
@ -481,11 +482,11 @@ def test_parse_endpoints_tcp_only():
|
||||||
|
|
||||||
reg_addr = result['registry'][0]
|
reg_addr = result['registry'][0]
|
||||||
assert isinstance(reg_addr, TCPAddress)
|
assert isinstance(reg_addr, TCPAddress)
|
||||||
assert reg_addr.unwrap() == ('127.0.0.1', 1616)
|
assert reg_addr.unwrap() == ('tcp', '127.0.0.1', 1616)
|
||||||
|
|
||||||
feed_addr = result['data_feed'][0]
|
feed_addr = result['data_feed'][0]
|
||||||
assert isinstance(feed_addr, TCPAddress)
|
assert isinstance(feed_addr, TCPAddress)
|
||||||
assert feed_addr.unwrap() == ('0.0.0.0', 5555)
|
assert feed_addr.unwrap() == ('tcp', '0.0.0.0', 5555)
|
||||||
|
|
||||||
|
|
||||||
def test_parse_endpoints_mixed_tpts():
|
def test_parse_endpoints_mixed_tpts():
|
||||||
|
|
@ -505,12 +506,13 @@ def test_parse_endpoints_mixed_tpts():
|
||||||
|
|
||||||
assert len(addrs) == 2
|
assert len(addrs) == 2
|
||||||
assert isinstance(addrs[0], TCPAddress)
|
assert isinstance(addrs[0], TCPAddress)
|
||||||
assert addrs[0].unwrap() == ('127.0.0.1', 4040)
|
assert addrs[0].unwrap() == ('tcp', '127.0.0.1', 4040)
|
||||||
|
|
||||||
assert isinstance(addrs[1], UDSAddress)
|
assert isinstance(addrs[1], UDSAddress)
|
||||||
filedir, filename = addrs[1].unwrap()
|
assert addrs[1].unwrap() == (
|
||||||
assert filename == 'broker.sock'
|
'unix',
|
||||||
assert str(filedir) == '/tmp/tractor'
|
'/tmp/tractor/broker.sock',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_parse_endpoints_wg_maddr():
|
def test_parse_endpoints_wg_maddr():
|
||||||
|
|
@ -550,7 +552,7 @@ def test_parse_endpoints_unwrapped_tuples():
|
||||||
|
|
||||||
addr = result['ems'][0]
|
addr = result['ems'][0]
|
||||||
assert isinstance(addr, TCPAddress)
|
assert isinstance(addr, TCPAddress)
|
||||||
assert addr.unwrap() == ('127.0.0.1', 6666)
|
assert addr.unwrap() == ('tcp', '127.0.0.1', 6666)
|
||||||
|
|
||||||
|
|
||||||
def test_parse_endpoints_mixed_str_and_tuple():
|
def test_parse_endpoints_mixed_str_and_tuple():
|
||||||
|
|
@ -570,10 +572,10 @@ def test_parse_endpoints_mixed_str_and_tuple():
|
||||||
|
|
||||||
assert len(addrs) == 2
|
assert len(addrs) == 2
|
||||||
assert isinstance(addrs[0], TCPAddress)
|
assert isinstance(addrs[0], TCPAddress)
|
||||||
assert addrs[0].unwrap() == ('127.0.0.1', 7777)
|
assert addrs[0].unwrap() == ('tcp', '127.0.0.1', 7777)
|
||||||
|
|
||||||
assert isinstance(addrs[1], TCPAddress)
|
assert isinstance(addrs[1], TCPAddress)
|
||||||
assert addrs[1].unwrap() == ('127.0.0.1', 8888)
|
assert addrs[1].unwrap() == ('tcp', '127.0.0.1', 8888)
|
||||||
|
|
||||||
|
|
||||||
def test_parse_endpoints_unsupported_proto():
|
def test_parse_endpoints_unsupported_proto():
|
||||||
|
|
|
||||||
|
|
@ -183,7 +183,7 @@ def test_non_registrar_root_tpt_bind_addrs(
|
||||||
for uw_addr in bound:
|
for uw_addr in bound:
|
||||||
w = wrap_address(uw_addr)
|
w = wrap_address(uw_addr)
|
||||||
if w.proto_key == 'tcp':
|
if w.proto_key == 'tcp':
|
||||||
_host, port = uw_addr
|
_, _host, port = uw_addr
|
||||||
assert port > 0
|
assert port > 0
|
||||||
|
|
||||||
trio.run(_main)
|
trio.run(_main)
|
||||||
|
|
@ -255,7 +255,7 @@ def test_tpt_bind_addrs_as_maddr_str(
|
||||||
for uw_addr in actor.accept_addrs:
|
for uw_addr in actor.accept_addrs:
|
||||||
w = wrap_address(uw_addr)
|
w = wrap_address(uw_addr)
|
||||||
if w.proto_key == 'tcp':
|
if w.proto_key == 'tcp':
|
||||||
_host, port = uw_addr
|
_, _host, port = uw_addr
|
||||||
assert port > 0
|
assert port > 0
|
||||||
|
|
||||||
trio.run(_main)
|
trio.run(_main)
|
||||||
|
|
@ -287,7 +287,7 @@ def test_registrar_merge_binds_union(
|
||||||
# actually differ (always true for TCP, may
|
# actually differ (always true for TCP, may
|
||||||
# collide for UDS).
|
# collide for UDS).
|
||||||
expect_disjoint: bool = (
|
expect_disjoint: bool = (
|
||||||
tuple(reg_addr) != rando.unwrap()
|
reg_wrapped.unwrap() != rando.unwrap()
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _main():
|
async def _main():
|
||||||
|
|
|
||||||
|
|
@ -57,17 +57,20 @@ def test_uds_bindspace_created_implicitly(
|
||||||
|
|
||||||
root: Actor = tractor.current_actor()
|
root: Actor = tractor.current_actor()
|
||||||
assert root.is_registrar
|
assert root.is_registrar
|
||||||
|
canonical_addr = _addr.wrap_address(
|
||||||
|
registry_addr,
|
||||||
|
).unwrap()
|
||||||
|
|
||||||
assert registry_addr in root.reg_addrs
|
assert canonical_addr in root.reg_addrs
|
||||||
assert (
|
assert (
|
||||||
registry_addr
|
canonical_addr
|
||||||
in
|
in
|
||||||
_state._runtime_vars['_registry_addrs']
|
_state._runtime_vars['_registry_addrs']
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
_addr.wrap_address(registry_addr)
|
canonical_addr
|
||||||
in
|
in
|
||||||
root.registry_addrs
|
[addr.unwrap() for addr in root.registry_addrs]
|
||||||
)
|
)
|
||||||
|
|
||||||
trio.run(main)
|
trio.run(main)
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,8 @@ def test_server_peels_before_endpoint_construction():
|
||||||
endpoint = eps[0]
|
endpoint = eps[0]
|
||||||
|
|
||||||
assert type(endpoint.addr) is TCPAddress
|
assert type(endpoint.addr) is TCPAddress
|
||||||
host, port = endpoint.addr.unwrap()
|
_, host, port = endpoint.addr.unwrap()
|
||||||
assert host == overlay.unwrap()[0]
|
assert host == overlay.unwrap()[1]
|
||||||
assert port > 0
|
assert port > 0
|
||||||
assert endpoint.addr is not tunnelled
|
assert endpoint.addr is not tunnelled
|
||||||
assert tunnels_of(tunnelled) == (
|
assert tunnels_of(tunnelled) == (
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import trio
|
||||||
import tractor
|
import tractor
|
||||||
|
|
||||||
from tractor._testing import tractor_test
|
from tractor._testing import tractor_test
|
||||||
|
from tractor.discovery._addr import wrap_address
|
||||||
|
|
||||||
|
|
||||||
def test_no_runtime():
|
def test_no_runtime():
|
||||||
|
|
@ -48,7 +49,7 @@ async def test_self_is_registered_localportal(reg_addr):
|
||||||
with trio.fail_after(0.2):
|
with trio.fail_after(0.2):
|
||||||
sockaddr = await portal.run_from_ns(
|
sockaddr = await portal.run_from_ns(
|
||||||
'self', 'wait_for_actor', name='root')
|
'self', 'wait_for_actor', name='root')
|
||||||
assert sockaddr[0] == reg_addr
|
assert sockaddr[0] == wrap_address(reg_addr).unwrap()
|
||||||
|
|
||||||
|
|
||||||
def test_local_actor_async_func(reg_addr):
|
def test_local_actor_async_func(reg_addr):
|
||||||
|
|
|
||||||
|
|
@ -84,16 +84,15 @@ TaggedAddress: TypeAlias = (
|
||||||
|TaggedUnixAddress
|
|TaggedUnixAddress
|
||||||
)
|
)
|
||||||
|
|
||||||
# Input-only compatibility forms. `UnwrappedAddress` remains the
|
# Input-only compatibility forms retained for older callers and
|
||||||
# emitted form until the tagged writer migration switches it in the
|
# serialized payloads.
|
||||||
# next atomic change.
|
|
||||||
LegacyTCPAddress: TypeAlias = tuple[str, int]
|
LegacyTCPAddress: TypeAlias = tuple[str, int]
|
||||||
LegacyUDSAddress: TypeAlias = tuple[str, str]
|
LegacyUDSAddress: TypeAlias = tuple[str, str]
|
||||||
LegacyUnwrappedAddress: TypeAlias = (
|
LegacyUnwrappedAddress: TypeAlias = (
|
||||||
LegacyTCPAddress
|
LegacyTCPAddress
|
||||||
|LegacyUDSAddress
|
|LegacyUDSAddress
|
||||||
)
|
)
|
||||||
UnwrappedAddress = LegacyUnwrappedAddress
|
UnwrappedAddress = TaggedAddress
|
||||||
# ?TODO? should we also include another 2 fields from our `Aid` msg
|
# ?TODO? should we also include another 2 fields from our `Aid` msg
|
||||||
# such that we include the runtime `Actor.uid` of `.name` and `.uuid`?
|
# such that we include the runtime `Actor.uid` of `.name` and `.uuid`?
|
||||||
# - would ensure uniqueness across entire net?
|
# - would ensure uniqueness across entire net?
|
||||||
|
|
@ -104,7 +103,7 @@ UnwrappedAddress = LegacyUnwrappedAddress
|
||||||
# TODO, maybe rename to `SocketAddress`?
|
# TODO, maybe rename to `SocketAddress`?
|
||||||
class Address(Protocol):
|
class Address(Protocol):
|
||||||
proto_key: ClassVar[str]
|
proto_key: ClassVar[str]
|
||||||
unwrapped_type: ClassVar[UnwrappedAddress]
|
unwrapped_type: ClassVar[type]
|
||||||
|
|
||||||
# TODO, i feel like an `.is_bound()` is a better thing to
|
# TODO, i feel like an `.is_bound()` is a better thing to
|
||||||
# support?
|
# support?
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ def mk_maddr(
|
||||||
|
|
||||||
match proto_key:
|
match proto_key:
|
||||||
case 'tcp':
|
case 'tcp':
|
||||||
host, port = addr.unwrap()
|
_, host, port = addr.unwrap()
|
||||||
ip = ipaddress.ip_address(host)
|
ip = ipaddress.ip_address(host)
|
||||||
net_proto: str = (
|
net_proto: str = (
|
||||||
'ip4' if ip.version == 4
|
'ip4' if ip.version == 4
|
||||||
|
|
@ -84,13 +84,12 @@ def mk_maddr(
|
||||||
)
|
)
|
||||||
|
|
||||||
case 'uds':
|
case 'uds':
|
||||||
filedir, filename = addr.unwrap()
|
_, sockpath = addr.unwrap()
|
||||||
filepath = Path(filedir) / filename
|
|
||||||
# NOTE, strip any leading `/` to avoid
|
# NOTE, strip any leading `/` to avoid
|
||||||
# double-slash `/unix//run/..` which the
|
# double-slash `/unix//run/..` which the
|
||||||
# multiaddr parser rejects as "empty
|
# multiaddr parser rejects as "empty
|
||||||
# protocol path".
|
# protocol path".
|
||||||
fpath_str: str = str(filepath).lstrip('/')
|
fpath_str: str = sockpath.lstrip('/')
|
||||||
return Multiaddr(
|
return Multiaddr(
|
||||||
f'/{maddr_proto}/{fpath_str}'
|
f'/{maddr_proto}/{fpath_str}'
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ from __future__ import annotations
|
||||||
import ipaddress
|
import ipaddress
|
||||||
from typing import (
|
from typing import (
|
||||||
ClassVar,
|
ClassVar,
|
||||||
|
TYPE_CHECKING,
|
||||||
)
|
)
|
||||||
# from contextlib import (
|
# from contextlib import (
|
||||||
# asynccontextmanager as acm,
|
# asynccontextmanager as acm,
|
||||||
|
|
@ -42,6 +43,9 @@ from tractor.ipc._transport import (
|
||||||
MsgpackTransport,
|
MsgpackTransport,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from tractor.discovery._addr import TaggedTCPAddress
|
||||||
|
|
||||||
|
|
||||||
log = get_logger()
|
log = get_logger()
|
||||||
|
|
||||||
|
|
@ -62,7 +66,7 @@ class TCPAddress(
|
||||||
) from valerr
|
) from valerr
|
||||||
|
|
||||||
proto_key: ClassVar[str] = 'tcp'
|
proto_key: ClassVar[str] = 'tcp'
|
||||||
unwrapped_type: ClassVar[type] = tuple[str, int]
|
unwrapped_type: ClassVar[type] = tuple
|
||||||
def_bindspace: ClassVar[str] = '127.0.0.1'
|
def_bindspace: ClassVar[str] = '127.0.0.1'
|
||||||
|
|
||||||
# ?TODO, actually validate ipv4/6 with stdlib's `ipaddress`
|
# ?TODO, actually validate ipv4/6 with stdlib's `ipaddress`
|
||||||
|
|
@ -130,8 +134,9 @@ class TCPAddress(
|
||||||
f'{addr}\n'
|
f'{addr}\n'
|
||||||
)
|
)
|
||||||
|
|
||||||
def unwrap(self) -> tuple[str, int]:
|
def unwrap(self) -> TaggedTCPAddress:
|
||||||
return (
|
return (
|
||||||
|
self.proto_key,
|
||||||
self._host,
|
self._host,
|
||||||
self._port,
|
self._port,
|
||||||
)
|
)
|
||||||
|
|
@ -230,7 +235,8 @@ class MsgpackTCPStream(MsgpackTransport):
|
||||||
**kwargs
|
**kwargs
|
||||||
) -> MsgpackTCPStream:
|
) -> MsgpackTCPStream:
|
||||||
stream = await trio.open_tcp_stream(
|
stream = await trio.open_tcp_stream(
|
||||||
*destaddr.unwrap(),
|
destaddr._host,
|
||||||
|
destaddr._port,
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
return MsgpackTCPStream(
|
return MsgpackTCPStream(
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ from tractor.runtime._state import (
|
||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from tractor.discovery._addr import TaggedUnixAddress
|
||||||
from tractor.runtime._runtime import Actor
|
from tractor.runtime._runtime import Actor
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -114,7 +115,7 @@ class UDSAddress(
|
||||||
# -[ ] need to check what other mult-transport frameworks do
|
# -[ ] need to check what other mult-transport frameworks do
|
||||||
# like zmq, nng, uri-spec et al!
|
# like zmq, nng, uri-spec et al!
|
||||||
proto_key: ClassVar[str] = 'uds'
|
proto_key: ClassVar[str] = 'uds'
|
||||||
unwrapped_type: ClassVar[type] = tuple[str, int]
|
unwrapped_type: ClassVar[type] = tuple
|
||||||
def_bindspace: ClassVar[Path] = get_rt_dir()
|
def_bindspace: ClassVar[Path] = get_rt_dir()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -132,7 +133,7 @@ class UDSAddress(
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sockpath(self) -> Path:
|
def sockpath(self) -> Path:
|
||||||
return self.bindspace / self.filename
|
return Path(self.bindspace) / self.filename
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_valid(self) -> bool:
|
def is_valid(self) -> bool:
|
||||||
|
|
@ -180,12 +181,10 @@ class UDSAddress(
|
||||||
f'{addr!r}\n'
|
f'{addr!r}\n'
|
||||||
)
|
)
|
||||||
|
|
||||||
def unwrap(self) -> tuple[str, int]:
|
def unwrap(self) -> TaggedUnixAddress:
|
||||||
# XXX NOTE, since this gets passed DIRECTLY to
|
|
||||||
# `.ipc._uds.open_unix_socket_w_passcred()`
|
|
||||||
return (
|
return (
|
||||||
str(self.filedir),
|
'unix',
|
||||||
str(self.filename),
|
str(self.sockpath),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
||||||
|
|
@ -211,10 +211,11 @@ class SpawnSpec(
|
||||||
# module import capability
|
# module import capability
|
||||||
enable_modules: dict[str, str]
|
enable_modules: dict[str, str]
|
||||||
|
|
||||||
# TODO: not just sockaddr pairs?
|
# Tagged addresses have protocol-specific tuple shapes which
|
||||||
# -[ ] abstract into a `TransportAddr` type?
|
# msgspec cannot express as one decodable union. `wrap_address()`
|
||||||
reg_addrs: list[tuple[str, str|int]]
|
# validates each tuple at the transport boundary.
|
||||||
bind_addrs: list[tuple[str, str|int]]|None
|
reg_addrs: list[tuple]
|
||||||
|
bind_addrs: list[tuple]|None
|
||||||
|
|
||||||
|
|
||||||
# TODO: caps based RPC support in the payload?
|
# TODO: caps based RPC support in the payload?
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue