Client tutorial
If you haven’t already read it, you should read the Server tutorial first.
A simple client
A client is represented by the aiokatcp.Client
class. For more
advanced cases you may need to subclass it, but for simply making requests it
can be used as-is. Here is a simple function that connects to a server and
requests the help:
import asyncio
import aiokatcp
async def run_client():
client = await aiokatcp.Client.connect('localhost', 4444)
async with client:
reply, informs = await client.request('help')
for inform in informs:
print(b' '.join(inform.arguments).decode('ascii'))
asyncio.get_event_loop().run_until_complete(run_client())
Let’s look at this one piece at a time. Firstly, we call
Client.connect()
, which blocks until a connection is established. Note
that it will keep trying indefinitely, rather than raising an exception if a
connection could not immediately be established, so you may want to set a
timeout (for example, using async_timeout). For more details, see
Automatic reconnection.
If you just want to create the Client
object
without waiting for it to connect, you can just use the constructor, and later
used Client.wait_connected()
to wait for a successful connection.
Next, we use async with
to ensure that the client is closed when we
exit the block, even if there was an exception. A request can raise exceptions
for two reasons:
If there is a problem with the connection, either at the time the request call is made or during the request, a
ConnectionError
is raised. Note that if the request has side-effects, they might or might not have taken effect.If the server returns a response which starts with
fail
orinvalid
, aaiokatcp.FailReply
oraiokatcp.InvalidReply
will be raised respectively.aiokatcp.InvalidReply
is also used if the response was received, but did not have a valid response code i.e.,ok
,fail
orinvalid
. If you prefer not to have these exceptions, useClient.request_raw()
, which will return the reply as aMessage
rather than a list of arguments excluding theok
.
We then print the informs, which for a help
request consists of one per
request supported by the server. Note that the informs are instances of
Message
, and the arguments are raw bytes. We thus have to decode the
values into text.
Asynchronous informs
There are two ways to handle asynchronous informs. The first is to subclass
Client
and implement inform handlers. An inform handler is a message named
inform_name
where name is the name of the inform. The type
annotations on the arguments are used to convert the arguments from raw bytes
to those types. For more details, see Type conversions.
Inform handlers are very similar to request handlers for servers. The
differences are that inform handlers are regular methods rather than
coroutines, and are not expected to return a value. They may raise
InvalidReply
or FailReply
, but this results only in a log
message.
As an example, here is how one might handle #sensor-status
informs:
class MyClient(aiokatcp.Client):
def inform_sensor_status(self, timestamp: aiokatcp.Timestamp,
num_sensors: int, *args) -> None:
if len(args) != 3 * num_sensors:
raise FailReply('Wrong number of arguments')
...
It is also possible to overload Client.unhandled_inform()
to handle
informs for which there is no specific handler.
Note that the base class already contains inform handlers for a number of standard informs.
The second way to deal with asynchronous informs is to register callbacks. This
has the advantage that it does not require subclassing and so can be used on an
already-constructed Client
. Instead of a method inside the class,
write a function (with type annotations on the arguments, as before) and pass
it to Client.add_inform_callback()
.