Getting started

Installation

  • required: python >= 3.7

  • install from PyPi via pip install ubii-message-formats or check out source and install locally

  • for 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.

./examples/inheritance.py
#!/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 a ServiceList). 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).

  • ServiceRequest and ServiceReply messages exchanged during service call request-reply communication. Refer to the wiki, for more information about service requests – e.g. which fields in the ServiceRequest message are required to be present for which kind of request and related default topics for the topic field. When sending messages to the REST backend, the message payload needs to be JSON encoded e.g. with the ubii.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 its ubii.proto.Server specification, so that the client can be implemented agnostic of the actual values. The combination of the ubii.proto.Constants.DEFAULT_TOPICS.SERVICES and 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 the ubii.proto.Constants.DEFAULT_TOPICS.INFO_TOPICS, to notify subscribed clients of events such as started or stopped Sessions.

Typical communication of a client node (advanced):

For more information refer to the documentation of a client node of your choice.