.. _manual-user-manager: User Manager ============ This tutorial builds on the :ref:`manual-helloworld` 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 :class:`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 :func:`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 :ref:`manual-sqlalchemy` document where the :class:`rpclib.model.table.TableModel` class and its helpers are introduced. You can also have look at the :ref:`manual-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.