In the previous section, I made the point that Spring Python doesn't require us to use XML. However, there is support for XML along with other formats we haven't looked at yet. Other formats are provided to make it easier for Spring Java developers to migrate to Python using what they already know.
Spring Python has two file parsers that read XML files containing object definitions. SpringJavaConfig
reads Spring Java XML configuration files, and XMLConfig
reads a format similar to that, but uniquely defined to offer Python-oriented options, such as support for dictionaries, tuples, sets, and frozen sets.
Let's explore the steps taken if our wiki engine software had originally been written in Java, and we are exploring the option to migrate to Python. To make this as seamless as possible, we will run the Python bits in Jython.
Jython is a Python compiler written in Java and designed to run on the JVM. Jython scripts can access both Python and Java-based libraries. However, Jython can not access Python extensions coded in C. Spring Python 1.1 runs on Jython 2.5.1. Information about download, installation, and other documentation of Jython can be found at http://jython.org.
public interface DataAccess { public int hits(String pageName); public int edits(String pageName); }
public class MySqlDataAccess implements DataAccess { private JdbcTemplate jt; public MySqlDataAccess(DataSource ds) { this.jt = new JdbcTemplate(ds); } public int hits(String pageName) { return jt.queryForInt( "select HITS from METRICS " + "where PAGE = ?", pageName); } public int edits(String pageName) { return jt.queryForInt( "select count(*) from VERSIONS " + "where PAGE = ?", pageName); } }
This Java code may appear contrived, considering MySqlDataAccess
is supposed to be dependent on MySQL. It is for demonstration purposes that this over simplified version was created.
WikiService
.public class WikiService { private DataAccess dataAccess; public DataAccess getDataAccess() { return this.dataAccess; } public void setDataAccess(DataAccess dataAccess) { this.dataAccess = dataAccess; } public double[] statistics(String pageName) { double hits = dataAccess.hits(pageName); double ratio = hits / dataAccess.edits(pageName); return new double[]{hits, ratio}; } }
javaBeans.xml
.<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <bean id="dataAccess" class="MySqlDataAccess"/> <bean id="wikiService" class="WikiService"> <property name="dataAccess" ref="dataAccess"/> </bean> </beans>
Note that this XML format is defined and managed by Spring Java, not Spring Python.
if __name__ == "__main__": from springpython.context import ApplicationContext from springpython.config import SpringJavaConfig ctx = ApplicationContext(SpringJavaConfig("javaBeans.xml")) service = ctx.get_object("wikiService") service.calculateWikiStats()
Thanks to Jython's ability to transparently create both Python and Java objects, our Spring Python container can parse the object definitions in javaBeans.xml
.
MySqlDataAccess
to Python. javaBeans.xml
to Spring Python's XML format and save it in production.xml
.<?xml version="1.0" encoding="UTF-8"?> <objects xmlns="http://www.springframework.org/springpython/schema/objects" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/springpython/schema/objects http://springpython.webfactional.com/schema/context/spring-python-context-1.0.xsd"> <object id="data_access" class="Py.MySqlDataAccess"/> <object id="wiki_service" class="WikiService"> <property name="dataAccess" ref="data_access"/> </object> </objects>
This is very similar to the Spring Java format, and that's the point. By changing the header and replacing 'bean' with 'object', we can now use XMLConfig
.
if __name__ == "__main__": from springpython.context import ApplicationContext from springpython.config import XMLConfig ctx = ApplicationContext(XMLConfig("production.xml")) service = ctx.get_object("wiki_service") service.calculateWikiStats()
We now have access to more options such as the following:
<property name="some_set"> <set> <value>Hello, world!</value> <ref object="SingletonString"/> <value>Spring Python</value> </set> </property> <property name="some_frozen_set"> <frozenset> <value>Hello, world!</value> <ref object="SingletonString"/> <value>Spring Python</value> </frozenset> </property> <property name="some_tuple"> <tuple> <value>Hello, world!</value> <ref object="SingletonString"/> <value>Spring Python</value> </tuple> </property>
MySqlDataAccess
with StubDataAccess
using an alternative configuration. To avoid changing production.xml
, we create an alternate configuration file test.xml
.<?xml version="1.0" encoding="UTF-8"?> <objects xmlns="http://www.springframework.org/springpython/schema/objects" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/springpython/schema/objects http://springpython.webfactional.com/schema/context/spring-python-context-1.0.xsd"> <object id="data_access" class="Py.StubDataAccess"/> </objects>
ApplicationContext
with a list of configurations.import unittest from springpython.context import ApplicationContext from springpython.config import XMLConfig class WikiServiceTestCase(unittest.TestCase): def testHittingWikiService(self): ctx = ApplicationContext([XMLConfig("production.xml"), XMLConfig("test.xml")]) service = ctx.get_object("wiki_service") results = service.statistics("stub page") self.assertEquals(10.0, results[0]) self.assertEquals(2.0, results[1])
The order of fi lenames is important. First, a list of defi nitions is read from
production.xml
. Next, more defi nitions are read from test.xml
. Any new
defi nitions with the same name as previous ones will overwrite the previous
defi nitions. In our case, we overwrite data_access
with a reference to our
stub object.
production.xml
to WikiProductionAppConfig
.from springpython.config import PythonConfig, Object from Py import MySqlDataAccess import WikiService class WikiProductionAppConfig(PythonConfig): def __init__(self): PythonConfig.__init__(self) @Object def data_access(self): return MySqlDataAccess() @Object def wiki_service(self): results = WikiService() results.dataAccess = self.data_access() return results
test.xml
.import unittest from springpython.context import ApplicationContext from springpython.config import XMLConfig class WikiServiceTestCase(unittest.TestCase): def testHittingWikiService(self): ctx = ApplicationContext([WikiProductionAppConfig(), XMLConfig("test.xml")]) service = ctx.get_object("wiki_service") results = service.statistics("stub page") self.assertEquals(10.0, results[0]) self.assertEquals(2.0, results[1])
We simply replace XMLConfig("production.xml")
with WikiProductionAppConfig(). ApplicationContext
comfortably accepts any combination of definitions stored in different formats. Spring Python's flexible container gives us a fine grained control over how we store all our definitions.
We could continue and easily replace test.xml
with something like WikiTestAppConfig
written in pure Python.
This demonstrates how easy it is to convert things a piece at a time. We could have stopped earlier based on what we wanted to keep in Java and what we wanted to move to Python. If we move the rest of the Java parts to Python solutions, then we may opt to use either Python or Jython. The point is that XML support makes it easy to move from Java to Python with minimal impact.