Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pyramid resource: In plain English

I've been reading on the ways to implement authorization (and authentication) to my newly created Pyramid application. I keep bumping into the concept called "Resource". I am using python-couchdb in my application and not using RDBMS at all, hence no SQLAlchemy. If I create a Product object like so:

class Product(mapping.Document):
  item = mapping.TextField()
  name = mapping.TextField()
  sizes = mapping.ListField()

Can someone please tell me if this is also called the resource? I've been reading the entire documentation of Pyramids, but no where does it explain the term resource in plain simple english (maybe I'm just stupid). If this is the resource, does this mean I just stick my ACL stuff in here like so:

class Product(mapping.Document):
  __acl__ = [(Allow, AUTHENTICATED, 'view')]
  item = mapping.TextField()
  name = mapping.TextField()
  sizes = mapping.ListField()

  def __getitem__(self, key):
      return <something>

If I were to also use Traversal, does this mean I add the getitem function in my python-couchdb Product class/resource?

Sorry, it's just really confusing with all the new terms (I came from Pylons 0.9.7).

Thanks in advance.

like image 902
Mark Avatar asked Mar 02 '12 20:03

Mark


1 Answers

I think the piece you are missing is the traversal part. Is Product the resource? Well it depends on what your traversal produces, it could produce products.....

Perhaps it might be best to walk this through from the view back to how it gets configured when the application is created...

Here's a typical view.

  @view_config(context=Product, permission="view")
  def view_product(context, request):
      pass # would do stuff  

So this view gets called when context is an instance of Product. AND if the acl attribute of that instance has the "view" permission. So how would an instance of Product become context?

This is where the magic of traversal comes in. The very logic of traversal is simply a dictionary of dictionaries. So one way that this could work for you is if you had a url like

/product/1

Somehow, some resource needs to be traversed by the segments of the url to determine a context so that a view can be determined. What if we had something like...

  class ProductContainer(object):
      """
      container = ProductContainer()
      container[1]
      >>> <Product(1)>
      """
      def __init__(self, request, name="product", parent=None):
          self.__name__ = name
          self.__parent__ = parent
          self._request = request

      def __getitem__(self, key):
          p = db.get_product(id=key)

          if not p:
              raise KeyError(key)
          else:
              p.__acl__ = [(Allow, Everyone,"view")]
              p.__name__ = key
              p.__parent__ = self
              return p

Now this is covered in the documentation and I'm attempting to boil it down to the basics you need to know. The ProductContainer is an object that behaves like a dictionary. The "name" and "parent" attributes are required by pyramid in order for the url generation methods to work right.

So now we have a resource that can be traversed. How do we tell pyramid to traverse ProductContainer? We do that through the Configurator object.

  config = Configurator()
  config.add_route(name="product",
                   path="/product/*traverse",
                   factory=ProductContainer)
  config.scan()
  application = config.make_wsgi_app()

The factory parameter expects a callable and it hands it the current request. It just so happens that ProductContainer.init will do that just fine.

This might seem a little much for such a simple example, but hopefully you can imagine the possibilities. This pattern allows for very granular permission models.

If you don't want/need a very granular permission model such as row level acl's you probably don't need traversal, instead you can use routes with a single root factory.

  class RootFactory(object):
      def __init__(self, request):
          self._request = request
          self.__acl__ = [(Allow, Everyone, "view")]  # todo: add more acls


  @view_config(permission="view", route_name="orders")
  def view_product(context, request):
      order_id, product_id = request.matchdict["order_id"], request.matchdict["product_id"]
      pass # do what you need to with the input, the security check already happened

  config = Configurator(root_factory=RootFactory)

  config.add_route(name="orders",
                   path="/order/{order_id}/products/{product_id}")

  config.scan()
  application = config.make_wsgi_app()

note: I did the code example from memory, obviously you need all the necessary imports etc. in other words this isn't going to work as a copy/paste

like image 68
Tom Willis Avatar answered Oct 16 '22 10:10

Tom Willis