User Manager

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:

  • Defining complex types.
  • Customizing types.
  • Defining events.

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)

What’s next?

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.

Table Of Contents

Previous topic

Hello World

Next topic

SQLAlchemy Integration

This Page