Attribute Control

Introduction

This document explains how to have more fine grain control over what gets encoded by PyAMF 0.5 and newer.

Control What Gets Encoded

A simple example is that of a user object. This User class has username and password attributes as well a number of meta properties.

We are going to use the Google App Engine adapter for this example, but the ideas apply in all situations.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# models.py

from google.appengine.ext import db

import pyamf


class User(db.Model):
    username = db.StringProperty()
    password = db.StringProperty()

    name = db.StringProperty()
    dob = db.DateProperty()


pyamf.register_class(User, 'com.acme.app.User')

This class is used in a (theoretical) PyAMF application to represent User objects through a gateway:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import logging

from google.appengine.ext import db

from pyamf.remoting.gateway.wsgi import WSGIGateway

from models import User


class UserService(object):
    def saveUser(self, user):
        user.put()

    def getUsers(self):
        return User.all()


services = {
    'user': UserService
}

gw = WSGIGateway(services, logger=logging)

Loading Users

Lets examine getUsers. Assuming that User.all() worked as expected a list of User objects would be returned to PyAMF for encoding which is the expected behaviour. What is not the desired behaviour is that the password attribute will be encoded with each User object, thereby handing all user passwords out to whomever desires them. Definitely not a good idea! The best solution would be to completely remove the password attribute from each User object as it is encoded.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# models.py

from google.appengine.ext import db

import pyamf


class User(db.Model):
    class __amf__:
        exclude = ('password',)

    username = db.StringProperty()
    password = db.StringProperty()

    name = db.StringProperty()
    dob = db.DateProperty()


pyamf.register_class(User, 'com.acme.app.User')

Notice the class attribute __amf__. PyAMF looks for attributes on the object class that contain instructions on how to handle encoding and decoding instances.

The exclude property is a list of attributes that will be excluded from encoding and removed from the instance if it exists in decoding. Setting exclude = ('password',) gives us the desired effect of not sending the passwords of each User in the user.getUsersservice call.

Saving a User

The first argument of the UserService.saveUser method is a User object that has been decoded by PyAMF and applied to the service method. Some type checking might be in order here, because anything could be sent as the user payload.

1
2
3
4
5
 def saveUser(self, user):
     if not isinstance(user, User):
         raise TypeError('User expected')

     db.save(user)

So now we can ensure that any call to user.put will be an instance (or subclass) of User. Since we plan to persist the User object, some validation is in order. If the correct attribute (_key) is sent to PyAMF, the GAE Adapter will load the instance from the datastore and it then applies the object attributes on top of this instance. This means that if the _key is known to a malicious hacker, a malformed client request could attempt to change the username property (or indeed change any other property on the model). The same thing could apply to the password field, but that has been solved by excluding it in the previous section.

Certainly something we don’t want to happen!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# models.py

from google.appengine.ext import db

import pyamf


class User(db.Model):
    class __amf__:
        exclude = ('password',)
        readonly = ('username',)

    username = db.StringProperty()
    password = db.StringProperty()

    name = db.StringProperty()
    dob = db.DateProperty()


pyamf.register_class(User, 'com.acme.app.User')

Notice the new readonly property. This should be pretty self-explanatory but it is a list of attributes that are considered read-only when applying an attribute collection to an instance, just after they have been decoded.

Other Things

Those are probably the two most used properties out of the way, so what else is there?

  • Static Attributes
  • Proxied attributes
  • IExternalizable classes

Static Attributes

A static attribute is an attribute that is expected to be on every instance of a given class. A good example would be the primary key for an ORM object. It allows the AMF payloads to be reduced substantially (using AMF3 only).

1
2
3
4
5
6
7
import pyamf

class Person(object):
    class __amf__:
        static = ('gender', 'dob')

pyamf.register_class(Person, 'com.acme.app.Person')

This means that the gender and dob attributes must be on every instance of the Person class. Decoding an instance that does not have these attributes will cause an AttributeError whilst decoding/encoding.

Proxied Attributes

Flex provides two classes that are ‘bindable’ (ArrayCollection and ObjectProxy), making things easier for Flex developers (plenty of info/tutorial on the web!). A proxied attribute is purely AMF3 specific - when encoding an attribute, if it is labeled as proxy then a proxied version will be encoded. The reverse happens on decode, if a proxied version is encountered then the unproxied version is returned. This allows transparent proxying without having to disturb the underlying ‘raw’ attribute.

1
2
3
4
5
6
7
import pyamf

class Person(object):
    class __amf__:
        proxy = ('address',)

pyamf.register_class(Person, 'com.acme.app.Person')

IExternalizable

AMF provides the opportunity for the developer to customise the (de)serialisation of instances through the implementation of IExternalizable (once again, plenty of docs and tuts on the web). PyAMF makes no exception.

To implement IExternalizable:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import pyamf

class Person(object):
    class __amf__:
        external = True

    def __writeamf__(self, output):
        # Implement the encoding here
        pass

    def __readamf__(self, input):
        # Implement the decoding here
        pass

pyamf.register_class(Person, 'com.acme.app.Person')

Table Of Contents

Previous topic

Adapter Framework

Next topic

About these documents

This Page