X Tutup
Skip to content

Commit 1ca5817

Browse files
committed
SESPRINGPYTHONPY-88: Merged branch changes into trunk. Verified all test cases passed.
git-svn-id: https://src.springframework.org/svn/se-springpython-py/trunk/springpython@447 ce8fead1-4192-4296-8608-a705134b927f
1 parent cca96f8 commit 1ca5817

File tree

11 files changed

+504
-73
lines changed

11 files changed

+504
-73
lines changed

docs/reference/src/objects.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ service = container.get_object("MovieLister")
431431
<section id="objects-config-xmlconfig-constructors">
432432
<title>Constructors</title>
433433

434-
<para>Python funtions can have both positional and named arguments. Positional arguments get assembled
434+
<para>Python functions can have both positional and named arguments. Positional arguments get assembled
435435
into a tuple, and named arguments are assembled into a dictionary, before being passed to a function
436436
call. Spring Python takes advantage of that option when it comes to constructor calls. The following
437437
block of configuration data shows defining positional constructors.</para>

docs/reference/src/remoting.xml

Lines changed: 215 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@
3636
<section id="remoting-pyro">
3737
<title>Remoting with PYRO (Python Remote Objects)</title>
3838

39-
<section id="remoting-pyro-conversion">
40-
<title>Converting a local service to an remote one</title>
39+
<section id="remoting-pyro-decoupling">
40+
<title>Decoupling a simple service, to setup for remoting</title>
4141

4242
<para>For starters, let's define a simple service.</para>
4343

@@ -133,10 +133,54 @@ print service.get_data("Hello")
133133
</listitem>
134134
</orderedlist>
135135

136+
<section id="remoting-pyro-hostname-port">
137+
<title>Hostname/Port overrides</title>
138+
139+
<para>Pyro defaults to advertising the service at <emphasis>localhost:7766</emphasis>. However, you
140+
can easily override that by setting the <methodname>service_host</methodname> and
141+
<methodname>service_port</methodname> properties of the <classname>PyroServiceExporter</classname>
142+
object, either through setter or
143+
<link linkend="objects-config-xmlconfig-constructors">constructor injection</link>.</para>
144+
145+
<programlisting><![CDATA[
146+
<?xml version="1.0" encoding="UTF-8"?>^M
147+
<objects xmlns="http://www.springframework.org/springpython/schema/objects"^M
148+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"^M
149+
xsi:schemaLocation="http://www.springframework.org/springpython/schema/objects^M
150+
http://springpython.webfactional.com/schema/context/spring-python-context-1.0.xsd">^M
151+
152+
<object id="remoteService" class="Service"/>
153+
154+
<object id="service_exporter" class="springpython.remoting.pyro.PyroServiceExporter">
155+
<property name="service_name" value="ServiceName"/>
156+
<property name="service" ref="remoteService"/>
157+
<property name="service_host" value="127.0.0.1"/>
158+
<property name="service_port" value="7000"/>
159+
</object>
160+
161+
<object id="service" class="springpython.remoting.pyro.PyroProxyFactory">
162+
<property name="service_url" value="PYROLOC://127.0.0.1:7000/ServiceName"/>
163+
</object>
164+
165+
</objects>
166+
]]></programlisting>
167+
168+
<para>In this variation, your service is being hosted on port 7000 instead of the default 7766. This is
169+
also key, if you need to advertise to another IP address, to make it visible to another host.</para>
170+
171+
</section>
172+
136173
<para>Now when the client runs, it will fetch the <classname>PyroProxyFactory</classname>, which will use Pyro
137174
to look up the exported module, and end up calling our remote Spring service. And notice how neither our service
138175
nor the client have changed!</para>
139176

177+
<note>
178+
<title>Python doesn't need an interface declaration for the client proxy</title>
179+
180+
<para>If you have used Spring Java's remoting client proxy beans, then you may be used to the idiom
181+
of specifying the interface of the client proxy. Due to Python's dynamic nature, you don't have to do this.</para>
182+
</note>
183+
140184
<para>We can now split up this application into two objects. Running the remote service on another server only
141185
requires us to edit the client's application context, changing the URL to get to the service. All without telling
142186
the client and server code.</para>
@@ -161,6 +205,7 @@ remoteService = Service()
161205
service_exporter = PyroServiceExporter()
162206
service_exporter.service_name = "ServiceName"
163207
service_exporter.service = remoteService
208+
service_exporter.after_properties_set()
164209
165210
# Get a handle on a client-side proxy that will remotely call the service.
166211
service = PyroProxyFactory()
@@ -170,11 +215,173 @@ service.service_url = "PYROLOC://localhost:7766/ServiceName"
170215
print service.get_data("Hello")
171216
]]></programlisting>
172217

173-
<para>That is effectively the same steps that the IoC container executes. And it is just as easy to split this
174-
up between two machines. Chop the script and run the <classname>PyroServiceExporter</classname> on one machine,
175-
and run the <classname>PyroProxyFactory</classname> on the other. Then all you have to do is edit the
176-
<emphasis>service_url</emphasis>.</para>
177-
218+
<para>Against, you can override the hostname/port values as well</para>
219+
220+
<programlisting><![CDATA[
221+
...
222+
# Export it via Pyro using Spring Python's utility classes
223+
service_exporter = PyroServiceExporter()
224+
service_exporter.service_name = "ServiceName"
225+
service_exporter.service = remoteService
226+
service_exporter.service_host = "127.0.0.1" # or perhaps the machines actual hostname
227+
service_exporter.service_port = 7000
228+
service_exporter.after_properties_set()
229+
...
230+
]]></programlisting>
231+
232+
<para>That is effectively the same steps that the IoC container executes.</para>
233+
234+
<note>
235+
<title>Don't forget after_properties_set!</title>
236+
237+
<para>Since <classname>PyroServiceExporter</classname> is an <classname>InitializingObject</classname>,
238+
you must call <methodname>after_properties_set</methodname> in order for it to start the Pyro thread.
239+
Normally the IoC container will do this step for you, but if you choose to create the proxy yourself,
240+
you are responsible for this step.</para>
241+
</note>
242+
243+
</section>
244+
245+
<section id="remoting-pyro-client-server">
246+
<title>Splitting up the client and the server</title>
247+
248+
<para>This configuration sets us up to run the server and the client in two different Python VMs. All we have
249+
to do is split things into two parts.</para>
250+
251+
<para>Copy the following into <filename>server.xml</filename>:</para>
252+
253+
<programlisting><![CDATA[
254+
<?xml version="1.0" encoding="UTF-8"?>
255+
<objects xmlns="http://www.springframework.org/springpython/schema/objects"
256+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
257+
xsi:schemaLocation="http://www.springframework.org/springpython/schema/objects
258+
http://springpython.webfactional.com/schema/context/spring-python-context-1.0.xsd">
259+
260+
<object id="remoteService" class="server.Service"/>
261+
262+
<object id="service_exporter" class="springpython.remoting.pyro.PyroServiceExporter">
263+
<property name="service_name" value="ServiceName"/>
264+
<property name="service" ref="remoteService"/>
265+
<property name="service_host" value="127.0.0.1"/>
266+
<property name="service_port" value="7000"/>
267+
</object>
268+
269+
</objects>
270+
]]></programlisting>
271+
272+
<para>Copy the following into <filename>server.py</filename>:</para>
273+
274+
<programlisting><![CDATA[
275+
import logging
276+
from springpython.config import XMLConfig
277+
from springpython.context import ApplicationContext
278+
279+
class Service(object):
280+
def get_data(self, param):
281+
return "You got remote data => %s" % param
282+
283+
if __name__ == "__main__":
284+
# Turn on some logging in order to see what is happening behind the scenes...
285+
logger = logging.getLogger("springpython")
286+
loggingLevel = logging.DEBUG
287+
logger.setLevel(loggingLevel)
288+
ch = logging.StreamHandler()
289+
ch.setLevel(loggingLevel)
290+
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
291+
ch.setFormatter(formatter)
292+
logger.addHandler(ch)
293+
294+
appContext = ApplicationContext(XMLConfig("server.xml"))
295+
]]></programlisting>
296+
297+
<para>Copy the following into <filename>client.xml</filename>:</para>
298+
299+
<programlisting><![CDATA[
300+
<?xml version="1.0" encoding="UTF-8"?>
301+
<objects xmlns="http://www.springframework.org/springpython/schema/objects"
302+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
303+
xsi:schemaLocation="http://www.springframework.org/springpython/schema/objects
304+
http://springpython.webfactional.com/schema/context/spring-python-context-1.0.xsd">
305+
306+
<object id="service" class="springpython.remoting.pyro.PyroProxyFactory">
307+
<property name="service_url" value="PYROLOC://127.0.0.1:7000/ServiceName"/>
308+
</object>
309+
310+
</objects>
311+
]]></programlisting>
312+
313+
<para>Copy the following into <filename>client.py</filename>:</para>
314+
<programlisting><![CDATA[
315+
import logging
316+
from springpython.config import XMLConfig
317+
from springpython.context import ApplicationContext
318+
319+
if __name__ == "__main__":
320+
# Turn on some logging in order to see what is happening behind the scenes...
321+
logger = logging.getLogger("springpython")
322+
loggingLevel = logging.DEBUG
323+
logger.setLevel(loggingLevel)
324+
ch = logging.StreamHandler()
325+
ch.setLevel(loggingLevel)
326+
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
327+
ch.setFormatter(formatter)
328+
logger.addHandler(ch)
329+
330+
appContext = ApplicationContext(XMLConfig("client.xml"))
331+
service = appContext.get_object("service")
332+
print "CLIENT: %s" % service.get_data("Hello")
333+
]]></programlisting>
334+
335+
<para>First, launch the server script, and then launch the client script, both on the same machine. They should
336+
be able to talk to each other with no problem at all, producing some log chatter like this:</para>
337+
338+
<programlisting><![CDATA[
339+
$ python server.py &
340+
[1] 20854
341+
342+
2009-01-08 12:06:20,021 - springpython.container.ObjectContainer - DEBUG - === Scanning configuration <springpython.config.XMLConfig object at 0xb7fa276c> for object definitions ===
343+
2009-01-08 12:06:20,021 - springpython.config.XMLConfig - DEBUG - ==============================================================
344+
2009-01-08 12:06:20,022 - springpython.config.XMLConfig - DEBUG - * Parsing server.xml
345+
2009-01-08 12:06:20,025 - springpython.config.XMLConfig - DEBUG - ==============================================================
346+
2009-01-08 12:06:20,025 - springpython.container.ObjectContainer - DEBUG - remoteService object definition does not exist. Adding to list of definitions.
347+
2009-01-08 12:06:20,026 - springpython.container.ObjectContainer - DEBUG - service_exporter object definition does not exist. Adding to list of definitions.
348+
2009-01-08 12:06:20,026 - springpython.container.ObjectContainer - DEBUG - === Done reading object definitions. ===
349+
2009-01-08 12:06:20,026 - springpython.context.ApplicationContext - DEBUG - Eagerly fetching remoteService
350+
2009-01-08 12:06:20,026 - springpython.context.ApplicationContext - DEBUG - Did NOT find object 'remoteService' in the singleton storage.
351+
2009-01-08 12:06:20,026 - springpython.context.ApplicationContext - DEBUG - Creating an instance of id=remoteService props=[] scope=scope.SINGLETON factory=ReflectiveObjectFactory(server.Service)
352+
2009-01-08 12:06:20,026 - springpython.factory.ReflectiveObjectFactory - DEBUG - Creating an instance of server.Service
353+
2009-01-08 12:06:20,027 - springpython.context.ApplicationContext - DEBUG - Stored object 'remoteService' in container's singleton storage
354+
2009-01-08 12:06:20,027 - springpython.context.ApplicationContext - DEBUG - Eagerly fetching service_exporter
355+
2009-01-08 12:06:20,027 - springpython.context.ApplicationContext - DEBUG - Did NOT find object 'service_exporter' in the singleton storage.
356+
2009-01-08 12:06:20,027 - springpython.context.ApplicationContext - DEBUG - Creating an instance of id=service_exporter props=[<springpython.config.ValueDef object at 0xb7a4664c>, <springpython.config.ReferenceDef object at 0xb7a468ac>, <springpython.config.ValueDef object at 0xb7a4692c>, <springpython.config.ValueDef object at 0xb7a46d2c>] scope=scope.SINGLETON factory=ReflectiveObjectFactory(springpython.remoting.pyro.PyroServiceExporter)
357+
2009-01-08 12:06:20,028 - springpython.factory.ReflectiveObjectFactory - DEBUG - Creating an instance of springpython.remoting.pyro.PyroServiceExporter
358+
2009-01-08 12:06:20,028 - springpython.context.ApplicationContext - DEBUG - Stored object 'service_exporter' in container's singleton storage
359+
2009-01-08 12:06:20,028 - springpython.remoting.pyro.PyroServiceExporter - DEBUG - Exporting ServiceName as a Pyro service at 127.0.0.1:7000
360+
2009-01-08 12:06:20,029 - springpython.remoting.pyro.PyroDaemonHolder - DEBUG - Registering ServiceName at 127.0.0.1:7000 with the Pyro server
361+
2009-01-08 12:06:20,029 - springpython.remoting.pyro.PyroDaemonHolder - DEBUG - Pyro thread needs to be started at 127.0.0.1:7000
362+
2009-01-08 12:06:20,030 - springpython.remoting.pyro.PyroDaemonHolder._PyroThread - DEBUG - Starting up Pyro server thread for 127.0.0.1:7000
363+
364+
$ python client.py
365+
366+
2009-01-08 12:06:26,291 - springpython.container.ObjectContainer - DEBUG - === Scanning configuration <springpython.config.XMLConfig object at 0xb7ed45ac> for object definitions ===
367+
2009-01-08 12:06:26,292 - springpython.config.XMLConfig - DEBUG - ==============================================================
368+
2009-01-08 12:06:26,292 - springpython.config.XMLConfig - DEBUG - * Parsing client.xml
369+
2009-01-08 12:06:26,294 - springpython.config.XMLConfig - DEBUG - ==============================================================
370+
2009-01-08 12:06:26,294 - springpython.container.ObjectContainer - DEBUG - service object definition does not exist. Adding to list of definitions.
371+
2009-01-08 12:06:26,294 - springpython.container.ObjectContainer - DEBUG - === Done reading object definitions. ===
372+
2009-01-08 12:06:26,295 - springpython.context.ApplicationContext - DEBUG - Eagerly fetching service
373+
2009-01-08 12:06:26,295 - springpython.context.ApplicationContext - DEBUG - Did NOT find object 'service' in the singleton storage.
374+
2009-01-08 12:06:26,295 - springpython.context.ApplicationContext - DEBUG - Creating an instance of id=service props=[<springpython.config.ValueDef object at 0xb797948c>] scope=scope.SINGLETON factory=ReflectiveObjectFactory(springpython.remoting.pyro.PyroProxyFactory)
375+
2009-01-08 12:06:26,295 - springpython.factory.ReflectiveObjectFactory - DEBUG - Creating an instance of springpython.remoting.pyro.PyroProxyFactory
376+
2009-01-08 12:06:26,295 - springpython.context.ApplicationContext - DEBUG - Stored object 'service' in container's singleton storage
377+
378+
CLIENT: You got remote data => Hello
379+
]]></programlisting>
380+
381+
<para>This shows one instance of Python running the client, connecting to the instance of Python hosting the
382+
server module. After that, moving these scripts to other machines only requires changing the hostname
383+
in the XML files.</para>
384+
178385
</section>
179386

180387

@@ -239,5 +446,5 @@ service.get_data("World")
239446
be a way to grow the services. But this gets us off to a good start.</para>
240447

241448
</section>
242-
449+
243450
</chapter>

src/springpython/container/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class ObjectContainer(object):
3333
can refer to another object in another source OF ANY FORMAT as a property.
3434
"""
3535
def __init__(self, config = None):
36-
self.logger = logging.getLogger("springpython.context.ObjectContainer")
36+
self.logger = logging.getLogger("springpython.container.ObjectContainer")
3737

3838
if config is None:
3939
self.configs = []

src/springpython/context/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ def __init__(self, config = None):
4343

4444
def _apply(self, obj):
4545
if len([True for type_to_avoid in self.types_to_avoid if isinstance(obj, type_to_avoid)]) == 0:
46+
if hasattr(obj, "after_properties_set"):
47+
obj.after_properties_set()
4648
if hasattr(obj, "post_process_after_initialization"):
4749
obj.post_process_after_initialization(self)
4850
if hasattr(obj, "set_app_context"):

src/springpython/factory/__init__.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
"""
1616

1717
import logging
18-
import sys
19-
18+
import sys
19+
2020
class ObjectFactory(object):
2121
def create_object(self, constr, named_constr):
2222
raise NotImplementedError()
@@ -60,3 +60,8 @@ def create_object(self, constr, named_constr):
6060

6161
def __str__(self):
6262
return "PythonObjectFactory(%s)" % self.method
63+
64+
class InitializingObject(object):
65+
"""This allows definition of a method which is invoked by the container after an object has had all properties set."""
66+
def after_properties_set(self):
67+
pass

0 commit comments

Comments
 (0)
X Tutup