Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions Pyro5/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,6 @@ def __dir__(self):

def _pyroRelease(self):
"""release the connection to the pyro daemon"""
self.__check_owner()
if self._pyroConnection is not None:
self._pyroConnection.close()
self._pyroConnection = None
Expand Down Expand Up @@ -194,7 +193,6 @@ def __pyroSetTimeout(self, timeout):

def _pyroInvoke(self, methodname, vargs, kwargs, flags=0, objectId=None):
"""perform the remote method call communication"""
self.__check_owner()
core.current_context.response_annotations = {}
if self._pyroConnection is None:
self.__pyroCreateConnection()
Expand Down Expand Up @@ -326,7 +324,6 @@ def connect_and_handshake(conn):
log.error(err)
raise errors.ProtocolError(err)

self.__check_owner()
if self._pyroConnection is not None:
return False # already connected
uri = core.resolve(self._pyroUri)
Expand Down Expand Up @@ -451,11 +448,6 @@ def __serializeBlobArgs(self, vargs, kwargs, annotations, flags, objectId, metho
# replaces SerializedBlob argument with the data to be serialized
return serializer.dumpsCall(objectId, methodname, blob._data, kwargs), flags

def __check_owner(self):
if get_ident() != self.__pyroOwnerThread:
raise errors.PyroError("the calling thread is not the owner of this proxy, "
"create a new proxy in this thread or transfer ownership.")


class _RemoteMethod(object):
"""method call abstraction"""
Expand Down
5 changes: 3 additions & 2 deletions Pyro5/nameserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,8 @@ def ping(self):
class NameServerDaemon(server.Daemon):
"""Daemon that contains the Name Server."""

def __init__(self, host=None, port=None, unixsocket=None, nathost=None, natport=None, storage=None):
def __init__(self, host=None, port=None, unixsocket=None, nathost=None, natport=None, storage=None,
only_exposed=True):
if host is None:
host = config.HOST
elif not isinstance(host, str):
Expand All @@ -471,7 +472,7 @@ def __init__(self, host=None, port=None, unixsocket=None, nathost=None, natport=
if existing_count > 0:
log.debug("number of existing entries in storage: %d", existing_count)
super(NameServerDaemon, self).__init__(host, port, unixsocket, nathost=nathost, natport=natport)
self.register(self.nameserver, core.NAMESERVER_NAME)
self.register(self.nameserver, core.NAMESERVER_NAME, only_exposed=only_exposed)
metadata = {"class:Pyro5.nameserver.NameServer"}
self.nameserver.register(core.NAMESERVER_NAME, self.uriFor(self.nameserver), metadata=metadata)
if config.NS_AUTOCLEAN > 0:
Expand Down
44 changes: 43 additions & 1 deletion Pyro5/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

import array
import builtins
import copyreg
import pickle
import uuid
import logging
import struct
Expand All @@ -20,6 +22,7 @@
from . import errors

__all__ = ["SerializerBase", "SerpentSerializer", "JsonSerializer", "MarshalSerializer", "MsgpackSerializer",
"PickleSerializer",
"serializers", "serializers_by_id"]

log = logging.getLogger("Pyro5.serializers")
Expand Down Expand Up @@ -492,12 +495,51 @@ def register_type_replacement(cls, object_type, replacement_function):
cls.__type_replacements[object_type] = replacement_function


class PickleSerializer(SerializerBase):
"""
A (de)serializer that wraps the Pickle serialization protocol.
It can optionally compress the serialized data, and is thread safe.
"""
serializer_id = 5 # never change this

# def _convertToBytes(self, data):
# ret = super()._convertToBytes(data)
# return self.loads(data)

def dumpsCall(self, obj, method, vargs, kwargs):
return pickle.dumps((obj, method, vargs, kwargs))

def dumps(self, data):
return pickle.dumps(data)

def loadsCall(self, data):
data = self._convertToBytes(data)
return pickle.loads(data)

def loads(self, data):
data = self._convertToBytes(data)
return pickle.loads(data)

@classmethod
def register_type_replacement(cls, object_type, replacement_function):
def copyreg_function(obj):
return replacement_function(obj).__reduce__()

if object_type is type or not inspect.isclass(object_type):
raise ValueError("refusing to register replacement for a non-type or the type 'type' itself")
try:
copyreg.pickle(object_type, copyreg_function)
except TypeError:
pass


"""The various serializers that are supported"""
serializers = {
"serpent": SerpentSerializer(),
"marshal": MarshalSerializer(),
"json": JsonSerializer(),
"msgpack": MsgpackSerializer()
"msgpack": MsgpackSerializer(),
"pickle": PickleSerializer(),
}

"""The available serializers by their internal id"""
Expand Down
48 changes: 28 additions & 20 deletions Pyro5/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import logging
import inspect
import warnings
from functools import partial

import serpent
from . import config, core, errors, serializers, socketutil, protocol, client

Expand Down Expand Up @@ -152,13 +154,13 @@ def info(self):
core.DAEMON_NAME, self.daemon.locationStr, self.daemon.natLocationStr,
len(self.daemon.objectsById), self.daemon.transportServer)

def get_metadata(self, objectId, as_lists=False):
def get_metadata(self, objectId, as_lists=False, only_exposed=True):
"""
Get metadata for the given object (exposed methods, oneways, attributes).
"""
obj = self.daemon.objectsById.get(objectId)
if obj is not None:
metadata = get_exposed_members(obj, as_lists=as_lists)
metadata = get_exposed_members(obj, only_exposed=only_exposed, as_lists=as_lists)
if not metadata["methods"] and not metadata["attrs"]:
# Something seems wrong: nothing is remotely exposed.
warnings.warn("Class %r doesn't expose any methods or attributes. Did you forget setting @expose on them?" % type(obj))
Expand Down Expand Up @@ -192,6 +194,8 @@ class Daemon(object):
to the appropriate objects.
"""

only_exposed = True

def __init__(self, host=None, port=0, unixsocket=None, nathost=None, natport=None, interface=DaemonObject, connected_socket=None):
if connected_socket:
nathost = natport = None
Expand Down Expand Up @@ -265,7 +269,7 @@ def selector(self):
return self.transportServer.selector

@staticmethod
def serveSimple(objects, host=None, port=0, daemon=None, ns=True, verbose=True):
def serveSimple(objects, host=None, port=0, daemon=None, ns=True, verbose=True, only_exposed=True):
"""
Basic method to fire up a daemon (or supply one yourself).
objects is a dict containing objects to register as keys, and
Expand All @@ -284,7 +288,7 @@ def serveSimple(objects, host=None, port=0, daemon=None, ns=True, verbose=True):
localname = None # name is used for the name server
else:
localname = name # no name server, use name in daemon
uri = daemon.register(obj, localname)
uri = daemon.register(obj, localname, only_exposed=only_exposed)
if verbose:
print("Object {0}:\n uri = {1}".format(repr(obj), uri))
if name and ns:
Expand Down Expand Up @@ -359,7 +363,8 @@ def _handshake(self, conn, denied_reason=None):
handshake_response = self.validateHandshake(conn, data["handshake"])
handshake_response = {
"handshake": handshake_response,
"meta": self.objectsById[core.DAEMON_NAME].get_metadata(data["object"], as_lists=True)
"meta": self.objectsById[core.DAEMON_NAME].get_metadata(data["object"], as_lists=True,
only_exposed=self.only_exposed)
}
data = serializer.dumps(handshake_response)
msgtype = protocol.MSG_CONNECTOK
Expand Down Expand Up @@ -454,7 +459,7 @@ def handleRequest(self, conn):
# batched method calls, loop over them all and collect all results
data = []
for method, vargs, kwargs in vargs:
method = get_attribute(obj, method)
method = get_attribute(obj, method, only_exposed=self.only_exposed)
try:
result = method(*vargs, **kwargs) # this is the actual method call to the Pyro object
except Exception as xv:
Expand All @@ -469,12 +474,12 @@ def handleRequest(self, conn):
# normal single method call
if method == "__getattr__":
# special case for direct attribute access (only exposed @properties are accessible)
data = get_exposed_property_value(obj, vargs[0])
data = get_exposed_property_value(obj, vargs[0], only_exposed=self.only_exposed)
elif method == "__setattr__":
# special case for direct attribute access (only exposed @properties are accessible)
data = set_exposed_property_value(obj, vargs[0], vargs[1])
data = set_exposed_property_value(obj, vargs[0], vargs[1], only_exposed=self.only_exposed)
else:
method = get_attribute(obj, method)
method = get_attribute(obj, method, only_exposed=self.only_exposed)
if request_flags & protocol.FLAGS_ONEWAY:
# oneway call to be run inside its own thread, otherwise client blocking can still occur
# on the next call on the same proxy
Expand Down Expand Up @@ -636,7 +641,7 @@ def _sendExceptionResponse(self, connection, seq, serializer_id, exc_value, tbin
protocol.log_wiredata(log, "daemon wiredata sending (error response)", msg)
connection.send(msg.data)

def register(self, obj_or_class, objectId=None, force=False):
def register(self, obj_or_class, objectId=None, force=False, only_exposed=True):
"""
Register a Pyro object under the given id. Note that this object is now only
known inside this daemon, it is not automatically available in a name server.
Expand Down Expand Up @@ -668,9 +673,11 @@ def register(self, obj_or_class, objectId=None, force=False):
# we need to do this for all known serializers
for ser in serializers.serializers.values():
if inspect.isclass(obj_or_class):
ser.register_type_replacement(obj_or_class, pyro_obj_to_auto_proxy)
ser.register_type_replacement(obj_or_class,
partial(pyro_obj_to_auto_proxy, only_exposed=only_exposed))
else:
ser.register_type_replacement(type(obj_or_class), pyro_obj_to_auto_proxy)
ser.register_type_replacement(type(obj_or_class),
partial(pyro_obj_to_auto_proxy, only_exposed=only_exposed))
# register the object/class in the mapping
self.objectsById[obj_or_class._pyroId] = obj_or_class
return self.uriFor(objectId)
Expand Down Expand Up @@ -730,7 +737,7 @@ def resetMetadataCache(self, objectOrId, nat=True):
reset_exposed_members(registered_object, as_lists=True)
reset_exposed_members(registered_object, as_lists=False)

def proxyFor(self, objectOrId, nat=True):
def proxyFor(self, objectOrId, nat=True, only_exposed=True):
"""
Get a fully initialized Pyro Proxy for the given object (or object id) for this daemon.
If nat is False, the configured NAT address (if any) is ignored.
Expand All @@ -743,7 +750,7 @@ def proxyFor(self, objectOrId, nat=True):
registered_object = self.objectsById[uri.object]
except KeyError:
raise errors.DaemonError("object isn't registered in this daemon")
meta = get_exposed_members(registered_object)
meta = get_exposed_members(registered_object, only_exposed=only_exposed)
proxy._pyroGetMetadata(known_metadata=meta)
return proxy

Expand Down Expand Up @@ -825,16 +832,16 @@ def __deserializeBlobArgs(self, protocolmsg):
serializers.SerializerBase.register_class_to_dict(Daemon, serializers.serialize_pyro_object_to_dict, serpent_too=False)


def pyro_obj_to_auto_proxy(obj):
def pyro_obj_to_auto_proxy(obj, only_exposed=True):
"""reduce function that automatically replaces Pyro objects by a Proxy"""
daemon = getattr(obj, "_pyroDaemon", None)
if daemon:
# only return a proxy if the object is a registered pyro object
return daemon.proxyFor(obj)
return daemon.proxyFor(obj, only_exposed=only_exposed)
return obj


def get_attribute(obj, attr):
def get_attribute(obj, attr, only_exposed=True):
"""
Resolves an attribute name to an object. Raises
an AttributeError if any attribute in the chain starts with a '``_``'.
Expand All @@ -843,11 +850,12 @@ def get_attribute(obj, attr):
"""
if is_private_attribute(attr):
raise AttributeError("attempt to access private attribute '%s'" % attr)
else:
obj = getattr(obj, attr)
obj = getattr(obj, attr)
if getattr(obj, "_pyroExposed", False):
return obj
raise AttributeError("attempt to access unexposed attribute '%s'" % attr)
if only_exposed:
raise AttributeError("attempt to access unexposed attribute '%s'" % attr)
return obj


__exposed_member_cache = {}
Expand Down
4 changes: 2 additions & 2 deletions tests/test_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,12 @@ def test_validate(self):
msg.data[38] = 0xff # kill the magic number
with pytest.raises(Pyro5.errors.ProtocolError) as x:
Pyro5.protocol.ReceivingMessage.validate(msg.data)
assert "magic number" in str(x)
assert "magic number" in str(x.value)
msg.data[38] = orig_magic # repair the magic number
msg.data[5] = 0xff # invalid protocol version
with pytest.raises(Pyro5.errors.ProtocolError) as x:
Pyro5.protocol.ReceivingMessage.validate(msg.data)
assert "protocol version" in str(x)
assert "protocol version" in str(x.value)

def test_create_nopayload(self):
send_msg = self.createmessage(compression=True)
Expand Down