diff --git a/ai/prompt-io/opencode/20260903T201530Z_d097090f_discovery_types.md b/ai/prompt-io/opencode/20260903T201530Z_d097090f_discovery_types.md new file mode 100644 index 00000000..d87a7c6a --- /dev/null +++ b/ai/prompt-io/opencode/20260903T201530Z_d097090f_discovery_types.md @@ -0,0 +1,70 @@ +--- +model: openai/gpt-5.6-sol +service: opencode +session: dependency-neutral-discovery-types +timestamp: 2026-09-03T20:15:30Z +git_ref: d097090f +scope: code +substantive: true +raw_file: 20260903T201530Z_d097090f_discovery_types.raw.md +--- + +## Prompt + +The maintainer rejected annotation-only model names rebound to `Any`, +questioned the self-referential `get_type_hints()` regression, selected a +dependency-neutral `tractor.discovery.types` module as the preferred +direction, and then requested implementation of that first design layer. + +The maintainer separately asked how to handle the disliked staged +`tractor/net/_bootstrap.py` implementation. That file was excluded from +this implementation boundary and retained as review evidence. + +## Response summary + +Added a dependency-neutral module for canonical primitive address +descriptors and separate structural contracts for concrete transport +addresses and tunnel-capable address declarations. Migrated discovery, +IPC, root, runtime, and tunnel annotations to import those real types, +removing every runtime model-name assignment to `Any`. + +Kept parser table aliases beside `parse_endpoints()` instead of turning +the new module into a general alias registry. Kept transport-specific +factories on TCP and UDS classes because their accepted descriptor and +bindspace types differ. Replaced the old `get_type_hints()` assertions +with cold-import and canonical-identity coverage. + +## Files changed + +- `tractor/discovery/types.py` - dependency-neutral descriptors and + address protocols. +- `tractor/discovery/_addr.py` - imports and re-exports canonical types; + retains concrete factory registry. +- `tractor/discovery/_multiaddr.py` - uses the structural declaration + type and runtime-safe parsed table alias. +- `tractor/ipc/_tcp.py` - imports the real TCP descriptor alias. +- `tractor/ipc/_uds.py` - imports the real Unix descriptor alias. +- `tractor/ipc/_transport.py` - imports the concrete-address protocol + without cycling through `_addr`. +- `tractor/ipc/_chan.py` - accepts structural address declarations + without a `TunnelledAddress = Any` fallback. +- `tractor/ipc/_server.py` - retains declaration metadata structurally + and routes reconstruction through the concrete address registry. +- `tractor/net/_tunnel.py` - imports real dependency-neutral address + types while retaining dynamic `Any` only for external netlink data. +- `tractor/_root.py` - removes the `Bindspace = Any` fallback and uses + concrete address-factory dispatch. +- `tractor/runtime/_runtime.py` - infers concrete registry address types + for random listener allocation. +- `tests/test_lazy_imports.py` - replaces workaround introspection with + dependency and canonical-identity checks. + +## Human edits + +The maintainer identified the `Any` aliases and their introspection test +as an unjustified circular contract, directed investigation of the real +cycle/import-performance cause, selected `tractor.discovery.types`, and +approved beginning implementation. The maintainer also required that the +current bootstrap implementation not be treated as acceptable design and +provided direct review annotations which remain preserved in the +worktree. diff --git a/ai/prompt-io/opencode/20260903T201530Z_d097090f_discovery_types.raw.md b/ai/prompt-io/opencode/20260903T201530Z_d097090f_discovery_types.raw.md new file mode 100644 index 00000000..a0d641d7 --- /dev/null +++ b/ai/prompt-io/opencode/20260903T201530Z_d097090f_discovery_types.raw.md @@ -0,0 +1,34 @@ +--- +model: openai/gpt-5.6-sol +service: opencode +timestamp: 2026-09-03T20:15:30Z +git_ref: d097090f +diff_cmd: git diff HEAD~1..HEAD +--- + +Implemented a dependency-neutral discovery typing layer after reviewing +the address, transport, tunnel, and lazy-import dependency graph. + +> `git diff HEAD~1..HEAD -- tractor/discovery/types.py tractor/discovery/_addr.py tractor/discovery/_multiaddr.py tractor/ipc/_tcp.py tractor/ipc/_uds.py tractor/ipc/_transport.py tractor/ipc/_chan.py tractor/ipc/_server.py tractor/net/_tunnel.py tractor/_root.py tractor/runtime/_runtime.py tests/test_lazy_imports.py` + +The generated changes move primitive address descriptors and structural +address protocols into `tractor.discovery.types`. Concrete transport +factories remain outside the shared protocol because TCP and UDS accept +different descriptor and bindspace inputs. Runtime names are no longer +rebound to `Any` merely to satisfy `typing.get_type_hints()`. + +The old annotation-introspection regression was replaced with coverage +that checks dependency-neutral cold imports and canonical type identity +across discovery, IPC, and tunnel modules. + +Verification completed with the worktree-local Python 3.13 environment: + +- focused address/lazy/tunnel coverage: 69 passed; +- discovery, IPC, and local coverage: 77 passed, 2 xpassed; +- root/runtime coverage: 6 passed with 24 multiprocessing fork + deprecation warnings. + +Ruff was unavailable in the existing `py313` environment. Scoped +`git diff --check` passed. The global whitespace check reports only a +pre-existing maintainer annotation in `tractor/net/_bootstrap.py`, which +was deliberately left untouched. diff --git a/tests/test_lazy_imports.py b/tests/test_lazy_imports.py index b2b61f35..02361544 100644 --- a/tests/test_lazy_imports.py +++ b/tests/test_lazy_imports.py @@ -7,21 +7,6 @@ import os from statistics import median import subprocess import sys -from typing import ( - Any, - get_type_hints, -) - -import tractor -from tractor.discovery import ( - _addr, - _multiaddr, -) -from tractor.ipc import ( - _tcp, - _uds, -) - def run_cold_import(code: str) -> dict[str, object]: result = subprocess.run( @@ -260,28 +245,56 @@ def test_net_root_export_and_old_discovery_surface(): } -def test_lazy_annotation_names_resolve(): +def test_discovery_types_are_dependency_neutral(): ''' - Resolve annotations without importing optional dependencies. + Keep canonical address types independent of runtime implementations. - Moving annotation-only third-party names under `TYPE_CHECKING` - left their runtime globals undefined, causing - `typing.get_type_hints()` to raise `NameError`. Resolve every - affected API and prove the lazy aliases retain import-free runtime - introspection. + Annotation-only imports previously rebound unavailable model names + to `Any` so `get_type_hints()` could resolve them. That test only + verified its own workaround and hid the `_addr`/transport import + cycle. Import the canonical declarations in a cold interpreter and + prove they do not pull in optional networking modules. Then prove + each implementation imports the same real declarations instead of + substituting `Any` under the model names. ''' - assert get_type_hints(_multiaddr.mk_maddr)['return'] is Any - assert get_type_hints(_tcp.MsgpackTCPStream.maddr.fget)[ - 'return' - ] is Any - assert get_type_hints(_uds.MsgpackUDSStream.maddr.fget)[ - 'return' - ] == Any|str - assert get_type_hints(_addr.Address.get_random)[ - 'current_actor' - ] is Any - assert get_type_hints(tractor.open_root_actor)[ - 'bindspace' - ] == Any|None - assert _addr.__annotations__['_address_types'].startswith('dict') + cold = run_cold_import( + 'import json, sys; ' + 'from tractor.discovery import types; ' + 'blocked = (' + '"multiaddr", "multibase", "pyroute2", ' + '"tractor.discovery._multiaddr", "tractor.net", ' + '"tractor.net._bindspace", "tractor.net._tunnel"); ' + 'print(json.dumps({' + '"address_module": types.Address.__module__, ' + '"blocked": [name for name in blocked ' + 'if name in sys.modules]}))' + ) + assert cold == { + 'address_module': 'tractor.discovery.types', + 'blocked': [], + } + + from tractor.discovery import ( + _addr, + _multiaddr, + types, + ) + from tractor.ipc import ( + _chan, + _server, + _tcp, + _transport, + _uds, + ) + from tractor.net import _tunnel + + assert _addr.Address is types.Address + assert _addr.UnwrappedAddress is types.UnwrappedAddress + assert _multiaddr.AddressDeclaration is types.AddressDeclaration + assert _tcp.TaggedTCPAddress is types.TaggedTCPAddress + assert _uds.TaggedUnixAddress is types.TaggedUnixAddress + assert _transport.Address is types.Address + assert _chan.AddressDeclaration is types.AddressDeclaration + assert _server.AddressDeclaration is types.AddressDeclaration + assert _tunnel.AddressDeclaration is types.AddressDeclaration diff --git a/tractor/_root.py b/tractor/_root.py index 13c9132a..5c48f42e 100644 --- a/tractor/_root.py +++ b/tractor/_root.py @@ -53,12 +53,15 @@ from .runtime import _state from . import log from .discovery._api import _probe_registry_addrs from .discovery._addr import ( - Address, - UnwrappedAddress, default_lo_addrs, + get_address_cls, mk_uuid, wrap_address, ) +from .discovery.types import ( + Address, + UnwrappedAddress, +) from .trionics import ( is_multi_cancelled, collapse_eg, @@ -69,8 +72,6 @@ from ._exceptions import ( if TYPE_CHECKING: from .net._bindspace import Bindspace -else: - Bindspace = Any logger = log.get_logger('tractor') @@ -187,7 +188,7 @@ async def open_root_actor( bindspace: Bindspace|None = None, tpt_bind_addrs: list[ - Address # `Address.get_random()` case + Address # concrete transport address case |UnwrappedAddress # registrar case `= uw_reg_addrs` ]|None = None, @@ -561,6 +562,9 @@ async def open_root_actor( for addr in ponged_addrs: bindable_addr: Address = strip_tunnels(addr) + address_type = get_address_cls( + bindable_addr.proto_key + ) tpt_bind_addrs.append( # XXX, these are `Address` NOT `UnwrappedAddress`. # @@ -568,7 +572,7 @@ async def open_root_actor( # protos we allocate port=0 such that the system # allocates a random value at bind time; this # happens in the `.ipc.*` stack's backend. - bindable_addr.get_random( + address_type.get_random( bindspace=bindable_addr.bindspace, ) ) diff --git a/tractor/discovery/_addr.py b/tractor/discovery/_addr.py index ed080690..a7ea862f 100644 --- a/tractor/discovery/_addr.py +++ b/tractor/discovery/_addr.py @@ -15,19 +15,7 @@ # along with this program. If not, see . from __future__ import annotations from uuid import uuid4 -from typing import ( - Any, - Protocol, - ClassVar, - Literal, - Type, - TypeAlias, - TYPE_CHECKING, -) - -from trio import ( - SocketListener, -) +from typing import TypeAlias from ..log import get_logger from ..runtime._state import ( @@ -38,70 +26,19 @@ from ..ipc._uds import ( UDSAddress, HAS_UDS, ) - -if TYPE_CHECKING: - # ONLY type-annots, the eager import costs ~4.5ms - # of `import tractor` wall-time (gh #470). - from tractor.net._tunnel import ( - TunnelledAddress, - ) - from ..runtime._runtime import Actor -else: - Actor = Any - TunnelledAddress = Any +from .types import ( + Address as Address, + AddressDeclaration, + LegacyUnwrappedAddress, + TaggedAddress, + TaggedUDSAlias, + UnwrappedAddress, +) log = get_logger() -# TODO, maybe breakout the netns key to a struct? -# class NetNs(Struct)[str, int]: -# ... +_AddressType: TypeAlias = type[TCPAddress]|type[UDSAddress] -# TODO, can't we just use a type alias -# for this? namely just some `tuple[str, int, str, str]`? -# -# -[ ] would also just be simpler to keep this as SockAddr[tuple] -# or something, implying it's just a simple pair of values which can -# presumably be mapped to all transports? -# -[ ] `pydoc socket.socket.getsockname()` delivers a 4-tuple for -# ipv6 `(hostaddr, port, flowinfo, scope_id)`.. so how should we -# handle that? -# -[ ] as a further alternative to this wrap()/unwrap() approach we -# could just implement `enc/dec_hook()`s for the `Address`-types -# and just deal with our internal objs directly and always and -# leave it to the codec layer to figure out marshalling? -# |_ would mean only one spot to do the `.unwrap()` (which we may -# end up needing to call from the hook()s anyway?) -# -[x] rename to `UnwrappedAddress[Descriptor]` ?? -# seems like the right name as per, -# https://www.geeksforgeeks.org/introduction-to-address-descriptor/ -# -TaggedTCPAddress: TypeAlias = tuple[ - Literal['tcp'], - str, - int, -] -TaggedUnixAddress: TypeAlias = tuple[ - Literal['unix'], - str, -] -TaggedUDSAlias: TypeAlias = tuple[ - Literal['uds'], - str, -] -TaggedAddress: TypeAlias = ( - TaggedTCPAddress - |TaggedUnixAddress -) - -# Input-only compatibility forms retained for older callers and -# serialized payloads. -LegacyTCPAddress: TypeAlias = tuple[str, int] -LegacyUDSAddress: TypeAlias = tuple[str, str] -LegacyUnwrappedAddress: TypeAlias = ( - LegacyTCPAddress - |LegacyUDSAddress -) -UnwrappedAddress = TaggedAddress # ?TODO? should we also include another 2 fields from our `Aid` msg # such that we include the runtime `Actor.uid` of `.name` and `.uuid`? # - would ensure uniqueness across entire net? @@ -109,106 +46,14 @@ UnwrappedAddress = TaggedAddress # name" -# TODO, maybe rename to `SocketAddress`? -class Address(Protocol): - proto_key: ClassVar[str] - unwrapped_type: ClassVar[type] - - # TODO, i feel like an `.is_bound()` is a better thing to - # support? - # Lke, what use does this have besides a noop and if it's not - # valid why aren't we erroring on creation/use? - @property - def is_valid(self) -> bool: - ... - - # TODO, maybe `.netns` is a better name? - @property - def namespace(self) -> tuple[str, str|int]|None: - ''' - The if-available, OS-specific "network namespace" key. - - ''' - ... - - @property - def bindspace(self) -> str: - ''' - Deliver the socket address' "bindable space" from - a `socket.socket.bind()` and thus from the perspective of - specific transport protocol domain. - - I.e. for most (layer-4) network-socket protocols this is - normally the ipv4/6 address, for UDS this is normally - a filesystem (sub-directory). - - For (distributed) network protocols this is normally the routing - layer's domain/(ip-)address, though it might also include a "network namespace" - key different then the default. - - For local-host-only transports this is either an explicit - namespace (with types defined by the OS: netns, Cgroup, IPC, - pid, etc. on linux) or failing that the sub-directory in the - filesys in which socket/shm files are located *under*. - - ''' - ... - - @classmethod - def from_addr(cls, addr: UnwrappedAddress) -> Address: - ... - - def unwrap(self) -> UnwrappedAddress: - ''' - Deliver the underying minimum field set in - a primitive python data type-structure. - ''' - ... - - @classmethod - def get_random( - cls, - current_actor: Actor, - bindspace: str|None = None, - ) -> Address: - ... - - # TODO, this should be something like a `.get_def_registar_addr()` - # or similar since, - # - it should be a **host singleton** (not root/tree singleton) - # - we **only need this value** when one isn't provided to the - # runtime at boot and we want to implicitly provide a host-wide - # registrar. - # - each rooted-actor-tree should likely have its own - # micro-registry (likely the root being it), also see - @classmethod - def get_root(cls) -> Address: - ... - - def __repr__(self) -> str: - ... - - def __eq__(self, other) -> bool: - ... - - async def open_listener( - self, - **kwargs, - ) -> SocketListener: - ... - - async def close_listener(self): - ... - - # the address types available on this host: TCP always, UDS only # where usable (`HAS_UDS`). Both registries derive from this single # list via each type's `proto_key`. -_address_protos: list[Type[Address]] = [TCPAddress] +_address_protos: list[_AddressType] = [TCPAddress] if HAS_UDS: _address_protos.append(UDSAddress) -_address_types: dict[str, Type[Address]] = { +_address_types: dict[str, _AddressType] = { cls.proto_key: cls for cls in _address_protos } @@ -216,13 +61,22 @@ _address_types: dict[str, Type[Address]] = { # TODO! really these are discovery sys default addrs ONLY useful for # when none is provided to a root actor on first boot. +# +# TODO, this should be something like a `.get_def_registar_addr()` +# or similar since, +# - it should be a **host singleton** (not root/tree singleton) +# - we **only need this value** when one isn't provided to the +# runtime at boot and we want to implicitly provide a host-wide +# registrar. +# - each rooted-actor-tree should likely have its own +# micro-registry (likely the root being it), also see _default_lo_addrs: dict[str, UnwrappedAddress] = { cls.proto_key: cls.get_root().unwrap() for cls in _address_protos } -def get_address_cls(name: str) -> Type[Address]: +def get_address_cls(name: str) -> _AddressType: try: return _address_types[name] except KeyError: @@ -233,7 +87,7 @@ def get_address_cls(name: str) -> Type[Address]: ) -def is_wrapped_addr(addr: any) -> bool: +def is_wrapped_addr(addr: object) -> bool: # XXX NOTE, a `TunnelledAddress` is genuinely "wrapped" but is # deliberately NOT in `_address_types`: it has no # `MsgTransport` of its own (a tunnel is transparent to @@ -264,10 +118,9 @@ def wrap_address( |LegacyUnwrappedAddress |list[str|int] |str - |Address - |TunnelledAddress + |AddressDeclaration ), -) -> Address|TunnelledAddress: +) -> AddressDeclaration: ''' Wrap an `UnwrappedAddress` as an `Address`-type based on matching builtin python data-structures which we adhoc @@ -284,7 +137,7 @@ def wrap_address( if is_wrapped_addr(addr): return addr - cls: Type|None = None + cls: _AddressType|None = None # if 'sock' in addr[0]: # import pdbp; pdbp.set_trace() match addr: @@ -328,7 +181,7 @@ def wrap_address( | [None, None] ): - cls: Type[Address] = get_address_cls(_def_tpt_proto) + cls = get_address_cls(_def_tpt_proto) addr: UnwrappedAddress = cls.get_root().unwrap() # multiaddr-format string, e.g. @@ -354,7 +207,7 @@ def wrap_address( def default_lo_addrs( transports: list[str], -) -> list[Type[Address]]: +) -> list[UnwrappedAddress]: ''' Return the default, host-singleton, registry address for an input transport key set. diff --git a/tractor/discovery/_multiaddr.py b/tractor/discovery/_multiaddr.py index 2fd7899d..b10a97a3 100644 --- a/tractor/discovery/_multiaddr.py +++ b/tractor/discovery/_multiaddr.py @@ -27,24 +27,15 @@ Multiaddress support using the upstream `py-multiaddr` lib from __future__ import annotations import ipaddress from pathlib import Path -from typing import ( - Any, - TYPE_CHECKING, -) +from typing import TYPE_CHECKING + +from .types import AddressDeclaration if TYPE_CHECKING: # NOTE, `multiaddr` is lazy-imported at first use # (in the fns below) to keep it off the eager # `import tractor` path (gh #470). from multiaddr import Multiaddr - from tractor.discovery._addr import Address - from tractor.net._tunnel import ( - TunnelledAddress, - ) -else: - Multiaddr = Any - Address = Any - TunnelledAddress = Any # map from tractor-internal `proto_key` identifiers # to the standard multiaddr protocol name strings. @@ -61,7 +52,7 @@ _maddr_to_tpt_proto: dict[str, str] = { def mk_maddr( - addr: 'Address|TunnelledAddress', + addr: AddressDeclaration, ) -> Multiaddr: ''' Construct a `Multiaddr` from a tractor `Address` instance, @@ -111,7 +102,7 @@ def mk_maddr( def parse_maddr( maddr_str: str, -) -> 'Address|TunnelledAddress': +) -> AddressDeclaration: ''' Parse a multiaddr string into a tractor `Address`. @@ -173,15 +164,15 @@ def parse_maddr( # or raw unwrapped-address tuples (as accepted by # `wrap_address()`). EndpointsTable = dict[ - str, # actor/service name - list[str|tuple], # maddr strs or UnwrappedAddress + str, # actor/service name + list[str|tuple|AddressDeclaration], ] # output table: actor/service name -> list of wrapped address # declarations ready for bindspace handling. ParsedEndpoints = dict[ - str, # actor/service name - list['Address|TunnelledAddress'], + str, # actor/service name + list[AddressDeclaration], ] diff --git a/tractor/discovery/types.py b/tractor/discovery/types.py new file mode 100644 index 00000000..571f8e58 --- /dev/null +++ b/tractor/discovery/types.py @@ -0,0 +1,176 @@ +# 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 +# . + +''' +Dependency-neutral address typing declarations. + +Keep this module independent of concrete IPC transports, actor +runtime models, network resources, tunnels, and optional +dependencies. + +''' +from __future__ import annotations + +from pathlib import Path +from typing import ( + ClassVar, + Literal, + Protocol, + TypeAlias, +) + + +# TODO, maybe breakout the netns key to a struct? +# class NetNs(Struct)[str, int]: +# ... + +# TODO, can't we just use a type alias +# for this? namely just some `tuple[str, int, str, str]`? +# +# -[ ] would also just be simpler to keep this as +# SockAddr[tuple] or something, implying it's just a simple pair +# of values which can presumably be mapped to all transports? +# -[ ] `pydoc socket.socket.getsockname()` delivers a 4-tuple for +# ipv6 `(hostaddr, port, flowinfo, scope_id)`.. so how should we +# handle that? +# -[ ] as a further alternative to this wrap()/unwrap() approach we +# could just implement `enc/dec_hook()`s for the `Address`-types +# and just deal with our internal objs directly and always and +# leave it to the codec layer to figure out marshalling? +# |_ would mean only one spot to do the `.unwrap()` (which we may +# end up needing to call from the hook()s anyway?) +# -[x] rename to `UnwrappedAddress[Descriptor]` ?? +# seems like the right name as per the GeeksForGeeks article, +# "Introduction to Address Descriptor". +# +TaggedTCPAddress: TypeAlias = tuple[ + Literal['tcp'], + str, + int, +] +TaggedUnixAddress: TypeAlias = tuple[ + Literal['unix'], + str, +] +TaggedUDSAlias: TypeAlias = tuple[ + Literal['uds'], + str, +] +TaggedAddress: TypeAlias = ( + TaggedTCPAddress + |TaggedUnixAddress +) + +# Input-only compatibility forms retained for older callers and +# serialized payloads. +LegacyTCPAddress: TypeAlias = tuple[str, int] +LegacyUDSAddress: TypeAlias = tuple[str, str] +LegacyUnwrappedAddress: TypeAlias = ( + LegacyTCPAddress + |LegacyUDSAddress +) +UnwrappedAddress = TaggedAddress + + +class AddressDeclaration(Protocol): + ''' + Common instance shape of plain and tunnelled addresses. + + This protocol deliberately excludes transport registration and + listener operations. A tunnel declaration delegates these address + properties to its innermost concrete transport address. + + ''' + @property + def proto_key(self) -> str: + ... + + @property + def is_valid(self) -> bool: + ... + + @property + def namespace(self) -> tuple[str, str|int]|None: + ... + + @property + def bindspace(self) -> str|Path: + ... + + def unwrap(self) -> UnwrappedAddress: + ... + + +# TODO, maybe rename to `SocketAddress`? +class Address(Protocol): + ''' + Concrete address contract used by IPC transport backends. + + Unlike `AddressDeclaration`, transport registries require + class-level protocol metadata. Transport-specific factories stay + on their concrete address classes because their descriptor and + bindspace inputs differ. + + ''' + proto_key: ClassVar[str] + unwrapped_type: ClassVar[type] + + # TODO, i feel like an `.is_bound()` is a better thing to + # support? + # Lke, what use does this have besides a noop and if it's not + # valid why aren't we erroring on creation/use? + @property + def is_valid(self) -> bool: + ... + + # TODO, maybe `.netns` is a better name? + @property + def namespace(self) -> tuple[str, str|int]|None: + ''' + The if-available, OS-specific "network namespace" key. + + ''' + ... + + @property + def bindspace(self) -> str|Path: + ''' + Deliver the address' transport-specific bindable space. + + ''' + ... + + def unwrap(self) -> UnwrappedAddress: + ''' + Deliver the underlying primitive address descriptor. + + ''' + ... + +__all__ = ( + 'Address', + 'AddressDeclaration', + 'LegacyTCPAddress', + 'LegacyUDSAddress', + 'LegacyUnwrappedAddress', + 'TaggedAddress', + 'TaggedTCPAddress', + 'TaggedUDSAlias', + 'TaggedUnixAddress', + 'UnwrappedAddress', +) diff --git a/tractor/ipc/_chan.py b/tractor/ipc/_chan.py index 13aa0244..750ff1c6 100644 --- a/tractor/ipc/_chan.py +++ b/tractor/ipc/_chan.py @@ -43,7 +43,10 @@ from ._types import ( from tractor.discovery._addr import ( is_wrapped_addr, wrap_address, +) +from tractor.discovery.types import ( Address, + AddressDeclaration, UnwrappedAddress, ) from tractor.log import get_logger @@ -59,9 +62,6 @@ from tractor.msg import ( if TYPE_CHECKING: from ._transport import MsgTransport - from tractor.net._tunnel import TunnelledAddress -else: - TunnelledAddress = Any log = get_logger() @@ -185,7 +185,7 @@ class Channel: @classmethod async def from_addr( cls, - addr: UnwrappedAddress|Address|TunnelledAddress, + addr: UnwrappedAddress|AddressDeclaration, **kwargs ) -> Channel: @@ -557,7 +557,7 @@ class Channel: @acm async def _connect_chan( - addr: UnwrappedAddress|Address|TunnelledAddress, + addr: UnwrappedAddress|AddressDeclaration, close_timeout: float|None = None, ) -> typing.AsyncGenerator[Channel, None]: ''' diff --git a/tractor/ipc/_server.py b/tractor/ipc/_server.py index 84f5ebc1..dd3d8b69 100644 --- a/tractor/ipc/_server.py +++ b/tractor/ipc/_server.py @@ -59,8 +59,10 @@ from ..msg import ( from ..trionics import maybe_open_nursery from ..runtime import _state from .. import log -from ..discovery._addr import ( +from ..discovery._addr import get_address_cls +from ..discovery.types import ( Address, + AddressDeclaration, UnwrappedAddress, ) from ._chan import Channel @@ -68,7 +70,6 @@ from ._transport import MsgTransport if TYPE_CHECKING: - from ..net._tunnel import TunnelledAddress from ..runtime._runtime import Actor from ..runtime._supervise import ActorNursery @@ -631,7 +632,7 @@ class Endpoint(Struct): ''' addr: Address - declared_addr: Address|TunnelledAddress + declared_addr: AddressDeclaration listen_tn: Nursery stream_handler_tn: Nursery|None = None @@ -689,7 +690,8 @@ class Endpoint(Struct): != self.addr.unwrap() ): - self.addr=self.addr.from_addr(unwrapped) + address_type = get_address_cls(self.addr.proto_key) + self.addr = address_type.from_addr(unwrapped) self._listener = lstnr return lstnr @@ -991,7 +993,7 @@ class Server(Struct): self, *, accept_addrs: list[ - UnwrappedAddress|Address|TunnelledAddress + UnwrappedAddress|AddressDeclaration ]|None = None, stream_handler_nursery: Nursery|None = None, ) -> list[Endpoint]: @@ -1075,7 +1077,7 @@ async def _serve_ipc_eps( *, server: IPCServer, stream_handler_tn: Nursery, - listen_addrs: list[Address|TunnelledAddress], + listen_addrs: list[AddressDeclaration], task_status: TaskStatus[ Nursery, diff --git a/tractor/ipc/_tcp.py b/tractor/ipc/_tcp.py index 40cb8e81..fe3e525e 100644 --- a/tractor/ipc/_tcp.py +++ b/tractor/ipc/_tcp.py @@ -20,7 +20,6 @@ TCP implementation of tractor.ipc._transport.MsgTransport protocol from __future__ import annotations import ipaddress from typing import ( - Any, ClassVar, TYPE_CHECKING, ) @@ -41,15 +40,12 @@ from tractor.ipc._transport import ( MsgTransport, MsgpackTransport, ) +from tractor.discovery.types import TaggedTCPAddress if TYPE_CHECKING: # ONLY type-annots, the eager import costs # `import tractor` wall-time (gh #470). from multiaddr import Multiaddr - from tractor.discovery._addr import TaggedTCPAddress -else: - Multiaddr = Any - TaggedTCPAddress = Any log = get_logger() diff --git a/tractor/ipc/_transport.py b/tractor/ipc/_transport.py index 018b70c4..5afdde27 100644 --- a/tractor/ipc/_transport.py +++ b/tractor/ipc/_transport.py @@ -55,9 +55,7 @@ from tractor.msg import ( types as msgtypes, pretty_struct, ) - -if TYPE_CHECKING: - from tractor.discovery._addr import Address +from tractor.discovery.types import Address log = get_logger() diff --git a/tractor/ipc/_uds.py b/tractor/ipc/_uds.py index cfba9519..387733d0 100644 --- a/tractor/ipc/_uds.py +++ b/tractor/ipc/_uds.py @@ -43,7 +43,6 @@ except ImportError: AF_UNIX = None import struct from typing import ( - Any, Type, TYPE_CHECKING, ClassVar, @@ -71,17 +70,13 @@ from tractor.runtime._state import ( current_actor, is_root_process, ) +from tractor.discovery.types import TaggedUnixAddress if TYPE_CHECKING: # ONLY type-annots, the eager import costs # `import tractor` wall-time (gh #470). from multiaddr import Multiaddr - from tractor.discovery._addr import TaggedUnixAddress from tractor.runtime._runtime import Actor -else: - Multiaddr = Any - Actor = Any - TaggedUnixAddress = Any # Platform-specific credential passing constants diff --git a/tractor/net/_tunnel.py b/tractor/net/_tunnel.py index 34dfe79a..c76e64eb 100644 --- a/tractor/net/_tunnel.py +++ b/tractor/net/_tunnel.py @@ -93,6 +93,11 @@ import multibase import trio from ..msg._local import ProcessLocal +from ..discovery.types import ( + Address, + AddressDeclaration, + UnwrappedAddress, +) from ._bindspace import ( Bindspace, BindspaceRef, @@ -103,16 +108,6 @@ from ._bindspace import ( if TYPE_CHECKING: from multiaddr import Multiaddr - from ..discovery._addr import ( - Address, - UnwrappedAddress, - ) -else: - Address = Any - Multiaddr = Any - UnwrappedAddress = Any - - class WGTunnelSpec( msgspec.Struct, frozen=True, @@ -879,7 +874,7 @@ class TunnelledAddress( `strip_tunnels()`. ''' - overlay: Address|TunnelledAddress + overlay: AddressDeclaration tunnel: TunnelSpec bindspace_ref: BindspaceRef|None = None @@ -1057,7 +1052,7 @@ def parse_wg_maddr( match overlay_names: case [('ip4' | 'ip6'), 'tcp']: from ..discovery._multiaddr import parse_maddr - overlay: Address|TunnelledAddress = parse_maddr( + overlay: AddressDeclaration = parse_maddr( str(overlay_ma) ) @@ -1172,7 +1167,7 @@ def mk_wg_maddr( def strip_tunnels( - addr: Address|TunnelledAddress, + addr: AddressDeclaration, ) -> Address: ''' Deliver the bindable `Address`, peeling any tunnel @@ -1191,7 +1186,7 @@ def strip_tunnels( def tunnels_of( - addr: Address|TunnelledAddress, + addr: AddressDeclaration, ) -> tuple[TunnelSpec, ...]: ''' Deliver every tunnel spec wrapping `addr`, outermost first. diff --git a/tractor/runtime/_runtime.py b/tractor/runtime/_runtime.py index 27e861f8..dcc187a3 100644 --- a/tractor/runtime/_runtime.py +++ b/tractor/runtime/_runtime.py @@ -60,7 +60,6 @@ import sys from typing import ( Any, Callable, - Type, TYPE_CHECKING, ) import uuid @@ -1684,10 +1683,10 @@ async def async_main( [_state._def_tpt_proto] ) for transport_key in enable_transports: - transport_cls: Type[Address] = get_address_cls( + address_type = get_address_cls( transport_key ) - addr: Address = transport_cls.get_random() + addr: Address = address_type.get_random() accept_addrs.append(addr.unwrap()) # XXX, either passed in by caller or delivered