I’ve noticed a lot of people asking questions about SQLAlchemy in the #turbogears channel on Freenode and the TurboGears mailing list, and there aren’t that many step-by-step tutorials in using TG and SA so I decided to write one.
The tutorial will be published in two sections; Part 1 will cover the basics and part 2 will extend part 1 to cover a few of the trickier aspects of creating a project with SA.
Requirements:
- SQLAlchemy
- DocUtils
SimpleBlog will be a simple blogging app built from scratch showing off the power of SQLAlchemy. It is suggested that you go through the SQLAlchemy tutorial before reading this.
Throughout this tutorial we’ll be using assign_mapper which makes life easier when dealing with SA’s objects. I’ll try and highlight where the use of assign_mapper makes a difference, but the basic syntax for selecting objects stays the same.
We will also build SimpleBlog in a number of stages. At the beginning of each stage we will drop our database and re-initialise it as there will be schema changes to build our knowledge of SA in small steps.
Quickstart
First we need to ask TurboGears to quickstart the project for us.
shell> tg-admin quickstart --sqlalchemy -i
The above command has two extra arguments that you may not be familiar with:
--sqlalchemytells quickstart that you want to use SQLAlchemy for this project rather than the default, SQLObject.-itells quickstart that you want to use the Identity module.
The next step is to use your favourite editor to open model.py. As you can see, quickstart has already created the default SQLAlchemy (SA) tables, classes and mappings using a combination of plain SA and ActiveMapper.
For now, we’re going to ignore this code and build the other SA tables and objects we need for this project. We’ll come back to the identity code later.
The basic theory, simplified
SA requires slightly more lines of code than SQLObject to start with, but it’s a price that pays off in the long run due to SA’s flexibility and power.
Each database object created in SQLObject has three main ’stages’ in an equivalent SA model:
- Table creation
- Object creation
- Mapping of table to object
We’re now going to go through the stages with one simple table and expand on it throughout the tutorial.
Your first SA table
The first table we’re going to create will hold all the data for your blog posts.
First we need a new import to allow us to use datetime.now() easily:
from datetime import datetime
Next, the posts table:
post_table = Table("posts", metadata,
Column("id", Integer, primary_key=True),
Column("title", Unicode(50)),
Column("content", Unicode),
Column("post_date", DateTime, default=datetime.now()),
Column("is_published", Boolean, default=False)
)
As you can see, the table definitions are fairly simple. The first argument to Table() will be the name of the table in your SQL Database. The second, metadata, must be passed to Table for every use as it tells SA all sorts of information about your project when it’s running (you can see that metadata is imported from turbogears.database in your model.py).
Next come the Column definitions. Column() takes a minimum of two arguments:
- The name of the column.
- The type of the column.
Lets break down this table definition.
Column("id", Integer, primary_key=True): SQLObject creates its own Integer primary key when you create an object, but with SA you have to create it yourself. The advantage of this is that a table’s primary key does not have to be an integer, which is a point we’ll touch on later.Column("title", Unicode(50))andColumn("content", Unicode): Both columns are fairly self explanatory. The Unicode column type is very similar to SQLObject’sUnicodeCol. The text is encoded and decoded automatically by SA between the database and the results.Column("post_date", DateTime, default=datetime.now()): This column has an extra argument -default. This is, again, very similar to SQLObject and will run the method passed to it on object creation if no value has already been assigned to it.Column("is_published", Boolean, default=False): Given the descriptions above this is fairly self explanatory.
Great, we have our table defined. Now we create our object:
class Post(object):
pass
And we’re done. “Surely you’re missing something!” I can hear you cry. I’m happy to say, I’m not. For simple objects SA does all the grunt work for you using mappers.
Mappers allow you to map your table to an object for use in your projects. We’re going to use assign_mapper which is an extension to SA that allows a more SQLObject-like approach to selecting your data from the database.
First we need to add a new import for assign_mapper as it is not part of SA’s core:
from sqlalchemy.ext.assignmapper import assign_mapper
Once this is done you can now link the post table we created to the bare Post object:
assign_mapper(session.context, Post, post_table)
That’s it, we’re done. Your model.py should now look like this:
from datetime import datetime
from sqlalchemy import *
from sqlalchemy.ext.activemapper import ActiveMapper, column, \
one_to_many, one_to_one, many_to_many
from sqlalchemy.ext.assignmapper import assign_mapper
from turbogears import identity
from turbogears.database import metadata, session
from datetime import datetime
post_table = Table("posts", metadata,
Column("id", Integer, primary_key=True),
Column("title", Unicode(50)),
Column("content", Unicode),
Column("post_date", DateTime, default=datetime.now()),
Column("is_published", Boolean, default=False)
)
class Post(object):
pass
assign_mapper(session.context, Post, post_table)
# Identity details omitted for brevity.
...
To create the database tables you need to make sure your database configuration is present and correct in your dev.cfg (most of the time the default settings will be fine), then run tg-admin sql create.
WARNING: The 1.0b1 release of TG contains a bug in it’s SA quickstart code that will cause
tg-admin sql createto fail when using certain RDBMS’s.To fix this, find the line in the
VisitIdentityclass that definesvisit_key:
visit_key = column(String, # foreign_key="visit.visit_key",
and add
(40)to the String type:
visit_key = column(String(40), # foreign_key="visit.visit_key",
This bug has been fixed and will be available in the next TurboGears release.
Using the model
Now we’re going to write the controller code that will allow you to publish your blog posts. We need to import our model:
from simpleblog import model
Open controllers.py and replace the default index() method with the following:
@expose(template="simpleblog.templates.index")
def index(self):
posts = model.Post.select()
return dict(posts=posts)
This is where
assign_mappercomes into it’s own. Those of you familiar with SQLObject will note that the syntax looks identical for a simple select. The SQLAlchemy Tutorial shows that to get the same result without usingassign_mapperyou’d have to usesession.query(model.Post).select().
Now we have to create our template. Create index.kid in the simpleblog/templates directory and open in an editor.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#"
py:extends="'master.kid'">
<head>
<meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
<title>My Blog</title>
</head>
<body>
<div id="posts">
<div py:for="post in posts" id="post_${post.id}">
<h2 py:content="post.title">Post title here</h2>
<div class="postmeta">
Posted on <span class="postdate" py:content="post.post_date">01/01/01</span>
</div>
<div class="content" py:content="post.content">
This is where your post's content is displayed.
</div>
</div>
</div>
</body>
</html>
The template above is extremely simple but will suffice for the purposes of this tutorial.
You may realise that we don’t yet have any posts to display. Lets add one so we can take a look at our template at work. To add a post we’ll need to go into the tg-admin shell as Catwalk currently only supports SQLObject.
Once in the shell, we can add a post very easily:
>>> p = Post()
>>> p.title = "First post on simpleblog!"
>>> p.content = """This is the first post using simpleblog.
...
... And so we continue coding..."""
>>> p.flush()
Now, simply start the application (python start-simpleblog.py) and browse to http://localhost:8080/ to see the fruits of your labour.
Tweaking the objects
You may notice that we put a paragraph in the p.content field when we added the first post via the shell, but the newlines are not displayed properly when viewing the post on our new blog page. We’re going to remedy this now by adding a new property to our Post object.
We’re going to add a new property to our object that will format our plain text content field as html using docutils. You’ll need to import the function from docutils that we’re going to use in model.py:
from docutils.core import publish_parts
Then find our Post class as change it as follows:
class Post(object):
def _get_html_content(self):
return publish_parts(self.content,
writer_name="html")["html_body"]
html_content = property(_get_html_content)
Now, when we have a post object, we can access the html formatted version of the content! This will require a slight change to our template, changing:
<div class="content" py:content="post.content">
to:
<div class="content" py:content="XML(post.html_content)">
Lo, and behold! We now have HTML instead of plain text for our post!
We could have just used the
_get_html_content()function (renamed without the prepended_of course), but I personally prefer to use python’s built-inproperty()function as it saves me typing a pair of parentheses every time I use it.
So what have we achieved?
In this section of the series we have:
- Created a simple SQLAlchemy table and object, mapping them together
- Used the model in a simple controller and template
- Modified the Post object to automatically create HTML output of plain text using python’s
property()function.
This concludes Part 1 of the SimpleBlog series. Part 1 was simple so we all start on common ground for the upcoming sections. The next chapters of the series will become increasingly challenging and fun, illustrating the power and flexibility of SQLAlchemy.
See you next week!
stickystyle | 14-Oct-06 at 11:06 pm | Permalink
Thanks splee! Well written and helpful.
Hummm…and I was just asking you about SA on #turbogears last week ;-)
Joshua Conway | 15-Oct-06 at 1:50 am | Permalink
Thanks
This cleared up my questions about using sqlalchemy. I can’t wait to read part 2
Splee | 15-Oct-06 at 2:49 am | Permalink
Thanks for the comments. Sorry about the state of the post… there is something going on in that thar HTML that is screwing up the layout of the site.
I hope to have it fixed fairly soon.
Update: Fixed it. Something odd sneaked into my markdown document while I was pasting the text across.
Tom Lazar | 16-Oct-06 at 12:58 pm | Permalink
Hi Splee,
that’s a fine tutorial, well done — and keep it coming! You really seem to have come along rather nicely since ye ol’ days of CoreBlog[1] ;-) Remember that?
While I’m firmly planted in the Zope/Plone world, I find TG very interesting and tutorials like these let me keep up a bit with it…
‘See ya’,
Tom
[1] http://tomster.org/blog/archive/2005/01/25/quote-of-the-day255
Splee | 16-Oct-06 at 2:50 pm | Permalink
Hi Tom, good to see you have an interest in TG.
And wow… that’s a blast from the past. Looking back on it I’m quite impressed with my own analogy there. pats self on back. ;)
For the sake of posterity, here’s the link to the original post (the link on your page is one of the old CoreBlog permalinks I think).
http://www.splee.co.uk/2005/01/19/ms-anti-spyware-beta/
Chris | 17-Oct-06 at 5:24 pm | Permalink
Hi, thanks for the well written tutorial. I didn’t read the SQLAlchemy tutorial before, but with some SQLObject background it was easy enough to grasp what’s going on.
One thing though: the SQLAlchemy column types seem to be classes themselves, as opposed to SQLObject where you have a different column class for every column type. That was the main difference to SQLObject in the table definition that struck me, so it might be a good idea to add a claryfing comment on this to the resp. section of your tutorial. And do they support any other arguments except the column length? (Answer: depends. see http://www.sqlalchemy.org/docs/types.myt)
Splee | 18-Oct-06 at 4:29 pm | Permalink
@Chris: That’s a good point. It’s something that I didn’t put in the tutorial simply because it didn’t occur to me to point it out. When you use something every day you take some of the things for granted and they seem “obvious”.
Also, I’m trying to keep this as a step-by-step guide rather than a theory lesson. I personally don’t like theory lessons; getting something working is much more fun!
Once I’ve got it working I tend to pick it apart to see how it works and I’m sure many people are the same.
links for 2006-10-19 at Blue Sky On Mars | 19-Oct-06 at 10:25 am | Permalink
[...] splee.blog » Blog Archive » SimpleBlog - Part 1 (tags: turbogears sqlalchemy howto python programming web development) [...]
anthony baxter | 23-Oct-06 at 8:59 am | Permalink
Is that default=datetime.now() correct? Won’t that set the default value to all the same value, the ‘now()’ of when the table object was instantiated?
Michele | 11-Nov-06 at 2:05 am | Permalink
i have problems:
1. I don’t speak english very much
2. Yuor exaples return an error 500, the last line is:
SQLError: (OperationalError) no such table: visit ‘SELECT visit.visitkey AS visitvisitkey, visit.expiry AS visitexpiry, visit.created AS visitcreated \nFROM visit \nWHERE visit.visitkey = ? ORDER BY visit.oid’ ['67dd01e986baf263bc5475270b82d5efbc1d7968']
Thanks
Splee | 11-Nov-06 at 11:32 am | Permalink
@Michele: Did you remember to run
tg-admin sql createat the command line in your project’s root directory?Michele | 11-Nov-06 at 1:32 pm | Permalink
Yes, i don’t run it. Now it’s ok.
Thanks!
Joel | 04-Dec-06 at 9:29 pm | Permalink
nice. thanks very much.
BTW, there is an error in the index.kid content that you supplied in the article. There is a space between the ‘
Joel | 04-Dec-06 at 9:31 pm | Permalink
that didn’t work too well, let’s try this:
there is an error in the index.kid content that you supplied in the article. There is a space between the ‘<’ and the ‘!’ in ‘< !DOCTYPE html …’
Splee | 05-Dec-06 at 3:15 pm | Permalink
@Joel: Fixed. Thanks for pointing it out. :)
sjh | 11-Oct-07 at 10:54 am | Permalink
Hi, going to go through your tutorial, just thought i would not i had to modify the
>from sqlalchemy import (Table, Column, String …
line to include Boolean before tg-admin sql create would work ;)
Splee | 12-Oct-07 at 3:09 pm | Permalink
@sjh: in all my code
from sqlalchemy import *is used, so that isn’t a problem. If you’ve changed your import lines then you’ll have to modify your own code so all the relevant imports have been completed :)links for 2007-10-20 « PaxoBlog | 21-Oct-07 at 12:31 am | Permalink
[...] splee.blog :: SimpleBlog - Part 1 I’ve noticed a lot of people asking questions about SQLAlchemy in the #turbogears channel on Freenode and the TurboGears mailing list, and there aren’t that many step-by-step tutorials in using TG and SA so I decided to write one. (tags: sqlalchemy turbogears tutorial blog engine howto) [...]
Hristos | 01-Dec-07 at 7:54 am | Permalink
Cool.