Skip to content

Client

TCP client for tensor transport.

This class provides a thin wrapper around Python's socket for sending and receiving messages that follow the tannic tensor protocol:

[Header][Payload]
  • Header is defined in pytannic.header.Header. It includes:
  • magic number (MAGIC) for validation
  • protocol version
  • checksum (not validated yet)
  • payload size in bytes
  • Payload is application-dependent:
  • Serialized tensor (see pytannic.torch.tensor)
  • Serialized metadata (see pytannic.torch.parameters)
  • Or other binary blobs

The client ensures full reception of both the header and payload.

Client

TCP client for communicating with a tannic tensor server.

Supports context-manager usage:

with Client("localhost", 9000) as client: ... client.send(b"hello") ... reply = client.receive()

Attributes

host : str Server hostname or IP. port : int Server TCP port. socket : socket.socket or None Underlying socket object. Created in begin().

Source code in pytannic/client.py
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
class Client:
    """
    TCP client for communicating with a tannic tensor server.

    Supports context-manager usage:

    >>> with Client("localhost", 9000) as client:
    ...     client.send(b"hello")
    ...     reply = client.receive()

    Attributes
    ----------
    host : str
        Server hostname or IP.
    port : int
        Server TCP port.
    socket : socket.socket or None
        Underlying socket object. Created in `begin()`.
    """
    def __init__(self, host: str, port: int):
        self.host = host
        self.port = port
        self.socket = None

    def __enter__(self):
        """Enable `with Client(...) as client:` usage."""
        self.begin()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Close the socket when leaving context manager scope."""
        self.close()

    def begin(self):
        """
        Establish a TCP connection to the server.

        Raises
        ------
        OSError
            If the socket cannot connect to the given host/port.
        """
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.connect((self.host, self.port))

    def close(self):
        """
        Close the socket connection if open.
        """
        if self.socket:
            self.socket.close()
            self.socket = None

    def send(self, data: bytes): 
        """
        Send raw bytes to the server.

        Parameters
        ----------
        data : bytes
            A complete tannic message, including header and payload.

        Raises
        ------
        RuntimeError
            If the socket is not connected.
        """
        if not self.socket:
            raise RuntimeError("Socket not connected")
        self.socket.sendall(data)

    def receive(self) -> bytes: 
        """
        Receive a complete tannic message (header + payload).

        Returns
        -------
        bytes
            The raw message received, including the header and payload.

        Raises
        ------
        ValueError
            If the received header has an invalid magic number.
        ConnectionError
            If the socket closes before the expected number of bytes is received.
        """
        hsize = calcsize(Header.FORMAT)
        header_data = self._recvall(hsize)
        header = Header.unpack(header_data)
        if header.magic != MAGIC:
            raise ValueError("Invalid magic number in received data")

        payload = self._recvall(header.nbytes) 
        return header_data + payload

    def _recvall(self, size: int) -> bytes: 
        buffer = b""
        while len(buffer) < size:
            chunk = self.socket.recv(size - len(buffer))
            if not chunk:
                raise ConnectionError("Socket closed before receiving enough data")
            buffer += chunk
        return buffer

__enter__()

Enable with Client(...) as client: usage.

Source code in pytannic/client.py
62
63
64
65
def __enter__(self):
    """Enable `with Client(...) as client:` usage."""
    self.begin()
    return self

__exit__(exc_type, exc_val, exc_tb)

Close the socket when leaving context manager scope.

Source code in pytannic/client.py
67
68
69
def __exit__(self, exc_type, exc_val, exc_tb):
    """Close the socket when leaving context manager scope."""
    self.close()

begin()

Establish a TCP connection to the server.

Raises

OSError If the socket cannot connect to the given host/port.

Source code in pytannic/client.py
71
72
73
74
75
76
77
78
79
80
81
def begin(self):
    """
    Establish a TCP connection to the server.

    Raises
    ------
    OSError
        If the socket cannot connect to the given host/port.
    """
    self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    self.socket.connect((self.host, self.port))

close()

Close the socket connection if open.

Source code in pytannic/client.py
83
84
85
86
87
88
89
def close(self):
    """
    Close the socket connection if open.
    """
    if self.socket:
        self.socket.close()
        self.socket = None

receive()

Receive a complete tannic message (header + payload).

Returns

bytes The raw message received, including the header and payload.

Raises

ValueError If the received header has an invalid magic number. ConnectionError If the socket closes before the expected number of bytes is received.

Source code in pytannic/client.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
def receive(self) -> bytes: 
    """
    Receive a complete tannic message (header + payload).

    Returns
    -------
    bytes
        The raw message received, including the header and payload.

    Raises
    ------
    ValueError
        If the received header has an invalid magic number.
    ConnectionError
        If the socket closes before the expected number of bytes is received.
    """
    hsize = calcsize(Header.FORMAT)
    header_data = self._recvall(hsize)
    header = Header.unpack(header_data)
    if header.magic != MAGIC:
        raise ValueError("Invalid magic number in received data")

    payload = self._recvall(header.nbytes) 
    return header_data + payload

send(data)

Send raw bytes to the server.

Parameters

data : bytes A complete tannic message, including header and payload.

Raises

RuntimeError If the socket is not connected.

Source code in pytannic/client.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def send(self, data: bytes): 
    """
    Send raw bytes to the server.

    Parameters
    ----------
    data : bytes
        A complete tannic message, including header and payload.

    Raises
    ------
    RuntimeError
        If the socket is not connected.
    """
    if not self.socket:
        raise RuntimeError("Socket not connected")
    self.socket.sendall(data)