簡介

對象關系映射器(Object Relational Mappers,ORM)在過去數年吸引了不少人的目光。主要原因是 ORM 經常會在 Web 應用程序框架中被提起,因為它是快速開發(Rapid Development)棧中的關鍵組件。Django 和 Ruby on Rails 等 Web 框架采用了設計一個獨立棧的方法,將自主開發的 ORM 緊密集成到該框架中。而其他框架,如 Pylons、Turbogears 和 Grok,則采用更加基于組件的架構結合可交換的第三方組件。兩種方法都有各自的優勢:緊密集成允許非常連貫的體驗(如果問題映射到框架),而基于組件的架構則允許最大的設計靈活性。但是,本文的主題并不是 Web 框架;而是 SQLAlchemy。

SQLAlchemy 在構建在 WSGI 規范上的下一代 Python Web 框架中得到了廣泛應用,它是由 Mike Bayer 和他的核心開發人員團隊開發的一個單獨的項目。使用 ORM 等獨立 SQLAlchemy 的一個優勢就是它允許開發人員首先考慮數據模型,并能決定稍后可視化數據的方式(采用命令行工具、Web 框架還是 GUI 框架)。這與先決定使用 Web 框架或 GUI 框架,然后再決定如何在框架允許的范圍內使用數據模型的開發方法極為不同。

什么是 WSGI?

WSGI 是下一代 Python Web 框架、應用程序和服務器應該遵循的規范。WSGI 中 一個有趣的方面是創建 Python 中間件,并在使用 Python 或任何語言創建的 Web 應用程序中使用。請參閱 參考資料,獲取關于 WSGI 和 WSGI 社區(Pypefitters)的大量鏈接。

SQLAlchemy 的一個目標是提供能兼容眾多數據庫(如 SQLite、MySQL、Postgres、Oracle、MS-SQL、SQLServer 和 Firebird)的企業級持久性模型。SQLAlchemy 正處于積極開發階段,當前最新的 API 將圍繞版本 0.5 設計。請參閱參考資料部分,獲取官方 API 文檔、教程和 SQLAlchemy 書籍的鏈接。

SQLAlchemy 取得成功的一個證明就是圍繞它已建立了豐富的社區。針對 SQLAlchemy 的擴展和插件包括:declarative、Migrate、Elixir、SQLSoup、django-sqlalchemy、DBSprockets、FormAlchemy 和 z3c.sqlalchemy。在本文中,我們將學習一篇關于新 0.5 API 的教程,探究一些第三方庫,以及如何在 Pylons 中使用它們。

誰是 Mike Bayer?

Michael Bayer 是居住在紐約的一名軟件承包商,他擁有十余年處理各類關系數據庫的經驗。他曾使用 C、Java™ 和 Perl 編寫了許多自主研發的數據庫抽象層,并在 Major League Baseball 與大量多服務器 Oracle 系統打了多年交道,借助這些經驗,他成功編寫了 “終極工具包” SQLAlchemy,用于生成 SQL 和處理數據庫。其目標是貢獻一個世界級、獨樹一幟的面向 Python 的工具包,以幫助 Python 成為一個廣泛普及的編程平臺。

安裝

本文假定您使用 Python 2.5 或更高版本,并且安裝了子版本。Python 2.5 包括 SQLite 數據庫,因此也是測試 SQLALchemy 內存的好工具。如果您已經安裝了 Python 2.5,則只需通過設置工具安裝 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 安裝中。最后一個代碼片段將安裝 IPython,它是一個實用的聲明式 Python 解釋器,我將在本文中使用它。首先,我需要測試已安裝的 SQLAlchemy 版本。您可以測試自己的版本是否為 0.5.x,方法是在 IPython 或普通 Python 解釋器中發起以下命令。

 In [1]: import sqlalchemy      In [2]: sqlalchemy.__version__      Out[2]: '0.5.0beta1'      

SQLAlchemy 0.5 快速入門指南

新的 0.5 發行版在 SQLAlchemy 中引入了一些顯著的變更。此外列出了這些變更的概要信息:

  • 聲明式擴展是多數情況下建議的開始方式。
  • session.query() 可以接受任意組合的 class/column 表達式。
  • session.query() 或多或少也是 select() 的支持 ORM 的替代方法。
  • 查詢提供了一些試驗性的 update()/delete() 方法,用于實現基于標準的更新/刪除。
  • 會話將在 rollback() 和 commit() 方法后自動過期;因此使用默認的 sessionmaker() 意味著您通常不必調用 clear() 或 close();對象將自動與當前的事務同步。
  • 使用 session.add()、session.add_all()(save/update/save_or_update 已刪除)在會話中添加內容。

雖然 declarative 擴展從 0.4 開始便一直出現在 SQLAlchemy 中,但它也經過了一些小修改,這使它在大多數 SQLAlchemy 項目中都成為了一種強有力的便捷方式。新的 declarative 語法允許在一步中創建表、類和數據庫映射。下面我們來看看這種新語法的工作原理,以我編寫的一個用于跟蹤文件系統變化的工具為例。


清單 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)        

通過這種新的聲明樣式,SQLAlchemy 能夠在一步中創建一個數據庫表、創建一個類以及類與表之間的映射。如果您剛開始接觸 SQLAlchemy,或許應該學習這種建立 ORM 的方法。此外,了解另一種更加顯式地控制各步驟的方式也是有益的(如果您的項目要求這種級別的詳細程度)。

在閱讀這段代碼時,需要指出一些可能會讓初次接觸 SQLAlchemy 或聲明性擴展的用戶犯難的地方。首先,

Base = declarative_base()

行創建了一個類,稍后的 Filesystem 類便繼承自該類。如果您保存并在 declarative_style 中運行該代碼,然后將它導入到 IPython 中,則會看到以下輸出:

In [2]: declarative_style.Filesystem? Type:		DeclarativeMeta Base Class:	<class 'sqlalchemy.ext.declarative.DeclarativeMeta'> String Form:	<class 'declarative_style.Filesystem'>         

這個 DeclarativeMeta 類型的魔力就是允許所有操作發生在一個簡單的類定義中。

另一個需要指出的地方是本示例并未實際執行任何操作。在運行創建表的代碼之前,將不會創建實際的表,并且,您還需要定義 SQLAlchemy 將使用的數據庫引擎。這兩行代碼如下所示:

       engine = create_engine('sqlite:///meta.db', echo=True)        Base.metadata.create_all(engine)           

SQlite 是試驗 SQLAlchemy 的理想選擇,并且您還可以選擇使用內存數據庫,在這種情況下,您的代碼行應如下所示:

        engine = create_engine('sqlite:///:memory:', echo=True)       

或者,只創建一個簡單的文件,如第一個例子所示。如果您選擇創建一個基于 SQLite 文件的數據庫,則可以通過拋棄數據庫中的所有表從零開始,而不需要刪除文件。為此,您可以發起以下代碼行:

             Base.metadata.drop_all(engine)         

此時,我們已經了解了創建 SQLAlchemy 項目和通過 SQLAlchemy API 控制數據庫所需的知識。在開始實際應用之前,惟一需要掌握一點是會話的概念。SQLAlchemy “官方” 文檔將會話描述為數據庫的句柄。在實際應用中,它允許不同的基于事務的連接發生在 SQLAlchemy 一直在等待的連接池中。在會話內部,這通常是添加數據到數據庫中、執行查詢或刪除數據。

要創建會話,請執行下面這些后續步驟:

        #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 提供了一個非常復雜的 API 來處理許多復雜的事情,但它實際上非常容易使用。在本節結束時,我還想指出,上例使用 echo=True 創建引擎。這是查看由 SQLAlchemy 創建的 SQL 的便捷方法。對于 SQLAlchemy 初學者,強烈建議使用該方法,因為它會讓您覺得 SQLAlchemy 不再那么神秘。現在,運行自己創建的一些代碼,并查看 SQL 創建表的過程。


清單 2. SQLAlchemy SQL 表創建輸出
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:類似于 Spotlight 或 Beagle 的實時文件系統元數據索引程序

抽象地討論如何使用某個工具會讓許多人不好理解,因此,我將使用 SQLAlchemy 演示如何創建一個元數據工具。此工具的目標是監控文件系統、創建和刪除事件,以及在一個 SQLAlchemy 數據庫中保存這些變更的記錄。如果您曾經在 OS X 上使用過 Spotlight,或在 Linux® 上使用過 Beagle,那就應該使用過一款實時的文件系統索引工具。要繼續本文,您需要運行 Linux 內核 2.6.13 或更高版本。

下一個示例比較大,大約有 100 行代碼。查看整個示例并運行它,然后,我將介紹代碼各部分的作用。要運行此腳本,您必須在終端中執行以下步驟:

  1. wget http://peak.telecommunity.com/dist/ez_setup.py
  2. sudo python ez_setup.py
  3. sudo easy_install 
    "http://git.dbzteam.org/?p=pyinotify.git;a=snapshot;h=HEAD;sf=tgz"
  4. 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)       

要查看此腳本的實際運行結果,您需要打開兩個終端窗口。在第一個窗口中,運行 pylesystem.py 腳本。您將看到一系列輸出內容,如下所示(請注意,以下版本經過適當縮減):

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   

第一個腳本運行一個多線程文件系統事件監控引擎,它將 /tmp 的所有創建和刪除變更寫入到 sqlalchemy 數據庫中。注意:由于它是多線程的,當您 完成此教程時,需要鍵入 Control + \ 來停止線程應用程序。

成功運行之后,您可以在第二個終端窗口中創建事件,新創建或刪除的文件將實時添加到數據庫中或從數據庫中刪除。如果您只創建了 /tmp 目錄中的某個文件,比如說 touch foobar.txt,則會在第一個窗口中看到以下輸出:

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 嗎?鑒于此,當代碼將此新條目添加到文件系統中時,您可以看到 SQL 語句。如果您現在刪除該文件,您也可以看到刪除的過程。下面是您鍵入 rm 語句 rm foobar.txt 時的輸出:

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 類中,您添加了一個 transactional 方法,您將使用一個修飾類來處理將文件系統事件提交給數據庫的語義。Pyinotify 中的實際 Filesystem I/O 監控由 ProcessDir 類完成,該類繼承自 ProcessEvents 并覆蓋了其中的方法。如果您注意了 process_IN_CREATE 和 process_IN_DELETE 方法,會發現它們都附加了一個 transactional 修飾類。隨后,它們將接受創建或刪除事件并對數據庫執行修改。

還有一個名稱為 initial_repository 的方法,每次運行腳本時它都會填充數據庫,實現方法是銷毀數據庫中的表并重新創建。腳本的最底部將通知 Pyinotify 代碼以不確定的方式運行,而這最終表示作為守護進程運行。

結束語

本文介紹了 SQLAlchemy 的一些特性,并演示了它和 API 的使用是多么簡單。借助 SQLAlchemy 和開源庫 Pyinotify,您還使用不到 100 行 Python 代碼構建了一個 功能異常強大的工具。這是簡單但功能強大的 ORM 的特性之一。它消除了復雜的關系數據庫處理操作,現在它為用戶添加了快樂而不是負擔。隨后,這些省下來的精力可以用于解決感興趣的問題,因為 SQLAlchemy 將是最簡單的環節。

如果您有興趣了解更多關于 SQLAlchemy 的信息,則應該閱讀本文末尾列出的 參考資料。其中包括一本出色的書籍和大量優秀的在線文檔,您可以考慮研究其他一些使用 SQLAlchemy 的項目并擴展它們。最近一個較有興趣的 SQLAlchemy 相關項目就是 Website reddit.com。它使用純 WSGI 框架 Pylons 構建,并整合了 SQLAlchemy 作為其默認 ORM。我附帶了到 reddit 完整源代碼的鏈接。借助新掌握的 SQLAlchemy 知識,您應該能夠快速實現自己的 reddit,并且應該能執行一些數據庫查詢操作。祝您好運!


回頁首

下載

描述名字大小下載方法
SQLAlchemy 示例代碼sqlachemy_code.zip5KBHTTP

關于下載方法的信息


參考資料

學習

獲得產品和技術

  • IBM 試用軟件:使用可從 developerWorks 直接下載的軟件構建您的下一個開發項目。

討論

關于作者

http://www.ibm.com/developerworks/i/p-ngift.jpg

Noah Gift 是 O'Reilly 出版的“Python For Unix and Linux”一書的合著者。他是一名作家、演說家、顧問和社區負責人,并為 IBM developerWorks、Red Hat Magazine、O'Reilly 和 MacTech 眾多出版商撰稿。他的咨詢公司的網站是 www.giftc.com,他的個人網站是 www.noahgift.com。Noah 目前還是 www.pyatl.org 網站的組織者,該網站是佐治亞州亞特蘭大的 Python 用戶組。他擁有加州洛杉磯的 CIS 的碩士學位,加州 Poly San Luis Obispo 的營養科學學士學位,他還是通過 Apple 和 LPI 認證的系統管理員,他曾經在許多公司工作過,如加利福尼亞理工學院、Disney Feature Animation、Sony Imageworks 和 Turner Studios。在空閑的時候,他喜歡和妻子 Leah,以及他們的兒子 Liam 一起度過,彈奏鋼琴以及進行宗教活動。