Archive

Posts Tagged ‘Spring Python’

Installing sec-wall on Debian wheezy/sid

June 13th, 2011 Dariusz Suchojad No comments

The unstoppable Miguel Landaeta did it again and I’m happy to let you all know that sec-wall, the security proxy, can be now installed on Debian wheezy/sid using nothing but DEBs, like below. The only prerequisite is that you need to visit http://alioth.debian.org/~nomadium-guest/debian/unstable/ to find and download the latest DEB – at the time of this writing it’s sec-wall_1.0.0-0miguel2_all.deb but it sure is going to change shortly because sec-wall 1.1 is about to be released soon.

First, the dependencies:

apt-get install python-springpython python-gevent
apt-get install python-argparse python-lxml
apt-get install python-pesto python-zdaemon
apt-get install python-pkg-resources

Now install sec-wall:

dpkg -i ./sec-wall_1.0.0-0miguel2_all.deb

And that’s all! :-)

Share

Announcing sec-wall 1.0.0, a feature packed high-performance security proxy

April 8th, 2011 Dariusz Suchojad No comments

This is mostly a copy of the announcement sent to the python-announce@ list, although I’ve added a couple more usage examples at the end of the post.

sec-wall is a feature packed high-performance security proxy which has many interesting features, including the support for SSL/TLS, WS-Security, HTTP Auth Basic/Digest, extensible authentication schemes based on custom HTTP headers and XPath expressions, powerful URL matching/rewriting and an optional headers enrichment.

sec-wall uses and is built on top of several fantastic Python open source technologies, such as gevent, Spring Python, Pesto, lxml, zdaemon or PyYAML and is meant to be highly customizable and easy to use. Good performance, tests, documentation and building an awesome community are at the very heart of the project.

Here are  examples showing how little is needed to secure a backend server with HTTP Basic Auth, SSL/TLS client certificates and WS-Security.

# -*- coding: utf-8 -*-
 
# stdlib
import uuid
 
# Don't share it with anyone.
INSTANCE_SECRET = '5bf4e78c256746eda2ce3e0e73f256d0'
 
# May be shared with the outside world.
INSTANCE_UNIQUE = uuid.uuid4().hex
 
def default():
    return {
        'basic-auth':True,
        'basic-auth-username':'MyUser',
        'basic-auth-password':'MySecret',
        'basic-auth-realm':'Secure area',
        'host': 'http://example.com'
    }
 
urls = [
    ('/*', default()),
]

# -*- coding: utf-8 -*-
 
# stdlib
import os.path as path, uuid
 
# Don't share it with anyone.
INSTANCE_SECRET = '5bf4e78c256746eda2ce3e0e73f256d0'
 
# May be shared with the outside world.
INSTANCE_UNIQUE = uuid.uuid4().hex
 
# Useful constants
cur_dir = path.dirname(__file__)
 
# Crypto
keyfile = path.join(cur_dir, './crypto/server-priv.pem')
certfile = path.join(cur_dir, './crypto/server-cert.pem')
ca_certs = path.join(cur_dir, './crypto/ca-cert.pem')
 
server_type = 'https'
 
def default():
    return {
        'ssl': True,
        'ssl-cert': True,
        'ssl-cert-commonName': 'My Client',
        'ssl-cert-organizationName': 'My Company',
        'host': 'http://example.com'
    }
 
urls = [
    ('/*', default()),
]

# -*- coding: utf-8 -*-
 
# stdlib
import uuid
 
# Don't share it with anyone.
INSTANCE_SECRET = '5bf4e78c256746eda2ce3e0e73f256d0'
 
# May be shared with the outside world.
INSTANCE_UNIQUE = uuid.uuid4().hex
 
def default():
    return {
        'wsse-pwd': True,
        'wsse-pwd-username': 'MyUser',
        'wsse-pwd-password': 'MySecret', # Needs to be given in clear text
        'wsse-pwd-reject-empty-nonce-creation': True,
        'wsse-pwd-reject-stale-tokens': True,
        'wsse-pwd-reject-expiry-limit': 120,
        'wsse-pwd-nonce-freshness-time': 120,
        'host': 'http://example.com'
    }
 
urls = [
    ('/*', default()),
]

Links:

Project’s homepage: http://sec-wall.gefira.pl/
Getting started: http://sec-wall.gefira.pl/documentation/getting-started/index.html
Usage examples: http://sec-wall.gefira.pl/documentation/usage-examples/index.html
Twitter: https://twitter.com/fourthrealm
Blog: http://www.gefira.pl/blog
IRC: #sec-wall channel on Freenode network

Cheers!

Share

Spring Python 1.2 & SSL/TLS XML-RPC (a friendly reminder)

February 20th, 2011 Dariusz Suchojad Comments off

Spring Python 1.2 has been released some time ago and one of its newest features is the support for wrapping XML-RPC with SSL/TLS, including both clients and servers. I’ve covered it already in a tutorial some time ago but I don’t think Planet Python was picking up the blog’s posts at that time, besides, Spring Python was still a release candidate when I was writing it so things could’ve changed (but they didn’t :-) ). In any case, now that 1.2 is a stable release, I thought I’d just mention it in case you were looking for means to secure your XML-RPC traffic. Here’s the reference material and the tutorial is here.

Share

HOWTO: Installing Spring Python and its dependencies

December 17th, 2010 Dariusz Suchojad Comments off

Depending on your exact needs and the work environment, there are several ways to install Spring Python with its dependencies and this post will go through what’s available.

First thing is, install pip, this is the command line tool to use for installing Python software.

Here are the installation options:

  1. Telling pip to fetch it from PyPI and then install it, type:
    • pip install springpython, this will download the latest version – currently it’s 1.2.0RC1 – from the remote repository and install it
  2. Downloading the source installer from the Spring Source site manually – the link always points to the latest release of Spring Python though you can also pick from any previous releases. At the time of this writing, the latest release is springpython-1.2.0.RC1.tar.gz, place it in any directory and type pip install springpython-1.2.0.RC1.tar.gz to install it.
  3. Using the latest git version (read only) – create a directory, cd to it and type:
    • git clone git://git.springsource.org/spring-python/spring-python.git, this will create a local clone of the latest version of the source code
    • cd spring-python
    • python build.py –package, note the name of a tar.gz package that will be created, in my case it was springpython-1.3.0.BUILD-20101216115817
    • pip install target/artifacts/springpython-1.3.0.BUILD-20101216115817.tar.gz to install the newly built package
  4. Using the latest git version (read/write, for core developers) – same as the previous point except for the first command, its should be git clone git@git.springsource.org:spring-python/spring-python.git, note that the command is very similar to the previous one and for me personally it used to be a source of confusion, but that I guess is a git thing.
  5. Using the DEB installer created by Sven Wilhelm and Miguel Landaeta
    • Add deb http://ppa.wiredobjects.eu/springpython/ppa/debian experimental main to /etc/apt/sources.list
    • Issue sudo apt-get update && apt-get sudo install python-springpython to refresh the sources list and install Spring Python
    • Note there’s ‘debian’ in the name but I’ve had no problems with using the DEB on Ubuntu 10.04

A word on the project’s dependencies. The commands above will install Spring Python but it still doesn’t mean you’ll be able to make use of each and every piece of functionality. The installation methods mentioned above haven’t been (yet) broken into smaller ones, each of which could express its own concrete dependencies and on the other hand, adding a huge list of mandatory dependencies would clearly be an overkill, after all, not every project needs to access XML, Yaml, Oracle, Postgres, JMS WebSphere MQ, Pyro, Hessian, CherryPy and other external resources at the same time :-) So, once you install Spring Python, you still need to check the documentation for a list of things to install before you can start using a given Spring Python’s feature. Each chapter has a section devoted to listing all the dependencies or stating that a stock Python distribution will do, see for instance Security, AOP, IoC, JMS and Remoting.

I hope it sheds some light on the matter, in case you need any help, be sure to drop in the forum, mailing list or the IRC channel (#springpython on Freenode network). See you!

Share

Securing XML-RPC with Spring Python and SSL/TLS

November 5th, 2010 Dariusz Suchojad No comments

One of the new features in Spring Python 1.2 is the support for wrapping the XML-RPC conversations in SSL/TLS, sporting plain encryption of communications as well as advanced tricks such as configuring the server to validate the particular values of a client’s certificate. There’s already a good deal of documentation available at the project’s website and in addition to that this article will guide you through the process of setting things up and configuring both secure clients and servers.

Note that I’m using Linux but it’s simply a matter of personal preferences, things will work as well on Windows, Solaris, AIX or any other system. It’s all pure Python with no C modules underneath.

Let’s go ahead and use pip to install Spring Python:

$ sudo pip install springpython

The command above will contact PyPI and fetch the latest release of Spring Python, which at the time of this writing is 1.2.0.RC1.

What will also come handy is the bundle of sample certificates and keys the Spring Python team has prepared for people learning the API, so let’s download it now

$ wget -c http://springpython.webfactional.com/1.2.x/sphinx/html/_static/pki.zip

The archive contains crypto material for a simple PKI, two CAs and two end users of the certificates – an XML-RPC server and its client. Take a look at the diagram at the project’s site which depicts closely whose certificate has been signed off by whom and who to trust in this PKI. There’s plenty of time for playing around with the API, the certificates will not expire until 2020.

A word of caution – the ZIP archive you’ve just downloaded contains the private keys and that basically renders it unsuitable for any use outside your development box, in real life scenarios you need to make sure you use certificates signed off by your company’s CA(s), or by a CA of your own; just make sure you’re not using the samples for anything close to production environments.

OK, let’s create a first client/server pair, one which will simply have the link encrypted and which will also make sure the certificates have been signed off by a known CA. The client sure has to authenticate itself to the server and in this simple case it will be using a username/password combination.

Client code:

# -*- coding: utf-8 -*-
 
# 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.get_randint("user", "password")

Server code:

# -*- coding: utf-8 -*-
 
# stdlib
import random, ssl, sys
 
# Spring Python
from springpython.remoting.xmlrpc import SSLServer
 
random.seed()
 
class MySSLServer(SSLServer):
 
    security_db = {"user": "password"}
 
    def get_randint(self, username, password):
        if self.security_db.get(username, "") == password:
            return random.randint(0, 1000)
 
        raise Exception("I don't know you!")
 
    def register_functions(self):
        self.register_function(self.get_randint)
 
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_OPTIONAL)
server.serve_forever()

Client doesn’t need anything except for the file (ca_certs) containing a list of CAs it is willing to trust, that is, the server’s certificate will be accepted by the client only if it’s been signed off by a CA whose certificate is in this file. The file may contain an unlimited number of certificates, including chains of certificates.

The server code is a tad more complex but only a little bit. In addition to “ca_certs” parameter which has exactly the same purpose as it had on the client end, the server needs to have its own certificate and a private key. On the business-level side of things, you can notice that just like in the stdlib, you’re using self.register_function for exposing methods over the wire. Oh, and make a mental note that the server’s cert_reqs parameters is ssl.CERT_OPTIONAL. What it means is that we don’t really care whether client sends us any certificate, it may do so but that’s no big deal if it doesn’t.

So that’s how things could possibly stay if it weren’t for the authentication. I don’t know about you but I personally can’t stand the fact that a business method (get_randint) has been tasked with checking the client’s credentials. It could be worked around in several ways but for the sake of this article we’re going to make client application use its (drumroll..)  client certificate! It makes sense to use one because we already have a PKI and the server’s using its own certificate. While we’ll be at making the client use a certificate, we’ll also tell the server to let the client in only if a given set of fields in the client certificate matches the server’s configuration. To be precise – we’ll make sure that the client cert’s commonName is equal to My Client and organizationName is My Company. It follows that we’ll change the cert_reqs value to ssl.CERT_REQUIRED at the same time.

Here’s the code for the client:

# -*- 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.get_randint()

.. and the server:

# -*- coding: utf-8 -*-
 
# stdlib
import random, ssl
 
# Spring Python
from springpython.remoting.xmlrpc import SSLServer
 
random.seed()
 
class MySSLServer(SSLServer):
 
    def get_randint(self):
        return random.randint(0, 1000)
 
    def register_functions(self):
        self.register_function(self.get_randint)
 
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"}
 
server = MySSLServer(host, port, keyfile, certfile, ca_certs,
                  cert_reqs=ssl.CERT_REQUIRED, verify_fields=verify_fields)
server.serve_forever()

And that’s all there is to it, at least in this case. The server concerns itself with, well, services only, the link is encrypted, the client is being authenticated and everyone’s happy :-) These aren’t all the options you can use but the rest is there, in the project’s documentation so go have a read if you need more customization. And in case you have any questions, need help or just want to chat, be sure to drop by on IRC (#springpython@Freenode), visit the project’s forum or the mailing list. Cheers!

Share

Spring Python 1.1 book review

August 22nd, 2010 Dariusz Suchojad No comments

Here’s a copy of my Amazon’s Spring Python 1.1 book review.

Spring Python 1.1 is a book for professional programmers who either wish to venture into a world beyond traditional OOP and tackle problems differently thanks to Spring Python’s IoC & AOP features or simply have practical issues – such as spreading the application across multiple nodes – to solve. The book does not merely rephrase the comprehensive reference documentation available at the project’s website and I was actually very curious on how the author would be laying out the material.

Greg took a very good approach of explaining the basic of IoC first and building upon it in subsequent chapters although they can all be read separately. All of the major Spring Python’s building blocks are covered and I can only applaud that despite its Java roots almost no XML is used – that’s a good news for all Python programmers fearing that “Spring” means a lot of unnecessary XML, the prevailing majority of examples is in pure Python. Java programmers wishing to leverage their existing Spring skills won’t be disappointed though as there are some chapters devoted to how one can easily migrate a Spring Java IoC container over to Spring Python one, there’s also a discussion of how to use Spring Python & Jython which, along with CPython, is also a target Python implementation Spring Python can run on. But let that not confuse you, the book is mostly written for Python programmers.

I liked it that the book was focusing on getting things done without turning aside into discussing vaguely related concepts or nuances, for instance the chapter on SQL doesn’t do any ‘quick recaps’ of what SQL and relational databases are as is unfortunately quite common in many other books. On the other hand, not everyone is familiar with IoC and AOP and I know that anyone unfamiliar with those concepts – regardless of their primary programming language of choice – can easily take the book and get acquainted with them. A superb idea was that most of the chapters teach you how to write unit tests for the given Spring Python component you’d want to use in your code, IoC, AOP, security, database access and so on, that’s an often neglected area and I was very happy to see the author having placed an importance on it. Another good idea was to create a case-study chapter which combines all the knowledge and discusses it thoroughly.

All in all, it’s a good book written by a practitioner for practitioners, full of diagrams and examples that by necessity are simple yet don’t feel contrived and it’s certainly not something you need to plough through, it does a very good job of introducing the reader to Spring Python and showing how to use its pluggable components to write interesting and useful applications solving real-world problems. If I were to pick a nit I’d only say that it would be nice if the second edition contained at least a couple of examples of using Spring Python’s IoC YamlConfig, but that’s only a matter of syntax so the issue is really minor.

Share
Categories: Software Tags: ,

Intro To Spring Python Slides

June 3rd, 2010 Dariusz Suchojad No comments

For those of you who are still wavering over whether to use Spring Python in your next project, have a look at this slidecast describing the current Spring Python project’s status, as of release 1.1, and where it’s heading to.

Share
Categories: Software Tags:

An introduction to Spring Python and IoC/DI (Inversion of Control/Dependency Injection)

Continuing on the last post, we’ll now be extending the code with an IoC (Inversion of Control)-driven configuration. Spring Python offers an IoC container which may be used for injecting dependencies between the application’s objects. The concept is that objects are to focus on their job and usually should be blissfully unaware of where the other objects they operate upon come from. The dependencies are dynamically injected so the control of who uses what is sort of inverted, it’s the container who knows what and when to distribute to others in order for everything to play nicely together and that’s why language lawyers can’t decide whether to call it IoC (Inversion of Control) or DI (Dependency Injection) but let that not stop us from applying it in practice :-) And of course it’s only a concept, it’s not something magical, it’s simply a decent addition to your toolbox which you may like to get familiar with and Spring Python with its gentle learning curve is just the right tool. Throughout the article, Spring Python 1.1 will be used.

Picking off from where we left last time, here’s a bare bones ‘uptime’ server and its client.

# An 'uptime' server
 
# stdlib
import logging
import commands
 
# Spring Python
from springpython.remoting.pyro import PyroServiceExporter
 
# Configure logging subsystem
logging.basicConfig(level=logging.DEBUG)
 
class Service(object):
    def get_uptime(self):
        logging.info("get_uptime invoked")
        return commands.getoutput("uptime")
 
# Create a service ..
service = Service()
 
# .. export it through Pyro ..
service_exporter = PyroServiceExporter()
service_exporter.service = service
service_exporter.service_name = "Service"
service_exporter.service_host = "127.0.0.1"
service_exporter.service_port = "16099"
 
# .. and start the server.
service_exporter.after_properties_set()
# Client
 
# stdlib
import logging
 
# Spring Python
from springpython.remoting.pyro import PyroProxyFactory
 
# Configure logging subsystem
logging.basicConfig(level=logging.DEBUG)
 
# Point the client to a server ..
service = PyroProxyFactory()
service.service_url = "PYROLOC://127.0.0.1:16099/Service"
 
# .. invoke the service ..
uptime = service.get_uptime()
 
# and print it on the console.
logging.info("Current uptime: [%s]" % uptime)

A word on Spring Python’s IoC syntax, there’s a plenty of choices to pick from, with three major ones being PythonConfig, YamlConfig and XMLConfig. Being agile Python folks as we are, we chose to use YamlConfig and PythonConfig for configuring things but it should be noted that all other config modes would’ve allowed us to achieve the same.

As always, it’s all up to you to decide what makes more sense in given circumstances though my recommendation is to use YamlConfig or XMLConfig with a strong preference on the former if you work in a diverse Python/Java/.NET environment with people who already know Spring Framework and PythonConfig/YamlConfig if you’re free to choose whatever you like.

So here’s the server’s basic configuration written in YAML..

# server-config.yml
 
objects:
    - object: service_name
      str: Service
 
    - object: service_host
      str: 127.0.0.1
 
    - object: service_port
      str: 16099

.. and here’s some code to read if off a file we stored it in:

# ioc-yaml-sample.py
 
# Spring Python
from springpython.config import YamlConfig
from springpython.context import ApplicationContext
 
config = YamlConfig("./server-config.yml")
container = ApplicationContext(config)
 
service_name = container.get_object("service_name")
print "Hey, the name is '%s'" % service_name

YamlConfig’s instance reads the file, parses it, creates an internal cache of definitions found and then the config may be fed to ApplicationContext which is the actual class used for fetching objects from a container. Why is the class called ApplicationContext instead of Container, you wonder? Well in fact the actual container is called springpython.container.ObjectContainer and ApplicationContext subclasses it in order to add some additional goodies such as the possibility of hooking into an object’s lifecycle.

Let’s now introduce server to the config and let’s create the main application’s module, boringly named ‘main.py’, so that in result we have following files ..

.
|-- client.py
|-- main.py
|-- server-config.yml
`-- server.py

.. with such a contents ..

# client.py (no changes here so far)
 
# stdlib
import logging
 
# Spring Python
from springpython.remoting.pyro import PyroProxyFactory
 
# Configure logging subsystem
logging.basicConfig(level=logging.DEBUG)
 
# Point the client to a server ..
service = PyroProxyFactory()
service.service_url = "PYROLOC://127.0.0.1:16099/Service"
 
# .. invoke the service ..
uptime = service.get_uptime()
 
# and print it on the console.
logging.info("Current uptime: [%s]" % uptime)
# main.py
 
# stdlib
import logging
 
# Spring Python
from springpython.config import YamlConfig
from springpython.context import ApplicationContext
 
config = YamlConfig("./server-config.yml")
container = ApplicationContext(config)
 
# Fetch configuration from the container ..
log_level = container.get_object("log_level")
log_format = container.get_object("log_format")
 
# .. and configure logging.
logger = logging.getLogger("")
logger.setLevel(logging.getLevelName(log_level))
 
handler = logging.StreamHandler()
 
formatter = logging.Formatter(log_format)
handler.setFormatter(formatter)
 
logger.addHandler(handler)
# server-config.yml
 
objects:
    - object: service_name
      str: Service
 
    - object: service_host
      str: 127.0.0.1
 
    - object: service_port
      str: 16099
 
    - object: service
      class: server.Service
 
    - object: exporter
      class: springpython.remoting.pyro.PyroServiceExporter
      properties:
        service: {ref: service}
        service_name: {ref: service_name}
        service_host: {ref: service_host}
        service_port: {ref: service_port}
 
    - object: log_level
      str: DEBUG
 
    - object: log_format
      str: "%(asctime)s - %(levelname)s - %(process)d:%(threadName)s - %(name)s:%(lineno)d - %(message)s"
# server.py
 
# stdlib
import logging
import commands
 
class Service(object):
    def __init__(self):
        self.logger = logging.getLogger("%s:%s" % (__name__, self.__class__.__name__))
 
    def get_uptime(self):
        self.logger.info("get_uptime invoked")
        return commands.getoutput("uptime")

So what have we achieved? The actual business logic has been decoupled from its configuration so that it doesn’t know anything about Pyro because, after all, should it care about it? Its business is to return the current uptime and that’s what it’s good at. That it’s going to be exposed through Pyro is also not that important for its core functionality, it’s Pyro today but could be Zope tomorrow, who knows it. PyroServiceExporter also happily lives in a state of not really being aware of what service it is publishing outside. Changing either of those should not mean touching the source code even if it meant merely adding a couple of lines of code. And the other stuff, it’s just a simple configuration we had to externalize anyway so why not put it into same place for consistency? To keep things simpler, the client code hasn’t been changed yet though it will be later on when we’ll be working with PythonConfig.

When choosing what to place in the container there’s always a danger of going too far and letting it all look awkwardly and unpythonic or just plain ugly. As a rule of thumb, your application surely can be explained in terms of some kind of components which need to be wired up together and that’s where the division line could be initially drawn. Another point is standard configuration details such as host, port or the logging format above, it’s something that sysadmins love to customize so it should allow for an easy access (and that incidentally is another reason why XMLConfig is something you should think twice about before making use of as sysadmins usually have their, let’s call it, well-grounded opinion about working with XML). Remember how several years ago some applications were doing crazy things like ..

<query id="get_customer">
  <table name="customer" pkey="cust_no">
    <columns>
      <column name="cust_name" />
      <column name="address" />
      <column name="phone_no" />
    </columns>
  </table>
  <operator name="EQ" />
</query>

.. instead of ..

get_customer = "SELECT cust_name, address, phone_no FROM customer WHERE cust_no=?"

.. and they used to call it being “XML-ready” or “XML-aware”? Well, if you catch yourself on doing similar things with IoC then you know you’ve gone too far :-)

If you happened to jump directly into running the code above instead of reading the text then you probably were wondering why the server had been started even though nowhere in the code we explicitly told it to. That’s because PyroServiceExporter is an InitializingObject – it’s a subclass of springpython.context.InitializingObject which means its after_properties_set method gets called right after all of its properties are resolved and set in place and it just happens that PyroServiceExporter starts a new server thread in its after_properties_set hook. If you scroll up to the beginning of this post you’ll recall that odd-looking service_exporter.after_properties_set() line that was advertized to ’start the server’, which it did -  but if you’re using IoC, the container will call it on your behalf so that you don’t have to remember about it.

Other interesting classes which your objects may wish to subclass are springpython.context.ObjectPostProcessor and springpython.context.DisposableObject. Subclassing ObjectPostProcessor gives you two new methods to override – post_process_before_initialization which will be invoked before after_properties_set and post_process_after_initialization which will be called after after_properties_set will have been called. DisposableObject on the other hand lets you implement a method called destroy which will be called at the time when the container itself will be shutting down; to be precise, ApplicationContext registers its ’shutdown_hook’ method to the Python stdlib’s atexit module and the shutdown hook is responsible for calling each of the disposable objects’ ‘destroy’ method; ‘destroy’ is the right place for your components to clean up after themselves, e.g. it can be used to release any external resources held during an object’s lifetime.

It’s worth noting that ApplicationContext may be passed in a list of configurations and although it won’t be used in examples, each of them may be of different type and that’s what I usually end up with, bits that I feel are more or less of a static nature are kept in YamlConfig and PythonConfig stores those which sometimes need to do something more before they’re ready to be used.

Before focusing on adding security features there’s still one thing that badly needs be improved, it’s the client code which knows nothing about IoC and breaks the DRY principle by duplicating configuration. In real world situation this could be in some degree expected, as both the server and a client could’ve been developed or operated by different teams or organizations but here it looks feebly, though at the same time it provides a good pretext for focusing a bit more on PythonConfig :-) Let’s rewrite it all into PythonConfig while keeping in mind the DRY rule then:

.
|-- client-main.py
|-- client_config.py
|-- common_config.py
|-- server-main.py
|-- server.py
`-- server_config.py
# client-main.py
 
# stdlib
import logging
 
# Spring Python
from springpython.context import ApplicationContext
 
# Our application
from client_config import ClientConfig
 
client_config = ClientConfig()
container = ApplicationContext(client_config)
 
# Fetch configuration from the container ..
log_level = container.get_object("log_level")
log_format = container.get_object("log_format")
 
# .. and configure logging.
logging.basicConfig(level=logging.getLevelName(log_level), format=log_format)
 
# .. fetch the service ..
service = container.get_object("service")
 
# .. invoke it ..
uptime = service.get_uptime()
 
# and print the output.
logging.info("Current uptime: [%s]" % uptime)
# client_config.py
 
# Spring Python
from springpython.config import Object
from springpython.remoting.pyro import PyroProxyFactory
 
# Our application
from common_config import CommonConfig
 
class ClientConfig(CommonConfig):
 
    @Object
    def service(self):
        service = PyroProxyFactory()
        service.service_url = "PYROLOC://%s:%s/%s" % (self.service_host(),
                                self.service_port(), self.service_name())
        return service
# common_config.py
 
# stdlib
import logging
 
# Spring Python
from springpython.config import Object
from springpython.config import PythonConfig
 
class CommonConfig(PythonConfig):
 
    @Object
    def service_host(self):
        return "127.0.0.1"
 
    @Object
    def service_port(self):
        return "16099"
 
    @Object
    def service_name(self):
        return "Service"
 
    @Object
    def log_level(self):
        return "DEBUG"
 
    @Object
    def log_format(self):
        return "%(asctime)s - %(levelname)s - %(process)d:%(threadName)s - %(name)s:%(lineno)d - %(message)s"
# server-main.py
 
# stdlib
import logging
 
# Spring Python
from springpython.context import ApplicationContext
 
# Our application
from server_config import ServerConfig
 
server_config = ServerConfig()
container = ApplicationContext(server_config)
 
# Fetch configuration from the container ..
log_level = container.get_object("log_level")
log_format = container.get_object("log_format")
 
# .. and configure logging.
logging.basicConfig(level=logging.getLevelName(log_level), format=log_format)
# server.py (no changes here)
 
# stdlib
import logging
import commands
 
class Service(object):
    def __init__(self):
        self.logger = logging.getLogger("%s:%s" % (__name__, self.__class__.__name__))
 
    def get_uptime(self):
        self.logger.info("get_uptime invoked")
        return commands.getoutput("uptime")
# server_config.py
 
# Spring Python
from springpython.config import Object
from springpython.remoting.pyro import PyroServiceExporter
 
# Our application
from server import Service
from common_config import CommonConfig
 
class ServerConfig(CommonConfig):
 
    @Object
    def service(self):
        return Service()
 
    @Object
    def exporter(self):
        exporter = PyroServiceExporter()
 
        exporter.service = self.service()
        exporter.service_name = self.service_name()
        exporter.service_host = self.service_host()
        exporter.service_port = self.service_port()
 
        return exporter

Methods decorated with an @Object decorator are turned into container-managed objects and by default they are stored in an internal cache of singleton objects as opposed to being fetched each time they are requested. Objects can also be lazily-initialized when they are first time needed by someone. Finally, they can be abstract and/or have parents. Let’s discuss the options through.

@Object is a short-hand form of @Object(scope.SINGLETON) and these two are equivalent.

# Spring Python
from springpython.config import Object
from springpython.config import PythonConfig
 
from foo import SomeClass
 
class MyConfig(PythonConfig):
 
    @Object
    def my_singleton(self):
        singleton = SomeClass()
        return singleton
# Spring Python
from springpython.context import scope
from springpython.config import Object, PythonConfig
 
from foo import SomeClass
 
class MyConfig(PythonConfig):
 
    @Object(scope.SINGLETON)
    def my_singleton(self):
        singleton = SomeClass()
        return singleton

The code below defines a regular object which won’t be stored in any cache, its value, and if fact the object returned, will be different each time the ‘random_password’ will be requested from a container.

# stdlib
from uuid import uuid4
 
# Spring Python
from springpython.context import scope
from springpython.config import Object, PythonConfig
 
class MyConfig(PythonConfig):
 
    @Object(scope.PROTOTYPE)
    def random_password(self):
        return uuid4().hex

If an object is marked as being lazily-initialized (which isn’t the default behaviour) it won’t be created until someone will try to reference it for the first time. This is in contrast to the default container’s action which is to eagerly create an object upon first seeing its definition in the config. In Python programming terms, it means that the first method below will be invoked as soon as the container will come across it whereas the second one won’t be.

# Spring Python
from springpython.config import Object
from springpython.config import PythonConfig
 
from foo import SomeClass
 
class MyConfig(PythonConfig):
 
    @Object
    def my_singleton(self):
        singleton = SomeClass()
        return singleton
# Spring Python
from springpython.config import Object
from springpython.config import PythonConfig
 
from foo import SomeClass
 
class MyConfig(PythonConfig):
 
    @Object(lazy_init=True)
    def my_singleton(self):
        singleton = SomeClass()
        return singleton

As in real life, parent-child relationships can be a bit more complex because an object can be both a parent and a child of yet another objects. Objects that shouldn’t be fetched directly from the container are quite obviously called abstract ones. If an object wishes to subclass another one, it needs to name the parent in its parent attribute. Note that credentials and crm_credentials objects below need to be made prototypes as they would be otherwise cached and crm_dev_credentials, crm_uat_credentials and ivr_credentials would be referencing the same Python objects.

# Spring Python
from springpython.context import scope
from springpython.config import Object
from springpython.config import PythonConfig
 
class Credentials(object):
    def __init__(self, app=None, user=None, password=None):
        self.app = app
        self.user = user
        self.password = password
 
    def __str__(self):
        return "%s %s %s" % (self.app, self.user, self.password)
 
class ServerConfig(PythonConfig):
 
    @Object(scope.PROTOTYPE, abstract=True)
    def credentials(self):
        return Credentials("MyApp")
 
    @Object(scope.PROTOTYPE, parent="credentials")
    def crm_credentials(self, credentials=None):
        credentials.user = "MYUSER"
        return credentials
 
    @Object(parent="crm_credentials")
    def crm_dev_credentials(self, credentials=None):
        credentials.password = "MYPASSWORD1"
        return credentials
 
    @Object(parent="crm_credentials")
    def crm_uat_credentials(self, credentials=None):
        credentials.password = "MYPASSWORD2"
        return credentials
 
    @Object(parent="credentials")
    def ivr_credentials(self, credentials=None):
        credentials.user = "FOO"
        credentials.password = "BAR"
        return credentials

Armed with all that knowledge we can finally start configuring security stuff. The method returning an uptime shouldn’t of course be troubled with authenticating incoming requests, that should be a responsibility of some kind of a security provider and the server should have a dependency on it. We’ll add a new module and update a few.

.
|-- client-main.py
|-- client.py
|-- client_config.py
|-- common_config.py
|-- security.py
|-- server-main.py
|-- server.py
`-- server_config.py

Only new or modified modules are shown below:

# security.py
 
class UsernamePasswordCredentials(object):
 
    def __init__(self, username, password):
        self.username = username
        self.password = password
 
    def __str__(self):
        return "&lt;%s at %s, username=%s&gt;" % (self.__class__.__name__, hex(id(self)),
                    self.username)
 
class UsernamePasswordSecurityProvider(object):
 
    def __init__(self, database=None):
        self.database = database
 
    def authenticate(self, credentials):
        if credentials.username in self.database:
            return self.database[credentials.username] == credentials.password
# server.py
 
# stdlib
import logging
import commands
 
class Forbidden(Exception):
    pass
 
class Service(object):
    def __init__(self, security_provider=None):
        self.security_provider = security_provider
        self.logger = logging.getLogger("%s:%s" % (__name__, self.__class__.__name__))
 
    def get_uptime(self, credentials):
        self.logger.info("get_uptime invoked")
 
        if self.security_provider.authenticate(credentials):
            self.logger.info("Letting the client in")
            return commands.getoutput("uptime")
 
        self.logger.info("Unknown client %s" % credentials)
 
        raise Forbidden("No chance mate!")
# server_config.py
 
# Spring Python
from springpython.config import Object
from springpython.remoting.pyro import PyroServiceExporter
 
# Our application
from server import Service
from common_config import CommonConfig
from security import UsernamePasswordSecurityProvider
 
class ServerConfig(CommonConfig):
 
    @Object
    def service(self):
        service = Service()
        service.security_provider = self.security_provider()
 
        return service
 
    @Object
    def exporter(self):
        exporter = PyroServiceExporter()
 
        exporter.service = self.service()
        exporter.service_name = self.service_name()
        exporter.service_host = self.service_host()
        exporter.service_port = self.service_port()
 
        return exporter
 
    @Object
    def security_provider(self):
        security_provider = UsernamePasswordSecurityProvider()
        security_provider.database = self.security_database()
 
        return security_provider
 
    @Object
    def security_database(self):
        return {"FOO": "BAR"}
# client-main.py
 
# stdlib
import logging
 
# Spring Python
from springpython.context import ApplicationContext
 
# Our application
from client_config import ClientConfig
from security import UsernamePasswordCredentials
 
client_config = ClientConfig()
container = ApplicationContext(client_config)
 
# Fetch configuration from the container ..
log_level = container.get_object("log_level")
log_format = container.get_object("log_format")
 
# .. and configure logging.
logging.basicConfig(level=logging.getLevelName(log_level), format=log_format)
 
# .. fetch the service ..
service = container.get_object("service")
 
# .. invoke it ..
credentials = UsernamePasswordCredentials("FOO", "BAR")
uptime = service.get_uptime(credentials)
 
# and print the output.
logging.info("Current uptime: [%s]" % uptime)

It’s the security provider’s burden to authenticate clients and server only needs a simple yes/no decision before returning data or raising an exception. Setting it up in that fashion, the server won’t have to be touched at all should we wish to change providers and start authenticating clients in some other way, for instance, through SSL certificates. And I know you’ll start yawning when I tell you once again that I think it’s not a provider’s business how to get its hand on the usernames/passwords database either, it just needs to operate on it.

A nice side-effect of not importing dependencies directly is that it’s easier to test the components in isolation without resorting to patching (which still comes handy at times) – replacing real objects with mocks and stubs becomes only a matter of a container’s returning different objects, though of course they still need to coded to given interfaces. Given that PythonConfig is pure Python code, it’s laughably easy to use  it in tests, you just need to subclass the real config and return mock objects instead of real ones. For instance, we don’t really need to start a new Pyro thread just to make sure server and security provider can play nicely together – Pyro is just the transport layer and has nothing to do with how other components co-operate.

.
|-- client-main.py
|-- client.py
|-- client_config.py
|-- common_config.py
|-- security.py
|-- server-main.py
|-- server.py
|-- server_config.py
|-- server_test_config.py
`-- test_all.py

(Only new code below)

# server_test_config.py
 
# Spring Python
from springpython.config import Object
 
# Our application
from server_config import ServerConfig
 
class _DummyExporter(object):
    pass
 
class TestServerConfig(ServerConfig):
 
    @Object
    def exporter(self):
        return _DummyExporter()
# test_all.py
 
# stdlib
import unittest
 
# Spring Python
from springpython.context import ApplicationContext
 
# Our application
from server_test_config import TestServerConfig
from security import UsernamePasswordCredentials
 
class ServerTestCase(unittest.TestCase):
 
    def setUp(self):
        self.container = ApplicationContext(TestServerConfig())
        self.default_credentials = UsernamePasswordCredentials("FOO", "BAR")
 
    def tearDown(self):
        self.container.shutdown_hook()
 
    def test_uptime(self):
        service = self.container.get_object("service")
        self.assertTrue(service.get_uptime(self.default_credentials))
 
if __name__ == "__main__":
    unittest.main()

Well, that’s about all for now. I’ve only touched the tip of the iceberg and showed you toy-size examples though it is almost always the case that IoC starts to shine when the code gets bigger and bigger -  it’s just that creating a full-fledged application seemed a bit too much for a blog post :-) Is IoC a panacea? Of course it isn’t, when not used judiciously you can quickly go from one extreme to another and instead of having dependencies intertwined right in the code you can lose the sight of the bigger picture of how control flows through an application’s building blocks simply because everything will seem too magical and declarative. As always, don’t overengineer things and don’t overdo with IoC, especially if you choose to use XML.

There’s also a question of whether Python really needs IoC, isn’t that really useful only in less dynamic languages like Java. Well, I truly believe that IoC should not really, or maybe not only, be thought of as a way to circumvent their way of dealing with changes to the source code, namely, that it needs to be recompiled each and every time, which is something that sometimes takes ages. It’s rather a fine way of bringing in more order and letting the programmers focus on their job while at the same time letting them be less afraid of introducing changes which could otherwise have a negative impact on applications. And it really helps in testing!

In case you ever need help with Spring Python or just want to chat, here’s the mailing list, a forum, a LinkedIn group,  an IRC #springpython channel on Freenode network and there’s also a Spring Python 1.1 book upcoming.

Share
Categories: Software Tags: ,

Spring Python, Pyro and remote services

April 25th, 2010 Dariusz Suchojad Comments off

One of the Spring Python’s features is a wrapper around Pyro, a Python Remote
Objects library which makes it very easy to expose Python applications as network services
running across distributed systems. You write ordinary Python code that is unaware
of any networking and Spring Python lets you publish and invoke it through
its PyroServiceExporter and PyroProxyFactory objects. The setup uses a binary
pickle protocol underneath so it’s only available if both sides, both client and the
server, are written in Python.

Here’s how the service’s code for returning current system’s uptime might look
like. As you can see, there’s nothing Spring Python going on there and in fact
it’s just a simple class with just one method which knows nothing about Spring
Python nor is it aware that it’s going to be available through any network.
Staying out of your way and letting you reuse an already existing code is one
of the biggest advantages of Spring Python, sure, you need to learn its API
and principles but you can still simply code in Python and not be afraid of
getting caught in some artificial maze of interfaces and dependencies.

# stdlib
import logging
import commands

# Configure logging subsystem
logging.basicConfig(level=logging.DEBUG)

class Service(object):
    def get_uptime(self):
        logging.info("get_uptime invoked")
        return commands.getoutput("uptime")

# Create a service ..
service = Service()

# .. get the uptime ..
uptime = service.get_uptime()

# and print it on the console.
logging.info("Current uptime: [%s]" % uptime)

And here’s the Spring Python’s approach to exposing the service through Pyro
protocol, new bits have been highlighted in green. PyroServiceExporter is an
object which grabs a service, lets you set its name and let it run on a given
host and port. Note that the service’s code hasn’t been changed at all.

# stdlib
import logging
import commands

# Spring Python
from springpython.remoting.pyro import PyroServiceExporter

# Configure logging subsystem
logging.basicConfig(level=logging.DEBUG)

class Service(object):
    def get_uptime(self):
        logging.info("get_uptime invoked")
        return commands.getoutput("uptime")

# Create a service ..
service = Service()

# .. export it through Pyro ..
service_exporter = PyroServiceExporter()
service_exporter.service = service
service_exporter.service_name = "Service"
service_exporter.service_host = "127.0.0.1"
service_exporter.service_port = "16099"

# .. and start the server.
service_exporter.after_properties_set()

Such a service needs a client now and here’s the code that invokes the server
looks like, key parts are in green again. PyroProxyFactory needs a server’s
location and then invoking a server is only a matter of invoking a method of
an object, it can’t be easier than that :-)

# stdlib
import logging

# Spring Python
from springpython.remoting.pyro import PyroProxyFactory

# Configure logging subsystem
logging.basicConfig(level=logging.DEBUG)

# Point the client to a server ..
service = PyroProxyFactory()
service.service_url = "PYROLOC://127.0.0.1:16099/Service"

# .. invoke the service ..
uptime = service.get_uptime()

# and print it on the console.
logging.info("Current uptime: [%s]" % uptime)

Of course there’s still room for improvement. Firstly, there’s no authorization
of whatsoever, everyone can invoke the service. Secondly, the configuration of
both parties is written directly in the source code. It would be much better if
it all were externalized to some other place and that’s exactly what the next
installment will be about, we’ll be using Spring Python’s Inversion of Control
features to decouple the actual implementation from its configuration.

Update: here is the second part which shows Spring Python’s IoC in action.

Share
Categories: Software Tags: ,

Spring Python i wprowadzenie do IoC (Inversion of Control)

December 22nd, 2009 Dariusz Suchojad Comments off

Jednym z feature’ów Spring Pythona jest kontener IoC, Inversion of Control, poniższy artykuł przedstawia konfigurację kontenera przy wykorzystaniu Spring Pythona w wersji 1.0.

Do czego może służyć Inversion of Control? Oczywiście do odwrócenia kontroli! :-) Cały trick polega na tym, żeby istniał pewien byt – właśnie kontener IoC – który zarządza zależnościami pomiędzy modułami aplikacji. Kontener taki bywa również określany mianem kontekstu aplikacji.

Klasy Pythona są bardzo dobre, moduły są dobre, pakiety, funkcje i metody także są dobre, kontener jednak pozwala w dodatkowy sposób podzielić aplikację na moduły, niezależnie od tego czy poszczególne moduły są pojedynczymi klasami, instancjami, funkcjami czy może po prostu stringami lub integerami.

Na czym polega odwrócenie? Na tym, że moduły aplikacji (nazywane dalej obiektami kontenera, kontekstu) nie tworzą bezpośrednio zależności pomiędzy sobą, np. nie importują siebie nawzajem. Pisząc więc klasę reprezentującą klienta, który wywołuje zdalną usługę serwera i jednocześnie uwierzytelnia się przed tym serwerem, nie importujemy w klasie klienckiej klasy implementującej uwierzytelnianie, a jedynie korzystamy z tej klasy  uwierzytelniającej – pozwalamy kontenerowi na to, żeby nam w runtimie wstrzyknął sam odpowiednia skonfigurowaną klasę, która zapewni uwierzytelnianie. Ostatecznie, klasa, która ma wywołać usługę serwera nie musi sobie zawracać głowy tym, skąd wziąć i jak skonfigurować uwierzytelnianie, prawda? Jej rolą, zadaniem i zmartwieniem jest wywołanie serwera, a nie związane z tym kwestie poboczne. Jeśli serwer kiedyś zmieni (a zawsze następuje to wcześniej niż można spodziewać się) sposób uwierzytelniania to nie będziemy musieli w ogóle dotykać klasy klienckiej – a ostatecznie celem jest to, żeby dobrze przetestowanego i spełniającego na produkcji swoją biznesową rolę kodu nie zmieniać (w każdym razie, nie zmieniać bez potrzeby).

Kluczowe w idei IoC jest to, że kontener konfigurowany jest deklaratywnie, jest to raczej statyczna konfiguracja niż miejsce, w który można poszaleć z pętlami czy wyjątkami. Ma to taką miłą zaletę, że kontener konfigurujemy z boku całej aplikacji, bez zmian w jej kodzie implementującym logikę biznesową.

Inną cechą kontekstu jest to, że obiekty definiowane w nim mogą być pobierane z niego jako singletone’y lub jako zwykłe instancje klas (tzw. prototypy).

IoC znakomicie też ułatwia testowanie aplikacji, całość składana jest z klocków, więc nie ma problemu z tym, żeby prawdziwe klocki, te, które faktycznie tworzą aplikację, zastępować mockami. Dzięki temu łatwo pisze się unittesty.

W wersji 1.0 Spring Pythona, IoC można skonfigurować korzystając przede wszystkim z dwóch składni:

- XMLConfig – pozwala na konfigurację kontenera w XML-u
- PythonConfig – umożliwiwa konfigurację kontenera w samym Pythonie

Od wersji 1.1 w Spring Pythonie dostępny będzie także YamlConfig, jak wynika z nazwy – IoC będzie można też konfigurować w YAML-u i jest to bardzo wygodne, choć jest to już sprawa na inny artykuł.

W dalszej części będę używał PythonConfiga, aczkolwiek dokładnie to samo można osiągnąć korzystając z XMLConfiga (a od wersji 1.1 także w YamlConfigu).

W porządku, załóżmy więc, że chcemy napisać aplikację, która wywoła usługę serwera zwracającą kursy walut. Trzeba więc utworzyć prosty serwer, który będzie zwracał kursy dla, powiedzmy, dwóch walut. Przed serwerem tym trzeba będzie też jakoś uwierzytelnić się.

Serwer będzie napisany w Pyro, bez użycia Spring Pythona, a poniżej jest jego kod źródłowy:

# -*- coding: utf-8 -*-

# server.py

import Pyro.core as pyro

class ExchangeRates(pyro.ObjBase):
    """ Minimalny serwer zwracający, dla wybranych walut, kursy wymiany złotego.
    """
    def get_exchange_rate(self, user, password, currency):
        if user != "foo" and password != "bar":
            return "FORBIDDEN"

        if currency == "EUR":
            return "4.1959"
        elif currency == "CHF":
            return "2.8080"
        else:
            return "UNRECOGNIZED_CURRENCY"

def run_server():
    """ Startuje serwer kursów walut."
    """
    pyro.initServer()
    daemon = pyro.Daemon()
    uri = daemon.connect(ExchangeRates(), "exchange-rates")

    print "Server started, port: %s" % daemon.port

    daemon.requestLoop()

if __name__ == "__main__":
    run_server()

I taki serwer spokojnie nam wystarczy, przejdźmy teraz do klienta, który będzie korzystał ze Spring Pythona. Tak się szczęśliwie składa, że Spring Python wspiera także korzystanie z Pyro, więc z tego też skorzystamy.

Pierwsza wersja klienta:

# -*- coding: utf-8 -*-

# client1.py

from springpython.remoting.pyro import PyroProxyFactory

class Client(object):
    def __init__(self, url, user, password):
        self.url = url
        self.user = user
        self.password = password

        self.proxy = PyroProxyFactory()
        self.proxy.service_url = self.url

    def get_exchange_rate(self, currency):
        return self.proxy.get_exchange_rate(self.user, self.password, currency)

client = Client("PYROLOC://127.0.0.1:7766/exchange-rates", "foo", "bar")
print client.get_exchange_rate("EUR")

Uruchomienie jej zakończy się sukcesem

$ python client1.py
Pyro Client Initialized. Using Pyro V3.7
4.1959
$

Użyjmy teraz IoC do skonfigurowania klienta, na początek przenieśmy tam po prostu wszystkie zmienne, które na pierwszy rzut oka wydają się warte przeniesienia, czyli URL usługi, usera i hasło.

Kontener korzystający ze składni Pythona to zwykła klasa dziedzicząca po springpython.config.PythonConfig. Spring Python zarządza tylko tymi obiektami, które przypisany mają dekorator springpython.config.Object. Pierwsza wersja kontekstu może wyglądać tak:

# -*- coding: utf-8 -*-

# ctx1.py

from springpython.config import Object
from springpython.config import PythonConfig

class ClientContext(PythonConfig):

    @Object
    def url(self):                      
        return "PYROLOC://127.0.0.1:7766/exchange-rates"

    @Object    
    def user(self):
        return "foo"

    @Object
    def password(self):
        return "bar"

a klient korzystający z kontekstu w ctx1.py ma postać:

# -*- coding: utf-8 -*-

# client2.py

from springpython.context import ApplicationContext
from springpython.remoting.pyro import PyroProxyFactory

from ctx1 import ClientContext

class Client(object):
    def __init__(self, url, user, password):
        self.url = url
        self.user = user
        self.password = password

        self.proxy = PyroProxyFactory()
        self.proxy.service_url = self.url

    def get_exchange_rate(self, currency):
        return self.proxy.get_exchange_rate(self.user, self.password, currency)

ctx = ApplicationContext(ClientContext())

url = ctx.get_object("url")
user = ctx.get_object("user")
password = ctx.get_object("password")

client = Client(url, user, password)
print client.get_exchange_rate("EUR")

Tworzymy więc kontekst aplikacji poprzez powołanie nowej instancji klasy springpython.context.ApplicationContext, której przekazujemy instancję naszej konfiguracji z ctx1.py. Do obiektów zarządzanych przez kontekst mamy dostęp przez metodę .get_object(nazwa_obiektu) – ‘nazwa_obiektu’ jest taka sama jak nazwa metody zdefiniowana w ctx1.py, wywołanie tej metody zwróci obiekt z kontenera. Warto zwrócić uwagę na to, że w Spring Pythonie zawsze używamy metody .get_object do pobierania obiektów z kontenera, niezależnie od tego, czy (tak jak tutaj) korzystamy z PythonConfiga, z XMLConfiga czy też – od Spring Pythona 1.1 – z YamlConfiga.

Mamy więc kontekst, w którym trzymana jest podstawowa konfiguracja. Domyślne użycie dekoratora @Object tak jak powyżej oznacza, że dany obiekt jest singletonem. Możliwe jest także podanie explicite, że obiekt ma być singletonem; można także podać, że obiekt ma być prototypem, czyli przy każdym jego pobraniu z użyciem .get_object będzie zwracana jego nowa instancja.

Pójdźmy teraz dalej i zdejmijmy z klasy Client odpowiedzialność za tworzenie proxy do wywoływania serwera – przede wszystkim, klient ma serwer przez proxy wywoływać, a nie pamiętać o tworzeniu proxy (niech się tym zajmie kontekst), po drugie, w przykładzie tworzymy tylko jednego klienta, ale w rzeczywistości instancji klasy Client możemy tworzyć wiele i nie ma sensu tworzyć nowego proxy dla każdej instancji, niech proxy będzie singletonem. Przy okazji przeniesiemy też do kontekstu definicję klienta, i będzie ona prototypem, przy każdym pobraniu klienta z kontekstu będziemy otrzymywali nową instancję klasy Client.

Tak może wyglądać kontekst ctx2.py

# -*- coding: utf-8 -*-

# ctx2.py

from springpython.remoting.pyro import PyroProxyFactory
from springpython.config import scope, Object, PythonConfig

from client3 import Client

class ClientContext(PythonConfig):

    @Object
    def url(self):                      
        return "PYROLOC://127.0.0.1:7766/exchange-rates"

    @Object    
    def user(self):
        return "foo"

    @Object
    def password(self):
        return "bar"

    @Object
    def proxy(self):
        proxy = PyroProxyFactory()
        proxy.service_url = self.url()

        return proxy

    @Object(scope.PROTOTYPE)        
    def client(self):
        client = Client()
        client.proxy = self.proxy()
        client.user = self.user()
        client.password = self.password()

        return client

Z kolei client3.py mocno zmniejszył się

# -*- coding: utf-8 -*-

# client3.py

class Client(object):
    def __init__(self, proxy=None, user=None, password=None):

        self.proxy = proxy
        self.user = user
        self.password = password

    def get_exchange_rate(self, currency):
        return self.proxy.get_exchange_rate(self.user, self.password, currency)

Potrzebujemy teraz czegoś, co uruchomi całość, np. app1.py:

# -*- coding: utf-8 -*-

# app1.py

from springpython.context import ApplicationContext

from ctx2 import ClientContext

ctx = ApplicationContext(ClientContext())

client = ctx.get_object("client")
print client.get_exchange_rate("EUR")

Co teraz osiągnęliśmy? Oddzieliliśmy od siebie komponenty. Klient nie wie -  i dobrze, skoro nie musi tego wiedzieć – skąd bierze się proxy, ważne, że może je wywołać. Nie wie nawet jakiego rodzaju jest to proxy, akurat teraz jest to Pyro, ale kiedyś może być to komunikacja z serwerem przy użyciu innej technologii. Proxy także nie wie skąd bierze się jego konfiguracja, akurat teraz URL jest zapisany bezpośrednio w kontekście, ale kto wie, może za miesiąc będzie pobierany z LDAP-a czy z innego źródła danych – ponownie, nie powinno być zmartwieniem proxy skąd wziąć URL, URL powinien być dla proxy po prostu dostępny, a jego udostępnieniem zajmuje się kontekst. Komponenty zajmują się jedynie wykonywaniem swojej pracy, a nie zadaniami konfiguracyjnymi, nie są ze sobą powiązane zależnościami w kodzie. Można je więc spokojnie rozdzielić, osobno dystrybuować, osobno developować, i nie przejmować się tym w jaki sposób zostaną w przyszłości użyte – ważne jest tylko to, że dobrze spełniają swoje biznesowe zadania.

Sprawdźmy teraz w jaki sposób IoC ułatwia nam napisanie unittestów – nie możemy oczywiście zakładać, że będziemy mieli zawsze dostęp do serwera (zresztą, o to chodzi w unittestach, żeby testować pojedyncze unity, w różnych warunkach izolacji od otoczenia), napiszemy więc fake’owe proxy, który będzie nam symulowało prawdziwy serwer i wstrzykniemy je do kontekstu w miejsce prawdziwego proxy.

# -*- coding: utf-8 -*-

# test_client.py

# stdlib
import unittest
from decimal import Decimal

# Spring Python
from springpython.config import Object
from springpython.context import ApplicationContext

# Nasza aplikacja
from ctx2 import ClientContext

class FakeProxy(object):
    """ Fake'owe proxy, używane w unittestach aplikacji klienckiej.
    """
    def get_exchange_rate(self, user, password, currency):
        return "4.3726"

class TestClientContext(ClientContext):
    """ Kontekst używany do testów aplikacji klienckiej.
    """

    @Object
    def proxy(self):
        return FakeProxy()

class TestClient(unittest.TestCase):

    def setUp(self):
        self.ctx = ApplicationContext(TestClientContext())

    def test_client_exchange_rate_decimal(self):
        client = self.ctx.get_object("client")
        rate = client.get_exchange_rate("EUR")

        # Załóżmy, że nie wiemy jaką dokładnie wartość zwraca serwer; wiemy
        # tylko, że jest to Decimal.
        rate_decimal = Decimal(rate)

        # Jeśli powyżej nie wystąpił wyjątek to znaczy, że wartość zwrócona
        # przez klienta była faktycznie możliwa do konwersji na typ Decimal,
        # czyli test powiódł się.

if __name__ == "__main__":
    unittest.main()

Na czym polega test? Na sprawdzeniu tego, czy klient zwraca wartości możliwe do skonwertowania na typ decimal.Decimal. Nie chcemy oczywiście uruchamiać do tego całego serwera, tworzymy więc subklasę kontekstu, w której metoda ‘proxy’ jest przeciążona, zwraca nasze fake’owe proxy, które w tym prostym przykładzie zwraca zawsze taką samą wartość – docelowo proxy to byłoby o wiele bardziej rozbudowane i symulowałoby faktyczną komunikację z serwerem, zwracałoby więcej kursów i obsługiwałoby uwierzytelnianie – ale dla potrzeb przykładu takie proste proxy w zupełności wystarczy do zilustrowania zasady, że wystarczy utworzyć testowy kontekst przeciążający te metody (czyli zwracający te obiekty), które są nam potrzebne w teście. Ponownie – trzeba zauważyć, że nie zmienialiśmy w ogóle kodu aplikacji, aplikacja jako taka nic nie wie, że tym razem jest poddawana testom i że proxy jest fake’owe.

Mam nadzieję, że to proste wprowadzenie zachęciło Was do korzystania z IoC i Spring Pythona. Nie jest to jednak koniec możliwości, ani Spring Pythona, ani IoC jako sposobu na modelowanie zależności komponentów aplikacji. Więcej informacji o Spring Pythonie można znaleźć tutaj, a dla osób zainteresowanych konfigurowaniem kontekstów w YAML-u jest dokumentacja do zbliżającej się wersji 1.1 Spring Pythona. Spring Python ma także swoje forum, listę mailingową i grupę na LinkedIn, można tam spokojnie wstąpić i zawsze ktoś pomoże lub doradzi :-)

Share