Skip to content

Latest commit

 

History

History
452 lines (362 loc) · 13.6 KB

soaplib_docs.markdown

File metadata and controls

452 lines (362 loc) · 13.6 KB

Soaplib Documentation

Table of Contents

  1. Overview
    • What is soaplib?
    • Requirements
  2. Intro Examples
    • HelloWorld
    • UserManager
  3. Serializers
    • Primitive
    • Collections
    • Class
    • Fault
    • Binary
    • Any
    • Custom
  4. Client
  5. Message API
  6. WSGI
    • Deployment
    • Hooks
    • servers
  7. Async. Web Services
    • Correlation
  8. Interoperability
    • Axis
    • .NET
  9. wsdl2py
  10. security
    • none

Overview

What is soaplib?

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.

Features

  • deploy services as WSGI applications
  • handles all xml (de)serialization
  • on-demand WSDL generation
  • doesn't get in your way!!!

Requirements

  • Python 2.4 or greater (tested mainly on 2.4.3)
  • ElementTree (available through easy_install)
  • cElementTree (available through easy_install)
  • a WSGI-compliant web server (CherryPy, WSGIUtils, Flup, etc.)
  • pytz(available through easy_install)
  • easy_install (optional)

Intro Examples

HelloWorld

  1. 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 returns

     from 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 a String, an Integer 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
    
  2. 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()
    
  3. 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.

User Manager

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 = user_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.start()

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
>>> 

Serializers

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.

Primitives

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'
>>> 

Collections

The two collections available in soaplib are Arrays and Maps. 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 Arrays and Maps 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']
>>>	  

Class

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:null="1" /><userid xsi:null="1" /><firstname xsi:null="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'
>>>

Attachment

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

Any

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.

Custom

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.

Client

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.

Message API

TODO

WSGI

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.

Deployment

Soaplib has been extensively used with Paste for server configuration and application composition.

Hooks

WSGISoapApps 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.

Servers

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