Usage

Install

To install the package, you can use pip.

pip install interactionfreepy

Host a Broker

It is simple to host a Broker. One may start with the IFBroker class.

1from interactionfreepy import IFBroker, IFLoop
2
3broker = IFBroker()
4IFLoop.join()

Here, IFBroker() creates the Broker instance and listens on the default port 1061. You can also change the port by explicitly passing the port number to the constructor, for example, 8061.

1from interactionfreepy import IFBroker, IFLoop
2
3broker = IFBroker('*:8061')
4IFLoop.join()

The address can also be specified (by replacing * with the IP address of the machine) to allow only local connections. The IFLoop.join() is a blocking call that will keep the program running until terminated.

You can also host the Broker in a Docker container. The Docker image is available as hwaipy/ifbroker. To run the broker in a Docker container, you can use the following command:

  docker run -d -p 1061:1061 --name IFBroker hwaipy/ifbroker:latest

Connect to a Broker

Use the IFWorker class to connect to a Broker. For the purpose of giving an example, we will use the public Broker hosted by interactionfree.cn:1061. You could change the address to your own Broker if you have already hosted one.

1from interactionfreepy import IFWorker
2
3worker = IFWorker('interactionfree.cn:1061')

After connecting to the Broker, you can check the protocol version to ensure the connectivity.

1print(worker.protocol())

The output should be IF1 by default.

Registrate a Service

After connecting to the Broker, you can register the Worker as a Service by invoking bindService. We can still use the dragon cipher as an example. If you have a class named DragonCipher that provides a function encrypt_to_dragon_speech, as defined below,

 1class DragonCipher:
 2  def encrypt_to_dragon_speech(self, text: str) -> str:
 3    result = []
 4    for char in text:
 5      lower_char = char.lower()
 6      if lower_char in {'a', 'e', 'i', 'o', 'u'}:
 7        new_char = '*'
 8      elif char.isalpha():
 9        new_char = f"{char}-ar" if char.isupper() else f"{char}-ar"
10      else:
11        new_char = char
12      result.append(new_char)
13    return ''.join(result)

You can simply publish it as a service by calling the bindService method of the IFWorker instance.

1dragon_cipher = DragonCipher()
2worker.registerAsService('DragonCipher', dragon_cipher)

You can even combine the creation of IFWorker instance and binding the service together, as follows,

1worker = IFWorker('tcp://interactionfree.cn:1061', 'DragonCipher_Alice', DragonCipher())

Invoking a remote Service

An IFWorker, whether named or anonymous, can invoke a remote service by calling the function directly. As a client, you do not need to provide the service address or the instance of the service. With the broker address provided, you can list all the services available by calling listServiceNames(). Then, you can call the service directly by using the service name as an attribute of the worker instance, worker.DragonCipher_Alice.encrypt_to_dragon_speech(human_speech). The most interesting part is that you can run the code on a completely different machine, and never need to know the implementation details of the service. You do not even need to import anything related to the service.

For example, if you have a Service named DragonCipher_Alice that provides a function encrypt_to_dragon_speech, you can call it from a completely machine by:

1from interactionfreepy import IFWorker
2
3worker = IFWorker('interactionfree.cn:1061')
4
5humam_speech = 'To be or not to be'
6dragon_speech = worker.DragonCipher_Alice.encrypt_to_dragon_speech(humam_speech)
7print(f'{humam_speech} -> {dragon_speech}')

The parameters can be numbers, strings, lists, maps, or any data type that is supported by the standard MessagePack protocol, see here.

With this code running, you might get the output like this:

['DragonCipher_Alice']
To be or not to be -> T-ar* b-ar* *r-ar n-ar*t-ar t-ar* b-ar*

Customizing the Manager

As mentioned above, any function invoked on IFWorker without specifying a ServiceName will be routed to the Broker’s Manager. By default, the IFBroker uses an instance of interactionfreepy.broker.Manager as its manager. You can customize the manager by inheriting the Manager class and overriding the methods you want to customize, or adding new methods.

For example, if you want to add two new methods, one is echo and the other is whatIsMyID, to the manager, you can do it when creating the IFBroker instance, as follows,

 1from interactionfreepy import IFBroker
 2from interactionfreepy.broker import Manager
 3
 4class CustomManager(Manager):
 5    def echo(self, sourcePoint, message):
 6        return f'ECHO: {message}'
 7
 8    def whatIsMyID(self, sourcePoint):
 9        return f'Your ID is {sourcePoint}.'
10
11broker = IFBroker(manager=CustomManager())
12IFLoop.join()

Here, the echo method simply returns the message passed to it, and the whatIsMyID method returns the unique ID of the caller. Please note that the first parameter for a method of the manager must be sourcePoint, which is a special parameter that will be automatically passed by the protocol, indicating the unique ID of the caller.

Then, any IFWorker connected to the new IFBroker can invoke these methods.

1echo = worker.echo('response to me!')
2myID = worker.whatIsMyID()
3print(echo)
4print(myID)

and get results like

ECHO: response to me!
Your ID is b'\x00k\x8bEg'.

Of course, the specific ID will be different in your case, and maybe at each run. You can also add a filter for the ServiceName by overloading the method bindService, requiring that ServiceName must begin with a capital letter.

 1from interactionfreepy import IFBroker
 2from interactionfreepy.broker import Manager
 3
 4class CustomManager(Manager):
 5    def registerAsService(self, sourcePoint, name, interfaces=None, force=False):
 6        if not name or not name[0].isalpha() or not name[0].isupper():
 7          raise Exception('The first letter of the service name should be uppercase.')
 8        return super().registerAsService(sourcePoint, name, interfaces, force)
 9
10broker = IFBroker(manager=CustomManager())
11IFLoop.join()

Then if you register a Service with an invalid ServiceName, you should get the exception,

interactionfreepy.core.IFException: The first letter of the service name should be uppercase.