2018-04-13 18:20:00 +00:00
|
|
|
"""
|
|
|
|
TFW JSON message format
|
|
|
|
|
|
|
|
message:
|
|
|
|
{
|
|
|
|
"key": string, # addressing
|
|
|
|
"data": {...}, # payload
|
|
|
|
"trigger": string # FSM trigger
|
|
|
|
}
|
|
|
|
|
|
|
|
ZeroMQ's sub-pub sockets use enveloped messages
|
|
|
|
(http://zguide.zeromq.org/page:all#Pub-Sub-Message-Envelopes)
|
|
|
|
and TFW also uses them internally. This means that on ZMQ sockets
|
2018-04-16 10:50:08 +00:00
|
|
|
we always send the messages key separately and then the actual
|
2018-04-13 18:20:00 +00:00
|
|
|
message (which contains the key as well) like so:
|
|
|
|
|
|
|
|
socket.send_multipart([message['key'], message])
|
|
|
|
|
|
|
|
The purpose of this module is abstracting away this low level behaviour.
|
|
|
|
"""
|
2018-04-03 12:49:14 +00:00
|
|
|
|
2018-02-02 17:39:06 +00:00
|
|
|
import json
|
2018-06-29 09:50:36 +00:00
|
|
|
from functools import wraps
|
2018-02-02 17:39:06 +00:00
|
|
|
|
|
|
|
|
2018-04-13 14:34:40 +00:00
|
|
|
def serialize_tfw_msg(message):
|
2018-05-11 12:36:56 +00:00
|
|
|
"""
|
|
|
|
Create TFW multipart data from message dict
|
|
|
|
"""
|
2018-04-13 18:29:02 +00:00
|
|
|
return _serialize_all(message['key'], message)
|
2018-04-13 14:34:40 +00:00
|
|
|
|
|
|
|
|
2018-06-29 09:50:36 +00:00
|
|
|
def with_deserialize_tfw_msg(fun):
|
|
|
|
@wraps(fun)
|
|
|
|
def wrapper(message_parts):
|
|
|
|
message = deserialize_tfw_msg(*message_parts)
|
|
|
|
return fun(message)
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
|
2018-04-13 14:34:40 +00:00
|
|
|
def deserialize_tfw_msg(*args):
|
2018-05-11 12:36:56 +00:00
|
|
|
"""
|
|
|
|
Return message from TFW multipart data
|
|
|
|
"""
|
2020-05-07 07:47:35 +00:00
|
|
|
envelope = _deserialize_all(*args)
|
|
|
|
return _repair_if_needed(envelope)
|
2018-04-13 14:34:40 +00:00
|
|
|
|
|
|
|
|
2018-04-13 18:29:02 +00:00
|
|
|
def _serialize_all(*args):
|
2018-06-04 20:16:44 +00:00
|
|
|
return tuple(
|
|
|
|
_serialize_single(arg)
|
|
|
|
for arg in args
|
|
|
|
)
|
2018-04-10 11:42:51 +00:00
|
|
|
|
|
|
|
|
2018-04-13 18:29:02 +00:00
|
|
|
def _deserialize_all(*args):
|
2018-06-04 20:16:44 +00:00
|
|
|
return tuple(
|
|
|
|
_deserialize_single(arg)
|
|
|
|
for arg in args
|
|
|
|
)
|
2018-04-10 11:42:51 +00:00
|
|
|
|
|
|
|
|
|
|
|
def _serialize_single(data):
|
2018-05-11 12:36:56 +00:00
|
|
|
"""
|
|
|
|
Return input as bytes
|
|
|
|
(serialize input if it is JSON)
|
|
|
|
"""
|
2018-04-10 11:42:51 +00:00
|
|
|
if not isinstance(data, str):
|
2018-07-15 15:26:00 +00:00
|
|
|
data = message_bytes(data)
|
2018-04-13 18:29:02 +00:00
|
|
|
return _encode_if_needed(data)
|
2018-04-10 11:42:51 +00:00
|
|
|
|
|
|
|
|
2018-07-15 15:26:00 +00:00
|
|
|
def message_bytes(message):
|
|
|
|
return json.dumps(message, sort_keys=True).encode()
|
|
|
|
|
|
|
|
|
2018-04-10 11:42:51 +00:00
|
|
|
def _deserialize_single(data):
|
2018-05-11 12:36:56 +00:00
|
|
|
"""
|
|
|
|
Try parsing input as JSON, return it as
|
|
|
|
string if parsing fails.
|
|
|
|
"""
|
2018-04-10 11:42:51 +00:00
|
|
|
try:
|
|
|
|
return json.loads(data)
|
|
|
|
except ValueError:
|
2018-04-13 18:29:02 +00:00
|
|
|
return _decode_if_needed(data)
|
2018-04-10 11:42:51 +00:00
|
|
|
|
|
|
|
|
2020-05-07 07:47:35 +00:00
|
|
|
def _repair_if_needed(envelope):
|
|
|
|
"""
|
|
|
|
Quick fix for broken messages received from separate processes.
|
|
|
|
"""
|
|
|
|
if len(envelope) == 2:
|
|
|
|
return envelope[1]
|
|
|
|
for part in envelope:
|
|
|
|
if isinstance(part, dict):
|
|
|
|
return part
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
2018-04-13 18:29:02 +00:00
|
|
|
def _encode_if_needed(value):
|
2018-05-11 12:36:56 +00:00
|
|
|
"""
|
|
|
|
Return input as bytes
|
|
|
|
(encode if input is string)
|
|
|
|
"""
|
2018-02-02 16:30:26 +00:00
|
|
|
if isinstance(value, str):
|
|
|
|
value = value.encode('utf-8')
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
2018-04-13 18:29:02 +00:00
|
|
|
def _decode_if_needed(value):
|
2018-05-11 12:36:56 +00:00
|
|
|
"""
|
|
|
|
Return input as string
|
|
|
|
(decode if input is bytes)
|
|
|
|
"""
|
2018-02-02 16:30:26 +00:00
|
|
|
if isinstance(value, (bytes, bytearray)):
|
|
|
|
value = value.decode('utf-8')
|
|
|
|
return value
|