Read Sean

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


          周末花了一個(gè)下午和一個(gè)晚上把Scott Rosenberg的Dreaming in Code從頭到尾看了一遍。最直接的感受是這本書把我之前很多記憶碎片串在了一起,成為一個(gè)生動(dòng)而"完整"的故事,雖然書的內(nèi)容本身組織的多少還是有點(diǎn)散。

          dreaming_in_code.jpg

          我本人對(duì)Chandler這個(gè)項(xiàng)目并不陌生:之前出于對(duì)Python/wxWidget和開源本身的興趣,陸續(xù)用過幾個(gè)0.x的版本,一開始并不是十分友好,性能上也有問題,甚至?xí)某缘粑覚C(jī)器上的數(shù)百兆(或者上G?)空間。后來的版本在性能和可用性上確實(shí)提高了不少,但一直感覺這個(gè)項(xiàng)目缺少必要的、以及許多開源項(xiàng)目應(yīng)有的momentum。Phillip J. Eby對(duì)Chandler開發(fā)人員不懂Python的批評(píng),當(dāng)時(shí)我的印象也很深。而項(xiàng)目中出現(xiàn)的人物,包括Mitchell Kapor、Ted Leung,也都在Chandler這個(gè)范疇之外follow過。其他細(xì)節(jié)包括:Chandler和Cosmo這兩個(gè)名稱的由來、Chandler項(xiàng)目組中女性成員相對(duì)高的比例、一些熟悉的人物及其觀點(diǎn)(Alan Kay, Bill Joy, Frederick Brooks, Donald Knuth、Linus Torvalds, Ward Cunningham, Larry Wall, Guido van Rossum, Joel Spolsky, etc.)、一些公司的分分和和以及人員流動(dòng)等等。感覺挺親切的。

          可能更重要、也更深刻的原因是:盡管書中一再提到"There's no such thing as a typical software project, every project is different",我仍然深深的感覺到,Chandler遇到的這些問題,其實(shí)也是我親歷的很多項(xiàng)目的種種問題的一個(gè)縮影。對(duì)這些問題,作者并沒有給出解決方案,其實(shí)誰(shuí)也沒有標(biāo)準(zhǔn)答案。軟件開發(fā)是一項(xiàng)非常具有挑戰(zhàn)性的工作,也正是像我們這樣有熱情、有涵養(yǎng)的專業(yè)人士生存的空間和價(jià)值所在。

          posted @ 2009-03-02 00:44 laogao 閱讀(705) | 評(píng)論 (0)編輯 收藏


          以下是一段視頻,Ward Cunningham針對(duì)Debt Metaphor這個(gè)隱喻的由來和人們對(duì)它的一些誤解進(jìn)行了澄清:



          我最感興趣的是Burden這一段:Cunningham解釋說,經(jīng)常看到有些開發(fā)團(tuán)隊(duì),他們快速的開發(fā)出軟件產(chǎn)品推向市場(chǎng),不斷的往里面添加新的特性,在這個(gè)過程中,不斷的學(xué)習(xí),但從不把學(xué)到的東西、總結(jié)的經(jīng)驗(yàn)教訓(xùn)應(yīng)用回去,這就像是從銀行借貸,但從不想著有一天需要償還(是不是有點(diǎn)像是在說引發(fā)這次次貸危機(jī)的美國(guó)人的消費(fèi)習(xí)慣和觀念?),到最后,你所有的收入都將用于償還利息,你的購(gòu)買力也將降為零。映射回軟件開發(fā)的語(yǔ)境,如果我們?cè)谝粋€(gè)較長(zhǎng)的時(shí)間跨度內(nèi),開發(fā)一個(gè)軟件,不斷的增加feature,但從不回過頭去整理和重構(gòu),把對(duì)這個(gè)軟件和這些特性的新的理解和認(rèn)知寫回去,那么最終這個(gè)軟件、這些feature將不再會(huì)有任何實(shí)際的價(jià)值,對(duì)它們做任何事,都將花費(fèi)越來越多的時(shí)間和精力,開發(fā)進(jìn)度也就因此下降為零。


          posted @ 2009-02-22 17:50 laogao 閱讀(387) | 評(píng)論 (0)編輯 收藏


          經(jīng)歷了有史以來最忙碌的一周,當(dāng)然要好好放松一下,除了聽上乘的古典音樂,沏上一壺上等的烏龍細(xì)細(xì)品味,也是一種享受。烏龍茶和紫砂壺可是絕配,如果是安溪的鐵觀音,加上做工精良的宜興紫砂壺,那滋味,唇齒留香,別提多愜意了。

          好的紫砂壺是需要"養(yǎng)"的,今天專程去茶城敗了一只回來,開始"喂"鐵觀音,哈哈。

          DSC_1768s.JPG


          posted @ 2009-02-21 22:49 laogao 閱讀(591) | 評(píng)論 (5)編輯 收藏


          先簡(jiǎn)單介紹一下問題的語(yǔ)境。

          手頭有個(gè)開發(fā)了3年的Spring+iBATIS應(yīng)用(經(jīng)典三層架構(gòu)),最近嘗試用Hibernate實(shí)現(xiàn)原有SQLMap版的部分CRUD操作。除開混合事務(wù)和其他一些底層配置細(xì)節(jié)(如TransactionAwareDataSource、禁用lazy-load等)之外,最大的一個(gè)"pattern-mismatch"是:Model層和持久層采用了dirty flag機(jī)制,即每次INSERT和UPDATE操作,都會(huì)根據(jù)每個(gè)字段的dirty與否決定是否參加INSERT/UPDATE,而這些dirty flag可以被外部重置,所以業(yè)務(wù)層的代碼,經(jīng)常可以看到類似這樣的pattern:

          1- new一個(gè)model類并setId() (或者在已有的model上重置dirty flag)
          2- set需要update的字段(通常都只是部分字段)
          3- 丟給DAO層去update

          最終的效果是某張表某個(gè)ID的某條記錄的某些字段被更新了,而其他字段不受影響,這就是我在標(biāo)題中提到的所謂"暴力"update,雖不elegant,但卻也簡(jiǎn)單實(shí)用,至少很"直接"。

          為了讓Hibernate版的DAO(默認(rèn)除Trasient之外全體字段參加INSERT和UPDATE)繼續(xù)支持這樣的"use-pattern",除了按照Hibernate的習(xí)慣去配置映射和SessionFactory等之外,我們需要做一些額外的工作:

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

          2- 實(shí)現(xiàn)org.hibernate.Interceptor接口的findDirty()方法,Hibernate提供了一個(gè)EmptyInterceptor可以作為起點(diǎn),方法簽名如下:
          public?int[]?findDirty(
          ????Object?entity,?
          ????Serializable?id,?
          ????Object[]?currentState,?
          ????Object[]?previousState,?
          ????String[]?propertyNames,?
          ????Type[]?types
          );
          返回的int[]包含所有應(yīng)該被認(rèn)為是dirty的字段索引,返回null表示默認(rèn)處理,傳入的entity是持久對(duì)象,字段列表會(huì)通過propertyNames參數(shù)傳給你。

          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>

          在這樣的配置下,業(yè)務(wù)層的代碼就可以繼續(xù)"暴力"update了。

          posted @ 2009-01-29 16:54 laogao 閱讀(2841) | 評(píng)論 (4)編輯 收藏


          如果你使用早前版本的Spring,又恰好采用了Annotation注解方式(而非傳統(tǒng)XML方式)配置Hibernate對(duì)象關(guān)系映射,那么在通過org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean配置sessionFactory時(shí),你一定對(duì)annotatedClasses、annotatedPackages有一種說不出的胸悶的感覺,如此以高配置性見長(zhǎng)的Spring,怎么在這一個(gè)小小的環(huán)節(jié)上就不能做得再靈活些呢,一定要一個(gè)個(gè)手寫Class路徑么?

          估計(jì)有不少人無奈選擇了從AnnotationSessionFactoryBean繼承一個(gè)自定義的子類,自己實(shí)現(xiàn)掃描邏輯,找出@Entity注解過的類清單配置進(jìn)去。

          Spring 2.5.6里有個(gè)不怎么起眼的改進(jìn),那就是在AnnotationSessionFactoryBean上增加了一個(gè)新的方法:
          setPackagesToScan(String[] packagesToScan)

          有了這個(gè)方法,我們不再需要自己動(dòng)手去實(shí)現(xiàn)實(shí)體類的掃描了,直接在Spring配置文件中AnnotationSessionFactoryBean這個(gè)section上增加類似如下的一個(gè)property即可(假定你需要加載的實(shí)體類所在的包名match這個(gè)字符串"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 閱讀(22733) | 評(píng)論 (2)編輯 收藏


          Pylons是一個(gè)典型的MVC Web框架,在之前的幾篇隨筆中,我們一起了解了Pylons的安裝、默認(rèn)項(xiàng)目結(jié)構(gòu)、Routes和controller("C")以及SQLAlchemy("M"),在這個(gè)系列的最后,我們一起來看看"V"。

          在我們的controller代碼中,每個(gè)action在return的時(shí)候,可以選擇:
          1- 直接return字符串
          2- 通過render()函數(shù)將輸出交給頁(yè)面模板引擎處理
          3- redirect_to()重定向到其他URL

          直接return太簡(jiǎn)單,redirect_to也沒有特別需要介紹的,重點(diǎn)看render()。如果你一直follow這個(gè)系列,那么在你的controllers/hello.py中,可以看到這樣一行import:
          from?newapp.lib.base?import?BaseController,?render

          從lib.base引入了一個(gè)render函數(shù),跟到lib/base代碼里查看,我們會(huì)知道:
          from?pylons.templating?import?render_mako?as?render
          其實(shí)我們用到的render()函數(shù),是pylons.templating.render_mako的別名。

          注: 這里假定你在paster create時(shí)選擇了默認(rèn)的mako,其他Pylons原生支持的頁(yè)面模板引擎還有結(jié)構(gòu)相對(duì)更層次化的Genshi和更接近Django實(shí)現(xiàn)的Jinja。

          render_mako()函數(shù)簽名如下:
          render_mako(template_name, extra_vars=None, cache_key=None, cache_type=None, cache_expire=None)

          最基本的用法是給出template文件名,然后通過extra_vars傳入?yún)?shù),Pylons默認(rèn)查找頁(yè)面模板文件是在項(xiàng)目的templates子目錄,這個(gè)路徑也可以在config/environment.py中修改。在Pylons中,被推薦的傳參做法是使用tmpl_context,在生成controller的時(shí)候,已經(jīng)自動(dòng)import了tmpl_context并別名為c,這樣,我們只需要在c上綁上我們需要傳遞給模板的數(shù)據(jù),模板在解析時(shí),也就能夠通過c得到這些數(shù)據(jù)了。像這樣:
          c.name?=?u'Pylons?n00b'
          return?render('hello.mako')

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

          在模板代碼中,有些Pylons系統(tǒng)變量/函數(shù)是可以直接訪問的,包括:
          tmpl_context (c) - 用于controller和template傳遞數(shù)據(jù)
          config - 配置信息
          app_globals (g) - 應(yīng)用的全局變量
          h - WebHelpers,包括h.form、h.link_to、h.url_for等等實(shí)用函數(shù)
          request - 請(qǐng)求
          response - 應(yīng)答
          session - 會(huì)話信息
          translator、ungettext()、_()、N_() - 處理國(guó)際化

          除了基本的${}變量替代語(yǔ)法之外,類似JSP,Mako還支持注釋、if/else/for控制邏輯、代碼片段、return、標(biāo)簽等,具體的可以掃一眼官方說明:
          http://www.makotemplates.org/docs/syntax.html
          很精簡(jiǎn),也非常容易理解,這里就不詳細(xì)說明了。

          至此,我們已經(jīng)了解了Pylons最核心的幾個(gè)組件,足夠我們搭建常規(guī)的Web應(yīng)用了。其他值得大家繼續(xù)挖掘的內(nèi)容包括:國(guó)際化、表單驗(yàn)證(FormEncode)、用戶驗(yàn)證和權(quán)限(AuthKit、repoze.who、repoze.what)、AJAX、Python 3.0、WSGI基礎(chǔ)架構(gòu)等。

          本文是該系列最后一篇,希望通過簡(jiǎn)單的介紹和學(xué)習(xí),大家能夠喜歡并順利的上手Pylons,也希望越來越多的人關(guān)注這個(gè)優(yōu)秀的Python Web應(yīng)用框架。

          posted @ 2009-01-27 15:50 laogao 閱讀(2229) | 評(píng)論 (0)編輯 收藏


          在前面的4篇隨筆中,我們簡(jiǎn)要的介紹了SQLAlchemy,不過SQLAlchemy如何被集成到Pylons應(yīng)用中呢?

          首先我們看一下自動(dòng)生成代碼中的model子目錄,其中有兩個(gè)文件__init__.py和meta.py,其中meta.py定義了engine、Session和metadata三個(gè)公用變量,而__init__.py提供了一個(gè)核心的init_model(engine)方法,該方法分別將數(shù)據(jù)庫(kù)engine和經(jīng)過sessionmaker和scoped_session包裝的Session對(duì)象植入到meta中,像這樣:
          ????sm?=?orm.sessionmaker(autoflush=True,?autocommit=True,?bind=engine)

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

          這樣一來,整個(gè)背后的"magic"就還剩下最后一塊"拼圖":誰(shuí)來把engine初始化好并調(diào)用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在初始化運(yùn)行環(huán)境時(shí),從config中讀取sqlalchemy相關(guān)的配置信息,然后通過這些配置信息創(chuàng)建數(shù)據(jù)庫(kù)engine,并調(diào)用init_model()方法初始化SQLAlchemy功能的核心對(duì)象:metadata和Session。有了meta.Session,我們就可以方便的在代碼中執(zhí)行對(duì)model層/數(shù)據(jù)庫(kù)的訪問了。

          posted @ 2009-01-27 14:00 laogao 閱讀(1193) | 評(píng)論 (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

          這里我們定義兩個(gè)類,繼承自object,類似JavaBeans或者POJO,這里的__init__方法和__repr__方法不是必須的,只是為了創(chuàng)建對(duì)象和輸出對(duì)象內(nèi)容比較方便。然后就可以用SQLAlchemy的mapper和sessionmaker來建立映射關(guān)系并處理持久和查詢等操作:
          ?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,那么這些代碼對(duì)你來說,理解起來應(yīng)該沒有任何難度。

          假如我告訴你,每次都要像這樣先定義Table(schema),再定義class,然后用mapper建立對(duì)照,是不是有點(diǎn)那啥?SQLAlchemy的開發(fā)者們也意識(shí)到這一點(diǎn),所以從0.5開始,SQLAlchemy可以通過sqlalchemy.ext.declarative支持我們實(shí)現(xiàn)更緊湊的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關(guān)系,其他的常見用法還包括many-to-one、one-to-many、JOIN、子查詢、EXISTS、Lazy/Eager Load、Cascade (all/delete/delete-orphan)等等,大家可以根據(jù)需要查閱官方文檔。

          posted @ 2009-01-27 12:52 laogao 閱讀(1695) | 評(píng)論 (0)編輯 收藏


          在介紹SQLAlchemy最核心最有價(jià)值的ORM部分之前,我們?cè)俸?jiǎn)單過一遍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()

          簡(jiǎn)單說明一下代碼邏輯:
          首先從engine建立連接,然后做兩個(gè)insert動(dòng)作,分別insert一條book記錄(title為'Groovy in Action')和一條author記錄(name為'Andrew Glover'),這之后分別再做兩次select,得到剛insert的這兩條記錄,其中book記錄的select用到了過濾條件,相當(dāng)于"WHERE book.title like 'Groovy%'",然后構(gòu)建一條新的insert語(yǔ)句,用于insert一條bookauthor關(guān)系記錄,接下來,做一次update,將book.title為'Groovy in Action'的更新為'Groovy in Action (中文版)',最后,在關(guān)閉連接之前,做一次delete,刪除bookauthor中的記錄。

          在指定WHERE條件時(shí),.c是.columns的簡(jiǎn)寫,所以book_table.c.title指代的就是book表的title列。更高級(jí)的用法是采用"&"、"|"、"!"三個(gè)符號(hào),分別表示AND、OR和NOT,加上必要的"("和")"實(shí)現(xiàn)復(fù)雜的條件定義。由于傳遞給select()的第一個(gè)參數(shù)是個(gè)list,所以你應(yīng)該已經(jīng)猜到了,我們也可以多張表做關(guān)聯(lián)查詢。

          posted @ 2009-01-26 23:40 laogao 閱讀(1410) | 評(píng)論 (0)編輯 收藏


          在sqlalchemy.schema和sqlalchemy.types這兩個(gè)module中,包含了定義數(shù)據(jù)庫(kù)schema所需要的所有類,如Table、Column、String、Text、Date、Time、Boolean等。還是來看一個(gè)例子:

          ?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,然后新建一個(gè)MetaData對(duì)象,把engine綁上去,接下來,開始在metadata中定義表結(jié)構(gòu)(metadata由Table構(gòu)造函數(shù)傳入),我們這里定義了3張表,分別是book、author和bookauthor關(guān)系表(“多對(duì)多”),其中新建一個(gè)Sequence對(duì)象,專門處理主鍵生成。最后我們通過執(zhí)行metadata.create_all()創(chuàng)建數(shù)據(jù)庫(kù)表,參數(shù)checkfirst=True表示如果數(shù)據(jù)庫(kù)相關(guān)對(duì)象已經(jīng)存在,則不重復(fù)執(zhí)行創(chuàng)建。

          對(duì)于已經(jīng)存在于數(shù)據(jù)庫(kù)中的表,我們可以通過傳入autoload=True參數(shù)到Table構(gòu)造函數(shù)的方式來加載現(xiàn)有的表結(jié)構(gòu)到metadata中,而不必挨個(gè)兒再寫一遍Column清單。

          看到這兒,你也許覺得挺麻煩,不是么?Django和RoR都是可以直接定義數(shù)據(jù)model類,順帶就把schema也定義了,而不是像這樣單獨(dú)去寫表結(jié)構(gòu)的schema,顯得很"底層"。確實(shí),這樣用SQLAlchemy并不是最優(yōu)化的,SQLAlchemy本身并不會(huì)自動(dòng)的幫你做很多事,但它基礎(chǔ)打得很牢。如果你感興趣,也可以先去看一下SQLAlchemy的擴(kuò)展模塊Elixir,通過Elixir,你可以像Ruby on Rails那樣定義出實(shí)體和關(guān)系("Active Record")。

          在稍后的第4部分中,我們會(huì)去了解如何以聲明方式來更緊湊的定義我們的model/schema(0.5新特性)。鑒于筆者傾向于更強(qiáng)的控制力,所以在這個(gè)系列中就不去介紹SQLAlchemy的其他擴(kuò)展模塊了,如Elixir、SQLSoup等,大家可以根據(jù)需要去找下官方文檔。

          posted @ 2009-01-26 22:14 laogao 閱讀(2466) | 評(píng)論 (0)編輯 收藏


          ORM是個(gè)大話題,大到可能好幾本書都說不完。SQLAlchemy,別看它剛出到0.5.2,已然是Python世界ORM的事實(shí)標(biāo)準(zhǔn),受到眾多開發(fā)者和無數(shù)框架的青睞。

          如果之前沒有或很少接觸SQLAlchemy,那么學(xué)習(xí)Pylons可能有相當(dāng)一部分時(shí)間都會(huì)花在SQLAlchemy上。通常,人們選擇Pylons或者TurboGears而不是Django,SQLAlchemy在這些決定的背后有著很重的分量。作為Pylons學(xué)習(xí)筆記的一部分,接下來我將分4篇隨筆介紹SQLAlchemy,分別是Engine API、Schema Management (MetaData/Types)、SQL Expression Language和Object Relational Mapper。此文為第1篇,重點(diǎn)介紹Engine API。

          類似Java的JDBC,Python也有一個(gè)類似的數(shù)據(jù)庫(kù)訪問接口規(guī)范,那就是DB-API(目前是2.0),不同的常見RDBMS都有符合DB-API標(biāo)準(zhǔn)的Python庫(kù),比如PostgreSQL有psycopg2,Oracle有cx_Oracle等。有了這個(gè)基礎(chǔ),SQLAlchemy也就得以方便的通過DB-API連接不同的數(shù)據(jù)庫(kù)。

          以PostgreSQL為例,通過SQLAlchemy的Engine API訪問數(shù)據(jù)庫(kù)的代碼可以這樣來寫:
          ?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,并不是簡(jiǎn)單的DBAPI調(diào)用,而是包裝了其他內(nèi)容,如數(shù)據(jù)庫(kù)連接池,我們可以在create_engine的時(shí)候指定連接池參數(shù)和其他額外配置,類似這樣:
          engine?=?create_engine('postgres://user:pass@localhost/testdb',?pool_size=10, convert_unicode=True)

          類似Spring的JdbcTemplate,更多的時(shí)候,統(tǒng)一使用SQLAlchemy的Engine API而不是DB-API能給我們帶來更大的靈活性,通常也更方便、更安全。

          posted @ 2009-01-26 20:46 laogao 閱讀(2507) | 評(píng)論 (1)編輯 收藏


          在開始之前,說點(diǎn)提外話,隨著對(duì)Pylons了解的深入,你可能時(shí)不時(shí)需要看看相關(guān)組件/軟件包是否有更新出來,方法也不復(fù)雜,通過"easy_install -U [組件名]"即可,在學(xué)習(xí)或者是開發(fā)過程中,最好是保持環(huán)境相對(duì)較新,直到出現(xiàn)相對(duì)大的release或者即將進(jìn)入產(chǎn)品部署階段。

          繼續(xù)介紹Pylons組件,先看個(gè)例子。首先用"paster controller hello"增加一個(gè)controller,路徑中會(huì)增加出以下兩個(gè)文件:
          controllers/hello.py
          tests/functional/test_hello.py

          分別對(duì)應(yīng)新增的controller類HelloController和功能測(cè)試類TestHelloController,它們分別繼承自WSGIController->BaseController和TestCase->TestController。

          我們主要看hello.py,默認(rèn)內(nèi)容如下:
          ?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'

          如果你的服務(wù)器沒有Ctrl-C停掉,那么這個(gè)時(shí)候你已經(jīng)可以通過
          http://127.0.0.1:5000/hello/index
          看到該controller的處理結(jié)果了(Hello World)。

          簡(jiǎn)單改造一下17行:
          ????????from?pylons?import?config
          ????????
          return?'<br/>'.join(config.keys())

          我們就可以在返回頁(yè)面上顯示出所有可以通過pylons.config訪問到的參數(shù)列表。出了返回文本,也可以通過render()方法交給頁(yè)面模板引擎生成頁(yè)面,也可以通過redirect_to()跳轉(zhuǎn)到其他URL。

          Pylons是如何找到該請(qǐng)求應(yīng)該由HelloController的index方法來處理的呢?這背后發(fā)生了什么?答案就是Routes。

          Routes的作者是Ben Bangert,是Pylons框架三個(gè)主要作者/維護(hù)者之一,早期的版本主要是仿照Ruby on Rails的routes.rb開發(fā)的,有RoR經(jīng)驗(yàn)的朋友可能一眼就能發(fā)現(xiàn)它們之間的相似之處。目前Routes的最新版是1.10.2。

          Pylons應(yīng)用中,routing的配置在config/routing.py,默認(rèn)生成的內(nèi)容如下:
          ?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

          在這個(gè)配置中,對(duì)我們剛才的實(shí)例起到?jīng)Q定性作用的是第23行,我們的輸入U(xiǎn)RL為"http://127.0.0.1:5000/hello/index",其中"/hello/index"通過"/{controller}/{action}"這個(gè)表達(dá)式match出controller為hello而action為index的解析結(jié)果,從而在controllers目錄找到hello.py,和其中HelloController的index方法,進(jìn)行調(diào)用。

          map.connect()在上面代碼中體現(xiàn)出兩種用法:
          map.connect('pattern', key=value) - 指定默認(rèn)的controller、action、id等
          map.connect('pattern') - 直接指定pattern

          pattern字符串允許通配符,通常在最后一個(gè)元素上,比如'/{controller}/{action}/{*url}',將后面的整個(gè)URL片段交給前面指定的controller/action處理。除此以外,map.connect()還支持

          1- "路徑別名",如:
          map.connect('name', 'pattern', [_static=True])
          如果_static設(shè)為"True",表示為"靜態(tài)命名路徑"。
          2- 額外的匹配條件,如:
          map.connect('/{controller}/{action}/{id}', requirements={'year': '\d+',})
          map.connect('/{controller}/{action}/{id}', conditions=dict(method=['GET','POST']))

          所有的route優(yōu)先級(jí)為從上到下。Routes除了提供解析進(jìn)來的URL的邏輯,在我們的controller和template代碼中,我們還可以方便的通過WebHelpers的url_for()方法計(jì)算相應(yīng)的URL。

          Routes 1.x中的有一些仿routes.rb功能將會(huì)在2.0中被去掉,包括Route Minimization、Route Memory、Implicit Defaults等。如果有興趣的話,可以參考一下官方文檔,這里就不一一介紹了。為什么要去掉?當(dāng)然主要的動(dòng)機(jī)還是減少歧義,避免一些不必要的混淆。至于深層次的原因么,可以參考Tim Peters《The Zen of Python》中的一句經(jīng)典的Python哲學(xué):Explicit is better than implicit。什么?沒有聽說過?打開python命令行,輸入"import this"后回車,慢慢體會(huì)其中的道理吧。:)


          posted @ 2009-01-26 16:21 laogao 閱讀(3999) | 評(píng)論 (0)編輯 收藏


          大家新年好!

          前一篇隨筆中,大家了解了什么是Pylons,有哪些特點(diǎn),今天筆者繼續(xù)給介紹默認(rèn)生成的項(xiàng)目結(jié)構(gòu)。

          通過Paste創(chuàng)建新的Pylons應(yīng)用很簡(jiǎn)單,就是一句"paster create -t pylons [應(yīng)用名]"命令,其中"-t pylons"或者全稱"--template=pylons",用以告訴Paste我們新建的項(xiàng)目,將是一個(gè)Pylons應(yīng)用,或者說,從一個(gè)預(yù)定義好的Pylons默認(rèn)項(xiàng)目模板生成。如果你愿意,你也可以自己來制作新的模板以符合需要。"paster create --list-templates"可以查看當(dāng)前可用的模板。

          假定我們新建的Pylons項(xiàng)目名稱為NewApp,那么執(zhí)行"paster create -t pylons NewApp"后,我們將得到一個(gè)新的NewApp目錄,其中包含一個(gè)docs目錄(文檔)、一個(gè)newapp目錄(代碼)、一個(gè)NewApp.egg-info目錄(元數(shù)據(jù))和一些頂級(jí)配置文件。

          在開發(fā)過程中,我們經(jīng)常會(huì)用到的是代碼目錄(這里是newapp)和位于項(xiàng)目根目錄下的development.ini和test.ini文件了。注意這里newapp的大小寫,Pylons在生成項(xiàng)目的時(shí)候,不論你指定的項(xiàng)目名稱大小寫是怎樣,這里會(huì)自動(dòng).lower()轉(zhuǎn)小寫,只有項(xiàng)目頂級(jí)路徑和egg信息會(huì)保留原始大小寫,筆者認(rèn)為這更加符合Web應(yīng)用的nature。

          代碼目錄下包括config、controllers、lib、model、public、templates和tests子目錄,分別用于存放配置(如環(huán)境、中間件、路徑查找邏輯)、控制器(處理請(qǐng)求)、全局輔助代碼(全局變量、helpers等)、模型(后臺(tái)數(shù)據(jù)訪問)、靜態(tài)頁(yè)面/媒體文件、頁(yè)面模板和測(cè)試代碼。

          最后說說ini文件:默認(rèn)生成的development.ini和test.ini顧名思義分別對(duì)應(yīng)開發(fā)和測(cè)試需要的基本配置,我們可以通過修改相應(yīng)的參數(shù)配置來指定具體設(shè)定,如服務(wù)器IP和端口、數(shù)據(jù)庫(kù)連接、日志等。看到這里你也許會(huì)問,那么產(chǎn)品環(huán)境呢?答案是默認(rèn)沒有生成,你可以從development.ini修改成需要的產(chǎn)品環(huán)境配置,也可以通過paster make-config newapp production.ini生成一個(gè)默認(rèn)的產(chǎn)品環(huán)境典型配置。

          以development.ini為例,Pylons的ini文件主要包括4個(gè)部分的內(nèi)容:

          1- 全局默認(rèn)參數(shù),如
          [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- 服務(wù)器配置,如
          [server:main]
          use?
          =?egg:Paste#http
          host?
          =?127.0.0.1
          port?
          =?5000



          3- 應(yīng)用程序配置,如
          [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
          簡(jiǎn)單說明一下,這里的full_stack設(shè)置為true表示打開交互式調(diào)試和錯(cuò)誤報(bào)告等功能,"%(here)s"會(huì)被替換成項(xiàng)目所在路徑,類似相對(duì)路徑url中的"."轉(zhuǎn)絕對(duì)路徑,而beaker.*為配置會(huì)話相關(guān)的設(shè)定,如緩存、cookie基本內(nèi)容等。對(duì)于產(chǎn)品環(huán)境來說,比較重要的一個(gè)配置是"set debug=false",否則一旦出現(xiàn)異常,交互式調(diào)試將使得攻擊者能夠執(zhí)行系統(tǒng)命令。

          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 閱讀(997) | 評(píng)論 (0)編輯 收藏


          Pylons是一個(gè)Python語(yǔ)言的Web應(yīng)用程序框架,如果你簡(jiǎn)單了解過Ruby on Rails和Django,你大概會(huì)問,Pylons有什么不一樣呢?Pylons最大的特點(diǎn)是模塊化,將處理Web應(yīng)用環(huán)境下不同領(lǐng)域、不同問題的軟件包集成在一起,形成一個(gè)整體,在提供一攬子解決方案的同時(shí),不阻礙你選擇別的替代組件。另外,Pylons是目前對(duì)WSGI標(biāo)準(zhǔn)支持最好的框架之一,未來的TurboGears 2.0也會(huì)基于Pylons構(gòu)建。

          Pylons從Ruby on Rails借鑒了不少東西,比如Routes,比如WebHelpers,從表面看更像是Python版的RoR,不過底下的架構(gòu)應(yīng)該說更加輕量和靈活,因?yàn)槟憧梢造`活選擇自己熟悉或者更貼和具體應(yīng)用實(shí)際的組件,從ORM到頁(yè)面模板,Pylons只是推薦一些大家普遍比較認(rèn)可的選項(xiàng),但并不強(qiáng)制你使用它們。

          說完和Ruby on Rails的異同,當(dāng)然也要回過頭來說說同樣是Python編寫的Django。如果你只是想迅速的構(gòu)建一個(gè)可以支撐大量訪問的Web應(yīng)用,Django是個(gè)不錯(cuò)的選擇,但和RoR一樣,你在很大程度上被限制在一定的pattern中:如果你按照Django的思路去實(shí)現(xiàn)你的應(yīng)用,你會(huì)很happy;但一旦你覺得某個(gè)組件你不喜歡、不符合某個(gè)實(shí)際要求,想要來點(diǎn)定制,你就會(huì)覺得有些伸不開拳腳,或者工程浩大。目前感覺Django比較不爽的地方有:頁(yè)面模板較弱,表現(xiàn)力有些不足,也有人說夠用了;ORM目前是自己的一套,暫時(shí)沒有成熟的SQLAlchemy支持,需要第三方包或者自己做;從架構(gòu)上,Django對(duì)MVC的解讀是MTV(Model-Template-View),大家都叫作controller的東東,在Django的世界里是view,以至于每次和別人解釋,都要多費(fèi)一番口舌。

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

          首先你必須有Python(2.3+),然后你可以選擇直接easy_install Pylons或者新建一個(gè)Virtual Environment,和系統(tǒng)中的Python環(huán)境隔離開,依賴的包可以獨(dú)立升級(jí)。這里我們按照后一種方式,如果你是第一次使用Pylons,建議你也在獨(dú)立Python virtualenv中安裝。

          1- easy_install virtualenv (這將安裝Python虛擬環(huán)境工具)
          2- python virtualenv.py ENV (創(chuàng)建新的虛擬環(huán)境。這里的ENV是你新建虛擬環(huán)境的路徑,如"mydevenv")
          3- source ENV/bin/activate (激活虛擬環(huán)境。如果是Windows的話,這里需要執(zhí)行ENV\bin\activate.bat)
          4- easy_install Pylons (這里使用的是虛擬環(huán)境的easy_install安裝)

          如果你覺得上面的步驟麻煩,Pylons開發(fā)團(tuán)隊(duì)提供了一個(gè)腳本來處理安裝過程,下載后用Python執(zhí)行即可:
          http://www.pylonshq.com/download/0.9.7/go-pylons.py

          如果需要SQLAlchemy,則再執(zhí)行一下
          easy_install SQLAlchemy

          安裝成功后,通過
          paster create -t pylons [應(yīng)用名]
          即可新建Web應(yīng)用主框架,然后cd到應(yīng)用下,通過
          paster serve --reload development.ini
          啟動(dòng)Web服務(wù),默認(rèn)地址在
          http://127.0.0.1:5000/

          更詳細(xì)的信息,可參考Pylons項(xiàng)目主頁(yè):
          http://pylonshq.com/

          隨著使用的深入,筆者還會(huì)陸續(xù)對(duì)Pylons和其他相關(guān)組件進(jìn)行進(jìn)一步的介紹。祝各位農(nóng)歷新年快樂!


          posted @ 2009-01-25 20:05 laogao 閱讀(2037) | 評(píng)論 (2)編輯 收藏


          在Unix環(huán)境下,命令行或者shell中sleep和kill是常見的動(dòng)作,在Windows的.bat文件中處理類似的任務(wù)就不那么直接了,備忘如下:

          [sleep]
          ping 127.0.0.1 -n 需要的秒數(shù)+1 -w 1000 > nul

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

          其中/f表示強(qiáng)制,/im表示image鏡像名(可執(zhí)行文件名),/fi表示filter,后面跟表達(dá)式,比如這里的"窗體標(biāo)題等于notepad*",支持wildcast通配符。


          posted @ 2009-01-23 19:36 laogao 閱讀(2226) | 評(píng)論 (0)編輯 收藏

          僅列出標(biāo)題
          共34頁(yè): 上一頁(yè) 1 2 3 4 5 6 7 8 9 下一頁(yè) Last 
          主站蜘蛛池模板: 阜新| 淮阳县| 营山县| 佛山市| 阜新市| 安龙县| 施甸县| 卓资县| 伊吾县| 峨边| 开化县| 五莲县| 武定县| 南华县| 永川市| 铜梁县| 余江县| 巫溪县| 蒙城县| 会昌县| 鄂伦春自治旗| 东阳市| 温宿县| 永新县| 黑山县| 弥勒县| 四平市| 沙坪坝区| 浏阳市| 壶关县| 泉州市| 荔浦县| 兴和县| 永康市| 佛山市| 略阳县| 三河市| 成安县| 徐汇区| 方山县| 东宁县|