qmi.core.rpc
Remote Procedure Call mechanism for QMI.
This module provides support for Remote Procedure Calls: calling methods on an object that exists in a different QMI context.
Defining RPC methods
Only classes that inherit QMI_RpcObject can define RPC methods. Within such a class, use the @rpc_method decorator to mark a method as RPC-callable. Note that QMI_Instrument inherits QMI_RpcObject, therefore instrument drivers are able to define RPC methods.
For example, the following fragment defines an RPC-callable method MyClass.square:
class MyClass(QMI_RpcObject):
def __init__(self, context, name, ...):
super().__init__(context, name)
@rpc_method
def square(self, x):
return x * x
Some restrictions apply to the types of data that can be passed in and out of RPC methods: Parameters passed to an RPC method and return values or exceptions produced by an RPC method must be pickle-able, i.e. the Python pickle module must be able to serialize the data types used.
Built-in types are generally pickle-able.
Custom defined value-like types such as enums, named tuples and exceptions, are generally pickle-able provided that the type definition can be imported by the receiving program. This implies that such types must be defined in a Python module, not in a top-level Python script.
Numpy arrays are pickle-able.
Other kinds of custom defined classes are generally not pickle-able.
Calling RPC methods
Classes that inherit QMI_RpcObject are instantiated via the QMI context. The context then returns a proxy for the new object instance.
RPC methods can be called by simply invoking a method with the same name on the proxy. The RPC mechanism translates this into a call to the real method on the real object. The return value from the real method is passed back as the return value from the proxy call.
For example:
proxy = qmi.context().make_rpc_object("my_object", MyClass, ...)
y = proxy.square(5)
It is also possible to create a proxy for an object in a different QMI context. In this case, calling a method on the proxy will cause the method with the same name to run in the remote context:
proxy = qmi.context().get_rpc_object_by_name("other_context.my_object")
y = proxy.square(5)
Locking RPC objects
Class that inherit from QMI_RpcObject are lockable. This means that only the proxy that owns the lock (i.e. that acquired it) can invoke RPC methods. Other proxies will not be able to call any RPC method until the object is unlocked. An example use case is where you have a script running to do some measurement and you want to avoid that a GUI or automatic calibration interferes with that measurement: in that case you can have the script lock the instruments.
Example:
proxy = qmi.context().get_rpc_object_by_name("other_context.my_object")
proxy.lock() # object is now locked
# do things ...
proxy.unlock()
You can query if an object is locked via is_locked(). If for whatever reason an object is locked and the proxy that owns the locked no longer exists, you can force unlock the object via force_unlock(); use this with care!
The normal proxy locking is available only within the same context for the same proxy. Since QMI V0.29.1 it is also possible to lock and unlock with a custom token from other proxies as well, as long as the contexts for the other proxies have the same name.
Example 1:
# Two proxies in same context.
proxy1 = context.make_rpc_object("my_object", MyRpcTestClass)
proxy2 = context.get_rpc_object_by_name("my_context.my_object")
custom_token = "thisismineallmine"
proxy1.lock(lock_token=custom_token)
proxy2.is_locked() # Returns True
proxy2.unlock(lock_token=custom_token)
proxy2.is_locked() # Returns False
Example 2:
# Three proxies in different contexts. The first one serves as an "object provider".
c1 = QMI_Context("c1", config)
c1.start()
c1_port = c1.get_tcp_server_port()
c1_address = "localhost:{}".format(c1_port)
# Instantiate class in context c1 as object provider.
proxy1 = c1.make_rpc_object("tc1", MyRpcTestClass)
# Now make another context, and get a proxy to the object
custom_token = "block"
c2 = QMI_Context("c2", config)
c2.start()
c2.connect_to_peer("c1", c1_address)
proxy2 = c2.get_rpc_object_by_name("c1.tc1")
proxy2.is_locked() # Returns False
proxy2.lock(lock_token=custom_token)
proxy2.is_locked() # Returns True
# And then close it
c2.stop()
# Then start third context with the same name as second context, obtain the object and unlock it.
c3 = QMI_Context("c2", config)
c3.start()
c3.connect_to_peer("c1", c1_address)
proxy3 = c3.get_rpc_object_by_name("c1.tc1")
proxy3.is_locked() # Returns True
proxy3.unlock() # Returns False as it fails without the correct token
proxy3.unlock(lock_token=custom_token) # Now succeeds and returns True
proxy3.is_locked() # Returns False
# And then close it
c3.stop()
# Then finally stop the "object provider"
c1.stop()
Reference
Functions
|
Helper function that performs a blocking call to a specific method of the target RPC object. |
|
Return True if the object is a method that can be called via RPC. |
|
Create a description of the (subset of the) interface of the specified QMI_RpcObject subclass that can be accessed via RPC. |
|
Helper function that performs a non-blocking call to a specific method of the target RPC object. |
|
Decorator to indicate that a method can be called via RPC. |
Classes
|
Actions that can be performed on a lock. |
|
Message sent back to an RPC client with the result of the action. |
|
Message sent by an RPC client to interact with the lock state of a remote object. |
|
Unique lock token that is used to lock/unlock RPC objects. |
|
Message sent back to an RPC client with the result of a remote method invocation. |
|
Message sent by an RPC client to invoke a remote method. |
|
Representation of the future completion of a method invoked via RPC. |
|
Possible states of an QMI_RpcFuture instance. |
|
Proxy class for RPC objects that performs non-blocking calls. |
|
Base class for classes which expose (a subset of) their methods via RPC. |
|
Proxy class for RPC objects that performs blocking calls. |
|
Description of an RPC constant. |
|
Description of the subset of the interface of an RPC object class that can be accessed via RPC. |
|
Description of an RPC method. |
|
Description of an RPC object instance. |
|
Manages a single instance of QMI_RpcObject. |
|
Description of a QMI signal. |
- class qmi.core.rpc.RpcConstantDescriptor(name: str, value: Any)
Description of an RPC constant.
- name
Name of the constant.
- Type:
str
- value
Value of the constant.
- Type:
Any
- name: str
Alias for field number 0
- value: Any
Alias for field number 1
- count(value, /)
Return number of occurrences of value.
- index(value, start=0, stop=9223372036854775807, /)
Return first index of value.
Raises ValueError if the value is not present.
- class qmi.core.rpc.RpcMethodDescriptor(name: str, signature: str, docstring: str)
Description of an RPC method.
- name
Name of the RPC method.
- Type:
str
- signature
String representation of the signature of the RPC method, including type annotations.
- Type:
str
- docstring
Docstring of the RPC method.
- Type:
str
- name: str
Alias for field number 0
- signature: str
Alias for field number 1
- docstring: str
Alias for field number 2
- count(value, /)
Return number of occurrences of value.
- index(value, start=0, stop=9223372036854775807, /)
Return first index of value.
Raises ValueError if the value is not present.
- class qmi.core.rpc.RpcSignalDescriptor(name: str, arg_types: str)
Description of a QMI signal.
- name
Name of the signal.
- Type:
str
- arg_types
String representation of the list of argument types.
- Type:
str
- name: str
Alias for field number 0
- arg_types: str
Alias for field number 1
- count(value, /)
Return number of occurrences of value.
- index(value, start=0, stop=9223372036854775807, /)
Return first index of value.
Raises ValueError if the value is not present.
- class qmi.core.rpc.RpcInterfaceDescriptor(rpc_class_module: str, rpc_class_name: str, rpc_class_docstring: str | None, constants: list[RpcConstantDescriptor], methods: list[RpcMethodDescriptor], signals: list[RpcSignalDescriptor])
Description of the subset of the interface of an RPC object class that can be accessed via RPC. This includes the methods marked using the @rpc_method decorator and the signals declared by the RPC object class or a delegate class.
- rpc_class_module
Name of the module in which the RPC object class was defined.
- Type:
str
- rpc_class_name
Name of the RPC object class.
- Type:
str
- rpc_class_docstring
Docstring of the RPC object class.
- Type:
str | None
- constants
A list of constant descriptors for the RPC constants declared by the RPC object class.
- Type:
- methods
A list of method descriptors for the RPC methods declared by the RPC object class.
- Type:
- signals
A list of signal descriptors for the signals declared by the RPC object class or a delegate class.
- Type:
- rpc_class_module: str
Alias for field number 0
- rpc_class_name: str
Alias for field number 1
- rpc_class_docstring: str | None
Alias for field number 2
- constants: list[RpcConstantDescriptor]
Alias for field number 3
- methods: list[RpcMethodDescriptor]
Alias for field number 4
- signals: list[RpcSignalDescriptor]
Alias for field number 5
- count(value, /)
Return number of occurrences of value.
- index(value, start=0, stop=9223372036854775807, /)
Return first index of value.
Raises ValueError if the value is not present.
- class qmi.core.rpc.RpcObjectDescriptor(address: QMI_MessageHandlerAddress, category: str | None, interface: RpcInterfaceDescriptor)
Description of an RPC object instance.
- address
Unique address of the RPC object.
- category
Free-form name of the category of objects this RPC object belongs to.
- Type:
str | None
- interface
Description of the subset of the interface of the RPC object that can be accessed via RPC, including signals.
- address: QMI_MessageHandlerAddress
Alias for field number 0
- category: str | None
Alias for field number 1
- interface: RpcInterfaceDescriptor
Alias for field number 2
- count(value, /)
Return number of occurrences of value.
- index(value, start=0, stop=9223372036854775807, /)
Return first index of value.
Raises ValueError if the value is not present.
- class qmi.core.rpc.QMI_LockTokenDescriptor(context_id: str, token: str)
Unique lock token that is used to lock/unlock RPC objects.
- context_id
Name of the context that owns the RPC proxy that requested the lock.
- Type:
str
- token
Unique token.
- Type:
str
- context_id: str
Alias for field number 0
- token: str
Alias for field number 1
- count(value, /)
Return number of occurrences of value.
- index(value, start=0, stop=9223372036854775807, /)
Return first index of value.
Raises ValueError if the value is not present.
- class qmi.core.rpc.QMI_RpcFutureState(value)
Possible states of an QMI_RpcFuture instance.
- class qmi.core.rpc.QMI_LockRpcAction(value)
Actions that can be performed on a lock.
- class qmi.core.rpc.QMI_LockRpcRequestMessage(source_address: QMI_MessageHandlerAddress, destination_address: QMI_MessageHandlerAddress, lock_token: QMI_LockTokenDescriptor | None, lock_action: QMI_LockRpcAction)
Message sent by an RPC client to interact with the lock state of a remote object.
See QMI_LockRpcReplyMessage for how to interpret the reply to a request.
- lock_token
The unique token to use for the lock.
- lock_action
The action to be performed on the lock state.
- class qmi.core.rpc.QMI_LockRpcReplyMessage(source_address: QMI_MessageHandlerAddress, destination_address: QMI_MessageHandlerAddress, request_id: str, lock_token: QMI_LockTokenDescriptor | None)
Message sent back to an RPC client with the result of the action.
The reply only contains a lock token (an actual token or a placeholder), indicating if the request was successful and what the state of the object lock is after the request.
- Specifically:
if you requested a lock and the returned token matches your token, you now own the lock;
if your requested to unlock and the returned token is None, the unlock was successful;
if your queried the lock status and the returned token is not None when the object is locked and None if the object is unlocked;
in all other cases, the request was denied.
- lock_token
The unique token that locked the object.
- class qmi.core.rpc.QMI_MethodRpcRequestMessage(source_address: QMI_MessageHandlerAddress, destination_address: QMI_MessageHandlerAddress, method_name: str, method_args: tuple, method_kwargs: dict, lock_token: QMI_LockTokenDescriptor | None = None)
Message sent by an RPC client to invoke a remote method.
- method_name
Name of the method to invoke.
- method_args
Tuple of positional arguments to the method.
- method_kwargs
Dictionary of keyword arguments to the method.
- lock_token
The unique token to use for the lock.
- class qmi.core.rpc.QMI_MethodRpcReplyMessage(source_address: QMI_MessageHandlerAddress, destination_address: QMI_MessageHandlerAddress, request_id: str, state: QMI_RpcFutureState, result: Any)
Message sent back to an RPC client with the result of a remote method invocation.
- state
Either RESULT_IS_VALUE, RESULT_IS_EXCEPTION or OBJECT_IS_LOCKED.
- result
Return value from the method or exception raised by the method.
- class qmi.core.rpc.QMI_RpcFuture(context: qmi.core.context.QMI_Context, rpc_object_address: QMI_MessageHandlerAddress, lock_token: QMI_LockTokenDescriptor | None)
Representation of the future completion of a method invoked via RPC.
An instance of QMI_RpcFuture is created when a call to an RPC method is dispatched. The future is completed when the RPC method finishes running. This happens asynchronously, because the real RPC method execution happens in a background thread.
This class is used internally when the application invokes a method on an RPC proxy object. In this case the proxy method will block until the future is completed. Alternatively, when the application invokes a non-blocking RPC call, the proxy method returns an instance of this class without waiting for the real method call to end.
- send_method_rpc_request_message(rpc_method_name: str, rpc_method_args: tuple, rpc_method_kwargs: dict) None
Send a request message to the RPC object to invoke the specified method.
- Parameters:
rpc_method_name – Name of the method to call.
rpc_method_args – Tuple of positional arguments.
rpc_method_kwargs – Dictionary of keyword arguments.
- handle_message(message: QMI_Message) None
Called when a reply message is received.
- wait(timeout: float | None = None) Any
Wait until the RPC call completes.
- Parameters:
timeout – Maximum wait time in seconds, or None to wait forever.
- Returns:
The return value from the associated RPC method call.
- Raises:
QMI_RpcTimeoutException – If the timeout expires before the RPC call completes.
Exception – If the associated RPC method call raised an exception.
- shutdown() None
Called by the QMI message router during unregistering of the handler.
After this call, no more messages will be received.
Subclasses can implement this method to free resources they are using. The default implementation does nothing.
Do not call this method explicitly. It will be called automatically during unregistering of the message handler.
- qmi.core.rpc.non_blocking_rpc_method_call(context: qmi.core.context.QMI_Context, rpc_object_address: QMI_MessageHandlerAddress, method_name: str, rpc_lock_token: QMI_LockTokenDescriptor | None, *args: Any, **kwargs: Any) Any
Helper function that performs a non-blocking call to a specific method of the target RPC object.
- qmi.core.rpc.blocking_rpc_method_call(context: qmi.core.context.QMI_Context, rpc_object_address: QMI_MessageHandlerAddress, method_name: str, rpc_lock_token: QMI_LockTokenDescriptor | None, *args: Any, rpc_timeout: float | None = None, **kwargs: Any) Any
Helper function that performs a blocking call to a specific method of the target RPC object.
- class qmi.core.rpc.QMI_RpcNonBlockingProxy(context: qmi.core.context.QMI_Context, descriptor: RpcObjectDescriptor)
Proxy class for RPC objects that performs non-blocking calls. Direct instantiation is not recommended.
This is always also instantiated in QMI_RpcProxy as self.rpc_nonblocking attribute. Typically, if user wants to use a non-blocking call with an RPC object rpc_proxy, they would do:
`python future = rpc_proxy.rpc_nonblocking.some_rpc_command(args) # Other stuff can be done here in the meanwhile, and the `proxy` is not blocked while waiting to return retval = future.wait() `Instead of the usual`python retval = rpc_proxy.some_rpc_commands(args) `
- class qmi.core.rpc.QMI_RpcProxy(context: qmi.core.context.QMI_Context, descriptor: RpcObjectDescriptor)
Proxy class for RPC objects that performs blocking calls. All RPC objects created return this proxy class to enable RPC communication between objects.
Direct instantiation of this class is not meant to be done by users; internal use only!
- property address: str
Return the “address” of the proxy with context name and object name.
- Returns:
String “{context name}.{name}”.
- lock(timeout: float = 0.0, lock_token: str | None = None) bool
Substitutes the lock method stub of QMI_RpcObject.
- unlock(lock_token: str | None = None) bool
Substitutes the unlock method stub of QMI_RpcObject.
- force_unlock() None
Substitutes the force_unlock method stub of QMI_RpcObject.
- is_locked() bool
Substitutes the is_locked method stub of QMI_RpcObject.
- qmi.core.rpc.rpc_method(method: _T) _T
Decorator to indicate that a method can be called via RPC.
- qmi.core.rpc.is_rpc_method(object: Any) bool
Return True if the object is a method that can be called via RPC.
- class qmi.core.rpc.QMI_RpcObject(context: qmi.core.context.QMI_Context, name: str, signal_declaration_class: type | None = None)
Base class for classes which expose (a subset of) their methods via RPC.
Each instance of QMI_RpcObject has a name, unique within its context. The context maintains a list of QMI_RpcObject instances.
Subclasses of QMI_RpcObject apply the @rpc_method decorator to (a subset of) their methods to mark them as callable via RPC.
Subclasses of QMI_RpcObject may choose to export (a subset of) their constant class attributes to be accessible directly via the proxy. This is done by creating a class attribute _rpc_constants holding a list of attribute names to be exported.
Each instance of QMI_RpcObject runs in a separate thread. It is not allowed to invoke methods of the QMI_RpcObject directly from outside the class. Instead, the proper way to access an instance of QMI_RpcObject is to invoke a method on a special “proxy” object, which translates the call into an RPC request, which triggers an invocation of the real method of the QMI_RpcObject instance.
Instances of QMI_RpcObject may publish QMI signals. Each instance may register a set of signals. Once registered, such signals can be published into the QMI network and routed to subscribed receivers.
- classmethod get_category() str | None
Return the optional name of the category this object belongs to.
A category name is a free-form string that has no special significance. Its purpose is to distinguish between groups of RPC objects that fulfill similar roles.
- lock(timeout: float = 0.0, lock_token: str | None = None) bool
Lock the remote object. If timeout is given, try every 0.1s within the given timeout value. The remote object can be locked with an optional custom lock token by giving a string into lock_token keyword argument.
If successful, this proxy is the only proxy that can invoke RPC methods on the remote object; other proxies will receive an “object is locked” response. The return value indicates if the lock was granted; a denied lock means the object was already locked by another proxy.
Do not override this stub method in subclasses. It has already been implemented in QMI_RpcProxy.
- unlock(lock_token: str | None = None) bool
Unlock the remote object.
Without optional parameters, this is only allowed by the proxy that initially locked the object. By giving the lock token as an input parameter, the specific object locked by this token can be unlocked. The return value indicates if the unlocking was successful.
Do not override this stub method in subclasses. It has already been implemented in QMI_RpcProxy.
- is_locked() bool
Query if the remote object is locked.
Do not override this stub method in subclasses. It has already been implemented in QMI_RpcProxy.
- force_unlock() None
Forcefully unlock the remote object.
This unlocks the object, regardless of who owns the lock. This allows you to unlock an object if the locking proxy has been destroyed without unlocking.
Use this with care.
Do not override this stub method in subclasses. It has already been implemented in QMI_RpcProxy.
- release_rpc_object() None
This method is called just before the RPC object is removed from the context.
When an RPC object is removed from the context (via context.remove_rpc_object()), this method is invoked to release any resources used by the RPC object instance.
Subclasses may override this method to release specific resources. The default implementation does nothing.
- get_name() str
Return the name of this object.
- Returns:
name attribute.
- get_signals() list[SignalDescription]
Return a list of signals that can be published by this object.
- Returns:
List consisting of qmi_signals attributes.
- qmi.core.rpc.make_interface_descriptor(rpc_object_class: Type[QMI_RpcObject], signal_declaration_class: Type[QMI_RpcObject] | None = None) RpcInterfaceDescriptor
Create a description of the (subset of the) interface of the specified QMI_RpcObject subclass that can be accessed via RPC. Signal declarations are taken from the specified signal declaration class, which may be a different class than that from which the RPC methods are extracted.
- class qmi.core.rpc.RpcObjectManager(address: QMI_MessageHandlerAddress, context: qmi.core.context.QMI_Context, rpc_object_maker: Callable[[], QMI_RpcObject])
Manages a single instance of QMI_RpcObject.
A dedicated RpcObjectManager is created by the context for each instance of QMI_RpcObject. The RpcObjectManager owns a single RpcThread instance (which, in turn, owns a single QMI_RpcObject instance).
An RpcObjectManager receives RPC request messages on behalf of the RPC object. It pushes these messages into a queue, from where they are picked up and handled by the RpcThread.
The message handler address of the RpcObjectManager is based on the name of the associated RPC object.
This class is intended for internal use within QMI. Application programs should not interact with this class directly.
- start() None
Create a background thread and start handling RPC calls.
- stop() None
Stop the background thread and clean it up.
- make_proxy() QMI_RpcProxy
Return a new instance of the RpcProxy class corresponding to the RPC object.
Wait until initialization of the RPC object instance is finished, if necessary. If initialization was successful, create and return a RpcProxy instance for the RPC object.
Otherwise re-raise the exception that occurred during initialization.
- handle_message(message: QMI_Message) None
Called when a QMI message is delivered for our RPC object.
- rpc_object() QMI_RpcObject
Return the RPC object instanced managed by this RpcObjectManager.
- shutdown() None
Called by the QMI message router during unregistering of the handler.
After this call, no more messages will be received.
Subclasses can implement this method to free resources they are using. The default implementation does nothing.
Do not call this method explicitly. It will be called automatically during unregistering of the message handler.