Getting started

Installation

Python Version

The Ubi-Interact-Python-Node should be compatible with all python versions >= 3.7. If you experience bugs feel free to report them.

Windows

The Ubi-Interact-Python-Node aims to be cross-platform, working with most interesting computational packages is easier under Linux nonetheless. Installation via pip is recommended, if you use something else (e.g. Anaconda) refer to the documentation of your python distribution / package management tool how to install packages from pypi.

You can use the Windows python wrapper py.exe (detailed instructions in the Python Documentation) to choose the python version of your environment.

If you plan to use a virtual environment – which is recommended – typically an Unrestricted execution policy is needed to run the activation script for the python environment. The following instructions assume you are using Powershell, and have the right ExecutionPolicy set.

  • Python version 3.7 or greater

  • Virtual Environment (recommended) with pip installed (py -m venv env followed by ./env/Scripts/activate.ps1)

  • Continue at PyPi

Linux (and MacOS)

If you are not using Windows, you probably have a working python installation. Sometimes the different versions have aliases such as python3, so make sure to create the virtual environment with the appropriate python executable (or specify the executable for the environment during creation).

  • Python version 3.7 of greater

  • Virtual Environment (recommended) with pip installed (e.g. python3 -m venv env followed by source ./env/bin/activate)

  • Continue at PyPi

PyPi

After activating the environment, install the package from pypi. The package supplies different extras, to install additional dependencies for optional features. You can make sure everything is working correctly by calling the ubii-client script which gets installed as part of the cli extra.

$ python -m pip install ubii-node-python[cli]
$ ubii-client --help

Editable / Local Install

Instead of installing from PyPi you can clone the repository and install the package “from source”. Editable installs are supported.

$ git clone git@github.com:SandroWeber/ubii-node-python.git
$ cd ubii-node-python
$ < create and activate virtual env >
$ pip install -e .[cli]
$ ubii-client --help

Extras

This packages uses extras.

  • [test] Requirements to run pytest suite if you install the package from source, and not from PyPi

  • [cli] Installs a CLI script which runs the node and auto-discovers installed Processing Modules (see below)

  • [docs] Install requirements to build documentation

Usage

To use the ubii-node-python package to implement your own python nodes refer to the package documentation. To start a python client refer to CLI.

CLI

Basic functionality is provided through a command line interface which allows to run a python node which is able to import and load processing modules.

$ ubii-client --help
  usage: ubii-client [-h] [--processing-modules PROCESSING_MODULES] [--no-discover]
                    [--module-args MODULE_ARGS] [--verbose] [--debug]
                    [--log-config LOG_CONFIG]

  options:
    -h, --help            show this help message and exit
    --processing-modules PROCESSING_MODULES
                          Import processing modules to load. You can also use the format
                          {name}:{type} to load them with specific names.
    --no-discover         Don't use the automatic processing module discovery mechanism
    --module-args MODULE_ARGS
                          Format {name}:{key}={value}(,{key}={value})*, e.g. my-
                          module:tags=['custom'],description='Wow!'
    --verbose, -v
    --debug
    --log-config LOG_CONFIG

(non obvious) arguments:

  • --debug Debug mode changes the exception handling, and increases verbosity. In debug mode the Node does not try to handle exceptions, and fails loudly

  • --log-config optional path to a .yaml file containing a dictionary of logging options consistent with the logging.config.dictConfig format (example config)

  • --no-discover flag to turn off auto discovery of processing modules via entry points

  • --processing-modules specify a list of import paths for Ubi Interact Procesing Modules implemented using the ubi-interact-python framework, see processing-modules. Use it together with auto discovery during development or as a fallback. Also allows to specify alternative names.

  • --module-args specify a mapping of processing module names to argument maps, e.g. --module-args test:name='test' to specify argument name as 'test' for the module loaded with name test.

Processing Modules

Below is a list of processing modules that are compatible with the python node. To try them, install them inside the same virtual environment (refer to the documentation of the specific module). If you develop new Processing Modules, use the entry point group ubii.processing_modules to advertise them in your package, so that the ubii-client script (or your own implementation) can discover them. Read the setup.cfg configs of the example modules below and the setuptools documentation for more details.

Example usage after install of module:

$ pip install ubii-processing-module-ocr
$ ubii-client
> Loaded processing module factories <class 'ubii.processing_modules.ocr.tesseract_ocr.TesseractOCR_EAST'>, ...
> ...

or with cli argument to only load specific processing modules (also turning off auto discovery in this example)

$ pip install ubii-processing-module-ocr
$ ubii-client --no-discover --processing-modules ubii.processing_modules.ocr.tesseract_ocr.TesseractOCR_EAST
> Loaded processing module factories <class 'ubii.processing_modules.ocr.tesseract_ocr.TesseractOCR_EAST'>
> ...

Writing a custom client

The alternative to implementing a processing module is to implement a custom client node. To test communication with the master node start with the default client by using ubii.node.connect_client.

Note

You can start a async python REPL by using python -m asyncio to use Await expression directly instead of writing everything in a main() function and using asyncio.run()

  1. Make sure the master node is running, note the URL for the service endpoint for json data, e.g. http://*:8102/services/json (the output of the running master node should show the URL)

  2. Either set the appropriate environment variable (see UBII_URL_ENV) to the url of your service endpoint, or pass it to ubii.node.connect_client e.g.

    >>> from ubii.node import connect_client
    >>> client = await connect_client('http://localhost:8102/services/json')
    
  3. You can read about the client created by connect_client in the API documentation.

Client Example

Goal of this example is to create a Ubi Interact client node and subscribe to a topic with a custom callback. See example for full code.

 7import argparse
 8from ubii.framework.logging import parse_args
 9parser = argparse.ArgumentParser()
10parser.add_argument('--url', type=str, action='store', default=None, help='URL of master node service endpoint')

Use the parse_args() function to give the argument parser some default arguments (to increase verbosity and set debug mode). The parser also gets a custom argument to pass the URL of the master node service endpoint.

14
15from ubii.node import connect_client, Subscriptions, Publish, UbiiClient
16
17async def run():
18    client: UbiiClient
19    async with connect_client(url=args.url) as client:
20        # we don't hard code the topic, we use the DEFAULT TOPIC from the master node
21        constants = client.protocol.context.constants
22        assert constants
23        info_topic_pattern = constants.DEFAULT_TOPICS.INFO_TOPICS.REGEX_ALL_INFOS
24
25        await client[Subscriptions].subscribe_regex(info_topic_pattern).with_callback(print)
26        print(f"Subscribed to topic {info_topic_pattern!r}")
27

We subscribe to a topic glob pattern (in our case /info/*) but we use the default topics of the master node instead of hard coding that value.

The default topics get sent by the master node as part of the Server reply to the initial server configuration service request performed by the client protocol

See also

ubii.node.protocol.LegacyProtocol.update_config() – this callback makes the server_config service call and updates the constants in the protocol context with the constants sent by the master node

The constants that the client protocol uses, are actually the constants of the global config since no other config was passed to connect_client, but referencing them as the client.protocol.constants is advisable if one later decides to not use the global configuration.

Subscribing to the topic pattern returns a Topic (actually a tuple of topics, one value for each pattern used in the subscription call, note the unpacking of the tuple) which can be used to register a callback for new topic data. The example client just registers print().

Last but not least, start a loop and publish some very interesting topic data to a topic that should be covered by the glob pattern the client subscribed to.

28value = None
29
30while client.state != client.State.UNAVAILABLE:
31    value = 'foo' if value == 'bar' else 'bar'
32    await asyncio.sleep(1)
33    await client[Publish].publish({'topic': '/info/custom_topic', 'string': value})

Now just start everything in the asyncio event loop. You can use a TaskNursery to handle signals (e.g. when the user presses Ctrl+C in the shell) for us.

def info_log_client():
    """
    Example for tutorial to create a simple client that prints messages received in the info topics,
    and continuously publishes a value to a custom info topic. See :ref:`Client Example`
    """
    import asyncio
    import argparse
    from ubii.framework.logging import parse_args
    parser = argparse.ArgumentParser()
    parser.add_argument('--url', type=str, action='store', default=None, help='URL of master node service endpoint')
    args = parse_args(parser=parser)

    loop = asyncio.get_event_loop_policy().get_event_loop()

    from ubii.node import connect_client, Subscriptions, Publish, UbiiClient

    async def run():
        client: UbiiClient
        async with connect_client(url=args.url) as client:
            # we don't hard code the topic, we use the DEFAULT TOPIC from the master node
            constants = client.protocol.context.constants
            assert constants
            info_topic_pattern = constants.DEFAULT_TOPICS.INFO_TOPICS.REGEX_ALL_INFOS

            await client[Subscriptions].subscribe_regex(info_topic_pattern).with_callback(print)
            print(f"Subscribed to topic {info_topic_pattern!r}")

            value = None

            while client.state != client.State.UNAVAILABLE:
                value = 'foo' if value == 'bar' else 'bar'
                await asyncio.sleep(1)
                await client[Publish].publish({'topic': '/info/custom_topic', 'string': value})

        loop.stop()

    from codestare.async_utils.nursery import TaskNursery
    nursery = TaskNursery(name="__main__", loop=loop)
    nursery.create_task(run())
    loop.run_forever()

    assert not loop.is_running()
    loop.close()

You can test the client by running the example-client command when you install this python package with [cli] extra

$ pip install ubii-node-python[cli]
$ example-client --help
usage: example-client [-h] [--url URL] [--verbose] [--debug] [--log-config LOG_CONFIG]                                                                                                                                                  optional arguments:
  -h, --help            show this help message and exit
  --url URL             URL of master node service endpoint
  --verbose, -v
  --debug
  --log-config LOG_CONFIG