Read Sean

Read me, read Sean.
posts - 483, comments - 578, trackbacks - 9, articles - 3

2009年6月8日


周末去败了只全新的紫砂回来,宜兴红泥的,专门用来泡台湾高山乌龙。用之前,当然少不了开壶的过程,虽然比较耗时,但也挺有趣,在这里做个记录。

首先将新买的紫砂壶用清水洗净,浸泡一整天,其间换水2~3次,去除掉残余的泥土、灰尘等。然后放入锅内,加入清水煮上1个小时,继续消毒。


接下来,放置一边,冷却后,取老豆腐半块,放入清水中,再煮1小时,去火气。


第三步,用清水洗净,根据今后用这个紫砂壶泡什么类型的茶,选用单次用量的2~4倍茶叶,加清水,煮沸后,待茶汁完全浸出,捞起茶叶残渣,继续用茶汤煮上1个小时。


出锅。


这是成品,可以正式开始享用了:


现在开始,从头养我的第二只紫砂,细细体味那份安静和从容,让每日忙碌和疲惫的身心,有一个休息和平复的空间。

posted @ 2009-06-08 22:07 大胃 阅读(101) | 评论 (1)编辑 收藏

2009年6月1日

各位童鞋们过节好啊,今天给大家带来的是在bash中DIY制表符键自动补全。

bash是大多数主流Linux发行版的默认shell,如果你用过bash,那么一定会接触到<tab>键自动补全的这个方便的功能,当你一个命令的头几个字符敲下去,按下<tab>,如果以此开头的命令只有1个, bash会直接帮你补全,如果有多个,则会有相应提示,而在后续的参数输入时,也会带有默认的自动补全文件路径的功能。当你习惯了<tab>,很难想象没有自动补全的日子会是什么样子。

bash默认支持常见的补全功能,如可执行命令、文件路径等,如果安装了bash-completion包,甚至连chown, man, svn, ssh这些也会带有相应的自动补全提示,而不是单纯的文件路径补全。好奇的你一定想知道是怎么实现的吧,其实很简单,我们举个例子来说:

假定你有一个命令,叫做abc,它又有自己的子命令,分别是build_all、compile和update,其中compile这个子命令需要的参数必须来自project.list这个文件中列出的值,怎么实现<tab>自动补全,让bash知道abc的合法子命令和compile子命令的合法参数列表呢?

在你的~/.bashrc或者任何一个启动bash时会被执行的文件中加入下面的代码:

function _abc() {
    COMPREPLY=()
    local cur=${COMP_WORDS[COMP_CWORD]};
    local com=${COMP_WORDS[COMP_CWORD-1]};
    case $com in
    'abc')
        COMPREPLY=($(compgen -W 'build_all compile update' -- $cur))
        ;;
    'compile')
        local pro=($(awk '{print $1}' project.list))
        COMPREPLY=($(compgen -W '${pro[@]}' -- $cur))
        ;;
    *)
        ;;
    esac
    return 0
}

complete -F _abc abc

手动载入一下,或者重启bash,再敲abc命令,即可自动补全子命令,如果子命令是compile,还能自动补全相应的参数值。我们来简单分析一下这段代码。首先我们定义一个function _abc,这个函数先清空自动补全列表,根据当前输入位置前一个token判断目前需要自动补全的语境,如果是abc,则将自动补全内容设置为'build_all'、'compile'和'update',如果是'compile',则将project.list文件内容输出到补全列表,当然,这里我们也可以换成其他任何必要的方式。最后我们通过complete -F _abc abc将这段自动补全逻辑注册到abc这个主词上。这样当我们敲abc时,后续内容就能自动补全了。

Enjoy!

posted @ 2009-06-01 00:11 大胃 阅读(106) | 评论 (0)编辑 收藏

2009年5月5日


本来是手写的一张草稿,清理台面的时候,正要扔,发现内容还有点意思,干脆吧,开个随笔记录一下,备忘,说不定还能帮到别人。

在我们日常生活和工作中,尤其浏览含有中文的网站,时常会为乱码问题犯愁,一些天生Unicode支持不到位的编程语言和软件更是加重了这个现象。虽说已经是2009年了,我们时不时还是能碰到一些明显脑残的code,吐出一堆乱码,不论你用选什么字符集,似乎都无法还原最初的中文。比如"å·²",或者同一个页面,无法用同一个字符集显示,任何一种字符都至少有一部分显示不正确,不是这儿就是那儿。

首先科普一下UTF-8。UTF-8是Unicode所有字符编码实现中,应用范围最广的一个,最大的特点是兼容ASCII码,在此基础上,通过1..4个byte来表示每一个Unicode字符。它是怎么做到的?其实很简单,看首个byte:
00000000 ~ 01111111 : 0~127 (ASCII, 单个byte) 表示Unicode范围\u0000 ~ \u007F
11000000 ~ 11011111 : (2个1打头,所以2个byte) 表示Unicode范围\u0080 ~ \u07FF
11100000 ~ 11101111 : (3个1打头,所以3个byte) 表示Unicode范围\u0800 ~ \uFFFF
11110000 ~ 11110111 : (4个1打头,所以4个byte) 表示Unicode范围\u10000 ~ \u10FFFF

除了单个byte这个case,其他情况下,后续的byte均以10打头。这些打头的bit:10、110、1110、11110,都不作为具体编码的一部分参与真正Unicode编码的计算。所以1..4个byte,其有效位数分别为:7、11、16、21,因此才有了\u007F、\u07FF、\uFFFF这样的临界值,且4个byte的空间还有富余。

有了这个基础知识,我们就来具体看看这个"已"字,是怎么被UTF-8表示的,以及为什么处理不当的代码会最终输出"å·²"。

"已"字,用Unicode表示法,是\u5DF2,按照2个byte拆开成二进制表示:
01011101 11110010
如果用UTF-8编码,采用1110???? 10?????? 10??????这个格式,?号部分填入上述01011101 11110010,得到
11100101 10110111 10110010 这样3个byte。

好了,这个时候脑残代码出现,假如它不认识UTF-8,那么他会看到这样3个前后没有关联的byte,在西欧Latin-1字符集(即ISO 8859-1)中,它们分别表示"å"、"·"、"²"这3个字符。如果把它们分别再按照UTF-8编码,就成了:
11000011 10100101 11000010 10110111 11000010 10110010
完美的UTF-8编码,只不过,这完全是假象,只能通过非常规手段才能恢复它原本的样子。


posted @ 2009-05-05 19:24 大胃 阅读(104) | 评论 (0)编辑 收藏

2009年4月17日


上周在北京参加QCon大会,回来以后一直没有成块的时间,把一些之前只是通过Twitter分享出来的信息汇总整理出来。已经有不少朋友都在各自的博客上记录了大会感闻,所以我还是抓紧些吧,不然真要成没有营养的废话了。

QCon这次是首次进入中国,我有幸得以抽身参加。大会为期三天,由InfoQ中文站组织策划,包括 @taiwen 在内InfoQ中文站只有3个人,这是我之前没有想到的,感谢他们和志愿者的辛勤劳动,使得这次大会得以顺利举行。大会嘉宾包括了国内外许多大名鼎鼎的牛人,比如前TSS和后来InfoQ的主创Floyd Marinescu、ThoughtWorks首席科学家Martin Fowler、Spring之父Rod Johnson、eBay架构师Randy Shoup、《硝烟中的Scrum和XP》作者Henrik Kniberg、《大道至简》作者周爱民、支付宝数据库架构师冯大辉 @Fenng 等。

大会第一天,上午和下午都安排了Martin Fowler的session,分别是DSL和Ruby,虽然内容算不上特别新,但幻灯片准备很到位,而大师就是大师,思路清晰,言简意赅,尺度分寸拿捏的很好。唯一感觉可能是大家都比较含蓄,或者热身不够,沟通和对话的环节显得不是很活跃,除了买书签名的,到后面Dojo的创始人Dylan Schiemann讲Open Web,气氛就开始暖和一些了。至于Amazon的Jeff Barr,不少朋友也都说了,基本上就是来打广告的,可惜QCon北京的赞助商中,没有发现Amazon。晚上的沙龙,国际讲师唱主角,分享关于软件开发趋势的见解。

大会第二天,可能是最精彩的一天,尤其是下午 @Fenng 主持的网站架构这个Track。上午的大戏是Rod Johnson,不过也许是大家期望比较高,反倒是没觉出特别出彩的地方,Rod特别强调Simplicity和Lean,同时通批了一顿Java EE,不过把Tomcat作为simplicity和lean的代表举例,似乎大家都不是特别买账。后来才知道原来Tomcat 90%的commit都来自SpringSource,而稍后SpringSource会在此基础上推出功能更丰富的Spring tc server,以及(我猜)Spring 3.0。接下来eBay的Randy Shoup,带来了一场颇为精彩的演讲。下午来自支付宝、豆瓣网、网易有道和优酷网的架构师们共同创造了本次QCon大会最受欢迎的半天,尤其是豆瓣的 @hongqn ,内容相当务实、精彩、有货。说到这,插句题外话,如果没记错, @hongqn 是这次大会中唯一一位使用Mac的国内讲师,国际讲师用Mac的好像也只有Martin Fowler (如果说的不对还请同学们指正)。到了这个时候,国际讲师们渐渐淡出,场子交给了国内的讲师们,会场气氛也更加热闹和随意起来,包括晚上的沙龙。

大会第三天,从日程上看,并没有特别重量级的session,似乎除了云还是云,不过如果因此选择不参加的话,可能就会错过了两场很不错的演讲,分别是高焕堂的"提高架构质量的10个观点"和周爱民的"我之于架构的主要观点"。高焕堂的演讲让人耳目一新,明显感觉来自台湾的专家,比大陆的专家,对中国传统文化,有更深的理解和思考,他的"序"、"容"、"易"三角,让人印象深刻,另外他还对目前我们软件产业的结构性问题提出了批评。而周爱民的演讲同样很有深度,尤其是后面说到架构师的决策作用时,很引起我的共鸣:"架构师不需要让所有人都理解你的每一个决定和这些决定最终要达成的目标,去做就是了。"(可能原话不是这么说的,但意思是这样) 也许有些朋友觉得这两个session虚幻的成分比较重,但对我来说,收益颇丰,因为很多观点,都能够在我之前经历的一些项目和设计中找到支撑。至于其他几讲,关于云计算的,确实提不起什么兴趣,尤其是Azure,干的要命(小插曲: 现场演示一段ASP.NET代码,至少修改和重编译了3次才搞定)。大公司带着推销产品/服务的目的来参加开发会议,有点浪费大家的时间和银子啊。

最后说说大会的组织相对还有待提高的地方:
1- 第一天入场时,志愿者的引导不是很到位,报到的地方和其他展台秩序稍显混乱。
2- 同声传译据说质量不高,白天没用,但是周围的朋友在用,耳机漏音比较严重,稍影响一些效果,晚上直接通过扩音设备翻译,就很明显的感觉到脱节了。
3- 午餐和沙龙的地方,显得还是小了些,而且中午所有人都集中在一个时间去,不论排队打饭还是找位置,都比较吃力,以至于第二天中午有一小撮脑筋比较活络的同学,提前下课去打饭吃。
4- 现场网络环境比较差,害我只能用手机上twittai。

总的来说,这次的QCon还是挺不错的,希望明年的QCon会继续在中国举办,更多的嘉宾,更丰富的内容,更有深度的主题,更加精彩。


posted @ 2009-04-17 00:36 大胃 阅读(223) | 评论 (0)编辑 收藏

2009年3月18日


其实很早就听说有这个东东,只是一直没玩过,最近公司调整PC服务器,正好找个空闲实战了一把。基本的配置步骤如下(时间有限,挑简单的说,假定你要Wake-on-LAN的机器是Windows,控制服务器是Linux):

[被控制方]
1- 正常开机进入BIOS设置
2- 找到Wake-on-LAN的选项enable它(如果是Dell的机器这个选项叫Remote Wake Up)
3- 进入OS,在需要配置Wake-on-LAN的网络端口的配置项中(网络连接属性->配置->电源管理),选择允许此设备使计算机脱离待机状态
4- 记录网卡的MAC地址
5- 正常关机
// 如果被控制方是Linux/Ubuntu,OS的配置方法参考链接[4]

[控制方]
1- 安装wakeonlan,可以选择(如果有的选) apt-get install wakeonlan,或者从链接[3]获取源码手工安装(是Perl写的)
2- 通过wakeonlan+MAC地址的命令行方式控制需要wake up的机器,如 wakeonlan 01:23:45:67:89:AB
// 更高级的用法包括编写脚本,添加到cron,以及通过-f指定一个包含多个MAC地址的文件同时操作等

[基本原理]

Wake-on-LAN的相关通信协议位于OSI七层模型中的数据链路层,比IP需要的网络层还要低一层,在局域网范围发送广播,数据包格式为:
FF FF FF FF FF FF $MAC*16
即 FF FF FF FF FF FF然后重复16次对方的MAC地址,被戏称为"magic packet"。

配置成功后,只要被控制方正常关机、挂起、休眠,且环境始终不掉电,任何时候在局域网中广播"magic packet",指定网卡的机器就可以被唤醒。

链接:

[1] http://en.wikipedia.org/wiki/Wake-on-LAN
[2] http://en.wikipedia.org/wiki/OSI_model
[3] http://gsd.di.uminho.pt/jpo/software/wakeonlan/
[4] http://ubuntuforums.org/showthread.php?t=234588


posted @ 2009-03-18 20:03 大胃 阅读(144) | 评论 (0)编辑 收藏

2009年3月2日


周末花了一个下午和一个晚上把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 大胃 阅读(156) | 评论 (0)编辑 收藏

2009年2月22日


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



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


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

2009年2月21日


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

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

DSC_1768s.JPG


posted @ 2009-02-21 22:49 大胃 阅读(121) | 评论 (0)编辑 收藏

2009年1月29日


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

手头有个开发了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 大胃 阅读(1726) | 评论 (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 大胃 阅读(1738) | 评论 (0)编辑 收藏

2009年1月27日


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 大胃 阅读(311) | 评论 (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 大胃 阅读(215) | 评论 (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 大胃 阅读(237) | 评论 (0)编辑 收藏

2009年1月26日


在介绍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 大胃 阅读(224) | 评论 (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 大胃 阅读(220) | 评论 (0)编辑 收藏