UP | HOME

ZIO Tutorial: Messages in Python

Table of Contents

ZIO uses a single zio.Message class which acts as a general container. Details can be found in the messages write-up. Here, we show how to work with messages.

1 Creating a message

A zio.Message can be created in a flexible way depending on the form of information you have.

import zio
# empty message
m1 = zio.Message()
# more specified
m2 = zio.Message(form='TEXT', label='my source', 
                 level=zio.MessageLevel.warning,
                 payload=['The system is on fire']) 

The constructor takes many keyword parameters and builds the message content in a cascading manner from most general to most specific, where the latter may override the former. For example, you may specify a full message encoding and then override some part.

ph = zio.PrefixHeader('ZIO6TEXTmy source')
m = zio.Message(level=zio.MessageLevel.info, prefix=ph)
print(m)
# zio.Message: "ZIO4TEXTmy source" + [0x0,0,0] + [0]

Note the 6 in ZIO6 becomes a 4.

2 Modifying a message

Given a message object, it can be modified. This may be particularly useful when a message must be crafted which is a response from an incoming message. For example, in data flow protocol the .label attribute is assumed to be a JSON encoded string providing protocol information. In particular the begin of transmission (BOT) handshake involves "reversing" a value held in the JSON between the two endpoints.

m = zio.Message(form='FLOW', 
                label=json.dumps(dict(flow='BOT',
                                      direction='inject',
                                      credit=10)))
print(m)
# zio.Message: "ZIO0FLOW{"flow": "BOT", "direction": "inject", "credit": 10}" + [0x0,0,0] + [0]

# now pretend we send->recv "m" to another ZIO app

fobj = json.loads(m.label)
fobj["direction"] = 'inject' if fobj["direction"] == "extract" else "extract"
m.label = json.dumps(fobj)
print(m)
# zio.Message: "ZIO0FLOW{"flow": "BOT", "direction": "extract", "credit": 10}" + [0x0,0,0] + [0]

# and now we'd probably send "m" back to the first endpoint...

3 Origin, granule, seqno, oh my!

In addition to the ZIO prefix, the "level" and an optional "label", all ZIO message headers have a trio of numbers: origin, granule and seqno. These are described in messages. The zio.Message object itself does not manage their values. Rather provides accessors:

import time
m = zio.Message(form='NULL', origin=42, granule=time.time())
print (m)
# zio.Message: "ZIO0NULL" + [0x2a,1580828219,0] + [0]
time.sleep(1)
m.granule = time.time()
m.seqno += 1
print (m)
# zio.Message: "ZIO0NULL" + [0x2a,1580828220,1] + [0]

4 Serializing a message

A ZIO message is meant to be useful with established ZeroMQ sockets that may be multi-part as well as newer thread-safe sockets which must be sent as a monolithic single-part message. The serializing methods are exposed to the user.

m = zio.Message(form='TEXT', label='my logger', 
                level=zio.MessageLevel.warning, 
                payload=['Hello', 'World'])
print(m)
# zio.Message: "ZIO6TEXTmy logger" + [0x0,0,0] + [2]
print (m.toparts())
# [b'ZIO6TEXTmy logger',
#  b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 
#  b'Hello', 
#  b'World']
print (m.encode())
# b'\x11\x00\x00\x00ZIO6TEXTmy logger\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00Hello\x05\x00\x00\x00World'

The decode() and fromparts() are the inverse of these two methods.

When messages are used with a port (see also port tutorial) the user need not worry about this detail.