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 following is an simple example using complex, nested data. It’s available here: http://github.com/arskom/rpclib/blob/master/examples/user_manager/server_basic.py
import logging
logging.basicConfig(level=logging.DEBUG)
logging.getLogger('rpclib.protocol.xml').setLevel(logging.DEBUG)
from rpclib.application import Application
from rpclib.decorator import rpc
from rpclib.interface.wsdl import Wsdl11
from rpclib.protocol.soap import Soap11
from rpclib.model.primitive import String
from rpclib.model.primitive import Integer
from rpclib.model.complex import Array
from rpclib.model.complex import Iterable
from rpclib.model.complex import ComplexModel
from rpclib.server.wsgi import WsgiApplication
from rpclib.service import ServiceBase
_user_database = {}
_user_id_seq = 1
class Permission(ComplexModel):
__namespace__ = 'rpclib.examples.user_manager'
application = String
operation = String
class User(ComplexModel):
__namespace__ = 'rpclib.examples.user_manager'
user_id = Integer
user_name = String
first_name = String
last_name = String
permissions = Array(Permission)
class UserManagerService(ServiceBase):
@rpc(User, _returns=Integer)
def add_user(ctx, user):
user.user_id = ctx.udc.get_next_user_id()
ctx.udc.users[user.user_id] = user
return user.user_id
@rpc(Integer, _returns=User)
def get_user(ctx, user_id):
return ctx.udc.users[user_id]
@rpc(User)
def set_user(ctx, user):
ctx.udc.users[user.user_id] = user
@rpc(Integer)
def del_user(ctx, user_id):
del ctx.udc.users[user_id]
@rpc(_returns=Iterable(User))
def get_all_users(ctx):
return ctx.udc.users.itervalues()
application = Application([UserManagerService], 'rpclib.examples.user_manager',
interface=Wsdl11(), in_protocol=Soap11(), out_protocol=Soap11())
def _on_method_call(ctx):
ctx.udc = UserDefinedContext()
application.event_manager.add_listener('method_call', _on_method_call)
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
if __name__=='__main__':
try:
from wsgiref.simple_server import make_server
except ImportError:
print "Error: example server code requires Python >= 2.5"
server = make_server('127.0.0.1', 7789, WsgiApplication(application))
print "listening to http://127.0.0.1:7789"
print "wsdl is at: http://localhost:7789/?wsdl"
server.serve_forever()
Juping into what’s new: Rpclib uses ComplexModel as a general type that when extended 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 = String
feature = String
Let’s also look at the User class:
class User(ComplexModel):
user_id = Integer
username = String
firstname = String
lastname = String
Nothing new so far.
Below, you can see that the email member which has a regular expression restriction defined. The String type accepts other restrictions, please refer to the rpclib.model.primitive.String documentation for more information:
email = String(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 protocol and interface points of view:
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')
Here, we need to use the rpclib.model._base.ModelBase.customize() call because calling a ComplexModel child instantiates that class, whereas calling a SimpleModel child returns a duplicate 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:
UserManagerService.event_manager.add_listener('method_call', _on_method_call)
Here, 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 basic services. You can read the SQLAlchemy Integration document where the rpclib.model.table.TableModel class and its helpers are introduced. You can also have look at the Working with RPC Metadata section where service metadata management apis are introduced.
Otherwise, please refer to the rest of the documentation or the mailing list if you have further questions.