Coupling Aspect Oriented Programming with different types of Python remoting services makes it easy to convert your local application into a distributed one. Technically, the remoting segment of Spring Python doesn't use AOP. However, it is very similar in the concept that you won't have to modify either your servers or your clients.
Distributed applications have multiple objects. These can be spread across different instances of the Python interpreter on the same machine, as well on different machines on the network. The key factor is that they need to talk to each other. The developer shouldn't have to spend a large effort coding a custom solution. Another common practice in the realm of distributed programming is that fact that programmers often develop standalone. When it comes time to distribute the application to production, the configuration may be very different. Spring Python solves this by making the link between client and server objects a step of configuration not coding.
In the context of this section of documentation, the term client refers to a client-application that is trying to access some remote service. The service is referred to as the server object. The term remote is subjective. It can either mean a different thread, a different interpretor, or the other side of the world over an Internet connection. As long as both parties agree on the configuration, they all share the same solution.
Spring Python currently supports and requires the installation of at least one of the libraries:
For starters, let's define a simple service.
class Service(object): def get_data(self, param): return "You got remote data => %s" % param
Now, we will create it locally and then call it.
service = Service() print service.get_data("Hello") "You got remote data => Hello"
Okay, imagine that you want to relocate this service to another instance of Python, or perhaps another machine on your network. To make this easy, let's utilize Inversion Of Control, and transform this service into a Spring service. First, we need to define an application context. We will create a file called applicationContext.xml.
<?xml version="1.0" encoding="UTF-8"?> <objects xmlns="http://www.springframework.org/springpython/schema/objects/1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/springpython/schema/objects/1.1 http://springpython.webfactional.com/schema/context/spring-python-context-1.1.xsd"> <object id="service" class="Service"/> </objects>
The client code is changed to this:
appContext = ApplicationContext(XMLConfig("applicationContext.xml")) service = appContext.get_object("service") print service.get_data("Hello") "You got remote data => Hello"
Not too tough, ehh? Well, guess what. That little step just decoupled the client from directly creating the service. Now we can step in and configure things for remote procedure calls without the client knowing it.
In order to reach our service remotely, we have to export it. Spring Python provides PyroServiceExporter to export your service through Pyro. Add this to your application context.
<?xml version="1.0" encoding="UTF-8"?> <objects xmlns="http://www.springframework.org/springpython/schema/objects/1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/springpython/schema/objects/1.1 http://springpython.webfactional.com/schema/context/spring-python-context-1.1.xsd"> <object id="remoteService" class="Service"/> <object id="service_exporter" class="springpython.remoting.pyro.PyroServiceExporter"> <property name="service_name" value="ServiceName"/> <property name="service" ref="remoteService"/> </object> <object id="service" class="springpython.remoting.pyro.PyroProxyFactory"> <property name="service_url" value="PYROLOC://localhost:7766/ServiceName"/> </object> </objects>
Three things have happened:
Our original service's object name has been changed to remoteService.
Another object was introduced called service_exporter. It references object remoteService, and provides a proxied interface through a Pyro URL.
We created a client called service. That is the same name our client code it looking for. It won't know the difference!
Pyro defaults to advertising the service at localhost:7766. However, you can easily override that by setting the service_host and service_port properties of the PyroServiceExporter object, either through setter or constructor injection.
<?xml version="1.0" encoding="UTF-8"?> <objects xmlns="http://www.springframework.org/springpython/schema/objects/1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/springpython/schema/objects/1.1 http://springpython.webfactional.com/schema/context/spring-python-context-1.1.xsd"> <object id="remoteService" class="Service"/> <object id="service_exporter" class="springpython.remoting.pyro.PyroServiceExporter"> <property name="service_name" value="ServiceName"/> <property name="service" ref="remoteService"/> <property name="service_host" value="127.0.0.1"/> <property name="service_port" value="7000"/> </object> <object id="service" class="springpython.remoting.pyro.PyroProxyFactory"> <property name="service_url" value="PYROLOC://127.0.0.1:7000/ServiceName"/> </object> </objects>
In this variation, your service is being hosted on port 7000 instead of the default 7766. This is also key, if you need to advertise to another IP address, to make it visible to another host.
Now when the client runs, it will fetch the PyroProxyFactory, which will use Pyro to look up the exported module, and end up calling our remote Spring service. And notice how neither our service nor the client have changed!
![]() | Python doesn't need an interface declaration for the client proxy |
---|---|
If you have used Spring Java's remoting client proxy beans, then you may be used to the idiom of specifying the interface of the client proxy. Due to Python's dynamic nature, you don't have to do this. |
We can now split up this application into two objects. Running the remote service on another server only requires us to edit the client's application context, changing the URL to get to the service. All without telling the client and server code.
No. Again, Spring Python provides you the freedom to do things using the IoC container, or programmatically.
To do the same configuration as shown above looks like this:
from springpython.remoting.pyro import PyroServiceExporter from springpython.remoting.pyro import PyroProxyFactory # Create the service remoteService = Service() # Export it via Pyro using Spring Python's utility classes service_exporter = PyroServiceExporter() service_exporter.service_name = "ServiceName" service_exporter.service = remoteService service_exporter.after_properties_set() # Get a handle on a client-side proxy that will remotely call the service. service = PyroProxyFactory() service.service_url = "PYROLOC://127.0.0.1:7000/ServiceName" # Call the service just you did in the original, simplified version. print service.get_data("Hello")
Against, you can override the hostname/port values as well
... # Export it via Pyro using Spring Python's utility classes service_exporter = PyroServiceExporter() service_exporter.service_name = "ServiceName" service_exporter.service = remoteService service_exporter.service_host = "127.0.0.1" # or perhaps the machines actual hostname service_exporter.service_port = 7000 service_exporter.after_properties_set() ...
That is effectively the same steps that the IoC container executes.
![]() | Don't forget after_properties_set! |
---|---|
Since PyroServiceExporter is an InitializingObject, you must call after_properties_set in order for it to start the Pyro thread. Normally the IoC container will do this step for you, but if you choose to create the proxy yourself, you are responsible for this step. |
This configuration sets us up to run the server and the client in two different Python VMs. All we have to do is split things into two parts.
Copy the following into server.xml:
<?xml version="1.0" encoding="UTF-8"?> <objects xmlns="http://www.springframework.org/springpython/schema/objects/1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/springpython/schema/objects/1.1 http://springpython.webfactional.com/schema/context/spring-python-context-1.1.xsd"> <object id="remoteService" class="server.Service"/> <object id="service_exporter" class="springpython.remoting.pyro.PyroServiceExporter"> <property name="service_name" value="ServiceName"/> <property name="service" ref="remoteService"/> <property name="service_host" value="127.0.0.1"/> <property name="service_port" value="7000"/> </object> </objects>
Copy the following into server.py:
import logging from springpython.config import XMLConfig from springpython.context import ApplicationContext class Service(object): def get_data(self, param): return "You got remote data => %s" % param if __name__ == "__main__": # Turn on some logging in order to see what is happening behind the scenes... logger = logging.getLogger("springpython") loggingLevel = logging.DEBUG logger.setLevel(loggingLevel) ch = logging.StreamHandler() ch.setLevel(loggingLevel) formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") ch.setFormatter(formatter) logger.addHandler(ch) appContext = ApplicationContext(XMLConfig("server.xml"))
Copy the following into client.xml:
<?xml version="1.0" encoding="UTF-8"?> <objects xmlns="http://www.springframework.org/springpython/schema/objects/1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/springpython/schema/objects/1.1 http://springpython.webfactional.com/schema/context/spring-python-context-1.1.xsd"> <object id="service" class="springpython.remoting.pyro.PyroProxyFactory"> <property name="service_url" value="PYROLOC://127.0.0.1:7000/ServiceName"/> </object> </objects>
Copy the following into client.py:
import logging from springpython.config import XMLConfig from springpython.context import ApplicationContext if __name__ == "__main__": # Turn on some logging in order to see what is happening behind the scenes... logger = logging.getLogger("springpython") loggingLevel = logging.DEBUG logger.setLevel(loggingLevel) ch = logging.StreamHandler() ch.setLevel(loggingLevel) formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") ch.setFormatter(formatter) logger.addHandler(ch) appContext = ApplicationContext(XMLConfig("client.xml")) service = appContext.get_object("service") print "CLIENT: %s" % service.get_data("Hello")
First, launch the server script, and then launch the client script, both on the same machine. They should be able to talk to each other with no problem at all, producing some log chatter like this:
$ python server.py & [1] 20854 2009-01-08 12:06:20,021 - springpython.container.ObjectContainer - DEBUG - === Scanning configuration <springpython.config.XMLConfig object at 0xb7fa276c> for object definitions === 2009-01-08 12:06:20,021 - springpython.config.XMLConfig - DEBUG - ============================================================== 2009-01-08 12:06:20,022 - springpython.config.XMLConfig - DEBUG - * Parsing server.xml 2009-01-08 12:06:20,025 - springpython.config.XMLConfig - DEBUG - ============================================================== 2009-01-08 12:06:20,025 - springpython.container.ObjectContainer - DEBUG - remoteService object definition does not exist. Adding to list of definitions. 2009-01-08 12:06:20,026 - springpython.container.ObjectContainer - DEBUG - service_exporter object definition does not exist. Adding to list of definitions. 2009-01-08 12:06:20,026 - springpython.container.ObjectContainer - DEBUG - === Done reading object definitions. === 2009-01-08 12:06:20,026 - springpython.context.ApplicationContext - DEBUG - Eagerly fetching remoteService 2009-01-08 12:06:20,026 - springpython.context.ApplicationContext - DEBUG - Did NOT find object 'remoteService' in the singleton storage. 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) 2009-01-08 12:06:20,026 - springpython.factory.ReflectiveObjectFactory - DEBUG - Creating an instance of server.Service 2009-01-08 12:06:20,027 - springpython.context.ApplicationContext - DEBUG - Stored object 'remoteService' in container's singleton storage 2009-01-08 12:06:20,027 - springpython.context.ApplicationContext - DEBUG - Eagerly fetching service_exporter 2009-01-08 12:06:20,027 - springpython.context.ApplicationContext - DEBUG - Did NOT find object 'service_exporter' in the singleton storage. 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) 2009-01-08 12:06:20,028 - springpython.factory.ReflectiveObjectFactory - DEBUG - Creating an instance of springpython.remoting.pyro.PyroServiceExporter 2009-01-08 12:06:20,028 - springpython.context.ApplicationContext - DEBUG - Stored object 'service_exporter' in container's singleton storage 2009-01-08 12:06:20,028 - springpython.remoting.pyro.PyroServiceExporter - DEBUG - Exporting ServiceName as a Pyro service at 127.0.0.1:7000 2009-01-08 12:06:20,029 - springpython.remoting.pyro.PyroDaemonHolder - DEBUG - Registering ServiceName at 127.0.0.1:7000 with the Pyro server 2009-01-08 12:06:20,029 - springpython.remoting.pyro.PyroDaemonHolder - DEBUG - Pyro thread needs to be started at 127.0.0.1:7000 2009-01-08 12:06:20,030 - springpython.remoting.pyro.PyroDaemonHolder._PyroThread - DEBUG - Starting up Pyro server thread for 127.0.0.1:7000 $ python client.py 2009-01-08 12:06:26,291 - springpython.container.ObjectContainer - DEBUG - === Scanning configuration <springpython.config.XMLConfig object at 0xb7ed45ac> for object definitions === 2009-01-08 12:06:26,292 - springpython.config.XMLConfig - DEBUG - ============================================================== 2009-01-08 12:06:26,292 - springpython.config.XMLConfig - DEBUG - * Parsing client.xml 2009-01-08 12:06:26,294 - springpython.config.XMLConfig - DEBUG - ============================================================== 2009-01-08 12:06:26,294 - springpython.container.ObjectContainer - DEBUG - service object definition does not exist. Adding to list of definitions. 2009-01-08 12:06:26,294 - springpython.container.ObjectContainer - DEBUG - === Done reading object definitions. === 2009-01-08 12:06:26,295 - springpython.context.ApplicationContext - DEBUG - Eagerly fetching service 2009-01-08 12:06:26,295 - springpython.context.ApplicationContext - DEBUG - Did NOT find object 'service' in the singleton storage. 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) 2009-01-08 12:06:26,295 - springpython.factory.ReflectiveObjectFactory - DEBUG - Creating an instance of springpython.remoting.pyro.PyroProxyFactory 2009-01-08 12:06:26,295 - springpython.context.ApplicationContext - DEBUG - Stored object 'service' in container's singleton storage CLIENT: You got remote data => Hello
This shows one instance of Python running the client, connecting to the instance of Python hosting the server module. After that, moving these scripts to other machines only requires changing the hostname in the XML files.
![]() | Caucho's python library for Hessian is incomplete |
---|---|
Due to minimal functionality provided by Caucho's Hessian library for python, there is minimal documentation to show its functionality. |
The following shows how to connect a client to a Hessian-exported service. This can theoretically be any technology. Currently, Java objects are converted into python dictionaries, meaning that the data and transferred, but there are not method calls available.
<?xml version="1.0" encoding="UTF-8"?> <objects xmlns="http://www.springframework.org/springpython/schema/objects/1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/springpython/schema/objects/1.1 http://springpython.webfactional.com/schema/context/spring-python-context-1.1.xsd"> <object id="personService" class="springpython.remoting.hessian.HessianProxyFactory"> <property name="service_url"><value>http://localhost:8080/</value></property> </object> </objects>
The Caucho library appears to only support Python being a client, and not yet as a service, so there is no HessianServiceExporter available yet.
This props you up for many options to increase availability. It is possible to run a copy of the server on multiple machines. You could then institute some type of round-robin router to go to different URLs. You could easily run ten copies of the remote service.
pool = [] for i in range(10): service_exporter = PyroServiceExporter(service_name = "ServiceName%s" % i, service = Service()) pool.append(service_exporter)
(Yeah, I know, you can probably do this in one line with a list comprehension).
Now you have ten copies of the server running, each under a distinct name.
For any client, your configuration is a slight tweak.
services = [] for i in range(10): services.append(PyroProxyFactory(service_url = "PYROLOC://localhost:7766/ServiceName%s" % i))
Now you have an array of possible services to reach, easily spread between different machines. With a little client-side utility class, we can implement a round-robin solution.
class HighAvailabilityService(object): def __init__(self, service_pool): self.service_pool = service_pool self.index = 0 def get_data(self, param): self.index = (self.index+1) % len(self.service_pool) try: return self.service_pool[self.index].get_data(param) except: del(self.service_pool[i]) return self.get_data(param) service = HighAvailabilityService(service_pool = services) service.get_data("Hello") service.get_data("World")
Notice how each call to the HighAvailabilityService class causes the internal index to increment and roll over. If a service doesn't appear to be reachable, it is deleted from the list and attempted again. A little more sophisticated error handling should be added in case there are no services available. And there needs to be a way to grow the services. But this gets us off to a good start.