From e904af679ba74e3bc4de187cdbbbb20ecd735b21 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Sun, 30 Mar 2025 22:35:35 -0400 Subject: [PATCH] Add a big boi `Channel.pformat()/__repr__()` Much like how `Context` has been implemented, try to give tons of high level details on all the lower level encapsulated primitives, namely the `.msgstream/.transport` and any useful runtime state. B) Impl deats, - adjust `.from_addr()` to only call `._addr.wrap_address()` when we detect `addr` is unwrapped. - add another `log.runtime()` using the new `.__repr__()` in `Channel.from_addr()`. - change to `UnwrappedAddress` as in prior commits. --- tractor/ipc/_chan.py | 78 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 16 deletions(-) diff --git a/tractor/ipc/_chan.py b/tractor/ipc/_chan.py index 93f17132..1175cbb6 100644 --- a/tractor/ipc/_chan.py +++ b/tractor/ipc/_chan.py @@ -24,6 +24,7 @@ from contextlib import ( asynccontextmanager as acm, contextmanager as cm, ) +import os import platform from pprint import pformat import typing @@ -39,9 +40,10 @@ from tractor.ipc._types import ( transport_from_stream, ) from tractor._addr import ( + is_wrapped_addr, wrap_address, Address, - AddressTypes + UnwrappedAddress, ) from tractor.log import get_logger from tractor._exceptions import ( @@ -88,7 +90,8 @@ class Channel: self.uid: tuple[str, str]|None = None self._aiter_msgs = self._iter_msgs() - self._exc: Exception|None = None # set if far end actor errors + self._exc: Exception|None = None + # ^XXX! ONLY set if a remote actor sends an `Error`-msg self._closed: bool = False # flag set by ``Portal.cancel_actor()`` indicating remote @@ -124,17 +127,26 @@ class Channel: @classmethod async def from_addr( cls, - addr: AddressTypes, + addr: UnwrappedAddress, **kwargs ) -> Channel: - addr: Address = wrap_address(addr) - transport_cls = transport_from_addr(addr) - transport = await transport_cls.connect_to(addr, **kwargs) - log.transport( - f'Opened channel[{type(transport)}]: {transport.laddr} -> {transport.raddr}' + if not is_wrapped_addr(addr): + addr: Address = wrap_address(addr) + + transport_cls = transport_from_addr(addr) + transport = await transport_cls.connect_to( + addr, + **kwargs, ) - return Channel(transport=transport) + assert transport.raddr == addr + chan = Channel(transport=transport) + log.runtime( + f'Connected channel IPC transport\n' + f'[>\n' + f' |_{chan}\n' + ) + return chan @cm def apply_codec( @@ -154,16 +166,50 @@ class Channel: self._transport.codec = orig # TODO: do a .src/.dst: str for maddrs? - def __repr__(self) -> str: + def pformat(self) -> str: if not self._transport: return '' - return repr( - self._transport - ).replace( # type: ignore - "socket.socket", - "Channel", + tpt: MsgTransport = self._transport + tpt_name: str = type(tpt).__name__ + tpt_status: str = ( + 'connected' if self.connected() + else 'closed' ) + return ( + f'\n' + ) + + # NOTE: making this return a value that can be passed to + # `eval()` is entirely **optional** FYI! + # https://docs.python.org/3/library/functions.html#repr + # https://docs.python.org/3/reference/datamodel.html#object.__repr__ + # + # Currently we target **readability** from a (console) + # logging perspective over `eval()`-ability since we do NOT + # target serializing non-struct instances! + # def __repr__(self) -> str: + __str__ = pformat + __repr__ = pformat @property def laddr(self) -> Address|None: @@ -338,7 +384,7 @@ class Channel: @acm async def _connect_chan( - addr: AddressTypes + addr: UnwrappedAddress ) -> typing.AsyncGenerator[Channel, None]: ''' Create and connect a channel with disconnect on context manager