Providers

Providers are objects that encapsulate a number of fonctions exposed as RPCs. While the previous tutorials already show how one can register methods of an object’s instance to use as and RPC, PyMargo provides a number of decorators to automatize the registration of such functions.

The following server code shows these decorators.

from pymargo.core import Engine, Handle, provider, \
                         remote, on_finalize, on_prefinalize


@provider(service_name="my_service")
class MyProvider:

    @remote
    def say_hello(self, handle, firstname, lastname):
        print(f"Hello {firstname} {lastname}!")
        handle.respond(f"Good bye {firstname}")

    @on_finalize
    def finalize(self):
        print("Called when the engine finalizes")

    @on_prefinalize
    def prefinalize(self):
        print("Called right before the engine finalizes")


if __name__ == "__main__":
    with Engine("tcp") as engine:
        provider = MyProvider()
        engine.register_provider(provider, provider_id=42)
        print(f"Service running at {engine.address}")
        engine.enable_remote_shutdown()
        engine.wait_for_finalize()

The provider decorator can be used to decorate a class with a service name. This service name will be prepended to function names, along with a “_”, to make up the full RPC names.

The remote decorator indicates that a function is remotely callable. This decorator can be used with optional arguments, e.g., remote(rpc_name="name", disable_response=True). When registered, the RPC name used will be the function name, unless an rpc_name parameter is provided to the decorator.

The on_prefinalize and on_finalize decorators are here to specify that a function should be called before the engine is finalized, and during finalization (the difference being that in pre-finalization stage, it is still allowed to send and receive RPCs, while in finalization phase it is not).

Once an instance of MyProvider is created, we simply pass it to engine.register_provider and the engine will discover the functions marked as RPCs and the pre-finalization and finalization callbacks. This function also takes an optional provider_id argument, so that multiple instances can be registered, distinguished by their provider id.

The following code shows the corresponding client.

import sys
from pymargo.core import Engine, MargoException
import pymargo

if __name__ == "__main__":
    with Engine("tcp", mode=pymargo.client) as engine:
        hello = engine.register("my_service_say_hello")
        address = engine.lookup(sys.argv[1])
        try:
            response = hello.on(address, provider_id=42)("Matthieu", lastname="Dorier")
            assert response == "Good bye Matthieu"
        except MargoException as e:
            print(e)
        address.shutdown()

The client needs to build the full RPC name by itself (here “my_service_say_hello”). The on function, which binds a RemoteFunction with an Address, takes an optional provider_id argument.

Important

The code above also protects the call to the RPC with a try ... except. It is important in PyMargo code to catch exceptions and properly act on them. Uncaught, an exception could make objects like RPC handles outlive the engine that created them, causing segmentation faults when the garbage collector tries to free them.

Note

We have demonstrated here the registration of a provider object, but since in Python everything is an object, we can also pass a module with global functions decorated in the same manner to register_provider.