SOAP server can not be considered a good one if SOAP clients don’t work with it. Therefore we need a lot of tests proving interoperability. This chapter describes a test bench used by numerous tests proving interoperability included in pyws. The next chapter Running tests explains how to run a server and all the tests. All of this can also be considered examples of pyws usage since much of pyws’s functionality is used here.
To proceed with the tests, first, ensure that pyws is installed, read more about Requirements and Installation, this tutorial is based on WSGI adapter, so check up its requirements too. You should also note that tests and examples can be found only in a source distribution archive or in your own clone of the GIT repo.
In pyws distribution, there is a directory called examples, let’s have a look at its contents:
examples/
_django/ # not for now ...
_twisted/ # not for now either ...
_wsgi/
__init__.py # a simple WSGI application for testing
Makefile # a make file to automate running and stopping
# a server
__init__.py
api_settings.py # common pyws server settings
authenticate.py # authentication related stuff
functions.py # example functions
server.py # server definition
First, let’s consider server.py:
import api_settings
server = SoapServer(api_settings, *api_settings.SOAP_PROTOCOL_PARAMS)
Here we create a server using class SoapServer with module object api_settings as settings and with api_settings.SOAP_PROTOCOL_PARAMS as SOAP protocol parameters.
Now, let’s turn to api_settings.py:
DEBUG = True
That’s easy, turns debug mode on, read more in DEBUG.
PROTOCOLS = (
RestProtocol(),
JsonProtocol(),
)
Additional protocols for the server, read more in PROTOCOLS.
SOAP_PROTOCOL_PARAMS = (
'Test',
'http://example.com/',
'http://localhost:8000/api/soap',
soap_headers_schema
)
These parameters are passed directly to SOAP protocol constructor, read more in Concerning SOAP protocol.
CREATE_CONTEXT = authenticate
The function creating a context , read more in CREATE_CONTEXT.
In the previous paragraph, there were mentioned two variables: soap_headers_schema and authenticate, they are defined in authenticate.py, read more in Authentcation.
All the functions used in tests are defined in file functions.py. They are an example of how functions can be registered to a server and how their parameters are described, so you might want to cast a glance at it. Most use cases are described in this paragraph, but the file contains a little bit more examples.
from pyws.functions.register import register
@register()
@register('add_integers', return_type=int, args=((int, 0), (int, 0)))
@register('add_floats', return_type=float, args=((float, 0), (float, 0)))
def add_simple(a, b):
return a + b
Decorators created by function register only register a function to a server without changing it at all, so we can safely use several decorators at once. As one function can work with different types of variables we can register one function with different names and argument specification.
args describes function arguments, each element of the tuple represents one argument, argument names are infered from the function. (int, 0) means that an argument has type int and that instead of None you’ll get 0. We could have just use int instead, but then we had to check argument values on not being None in function itself, which might not be convenient.
The first decorator register() will register a function with its own name and all arguments and return values will be treated as strings.
Read more in chapters Registering a function, Arguments & fields specification, Type specification.
def flip_boolean(b):
return b ^ True
This function requires a context, so we register it specifying needs_context as True. If a server fails to create a context, then this function is not called and an exception is raised instead. The context created by a server is passed to the function as keyword argument context.
Read more in chapter Context.
return 'hello ' + context
# = next month ================================================================
@register(return_type=date, args=(date, ))
@register('next_month_dt', return_type=datetime, args=(datetime, ))
def next_month(d):
if not d:
return None
month = d.month + 1
Well, it looks pretty straight forward.
Read more in chapters Arguments & fields specification, Type specification.
Value with key 0 is the name of a dictionary type, other keys and values represent field names and their types.
Callables can be used as none values.
args=(
(ABStringDict, ab_string_dict_none),
(ABStringDict, ab_string_dict_none),
),
)
@register(
'add_integer_dicts',
return_type=ABIntegerDict,
args=(
(ABIntegerDict, ab_integer_dict_none),
(ABIntegerDict, ab_integer_dict_none),
),
)
}
# = add lists =================================================================
StringList = [str]
@register(
'add_string_lists',
return_type=StringList,
args=(StringList, StringList),
)
def add_string_lists(p, q):
List type definition is pretty simple, isn’t it?
p.extend([''] * (len(q) - len(p)))
if len(q) < len(p):
q.extend([''] * (len(p) - len(q)))
return list(p[i] + q[i] for i in xrange(len(p)))
IntegerList = [int, 0]
@register(
'add_integer_lists',
return_type=IntegerList,
args=(IntegerList, IntegerList),
)
def add_integer_lists(p, q):
IntegerList‘s second element is an element none value.
return list(p[i] + q[i] for i in xrange(len(p)))
# = trees =====================================================================
Tree = DictOf('Tree',
('value', int, 0),
)
Tree.add_fields(
('left', Tree),
('right', Tree),
)
So far, the only way to create a recursive type is monkey pathcing.
}
# = raises exception ==========================================================
class HelloError(Exception):
def __init__(self, name):
super(HelloError, self).__init__(u'%s error' % name)
@register()
It’s easy, just raise any exception you want.