ObjectProxy and Future

After initial setup, these classes are the main API through which a remote process is controlled.

class pyacq.core.rpc.ObjectProxy(rpc_addr, obj_id, ref_id, type_str='', attributes=(), **kwds)[source]

Proxy to an object stored by a remote RPCServer.

A proxy behaves in most ways like the object that it wraps–you can request the same attributes, call methods, etc. There are a few important differences:

  • A proxy can be on a different thread, process, or machine than its object, so long as the object’s thread has an RPCServer and the proxy’s thread has an associated RPCClient.
  • Attribute lookups and method calls can be slower because the request and response must traverse a socket. These can also be performed asynchronously to avoid blocking the client thread.
  • Function argument and return types must be serializable or proxyable. Most basic types can be serialized, including numpy arrays. All other objects are automatically proxied, but there are some cases when this will not work well.
  • __repr__() and __str__() are overridden on proxies to allow safe debugging.
  • __hash__() is overridden to ensure that remote hash values are not used locally.

For the most part, object proxies can be used exactly as if they are local objects:

client = RPCClient(address)  # connect to RPCServer
rsys = client._import('sys') # returns proxy to sys module on remote process
rsys.stdout.write            # proxy to remote sys.stdout.write
rsys.stdout.write('hello')   # calls sys.stdout.write('hello') on remote machine
                             # and returns the result (None)

When calling a proxy to a remote function, the call can be made synchronous (caller is blocked until result can be returned), asynchronous (Future is returned immediately and result can be accessed later), or return can be disabled entirely:

ros = proc._import('os')

# synchronous call; caller blocks until result is returned
pid = ros.getpid()

# asynchronous call
request = ros.getpid(_sync='async')
while not request.hasResult():
    time.sleep(0.01)
pid = request.result()

# disable return when we know it isn't needed.
rsys.stdout.write('hello', _sync='off')

Additionally, values returned from a remote function call are automatically returned either by value (must be picklable) or by proxy. This behavior can be forced:

rnp = proc._import('numpy')
arrProxy = rnp.array([1,2,3,4], _return_type='proxy')
arrValue = rnp.array([1,2,3,4], _return_type='value')

The default sync and return_type behaviors (as well as others) can be set for each proxy individually using ObjectProxy._set_proxy_options() or globally using proc.set_proxy_options().

It is also possible to send arguments by proxy if an RPCServer is running in the caller’s thread (this can be used, for example, to connect Qt signals across network connections):

def callback():
    print("called back.")

# Remote process can invoke our callback function as long as there is 
# a server running here to process the request.
remote_object.set_callback(proxy(callback))
__call__(*args, **kwargs)[source]

Call the proxied object from the remote process.

All positional and keyword arguments (except those listed below) are sent to the remote procedure call.

In synchronous mode (see parameters below), this method blocks until the remote return value has been received, and then returns that value. In asynchronous mode, this method returns a Future instance immediately, which may be used to retrieve the return value later. If return is disabled, then the method immediately returns None.

If the remote call raises an exception on the remote process, then this method will raise RemoteCallException if in synchronous mode, or calling Future.result() will raise RemoteCallException if in asynchronous mode. If return is disabled, then remote exceptions will be ignored.

Parameters:
_sync: ‘off’, ‘sync’, or ‘async’

Set the sync mode for this call. The default value is determined by the ‘sync’ argument to _set_proxy_options().

_return_type: ‘value’, ‘proxy’, or ‘auto’

Set the return type for this call. The default value is determined by the ‘return_type’ argument to _set_proxy_options().

_timeout: float

Set the timeout for this call. The default value is determined by the ‘timeout’ argument to _set_proxy_options().

__getattr__(attr)[source]

Calls __getattr__ on the remote object and returns the attribute by value or by proxy depending on the options set (see ObjectProxy._set_proxy_options and RemoteEventHandler.set_proxy_options)

If the option ‘defer_getattr’ is True for this proxy, then a new proxy object is returned _without_ asking the remote object whether the named attribute exists. This can save time when making multiple chained attribute requests, but may also defer a possible AttributeError until later, making them more difficult to debug.

__setattr__(*args)[source]

Implement setattr(self, name, value).

_delete(sync='sync', **kwds)[source]

Ask the RPC server to release the reference held by this proxy.

Note: this does not guarantee the remote object will be deleted; only that its reference count will be reduced. Any copies of this proxy will no longer be usable.

_get_value()[source]

Return the value of the proxied object.

If the object is not serializable, then raise an exception.

_set_proxy_options(**kwds)[source]

Change the behavior of this proxy. For all options, a value of None will cause the proxy to instead use the default behavior defined by its parent Process.

Parameters:
sync : ‘sync’, ‘async’, ‘off’, or None

If ‘async’, then calling methods will return a Future object that can be used to inquire later about the result of the method call. If ‘sync’, then calling a method will block until the remote process has returned its result or the timeout has elapsed (in this case, a Request object is returned instead). If ‘off’, then the remote process is instructed not to reply and the method call will return None immediately. This option can be overridden by supplying a _sync keyword argument when calling the method (see __call__()).

return_type : ‘auto’, ‘proxy’, ‘value’, or None

If ‘proxy’, then the value returned when calling a method will be a proxy to the object on the remote process. If ‘value’, then attempt to pickle the returned object and send it back. If ‘auto’, then the decision is made by consulting the ‘no_proxy_types’ option. This option can be overridden by supplying a _return_type keyword argument when calling the method (see __call__()).

auto_proxy : bool or None

If True, arguments to __call__ are automatically converted to proxy unless their type is listed in no_proxy_types (see below). If False, arguments are left untouched. Use proxy(obj) to manually convert arguments before sending.

timeout : float or None

Length of time to wait during synchronous requests before returning a Request object instead. This option can be overridden by supplying a _timeout keyword argument when calling a method (see __call__()).

defer_getattr : True, False, or None

If False, all attribute requests will be sent to the remote process immediately and will block until a response is received (or timeout has elapsed). If True, requesting an attribute from the proxy returns a new proxy immediately. The remote process is not contacted to make this request. This is faster, but it is possible to request an attribute that does not exist on the proxied object. In this case, AttributeError will not be raised until an attempt is made to look up the attribute on the remote process.

no_proxy_types : list

List of object types that should not be proxied when sent to the remote process.

auto_delete : bool

If True, then the proxy will automatically call self._delete() when it is collected by Python.

class pyacq.core.rpc.Future(client, call_id)[source]

Represents a return value from a remote procedure call that has not yet arrived.

Instances of Future are returned from ObjectProxy.__call__() when used with _sync='async'. This is the mechanism through which remote functions may be called asynchronously.

Use done() to determine whether the return value (or an error message) has arrived, and result() to get the return value. If the remote call raised an exception, then calling result() will raise RemoteCallException with a transcript of the original exception.

See concurrent.futures.Future in the Python documentation for more information.

cancel()[source]

Cancel the future if possible.

Returns True if the future was cancelled, False otherwise. A future cannot be cancelled if it is running or has already completed.

result(timeout=None)[source]

Return the result of this Future.

If the result is not yet available, then this call will block until the result has arrived or the timeout elapses.