- Overview
- What is soaplib?
- Requirements
- Intro Examples
- HelloWorld
- UserManager
- Serializers
- Primitive
- Collections
- Class
- Fault
- Binary
- Any
- Custom
- Client
- Message API
- WSGI
- Deployment
- Hooks
- servers
- Async. Web Services
- Correlation
- Interoperability
- Axis
- .NET
- wsdl2py
- security
- none
Soaplib is an easy to use python library written at Optio Software, Inc. for writing and calling soap web services. Writing soap web services in any language has always been extremely difficult and yielded mixed results. With a very small amount of code, soaplib allows you to write a useful web service and deploy it as a WSGI application. WSGI is a python web standard for writing portable, extendable web applications in python. More information on WSGI can be found here.
- deploy services as WSGI applications
- handles all xml (de)serialization
- on-demand WSDL generation
- doesn't get in your way!!!
- Python 2.4 or greater (tested mainly on 2.4.3)
- lxml (available through easy_install)
- a WSGI-compliant web server (CherryPy, WSGIUtils, Flup, etc.)
- pytz(available through easy_install)
- easy_install (optional)
-
Declaring a Soaplib Service
from soaplib.wsgi_soap import SimpleWSGISoapApp from soaplib.service import soapmethod from soaplib.serializers.primitive import String, Integer, Array class HelloWorldService(SimpleWSGISoapApp): @soapmethod(String,Integer,_returns=Array(String)) def say_hello(self,name,times): results = [] for i in range(0,times): results.append('Hello, %s'%name) return results if __name__=='__main__': from wsgiref.simple_server import make_server server = make_server('localhost', 7789, HelloWorldService()) server.serve_forever()
Dissecting this example:
SimpleWSGISoapApp
is the base class for WSGI soap services.from soaplib.wsgi_soap import SimpleWSGISoapApp
The
soapmethod
decorator exposes methods as soap method and declares the data types it accepts and returnsfrom soaplib.service import soapmethod
Import the serializers to for this method (more on serializers later)
from soaplib.serializers.primitive import String, Integer, Array
Extending
SimpleWSGISoapApp
is an easy way to soap service that can be deployed as a WSGI application.class HelloWorldService(SimpleWSGISoapApp):
The
soapmethod
decorator flags each method as a soap method, and defines the types and order of the soap parameters, as well as the return value. This method takes in aString
, anInteger
and returns an Array of Strings ->Array(String)
.@soapmethod(String,Integer,_returns=Array(String))
The method itself has nothing special about it whatsoever. All input variables and return types are standard python objects.
def say_hello(self,name,times): results = [] for i in range(0,times): results.append('Hello, %s'%name) return results
-
Deploying the service
deploy this web service. Soaplib has been tested with several other web servers, This example uses the reference WSGI web server (available in Python 2.5+) to and any WSGI-compliant server should work.
if __name__=='__main__': from wsgiref.simple_server import make_server server = make_server('localhost', 7789, HelloWorldService()) server.serve_forever()
-
Calling this service
>>> from soaplib.client import make_service_client >>> client = make_service_client('http://localhost:7789/',HelloWorldService()) >>> print client.say_hello("Dave",5) ['Hello, Dave','Hello, Dave','Hello, Dave','Hello, Dave','Hello, Dave']
soaplib.client.make_service_client
is a utility method to construct a callable client to the remote web service.make_service_client
takes the url of the remote functionality, as well as a stub of the remote service. As in this case, the stub can be the instance of the remote functionality, however the requirements are that it just have the same method signatures and definitions as the server implementation.
Lets try a more complicated example than just strings and integers! The following is an extremely simple example using complex, nested data.
from soaplib.wsgi_soap import SimpleWSGISoapApp
from soaplib.service import soapmethod
from soaplib.serializers.primitive import String, Integer, Array
from soaplib.serializers.clazz import ClassSerializer
user_database = {}
userid_seq = 1
class Permission(ClassSerializer):
class types:
application = String
feature = String
class User(ClassSerializer):
class types:
userid = Integer
username = String
firstname = String
lastname = String
permissions = Array(Permission)
class UserManager(SimpleWSGISoapApp):
@soapmethod(User,_returns=Integer)
def add_user(self,user):
global user_database
global userid_seq
user.userid = userid_seq
userid_seq = userid_seq + 1
user_database[user.userid] = user
return user.userid
@soapmethod(Integer,_returns=User)
def get_user(self,userid):
global user_database
return user_database[userid]
@soapmethod(User)
def modify_user(self,user):
global user_database
user_database[user.userid] = user
@soapmethod(Integer)
def delete_user(self,userid):
global user_database
del user_database[userid]
@soapmethod(_returns=Array(User))
def list_users(self):
global user_database
return [v for k,v in user_database.items()]
if __name__=='__main__':
from wsgiref.simple_server import make_server
server = make_server('localhost', 7789, UserManager())
server.serve_forever()
Jumping into what's new:
class Permission(ClassSerializer):
class types:
application = String
feature = String
class User(ClassSerializer):
class types:
userid = Integer
username = String
firstname = String
lastname = String
permissions = Array(Permission)
The Permission
and User
structures in the example are standard python objects
that extend ClassSerializer
. The ClassSerializer
uses an innerclass called
types
to declare the attributes of this class. At instantiation time, a
metaclass is used to inspect the types
and assigns the value of None
to
each attribute of the types
class to the new object.
>>> u = User()
>>> u.username = 'jimbob'
>>> print u.userid
None
>>> u.firstname = 'jim'
>>> print u.firstname
jim
>>>
In soaplib, the serializers are the components responsible for converting indivdual parameters to and from xml, as well as supply the information necessary to build the wsdl. Soaplib has many built-in serializers that give you most of the common datatypes generally needed.
The basic primitive types are String
, Integer
, DateTime
, Null
, Float
, Boolean
. These are some
of the most basic blocks within soaplib.
>>> from soaplib.serializers.primitive import *
>>> import cElementTree as et
>>> element = String.to_xml('abcd','nodename')
>>> print et.tostring(element)
<nodename xsi:type="xs:string">abcd</nodename>
>>> print String.from_xml(element)
abcd
>>> String.get_datatype()
'string'
>>> String.get_datatype(nsmap)
'xs:string'
>>>
The two collections available in soaplib are Array
s and Map
s. Unlike the primitive
serializers, both of these serializers need to be instantiated with the proper internal
type so it can properly (de)serialize the data. All Array
s and Map
s are homogeneous,
meaning that the data they hold are all of the same type. For mixed typing or more dynamic
data, use the Any
type.
>>> from soaplib.serializers.primitive import *
>>> import cElementTree as et
>>> array_serializer = Array(String)
>>> element = array_serializer.to_xml(['a','b','c','d'])
>>> print et.tostring(element)
<xsd:retval SOAP-ENC:arrayType="xs:string[4]"><string xsi:type="xs:string">a</string><string xsi:type="xs:string">b</string><string xsi:type="xs:string">c</string><string xsi:type="xs:string">d</string></xsd:retval>
>>> print array_serializer.from_xml(element)
['a', 'b', 'c', 'd']
>>>
The ClassSerializer
is used to define and serialize complex, nested structures.
>>> from soaplib.serializers.primitive import *
>>> import cElementTree as et
>>> from soaplib.serializers.clazz import *
>>> class Permission(ClassSerializer):
... class types:
... application = String
... feature = String
>>>
>>> class User(ClassSerializer):
... class types:
... userid = Integer
... username = String
... firstname = String
... lastname = String...
... permissions = Array(Permission)
>>>
>>> u = User()
>>> u.username = 'bill'
>>> u.permissions = []
>>> p = Permission()
>>> p.application = 'email'
>>> p.feature = 'send'
>>> u.permissions.append(p)
>>> element = User.to_xml(u)
>>> et.tostring(element)
'<xsd:retval><username xsi:type="xs:string">bill</username><lastname xsi:nil="1" /><userid xsi:nil="1" /><firstname xsi:nil="1" /><permissions SOAP-ENC:arrayType="typens:Permission[1]"><Permission><application xsi:type="xs:string">email</application><feature xsi:type="xs:string">send</feature></Permission></permissions></xsd:retval>'
>>> User.from_xml(element).username
'bill'
>>>
The Attachment
serializer is used for transmitting binary data as base64 encoded strings.
Data in Attachment
objects can be loaded manually, or read from file. All encoding of
the binary data is done just prior to the data being sent, and decoding immediately upon
receipt of the Attachment
.
>>> from soaplib.serializers.binary import Attachment
>>> import cElementTree as et
>>> a = Attachment(data='my binary data')
>>> element = Attachment.to_xml(a)
>>> print et.tostring(element)
<xsd:retval>bXkgYmluYXJ5IGRhdGE=
</xsd:retval>
>>> print Attachment.from_xml(element)
<soaplib.serializers.binary.Attachment object at 0x5c6d90>
>>> print Attachment.from_xml(element).data
my binary data
>>> a2 = Attachment(fileName='test.data') # load from file
The Any
type is a serializer used to transmit unstructured xml data. Any
types are very
useful for handling dynamic data, and provides a very pythonic way for passing data using
soaplib. The Any
serializer does not perform any useful task because the data passed in
and returned are Element
objects. The Any
type's main purpose is to declare its presence
in the wsdl
.
Soaplib provides a very simple interface for writing custom serializers. Any object conforming to the following interface can be used as a soaplib serializer.
class MySerializer:
def to_xml(self,value,name='retval',nsmap=None):
pass
def from_xml(self,element):
pass
def get_datatype(self,nsmap=None):
pass
def get_namespace_id(self):
pass
def add_to_schema(self,added_params,nsmap):
pass
This feature is particularly useful when adapting soaplib to an existing project and converting existing
object to ClassSerializers
is impractical.
Soaplib provides a simple soap client to call remote soap implementations. Using the ServiceClient
object
is the simplest way to make soap client requests. The ServiceClient
uses an example or stub
implementation to know how to properly construct the soap messages.
>>> from soaplib.client import make_service_client
>>> client = make_service_client('http://localhost:7789/',HelloWorldService())
>>> print client.say_hello("Dave",5)
This method provides the most straightforward method of creating a SOAP client using soaplib.
TODO
All soaplib services can be deployed as WSGI applications, and this gives soaplib great flexibility with how they can be deployed. Any WSGI middleware layer can be put between the WSGI webserver and the WSGI soap application.
Soaplib has been extensively used with Paste for server configuration and application composition.
WSGISoapApp
s have a set of extensible 'hooks' that can be implemented to capture different
events in the execution of the wsgi request.
def onCall(self,environ):
'''This is the first method called when this WSGI app is invoked'''
pass
def onWsdl(self,environ,wsdl):
'''This is called when a wsdl is requested'''
pass
def onWsdlException(self,environ,exc,resp):
'''Called when an exception occurs durring wsdl generation'''
pass
def onMethodExec(self,environ,body,py_params,soap_params):
'''Called BEFORE the service implementing the functionality is called'''
pass
def onResults(self,environ,py_results,soap_results):
'''Called AFTER the service implementing the functionality is called'''
pass
def onException(self,environ,exc,resp):
'''Called when an error occurs durring execution'''
pass
def onReturn(self,environ,returnString):
'''Called before the application returns'''
pass
These hooks are useful for transaction handling, logging and measuring performance.
Soaplib services can be deployed as WSGI applications, in any WSGI-compliant web server. Soaplib services have been successfully run on the following web servers:
- CherryPy 2.2
- Flup
- Twisted.web2
- WSGIUtils 0.9