Package: pyramid_controllers
Thepyramid_controllerspackage is a pyramid plugin that provides
de-centralized hierarchical object dispatch, similar to how the
standard TurboGears request dispatch works. You may also be interested
in thepyramid-describepackage, which can make these controllers
self-documenting.TL;DRInstall:$pipinstallpyramid-controllersUse:# the following application serves these URLs:#   /#   /about/team#   /about/mission#   /resource/{RESOURCE_ID}   (RESTful: GET and PUT)# standard pyramid-controller importsfrompyramid_controllersimport\Controller,RestController,\expose,expose_defaults,index,default,lookup,wrap,fiddle# create a controller for "/about/team" and "/about/mission"classAboutController(Controller):@expose(renderer='mymodule:path/to/template.mako')defteam(self,request):returndict(team=get_team_members())@exposedefmission(self,request):return'Our mission: rock the world.'# create a RESTful (GET, PUT) controller for "/resource/{RESOURCE_ID}"classResourceController(RestController):@exposedefget(self,request):return'Name: '+request.res.name@exposedefput(self,request):request.res.name=request.params.get('name')returnself.get(request)# create the dispatcher that will lookup resources by IDclassResourceDispatcher(Controller):RESOURCE_ID=ResourceController(expose=False)@lookupdeflookup(self,request,res_id,*rem):request.res=get_resource_by_id(res_id)return(self.RESOURCE_ID,rem)# the root controller with support for "/" and sub-controllersclassRootController(Controller):about=AboutController()resource=ResourceDispatcher()@indexdefindex(self,request):returnHTTPFound('/about/mission')# and hook it all into pyramid in the app's main()defmain(global_config,**settings):# ... (the usual pyramid startup calls) ...config.include('pyramid_controllers')config.add_controller('root','/',RootController())InstallationYou can manually install it by running:$pipinstallpyramid-controllersHowever, a better approach is to use standard python distribution
utilities, and add pyramid_controllers as a dependency to your
project’sinstall_requiresparameter in yoursetup.py. Then run
apython setup.py develop.Then, enable the package either in your INI file via:pyramid.includes = pyramid_controllersor with code in your package’s application initialization via:defmain(global_config,**settings):# ...config.include('pyramid_controllers')# ...UsageNow that your pyramid application has access to the plugin, anchor the
root controller to a URL entrypoint via theconfig.add_controller()method. Note that unlike many of the other
controller approaches, a pyramid_controller route takes control of all
URLs that are prefixed with the specified entrypoint. For example, the
following:defmain(global_config,**settings):# ...config.include('pyramid_controllers')# ...config.add_controller('rootController','/root','.controllers.RootController')# ...will allow the class.controllers.RootControllerto handle any request
for the URL/rootor URLs that start with/root/....ConceptThe basic gist of pyramid_controllers is that for any incoming URL, it
will be split into components based on forwarded slashes (“/”) and
sequentially lookup the controller in the series while applying name
lookups, defaulting, access control, and generic request manipulation.For example, assuming thatRootControlleris anchored at “/”, then
the following code will handle a request for/how/are/youby responding
with the “A-OK!” response.frompyramid_controllersimportController,expose# NOTE: These classes are defined in order of semantic use. For this#       to actually work, the controllers would need to be defined#       before they are invoked (so in opposite order), of course.classRootController(Controller):how=HowController()classHowController(Controller):are=AreController()classAreController(Controller):@exposedefyou(self,request):return'A-OK!'Here, the initial request is received byRootController. A lookup
of the “how” attribute finds that it is associated with another
controller, so the request is dispatched to that object. The same
thing happens when theHowControllerreceives the request, which
in turn dispatches it to theAreController. When the framework
does a lookup of the “you” attribute, it finds that it is a method. To
control which methods are invocable via a URL, you must define the
method to be exposed to the framework via the@exposedecorator.At this point the framework hands the request to the object’s method for
handling, providing the activerequestobject as the first parameter,
in standard pyramid fashion.TODO: add documentation about the various supported response and
exception types.ControllersThere exist several classes that can be subclassed to produce
controller classes:pyramid_controllers.Controller: this class is the base class
of all controllers, and does not provide much functionality other
than allowing the framework to know that a class is intended to
handle requests in a pyramid_controllers approach.pyramid_controllers.RestController: this class routes the
various RESTful verbs to controller methods by the same name
(note that the method names are lower-cased).Here is an example of the RestController, which will accept any of the
standard HTTP verbs (GET, PUT, POST, DELETE) to the URL “/hello” and
will emit a response that simply reflects the method used (with a
little poetic licence thrown in):frompyramid_controllersimportController,RestControllerclassRootController(Controller):hello=ReflectController()classReflectController(RestController):@exposedefget(self,request):return'I am *not* a dog, go GET it yourself!'@exposedefput(self,request):return'Apparently you golf. PUTting is just part of the game.'@exposedefpost(self,request):return'People use email today, silly. Stop using the POST!'@exposedefdelete(self,request):return'Hey! This is not the CIA, you cannot just DELETE me!'DecoratorsThere are several decorators provided by the pyramid_controllers
package that influence how a request is handled, as follows:@expose: the most common decorator, it simply declares that the
decorated method is intended to handle incoming requests, and is
therefore “exposed” to the request traversal and dispatching. Note
that although it is exposed, access control restrictions may
restrict who can actually access it.@index: declares that the decorated method is the method that
will handle the request if no further components in the URL path
exist. Think of this as theindex.htmlin an htdocs directory.@default: if the standard component traversal strategy fails to
match either a sub-controller or an exposed method to handle a
request, then the framework searches for a method that has been
decorated as a@defaultor@lookupmethod (@lookupdecorators take precedence). The default method is expected to
behave identically to an “exposed” method in that it should respond
to the request.@lookup: similar to the@defaultdecorator, the@lookupdecorator is invoked when the framework could not find another
method or sub-controller to handle the request. The @lookup method,
unlike the @default method, isnotexpected to handle the actual
request, but instead to return a new controller with which the
framework will continue the hierarchical request handling. See below
for details on what parameters are passed and what is expected to be
returned.@wrap: a method that will wrap a request handling call. A @wrap
method is passed both arequestobject and ahandlercallable –
at some point the @wrap method should invokehandler(request)and return the response. Both the request and the response can be
modified or replaced inside the @wrap method. Cascading @wrap
methods will be invoked based on Controller traversal. Inherited and
multiple @wrap methods per controller are currently not explicitly
supported – their behavior is undefined.@fiddle: a method declared as a “fiddler” will be called before
any other method in the given controller and is expected to do
nothing more than alter the request in some way (such as add
additional attributes) or throw an exception. A fiddler methodMUST NOTactually respond to a request via standard methods,
however it can raise exceptions (such asHTTPForbidden), which
will terminate request dispatching.@expose_defaults: a Controller class decorator that sets default
parameters for @expose, @index, and @default methods, such as the
default renderer and extensions.Complex Examplefrompyramid.httpexceptionsimportHTTPForbidden,HTTPNotFound# import the controller base classesfrompyramid_controllersimportController,RestController# import the decoratorsfrompyramid_controllersimportexpose,index,lookup,default,wrap,fiddleclassRootController(Controller):public=PublicController()admin=AdminController()member=MemberDispatchController()classPublicController(Controller):login=AuthController()@exposedefabout(self,request):return'We are a snazy company!'classAuthController(RestController):@exposedefget(self,request):return'<html><form><input name="u"/><input name="p"/></form></html>'@exposedefpost(self,request):# todo: perform authentication...classAdminController(Controller):@fiddledefcheckAuth(self,request):ifnotuserHasAdminAccess(request):raiseHTTPForbidden()@indexdefindex(self,request):return'View the list of <a href="users">active users</a>.'@exposedefusers(self,request):return'<ul><li>you</li></ul>'classMemberDispatchController(Controller):@wrapdefcheckAuth(self,request,handler):ifnotuserHasMemberAccess(request):raiseHTTPForbidden()returnhandler(request)@lookupdef_lookup(self,username,*rem):user=findUserByUsername(username)ifnotuser:raiseHTTPNotFound()return(MemberController(user),rem)classMemberController(Controller):def__init__(self,user):self.user=user@indexdefindex(self,request):return'Hi, my name is '+self.user.name@exposedefage(self,request):return'I am%dyears old.'%(self.user.age,)@defaultdef_default(self,request,attribute,*rem):return'My "%s" is "%r".'%(attribute,getattr(self.user,attribute))