Pylons 0.9.7 was released in February 2009. This article describes its new features, and how to migrate older applications.
- Pylons Roadmap to 1.0 lists the plans for future releases
- Pylons Execution Analysis 0.9.6 provides the latest walk through the source code. It has not be updated for 0.9.7, but is still mostly accurate.
Templating Changes
Prior to 0.9.7, all templating was handled through a layer called 'Buffet'. This layer frequently made customization of the template engine difficult as any customization required additional plugin modules being installed.
In 0.9.7, a significantly simpler 'render_mako', 'render_genshi' and 'render_jinja' are provided in 'pylons.templating'. New projects and controllers made will include a comment on how to import the render function of choice.
Note that these new render functions now return unicode (actually literal objects, a subclass of unicode), whereas Buffet returned raw strs.
The template engine is now setup in the 'config/environment.py' file in new projects. The new Mako engine configuration includes the common toggles most users will want:
1 2 3 4 5 6 7 8 9 10 11 12 | # Add these imports to the top from mako.lookup import TemplateLookup from pylons.error import handle_mako_error # Create the Mako TemplateLookup, with the default auto-escaping config['pylons.app_globals'].mako_lookup = TemplateLookup( directories=paths['templates'], error_handler=handle_mako_error, module_directory=os.path.join(app_conf['cache_dir'], 'templates'), input_encoding='utf-8', output_encoding='utf-8', imports=['from webhelpers.html import escape'], default_filters=['escape']) |
(If you have a newer application with config['pylons.app_globals'] instead of config['pylons.g'], use that instead in the incantation above.)
For those upgrading, its recommended that you create a new project with the desired template engine (mako, genshi or jinja are supported out of the box), and copy the engine creation lines into your existing project.
Mako Automatic HTML Escaping
When using the new template engine setup, the default filter is 'escape' which will automatically HTML escape all non-literal objects. For passing strings into templates that should not be escaped, wrap them in the 'literal' object that is in 'webhelpers.html'. This escape by default behavior helps reduce the risk of XSS attacks. Example:
1 | c.description = h.literal("Trusted text with <strong>HTML markup</strong> that I <em>know</em> is safe.") |
The HTML helpers automatically return a literal so you don't have to wrap those:
1 | c.stupid_link = h.link_to("I'm a stupid hyperlink", h.url_for(...)) |
(Third-party packages wrapped by WebHelpers such as webhelpers.htmlgen, webhelpers.hinclude, and webhelpers.textile may not have been patched to return literals, so you may have to wrap those values. webhelpers.feedgenerator was created in-house so it does return proper literals.
Implicit Behavior changes
Some of the implicit behavior of Pylons is no longer the default. This includes the automatic setting of 'c' to have all the action's args, and the 'strict_c' setting.
This means that in Pylons 0.9.7, accessing an attribute of 'c' that does not exist will throw an AttributeError exception, to restore the old implicit behavior add the following line to environment.py:
# After config.init_app:
config['pylons.strict_c'] = False
Action arguments are no longer attached to 'c' by default as well, to restore the old implicit behavior add the following line to environment.py:
# After config.init_app:
config['pylons.c_attach_args'] = True
New create options
paster create has a few new options to preconfigure an application more tailored to your needs. You can specify them on the command line or paster will prompt for them interactively.
paster create -t pylons myproject version=0.1 sqlalchemy=true template_engine=genshi zip_safe=false
'version' and 'zip_safe' set the appropriate options in setup.py. If 'zip_safe' is false (default), easy_install will never install your application as a compressed .egg file (more information).
If 'sqlalchemy' is true, paster makes several adjustments to the model, config file, and other files to make it easier to set up a database application.
'template_engine', unsurprisingly, sets your default template engine.
None of these options adjusts the application's dependencies in setup.py. You should list in "install_requires" all non-Pylons packages your application depends on; e.g., Genshi, SQLAlchemy, MySQL-python.
url object
pylons.url replaces url_for, although the latter is still available via an import ("from routes import url_for"). url is automatically in the template namespace. It works the same as url_for for named and unnamed routes, but accessing the current URL now requires a method: url.current(). (This replaces route memory.)
If you want to use h.url_for as in earlier versions of Pylons, add "from routes import url_for" to myapp/lib/helpers.py.
WebHelpers 0.6
WebHelpers 0.6 adds several new features, including an HTML tag generator and the aforementioned smart escaping. webhelpers.rails has been deprecated; a new set of helpers implements most of its functionality. A new paginate module replaces pagination. All Javascript libraries have been deprecated. The deprecated code remains intact for now but it will be removed before Pylons 1.0.
Deprecated modules and packages
webhelpers.commands: compress_resources is broken and has external dependencies. A new minimization library is being finalized to replace it.
webhelpers.hinclude: Uses webhelpers.rails and is too trivial to port.
webhelpers.htmlgen: Old version of webhelpers.html.builder without smart escaping.
webhelpers.pagination: Old version of webhelpers.paginate.
webhelpers.rails: Most useful functions have been reimplemented outside the rails namespace, sometimes with a few API changes. The versions of Prototype and Scripaculous in WebHelpers are out of date and will not be updated. Please download the latest Prototype or Scriptaculous yourself if you need them, or start transitioning to another Javascript library: Fork Javascript, JQuery, YUI, etc.
Import changes
The top-level webhelpers module no longer imports tons of helpers from its submodules and from Routes. This means that if you have "from webhelpers import *" in your myapp/lib/helpers.py, it no longer does anything useful, and no helpers will exist under the h object. You should import all helpers desired into helpers.py.
Almost all applications will want to add these lines:
1 2 | from webhelpers.html import escape, HTML, literal, url_escape from webhelpers.html.tags import * |
To get all the new functions that are equivalent to the ones that used to be under h, add these lines too:
1 2 3 4 5 6 | from webhelpers.date import * from webhelpers.text import * from webhelpers.html.converters import * from webhelpers.html.tools import * from webhelpers.util import * from routes import url_for |
The new functions may have a slightly different API from the old functions, so consult the docstrings. "pydoc -p 1111 webhelpers" will start a webserver on http://localhost:1111/ with documentation on the helpers and links to the submodules.
If you have an existing application that depends on the deprecated helpers that used to be in h:
1 | from webhelpers.rails.wrapped import * |
(And remove any other rails import.) The wrapped module contains special versions of all the rails helpers which will not be escaped by Pylons 0.9.7's default Mako filter. Otherwise you'll find "<" being escaped to "<" in your webpages.
Note that many of the new helpers have the same names as the rails helpers, so they can't both be "import *"'d at the same time.
Dependencies
A few helpers have dependencies on Routes or Pylons. This has always been the case but now we're making it more explicit. webhelpers.paginate currently depends on routes.url_for. webhelpers.tags.Flash depends on Pylons and Beaker sessions. However, you can easily copy these classes to another framework and make them compatible with just a few changes.
Future plans
In the following version of WebHelpers (0.7) we're anticipating more HTML helpers, more text helpers, more collection helpers (e.g., counter and accumulator), and possibly a couple more packages. The 'unfinished' directory in the WebHelpers source distribution contains functions that have not yet been ported to WebHelpers' standards. We're also mulling over a couple ideas to allow helpers to access framework services (session, WSGI environment, and URL builders) in a framework-independent manner. We haven't found a satisfactory solution yet, which is why some helpers import url_for or pylons.session directly.
Required middleware
Pylons 0.9.7 enables the routing, session, and cache middleware in middleware.py rather than the Pylons core. This makes it easier to customize these features, provide your own equivalent substitutes, or suppress them in subapplications managed by a larger Pylons app. If you create a new Pylons project, these will automatically be listed in middleware.py. To make your application compatible with both 0.9.7 and 0.9.6, put them in an if stanza below the "# CUSTOM MIDDLEWARE HERE" line:
1 2 3 4 5 6 7 8 | import pylons if pylons.__version__ >= "0.9.7": # Routing/Session/Cache Middleware from beaker.middleware import CacheMiddleware, SessionMiddleware from routes.middleware import RoutesMiddleware app = RoutesMiddleware(app, config['routes.map']) app = SessionMiddleware(app, config) app = CacheMiddleware(app, config) |
If you're embedding a Pylons application inside another Pylons application, either by making it a "controller" or by using Cascade or paste.urlmap or doing some Paste jiu-jitsu in the config file, you probably want to enable some or all of this middleware only in the top-level application, and let the inner application use its services. If so, move those lines of code into the if asbool(full_stack) stanza.
Error handling
Pylons 0.9.7 uses WebError for error handling. The default configuration in middleware.py is:
1 2 3 4 5 6 7 8 9 10 | if asbool(full_stack): # Handle Python exceptions app = ErrorHandler(app, global_conf, **config['pylons.errorware']) # Display error documents for 401, 403, 404 status codes (and # 500 when debug is disabled) if asbool(config['debug']): app = StatusCodeRedirect(app) else: app = StatusCodeRedirect(app, [401, 403, 404, 500]) |
ErrorHandler handles non-HTTP exceptions like SyntaxError. When running in development mode ("debug = true" in development.ini), it uses uses WebError's EvalException to generate the interactive traceback. In production mode, it uses WebError's ErrorMiddleware to send an email alert.
StatusCodeRedirect handles HTTP errors by making a subrequest back into your application for a nice-looking error page with styles and images. You can generate such an error in your application by calling abort or redirect_to (the recommended way), by raising one of the objects in paste.httpexceptions (which is what those functions do), or setting the response status by hand.
If you comment out the StatusCodeRedirect completely, you'll get simple error messages similar to Apache's default, without sub-requests.
The interactive traceback has the same functionality and tabbed interface of the Pylons 0.9.6 version, but the look is different.
Making a functional test with WebError
WebError is easier than paste.fixture for writing functional tests.
XXX Need an example.
WebOb
Pylons 0.9.7 uses request/response objects from WebOb instead of WSGIRequest/WSGIResponse. WebOb is a framework-neutral request/response API for WSGI applications. It not only provides a richer set of convenience attributes and methods than Pylons has previously had, but it also makes it much easier to write your own WSGI middleware or application. Django is considering WebOb, and custom Paste applications are using it. The WebOb site has extensive documentation, including a comparision of WebOb and Pylons' previous system. The tradeoff for these features is some changes in syntax. request.params remains the same, but the following issues have come up in Pylons applications:
- HTTP exceptions are now webob.Response subclasses. The numeric status is response.status_int instead of response.status_code.
- WebOb is strict about returning unicode responses when there is no charset defined. The response's charset is calculated from its Content-Type header-- be particularly careful when resetting the header directly without a charset param, e.g.: as this resets the charset. However the WebOb shorthand version:
1
response.headers['Content-Type'] = 'application/ms-excel'
will not reset the charset. This Mailing list thread has more information.1
response.content_type = 'application/ms-excel'
WebOb is enabled by default.
Writing your own middleware with WebOb
WebOb encapsulates WSGI's "start response" voodoo, making it much easier to write your own middleware, raw WSGI application, or Web framework. For example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | from webob import Request, Response from my_wsgi_application import App class MyMiddleware(object): def __init__(self, app): self.app = app def __call__(self, environ, start_response): req = Request(environ) if req.params.get("foo", 0): ... rsp = req.get_response(app) if rsp.status_int == 404: rsp.body = "<p>My fancy error message.</p>" return rsp(environ, start_response) |
Both the request and response are WSGI applications themselves, so you can call them to get their current output. If you don't have a WSGI environment dict, Request.blank("/my/url?id=1") will create a minimal one for you. To use your new middleware in Pylons, import it and put app = MyMiddleware(app) under "# CUSTOM MIDDLEWARE HERE" in middleware.py.
If you call req.get_response(app, True). any exceptions raised by the wrapped application are caught and ignored.
Routes 1.9 – no minimization by default
Routes 1.9 has minimization disabled by default. This means the default ":controller/:action/:id" route no longer matches "/foo" or "/foo/bar". To get around this you can either add:
1 | map.minimize = True |
to routing.py immediately after map is created, or add the implied routes:
1 2 | map.connect(":controller", action="index", id=None) map.connect(":controller/:action", id=None) |
Global Response
Returning a Response object is now fully deprecated (emits DeprecationWarnings); please return the response content directly and or use pylons.response instead.
Last Release with Python 2.3 Support
WARNING: Pylons 0.9.7 will be the last version of Pylons to support Python 2.3. A minimum of Python 2.4 will be required for the next major version of Pylons (0.9.8).
Decorators
Pylons 0.9.7 ships with several action decorators in the pylons.decorators package:
- @authenticate_form: Detect cross-site request forgeries; see webhelpers.rails.secure_form_tag.
- @dispatch_on: Switch to an alternate action based on the HTTP method.
- @https: Require HTTPS. Redirect to an alternate URL if not HTTPS.
- @jsonify: Convert an action's return value to JSON.
- @validate: Validate input against a FormEncode schema or individual validators.
- @restrict: Restrict access to the action to certain HTTP methods.
Comments (1)
May 28, 2009
Shannon -jj Behrens says:
The above documentation mentions "h.url_for". When I ran "paster create" in ord...The above documentation mentions "h.url_for". When I ran "paster create" in order to update my project to 0.9.7, "h.url_for" was no longer available. To get it back, add the following to your helpers.py, "from routes import url_for".