SQLAlchemy 是下一代的 Python Object Relational 映射器。通過本文您將了解如何使用新的 0.5 API、與第三方組件協作,並構建一個基本的 Web 應用程序。
簡介
對象關係映射器(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 框架,然後再決定如何在框架允許的範圍內使用數據模型的開發方法極為不同。
|
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 中使用它們。
|
安裝
本文假定您使用 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 中引入了一些顯著的變更。此外列出了這些變更的概要信息:
雖然 declarative 擴展從 0.4 開始便一直出現在 SQLAlchemy 中,但它也經過了一些小修改,這使它在大多數 SQLAlchemy 項目中都成為了一種強有力的便捷方式。新的 declarative 語法允許在一步中創建表、類和資料庫映射。下面我們來看看這種新語法的工作原理,以我編寫的一個用於跟蹤文件系統變化的工具為例。
#/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() |
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 創建表的過程。
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 行代碼。查看整個示例並運行它,然後,我將介紹代碼各部分的作用。要運行此腳本,您必須在終端中執行以下步驟:
#/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,並且應該能執行一些資料庫查詢操作。祝您好運! (責任編輯:A6)
[火星人 ] 使用 SQLAlchemy已經有699次圍觀