"""
**NetRef**: a transparent *network reference*. This module contains quite a lot
of *magic*, so beware.
"""
import sys
import inspect
import types
from rpyc.lib.compat import pickle, is_py3k, maxint
from rpyc.core import consts
_local_netref_attrs = frozenset([
'____conn__', '____oid__', '____refcount__', '__class__', '__cmp__', '__del__', '__delattr__',
'__dir__', '__doc__', '__getattr__', '__getattribute__', '__hash__',
'__init__', '__metaclass__', '__module__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__slots__', '__str__',
'__weakref__', '__dict__', '__members__', '__methods__',
])
"""the set of attributes that are local to the netref object"""
_builtin_types = [
type, object, bool, complex, dict, float, int, list, slice, str, tuple, set,
frozenset, Exception, type(None), types.BuiltinFunctionType, types.GeneratorType,
types.MethodType, types.CodeType, types.FrameType, types.TracebackType,
types.ModuleType, types.FunctionType,
type(int.__add__), # wrapper_descriptor
type((1).__add__), # method-wrapper
type(iter([])), # listiterator
type(iter(())), # tupleiterator
type(iter(set())), # setiterator
]
"""a list of types considered built-in (shared between connections)"""
try:
BaseException
except NameError:
pass
else:
_builtin_types.append(BaseException)
if is_py3k:
_builtin_types.extend([
bytes, bytearray, type(iter(range(10))), memoryview,
])
xrange = range
else:
_builtin_types.extend([
basestring, unicode, long, xrange, type(iter(xrange(10))), file,
types.InstanceType, types.ClassType, types.DictProxyType,
])
_normalized_builtin_types = dict(((t.__name__, t.__module__), t)
for t in _builtin_types)
[docs]def syncreq(proxy, handler, *args):
"""Performs a synchronous request on the given proxy object.
Not intended to be invoked directly.
:param proxy: the proxy on which to issue the request
:param handler: the request handler (one of the ``HANDLE_XXX`` members of
``rpyc.protocol.consts``)
:param args: arguments to the handler
:raises: any exception raised by the operation will be raised
:returns: the result of the operation
"""
conn = object.__getattribute__(proxy, "____conn__")()
if not conn:
raise ReferenceError('weakly-referenced object no longer exists')
oid = object.__getattribute__(proxy, "____oid__")
return conn.sync_request(handler, oid, *args)
[docs]def asyncreq(proxy, handler, *args):
"""Performs an asynchronous request on the given proxy object.
Not intended to be invoked directly.
:param proxy: the proxy on which to issue the request
:param handler: the request handler (one of the ``HANDLE_XXX`` members of
``rpyc.protocol.consts``)
:param args: arguments to the handler
:returns: an :class:`AsyncResult <rpyc.core.async.AsyncResult>` representing
the operation
"""
conn = object.__getattribute__(proxy, "____conn__")()
if not conn:
raise ReferenceError('weakly-referenced object no longer exists')
oid = object.__getattribute__(proxy, "____oid__")
return conn.async_request(handler, oid, *args)
[docs]class BaseNetref(object):
"""The base netref class, from which all netref classes derive. Some netref
classes are "pre-generated" and cached upon importing this module (those
defined in the :data:`_builtin_types`), and they are shared between all
connections.
The rest of the netref classes are created by :meth:`rpyc.core.protocl.Connection._unbox`,
and are private to the connection.
Do not use this class directly; use :func:`class_factory` instead.
:param conn: the :class:`rpyc.core.protocol.Connection` instance
:param oid: the unique object ID of the remote object
"""
# this is okay with py3k -- see below
__metaclass__ = NetrefMetaclass
__slots__ = ["____conn__", "____oid__", "__weakref__", "____refcount__"]
def __init__(self, conn, oid):
self.____conn__ = conn
self.____oid__ = oid
self.____refcount__ = 1
def __del__(self):
try:
asyncreq(self, consts.HANDLE_DEL, self.____refcount__)
except Exception:
# raised in a destructor, most likely on program termination,
# when the connection might have already been closed.
# it's safe to ignore all exceptions here
pass
def __getattribute__(self, name):
if name in _local_netref_attrs:
if name == "__class__":
cls = object.__getattribute__(self, "__class__")
if cls is None:
cls = self.__getattr__("__class__")
return cls
elif name == "__doc__":
return self.__getattr__("__doc__")
elif name == "__members__": # for Python < 2.6
return self.__dir__()
else:
return object.__getattribute__(self, name)
elif name == "__call__": # IronPython issue #10
return object.__getattribute__(self, "__call__")
else:
return syncreq(self, consts.HANDLE_GETATTR, name)
def __getattr__(self, name):
return syncreq(self, consts.HANDLE_GETATTR, name)
def __delattr__(self, name):
if name in _local_netref_attrs:
object.__delattr__(self, name)
else:
syncreq(self, consts.HANDLE_DELATTR, name)
def __setattr__(self, name, value):
if name in _local_netref_attrs:
object.__setattr__(self, name, value)
else:
syncreq(self, consts.HANDLE_SETATTR, name, value)
def __dir__(self):
return list(syncreq(self, consts.HANDLE_DIR))
# support for metaclasses
def __hash__(self):
return syncreq(self, consts.HANDLE_HASH)
def __cmp__(self, other):
return syncreq(self, consts.HANDLE_CMP, other)
def __repr__(self):
return syncreq(self, consts.HANDLE_REPR)
def __str__(self):
return syncreq(self, consts.HANDLE_STR)
# support for pickling netrefs
def __reduce_ex__(self, proto):
return pickle.loads, (syncreq(self, consts.HANDLE_PICKLE, proto),)
if not isinstance(BaseNetref, NetrefMetaclass):
# python 2 and 3 compatible metaclass...
ns = dict(BaseNetref.__dict__)
for slot in BaseNetref.__slots__:
ns.pop(slot)
BaseNetref = NetrefMetaclass(BaseNetref.__name__, BaseNetref.__bases__, ns)
def _make_method(name, doc):
"""creates a method with the given name and docstring that invokes
:func:`syncreq` on its `self` argument"""
slicers = {"__getslice__" : "__getitem__", "__delslice__" : "__delitem__", "__setslice__" : "__setitem__"}
name = str(name) # IronPython issue #10
if name == "__call__":
def __call__(_self, *args, **kwargs):
kwargs = tuple(kwargs.items())
return syncreq(_self, consts.HANDLE_CALL, args, kwargs)
__call__.__doc__ = doc
return __call__
elif name in slicers: # 32/64 bit issue #41
def method(self, start, stop, *args):
if stop == maxint:
stop = None
return syncreq(self, consts.HANDLE_OLDSLICING, slicers[name], name, start, stop, args)
method.__name__ = name
method.__doc__ = doc
return method
else:
def method(_self, *args, **kwargs):
kwargs = tuple(kwargs.items())
return syncreq(_self, consts.HANDLE_CALLATTR, name, args, kwargs)
method.__name__ = name
method.__doc__ = doc
return method
[docs]def inspect_methods(obj):
"""introspects the given (local) object, returning a list of all of its
methods (going up the MRO).
:param obj: any local (not proxy) python object
:returns: a list of ``(method name, docstring)`` tuples of all the methods
of the given object
"""
methods = {}
attrs = {}
if isinstance(obj, type):
# don't forget the darn metaclass
mros = list(reversed(type(obj).__mro__)) + list(reversed(obj.__mro__))
else:
mros = reversed(type(obj).__mro__)
for basecls in mros:
attrs.update(basecls.__dict__)
for name, attr in attrs.items():
if name not in _local_netref_attrs and hasattr(attr, "__call__"):
methods[name] = inspect.getdoc(attr)
return methods.items()
[docs]def class_factory(clsname, modname, methods):
"""Creates a netref class proxying the given class
:param clsname: the class's name
:param modname: the class's module name
:param methods: a list of ``(method name, docstring)`` tuples, of the methods
that the class defines
:returns: a netref class
"""
clsname = str(clsname) # IronPython issue #10
modname = str(modname) # IronPython issue #10
ns = {"__slots__" : ()}
for name, doc in methods:
name = str(name) # IronPython issue #10
if name not in _local_netref_attrs:
ns[name] = _make_method(name, doc)
ns["__module__"] = modname
if modname in sys.modules and hasattr(sys.modules[modname], clsname):
ns["__class__"] = getattr(sys.modules[modname], clsname)
elif (clsname, modname) in _normalized_builtin_types:
ns["__class__"] = _normalized_builtin_types[clsname, modname]
else:
# to be resolved by the instance
ns["__class__"] = None
return type(clsname, (BaseNetref,), ns)
builtin_classes_cache = {}
"""The cache of built-in netref classes (each of the types listed in
:data:`_builtin_types`). These are shared between all RPyC connections"""
# init the builtin_classes_cache
for cls in _builtin_types:
builtin_classes_cache[cls.__name__, cls.__module__] = class_factory(
cls.__name__, cls.__module__, inspect_methods(cls))