Skip to content

Commit 6d83675

Browse files
author
Greg Turnquist
committed
Merge branch 'SESPRINGPYTHONPY-156'
2 parents 5a6c478 + 7e4f4d3 commit 6d83675

File tree

12 files changed

+570
-6
lines changed

12 files changed

+570
-6
lines changed

build.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ def docs_pdf(version):
386386
def docs_sphinx():
387387
cur = os.getcwd()
388388
os.chdir("docs/sphinx")
389-
os.system("make html")
389+
os.system("make html epub")
390390
os.chdir(cur)
391391
shutil.copytree("docs/sphinx/build/html", "target/docs/sphinx")
392392

dependencies/Pyro4-4.2.tar.gz

66.1 KB
Binary file not shown.

docs/sphinx/source/remoting.rst

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ Spring Python currently supports and requires the installation of at least one o
3131

3232
* `Pyro <http://pyro.sourceforge.net/>`_ (Python Remote Objects) - a pure Python transport mechanism
3333

34+
* `Pyro4 <http://www.razorvine.net/python/Pyro/>` - (Python Remote Object v.4) - an updated version of the Pyro API.
35+
3436
* `Hessian <http://hessian.caucho.com/>`_ - support for Hessian has just started. So far, you can call
3537
Python-to-Java based on libraries released from Caucho.
3638

@@ -367,6 +369,20 @@ This shows one instance of Python running the client, connecting to the instance
367369
of Python hosting the server module. After that, moving these scripts to other
368370
machines only requires changing the hostname in the XML files.
369371

372+
New support for Pyro 4
373+
++++++++++++++++++++++
374+
375+
Pyro has recently released a beta version of its overhauled API labeled *Pyro 4*. This release of Spring Python includes support for it. The only changes you will need to make are:
376+
377+
- replace *springpython.remoting.pyro.PyroProxyFactory* with *springpython.remoting.pyro.Pyro4ProxyFactory*
378+
- replace *springpython.remoting.pyro.PyroServiceExporter* with *springpython.remoting.pyro.Pyro4ServiceExporter*
379+
- replace any URI entries of *PYROLOC:<hostname>:<port>/<service_name>* with *PYRO:<service_name>@<host>:<port>*
380+
381+
.. note::
382+
383+
Pyro 4 is unstable and still in development. For proper usage, you must install at least version 4.2+.
384+
Their API is also subject to change, and we will try to keep up until it stabilizes.
385+
370386
Remoting with Hessian
371387
---------------------
372388

@@ -943,4 +959,4 @@ A sample SSL XML-RPC client configured to use the verbose mode::
943959

944960
client = SSLClient(server_location, ca_certs, keyfile, certfile, verbose=1)
945961

946-
print client.listdir("/home")
962+
print client.listdir("/home")

src/build.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
except ImportError:
2323
from setuptools import setup
2424

25-
if sys.version_info < (2, 4):
26-
print "Spring Python only supports Python 2.4 and higher"
25+
if sys.version_info < (2, 6):
26+
print "Spring Python only supports Python 2.6 and higher"
2727
sys.exit(1)
2828

2929
setup(name='springpython',
@@ -33,7 +33,7 @@
3333
author='Greg L. Turnquist',
3434
author_email='greg.turnquist at springsource dot com',
3535
url='http://springpython.webfactional.com',
36-
platforms = ["Python >= 2.4"],
36+
platforms = ["Python >= 2.6"],
3737
license='Apache Software License (http://www.apache.org/licenses/LICENSE-2.0)',
3838
scripts=['plugins/coily'],
3939
packages=['springpython',

src/springpython/context/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def __init__(self, config = None):
3131
atexit.register(self.shutdown_hook)
3232

3333
self.logger = logging.getLogger("springpython.context.ApplicationContext")
34-
self.classnames_to_avoid = set(["PyroProxyFactory", "ProxyFactoryObject"])
34+
self.classnames_to_avoid = set(["PyroProxyFactory", "ProxyFactoryObject", "Pyro4ProxyFactory", "Pyro4FactoryObject"])
3535

3636
for object_def in self.object_defs.values():
3737
self._apply(object_def)
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
"""
2+
Copyright 2006-2008 SpringSource (http://springsource.com), All Rights Reserved
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
"""
16+
import logging
17+
import threading
18+
import time
19+
import Pyro4
20+
21+
from socket import getaddrinfo, gethostbyname
22+
23+
pyro_threads = {}
24+
serviceList = {}
25+
logger = logging.getLogger("springpython.remoting.pyro.Pyro4DaemonHolder")
26+
27+
def resolve(host, port):
28+
canonhost = gethostbyname(host)
29+
canonport = getaddrinfo(host, port)[0][4][1]
30+
31+
return canonhost, canonport
32+
33+
def register(pyro_obj, service_name, host, port):
34+
"""
35+
Register the Pyro4 object and its service name with the daemon.
36+
Also add the service to a dictionary of objects. This allows the
37+
PyroDaemonHolder to intelligently know when to start and stop the
38+
daemon thread.
39+
"""
40+
logger.debug("Registering %s at %s:%s with the Pyro4 server" % (service_name, host, port))
41+
42+
host, port = resolve(host, port)
43+
44+
serviceList[(service_name, host, port)] = pyro_obj
45+
46+
if (host, port) not in pyro_threads:
47+
48+
logger.debug("Pyro4 thread needs to be started at %s:%s" % (host, port))
49+
50+
pyro_threads[(host, port)] = _Pyro4Thread(host, port)
51+
pyro_threads[(host, port)].start()
52+
53+
if not hasattr(pyro_obj, "_pyroId"):
54+
uri = pyro_threads[(host, port)].pyro_daemon.register(pyro_obj, service_name)
55+
56+
def deregister(service_name, host, port):
57+
"""
58+
Deregister the named service by removing it from the list of
59+
managed services and also disconnect from the daemon.
60+
"""
61+
logger.debug("Deregistering %s at %s:%s with the Pyro4 server" % (service_name, host, port))
62+
63+
host, port = resolve(host, port)
64+
65+
if (host, port) in pyro_threads:
66+
pyro_threads[(host, port)].pyro_daemon.unregister(serviceList[(service_name, host, port)])
67+
del(serviceList[(service_name, host, port)])
68+
69+
def get_address((service_name, host, port)):
70+
return (host, port)
71+
72+
if len([True for x in serviceList.keys() if get_address(x) == (host, port)]) == 0:
73+
shutdown(host, port)
74+
75+
def shutdown(daemon_host, daemon_port):
76+
"""This provides a hook so an application can deliberately shutdown a
77+
daemon thread."""
78+
logger.debug("Shutting down Pyro4 daemon at %s:%s" % (daemon_host, daemon_port))
79+
80+
daemon_host, daemon_port = resolve(daemon_host, daemon_port)
81+
82+
try:
83+
pyro_threads[(daemon_host, daemon_port)].shutdown()
84+
time.sleep(1.0)
85+
del(pyro_threads[(daemon_host, daemon_port)])
86+
except Exception, e:
87+
logger.debug("Failed to shutdown %s:%s => %s" % (daemon_host, daemon_port, e))
88+
89+
class _Pyro4Thread(threading.Thread):
90+
"""
91+
This is a thread that runs the Pyro4 daemon. It is instantiated automatically
92+
from within Pyro4ServiceExporter.
93+
"""
94+
95+
def __init__(self, host, port):
96+
"""
97+
When this class is created, it also created a Pyro4 core daemon to manage.
98+
"""
99+
threading.Thread.__init__(self)
100+
self.host = host
101+
self.port = port
102+
self.logger = logging.getLogger("springpython.remoting.pyro.Pyro4DaemonHolder._Pyro4Thread")
103+
104+
self.logger.debug("Creating Pyro4 daemon")
105+
self.pyro_daemon = Pyro4.Daemon(host=host, port=port)
106+
107+
def run(self):
108+
"""
109+
When this thread starts up, it initializes the Pyro4 server and then puts the
110+
daemon into listen mode so it can process remote requests.
111+
"""
112+
self.logger.debug("Starting up Pyro4 server thread for %s:%s" % (self.host, self.port))
113+
self.pyro_daemon.requestLoop()
114+
115+
def shutdown(self):
116+
"""
117+
This is a hook in order to signal the thread that its time to shutdown
118+
the Pyro4 daemon.
119+
"""
120+
self.logger.debug("Signaling shutdown of Pyro4 server thread for %s:%s" % (self.host, self.port))
121+
class ShutdownThread(threading.Thread):
122+
def __init__(self, pyro_daemon):
123+
threading.Thread.__init__(self)
124+
self.pyro_daemon = pyro_daemon
125+
self.logger = logging.getLogger("springpython.remoting.pyro.Pyro4DaemonHolder.ShutdownThread")
126+
def run(self):
127+
self.logger.debug("Sending shutdown signal...")
128+
self.pyro_daemon.shutdown()
129+
130+
ShutdownThread(self.pyro_daemon).start()
131+
132+

src/springpython/remoting/pyro/__init__.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,89 @@ def __getattr__(self, name):
8585
self.__dict__["client_proxy"] = Pyro.core.getProxyForURI(self.service_url)
8686
return getattr(self.client_proxy, name)
8787

88+
class Pyro4ServiceExporter(InitializingObject):
89+
"""
90+
This class will expose an object using Pyro. It requires that a daemon thread
91+
be up and running in order to receive requests and allow dispatching to the exposed
92+
object.
93+
"""
94+
def __init__(self, service = None, service_name = None, service_host = "localhost", service_port = 7766):
95+
self.logger = logging.getLogger("springpython.remoting.pyro.Pyro4ServiceExporter")
96+
self.service = service
97+
self.service_name = service_name
98+
self.service_host = service_host
99+
self.service_port = service_port
100+
self._pyro_thread = None
101+
102+
def __del__(self):
103+
"""
104+
When the service exporter goes out of scope and is garbage collected, the
105+
service must be deregistered.
106+
"""
107+
from springpython.remoting.pyro import Pyro4DaemonHolder
108+
Pyro4DaemonHolder.deregister(self.service_name, self.service_host, self.service_port)
109+
110+
def __setattr__(self, name, value):
111+
"""
112+
Only the explicitly listed attributes can be assigned values. Everything else is passed through to
113+
the actual service.
114+
"""
115+
if name in ["logger", "service", "service_name", "service_host", "service_port", "_pyro_thread"]:
116+
self.__dict__[name] = value
117+
else:
118+
object.__setattr__(self, name, value)
119+
120+
def after_properties_set(self):
121+
import Pyro4
122+
from springpython.remoting.pyro import Pyro4DaemonHolder
123+
if self.service is None: raise Exception("service must NOT be None")
124+
if self.service_name is None: raise Exception("service_name must NOT be None")
125+
if self.service_host is None: raise Exception("service_host must NOT be None")
126+
if self.service_port is None: raise Exception("service_port must NOT be None")
127+
self.logger.debug("Exporting %s as a Pyro service at %s:%s" % (self.service_name, self.service_host, self.service_port))
128+
wrapping_obj = PyroWrapperObj(self.service)
129+
Pyro4DaemonHolder.register(wrapping_obj, self.service_name, self.service_host, self.service_port)
130+
131+
class PyroWrapperObj(object):
132+
def __init__(self, delegate):
133+
self.delegate = delegate
134+
135+
def __getattr__(self, name):
136+
if name in ["__pyroInvoke", "__call__", "_pyroId", "_pyroDaemon", "delegate"]:
137+
return self.__dict__[name]
138+
else:
139+
return getattr(self.delegate, name)
140+
141+
def __setattr__(self, name, value):
142+
if name in ["__pyroInvoke", "__call__", "_pyroId", "_pyroDaemon", "delegate"]:
143+
self.__dict__[name] = value
144+
else:
145+
setattr(self.delegate, name, value)
146+
147+
class Pyro4ProxyFactory(object):
148+
"""
149+
This is wrapper around a Pyro client proxy. The idea is to inject this object with a
150+
Pyro service_url, which in turn generates a Pyro client proxy. After that, any
151+
method calls or attribute accessses will be forwarded to the Pyro client proxy.
152+
"""
153+
def __init__(self):
154+
self.__dict__["client_proxy"] = None
155+
self.__dict__["service_url"] = None
156+
157+
def __setattr__(self, name, value):
158+
if name == "service_url":
159+
self.__dict__["service_url"] = value
160+
else:
161+
setattr(self.client_proxy, name, value)
162+
163+
def __getattr__(self, name):
164+
import Pyro4
165+
if name in ["service_url"]:
166+
return self.__dict__[name]
167+
elif name in ["post_process_before_initialization", "post_process_after_initialization"]:
168+
raise AttributeError, name
169+
else:
170+
if self.client_proxy is None:
171+
self.__dict__["client_proxy"] = Pyro4.Proxy(self.service_url)
172+
return getattr(self.client_proxy, name)
173+

test/springpythontest/all.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from springpythontest.databaseTransactionTestCases import PostGreSQLTransactionTestCase
99
from springpythontest.databaseTransactionTestCases import SqliteTransactionTestCase
1010
from springpythontest.remotingTestCases import PyroRemotingTestCase
11+
from springpythontest.remotingTestCases import Pyro4RemotingTestCase
1112
from springpythontest.remotingTestCases import HessianRemotingTestCase
1213
from springpythontest.securityEncodingTestCases import *
1314
from springpythontest.securityProviderTestCases import *

test/springpythontest/checkin.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@
1414
from springpythontest.util_test_cases import *
1515
from springpythontest.unicodeTestCases import *
1616
from springpythontest.remoting_xmlrpc import *
17+
from springpythontest.remotingTestCases import Pyro4RemotingTestCase

test/springpythontest/pyro4.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#from springpythontest.remotingTestCases import PyroRemotingTestCase
2+
from springpythontest.remotingTestCases import Pyro4RemotingTestCase
3+

0 commit comments

Comments
 (0)