Now that you have seen how to set up a Python environment and have used Easy Install to install Pylons and its dependencies, it is time to create your first Pylons project. In time-honored tradition, I’ll start with an example that outputs the words Hello world! to the web browser.
In this chapter, you’ll also learn the basics of the Hypertext Transfer Protocol, learn about Pylons’ request and response objects, and begin learning how Pylons works and about the other objects Pylons sets up for you.
As you learned in Chapter 1, Pylons is more like a collection of very carefully chosen separate components than a tightly integrated framework, but it can often be difficult to work out exactly how each of the components fits together. You’ll learn this over the course of the book, but let’s take a quick look at the packages that were installed along with Pylons so that you can get an idea of the big picture.
If you followed the installation instructions from the previous chapter, take a look at the lib/python2.5/site-packages directory in your virtual Python environment. You will see all of Pylons’ dependencies as well as the easy-install.pth and setuptools.pth files, which Easy Install uses to keep track of the installed eggs. If you’ve installed other packages too, you will also see them.
The following list contains the components relevant to Pylons 0.9.7. Of course, the version numbers might change slightly over time and future versions of Pylons might have slightly different dependencies, but the following list is correct at the time of this writing:
Paste comes in three packages for the benefit of framework developers who require only one part of its functionality. Pylons uses all three packages for a wide variety of things throughout the framework, but once again, as a Pylons application developer, you won’t normally directly interact with the Paste components yourself.
Over time, the functionality in the Paste modules has been split up into custom packages. For example, the paste.wsgiwrappers module, which provided the pylons.request and pylons.response objects in Pylons 0.9.6, is now replaced by WebOb, which provides the Pylons 0.9.7 versions of those Pylons objects. The paste.eval_exception module, which provided the 0.9.6 error handling, is replaced by WebError in Pylons 0.9.7, and even the paste.auth functionality has been built upon and improved in AuthKit, which you’ll learn about in Chapter 18. Don’t be surprised if future versions of Pylons include even more projects spun out from their roots in Paste.
Despite the gradual shift to separate packages, Pylons still relies on Paste for its configuration files, registry manager, development HTTP server, project template creation, test fixtures, error documents, and more. The various parts of Paste are described throughout the book as they are encountered.
You might have noticed that SQLAlchemy, a database toolkit you’ll learn about in Chapter 7, and AuthKit, a toolkit you’ll learn about in Chapter 18, are not included in the list of packages installed automatically with Pylons. This is because Pylons can be used perfectly well without them, and although most users will choose to install them, some developers will want to choose alternatives instead.
Installing Pylons also installs some scripts. If you look in your virtual Python environment’s bin directory (or the Scripts directory on Windows), you will see the following:
Your bin (or Scripts) directory might also see a file such as easy_install-2.5, which is simply a Python 2.5 version of the easy_install script, or a python2.5 script, which is a Python 2.5 version of the python script if you are using multiple versions of Python on your system. You should generally use the easy_install and python versions because they match the version of Python you used when you ran the python virtual_python.py env command in Chapter 2.
Don’t worry if you don’t understand everything I’ve mentioned in this section; it will all become clear as you get more familiar with Pylons.
Now that you’ve seen a quick overview of all the Pylons components, it’s time to create your first Pylons project.
Let’s get started by using Paste’s paster create command to automatically generate a Pylons project directory structure for you. You are completely free to create all the files and directories yourself if you prefer, but the Pylons project template provides a useful starting point that most people prefer to use.
Caution
In Pylons terminology, there are two different types of template, and it is important not to get confused between the two. View templates are text files that contain a mixture of Python and HTML and are used to generate HTML fragments to return to the browser as the view component of the MVC architecture. Any template written with Mako is a view template. Project templates are sets of files and directories that are used by the paster create command to generate a complete project directory structure to use as a basis for a new Pylons application.
Create a new project named HelloWorld based on the Pylons default project template with this command:
$ paster create --template=pylons HelloWorld
The --template option tells the paster create command which project template to use to create the HelloWorld project. You will also see examples using -t instead of --template, but both have the same effect:
$ paster create -t pylons HelloWorld
Note
If you have problems running the previous paster create command, it is likely you have forgotten to include the full path to the paster script (or the paster.exe executable in the case of Windows). You might see a message such as paster: command not found or 'paster' is not recognized as an internal or external command, operable program or batch file.
Remember that if you are using a virtual Python environment, you will need to type the full path to the executable (for example, env/bin/paster or C:\env\Scritps\paster) or modify your PATH as described in Chapter 2, either directly or by using the activate or activate.bat script. The examples in this book assume you have read Chapter 2 and will type whatever is appropriate for your installation.
The project template used in the previous paster create command is called pylons, which is the default for Pylons. You can always see which project templates are available with the --list-templates option shown here, but bear in mind that because Paste is a general-purpose library, not all the templates available will be for generating Pylons projects. In Chapter 19, you’ll learn how to create your own Pylons project template to add to this list:
$ paster create --list-templates
Available templates:
basic_package: A basic setuptools-enabled package
paste_deploy: A web application deployed through paste.deploy
pylons: Pylons application template
pylons_minimal: Pylons minimal application template
Advanced users might like to experiment with the pylons_minimal application template, which leaves you to do more configuration yourself.
Now let’s return to the HelloWorld example. Once you have run the paster create --template=pylons HelloWorld command, you will be asked some questions about how you want your project set up. You can choose which view template language you want to use for the project and whether you want SQLAlchemy support built in. For this example, choose the defaults of Mako for the view template language and no SQLAlchemy support since you haven’t installed SQLAlchemy yet anyway. Just press Enter at each of the prompts, and the script will quickly generate your new project.
Now that you have a sample project application, it is a good idea to run it with a web server to see what the default project provides; however, before you can serve the Pylons application you’ve just created, you need to learn about configuration files.
Configuration files enable you to set up the same Pylons application on different servers with different settings and for different purposes without having to change the source code for the project.
The project template you have used creates two configuration files for you in the HelloWorld directory, one named development.ini and one named test.ini. The development.ini file contains settings to run the Pylons application in a development environment such as your local workstation, whereas the test.ini file contains the settings you want to use when testing the application. You create a new configuration file called production.ini when you are ready to serve the application file in production.
The following code is the top part of the development.ini file generated as part of the application template. There is also some logging configuration, which you’ll learn about in Chapter 20.
#
# helloworld - Pylons development environment configuration
#
# The %(here)s variable will be replaced with the parent directory of this file
#
[DEFAULT]
debug = true
# Uncomment and replace with the address which should receive any error reports
#email_to = you@yourdomain.com
smtp_server = localhost
error_email_from = paste@localhost
[server:main]
use = egg:Paste#http
host = 127.0.0.1
port = 5000
[app:main]
use = egg:helloworld
full_stack = true
cache_dir = %(here)s/data
beaker.session.key = helloworld
beaker.session.secret = somesecret
# If you'd like to fine-tune the individual locations of the cache data dirs
# for the Cache data, or the Session saves, un-comment the desired settings
# here:
#beaker.cache.data_dir = %(here)s/data/cache
#beaker.session.data_dir = %(here)s/data/sessions
# WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT*
# Debug mode will enable the interactive debugging tool, allowing ANYONE to
# execute malicious code after an exception is raised.
#set debug = false
As you can see, the configuration file is in three parts. The [DEFAULT] section contains global configuration options that can be overridden in other sections. The [server:main] part contains information for the server used to serve the Pylons application, and the [app:main] section contains configuration options for the Pylons application. All the option values can contain the string %(here)s, which gets replaced with the location of the config file, enabling you to easily specify relative paths.
Let’s discuss the options available:
To run your application for development purposes, it is recommended you use the Paste HTTP server from the Paste package that was installed as one of Pylons’ dependencies. The Paste HTTP server does for Pylons applications what Apache does for PHP and other languages; it listens for HTTP requests and dispatches them to the running application, returning the result via HTTP to the user’s browser.
The Paste HTTP server has two features that make it more suitable for Pylons development than most web servers:
- It can be made to automatically reload when you change the source code.
- It understands the configuration files used by Pylons, so they can be used directly. As you’ll see in Chapter 19, other servers require the assistance of the Paste Deploy package to turn a Pylons config file into an application.
You can start the server with your development configuration with this command:
$ cd HelloWorld
$ paster serve --reload development.ini
Starting subprocess with file monitor
Starting server in PID 17586.
serving on 127.0.0.1:5000 view at http://127.0.0.1:5000
If you are Windows user, you may be prompted to unblock Python on port 5000 depending on your firewall settings.
The --reload option puts the Paste HTTP server into a very useful mode where the server carefully monitors all Python modules used by your application as well as the development.ini configuration file. If any of them change, the server is automatically reloaded so that you can immediately test your changes. This is useful during development because it saves you from having to manually stop and start the server every time you make a change.
To stop the server, you can press Ctrl+C (or Ctrl+D if you’re running Windows), but don’t stop it yet. If you visit http://127.0.0.1:5000/ in your web browser when the server is running, you will see the welcome page shown in Figure 3-1.
Now that the server is running, try creating a new file named hello.html in the HelloWorld/helloworld/public directory with the following content:
<html>
<body>
Hello world!
</body>
</html>
If you visit http://127.0.0.1:5000/hello.html, you will see the Hello world! message.
A Pylons project’s public folder is a bit like an htdocs directory in Apache. Any files in the public directory are treated as static files and are served treating the URL as the path to the file to serve. If the URL represents a directory and that directory contains an index.html file, it will be served instead; however, for security reasons, Pylons does not provide a directory index facility, so if no index.html file is present in a directory, a “404 File Not Found” response is returned.
Pylons automatically provides E-Tag caching for static files in the public directory. This allows browsers that support E-Tag caching to check to see whether a file has changed since the last time they fetched it. If it hasn’t changed, the browser won’t download the file a second time.
The numbers 127.0.0.1 in the URL you have been using to test your HelloWorld application represent the IP address on which the Paste HTTP server is serving the Pylons application. The IP address 127.0.0.1 is a special IP address that references your own computer. This means your Pylons application can be accessed only from the computer on which you are running the Paste HTTP server. This is actually very important because, as you will learn in the next chapter, Pylons comes with a powerful interactive debugging tool that is enabled by default when using the development.ini configuration file. If people could access your running development instance and an error occurred, they might be able to use the interactive debugger to enter malicious commands, so this is why as of Pylons 0.9.7 the default configuration file instructs the Paste HTTP server to bind to 127.0.0.1 so it answers requests only from the local computer.
If you want to test how the application works from a different computer, you can change the IP address the Paste HTTP server uses by editing the host option in the [server:main] section of the development.ini file. You can also change the port on which the server runs by changing the port variable. If you don’t want :5000 in your URLs, you should set the server to run on port 80. Browsers will connect to port 80 by default for HTTP requests if no port is specified in the URL. Most production systems run on port 80 for this reason, but for development it is fine to run your application on port 5000.
The IP address 0.0.0.0 is also worth knowing about. Setting the host option to 0.0.0.0 will cause the Paste HTTP server to respond to requests on all IP addresses on your server. This is the setting normally used in production configurations when the Pylons interactive debugger has been disabled.
Servers frequently have hostnames mapped to particular IP addresses, and you can specify a server’s hostname instead of the IP address if you prefer. It is a convention on most platforms that the hostname localhost will refer to the IP address 127.0.0.1, so for the majority of the time, you can use localhost rather than the IP address 127.0.0.1. With the Paste HTTP server still running, try visiting http://localhost:5000/hello.html; chances are you will still see the same Hello World! message you did before.
Now that you’ve seen a simple Pylons application serving static files from the public directory, I’ll take the opportunity to show you the rest of the files and directories that the paster create command generated for you from the default pylons application template.
The main HelloWorld directory contains the following files and directories:
You may also notice a data directory that contains cached session and template information. It is created the first time you run a Pylons application that needs it to be present. The location of this directory can be configured with the cache_dir option you saw a moment ago when I discussed configuration file options.
Now let’s take a look at the main Pylons application directory. As mentioned a moment ago, this will have a name based on the package name, so it will be different in each Pylons project. In this case, it is called helloworld and contains the following files and directories:
It’s now time to learn how to generate the message dynamically using a Pylons controller. Controllers are the basic building blocks of Pylons applications. They contain all the programming logic and can be thought of as mini-applications. Controllers are implemented as Python classes. Each method of the class is known in Pylons as an action. On each request Pylons routes the HTTP information to a particular controller action based on the URL that was requested. The action should return a response, which Pylons passes back to the server and on to the browser.
Let’s see this in practice by creating a controller. Once again you can use a paster command to help you get started quickly:
$ paster controller hello
This command creates a skeleton controllers/hello.py file for you as well as a helloworld/tests/functional/test_hello.py file that is used for running some of the functional tests of the controller discussed in Chapter 12. If you are using the Subversion revision control system to manage the source files in a Pylons project, the paster controller command will also automatically add both files to your working copy.
Modify the index() action of the HelloController to look like this:
class HelloController(BaseController):
def index(self):
return 'Hello from the index() action!'
Let’s test this code. With the server still running, visit http://127.0.0.1:5000/hello/index, and you should be greeted with the Hello from the index() action! message.
Let’s look closely at that URL. If you have used ASP, PHP, or CGI scripts, you’ll know that the URL the user visits directly represents the path on the filesystem of the script the server should execute. Pylons is much more flexible than this; it uses a system called Routes to map sets of URLs to particular controllers. This means your URLs don’t always have to directly represent the controllers that handle them. By default, Routes is set up so that the first URL fragment after the hostname and port represents the controller and the second part represents the action. In this case, /hello/index means use the index action of the hello controller, and so the URL you just visited results in the index() method of HelloController being called to display the response.
One very common requirement is the ability to map the root URL http://localhost:5000/ to a controller action. After all, you wouldn’t want to be limited to having a static file as the root URL. To do this, you need to add a line to the routes configuration. Add a new line to the top of the main route map in helloworld/config/routing.py just after the # CUSTOM ROUTES HERE comment so that the routes are defined like this:
# CUSTOM ROUTES HERE
map.connect('/', controller='hello', action='index')
map.connect('/{controller}/{action}')
map.connect('/{controller}/{action}/{id}')
This tells Routes that the root URL / should be mapped to the index action of HelloController. Otherwise, Routes should look for URLs in the form /controller/action/, and /controller/action/id. This, along with other details of Routes, is described in detail in Chapter 9.
Since you have made changes to a Python file used by the project, you would need to restart the server for the changes to be picked up. Because you started the server with the --reload option, though, this will happen automatically.
If you visit http://127.0.0.1:5000/ again, you will notice that the static welcome page is still there. This is because Pylons looks in the public directory for files to serve before attempting to match a controller. Because the public/index.html file still exists, Pylons serves that file.
If you delete the public/index.html file, you should see the Hello from the index() action! message you were expecting.
At this point it is worth taking a step back from Pylons to understand what is actually going on to display the Hello from the index() action! message.
At its heart, web development is all about the Hypertext Transfer Protocol (HTTP). Any web page you visit that starts with http:// is using HTTP to communicate between the browser and the server. Other protocols are used on the Internet too, such as the File Transfer Protocol (FTP), but for creating data-driven web sites with Pylons, HTTP is the only one you need to understand.
Note
If you’re new to web development, it is important not to get confused between HTTP and HTML. HTTP is the protocol with which the browser communicates with a server, whereas HTML is a markup language used to create web pages.
To understand exactly what is going on when you create a web page, it is useful to be able to see the HTTP information being sent back and forth between a web browser and a Pylons application. One good tool for doing this is the LiveHTTPHeaders extension for the Firefox web browser, which you can download from http://livehttpheaders.mozdev.org/. Once you have installed it, you can select View -> Sidebar -> LiveHTTPHeaders from the menu to load the extension in the sidebar, and it will display all the HTTP information sent and received on each request.
Tip
You can download the Firefox web browser from http://mozilla.com/products/firefox. It will run on the Windows, Mac OS X, Linux, and BSD platforms. It is particularly useful for web development because of the extensions available that give you fuller access to the processes going on within the web browser.
Another particularly useful extension is the Web Developer toolbar available from https://addons.mozilla.org/en-US/firefox/addon/60, which offers facilities for managing cookies and style sheets as well as for outlining block-level elements and tables.
Firefox also has powerful extensions such as Firebug, which in addition to its JavaScript and DOM manipulation facilities allows you to analyze page load times. I will discuss Firebug in Chapter 15 when I cover Ajax.
When you request a page, the browser sends an HTTP request to the server. When the server receives that request, it will calculate an HTTP response. Depending on the request, it may retrieve information from a database or read a file from the filesystem to prepare the response.
HTTP supports different types of requests. You are probably already familiar with the GET method used to retrieve information from a URL and the POST method used primarily to send form data, but there are other less well-known methods such as HEAD, OPTIONS, PUT, DELETE, TRACE, and CONNECT.
Figure 3-2 shows a simple HTTP GET request where you can see the HTTP information sent when visiting http://127.0.0.1:5000/.
Figure 3-3 shows the response returned.
As you can see, the browser sends quite a lot of information to the server. The application then processes this information and performs any necessary operations before returning a status and any HTTP headers it wants to send back. Of particular note is the Content-type header that tells the browser what sort of content is going to be sent (in this case text/html). Finally, the application returns the content that will be displayed by the browser (in this case the HTML that makes up the page). The server may add extra HTTP headers or perform other modifications before the response is returned.
In this example, the HTTP status code is 200, which means everything went fine and no error occurred. The application and server can use many other status codes to tell the browser what happened while processing the request. Table 3-1 describes some of the most commonly used codes.
Status Code | Description |
---|---|
200 OK | The request has succeeded. |
401 Unauthorized | The request requires user authentication. |
403 Forbidden | The server won’t let the user access what was requested, perhaps because the user doesn’t have the appropriate permissions. |
404 Not Found | The server has not found anything matching the request URI. |
500 Internal Server Error | The server encountered an unexpected condition that prevented it from fulfilling the request. |
Table 3-1. Commonly Used HTTP Status Codes
For the vast majority of web development situations, the areas of the protocol I have discussed are all you need to know, but if you are interested in the details of HTTP, you can find the full specification at http://www.w3.org/Protocols/rfc2616/rfc2616.txt, including an explanation of all the request methods and status codes.
Information about the HTTP request as well other information is presented to your Pylons application through environment variables. These are a set of dynamic values set by the web server on each request. Exactly which variables are set depends on the web server, the browser, and the action the user is trying to perform. Once Pylons receives these variables from the environment, they are passed to the Pylons request object for use in your controllers. You wouldn’t usually access environment variables directly, but it is useful to know where the Pylons request object gets its information from.
If you have ever written CGI scripts or used PHP, you will most likely be familiar with using environment variables already, in which case many of the variables mentioned in this section will be familiar. It is worth remembering, though, that Pylons controllers aren’t executed in the same way as CGI or PHP scripts, so consequently some of these variables have slightly different meanings in the Pylons context.
The following variable is set for all requests and are not request specific:
The following are specific to the request:
In addition to these environment variables, any HTTP headers that are sent in the request and not dealt with by previous environment variables are also included after being prefixed with HTTP_ and having any - characters replaced with _ characters. Since these are set by the user’s browser, they warrant more suspicion than the previous environment variables. Here are some of the more familiar ones as examples:
Although you would normally access these variables through the more convenient request object, Pylons is all about giving power to the developer, so you can still access environment variables directly in your controllers if you choose. They are available as the request.environ dictionary. You’ll see how to use the request object in a few moments.
In addition to the CGI-style variables listed earlier, the server running your Pylons application also adds extra information to the environment called WSGI variables, which can sometimes be useful to know about for use in your Pylons application.
Tip
WSGI stands for the Web Server Gateway Interface, and although you don’t need to know anything about it to develop Pylons applications, it is actually a very powerful API on which much of Pylons is based, so I’ll cover it in detail in Chapters 16 and 17.
Here are the WSGI variables you will also find in the Pylons request.environ dictionary:
Once again, you will rarely need to access WSGI variables directly because the components in Pylons that add them also present a clean API for their use, but it is useful to know they are there.
To see all the environment variables that Pylons sets up for you, add another action called environ() to the HelloController you created earlier so that it looks like this:
class HelloController(BaseController):
def index(self):
return 'Hello from the index() action!'
def environ(self):
result = '<html><body><h1>Environ</h1>'
for key, value in request.environ.items():
result += '%s: %r <br />'%(key, value)
result += '</body></html>'
return result
If you visit http://127.0.0.1:5000/hello/environ, you will see all the keys and values displayed, including the WSGI variables and the traditional CGI-style environment variables.
Now that you have learned about the fundamentals of HTTP and the environment, let’s return to learning about Pylons, specifically, the request and response objects.
The request and response objects together represent all the information Pylons receives about the HTTP request and all the information Pylons is going to send in the response. Let’s start by looking at the request object.
The Pylons request object is actually a subclass of the webob.Request class provided by the WebOb package, which was automatically installed as one of Pylons’ dependencies. A new instance of the class is created on each request based on the HTTP information Pylons receives via the environment. The object is available as the pylons.request object and is automatically imported at the top of any controllers you create with the paster controller command.
You can look at the API reference on the WebOb web site at http://pythonpaste.org/webob/ for full details of the Webob.Request object’s API and on the Pylons web site at http://docs.pylonshq.com/modules/controllers_util/#pylons.controllers.util.Request for details of the subclass, but there are a few methods and attributes that are particularly worth mentioning now:
The request object also has attributes for most of the common HTTP request headers, such as request.accept_language, request.content_length, and request.user_agent. These properties expose the parsed form of each header for whatever parsing makes sense. For instance, request.if_modified_since returns a datetime object (or None if the header was not provided).
Although the request object has plenty of useful attributes and methods, the one you are likely to use the most is request.params. This contains a MultiDict object representing all the GET and POST parameters of the request. Of course, you can access the GET and POST parameters separately via the request.GET and request.POST attributes, but most of the time you are unlikely to be trying to obtain GET and POST parameters at once, so you can just use request.params that combines the data from each.
Note
A MultiDict object is a dictionary-like object defined in the WebOb package that allows multiple values with the same key.
The request.params object can be treated in a number of ways. Imagine you’ve visited the URL http://localhost:5000/hello/index?a=1&a=2; you could then use the request.params object in the following ways:
>>> request.params
MultiDict([('a', '1'), ('a', '2')])
>>> request.params['a']
'1'
>>> request.params.get('a', 'Not present')
'1'
>>> request.params.get('b', 'Not present')
'Not present'
>>> request.params.getall('a')
['1','2']
Caution
If you are used to programming with Python’s cgi module, the way request.params works might surprise you because if the request has two parameters with the same name, as is the case with our example variable a, using request.params['a'] and request.get('a') returns only the first value rather than returning a list. Also, the methods described return the actual value, not an object whose .value attribute contains the value of the parameter.
There is also one other method, request.params.getone(), which returns just one value for the parameter it is getting. In this case, calling request.params.getone('a') raises an error because there is more than one value for a:
>>> request.params.getone('a')
...
raise KeyError('Multiple values match %r: %r' % (key, v))
KeyError: "Multiple values match 'a': ['1', '2']"
To avoid problems obtaining just one value for a parameter when you expected many or obtaining many when you expected just one, you are encouraged to use the request.params.getone() and request.params.getall() methods in your own code rather than the dictionary-like interface.
Now that you’ve looked in detail at the request object, you can turn your attention to the response object. You have actually been implicitly using the response object already if you’ve been following the examples in this chapter. Any time you return a string from a controller action, Pylons automatically writes it to the response object for you. Pylons then uses the response object to generate the HTTP information it returns to the browser, so any changes you make to the response object affect the HTTP response returned. Let’s look at the response object in more detail.
The response object is also a subclass of webob.Response, and once again you can find full details on the WebOb web site and the Pylons documentation web site at the same URLs as mentioned for the request object, but there are some features worth drawing your attention to here.
The response object has three fundamental parts:
Everything else derives from this underlying state. Here are the highlights:
Looking at the HelloWorld example from earlier in the chapter, you might have noticed that although the Content-type header sent to the browser was text/html, the message you returned was actually plain text, so the Content-type header should have been set to text/plain. Now that you have learned about the response object, you can correct this. Update the index() action of the hello controller so that it uses this response:
def index(self):
response.content_type = 'text/plain'
return 'Hello from the index() action!'
The server will reload when you save the change, and you can test the example again by visiting http://localhost:5000/. This time, the browser treats the message as plain text instead of HTML. If you are using the Firefox browser, you may notice it uses a different font to display the message this time.
The request and response objects you learned about in the previous section are referred to as Pylons globals because Pylons takes great care behind the scenes to make sure they can be used throughout your application including in your project’s controllers and its templates. In this section, I’ll cover all the other Pylons globals you can use in your application. Let’s start by looking at the objects made available by default in your controllers.
If you look at the controllers/hello.py file in the HelloWorld project I’ve been using as an example, you will see the following imports at the top:
from pylons import request, response, session, tmpl_context as c
from pylons.controllers.util import abort, redirect_to
These lines are for importing the core Pylons globals, but in addition to these globals, there are also some other imports Pylons developers can add to their controllers to import optional Pylons globals:
import helloworld.lib.helpers as h
from helloworld.lib import app_globals
from pylons import config
You’ve already learned about the request and response globals, and I’ll cover h, app_globals, and the template context object c in detail in the following sections, so let’s just concentrate on session, abort, redirect_to, and config for the time being:
Occasionally you might want to immediately stop execution of the request and return a response bypassing the normal flow of execution of your application. You can do this with the abort() function, which is used like this:
def test_abort(self):
username = request.environ.get('REMOTE_USER')
if not username:
abort(401)
else:
return"Hello %s"%username
In this example, if no REMOTE_USER environment variable is set, it means that no user has signed in, so the request is immediately aborted and returns a 401 status code (the correct HTTP status code for when a user has not been authenticated and is therefore not authorized to see a particular resource).
The function also allows you to add some text to form part of the status as well as any extra headers that should be set. If you use a 300 status code, the detail option should be the location to redirect to, but you would be better using the redirect_to() function in such circumstances.
Internally, the abort() function uses WebOb HTTPExceptions, which you’ll learn about in Chapter 17.
This function takes the same arguments as the url_for() function you will learn about in the next section. It allows you to specify a URL to redirect to by stating the routing variables that you want the URL to produce once it is called.
It is also possible to specify the HTTP status code you want to use with the _code argument.
For example:
redirect_to(controller='hello', action='other_action', _code=303)
In Chapter 11, you will also learn about four more Pylons globals named translator, ungettext(), _(), and N_(), but these are too advanced for now.
Helper functions (or helpers as they are known) are a concept borrowed from Ruby on Rails and are simply useful functions that you will find yourself using over and over again to perform common tasks such as generating form fields or creating links. Helper functions are all kept in the lib/helpers.py module in your project directory structure, but you must manually import them into your controllers if you want to use them. Since helpers are used frequently, it is common to import them using the shortened module name h rather than the full name helpers to save on typing:
import helloworld.lib.helpers as h
One of the most useful helpers is h.url_for(), which is a function that comes from the Routes package to help you generate URLs. You’ve already seen how the Routes system maps a URL to a particular controller action. Well, the h.url_for() helper does the reverse, mapping a controller and action to a URL. For example, to generate a URL for the environ action of the hello controller, you would use the following code:
h.url_for(controller='hello', action='environ')
In Pylons it is generally considered bad practice to ever write a URL manually in your code because at some point in the future you might want to change your URL structure, or your Pylons application might be mounted at a different URL. By using h.url_for() to generate your URLs, you avoid these problems since the correct URL is always generated automatically.
The Routes system is actually extremely powerful, and you will see some of the details of the way it works in Chapter 9. One aspect worth mentioning now is that you can also specify extra variables in your route maps. For example, if you wanted URLs in the format /calendar/view/2007/06/15, you might set up a map that treats calendar as the controller and view as the action and then assigns 2007 to a variable year, 06 to a variable month, and 15 to a variable called day. When Pylons called your controller action, it would pass in year, month, and day as the parameters, so your action would look like this:
def view(self, year, month, day):
return "This is the page for %s/%s/%s"%(year, month, day)
In this way, the URL itself can contain important information about what the page should display, and this both saves you having to pass such variables around your application as hidden fields and means your URL structure much better matches what is actually going on in your application.
You can use many other useful helpers to make your programming easier, but one of the great things about Pylons is that you can easily add your own too. Adding a new helper to the h object is as simple as importing a new function into your project’s lib/helpers.py module or defining a new object in it. As an example, let’s refactor the code you wrote earlier to print the environment into a useful helper. At the end of your project’s lib/helpers.py file, add the following function:
def format_environ(environ):
result = []
keys = environ.keys()
keys.sort()
for key in keys:
result.append("%s: %r"%(key, environ[key]))
return '\n'.join(result)
Then after importing the helpers as h, you can update your action to look like this:
def environ(self):
response.content_type = 'text/plain'
return h.format_environ(request.environ)
Tip
In this case, the helper function needed access to the environment that formed part of the request, so if you are a keen object-oriented programmer and had wanted to refactor the environment formatting code, you might have been tempted to add a private method to the controller rather than create a helper function that needs the request information passed in. Generally speaking, it is a lot better to add useful code that you intend to use a lot as a simple helper object than to add methods to controller classes because then they can easily be accessed throughout your Pylons application rather than just in controller actions.
You’ll see more of the built-in Pylons helpers when I cover form handling in Chapter 6.
When you develop a real Pylons application, you will quickly find you need to pass request-specific information to different parts of your code. For example, you might need to set some variables to be used in a template or in a form validator.
Because Pylons is designed to work in a multithreaded environment, it is important that this information is passed around your application in a thread-safe way so that variables associated with one request don’t get confused with variables from any other requests that are being executed at the same time.
Pylons provides the context object tmpl_context for precisely this purpose, and again, since it is used so frequently, it is imported by default into your controllers as the c object. The c object is a StackedObjectProxy that always returns the correct data for the current request.
You can assign attributes to the c object like this, and they will be available throughout the application:
c.my_data = 'Important data'
You can choose any attribute name you want to assign variables to as long as they don’t start with the _ character and are a valid Python name. They will then be available throughout your templates and application code and will always contain the correct data for the thread that is handling a particular request.
As was mentioned in the discussion about helpers, the Routes system Pylons uses allows you to use parts of the URL as variables in your application. Since you frequently need to access these variables in templates and other areas of your code, Pylons automatically sets up the c object to have any of the Routes variables that your action specified attached to it. You could therefore modify the helpers example from earlier to look like this, and it would still work in the same way:
def view(self, year, month, day):
return "This is the page for %s/%s/%s"%(c.year, c.month, c.day)
There is one more important aspect to learn about the c variable that you might not expect at first. If you access an attribute that doesn’t exist, the value returned will be the empty string '' rather than an AttributeError being raised as you might expect. This enables you to write code such as this without needing to test each part of the statement to check that the attribute exists:
data = c.some_value or c.some_other_value or "Not specified"
This code will set data to be c.some_value if it exists and otherwise c.some_other_value if that exists; if neither exist, the value will be set to "Not specified".
This style of code can be useful occasionally, but if you are not used to writing code like this, I strongly recommend you stick to explicitly testing values of attributes you are not sure about to avoid the risk of errors. You can do this with the Python functions hasattr() and getattr(), which are used like this:
if hasattr(c, "foo"):
x = c.foo
else:
x = 'default'
y = getattr(c, "bar", "default")
To use the strict version of the c global, edit your project’s config/environment.py file, and add the following line just before the lines to customize your templating options:
config['pylons.strict_c'] = True
# Customize templating options via this variable
With the strict_c option enabled, c will raise an AttributeError as you would expect. It is strongly recommended you set the strict_c option if you are a Pylons beginner.
Caution
If you come across an error you can’t quite understand when performing some operation on an attribute of c, it is possible that you have forgotten to specify the attribute and that the empty string is being returned instead. Trying to perform an operation on the string when you expected it to be a different object may be what is causing the error.
Sometimes you might want information to be available to all controllers and not be reset on each request. For example, you might want to set up a database connection pool when the application is loaded rather than creating a connection on each request. You can achieve this with the app_globals object.
Note
In previous versions of Pylons, the app_globals global was simply named g. New Pylons applications should use the full name app_globals instead.
The app_globals variable is actually just an instance of your Globals class in your application’s lib/app_globals.py file. It gets set up in the config/environment.py file of your project.
Any attributes set to self in the __init__() method of the Globals class will be available as attributes of app_globals throughout your Pylons application. Any attributes set on app_globals during one request will remain changed for all subsequent requests, so you have to be very careful not to accidentally change any global data by mistake.
Here is a simple counter example that demonstrates how the app_globals object works. First modify your lib/app_globals.py Globals class so that the __init__.py method looks like this:
def __init__(self):
self.visits = 0
You will now be able to access the visits attribute as app_globals.visits in your controllers. First import the global from Pylons:
from pylons import app_globals
Next add a new action to the end of the HelloController:
def app_globals_test(self):
app_globals.visits += 1
return "You are visitor number %s." % app_globals.visits
If you restart the Paste HTTP server and visit http://localhost:5000/hello/app_globals_test, you should see the message You are visitor number 1. If you visit the page again, the message will be changed to You are visitor number 2. On each subsequent request, the counter will increase.
If you restart the server, you will see that the value of app_globals.visits is reset to 0 because a new instance of the helloworld.lib.app_globals.Globals class is created when the server starts.
Caution
Because the app_globals object is persistent across requests, it can be modified by any thread. This means it is possible that a value attached to app_globals might be different at the start of a request than at the end if another thread has changed its value in between. This doesn’t matter in our example, but it is something to be aware of if you aren’t used to working in multithreaded environments.
You configure Pylons applications in two places. The first is the configuration file you saw earlier in the chapter when you looked at the HelloWorld project’s development.ini file. The configuration file is where any per-instance configuration options should be put. For example, the port and hostname might vary between a production deployment of your Pylons application and a development installation, so these are options that are set in the configuration file.
Any configuration that should affect the whole application and should not be customizable on a per-instance basis is set in Python code in one of the files in your project’s config directory. These are the following:
Once you have configured your application, whether in the configuration file or the config/environment.py file, you will want to be able to access that configuration in your Pylons application. All configuration is contained in the config global, which can be imported into a controller like this: from pylons import config. The config object has a number of attributes representing different aspects of Pylons configuration. These are described in detail at http://docs.pylonshq.com/modules/configuration/, but these are two that you are likely to use frequently:
There are also attributes for the package name, default character set, paths to look for templates, and more. See the documentation for full details.
In addition to the Pylons globals I’ve described, there are a number of other useful objects that Pylons provides for you to use in your controllers. Each of these objects is described in detail in the Pylons module documentation, which you can browse online at http://docs.pylonshq.com/modules/, but they are worth mentioning here so that you are aware of them.
First let’s look at the available decorators:
Pylons also provides two different types of controllers:
Finally, it is worth mentioning three more objects you are likely to use a lot in your controllers, but each of these has their own chapter and will be dealt with later in the book:
In its simplest form, you can simply write log.error('Log this message') in a controller, and the message will be logged to the console of the Paste HTTP server. Logging is described in detail in Chapter 20.
This chapter covered quite a lot of ground, including using static files, understanding HTTP and the environment, using the request and response objects, and understanding the basics of Routes. In the next chapter, you’ll take a detailed look at how to track down problems when they occur and how to handle them so that the user is presented with an appropriate error message. After that, you’ll be ready to get properly stuck into Pylons development, so you’ll learn how to use templates before looking at how forms are handled.