Pylons 0.9.6 Cheat Sheet

This cheat sheet is a very condensed form of documentation for the Pylons web framework. It is no replacement for the official documentation and not useful for learning it. Merely to look up details.

This cheat sheet is outdated because there is a newer Pylons version available.

Links

Installation

  • Debian/Ubuntu
    • Install: aptitude install python-pylons (available in lenny/testing or sid/unstable but not Etch!)
    • Update: aptitude update && aptitude install python-pylons
  • Easy-Install:
    • Install: easy_install Pylons
    • Update: easy_install -U Pylons
  • virtualenv

Project (Paste)

  • Create new project
    • paster create -t pylons myapplicationsname
    • Remove the public/index.html welcome page
    • Customize the routing in config/routing.py (e.g. set '' to a start controller)
  • Update project to new Pylons version: paster create -t pylons myapplicationsname (from above the directory where your development.ini is located)
  • Serve application via HTTP: paster serve --reload development.ini (application runs at http://localhost:5000)
  • Interactive shell: paster shell (install ipython for additional convenience)
  • Gory details of Pylons execution

development.ini

  • [server:main] (Paste)
    • use: points to egg of Paste's web server
    • host: the IP address the web server listens on
    • port: the TCP port the web server listens on
  • [app:main] (Pylons)
    • use: points to your application
    • full_stack: enables (by default) the error and exeption handling
    • cache_dir: directory where cached HTML templates and cookie-sessions are saved

(%(here)s refers the project's root directory (where the development.ini lives))

  • Accessing settings from the [app:main] section:

    from pylons import config
    my_setting = config['foo.bar']

Directory structure

data/sessions
Cookie-based sessions are saved into files in this directory.

data/templates
Cached HTML files that are rendered from your template files are stored here.

development.ini
The startup configuration for Paste.

myapplication/config/
Global configuration files of your project. Used to define routes for URL dispatching, middleware (like for adding authentication) or the template system you prefer.

myapplication/controllers/
The location of the classes that contain your application logic. This code controls your application, renders templates, and queries the database.

myapplication/docs/
Put any documentation on the application here. Preferably in rest (restructured text) format.

myapplication/i18n/
Files that deal with localized message strings of your project. (i18n = internationalization)

myapplication/lib/
Contains files that set up a number of global variables and objects that you can use in your controllers. For example everything in lib/base.py is made available in every controller.

myapplication/model/
Here go your database models. They define the database schema and sets up ORM mappers.

myapplication/public/
Static files like images, CSS style sheets or Javascript files may go here.

myapplication/templates/
Your templates that render the actual HTML output are saved here.

myapplication/tests/
Every controller you create gets a counterpart to implement automated tests here.

README.txt
Some basic instructions that you can give to the admininstrator who is supposed to install your application.

ez_setup, myapplication.egg-info, setup.cfg, setup.py
Administrative files that are used to create an egg from your project that can be deployed on a web server then.

test.ini
Similar to the development.ini. This file is used when you want to run automated tests on your project.

Controllers

  • Create new controller: paster controller name-of-new-controller
  • Controller mycontroller is located in controllers/mycontroller.py as class MycontrollerController
  • The index method is called when no action is specified
  • All symbols from lib/base.py are imported
  • Routes parameters can be accepted as arguments:

    def index(self, id):
    return 'Your ID is ' + id

  • Other parameters are available through request.params['my_paramter']
  • Return values
    • A (unicode) string
    • Rending a Mako template: render('/mytemplate')
    • Sending a HTTP error code: abort(404)
    • An iterator (StreamingController example)
    • Redirecting to another URL: redirect_to(controller='start', action='about') or redirect_to('/start/about')
  • Modifying the reponse object (to be done before the action's return statement:

    response.headers['content-type'] = 'text/xml; charset=utf-8'
    response.set_cookie('sitelang', 'uk')
    response.status_code = 201

Global objects
Configuration data is available through the config object

  • Variables from the app:main section of the development.ini: config['app_conf'] dictionary
  • Variables from the [DEFAULT] section of the development.ini: config['global_conf'] dictionary
  • Absolute path to the ini file: config['__file__']
  • Name of the application: config['package'] or config['pylons.package']
  • Path to the current project: config['here']
  • The 'h' (webhelpers) object: config['pylons.h'] (set up in config/environment.py)
  • The 'g' (globals) object: config['pylons.g'] (set up in config/environment.py)
  • Path configuration (where controllers, templates and static files are located): config['pylons.paths'] (set up in config/environment.py)

Routes

  • Links
  • Defined in config/routing.py
  • Start controller (for path /) can be specified as map.connect('', controller='start')
  • map.connect('newstoday', controller='news') -> calls the ''class NewsController'' in the controllers/news.py file.
  • Additional arguments are passed to the controller's method as named arguments (e.g. def __index__(self, id, name, city):)

Mako (Templates)

  • Unicode
    • Start all template with: # -*- coding: utf-8 -*-
  • Comments start with '##' - those lines are not rendered
  • Commands (do not forget the trailing ':')
    • % for / % endfor
    • % if / % elif / % else / % endif
  • Printing variables
    • ${ c.variablename }
    • The closing bracket must not be used alone on a line

Sample base template (to be inherited)

# -*- coding: utf-8 -*-
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<title>...........</title>
${ h.stylesheet_link_tag( '/style1.css', '/style2.css') }
${ h.javascript_include_tag( 'jquery.js', 'jquery.debug.js') }
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
</head>
<body>
<h1>My application</h1>
${ next.body() }
</body>
</html>

Inheriting from base template

# -*- coding: utf-8 -*-
<%inherit file="master.mako"/>
<p>Hello World</p>

Database access

  • Documentation links
  • pylons.database and SAContext are deprecated!
  • See Using SQLAlchemy with Pylons.
  • development.ini:

    sqlalchemy.url = sqlite:///%(here)s/phonebook.db
    sqlalchemy.url = mysql://username:password@host:3306/database
    sqlalchemy.url = postgres://username:password@host:5432/database
    sqlalchemy.url = oracle://username:password@host:1521/sidname

  • Options
  • Defining tables and ORM-mapping in model/__init__.py
    • Simple mapping:

      import sqlalchemy as sql
      import sqlalchemy.orm as orm

      metadata = MetaData()

      # Define table
      my_table = sql.Table(
      'tablename', metadata,
      sql.Column('id', sql.Integer, primary_key=True), # gets a sequence/serial assigned
      sql.Column('othertable_id', sql.Integer, sql.ForeignKey("othertable.id")),
      sql.Column('name', sql.Unicode(80), nullable=False, unique=True)
      )

      # Define object class for ORM-mapping (every object matches one row)
      class MyModel(object):
      def __repr__(self):
      return "MyModel (%s)" % self.name

      orm.mapper(MyModel, my_table)

  • Test access to the mapped models comfortably using paster shell
  • Creating queries and getting results
    • Get one row with certain field content (raises an exception if there are more rows returned): model.Session.query(model.MyModel).filter_by(name='John').one()
    • Get an iterator of items matching certain criteria: model.Session.query(model.MyModel).filter(model.MyModel.name=='John')
    • Get an iterator of items having certain field content: model.Session.query(model.MyModel).filter_by(name='John')
    • Get an iterator of items selected by a custom SQL condition: model.Session.query(model.MyModel).filter_by("id<:value and name=:name").params(value=224, name='fred')
    • Get one row with primary key 42: model.Session.query(model.MyModel).get(42)
    • Get all rows: model.Session.query(model.MyModel).all()
    • Get the first row: model.Session.query(model.MyModel).first()
    • Get the single row (raises an exception if there are more rows returned): model.Session.query(model.MyModel).one()
    • Get a limited number of rows (as a list)
      • model.Session.query(model.MyModel).offset(50).limit(10).all()
      • model.Session.query(model.MyModel)[10:50].all()
    • Order rows
      • model.Session.query(model.MyModel).order_by(model.MyModel.age).all()
      • model.Session.query(model.MyModel).order_by(model.sql.desc(model.MyModel.age)).all()
      • model.Session.query(model.MyModel).order_by([model.MyModel.age, model.MyModel.city]).all()
    • Count rows in result: model.Session.query(model.MyModel).count()
    • Joins (beware of cartesian products): model.Session.query(model.Model1, model.Model2).all()
    • Use filter and filter_by for generative queries:

      query = model.Session.query(model.MyModel)
      query = query.filter_by(name='John', type=189)
      query = query.filter_by(city='Hamburg')
      results = query.all()

    • Logical operators
      • AND: and_(condition1, condition2, ...)
      • AND: (condition1 & condition2 & ...)
      • OR: or_(condition1, condition2, ...)
      • OR: (condition1 | condition2 | ...)
      • NOT: not_(condition)
      • NOT: ~(condition)
  • Query operators:
    • ==
    • <
      • >
      • <=
      • >=
      • startswith('foo')
      • endswith('.com')
      • like('%jean')
      • between(100,500)
      • in('A', 'CNAME', 'MX')
      • + (concatenation of strings)
      • op('...') (custom operator)
      • ==None (NULL comparison)
  • Query functions:
    •       Gen  eral syntax: func.FUNCTIONNAME(...)
    • e.g. func.count() or func.now()
  • Creating new objects:

    new_object = model.MyModel()
    new_object.name = 'Jane'
    new_object.city = 'Tokio'
    model.Session.save(new_object)
    model.Session.flush() # not needed if set autoflush=True
    model.Session.commit() # needed because SQLAlchemy 0.4 uses transactions everywhere

  • Altering objects:

    old_object = model.Session.query(model.MyModel).get(42)
    old_object.city = 'Paris'
    model.Session.flush() # not needed if set autoflush=True
    model.Session.commit() # needed because SQLAlchemy 0.4 uses transactions everywhere

  • Deleting objects (do not use del(...)):

    old_object = model.sac.query(model.MyModel).get(42)
    model.Session.delete(old_object)
    model.Session.flush() # not needed if set autoflush=True
    model.Session.commit() # needed because SQLAlchemy 0.4 uses transactions everywhere

  • Arbitrary selects (return what you query for instead of complete rows):

    query = model.sql.select([model.MyModel.some_column])
    result = model.Session.execute(query).fetchall() # or .fetchmany() or .fetchone()

  • Types
    • String(length=None) [better use Unicode instead]
    • Integer
    • SmallInteger
    • Numeric(precision=10, length=2)
    • Float(precision=10)
    • DateTime [corresponds to datetime.datetime]
    • Date [corresponds to datetime.date]
    • Time [corresponds to datetime.time]
    • Binary(length=None)
    • Boolean
    • Unicode(length=None)
    • PickleType

Cookie-based Sessions

  • Beaker home page
  • development.ini
    • beaker.session.key = myproject_session (name of the session cookie being sent to the browser)
    • beaker.session.secret = somesecret (random string that the cookie string sent to the user is signed with)
    • beaker.session.cookie_expires = True (whether the cookie is a session cookie that expires when the browser is closed)
    • beaker.session.timeout = ... (time in seconds until the session times out)
    • beaker.session.data_dir = ... (path where the 'sessions' directory is located storing the session data)
    • beaker.session.type = ... (Storage type for session information.)
      • dbm stores sessions in files on disk
      • file stores sessions in files on disk
      • memory stores sessions in RAM
      • ext:memcached stores sessions on memcached servers. Memcache servers are configured as beaker.session.url = server1, server2, ..
  • The session variable comes from pylons.session and is imported in your lib/base.py. It behaves like a dictionary.
  • Loading a value from a session:

    value = session['whatever']

  • Saving a value into the session:

    session['whatever'] = value
    session.save()

  • Deleting a key from the session:

    del session['whatever']
    session.save()

  • Clearing the session:

    session.clear() session.save()

Formencode

  • Documentation
  • Using formencode:
    • Define a validation schema
    • Decorate your controller's method/action (@formencode.validate(MySchema))
    • The validated parameter will be available in the self.form_result dictionary. Note: If the schema changes values (like if specifying if_empty) then this will not be visible in the request.params dictionary.
    • If the form that the user submitted does not validate then formencode will re-run the current action (or run the action that you specify as form) as in @formencode.validate(MySchema, form='anotheraction') and display error messages right above the input fields with a CSS class of '.error-message'
  • Example validation class:

    # My form schema
    class MySchema(formencode.Schema):
    allow_extra_fields = True
    filter_extra_fields = True
    days = formencode.validators.IntRange(min=0, max=365, not_empty=True)
    name = formencode.validators.MaxLength(50)

    # MySchema schema without the 'days' field
    class MySchema2(MySchema):
    days = None # (removes days field from the superclass)

    # MySchema schema with additional 'comment' field
    class MySchema3(MySchema):
    comment = String(min=20)

  • Subclass variables:
    • allow_extra_fields = False (whether to display an error if fields are found in the POST request that are not mentioned in this subclass)
    • filter_extra_fields = False (whether to remove fields that are not mentioned in this subclass) are missing in the POST request but defined in this subclass)
    • ignore_key_missing = False (whether to display an error if fields are missing in the POST request but defined in this subclass)
    • if_key_missing (fields that are missing in the POST request will get this value assigned)
  • (Fancy)Validator variables:
    • if_empty = False (default value in case of an empty field)
    • not_empty = False (whether the field is allowed to be left empty)
    • strip = False (whether the field will be stripped from whitespace)
    • if_invalid = ... (return this value if the user entered something invalid)
    • if_missing = ... (return this value if the field is missing from the POST request)

Javascript libraries

Webhelpers

  • The functions from webhelpers.rails are imported automatically
  • Controllers and templates can access the webhelpers module by the 'h' (helper) name
  • Links:

Internationalisation (i18n)

  • Install Babel:
    • Debian/Ubuntu: aptitude install python-pybabel (formerly accidentally named "python-babel" but was renamed later)
    • Other operatings systems: easy_install Babel
  • Wiki article
  • Edit your setup.py and enable the message_extractors section to parse gettext strings in templates.
  • Use the "_" function (an alias to the gettext function) everywhere you would use normal strings.
    • Controllers: _('Hello World')
    • Mako templates: ${ _('Hello World') }
  • First time:
    • Create an english pot template file in the i18n directory: python setup.py extract_messages
    • Create a po file for every language (here the language is 'es'): python setup.py init_catalog -l es
    • Edit the po file in i18n/es/LC_MESSAGES/*.po and add translated strings in the msgstr fields
    • Compile the po files into mo files: python setup.py compile_catalog
  • Update:
    • Overwrite the english pot template:: python setup.py extract_messages
    • Update the po files with new strings (does not overwrite the already translated strings): python setup.py update_catalog -l es
    • Compile the po files into mo files: python setup.py compile_catalog
  • Hint: paster serve --reload does not detect changes in i18n. You will have to restart the web server.
  • To set the gettext language matching the browser language add this to your lib/base.py:

    def __before__(self):
    user_agent_language = request.languages[0][0:2]
    set_lang(user_agent_language)

    request.languages is an array of preferred languages that the users sets in the browser. I.e. ['de', 'en', 'en-us'].

Unit Tests (Nose)

Community

  • IRC: #pylons @ irc.freenode.net