| Author: | Dave Kuhlman |
|---|---|
| Address: | dkuhlman@rexx.com http://www.rexx.com/~dkuhlman |
| Revision: | 1.0a |
| Date: | Mar. 23, 2007 |
| Copyright: | Copyright (c) 2007 Dave Kuhlman. All Rights Reserved. This software is subject to the provisions of the MIT License http://www.opensource.org/licenses/mit-license.php. |
Abstract
This document attempts to help you to quickly build your first Pylons Web application.
Contents
There is good documentation at the Pylons Web site. In particular, you may want to work through the example applications at The Path to Mastery.
Follow the instructions at Installing Pylons.
Hopefully, you can install Pylons with the following:
$ easy_install Pylons
A small word of caution here -- When you use easy_install to install Pylons, a number of different Python packages will be installed on your machine. And, easy_install forces the path to the packages that it installs up higher in sys.path than other packages installed on your system. If you have a specially customized version of one of these packages, your Python will no longer find that version. I had this difficulty with Docutils, which contains an extra output writer ODF/ODT writer for Docutils which I wrote to produce files that can be loaded into OpenOffice Writer and which is not in the standard Docutils distribution. After installing Pylons, Python finds the version of Docutils installed by easy_install instead of the version in which the ODF/ODT writer is installed. If this is an issue for you, then you may have to use a sitecustomize.py module or other technique in order to use your version of that module from Python.
easy_install, in a sense, hijacks your Python environment.
If you are concerned about this, you may want to read my notes about easy_install and workingenv.py: sys.path, PYTHONPATH, site.py, contd..
Pylons allows you to choose which of several templating languages is used in your application. The default is Myghty. However, it appears that there is movement toward Mako. Also consider:
If you decide to use a templating language other than the default (currently Myghty), then see Template Language Plugins for instructions.
Also see the Pylons Wiki.
Installing Mako can done with:
$ easy_install Mako
Generate the application skeleton and files -- Python Paste does this for us. Paste was installed when you installed Pylons, if it was not already there.
Let's create an application called FirstApp, which will be the example used throughout this document. Use the following:
$ paster create --template=pylons FirstApp
Edit the configuration file -- FirstApp/development.ini. For example, you may want to change the port on which your application is served. In my case I changed the port to 5001:
[server:main]
o
o
o
port = 5001
Start your new application -- You should be able to start your server with the following:
$ cd FirstApp $ paster serve --reload development.ini
Notice the flag --reload. This flag will enable you to make changes in your application, and then to see those changes without having to re-start your server.
When you start the server, it might look something like this:
$ paster serve --reload development.ini Starting subprocess with file monitor /home/dkuhlman/a1/Python/Pylons/PylonsEnv/lib/python2.5/simplejson-1.4-py2.5.egg/simplejson/scanner.py:6: DeprecationWarning: The sre module is deprecated, please import re. from sre import VERBOSE, MULTILINE, DOTALL Starting server in PID 6419. serving on 0.0.0.0:5001 view at http://127.0.0.1:5001
Interact with your new application -- If, in development.ini you changed the port to 5001, for example, then you should be able to view a default page in your Web browser at http://localhost:5001/.
Now we will create a controller and map a URL to a method in the conroller, so that when that URL is requested, our method in the controller will be called. The controller tells our application what to do in response to each request. A controller is the module and class that handles requests from your clients.
Create the controller -- We will create a controller using Python Paste. Something like the following should do it:
$ cd FirstApp $ paster controller firstcontroller
This creates a new module firstapp/controllers/firstcontroller.py.
Map a request to the controller -- In order to do this, we will edit the file firstapp/config/routing.py. Add a line like the following:
map.connect('mapping1', '/firstapp',
controller='firstcontroller', action='index' )
This maps the URL http://localhost:5004/firstapp to the action/handler index.
See the Routes documentation and Adding More Complex Mappings below for more on this.
Create the handler (action) in the controller -- Our controller is firstcontroller and our action is index. So, in controllers/firstcontroller.py, we add a method named index. It might look something like this:
class FirstcontrollerController(BaseController):
def index(self):
return Response('<p>firstapp default</p>')
Now refresh the Web page http://localhost:5004/firstapp and you should see:
firstapp default
And if it did not work:
To do this, map the URL http://localhost:5004/firstapp/test1 to the action test1. In firstapp/config/routing.py, add:
map.connect('mapping2', '/firstapp/test1',
controller='firstcontroller', action='test1' )
In firstapp/controllers/firstcontroller.py, add:
def test1(self):
return render_response('/firstapp/test1.myt')
This tells our application to produce content by calling the action/handler test1 in our controller firstcontroller. And, that test1 method in turn, generates the content by processing the file firstapp/templates/firstapp/test1.myt with Myghty (since we have not changed the default template plugin).
Next, create firstapp/templates/firstapp/test1.myt, containing something like the following:
<html>
<head>
<title>Test #1</title>
</head>
<body>
<h1>Test #1</h1>
<%python>
items = ['one', 'two', 'three', 'four', ]
</%python>
<ol>
% for item in items:
<li>Item <% item.capitalize() %></li>
% # end for
</ol>
</body>
</html>
The above template contains Myghty code. For the present, it may be enough to understand:
See the Myghty Documentation for help with learning how to write this kind of content.
Although you can include Python code in your Myghty (or other) template, you will often want to place application logic in your controller, and then pass computed values to your template. Here's how. I'll map another URL to a new action as an example.
In firstapp/config/routing.py, add:
map.connect('mapping3', '/firstapp/test2',
controller='firstcontroller', action='test2' )
In firstapp/controllers/firstcontroller.py, add:
def test2(self):
random1 = random.randint(1, 10)
random2 = random.randint(1, 10)
random3 = random.randint(1, 10)
random4 = random.randint(1, 10)
c.random_values = [random1, random2, random3, random4, ]
return render_response('/firstapp/test2.myt')
Explanation:
And, now add a template to use those values, specifically, firstapp/templates/firstapp/test2.myt:
<html>
<head>
<title>Test #2</title>
</head>
<body>
<h1>Test #2</h1>
<ol>
% for item in c.random_values:
<li> <% item %></li>
% # end for
</ol>
</body>
</html>
Explanation:
The underlying technology used by Pylons to map URLs to actions is Routes. See the Routes Manual.
We have already seen how to add a simple mapping that routes a fixed URL to a specific action (method). In this section we will learn two techniques that make that mapping more flexible.
Here is an example.
First, a mapping in in firstapp/config/routing.py:
map.connect('mapping4', '/firstapp/test4/*category/help',
controller='firstcontroller', action='test4')
Next, the action/method in firstapp/controllers/firstcontroller.py:
def test4(self):
return render_response('/firstapp/test4.myt')
And, some trivial content in firstapp/templates/firstapp/test4.myt:
<html> <head> <title>Test #4</title> </head> <body> <h1>Test #4</h1> <p>Hello. You want help, right?</p> </body> </html>
Caution: Be aware that problems can occur when mixing wildcard parts with dynamic parts. See Wildcard Limitations and Gotchas for more on this.
A dynamic path is Routes terminology for the ability to assign a part of a path (in a URL) to a variable. Let's consider an example:
First, add the mapping in firstapp/config/routing.py:
map.connect('mapping3', '/firstapp/test3/:userid',
controller='firstcontroller', action='test3',
userid='[nobody]' )
Notice the colon in the path. The value received in that part of the path is assigned, in this case, to the variable userid. Also notice that the variable userid is given a default value "[nobody]".
Next, add the action in firstapp/controllers/firstcontroller.py:
def test3(self, userid):
c.userid = userid
return render_response('/firstapp/test3.myt')
And now, add some content in firstapp/templates/firstapp/test3.myt:
<html> <head> <title>Test #3</title> </head> <body> <h1>Test #3</h1> <p>Hello user <% c.userid %>.</p> </body> </html>
Notice that you can have more than one dynamic part in your URL specification. For example:
map.connect('mapping3', '/firstapp/test3/:userid/:username',
controller='firstcontroller', action='test3',
userid='[nobody]', username='[noname]' )
And, we can capture these values in our action with something like the following:
def test3(self, userid, username):
c.userid = userid
c.username = username
return render_response('/firstapp/test3.myt')
This section explains two methods of creating HTML forms for Pylons: (1) a simple, manual way and (2) the Web Helpers way.
First, we add a mapping to firstapp/config/routing.py:
map.connect('mapping5', '/firstapp/test5/:itemnumber/:color',
controller='firstcontroller', action='test5',
itemnumber='[void]', color='[default]')
Next, add an action in firstapp/controllers/firstcontroller.py:
def test5(self, itemnumber, color):
if 'itemnumber' in request.params and 'color' in request.params:
c.itemnumber = request.params['itemnumber']
c.color = request.params['color']
else:
c.itemnumber = itemnumber
c.color = color
return render_response('/firstapp/test5.myt')
Our action simply feeds values back to the form. If values have been submitted from the form, the action feeds those back, and if not, it feeds in values (possibly defaults from the mapping) from dynamic parts in the URL.
Now, the content, including the form in firstapp/templates/firstapp/test5.myt:
<html> <head> <title>Test #5</title> </head> <body> <form name="test5form" action="/firstapp/test5" method="PUT"> <h1>Test #5</h1> <p>Your current item number is <% c.itemnumber %> and your current color is <% c.color %>. </p> <p>Item number: <input type="text" name="itemnumber" value=<% c.itemnumber %> /> </p> <p>Color: <input type="text" name="color" value=<% c.color %> /> </p> <p> <input type="submit" name="submit" value="submit" /> </p> </form> </body> </html>
A few notes:
Here we use Webhelpers in creating our form. You can learn more about these and other helpers available in Pylons applications at Web Helpers.
Add the mapping to firstapp/config/routing.py:
map.connect('mapping6', '/firstapp/test6/:itemnumber/:color',
controller='firstcontroller', action='test6',
itemnumber='[void]', color='[default]')
Add an action in firstapp/controllers/firstcontroller.py:
def test6(self, itemnumber, color):
if 'itemnumber' in request.params and 'color' in request.params:
c.itemnumber = request.params['itemnumber']
c.color = request.params['color']
else:
c.itemnumber = itemnumber
c.color = color
return render_response('/firstapp/test6.myt')
It is almost the same as the previous example.
Add the content in firstapp/templates/firstapp/test6.myt:
<html>
<head>
<title>Test #6</title>
</head>
<body>
<h1>Test #6</h1>
<p>Your current item number is <% c.itemnumber %> and your
current color is <% c.color %>.
</p>
<% h.form(h.url(action='test6'), method='put') %>
<fieldset>
<table border="0" width="50%">
<tr>
<td width="50%"><label for="itemnumber">Item number:</label></td>
<td width="50%"><% h.text_field('itemnumber', value=c.itemnumber) %></td>
</tr>
<tr>
<td width="50%"><label for="color">Color:</label></td>
<td width="50%"><% h.text_field('color', value=c.color) %></td>
</tr>
<tr>
<td colspan="2" align="center"><% h.submit('Submit') %></td>
</tr>
</table>
</fieldset>
<% h.end_form() %>
</form>
</body>
</html>
Notes:
First read Configuration Files for information on working with Pylons configuration values.
Next, look at Paste Deployment and click on "Config Format" to learn about the format for Pylons configuration files.
When you start your application with something like:
$ paster serve --reload development.ini
you are telling the server to use development.ini as the configuration file.
When the request object is available, the configuration object may be obtained as follows:
config = request.environ['paste.config']
Where the request object is not available, you can get the config object with the following:
from paste.deploy import CONFIG config = CONFIG
The config object is a dictionary like object containing following keys:
So, for example, if I add the following under the [app:main] section of my configuration file:
decoration.title = A Pretty Heading
Then I can get the value of decoration.title with either:
from paste.deploy import CONFIG config = CONFIG app_conf = config['app_conf'] title = app_conf['decoration.title'] description = app_conf['decoration.description']
Or, when the request object is available:
config = request.environ['paste.config'] app_conf = config['app_conf'] title = app_conf['decoration.title'] description = app_conf['decoration.description']
You can also add values to the g globals variable. You do this in lib/app_globals.py. For example, to set a global value named level, add the following to the __init__ method in the Globals class:
self.level = 6
Then in your controller (wherever the g variable is available), you can access that value with, for example:
level = g.level
Or, in your template, with:
<p>My level is: <% g.level %></p>
We'll use SQLAlchemy, since that seems more in the future. For more information see The Python SQL Toolkit and Object Relational Mapper.
Uncomment and edit the SQLAlchemy line in FirstApp/development.ini. In my case, I have:
sqlalchemy.dburi = postgres://postgres:xxxx@localhost:5432/test sqlalchemy.echo_queries = true
Which specifies:
Now add the following to firstapp/lib/app_globals.py:
from firstapp.models import init_model
And, add the following to the __init__ method, also in firstapp/lib/app_globals.py:
init_model(app_conf)
This will allow us to make use of app_conf in models/__init__.py in order to get the value for the DSN that we defined in development.ini. In my case, that value is defined in development.ini by the line:
sqlalchemy.dburi = postgres://postgres:mypassword@localhost:5432/test
I'm using PostgreSQL on my machine. Here is what my test database looks like, as shown using the PostgreSQL utility psql:
test=# select * from plant_db; p_name | p_desc | p_rating ------------+------------------+---------- lemon | yello and tart | 5 sunflower | brite yellow | 3 tangerine | orange and sweet | 7 grapefruit | pink and sweet | 7 (4 rows)
So, in FirstApp/firstapp/lib/app_globals.py we add:
from firstapp.models import init_model
near the top. And, in the same file, we add:
init_model(app_conf)
in the __init__() method.
Next, we add the following to FirstApp/firstapp/models/__init__.py:
import sqlalchemy as sa
import sqlalchemy.orm as orm
class Plant(object):
def __repr__(self):
return "%s(%r,%r,%r)" % (
self.__class__.__name__,
self.p_name,
self.p_desc,
self.p_rating,
)
def init_model(app_conf):
global metadata, plant_table
if not globals().get('metadata'):
dsn = app_conf['sqlalchemy.dburi']
metadata = sa.BoundMetaData(dsn)
plant_table = sa.Table('plant_db', metadata, autoload=True)
orm.mapper(Plant, plant_table)
def create_all(app_conf):
init_model(app_conf)
metadata.create_all()
Notes:
To test all this, we'll add a new mapping for a new URL, as well as a new action and Web page (template).
Add the following to firstapp/config/routing.py:
map.connect('mapping7', '/firstapp/test7/:p_name',
controller='firstcontroller', action='test6',
p_name='weed')
The :p_name dynamic part of the URL will allow us to pass in a plant name.
The action (method) is where we will do the database work, for example, the database queries, the Add operation, the Delete operation, and the Update operation.
As a sample, add something like the following to firstapp/controllers/firstcontroller.py:
import sqlalchemy as sa
import sqlalchemy.orm as orm
from firstapp.models import Plant # Note 1 (see below)
class FirstcontrollerController(BaseController):
o
o
o
def test7(self, p_name):
if ('p_name' in request.params and # Note 2
'p_desc' in request.params and
'p_rating' in request.params):
c.p_name = request.params['p_name']
c.p_desc = request.params['p_desc']
c.p_rating = request.params['p_rating']
else:
c.p_name = p_name
c.p_desc = '[void]'
c.p_rating = '[void]'
dbsession = orm.create_session() # Note 3
if 'commit' in request.params:
if request.params['commit'] == 'Add': # Note 4
query = dbsession.query(Plant)
plant = query.get_by(p_name=c.p_name)
if plant is None:
plant = Plant()
plant.p_name = c.p_name
plant.p_desc = c.p_desc
plant.p_rating = c.p_rating
dbsession.save(plant)
dbsession.flush()
elif request.params['commit'] == 'Delete': # Note 5
query = dbsession.query(Plant)
plant = query.get_by(p_name=c.p_name)
if plant is not None:
dbsession.delete(plant)
dbsession.flush()
elif request.params['commit'] == 'Update': # Note 6
query = dbsession.query(Plant)
plant = query.get_by(p_name=c.p_name)
if plant is not None:
plant.p_desc = c.p_desc
plant.p_rating = c.p_rating
dbsession.update(plant)
dbsession.flush()
elif request.params['commit'] == 'Show':
pass
query = dbsession.query(Plant) # Note 7
c.plants = query.execute('select * from plant_db order by p_name')
dbsession.close()
return render_response('/firstapp/test7.myt')
Notes:
Here is a page (using the Myghty templating language) that displays the results of the database query and enables the user to add, delete, and update rows:
<html>
<head>
<title>Test #7</title>
</head>
<body>
<h1>Test #7 -- Plants Database</h1>
<p>Your current item number is <% c.itemnumber %> and your
current color is <% c.color %>.
</p>
<!-- Show the existing database.
-->
<table border="1" width="100%">
<tr>
<th>Name</th>
<th>Description</th>
<th>Rating</th>
</tr>
% for plant in c.plants:
<tr>
<td width="20%"><% plant.p_name %></td>
<td width="60%"><% plant.p_desc %></td>
<td width="20%"><% plant.p_rating %></td>
</tr>
% # end for
</table>
<!-- A form for database entries.
-->
<% h.form(h.url(action='test7'), method='put') %>
<fieldset>
<table border="1" width="50%">
<tr>
<td width="50%"><label for="p_name">Plant name:</label></td>
<td width="50%"><% h.text_field('p_name', value=c.p_name) %></td>
</tr>
<tr>
<td width="50%"><label for="p_desc">Description:</label></td>
<td width="50%"><% h.text_field('p_desc', value=c.p_desc) %></td>
</tr>
<tr>
<td width="50%"><label for="p_rating">Rating:</label></td>
<td width="50%"><% h.text_field('p_rating', value=c.p_rating) %></td>
</tr>
</table>
<table border="1" width="50%">
<tr>
<td align="center"><% h.submit('Add') %></td>
<td align="center"><% h.submit('Delete') %></td>
<td align="center"><% h.submit('Update') %></td>
<td align="center"><% h.submit('Show') %></td>
</tr>
</table>
</fieldset>
<% h.end_form() %>
</form>
</body>
</html>
This section attempts to give some help with structuring and organizing the code in your application. It's a "what goes where and why" sort of section.
The first thing to notice is that Pylons already gives quite a bit of guidance about structure. For example:
routing goes in myapp/config/routing.py.
Actions go in myapp/controllers/mycontroller.py where "mycontroller" is the argument you passed to paster. For example:
$ paster controller mycontroller
Templates go under myapp/templates.
What follows describes a few options and variations.
All in one controller -- Put all your code in one class in a single controller. Consider implementing extended behavior in helper methods (optionally named with a single leading underscore, which the Python style guide says is the convention for non-public members) or in functions outside the controller class.
One controller plus import -- Use a single controller, but put application logic in separate modules. Then, in your controller, import those modules and use the application code that is in classes or functions in those application modules. Consider putting those additional modules in myapp/lib or myapp/controllers or in a subdirectory that you create under one of those. If you create a subdirectory, be sure to add an __init__.py file in the subdirectory.
Multiple controllers -- Use Python Paste to generate multiple controllers, then use option 1 (all in one controller) or option 2 (one controller plus import) above in each of those modules.
If you need variables, functions, or classes for use in multiple templates, you can put definitions in lib/helpers.py. Those definitions will be available in your templates in the h variable. See the comment in lib/helpers.py, which reads as follows:
"All names available in this module will be available under the Pylons h object."
If you need global variables that live across multiple request/responses, you can assign values to the variable g. You can also initialize the variables in myapp/lib/app_globals.py.
Sessions in Pylons are reasonably easy. This section presents a quick and trivial example. The example initializes and increments a counter each time this page is visited. If you visit this page from two separate browsers or restart your browser, you should see two separate counts.
Documentation on the use of sessions in Pylons is available under Using Sessions.
For our example, we add the following to firstapp/config/routing.py:
map.connect('mapping8', '/firstapp/test8',
controller='firstcontroller', action='test8')
And, add an action handler something like the following to your controller, in our case controllers/firstcontroller.py:
def test8(self):
if 'count' in session:
count = session['count']
else:
count = 0
count += 1
session['count'] = count
session.save()
c.count = count
response = render_response('/firstapp/test8.myt')
return response
Then implement a template to show our counter and how it is incremented. Here is a sample templates/firstapp/test8.myt:
<html> <head> <title>Test #8</title> </head> <body> <p>Hello, user.</p> <p>Count: <% c.count %></p> </body> </html>
A few additional notes on sessions:
The session object is available in your controller. You imported it with:
from firstapp.lib.base import *
Treat the session as a dictionary. For example, use:
'mykey' in session
or:
session.has_key('mykey')
to check for the existence of a key.
Use, for example:
if 'address' in session:
address = session['address']
and:
session['address'] = address
to get and set a value in the session.
Be sure to save the session before rendering your page:
session.save()
The Pylons site provides a good first example of the use of Ajax in Pylons. See: Getting started with AJAX.
This section provides an additional Pylons/Ajax example. In this example, the Web page makes a call back into the server to retrieve a specific value, then sets the value of a form input element with that retrieved value.
First let's look at some JavaScript. Here is the event that triggers the call to a JavaScript function:
onClick="javascript:set_cell_location(<% idx1+1 %>, <% idx2+1 %>)">
The above code is on a cell (a <td> element) in a table of cells in a Myghty template. The cells are generated using the index variables idx1 and idx2, which correspond to the row and column within the table.
Now lets look at the JavaScript function that is executed when the user clicks on a cell in the table:
<script language ="JavaScript">
function set_cell_location(row, column) {
var el1;
el1 = document.getElementById("row_field");
el1.value = row;
el1 = document.getElementById("column_field");
el1.value = column;
el1 = document.getElementById("value_field");
set_cell_location1(row, column);
el1.focus();
}
function set_cell_location1(row, column) {
var url = '/sudoku/get_possibles';
var pars = 'row=' + row + '&column=' + column;
var myAjax = new Ajax.Request(
url,
{
method: 'get',
parameters: pars,
onComplete: set_cell_location2
});
}
function set_cell_location2(request) {
var xmlDoc = request.responseXML;
var possible = xmlDoc.getElementsByTagName("possible");
if (possible[0].childNodes.length > 0) {
possible = possible[0].childNodes[0].nodeValue;
} else {
possible = '';
} // if
el1 = document.getElementById("value_field");
el1.value = "";
el1.value = possible;
}
</script>
Notes and explanation:
When the user clicks on a cell, the JavaScript function set_cell_location is called. It receives the row and column as parameters.
set_cell_location sets the values of row_field and column_field to the row and column respectively, then calls set_cell_location1 to make our Ajax call.
set_cell_location1 makes the Ajax call. Note the URL and parameters. sudoku is my Pylons controller and get_possibles is the method in that controller (actually in the controller class) that we want to call. The parameters are formatted as URL-encoded arguments on the URL used for the request.
Note the onComplete option on the Ajax call. It specifies the the JavaScript call-back function that will get control when get_possibles (on the server) returns.
set_cell_location2, when it gets control, retrieves the XML that was returned, as a response, by get_possibles. It extracts a value from that chunk of XML and sets the value of an input field in the HTML form to that value. Note the use of:
var xmlDoc = request.responseXML;
to retrieve the XML from the server.
Now let's look at the get_possibles method on the server:
class SudokuController(BaseController):
o
o
o
def get_possibles(self, row='1', column='1'):
"""Ajax call-back.
Return XML containing row, column, and value/possible.
Only return value if there is a single possible.
"""
if 'row' in request.params:
row = request.params['row']
if 'column' in request.params:
column = request.params['column']
irow = int(row) - 1
icolumn = int(column) - 1
if 'table' in session:
table = session['table']
cell = table.get_cell(irow, icolumn)
possibles = cell.get_possibles()
possibles = list(possibles)
if len(possibles) == 1:
possibles = possibles[0]
else:
possibles = 'Select'
s1 = RESULT1 % (row, column, possibles, )
response = Response(s1)
response.headers['content-type'] = 'text/xml'
return response
RESULT1 = '''\
<result>
<row>%s</row>
<column>%s</column>
<possible>%s</possible>
</result>
'''
Notes and explanation:
Summary -- It's a little involved, but I think you can see a pattern here:
When your Pylons application on the server breaks (throws an exception), you will see a traceback in your Web browser window.
Here are a few things that you can do when you receive a traceback:
Notice that you can use something like the following to break your application at any point and get a traceback:
raise RuntimeError, 'breaking at ...'
Try adding the following in your controller/action:
import pdb; pdb.set_trace()
Type "help" at the debugger prompt to see a list of commands, then type "help" followed by a command name for documentation on that command. Return to and continue on responding to the request by pressing "c".
Learn more about pdb, the Python debugger at The Python Debugger.
IPython provides a more powerfull interactive prompt and a powerful embedded shell. If you are a Python programmer and have not yet tried IPython, you definitely should look into it.
First, import from IPython -- Add something like the following at the top of your controller module, in our case in firstapp/controllers/firstcontroller.py:
from IPython.Shell import IPShellEmbed
args = ['-pdb', '-pi1', 'In <\\#>: ', '-pi2', ' .\\D.: ',
'-po', 'Out<\\#>: ', '-nosep']
ipshell = IPShellEmbed(args,
banner = 'Entering IPython. Press Ctrl-D to exit.',
exit_msg = 'Leaving Interpreter, back to Pylons.')
Then, place this code in your action/method:
ipshell('We are at action abc')
Return to Pylons and continue on responding to the request by pressing Ctrl-D.
Note that because of some idiosyncratic feature of IPython.Shell.IPShellEmbed, I had to put the following before each call to ipshell():
ipshell.IP.exit_now = False
ipshell('We are at action abc')
Learn more about IPython at IPython: an Enhanced Python Shell.
When you are ready to go live, you will want to do (at least) two things:
Uncomment the following line in development.ini:
set debug = false
Omit the --reload option when you use Python Paste start your server. In other words, use:
$ paster serve development.ini
rather than:
$ paster serve --reload development.ini
But, there are other and better deployment options for Pylons applications. To learn about them, go to the Pylons Wiki at http://pylonshq.com/project/ and look for "Application Deployment".