Commits

Brent Tubbs committed 6287117

no more _attrs. All fields are classes now

  • Participants
  • Parent commits 09aa64f
  • Tags 0.0.4

Comments (0)

Files changed (3)

 In the previous example no HTTP request was made until command 4, the access
 to conn.me.username.  Trollop objects are lazy.
 
-The connection object has methods for getting objects by their IDs.  The
-objects will have attributes named exactly as they are in the Trello API docs.::
+The connection object has methods for getting objects by their IDs::
 
     In [5]: card = conn.get_card('4f2e454cefab2bbd4ea71b02')
 
     In [7]: card.desc
     Out[7]: u'And call it Trollop.'
 
-    In [8]: card.idList
-    Out[8]: u'4f17cb04d5c817032301c179' 
 
-In addition, there are convenience properties to automatically look up related
-objects.::
+There are convenience properties to automatically look up related
+objects::
 
     In [9]: lst = card.list
 
 
 setup(
     name='trollop',
-    version='0.0.3',
+    version='0.0.4',
     author='Brent Tubbs',
     author_email='brent.tubbs@gmail.com',
 	packages=find_packages(),
     Parent class for Trello objects (cards, lists, boards, members, etc).  This
     should always be subclassed, never used directly.
     """
-    # The Trello API path where objects of this type may be found.
-    _prefix = '' # eg '/cards/'
-
-    # In subclasses, these values should be filled in with a set of the
-    # attribute names that class has data for, and the path prefix like
-    # '/cards/'.  A set is used instead of a list in order to provide more
-    # efficient 'in' tests.  These are the attributes that will trigger an http
-    # GET before trying to return a value.
-    _attrs = set() # eg set(['name', 'url'])
+    # The Trello API path where objects of this type may be found. eg '/cards/'
+    @property
+    def _prefix(self):
+        raise NotImplementedError, "LazyTrello subclasses MUST define a _prefix"
 
     def __init__(self, conn, obj_id, data=None):
-        self.id = obj_id
-        self.conn = conn
-        self.path = self._prefix + obj_id
+        self._id = obj_id
+        self._conn = conn
+        self._path = self._prefix + obj_id
 
         # If we've been passed the data, then remember it and don't bother
         # fetching later.
             self._data = data
 
     def __getattr__(self, attr):
-        # For attributes specified in self._attrs, query Trello upon access
-        if (attr == '_data' or attr in self._attrs):
+        if attr == '_data':
+            # Something is trying to access the _data attribute.  If we haven't
+            # fetched data from Trello yet, do so now.  Cache the result on the
+            # object.
             if not '_data' in self.__dict__:
-                self._data = json.loads(self.conn.get(self.path))
+                self._data = json.loads(self._conn.get(self._path))
 
-            # '_data' is special-cased, since it can be looked up on its own,
-            # but is also the source of our other dynamic attributes.
-            if attr == '_data':
-                return self._data
-            elif attr in self._data:
-                return self._data[attr]
-
-            raise AttributeError("Trello data has no '%s' key" % attr)
+            return self._data
         else:
             raise AttributeError("%r object has no attribute %r" %
                                  (type(self).__name__, attr))
 
 
 class Closable(object):
+    """
+    Mixin for Trello objects for which you're allowed to PUT to <id>/closed.
+    """
     def close(self):
-        path = self._prefix + self.id + '/closed'
+        path = self._prefix + self._id + '/closed'
         params = {'value': 'true'}
-        result = self.conn.put(path, params=params)
+        result = self._conn.put(path, params=params)
 
 
-class TrelloField(object):
+class Field(object):
+    """
+    A simple field on a Trello object.  Maps the attribute to a key in the
+    object's _data dict.
+    """
+    # Accessing obj._data will trigger a fetch from Trello if _data isn't
+    # already present.
 
-    def get_instance(self, conn, obj_id):
-        return get_class(self.cls)(conn, obj_id)
+    def __init__(self, key):
+        self.key = key
 
+    def __get__(self, instance, owner):
+        return instance._data[self.key]
 
-class ObjectField(TrelloField):
+
+class DateField(Field):
+
+    def __get__(self, instance, owner):
+        strdata = instance._data[self.key]
+        return isodate.parse_datetime(strdata)
+
+
+class ObjectField(Field):
     """
     Maps an idSomething string attr on an object to another object type.
     """
 
-    def __init__(self, key=None, cls=None):
+    def __init__(self, key, cls):
 
         self.key = key
         self.cls = cls
 
     def __get__(self, instance, owner):
-        return self.get_instance(instance.conn, instance._data[self.key])
+        return self.related_instance(instance._conn, instance._data[self.key])
+
+    def related_instance(self, conn, obj_id):
+        return get_class(self.cls)(conn, obj_id)
 
 
 class ListField(ObjectField):
 
     def __get__(self, instance, owner):
         ids = instance._data[self.key]
-        conn = instance.conn
-        return [self.get_instance(conn, id) for id in ids]
+        conn = instance._conn
+        return [self.related_instance(conn, id) for id in ids]
 
-class DateField(TrelloField):
-    def __init__(self, key):
-        self.key = key
 
-    def __get__(self, instance, owner):
-        strdata = instance._data[self.key]
-        return isodate.parse_datetime(strdata)
-
-
-class SubList(TrelloField):
+class SubList(object):
     """
     Kinda like a ListField, but for things listed under a URL subpath (like
     /boards/<id>/cards), as opposed to a list of ids in the document body
     def __get__(self, instance, owner):
         if not hasattr(self, '_list'):
             cls = get_class(self.cls)
-            path = instance._prefix + instance.id + cls._prefix
-            data = json.loads(instance.conn.get(path))
-            self._list = [cls(instance.conn, d['id'], d) for d in data]
+            path = instance._prefix + instance._id + cls._prefix
+            data = json.loads(instance._conn.get(path))
+            self._list = [cls(instance._conn, d['id'], d) for d in data]
         return self._list
 
+### BEGIN ACTUAL WRAPPER OBJECTS
 
 class Action(LazyTrello):
 
     _prefix = '/actions/'
-    _attrs = set([
-        'data',
-        'type',
-        'date',
-        'idMemberCreator',
-    ])
-
+    data = Field('data')
+    type = Field('type')
     date = DateField('date')
+    creator = ObjectField('idMemberCreator', 'Member')
 
 
 class Board(LazyTrello, Closable):
 
     _prefix = '/boards/'
 
-    _attrs = set([
-        'url',
-        'name',
-        'pinned',
-        'prefs',
-        'desc',
-        'closed',
-        'idOrganization',
-        'prefs',
-    ])
+    url = Field('url')
+    name = Field('name')
+    pinned = Field('pinned')
+    prefs = Field('prefs')
+    desc = Field('desc')
+    closed = Field('closed')
+
+    organization = ObjectField('idOrganization', 'Organization')
 
     actions = SubList('Action')
     cards = SubList('Card')
     checklists = SubList('Checklist')
+    lists = SubList('List')
     members = SubList('Member')
 
-    organization = ObjectField('idOrganization', 'Organization')
-
 
 class Card(LazyTrello, Closable):
 
     _prefix = '/cards/'
-    _attrs = set([ 'url', 'idList', 'closed', 'name', 'badges',
-                  'checkItemStates', 'desc', 'idBoard', 'idMembers', 'labels',
-                 ])
+
+    url = Field('url')
+    closed = Field('closed')
+    name = Field('name')
+    badges = Field('badges')
+    checkItemStates = Field('checkItemStates')
+    desc = Field('desc')
+    labels = Field('labels')
 
     board = ObjectField('idBoard', 'Board')
     list = ObjectField('idList', 'List')
 class Checklist(LazyTrello):
 
     _prefix = '/checklists/'
-    _attrs = set([ 'checkitems', 'idBoard', 'name', ])
-
+    checkitems = Field('checkitems')
+    name = Field('name')
     board = ObjectField('idBoard', 'Board')
-
     cards = SubList('Card')
 
     # TODO: provide a nicer API for checkitems.  Figure out where they're
 class List(LazyTrello, Closable):
 
     _prefix = '/lists/'
-    _attrs = set([ 'url', 'idBoard', 'closed', 'name' ])
 
+    closed = Field('closed')
+    name = Field('name')
+    url = Field('url')
     board = ObjectField('idBoard', 'Board')
     cards = SubList('Card')
 
     # TODO: Generalize this pattern, add it to a base class, and make it work
     # correctly with SubList
     def add_card(self, name, desc=None):
-        path = self._prefix + self.id + '/cards'
-        body = json.dumps({'name': name, 'idList': self.id, 'desc': desc,
-                           'key': self.conn.key, 'token': self.conn.token})
-        data = json.loads(self.conn.post(path, body=body))
-        card = Card(self.conn, data['id'], data)
+        path = self._prefix + self._id + '/cards'
+        body = json.dumps({'name': name, 'idList': self._id, 'desc': desc,
+                           'key': self._conn.key, 'token': self._conn.token})
+        data = json.loads(self._conn.post(path, body=body))
+        card = Card(self._conn, data['id'], data)
         return card
 
 
 class Member(LazyTrello):
 
     _prefix = '/members/'
-    _attrs = set([ 'url', 'fullName', 'bio', 'gravatar', 'username', ])
+
+    url = Field('url')
+    fullname = Field('fullName')
+    username = Field('username')
 
     actions = SubList('Action')
     boards = SubList('Board')
 class Notification(LazyTrello):
 
     _prefix = '/notifications/'
-    _attrs = set([ 'data', 'date', 'idMemberCreator', 'type', 'unread', ])
+
+    data = Field('data')
+    date = DateField('date')
+    type = Field('type')
+    unread = Field('unread')
 
     creator = ObjectField('idMemberCreator', 'Member')
 
 class Organization(LazyTrello):
 
     _prefix = '/organizations/'
-    _attrs = set([ 'url', 'desc', 'displayName', 'name', ])
+
+    url = Field('url')
+    desc = Field('desc')
+    displayname = Field('displayName')
+    name = Field('name')
 
     actions = SubList('Action')
     boards = SubList('Board')