Read Sean

Read me, read Sean.
posts - 508, comments - 655, trackbacks - 9, articles - 4


周末花了一个下午和一个晚上把Scott Rosenberg的Dreaming in Code从头到尾看了一遍。最直接的感受是这本书把我之前很多记忆碎片串在了一起,成为一个生动而"完整"的故事,虽然书的内容本身组织的多少还是有点散。

dreaming_in_code.jpg

我本人对Chandler这个项目并不陌生:之前出于对Python/wxWidget和开源本身的兴趣,陆续用过几个0.x的版本,一开始并不是十分友好,性能上也有问题,甚至会莫名的吃掉我机器上的数百兆(或者上G?)空间。后来的版本在性能和可用性上确实提高了不少,但一直感觉这个项目缺少必要的、以及许多开源项目应有的momentum。Phillip J. Eby对Chandler开发人员不懂Python的批评,当时我的印象也很深。而项目中出现的人物,包括Mitchell Kapor、Ted Leung,也都在Chandler这个范畴之外follow过。其他细节包括:Chandler和Cosmo这两个名称的由来、Chandler项目组中女性成员相对高的比例、一些熟悉的人物及其观点(Alan Kay, Bill Joy, Frederick Brooks, Donald Knuth、Linus Torvalds, Ward Cunningham, Larry Wall, Guido van Rossum, Joel Spolsky, etc.)、一些公司的分分和和以及人员流动等等。感觉挺亲切的。

可能更重要、也更深刻的原因是:尽管书中一再提到"There's no such thing as a typical software project, every project is different",我仍然深深的感觉到,Chandler遇到的这些问题,其实也是我亲历的很多项目的种种问题的一个缩影。对这些问题,作者并没有给出解决方案,其实谁也没有标准答案。软件开发是一项非常具有挑战性的工作,也正是像我们这样有热情、有涵养的专业人士生存的空间和价值所在。

posted @ 2009-03-02 00:44 laogao 阅读(697) | 评论 (0)编辑 收藏


以下是一段视频,Ward Cunningham针对Debt Metaphor这个隐喻的由来和人们对它的一些误解进行了澄清:



我最感兴趣的是Burden这一段:Cunningham解释说,经常看到有些开发团队,他们快速的开发出软件产品推向市场,不断的往里面添加新的特性,在这个过程中,不断的学习,但从不把学到的东西、总结的经验教训应用回去,这就像是从银行借贷,但从不想着有一天需要偿还(是不是有点像是在说引发这次次贷危机的美国人的消费习惯和观念?),到最后,你所有的收入都将用于偿还利息,你的购买力也将降为零。映射回软件开发的语境,如果我们在一个较长的时间跨度内,开发一个软件,不断的增加feature,但从不回过头去整理和重构,把对这个软件和这些特性的新的理解和认知写回去,那么最终这个软件、这些feature将不再会有任何实际的价值,对它们做任何事,都将花费越来越多的时间和精力,开发进度也就因此下降为零。


posted @ 2009-02-22 17:50 laogao 阅读(379) | 评论 (0)编辑 收藏


经历了有史以来最忙碌的一周,当然要好好放松一下,除了听上乘的古典音乐,沏上一壶上等的乌龙细细品味,也是一种享受。乌龙茶和紫砂壶可是绝配,如果是安溪的铁观音,加上做工精良的宜兴紫砂壶,那滋味,唇齿留香,别提多惬意了。

好的紫砂壶是需要"养"的,今天专程去茶城败了一只回来,开始"喂"铁观音,哈哈。

DSC_1768s.JPG


posted @ 2009-02-21 22:49 laogao 阅读(580) | 评论 (5)编辑 收藏


先简单介绍一下问题的语境。

手头有个开发了3年的Spring+iBATIS应用(经典三层架构),最近尝试用Hibernate实现原有SQLMap版的部分CRUD操作。除开混合事务和其他一些底层配置细节(如TransactionAwareDataSource、禁用lazy-load等)之外,最大的一个"pattern-mismatch"是:Model层和持久层采用了dirty flag机制,即每次INSERT和UPDATE操作,都会根据每个字段的dirty与否决定是否参加INSERT/UPDATE,而这些dirty flag可以被外部重置,所以业务层的代码,经常可以看到类似这样的pattern:

1- new一个model类并setId() (或者在已有的model上重置dirty flag)
2- set需要update的字段(通常都只是部分字段)
3- 丢给DAO层去update

最终的效果是某张表某个ID的某条记录的某些字段被更新了,而其他字段不受影响,这就是我在标题中提到的所谓"暴力"update,虽不elegant,但却也简单实用,至少很"直接"。

为了让Hibernate版的DAO(默认除Trasient之外全体字段参加INSERT和UPDATE)继续支持这样的"use-pattern",除了按照Hibernate的习惯去配置映射和SessionFactory等之外,我们需要做一些额外的工作:

1- 在BO/Entity类上追加注解
@org.hibernate.annotations.Entity(dynamicInsert=true, dynamicUpdate=true)

2- 实现org.hibernate.Interceptor接口的findDirty()方法,Hibernate提供了一个EmptyInterceptor可以作为起点,方法签名如下:
public int[] findDirty(
    Object entity, 
    Serializable id, 
    Object[] currentState, 
    Object[] previousState, 
    String[] propertyNames, 
    Type[] types
);
返回的int[]包含所有应该被认为是dirty的字段索引,返回null表示默认处理,传入的entity是持久对象,字段列表会通过propertyNames参数传给你。

3- 注入到Spring的Application Context中,类似这样:
<bean id="findDirtyInterceptor" class="gao.sean.hybrid.interceptor.FindDirtyInterceptor"/>

<bean id="sessionFactory"
    class
="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    
    
<property name="entityInterceptor"><ref bean="findDirtyInterceptor"/></property>
    
</bean>

在这样的配置下,业务层的代码就可以继续"暴力"update了。

posted @ 2009-01-29 16:54 laogao 阅读(2831) | 评论 (4)编辑 收藏


如果你使用早前版本的Spring,又恰好采用了Annotation注解方式(而非传统XML方式)配置Hibernate对象关系映射,那么在通过org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean配置sessionFactory时,你一定对annotatedClasses、annotatedPackages有一种说不出的胸闷的感觉,如此以高配置性见长的Spring,怎么在这一个小小的环节上就不能做得再灵活些呢,一定要一个个手写Class路径么?

估计有不少人无奈选择了从AnnotationSessionFactoryBean继承一个自定义的子类,自己实现扫描逻辑,找出@Entity注解过的类清单配置进去。

Spring 2.5.6里有个不怎么起眼的改进,那就是在AnnotationSessionFactoryBean上增加了一个新的方法:
setPackagesToScan(String[] packagesToScan)

有了这个方法,我们不再需要自己动手去实现实体类的扫描了,直接在Spring配置文件中AnnotationSessionFactoryBean这个section上增加类似如下的一个property即可(假定你需要加载的实体类所在的包名match这个字符串"com.**.bo"):
<property name="packagesToScan" value="com.**.bo"/>

你也可以以清单的方式指定多于1条的匹配字串,如:
<property name="packagesToScan">
    
<list>
        
<value>com.abc.core.bo</value>
        
<value>com.abc.auditing.bo</value>
    
</list>
</property>

posted @ 2009-01-29 02:59 laogao 阅读(22711) | 评论 (2)编辑 收藏


Pylons是一个典型的MVC Web框架,在之前的几篇随笔中,我们一起了解了Pylons的安装、默认项目结构、Routes和controller("C")以及SQLAlchemy("M"),在这个系列的最后,我们一起来看看"V"。

在我们的controller代码中,每个action在return的时候,可以选择:
1- 直接return字符串
2- 通过render()函数将输出交给页面模板引擎处理
3- redirect_to()重定向到其他URL

直接return太简单,redirect_to也没有特别需要介绍的,重点看render()。如果你一直follow这个系列,那么在你的controllers/hello.py中,可以看到这样一行import:
from newapp.lib.base import BaseController, render

从lib.base引入了一个render函数,跟到lib/base代码里查看,我们会知道:
from pylons.templating import render_mako as render
其实我们用到的render()函数,是pylons.templating.render_mako的别名。

注: 这里假定你在paster create时选择了默认的mako,其他Pylons原生支持的页面模板引擎还有结构相对更层次化的Genshi和更接近Django实现的Jinja。

render_mako()函数签名如下:
render_mako(template_name, extra_vars=None, cache_key=None, cache_type=None, cache_expire=None)

最基本的用法是给出template文件名,然后通过extra_vars传入参数,Pylons默认查找页面模板文件是在项目的templates子目录,这个路径也可以在config/environment.py中修改。在Pylons中,被推荐的传参做法是使用tmpl_context,在生成controller的时候,已经自动import了tmpl_context并别名为c,这样,我们只需要在c上绑上我们需要传递给模板的数据,模板在解析时,也就能够通过c得到这些数据了。像这样:
c.name = u'Pylons n00b'
return render('hello.mako')

然后,在hello.mako中:
<h3>Hello <b>${c.name}</b>!</h3>

在模板代码中,有些Pylons系统变量/函数是可以直接访问的,包括:
tmpl_context (c) - 用于controller和template传递数据
config - 配置信息
app_globals (g) - 应用的全局变量
h - WebHelpers,包括h.form、h.link_to、h.url_for等等实用函数
request - 请求
response - 应答
session - 会话信息
translator、ungettext()、_()、N_() - 处理国际化

除了基本的${}变量替代语法之外,类似JSP,Mako还支持注释、if/else/for控制逻辑、代码片段、return、标签等,具体的可以扫一眼官方说明:
http://www.makotemplates.org/docs/syntax.html
很精简,也非常容易理解,这里就不详细说明了。

至此,我们已经了解了Pylons最核心的几个组件,足够我们搭建常规的Web应用了。其他值得大家继续挖掘的内容包括:国际化、表单验证(FormEncode)、用户验证和权限(AuthKit、repoze.who、repoze.what)、AJAX、Python 3.0、WSGI基础架构等。

本文是该系列最后一篇,希望通过简单的介绍和学习,大家能够喜欢并顺利的上手Pylons,也希望越来越多的人关注这个优秀的Python Web应用框架。

posted @ 2009-01-27 15:50 laogao 阅读(2213) | 评论 (0)编辑 收藏


在前面的4篇随笔中,我们简要的介绍了SQLAlchemy,不过SQLAlchemy如何被集成到Pylons应用中呢?

首先我们看一下自动生成代码中的model子目录,其中有两个文件__init__.py和meta.py,其中meta.py定义了engine、Session和metadata三个公用变量,而__init__.py提供了一个核心的init_model(engine)方法,该方法分别将数据库engine和经过sessionmaker和scoped_session包装的Session对象植入到meta中,像这样:
    sm = orm.sessionmaker(autoflush=True, autocommit=True, bind=engine)

    meta.engine 
= engine
    meta.Session 
= orm.scoped_session(sm)

这样一来,整个背后的"magic"就还剩下最后一块"拼图":谁来把engine初始化好并调用init_model方法呢?看看config/environment.py就清楚了:
 1 """Pylons environment configuration"""
 2 import os
 3 
 4 from mako.lookup import TemplateLookup
 5 from pylons.error import handle_mako_error
 6 from pylons import config
 7 from sqlalchemy import engine_from_config
 8 
 9 import newapp.lib.app_globals as app_globals
10 import newapp.lib.helpers
11 from newapp.config.routing import make_map
12 from newapp.model import init_model
13 
14 def load_environment(global_conf, app_conf):
15     """Configure the Pylons environment via the ``pylons.config``
16     object
17     """
18     # Pylons paths
19     root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
20     paths = dict(root=root,
21                  controllers=os.path.join(root, 'controllers'),
22                  static_files=os.path.join(root, 'public'),
23                  templates=[os.path.join(root, 'templates')])
24 
25     # Initialize config with the basic options
26     config.init_app(global_conf, app_conf, package='newapp', paths=paths)
27 
28     config['routes.map'= make_map()
29     config['pylons.app_globals'= app_globals.Globals()
30     config['pylons.h'= newapp.lib.helpers
31 
32     # Create the Mako TemplateLookup, with the default auto-escaping
33     config['pylons.app_globals'].mako_lookup = TemplateLookup(
34         directories=paths['templates'],
35         error_handler=handle_mako_error,
36         module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
37         input_encoding='utf-8', output_encoding='utf-8',
38         imports=['from webhelpers.html import escape'],
39         default_filters=['escape'])
40     
41     # Setup SQLAlchemy database engine
42     engine = engine_from_config(config, 'sqlalchemy.')
43     init_model(engine)
44     
45     # CONFIGURATION OPTIONS HERE (note: all config options will override
46     # any Pylons config options)

注意第7行的import和第42、43行代码,是不是豁然开朗?Pylons在初始化运行环境时,从config中读取sqlalchemy相关的配置信息,然后通过这些配置信息创建数据库engine,并调用init_model()方法初始化SQLAlchemy功能的核心对象:metadata和Session。有了meta.Session,我们就可以方便的在代码中执行对model层/数据库的访问了。

posted @ 2009-01-27 14:00 laogao 阅读(1179) | 评论 (0)编辑 收藏


接着前面的例子说,我们定义了book_table和author_table,接下来:
 1 class Book(object):
 2     def __init__(self, title):
 3         self.title = title
 4     def __repr__(self):
 5         return "<Book('%s')>" % self.title
 6 
 7 class Author(object):
 8     def __init__(self, name):
 9         self.name = name
10     def __repr__(self):
11         return "<Author('%s')>" % self.name

这里我们定义两个类,继承自object,类似JavaBeans或者POJO,这里的__init__方法和__repr__方法不是必须的,只是为了创建对象和输出对象内容比较方便。然后就可以用SQLAlchemy的mapper和sessionmaker来建立映射关系并处理持久和查询等操作:
 1 from sqlalchemy.orm import mapper,sessionmaker
 2 
 3 mapper(Book, book_table)
 4 mapper(Author, author_table)
 5 
 6 Session = sessionmaker(bind=engine)
 7 session = Session()
 8 
 9 gia = Book(u'Groovy in Action')
10 ag = Author(u'Andrew Glover')
11 
12 session.add(gia)
13 session.add(ag)
14 session.add_all([Book('Hibernate in Action'), Author('Gavin King')])
15 s_gia = session.query(Book).filter_by(title=u'Groovy in Action').first()
16 s_gia.title =u'Groovy in Action Updated'
17 
18 print "[DIRTY]", session.dirty
19
20 session.commit() # or session.rollback()

如果你用过Hibernate,那么这些代码对你来说,理解起来应该没有任何难度。

假如我告诉你,每次都要像这样先定义Table(schema),再定义class,然后用mapper建立对照,是不是有点那啥?SQLAlchemy的开发者们也意识到这一点,所以从0.5开始,SQLAlchemy可以通过sqlalchemy.ext.declarative支持我们实现更紧凑的model/schema定义:
 1 from sqlalchemy.schema import Table, Column, ForeignKey, Sequence
 2 from sqlalchemy.types import *
 3 from sqlalchemy.orm import relation
 4 from sqlalchemy.ext.declarative import declarative_base
 5 
 6 Base = declarative_base()
 7 metadata = Base.metadata
 8 
 9 bookauthor_table = Table('bookauthor', metadata,
10     Column('book_id', Integer, ForeignKey('book.id'), nullable=False),
11     Column('author_id', Integer, ForeignKey('author.id'), nullable=False),
12 )
13 
14 class Book(Base):
15     __tablename__ = 'book'
16     id = Column(Integer, Sequence('seq_pk'), primary_key=True)
17     title = Column(Unicode(255), nullable=False)
18     authors = relation('Author', secondary=bookauthor_table)
19 
20 
21 class Author(Base):
22     __tablename__ = 'author'
23     id = Column(Integer, Sequence('seq_pk'), primary_key=True)
24     name = Column(Unicode(255), nullable=False)
25     books = relation('Book', secondary=bookauthor_table)

这里我们用到了many-to-many关系,其他的常见用法还包括many-to-one、one-to-many、JOIN、子查询、EXISTS、Lazy/Eager Load、Cascade (all/delete/delete-orphan)等等,大家可以根据需要查阅官方文档。

posted @ 2009-01-27 12:52 laogao 阅读(1684) | 评论 (0)编辑 收藏


在介绍SQLAlchemy最核心最有价值的ORM部分之前,我们再简单过一遍SQLAlchemy提供的SQL Expression Language用法,就从最基本的CRUD来举例说明吧(接着上一篇的示例):

 1 from sqlalchemy import select,update,delete
 2 
 3 conn = engine.connect()
 4 book_ins = book_table.insert(values=dict(title=u'Groovy in Action'))
 5 author_ins = author_table.insert(values=dict(name=u'Andrew Glover'))
 6 conn.execute(book_ins)
 7 conn.execute(author_ins)
 8 book = conn.execute(select([book_table], book_table.c.title.like(u'Groovy%'))).fetchone()
 9 author = conn.execute(select([author_table])).fetchone()
10 bookauthor_ins = bookauthor_table.insert(values=dict(book_id=book[0],author_id=author[0]))
11 conn.execute(bookauthor_ins)
12 conn.execute(update(book_table,book_table.c.title==u'Groovy in Action'), title=u'Groovy in Action (中文版)')
13 conn.execute(delete(bookauthor_table))
14 conn.close()

简单说明一下代码逻辑:
首先从engine建立连接,然后做两个insert动作,分别insert一条book记录(title为'Groovy in Action')和一条author记录(name为'Andrew Glover'),这之后分别再做两次select,得到刚insert的这两条记录,其中book记录的select用到了过滤条件,相当于"WHERE book.title like 'Groovy%'",然后构建一条新的insert语句,用于insert一条bookauthor关系记录,接下来,做一次update,将book.title为'Groovy in Action'的更新为'Groovy in Action (中文版)',最后,在关闭连接之前,做一次delete,删除bookauthor中的记录。

在指定WHERE条件时,.c是.columns的简写,所以book_table.c.title指代的就是book表的title列。更高级的用法是采用"&"、"|"、"!"三个符号,分别表示AND、OR和NOT,加上必要的"("和")"实现复杂的条件定义。由于传递给select()的第一个参数是个list,所以你应该已经猜到了,我们也可以多张表做关联查询。

posted @ 2009-01-26 23:40 laogao 阅读(1392) | 评论 (0)编辑 收藏


在sqlalchemy.schema和sqlalchemy.types这两个module中,包含了定义数据库schema所需要的所有类,如Table、Column、String、Text、Date、Time、Boolean等。还是来看一个例子:

 1 from sqlalchemy.engine import create_engine
 2 from sqlalchemy.schema import MetaData, Table, Column, ForeignKey, Sequence
 3 from sqlalchemy.types import *
 4 
 5 engine = create_engine('postgres://test:test@localhost/test', echo=True)
 6 
 7 metadata = MetaData()
 8 metadata.bind = engine
 9 
10 book_table = Table('book', metadata,
11     Column('id', Integer, Sequence('seq_pk'), primary_key=True),
12     Column('title', Unicode(255), nullable=False),
13 )
14 
15 author_table = Table('author', metadata,
16     Column('id', Integer, Sequence('seq_pk'), primary_key=True),
17     Column('name', Unicode(255), nullable=False),
18 )
19 
20 bookauthor_table = Table('bookauthor', metadata,
21    Column('book_id', Integer, ForeignKey('book.id'), nullable=False),
22    Column('author_id', Integer, ForeignKey('author.id'), nullable=False),
23)
24
25metadata.create_all(checkfirst=True)

首先我们还是create_engine,然后新建一个MetaData对象,把engine绑上去,接下来,开始在metadata中定义表结构(metadata由Table构造函数传入),我们这里定义了3张表,分别是book、author和bookauthor关系表(“多对多”),其中新建一个Sequence对象,专门处理主键生成。最后我们通过执行metadata.create_all()创建数据库表,参数checkfirst=True表示如果数据库相关对象已经存在,则不重复执行创建。

对于已经存在于数据库中的表,我们可以通过传入autoload=True参数到Table构造函数的方式来加载现有的表结构到metadata中,而不必挨个儿再写一遍Column清单。

看到这儿,你也许觉得挺麻烦,不是么?Django和RoR都是可以直接定义数据model类,顺带就把schema也定义了,而不是像这样单独去写表结构的schema,显得很"底层"。确实,这样用SQLAlchemy并不是最优化的,SQLAlchemy本身并不会自动的帮你做很多事,但它基础打得很牢。如果你感兴趣,也可以先去看一下SQLAlchemy的扩展模块Elixir,通过Elixir,你可以像Ruby on Rails那样定义出实体和关系("Active Record")。

在稍后的第4部分中,我们会去了解如何以声明方式来更紧凑的定义我们的model/schema(0.5新特性)。鉴于笔者倾向于更强的控制力,所以在这个系列中就不去介绍SQLAlchemy的其他扩展模块了,如Elixir、SQLSoup等,大家可以根据需要去找下官方文档。

posted @ 2009-01-26 22:14 laogao 阅读(2449) | 评论 (0)编辑 收藏


ORM是个大话题,大到可能好几本书都说不完。SQLAlchemy,别看它刚出到0.5.2,已然是Python世界ORM的事实标准,受到众多开发者和无数框架的青睐。

如果之前没有或很少接触SQLAlchemy,那么学习Pylons可能有相当一部分时间都会花在SQLAlchemy上。通常,人们选择Pylons或者TurboGears而不是Django,SQLAlchemy在这些决定的背后有着很重的分量。作为Pylons学习笔记的一部分,接下来我将分4篇随笔介绍SQLAlchemy,分别是Engine API、Schema Management (MetaData/Types)、SQL Expression Language和Object Relational Mapper。此文为第1篇,重点介绍Engine API。

类似Java的JDBC,Python也有一个类似的数据库访问接口规范,那就是DB-API(目前是2.0),不同的常见RDBMS都有符合DB-API标准的Python库,比如PostgreSQL有psycopg2,Oracle有cx_Oracle等。有了这个基础,SQLAlchemy也就得以方便的通过DB-API连接不同的数据库。

以PostgreSQL为例,通过SQLAlchemy的Engine API访问数据库的代码可以这样来写:
 1 from sqlalchemy.engine import create_engine
 2 
 3 engine = create_engine('postgres://user:pass@localhost/testdb')
 4 connection = engine.connect()
 5 connection.execute(
 6     """
 7     CREATE TABLE book
 8     (
 9       id serial NOT NULL,
10      title character varying(30) NOT NULL,
11      CONSTRAINT pk_book PRIMARY KEY (id)
12     );
13    """
14 )
15 connection.execute(
16     """
17     INSERT INTO book (title) VALUES (%s);
18     """,
19     "The Art of UNIX Programming"
20 )
21 rs = connection.execute("SELECT title FROM book")
22 for row in rs:
23     print "Book Title: ", row['title']
24 connection.close()

基本步骤就是create_engine、connect、execute和close,没有很特别的地方,不过SQLAlchemy的Engine API,并不是简单的DBAPI调用,而是包装了其他内容,如数据库连接池,我们可以在create_engine的时候指定连接池参数和其他额外配置,类似这样:
engine = create_engine('postgres://user:pass@localhost/testdb', pool_size=10, convert_unicode=True)

类似Spring的JdbcTemplate,更多的时候,统一使用SQLAlchemy的Engine API而不是DB-API能给我们带来更大的灵活性,通常也更方便、更安全。

posted @ 2009-01-26 20:46 laogao 阅读(2485) | 评论 (1)编辑 收藏


在开始之前,说点提外话,随着对Pylons了解的深入,你可能时不时需要看看相关组件/软件包是否有更新出来,方法也不复杂,通过"easy_install -U [组件名]"即可,在学习或者是开发过程中,最好是保持环境相对较新,直到出现相对大的release或者即将进入产品部署阶段。

继续介绍Pylons组件,先看个例子。首先用"paster controller hello"增加一个controller,路径中会增加出以下两个文件:
controllers/hello.py
tests/functional/test_hello.py

分别对应新增的controller类HelloController和功能测试类TestHelloController,它们分别继承自WSGIController->BaseController和TestCase->TestController。

我们主要看hello.py,默认内容如下:
 1 import logging
 2 
 3 from pylons import request, response, session, tmpl_context as c
 4 from pylons.controllers.util import abort, redirect_to
 5 
 6 from newapp.lib.base import BaseController, render
 7 #from newapp import model
 8 
 9 log = logging.getLogger(__name__)
10 
11 class HelloController(BaseController):
12 
13     def index(self):
14         # Return a rendered template
15         #   return render('/template.mako')
16         # or, Return a response
17         return 'Hello World'

如果你的服务器没有Ctrl-C停掉,那么这个时候你已经可以通过
http://127.0.0.1:5000/hello/index
看到该controller的处理结果了(Hello World)。

简单改造一下17行:
        from pylons import config
        
return '<br/>'.join(config.keys())

我们就可以在返回页面上显示出所有可以通过pylons.config访问到的参数列表。出了返回文本,也可以通过render()方法交给页面模板引擎生成页面,也可以通过redirect_to()跳转到其他URL。

Pylons是如何找到该请求应该由HelloController的index方法来处理的呢?这背后发生了什么?答案就是Routes。

Routes的作者是Ben Bangert,是Pylons框架三个主要作者/维护者之一,早期的版本主要是仿照Ruby on Rails的routes.rb开发的,有RoR经验的朋友可能一眼就能发现它们之间的相似之处。目前Routes的最新版是1.10.2。

Pylons应用中,routing的配置在config/routing.py,默认生成的内容如下:
 1 """Routes configuration
 2 
 3 The more specific and detailed routes should be defined first so they
 4 may take precedent over the more generic routes. For more information
 5 refer to the routes manual at http://routes.groovie.org/docs/
 6 """
 7 from pylons import config
 8 from routes import Mapper
 9 
10 def make_map():
11     """Create, configure and return the routes Mapper"""
12     map = Mapper(directory=config['pylons.paths']['controllers'],
13                  always_scan=config['debug'])
14     map.minimization = False
15     
16     # The ErrorController route (handles 404/500 error pages); it should
17     # likely stay at the top, ensuring it can always be resolved
18     map.connect('/error/{action}', controller='error')
19     map.connect('/error/{action}/{id}', controller='error')
20 
21     # CUSTOM ROUTES HERE
22 
23     map.connect('/{controller}/{action}')
24     map.connect('/{controller}/{action}/{id}')
25 
26     return map

在这个配置中,对我们刚才的实例起到决定性作用的是第23行,我们的输入URL为"http://127.0.0.1:5000/hello/index",其中"/hello/index"通过"/{controller}/{action}"这个表达式match出controller为hello而action为index的解析结果,从而在controllers目录找到hello.py,和其中HelloController的index方法,进行调用。

map.connect()在上面代码中体现出两种用法:
map.connect('pattern', key=value) - 指定默认的controller、action、id等
map.connect('pattern') - 直接指定pattern

pattern字符串允许通配符,通常在最后一个元素上,比如'/{controller}/{action}/{*url}',将后面的整个URL片段交给前面指定的controller/action处理。除此以外,map.connect()还支持

1- "路径别名",如:
map.connect('name', 'pattern', [_static=True])
如果_static设为"True",表示为"静态命名路径"。
2- 额外的匹配条件,如:
map.connect('/{controller}/{action}/{id}', requirements={'year': '\d+',})
map.connect('/{controller}/{action}/{id}', conditions=dict(method=['GET','POST']))

所有的route优先级为从上到下。Routes除了提供解析进来的URL的逻辑,在我们的controller和template代码中,我们还可以方便的通过WebHelpers的url_for()方法计算相应的URL。

Routes 1.x中的有一些仿routes.rb功能将会在2.0中被去掉,包括Route Minimization、Route Memory、Implicit Defaults等。如果有兴趣的话,可以参考一下官方文档,这里就不一一介绍了。为什么要去掉?当然主要的动机还是减少歧义,避免一些不必要的混淆。至于深层次的原因么,可以参考Tim Peters《The Zen of Python》中的一句经典的Python哲学:Explicit is better than implicit。什么?没有听说过?打开python命令行,输入"import this"后回车,慢慢体会其中的道理吧。:)


posted @ 2009-01-26 16:21 laogao 阅读(3975) | 评论 (0)编辑 收藏


大家新年好!

前一篇随笔中,大家了解了什么是Pylons,有哪些特点,今天笔者继续给介绍默认生成的项目结构。

通过Paste创建新的Pylons应用很简单,就是一句"paster create -t pylons [应用名]"命令,其中"-t pylons"或者全称"--template=pylons",用以告诉Paste我们新建的项目,将是一个Pylons应用,或者说,从一个预定义好的Pylons默认项目模板生成。如果你愿意,你也可以自己来制作新的模板以符合需要。"paster create --list-templates"可以查看当前可用的模板。

假定我们新建的Pylons项目名称为NewApp,那么执行"paster create -t pylons NewApp"后,我们将得到一个新的NewApp目录,其中包含一个docs目录(文档)、一个newapp目录(代码)、一个NewApp.egg-info目录(元数据)和一些顶级配置文件。

在开发过程中,我们经常会用到的是代码目录(这里是newapp)和位于项目根目录下的development.ini和test.ini文件了。注意这里newapp的大小写,Pylons在生成项目的时候,不论你指定的项目名称大小写是怎样,这里会自动.lower()转小写,只有项目顶级路径和egg信息会保留原始大小写,笔者认为这更加符合Web应用的nature。

代码目录下包括config、controllers、lib、model、public、templates和tests子目录,分别用于存放配置(如环境、中间件、路径查找逻辑)、控制器(处理请求)、全局辅助代码(全局变量、helpers等)、模型(后台数据访问)、静态页面/媒体文件、页面模板和测试代码。

最后说说ini文件:默认生成的development.ini和test.ini顾名思义分别对应开发和测试需要的基本配置,我们可以通过修改相应的参数配置来指定具体设定,如服务器IP和端口、数据库连接、日志等。看到这里你也许会问,那么产品环境呢?答案是默认没有生成,你可以从development.ini修改成需要的产品环境配置,也可以通过paster make-config newapp production.ini生成一个默认的产品环境典型配置。

以development.ini为例,Pylons的ini文件主要包括4个部分的内容:

1- 全局默认参数,如
[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



2- 服务器配置,如
[server:main]
use 
= egg:Paste#http
host 
= 127.0.0.1
port 
= 5000



3- 应用程序配置,如
[app:main]
use 
= egg:NewApp
full_stack 
= true

cache_dir 
= %(here)s/data
beaker.session.key 
= newapp
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

# SQLAlchemy database URL
sqlalchemy.url 
= sqlite:///%(here)s/development.db

# 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
简单说明一下,这里的full_stack设置为true表示打开交互式调试和错误报告等功能,"%(here)s"会被替换成项目所在路径,类似相对路径url中的"."转绝对路径,而beaker.*为配置会话相关的设定,如缓存、cookie基本内容等。对于产品环境来说,比较重要的一个配置是"set debug=false",否则一旦出现异常,交互式调试将使得攻击者能够执行系统命令。

4- 日志,如
[loggers]
keys 
= root, routes, newapp, sqlalchemy

[handlers]
keys 
= console

[formatters]
keys 
= generic

[logger_root]
level 
= INFO
handlers 
= console

[logger_routes]
level 
= INFO
handlers 
=
qualname 
= routes.middleware
"level = DEBUG" logs the route matched and routing variables.

[logger_newapp]
level 
= DEBUG
handlers 
=
qualname 
= newapp

[logger_sqlalchemy]
level 
= INFO
handlers 
=
qualname 
= sqlalchemy.engine
"level = INFO" logs SQL queries.
"level = DEBUG" logs SQL queries and results.
"level = WARN" logs neither.  (Recommended for production systems.)

[handler_console]
class 
= StreamHandler
args 
= (sys.stderr,)
level 
= NOTSET
formatter 
= generic

[formatter_generic]
format 
= %(asctime)s,%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
datefmt 
= %H:%M:%S

OK,这一篇就先讲到这儿,下一篇将介绍Routes和controller。


posted @ 2009-01-26 12:33 laogao 阅读(979) | 评论 (0)编辑 收藏


Pylons是一个Python语言的Web应用程序框架,如果你简单了解过Ruby on Rails和Django,你大概会问,Pylons有什么不一样呢?Pylons最大的特点是模块化,将处理Web应用环境下不同领域、不同问题的软件包集成在一起,形成一个整体,在提供一揽子解决方案的同时,不阻碍你选择别的替代组件。另外,Pylons是目前对WSGI标准支持最好的框架之一,未来的TurboGears 2.0也会基于Pylons构建。

Pylons从Ruby on Rails借鉴了不少东西,比如Routes,比如WebHelpers,从表面看更像是Python版的RoR,不过底下的架构应该说更加轻量和灵活,因为你可以灵活选择自己熟悉或者更贴和具体应用实际的组件,从ORM到页面模板,Pylons只是推荐一些大家普遍比较认可的选项,但并不强制你使用它们。

说完和Ruby on Rails的异同,当然也要回过头来说说同样是Python编写的Django。如果你只是想迅速的构建一个可以支撑大量访问的Web应用,Django是个不错的选择,但和RoR一样,你在很大程度上被限制在一定的pattern中:如果你按照Django的思路去实现你的应用,你会很happy;但一旦你觉得某个组件你不喜欢、不符合某个实际要求,想要来点定制,你就会觉得有些伸不开拳脚,或者工程浩大。目前感觉Django比较不爽的地方有:页面模板较弱,表现力有些不足,也有人说够用了;ORM目前是自己的一套,暂时没有成熟的SQLAlchemy支持,需要第三方包或者自己做;从架构上,Django对MVC的解读是MTV(Model-Template-View),大家都叫作controller的东东,在Django的世界里是view,以至于每次和别人解释,都要多费一番口舌。

Pylons目前版本是0.9.7(rc4),主要用到的第三方/独立组件有Paste、Routes、Beaker、Mako、FormEncode、WebHelpers和SQLAlchemy。安装方法如下:

首先你必须有Python(2.3+),然后你可以选择直接easy_install Pylons或者新建一个Virtual Environment,和系统中的Python环境隔离开,依赖的包可以独立升级。这里我们按照后一种方式,如果你是第一次使用Pylons,建议你也在独立Python virtualenv中安装。

1- easy_install virtualenv (这将安装Python虚拟环境工具)
2- python virtualenv.py ENV (创建新的虚拟环境。这里的ENV是你新建虚拟环境的路径,如"mydevenv")
3- source ENV/bin/activate (激活虚拟环境。如果是Windows的话,这里需要执行ENV\bin\activate.bat)
4- easy_install Pylons (这里使用的是虚拟环境的easy_install安装)

如果你觉得上面的步骤麻烦,Pylons开发团队提供了一个脚本来处理安装过程,下载后用Python执行即可:
http://www.pylonshq.com/download/0.9.7/go-pylons.py

如果需要SQLAlchemy,则再执行一下
easy_install SQLAlchemy

安装成功后,通过
paster create -t pylons [应用名]
即可新建Web应用主框架,然后cd到应用下,通过
paster serve --reload development.ini
启动Web服务,默认地址在
http://127.0.0.1:5000/

更详细的信息,可参考Pylons项目主页:
http://pylonshq.com/

随着使用的深入,笔者还会陆续对Pylons和其他相关组件进行进一步的介绍。祝各位农历新年快乐!


posted @ 2009-01-25 20:05 laogao 阅读(2019) | 评论 (2)编辑 收藏


在Unix环境下,命令行或者shell中sleep和kill是常见的动作,在Windows的.bat文件中处理类似的任务就不那么直接了,备忘如下:

[sleep]
ping 127.0.0.1 -n 需要的秒数+1 -w 1000 > nul

[kill]
taskkill /f /im "进程名(如notepad.exe)"
taskkill /f /fi "WINDOWTITLE eq notepad*"

其中/f表示强制,/im表示image镜像名(可执行文件名),/fi表示filter,后面跟表达式,比如这里的"窗体标题等于notepad*",支持wildcast通配符。


posted @ 2009-01-23 19:36 laogao 阅读(2214) | 评论 (0)编辑 收藏

仅列出标题
共34页: 上一页 1 2 3 4 5 6 7 8 9 下一页 Last