Package: pyramid-resource
pyramid-resourcePyramid's URL traversal is a powerful tool and personally one of my favorite features of the framework.
Unfortunately, Pyramid doesn't provide any framework or utilities for implementing resource trees.
This project aims to reduce the boilerplate necessary for creating feature-full resource trees.PrerequisitesTo use this package, you'll first need to be familiar with Pyramid'sURL traversal.Once you're ready to get started, install pyramid-resource:pip install pyramid-resourceBasic UsageYou can create a new resource by subclassingpyramid_resource.Resource.
For example, here's a simple application that has a resource tree with only a root resource.fromwsgiref.simple_serverimportmake_serverfrompyramid.configimportConfiguratorfrompyramid.responseimportResponsefrompyramid_resourceimportResourceclassRoot(Resource):passdefhello_world(request):returnResponse('Hello!\n')if__name__=='__main__':withConfigurator()asconfig:config.set_root_factory(Root)config.add_view(hello_world,context=Root)app=config.make_wsgi_app()server=make_server('0.0.0.0',8080,app)server.serve_forever()You can define child resources by setting the__children__property to a dictionary.
The key corresponds the URL segment and the value should be a resource subclass.
pyramid-resource will automatically make the resources location-aware.classChild(Resource):passclassRoot(Resource):__children__={'child':Child,}You can see the full examplehere.Name ResolutionFor convenience, you can reference children asdotted namestrings.
This is useful for putting parent resources above child resources in your code, e.g.:classRoot(Resource):__children__={'child':'.Child',}classChild(Resource):passFor this to properly function, you will need to runConfigurator.scanon the module.
If you prefer not to usescan, you can also invoke the resolution manually usingResource.resolve_children:defincludeme(config):Root.resolve_children(config)This functionality resolves strings usingConfigurator.maybe_dottedand will behave the same.
This means that namesare relative to the configurator,not necessarily the package that contains the resource.Dynamic resource treesOne of the more interesting features of URL traversal is that trees can be created on the fly.
This allows for dynamic resource trees that can mirror the application state, such as objects in a database.Dynamic resource trees can be created by implementing aget_childmethod on a resource class.
This method should accept a single argument of a URL segment and will be called if no child is found in the__children__property.
If the URL segment corresponds to a valid child resource,get_childshould return a resource class and the child resource will be instanciated from that.
If no corresponding child is found,Noneshould be returned orKeyErrorraised, and traversal will be halted.classRoot(Resource):defget_child(self,key):ifexists_in_db(key):returnChildelse:returnNoneclassChild(Resource):passOf course, this isn't particularly useful if you can't attach information to the child resource.
You can instantiate a resource with additional data as kwargs and return it fromget_child:classRoot(Resource):defget_child(self,key):ifexists_in_db(key):returnChild(id=key)classChild(Resource):passThe object ID will now be accessible viacontext.idin views on the child resource.Resources will proxy the attributes of their parent, socontext.idwill also be accessible in views further down the tree.If you need to access the current request in yourget_childimplementations, it's available viaself.request.An ExampleHere's an example that demonstrates how a real application might utilize pyramid-resource.fromwsgiref.simple_serverimportmake_serverfrompyramid.configimportConfiguratorfrompyramid.decoratorimportreifyfrompyramid.viewimportview_configfrompyramid_resourceimportResourceclassRoot(Resource):__children__={"widget":".dynamic.WidgetContainer",}classWidgetContainer(Resource):"""A resource containing the Widget resources."""defget_child(self,key):"""Return a child resource if the widget exists in the database."""try:id=int(key)exceptValueError:raiseKeyErrorifself.request.widget_db.exists(id):returnWidget(widget_id=id)classWidget(Resource):"""A resource representing a widget in the mock database."""@reifydefwidget(self):"""Lookup the widget from the database."""returnself.request.widget_db.find(self.widget_id)@view_config(context=WidgetContainer,renderer="string")deflist_widgets(context,request):"""GET /widget/List the URLs of all widgets."""urls=[]forwidget_idinrequest.widget_db:urls.append(request.resource_path(context[widget_id]))return"\n".join(urls)+"\n"@view_config(context=Widget,renderer="string")defget_widget(context,request):"""GET /widget/{id}/Greet the current widget."""return"Hello{}!\n".format(context.widget)classMockDatabase:"""An imitation of a widget database."""DATA={1:"Widget 1",2:"Widget 2",}defexists(self,id):returnidinself.DATAdeffind(self,id):returnself.DATA[id]def__iter__(self):returniter(self.DATA.keys())defmake_app():config=Configurator()config.set_root_factory(Root)config.add_request_method(lambda_:MockDatabase(),"widget_db",property=True,)config.scan(".dynamic")returnconfig.make_wsgi_app()if__name__=="__main__":app=make_app()server=make_server("0.0.0.0",8080,app)server.serve_forever()The resulting application will behave like this:>>> curl localhost:8080/widget/
> /widget/1/
> /widget/2/

>>> curl localhost:8080/widget/1/
> Hello Widget 1!

>>> curl localhost:8080/widget/2/
> Hello Widget 2!Prior artThepyramid_traversalwrapperproject proxies a location-ignorant resource tree to make it location-aware.