.TH "SPRINGPYTHON" "1" "May 21, 2012" "1.2" "Spring Python" .SH NAME springpython \- Spring Python Documentation . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .\" Man page generated from reStructeredText. . .sp Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically. .sp Contents: .SH OVERVIEW .sp Spring Python intends to take the concepts that were developed, tested, and proven with the Spring Framework, and carry them over to the language of Python. If anyone has developed a solution using multiple technologies including Java, C#/.NET, and Python, they will realize that certain issues exist in all these platforms. .sp This is not a direct port of existing source code, but rather, a port of proven solutions, while still remaining faithful to the style, idioms, and overall user community of Python. .IP Note . \fISpring Python is an offshoot of the Java\-based Spring Framework and Spring Security Framework, targeted for Python. Spring provides many useful features, and I wanted those same features available when working with Python.\fP .sp \-\-Greg Turnquist, Spring Python project lead .RE .SS Key Features .sp The following features have been implemented: .INDENT 0.0 .IP \(bu 2 . \fBInversion Of Control\fP \- The idea is to decouple two classes at the interface level. This lets you build many reusable parts in your software, and your whole application becomes more pluggable. You can use PythonConfig, YamlConfig or XMLConfig to plugin your object definition to an ApplicationContext. .IP \(bu 2 . \fBAspect Oriented Programming\fP \- Spring Python provides great ways to wrap advice around objects. It is utilized for remoting. Another use is for debug tracers and performance tracing. .IP \(bu 2 . \fBDatabaseTemplate\fP \- Reading from the database requires a monotonous cycle of opening cursors, reading rows, and closing cursors, along with exception handlers. With this template class, all you need is the SQL query and row\-handling function. Spring Python does the rest. .IP \(bu 2 . \fBDatabase Transactions\fP \- Wrapping multiple database calls with transactions can make your code hard to read. This module provides multiple ways to define transactions without making things complicated. .IP \(bu 2 . \fBSecurity\fP \- Plugin security interceptors to lock down access to your methods, utilizing both authentication and domain authorization. .IP \(bu 2 . \fBRemoting\fP \- It is easy to convert your local application into a distributed one. If you have already built your client and server pieces using the IoC container, then going from local to distributed is just a configuration change. .IP \(bu 2 . \fBJMS Messaging\fP \- Connect to Java or Python applications using queueing middleware. Spring Python can act as a standalone client of a JMS provider with no Java EE infrastructure needed on Python side. .IP \(bu 2 . \fBPlug\-ins/command\-line tool\fP \- Use the plugin system designed to help you rapidly develop applications. .IP \(bu 2 . \fBSamples\fP \- to help demonstrate various features of Spring Python, some sample applications have been created: .INDENT 2.0 .IP \(bu 2 . PetClinic \- Everybody\(aqs favorite Spring sample application has been rebuilt from the ground up using various web containers including: CherryPy. Go check it out for an example of how to use this framework. .IP \(bu 2 . Spring Wiki \- Wikis are powerful ways to store and manage content, so we created a simple one as a demo! .IP \(bu 2 . Spring Bot \- Use Spring Python to build a tiny bot to manage the IRC channel of your open source project. .UNINDENT .UNINDENT .SS What Spring Python is NOT .sp Spring Python is NOT another web framework. I think there are plenty that are fine to use, like Django, TurboGears, Zope, CherryPy, Quixote, and more. Spring Python is meant to provide utilities to support any python application, including a web\-based one. .sp So far, the demos have been based on CherryPy, but the idea is that these features should work with any python web framework. The Spring Python team is striving to make things reusable with any python\-based web framework. There is always the goal of expanding the samples into other frameworks, whether they are web\-based, \fI\%RIA\fP, or thick\-client. .SS Support .SS Spring Python 1.1 book [image] .sp You can order either a printed or electronic version of the \fI\%Spring Python 1.1 book\fP written by project creator Greg Turnquist. This contains a lot of code samples, diagrams, and descriptions of the concepts behind Spring Python. This book was completed right as Spring Python 1.1 was released, and 1.2.x was started, so it contains up\-to\-date information on the project. .SS Forums and Email .INDENT 0.0 .IP \(bu 2 . You can read the messages on \fI\%Spring Python's forums\fP at the official Spring forum site. .IP \(bu 2 . If you are interested, you can sign up for the \fI\%springpython-developer mailing list\fP. .IP \(bu 2 . You can read the \fI\%current archives of the spring-users mailing list\fP. .IP \(bu 2 . You can also read the \fI\%old archives of the retired spring-developer mailing list\fP. .IP \(bu 2 . If you want to join this project, see \fIhow to become a team member\fP .UNINDENT .SS IRC .sp Join us on the #springpython IRC channel at \fI\%Freenode\fP. .SS Downloads / Source Code .sp If you want a release, check out \fI\%Spring's download site for Spring Python\fP. .sp Spring Python has migrated to \fI\%git\fP, the distributed version control system. If you want the latest source code type: .sp .nf .ft C git clone git://git.springsource.org/spring\-python/spring\-python.git .ft P .fi .sp That will create a new spring\-python folder containing the entire repository. This includes both the source code and the demo applications (PetClinic and SpringWiki). You will be on the master branch. From there, you can switch to various branches the team is working on, or create your own branch to experiment and develop patches. .sp You can browse the code at \fI\%https://fisheye.springsource.org/browse/se-springpython-py\fP. .SS Installation .sp This section is focused on helping you set up Spring Python. .INDENT 0.0 .IP 1. 3 . Go to \fI\%Spring's download site for Spring Python\fP. .IP 2. 3 . Click on \fBSpring Python\fP. .IP 3. 3 . Download \fBspringpython\-[release].tar.gz\fP to get the core library. .IP 4. 3 . Unpack the tarball, and go to the directory containing setup.py. (NOTE: This has been tested on Mac OSX 10.5/10.6, and Ubuntu Linux 9.04+) .IP 5. 3 . Type \fBpython setup.py install\fP to install Spring Python. (NOTE: You may need administrative power to do this!) .UNINDENT .sp This installs the core library of Spring Python. Version 1.2 supports Python 2.6+ (but not Python 3). .sp To use other features like CherryPy and Pyro, you need to execute some of the following extra steps. .INDENT 0.0 .IP 1. 3 . Go to setuptools and follow the steps for your platform to install setuptools. .IP 2. 3 . Install CherryPy for web app development by typing \fBeasy_install cherrypy\fP. This should install CherryPy 3.2. .IP 3. 3 . Install Pyro for RPC functionality by typing \fBeasy_install pyro\fP. This should install Pyro (not Pyro 3). .IP 4. 3 . Install PyYAML if you want the YAML\-based IoC configuratin options by typing \fBeasy_install pyyaml\fP. .IP 5. 3 . Install MySQLdb by typing \fBeasy_install mysql\-python\fP. .IP 6. 3 . Install PySqlite by typing \fBeasy_install pysqlite\fP. .IP 7. 3 . Install ElementTree if you are using a version of Python that doesn\(aqt incluee it by default by typing \fBeasy_install elementtree\fP. .UNINDENT .sp You may not need all of these libraries. Determine what you need and then install it. .sp Another feature which is very useful, is to install everything (including Spring Python) inside a virtual environment. You can read A Primer on virtualenv to get an introduction to virtualenv. After installing easy_install, you simply type \fBeasy_install virtualenv\fP to install this tool. From there, you can create an virtual installation by typing \fBvirtualenv \-\-no\-site\-packages name_of_your_folder\fP. On UNIX systems, to activate it, type \fB. name_of_your_folder/bin/activate\fP. The virtualenv will manipulate your path settings and point you to a different location of the python executable, a different easy_install and pip, and a different PYTHON_PATH. Essentially, python setup.py foobar will install into this folder you just created instead of the system version. For isolation, this is a highly recommended way to install everything. .IP Note . \fBUsing easy_install and pip vs. OS package installation tools\fP .sp A lot of operating systems, like Ubuntu Linux, offer the same python libraries through tools like RPM, APT, etc. Due to personal experience, it is recommended to NOT use these when it comes to using Spring Python. Using OS package management can result in library upgrades when performing system upgrades. Using virtualenv is the best way to control the version of library installed and also shield your system from system upgrades. .RE .SS Licensing .sp Spring Python is released under the \fI\%Apache Server License 2.0\fP and the copyright is held by SpringSource. .SS Spring Python\(aqs team .sp Spring Python\(aqs official team (those with committer rights): .INDENT 0.0 .IP \(bu 2 . Project Lead: Greg L. Turnquist .IP \(bu 2 . Project Contributor: Dariusz Suchojad .IP \(bu 2 . Project Contributor: Sven Wilhelm .UNINDENT .sp Many others have also contributed through reporting issues, raising questions, and even sending patches. .SS How to become a team member .sp We like hearing about new people interesting in joining the project. We are also excited in hearing from people interested in working on a particular jira feature. .sp The way we do things around here, we like to work through a few patches before granting you any committer rights. You can checkout a copy of the code anonymously, and then work on your patch. Email your patch to one of the official team members, and we will inspect things. From there we will consider committing your patch, or send you feedback. .sp Before sending us a patch, we ask you to sign the \fI\%SpringSource Individual Contributor Agreement\fP. .sp After a few patches, if things are looking good, we may evaluate giving you committer rights. .sp Spring Python is a \fI\%TDD-based\fP project, meaning if you are working on code, be sure to write an automated test case and write the test case FIRST. For insight into that, take a trip into the code repository\(aqs test section to see how current things are run. Your patch can get sold off and committed much faster if you include automated test cases and a pasted sample of your test case running successfully along with the rest of the baseline test suite. .sp You don\(aqt have to become a team member to contribute to this project, but if you want to contribute code, then we ask that you follow the details of this process, because this project is focused on high quality code, and we want to hold everyone to the same standard. Getting started with contributing.INDENT 0.0 .IP 1. 3 . First of all, I suggest you sign up on our \fI\%springpython-developer\fP mailing list. That way, you\(aqll get notified about big items as well be on the inside for important developments that may or may not get published to the web site. \fINOTE: Use the springsource list, NOT the sourceforge one.\fP .IP 2. 3 . Second, I suggest you register for a \fI\%jira account\fP, so you can leave comments, etc. on the ticket. I think that works (I don\(aqt manage jira, so if it doesn\(aqt let me know, and we will work from there) NOTE: I like notes and comments tracking what you have done, or what you think needs to be done. It gives us input in case someone else eventually has to complete the ticket. That would also be the place where you can append new files or patches to existing code. .IP 3. 3 . Third, register at the \fI\%SpringSource community forum\fP, and if you want to kick ideas around or float a concept, feel free to start a thread in our Spring Python forum. .IP 4. 3 . Finally, we really like to have supporting documentation as well as code. That helps other people who aren\(aqt as up\-to\-speed on your piece of the system. Go ahead and start your patch, but don\(aqt forget to look into the docs folder and update or add to relevant documentation. Our documentation is part of the source code, so you can submit doc mods as patches also. Include information such as dependencies, design notes, and whatever else you think would be valuable. .UNINDENT .sp With all that said, happy coding! .SS Deprecated Code .sp To keep things up\-to\-date, we need to deprecate code from time to time. Python has built in functionality to put warnings into certain sections of code, so that if you import a deprecated module, you will be properly warned. With each major release (1.0, 2.0, 3.0, etc.), the Spring Python team has the option to remove any and all deprecated code. .SH WHAT'S NEW IN SPRING PYTHON 1.2 .sp Spring Python 1.2 has some new features. .SS New Features .sp Spring Python 1.2 has added support for: .INDENT 0.0 .IP \(bu 2 . \fINew support for Pyro 4\fP \- Since the beginning, Spring Python has provided seamless remoting by integration with Pyro. We now have support for Pyro 4. .IP \(bu 2 . \fISecure XML\-RPC\fP \- Python provides out\-of\-the\-box XML\-RPC capabilities. This extra module makes it easy to export existing services using this capability while also enabling you to encrypt the tunnel. .UNINDENT .sp There have also been bug fixes. For the most part there is little impact to the core API of Spring Python. This means that the material in the book Spring Python 1.1 is valid. It just doesn\(aqt document the new features listed above. .SH THE IOC CONTAINER .sp Inversion Of Control (IoC), also known as \fI\%dependency injection\fP is more of an architectural concept than a simple coding pattern. .sp The idea is to decouple classes that depend on each other from inheriting other dependencies, and instead link them only at the interfacing level. This requires some sort of 3rd party software module to instantiate the concrete objects and "inject" them into the class that needs to call them. .sp In Spring, there are certain classes whose instances form the backbone of your application and that are managed by the Spring IoC container. While Spring Java calls them beans, Spring Python and Spring for .NET call them \fIobjects\fP. An object is simply a class instance that was instantiated, assembled and otherwise managed by a Spring IoC container instead of directly by your code; other than that, there is nothing special about a object. It is in all other respects one of probably many objects in your application. These objects, and the dependencies between them, are reflected in the configuration meta\-data used by a container. .IP Note . In early 2004, Martin Fowler asked the readers of his site: when talking about Inversion of Control: \fI"the question is, what aspect of control are [they] inverting?"\fP. Fowler then suggested renaming the principle (or at least giving it a more self\-explanatory name), and started to use the term \fIDependency Injection\fP. His article then continued to explain the ideas underpinning the Inversion of Control (IoC) and Dependency Injection (DI) principle. .sp If you need a decent insight into IoC and DI, please do refer to said article : \fI\%http://martinfowler.com/articles/injection.html\fP. .RE .sp The following diagram demonstrates a key Spring concept: building useful services on top of simple objects, configured through a container\(aqs set of blueprints, provides powerful services that are easier to maintain. [image] .SS External dependencies .sp XML\-based IoC configuration formats use ElementTree which is a part of Python\(aqs stantard library in Python 2.5 and newer. If you use Python 2.4 you can download ElementTree from here. YamlConfig requires installation of PyYAML which may be found \fI\%here\fP. No additional dependencies needs be installed if you choose PythonConfig. .SS Container .sp A container is an object you create in your code that receives the definitions for objects from various sources. Your code goes to the container to request the object, and the container then does everything it needs to create an instance of that. .sp Depending on the scope of the object definition, the container may create a new instance right there on the spot, or it may fetch a reference to a singleton instance that was created previously. If this is the first time a singleton\-scoped object is requested, is created, stored, and then returned to you. For a prototype\-scoped object, EVERY TIME you request an object, a new instance is created and NOT stored in the singleton cache. .sp Containers depend on various object factories to do the heavy lifting of construction, and then itself will set any additional properties. There is also the possibility of additional behavior outside of object creation, which can be defined by extending the \fIObjectContainer\fP class. .sp The reason it is called a container is the idea that you are going to a central place to get your top level object. While it is also possible to get all your other objects, the core concept of \fI\%dependency injection\fP is that below your top\-most object, all the other dependencies have been injected and thus not require container access. That is what we mean when we say most of your code does NOT have to be Spring Python\-aware. .IP Note . Pay special note that there is no fixed requirement that a container actually be in a certain location. While the current solution is memory based, meaning your objects will be lost when your application shuts down, there is always the possibility of implementing some type of distributed, persistent object container. For example, it is within the realm of possibilities to implement a container that utilizes a back\-end database to "contain" things or utilizes some distributed memory cache spread between nodes. .RE .SS ObjectContainer vs. ApplicationContext .sp The name of the container is \fIObjectContainer\fP. Its job is to pull in object meta\-data from various sources, and then call on related object factories to create the objects. In fact, this container is capable of receiving object definitions from multiple sources, each of differing types such as XML, YAML, Python code, and other future formats. .sp The following block of code shows an example of creating an object container, and then pulling an object out of the container.: .sp .nf .ft C # TODO: Use YUI here from springpython.context import ApplicationContext from springpython.config import XMLConfig container = ApplicationContext(XMLConfig("app\-context.xml")) service = container.get_object("sampleService") .ft P .fi .sp The first thing you may notice is the fact that \fIApplicationContext\fP was used instead of \fIObjectContainer\fP. \fIApplicationContext\fP is a subclass of \fIObjectContainer\fP, and is typically used because it also performs additional pre\- and post\-creational logic. .sp For example, any object that implements \fIApplicationContextAware\fP will have an additional app_context attribute added, populated with a copy of the \fIApplicationContext\fP. If your object\(aqs class extends \fIObjectPostProcessor\fP and defines a \fIpost_process_after_initialization\fP, the ApplicationContext will run that method against every instance of that object. .sp If your singleton objects hold references to some external resources, e.g. connections to a resource manager of some sort, you may also want to subclass \fIspringpython.context.DisposableObject\fP to have a means for the resources to get released. Any singleton subclassing \fIDisposableObject\fP may define a destroy method which is guaranteed to be executed on \fIApplicationContext\fP shutdown. An alternative to creating a destroy method is to define the \fIdestroy_method\fP attribute of an object which should be a name of the custom method to be invoked on \fIApplicationContext\fP shutdown. If an object defines both destroy and destroy_method then the former will take precedence. It is an error to extend \fIDisposableObject\fP without providing either destory or destroy_method. If this occurs, an error will be written to Spring Python logs when the container shuts down. .SS Scope of Objects / Lazy Initialization .sp Another key duty of the container is to also manage the scope of objects. This means at what time that objects are created, where the instances are stored, how long before they are destroyed, and whether or not to create them when the container is first started up. .sp Currently, two scopes are supported: SINGLETON and PROTOTYPE. A singleton\-scoped object is cached in the container until application shutdown. A prototype\-scoped object is never stored, thus requiring the object factory to create a new instance every time the object is requested from the container. .sp The default policy for the container is to make everything SINGLETON and also eagerly fetch all objects when the container is first created. The scope for each object can be individually overriden. Also, the initialization of each object can be shifted to "lazy", whereby the object is not created until the first time it is fetched or referenced by another object. .SS Configuration .sp Spring Python supports different formats for defining objects. .sp In the spirit of \fI\%Spring JavaConfig\fP and \fI\%a blog posting\fP by Rod Johnson, the \fBPythonConfig\fP format has been defined. By extending \fBPythonConfig\fP and using the @Object Python decorator, objects may be defined with pure Python code in a centralized class. .sp \fBXMLConfig\fP (\fBsee the XSD spec\fP) closely models the original Spring Java version. It has support for \fIreferenced objects\fP, \fIinner objects\fP, various \fIcollections\fP (lists, sets, frozen sets, tuples, dictionaries, and Java\-style props), and values. .sp Spring Python also has a YAML\-based parser called \fBYamlConfig\fP. .sp Spring Python is ultimately about choice, which is why developers may extend the \fIspringpython.config.Config\fP class to define their own object definition scanner. By plugging an instance of their scanner into \fIApplicationContext\fP, definitions can result in instantiated objects. .IP Note . This project first began using the format defined by PyContainer, \fIa now inactive project\fP. The structure has been \fBcaptured into an XSD spec\fP. This format is primarily to support legacy apps that have already been built with Spring Python from its inception. There is no current priority to extend this format any further. Any new schema developments will be happening with XMLConfig and YamlConfig. .RE .sp You may be wondering, amidst all these choices, which one to pick? Here are some suggestions based on your current solution space: .INDENT 0.0 .INDENT 3.5 .INDENT 0.0 .IP \(bu 2 . New projects are encouraged to pick either \fBPythonConfig\fP, \fBXMLConfig\fP, or \fBYamlConfig\fP, based on your preference for pure Python code, XML, or YAML. .IP \(bu 2 . Projects migrating from Spring Java can use \fIobjects\-other\-formats\-springjavaconfig\fP to ease transition, with a long term goal of migrating to \fIXMLConfig\fP, and perhaps finally \fIPythonConfig\fP. .IP \(bu 2 . Apps already developed with Spring Python can use \fIPyContainerConfig\fP to keep running, but it is highly suggested you work towards \fIXMLConfig\fP. .IP \(bu 2 . Projects currently using \fIXMLConfig\fP should be pretty easy to migrate to \fIPythonConfig\fP, since it is basically a one\-to\-one translation. The pure Python configuration may turn out much more compact, especially if you are using lists, sets, dictionaries, and props. .IP \(bu 2 . It should also be relatively easy to migrate an \fIXMLConfig\fP\-based configuration to \fIYamlConfig\fP. YAML tends to be more compact than XML, and some prefer not having to deal with the angle\-bracket tax. .UNINDENT .UNINDENT .UNINDENT .SS XMLConfig \- Spring Python\(aqs native XML format .sp \fIXMLConfig\fP is a class that scans object definitions stored in the XML format defined for Spring Python. It looks very similar to Spring Java\(aqs 2.5 XSD spec, with some small changes. .sp The following is a simple definition of objects. Later sections will show other options you have for wiring things together.: .sp .nf .ft C support/movies1.txt .ft P .fi .sp The definitions stored in this file are fed to an \fIXMLConfig\fP instance which scans it, and then sends the meta\-data to the \fIApplicationContext\fP. Then, when the application code requests an object named MovieLister from the container, the container utilizes an object factory to create the object and return it: .sp .nf .ft C from springpython.context import ApplicationContext from springpython.config import XMLConfig container = ApplicationContext(XMLConfig("app\-context.xml")) service = container.get_object("MovieLister") .ft P .fi .SS Referenced Objects .sp A referenced object is where an object is needed, but instead of providing the definition right there, there is, instead, a name, referring to another object definition. .sp Object definitions can refer to other objects in many places including: properties, constructor arguments, and objects embedded inside various collections. This is the way to break things down into smaller pieces. It also allows you more efficiently use memory and guarantee different objects are linked to the same backend object. .sp The following fragment, pulled from the earlier example, shows two different properties referencing other objects. It demonstrates the two ways to refer to another object: .sp .nf .ft C .ft P .fi .sp This means that instead of defining the object meant to be injected into the \fIdescription\fP property right there, the container must look elsewhere amongst its collection of object definitions for an object named \fISingletonString\fP. .IP Note .INDENT 0.0 .IP \(bu 2 . \fBReferenced objects don\(aqt have to be in same configuration\fP .sp When a referenced object is encountered, finding its definition is referred back to the container. This means ANY of the input sources provided to the container can hold this definition, REGARDLESS of format. .IP \(bu 2 . \fBSpring Python ONLY supports global references\fP .sp While Spring Java has different levels of reference like parent, local, and global, Spring Python only supports global at this time. .UNINDENT .RE .sp In the following subsections, other types of object definitions are given. Each will also include information about embedding reference objects. .SS Inner Objects .sp Inner objects are objects defined inside another structure, and not at the root level of the XML document. The following shows an alternative configuration of a \fIMovieLister\fP where the \fIfinder\fP uses a \fInamed inner object\fP: .sp .nf .ft C support/movies1.txt .ft P .fi .sp The \fIColonMovieFinder\fP is indeed an inner object because it was defined inside the \fIMovieLister3\fP object. Objects defined at the top level have a container\-level name that matches their \fIid\fP value. In this case, asking the container for a copy of \fIMovieLister3\fP will yield the top level object. However, named objects develop a path\-like name based on where they are located. In this case, the inner \fIColonMovieFinder\fP object will have a container\-level name of \fIMovieLister3.finder.named\fP. .sp Typically, neither your code nor other object definitions will have any need to reference \fIMovieLister3.finder.named\fP, but there may be cases where you need this. The \fIid\fP attribute of ColonMovieFinder can be left out (it is optional for inner objects) like this: .sp .nf .ft C support/movies1.txt .ft P .fi .sp That is slightly more compact, and usually alright because you usually wouldn\(aqt access this object from anywhere. However, if you must, the name in this case is \fIMovieLister2.finder.\fP indicating an anonymous object. .sp It is important to realize that inner objects have all the same privileges as top\-level objects, meaning that they can also utilize \fIreference objects\fP, \fIcollections\fP, and inner objects themselves. .SS Collections .sp Spring Java supports many types of collections, including lists, sets, frozen sets, maps, tuples, and Java\-style properties. Spring Python supports these as well. The following configuration shows usage of \fIdict\fP, \fIlist\fP, \fIprops\fP, \fIset\fP, \fIfrozenset\fP, and \fItuple\fP: .sp .nf .ft C HelloWorld SpringPython holder another copy Hello, world! Spring Python administrator@example.org support@example.org development@example.org Hello, world! Spring Python Hello, world! Spring Python Hello, world! Spring Python .ft P .fi .INDENT 0.0 .IP \(bu 2 . some_dict is a Python dictionary with four entries. .IP \(bu 2 . some_list is a Python list with three entries. .IP \(bu 2 . some_props is also a Python dictionary, containing three values. .IP \(bu 2 . some_set is an instance of Python\(aqs \fI\%mutable set\fP. .IP \(bu 2 . some_frozen_set is an instance of Python\(aqs \fI\%frozen set\fP. .IP \(bu 2 . some_tuple is a Python tuple with three values. .UNINDENT .IP Note . Java uses maps, Python uses dictionaries .sp While java calls key\-based structures maps, Python calls them dictionaries. For this reason, the code fragment shows a "dict" entry, which is one\-to\-one with Spring Java\(aqs "map" definition. .sp Java also has a \fIProperty\fP class. Spring Python translates this into a Python dictionary, making it more like an alternative to the configuring mechanism of dict. .RE .SS Constructors .sp Python functions can have both positional and named arguments. Positional arguments get assembled into a tuple, and named arguments are assembled into a dictionary, before being passed to a function call. Spring Python takes advantage of that option when it comes to constructor calls. The following block of configuration data shows defining positional constructors: .sp .nf .ft C elemental value .ft P .fi .sp Spring Python will read these and then feed them to the class constructor in the same order as shown here. .sp The following code configuration shows named constructor arguments. Spring Python converts these into keyword arguments, meaning it doesn\(aqt matter what order they are defined: .sp .nf .ft C alt a alt b alt c alt b .ft P .fi .sp This was copied from the code\(aqs test suite, where a test case is used to prove that order doesn\(aqt matter. It is important to note that positional constructor arguments are fed before named constructors, and that overriding a the same constructor parameter both by position and by name is not allowed by Python, and will in turn, generate a run\-time error. .sp It is also valuable to know that you can mix this up and use both. .SS Values .sp For those of you that used Spring Python before XMLConfig, you may have noticed that expressing values isn\(aqt as succinct as the old format. A good example of the old \fIPyContainer\fP format would be: .sp .nf .ft C { "basichiblueuser" : ("password1", ["ROLE_BASIC", "ASSIGNED_BLUE", "LEVEL_HI"], True), "basichiorangeuser": ("password2", ["ROLE_BASIC", "ASSIGNED_ORANGE", "LEVEL_HI"], True), "otherhiblueuser" : ("password3", ["ROLE_OTHER", "ASSIGNED_BLUE", "LEVEL_HI"], True), "otherhiorangeuser": ("password4", ["ROLE_OTHER", "ASSIGNED_ORANGE", "LEVEL_HI"], True), "basicloblueuser" : ("password5", ["ROLE_BASIC", "ASSIGNED_BLUE", "LEVEL_LO"], True), "basicloorangeuser": ("password6", ["ROLE_BASIC", "ASSIGNED_ORANGE", "LEVEL_LO"], True), "otherloblueuser" : ("password7", ["ROLE_OTHER", "ASSIGNED_BLUE", "LEVEL_LO"], True), "otherloorangeuser": ("password8", ["ROLE_OTHER", "ASSIGNED_ORANGE", "LEVEL_LO"], True) } .ft P .fi .IP Note . Why do I see components and not objects? .sp In the beginning, PyContainer was used and it tagged the managed instances as components. After replacing PyContainer with a more sophisticated IoC container, the instances are now referred to as objects, however, to maintain this legacy format, you will see component tags inside \fIPyContainerConfig\fP\-based definitions. .RE .sp While this is very succinct for expressing definitions using as much Python as possible, that format makes it very hard to embed referenced objects and inner objects, since PyContainerConfig uses Python\(aqs \fI\%eval\fP method to convert the material. .sp The following configuration block shows how to configure the same thing for XMLConfig: .sp .nf .ft C basichiblueuser password1 ROLE_BASICASSIGNED_BLUELEVEL_HI True basichiorangeuser password2 ROLE_BASICASSIGNED_ORANGELEVEL_HI True otherhiblueuser password3 ROLE_OTHERASSIGNED_BLUELEVEL_HI True otherhiorangeuser password4 ROLE_OTHERASSIGNED_ORANGELEVEL_HI True basicloblueuser password5 ROLE_BASICASSIGNED_BLUELEVEL_LO True basicloorangeuser password6 ROLE_BASICASSIGNED_ORANGELEVEL_LO True otherloblueuser password7 ROLE_OTHERASSIGNED_BLUELEVEL_LO True otherloorangeuser password8 ROLE_OTHERASSIGNED_ORANGELEVEL_LO True .ft P .fi .sp Of course this is more verbose than the previous block. However, it opens the door to having a much higher level of detail: .sp .nf .ft C Hello, world! yes This is working no Maybe it\(aqs not? Hello, from Spring Python! Spring Python yes This is working no Maybe it\(aqs not? This is a list element inside a tuple. And so is this :) 1 2 1 a b a .ft P .fi .SS XMLConfig and basic Python types .sp Objects of most commonly used Python types \- \fIstr\fP, \fIunicode\fP, \fIint\fP, \fIlong\fP, \fIfloat\fP, \fIdecimal.Decimal\fP, \fIbool\fP and \fIcomplex\fP \- may be expressed in XMLConfig using a shorthand syntax which allows for a following XMLConfig file: .sp .nf .ft C My string Zażółć gęślą jaźń 10 100000000000000000000000 3.14 12.34 False 10+0j .ft P .fi .sp _objects\-xmlconfig\-inheritance .SS Object definition inheritance .sp XMLConfig\(aqs definitions may be stacked up into hierarchies of abstract parents and their children objects. A child object not only inherits all the properties and constructor arguments from its parent but it can also easily override any of the inherited values. This can save a lot of typing when configuring non\-trivial application contexts which would otherwise need to repeat the same configuration properties over many objects definitions. .sp An abstract object is identified by having an \fIabstract="True"\fP attribute and the child ones are those which have a \fIparent\fP attribute set to ID of an object from which the properties or constructor arguments should be inherited. Child objects must not specify the \fIclass\fP attribute, its value is taken from their parents. .sp An object may be both a child and an abstract one. .sp Here\(aqs a hypothetical configuration of a set of services exposed by a server. Note how you can easily change the CRM environment you\(aqre invoking by merely changing the concrete service\(aqs (get_customer_id or get_customer_profile) parent ID: .sp .nf .ft C 192.168.1.153 3392 3393 /soap/invoke/get_customer_id /soap/invoke/get_customer_profile .ft P .fi .sp Here\(aqs how you can override inherited properties; both get_customer_id and get_customer_profile object definitions will inherit the path property however the actual objects returned by the container will use local, overridden, values of the property: .sp .nf .ft C 192.168.1.153 3392 /DOES\-NOT\-EXIST /soap/invoke/get_customer_id /soap/invoke/get_customer_profile .ft P .fi .sp If you need to get an abstract object from a container, use the .get_object\(aqs \fIignore_abstract\fP parameter, otherwise \fIspringpython.container.AbstractObjectException\fP will be raised. Observe the difference: .sp .nf .ft C # .. skip creating the context # No exception will be raised, even though \(aqservice\(aq is an abstract object service = ctx.get_object("service", ignore_abstract=True) # Will show the object print service # Will raise AbstractObjectException service = ctx.get_object("service") .ft P .fi .SS YamlConfig \- Spring Python\(aqs YAML format .sp \fIYamlConfig\fP is a class that scans object definitions stored in a \fI\%YAML 1.1\fP format using the \fI\%PyYAML\fP project. .sp The following is a simple definition of objects, including scope and lazy\-init. Later sections will show other options you have for wiring things together: .sp .nf .ft C objects: \- object: MovieLister class: springpythontest.support.testSupportClasses.MovieLister scope: prototype properties: finder: {ref: MovieFinder} description: {ref: SingletonString} \- object: MovieFinder class: springpythontest.support.testSupportClasses.ColonMovieFinder scope: singleton lazy\-init: True properties: filename: support/movies1.txt \- object: SingletonString class: springpythontest.support.testSupportClasses.StringHolder lazy\-init: True properties: str: There should only be one copy of this string .ft P .fi .sp The definitions stored in this file are fed to an \fIYamlConfig\fP instance which scans it, and then sends the meta\-data to the \fIApplicationContext\fP. Then, when the application code requests an object named \fIMovieLister\fP from the container, the container utilizes an object factory to create the object and return it: .sp .nf .ft C from springpython.context import ApplicationContext from springpython.config import YamlConfig container = ApplicationContext(YamlConfig("app\-context.yml")) service = container.get_object("MovieLister") .ft P .fi .SS Referenced Objects .sp A referenced object is where an object is needed, but instead of providing the definition right there, there is, instead, a name, referring to another object definition. .sp Object definitions can refer to other objects in many places including: properties, constructor arguments, and objects embedded inside various \fIcollections\fP. This is the way to break things down into smaller pieces. It also allows you more efficiently use memory and guarantee different objects are linked to the same backend object. .sp The following fragment, pulled from the earlier example, shows two different properties referencing other objects. It demonstrates the two ways to refer to another object: .sp .nf .ft C object: MovieLister class: springpythontest.support.testSupportClasses.MovieLister scope: prototype properties: finder: {ref: MovieFinder} description: {ref: SingletonString} .ft P .fi .sp This means that instead of defining the object meant to be injected into the \fIdescription\fP property right there, the container must look elsewhere amongst its collection of object definitions for an object named \fISingletonString\fP. .IP Note .INDENT 0.0 .IP \(bu 2 . \fBReferenced objects don\(aqt have to be in same configuration\fP .sp When a referenced object is encountered, finding its definition is referred back to the container. This means ANY of the input sources provided to the container can hold this definition, REGARDLESS of format. .IP \(bu 2 . \fBSpring Python ONLY supports global references\fP .sp While Spring Java has different levels of reference like parent, local, and global, Spring Python only supports global at this time. .UNINDENT .RE .sp In the following subsections, other types of object definitions are given. Each will also include information about embedding reference objects. .SS Inner Objects .sp Inner objects are objects defined inside another structure, and not at the root level of the YAML document. The following shows an alternative configuration of a \fIMovieLister\fP where the finder uses a \fInamed inner object\fP: .sp .nf .ft C object: MovieLister3 class: springpythontest.support.testSupportClasses.MovieLister properties: finder: object: named class: springpythontest.support.testSupportClasses.ColonMovieFinder properties: filename: support/movies1.txt description: {ref: SingletonString} .ft P .fi .sp The \fIColonMovieFinder\fP is indeed an inner object because it was defined inside the \fIMovieLister3\fP object. Objects defined at the top level have a container\-level name that matches their id value. In this case, asking the container for a copy of \fIMovieLister3\fP will yield the top level object. However, named objects develop a path\-like name based on where they are located. In this case, the inner \fIColonMovieFinder\fP object will have a container\-level name of \fIMovieLister3.finder.named\fP. .sp Typically, neither your code nor other object definitions will have any need to reference \fIMovieLister3.finder.named\fP, but there may be cases where you need this. The value of the object key of \fIColonMovieFinder\fP can be left out (it is optional for inner objects) like this: .sp .nf .ft C object: MovieLister2 class: springpythontest.support.testSupportClasses.MovieLister properties: finder: object: class: springpythontest.support.testSupportClasses.ColonMovieFinder properties: filename: support/movies1.txt description: {ref: SingletonString} .ft P .fi .sp That is slightly more compact, and usually alright because you usually wouldn\(aqt access this object from anywhere. However, if you must, the name in this case is \fIMovieLister2.finder.\fP indicating an anonymous object. .sp It is important to realize that inner objects have all the same privileges as top\-level objects, meaning that they can also utilize \fIreference objects\fP, \fIcollections\fP, and inner objects themselves. .SS Collections .sp Spring Java supports many types of collections, including lists, sets, frozen sets, maps, tuples, and java\-style properties. Spring Python supports these as well. The following configuration shows usage of \fIdict\fP, \fIlist\fP, \fIset\fP, \fIfrozenset\fP, and \fItuple\fP: .sp .nf .ft C object: ValueHolder class: springpythontest.support.testSupportClasses.ValueHolder constructor\-args: \- {ref: SingletonString} properties: some_dict: Hello: World Spring: Python holder: {ref: SingletonString} another copy: {ref: SingletonString} some_list: \- Hello, world! \- ref: SingletonString \- Spring Python some_props: administrator: administrator@example.org support: support@example.org development: development@example.org some_set: set: \- Hello, world! \- ref: SingletonString \- Spring Python some_frozen_set: frozenset: \- Hello, world! \- ref: SingletonString \- Spring Python some_tuple: tuple: \- Hello, world! \- ref: SingletonString \- Spring Python .ft P .fi .INDENT 0.0 .IP \(bu 2 . some_dict is a Python dictionary with four entries. .IP \(bu 2 . some_list is a Python list with three entries. .IP \(bu 2 . some_props is also a Python dictionary, containing three values. .IP \(bu 2 . some_set is an instance of Python\(aqs \fI\%mutable set\fP. .IP \(bu 2 . some_frozen_set is an instance of Python\(aqs \fI\%frozen set\fP. .IP \(bu 2 . some_tuple is a Python tuple with three values. .UNINDENT .IP Note . Java uses maps, Python uses dictionaries .sp While Java calls key\-based structures maps, Python calls them dictionaries. For this reason, the code fragment shows a "dict" entry, which is one\-to\-one with Spring Java\(aqs "map" definition. .sp Java also has a \fIProperty\fP class. Since YAML already supports a key/value structure as\-is, \fIYamlConfig\fP does not have a separate structural definition. .RE .SS Support for Python builtin types and mappings of other types onto YAML syntax .sp Objects of commonly used Python builtin types may be tersely expressed in YamlConfig. Supported types are \fIstr\fP, \fIunicode\fP, \fIint\fP, \fIlong\fP, \fIfloat\fP, \fIdecimal.Decimal\fP, \fIbool\fP, \fIcomplex\fP, \fIdict\fP, \fIlist\fP and \fItuple\fP. .sp Here\(aqs a sample YamlConfig featuring their usage. Note that with the exception of \fIdecimal.Decimal\fP, names of the YAML attributes are the same as the names of Python types: .sp .nf .ft C objects: \- object: MyString str: My string \- object: MyUnicode unicode: Zażółć gęślą jaźń \- object: MyInt int: 10 \- object: MyLong long: 100000000000000000000000 \- object: MyFloat float: 3.14 \- object: MyDecimal decimal: 12.34 \- object: MyBoolean bool: False \- object: MyComplex complex: 10+0j \- object: MyList list: [1, 2, 3, 4] \- object: MyTuple tuple: ["a", "b", "c"] \- object: MyDict dict: 1: "a" 2: "b" 3: "c" \- object: MyRef decimal: ref: MyDecimal .ft P .fi .sp Under the hood, while parsing the YAML files, Spring Python will translate the definitions such as the one above into the following one: .sp .nf .ft C objects: \- object: MyString class: types.StringType constructor\-args: ["My string"] \- object: MyUnicode class: types.UnicodeType constructor\-args: ["Zażółć gęślą jaźń"] \- object: MyInt class: types.IntType constructor\-args: [10] \- object: MyLong class: types.LongType constructor\-args: [100000000000000000000000] \- object: MyFloat class: types.FloatType constructor\-args: [3.14] \- object: MyDecimal class: decimal.Decimal constructor\-args: ["12.34"] \- object: MyBoolean class: types.BooleanType constructor\-args: [False] \- object: MyComplex class: types.ComplexType constructor\-args: [10+0j] \- object: MyList class: types.ListType constructor\-args: [[1,2,3,4]] \- object: MyTuple class: types.TupleType constructor\-args: [["a", "b", "c"]] \- object: MyDict class: types.DictType constructor\-args: [{1: "a", 2: "b", 3: "c"}] \- object: MyRef class: decimal.Decimal constructor\-args: [{ref: MyDecimal}] .ft P .fi .sp Configuration of how YAML elements are mapped onto Python types is stored in the \fIspringpython.config.yaml_mappings\fP dictionary which can be easily customized to fulfill one\(aqs needs. The dictionary\(aqs keys are names of the YAML elements and its values are the coresponding Python types, written as strings in the form of \fI"package_name.module_name.class_name"\fP \- note that the \fI"package_name.module_name."\fP part is required, it needs to be a fully qualified name. .sp Let\(aqs assume that in your configuration you\(aqre frequently creating objects of type \fIinterest_rate.InterestRateFrequency\fP, here\(aqs how you can save yourself a lot of typing by customizing the mappings dictionary. First, on Python side, create an \fIInterestRate\fP class, such as: .sp .nf .ft C class InterestRate(object): def __init__(self, value=None): self.value = value .ft P .fi .sp which will allow you to create such a YAML context: .sp .nf .ft C objects: \- object: base_interest_rate interest_rate: "7.35" .ft P .fi .sp then, before creating the context, update the mappings dictionary as needed and next you\(aqll be able to access the base_interest_rate object as if it had been defined using the standard syntax: .sp .nf .ft C from springpython.context import ApplicationContext from springpython.config import YamlConfig, yaml_mappings yaml_mappings.update({"interest_rate": "interest_rate.InterestRate"}) # .. create the context now container = ApplicationContext(YamlConfig("./app\-ctx.yaml")) # .. fetch the object base_interest_rate = container.get_object("base_interest_rate") # .. will show "7.35", as defined in the "./app\-ctx.yaml" config print base_interest_rate.value .ft P .fi .SS Constructors .sp Python functions can have both positional and named arguments. Positional arguments get assembled into a tuple, and named arguments are assembled into a dictionary, before being passed to a function call. Spring Python takes advantage of that option when it comes to constructor calls. The following block of configuration data shows defining positional constructors: .sp .nf .ft C object: AnotherSingletonString class: springpythontest.support.testSupportClasses.StringHolder constructor\-args: \- position 1\(aqs constructor value .ft P .fi .sp Spring Python will read these and then feed them to the class constructor in the same order as shown here. .sp The following code configuration shows named constructor arguments. Spring Python converts these into keyword arguments, meaning it doesn\(aqt matter what order they are defined: .sp .nf .ft C object: MultiValueHolder class: springpythontest.support.testSupportClasses.MultiValueHolder constructor\-args: a: alt a b: alt b .ft P .fi .sp This was copied from the code\(aqs test suite, where a test case is used to prove that order doesn\(aqt matter. It is important to note that positional constructor arguments are fed before named constructors, and that overriding a the same constructor parameter both by position and by name is not allowed by Python, and will in turn, generate a run\-time error. .sp It is also valuable to know that you can mix this up and use both. .SS Object definition inheritance .sp Just like XMLConfig, YamlConfig allows for wiring the objects definitions into hierarchies of abstract and children objects, thus this section is in most parts a repetition of what\(aqs documented \fIhere\fP. .sp Definitions may be stacked up into hierarchies of abstract parents and their children objects. A child object not only inherits all the properties and constructor arguments from its parent but it can also easily override any of the inherited values. This can save a lot of typing when configuring non\-trivial application contexts which would otherwise need to repeat the same configuration properties over many objects definitions. .sp An abstract object is identified by having an \fIabstract\fP attribute equal to True and the child ones are those which have a \fIparent\fP attribute set to ID of an object from which the properties or constructor arguments should be inherited. Child objects must not specify the \fIclass\fP attribute, its value is taken from their parents. .sp An object may be both a child and an abstract one. .sp Here\(aqs a hypothetical configuration of a set of services exposed by a server. Note how you can easily change the CRM environment you\(aqre invoking by merely changing the concrete service\(aqs (get_customer_id or get_customer_profile) parent ID: .sp .nf .ft C objects: \- object: service class: springpythontest.support.testSupportClasses.Service abstract: True scope: singleton lazy\-init: True properties: ip: 192.168.1.153 \- object: crm_service_dev abstract: True parent: service properties: port: "3392" \- object: crm_service_test abstract: True parent: service properties: port: "3393" \- object: get_customer_id parent: crm_service_dev properties: path: /soap/invoke/get_customer_id \- object: get_customer_profile parent: crm_service_test properties: path: /soap/invoke/get_customer_profile .ft P .fi .sp Here\(aqs how you can override inherited properties; both get_customer_id and get_customer_profile object definitions will inherit the path property however the actual objects returned by the container will use local, overridden, values of the property: .sp .nf .ft C objects: \- object: service class: foo.Service abstract: True scope: singleton lazy\-init: True properties: ip: 192.168.1.153 port: "3392" path: /DOES\-NOT\-EXIST \- object: get_customer_id parent: service properties: path: /soap/invoke/get_customer_id \- object: get_customer_profile parent: service properties: path: /soap/invoke/get_customer_profile .ft P .fi .SS PythonConfig and @Object \- decorator\-driven configuration .sp By defining a class that extends \fIPythonConfig\fP and using the \fI@Object\fP decorator, you can wire your application using pure Python code.: .sp .nf .ft C from springpython.config import PythonConfig from springpython.config import Object from springpython.context import scope class MovieBasedApplicationContext(PythonConfig): def __init__(self): super(MovieBasedApplicationContext, self).__init__() @Object(scope.PROTOTYPE) def MovieLister(self): lister = MovieLister() lister.finder = self.MovieFinder() lister.description = self.SingletonString() self.logger.debug("Description = %s" % lister.description) return lister @Object(scope.SINGLETON) def MovieFinder(self): return ColonMovieFinder(filename="support/movies1.txt") @Object(lazy_init=True) # scope.SINGLETON is the default def SingletonString(self): return StringHolder("There should only be one copy of this string") def NotExposed(self): pass .ft P .fi .sp As part of this example, the method NotExposed is also shown. This indicates that using \fIget_object\fP won\(aqt fetch that method, since it isn\(aqt considered an object. .sp By using pure Python, you don\(aqt have to deal with any XML. If you look closely, you will notice that the container code below is only different in the line actually creating the container. Everything else is the same as was in the \fBXMLConfig\fP & \fBYamlConfig\fP examples.: .sp .nf .ft C from springpython.context import ApplicationContext container = ApplicationContext(MovieBasedApplicationContext()) service = container.get_object("MovieLister") .ft P .fi .SS Object definition inheritance .sp PythonConfig\(aqs support for abstract objects is different to that of XMLConfig or YamlConfig. With PythonConfig, the children object do not automatically inherit nor override the parents\(aq properties, they in fact receive the values returned by their parents and it\(aqs up to them to decide, using Python code, whether to use or to discard the values received. .sp A child object must have as many optional arguments as there are expected to be returned by its parent. .sp Observe that in the following example the child definitions must define an optional \(aqreq\(aq argument; in runtime they will be passed its value basing on what their parent object will return. Note also that request is of PROTOTYPE scope, if it were a SINGLETON then both get_customer_id_request and get_customer_profile_request would receive the very same Request instance which, in other circumstances, could be a desirable effect but not in the example.: .sp .nf .ft C # stdlib import uuid4 # .. skip Spring Python imports class Request(object): def __init__(self, nonce=None, user=None, password=None): self.nonce = nonce self.user = user self.password = password def __str__(self): return "" % (hex(id(self)), self.nonce, self.user, self.password) class TestAbstractContext(PythonConfig): @Object(scope.PROTOTYPE, abstract=True) def request(self): return Request() @Object(parent="request") def request_dev(self, req=None): req.user = "dev\-user" req.password = "dev\-password" return req @Object(parent="request") def request_test(self, req=None): req.user = "test\-user" req.password = "test\-password" return req @Object(parent="request_dev") def get_customer_id_request(self, req=None): req.nonce = uuid4().hex return req @Object(parent="request_test") def get_customer_profile_request(self, req=None): req.nonce = uuid4().hex return req .ft P .fi .sp Same as with the other configuration modes, if you need to get an abstract object from a container, use the .get_object\(aqs ignore_abstract parameter, otherwise springpython.container.AbstractObjectException will be raised: .sp .nf .ft C # .. skip creating the context # No exception will be raised, even though \(aqrequest\(aq is an abstract object request = ctx.get_object("request", ignore_abstract=True) # Will show the object print request # Will raise AbstractObjectException request = ctx.get_object("request") .ft P .fi .SS Other configuration formats .SS PyContainerConfig \- Spring Python\(aqs original XML format .sp \fIPyContainerConfig\fP is a class that scans object definitions stored in the format defined by PyContainer, which was the original XML format used by Spring Python to define objects. .IP Warning . PyContainer\(aqs format is deprecated .sp PyContainer\(aqs format and the original parser was useful for getting this project started. However, it has shown its age by not being easy to revise nor extend. So this format is being retired. This parser is solely provided to help sustain existing Spring Python apps until they can migrate to the \fBPythonConfig\fP, \fBXMLConfig\fP or the \fBYamlConfig\fP format. .RE .sp An important thing to note is that PyContainer used the term \fIcomponent\fP, while Spring Python uses \fIobject\fP. In order to support this legacy format, \fIcomponent\fP will show up in \fIPyContainerConfig\fP\-based configurations: .sp .nf .ft C "support/movies1.txt" "There should only be one copy of this string" .ft P .fi .sp The definitions stored in this file are fed in to a \fIPyContainerConfig\fP which scans it, and then sends the meta\-data to the \fIApplicationContext\fP. Then, when the application code requests an object named "MovieLister" from the container, the container utilizes an object factory to create an object and return it: .sp .nf .ft C from springpython.context import ApplicationContext from springpython.config import PyContainerConfig container = ApplicationContext(PyContainerConfig("app\-context.xml")) service = container.get_object("MovieLister") .ft P .fi .SS SpringJavaConfig .sp The \fISpringJavaConfig\fP is a class that scans object definitions stored in the format defined by the Spring Framework\(aqs original java version. This makes it even easier to migrate parts of an existing Spring Java application onto the Python platform. .IP Note . This is about configuring Python objects NOT Java objects .sp It is important to point out that this has nothing to do with configuring Java\-backed beans from Spring Python, or somehow injecting Java\-backed beans magically into a Python object. This is PURELY for configuring Python\-backed objects using a format that was originally designed for pure Java beans. .sp When ideas like "converting Java to Python" are mentioned, it is meant that re\-writing certain parts of your app in Python would require a similar IoC configuration, however, for the Java and Python parts to integrate, you must utilize interoperable solutions like web service or other \fBremoting\fP technologies. .RE .sp .nf .ft C support/movies1.txt .ft P .fi .sp The definitions stored in this file are fed in to a \fISpringJavaConfig\fP which scans it, and then sends the meta\-data to the \fIApplicationContext\fP. Then, when the application code requests an object named "MovieLister" from the container, the container utilizes an object factory to create an object and return it: .sp .nf .ft C from springpython.context import ApplicationContext from springpython.config import SpringJavaConfig container = ApplicationContext(SpringJavaConfig("app\-context.xml")) service = container.get_object("MovieLister") .ft P .fi .sp Again, the only difference in your code is using \fISpringJavaConfig\fP instead of \fIPyContainerConfig\fP on one line. Everything is the same, since it is all inside the \fIApplicationContext\fP. .IP Note . What parts of Spring Java configuration are supported? .sp It is important to note that only spring\-beans\-2.5 has been tested at this point in time. It is possible that older versions of the XSD spec may also work. .sp Spring Java\(aqs other names spaces, like \fItx\fP and \fIaop\fP, probably DON\(aqT work. They haven\(aqt been tested, and there is no special code that will utilize their feature set. .sp How much of Spring Java will be supported? That is an open question, best discussed on \fI\%Spring Python's community forum\fP. Basically, this is meant to ease current Java developers into Spring Python and/or provide a means to split up objects to support porting parts of your application into Python. There isn\(aqt any current intention of providing full blown support. .RE .SS More on IoC .SS Object Factories .sp Spring Python offers two types of factories, \fIspringpython.factory.ReflectiveObjectFactory\fP and \fIspringpython.factory.PythonObjectFactory\fP. These classes should rarely be used directly by the developer. They are instead used by the different types of configuration scanners. .SS Testable Code .sp One key value of using the IoC container is how you can isolate parts of your code for better testing. Imagine you had the following configuration: .sp .nf .ft C from springpython.config import * from springpython.context import * class MovieBasedApplicationContext(PythonConfig): def __init__(self): super(MovieBasedApplicationContext, self).__init__() @Object(scope.PROTOTYPE) def MovieLister(self): lister = MovieLister() lister.finder = self.MovieFinder() lister.description = self.SingletonString() self.logger.debug("Description = %s" % lister.description) return lister @Object(scope.SINGLETON) def MovieFinder(self): return ColonMovieFinder(filename="support/movies1.txt") @Object # scope.SINGLETON is the default def SingletonString(self): return StringHolder("There should only be one copy of this string") .ft P .fi .sp To inject a test double for \fIMovieFinder\fP, your test code would only have to extend the class and override the \fIMovieFinder\fP method, and replace it with your stub or mock object. Now you have a nicely isolated instance of \fIMovieLister\fP: .sp .nf .ft C class MyTestableAppContext(MovieBasedApplicationContext): def __init__(self): super(MyTestableAppContext, self).__init__() @Object def MovieFinder(self): return MovieFinderStub() .ft P .fi .SS Mixing Configuration Modes .sp Spring Python also supports providing object definitions from multiple sources, and allowing them to reference each other. This section shows the same app context, but split between two different sources. .sp First, the XML file containing the key object that gets pulled: .sp .nf .ft C "There should only be one copy of this string" .ft P .fi .sp Notice that \fIMovieLister\fP is referencing \fIMovieFinder\fP, however that object is NOT defined in this location. The definition is found elsewhere: .sp .nf .ft C class MixedApplicationContext(PythonConfig): def __init__(self): super(MixedApplicationContext, self).__init__() @Object(scope.SINGLETON) def MovieFinder(self): return ColonMovieFinder(filename="support/movies1.txt") .ft P .fi .IP Note . Object ref must match function name .sp In this situation, an XML\-based object is referencing Python code by the name MovieFinder. It is of paramount importance that the Python function have the same name as the referenced string. .RE .sp With some simple code, this is all brought together when the container is created: .sp .nf .ft C from springpython.context import ApplicationContext from springpython.config import PyContainerConfig container = ApplicationContext([MixedApplicationContext(), PyContainerConfig("mixed\-app\-context.xml")]) movieLister = container.get_object("MovieLister") .ft P .fi .sp In this case, the XML\-based object definition signals the container to look elsewhere for a copy of the MovieFinder object, and it succeeds by finding it in MixedApplicationContext. .sp It is possible to switch things around, but it requires a slight change: .sp .nf .ft C class MixedApplicationContext2(PythonConfig): def __init__(self): super(MixedApplicationContext2, self).__init__() @Object(scope.PROTOTYPE) def MovieLister(self): lister = MovieLister() lister.finder = self.app_context.get_object("MovieFinder") # <\-\- only line that is different lister.description = self.SingletonString() self.logger.debug("Description = %s" % lister.description) return lister @Object # scope.SINGLETON is the default def SingletonString(self): return StringHolder("There should only be one copy of this string") .ft P .fi .sp .nf .ft C "support/movies1.txt" .ft P .fi .sp An XML\-based object definition can refer to a @Object by name, however, the Python code has to change its direct function call to a container lookup, otherwise it will fail. .IP Note . PythonConfig is ApplicationContextAware .sp In order to perform a \fIget_object\fP, the configuration needs a handle on the surrounding container. The base class \fIPythonConfig\fP provides this, so that you can easily look for any object (local or not) by using \fIself.app_context.get_object("name")\fP. .RE .SS Querying and modifying the ApplicationContext in runtime .sp \fIApplicationContext\fP instances expose two attributes and an utility method which let you learn about their current state and dynamically alter them in runtime. .INDENT 0.0 .IP \(bu 2 . \fIobject_defs\fP is a dictionary of objects definitions, that is, the templates based upon which the container will create appropriate objects, e.g. your singletons, .IP \(bu 2 . \fIobjects\fP is a dictionary of already created objects stored for later use, .IP \(bu 2 . \fIget_objects_by_type(type, include_type=True)\fP returns those ApplicationContext\(aqs objects which are instances of a given type or of its subclasses. If \fIinclude_type\fP is False then only instances of the type\(aqs \fIsubclasses\fP will be returned. .UNINDENT .sp Here\(aqs an example showing how you can easily query a context to find out what definitions and objects it holds. The context itself is stored using \fBPythonConfig\fP in the \fIsample_context.py\fP module and \fIdemo.py\fP contains the code which examines the context: .sp .nf .ft C # # sample_context.py # from springpython.config import Object from springpython.context import scope from springpython.config import PythonConfig class MyClass(object): pass class MySubclass(MyClass): pass class SampleContext(PythonConfig): def __init__(self): super(SampleContext, self).__init__() @Object def http_port(self): return 18000 @Object def https_port(self): return self._get_https_port() def _get_https_port(self): return self.http_port() + 443 @Object def my_class_object1(self): return MyClass() @Object def my_class_object2(self): return MyClass() @Object def my_subclass_object1(self): return MySubclass() @Object def my_subclass_object2(self): return MySubclass() @Object def my_subclass_object3(self): return MySubclass() .ft P .fi .sp .nf .ft C # # demo.py # # Spring Python from springpython.context import ApplicationContext # Our sample code. from sample_context import SampleContext, MyClass, MySubclass # Create the context. ctx = ApplicationContext(SampleContext()) # Do we have an \(aqhttp_port\(aq object? print "http_port" in ctx.objects # Does the context have a definition of an \(aqftp_port\(aq object? print "ftp_port" in ctx.object_defs # How many objects are there? Observe the result is 7, that\(aqs because one of # the methods \- _get_https_port \- is not managed by the container. print len(ctx.objects) # List the names of all objects defined. print ctx.object_defs.keys() # Returns all instances of MyClass and of its subclasses. print ctx.get_objects_by_type(MyClass) # Returns all instances of MyClass\(aq subclasses only. print ctx.get_objects_by_type(MyClass, False) # Returns all integer objects. print ctx.get_objects_by_type(int) .ft P .fi .sp The .object_defs dictionary stores instances of \fIspringpython.config.ObjectDef\fP class, these are the objects you need to inject into the container to later successfully access them as if they were added prior to the application\(aqs start. An \fIObjectDef\fP allows one to specify the very same set of parameters an \fI@Object\fP decorator does. The next examples shows how to insert two definitions into a context, one will be a prototype \- a new instance of \fIFoo\fP will be created on each request, the second one will be a singleton \- only one instance of \fIBar\fP will ever be created and stored in a cache of singletons. This time the example employs the Python\(aqs standard library \fIlogging\fP module to better show in the \fIDEBUG\fP mode what is going on under the hood: .sp .nf .ft C # # sample_context2.py # # Spring Python from springpython.config import PythonConfig class SampleContext2(PythonConfig): def __init__(self): super(SampleContext2, self).__init__() .ft P .fi .sp .nf .ft C # # demo2.py # # stdlib import logging # Spring Python from springpython.config import Object, ObjectDef from springpython.context import ApplicationContext from springpython.factory import PythonObjectFactory from springpython.context.scope import SINGLETON, PROTOTYPE # Our sample code. from sample_context2 import SampleContext2 # Configure logging. log_format = "%(msecs)d \- %(levelname)s \- %(name)s \- %(message)s" logging.basicConfig(level=logging.DEBUG, format=log_format) class Foo(object): def run(self): return "Foo!" class Bar(object): def run(self): return "Bar!" # Create the context \- part 1. in the logs. ctx = ApplicationContext(SampleContext2()) # Definitions of objects that will be dynamically injected into container. @Object(PROTOTYPE) def foo(): """ Returns a new instance of Foo on each call. """ return Foo() @Object # SINGLETON is the default. def bar(): """ Returns a singleton Bar every time accessed. """ return Bar() # A reference to the function wrapping the actual \(aqfoo\(aq function. foo_wrapper = foo.func_globals["_call_"] # Create an object definition, note that we\(aqre telling to return foo_object_def = ObjectDef(id="foo", factory=PythonObjectFactory(foo, foo_wrapper), scope=PROTOTYPE, lazy_init=foo_wrapper.lazy_init) # A reference to the function wrapping the actual \(aqbar\(aq function. bar_wrapper = foo.func_globals["_call_"] bar_object_def = ObjectDef(id="foo", factory=PythonObjectFactory(bar, bar_wrapper), scope=SINGLETON, lazy_init=bar_wrapper.lazy_init) ctx.object_defs["foo"] = foo_object_def ctx.object_defs["bar"] = bar_object_def # Access "foo" \- part 2. in the logs. for x in range(3): foo_instance = ctx.get_object("foo") # Access "bar" \- part 3. in the logs. for x in range(3): bar_instance = ctx.get_object("bar") .ft P .fi .sp Here\(aqs how it shows in the logs. For clarity, the log has been divided into three parts. Part 1. reads the object definitions from SampleContext2, as we see, nothing has been read from it as it\(aqs still been empty at this point. After adding definitions to the .object_defs dictionary, we\(aqre now at parts 2. and 3. \- in 2. the \(aqfoo\(aq object, a prototype one, is being created three times, as expected. In part 3. the singleton \(aqbar\(aq object is created and stored in a singleton cache once only even though we\(aqre accessing it three times in our code. .sp .nf .ft C # Part 1. 100 \- DEBUG \- springpython.config.PythonConfig \- ============================================================== 100 \- DEBUG \- springpython.config.PythonConfig \- Parsing 101 \- DEBUG \- springpython.config.PythonConfig \- ============================================================== 101 \- DEBUG \- springpython.container.ObjectContainer \- === Done reading object definitions. === # Part 2. 102 \- DEBUG \- springpython.context.ApplicationContext \- Did NOT find object \(aqfoo\(aq in the singleton storage. 102 \- DEBUG \- springpython.context.ApplicationContext \- Creating an instance of id=foo props=[] scope=scope.PROTOTYPE factory=PythonObjectFactory() 102 \- DEBUG \- springpython.factory.PythonObjectFactory \- Creating an instance of foo 102 \- DEBUG \- springpython.config.objectPrototype \- ()scope.PROTOTYPE \- This IS the top\-level object, calling foo(). 102 \- DEBUG \- springpython.config.objectPrototype \- ()scope.PROTOTYPE \- Found <__main__.Foo object at 0x184b650> 102 \- DEBUG \- springpython.context.ApplicationContext \- Did NOT find object \(aqfoo\(aq in the singleton storage. 102 \- DEBUG \- springpython.context.ApplicationContext \- Creating an instance of id=foo props=[] scope=scope.PROTOTYPE factory=PythonObjectFactory() 102 \- DEBUG \- springpython.factory.PythonObjectFactory \- Creating an instance of foo 103 \- DEBUG \- springpython.config.objectPrototype \- ()scope.PROTOTYPE \- This IS the top\-level object, calling foo(). 103 \- DEBUG \- springpython.config.objectPrototype \- ()scope.PROTOTYPE \- Found <__main__.Foo object at 0x184b690> 103 \- DEBUG \- springpython.context.ApplicationContext \- Did NOT find object \(aqfoo\(aq in the singleton storage. 103 \- DEBUG \- springpython.context.ApplicationContext \- Creating an instance of id=foo props=[] scope=scope.PROTOTYPE factory=PythonObjectFactory() 103 \- DEBUG \- springpython.factory.PythonObjectFactory \- Creating an instance of foo 103 \- DEBUG \- springpython.config.objectPrototype \- ()scope.PROTOTYPE \- This IS the top\-level object, calling foo(). 103 \- DEBUG \- springpython.config.objectPrototype \- ()scope.PROTOTYPE \- Found <__main__.Foo object at 0x184b650> # Part 3. 103 \- DEBUG \- springpython.context.ApplicationContext \- Did NOT find object \(aqbar\(aq in the singleton storage. 103 \- DEBUG \- springpython.context.ApplicationContext \- Creating an instance of id=foo props=[] scope=scope.SINGLETON factory=PythonObjectFactory() 103 \- DEBUG \- springpython.factory.PythonObjectFactory \- Creating an instance of bar 104 \- DEBUG \- springpython.config.objectSingleton \- ()scope.SINGLETON \- This IS the top\-level object, calling bar(). 104 \- DEBUG \- springpython.config.objectSingleton \- ()scope.SINGLETON \- Found <__main__.Bar object at 0x184b690> 104 \- DEBUG \- springpython.context.ApplicationContext \- Stored object \(aqbar\(aq in container\(aqs singleton storage .ft P .fi .sp Please note that what has been shown above applies to runtime only, adding object definitions to the container doesn\(aqt mean the changes will be in any way serialized to the file system, they are transient and will be lost when the application will be shutting down. Another thing to keep in mind is that you\(aqll be modifying a raw Python dictionary and if your application is multi\-threaded, you\(aqll have to serialize the access from concurrent threads yourself. .SH ASPECT ORIENTED PROGRAMMING .sp \fIAspect oriented programming\fP (AOP) is a horizontal programming paradigm, where some type of behavior is applied to several classes that don\(aqt share the same vertical, object\-oriented inheritance. In AOP, programmers implement these \fIcross cutting concerns\fP by writing an \fIaspect\fP then applying it conditionally based on a \fIjoin point\fP. This is referred to as applying \fIadvice\fP. This section shows how to use the AOP module of Spring Python. .SS External dependencies .sp Spring Python\(aqs AOP itself doesn\(aqt require any special external libraries to work however the IoC configuration format of your choice, unless you use \fBPythonConfig\fP, will likely need some. Refer to the \fBIoC documentation\fP for more details. .SS Interceptors .sp Spring Python implements AOP advice using \fIproxies\fP and \fImethod interceptors\fP. NOTE: Interceptors only apply to method calls. Any request for attributes are passed directly to the target without AOP intervention. .sp Here is a sample service. Our goal is to wrap the results with "wrapped" tags, without modifying the service\(aqs code: .sp .nf .ft C class SampleService: def method(self, data): return "You sent me \(aq%s\(aq" % data def doSomething(self): return "Okay, I\(aqm doing something" .ft P .fi .sp If we instantiate and call this service directly, the results are straightforward: .sp .nf .ft C service = SampleService() print service.method("something") "You sent me \(aqsomething\(aq" .ft P .fi .sp To configure the same thing using the IoC container, put the following text into a file named app\-context.xml: .sp .nf .ft C .ft P .fi .sp To instantiate the IoC container, use the following code: .sp .nf .ft C from springpython.context import ApplicationContext from springpython.config import XMLConfig container = ApplicationContext(XMLConfig("app\-context.xml")) service = container.get_object("service") .ft P .fi .sp You can use either mechanism to define an instance of your service. Now, let\(aqs write an interceptor that will catch any results, and wrap them with tags: .sp .nf .ft C from springpython.aop import * class WrappingInterceptor(MethodInterceptor): def invoke(self, invocation): results = "" + invocation.proceed() + "" return results .ft P .fi .sp \fIinvoke(self, invocation)\fP is a dispatching method defined abstractly in the \fIMethodInterceptor\fP base class. \fIinvocation\fP holds the target method name, any input arguments, and also the callable target function. In this case, we aren\(aqt interested in the method name or the arguments. So we call the actual function using \fIinvocation.proceed()\fP, and than catch its results. Then we can manipulate these results, and return them back to the caller. .sp In order to apply this advice to a service, a stand\-in proxy must be created and given to the client. One way to create this is by creating a \fIProxyFactory\fP. The factory is used to identify the target service that is being intercepted. It is used to create the dynamic proxy object to give back to the client. .sp You can use the Spring Python APIs to directly create this proxied service: .sp .nf .ft C from springpython.aop import * factory = ProxyFactory() factory.target = SampleService() factory.interceptors.append(WrappingInterceptor()) service = factory.getProxy() .ft P .fi .sp Or, you can insert this definition into your app\-context.xml file: .sp .nf .ft C .ft P .fi .sp If you notice, the original Spring Python "service" object has been renamed as "targetService", and there is, instead, another object called "serviceFactory" which is a Spring AOP ProxyFactory. It points to the target service and also has an interceptor plugged in. In this case, the interceptor is defined as an inner object, not having a name of its own, indicating it is not meant to be referenced outside the IoC container. When you get a hold of this, you can request a proxy: .sp .nf .ft C from springpython.context import ApplicationContext from springpython.config import XMLConfig container = ApplicationContext(XMLConfig("app\-context.xml")) serviceFactory = container.get_object("serviceFactory") service = serviceFactory.getProxy() .ft P .fi .sp Now, the client can call \fIservice\fP, and all function calls will be routed to \fISampleService\fP with one simple detour through \fIWrappingInterceptor\fP: .sp .nf .ft C print service.method("something") "You sent me \(aqsomething\(aq" .ft P .fi .sp Notice how I didn\(aqt have to edit the original service at all? I didn\(aqt even have to introduce Spring Python into that module. Thanks to the power of Python\(aqs dynamic nature, Spring Python AOP gives you the power to wrap your own source code as well as other 3rd party modules. .SS Proxy Factory Objects .sp The earlier usage of a \fIProxyFactory\fP is useful, but often times we only need the factory to create one proxy. There is a shortcut called \fIProxyFactoryObject\fP: .sp .nf .ft C from springpython.aop import * service = ProxyFactoryObject() service.target = SampleService() service.interceptors = [WrappingInterceptor()] print service.method(" proxy factory object") "You sent me a \(aqproxy factory object\(aq" .ft P .fi .sp To configure the same thing using the IoC container, put the following text into a file named \fIapp\-context.xml\fP: .sp .nf .ft C .ft P .fi .sp In this case, the \fIProxyFactoryObject\fP acts as both a proxy and a factory. As a proxy, it behaves just like the target service would, and it also provides the ability to wrap the service with aspects. It saved us a step of coding, but more importantly, the \fIProxyFactoryObject\fP took on the persona of being our service right from the beginning. .sp To be more pythonic, Spring Python also allows you to initialize everything at once: .sp .nf .ft C from springpython.aop import * service = ProxyFactoryObject(target = SampleService(), interceptors = [WrappingInterceptor()]) .ft P .fi .SS Pointcuts .sp Sometimes we only want to apply advice to certain methods. This requires definition of a \fIjoin point\fP. Join points are composed of rules referred to as point cuts. .sp In this case, we want to only apply our \fIWrappingInterceptor\fP to methods that start with "do": .sp .nf .ft C from springpython.aop import * pointcutAdvisor = RegexpMethodPointcutAdvisor(advice = [WrappingInterceptor()], patterns = [".*do.*"]) service = ProxyFactoryObject(target = SampleService(), interceptors = pointcutAdvisor) print service.method("nothing changed here") "You sent me \(aqnothing changed here\(aq" print service.doSomething() "Okay, I\(aqm doing something" return results .ft P .fi .sp Now, everything that was referring to the original \fIColonMovieFinder\fP instance, is instead pointing to a wrapping interceptor. The caller and callee involved don\(aqt know anything about it, keeping your code isolated and clean. .IP Note . Shouldn\(aqt you decouple the interceptor from the IoC configuration? .sp It is usually good practice to split up configuration from actual business code. These two were put together in the same file for demonstration purposes. .RE .SH DATA ACCESS .SS DatabaseTemplate .sp Writing SQL\-based programs has a familiar pattern that must be repeated over and over. The DatabaseTemplate resolves that by handling the plumbing of these operations while leaving you in control of the part that matters the most, the SQL. .SS External dependencies .sp DatabaseTemplate requires the use of external libraries for connecting to SQL databases. Depending on which SQL connection factory you\(aqre about to use, you need to install following dependencies: .INDENT 0.0 .IP \(bu 2 . \fIspringpython.database.factory.MySQLConnectionFactory\fP \- needs \fI\%MySQLdb\fP for connecting to MySQL, .IP \(bu 2 . \fIspringpython.database.factory.PgdbConnectionFactory\fP \- needs \fI\%PyGreSQL\fP for connecting to PostgreSQL, .IP \(bu 2 . \fIspringpython.database.factory.Sqlite3ConnectionFactory\fP \- needs \fI\%PySQLite\fP for connecting to SQLite 3, note that PySQLite is part of Python 2.5 and later so you need to install it separately only if you\(aqre using Python 2.4, .IP \(bu 2 . \fIspringpython.database.factory.cxoraConnectionFactory\fP \- needs \fI\%cx_Oracle\fP for connecting to Oracle, .IP \(bu 2 . \fIspringpython.database.factory.SQLServerConnectionFactory\fP \- needs \fI\%PyODBC\fP for connecting to SQL Server. .UNINDENT .SS Traditional Database Query .sp If you have written a database SELECT statement following Python\(aqs \fI\%DB-API 2.0\fP, it would something like this (MySQL example): .sp .nf .ft C conn = MySQL.connection(username="me", password="secret", hostname="localhost", db="springpython") cursor = conn.cursor() results = [] try: cursor.execute("select title, air_date, episode_number, writer from tv_shows where name = %s", ("Monty Python",)) for row in cursor.fetchall(): tvShow = TvShow(title=row[0], airDate=row[1], episodeNumber=row[2], writer=row[3]) results.append(tvShow) finally: try: cursor.close() except Exception: pass conn.close() return results .ft P .fi .sp I know, you don\(aqt have to open and close a connection for every query, but let\(aqs look past that part. In every definition of a SQL query, you must create a new cursor, execute against the cursor, loop through the results, and most importantly (and easy to forget) \fIclose the cursor\fP. Of course you will wrap this in a method instead of plugging in this code where ever you need the information. But every time you need another query, you have to repeat this dull pattern over and over again. The only thing different is the actual SQL code you must write and converting it to a list of objects. .sp I know there are many object relational mappers (ORMs) out there, but sometimes you need something simple and sweet. That is where \fIDatabaseTemplate\fP comes in. .SS Database Template .sp The same query above can be written using a \fIDatabaseTemplate\fP. The only thing you must provide is the SQL and a \fIRowMapper\fP to process one row of data. The template does the rest: .sp .nf .ft C """ The following part only has to be done once.""" from springpython.database.core import * from springpython.database.factory import * connectionFactory = MySQLConnectionFactory(username="me", password="secret", hostname="localhost", db="springpython") dt = DatabaseTemplate(connectionFactory) class TvShowMapper(RowMapper): """This will handle one row of database. It can be reused for many queries if they are returning the same columns.""" def map_row(self, row, metadata=None): return TvShow(title=row[0], airDate=row[1], episodeNumber=row[2], writer=row[3]) results = dt.query("select title, air_date, episode_number, writer from tv_shows where name = %s", \e ("Monty Python",), TvShowMapper()) .ft P .fi .sp Well, no sign of a cursor anywhere. If you didn\(aqt have to worry about opening it, you don\(aqt have to worry about closing it. I know this is about the same amount of code as the traditional example. Where DatabaseTemplate starts to shine is when you want to write ten different TV_SHOW queries: .sp .nf .ft C results = dt.query("select title, air_date, episode_number, writer from tv_shows where episode_number < %s", \e (100,), TvShowMapper()) results = dt.query("select title, air_date, episode_number, writer from tv_shows where upper(title) like %s", \e ("%CHEESE%",), TvShowMapper()) results = dt.query("select title, air_date, episode_number, writer from tv_shows where writer in (\(aqCleese\(aq, \(aqGraham\(aq)", rowhandler=TvShowMapper()) .ft P .fi .sp You don\(aqt have to reimplement the rowhandler. For these queries, you can focus on the SQL you want to write, not the mind\-numbing job of managing database cursors. .SS Mapping rows into objects using convention over configuration .sp A powerful feature provided by databases is the ability to look up column names. Spring Python harnesses this by providing an out\-of\-the\-box row mapper that will automatically try matching a query column name to an class attribute name. This is known as \fIconvention over configuration\fP because it relieves you of the need to code the \fIRowMapper\fP provided you follow the convention of naming the attributes of your POPO after query columns. The only requirement is that class have a default constructor that doesn\(aqt require any arguments: .sp .nf .ft C results = dt.query("select title, air_date, episode_number, writer from tv_shows where episode_number < %s", \e (100,), SimpleRowMapper(TvShow)) results = dt.query("select title, air_date, episode_number, writer from tv_shows where upper(title) like %s", \e ("%CHEESE%",), SimpleRowMapper(TvShow)) results = dt.query("select title, air_date, episode_number, writer from tv_shows where writer in (\(aqCleese\(aq, \(aqGraham\(aq)", rowhandler=SimpleRowMapper(TvShow)) .ft P .fi .IP Note . Convention is based on query, not tables .sp Query metadata is based on the column names as defined in the query, NOT what is in the table. This is important when you use expressions like COUNT(*). These columns should be aliased to fit the attribute name. .RE .SS Mapping rows into dictionaries .sp A convenient alternative to mapping database rows into python objects, it to map them into dictionaries. Spring Python offers \fIspringpython.database.DictionaryRowMapper\fP as an out\-of\-the\-box way to query the database, and return a list of dictionary entries, based on the column names of the queries. Using this mapper, you don\(aqt have to code a \fITvRowMapper\fP as shown earlier: .sp .nf .ft C results = dt.query("select title, air_date, episode_number, writer from tv_shows where episode_number < %s", \e (100,), DictionaryRowMapper()) results = dt.query("select title, air_date, episode_number, writer from tv_shows where upper(title) like %s", \e ("%CHEESE%",), DictionaryRowMapper()) results = dt.query("select title, air_date, episode_number, writer from tv_shows where writer in (\(aqCleese\(aq, \(aqGraham\(aq)", rowhandler=DictionaryRowMapper()) .ft P .fi .IP Note . Dictionary keys are based on query not original tables .sp Query metadata is based on the column names as defined in the query, NOT what is in the table. This is important when you use expressions like COUNT(*). These columns should be aliased in order to generate a useful key in the dictionary. .RE .SS What is a Connection Factory? .sp You may have noticed I didn\(aqt make a standard connection in the example above. That is because to support \fI\%Dependency Injection\fP, I need to setup my credentials in an object before making the actual connection. \fIMySQLConnectionFactory\fP holds credentials specific to the MySQL DB\-API, but contains a common function to actually create the connection. I don\(aqt have to use it myself. \fIDatabaseTemplate\fP will use it when necessary to create a connection, and then proceed to reuse the connection for subsequent database calls. .sp That way, I don\(aqt manage database connections and cursors directly, but instead let Spring Python do the heavy lifting for me. .SS Creating/altering tables, databases, and other DDL .sp Data Definition Language includes the database statements that involve creating and altering tables, and so forth. DB\-API defines an execute function for this. \fIDatabaseTemplate\fP offers the same. Using the execute() function will pass through your request to a cursor, along with the extra exception handler and cursor management. .SS SQL Injection Attacks .sp You may have noticed in the first three example queries I wrote with the \fIDatabaseTemplate\fP, I embedded a "%s" in the SQL statement. These are called \fIbinding variables\fP, and they require a tuple argument be included after the SQL statement. Do \fINOT\fP include quotes around these variables. The database connection will handle that. This style of SQL programming is \fIhighly recommended\fP to avoid \fI\%SQL injection attacks\fP. .sp For users who are familiar with Java database APIs, the binding variables are cited using "?" instead of "%s". To make both parties happy and help pave the way for existing Java programmers to use this framework, I have included support for both. You can mix\-and\-match these two binding variable types as you wish, and things will still work. .SS Have you used Spring Framework\(aqs JdbcTemplate? .sp If you are a user of Java\(aqs \fI\%Spring framework\fP and have used the \fI\%JdbcTemplate\fP, then you will find this template has a familiar feel. .TS center; |l|l|. _ T{ execute(sql_statement, args = None) T} T{ execute any statement, return number of rows affected T} _ T{ insert_and_return_id(sql_statement, args = None) T} T{ insert, return id of new row inserted T} _ T{ query(sql_query, args = None, rowhandler = None T} T{ query, return list converted by rowhandler T} _ T{ query_for_list(sql_query, args = None) T} T{ query, return list of DB\-API tuplesTrue T} _ T{ query_for_int(sql_query, args = None) T} T{ query for a single column of a single row, and return an integer (throws exception otherwise) T} _ T{ query_for_long(sql_query, args = None) T} T{ query for a single column of a single row, and return a long (throws exception otherwise) T} _ T{ query_for_object(sql_query, args = None, required_type = None) T} T{ query for a single column of a single row, and return the object with possibly no checking T} _ T{ update(sql_statement, args = None) T} T{ update the database, return number of rows updated T} _ .TE .sp \fIInserts\fP have classically been implemented through the \fBexecute()\fP function, just like in JdbcTemplate. In Spring Python 1.2, we added \fBinsert_and_return_id()\fP to give the option of returning the id of the newly created row. It has the same signature as \fBexecute()\fP. This is very useful when you plan to insert one row in one table, and then insert several rows in another table that reference the first row created. .SS Notes on using SQLServerConnectionFactory .sp \fISQLServerConnectionFactory\fP uses ODBC for connecting to SQL Server instances and it expects you to pass the ODBC parameters when creating connection factories or when injecting factory settings through IoC. The ODBC parameters you provide are directly translated into an ODBC connection string. .sp That means that you use the exact ODBC parameters your ODBC provider understands and not the standard username, password, hostname and db parameters as with other connection factories. .sp A simple example will demonstrate this. Here\(aqs how you would create a \fIDatabaseTemplate\fP on Windows for running queries against an SQL Server instance: .sp .nf .ft C from springpython.database.core import DatabaseTemplate from springpython.database.factory import SQLServerConnectionFactory driver = "{SQL Server}" server = "localhost" database = "springpython" uid = "springpython" pwd = "cdZS*RQRBdc9a" factory = SQLServerConnectionFactory(DRIVER=driver, SERVER=server, DATABASE=database, UID=uid, PWD=pwd) dt = DatabaseTemplate(factory) .ft P .fi .IP Note . SQLServerConnectionFactory is dictionary driven .sp Due to SQLServerConnectionFactory\(aqs pass\-through nature, it is coded to accept a dictionary. For pure python, this means you MUST name the arguments and NOT rely on argument position. .RE .sp For an XML\-based application context, you must populate the argument odbc_info with a dictionary. See the following example: .sp .nf .ft C DRIVER {SQL Server} SERVER localhost DATABASE springpython UID springpython PWD cdZS*RQRBdc9a .ft P .fi .SH TRANSACTION MANAGEMENT .sp When writing a program with database operations, you may need to use transactions. Your code can get ugly, and it often becomes hard to read the business logic due to starting, committing, or rolling back for various reasons. Another risk is that some of the transaction management code you write will have all the necessary steps, while you may forget some important steps in others. Spring Python offers a key level of abstraction that can remove that burden and allow you to focus on the business logic. .SS External dependencies .sp If you choose to use DatabaseTemplate along with Spring Python\(aqs support for transaction management you need to install \fBan appropriate SQL database driver\fP module. Depending on the IoC configuration format you\(aqre going to use you may also need to install one of its \fIdocumented dependencies\fP. .SS Solutions requiring transactions .sp For simple transactions, you can embed them programmatically. .sp Seen anything like this before?: .sp .nf .ft C def transfer(transfer_amount, source_account_num, target_account_num): conn = MySQLdb.connection("springpython", "springpython", "localhost", "springpython") cursor = conn.cursor() cursor.execute("update ACCOUNT set BALANCE = BALANCE \- %s where ACCOUNT_NUM = %s", (transfer_amount, source_account_num)) cursor.execute("update ACCOUNT set BALANCE = BALANCE + %s where ACCOUNT_NUM = %s", (transfer_amount, target_account_num)) cursor.close() .ft P .fi .sp This business method defines a transfer between bank accounts. Notice any issues here? What happens if the target account doesn\(aqt exist? What about transferring a negative balance? What if the transfer amount exceeded the source account\(aqs balance? All these things require checks, and if something is wrong the entire transfer must be aborted, or you find the first bank account leaking money. .sp To wrap this function transactionally, based on DB\-2.0 API specifications, we\(aqll add some checks. I have also completed some refactorings and utilized the \fIDatabaseTemplate\fP to clean up my database code: .sp .nf .ft C from springpython.database import * from springpython.database.core import * import types class Bank: def __init__(self): self.factory = factory.MySQLConnectionFactory("springpython", "springpython", "localhost", "springpython") self.dt = DatabaseTemplate(self.factory) def balance(self, account_num): results = self.dt.query_for_list("select BALANCE from ACCOUNT where ACCOUNT_NUM = %s", (account_num,)) if len(results) != 1: raise InvalidBankAccount("There were %s accounts that matched %s." % (len(results), account_num)) return results[0][0] def checkForSufficientFunds(self, source_balance, amount): if source_balance < amount: raise InsufficientFunds("Account %s did not have enough funds to transfer %s" % (source_account_num, amount)) def withdraw(self, amount, source_account_num): self.checkForSufficientFunds(self.balance(source_account_num), amount) self.dt.execute("update ACCOUNT set BALANCE = BALANCE \- %s where ACCOUNT_NUM = %s", (amount, source_account_num)) def deposit(self, amount, target_account_num): # Implicitly testing for valid account number self.balance(target_account_num) self.dt.execute("update ACCOUNT set BALANCE = BALANCE + %s where ACCOUNT_NUM = %s", (amount, target_account_num)) def transfer(self, transfer_amount, source_account_num, target_account_num): try: cursor = self.factory.getConnection().cursor() # DB\-2.0 API spec says that creating a cursor implicitly starts a transaction self.withdraw(transfer_amount, source_account_num) self.deposit(transfer_amount, target_account_num) self.factory.getConnection().commit() cursor.close() # There wasn\(aqt anything in this cursor, but it is good to close an opened cursor except InvalidBankAccount, InsufficientFunds: self.factory.getConnection().rollback() .ft P .fi .INDENT 0.0 .IP \(bu 2 . This has some extra checks put in to protect from overdrafts and invalid accounts. .IP \(bu 2 . \fIDatabaseTemplate\fP removes our need to open and close cursors. .IP \(bu 2 . Unfortunately, we still have to tangle with them as well as the connection in order to handle transactions. .UNINDENT .SS TransactionTemplate .sp We still have to deal with exceptions. What if another part of the code raised another exception that we didn\(aqt trap? It might escape our try\-except block of code, and then our data could lose integrity. If we plug in the \fITransactionTemplate\fP, we can really simplify this and also guarantee management of any exceptions. .sp The following code block shows swapping out manual transaction for \fITransactionTemplate\fP: .sp .nf .ft C from springpython.database.transaction import * class Bank: def __init__(self): self.factory = factory.MySQLConnectionFactory("springpython", "springpython", "localhost", "springpython") self.dt = DatabaseTemplate(self.factory) self.txManager = ConnectionFactoryTransactionManager(self.factory) self.txTemplate = TransactionTemplate(self.txManager) def transfer(self, transfer_amount, source_account_num, target_account_num): class txDefinition(TransactionCallbackWithoutResult): def doInTransactionWithoutResult(s, status): self.withdraw(transfer_amount, source_account_num) self.deposit(transfer_amount, target_account_num) try: self.txTemplate.execute(txDefinition()) print "If you made it to here, then your transaction has already been committed." except InvalidBankAccount, InsufficientFunds: print "If you made it to here, then your transaction has already been rolled back." .ft P .fi .INDENT 0.0 .IP \(bu 2 . We changed the init function to setup a \fITransactionManager\fP (based on ConnectionFactory) and also a \fITransactionTemplate\fP. .IP \(bu 2 . We also rewrote the transfer function to generate a callback. .UNINDENT .sp Now you don\(aqt have to deal with implicit cursors, commits, and rollbacks. Managing commits and rollbacks can really complicated especially when dealing with exceptions. By wrapping it into a nice callback, \fITransactionTemplate\fP does the work for us, and lets us focus on business logic, while encouraging us to continue to define meaningful business logic errors. .SS @transactional .sp Another option is to use the @transactional decorator, and mark which methods should be wrapped in a transaction when called: .sp .nf .ft C from springpython.database.transaction import * class Bank: def __init__(self, connectionFactory): self.factory = connectionFactory self.dt = DatabaseTemplate(self.factory) @transactional def transfer(self, transfer_amount, source_account_num, target_account_num): self.withdraw(transfer_amount, source_account_num) self.deposit(transfer_amount, target_account_num) .ft P .fi .sp This needs to be wired together with a \fITransactionManager\fP in an \fIApplicationContext\fP. The following example shows a \fBPythonConfig\fP with three objects: .INDENT 0.0 .IP \(bu 2 . the bank .IP \(bu 2 . a \fITransactionManager\fP (in this case \fIConnectionFactoryTransactionManager\fP) .IP \(bu 2 . an \fIAutoTransactionalObject\fP, which checks all objects to see if they have \fI@transactional\fP methods, and if so, links them with the \fITransactionManager\fP. .UNINDENT .sp The name of the method (i.e. component name) for \fIAutoTransactionalObject\fP doesn\(aqt matter: .sp .nf .ft C class DatabaseTxTestDecorativeTransactions(PythonConfig): def __init__(self, factory): super(DatabaseTxTestDecorativeTransactions, self).__init__() self.factory = factory @Object def transactionalObject(self): return AutoTransactionalObject(self.tx_mgr()) @Object def tx_mgr(self): return ConnectionFactoryTransactionManager(self.factory) @Object def bank(self): return TransactionalBank(self.factory) .ft P .fi .sp This can also be configured using \fBXMLConfig\fP: .sp .nf .ft C .ft P .fi .SS PROPAGATION_REQUIRED ... .sp Declarative transactions includes the ability to define transaction propagation. This allows you to define when a transaction should be started, and which operations need to be part of transactions. There are several levels of propagation defined: .INDENT 0.0 .IP \(bu 2 . PROPAGATION_SUPPORTS \- Code can run inside or outside a transaction. .IP \(bu 2 . PROPAGATION_REQUIRED \- If there is no current transaction, one will be started. .IP \(bu 2 . PROPAGATION_MANDATORY \- Code MUST be run inside an already started transaction. .IP \(bu 2 . PROPAGATION_NEVER \- Code must NOT be run inside an existing transaction. .UNINDENT .sp The following code is a revision of the Bank class, with this attribute plugged in: .sp .nf .ft C class TransactionalBankWithLotsOfTransactionalArguments(object): """This sample application can be used to demonstrate the value of atomic operations. The transfer operation must be wrapped in a transaction in order to perform correctly. Otherwise, any errors in the deposit will allow the from\-account to leak assets.""" def __init__(self, factory): self.logger = logging.getLogger("springpython.test.testSupportClasses.TransactionalBankWithLotsOfTransactionalArguments") self.dt = DatabaseTemplate(factory) @transactional(["PROPAGATION_REQUIRED"]) def open(self, accountNum): self.logger.debug("Opening account %s with $0 balance." % accountNum) self.dt.execute("INSERT INTO account (account_num, balance) VALUES (?,?)", (accountNum, 0)) @transactional(["PROPAGATION_REQUIRED"]) def deposit(self, amount, accountNum): self.logger.debug("Depositing $%s into %s" % (amount, accountNum)) rows = self.dt.execute("UPDATE account SET balance = balance + ? WHERE account_num = ?", (amount, accountNum)) if rows == 0: raise BankException("Account %s does NOT exist" % accountNum) @transactional(["PROPAGATION_REQUIRED"]) def withdraw(self, amount, accountNum): self.logger.debug("Withdrawing $%s from %s" % (amount, accountNum)) rows = self.dt.execute("UPDATE account SET balance = balance \- ? WHERE account_num = ?", (amount, accountNum)) if rows == 0: raise BankException("Account %s does NOT exist" % accountNum) return amount @transactional(["PROPAGATION_SUPPORTS","readOnly"]) def balance(self, accountNum): self.logger.debug("Checking balance for %s" % accountNum) return self.dt.queryForObject("SELECT balance FROM account WHERE account_num = ?", (accountNum,), types.FloatType) @transactional(["PROPAGATION_REQUIRED"]) def transfer(self, amount, fromAccountNum, toAccountNum): self.logger.debug("Transferring $%s from %s to %s." % (amount, fromAccountNum, toAccountNum)) self.withdraw(amount, fromAccountNum) self.deposit(amount, toAccountNum) @transactional(["PROPAGATION_NEVER"]) def nonTransactionalOperation(self): self.logger.debug("Executing non\-transactional operation.") @transactional(["PROPAGATION_MANDATORY"]) def mandatoryOperation(self): self.logger.debug("Executing mandatory transactional operation.") @transactional(["PROPAGATION_REQUIRED"]) def mandatoryOperationTransactionalWrapper(self): self.mandatoryOperation() self.mandatoryOperation() @transactional(["PROPAGATION_REQUIRED"]) def nonTransactionalOperationTransactionalWrapper(self): self.nonTransactionalOperation() .ft P .fi .sp You will notice several levels are being utilized. This class was pulled directly from the test suite, so some of the functions are deliberately written to generate controlled failures. .sp If you look closely at \fIwithdraw\fP, \fIdeposit\fP, and \fItransfer\fP, which are all set to PROPAGATION_REQUIRED, you can see what this means. If you use \fIwithdraw\fP or \fIdeposit\fP by themselves, which require transactions, each will start and complete a transaction. However, \fItransfer\fP works by re\-using these business methods. \fITransfer\fP itself needs to be an entire transaction, so it starts one. When it calls \fIwithdraw\fP and \fIdeposit\fP, those methods don\(aqt need to start another transaction because they are already inside one. In comparison, \fIbalance\fP is defined as PROPAGATION_SUPPORTS. Since it doesn\(aqt update anything, it can run by itself without a transaction. However, if it is called in the middle of another transaction, it will play along. .sp You may have noticed that balance also has "readOnly" defined. In the future, this may be passed onto the RDBMS in case the relational engine can optimize the query given its read\-only nature. .SH SECURITY .sp Spring Python\(aqs Security module is based on \fI\%Acegi Security's\fP architecture. You can read \fI\%Acegi's detailed reference manual\fP for a background on this module. .IP Note . Spring Security vs. Acegi Security .sp At the time this module was implemented, Spring Security was still Acegi Security. Links include reference documentation that was used at the time to implement this security module. .RE .SS External dependencies .sp \fIspringpython.security.cherrypy3\fP package depends on \fI\%CherryPy 3\fP being installed prior to using it. Other than that, there are no specific external libraries required by Spring Python\(aqs security system, however the IoC configuration format that you\(aqll be using may need some, check IoC documentation for more details. .SS Shared Objects .sp The major building blocks of Spring Python Security are .INDENT 0.0 .IP \(bu 2 . \fISecurityContextHolder\fP, to provide any type access to the \fISecurityContext\fP. .IP \(bu 2 . \fISecurityContext\fP, to hold the Authentication and possibly request\-specific security information. .IP \(bu 2 . \fIHttpSessionContextIntegrationFilter\fP, to store the \fISecurityContext\fP in the HTTP session between web requests. .IP \(bu 2 . \fIAuthentication\fP, to represent the principal in an Acegi Security\-specific manner. .IP \(bu 2 . \fIGrantedAuthority\fP, to reflect the application\-wide permissions granted to a principal. .UNINDENT .sp These objects are needed for both authentication and authorization. .SS Authentication .sp The first level of security involves verifying your credentials. Most systems today use some type of username/password check. To configure Spring Python, you will need to configure one or more \fIAuthenticationProviders\fP. All \fIAuthentication\fP implementations are required to store an array of \fIGrantedAuthority\fP objects. These represent the authorities that have been granted to the principal. The GrantedAuthority objects are inserted into the \fIAuthentication\fP object by the \fIAuthenticationManager\fP and are later read by \fIAccessDecisionManagers\fP when making authorization decisions. These are chained together inside an \fIAuthenticationManager\fP. .SS AuthenticationProviders .SS DaoAuthenticationProvider .sp This \fIAuthenticationProvider\fP allows you to build a dictionary of user accounts, and is very handy for integration testing without resorting to complex configuration of 3rd party systems. .sp To configure this using \fBa pythonic, decorator\-based IoC container\fP: .sp .nf .ft C class SampleContainer(PythonConfig): @Object def inMemoryDaoAuthenticationProvider(self): provider = DaoAuthenticationProvider() provider.user_details_service = inMemoryUserDetailsService() return provider @Object def inMemoryUserDetailsService(self): user_details_service = InMemoryUserDetailsService() user_details_service.user_dict = { "vet1": ("password1", ["VET_ANY"], False), "bdavis": ("password2", ["CUSTOMER_ANY"], False), "jblack": ("password3", ["CUSTOMER_ANY"], False), "disableduser": ("password4", ["VET_ANY"], True), "emptyuser": ("", [], False) } return user_details_service .ft P .fi .sp XML configuration using \fBXMLConfig\fP: .sp .nf .ft C user1 password1 role1blue True user2 password2 role1orange True adminuser password3 role1admin True disableduser password4 role1blue False emptyuser True .ft P .fi .sp This is the user map defined for one of the test cases. The first user, user1, has a password of password1, a list of granted authorities ("role1", "blue"), and is enabled. The fourth user, "disableduser", has a password and a list of granted authorities, but is NOT enabled. The last user has no password, which will cause authentication to fail. .SS LDAP Authentication Provider .sp Spring Python has an \fILdapAuthenticationProvider\fP that is able to authenticate users against an LDAP server using either binding or password comparison. It will also search the LDAP server for groups in order to identify roles. .IP Note . Spring Python\(aqs LDAP only works with CPython .sp Currently, Spring Python only provides LDAP support for CPython. There is on\-going effort to extend support to Jython as well. .RE .sp It is possible to the customize the query parameters, as well as inject an alternative version of authentication as well as role identification. .sp There are two ways to verify a password in ldap: binding to the server using the password, or fetching the password from ldap and comparing outside the server. Spring Python supports both. You can choose which mechanism by injecting either a \fIBindAuthenticator\fP or a \fIPasswordComparisonAuthenticator\fP into \fILdapAuthenticationProvider\fP. .sp The following XML fragment demonstrates how to configure Spring Python\(aqs \fILdapAuthenticationProvider\fP using a \fIBindAuthenticator\fP combined with a \fIDefaultLdapAuthoritiesPopulator\fP: .sp .nf .ft C .ft P .fi .INDENT 0.0 .IP \(bu 2 . \fIcontext_source\fP \- points to an ldap server, defining the base DN to start searching for users and groups. .IP \(bu 2 . \fIbindAuthenticator\fP \- configured to use the context_source, and does a user search based on sub\-entry \fIuid={0},ou=people\fP. \fI{0}\fP is the variable where an entered username will be substituted before executing the ldap search. .IP \(bu 2 . \fIauthoritiesPopulator\fP \- assuming the user is found, it uses the group_search_filter to find groups containing this attribute pointed at the user\(aqs DN. .IP \(bu 2 . \fIldapAuthenticationProvider\fP \- combines together the bindAuthenticator and the authoritiesPopulator, in order to process a \fIUsernamePasswordAuthenticationToken\fP. .IP \(bu 2 . \fIldapAuthenticationManager\fP \- just like the other examples, this \fIAuthenticationManager\fP iterates over the list of providers, giving them a chance to authenticate the user. .UNINDENT .sp The following shows the same configuration in pure Python, using \fBPythonConfig\fP: .sp .nf .ft C class LdapContext(PythonConfig): def __init__(self): PythonConfig.__init__(self) @Object def context_source(self): return DefaultSpringSecurityContext(url="ldap://localhost:53389/dc=springframework,dc=org") @Object def bind_authenticator(self): return BindAuthenticator(self.context_source(), user_dn_patterns="uid={0},ou=people") @Object def authorities_populator(self): return DefaultLdapAuthoritiesPopulator(self.context_source(), group_search_filter="member={0}") @Object def provider(self): return LdapAuthenticationProvider(self.bind_authenticator(), self.authorities_populator()) @Object def manager(self): return AuthenticationManager(auth_providers=[self.provider()]) .ft P .fi .sp To use the password comparison mechanism with \fBXMLConfig\fP, substitute PasswordComparisonAuthenticator for BindAuthenticator as follows: .sp .nf .ft C .ft P .fi .sp The following block shows the same configuration using the pure Python container: .sp .nf .ft C class LdapContext(PythonConfig): def __init__(self): PythonConfig.__init__(self) @Object def context_source(self): return DefaultSpringSecurityContext(url="ldap://localhost:53389/dc=springframework,dc=org") @Object def password_authenticator(self): return PasswordComparisonAuthenticator(self.context_source(), user_dn_patterns="uid={0},ou=people") @Object def authorities_populator(self): return DefaultLdapAuthoritiesPopulator(self.context_source(), group_search_filter="member={0}") @Object def provider(self): return LdapAuthenticationProvider(self.password_authenticator(), self.authorities_populator()) @Object def manager(self): return AuthenticationManager(auth_providers=[self.provider()]) .ft P .fi .sp By default, \fIPasswordComparisonAuthenticator\fP handles SHA encrypted passwords as well passwords stored in plain text. However, you can inject a custom \fIPasswordEncoder\fP to support alternative password encoding schemes. .SS Future AuthenticationProviders .sp So far, Spring Python has implemented a DaoAuthenticationProvider than can link with any database or use an in\-memory user data structure, as well as an LdapAuthenticationProvider. Future releases should include: .INDENT 0.0 .IP \(bu 2 . \fIOpenIDAuthenticationProvider\fP .IP \(bu 2 . Anonymous authentication provider \- allows you to tag anonymous users, and constrain what they can access, even if they don\(aqt provide a password .UNINDENT .SS AuthenticationManager .sp An AuthenticationManager holds a list of one or more AuthenticationProvider\(aqs, and will go through the list when attempting to authenticate. PetClinic configures it like this using \fBPythonConfig\fP: .sp .nf .ft C class SampleContainer(PythonConfig): @Object def authenticationManager(self): return AuthenticationManager(auth_providers = [self.authenticationProvider()]) .ft P .fi .sp XML\-based configuration with :doc\(gaXMLConfig \(ga: .sp .nf .ft C .ft P .fi .sp This \fIAuthenticationManager\fP has a list referencing one object already defined in the \fIApplicationContext\fP, authenticationProvider. The authentication manager is supplied as an argument to the security interceptor, so it can perform checks as needed. .SS Authorization .sp After successful authentication, a user is granted various roles. The next step of security is to determine if that user is authorized to conduct a given operation or access a particular web page. The \fIAccessDecisionManager\fP is called by the \fIAbstractSecurityInterceptor\fP and is responsible for making final access control decisions. The \fIAccessDecisionManager\fP interface contains two methods: .sp .nf .ft C def decide(self, authentication, object, config) def supports(self, attr) .ft P .fi .sp As can be seen from the first method, the \fIAccessDecisionManager\fP is passed via method parameters all information that is likely to be of value in assessing an authorization decision. In particular, passing the secure object enables those arguments contained in the actual secure object invocation to be inspected. For example, let\(aqs assume the secure object was a \fIMethodInvocation\fP. It would be easy to query the \fIMethodInvocation\fP for any Customer argument, and then implement some sort of security logic in the \fIAccessDecisionManager\fP to ensure the principal is permitted to operate on that customer. Implementations are expected to throw an \fIAccessDeniedException\fP if access is denied. .sp Whilst users can implement their own \fIAccessDecisionManager\fP to control all aspects of authorization, Spring Python Security includes several \fIAccessDecisionManager\fP implementations that are based on voting. Using this approach, a series of \fIAccessDecisionVoter\fP implementations are polled on an authorization decision. The \fIAccessDecisionManager\fP then decides whether or not to throw an \fIAccessDeniedException\fP based on its assessment of the votes. .sp The \fIAccessDecisionVoter\fP interface has two methods: .sp .nf .ft C def supports(self, attr) def vote(self, authentication, object, config) .ft P .fi .sp Concrete implementations return an integer, with possible values being reflected in the \fIAccessDecisionVoter\fP static fields ACCESS_ABSTAIN, ACCESS_DENIED and ACCESS_GRANTED. A voting implementation will return ACCESS_ABSTAIN if it has no opinion on an authorization decision. If it does have an opinion, it must return either ACCESS_DENIED or ACCESS_GRANTED. .sp There are three concrete \fIAccessDecisionManagers\fP provided with Spring Python Security that tally the votes. The \fIConsensusBased\fP implementation will grant or deny access based on the consensus of non\-abstain votes. Properties are provided to control behavior in the event of an equality of votes or if all votes are abstain. The \fIAffirmativeBased\fP implementation will grant access if one or more ACCESS_GRANTED votes were received (ie a deny vote will be ignored, provided there was at least one grant vote). Like the \fIConsensusBased\fP implementation, there is a parameter that controls the behavior if all voters abstain. The UnanimousBased provider expects unanimous ACCESS_GRANTED votes in order to grant access, ignoring abstains. It will deny access if there is any ACCESS_DENIED vote. Like the other implementations, there is a parameter that controls the behavior if all voters abstain. .sp It is possible to implement a custom AccessDecisionManager that tallies votes differently. For example, votes from a particular \fIAccessDecisionVoter\fP might receive additional weighting, whilst a deny vote from a particular voter may have a veto effect. .sp Python Security. The \fIRoleVoter\fP class will vote if any config attribute begins with \fIROLE_\fP. It will vote to grant access if there is a \fIGrantedAuthority\fP which returns a string representation exactly equal to one or more config attributes starting with \fIROLE_\fP. If there is no exact match of any config attribute starting with \fIROLE_\fP, the \fIRoleVoter\fP will vote to deny access. If no config attribute begins with \fIROLE_\fP, the voter will abstain. \fIRoleVoter\fP is case sensitive on comparisons as well as the \fIROLE_\fP prefix. .sp PetClinic has two \fIRoleVoters\fP in its \fBPython\-config based\fP configuration: .sp .nf .ft C class SampleContainer(PythonConfig): @Object def vetRoleVoter(self): return RoleVoter(role_prefix="VET") @Object def customerRoleVoter(self): return RoleVoter(role_prefix="CUSTOMER") .ft P .fi .sp XML\-based configuration with XMLConfig: .sp .nf .ft C VET CUSTOMER .ft P .fi .sp The first one votes on VET authorities, and the second one votes on CUSTOMER authorities. .sp The other concrete \fIAccessDecisionVoter\fP is the \fILabelBasedAclVoter\fP. It can be seen in the test cases. Maybe later it will be incorporated into a demo. .sp Petclinic has a custom \fIAccessDecisionVoter\fP, which votes on whether a user "owns" a record: .sp .nf .ft C class SampleContainer(PythonConfig): ... @Object def ownerVoter(self): return OwnerVoter(controller = self.controller()) .ft P .fi .sp XML\-based configuration using \fBXMLConfig\fP: .sp .nf .ft C .ft P .fi .sp This class is wired in the PetClinic controller module as part of the sample, which demonstrates how easy it is to plugin your own custom security handler to this module. .sp PetClinic wires together these \fIAccessDecisionVoters\fP into an \fIAccessDecisionManager\fP: .sp .nf .ft C class SampleContainer(PythonConfig): @Object def accessDecisionManager(self): manager = AffirmativeBased() manager.allow_if_all_abstain = False manager.access_decision_voters = [self.vetRoleVoter(), self.customerRoleVoter(), self.ownerVoter()] return manager .ft P .fi .sp XML\-based configuration using \fBXMLConfig\fP: .sp .nf .ft C False .ft P .fi .SH REMOTING .sp 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\(aqt use AOP. However, it is very similar in the concept that you won\(aqt have to modify either your servers or your clients. .sp 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\(aqt 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. .sp 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. .SS External dependencies .sp Spring Python currently supports and requires the installation of at least one of the libraries: .INDENT 0.0 .IP \(bu 2 . \fI\%Pyro\fP (Python Remote Objects) \- a pure Python transport mechanism .IP \(bu 2 . \fIPyro4 \fP \- (Python Remote Object v.4) \- an updated version of the Pyro API. .IP \(bu 2 . \fI\%Hessian\fP \- support for Hessian has just started. So far, you can call Python\-to\-Java based on libraries released from Caucho. .UNINDENT .SS Remoting with PYRO (Python Remote Objects) .SS Decoupling a simple service, to setup for remoting .sp For starters, let\(aqs define a simple service: .sp .nf .ft C class Service(object): def get_data(self, param): return "You got remote data => %s" % param .ft P .fi .sp Now, we will create it locally and then call it: .sp .nf .ft C service = Service() print service.get_data("Hello") "You got remote data => Hello" .ft P .fi .sp 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\(aqs 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 \fIapplicationContext.xml\fP: .sp .nf .ft C .ft P .fi .sp The client code is changed to this: .sp .nf .ft C appContext = ApplicationContext(XMLConfig("applicationContext.xml")) service = appContext.get_object("service") print service.get_data("Hello") "You got remote data => Hello" .ft P .fi .sp 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. .SS Exporting a Spring Service Using \fBInversion Of Control\fP .sp In order to reach our service remotely, we have to export it. Spring Python provides \fIPyroServiceExporter\fP to export your service through Pyro. Add this to your application context: .sp .nf .ft C .ft P .fi .sp Three things have happened: .INDENT 0.0 .IP \(bu 2 . Our original service\(aqs object name has been changed to \fIremoteService\fP. .IP \(bu 2 . Another object was introduced called \fIservice_exporter\fP. It references object \fIremoteService\fP, and provides a proxied interface through a Pyro URL. .IP \(bu 2 . We created a client called \fIservice\fP. That is the same name our client code it looking for. It won\(aqt know the difference! .UNINDENT .SS Hostname/Port overrides .sp Pyro defaults to advertising the service at \fIlocalhost:7766\fP. However, you can easily override that by setting the \fIservice_host\fP and \fIservice_port\fP properties of the \fIPyroServiceExporter\fP object, either through setter or \fIconstructor injection\fP: .sp .nf .ft C .ft P .fi .sp 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. .sp Now when the client runs, it will fetch the \fIPyroProxyFactory\fP, 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! .IP Note . Python doesn\(aqt need an interface declaration for the client proxy .sp If you have used Spring Java\(aqs remoting client proxy beans, then you may be used to the idiom of specifying the interface of the client proxy. Due to Python\(aqs dynamic nature, you don\(aqt have to do this. .RE .sp We can now split up this application into two objects. Running the remote service on another server only requires us to edit the client\(aqs application context, changing the URL to get to the service. All without telling the client and server code. .SS Do I have to use XML? .sp No. Again, Spring Python provides you the freedom to do things using the IoC container, or programmatically. .sp To do the same configuration as shown above looks like this: .sp .nf .ft C from springpython.remoting.pyro import PyroServiceExporter from springpython.remoting.pyro import PyroProxyFactory # Create the service remoteService = Service() # Export it via Pyro using Spring Python\(aqs 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") .ft P .fi .sp Again, you can override the hostname/port values as well: .sp .nf .ft C # ... # Export it via Pyro using Spring Python\(aqs 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 machine\(aqs actual hostname service_exporter.service_port = 7000 service_exporter.after_properties_set() # ... .ft P .fi .sp That is effectively the same steps that the IoC container executes. .IP Note . Don\(aqt forget after_properties_set! .sp Since \fIPyroServiceExporter\fP is an \fIInitializingObject\fP, you must call \fIafter_properties_set\fP 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. .RE .SS Splitting up the client and the server .sp 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. .sp Copy the following into \fIserver.xml\fP: .sp .nf .ft C .ft P .fi .sp Copy the following into \fIserver.py\fP: .sp .nf .ft C 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")) .ft P .fi .sp Copy the following into \fIclient.xml\fP: .sp .nf .ft C .ft P .fi .sp Copy the following into \fIclient.py\fP: .sp .nf .ft C 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") .ft P .fi .sp 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: .sp .nf .ft C $ python server.py & [1] 20854 2009\-01\-08 12:06:20,021 \- springpython.container.ObjectContainer \- DEBUG \- === Scanning configuration 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 \(aqremoteService\(aq 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 \(aqremoteService\(aq in container\(aqs 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 \(aqservice_exporter\(aq in the singleton storage. 2009\-01\-08 12:06:20,027 \- springpython.context.ApplicationContext \- DEBUG \- Creating an instance of id=service_exporter props=[, , , ] 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 \(aqservice_exporter\(aq in container\(aqs 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 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 \(aqservice\(aq in the singleton storage. 2009\-01\-08 12:06:26,295 \- springpython.context.ApplicationContext \- DEBUG \- Creating an instance of id=service props=[] 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 \(aqservice\(aq in container\(aqs singleton storage CLIENT: You got remote data => Hello .ft P .fi .sp 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. .SS New support for Pyro 4 .sp Pyro has recently released a beta version of its overhauled API labeled \fIPyro 4\fP. This release of Spring Python includes support for it. The only changes you will need to make are: .INDENT 0.0 .IP \(bu 2 . replace \fBspringpython.remoting.pyro.PyroProxyFactory\fP with \fBspringpython.remoting.pyro.Pyro4ProxyFactory\fP .IP \(bu 2 . replace \fBspringpython.remoting.pyro.PyroServiceExporter\fP with \fBspringpython.remoting.pyro.Pyro4ServiceExporter\fP .IP \(bu 2 . replace any URI entries of \fBPYROLOC::/\fP with \fBPYRO:@:\fP .UNINDENT .IP Note . Pyro 4 is unstable and still in development. For proper usage, you must install at least version 4.2+. Their API is also subject to change, and we will try to keep up until it stabilizes. .RE .SS Remoting with Hessian .IP Note . Caucho\(aqs Python library for Hessian is incomplete .sp Due to minimal functionality provided by Caucho\(aqs Hessian library for Python, there is minimal documentation to show its functionality. .RE .sp The following shows how to connect a client to a Hessian\-exported service. This can theoretically be any technology. Currently, Java objects are converted intoPpython dictionaries, meaning that the data and transferred, but there are not method calls available: .sp .nf .ft C http://localhost:8080/ .ft P .fi .sp The Caucho library appears to only support Python being a client, and not yet as a service, so there is no \fIHessianServiceExporter\fP available yet. .SS High\-Availability/Clustering Solutions .sp 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: .sp .nf .ft C pool = [] for i in range(10): service_exporter = PyroServiceExporter(service_name = "ServiceName%s" % i, service = Service()) pool.append(service_exporter) .ft P .fi .sp (Yeah, I know, you can probably do this in one line with a list comprehension). .sp Now you have ten copies of the server running, each under a distinct name. .sp For any client, your configuration is a slight tweak: .sp .nf .ft C services = [] for i in range(10): services.append(PyroProxyFactory(service_url = "PYROLOC://localhost:7766/ServiceName%s" % i)) .ft P .fi .sp 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: .sp .nf .ft C 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") .ft P .fi .sp Notice how each call to the \fIHighAvailabilityService\fP class causes the internal index to increment and roll over. If a service doesn\(aqt 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. .SS Secure XML\-RPC .sp Spring Python extends Python’s built\-in XML\-RPC mechanims by adding the support for securing the communications path. You can choose whether to: .INDENT 0.0 .IP \(bu 2 . simply encrypt the link, .IP \(bu 2 . have server require a client certificate signed off by a given CA or a chain of CAs, .IP \(bu 2 . validate the client certificate’s fields, for instance you can configure the server to only allow requests if a commonName is equal to an upon agreed value .UNINDENT .sp Note that you can use both the client and the server with other XML\-RPC implementations, there’s nothing preventing you from exposing secure XML\-RPC to Java or .NET clients or from connecting with the secure client to XML\-RPC servers implemented in other languages and technologies. .sp To aid with better understanding of how the components work out of the box, you can download \fIsample keys and certificates\fP prepared by the Spring Python team. Be sure not to ever use the sample keys & certificates for anything serious outside your testing environment, they are working and functional but because of private keys being available for download they should only be used for learning of how Spring Python\(aqs secure XML\-RPC works. .SS Encrypted connection only [image] .sp The most basic setup which requires the server to have a private key and a certificate and the client to have a list (possibly consisting of one element only) of Certificate Authorities it is allowed to trust. Client will connect to server only if the server’s certificate has been signed off by given CAs. This is the most common way of performing SSL akin to what browsers do when connecting to secure online sites that don’t require a client certificate such as the majority of online banking sites. .sp In the code below the server exposes a Python’s built\-in pow function over encrypted XML\-RPC link and the client invokes it to get the result. Server uses its private key and a certificate which must have been signed off by one of CAs the client is aware of: .sp .nf .ft C # \-*\- coding: utf\-8 \-*\- # Spring Python from springpython.remoting.xmlrpc import SSLServer class MySSLServer(SSLServer): def __init__(self, *args, **kwargs): super(MySSLServer, self).__init__(*args, **kwargs) def register_functions(self): self.register_function(pow) host = "localhost" port = 8000 keyfile = "./server\-key.pem" certfile = "./server\-cert.pem" server = MySSLServer(host, port, keyfile, certfile) server.serve_forever() .ft P .fi .sp .nf .ft C # \-*\- coding: utf\-8 \-*\- # stdlib import ssl # Spring Python from springpython.remoting.xmlrpc import SSLClient server_location = "https://localhost:8000/RPC2" ca_certs = "./ca\-chain.pem" client = SSLClient(server_location, ca_certs) print client.pow(41, 3) .ft P .fi .SS Server requires the client to have a certificate [image] .sp Same as above but this time the client must authenticate itself using its own certificate which must have been signed off by one of CAs known to the server. Server is still required to have a certificate whose signing CAs need to be known to the client: .sp .nf .ft C # \-*\- coding: utf\-8 \-*\- # stdlib import ssl # Spring Python from springpython.remoting.xmlrpc import SSLServer class MySSLServer(SSLServer): def __init__(self, *args, **kwargs): super(MySSLServer, self).__init__(*args, **kwargs) def register_functions(self): self.register_function(pow) host = "localhost" port = 8000 keyfile = "./server\-key.pem" certfile = "./server\-cert.pem" ca_certs = "./ca\-chain.pem" server = MySSLServer(host, port, keyfile, certfile, ca_certs, cert_reqs=ssl.CERT_REQUIRED) server.serve_forever() .ft P .fi .sp .nf .ft C # \-*\- coding: utf\-8 \-*\- # Spring Python from springpython.remoting.xmlrpc import SSLClient server_location = "https://localhost:8000/RPC2" keyfile = "./client\-key.pem" certfile = "./client\-cert.pem" ca_certs = "./ca\-chain.pem" client = SSLClient(server_location, ca_certs, keyfile, certfile) print client.pow(41, 3) .ft P .fi .SS Server requires the client to have a certificate and checks its fields [image] .sp Same as above (both sides need to have certificates signed off by trusted CAs) but this time the server inspects the client certificate’s fields and lets it in only if they match the configuration it was fed with. In the example below \fIcommonName\fP must be \fIMy Client\fP, \fIorganizationName\fP must be \fIMy Company\fP and the \fIstateOrProvinceName\fP must be \fIMy State\fP. Server checks for both their existance and value and if there’s any mismatch the connection will be dropped (client will receive a socket error) and the error reason will be logged on the server side but no details of the error will be leaked to the client: .sp .nf .ft C # \-*\- coding: utf\-8 \-*\- # stdlib import logging import ssl # Spring Python from springpython.remoting.xmlrpc import SSLServer class MySSLServer(SSLServer): def __init__(self, *args, **kwargs): super(MySSLServer, self).__init__(*args, **kwargs) def register_functions(self): self.register_function(pow) host = "localhost" port = 8000 keyfile = "./server\-key.pem" certfile = "./server\-cert.pem" ca_certs = "./ca\-chain.pem" verify_fields = {"commonName": "My Client", "organizationName":"My Company", "stateOrProvinceName":"My State"} logging.basicConfig(level=logging.ERROR) server = MySSLServer(host, port, keyfile, certfile, ca_certs, cert_reqs=ssl.CERT_REQUIRED, verify_fields=verify_fields) server.serve_forever() .ft P .fi .sp .nf .ft C # \-*\- coding: utf\-8 \-*\- # Spring Python from springpython.remoting.xmlrpc import SSLClient server_location = "https://localhost:8000/RPC2" keyfile = "./client\-key.pem" certfile = "./client\-cert.pem" ca_certs = "./ca\-chain.pem" client = SSLClient(server_location, ca_certs, keyfile, certfile) print client.pow(41, 3) .ft P .fi .SS Sample keys and certificates .sp \fI\%The downloadable package\fP contains the keys and certificates of CAs, client and the server shown in the examples. It\(aqs crucial to remember that these are only samples with known private keys and they should \fBonly\fP be used for playing around with SSL XML\-RPC\(aqs API. [image] .sp \fIclient\-key.pem\fP and \fIclient\-cert.pem\fP are the client\(aqs private key and its certificate while \fIserver\-key.pem\fP and \fIserver\-cert.pem\fP are their counterparts as used by the server. Both certificates have been signed off by the \fISAMPLE Signing CA\fP whose certificate has been in turn signed off by the \fISAMPLE Root CA\fP. SAMPLE Root CA\(aqs certificate is self\-signed. Private keys of CAs are in files \fIca\-root\-key.pem\fP and \fIca\-signing\-key.pem\fP. Certificates of both CAs \- \fIca\-root\-cert.pem\fP & \fIca\-signing\-cert.pem\fP have been concatenated into a \fIca\-chain.pem\fP file so that they form a chain of the Certificate Authorities both sides may trust. All certificates are valid until 2020 so there\(aqs a lot of time for experimenting. Type \fB1234\fP if asked for any password, it\(aqs the same one for each private key. .SS Configuration .sp The two main classes to use in secure XML\-RPC communications are \fIspringpython.remoting.xmlrpc.SSLServer\fP and \fIspringpython.remoting.xmlrpc.SSLClient\fP both of which support a number of options discussed below. Keep in mind that those classes are thin wrappers around the base objects found in Python\(aqs standard library and as such they always accept all the default arguments of their super\-classes along with those specific to Spring Python\(aqs secure XML\-RPC implementation. .SS SSLServer .sp SSLServer is a subclass of Python\(aqs \fI\%SimpleXMLRPCServer.SimpleXMLRPCServer\fP which accepts arguments related to SSL in addition to those inherited from the base class. You expose XML\-RPC services by extending SSLServer in your own subclass which is required to override one method, \fIregister_functions\fP. \fIregister_functions\fP may in turn use \fIself.register_function\fP for exposing those methods that should be accessible via XML\-RPC, see \fI\%Python's documentation\fP for details of using \fIself.register_function\fP. .sp SSLServer.__init__\(aqs default arguments: .sp .nf .ft C class SSLServer(object, SimpleXMLRPCServer): def __init__(self, host=None, port=None, keyfile=None, certfile=None, ca_certs=None, cert_reqs=ssl.CERT_NONE, ssl_version=ssl.PROTOCOL_TLSv1, do_handshake_on_connect=True, suppress_ragged_eofs=True, ciphers=None, log_requests=True, **kwargs): .ft P .fi .INDENT 0.0 .IP \(bu 2 . \fIhost\fP \- interface to listen on, e.g. "localhost", .IP \(bu 2 . \fIport\fP \- port to listen on, e.g. 8000, .IP \(bu 2 . \fIkeyfile\fP \- path to a PEM\-encoded private key of the server, e.g. "./server\-key.pem", .IP \(bu 2 . \fIcertfile\fP \- path to a PEM\-encoded certificate of the server, e.g. "./server\-cert.pem", .IP \(bu 2 . \fIca_certs\fP \- path to a PEM\-encoded list (possibly one element long) of certificates of Certificate Authorities signing the certificates of clients you deal with, e.g. "./ca\-chain.pem", .IP \(bu 2 . \fIcert_reqs\fP \- whether the client is required to authenticate itself with a certificate, see \fI\%Python's documentation\fP for supported values, .IP \(bu 2 . \fIssl_version\fP \- the SSL/TLS version to use, see \fI\%Python's documentation\fP for supported values, note that the same value \fBmust\fP be used by the client application, .IP \(bu 2 . \fIdo_handshake_on_connect\fP \- \fI\%same as in Python\fP, .IP \(bu 2 . \fIsuppress_ragged_eofs\fP \- \fI\%same as in Python\fP, .IP \(bu 2 . \fIciphers\fP \- \fI\%same as in Python\fP, the value will be silently ignored if not running Python 2.7 or newer, .IP \(bu 2 . \fIlog_requests\fP \- whether requests should be logged on stdout, the value is actually passed directly to the request handler and that\(aqs why in current version it doesn\(aqt allow for any customization such as using different logging formats. To keep it compatible with Python, the value is accessible under a camelCase \fI.logRequests\fP attribute of an SSLServer object, .IP \(bu 2 . \fI**kwargs\fP \- an open\-ended list of keyword arguments, currently the only argument being recognized is \fIverify_fields\fP which must be a dictionary containing fields and values of the client certificate that must exist when the client\(aqs connecting. Fields names should be in the format given in \fI\%Appendix A of RFC 3280\fP, which means using long names instead of short ones (commonName not CN, organizationName not O, etc.), for instance, setting verify_fields to: .sp .nf .ft C {"commonName":"My Client", "localityName":"My Town"} .ft P .fi .sp will make sure the client certificate\(aqs subject has both commonName and localityName set and will also validate their respective values. The connection will not be accepted unless the fields and values match. .UNINDENT .sp Sample SSL XML\-RPC server which expects the client to use a certificate whose fields must match the configuration. The server exposes one method, \fIlistdir\fP: .sp .nf .ft C # \-*\- coding: utf\-8 \-*\- # stdlib import logging import os import ssl # Spring Python from springpython.remoting.xmlrpc import SSLServer class MySSLServer(SSLServer): def __init__(self, *args, **kwargs): super(MySSLServer, self).__init__(*args, **kwargs) def _listdir(self, path): return os.listdir(path) def register_functions(self): self.register_function(self._listdir, "listdir") host = "localhost" port = 8000 keyfile = "./server\-key.pem" certfile = "./server\-cert.pem" ca_certs = "./ca\-chain.pem" verify_fields = {"commonName": "My Client", "organizationName":"My Company", "stateOrProvinceName":"My State"} logging.basicConfig(level=logging.ERROR) server = MySSLServer(host, port, keyfile, certfile, ca_certs, cert_reqs=ssl.CERT_REQUIRED, verify_fields=verify_fields) server.serve_forever() .ft P .fi .SS SSLClient .sp SSLClient extends Python\(aqs built\-in \fI\%xmlrpclib.ServerProxy\fP class and, unlike \fISSLServer\fP, can be used directly without the need for subclassing. You can simply create an instance and start invoking server\(aqs methods. .sp SSLClient.__init__’s default arguments: .sp .nf .ft C class SSLClient(ServerProxy): def __init__(self, uri=None, ca_certs=None, keyfile=None, certfile=None, cert_reqs=ssl.CERT_REQUIRED, ssl_version=ssl.PROTOCOL_TLSv1, transport=None, encoding=None, verbose=0, allow_none=0, use_datetime=0, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, strict=None): .ft P .fi .INDENT 0.0 .IP \(bu 2 . \fIuri\fP \- address of the XML\-RPC server, e.g. "\fI\%https://localhost:8000/RPC2\fP", .IP \(bu 2 . \fIca_certs\fP \- path to a PEM\-encoded list (possibly one element long) containing certificates of Certificate Authorities the client is to trust; client will be establishing authenticity of the server\(aqs certificate against certificates from that file; e.g. "./ca\-chain.pem", .IP \(bu 2 . \fIkeyfile\fP \- path to a PAM\-encoded private key of the client, e.g. "./client\-key.pem", .IP \(bu 2 . \fIcertfile\fP \- path to a PAM\-encoded certificate of the client, e.g. "./client\-key.pem", .IP \(bu 2 . \fIcert_reqs\fP \- whether a server is required to have a certificate, see \fI\%Python's documentation\fP for supported values, .IP \(bu 2 . \fIssl_version\fP \- the SSL/TLS version to use, see \fI\%Python's documentation\fP for supported values, note that the same value \fBmust\fP be used by the server, .IP \(bu 2 . \fItransport\fP \- \fI\%same as in Python\fP, .IP \(bu 2 . \fIencoding\fP \- \fI\%same as in Python\fP, .IP \(bu 2 . \fIverbose\fP \- \fI\%same as in Python\fP, .IP \(bu 2 . \fIallow_none\fP \- \fI\%same as in Python\fP, .IP \(bu 2 . \fIuse_datetime\fP \- \fI\%same as in Python\fP, .IP \(bu 2 . \fItimeout\fP \- \fI\%same as in Python\fP, .IP \(bu 2 . \fIstrict\fP \- \fI\%same as in Python\fP .UNINDENT .sp Sample SSL XML\-RPC client which uses a private key and a certificate, can be used for invoking the \fIserver\fP shown in previous chapter: .sp .nf .ft C # \-*\- coding: utf\-8 \-*\- # Spring Python from springpython.remoting.xmlrpc import SSLClient server_location = "https://localhost:8000/RPC2" keyfile = "./client\-key.pem" certfile = "./client\-cert.pem" ca_certs = "./ca\-chain.pem" client = SSLClient(server_location, ca_certs, keyfile, certfile) print client.listdir("/home") .ft P .fi .SS Logging .SS SSLServer .sp Your subclass of SSLServer can be configured to use Python\(aqs standard \fI\%logging\fP module. Currently, logging events are emitted at \fIlogging.DEBUG\fP and \fIlogging.ERROR\fP levels. .sp At ERROR level all failed attempts at validating of client certificates will be logged giving the exact reason for the failure. Interal errors (should they ever happen) are also logged at the ERROR level. .sp When told to run at DEBUG level, in addition to information logged at the ERROR level, the server will also log details of each client\(aqs certificate received along with the IP address of a client application connecting. .sp A server also accepts a \fIlog_requests\fP boolean argument, defaulting to True, which is passed directly to the underlying stdlib\(aqs mechanisms. The flag tells whether client requests should be printed on stdout. .sp A sample SSL XML\-RPC server running with full verbosity turned on: .sp .nf .ft C # \-*\- coding: utf\-8 \-*\- # stdlib import logging import os import ssl # Spring Python from springpython.remoting.xmlrpc import SSLServer class MySSLServer(SSLServer): def __init__(self, *args, **kwargs): super(MySSLServer, self).__init__(*args, **kwargs) def _listdir(self, path): return os.listdir(path) def register_functions(self): self.register_function(self._listdir, "listdir") host = "localhost" port = 8000 keyfile = "./server\-key.pem" certfile = "./server\-cert.pem" ca_certs = "./ca\-chain.pem" verify_fields = {"commonName": "My Client", "organizationName":"My Company", "stateOrProvinceName":"My State"} log_format = "%(asctime)s \- %(levelname)s \- %(process)d \- %(threadName)s \- %(name)s \- %(message)s" formatter = logging.Formatter(log_format) handler = logging.StreamHandler() handler.setFormatter(formatter) logger = logging.getLogger("MySSLServer") logger.setLevel(level=logging.DEBUG) logger.addHandler(handler) server = MySSLServer(host, port, keyfile, certfile, ca_certs, cert_reqs=ssl.CERT_REQUIRED, verify_fields=verify_fields) server.serve_forever() .ft P .fi .SS SSLClient .sp Although SSLClient does define a self.logger object it isn\(aqt currently used internally in any situation (subject to change without notice so you shouldn\(aqt rely on the current status). On the other hand, as a subclass of \fI\%xmlrpclib.ServerProxy\fP, the client may be configured to run in a \fIverbose\fP mode which means all HTTP traffic will be printed onto \fIstandard output\fP. .sp A sample SSL XML\-RPC client configured to use the verbose mode: .sp .nf .ft C # \-*\- coding: utf\-8 \-*\- # Spring Python from springpython.remoting.xmlrpc import SSLClient server_location = "https://localhost:8000/RPC2" keyfile = "./client\-key.pem" certfile = "./client\-cert.pem" ca_certs = "./ca\-chain.pem" client = SSLClient(server_location, ca_certs, keyfile, certfile, verbose=1) print client.listdir("/home") .ft P .fi .SH JMS MESSAGING .sp Java Message Service has been a well known means for decoupling the Java application\(aqs parts or to provide an integration service for otherwise disconnected Java applications. Thanks to JMS being purely an API, Spring Python offers a way for connecting to JMS providers and to participate in JMS messaging scenarios. JMS messages sent and received by a Spring Python powered application are no different than messages produced and consumed by Java applications, in fact, \fIyou can use Spring Python and JMS with no Java applications participating in message exchanging at all\fP. .sp Spring Python works as a JMS client, you still need a JMS provider, the server part, for message brokering. The only JMS provider currently supported by Spring Python is IBM\(aqs WebSphere MQ, formerly known as MQSeries. .sp Although Spring Python\(aqs JMS API is loosely based on Spring Java\(aqs, it\(aqs not a direct port and features a highly Pythonic look and feel. [image] .IP Note . Througout the chapter pure Python code or YAML syntax is used to illustrate the support for JMS however it only represents the author\(aqs preferences and it\(aqs worth noting that you can use \fBany of Spring Python\(aqs formats\fP to configure the IoC container. Or you can use no IoC at all as it\(aqs a completely optional feature and one that\(aqs not strictly required by JMS. .RE .SS Introduction .sp JMS messaging with Spring Python revolves around the idea of using a connection factory for obtaining a connection to a JMS provider and \fIspringpython.jms.core.JmsTemplate\fP as a means for sending and receiving messages. A JmsTemplate instance is tied to a connection factory however a single connection factory may be safely reused across multiple JmsTemplates. .sp In addition to that, \fIspringpython.jms.listener.SimpleMessageListenerContainer\fP allows for a purely configuration\-driven way to set up background JMS listeners to receive messages from JMS providers. .SS Dependencies .sp Support for JMS messaging with WebSphere MQ is built on top of the CPython\-only PyMQI library which provides Python applications an access to WebSphere MQ queue managers. You need to separately install PyMQI in order to use \fIspringpython.jms.factory.WebSphereMQConnectionFactory\fP. PyMQI, in turn, needs \fIa WebSphere MQ client\fP, a runtime library which may be freely downloaded from IBM\(aqs site. .sp \fIspringpython.jms.listener.SimpleMessageListenerContainer\fP, a Spring Python component which helps with running background JMS listeners, requires the installation of \fI\%Circuits 1.2+\fP and \fI\%threadpool 1.2.7 or newer\fP. .SS Quick start .sp Here\(aqs a few quick examples that will get you started with Spring Python and JMS. Both Python code and IoC with YAML syntax are shown. It\(aqs assumed there\(aqs a QM.1 queue manager running on host 192.168.1.121 with its listener on port 1434 and connections are made through the server connection channel SVRCONN.1 to queues TEST.1 and TEST.2. .SS Sending .sp First, let\(aqs send a message using nothing but pure Python code: .sp .nf .ft C from springpython.jms.core import JmsTemplate from springpython.jms.factory import WebSphereMQConnectionFactory qm_name = "QM.1" channel = "SVRCONN1.1" host = "192.168.1.121" listener_port = "1434" queue1 = "TEST.1" # The connection factory we\(aqre going to use. factory = WebSphereMQConnectionFactory(qm_name, channel, host, listener_port) # Every JmsTemplate uses a connection factory for actually communicating with a JMS provider. jms_template = JmsTemplate(factory) # And that\(aqs it, now we put the mandatory "Hello world" message on a queue. jms_template.send("Hello world", queue1) # We\(aqre not using an IoC so we must shut down the connection factory ourselves. factory.destroy() .ft P .fi .sp Now do the same but use an IoC container configured via \fBspringpython.config.YamlConfig\fP. The configuration should be saved in a "jms\-context.yml" file in the same directory the Python code using it will be saved in: .sp .nf .ft C objects: \- object: MyConnectionFactory class: springpython.jms.factory.WebSphereMQConnectionFactory properties: queue_manager: QM.1 channel: SVRCONN.1 host: 192.168.1.121 listener_port: "1434" \- object: MyTemplate class: springpython.jms.core.JmsTemplate properties: factory: {ref: MyConnectionFactory} \- object: MyQueue str: TEST.1 .ft P .fi .sp And the Python code using the above IoC configuration: .sp .nf .ft C from springpython.context import ApplicationContext from springpython.config import YamlConfig container = ApplicationContext(YamlConfig("./jms\-context.yml")) # Read the objects definitions from configuration. queue1 = container.get_object("MyQueue") jms_template = container.get_object("MyTemplate") # Send the message. jms_template.send("Hello world", queue1) # The connection factory is now being managed by the IoC container which takes # care of shutting down the factory. No need for manually destroying it. .ft P .fi .sp An obvious change is that the configuration is now kept separately from the implementation but another advantage is that the container will shut down the connection factory on itself as \fIspringpython.jms.factory.WebSphereMQConnectionFactory\fP is a subclass of \fIspringpython.context.DisposableObject\fP which means its .destroy method will be executed when the container will be shutting down. .SS Receiving .sp The very same connection factory and JmsTemplate can be used for both sending and receiving. Examples below use the same definitions of objects as the sending examples do, they are repeated here for the sake of completness: .sp .nf .ft C from springpython.jms.core import JmsTemplate from springpython.jms.factory import WebSphereMQConnectionFactory qm_name = "QM.1" channel = "SVRCONN.1" host = "192.168.1.121" listener_port = "1434" queue1 = "TEST.1" # The connection factory we\(aqre going to use. factory = WebSphereMQConnectionFactory(qm_name, channel, host, listener_port) # Every JmsTemplate uses a connection factory for actually communicating with a JMS provider. jms_template = JmsTemplate(factory) # Get a message off the queue. The call to receive will by default time out # after 1000ms and raise springpython.jms.NoMessageAvailableException then. jms_template.receive(queue1) # We\(aqre not using an IoC so we need to shut down the connection factory ourselves. factory.destroy() .ft P .fi .sp And here\(aqs a complementary example showing the usage of \fBYamlConfig\fP. The configuration should be saved in a "jms\-context.yml" file in the same directory the Python code using it will be saved in. Note that it\(aqs the same configuration that was used in the sending example: .sp .nf .ft C objects: \- object: MyConnectionFactory class: springpython.jms.factory.WebSphereMQConnectionFactory properties: queue_manager: QM.1 channel: SVRCONN.1 host: 192.168.1.121 listener_port: "1434" \- object: MyTemplate class: springpython.jms.core.JmsTemplate properties: factory: {ref: MyConnectionFactory} \- object: MyQueue str: TEST.1 .ft P .fi .sp The Python code used for receiving a message from a queue configured using the \fBYamlConfig\fP: .sp .nf .ft C from springpython.context import ApplicationContext from springpython.config import YamlConfig container = ApplicationContext(YamlConfig("./jms\-context.yml")) # Read the objects definitions from configuration queue1 = container.get_object("MyQueue") jms_template = container.get_object("MyTemplate") # Get a message off the queue. The call to receive will by default time out # after 1000ms and raise springpython.jms.NoMessageAvailableException then. jms_template.receive(queue1) # The connection factory is now being managed by the IoC container which takes # care of shutting down the factory. No need for manually destroying it. .ft P .fi .sp Here\(aqs a sample YAML context utilizing the SimpleMessageListenerContainer component and an accompanying Python code using it. As you can see, a mere fact of providing the configuration allows for receiving the messages: .sp .nf .ft C objects: \- object: connection_factory class: springpython.jms.factory.WebSphereMQConnectionFactory properties: queue_manager: QM.1 channel: SVRCONN.1 host: 192.168.1.121 listener_port: "1434" \- object: message_handler class: app.MyMessageHandler \- object: listener_container class: springpython.jms.listener.SimpleMessageListenerContainer properties: factory: {ref: connection_factory} handler: {ref: message_handler} destination: TEST.1 .ft P .fi .sp .nf .ft C # app.py from springpython.config import YamlConfig from springpython.context import ApplicationContext class MyMessageHandler(object): def handle(self, message): print "Got message!", message if __name__ == "__main__": # Obtaining a context will automatically start the SimpleMessageListenerContainer and its listeners in background. container = ApplicationContext(YamlConfig("./context.yml")) while True: # Here goes the application\(aqs logic. Any JMS messages, as configured # in ./context.yml, will be passed in to a singleton MyMessageHandler instance. pass .ft P .fi .SS Connection factories .SS WebSphereMQConnectionFactory .sp \fIspringpython.jms.factory.WebSphereMQConnectionFactory\fP implements access to WebSphere MQ JMS provider. Along with \fIJmsTemplate\fP and \fISimpleMessageListenerContainer\fP it\(aqs the class you\(aqll be most frequently using for sending and receiving of messages. .sp Each \fIWebSphereMQConnectionFactory\fP object will hold at most one connection to WebSphere MQ, which will be lazily established when it\(aqll be actually needed, e.g. when a message will need to be put on a queue for the first time. The connection will always be started in WebSphere MQ\(aqs client mode, there\(aqs no support for connecting in the bindings mode. .sp Like all Spring Python\(aqs classes \fIWebSphereMQConnectionFactory\fP can be configured using pure Python or you can use Spring Python\(aqs IoC to separate your business code from configuration. Using IoC has an added benefit of taking care of destroying any open queues and closing the connection when the IoC shuts down \- we\(aqll get to it in a moment. .sp \fIWebSphereMQConnectionFactory\fP provides several options that let you customize its behaviour and apart from the obvious ones which you must provide (like, the queue manager\(aqs host) all other options have sensible defaults which you\(aqll rarely need to change, if at all. .sp Here\(aqs a full initializer method reproduced for convenience and the explanation of default values used: .sp .nf .ft C def __init__(self, queue_manager=None, channel=None, host=None, listener_port=None, cache_open_send_queues=True, cache_open_receive_queues=True, use_shared_connections=True, dynamic_queue_template="SYSTEM.DEFAULT.MODEL.QUEUE", ssl=False, ssl_cipher_spec=None, ssl_key_repository=None): .ft P .fi .TS center; |l|l|. _ T{ \fBqueue_manager\fP T} T{ default: None .sp \fIMust be set manually\fP .sp Name of the queue manager, e.g. EAI.QM.1 T} _ T{ _ T{ _ T{ \fBchannel\fP T} T{ default: None .sp \fIMust be set manually\fP .sp Name of a server connection (SVRCONN) channel through which the connection will be established, e.g. EAI.SVRCONN.1 T} _ T{ _ T{ _ T{ \fBhost\fP T} T{ default: None .sp \fIMust be set manually\fP .sp Host name or IP on which the queue manager is running, e.g. 192.168.1.103 T} _ T{ _ T{ _ T{ \fBlistener_port\fP T} T{ default: None .sp \fIMust be set manually\fP .sp Port on which the queue manager\(aqs listener is accepting TCP connections, e.g. 1434 T} _ T{ _ T{ _ T{ \fBcache_open_send_queues\fP T} T{ default: True .sp By default, \fIWebSphereMQConnectionFactory\fP will keep references to open queues in a cache for later re\-use. This speeds\-up most operations as there\(aqs usually no need for closing a queue if it\(aqs going to be used in subsequent calls to queue manager. At times however, it\(aqs prefered to close the queues as soon as possible and \fIcache_open_send_queues\fP controls whether queues open for putting the messages on are to be kept in the cache. T} _ T{ _ T{ \fBcache_open_receive_queues\fP T} T{ default: True .sp This setting controls whether queues open for receving of messages should be kept in a cache. If set to False, they will be closed after the call to get a message off the queue will have finished. T} _ T{ _ T{ \fBuse_shared_connections\fP T} T{ default: True .sp A single WebSphereMQConnectionFactory object may be shared between multiple threads to provide better performance. This setting allows for marking the underlying connection to a queue manager as a non\-shareable and makes sure that only one thread will be able to use it, any call to the factory from a thread that didn\(aqt open the connection will result \fIin a springpython.jms.JMSException\fP being raised. The setting should only set to False when connecting to queue managers running on z/OS systems as it otherwise can hurt the performance of multi\-threaded applications. It has no impact on performance of single\-threaded applications. T} _ T{ _ T{ \fBdynamic_queue_template\fP T} T{ default: SYSTEM.DEFAULT.MODEL.QUEUE .sp The name of a model queue basing on which the dynamic queues will be created. It is usually desirable to override the default value as, unless customized, SYSTEM.DEFAULT.MODEL.QUEUE is a non\-shared (NOSHARE in MQ speak) queue and doesn\(aqt allow for opening the dynamic queues for both sending and receiving. T} _ T{ _ T{ \fBssl\fP T} T{ default: False .sp A boolean value which indicates whether connections to the queue manager should use a client SSL/TLS certificate. \fIssl_cipher_spec\fP and \fIssl_key_repository\fP must also be provided if \fIssl\fP is True. T} _ T{ _ T{ \fBssl_cipher_spec\fP T} T{ default: None .sp An SSL/TLS cipher spec to use for encrypted connections, its value must be equal to that of the MQ SVRCONN channel\(aqs SSLCIPH attribute. T} _ T{ _ T{ \fBssl_key_repository\fP T} T{ default: None .sp On\-disk location of an SSL/TLS client certificates repository. The repository must be of type CMS, such a repository can be created using gsk6cmd/gsk7cmd command line tools. Note that the value of this attribute must not contain a suffix; for instance, if there are following files in /var/mqm/security: client\-repo.crl, client\-repo.kdb, client\-repo.rdb and client\-repo.sth, then ssl_key_repository must be set to "/var/mqm/security/client\-repo". T} _ T{ _ .TE .sp Here\(aqs an example of programatically creating a \fIWebSphereMQConnectionFactory\fP object: .sp .nf .ft C from springpython.jms.factory import WebSphereMQConnectionFactory qm_name = "QM.1" channel = "SVRCONN.1" host = "192.168.1.121" listener_port = "1434" factory = WebSphereMQConnectionFactory(qm_name, channel, host, listener_port) # ... use factory here. # Always destroy the factory when not using an IoC container. factory.destroy() .ft P .fi .sp An example of using YamlConfig for configuring WebSphereMQConnectionFactory inside of an IoC container: .sp .nf .ft C objects: \- object: MyConnectionFactory class: springpython.jms.factory.WebSphereMQConnectionFactory properties: queue_manager: QM.1 channel: SVRCONN.1 host: 192.168.1.121 listener_port: "1434" .ft P .fi .sp All cached queues will not be closed by a factory until after its .destroy will have been called which will happen automatically if you\(aqre using an IoC container. If the factory is configured programatically in Python you must call .destroy yourself in your code. A call to .destroy also closes the factory\(aqs connection to a queue manager. .sp \fIWebSphereMQConnectionFactory\fP objects are thread\-safe and may be shared between multiple threads if the queue manager supports sharing a single connection which is the case on all platforms except for z/OS. .IP Note . For the curious one .sp \fIspringpython.jms.factory.WebSphereMQConnectionFactory\fP and \fIspringpython.jms.factory.MQRFH2JMS\fP wrap the WebSphere MQ\(aqs native MQRFH2 wire\-level format in a set of Python classes and hide any intricate details of communicating with queue managers. From the programmer\(aqs viewpoint, \fIMQRFH2JMS\fP is irrelevant, however it might be of interest to anyone willing to improve or expand Spring Python\(aqs JMS support. .RE .SS JmsTemplate .sp \fIspringpython.jms.core.JmsTemplate\fP is the class to use for sending JMS messages; along with \fISimpleMessageListenerContainer\fP it may also be used in order to receive them. A template must be associated with a connection factory and once configured, may be used for communicating in both directions. It\(aqs up to you to decide whether in your circumstances it makes sense to reuse a single template for all communications, to have a single template for each queue involved or perhaps to use separate, dedicated, templates, one for sending and one for receiving. Note however that \fBJmsTemplate instances are not guaranteed to be thread\-safe\fP and no attempt has been made to make them be so. .sp Remember that factories postpone connecting to a queue manager and creating a JmsTemplate instance doesn\(aqt necessarily mean there will be no connection errors when it will be first time used for sending or receiving. .sp Here\(aqs how a JmsTemplate may be instantiated using Python code: .sp .nf .ft C from springpython.jms.core import JmsTemplate from springpython.jms.factory import WebSphereMQConnectionFactory qm_name = "QM.1" channel = "SVRCONN1.1" host = "192.168.1.121" listener_port = "1434" factory = WebSphereMQConnectionFactory(qm_name, channel, host, listener_port) jms_template = JmsTemplate(factory) # Always destroy the factory when not using IoC factory.destroy() .ft P .fi .sp An example of using YamlConfig to configure a JmsTemplate: .sp .nf .ft C objects: \- object: MyConnectionFactory class: springpython.jms.factory.WebSphereMQConnectionFactory properties: queue_manager: QM.1 channel: SVRCONN.1 host: 192.168.1.121 listener_port: "1434" \- object: jms_template class: springpython.jms.core.JmsTemplate properties: factory: {MyConnectionFactory} .ft P .fi .sp JmsTemplate allows for a number of options to customize its behaviour. The only options required to set manually is the factory parameter. Except for factory, all the parameters may be overriden by individual calls to sending or receiving of messages: .sp .nf .ft C def __init__(self, factory=None, delivery_persistent=None, priority=None, time_to_live=None, message_converter=None, default_destination=None): .ft P .fi .TS center; |l|l|. _ T{ \fBfactory\fP T} T{ default: None .sp \fIMust be set manually\fP .sp A JMS connection factory associated with this JmsTemplate. T} _ T{ _ T{ _ T{ \fBdelivery_persistent\fP T} T{ default: None .sp Tells whether messages sent to a JMS provider are by default persistent. If not set, the persistency of messages is controlled on a per messages basis (and defaults to a persistent delivery). T} _ T{ _ T{ \fBpriority\fP T} T{ default: None .sp Messages sent to the provider may be of different priority, usually on a scale from 1 to 9. The setting controls the default priority of all messages sent by this JmsTemplate, unless overridden by individual messages. A JMS provider will set the default priority if no value is given here or when sending the individual messages. T} _ T{ _ T{ \fBtime_to_live\fP T} T{ default: None .sp JMS allows for expiry of messages after a certain time \fIexpressed in milliseconds\fP. The time to live of a message may be set here and it will be applied to all messages sent or can be set per each message sent. If no value is provided here and when sending the message to a destination, the message expiry time is left to the discretion of a JMS provider. T} _ T{ _ T{ \fBmessage_converter\fP T} T{ default: None .sp It is sometimes desirable to not have to deal with raw messages taken from or sent to JMS provider from within a JmsTemplate object, it may make more sense to delegate converting the objects from and to JMS representation to an external helper class. A message converter is an object that helps decoupling the domain objects from the fact that JMS is the transportation layer used for communicating. A single converter may be used for converting the incoming as well as outgoing messages. See the section on \fImessage converters\fP for more details and code examples. Setting the message converter here will take precedence over setting it on a per\-message basis. T} _ T{ _ T{ \fBdefault_destination\fP T} T{ default: None .sp It is sometimes desirable to not have to deal with raw messages taken from or sent to JMS provider from within a JmsTemplate object, it may make more sense to delegate converting the objects from and to JMS representation to an external helper class. A message converter is an object that helps decoupling the domain objects from the fact that JMS is the transportation layer used for communicating. A single converter may be used for converting the incoming as well as outgoing messages. See the section on message converters for more details and code examples. Setting the message converter here will take precedence over setting it on a per\-message basis. T} _ T{ _ .TE .SS Sending .sp The basic approach is to send ASCII strings or unicode objects, which must allow for encoding into UTF\-8: .sp .nf .ft C # \-*\- coding: utf\-8 \-*\- from springpython.jms.core import JmsTemplate from springpython.jms.factory import WebSphereMQConnectionFactory qm_name = "QM.1" channel = "SVRCONN.1" host = "192.168.1.121" listener_port = "1434" queue1 = "TEST.1" # The connection factory we\(aqre going to use. factory = WebSphereMQConnectionFactory(qm_name, channel, host, listener_port) # Every JmsTemplate uses a connection factory for actually communicating with a JMS provider. jms_template = JmsTemplate(factory) jms_template.default_destination = queue1 # Send some ASCII jms_template.send("Hi, Spring Python here") # Send unicode jms_template.send(u"Cześć, z tej strony Spring Python") # We\(aqre not using an IoC so we need to shut down the connection factory ourselves. factory.destroy() .ft P .fi .sp Note that in an example above the message\(aqs destination has been taken from JmsTemplate. We can also specify it on send time or we can combine both approaches, like here: .sp .nf .ft C # \-*\- coding: utf\-8 \-*\- from springpython.jms.core import JmsTemplate from springpython.jms.factory import WebSphereMQConnectionFactory qm_name = "QM.1" channel = "SVRCONN.1" host = "192.168.1.121" listener_port = "1434" queue1 = "TEST.1" queue2 = "TEST.2" # The connection factory we\(aqre going to use. factory = WebSphereMQConnectionFactory(qm_name, channel, host, listener_port) # Every JmsTemplate uses a connection factory for actually communicating with a JMS provider. jms_template = JmsTemplate(factory) jms_template.default_destination = queue1 # Send some ASCII to one queue jms_template.send("Hi, Spring Python here") # Send unicode to another queue jms_template.send(u"Cześć, z tej strony Spring Python", queue2) # We\(aqre not using an IoC so we need to shut down the connection factory ourselves. factory.destroy() .ft P .fi .sp Sending is not limited to strings or unicode objects though. You can customize a lot of message\(aqs properties by sending a \fIspringpython.jms.core.TextMessage\fP instead. The following example shows how a custom message ID and reply to destination can be specified for an outgoing message: .sp .nf .ft C # stdlib from uuid import uuid4 # Spring Python from springpython.jms.core import JmsTemplate, TextMessage from springpython.jms.factory import WebSphereMQConnectionFactory qm_name = "QM.1" channel = "SVRCONN.1" host = "192.168.1.121" listener_port = "1434" queue1 = "TEST.1" # The connection factory we\(aqre going to use. factory = WebSphereMQConnectionFactory(qm_name, channel, host, listener_port) # Every JmsTemplate uses a connection factory for actually communicating with a JMS provider. jms_template = JmsTemplate(factory) jms_template.default_destination = queue1 # Generate the correlation ID jms_correlation_id = uuid4().hex message = TextMessage("Hi, Spring Python here") message.jms_correlation_id = jms_correlation_id message.jms_reply_to = "REPLY.TO.QUEUE" # Send the message jms_template.send(message) # We\(aqre not using an IoC so we need to shut down the connection factory ourselves. factory.destroy() .ft P .fi .sp Using TextMessage instances instead of plain strings or unicode objects is also recommended when you\(aqre interested in values a JMS provider has given to JMS properties of a message after the message had been sent. Here you can see the values which were assigned automatically by the provider to jms_timestamp and jms_message_id properties: .sp .nf .ft C from springpython.jms.core import JmsTemplate, TextMessage from springpython.jms.factory import WebSphereMQConnectionFactory qm_name = "QM.1" channel = "SVRCONN.1" host = "192.168.1.121" listener_port = "1434" queue1 = "TEST.1" # The connection factory we\(aqre going to use. factory = WebSphereMQConnectionFactory(qm_name, channel, host, listener_port) # Every JmsTemplate uses a connection factory for actually communicating with a JMS provider. jms_template = JmsTemplate(factory) jms_template.default_destination = queue1 # Create a TextMessage instance. message = TextMessage("Hi, Spring Python here") # Send the message jms_template.send(message) print "jms_timestamp = %s" % message.jms_timestamp print "jms_message_id = %s" % message.jms_message_id # We\(aqre not using an IoC so we need to shut down the connection factory ourselves. factory.destroy() # # Shows the following here: # # $ python jms_properties_overriding.py # jms_timestamp = 1255885622380 # jms_message_id = ID:414d5120514d2e312020202020202020283cdb4a02220020 # $ .ft P .fi .sp Take a look here for more information about how to use \fITextMessages\fP. .SS Receiving .sp The same JmsTemplate instance may be used for both sending and receiving of messages. When you receive messages you may optionally provide a timeout value in milliseconds after exceeding which a \fIspringpython.jms.NoMessageAvailableException\fP will be raised if no message will have been available for a given JMS destination. Default timeout is 1000 milliseconds. .sp JmsTemplate may use a default JMS destination for each call to .receive or you can explicitly specify the destination\(aqs name when you receive messages: .sp .nf .ft C from springpython.jms.core import JmsTemplate, TextMessage from springpython.jms.factory import WebSphereMQConnectionFactory qm_name = "QM.1" channel = "SVRCONN.1" host = "192.168.1.121" listener_port = "1434" queue1 = "TEST.1" queue2 = "TEST.2" # The connection factory we\(aqre going to use. factory = WebSphereMQConnectionFactory(qm_name, channel, host, listener_port) # Every JmsTemplate uses a connection factory for actually communicating with a JMS provider. jms_template = JmsTemplate(factory) jms_template.default_destination = queue1 # Send a message to the first queue which is a default destination .. jms_template.send("Hi there!") # .. and now receive it. print jms_template.receive() # Now send a message to the second one .. jms_template.send("Hi there again!", queue2) # .. and now receive it .. print jms_template.receive(queue2) # .. try to receive a message again, this time requesting a timeout of 2 seconds. print jms_template.receive(queue2, 2000) # We\(aqre not using an IoC so we need to shut down the connection factory ourselves. factory.destroy() .ft P .fi .sp Note that \fISimpleMessageListenerContainer\fP provides a complementary way for receiving the messages, particularly well suited for long\-running processes, such as servers. .SS Dynamic queues .sp A dynamic queue is a usually short\-lived object created on\-demand by JMS applications, most often found in request\-reply scenarios when there\(aqs no need for the response to be persistently stored. An application initiating the communication will create a dynamic temporary queue, send the request to the other side providing the name of the dynamic queue as a destination for the responses to be sent to and wait for a certain amount of time. \fIWith Spring Python and WebSphere MQ, the requesting side must then explicitly close the dynamic queue\fP regardless of whether the response will be received or if the request timeouts. .sp The following example shows two JmsTemplate objects communicating via a dynamic queue and imitating an exchange of messages between two dispersed applications. You can observe than from the responding application\(aqs point of view a dynamic queue\(aqs name is like any other queue\(aqs name, the application doesn\(aqt need to be \- and indeed isn\(aqt \- aware that it\(aqs responding to a dynamic queue and not to a predefined one. For the requesting end a dynamic queue is also like a regular queue in that its name must be provided to the JmsTemplate\(aqs .receive method. Note that WebSphere MQ allows only non\-persistent messages to be put on \fItemporary\fP dynamic queues which are the kind of dynamic queues you get by default with Spring Python: .sp .nf .ft C from springpython.jms import DELIVERY_MODE_NON_PERSISTENT from springpython.jms.core import JmsTemplate, TextMessage from springpython.jms.factory import WebSphereMQConnectionFactory qm_name = "QM.1" channel = "SVRCONN.1" host = "192.168.1.121" listener_port = "1434" exchange_queue = "TEST.1" # The connection factory we\(aqre going to use. factory = WebSphereMQConnectionFactory(qm_name, channel, host, listener_port) requesting_side = JmsTemplate(factory) requesting_side.default_destination = exchange_queue responding_side = JmsTemplate(factory) responding_side.default_destination = exchange_queue # Create a dynamic queue. dyn_queue_name = requesting_side.open_dynamic_queue() # Note that we wrap the whole conversation in a try/finally block as we must # always close a WebSphere MQ dynamic queue. try: # Create a request message. message = TextMessage("Hey, what\(aqs up on the other side?") # WebSphere MQ messages sent to dynamic temporary queues must not # be persistent. message.jms_delivery_mode = DELIVERY_MODE_NON_PERSISTENT # Tell the other side where to send responses. message.jms_reply_to = dyn_queue_name # Send the request requesting_side.send(message) # Receive the request .. request = responding_side.receive() # .. prepare the response .. response = TextMessage("A bit stormy today!") response.jms_delivery_mode = DELIVERY_MODE_NON_PERSISTENT # .. and send our response to a jms_reply_to destination which as we know # is a dynamic queue in this example. responding_side.send(response, request.jms_reply_to) # Receive the response. It\(aqs being read as usual, as from any other queue, # there\(aqs no special JmsTemplate\(aqs method for getting messages # off dynamic queues. print requesting_side.receive(dyn_queue_name) finally: requesting_side.close_dynamic_queue(dyn_queue_name) # We\(aqre not using an IoC so we need to shut down the connection factory ourselves. factory.destroy() .ft P .fi .sp It\(aqs worth mentioning again that you must close WebSphere MQ dynamic queues yourself as Spring Python won\(aqt do that for you \- it\(aqs a slight deviation from how Java JMS works. .SS Message converters .sp It\(aqs quite possible that you\(aqll like to separate the code responsible for core JMS communication with outside systems from the logic needed for converting your business domain\(aqs objects back and forth to strings needed for passing into JmsTemplate\(aqs methods. You may utilize your own converting classes for it or you can use the Spring Python\(aqs converters for such a work. A converter is a subclass of \fIspringpython.jms.core.MessageConverter\fP which must implement at least one of the \fIto_message\fP or \fIfrom_message\fP methods. There\(aqs nothing magical about MessageConverter objects and they won\(aqt do any automatic convertions for you, they\(aqre just interfaces you can implement as you\(aqll likely need some sort of separation between the objects you deal with and the JMS API. .sp There\(aqs one difference you must take into account when using message converters \- you don\(aqt use the standard send and receive methods but dedicated \fIconvert_and_send\fP and \fIreceive_and_convert\fP ones. Other than that, the JMS API and features are exactly the same. .sp The code below shows a sample usage of MessageConverters. Note that you don\(aqt need to implement both \fIto_message\fP and \fIfrom_message\fP if that\(aqs not appropriate in your situation however it makes sense for the example below to handle requests and responses using only one converter object: .sp .nf .ft C from springpython.jms.factory import WebSphereMQConnectionFactory from springpython.jms.core import JmsTemplate, MessageConverter, TextMessage qm_name = "QM.1" channel = "SVRCONN.1" host = "192.168.1.121" listener_port = "1434" # Note that it\(aqs the same queue so we\(aqre going to later receive the same invoice we sent. request_queue = response_queue = "TEST.1" # One of the business domain\(aqs objects our application deals with. class Invoice(object): def __init__(self, customer_account_id=None, month=None, amount=None): self.customer_account_id = customer_account_id self.month = month self.amount = amount def __str__(self): return "<%s at %s, customer_account_id=%s, month=%s, amount=%s>" % ( self.__class__.__name__, hex(id(self)), self.customer_account_id, self.month, self.amount) # Let\(aqs imagine the other side of a JMS link wants to receive and send CSV data. class InvoiceConverter(MessageConverter): def to_message(self, invoice): """ Converts a business object to CSV. """ text = ";".join((invoice.customer_account_id, invoice.month, invoice.amount)) return TextMessage(text) def from_message(self, message): """ Produces a business object out of CSV data. """ customer_account_id, month, amount = message.text.split(";") invoice = Invoice() invoice.customer_account_id = customer_account_id invoice.month = month invoice.amount = amount return invoice # The connection factory we\(aqre going to use. factory = WebSphereMQConnectionFactory(qm_name, channel, host, listener_port) # Our JmsTemplate. jms_template = JmsTemplate(factory) # Here we tell the template to use our converter. invoice_converter = InvoiceConverter() jms_template.message_converter = invoice_converter # See how we\(aqre now dealing only with business objects at the JmsTemplate level. invoice = Invoice("00033010118", "200909", "136.32") jms_template.convert_and_send(invoice, request_queue) print jms_template.receive_and_convert(response_queue) # We\(aqre not using an IoC so we need to shut down the connection factory ourselves. factory.destroy() .ft P .fi .SS SimpleMessageListenerContainer and background JMS listeners .sp \fIspringpython.jms.listener.SimpleMessageListenerContainer\fP is a configuration\-driven component which is used to receive messages from JMS destinations. Once configured, the container starts as many background listeners as requested and each listener gets assigned a pool of threads to handle the incoming requests. The number of listeners started and threads in a pool is fixed upon the configuration is read and the container is started, they cannot be dynamically altered in runtime. .sp The advantage of using SimpleMessageListenerContainer comes from the fact that all you need to do in order to receive the messages is to create your own handler class and to configure the container, no JMS coding is required so you\(aqre focusing on creating the business logic, not on the JMS boilerplate. .TS center; |l|l|. _ T{ \fBfactory\fP T} T{ default: None .sp \fIMust be set manually\fP .sp A reference to a JMS connection factory. T} _ T{ _ T{ _ T{ \fBdestination\fP T} T{ default: None .sp \fIMust be set manually\fP .sp Name of a JMS destination to read the messages off. T} _ T{ _ T{ _ T{ \fBhandler\fP T} T{ default: None .sp \fIMust be set manually\fP .sp A reference to an object which will be receiving messages read from the JMS destination. A handler must implement handle(self, message) method, of which the message argument is a \fITextMessage\fP instance. There is a convenience class, \fIspringpython.jms.listener.MessageHandler\fP, which exposes such a method. The exact number of handlers available for message processing is controlled via the handlers_per_listener property. T} _ T{ _ T{ _ T{ \fBfactory\fP T} T{ default: 1 .sp Sets a number of background processes that connect to a JMS provider and read messages off the destination. T} _ T{ _ T{ \fBhandlers_per_listener\fP T} T{ default: 2 .sp Sets a number of background processes that connect to a JMS provider and read messages off the destination. T} _ T{ _ T{ \fBwait_interval\fP T} T{ default: 1000 (1 second) .sp A value in milliseconds expressing how often each of the listeners will check for the arrival of a new message. T} _ T{ _ .TE .sp Here\(aqs an example showing SimpleMessageListenerContainer in action together with \fBYamlConfig\(aqs\fP \fIabstract objects definitions\fP. customer_queue, credit_account_queue and deposit_account_queue subclass the listener_container object which holds the information common to all definitions of JMS destinations. 4 listeners will be assigned to each of the JMS destination, every listener will be assigned a pool of 5 threads for handling the messages read; a wait interval of 700 milliseconds has been set: .sp .nf .ft C objects: \- object: connection_factory class: springpython.jms.factory.WebSphereMQConnectionFactory properties: queue_manager: QM.1 channel: SVRCONN.1 host: 192.168.1.121 listener_port: "1434" \- object: message_handler class: app.MyMessageHandler \- object: listener_container abstract: True class: springpython.jms.listener.SimpleMessageListenerContainer concurrent_listeners: "4" handlers_per_listener: "5" wait_interval: "700" properties: factory: {ref: connection_factory} handler: {ref: message_handler} \- object: customer_queue parent: listener_container properties: destination: CUST.QUEUE.1 \- object: credit_account_queue parent: listener_container properties: destination: CREDACCT.QUEUE.1 \- object: deposit_account_queue parent: listener_container properties: destination: DEPACCT.QUEUE.1 .ft P .fi .sp Here\(aqs a Python code using the above IoC configuration. Note that the fact of reading a configuration alone suffices for JMS listeners to be started and run in the background of the main application: .sp .nf .ft C # app.py from springpython.config import YamlConfig from springpython.context import ApplicationContext class MyMessageHandler(object): def handle(self, message): print "Got message!", message if __name__ == "__main__": # Obtaining a context will automatically start the SimpleMessageListenerContainer # and its listeners in background. container = ApplicationContext(YamlConfig("./context.yml")) while True: # Here goes the main application\(aqs logic, which does nothing in this case. # However, the listeners have been already started and incoming messages # will be passed in to MyMessageHandler instance (as configured in YamlConfig). pass .ft P .fi .SS TextMessage .sp springpython.jms.core.TextMessage objects encapsulate the data being sent to or received from a JMS provider. Even if you use the plain \fIjms_template.send("Foobar")\fP to send an ordinary text, there\(aqs still a TextMessage instance created automatically underneath for you. .sp If all you need from JMS is simply to send and receive some text then you\(aqre not likely to be required to use TextMessages. However, if you have to set or read JMS attributes or you\(aqre interested in setting custom JMS properties then TextMessage is what you\(aqre looking for. .sp In Spring Python there are no clumsy setters and getters as in Java JMS. If you need to set the property of a message, you just write it, like for instance \fImessage.jms_correlation_id = "1234567"\fP. Here\(aqs the list of all TextMessage\(aqs attributes along with their explanation and usage notes. .TS center; |l|l|. _ T{ \fBtext\fP T} T{ The message contents, the actual business payload carried by a message. May be both read and written to. For messages sent to a JMS provider it must be either a string or a unicode object encodable into UTF\-8. .sp The following two code snippets are equivalent: .sp .nf .ft C message = TextMessage("Hey") .ft P .fi .sp .nf .ft C message = TextMessage() message.text = "Hey" .ft P .fi .sp Here\(aqs how to get the content of a message received by a JmsTemplate: .sp .nf .ft C # .. skip creating the connection factory and a JmsTemplate message = jms_template.receive() print message.text .ft P .fi T} _ T{ \fBjms_correlation_id\fP T} T{ Equivalent to Java\(aqs JMSCorrelationID message header. It must be a string instance when set manually \- a good way to produce correlation identifiers is to use the Python\(aqs \fI\%uuid4\fP type, e.g.: .sp .nf .ft C # stdlib from uuid import uuid4 # Spring Python from springpython.jms.core import TextMessage # Prapare the JMS correlation ID jms_correlation_id = uuid4().hex message = TextMessage("Howdy") message.jms_correlation_id = jms_correlation_id # Now the message will be sent with a JMS correlation ID such as # 6f5b070bb0ed472bbe63d511776bb1dc which is a 128 bits long ID. .ft P .fi T} _ T{ \fBjms_delivery_mode\fP T} T{ Equivalent to Java\(aqs JMSDeliveryMode, can be both read and written to and must be equal to one of the following values \fIspringpython.jms.DELIVERY_MODE_NON_PERSISTENT\fP, \fIspringpython.jms.DELIVERY_MODE_PERSISTENT\fP or \fIspringpython.jms.DEFAULT_DELIVERY_MODE\fP. The default value \- \fIDEFAULT_DELIVERY_MODE\fP\- equals to \fIDELIVERY_MODE_PERSISTENT\fP. T} _ T{ \fBjms_destination\fP T} T{ Equivalent to Java\(aqs JMSDestination, automatically populated by JmsTemplate objects on send or receive time. \fIMay be read from but must not be set manually\fP. T} _ T{ \fBjms_expiration\fP T} T{ Same as Java\(aqs JMSExpiration \- allow for a message to expire after a certain amount of time. The value is automatically set by JmsTemplate for received messages. For messages being sent the time expressed is in milliseconds, as in the following code: .sp .nf .ft C message = TextMessage("I will expire in half a second") # Set the message\(aqs expiration time to 500 ms message.jms_expiration = 500 .ft P .fi T} _ T{ \fBjms_message_id\fP T} T{ Same as Java\(aqs JMSMessageID. Automatically set by JmsTemplate for received messages, may be set manually but the value will be ignored by the JMS provider. T} _ T{ \fBjms_redelivered\fP T} T{ Same as Java\(aqs JMSRedelivered header. Should not be set manually. Default value for incoming messages is \fIFalse\fP; for messages received from WebSphere MQ (which is currently the only supported JMS provider) it will be \fITrue\fP if the underlying MQ message\(aqs \fIBackoutCount\fP attribute is 1 or greater. T} _ T{ \fBjms_reply_to\fP T} T{ Equivalent to Java\(aqs JMSReplyTo, the name of a JMS destination to which responses to the currently sent message should be delivered: .sp .nf .ft C message = TextMessage("Please, reply to me.") # Set the reply to queue message.jms_reply_to = "REPLY.TO.QUEUE.1" .ft P .fi .sp See \fIhere\fP for an example of how to use \fIjms_reply_to\fP in request/reply scenarios. T} _ T{ \fBjms_timestamp\fP T} T{ Same as Java\(aqs JMSTimestamp, the timestamp of a message returned as a number of milliseconds with a centiseconds precision. Should not be set manually. T} _ T{ \fBmax_chars_printed\fP T} T{ Specifies how many characters of the business payload (the \fI.text\fP attribute) will be returned by the TextMessage instance\(aqs \fI__str__\fP method, which is used, for instance, for logging purposes. Default value is 100 characters. .sp Consider the code below, in both cases the message\(aqs content is the same, the messages differ only by the value of the \fImax_chars_printed\fP attribute: .sp .nf .ft C # Spring Python from springpython.jms.core import TextMessage payload = "Business payload. " * 8 msg = TextMessage(payload) msg.max_chars_printed = 50 print msg .ft P .fi .sp .nf .ft C JMS message class: jms_text jms_delivery_mode: 2 jms_expiration: None jms_priority: None jms_message_id: None jms_timestamp: None jms_correlation_id: None jms_destination: None jms_reply_to: None jms_redelivered: None Business payload. Business payload. Business paylo Another 94 character(s) omitted .ft P .fi .sp .nf .ft C # Spring Python from springpython.jms.core import TextMessage payload = "Business payload. " * 8 msg = TextMessage(payload) msg.max_chars_printed = 20 print msg .ft P .fi .sp .nf .ft C JMS message class: jms_text jms_delivery_mode: 2 jms_expiration: None jms_priority: None jms_message_id: None jms_timestamp: None jms_correlation_id: None jms_destination: None jms_reply_to: None jms_redelivered: None Business payload. Bu Another 124 character(s) omitted .ft P .fi T} _ .TE .sp Attributes shown in the table above are standard JMS headers, available regardless of the JMS provider used. For WebSphereMQ \- which is currently the only JMS provider supported by Spring Python \- following attributes are also available: JMS_IBM_Report_Exception, JMS_IBM_Report_Expiration, JMS_IBM_Report_COA, JMS_IBM_Report_COD, JMS_IBM_Report_PAN, JMS_IBM_Report_NAN, JMS_IBM_Report_Pass_Msg_ID, JMS_IBM_Report_Pass_Correl_ID, JMS_IBM_Report_Discard_Msg, JMSXGroupID, JMSXGroupSeq, JMS_IBM_Feedback, JMS_IBM_Last_Msg_In_Group, JMSXUserID, JMS_IBM_PutTime, JMS_IBM_PutDate and JMSXAppID. Refer to the IBM\(aqs Java JMS documentation for info on how to use them. .sp Creating custom JMS properties is simply a matter of assigning a value to an attribute, there are no special methods such as \fIsetStringProperty/getStringProperty\fP which are used in Java JMS, thus the following code will create a custom \fIMESSAGE_NAME\fP property which can be read by \fIgetStringProperty\fP on the Java side: .sp .nf .ft C # Spring Python from springpython.jms.core import TextMessage msg = TextMessage("Hello!") msg.MESSAGE_NAME = "HelloRequest" .ft P .fi .sp Observe how custom properties will be printed to the console along with standard JMS headers: .sp .nf .ft C # Spring Python from springpython.jms.core import TextMessage msg = TextMessage("Hello!") msg.MESSAGE_NAME = "HelloRequest" msg.CLIENT = "CRM" msg.CUSTOMER_ID = "201888228" print msg .ft P .fi .sp .nf .ft C JMS message class: jms_text jms_delivery_mode: 2 jms_expiration: None jms_priority: None jms_message_id: None jms_timestamp: None jms_correlation_id: None jms_destination: None jms_reply_to: None jms_redelivered: None CLIENT:CRM CUSTOMER_ID:201888228 MESSAGE_NAME:HelloRequest Hello! .ft P .fi .sp Not all TextMessage\(aqs attributes can be set to a custom value, the exact list of reserved attributes\(aq names is available as \fIspringpython.jms.core.reserved_attributes\fP. There\(aqs a very slim chance you\(aqll ever encounter the conflict with your application\(aqs message attributes, nevertheless be sure to check the list before using custom JMS properties in your code. .SS Exceptions .sp \fIspringpython.jms.JMSException\fP is the base exception class for all JMS\-related issues that may be raised by Spring Python\(aqs JMS and a pair of its specialized subclasses is also available: \fIspringpython.jms.NoMessageAvailableException\fP and \fIspringpython.jms.WebSphereMQJMSException\fP. .sp NoMessageAvailableException is raised when a call to \fIreceive\fP or \fIreceive_and_convert\fP timeouts, which indicates that there\(aqs no message available for a given JMS destination. .sp WebSphereMQJMSException is raised when the underlying error is known to be caused by a call to WebSphere MQ API, such as a call to connect to a queue manager. Spring Python tries to populate these attributes of a WebSphereMQJMSException object when an error condition arises: .INDENT 0.0 .IP \(bu 2 . \fImessage\fP \- a descriptive information of what has happened; taken either from an exception raised deeper in a call stack or an explanation from Spring Python itself, .IP \(bu 2 . \fIcompletion_code\fP \- an integer code returned by the call a queue manager, may be either 1 (a warning) or 2 (an error), it\(aqs known as an MQCC in WebSphere MQ, .IP \(bu 2 . \fIreason_code\fP \- an integer code, as returned by the queue manager, giving a reason for the failure, known as MQRC in WebSphere MQ lingo. The meaning may be looked up in the IBM\(aqs "WebSphere MQ Constants" manual. .UNINDENT .sp Note that \fImessage\fP, \fIcompletion_code\fP and \fIreason_code\fP are all optional and there\(aqs no guarantee they will be actually returned. Should you caught a WebSphereMQJMSException, you should always check for their existence before making any use of them. .SS Logging and troubleshooting .sp Spring Python\(aqs JMS uses standard Python\(aqs \fI\%logging\fP module for emitting the messages. In general, you can expect JMS to behave sane, it won\(aqt overflow your logs with meaningless entries, e.g. if you configure it to log the messages at the \fIERROR\fP level then you\(aqll be notified of only truly erratic situtations. .sp In addition to logging\(aqs builtin levels, JMS uses one custom level \- \fIspringpython.util.TRACE1\fP, \fIenabling TRACE1 will degrade the performance considerably\fP and will result in a huge number of messages written to the logs. Use it sparingly at troubleshooting times when you\(aqd like to see the exact flow of messages, raw bytes and JMS headers passing by the Spring Python\(aqs JMS classes involved. Do not ever enable it in production environments unless you have a very compelling reason and you\(aqre sure you\(aqre comfortable with paying the performance penalty. Consider using the \fIlogging.DEBUG\fP level instead of \fITRACE1\fP if all you\(aqre after is simply seeing the messages\(aq payload. .sp JMS loggers currently employed by Spring Python are \fIspringpython.jms.factory.WebSphereMQConnectionFactory\fP, \fIspringpython.jms.listener.SimpleMessageListenerContainer\fP and \fIspringpython.jms.listener.WebSphereMQListener(LISTENER_INSTANCE_ID)\fP. .sp Here\(aqs how the WebSphere MQ connection factory\(aqs logger can be configured to work at the INFO level: .sp .nf .ft C # stdlib import logging log_format = "%(asctime)s \- %(levelname)s \- %(process)d \- %(threadName)s \- %(name)s \- %(message)s" formatter = logging.Formatter(log_format) handler = logging.StreamHandler() handler.setFormatter(formatter) jms_logger = logging.getLogger("springpython.jms.factory.WebSphereMQConnectionFactory") jms_logger.setLevel(level=logging.INFO) jms_logger.addHandler(handler) .ft P .fi .sp Here\(aqs how to configure it to log messages at the TRACE1 level: .sp .nf .ft C # stdlib import logging # Spring Python from springpython.util import TRACE1 log_format = "%(asctime)s \- %(levelname)s \- %(process)d \- %(threadName)s \- %(name)s \- %(message)s" formatter = logging.Formatter(log_format) handler = logging.StreamHandler() handler.setFormatter(formatter) jms_logger = logging.getLogger("springpython.jms.factory.WebSphereMQConnectionFactory") jms_logger.setLevel(level=TRACE1) jms_logger.addHandler(handler) .ft P .fi .sp \fIspringpython.jms.listener.SimpleMessageListenerContainer\fP is the logger used by the JMS listener container itself. .sp Each WebSphere MQ listener is assigned a \fIspringpython.jms.listener.WebSphereMQListener(LISTENER_INSTANCE_ID)\fP logger, where \fILISTENER_INSTANCE_ID\fP is an identifier uniquely associated with a listener to form a full name of a logger, such as \fIspringpython.jms.listener.WebSphereMQListener(0xc7f5e0)\fP. To be precise, its value is obtained by invoking hex(id(self)) on the listener\(aqs instance. Note that the value is not guaranteed to be globally unique, it\(aqs just an identifier of the Python object so its value may be very well reused across application\(aqs restarts. .sp How much information is being logged depends on the logging level, the average message size, the messages\(aq \fImax_chars_printed\fP attribute value and the message rate. .sp Here\(aqs an estimation of how fast log files will grow depending on the logging level. During the test, the message size was 5kB, there were a total of 10,000 messages sent, the \fImax_chars_printed\fP attribute had value of 100 and the log entries were written to an ordinary log file: .INDENT 0.0 .IP \(bu 2 . \fIERROR\fP \- 0KB, no errors were encountered thus no entries were written to the log file, .IP \(bu 2 . \fIINFO\fP \- 0.9KB, only very basic info was logged, such as events of connecting to and disconnecting from a JMS provider, .IP \(bu 2 . \fIDEBUG\fP \- 7,3MB, up to the \fImax_chars_printed\fP characters of each message were written to the file plus all of JMS headers and some additional info as well, .IP \(bu 2 . \fITRACE1\fP \- 79MB, full trace was taken which resulted in the log file\(aqs growing more than \fItenfold\fP as compared to the \fIDEBUG\fP level. .UNINDENT .SH SPRING PYTHON'S PLUGIN SYSTEM .sp Spring Python\(aqs plugin system is designed to help you rapidly develop applications. Plugin\-based solutions have been proven to enhance developer efficiency, with examples such as \fI\%Grails\fP and \fI\%Eclipse\fP being market leaders in usage and productivity. .sp This plugin solution was mainly inspired by the Grails demo presented by Graeme Rocher at the SpringOne Americas 2008 conference, in which he created a Twitter application in 40 minutes. Who wouldn\(aqt want to have something similar to support Spring Python development? .SS Introduction .sp Spring Python will manage an approved set of plugins. These are plugins written by the committers of Spring Python and are verified to work with an associated version of the library. These plugins are also hosted by the same services used to host Spring Python downloads, meaning they have the same level of support as Spring Python. .sp However, being an open source framework, developers have every right to code their own plugins. We fully support the concept of 3rd party plugins. We want to provide as much support in the way of documentation and extension points for you to develop your own plugins as well. .IP Note . Have you considered submitting your plugin as a Spring Extension? .sp \fI\%Spring Extensions\fP is the official incubator process for SpringSource. You can always maintain your own plugin separately, using whatever means you wish. But if want to get a larger adoption of your plugin, name association with SpringSource, and perhaps one day becoming an official part of the software suite of SpringSource, you may want to consider looking into the Spring Extensions process. .RE .SS Coily \- Spring Python\(aqs command\-line tool .sp Coily is the command\-line tool that utilizes the plugin system. It is similar to grails command\-line tool, in that through a series of installed plugins, you are able to do many tasks, including build skeleton apps that you can later flesh out. If you look at the details of this app, you will find a sophisticated, command driven tool to built to manage plugins. The real power is in the plugins themselves. .SS Commands .sp To get started, all you need is a copy of coily installed in some directory located on your path: .sp .nf .ft C % coily \-\-help .ft P .fi .sp The results should list available commands: .sp .nf .ft C Coily \- the command\-line management tool for Spring Python ========================================================== Copyright 2006\-2008 SpringSource (http://springsource.com), All Rights Reserved Licensed under the Apache License, Version 2.0 Usage: coily [command] \-\-help print this help message \-\-list\-installed\-plugins list currently installed plugins \-\-list\-available\-plugins list plugins available for download \-\-install\-plugin [name] install coily plugin \-\-uninstall\-plugin [name] uninstall coily plugin \-\-reinstall\-plugin [name] reinstall coily plugin .ft P .fi .INDENT 0.0 .IP \(bu 2 . \-\-help \- Print out the help menu being displayed .IP \(bu 2 . \-\-list\-installed\-plugins \- list the plugins currently installed in this account. It is important to know that each plugin creates a directly underneath the user\(aqs home directory in a hidden directory \fI~/.springpython\fP. If you delete this entire directory, you have effectively uninstalled all plugins. .IP \(bu 2 . \-\-list\-available\-plugins \- list the plugins available for installation. Coily will check certain network locations, such as the S3 site used to host Spring Python downloads. It will also look on the local file system. This is in case you have a checked out copy of the plugins source code, and want to test things out without uploading to the network. .IP \(bu 2 . \-\-install\-plugin \- install the named plugin. In this case, you don\(aqt have to specify a version number. Coily will figure out which version of the plugin you need, download it if necessary, and finally copy it into \fI~/.springpython\fP. .IP \(bu 2 . \-\-uninstall\-plugin \- uninstall the named plugin by deleting its entry from \fI~/.springpython\fP .IP \(bu 2 . \-\-reinstall\-plugin \- uninstall then install the plugin. This is particulary useful if you are working on a plugin, and need a shortcut step to deploy. .UNINDENT .sp In this case, no plugins have been installed yet. Every installed plugin will list itself as another available command to run. If you have already installed the \fIgen\-cherrypy\-app\fP plugin, you will see it listed: .sp .nf .ft C Coily \- the command\-line management tool for Spring Python ========================================================== Copyright 2006\-2008 SpringSource (http://springsource.com), All Rights Reserved Licensed under the Apache License, Version 2.0 Usage: coily [command] \-\-help print this help message \-\-list\-installed\-plugins list currently installed plugins \-\-list\-available\-plugins list plugins available for download \-\-install\-plugin [name] install coily plugin \-\-uninstall\-plugin [name] uninstall coily plugin \-\-reinstall\-plugin [name] reinstall coily plugin \-\-gen\-cherrypy\-app [name] plugin to create skeleton CherryPy applications .ft P .fi .sp You should notice an extra option listed at the bottom: \fIgen\-cherrypy\-app\fP is listed as another command with one argument. Later on, you can read official documentation on the existing plugins, and also how to write your own. .SS Officially Supported Plugins .sp This section documents plugins that are developed by the Spring Python team. .SS External dependencies .sp \fIgen\-cherrypy\-app\fP plugin requires the installation of \fI\%CherryPy 3\fP. .SS gen\-cherrypy\-app .sp This plugin is used to generate a skeleton \fI\%CherryPy\fP application based on feeding it a command\-line argument: .sp .nf .ft C % coily \-\-gen\-cherrypy\-app twitterclone .ft P .fi .sp This will generate a subdirectory \fItwitterclone\fP in the user\(aqs current directory. Inside twitterclone are several files, including \fItwitterclone.py\fP. If you run the app, you will see a working CherryPy application, with Spring Python security in place: .sp .nf .ft C % cd twitterclone % python twitterclone.py .ft P .fi .sp You can immediately start modifying it to put in your features. .SS Writing your own plugin .SS Architecture of a plugin .sp A plugin is pretty simple in structure. It is basically a Python package with some special things added on. \fIgen\-cherrypy\-app\fP plugin demonstrates this. [image] .sp The special things needed to define a plugin are as follows: .INDENT 0.0 .IP \(bu 2 . A root folder with the same name as your plugin and a \fI__init__.py\fP, making the plugin a Python package. .IP \(bu 2 . A package\-level variable named \fI__description__\fP This attribute should be assigned the string value description you want shown for your plugin when coily \-\-help is run. .IP \(bu 2 . A package\-level function named either \fIcreate\fP or \fIapply\fP .INDENT 2.0 .IP \(bu 2 . If your plugin needs one command line argument, define a \fIcreate\fP method with the following signature: .sp .nf .ft C def create(plugin_path, name) .ft P .fi .IP \(bu 2 . If your plugin doesn\(aqt need any arguments, define an \fIapply\fP method with the following signature: .sp .nf .ft C def apply(plugin_path) .ft P .fi .UNINDENT .sp In either case, your plugin gets passed an extra argument, plugin_path, which contains the directory the plugin is actually installed in. This is typically so you can reference other files your plugin needs access to. .IP Note . What does "package\-level" mean? .sp The code needs to be in the __init__.py file. This file makes the enclosing directory a Python package. .RE .UNINDENT .SS Case Study \- gen\-cherrypy\-app plugin .sp \fIgen\-cherrypy\-app\fP is a plugin used to build a \fI\%CherryPy\fP web application using Spring Python\(aqs feature set. It saves the developer from having to re\-configure Spring Python\(aqs security module, coding CherryPy\(aqs engine, and so forth. This allows the developer to immediately start writing business code against a working application. .sp Using this plugin, we will de\-construct this simple, template\-based plugin. This will involve looking line\-by\-line at \fIgen\-cherrypy\-app/__init__.py\fP. .SS Source Code .sp .nf .ft C """ Copyright 2006\-2008 SpringSource (http://springsource.com), All Rights Reserved Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE\-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import re import os import shutil __description__ = "plugin to create skeleton CherryPy applications" def create(plugin_path, name): if not os.path.exists(name): print "Creating CherryPy skeleton app %s" % name os.makedirs(name) # Copy/transform the template files for file_name in ["cherrypy\-app.py", "controller.py", "view.py", "app_context.py"]: input_file = open(plugin_path + "/" + file_name).read() # Iterate over a list of patterns, performing string substitution on the input file patterns_to_replace = [("name", name), ("properName", name[0].upper() + name[1:])] for pattern, replacement in patterns_to_replace: input_file = re.compile(r"\e$\e{%s}" % pattern).sub(replacement, input_file) output_filename = name + "/" + file_name if file_name == "cherrypy\-app.py": output_filename = name + "/" + name + ".py" app = open(output_filename, "w") app.write(input_file) app.close() # Recursively copy other parts shutil.copytree(plugin_path + "/images", name + "/" + "images") else: print "There is already something called %s. ABORT!" % name .ft P .fi .SS Deconstructing the factory .INDENT 0.0 .IP \(bu 2 . The opening section shows the copyright statement, which should tip you off that this is an official plugin. .IP \(bu 2 . __description__ is a required variable: .sp .nf .ft C __description__ = "plugin to create skeleton CherryPy applications" .ft P .fi .sp It contains the description displayed when a user runs: .sp .nf .ft C % coily \-\-help .ft P .fi .sp .nf .ft C Usage: coily [command] \&... \-\-gen\-cherrypy\-app [name] plugin to create skeleton CherryPy applications .ft P .fi .IP \(bu 2 . Opening line defines create with two arguments: .sp .nf .ft C def create(plugin_path, name): .ft P .fi .sp The arguments allow both the plugin path to be fed along with the command\-line argument that is filled in when the user runs the command: .sp .nf .ft C % coily \-\-gen\-cherrypy\-app [name] .ft P .fi .sp It is important to realize that \fIplugin_path\fP is needed in case the plugin needs to refer to any files inside its installed directory. This is because plugins are not installed anywhere on the \fIPYTHONPATH\fP, but instead, in the user\(aqs home directory underneath \fI~/.springpython\fP. .sp This mechanism was chosen because it gives users an easy ability to pick which plugins they wish to use, without requiring system admin power. It also eliminates the need to deal with multiple versions of plugins being installed on your \fIPYTHONPATH\fP. This provides maximum flexibility which is needed in a development environment. .IP \(bu 2 . This plugin works by creating a directory in the user\(aqs current working directory, and putting all relevant files into it. The argument passed into the command\-line is used as the name of an application, and the directory created has the same name: .sp .nf .ft C if not os.path.exists(name): print "Creating CherryPy skeleton app %s" % name os.makedirs(name) .ft P .fi .sp However, if the directory already exists, it won\(aqt proceed: .sp .nf .ft C else: print "There is already something called %s. ABORT!" % name .ft P .fi .IP \(bu 2 . This plugin then iterates over a list of filenames, which happen to match the names of files found in the plugin\(aqs directory. These are essentially template files, intended to be copied into the target directory. However, the files are not copied directly. Instead they are opened and read into memory: .sp .nf .ft C # Copy/transform the template files for file_name in ["cherrypy\-app.py", "controller.py", "view.py", "app_context.py"]: input_file = open(plugin_path + "/" + file_name).read() .ft P .fi .sp Then, the contents are scanned for key phrases, and substituted. In this case, the substitution is a variant of the name of the application being generated: .sp .nf .ft C # Iterate over a list of patterns, performing string substitution on the input file patterns_to_replace = [("name", name), ("properName", name[0].upper() + name[1:])] for pattern, replacement in patterns_to_replace: input_file = re.compile(r"\e$\e{%s}" % pattern).sub(replacement, input_file) .ft P .fi .sp The substituted content is written to a new output file. In most cases, the original filename is also the target filename. However, the key file, \fIcherrypy\-app.py\fP is renamed to the application\(aqs name: .sp .nf .ft C output_filename = name + "/" + file_name if file_name == "cherrypy\-app.py": output_filename = name + "/" + name + ".py" app = open(output_filename, "w") app.write(input_file) app.close() .ft P .fi .IP \(bu 2 . Finally, the images directory is recursively copied into the target directory: .sp .nf .ft C # Recursively copy other parts shutil.copytree(plugin_path + "/images", name + "/" + "images") .ft P .fi .UNINDENT .SS Summary .sp All these steps effectively copy a set of files used to template an application. With this template approach, the major effort of developing this plugin is spent working on the templates themselves, not on this template factory. While this is mostly working with python code for a python solution, the fact that this is a template requires reinstalling the plugin everytime a change is made in order to test them. .sp Users are welcome to use \fIgen\-cherypy\-app\fP\(aqs \fI__init__.py\fP file to generate their own template solutions, and work on other skeleton tools or solutions. .SH SAMPLES .SS PetClinic .sp PetClinic is a sample application demonstrating the usage of Spring Python. .INDENT 0.0 .IP \(bu 2 . It uses \fI\%CherryPy\fP as the web server object. .IP \(bu 2 . A \fI\%detailed design document\fP (NOTE: find latest version, and click on raw) is part of the source code. You can read it from here or by clicking on a hyperlink while running the application. .UNINDENT .SS How to run .sp Assuming you just checked out a copy of the source code, here are the steps to run PetClinic: .sp .nf .ft C bash$ cd /path/you/checked/out/springpython bash$ cd samples/petclinic bash$ python configure.py .ft P .fi .sp At this point, you will be prompted for MySQL\(aqs root password. This is NOT your system\(aqs root password. This assumes you have a MySQL server running. After that, it will have setup database \fIpetclinic\fP: .sp .nf .ft C bash$ cd cherrypy bash$ python petclinic.py .ft P .fi .sp This assumes you have \fI\%CherryPy 3\fP installed. It probably \fIwon\(aqt\fP work if you are still using CherryPy 2. NOTE: If you are using Python 2.5.2+, you must install CherryPy 3.1.2+. The older version of CherryPy (3.1.0) only works pre\-2.5.2. .sp Finally, after launching it, you should see a nice URL at the bottom: \fI\%http://localhost:8080\fP. Well, go ahead! Things should look good now! [image] .SS Spring Wiki .sp Spring Wiki is a wiki engine based that uses mediawiki\(aqs markup language. It utilizes the same stylesheets to have a very wikipedia\-like feel to it. .sp TODO: Add persistence. Currently, Spring Wiki only stores content in current memory. Shutting it down will cause all changes to be lost. .SS Spring Bot .sp This article will show how to write an IRC bot to manage a channel for your open source project. .SS Why write a bot? .sp I read an article, \fI\%Building a community around your open source project\fP, that talked about setting up an IRC channel for your project. This is a route to support existing users, and allow them to work with each other. .sp I became very interested in writing some IRC bot, and I since my project is based on Python, well, you can probably guess what language I wanted to write it in. .SS IRC Library .sp To build a bot, it pays to have use an already written library. I discovered python\-irclib. .sp For Ubuntu users: .sp .nf .ft C % sudo apt\-get install python\-irclib .ft P .fi .sp This bot also sports a web page using \fI\%CherryPy\fP. You also need to install that as well. .SS Articles .sp Well, of course I started reading. The documentation from the project\(aqs web site was minimal. Thankfully, I found some introductory articles that work with python\-irclib. .INDENT 0.0 .IP \(bu 2 . \fI\%http://www.devshed.com/c/a/Python/IRC-on-a-Higher-Level/\fP .IP \(bu 2 . \fI\%http://www.devshed.com/c/a/Python/IRC-on-a-Higher-Level-Continued/\fP .IP \(bu 2 . \fI\%http://www.devshed.com/c/a/Python/IRC-on-a-Higher-Level-Concluded/\fP .UNINDENT .SS What I built .sp Using this, I managed to get something primitive running. It took me a while to catch on that posting private messages on a channel name instead of a user is the way to publicly post to a channel. I guess it helped to trip through the \fI\%IRC RFC\fP manual, before catching on to this. .sp At this stage, you may wish to get familiar with \fI\%regular expressions in Python\fP. You will certainly need this in order to make intelligent looking patterns. Anything more sophisticated would probably require \fI\%PLY\fP. .sp What I really like is that fact that I built this application in approximately 24 hours, counting the time to learn how to use python\-irclib. I already knew ow to build a Spring Python/CherryPy web application. The history pages on this article should demonstrate how long it took. .sp NOTE: This whole script is contained in one file, and marked up as: .sp .nf .ft C """ Copyright 2006\-2008 SpringSource (http://springsource.com), All Rights Reserved Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE\-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ .ft P .fi .SS IRC Bot .sp So far, this handy little bot is able to monitor the channel, log all communications, persistently fetch/store things, and grant me operator status when I return to the channel. My next task is to turn it into a web app using Spring Python. That should let me have a web page to go along with the channel: .sp .nf .ft C class DictionaryBot(ircbot.SingleServerIRCBot): def __init__(self, server_list, channel, ops, logfile, nickname, realname): ircbot.SingleServerIRCBot.__init__(self, server_list, nickname, realname) self.datastore = "%s.data" % self._nickname self.channel = channel self.definition = {} try: f = open(self.datastore, "r") self.definition = cPickle.load(f) f.close() except IOError: pass self.whatIsR = re.compile(",?\es*[Ww][Hh][Aa][Tt]\es*[Ii][Ss]\es+([\ew ]+)[?]?") self.definitionR = re.compile(",?\es*([\ew ]+)\es+[Ii][Ss]\es+(.+)") self.ops = ops self.logfile = logfile def on_welcome(self, connection, event): """This event is generated after you connect to an irc server, and should be your signal to join your target channel.""" connection.join(self.channel) def on_join(self, connection, event): """This catches everyone who joins. In this case, my bot has a list of whom to grant op status to when they enter.""" self._log_event(event) source = event.source().split("!")[0] if source in self.ops: connection.mode(self.channel, "+o %s" % source) def on_mode(self, connection, event): """No real action here, except to log locally every mode action that happens on my channel.""" self._log_event(event) def on_pubmsg(self, connection, event): """This is the real meat. This event is generated everytime a message is posted to the channel.""" self._log_event(event) # Capture who posted the messsage, and what the message was. source = event.source().split("!")[0] arguments = event.arguments()[0] # Some messages are meant to signal this bot to do something. if arguments.lower().startswith("!%s" % self._nickname): # "What is xyz" command match = self.whatIsR.search(arguments[len(self._nickname)+1:]) if match: self._lookup_definition(connection, match.groups()[0]) return # "xyz is blah blah" command match = self.definitionR.search(arguments[len(self._nickname)+1:]) if match: self._set_definition(connection, match.groups()[0], match.groups()[1]) return # There are also some shortcut commands, so you don\(aqt always have to address the bot. if arguments.startswith("!"): match = re.compile("!([\ew ]+)").search(arguments) if match: self._lookup_definition(connection, match.groups()[0]) return def getDefinitions(self): """This is to support a parallel web app fetching data from the bot.""" return self.definition def _log_event(self, event): """Log an event to a flat file. This can support archiving to a web site for past activity.""" f = open(self.logfile, "a") f.write("%s::%s::%s::%s\en" % (event.eventtype(), event.source(), event.target(), event.arguments())) f.close() def _lookup_definition(self, connection, keyword): """Function to fetch a definition from the bot\(aqs dictionary.""" if keyword.lower() in self.definition: connection.privmsg(self.channel, "%s is %s" % self.definition[keyword.lower()]) else: connection.privmsg(self.channel, "I have no idea what %s means. You can tell me by sending \(aq!%s, %s is \(aq" % (keyword, self._nickname, keyword)) def _set_definition(self, connection, keyword, definition): """Function to store a definition in cache and to disk in the bot\(aqs dictionary.""" self.definition[keyword.lower()] = (keyword, definition) connection.privmsg(self.channel, "Got it! %s is %s" % self.definition[keyword.lower()]) f = open(self.datastore, "w") cPickle.dump(self.definition, f) f.close() .ft P .fi .sp I have trimmed out the instantiation of this bot class, because that part isn\(aqt relevant. You can go and immediately reuse this bot to manage any channel you have. .SS Web App .sp Well, after getting an IRC bot working that quickly, I want a nice interface to see what it is up to. For that, I will use Spring Python and build a Spring\-based web app: .sp .nf .ft C def header(): """Standard header used for all pages""" return """ Coily :: An IRC bot used to manage the #springpython irc channel (powered by CherryPy/Spring Python)
 
 

 

""" def footer(): """Standard footer used for all pages.""" return """
Home Coily :: a Spring Python IRC bot (powered by CherryPy)
""" def markup(text): """Convert any http://xyz references into real web links.""" httpR = re.compile(r"(http://[\ew.:/?\-]*\ew)") alteredText = httpR.sub(r\(aq\e1\(aq, text) return alteredText class CoilyView: """Presentation layer of the web application.""" def __init__(self, bot = None): """Inject a controller object in order to fetch live data.""" self.bot = bot @cherrypy.expose def index(self): """CherryPy will call this method for the root URI ("/") and send its return value to the client.""" return header() + """

Welcome

Hi, I\(aqm Coily! I\(aqm a bot used to manage the IRC channel #springpython.

If you visit the channel, you may find I have a lot of information to offer while you are there. If I seem to be missing some useful definitions, then you can help grow my knowledge.
Command Description
!coily, what is xyz? This is how you ask me for a definition of something.
!xyz This is a shortcut way to ask the same question.
!coily, xyz is some definition for xyz This is how you feed me a definition.

To save you from having to query me for every current definition I have, there is a link on this web site that lists all my current definitions. NOTE: These definitions can be set by other users.

List current definitions

""" + footer() @cherrypy.expose def listDefinitions(self): results = header() results += """ """ for key, value in self.bot.getDefinitions().items(): results += markup(""" """ % (value[0], value[1])) results += "
Keyword Definition
%s %s
" results += footer() return results .ft P .fi .SS Putting it all together .sp Well, so far, I have two useful classes. However, they need to get launched inside a script. This means objects need to be instantiated. To do this, I have decided to make this a Spring app and use \fBInversion of Control\fP. .sp So, I defined two contexts, one for the IRC bot and another for the web application. .SS IRC Bot\(aqs application context .sp .nf .ft C class CoilyIRCServer(PythonConfig): """This container represents the context of the IRC bot. It needs to export information, so the web app can get it.""" def __init__(self): super(CoilyIRCServer, self).__init__() @Object def remoteBot(self): return DictionaryBot([("irc.ubuntu.com", 6667)], "#springpython", ops=["Goldfisch"], nickname="coily", realname="Coily the Spring Python assistant", logfile="springpython.log") @Object def bot(self): exporter = PyroServiceExporter() exporter.service_name = "bot" exporter.service = self.remoteBot() return exporter .ft P .fi .SS Web App\(aqs application context .sp .nf .ft C class CoilyWebClient(PythonConfig): """ This container represents the context of the web application used to interact with the bot and present a nice frontend to the user community about the channel and the bot.\e """ def __init__(self): super(CoilyWebClient, self).__init__() @Object def root(self): return CoilyView(self.bot()) @Object def bot(self): proxy = PyroProxyFactory() proxy.service_url = "PYROLOC://localhost:7766/bot" return proxy .ft P .fi .SS Main runner .sp I fit all this into one executable. However, I quickly discovered that both CherryPy web apps and irclib bots like to run in the main thread. This means I need to launch two python shells, one running the web app, the other running the ircbot, and I need the web app to be able to talk to the irc bot. This is a piece of cake with Spring Python. All I need to utilize is a \fBremoting technology\fP: .sp .nf .ft C if __name__ == "__main__": # Parse some launching options. parser = OptionParser(usage="usage: %prog [\-h|\-\-help] [options]") parser.add_option("\-w", "\-\-web", action="store_true", dest="web", default=False, help="Run the web server object.") parser.add_option("\-i", "\-\-irc", action="store_true", dest="irc", default=False, help="Run the IRC\-bot object.") parser.add_option("\-d", "\-\-debug", action="store_true", dest="debug", default=False, help="Turn up logging level to DEBUG.") (options, args) = parser.parse_args() if options.web and options.irc: print "You cannot run both the web server and the IRC\-bot at the same time." sys.exit(2) if not options.web and not options.irc: print "You must specify one of the objects to run." sys.exit(2) if options.debug: 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) if options.web: # This runs the web application context of the application. It allows a nice web\-enabled view into # the channel and the bot that supports it. applicationContext = ApplicationContext(CoilyWebClient()) # Configure cherrypy programmatically. conf = {"/": {"tools.staticdir.root": os.getcwd()}, "/images": {"tools.staticdir.on": True, "tools.staticdir.dir": "images"}, "/html": {"tools.staticdir.on": True, "tools.staticdir.dir": "html"}, "/styles": {"tools.staticdir.on": True, "tools.staticdir.dir": "css"} } cherrypy.config.update({\(aqserver.socket_port\(aq: 9001}) cherrypy.tree.mount(applicationContext.get_object(name = "root"), \(aq/\(aq, config=conf) cherrypy.engine.start() cherrypy.engine.block() if options.irc: # This runs the IRC bot that connects to a channel and then responds to various events. applicationContext = ApplicationContext(CoilyIRCServer()) coily = applicationContext.get_object("bot") coily.service.start() .ft P .fi .SS Releasing your CherryPy web app to the internet .sp Now that you have a CherryPy web app running, how about making it visible to the internet? .sp If you already have an Apache web server running, and are using a Debian/Ubuntu installation, you just need to create a file in \fI/etc/apache2/sites\-available\fP like \fIcoily.conf\fP with the following lines: .sp .nf .ft C RedirectMatch ^/coily$ /coily/ ProxyPass /coily/ http://localhost:9001/ ProxyPassReverse /coily/ http://localhost:9001/ Order allow,deny Allow from all .ft P .fi .sp Now need to softlink this into /etc/apache2/sites\-enabled: .sp .nf .ft C % cd /etc/apache2/sites\-enabled % sudo ln \-s /etc/apache2/sites\-available/coily.conf 001\-coily .ft P .fi .sp This requires that mod_proxy be enabled: .sp .nf .ft C % sudo a2enmod proxy proxy_http .ft P .fi .sp Finally, restart Apache: .sp .nf .ft C % sudo /etc/init.d/apache2 \-\-force\-reload .ft P .fi .sp It should be visible on the site now. .SS Come and visit Coily .sp If you haven\(aqt figured it out yet, I use this code to run my own bot, Coily. Unfortunately, at this time, I don\(aqt have a mechanism to make it run persistently. .SS External Links .sp \fI\%See this article reported in LinuxToday\fP .SH AUTHOR Greg Turnquist, Dariusz Suchojad .SH COPYRIGHT 2006-2011, Greg Turnquist, Dariusz Suchojad .\" Generated by docutils manpage writer. .\" .