From 1856b11b7b840e78bcc2bd68ea60af355d35caf2 Mon Sep 17 00:00:00 2001 From: Mike Orr Date: Mon, 13 Feb 2012 02:02:15 -0800 Subject: [PATCH] Finish version 2 documentation. --- CHANGES.txt | 24 +- docs/changes.rst | 2 +- docs/demo/content.rst | 55 +- docs/demo/details.rst | 172 ++-- docs/demo/index.rst | 4 +- docs/demo/{features.rst => usage.rst} | 38 +- docs/index.rst | 54 +- docs/library/index.rst | 4 +- docs/library/pony.rst | 16 + docs/library/static.rst | 13 +- docs/library/urlgenerator.rst | 41 +- docs/rant_scaffold.rst | 2 +- docs/unfinished.rst | 1139 ------------------------- 13 files changed, 206 insertions(+), 1358 deletions(-) rename docs/demo/{features.rst => usage.rst} (56%) create mode 100644 docs/library/pony.rst delete mode 100644 docs/unfinished.rst diff --git a/CHANGES.txt b/CHANGES.txt index 83c5995..b13b8d9 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,15 +1,19 @@ -2.0 (unreleased) +2.0 (2012-02-12) ------------------ -- Move repository to Github (from Butbucket); convert to Git (from Mercurial). - Rename all "v" tags, removing the prefix (v1.0.1 -> 1.0.1), so that they sort - in Git before the "pyramid_sqla" tags, and to follow Pyramid's precedent. - -- Demo program in 'akhet.demo'. (Run as "python -m akhet.demo".) -- Delete 'akhet' scaffold. +- Move repository to https://github.com/Pylons/akhet and convert to Git format + (previously http://bitbucket.com/sluggo/Akhet in Mercurial format). +- Rename all "v" tags, removing the prefix (v1.0.1 -> 1.0.1), so that they sort + in Git before the older "pyramid_sqla" tags, and to follow Pyramid's precedent. + +- New Akhet demo program distributed separately at + https://github.com/mikeorr/akhet_demo . It does not include a SQLAlchemy + model, thus completing the break from Akhet's origin in the former "pyramid_sqla". +- Delete 'akhet' application scaffold; the demo replaces it. - We have a pony. (akhet.pony, based on paste.pony) -- Move non-Akhet-specific parts of the manual to the Pyramid Cookbook (as the - "Pyramid for Pylons Users" guide). -- Include for ``add_static_route`` is now "akhet.static" instead of "akhet". +- Move non-Akhet-specific parts of the manual to the Pyramid Cookbook, as the + "Pyramid for Pylons Users" guide. +- The include enabling static routes is now "akhet.static" instead of "akhet". + A backward compatibility shim exists. - The URL generator's ``route`` method can generate either an absolute (qualified) URL or a path-only (unqualified) URL, overriding the instance's default mode. diff --git a/docs/changes.rst b/docs/changes.rst index 767f191..3622cd5 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -1,4 +1,4 @@ -Full Changelog +Full changelog %%%%%%%%%%%%%% .. include:: ../CHANGES.txt diff --git a/docs/demo/content.rst b/docs/demo/content.rst index 2b5c843..cccb085 100644 --- a/docs/demo/content.rst +++ b/docs/demo/content.rst @@ -1,7 +1,7 @@ Templates and stylesheets ========================= -The demo's Mako templates and stylesheets are designed to function +The demo's templates and stylesheets are designed to function in a variety of environments, so you can copy them to your application as a starting point. The following files are included: @@ -27,7 +27,10 @@ first three lines are Mako constructs: Line 1 makes the template inherit from the site template, which will add the site's header and footer. Lines 2 and 3 are Mako methods. They output the body -title and the head title respectively. +title (the

at the top of the page) and the head title (the tag) +respectively. Mako templates and methods are not literally Python classes and +methods -- they compile to modules and functions respectively -- but Mako +treats them in a way that's similar to classes and methods. The "${varname}" syntax is a placeholder which will output the named variable. Template variables can come from several sources: (1) keys in the view's return @@ -46,21 +49,28 @@ placeholders to plug in content from the page template. The most important placeholder here is "${self.body()}", which outputs the body of the highest-level template in the inheritance chain. -The template also calls "self.title()" and "self.ht_title()", and defines -default implementations for these methods. The default body title is blank; the -default head title is whatever the body title returns. So you can just set -"title" in your pages and forget about "ht_title" if you want. Sometimes you'll -have to make them different, however: (1) The head title can't contain HTML -tags like <em> -- it will display them literally rather than changing the font. -(2) Sometimes the body title is too wordy for the head title. (3) Many sites -want the site name in the head title. A general rule of thumb for the head -title is something like "Page Title — Site Name". Search engines rank the -head title highly, so it should contain all the essential words that describe -the page, and it should be less than sixty or so characters long so it fits on -one line. - -There's one more method in the site template, "head_extra". It also is blank by -default, but page templates can override it to add additional tags in the head. +Note the difference between calling "${body()}" and "${self.body()}". The +former calls a <%def> method defined in the same template. The latter calls the +highest-level <%def> method with that name in the inheritance chain, which may +be in a different template. + +The site template also calls "self.title()" and "self.ht_title()", and defines +default implementations for these methods. The default body title outputs +nothing (resulting in an empty title); the default head title is whatever the +body title returns. So you can just define a "title" in your pages and forget about +"ht_title" if it's the same. But there are times when you'll want to make them +different: + +* When the body title contains embedded HTML tags like <em>. The head title + can't contain these because it will display them literally rather than + changing the font. +* Sometimes the body title is too wordy for the head title. +* Many sites want the site's name in the head title. A general rule of thumb is + "Short Page Title &emdash; Site Name". Or if you're part of a large + organization: "Short Page Title | Site Name | Organization Name". Search + engines pay special attention to the head title, so it should contain all the + essential words that describe the page, and it should be less than sixty or + so characters so it can be displayed in a variety of contexts. The other kind of placeholder in the site template is "${url.app}", which is used to form static URLs like "${url.app}/stylesheets.default.css". "url" is @@ -70,10 +80,10 @@ top-level application mounted at "/". But if the application is mounted at a sub-URL like "/site1", that will be what "url.app" is set to. Normally you'd generate URLs by route name, such as "${url('home')}" or its -full form "${url.route('home')}". But static URLs don't have a route name. If -we were using Pyramid's static view there would be another way to generate -them, but the demo uses the static route so it can't do that. So we're left -with literal URLs relative to the application prefix. +full form "${url.route('home')}". But static URLs don't have a route name, and +the URL generator does not have a ``static`` method (although you can define +one in a subclass). So we're left with literal URLs relative to the application +prefix. The template displays flash messages, which a view may have pushed into the session before redirecting. The code for this is: @@ -110,6 +120,9 @@ If you want something with more bells and whistles, some Pyramid developers recommend `HTML5 Boilerplate`_. It's also based on Meyer's stylesheet. +We're exploring stylesheet compilers like Less, but this version of the demo +does not include one. + .. _HTML5 Boilerplate: http://html5boilerplate.com/ Default stylesheet diff --git a/docs/demo/details.rst b/docs/demo/details.rst index 87cc14e..42a3d1a 100644 --- a/docs/demo/details.rst +++ b/docs/demo/details.rst @@ -7,41 +7,24 @@ development.ini The config file contains the following settings which aren't in Pyramid's built-in scaffolds: -.. code-block:: ini - - mako.directories = akhet_demo:templates - - # Beaker cache - cache.regions = default_term, second, short_term, long_term - cache.type = memory - cache.second.expire = 1 - cache.short_term.expire = 60 - cache.default_term.expire = 300 - cache.long_term.expire = 3600 - - # Beaker sessions - #session.type = file - #session.data_dir = %(here)s/data/sessions/data - #session.lock_dir = %(here)s/data/sessions/lock - session.type = memory - session.key = akhet_demo - session.secret = 0cb243f53ad865a0f70099c0414ffe9cfcfe03ac - -The "mako.includes" setting is necessary to set Mako's search path. You can add -other Mako options here if you wish. - -The "cache." settings initialize Beaker caching. This is not actually necessary -because the demo never uses a cache, but it's here for demonstration. - -The "session." settings initialize Beaker sessions. This is necessary if you -use sessions or flash messages. Beaker supports several forms of session -persistence: in-memory, files, memcached, database, etc. This configuration -uses memory mode, which holds the sessions in memory until the application -quits; it obviously works only with multi-threaded servers and not with than -multi-process serviers. The default Pylons mode is file-based sessions, which -is commented here. Recent recommendations suggest memcached is the most robust -mode because it can scale to multiple servers; you can set that option if you -wish. +* mako.directories: necessary when using Mako, to set the template search + path. (Theoretically you don't need this if you specify renderers by asset + spec rather than by relative path, but I couldn't get that to work.) +* cache.\*: Beaker cache settings. These are not actually necessary because + the demo never uses a cache, but they're here for demonstration. +* session.\*: Beaker session settings. These are necessary if you use sessions + or flash messages. + +Beaker supports several kinds of session +persistence: in-memory, files, memcached, database, etc. The demo's +configuration uses memory mode, which holds the sessions in memory until the application +quits. It contains commented settings for file-based sessions, which is Pylons' +default. Experienced developers seem to be choosing memcached mode nowadays. +Memory sessions disappear when the server is restarted, and work only with +multithreaded servers, not multiprocess servers. File-based sessions are +persistent, but add the complications of a directory and permissions and +maintenance. Memcached avoids all these problems, and it also scales to +multiple parallel servers, which can all share a memcached session. If you copy the session configuration to your application, do change "session.secret" to a random string. This is used to help ensure the integrity @@ -51,70 +34,39 @@ of the session, to prevent people from hijacking it. Init module and main function ============================= -Almost all of the *akhet_demo/__init__.py* module is unique to the demo, so -we'll just show the whole thing here: +The main function, in addition to the minimal Pyramid configuration, activates +Beaker sessions and caching, and sets up templates, subscribers, routes, and a +static route. The Beaker setup passes the ``settings`` dict to Beaker; that's +how your settings are read. Pyramid cofigures Mako the same way behind the +scenes, passing the settings to it. The "add_renderer" line tells Pyramid to +recognize filenames ending in ".html" as Mako templates. The subscribers +include we'll see in a minute. -.. code-block:: python - :linenos: - - from pyramid.config import Configurator - import pyramid_beaker - - def main(global_config, XXsettings): - """ This function returns a Pyramid WSGI application. - """ - config = Configurator(settings=settings) - - # Configure Beaker sessions and caching - session_factory = pyramid_beaker.session_factory_from_settings(settings) - config.set_session_factory(session_factory) - pyramid_beaker.set_cache_regions_from_settings(settings) - - # Configure renderers and event subscribers. - config.add_renderer(".html", "pyramid.mako_templating.renderer_factory") - config.include(".subscribers") - config.include("akhet.static") - - # Add routes and views. - config.add_route("home", "/") - config.include("akhet.pony") - config.add_static_route("akhet_demo", "static", cache_max_age=3600) - config.scan() - - return config.make_wsgi_app() - -(Note: "\*\*settings" is shown as "XXsettings" because vim's syntax -highlighting gets into a fit otherwise and mishighlights up the doc source file.) - -As you see, it activates Beaker sessions and caching, and sets up templates, -subscribers, routes, and a static route. The Beaker setup passes the -``settings`` dict to Beaker; that's how your settings are read. Pyramid -cofigures Mako the same way behind the scenes, passing the settings to it. -The "add_renderer" line tells Pyramid to recognize filenames ending in ".html" -as Mako templates. The subscribers include we'll see in a minute. - -The static route has an include line and an "add_static_route" call. +Activating static routes involves an include line and a "config.add_static_route" +call. Helpers ======= -*akhet_demo/lib/helpers.py* is unique to the demo. It's a Pylons-like helpers -module where you can put utility functions for your templates. The minimal -WebHelpers imports for HTML tag helpers are there, but commented. I'm tempted -to actually use the tag helpers in the site template but haven't done so yet. +The demo provides a Pylons-like helpers module, +*akhet_demo/lib/helpers.py*. You can put utility functions here for use in +your templates. The helper contains imports for WebHelper's HTML tag helpers, +but they're commented out. (WebHelpers is a Python package containing generic +functions for use in web applications and other applications.) I'm tempted to +actually use the tag helpers in the site template but haven't done so yet. Most of WebHelpers works with Pyramid, including the popular ``webhelpers.html`` subpackage, ``webhelpers.text``, and ``webhelpers.number``. -Pyramid does not depend on WebHelpers so you'll have to add the dependency to -your application if you want to use it. The only part that doesn't work with -Pyramid is the ``webhelpers.pylonslib`` subpackage, which depends on Pylons' -special globals. +You'll have to add a WebHelpers dependency to your application if you want to +use it. The only part of WebHelpers that doesn't work with Pyramid is the +``webhelpers.pylonslib`` subpackage, which depends on Pylons' special globals. -WebHelpers 1.3 has some new URL generator classes to make it easier to use -with Pyramid. See the ``webhelpers.paginate`` documentation for details. (Note: -this is *not* the same as Akhet's URL generator; it's a different kind of class -specifically for the paginator's needs.) +Note that ``webhelpers.paginate`` requires a slightly different configuration +with Pyramid than with Pylons, because ``pylons.url`` is not available. You'll +have to supply a URL generator, perhaps using one of the convenience classes +included in WebHelpers 1.3. Paginate's URL generator is *not* Akhet's URL +generator: it's a different kind of class specific to the paginator's needs. Subscribers @@ -157,39 +109,15 @@ The views module has a base class called ``Handler`` (but it's not related to "pyramid_handlers"). The index view demonstrates logging, optionally sets a flash message, and invokes a Mako template renderer. -:: - - import logging - - from pyramid.view import view_config - - log = logging.getLogger(__name__) - - class Handler(object): - def __init__(self, request): - self.request = request - - class Main(Handler): - - @view_config(route_name="home", renderer="index.html") - def index(self): - # Do some logging. - log.debug("testing logging; entered Main.index()") - - # Push a flash message if query param 'flash' is non-empty. - if self.request.params.get("flash"): - import random - num = random.randint(0, 999999) - message = "Random number of the day is: %s." % num - self.request.session.flash(message) - # Normally you'd redirect at this point but we have nothing to - # redirect to. - - # Return a dict of template variables for the renderer. - return {"project": "Akhet Demo"} - - - +The demo pushes a flash message by calling ``self.request.session.flash()`` +with the message text. By default this puts the message on the "info" queue, +and it's displayed using an "info" CSS class. You can push the message onto a +different queue by specifying the queue name as a second argument. But that's +only useful if the template pops the messages from the other queue by name, +otherwise they'll never be displayed. It's customary to name the queues +according to the Python logging hierarchy: debug, info (notice), warn(ing), +error, critical. The default stylesheet defines CSS classes with distinct +styling for several of these levels. .. include:: ../links.rst diff --git a/docs/demo/index.rst b/docs/demo/index.rst index 38e3fcd..b7244ef 100644 --- a/docs/demo/index.rst +++ b/docs/demo/index.rst @@ -1,9 +1,9 @@ -Demo Application +Demo application %%%%%%%%%%%%%%%% .. toctree:: :maxdepth: 1 - features + usage content details diff --git a/docs/demo/features.rst b/docs/demo/usage.rst similarity index 56% rename from docs/demo/features.rst rename to docs/demo/usage.rst index f98049b..affedfe 100644 --- a/docs/demo/features.rst +++ b/docs/demo/usage.rst @@ -1,14 +1,22 @@ -Usage and Features +Usage and features %%%%%%%%%%%%%%%%%% -The Akhet demo application includes Mako templates, a stylesheet and reset -stylesheet, and a basic view class to get you started. It shows the Akhet -library features in action. It's based on the former Akhet application scaffold -and what users have reported doing since the scaffold was released. +The Akhet demo application shows the Akhet library's features in action, and +contains templates and code you can copy into your own application as a +starting point. The demo is based on the former Akhet application scaffold +from Akhet 1, and what users of that scaffold have later reported doing in +their more recent applications. -The demo is not shipped with the Akhet package due to its larger number of -dependencies and more frequent changes. You can install it from its source -repository like any Pyramid application: +The demo is distributed separately from Akhet due to its larger number of +dependencies and more frequent changes. The Akhet library focuses on stability +and backward compatibility, while the demo is free to experiment more and make +backward-incompatible changes, and is in a permanent development mode. + +Installation +============ + +You can install the demo it from its source repository like any Pyramid +application: .. code-block:: console @@ -18,13 +26,16 @@ repository like any Pyramid application: (myenv)$ pip install -e . (myenv)$ pserve development.ini -The demo has the following features ported from the former 'akhet' scaffold -(which are not in the standard Pyramid scaffolds): +Features +======== + +The demo has the following features which originated in the former 'akhet' +scaffold: * Mako templates. * Site template to provide a common look and feel to your pages. * Automatically recognize filenames ending in .html as Mako templates. -* Starter stylesheet and browser-neutral reset stylesheet. +* Default stylesheet and browser-neutral reset stylesheet. * Pylons-like template globals including a helpers module 'h' and a URL generator 'url', and instructions for adding additional ones. * Serve static files at any URL, without being limited by URL prefixes. @@ -36,9 +47,9 @@ The demo has the following features ported from the former 'akhet' scaffold The demo introduces the following new features: * Class-based views using ``@view_config``. -* A pony and a unicorn. (Ported from 'paste.pony'.) +* A pony and a unicorn. -The demo does *not* have the following features from the former 'akhet' +The demo does *not* have these features that were in the former 'akhet' scaffold: * A SQLAlchemy model. The Pyramid 'alchemy' scaffold and the Models chapter in @@ -50,4 +61,5 @@ scaffold: you need them. + .. include:: ../links.rst diff --git a/docs/index.rst b/docs/index.rst index 9f9eb6f..1084488 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,34 +1,40 @@ Akhet -===== -:Version: 2.0, released XXXX-XX-XX +%%%%% + +:Version: 2.0, released 2012-02-12 +:Docs-Updated: same :PyPI: http://pypi.python.org/pypi/Akhet :Docs: http://docs.pylonsproject.org/projects/akhet/dev/ :Source: https://github.com/Pylons/akhet :Bugs: https://github.com/Pylons/akhet/issues :Discuss: pylons-discuss_ list +:Author: `Mike Orr <mailto:sluggoster@gmail.com>`_ +:Contributors: Michael Merickel, Marcin Lulek Akhet is a Pyramid_ library and demo application with a Pylons-like feel. -Earlier versions had an application scaffold, but version 2 replaces -it with the demo app. Much of the version 1 manual was moved to the `Pyramid -Cookbook`_ (as the `Pyramid for Pylons Users`_ guide). -(The guide is not yet available as of January 2012.) -The Akhet Python library is unchanged in version 2. -The library and demo app have different dependencies and goals, so the demo app -is distribued separately. The library focuses on backward compatibility, -minimal dependencies, and accepts only things that can be maintained long-term. -The demo app is more adventurous, and may contain incompatible changes from -version to version. The demo's main purpose is to contain the templates, -stylesheets, and large chunks of code from the old scaffold that you may want -to copy into your application. In the future, the demo will become more of a -testing ground for new techniques. +**Main changes in version 2: (A)** The 'akhet' scaffold gone, replaced by a demo +application, which you can cut and paste from. **(B)** General Pyramid/Pylons +material has been moved out of the manual to the `Pyramid Cookbook`_, section +`Pyramid for Pylons Users`_ guide. *(The guide is not yet online as of February +2012.)* **(C)** The include for static routes has changed to "akhet.static", but +"akhet" is still allowed for backward compatibility. **(D)** A new pony module. +**(E)** The repository is now on GitHub in the Pylons Project. + +The demo is distributed separately from the Akhet. Its repository URL is in the +Demo section. -The library runs on Python 2.5 - 2.7, and has been tested with Pyramid -1.3a6 and 1.2.4 on Ubuntu Linux 11.10. The next version will focus on Python 3 -support and will drop Python 2.5. The demo app currently has the same -compatibility range as the library. +Akhet runs on Python 2.5 - 2.7. Version 2 has been tested on Pyramid +1.3a6 and 1.2.4 using Pyramid 2.7.2 on Ubuntu Linux 11.10. The next Akhet +version, 2.1, will focus on Python 3 and will drop Python 2.5. +The demo application currently has the same compatibility range as Akhet +itself. +The word "akhet" is the name of the hieroglyph that is Pylons' icon: a sun +shining over two pylons. It means "horizon" or "mountain of light". +Documentation Contents +====================== .. toctree:: :maxdepth: 2 @@ -40,16 +46,14 @@ compatibility range as the library. :maxdepth: 1 changes - unfinished rant_scaffold -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` +.. + * :ref:`genindex` + * :ref:`modindex` + * :ref:`search` -The word "akhet" is the name of the hieroglyph that is Pylons' icon: a sun -shining over two pylons. It means "horizon" or "mountain of light". .. include:: links.rst diff --git a/docs/library/index.rst b/docs/library/index.rst index 7714bb5..ebb956d 100644 --- a/docs/library/index.rst +++ b/docs/library/index.rst @@ -1,14 +1,12 @@ Library %%%%%%% -The library currently has two components, a URL generator and a static -route helper. The Akhet demo app uses both. - .. toctree:: :maxdepth: 1 static urlgenerator + pony .. include:: ../links.rst diff --git a/docs/library/pony.rst b/docs/library/pony.rst new file mode 100644 index 0000000..63742e4 --- /dev/null +++ b/docs/library/pony.rst @@ -0,0 +1,16 @@ +Pony +%%%% + +``akhet.pony`` is a port of ``paste.pony`` in the Paste distribution, +originally written by Ian Bicking. Usage:: + + # In main(). + config.include("akhet.pony") + +This registers a route at URL "/pony", which displays an ASCII art pony. If +the user appends the query parameter "horn" with a non-blank value, as in +*/pony?horn=1*, it will display a unicorn instead. + +The page does not show your application name or anything in your site template, +but it does include a "Home" hyperlink which returns to the application's +home page (normally "/" unless the application is mounted under a URL prefix). diff --git a/docs/library/static.rst b/docs/library/static.rst index 040ed9d..dfed462 100644 --- a/docs/library/static.rst +++ b/docs/library/static.rst @@ -8,15 +8,15 @@ file exists, it falls back to the dynamic application. This static route helper works the same way: it works as an overlay on "/", so the route matches only if the file exists, otherwise Pyramid falls through to the next route. The difference is that Pylons' static app is a WSGI middleware, while the static -route helper registers an ordinary route and a view. By convention the static -route is put last in the route list, but it can go anywhere in the list. +route helper registers an ordinary route and a view. By convention you put the static +route last in the route list, but it can go anywhere in the list. Pyramid's standard `static view`_, in contrast, works only with a URL prefix like "/static"; it can't serve top-level URLs like "/robots.txt" and "/favicon.ico". If you want a separate disjunct prefix like "/w3c" (for "/w3c/p3p.xml", the Internet standard for a machine-readable privacy policy), you'd have to -configure a separate view and static directory for that prefix. With the route -helper, you don't have to configure anything extra, just create a file +configure a separate view and static directory for that prefix. With the static route +helper you don't have to configure anything extra, just create a file "myapp/static/w3c/p3p.xml" and you're done. The static route helper does have some disadvantages compared to Pyramid's @@ -68,5 +68,10 @@ The API is from Pyramid's early days, so it makes an asset spec out of also searches only a single directory rather than a search path. These limitations may be relaxed in a future version. +Changes in version 2 +==================== + +The include module is now "akhet.static"; in version 1 it was "akhet". A +backward compatibility shim is in place. .. include:: ../links.rst diff --git a/docs/library/urlgenerator.rst b/docs/library/urlgenerator.rst index 321511e..7cdda61 100644 --- a/docs/library/urlgenerator.rst +++ b/docs/library/urlgenerator.rst @@ -1,15 +1,15 @@ URL generator %%%%%%%%%%%%% -A class that consolidates the various URL-generating functions in Pyramid into -one concise API that's convenient for templates. It performs the same job as +A class that consolidates Pyramid's various URL-generating functions into one +concise API that's convenient for templates. It performs the same job as ``pylons.url`` in Pylons applications, but the API is different. Pyramid has several URL-generation routines but they're scattered between Pyramid request methods, WebOb request methods, Pyramid request attributes, WebOb request attributes, and Pyramid functions. They're named inconsistently, and the names are too long to put repeatedly in templates. The methods are -usually, but not always, paired: one method returning the URL path only +usually -- but not always -- paired: one method returning the URL path only ("/help"), the other returning the absolute URL ("http://example.com/help"). Pylons defaults to URL paths, while Pyramid tends to absolute URLs (because that's what the methods with "url" in their names return). The Akhet author @@ -17,26 +17,24 @@ prefers path URLs because the automatically adjust under reverse proxies, where the application has the wrong notion of what its visible scheme/host/port is, but the browser knows which scheme/host/port it requested the page on. -URLGenerator unifies all these by giving short one-word names to the most +``URLGenerator`` unifies all these by giving short one-word names to the most common methods, and having a switchable default between path URLs and absolute URLs. Usage ===== -Copy the "subscribers" module in the Akhet demo app (*akhet_demo/subscribers.py*) -to your own application, and modify if desired. Then, include it in your main +Copy the "subscribers" module in the Akhet demo (*akhet_demo/subscribers.py*) +to your own application, and modify it if desired. Then, include it in your main function:: # In main(). config.include(".subscribers") -The subscribers attach the URL generator to the request as ``request.u``, and -put it in the template namespace as ``u``. Both names are short and sweet, -conserving horizontal space in templates and views, giving you more space to -specify the arguments. +The subscribers attach the URL generator to the request as +``request.url_generator``, and inject it into the template namespace as ``url``. -URLGenerator was contributed by Michael Merickel and modified by Mike Orr. +``URLGenerator`` was contributed by Michael Merickel and modified by Mike Orr. API === @@ -54,11 +52,20 @@ Subclassing =========== The source code (*akhet/urlgenerator.py*) has some commented examples of things -you can do in a subclass. For instance, you can make a method to generate a URL -to a static asset in your application, or a static asset in Deform or -another third-party package, letting the method fill in parts of the asset -spec. The instance has ``request`` and ``context`` attributes, which you can -use to calculate any URL you wish. You can put a subclass in your application -and then adjust the subscribers to use it. +you can do in a subclass. For instance, you can define a ``static`` method to +generate a URL to a static asset in your application, or a ``deform`` method to +serve static files from the Deform form library. The instance has ``request`` +and ``context`` attributes, which you can use to calculate any URL you wish. +You can put a subclass in your application and then adjust the subscribers to +use it. + +The reason the base class does not define a ``static`` method, is that we're +not sure yet what the best long-term API is. We want something concise enough +for everyday use but also supporting unusual cases, and something we can +guarantee is correct and we're comfortable supporting long-term. There's also +the issue of the static route helper vs Pyramid's static view, or multiple +Pyramid static views responding to different sub-URLs. In the +meantime, if you want a ``static`` method, you can decide on your own favorite +API and implement it. .. include:: ../links.rst diff --git a/docs/rant_scaffold.rst b/docs/rant_scaffold.rst index ef0244b..4a9eb53 100644 --- a/docs/rant_scaffold.rst +++ b/docs/rant_scaffold.rst @@ -1,4 +1,4 @@ -Appendix: Rant about Scaffolds and PasteScript +Appendix: Rant about scaffolds and PasteScript ---------------------------------------------- The main reason the 'akhet' scaffold is gone is that maintaining it turned out diff --git a/docs/unfinished.rst b/docs/unfinished.rst deleted file mode 100644 index 85a0948..0000000 --- a/docs/unfinished.rst +++ /dev/null @@ -1,1139 +0,0 @@ -Unfinished: Fragments -%%%%%%%%%%%%%%%%%%%%% - -INI files, logging, and pserve -=============================== - -development.ini ---------------- - -Some Pyramid add-ons also look here for configuration settings. For instance, -Beaker looks for "cache.\*" and "session.\*" settings, and Mako looks for -"mako.\*" settings. You can use these to configure what kind of persistent -session store to use, how long until idle sessions are discarded, and what -character encoding to use for template output (the default is "utf-8"). -possible options are listed in the Pyramid manual or the add-on's manual. - -You can of course create multiple INI files if you want to try the application -out under different configuration scenarios, for instance to compare a -database and a PostgresSQL database. You can even run them simultaneously as -long as each configuration specifies a different port. - -If you're using Apache's mod_proxy to proxy to a Python HTTP server, you might -want to change this to "use = egg:pyramid#cherrypy". This uses the CherryPy -server, which is multithreaded like paste.httpserver (which Pylons and older -versions of Pyramid used), but is more robust under high loads. (You'll have to -install CherryPy to use this.) - -The Pyramid manual and Cookbook discuss other deployment scenarios like -mod_wsgi and FastCGI. - -I normally set "host = 127.0.0.1" and "port = 5000" after creating an -application. That way it serves only request coming from the same computer -rather than from any computer on the network. That enhances security when the -debug toolbar is enabled. Port 5000 is the Pylons tradition, and it's easier to -remember and type than 6543. - -"%(here)s" expands to the path of the directory containing the INI file. - - -production.ini --------------- - -The default production file is just slightly different from the development -file: - -.. code-block:: ini - - [app:main] - use = egg:Zzz - - pyramid.reload_templates = false - pyramid.debug_authorization = false - pyramid.debug_notfound = false - pyramid.debug_routematch = false - pyramid.debug_templates = false - pyramid.default_locale_name = en - pyramid.includes = pyramid_tm - - sqlalchemy.url = sqlite:///%(here)s/Zzz.db - - [server:main] - use = egg:pyramid#wsgiref - host = 0.0.0.0 - port = 6543 - -The most important difference here is that "pyramid_debugtoolbar" is NOT -enabled. **This is vital for security!** Otherwise miscreants can type -arbitrary Python commands in the interactive traceback if an exception occurs, -and potentially read password files or damage the system. - -If an exception occurs during a production request, the user will get a plain -white error screen, "A server error occurred. Please contact the -administrator." To customize that, see "Exception Views" in the Pyramid manual. -The traceback will be dumped to the console, and will not be shown to the user. -To customize how tracebacks are reported to the administrator, install the -pyramid_exclog_ tween, which is covered below in Logging. (This replaces the -WebError#error_catcher middleware which was used in Pylons and earlier versions -of Pyramid.) - -The debug settings are all set to false in production. This saves a few CPU -cycles while it's processing requests. - -The server section is unchanged from development.ini. The correct settings -here depend on what webserver you're running the application with, so you'll -have to configure this part yourself. - - -Logging -------- - -The bottom 2/3 of both INI files contain several sections to configure -Python's logging system. This is the same as in Pylons. We can't explain the -entire logging syntax here, but these are the sections most often customized by -users: - -.. code-block:: ini - - [logger_root] - level = INFO - handlers = console - - [logger_zzz] - level = DEBUG - handlers = - qualname = zzz - - [logger_sqlalchemy] - level = INFO - handlers = - qualname = sqlalchemy.engine - # "level = INFO" logs SQL queries. - # "level = DEBUG" logs SQL queries and results. - # "level = WARN" logs neither. (Recommended for production systems.) - -These define a logger "root", "zzz" (the application's package name), and -"sqlalchemy.engine" (specified in the qualname). Each has a 'level' variable -which can be DEBUG, INFO, WARN, ERROR, or CRITICAL. Each level also logs the -levels on its right, so WARN logs warnings and errors. Logger names are in a -dotted hierarchy, so that "sqlalchemy.engine" affects all loggers below it -("sqlalchemy.engine.ENGINE1", etc). "root" affects all loggers that aren't -otherwise specified. - -Generally, DEBUG is debugging information, INFO is chatty success messages, -WARN means something might be wrong, ERROR means something is -definitely wrong, and CRITICAL means you'd better fix it now or else. -But there's nothing to enforce this, so each library chooses how to log things. -So SQLAlchemy logs SQL queries at the INFO level on -"sqlalchemy.engine.ENGINE_NAME", even though some people would consider this -debugging information. - -Logger names do NOT automatically correspond to Python module names, although -it's customary to do so if there's no better name for the logger. You can do -this by putting the following at the top of each module: - - import logging - log = logging.getLogger(__name__) - -This creates a variable ``log`` which is a logger named after the module. So if -the module is ``zzz.views``, the logger is "zzz.views". - -The default *development.ini* displays all messages from the application -modules (logger_zzz = DEBUG). It displays SQL queries (logger_sqlalchemy = -INFO). It displays other messages only if they're warnings or above -(logger_root = WARN). The default *production.ini* sets all these to WARN, so -it will not log anything except warnings or errors. - -You can create additional loggers by adding a "[logger_yourname]" section, -listing it in the "[loggers]" section, and calling -``logging.getLogger("yourname")`` in the Python module. - -To activate logging in a utility script the way "pserve" does, do the -following:: - - import logging.config - logging.config.fileConfig(INI_FILENAME) - - - -Init module and main function -============================= - -a dict based on the "[GLOBAL]" section of the INI -file. - - -.. code-block:: python - :linenos: - - # AKHET 1 - - from pyramid.config import Configurator - import akhet - import pyramid_beaker - import sqlahelper - import sqlalchemy - - def main(global_config, XXsettings): - """ This function returns a Pyramid WSGI application. - """ - - # Here you can insert any code to modify the ``settings`` dict. - # You can: - # * Add additional keys to serve as constants or "global variables" in the - # application. - # * Set default values for settings that may have been omitted. - # * Override settings that you don't want the user to change. - # * Raise an exception if a setting is missing or invalid. - # * Convert values from strings to their intended type. - - # Create the Pyramid Configurator. - config = Configurator(settings=settings) - config.include("pyramid_handlers") - config.include("akhet") - - # Initialize database - engine = sqlalchemy.engine_from_config(settings, prefix="sqlalchemy.") - sqlahelper.add_engine(engine) - config.include("pyramid_tm") - - # Configure Beaker sessions - session_factory = pyramid_beaker.session_factory_from_settings(settings) - config.set_session_factory(session_factory) - - # Configure renderers and event subscribers - config.add_renderer(".html", "pyramid.mako_templating.renderer_factory") - config.add_subscriber("zzz.subscribers.create_url_generator", - "pyramid.events.ContextFound") - config.add_subscriber("zzz.subscribers.add_renderer_globals", - "pyramid.events.BeforeRender") - - # Set up view handlers - config.include("zzz.handlers") - - # Set up other routes and views - # ** If you have non-handler views, create create a ``zzz.views`` - # ** module for them and uncomment the next line. - # - #config.scan("zzz.views") - - # Mount a static view overlay onto "/". This will serve, e.g.: - # ** "/robots.txt" from "zzz/static/robots.txt" and - # ** "/images/logo.png" from "zzz/static/images/logo.png". - # - config.add_static_route("zzz", "static", cache_max_age=3600) - - # Mount a static subdirectory onto a URL path segment. - # ** This not necessary when using add_static_route above, but it's the - # ** standard Pyramid way to serve static files under a URL prefix (but - # ** not top-level URLs such as "/robots.txt"). It can also serve files from - # ** third-party packages, or point to an external HTTP server (a static - # ** media server). - # ** The first commented example serves URLs under "/static" from the - # ** "zzz/static" directory. The second serves URLs under - # ** "/deform" from the third-party ``deform`` distribution. - # - #config.add_static_view("static", "zzz:static") - #config.add_static_view("deform", "deform:static") - - return config.make_wsgi_app() - -(Note: ``**settings`` in line 7 is displayed as ``XXsettings`` due to a -limitation in our documentation generator: "``*``" in code blocks -outside comments make Vim's syntax highlighting go bezerk.) - -Lines 11-18 are a long comment explaining how you can modify the ``settings`` -dict. If you have any code to set "global variables" for the application, or to -validate the settings or convert the values from strings to other types, -put the code here. (We're considering a default routine to validate the -settings but haven't decided whether to use homegrown code, Colander, -FormEncode, or another validation library.) - -Line 21 instantiates a ``Configurator`` which will create the application. -(It's not the application itself.) Lines 22-23 add plug-in functionality to -the configurator. The argument is the name of a module that contains an -``includeme()`` function. Line 22 ultimately creates the -``config.add_handler()`` method; line 23 creates the -``config.add_static_route()`` method. - -Line 26 creates a SQLAlchemy engine based on the "sqlalchemy.url" setting in -*development.ini*. The default setting is -"sqlite:///%(here)s/db.sqlite", which creates or opens a database "db.sqlite" -in the same directory as the INI file. You can also pass other engine arguments -to SQLAlchemy, either by putting them in the INI file with the "sqlalchemy." -prefix, or by passing them as keyword args. Line 27 adds the engine to the -``sqlahelper`` library so that the model can use it; it also updates the -library's contextual session. Line 28 initializes the "pyramid_tm" transaction -manager. SQLAHelper is further explained in the Models section below; the -transaction manager is explained in the "Transaction Manager" chapter. - -(Note: if you answered 'n' to the SQLAlchemy question when creating the -application, lines 4-5 and 25-28 will not be present in your module.) - -Lines 31-32 configure the session factory. - -Line 35 tells Pyramid to render *\*.html* templates using Mako. Pyramid out of -the box renders Mako templates with the *\*.mako* or *\*.mak* extensions, and -Chameleon templates with the *\*.pt* extension, but you have to tell it if you -want to use a different extension or another template engine. Third-party -packages are available for using Jinja2 (``pyramid_jinja2``), and -a Genshi emulator using Chameleon (``pyramid_genshi_chameleon``), - -Lines 36-39 registers event subscribers, which are callback functions called at -specific points during request processing. Lines 36-37 register a callback that -instantiates a URL generator (described in the Templates section below and in -the API_ chapter). Lines 38-39 register a callback which adds several -Pylons-like variables to the template namespace whenever a template is -rendered. The callbacks are defined in the ``zzz.subscribers`` module, which -you can modify. - -Lines 42 configures routing. Actually it calls an include function in the -handlers package. We'll explore routing more fully later. - -Lines 44-48 and 56-67 are commented code; they show how to enable certain -advanced features. - -Line 54 is equivalent to the *public* directory in Pylons applications. It's -not a standard part of Pyramid, which handles static files a different way, but -this method is closer to the Pylons tradition. Any URLs which did not match a -dynamic route will be compared to the contents of the *zzz/static* directory, -and if a file exists for the URL, it is served. Unlike Pylons, this happens -after the dynamic routes are tried rather than before. This means that any -dynamic route that might accidentally match a static resource must explicitly -exclude that URL. - -This is just one of several ways to serve static files in Pyramid, each with -its own advantages and disadvantages. These are all discussed below in the -Static Files section. - -Line 69 creates and returns a Pyramid WSGI application based on the -configuration. - -This short main function -- compared to Pylons' three functions in three -modules -- allows an entire small application to be defined in a single module. -Half the lines are comments so they can be deleted. A short main function is -useful for small demos, but the principle also leads to a different developer -culture. Pylons' application skeleton is complex enough that most people don't -stray from it, and Pylons' documentation emphasizes using "paster serve" rather -than other invocation methods. Pyramid's docs encourage users to structure -everything outside ``main()`` as they wish, and they describe "paster serve" as -just one way to invoke the application. The INI files and "paster serve" are -just for your convenience; you don't have to use them. - -A bit more about Paster ------------------------ - -"paster serve" does several other things besides calling the main function. -It interpolates "%(here)s" placeholders in the INI file, as well as -variables in the "[DEFAULT]" section (which we aren't using here). It -configures logging, and finds the application by looking up the entry point -specified in the 'use' variable. All this can be done by the following code -in both Pyramid and Pylons:: - - import logging.config - import os - import paste.deploy.loadwsgi as loadwsgi - ini_path = "/path/to/development.ini" - logging.config.fileConfig(ini_path) - app_dir, ini_file = os.path.split(ini_path) - app = loadwsgi.loadapp("config:" + ini_file, relative_to=app_dir) - -Models -====== - -The default *zzz/models/__init__.py* looks like this:: - - import logging - import sqlahelper - import sqlalchemy as sa - import sqlalchemy.orm as orm - import transaction - - log = logging.getLogger(__name__) - - Base = sqlahelper.get_base() - Session = sqlahelper.get_session() - - - #class MyModel(Base): - # __tablename__ = "models" - # - # id = sa.Column(sa.Integer, primary_key=True) - # name = sa.Column(sa.Unicode(255), nullable=False) - -Pylons applications have a "zzz.model.meta" model to hold SQLAlchemy's -housekeeping objects, but Akhet uses the SQLAHelper library which holds them -instead. This gives you more freedom to structure your models as you wish, -while still avoiding circular imports (which would happen if you defined -Session in the main module and then import the other modules into it; the -other modules would import the main module to get the Session, and voilĂ  -circular imports). - -A real application would replace the commented ``MyModel`` class with -one or more ORM classes. The example uses SQLAlchemy's "declarative" syntax, -although of course you don't have to. - -SQLAHelper ----------- - -The SQLAHelper library is a holding place for the application's contextual -session (normally assigned to a ``Session`` variable with a capital S, to -distinguish it from a regular SQLAlchemy session), all engines used by the -application, and an optional declarative base. We initialized it via the -``sqlahelper.add_engine`` line in the main function. Because we did not specify -an engine name, the library set the engine name to "default", and also bound the -contextual session and the base's metadata to it. - -There's not much else to know about SQLAHelper. You can call ``get_session()`` -at any time to get the contextual session. You can call ``get_engine()`` or -``get_engine(name)`` to retrieve an engine that was previously added. You can -call ``get_base()`` to get the declarative base. - -If you need to modify the session-creation parameters, you can call -``get_session().config(...)``. But if you modify the session extensions, see -the "Transaction Manager" chapter to avoid losing the extension that powers the -transaction manager. - -View handlers -============= - -The default *zzz.handlers* package contains a *main* module which looks like -this:: - - import logging - - from pyramid_handlers import action - - import zzz.handlers.base as base - import zzz.models as model - - log = logging.getLogger(__name__) - - class Main(base.Handler): - @action(renderer="index.html") - def index(self): - log.debug("testing logging; entered Main.index()") - return {"project":"Zzz"} - -This is clearly different from Pylons, and the ``@action`` decorator looks a -bit like TurboGears. The decorator has three optional arguments: - -name - - The action name, which is the target of the route. Normally this is the - same as the view method name but you can override it, and you must override - it when stacking multiple actions on the same view method. - -renderer - - A renderer name or template filename (whose extension indicates the - renderer). A renderer converts the view's return value into a Response - object. Template renderers expect the view to return a dict; other - renderers may allow other types. Two non-template renderers are built into - Pyramid: "json" serializes the return value to JSON, and "string" calls - ``str()`` on the return value unless it's already a Unicode object. If you - don't specify a renderer, the view must return a Response object (or any - object having three particular attributes described in Pyramid's Response - documentation). In all cases the view can return a Response object to - bypass the renderer. HTTP errors such as HTTPNotFound also bypass the - renderer. - -permission - - A string permission name. This is discussed in the Authorization section - below. - -The Pyramid developers decided to go with the -return-a-dict approach because it helps in two use cases: - -1. Unit testing, where you want to test the data calculated rather than -parsing the HTML output. This works by default because ``@action`` itself does -not modify the return value or arguments; it merely sets function attributes or -interacts with the configurator. - -2. Situations where several URLs render the same data using different templates -or different renderers (like "json"). In that case, you can put multiple -``@action`` decorators on the same method, each with a different name and -renderer argument. - -Two functions in ``pyramid.renderers`` are occasionally useful in views: - -.. function:: pyramid.renderers.render(renderer_name, value, request=None, package=None) - - Render a template and return a string. 'renderer_name' is a template - filename or renderer name. 'value' is a dict of template variables. - 'request' is the request, which is needed only if the template cares - about it. - - If the function can't find the template, try passing "zzz:templates/" - as the ``package`` arg. - -.. function:: pyramid.renderers.render_to_response(renderer_name, value, request=None, package=None) - - Render a template, instantiate a Response, set the Response's body to - the result of the rendering, and return the Response. The arguments are the - same as for ``render()``, except that 'request' is more important. - - -The handler class inherits from a base class defined in *zzz.handlers.base*:: - - """Base classes for view handlers. - """ - - class Handler(object): - def __init__(self, request): - self.request = request - - #c = self.request.tmpl_context - #c.something_for_site_template = "Some value." - -Pyramid does not require a base class but Akhet defines one for convenience. -All handlers should set ``self.request`` in their ``.__init__`` method, and the -base handler does this. It also provides a place to put common methods used by -several handler classes, or to set ``tmpl_context`` (``c``) variables which are -used by your site template (common to all views or several views). (You -can use ``c`` in view methods the same way as in Pylons, although this is not -recommended.) - -Note that non-template renders such as "json" ignore ``c`` variables, so it's -really only useful for HTML-only data like which stylesheet to use. - -The routes are defined in *zzz/handlers/__init__.py*:: - - """View handlers package. - """ - - def includeme(config): - """Add the application's view handlers. - """ - config.add_handler("home", "/", "zzz.handlers.main:Main", - action="index") - config.add_handler("main", "/{action}", "zzz.handlers.main:Main", - path_info=r"/(?!favicon\.ico|robots\.txt|w3c)") - -``includeme`` is a configurator "include" function, which we've already seen. -This function calls ``config.add_handler`` twice to create two routes. The -first route connects URL "/" to the ``index`` view in the ``Main`` handler. - -The second route connects all other one-segment URLs (such as "/hello" or -"/help") to a same-name method in the ``Main`` handler. "{action}" is a path -variable which will be set the corresponding substring in the URL. Pyramid will -look for a method in the handler with the same action name, which can either be -the method's own name or another name specified in the 'name' argument to -``@action``. Of course, these other methods ("hello" and "help") don't exist in -the example, so Pyramid will return 400 Not Found status. - -The 'path_info' argument is a regex which excludes certain URLs from matching -("/favicon.ico", "/robots.txt", "/w3c"). These are static files or directories -that would syntactically match "/{action}", but we want these to go to a later -route instead (the static route). So we set a 'path_info' regex that doesn't -match them. - -Redirecting and HTTP errors ---------------------------- - -To issue a redirect inside a view, return an HTTPFound:: - - from pyramid.httpexceptions import HTTPFound - - def myview(self): - return HTTPFound(location=request.route_url("foo")) - # Or to redirect to an external site - return HTTPFound(location="http://example.com/") - -You can return other HTTP errors the same way: ``HTTPNotFound``, ``HTTPGone``, -``HTTPForbidden``, ``HTTPUnauthorized``, ``HTTPInternalServerError``, etc. -These are all subclasses of both ``Response`` and ``Exception``. Although you -can raise them, Pyramid prefers that you return them instead. If you intend to -raise them, you have to define an exception view that receives the exception -argument and returns it, as shown in the Views chapter in the Pyramid manual. -(On Python 2.4, you also have to call the instance's ``.exception()`` method -and raise that, because you can't raise instances of new-style classes in 2.4.) -A future version of Pyramid may have an exception view built-in; this would -conflict with your exception view so you'd need to delete it, but there's no -need to worry about that until/if it actually happens. - -Pyramid catches two non-HTTP exceptions by default, -``pyramid.exceptions.NotFound`` and ``pyramid.exceptions.Forbidden``, which -it sends to the Not Found View and the Forbidden View respectively. You can -override these views to display custom HTML pages. - -More on routing and traversal -============================= - -Routing methods and view decorators ------------------------------------ - -Pyramid has several routing methods and view decorators. The ones we've seen, -from the ``pyramid_handlers`` package, are: - -.. function:: @action(\*\*kw) - - I make a method in a class into a *view* method, which - ``config.add_handler`` can connect to a URL pattern. By definition, any class - that contains view methods is a view handler. My most interesting args are - 'name' and 'renderer'. If 'name' is NOT specified, the action name is the - same as the method name. If 'name' IS specified, the action name can be - different. If 'renderer' is specified, it indicates a renderer or template - (and the template's extension indicates a renderer). If multiple ``@action`` - decorators are put on a single method, each must have a different name, and - they presumably will have different renderers too. - -.. method:: config.add_handler(name, pattern, handler, action=None, \*\*kw) - - I create a route connecting the URL pattern to the handler class. If - 'action' is specified, I connect the route to that specific action (a method - decorated with the ``@action`` decorator). If 'action' is not specified, the - pattern must contain a "{action}" placeholder. In that case I scan the - handler class for all possible actions. It is an error to specify both "{action}" - and an ``action`` arg. I pass extra keyword args to ``config.add_route``, - and keyword args in the ``@action`` decorator to ``config.add_view``. - -``config.add_handler`` calls two lower-level methods which you can also call -directly: - -.. method:: config.add_route(name, pattern, \*\*kw) - - Create a route connecting a URL pattern directly to a view callable outside - a handler. The view is specified with a 'view' arg. If the view is a - function, it must take a Request argument and return a Response (or any - object with the three required attributes). If it's a class, the constructor - takes the Request argument and the specified method (``.__call__`` by - default) is called with no arguments. - -.. method:: config.add_view(\*\*kw) - - I register a view (specified with a 'view' arg). In URL dispatch, you - normally don't call this directly but let ``config.add_handler`` or - ``config.add_route`` call it for you. In traversal, you call this to - register a view. The 'name' argument is the view name, which is used by - traversal to choose which view to invoke. - -Two others you should know about: - -.. function:: config.scan(package=None) - - I scan the specified package (which may be an asset spec) and import all its - modules recursively, looking for functions decorated with ``@view_config``. - For each such function, I call ``add_view`` passing the decorator's args to - it. I can also scan a package, in which case all submodules in the package - are recursively scanned. If no package is specified, I scan the caller's - package (i.e., the entire application). - - I can also be called for my side effect of importing all of a package's - modules even if none of them contain ``@view_config``. - -.. function:: @view_config(\*\*kw) - - I decorate a function so that ``config.scan`` will recognize it as a view - callable, and I also hold ``add_view`` arguments that ``config.scan`` will - pick up and apply. I can also decorate a class or a method in a class. - - -Route arguments and predicates ------------------------------- - -``config.add_handler`` accepts a large number of keyword -arguments. We'll list the ones most commonly used with Pylons-like applications -here. For full documentation see the `add_route -<http://docs.pylonsproject.org/projects/pyramid/1.0/api/config.html#pyramid.config.Configurator.add_route>`_ -API. Most of these arguments can also be used with ``config.add_route``. - -The arguments are divided into *predicate arguments* and *non-predicate -arguments*. Predicate arguments determine whether the route matches the -current request: all predicates must pass in order for the route to be chosen. -Non-predicate arguments do not affect whether the route matches. - -name - - [Non-predicate] The first positional arg; required. This must be a unique - name for the route, and is used in views and templates to generate the URL. - -pattern - - [Predicate] The second positional arg; required. This is the URL path with - optional "{variable}" placeholders; e.g., "/articles/{id}" or - "/abc/{filename}.html". The leading slash is optional. By default the - placeholder matches all characters up to a slash, but you can specify a - regex to make it match less (e.g., "{variable:\d+}" for a numeric variable) - or more ("{variable:.*}" to match the entire rest of the URL including - slashes). The substrings matched by the placeholders will be available as - *request.matchdict* in the view. - - A wildcard syntax "\*varname" matches the rest of the URL and puts it into - the matchdict as a tuple of segments instead of a single string. So a - pattern "/foo/{action}/\*fizzle" would match a URL "/foo/edit/a/1" and - produce a matchdict ``{'action': u'edit', 'fizzle': (u'a', u'1')}``. - - Two special wildcards exist, "\*traverse" and "\*subpath". These are used - in advanced cases to do traversal on the right side of the URL, and should - be avoided otherwise. - -factory - - [Non-predicate] A callable (or asset spec). In URL dispatch, this returns a - *root resource* which is also used as the *context*. If you don't specify - this, a default root will be used. In traversal, the root contains one - or more resources, and one of them will be chosen as the context. - -xhr - - [Predicate] True if the request must have an "X-Reqested-With" header. Some - Javascript libraries (JQuery, Prototype, etc) set this header in AJAX - requests. - -request_method - - [Predicate] An HTTP method: "GET", "POST", "HEAD", "DELETE", "PUT". Only - requests of this type will match the route. - -path_info - - [Predicate] A regex compared to the URL path (the part of the URL after the - application prefix but before the query string). The URL must match this - regex in order for the route to match the request. - -request_param - - [Predicate] If the value doesn't contain "=" (e.g., "q"), the request must - have the specified parameter (a GET or POST variable). If it does contain - "=" (e.g., "name=value"), the parameter must have the specified value. - - This is especially useful when tunnelling other HTTP methods via - POST. Web browsers can't submit a PUT or DELETE method via a form, so it's - customary to use POST and to set a parameter ``_method="PUT"``. The - framework or application sees the "_method" parameter and pretends the - other HTTP method was requested. In Pyramid you can do this with - ``request_param="_method=PUT``. - -header - - [Predicate] If the value doesn't contain ":"; it specifies an HTTP header - which must be present in the request (e.g., "If-Modified-Since"). If it - does contain ":", the right side is a regex which the header value must - match; e.g., "User-Agent:Mozilla/.\*". The header name is case insensitive. - -accept - - [Predicate] A MIME type such as "text/plain", or a wildcard MIME type with - a star on the right side ("text/\*") or two stars ("\*/\*"). The request - must have an "Accept:" header containing a matching MIME type. - -custom_predicates - - [Predicate] A sequence of callables which will be called in order to - determine whether the route matches the request. The callables should - return ``True`` or ``False``. If any callable returns ``False``, the route - will not match the request. The callables are called with two arguments, - ``info`` and ``request``. ``request`` is the current request. ``info`` is a - dict which contains the following:: - - info["match"] => the match dict for the current route - info["route"].name => the name of the current route - info["route"].pattern => the URL pattern of the current route - - Use custom predicates argument when none of the other predicate args fit - your situation. See - <http://docs.pylonsproject.org/projects/pyramid/1.0/narr/urldispatch.html#custom-route-predicates>` - in the Pyramid manual for examples. - - You can modify the match dict to affect how the view will see it. For - instance, you can look up a model object based on its ID and put the object - in the match dict under another key. If the record is not found in the - model, you can return False to prevent the route from matching the request; - this will ultimately case HTTPNotFound if no other route or traversal - matches the URL. The difference between doing this and returning - HTTPNotFound in the view is that in the latter case the following routes - and traversal will never be consulted. That may or may not be an advantage - depending on your application. - -View arguments --------------- - -The 'name', 'renderer' and 'permission' arguments described for ``@action`` can -also be used with ``@view_config`` and ``config.add_view``. - -``config.add_route`` has counterparts to some of these such as -'view_permission'. - -``config.add_view`` also accepts a 'view' arg which is a view callable or asset -spec. This arg is not useful for the decorators which already know the view. - -The 'wrapper' arg can specify another view, which will be called when this view -returns. (XXX Is this compatible with view handlers?) - - -The request object -================== - -The Request object contains all information about the current request state and -application state. It's available as ``self.request`` in handler views, the -``request`` arg in view functions, and the ``request`` variable in templates. -In pshell or unit tests you can get it via -``pyramid.threadlocal.get_current_request()``. (You shouldn't use the -threadlocal back door in most other cases. If something you call from the view -requires it, pass it as an argument.) - -Pyramid's ``Request`` object is a subclass of WebOb Request just like -'pylons.request' is, so it contains all the same attributes in methods like -``params``, ``GET``, ``POST``, ``headers``, ``method``, ``charset``, ``date``, -``environ``, ``body``, and ``body_file``. The most commonly-used attribute is -``request.params``, which is the query parameters and POST variables. - -Pyramid adds several attributes and methods. ``context``, ``matchdict``, -``matched_route``, ``registry``, ``registry.settings``, ``session``, and -``tmpl_context`` access the request's state data and global application data. -``route_path``, ``route_url``, ``resource_url``, and ``static_url`` generate -URLs, shadowing the functions in ``pyramid.url``. (One function, -``current_route_url``, is available only as a function.) - -Rather than repeating the existing documentation for these attributes and -methods, we'll just refer you to the original docs: - -* `Pyramd Request, Response, HTTP Exceptions, and MultiDict <http://docs.pylonsproject.org/projects/pyramid/1.0/narr/webob.html>`_ -* `Pyramid Request API <http://docs.pylonsproject.org/projects/pyramid/1.0/api/request.html#request-module>`_ -* `WebOb Request API <http://pythonpaste.org/webob/reference.html#id1>`_ -* `Pyramid Response API <http://docs.pylonsproject.org/projects/pyramid/1.0/api/response.html>`_ -* `WebOb Response API <http://pythonpaste.org/webob/reference.html#id2>`_ - -MultiDict is not well documented so we've written our own `MultiDict API`_ -page. In short, it's a dict-like object that can have multiple values for each -key. ``request.params``, ``request.GET``, and ``request.POST`` are MultiDicts. - -Pyramid has no pre-existing Response object when your view starts executing. To -change the response status type or headers, you can either instantiate your own -``pyramid.response.Response`` object and return it, or use these special -Request attributes defined by Pyramid:: - - request.response_status = "200 OK" - request.response_content_type = "text/html" - request.response_charset = "utf-8" - request.response_headerlist = [ - ('Set-Cookie', 'abc=123'), ('X-My-Header', 'foo')] - request.response_cache_for = 3600 # Seconds - -Akhet adds one Request attribute. ``request.url_generator``, which is used to -implement the ``url`` template global described below. - - -Templates -========= - -Pyramid has built-in support for Mako and Chameleon templates. Chameleon runs -only on CPython and Google App Engine, not on Jython or other platforms. Jinja2 -support is available via the ``pyramid_jinja2`` package on PyPI, and a Genshi -emulator using Chameleon is in the ``pyramid_chameleon_genshi`` package. - -Whenever a renderer invokes a template, the template namespace includes all the -variables in the view's return dict, plus the following: - -.. attribute:: request - - The current request. - -.. attribute:: context - - The context (same as ``request.context``). - -.. attribute:: renderer_name - - The fully-qualified renderer name; e.g., "zzz:templates/foo.mako". - -.. attribute:: renderer_info - - An object with attributes ``name``, ``package``, and ``type``. - -The subscriber in your application adds the following additional variables: - -.. attribute:: c, tmpl_context - - ``request.tmpl_context`` - -.. attribute:: h - - The helpers module, defined as "zzz.helpers". This is set by a subscriber - callback in your application; it is not built into Pyramid. - -.. attribute:: session - - ``request.session``. - -.. attribute:: url - - In Akhet, a URLGenerator object. In Pyramid's built-in application templates - that use URL dispatch, an alias to the ``route_url`` *function*, which - requires you to pass the route name as the first arg and the request as the - second arg. - - The URLGenerator object has convenience methods for generating URLs based on - your application's routes. See the complete list on the API_ page. - - By default the generator creates unqualified URLs (i.e., without the - "scheme://hostname" prefix) if the underlying Pyramid functions allow it. - To get absolute URLs throughout the application, edit *zzz/subscribers.py*, - go to the line where the URLGenerator is instantiated, and change the - 'qualified' argument to True. Pylons traditionally uses unqualified URLs, - while Pyramid traditionally uses qualified URLs. Note that qualified URLs - may be wrong if the application is running behind a reverse proxy! (E.g., - Apache's mod_proxy.) The generated URL may be "http://localhost:5000" which - is correct for the application but invalid to the end user (who needs the - proxy's URL, "https://example.com"). - -Advanced template usage ------------------------ - -If you need to fill a template within view code or elsewhere, do this:: - - from pyramid.renderers import render - variables = {"foo": "bar"} - html = render("mytemplate.mako", variables, request=request) - -There's also a ``render_to_response`` function which invokes the template and -returns a Response, but usually it's easier to let ``@action`` or -``@view_config`` do this. However, if your view has an if-stanza that needs to -override the template specified in the decorator, ``render_to_response`` is -the way to do it. :: - - @action(renderer="index.html") - def index(self): - records = models.MyModel.all() - if not records: - return render_to_response("no_records.html") - return {"records": records} - -For further information on templating see the Templates section in the Pyramid -manual, the Mako manual, and the Chameleon manual. You can customize Mako's -TemplateLookup by setting "mako.*" variables in the INI file. - -Site template -------------- - -Most applications using Mako will define a site template something like this: - -.. code-block:: mako - - <!DOCTYPE html> - <html> - <head> - <title>${self.title()} - - - - - - ${self.body()} - - - - <%def name="title()" /> - -Then the page templates can inherit it like so: - -.. code-block:: mako - - <%inherit file="/site.html" /> - <%def name="title()">My Title - ... rest of page content goes here ... - -Static files -============ - -Pyramid has five ways to serve static files. Each way has different -advantages and limitations, and requires a different way to generate static -URLs. - -``config.add_static_route`` - - This is the Akhet default, - and is closest to Pylons. It serves the static directory as an overlay on - "/", so that URL "/robots.txt" serves "zzz/static/robots.txt", and URL - "/images/logo.png" serves "zzz/static/images/logo.png". If the file does - not exist, the route will not match the URL and Pyramid will try the next - route or traversal. You cannot use any of the URL generation methods with - this; instead you can put a literal URL like - "${application_url}/images/logo.png" in your template. - - Usage:: - - config.include('akhet') - config.add_static_route('zzz', 'static', cache_max_age=3600) - # Arg 1 is the Python package containing the static files. - # Arg 2 is the subdirectory in the package containing the files. - -``config.add_static_view`` - - This is Pyramid's default algorithm. It mounts a static directory under a - URL prefix such as "/static". It is not an overlay; it takes over the URL - prefix completely. So URL "/static/images/logo.png" serves file - "zzz/static/images/logo.png". You cannot serve top-level static files like - "/robots.txt" and "/favicon.ico" using this method; you'll have to serve - them another way. - - Usage:: - - config.add_static_view("static", "zzz:static") - # Arg 1 is the view name which is also the URL prefix. - # It can also be the URL of an external static webserver. - # Arg 2 is an asset spec referring to the static directory/ - - To generate "/static/images/logo.png" in a Mako template, which will serve - "zzz/static/images/logo.png": - - .. code-block:: mako - - href="${request.static_url('zzz:static/images/logo.png')} - - One advantage of add_static_view is that you can copy the static directory - to an external static webserver in production, and static_url will - automatically generate the external URL: - - .. code-block:: ini - - # In INI file - static_assets = "static" - # -OR- - static_assets = "http://staticserver.com/" - - .. code-block:: python - - config.add_static_view(settings["static_assets"], "zzz:static") - - .. code-block:: mako - - href="${request.static_url('zzz:static/images/logo.png')}" - ## Generates URL "http://staticserver.com/static/images/logo.png" - -Other ways - - There are three other ways to serve static files. One is to write a custom - view callable to serve the file; an example is in the Static Assets section - of the Pyramid manual. Another is to use ``paste.fileapp.FileApp`` or - ``paste.fileapp.DirectoryApp`` in a view. (More recent versions are in the - "PasteOb" distribution.) These three ways can be used with - ``request.route_url()`` because the route is an ordinary route. The - advantage of these three ways is that they can serve a static file or - directory from a normal view callable, and the view can be protected - separately using the usual authorization mechanism. - -Session, flash messages, and secure forms -========================================= - -Pyramid's session object is ``request.session``. It has its own interface but -uses Beaker on the back end, and is configured in the INI file the same way as -Pylons' session. It's a dict-like object and can store any pickleable value. -It's pulled from persistent storage only if it's accessed during the request -processing, and it's (re)saved only if the data changes. - -Unlike Pylons' sesions, you don't have to call ``session.save()`` after adding -or replacing keys because Pyramid does that for you. But you do have to call -``session.changed()`` if you modify a mutable value in place (e.g., a session -value that's a list or dict) because Pyramid can't tell that child objects have -been modified. - -You can call ``session.invalidate()`` to discard the session data at the end of -the request. ``session.created`` is an integer timestamp in Unix ticks telling -when the session was created, and ``session.new`` is true if it was created -during this request (as opposed to being loaded from persistent storage). - -Pyramid sessions have two extra features: flash messages and a secure form -token. These replace ``webhelpers.pylonslib.flash`` and -``webhelpers.pylonslib.secure_form``, which are incompatible with Pyramid. - -Flash messages are a session-based queue. You can push a message to be -displayed on the next request, such as before redirecting. This is often used -after form submissions, to push a success or failure message before redirecting -to the record's main screen. (This is different from form validation, which -normally redisplays the form with error messages if the data is rejected.) - -To push a message, call ``request.session.flash("My message.")`` The message is -normally text but it can be any object. Your site template will then have to -call ``request.session.pop_flash()`` to retrieve the list of messages, and -display then as it wishes, perhaps in
's or a
    . The queue is -automatically cleared when the messages are popped, to ensure they are -displayed only once. - -The full signature for the flash method is:: - - session.flash(message, queue='', allow_duplicate=True) - -You can have as many message queues as you wish, each with a different string -name. You can use this to display warnings differently from errors, or to show -different kinds of messages at different places on the page. If -``allow_duplicate`` is false, the message will not be inserted if an identical -message already exists in that queue. The ``session.pop_flash`` method also takes a -queue argument to specify a queue. You can also use ``session.peek_flash`` to -look at the messages without deleting them from the queue. - -The secure form token prevents cross-site request forgery (CSRF) -attacts. Call ``session.get_csrf_token()`` to get the session's token, which is -a random string. (The first time it's called, it will create a new random token and -store it in the session. Thereafter it will return the same token.) Put the -token in a hidden form field. When the form submission comes back in the next -request, call ``session.get_csrf_token()`` again and compare it to the hidden -field's value; they should be the same. If the form data is missing the field -or the value is different, reject the request, perhaps by returning a forbidden -status. ``session.new_csrf_token()`` always returns a new token, overwriting -the previous one if it exists. - -WebHelpers and forms -==================== - -Most of WebHelpers works with Pyramid, including the popular -``webhelpers.html`` subpackage, ``webhelpers.text``, and ``webhelpers.number``. -Pyramid does not depend on WebHelpers so you'll have to add the dependency to -your application if you want to use it. The only part that doesn't work with -Pyramid is the ``webhelpers.pylonslib`` subpackage, which depends on Pylons' -special globals. - -We are working on a form demo that compares various form libraries: Deform, -Formish, FormEncode/htmlfill. - -To organize the form display-validate-action route, we recommend the -``pyramid_simpleform`` package. It replaces ``@validate`` in Pylons. It's not a -decorator because too many people found the decorator too inflexible: they -ended up copying part of the code into their action method. - -WebHelpers 1.3 has some new URL generator classes to make it easier to use -with Pyramid. See the ``webhelpers.paginate`` documentation for details. (Note: -this is *not* the same as Akhet's URL generator; it's a different kind of class -specifically for the paginator's needs.) - - -Shell -===== - -**paster pshell** is similar to Pylons' "paster shell". It gives you an -interactive shell in the application's namespace with a dummy request. Unlike -Pylons, you have to specify the application section on the command line because -it's not "main". Akhet, for convenience, names the section "myapp" regardless -of the actual application name. - -.. code-block:: sh - - $ paster pshell development.ini myapp - Python 2.6.6 (r266:84292, Sep 15 2010, 15:52:39) - [GCC 4.4.5] on linux2 - Type "help" for more information. "root" is the Pyramid app root object, "registry" is the Pyramid registry object. - >>> registry.settings["sqlalchemy.url"] - 'sqlite:////home/sluggo/exp/pyramid-docs/main/workspace/Zzz/db.sqlite' - >>> import pyramid.threadlocal - >>> request = pyramid.threadlocal.get_current_request() - >>> - -As the example above shows, the interactice namespace contains two objects -initially: ``root`` which is the root object, and ``registry`` from which you -can access the settings. To get the request, you have to use Pyramid's -threadlocal library to fetch it. This is one of the few places where it's -recommended to use the threadlocal library. - -Deployment -========== - -Deployment is the same for Pyramid as for Pylons. Use "paster serve" with -mod_proxy, or mod_wsgi, or whatever else you prefer. - - -.. _MultiDict API: api.html#multidict -.. _API: api.html -.. _pyramid_exclog: http://docs.pylonsproject.org/projects/pyramid_exclog/en/latest/