LadonType for complex service types — Ladon 0.9.21 documentation

# LadonType for complex service types

class `ladon.types.ladontype.` `LadonType`(prime_dict=None, tc=None, export_dict=None)

  LadonType must be specialized by all complex service types. It tells the Ladon framework that it should expect multiple primitives and/or lists to be defined as class attributes. Using LadonType it is possible to build nested types.

  LadonType specializations must define it’s attributes as types (either primitive types, other LadonType specialization types, lists of primitive or lists of LadonType specialization). This explains the Ladon framework what to expect. There are 5 primitive types:

  > Python 2 | Python 3
  > --- | ---
  > str | bytes
  > unicode | str
  > bool | bool
  > float | float
  > int | int
  > attachment | attachment

## 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.