| Noah Gift, 軟件工程師, Giftcs
2008 年 10 月 23 日 SQLAlchemy 是下一代的 Python Object Relational 映射器。通過(guò)本文您將了解如何使用新的 0.5 API、與第三方組件協(xié)作,并構建一個(gè)基本的 Web 應用程序。 簡(jiǎn)介 對 象關(guān)系映射器(Object Relational Mappers,ORM)在過(guò)去數年吸引了不少人的目光。主要原因是 ORM 經(jīng)常會(huì )在 Web 應用程序框架中被提起,因為它是快速開(kāi)發(fā)(Rapid Development)棧中的關(guān)鍵組件。Django 和 Ruby on Rails 等 Web 框架采用了設計一個(gè)獨立棧的方法,將自主開(kāi)發(fā)的 ORM 緊密集成到該框架中。而其他框架,如 Pylons、Turbogears 和 Grok,則采用更加基于組件的架構結合可交換的第三方組件。兩種方法都有各自的優(yōu)勢:緊密集成允許非常連貫的體驗(如果問(wèn)題映射到框架),而基于組件的 架構則允許最大的設計靈活性。但是,本文的主題并不是 Web 框架;而是 SQLAlchemy。 SQLAlchemy 在構建在 WSGI 規范上的下一代 Python Web 框架中得到了廣泛應用,它是由 Mike Bayer 和他的核心開(kāi)發(fā)人員團隊開(kāi)發(fā)的一個(gè)單獨的項目。使用 ORM 等獨立 SQLAlchemy 的一個(gè)優(yōu)勢就是它允許開(kāi)發(fā)人員首先考慮數據模型,并能決定稍后可視化數據的方式(采用命令行工具、Web 框架還是 GUI 框架)。這與先決定使用 Web 框架或 GUI 框架,然后再決定如何在框架允許的范圍內使用數據模型的開(kāi)發(fā)方法極為不同。 | | 什么是 WSGI? WSGI 是下一代 Python Web 框架、應用程序和服務(wù)器應該遵循的規范。WSGI 中 一個(gè)有趣的方面是創(chuàng )建 Python 中間件,并在使用 Python 或任何語(yǔ)言創(chuàng )建的 Web 應用程序中使用。請參閱 參考資料,獲取關(guān)于 WSGI 和 WSGI 社區(Pypefitters)的大量鏈接。 | | SQLAlchemy 的一個(gè)目標是提供能兼容眾多數據庫(如 SQLite、MySQL、Postgres、Oracle、MS-SQL、SQLServer 和 Firebird)的企業(yè)級持久性模型。SQLAlchemy 正處于積極開(kāi)發(fā)階段,當前最新的 API 將圍繞版本 0.5 設計。請參閱參考資料部分,獲取官方 API 文檔、教程和 SQLAlchemy 書(shū)籍的鏈接。 SQLAlchemy 取得成功的一個(gè)證明就是圍繞它已建立了豐富的社區。針對 SQLAlchemy 的擴展和插件包括:declarative、Migrate、Elixir、SQLSoup、django-sqlalchemy、 DBSprockets、FormAlchemy 和 z3c.sqlalchemy。在本文中,我們將學(xué)習一篇關(guān)于新 0.5 API 的教程,探究一些第三方庫,以及如何在 Pylons 中使用它們。 | | 誰(shuí)是 Mike Bayer? Michael Bayer 是居住在紐約的一名軟件承包商,他擁有十余年處理各類(lèi)關(guān)系數據庫的經(jīng)驗。他曾使用 C、Java? 和 Perl 編寫(xiě)了許多自主研發(fā)的數據庫抽象層,并在 Major League Baseball 與大量多服務(wù)器 Oracle 系統打了多年交道,借助這些經(jīng)驗,他成功編寫(xiě)了 “終極工具包” SQLAlchemy,用于生成 SQL 和處理數據庫。其目標是貢獻一個(gè)世界級、獨樹(shù)一幟的面向 Python 的工具包,以幫助 Python 成為一個(gè)廣泛普及的編程平臺。 | | 安裝 本文假定您使用 Python 2.5 或更高版本,并且安裝了子版本。Python 2.5 包括 SQLite 數據庫,因此也是測試 SQLALchemy 內存的好工具。如果您已經(jīng)安裝了 Python 2.5,則只需通過(guò)設置工具安裝 sqlalchemy 0.5 beta 。要獲取設置工具腳本,請在您的終端中下載并運行以下 4 條命令: wget http://peak.telecommunity.com/dist/ez_setup.py python ez_setup.py sudo easy_install http://svn.sqlalchemy.org/sqlalchemy/trunk sudo easy_install ipython | 前 三行代碼檢查最新版本的 sqlalchemy,并將它作為包添加到您本地系統的 Python 安裝中。最后一個(gè)代碼片段將安裝 IPython,它是一個(gè)實(shí)用的聲明式 Python 解釋器,我將在本文中使用它。首先,我需要測試已安裝的 SQLAlchemy 版本。您可以測試自己的版本是否為 0.5.x,方法是在 IPython 或普通 Python 解釋器中發(fā)起以下命令。 In [1]: import sqlalchemy In [2]: sqlalchemy.__version__ Out[2]: '0.5.0beta1' | SQLAlchemy 0.5 快速入門(mén)指南 新的 0.5 發(fā)行版在 SQLAlchemy 中引入了一些顯著(zhù)的變更。此外列出了這些變更的概要信息: - 聲明式擴展是多數情況下建議的開(kāi)始方式。
- session.query() 可以接受任意組合的 class/column 表達式。
- session.query() 或多或少也是 select() 的支持 ORM 的替代方法。
- 查詢(xún)提供了一些試驗性的 update()/delete() 方法,用于實(shí)現基于標準的更新/刪除。
- 會(huì )話(huà)將在 rollback() 和 commit() 方法后自動(dòng)過(guò)期;因此使用默認的 sessionmaker() 意味著(zhù)您通常不必調用 clear() 或 close();對象將自動(dòng)與當前的事務(wù)同步。
- 使用 session.add()、session.add_all()(save/update/save_or_update 已刪除)在會(huì )話(huà)中添加內容。
雖然 declarative 擴展從 0.4 開(kāi)始便一直出現在 SQLAlchemy 中,但它也經(jīng)過(guò)了一些小修改,這使它在大多數 SQLAlchemy 項目中都成為了一種強有力的便捷方式。新的 declarative 語(yǔ)法允許在一步中創(chuàng )建表、類(lèi)和數據庫映射。下面我們來(lái)看看這種新語(yǔ)法的工作原理,以我編寫(xiě)的一個(gè)用于跟蹤文件系統變化的工具為例。 清單 1. 新 SQLAlchemy 聲明樣式 #/usr/bin/env python2.5 #Noah Gift from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey Base = declarative_base() class Filesystem(Base): __tablename__ = 'filesystem' path = Column(String, primary_key=True) name = Column(String) def __init__(self, path,name): self.path = path self.name = name def __repr__(self): return "<Metadata('%s','%s')>" % (self.path,self.name) | 通 過(guò)這種新的聲明樣式,SQLAlchemy 能夠在一步中創(chuàng )建一個(gè)數據庫表、創(chuàng )建一個(gè)類(lèi)以及類(lèi)與表之間的映射。如果您剛開(kāi)始接觸 SQLAlchemy,或許應該學(xué)習這種建立 ORM 的方法。此外,了解另一種更加顯式地控制各步驟的方式也是有益的(如果您的項目要求這種級別的詳細程度)。 在閱讀這段代碼時(shí),需要指出一些可能會(huì )讓初次接觸 SQLAlchemy 或聲明性擴展的用戶(hù)犯難的地方。首先, Base = declarative_base() | 行創(chuàng )建了一個(gè)類(lèi),稍后的 Filesystem 類(lèi)便繼承自該類(lèi)。如果您保存并在 declarative_style 中運行該代碼,然后將它導入到 IPython 中,則會(huì )看到以下輸出: In [2]: declarative_style.Filesystem? Type: DeclarativeMeta Base Class: <class 'sqlalchemy.ext.declarative.DeclarativeMeta'> String Form: <class 'declarative_style.Filesystem'> | 這個(gè) DeclarativeMeta 類(lèi)型的魔力就是允許所有操作發(fā)生在一個(gè)簡(jiǎn)單的類(lèi)定義中。 另一個(gè)需要指出的地方是本示例并未實(shí)際執行任何操作。在運行創(chuàng )建表的代碼之前,將不會(huì )創(chuàng )建實(shí)際的表,并且,您還需要定義 SQLAlchemy 將使用的數據庫引擎。這兩行代碼如下所示: engine = create_engine('sqlite:///meta.db', echo=True) Base.metadata.create_all(engine) | SQlite 是試驗 SQLAlchemy 的理想選擇,并且您還可以選擇使用內存數據庫,在這種情況下,您的代碼行應如下所示: engine = create_engine('sqlite:///:memory:', echo=True) | 或者,只創(chuàng )建一個(gè)簡(jiǎn)單的文件,如第一個(gè)例子所示。如果您選擇創(chuàng )建一個(gè)基于 SQLite 文件的數據庫,則可以通過(guò)拋棄數據庫中的所有表從零開(kāi)始,而不需要刪除文件。為此,您可以發(fā)起以下代碼行: Base.metadata.drop_all(engine) | 此 時(shí),我們已經(jīng)了解了創(chuàng )建 SQLAlchemy 項目和通過(guò) SQLAlchemy API 控制數據庫所需的知識。在開(kāi)始實(shí)際應用之前,惟一需要掌握一點(diǎn)是會(huì )話(huà)的概念。SQLAlchemy “官方” 文檔將會(huì )話(huà)描述為數據庫的句柄。在實(shí)際應用中,它允許不同的基于事務(wù)的連接發(fā)生在 SQLAlchemy 一直在等待的連接池中。在會(huì )話(huà)內部,這通常是添加數據到數據庫中、執行查詢(xún)或刪除數據。 要創(chuàng )建會(huì )話(huà),請執行下面這些后續步驟: #establish Session type, only need to be done once for all sessions Session = sessionmaker(bind=engine) #create record object create_record = Filesystem("/tmp/foo.txt", "foo.txt") #make a unique session session = Session() #do stuff in session. We are adding a record here session.add(create_record) #commit the transaction session.commit() | 這 些就是使 SQLAlchemy 正常運行所需的所有工作。雖然 SQLAlchemy 提供了一個(gè)非常復雜的 API 來(lái)處理許多復雜的事情,但它實(shí)際上非常容易使用。在本節結束時(shí),我還想指出,上例使用 echo=True 創(chuàng )建引擎。這是查看由 SQLAlchemy 創(chuàng )建的 SQL 的便捷方法。對于 SQLAlchemy 初學(xué)者,強烈建議使用該方法,因為它會(huì )讓您覺(jué)得 SQLAlchemy 不再那么神秘?,F在,運行自己創(chuàng )建的一些代碼,并查看 SQL 創(chuàng )建表的過(guò)程。 清單 2. SQLAlchemy SQL 表創(chuàng )建輸出 2008-06-22 05:33:46,403 INFO sqlalchemy.engine.base.Engine.0x..ec PRAGMA table_info("filesystem") 2008-06-22 05:33:46,404 INFO sqlalchemy.engine.base.Engine.0x..ec {} 2008-06-22 05:33:46,405 INFO sqlalchemy.engine.base.Engine.0x..ec CREATE TABLE filesystem ( path VARCHAR NOT NULL, name VARCHAR, PRIMARY KEY (path) ) | Pylesystem:類(lèi)似于 Spotlight 或 Beagle 的實(shí)時(shí)文件系統元數據索引程序 抽 象地討論如何使用某個(gè)工具會(huì )讓許多人不好理解,因此,我將使用 SQLAlchemy 演示如何創(chuàng )建一個(gè)元數據工具。此工具的目標是監控文件系統、創(chuàng )建和刪除事件,以及在一個(gè) SQLAlchemy 數據庫中保存這些變更的記錄。如果您曾經(jīng)在 OS X 上使用過(guò) Spotlight,或在 Linux? 上使用過(guò) Beagle,那就應該使用過(guò)一款實(shí)時(shí)的文件系統索引工具。要繼續本文,您需要運行 Linux 內核 2.6.13 或更高版本。 下一個(gè)示例比較大,大約有 100 行代碼。查看整個(gè)示例并運行它,然后,我將介紹代碼各部分的作用。要運行此腳本,您必須在終端中執行以下步驟: - wget http://peak.telecommunity.com/dist/ez_setup.py
- sudo python ez_setup.py
- sudo easy_install
"http://git.dbzteam.org/?p=pyinotify.git;a=snapshot;h=HEAD;sf=tgz" - sudo easy_install http://svn.sqlalchemy.org/sqlalchemy/trunk
清單 3. 文件系統事件監控數據庫 #/usr/bin/env python2.5 #Noah Gift 06/21/08 #tweaks by Mike Bayer 06/22/08 import os from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import scoped_session from pyinotify import * path = "/tmp" #SQLAlchemy engine = create_engine('sqlite:///meta.db', echo=True) Base = declarative_base() Session = scoped_session(sessionmaker(bind=engine)) class Filesystem(Base): __tablename__ = 'filesystem' path = Column(String, primary_key=True) name = Column(String) def __init__(self, path,name): self.path = path self.name = name def __repr__(self): return "<Metadata('%s','%s')>" % (self.path,self.name) def transactional(fn): """add transactional semantics to a method.""" def transact(self, *args): session = Session() try: fn(self, session, *args) session.commit() except: session.rollback() raise transact.__name__ = fn.__name__ return transact class ProcessDir(ProcessEvent): """Performs Actions based on mask values""" @transactional def process_IN_CREATE(self, session, event): print "Creating File and File Record:", event.pathname create_record = Filesystem(event.pathname, event.path) session.add(create_record) @transactional def process_IN_DELETE(self, session, event): print "Removing:", event.pathname delete_record = session.query(Filesystem). filter_by(path=event.pathname).one() session.delete(delete_record) def init_repository(): #Drop the table, then create again with each run Base.metadata.drop_all(engine) Base.metadata.create_all(engine) session = Session() #Initial Directory Walking Addition Brute Force for dirpath, dirnames, filenames in os.walk(path): for file in filenames: fullpath = os.path.join(dirpath, file) record = Filesystem(fullpath, file) session.add(record) session.flush() for record in session.query(Filesystem): print "Database Record Number: Path: %s , File: %s " % (record.path, record.name) session.commit() if __name__ == "__main__": init_repository() #Pyionotify wm = WatchManager() mask = IN_DELETE | IN_CREATE notifier = ThreadedNotifier(wm, ProcessDir()) notifier.start() wdd = wm.add_watch(path, mask, rec=True) | 要查看此腳本的實(shí)際運行結果,您需要打開(kāi)兩個(gè)終端窗口。在第一個(gè)窗口中,運行 pylesystem.py 腳本。您將看到一系列輸出內容,如下所示(請注意,以下版本經(jīng)過(guò)適當縮減): 2008-06-22 07:18:08,707 INFO sqlalchemy.engine.base.Engine.0x..ec ['/tmp/ba.txt', 'ba.txt'] 2008-06-22 07:18:08,710 INFO sqlalchemy.engine.base.Engine.0x..ec COMMIT 2008-06-22 07:18:08,715 INFO sqlalchemy.engine.base.Engine.0x..ec BEGIN 2008-06-22 07:18:08,716 INFO sqlalchemy.engine.base.Engine.0x..ec SELECT filesystem.path AS filesystem_path, filesystem.name AS filesystem_name FROM filesystem 2008-06-22 07:18:08,716 INFO sqlalchemy.engine.base.Engine.0x..ec [] Database Record Number: Path: /tmp/ba.txt , File: ba.txt | 第一個(gè)腳本運行一個(gè)多線(xiàn)程文件系統事件監控引擎,它將 /tmp 的所有創(chuàng )建和刪除變更寫(xiě)入到 sqlalchemy 數據庫中。注意:由于它是多線(xiàn)程的,當您 完成此教程時(shí),需要鍵入 Control + \ 來(lái)停止線(xiàn)程應用程序。 成功運行之后,您可以在第二個(gè)終端窗口中創(chuàng )建事件,新創(chuàng )建或刪除的文件將實(shí)時(shí)添加到數據庫中或從數據庫中刪除。如果您只創(chuàng )建了 /tmp 目錄中的某個(gè)文件,比如說(shuō) touch foobar.txt,則會(huì )在第一個(gè)窗口中看到以下輸出: Creating File and File Record: /tmp/foobar.txt 2008-06-22 08:02:19,468 INFO sqlalchemy.engine.base.Engine.0x..4c BEGIN 2008-06-22 08:02:19,471 INFO sqlalchemy.engine.base.Engine.0x..4c INSERT INTO filesystem (path, name) VALUES (?, ?) 2008-06-22 08:02:19,472 INFO sqlalchemy.engine.base.Engine.0x..4c ['/tmp/foobar.txt', '/tmp'] 2008-06-22 08:02:19,473 INFO sqlalchemy.engine.base.Engine.0x..4c COMMIT | 記得您之前啟用了 SQL echo 嗎?鑒于此,當代碼將此新條目添加到文件系統中時(shí),您可以看到 SQL 語(yǔ)句。如果您現在刪除該文件,您也可以看到刪除的過(guò)程。下面是您鍵入 rm 語(yǔ)句 rm foobar.txt 時(shí)的輸出: Removing: /tmp/foobar.txt 2008-06-22 08:06:01,727 INFO sqlalchemy.engine.base.Engine.0x..4c BEGIN 2008-06-22 08:06:01,733 INFO sqlalchemy.engine.base.Engine.0x..4c SELECT filesystem.path AS filesystem_path, filesystem.name AS filesystem_name FROM filesystem WHERE filesystem.path = ? LIMIT 2 OFFSET 0 2008-06-22 08:06:01,733 INFO sqlalchemy.engine.base.Engine.0x..4c ['/tmp/foobar.txt'] 2008-06-22 08:06:01,736 INFO sqlalchemy.engine.base.Engine.0x..4c DELETE FROM filesystem WHERE filesystem.path = ? 2008-06-22 08:06:01,736 INFO sqlalchemy.engine.base.Engine.0x..4c [u'/tmp/foobar.txt'] 2008-06-22 08:06:01,737 INFO sqlalchemy.engine.base.Engine.0x..4c COMMIT | 在 Filesystem 類(lèi)中,您添加了一個(gè) transactional 方法,您將使用一個(gè)修飾類(lèi)來(lái)處理將文件系統事件提交給數據庫的語(yǔ)義。Pyinotify 中的實(shí)際 Filesystem I/O 監控由 ProcessDir 類(lèi)完成,該類(lèi)繼承自 ProcessEvents 并覆蓋了其中的方法。如果您注意了 process_IN_CREATE 和 process_IN_DELETE 方法,會(huì )發(fā)現它們都附加了一個(gè) transactional 修飾類(lèi)。隨后,它們將接受創(chuàng )建或刪除事件并對數據庫執行修改。 還有一個(gè)名稱(chēng)為 initial_repository 的方法,每次運行腳本時(shí)它都會(huì )填充數據庫,實(shí)現方法是銷(xiāo)毀數據庫中的表并重新創(chuàng )建。腳本的最底部將通知 Pyinotify 代碼以不確定的方式運行,而這最終表示作為守護進(jìn)程運行。 結束語(yǔ) 本文介紹了 SQLAlchemy 的一些特性,并演示了它和 API 的使用是多么簡(jiǎn)單。借助 SQLAlchemy 和開(kāi)源庫 Pyinotify,您還使用不到 100 行 Python 代碼構建了一個(gè) 功能異常強大的工具。這是簡(jiǎn)單但功能強大的 ORM 的特性之一。它消除了復雜的關(guān)系數據庫處理操作,現在它為用戶(hù)添加了快樂(lè )而不是負擔。隨后,這些省下來(lái)的精力可以用于解決感興趣的問(wèn)題,因為 SQLAlchemy 將是最簡(jiǎn)單的環(huán)節。 如果您有興趣了解更多關(guān)于 SQLAlchemy 的信息,則應該閱讀本文末尾列出的 參考資料。 其中包括一本出色的書(shū)籍和大量?jì)?yōu)秀的在線(xiàn)文檔,您可以考慮研究其他一些使用 SQLAlchemy 的項目并擴展它們。最近一個(gè)較有興趣的 SQLAlchemy 相關(guān)項目就是 Website reddit.com。它使用純 WSGI 框架 Pylons 構建,并整合了 SQLAlchemy 作為其默認 ORM。我附帶了到 reddit 完整源代碼的鏈接。借助新掌握的 SQLAlchemy 知識,您應該能夠快速實(shí)現自己的 reddit,并且應該能執行一些數據庫查詢(xún)操作。祝您好運! |