Client-server subpackage (ska_ser_devices.client_server)

This subpackage supports client-server communication.

A key design element is that received data is provided to the application layer via a bytestring iterator:

Receipt of an application payload may be split across multiple buffers, and the manner by which the application layer decides that it has received enough bytes to comprise a full application payload is exclusively determined by the application.

For example, the application layer might deal in “lines”, and receiving a “line” might involve continually receiving bytestrings until a line terminator character sequence such as “rn” is encountered.

Using TCP as an example for how transport protocol details (e.g. socket reads) are commonly mixed with application-level decision logic, the concatenation of bytestrings into an application payload is typically implemented like:

data = my_socket.recv(1024)
while not data.endswith(b"\r\n"):
    data = data + my_socket.recv(1024)

Instead, this module handles this situation by passing a bytestring iterator to the application layer. Thus, it is the application layer’s job to iterate over bytestrings until it has constructed a full application payload. Meanwhile, the transport protocol details remain hidden, and the application remains agnostic of any transport mechanism.

data = next(bytes_iterator)
while not data.endswith(b"\r\n"):
    data = data + next(bytes_iterator)
class ska_ser_devices.client_server.ApplicationClient(bytes_client, request_marshaller, response_unmarshaller)

An application-layer client.

It uses an underlying transport-layer client (such as a TCP client), but requests are application payloads rather than bytestrings. These must be marshalled into bytestrings before sending to the server. When a response is received from the server, the received bytes are unmarshalled into an application payload which is returned to the caller. Thus, the caller deals only in application payloads, and does not know any details about how the transport layer transmits those payloads,

__enter__()

Establish a new connection, and enter the new session context.

Return type

ApplicationClientSession[TypeVar(RequestPayloadT), TypeVar(ResponsePayloadT)]

Returns

access to the session context.

__exit__(exc_type, exception, trace)

Exit method for “with” context.

Parameters
Return type

bool

Returns

whether the exception (if any) has been fully handled by this method and should be swallowed i.e. not re-raised

__init__(bytes_client, request_marshaller, response_unmarshaller)

Initialise a new instance.

Parameters
  • bytes_client (TransportClientProtocol) – the bytes-level client to use.

  • request_marshaller (Callable[[TypeVar(RequestPayloadT)], bytes]) – a callable that marshalls application-layer request payloads into bytes.

  • response_unmarshaller (Callable[[Iterator[bytes]], TypeVar(ResponsePayloadT)]) – a callable that unmarshalls byte into application-layer response payloads.

__weakref__

list of weak references to the object (if defined)

connect()

Establish a new connection.

Return type

ApplicationClientSession[TypeVar(RequestPayloadT), TypeVar(ResponsePayloadT)]

Returns

access to the new session.

class ska_ser_devices.client_server.ApplicationClientSession(request_marshaller, response_unmarshaller, transport_session)

A class for representing and managing a session.

__init__(request_marshaller, response_unmarshaller, transport_session)

Initialise a new session instance.

Parameters
  • request_marshaller (Callable[[TypeVar(RequestPayloadT)], bytes]) – a callable that marshalls application-layer request payloads into bytes.

  • response_unmarshaller (Callable[[Iterator[bytes]], TypeVar(ResponsePayloadT)]) – a callable that unmarshalls byte into application-layer response payloads.

  • transport_session (TransportClientSessionProtocol) – the underlying transport-layer session.

__weakref__

list of weak references to the object (if defined)

close()

Close the connection and end the session.

Return type

None

send(request)

Call the client with a request, for which no response is expected.

The client marshalls the request (an application payload) down to a bytestring, then hands the bytestring down to the bytestring TCP client for sending to the server.

Parameters

request (TypeVar(RequestPayloadT)) – the payload to be sent to the server

Return type

None

send_receive(request)

Call the client with a request, for which a response is expected.

The client marshalls the request (an application payload) down to a bytestring, then hands the bytestring down to the bytestring TCP client for sending to the server. Upon receive of a bytestring response, this client unmarshalls it into an application payload, which is returned to the caller.

Parameters

request (TypeVar(RequestPayloadT)) – the payload to be sent to the server

Return type

TypeVar(ResponsePayloadT)

Returns

a response payload

class ska_ser_devices.client_server.ApplicationServer(request_unmarshaller, response_marshaller, callback)

A server of application payloads.

It uses an underlying transport-layer server (such as a TCP server), but requests are unmarshalled into application-layer payloads before being delivered to the application-layer server backend. Responses from the server backend are application-layer payloads that this server marshals down to a bytestring and returns to the client. Thus, the server backend deals only in application payloads, and does not know any details about how the transport layer transmits those payloads.

__call__(bytes_iterator)

Handle receipt of bytes from the transport layer.

When the transport layer server receives some bytes, it calls this callback with a bytestring iterator. This callback uses that iterator to ingest bytestrings until it has enough bytes to unmarshall them into a complete application payload. The payload is passed to the server backend. Once this callback receives a response from the backend, it marshalls that response down to a bytestring, and returns it to the transport-layer server for returning to the client.

Parameters

bytes_iterator (Iterator[bytes]) – a bytestring iterator.

Return type

Optional[bytes]

Returns

the bytes to be returned to the client.

__init__(request_unmarshaller, response_marshaller, callback)

Initialise a new instance.

Parameters
  • request_unmarshaller (Callable[[Iterator[bytes]], TypeVar(RequestPayloadT)]) – a callable that unmarshalls byte into application-layer request payloads.

  • response_marshaller (Callable[[TypeVar(ResponsePayloadT)], bytes]) – a callable that marshalls application-layer response payloads into bytes

  • callback (Callable[[TypeVar(RequestPayloadT)], Optional[TypeVar(ResponsePayloadT)]]) – callback to the application layer. When this server receives a request payload, it calls the application layer with the request, and expects to receive a response payload back.

__weakref__

list of weak references to the object (if defined)

class ska_ser_devices.client_server.FixedLengthBytesMarshaller(length, logger=None)

A bytes marshaller for bytestrings of fixed, known length.

__init__(length, logger=None)

Initialise a new instance.

Parameters
  • length (int) – the length of the payload

  • logger (Optional[Logger]) – a python standard logger

__weakref__

list of weak references to the object (if defined)

marshall(payload)

Marshall application-layer payload bytes into bytes to be transmitted.

This class simply appends the sentinel character sequence.

Parameters

payload (bytes) – the application-layer payload bytes.

Return type

bytes

Returns

the bytes to be transmitted.

Raises

ValueError – if the received bytestring is not of the correct length

unmarshall(bytes_iterator)

Unmarshall transmitted bytes into application-layer payload bytes.

This method is implemented to continually receive bytestrings until it receives a bytestring terminated by the sentinel. It then strips the sentinel off, and returns the rest.

Parameters

bytes_iterator (Iterator[bytes]) – an iterator of bytestrings received by the server

Return type

bytes

Returns

the application-layer bytestring.

Raises

ValueError – if the received bytestring is not of the correct length

class ska_ser_devices.client_server.SentinelBytesMarshaller(sentinel, logger=None)

A bytes marshaller that marshalls and unmarshalls terminated bytestrings.

That is, the application-layer payload is a bytestring, the end of which is demarcated by a special character sequence. For example, the payload might be

  • A c-style string, terminated by the NUL character;

  • A line of text, terminated by an end-of-line sequence, such as “rn”.

  • A file, terminated by an EOF byte.

__init__(sentinel, logger=None)

Initialise a new instance.

Parameters
  • sentinel (bytes) – the sentinel character that marks the end of the payload

  • logger (Optional[Logger]) – a python standard logger

__weakref__

list of weak references to the object (if defined)

marshall(payload)

Marshall application-layer payload bytes into bytes to be transmitted.

This class simply appends the sentinel character sequence.

Parameters

payload (bytes) – the application-layer payload bytes.

Return type

bytes

Returns

the bytes to be transmitted.

unmarshall(bytes_iterator)

Unmarshall transmitted bytes into application-layer payload bytes.

This method is implemented to continually receive bytestrings until it receives a bytestring terminated by the sentinel. It then strips the sentinel off, and returns the rest.

Parameters

bytes_iterator (Iterator[bytes]) – an iterator of bytestrings received by the server

Return type

bytes

Returns

the application-layer bytestring, minus the terminator.

class ska_ser_devices.client_server.TcpClient(address, timeout=None, buffer_size=1024, logger=None)

A TCP client that operates at the bytestring level.

It handles client requests by sending the request bytes straight off to the server. However, when it receives a response, it creates a bytestring iterator and hands it up to the application layer, so that the application layer can receive as many bytestrings as it needs to constitute an application payload.

__enter__()

Establish a new connection and enter the session context.

Return type

TcpClientSession

Returns

access to the session context

__exit__(exc_type, exception, trace)

Exit method for “with” context.

Parameters
Return type

bool

Returns

whether the exception (if any) has been fully handled by this method and should be swallowed i.e. not re-raised

__init__(address, timeout=None, buffer_size=1024, logger=None)

Initialise a new instance.

Parameters
  • address (tuple[str | bytes | bytearray, int]) – tuple consisting of the host name or IP address, and the port, of the server.

  • timeout (Optional[float]) – how long to wait when attempting to send or receive data, in seconds. If None, the socket blocks indefinitely.

  • buffer_size (int) – maximum size of a bytestring.

  • logger (Optional[Logger]) – a python standard logger

__weakref__

list of weak references to the object (if defined)

connect()

Establish a new connection.

Return type

TcpClientSession

Returns

access to the established session.

class ska_ser_devices.client_server.TcpClientSession(address, timeout, buffer_size, logger)

A class for representing and managing a TCP session.

__init__(address, timeout, buffer_size, logger)

Establish a new session.

Parameters
  • address (tuple[str | bytes | bytearray, int]) – a tuple consisting of the host name or IP address, and the port, of the server.

  • timeout (Optional[float]) – how long to wait when attempting to send or receive data, in seconds. If None, the socket blocks indefinitely.

  • buffer_size (int) – maximum size of a bytestring.

  • logger (Logger) – a python standard logger

__weakref__

list of weak references to the object (if defined)

close()

Close the connection and end the session.

Return type

None

request(request)

Initiate a new client request.

For example:

with tcp_client as session:
    bytes_iterator = session.request(request_bytes):
    response_bytes = next(bytes_iterator)
    if not response_bytes.endswith(b"\r\n"):
        response_bytes += next(bytes_iterator)

That is,

  • First enter into a session with the TCP server

  • Then send the request data.

  • Since only the calling application can know

    when it has received enough bytes for a complete response, the session context returns a bytestring iterator for the application layer to use to retrieve blocks of bytes. (In this example, the application layer knows that the response is terminated by “rn”, so it keeps receiving data until it encounters that sequence and the end of a block.)

  • Upon exiting the session context, the session is closed.

Parameters

request (bytes) – request bytes.

Raises

ConnectionError – if the session socket is already closed.

Return type

Iterator[bytes]

Returns

a bytestring iterator.

class ska_ser_devices.client_server.TcpServer(host, port, callback, buffer_size=1024, logger=None)

A TCP server that operates at the bytestring level.

It handles client requests by creating a bytestring iterator, and handing it up to the application layer, so that the application layer can receive as many bytestrings as it needs to constitute an application payload. When the application layer returns a response, that response is sent back to the client.

__init__(host, port, callback, buffer_size=1024, logger=None)

Initialise a new instance.

Parameters
  • host (str) – host name or IP address of the server.

  • port (int) – port on which the server is running.

  • callback (Callable[[Iterator[bytes]], Optional[bytes]]) – the application layer callback to call when bytes are received

  • buffer_size (int) – maximum size of a bytestring.

  • logger (Optional[Logger]) – a python standard logger

class ska_ser_devices.client_server.TelnetClient(address, timeout=None, logger=None)

A Telnet client.

It handles client requests by sending the request bytes straight off to the server. However, when it receives a response, it creates a bytestring iterator and hands it up to the application layer, so that the application layer can receive as many bytestrings as it needs to constitute an application payload.

__enter__()

Establish a new connection and enter the session context.

Return type

TelnetClientSession

Returns

access to the session context

__exit__(exc_type, exception, trace)

Exit method for “with” context.

Parameters
Return type

bool

Returns

whether the exception (if any) has been fully handled by this method and should be swallowed i.e. not re-raised

__init__(address, timeout=None, logger=None)

Initialise a new instance.

Parameters
  • address (tuple[str, int]) – tuple consisting of the host name or IP address, and the port, of the server.

  • timeout (Optional[float]) – how long to wait when attempting to send or receive data.

  • logger (Optional[Logger]) – a python standard logger

__weakref__

list of weak references to the object (if defined)

connect()

Establish a new connection.

Return type

TelnetClientSession

Returns

access to the established session.

class ska_ser_devices.client_server.TelnetClientSession(address, timeout, logger)

A class for representing and managing a TCP session.

__init__(address, timeout, logger)

Establish a new session.

Parameters
  • address (tuple[str, int]) – a tuple consisting of the host name or IP address, and the port, of the server.

  • timeout (Optional[float]) – how long to wait when attempting to send or receive data, in seconds. If None, the socket blocks indefinitely.

  • logger (Logger) – a python standard logger

__weakref__

list of weak references to the object (if defined)

close()

Close the connection and end the session.

Return type

None

request(request)

Initiate a new client request.

Call this method like

with telnet_client as session:
    byte_iterator = session.request(request_bytes):
    response_bytes = next(bytes_iterator)
    if not response_bytes.endswith(b"\r\n"):
        response_bytes += next(bytes_iterator)

That is,

  • First enter into a session with the Telnet server

  • Then send the request data.

  • Since only the calling application can know

    when it has received enough bytes for a complete response, the session context returns a bytestring iterator for the application layer to use to retrieve blocks of bytes. (In this example, the application layer knows that the response is terminated by “rn”, so it keeps receiving data until it encounters that sequence and the end of a block.)

  • Upon exiting the session context, the session is closed.

Parameters

request (bytes) – request bytes.

Return type

Iterator[bytes]

Returns

a bytestring iterator.

class ska_ser_devices.client_server.TransportClientProtocol(*args, **kwargs)

Structural subtyping protocol for supported transport client.

In order for a transport client to be supported by this module, it must provide a connect method that establishes a session, and also implement a session context manager.

__enter__()

Establish a connection, and enter a session context.

Return type

TransportClientSessionProtocol

Returns

access to the session context.

__exit__(exc_type, exception, trace)

Close the session and exit the session context.

Parameters
Return type

bool

Returns

whether the exception (if any) has been fully handled by this method and should be swallowed i.e. not re-raised

__init__(*args, **kwargs)
__subclasshook__()

Abstract classes can override this to customize issubclass().

This is invoked early on by abc.ABCMeta.__subclasscheck__(). It should return True, False or NotImplemented. If it returns NotImplemented, the normal algorithm is used. Otherwise, it overrides the normal algorithm (and the outcome is cached).

__weakref__

list of weak references to the object (if defined)

connect()

Establish a connection, and return access to the session.

Return type

TransportClientSessionProtocol

Returns

access to the session context.

class ska_ser_devices.client_server.TransportClientSessionProtocol(*args, **kwargs)

Structural subtyping protocol for supported transport client session.

In order for a transport session to be supported by this module, it must provide a request method that is implemented to

  1. issue the client request, and

  2. return an iterator, for use by the application layer to read as many bytestrings as necessary in order to construct the complete response payload.

__init__(*args, **kwargs)
__subclasshook__()

Abstract classes can override this to customize issubclass().

This is invoked early on by abc.ABCMeta.__subclasscheck__(). It should return True, False or NotImplemented. If it returns NotImplemented, the normal algorithm is used. Otherwise, it overrides the normal algorithm (and the outcome is cached).

__weakref__

list of weak references to the object (if defined)

close()

Close the connection.

Return type

None

request(request)

Transact a client request.

Issue the request, and return an iterator for use by the application layer to read as many bytestrings as necessary to construct the complete response payload.

Parameters

request (bytes) – the request bytes

Yield

a bytes iterator by which to construct the response

Return type

Iterator[bytes]