Desktopcouch Tips and Tricks II

by mandel on March 2nd, 2010

Well, it looks like a lot of people (I blame @sil for it) have read my tips and tricks to work with Desktopccouch so I have decided to do a second post about it with two “topics”.

Put record revisited

In my last post I mentioned that the put_record returned the id of the record that was just stored in the db. Intially I filled that as a bug since the method will force you to do another round to the db when is not needed. After some discussion with the always wise Chad a decition was taken not to brake backwards compatibility. That does not mean that you will have to be doing that extra round to the database since the put_record method has the side effect of modifying the parameter you passed to the method. (sometimes I miss ADAs syntax when dealing with parameters).

Also, Chad made a merging sprint and merged a number of branches of desktopcouch to trunk and now we have some nice new features:

  • Pop and Remove have been implemented in the MergeableList.
  • A batch update has been added to allow you to perform the smaller amount of requests to the db. This method actually started a very nice discussion.

Pop and remove have been added because it was a pain to have to delete data for lists coming from desktopcouch while I think there is no need to explain why the batch_update was added.

Objects recipes

Because this week is Ubuntus’ Opportunistic Programmer week I have decided to provide two different recipes to help people using Desktopcouch records. There are good and bad points for both of the recepies and I will not give my opinion about which one I prefer but remember that:

“Favor ‘object composition’ over ‘class inheritance’.” (Gang of Four 1995:20)

Both recipes solve a personal problem that I have with records. I have always found the need to specify the use of the application annotations a major pain in the ass and therefore I have tried to avoid having to explicitly “typing” the code to take care of that inconvenience. The later of the recipes not only allows you to forget about the need of using “application_annotations” but also allows you to use a more object orientated technique which I personally prefer (I forgot I was not going to say anything).

StrictRecord: Extending Record

In this recipe we are going to create a strict record that will ensure that if we use a property that was no defined in the record, the data will be placed in the “application_annotations” property.

class StrictRecord(Record):
    """
    A strict record that make sure that you store data correctly.
    """
 
    def __init__(self, properties, data=None, record_type=None, record_id=None):
        if properties:
            super(StrictRecord, self).__init__(data=data, 
                record_type=record_type, record_id = record_id)
            self._properties = properties
        else:
            raise ValueError("Properties of the record should be provided.")
 
    def __getitem__(self, key):
        if is_internal(key):
            raise KeyError(key)
        if key in self._properties:
            return self.application_annotations[key]
        return super(Record, self).__getitem__(key)
 
    def __setitem__(self, key, item):
        if is_internal(key):
            raise IllegalKeyException(
                "Can't set '%s'. This is an internal key." % key)
        if key in self._properties:
            self.application_annotations[key] = item
        else:
            super(Record, self).__setitem__(key, item)
 
    def __contains__(self, key):
        if is_internal(key):
            return False
        if key in self._properties:
            return key in self.application_annotations
        return super(Record, self).__contains__(key)
 
    def __delitem__(self, key):
        if is_internal(key):
            raise KeyError(key)
        if key in self._properties:
            del self.application_annotations[key]
        else:
            super(Record, self).__delitem__(key)
 
    def get(self, key, default=None):
        """Get a value from the record by key."""
        if is_internal(key):
            return default
        if key in self._properties:
            return self.application_annotations.get(key, default)
        return super(Record, self).get(key, default)

Of course this code will let you forget about the “application_annotations” but it also gets you closer to XML schemes which is something that you probably don’t want (although I’m not sure this statement really makes sense)

Object composition

To ilustrate this recipe I’ll use the contact record as an example:

# set a list of the different fields defined in the contact record
contact_fields = ("first_name",
                  "middle_name",
                  "last_name",
                  "title",
                  "suffix",
                  "birth_date",
                  "nick_name",
                  "spouse_name",
                  "wedding_date",
                  "company",
                  "department",
                  "job_title",
                  "manager_name",
                  "assistant_name",
                  "office",
                  "categories")
 
# set a list of the different records that are considered to e special cases
special_attributes = ("_record",
                      "addresses",
                      "phone_numbers",
                      "email_addresses",
                      "urls",
                      "im_addresses",
                      "_application_annotations",)
 
class Contact(object):
    """
    Represents a contact in the database.
    """
    # collection of object that are used to perform the different filters
    # in the class
 
    application_name = None
 
    def __init__(self, first_name="", middle_name="", last_name=""):
        """
        Creates a new instance of the class.
        """
        self._record = Record(
            record_type="http://www.freedesktop.org/wiki/Specifications/desktopcouch/contact")
        self._record.record_id = uuid.uuid4().hex
        self.first_name = first_name
        self.middle_name = middle_name
        self.last_name = last_name
        self._application_annotations = {}
        # we set the application annotations for the app
        if not Contact.application_name is None:
            logger.info("Application annotations set to be {0}".format(
                Contact.application_name))
            self._application_annotations[Contact.application_name] = {}
        # sets used to keep track of the different contact_data
        self.addresses = set()
        self.phone_numbers = set()
        self.email_addresses = set()
        self.urls = set()
        self.im_addresses = set()
 
    def __setattr__(self, attribute, value):
        """
        Allows to set the internal attribute of the class by using a record.
        """
        # if the attribute is one of those that are defined at
        #
        if attribute in contact_fields:
            self._record[attribute] = value
        elif attribute in special_attributes:
            object.__setattr__(self, attribute, value)
        elif attribute == "avatar":
            if "avatar" in self._record.list_attachments():
                self._record.detach("avatar")
            self._record.attach(value, "avatar", "image/jpg")
        elif not Contact.application_name is None:
            self._application_annotations[Contact.application_name][attribute] = value
        else:
            message = "Attribute {0} could not be set because application name was not set.".format(
                    attribute)
            logger.error(message)
            raise AttributeError(message)
 
    def __getattribute__(self, attribute):
        if attribute in contact_fields:
            if attribute in self._record:
                return self._record[attribute]
            return None
        elif attribute in special_attributes:
            return object.__getattribute__(self, attribute)
        elif attribute == "avatar":
            if "avatar" in self._record.list_attachments():
                return self._record.attachment_data("avatar")[0]
            else:
                return None
        elif not Contact.application_name is None:
            if attribute in self._application_annotations[Contact.application_name]:
                return self._application_annotations[Contact.application_name][attribute]
            else:
                return object.__getattribute__(self, attribute)
        else:
            return object.__getattribute__(self, attribute)
 
    # private methods
 
    @logged(logger)
    def _get_id(self):
        """
        @rtype: int
        @return: The id of the contact.
        """
        return self._record.record_id
 
    # end private methods
 
    # properties
 
    _id = property(_get_id)

This recipe allows you to forget about the “application_annotations” but also allows you to be use a contact as an object making it a lot nicer to work with. For example we go from this:

contact = Record(record_type="http://www.freedesktop.org/wiki/Specifications/desktopcouch/contact")
contact["first_name"] = "Manuel"
contact["last_name"] = "de la Pena"
contact.application_annotations["my_app"]["my_data"] = "I'm lazy... too much typing here"

to this:

contact = Contact()
contact.first_name = "Manuel"
contact.last_name = "de la Pena"
contact.my_data = "I'm lazy... I prefer this"]

Well, I prefer the computer to do all the work :P

From Design, Python

Leave a Reply

You must be logged in to post a comment.