LadonType for complex service types

Direct Type Assignment (old-style)

There are two ways of defining LadonType attributes. Either you can assign an attribute directly to a type (old-style type-definition) or you can use the new-style dictionary type-definition.

Using the old-style type-definition means that every attribute in your LadonType is assigned to a type (primitive or other LadonType) rather than a value. You can also tell ladon that the attribute should expect a list of values of a certain type, by assigning the attribute to list containing only one element; the type to expect:

balance = float
transactions = [ Transaction ]

In the example above example Transaction is another LadonType.

This is a very nice and clear way to declare a service structure. The downside is that it offers no way to tell the framework if the attribute is optional, has a default value, is nullable or to write service documentation on attribute level.

Example:

class Group(LadonType):
        cn = str
        name = unicode
        description = unicode
        numbers = [int]

class Person(LadonType):
        uid = str
        name = unicode
        age = int
        groups = [Group]

Portable types

You can use Ladon’s compatability module for LadonTypes the same way as when you define parameter types with @ladonize. This makes it easier to write Python 2/3 compatible LadonTypes.

Let’s re-write the above example in a portable version:

from ladon.types.ladontype import LadonType
from ladon.compat import PORTABLE_STRING,PORTABLE_BYTES

class Group(LadonType):
        cn = PORTABLE_BYTES
        name = PORTABLE_STRING
        description = PORTABLE_STRING
        numbers = [int]

class Person(LadonType):
        uid = PORTABLE_BYTES
        name = PORTABLE_STRING
        age = int
        groups = [Group]

Dictionary Type-Definition

The new-style dictionary type-definition allows the service developer to specify additional attribute properties like default value and whether or not an attribute can be Null. It also offers the abillity to write documentation for the attribute.

Null and default values

Let’s take a look at nullable values.

In the following example the attributes “description” and “age” are marked as nullable, “recieve_newsletter” is not nullable but has a default value that will be used if the attribute is skipped by the client:

class Group(LadonType):
        cn = {
                'type': str
        }
        name = {
                'type': unicode
        }
        description = {
                # "description" is nullable, so it is allowed to force Null to this
                # value, if the attribute is skipped it will also be interpreted as Null
                'type': unicode,
                'nullable':True
        }
        numbers = [int]

class Person(LadonType):
        uid = {
                'type': str
        }
        name = {
                'type': unicode
        }
        age = {
                # "age" is nullable, so it is allowed to force Null to this value,
                # if the attribute is skipped it will also be interpreted as Null
                'type': int,
                'nullable': True
        }
        recieve_newsletter = {
                # Client cannot force Null to this attribute (Ladon will raise a
                # ClientFault exception), but if the attribute is unset it will default
                # to False
                'type': bool,
                'nullable': False,
                'default': False
        }
        groups = [Group]

Reuse code for dictionary type-definition

It is advised that you define common dictionary typedefs so you don’t have to rewrite code for attribute types that have the exact same properties:

email_typedef = {
        'type': PORTABLE_STRING,
        'nullable': False,
        'doc': 'Enter a valid email address',
}

class UserType(LadonType):
        username = email_typedef
        # more attributes go here...

Attribute filter functions

Furthermore it is possible to register filter functions for both incoming and outgoing attribute values which can be used to add validation/modification triggers on certain attributes. So if you have the attribute “zip_code” in your LadonType and you want to make sure that users of your service only type in zip-codes from the US. You add a filter on the attribute’s “incoming” filter list that does the check and raises a clientfault on non-US zip-codes.

In the following example a classic email validation function is registered on the incoming “username” attribute:

from ladon.ladonizer import ladonize
from ladon.types.ladontype import LadonType
from ladon.compat import PORTABLE_STRING,PORTABLE_BYTES
from ladon.exceptions.service import ClientFault

import re
def check_email(email):
        if not re.match('^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$',email):
                raise ClientFault('Invalid email')
        return email

email_typedef = {
        'type': PORTABLE_STRING,
        'nullable': False,
        'doc': 'Enter a valid email address',
        'filters': {
                'incoming': [check_email]
        }
}

class UserType(LadonType):
        username = email_typedef
        password = PORTABLE_STRING
        firstname = PORTABLE_STRING
        lastname = PORTABLE_STRING


class UserService(object):
        @ladonize(UserType,rtype=bool)
        def addUser(self,user):
                # Add user implementation goes here
                return True

Notice in the above example that the filter function is contained in a list. This is because the Ladon framework can accept multiple filter functions. The filters are executed in the same order as their placement in the list.

Filter functions can be injected in 4 different points of the RPC process. In the request stage you can inject filters before and after the Ladon framework parses an attribute, in the response stage you can inject triggers before and after an attribute is converted to the response object.

Filter Name Description
incoming_raw Applied before the attribute is parsed
incoming Applied after the attribute has been parsed
outgoing Applied before the attriute is converted to response format
outgoing_raw Applied after the attribute has been converted to response format

Note: “outgoing_raw” filters cannot expect that the attribute value type is always the same because some interfaces require convertion to string format before the final response object is built.