Getting started¶
Installation¶
required: python >= 3.7
install from PyPi via
pip install ubii-message-formatsor check out source and install locallyfor a local install, the
[compiler]extra installs cli tools to re-build the protobuf wrapper types
Proto Plus¶
Google’s protobuf API does not really behave like
native python code and makes it hard to write idiomatic code. The ubii.proto module
uses Google’s proto-plus package to create wrapper classes that can be
used like normal python objects.
Since types defined in ubii.proto are proto-plus Messages
i.e. wrappers around normal protobuf Messages, ubii.proto provides
a way to extend their behaviour by inheriting from existing ubii.proto wrapper types.
To do this, one has to use the custom ubii.proto.util.ProtoMeta metaclass.
import ubii.proto as ub
__protobuf__ = ub.__protobuf__
class CustomComponent(ub.Component, metaclass=ub.ProtoMeta):
def __init__(mapping, *, some_argument, **kwargs):
super().__init__(mapping, **kwargs)
self._arg = some_argument
def fancy_method(self):
return self._arg
The above code defines a class CustomComponent which acts like a wrapper around a
Component protobuf message (since it inherits from ubii.proto.Component)
Note
When you define a new proto-plus message
a new protbuf descriptor will be built under the hood, unless you define a
__protobuf__ attribute in the module. Setting the __protobuf__
attribute of the module to the ubii.proto.__protobuf__ attribute
tells the metaclass mechanism where to look for existing message descriptors
(see google.protobuf.descriptor_pool). This behaviour is not
very well documented in the proto-plus module (currently only in source
code), and is a likely source of bugs. Not setting the __protobuf__
attribute will produce wrappers that serialize and deserialize the messages
equivalently, but can’t be used interchangeably in your code.
#!/usr/bin/env python3
import ubii.proto as ub
def test_inheritance(module_protobuf):
global __protobuf__
if module_protobuf:
__protobuf__ = module_protobuf
class CustomSession(ub.Session, metaclass=ub.ProtoMeta):
pass
class WeirdProcessing(ub.ProcessingModule, metaclass=ub.ProtoMeta):
pass
inherited = CustomSession(name="Test")
basic = ub.Session(name="Test")
assert type(inherited).serialize(inherited) == type(basic).serialize(basic) # always seems to work
msg = f"Test with {'correct __protobuf__' if module_protobuf else 'no __protobuf__'} returned: \n"
try:
inherited.processing_modules = [WeirdProcessing()]
except TypeError as e:
print(msg + str(e))
else:
print(msg + "No error.")
test_inheritance(None) # Type Error!
print('\n')
test_inheritance(ub.__protobuf__) # works when __protobuf__ is set!
Here you can see an example how setting __protobuf__ affects the correct
inheritance of nested messages. When __protobuf__ is not set, the nested
ProcessingModule inside the custom
Session that is created in the example, will not be of the same
message type as the one in the base class (although it would work exactly the same).
Note the typical error message in such cases:
$ python ./examples/inheritance.py
Test with no __protobuf__ returned:
Parameter to MergeFrom() must be instance of same class: expected ubii.proto.v1.processing.ProcessingModule got WeirdProcessing.
Test with correct __protobuf__ returned:
No error.
Remember to always set __protobuf__ when inheriting from wrapper classes!
Ubii Messages¶
The purpose of the messages is explained in the README. Important messages if you are just getting started with the Ubi-Interact project include:
Services: (in a distributed setup typically the master node advertises them in aServiceList). Services represent endpoints for communication with the master node. Depending on the implementation of the master node and your client, this communication can happen via different protocols (e.g. HTTP requests to a REST backend).ServiceRequestandServiceReplymessages exchanged during service call request-reply communication. Refer to the wiki, for more information about service requests – e.g. which fields in theServiceRequestmessage are required to be present for which kind of request and related default topics for thetopicfield. When sending messages to the REST backend, the message payload needs to be JSON encoded e.g. with theubii.proto.util.ProtoEncoder.Server: this message contains information about the master node, mainly URLs for the different communication backends and the used “constants” (see below).Constants: Each master node advertises aliases for used service topics and message formats as part of itsubii.proto.Serverspecification, so that the client can be implemented agnostic of the actual values. The combination of theubii.proto.Constants.DEFAULT_TOPICS.SERVICESand the URL of the preferred service backend can then be used to send requests to the master node.ubii.proto.TopicData: In addition to the request-reply communication, the master node acts as a message broker to provide a publish-subscribe communication. In addition to any ad-hoc topics, some information is published in theubii.proto.Constants.DEFAULT_TOPICS.INFO_TOPICS, to notify subscribed clients of events such as started or stoppedSessions.
Typical communication of a client node (advanced):
request the master node specification by sending respective
ServiceRequestfor theSERVER_CONFIGtopic. This is the only interaction with the master node that has to rely on client side information about the service endpoint URL. Refer to the wiki for information about possible default values and the response message format (typically aubii.proto.Servermessage).Use the
Constants(currently only supplied as JSON for backwards compatibility, seeubii.proto.Server.constants_json) to make requests and subscribe to relevant info topics. E.g.registerthe client node, or get thelist of available services. Registration is necessary to subscribe to data topics.Act on
TopicDatapublished in subscribed topics. E.g. if the client node can runProcessingModulesthe following communication could take place:subscribe to the topic specified by
ubii.proto.Constants.DefaultTopics.INFO_TOPICS.NEW_SESSIONon new
TopicDatain this topic:inspect the
ubii.proto.TopicData.topic_data_recordfield, to get theTopicDataRecordmessageinspect the
ubii.proto.TopicDataRecord.sessionof this message, to get information about the started sessioninspect the
ubii.proto.Session.processing_modulesfield to get information aboutProcessingModuleswhich could be started by client node.inspect the
ubii.proto.Session.io_mappingsfield and makeServiceRequeststo handle necessary subscriptions. (Theubii.proto.IOMapping.input_mappingsmessage field contains information about input topics for the modules)after successfully starting the module (depending on client node implementation) make a
ServiceRequest(see above) for theubii.proto.Constants.DefaultTopics.Services.PM_RUNTIME_ADDtopic – as shown in the wiki the sentServiceRequestneeds to have theubii.proto.ServiceRequest.processing_module_listfield set to aProcessingModuleListinforming the master node about the started modules – and receive aSuccessorErrormessage as part of this communication
process the data until stopped, respecting the
ubii.proto.Session.io_mappingsthat have been applied (if you look through theubii.proto.IOMappingdocumentation you will find messages with relevant information about input and output topics)
For more information refer to the documentation of a client node of your choice.