Skip to content
Open
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: 4 additions & 4 deletions Pyro5/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
from . import __version__
from .configure import global_config as config
from .core import URI, locate_ns, resolve, type_meta
from .client import Proxy, BatchProxy, SerializedBlob
from .server import Daemon, DaemonObject, callback, expose, behavior, oneway, serve
from .client import Proxy, BatchProxy, ConcurrentProxy, SerializedBlob
from .server import Daemon, DaemonObject, callback, expose, behavior, oneway, serve, Functor
from .nameserver import start_ns, start_ns_loop
from .serializers import SerializerBase
from .callcontext import current_context
Expand All @@ -24,7 +24,7 @@


__all__ = ["config", "URI", "locate_ns", "resolve", "type_meta", "current_context",
"Proxy", "BatchProxy", "SerializedBlob", "SerializerBase",
"Daemon", "DaemonObject", "callback", "expose", "behavior", "oneway",
"Proxy", "BatchProxy", "ConcurrentProxy", "SerializedBlob", "SerializerBase",
"Daemon", "DaemonObject", "callback", "expose", "behavior", "oneway", "Functor",
"start_ns", "start_ns_loop", "serve", "register_dict_to_class",
"register_class_to_dict", "unregister_dict_to_class", "unregister_class_to_dict"]
20 changes: 20 additions & 0 deletions Pyro5/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import logging
import serpent
import contextlib
from threading import local
from collections import defaultdict
from . import config, core, serializers, protocol, errors, socketutil
from .callcontext import current_context
try:
Expand Down Expand Up @@ -180,6 +182,7 @@ def __dir__(self):
# obj.__getitem__(index)), the special methods are not looked up via __getattr__
# for efficiency reasons; instead, their presence is checked directly.
# Thus we need to define them here to force (remote) lookup through __getitem__.
def __call__(self, *args, **kwargs): return self.__getattr__('__call__')(*args, **kwargs)
def __bool__(self): return True
def __len__(self): return self.__getattr__('__len__')()
def __getitem__(self, index): return self.__getattr__('__getitem__')(index)
Expand Down Expand Up @@ -626,7 +629,24 @@ def _pyroInvoke(self, name, args, kwargs):
results = self.__proxy._pyroInvokeBatch(self.__calls)
self.__calls = [] # clear for re-use
return self.__resultsgenerator(results)

class ConcurrentProxy(Proxy):
"""
Proxy for remote python objects. The `Proxy` must be explicitly passed across threads. This class handles automatically
creating new proxies for the current thread when necessary.
"""
THREAD_PROXY_MAP = defaultdict(local)
def __init__(self, uri: str, **kwargs):
super().__init__(uri, **kwargs)
ConcurrentProxy.THREAD_PROXY_MAP[self._pyroUri].proxy = self

def _pyroInvoke(self, methodname, vargs, kwargs, flags=0, objectId=None):
local_data = ConcurrentProxy.THREAD_PROXY_MAP[self._pyroUri]
if not hasattr(local_data, "proxy"):
local_data.proxy = self.__copy__()
return Proxy._pyroInvoke(
local_data.proxy, methodname, vargs, kwargs, flags, objectId
)

class SerializedBlob(object):
"""
Expand Down
24 changes: 22 additions & 2 deletions Pyro5/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import weakref
import serpent
import ipaddress
from typing import TypeVar, Tuple, Union, Optional, Dict, Any, Sequence, Set
from typing import Generic, ParamSpec, TypeVar, Tuple, Union, Optional, Dict, Any, Sequence, Set
from . import config, core, errors, serializers, socketutil, protocol, client
from .callcontext import current_context
from collections.abc import Callable
Expand All @@ -29,7 +29,7 @@

_private_dunder_methods = frozenset([
"__init__", "__init_subclass__", "__class__", "__module__", "__weakref__",
"__call__", "__new__", "__del__", "__repr__",
"__new__", "__del__", "__repr__",
"__str__", "__format__", "__nonzero__", "__bool__", "__coerce__",
"__cmp__", "__eq__", "__ne__", "__hash__", "__ge__", "__gt__", "__le__", "__lt__",
"__dir__", "__enter__", "__exit__", "__copy__", "__deepcopy__", "__sizeof__",
Expand Down Expand Up @@ -138,6 +138,26 @@ def _behavior(clazz):
return _behavior


ReturnType = TypeVar("ReturnType")
ParamsTypes = ParamSpec("ParamsTypes")

@expose
class Functor(Generic[ParamsTypes, ReturnType]):
"""
A functor is a callable object that can be used as a function.
This is used to wrap functions that are not methods of a class.
"""

def __init__(self, func: Callable[ParamsTypes, ReturnType]):
self.func = func

@expose
def __call__(
self, *args: ParamsTypes.args, **kwargs: ParamsTypes.kwargs
) -> ReturnType:
return self.func(*args, **kwargs)


@expose
class DaemonObject(object):
"""The part of the daemon that is exposed as a Pyro object."""
Expand Down
1 change: 0 additions & 1 deletion tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1032,7 +1032,6 @@ def testIsPrivateName(self):
assert Pyro5.server.is_private_attribute("___p")
assert not Pyro5.server.is_private_attribute("__dunder__") # dunder methods should not be private except a list of exceptions as tested below
assert Pyro5.server.is_private_attribute("__init__")
assert Pyro5.server.is_private_attribute("__call__")
assert Pyro5.server.is_private_attribute("__new__")
assert Pyro5.server.is_private_attribute("__del__")
assert Pyro5.server.is_private_attribute("__repr__")
Expand Down