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
.