This tutorial builds on the Hello World tutorial. If you haven’t done so, we recommended you to read it first.
In this tutorial, we will talk about:
The simple example that we are going to be studying here using complex, nested data is available here: http://github.com/arskom/spyne/blob/master/examples/user_manager/server_basic.py
Jumping into what’s new: Spyne uses ComplexModel as a general type that, when subclassed, will produce complex serializable types that can be used in a public service. The Permission class is a fairly simple class with just two members:
class Permission(ComplexModel):
application = Unicode
feature = Unicode
Let’s also look at the User class:
class User(ComplexModel):
user_id = Integer
username = Unicode
firstname = Unicode
lastname = Unicode
Nothing new so far.
Below, you can see that the email member which has a regular expression restriction defined. The Unicode type accepts other restrictions, please refer to the spyne.model.primitive.Unicode documentation for more information:
email = Unicode(pattern=r'\b[a-z0-9._%+-]+@[a-z0-9.-]+\.[A-Z]{2,4}\b')
The permissions attribute is an array, whose native type is a list of Permission objects.
permissions = Array(Permission)
The following is deserialized as a generator, but looks the same from the points of view of protocol and interface documents:
permissions = Iterable(Permission)
The following is deserialized as a list of Permission objects, just like with the Array example, but is shown and serialized differently in Wsdl and Soap representations.
permissions = Permission.customize(max_occurs='unbounded')
With the Array and Iterable types, a container class wraps multiple occurences of the inner data type. So Array(Permission) is actually equivalent to:
class PermissionArray(ComplexModel):
Permisstion = Permission.customize(max_occurs='unbounded')
Here, we need to use the spyne.model._base.ModelBase.customize() call because calling a ComplexModel subclass instantiates that class, whereas calling a SimpleModel child implicitly calls the .customize method of that class.
The customize function just sets given arguments as class attributes to cls.Attributes class. You can refer to the documentation of each class to see which member of the Attributes class is used for the given object.
Here, we define a function to be called for every method call. It instantiates the UserDefinedContext class and sets it to the context object’s udc attribute, which is in fact short for ‘User Defined Context’.
def _on_method_call(ctx):
ctx.udc = UserDefinedContext()
We register it to the application’s ‘method_call’ handler.
application.event_manager.add_listener('method_call', _on_method_call)
Note that registering it to the service definition’s event manager would have the same effect, but it’d have to be set for every service definition:
UserManagerService.event_manager.add_listener('method_call', _on_method_call)
You can also prefer to define your own ServiceBase class and use it as a base class throughout your projects:
class MyServiceBase(ServiceBase):
pass
MyServiceBase.event_manager.add_listener('method_call', _on_method_call)
Next, we define the UserDefinedContext object. It’s just a regular python class with no specific api it should adhere to, other than your own.
class UserDefinedContext(object):
def __init__(self):
self.users = _user_database
@staticmethod
def get_next_user_id():
global _user_id_seq
_user_id_seq += 1
return _user_id_seq
Such custom objects could be used to manage everything from transactions to logging or to performance measurements. You can have a look at the events.py example in the examples directory in the source distribution for an example on using events to measure method performance)
This tutorial walks you through what you need to know to expose more complex services. You can read the SQLAlchemy Integration document where the spyne.model.table.TableModel class and its helpers are introduced. You can also have look at the Input Validation section where Spyne’s imperative and declarative input validation features are introduced.
Otherwise, please refer to the rest of the documentation or the mailing list if you have further questions.