← Back to team overview

lazr-users team mailing list archive

Using lLazr.restful with scoped collections

 

Hello:

We are trying to implement a URI scheme that looks like this:

/ person / uuid / music / uuid /

Where each person entry has a different collection of Music entries

For purposes of discussion, you can assume that no Person can have
the same Music that another person has (In lazr terms, Music is a scoped
collection of a specific Person entry, and Music is not a first level resource.

(We dont want to make everything first or top level, since there is an explicit
this belongs to that relationship that we'd like to convey in the ReST URIs)

In order for the URI generation / traversal to work, we need to implement ILocation
and provide __parent__  which is easy enough for Entry classes, but for collections
it seems harder then it needs to be.

We'd like a collection of entries to just be a standard python list of <Entry> objects. 
but since a python list knows nothing of __parent__, we've been painstakingly adding
custom classes that extend from list and take a parent arg in the constructor that is returned
by the __parent__ property.   We also ran into a minor issue with the handleCustomGet method
of the CustomOperationResourceMixin class of lazr.restful (although handleCustomPost might
also need this change)   to handle scoped collections correctly.  (see below for unified diff)  
but we're not sure if the problem we encountered is a lazr bug, or a problem in how we're using lazr.

It feels like lazr.restful should be able to derive the parents of collections if we define our
interfaces correctly and our ideal goal is to use sqlalchemy ORM models as Entry and
Collection classes and expose them by making *very* minor changes to the models
(adding implements(X), and the root resource's __parent__)

The only thing standing in the way of this goal is the parent / child issue

making a custom class that inherits from a standard list is a workable but less ideal solution and
it adds some cruft in our sqlalchemy models that we'd rather not have to have if it doesnt need to 
be there.

OR: does it need to be there to perform both sides of the object / uri mapping
   ie:  Traversal (URI to object)  and URI Generation (object to URI)


Here's a non-sqlalchemy example of what we'd like to accomplish

interface.py
  1:  class IHasGet(Interface):
  2:     def get(): pass
  3:  
  4:  class IMusic(IHasGet):
  5:     export_as_webservice_entry()
  6:     exported(text(title=u'song title')) 
  7:
  8: class IMusicCollection(IHasGet):
  9:     export_as_webservice_collection(IMusic)
10:
11:    @collection_default_content() 
12:     def find(): pass
13:
14:  class IPerson(IHasGet):
15:     export_as_webservice_entry()
16:     music = exported(CollectionField(title=u'my music', 
17:                                    value_type=Reference(schema=IMusic)))
18:
19:  class IPersonCollection(IHasGet):
20:     export_as_webservice_collection(IPerson)
21:
22:     @collection_default_content() 
23:     def find(): pass

In our entry classes, we'd like to have something like this

resource.py
  1: class Music(object):
  2:     implements(IMusic, ILocation)
  3:    @property
  4:     def song_title(self):  
  5:          return "....."
  6:
  7:     @property
  8:     def __parent__(self): 
  9:          return self.the_person_i_belong_to
10:
11: class Person(object):
12:     implements(IPerson, ILocation)
13:    @property
14:    def __parent__(self):
15:          return getUtility(IServiceRootResource)
16:
17:     @property
18:     def music(self):
19:           """ return a collection of music """
20:           return [  Music(), Music(), Music(), ... etc ]
21: 
22: class PersonCollection(object):
23:     implements(IPersonCollection, IHasGet, ILocation)
24:     provides(IPersonCollection)
25:     __name__ = 'person'
26:   
27:    @property
28:    def __parent__(self):
29:          return getUtility(IServiceRootResource)
30:   
31:    @property
32:    def find(self):
33:          return [ Person(), Person(), Person(), ... ]
34:
35:    @property
36:    def get(self, name):
37:         (code to return a single Person instance)

The collection returned on line 20 of resource.py is a standard python list of Music objects

/ person
    knows its parent, since its explicitly stated in the definition of a PersonCollection
    (resource.py line 27-29)

/ person / uuid
    could this derive its parent from the interface, since we have 1 (and only 1) interface that
    exports a collection of objects that each implement the IPerson interface
    (interface.py line 20)  

/ person / uuid / music
    the parent of this is an entry that implements IPerson (/ person / uuid) and we know it
    exports a 'music' property which is defined as a collection of Music

    could lazr derive its parent from the interface since its parent is  / person / uuid

/ person / uuid / music / uuid
    similar to / person / uuid, could we derive its parent from the interface, since we have
    1 (and only 1) interface that exports a collection of Music objects, and we know that
    its parent is an Entry that implements the IPerson interface?



(Unified diff of _resource.py)

--- lazr.restful-0.9.25/src/lazr/restful/_resource.py 
+++ lazr.restful-0.9.25/src/lazr/restful/_resource.py
@@ -682,0 +682,7 @@
+        context = self.context
+        if isinstance(context, ScopedCollection):
+            """Scoped collections dont expose the collection that is scoped
+               directly, instead, you can find this by calling the scoped
+               collection's 'collection' property"""
+            context = self.context.collection
+
@@ -683,1 +690,1 @@
-            operation = getMultiAdapter((self.context, self.request),
+            operation = getMultiAdapter((context, self.request),


We'd certainly welcome any feedback / advice / suggestions that anyone may have.



Edward F. Long, Jr.
Web Developer
AWeber Communications
x748

Programmer: (n)
   1: a multi-cellular organism that can convert caffeine into computer code (see also: geek)




Follow ups