pylons(包括TurboGears)实战札记

pylons(包括TurboGears)实战笔记pylons(包括TurboGears)实战笔记2011-1-5 星期三 阴天Why PylonsSmall and

pylons(包括TurboGears)实战笔记

pylons(包括TurboGears)实战笔记2011-1-5 星期三 阴天

Why Pylons

Small and Simple enough! Well documentation! Easy to migrate to TurboGear and, yes, Rails.

How

发现TurboGears的文档非常给力,基本上给出了从零搭建一个网站(一个TurboGears Project)的过程,而由于pylons和TG都是使用Paster,所以基本是一样的(至少在构建步骤和配置文件上)。
Quickstarting a TurboGears 2 projectpylons(包括TurboGears)实战札记(http://www.turbogears.org/2.0/docs/main/QuickStart.htmlpylons(包括TurboGears)实战札记)

具体如下:

0. 创建一个虚拟环境(推荐)
1. 创建项目结构——类似于antx gen

对于TG:

pylons(包括TurboGears)实战札记对于pylons,可以使用--list-templates查看可选的模板
package  paste_deploy:    A web application deployed through paste.deploy  pylons:          Pylons application template  pylons_minimal:  Pylons minimal application template
2. 引入项目需要的依赖——类似于antx或者maven
2.1 配置文件:对于pylons和TG,都是setup.py。(类似于antx的project.xml或者maven的pom.xml)

The setup.py and setup.cfg files control various aspects of how your Pylons application is packaged when you distribute it. They also contain metadata about the project.?
The setup.py file has a section which explicitly declares the dependencies of your application. The quickstart template has a few built in dependencies, and as you add new python libraries to your application's stack, you'll want to add them here too.

例如,如果你要使用FormBuild这个模块,那么可以在setup.py中作如下配置:
You should also add it as a dependency to your project by editing the setup.py file and adding FormBuild to the end of the install_requires argument:

"Pylons>=0.9.7",    "SQLAlchemy>=0.5,<=0.5.99",    "Mako",    "FormBuild>=2.0,<2.99",],
pylons(包括TurboGears)实战札记你当然可以直接使用
"SQLAlchemy>=0.5,<=0.5.99"
安装,但是不如同一个依赖管理,一键搭建轻松
2.2 安装依赖包:

对于pylons和TG都是一样的命令:
Then in order to make sure all those dependencies are installed you will want to run(Set up your project in development mode by entering this command):

pylons(包括TurboGears)实战札记paster会根据里面的依赖配置,执行类似如下命令:
"SQLAlchemy>=0.5,<=0.5.99"
3. 初始化web应用环境——给每个模块(依赖)一个执行初始化的时机(如SQLAlchemy),其实会执行每个module下的_init.py文件,如model/init.py_。pylons(包括TurboGears)实战札记python module
The model directory contains an _init_.py file which makes that directory name into a python module (so you can use the Python expression import model).

说明:这一步往往需要执行多次,每次做了什么修改,如数据模型修改,都可以跑一下。

Another key piece of TG2 application setup infrastructure is the paster setup-appcommand which takes a configuration file and runs websetup.py in that context. This allows you to use websetup.py to create database tables, pre-populate require data into your database, and otherwise make things nice for people fist setting up your app.
Note If it's the first time you're going to use the application, and you told quickstart to include authentication+authorizaiton, you will have to run setup-app to set it up (e.g., create a test database):

http://localhost:8080/pylons(包括TurboGears)实战札记?and you'll see a nice welcome page with the inform(flash) message and current time.

pylons(包括TurboGears)实战札记By default the paster serve command is not in auto-reload mode as the CherryPy server used to be. If you also want your application to auto-reload whenever you change a source code file just add the --reload option to paster serve:
pylons(包括TurboGears)实战札记You can easily edit development.ini to change the default server port used by the built-in web server:
pylons(包括TurboGears)实战札记You can access your models from within the python/ipython shell by typing:
SQLAlchemy 0.5.8 官方文档?pylons(包括TurboGears)实战札记(http://www.sqlalchemy.org/docs/05/reference/sqlalchemy/index.htmlpylons(包括TurboGears)实战札记)和pylonsbookpylons(包括TurboGears)实战札记(http://pylonsbook.com/en/1.1/introducing-the-model-and-sqlalchemy.htmlpylons(包括TurboGears)实战札记)的介绍比较给力。特别是后者,一个介绍web框架的文档,能从一个独立使用sqlalchemy的角度做了深入浅出的介绍,非常难得。

SQLAlchemy架构

pylons(包括TurboGears)实战札记

其中 The abstraction layer consists of the SQL Expression API and the Metadata and Type APIs, which help isolate your Python code from the details of the underlying database engine.

使用Engine API链接数据库进行操作

The lowest-level API you are likely to use is the Engine API. This represents a low-level abstraction of a database engine, allowing you to use the same API to create connections to different RDBMSs for sending SQL statements and for retrieving results.

engine_test.py
import create_engineengine = create_engine('sqlite:///:memory:')connection = engine.connect()connection.execute(            """            CREATE TABLE users (                    username VARCHAR PRIMARY KEY,                    password VARCHAR NOT NULL            );            """)connection.execute(            """            INSERT INTO users (username, password) VALUES (?, ?);            """,            "foo", "bar")result = connection.execute("select username from users")for row in result:    print "username:", row['username']connection.close()

To work with an engine, you need to have a connection to it. The connection in the example is an instance of a SQLAlchemy Connection object, and result is a SQLAlchemy ResultProxy object (very much like a DB-API cursor) that allows you to iterate over the results of the statement you have executed.
运行这个脚本,得到如下输出:

pylons(包括TurboGears)实战札记Different databases use different markers (known as param styles) to label where the variables you pass to execute() should be inserted. The example above used SQLite which uses ? as the param style but if you tried to use MySQL or PostgreSQL you would need to use %s as the param style instead. The SQL would then look like this:
"foo", "bar")

Using """ characters to mark the begining and end of the SQL allows you to use " and ' characters as part of the SQL and also allows you to add line breaks if your SQL statements get very long.

使用MetaData定义你的表结构metadata_test.py
import schema, typesmetadata = schema.MetaData()#To represent a Table, use the Table class:page_table = schema.Table('page', metadata,        schema.Column('id', types.Integer, primary_key=True),        schema.Column('name', types.Unicode(255), default=u''),        schema.Column('title', types.Unicode(255), default=u'Untitled Page'),        schema.Column('content', types.Text(), default=u''),)## MetaData object's methods:# getting a list of Tables in the order (or reverse) of their dependency:for t in metadata.sorted_tables:    print "Table name: ", t.name## Table' s methodsfor c in page_table.columns:    print "Column: ", c.name, c.typefor pk in page_table.primary_key:    print "primay_key: ", pkfor fk in page_table.foreign_keys:    print "foreign_keys: ", fk

Here you've created a metadata object from schema.MetaData, which will hold all the information about the tables, columns, types, foreign keys, indexes, and sequences that make up the database structure.
在虚拟环境中运行这个脚本,得到如下输入:

Integer()Column:  name Unicode(length=255)Column:  title Unicode(length=255)Column:  content Text(length=None, convert_unicode=False, assert_unicode=None)primay_key:  page.id

sqlalchemy基本上支持所有的数据库类型定义,还允许用户自定义数据库类型,具体参见官方文档Column and Data Typespylons(包括TurboGears)实战札记

邦定MetaData到引擎

At this stage, the metadata is just information; it doesn't relate to any properties of a real database. To connect the metadata to a real database, you need to bind the metadata object to an engine.

metadata_bind_test.py
import schema, typesfrom sqlalchemy.engine import create_enginemetadata = schema.MetaData()#To represent a Table, use the Table class:page_table = schema.Table('page', metadata,        schema.Column('id', types.Integer, primary_key=True),        schema.Column('name', types.Unicode(255), default=u''),        schema.Column('title', types.Unicode(255), default=u'Untitled Page'),        schema.Column('content', types.Text(), default=u''),)## MetaData object's methods:# getting a list of Tables in the order (or reverse) of their dependency:for t in metadata.sorted_tables:    print "Table name: ", t.name## Table' s methodsfor c in page_table.columns:    print "Column: ", c.name, c.typefor pk in page_table.primary_key:    print "primay_key: ", pkfor fk in page_table.foreign_keys:    print "foreign_keys: ", fk# 使用in-memory SQLite databaseengine = create_engine('sqlite:///:memory:')# 邦定metadata到数据库引擎,这样metadata就可以利用引擎真实操作数据库了(反过来,数据库引擎也可以利用metadata的信息作有意义的操作)metadata.bind = engine#如果metadata中定义的表还没有存在数据库中,可以使用metadata.create_all方法来自动创建他们#checkfirst=True表示create the table only if it doesn't already exist.metadata.create_all(checkfirst=True)
pylons(包括TurboGears)实战札记Tip It is worth being aware that you can have SQLAlchemy automatically convert all string types to handle Unicode automatically if you set up the engine like this:
create_engine('sqlite:///:memory:', convert_unicode=True)pylons(包括TurboGears)实战札记Reflecting Tables
反过来,如果数据库中定义的表还没有在metadata中声明,你也可以SQLAlchemy自动加载/反映(autoload=True, reflect)这些信息。需要注意的是此时的metadata必须已经邦定一个engine或者connection。
comment_table = schema.Table('comment', metadata, autoload=True)
具体参见官方文档Reflecting Tablespylons(包括TurboGears)实战札记
SQL Expression APIsqlexpression_test.py
import engine, page_tableprint "\nSQL Expression Example\n"connection = engine.connect()ins = page_table.insert(        values=dict(name=u'test', title=u'Test Page',  content=u'Some content!'))print insresult = connection.execute(ins)print "\nSelecting Results\n"from sqlalchemy.sql import selects = select([page_table])result = connection.execute(s)for row in result:    print rowprint "\nUpdating Results\n"from sqlalchemy import updateu = update(page_table, page_table.c.title==u'Test Page')connection.execute(u, title=u"New Title")print "\nAfter Update\n"s = select([page_table])result = connection.execute(s)print result.fetchall()print "\nDeleting Row\n"from sqlalchemy import deleted = delete(page_table, page_table.c.title==u'New Title')connection.execute(d)print "\nAfter Delete\n"s = select([page_table])result = connection.execute(s)print result.fetchall()connection.close()

运行,得到如下输出:

Integer()Column:  name Unicode(length=255)Column:  title Unicode(length=255)Column:  content Text(length=None, convert_unicode=False, assert_unicode=None)primay_key:  page.id2011-01-06 21:01:41,556 INFO sqlalchemy.engine.base.Engine.0x...31d0 PRAGMA table_info("page")2011-01-06 21:01:41,556 INFO sqlalchemy.engine.base.Engine.0x...31d0 ()2011-01-06 21:01:41,556 INFO sqlalchemy.engine.base.Engine.0x...31d0 CREATE TABLE page (id INTEGER NOT NULL, name VARCHAR(255), title VARCHAR(255), content TEXT, PRIMARY KEY (id))2011-01-06 21:01:41,556 INFO sqlalchemy.engine.base.Engine.0x...31d0 ()2011-01-06 21:01:41,557 INFO sqlalchemy.engine.base.Engine.0x...31d0 COMMITSQL Expression ExampleINSERT INTO page (name, title, content) VALUES (?, ?, ?)2011-01-06 21:01:41,557 INFO sqlalchemy.engine.base.Engine.0x...31d0 INSERT INTO page (name, title, content) VALUES (?, ?, ?)2011-01-06 21:01:41,557 INFO sqlalchemy.engine.base.Engine.0x...31d0 [u'test', u'Test Page', u'Some content!']2011-01-06 21:01:41,557 INFO sqlalchemy.engine.base.Engine.0x...31d0 COMMITSelecting Results2011-01-06 21:01:41,558 INFO sqlalchemy.engine.base.Engine.0x...31d0 SELECT page.id, page.name, page.title, page.content FROM page2011-01-06 21:01:41,558 INFO sqlalchemy.engine.base.Engine.0x...31d0 [](1, u'test', u'Test Page', u'Some content!')Updating Results2011-01-06 21:01:41,558 INFO sqlalchemy.engine.base.Engine.0x...31d0 UPDATE page SET title=? WHERE page.title = ?2011-01-06 21:01:41,558 INFO sqlalchemy.engine.base.Engine.0x...31d0 [u'New Title', u'Test Page']2011-01-06 21:01:41,558 INFO sqlalchemy.engine.base.Engine.0x...31d0 COMMITAfter Update2011-01-06 21:01:41,559 INFO sqlalchemy.engine.base.Engine.0x...31d0 SELECT page.id, page.name, page.title, page.content FROM page2011-01-06 21:01:41,559 INFO sqlalchemy.engine.base.Engine.0x...31d0 [][(1, u'test', u'New Title', u'Some content!')]Deleting Row2011-01-06 21:01:41,559 INFO sqlalchemy.engine.base.Engine.0x...31d0 DELETE FROM page WHERE page.title = ?2011-01-06 21:01:41,559 INFO sqlalchemy.engine.base.Engine.0x...31d0 [u'New Title']2011-01-06 21:01:41,559 INFO sqlalchemy.engine.base.Engine.0x...31d0 COMMITAfter Delete2011-01-06 21:01:41,559 INFO sqlalchemy.engine.base.Engine.0x...31d0 SELECT page.id, page.name, page.title, page.content FROM page2011-01-06 21:01:41,559 INFO sqlalchemy.engine.base.Engine.0x...31d0 [][]

可以看到,ins对象自动生成正确的插入SQL语句。实际上connection.execute()方法可以接受SQL语句,所以我们也可以这么写:

"INSERT INTO page (name, title, content ) VALUES ('%s', '%s', '%s')" % ("test", "Test Page", "Some Content")result = connection.execute(sql)connection.close()

在上面这个例子中,我们是手动打开关闭链接,实际上可以让metadata帮我们做这个事情:

ins = page_table.insert(    values=dict(na