簡介
Couchdb-python 是目前最常用的操作 CouchDB 的第三方 Python 庫,其提供了幾乎所有 CouchDB RESTful 介面的功能,主要包括三個模組:
- Client 模組:提供與 CouchDB server 的互動,對資料庫的基本操作如增刪改查,操作 temporary view 等功能包括在該模組中
- View 模組:為使用者提供操作 CouchDB 中預定義檢視的介面
- Mapping 模組:將 Python 物件與 CouchDB 的 JSON 文件對映在一起,在進行物件導向程式設計時十分有用
本文將重點介紹前兩個模組中的重要方法的使用。
Couchdb-python 下載地址。
安裝方法同普通的第三方 Python 庫,安裝後只需使用 import couchdb 語句即可匯入全部 couchdb-python 中的所有模組。
Client.server 中提供的方法
該模組提供了一系列獲取 CouchDB server 資訊的方法,通過這些方法使用者可以獲取關於 server 的各種狀態資訊。但在使用這些方法之前,首先要獲取 server 實體,方法如下:
1 |
server = couchdb.Server('http://yourhost:5984/') |
CouchDB 預設使用 5984 埠,我們可根據實際情況填入不同引數,如需獲取 admin 賬戶許可權,可使用帶有使用者名稱密碼的 host 地址:
1 |
server=couchdb.Server('http://couchdb:passw0rd@10.120.138.211:5984/') |
通過以上兩種方法獲取到 server 實體後,即可使用其他方法獲得 server 相關資訊。
表格 1 中描述了 server 中提供的常用方法:
通過表格中所列的 db=server[name] 或 db=create(name)獲取資料庫實體 db 後,即可使用其他方法對資料庫中的各種資料進行操作, 這裡我們建立一個名為”test”的資料庫。
使用 update(documents) 批量插入和更新資料
儘管我們可以通過建立一個 dict 型別的資料並使用 db.save(dict) 方法將一條記錄插入資料庫,但實際的專案中使用 update(documents) 方法可以更加高效地插入、更新一條或多條資料,因為 update(documents) 方法將多條記錄的資料包裹在一個 request 請求中一次性地傳送給 CouchDB server。
update(documents) 的引數及返回值說明如下:
引數:documents–包含一組 dict 物件的陣列,dict 物件即為要插入或更新的資料。
返回值:三元組列表 (success, docid, rev_or_exc)–列表元素依次對應 documents 中的元素,success 為布林型,表示是否更新成功,docid 表示對應的文件“_id”,rev_or_exc 表示新紀錄的版本號“_rev”或更新失敗的異常資訊。
這裡需要明確一個概念,CouchDB 中“_id”欄位用來唯一地標識一條記錄,“_rev”欄位用來表示一條記錄的更新版本號。任何的資料修改操作(除了 delete)都將在原資料的基礎上 append 一條新資料,並遞增原資料的“_rev”欄位。也就是說,CouchDB 中不存在資料覆蓋,舊資料仍然儲存在資料庫中,並可通過之前的“_rev”值找到,這也是 CouchDB 本身的一個特性。如圖 1 所示:
圖 1. 未經任何修改的一條新記錄
我們在 test 資料庫中建立了一條記錄,除“_id”和”_rev”外,此記錄還有 age,company,hometown 和 name 欄位。此時該記錄未經任何修改,其“_rev”欄位的首位為 1。將其 hometwon 欄位修改後, 此時“_rev”欄位的首位遞增為 2,但之前“_rev”欄位首位為 1 的記錄仍可通過點選 futon 中記錄左下角的“previous version”按鈕找到,如圖 2 所示:
圖 2. 記錄更新後“_rev”欄位遞增
基於 CouchDB 的這種特性,當我們想要通過 update(documents) 批量插入資料時,只需將要插入的資料 dict 加入 documents 陣列,如若未指定“_rev”和“_id”欄位,系統將自動產生;當需要批量更新資料時,不論更新哪個欄位,除賦予該欄位新值外,都必須完整地在 dict 中加入該記錄所有其他欄位(包括“_rev”和“_id”),否則記錄被更新後將丟失未列出的欄位。至於如何一次性地將包含“_rev”和“_id”欄位的整條記錄取出為一個 dict, 將在介紹 view 時提到。插入和更新的示例程式碼如下:
插入三條記錄:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
docs = [ dict(name=<em>'Mary'</em>,age=<em>'20'</em>,hometown=<em>'</em><em>Shenzhen</em><em>'</em>,company=<em>'NEC'</em> ), dict(name=<em>'</em><em>Leo</em><em>'</em>,age=<em>'45'</em>,hometown=<em>'</em><em>Wuhan</em><em>'</em>,company=<em>'MS'</em> ), dict(name=<em>'</em><em>Kata</em><em>'</em>,age=<em>'22'</em>,hometown=<em>'</em><em>Chengdu</em><em>'</em>,company=<em>'IBM'</em> ) ] resultList=db.update(docs) updateNum=0 for item in resultList: if(item[0]): updateNum+=1 else: log.info(<em>'%s </em><em>db</em><em>[%s]'</em> %(item[2],item[1])) log.info(<em>'%s update successfully\n'</em> %updateNum) |
迴圈遍歷 update() 的返回值 list 是為了記錄日誌,明確地知道是否資料全部插入成功,如若失敗,是什麼原因導致了哪些記錄插入失敗。
如圖 3,通過 futon 可以看到,三條新紀錄產生。
圖 3. 插入 3 條記錄成功,“_rev”值首位均為 1
更新上面三條記錄的 company 為“IBM China”,獲取三條記錄的完整欄位,存入一個列表 docs,之後呼叫 update 方法即可。
程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
docs = [dict( _id='bd50bad62946f07e202112a04b00d85e', _rev='1-df98f39480c1bfc022130732f8a3469c' name='Mary',age='20',hometown='Shenzhen',company='IBM China' ), dict( _id='bd50bad62946f07e202112a04b00df17', _rev='1-aa008ff2e24dc68a4b696c46fcd08540', name='Leo',age='45',hometown='Wuhan',company='IBM China' ), dict( _id='bd50bad62946f07e202112a04b00eeaa', _rev='1-80b875494e4672b6a8f623ef4ab7ffe8, name='Kata',age='22',hometown='Chengdu',company='IBM China' ) ] resultList=db.update(docs) updateNum=0 for item in resultList: if(item[0]): updateNum+=1 else: log.info('%s db[%s]' %(item[2],item[1])) log.info('%s update successfully\n' %updateNum) |
如圖 4,可以看到更新成功,且更新後的“_rev”值首位已經遞增為 2
圖 4. 更新 3 條記錄成功,“_rev”值首位遞增為 2
在實際開發過程中,如若需要批量插入資料,只需將每條資料拼成 Python 的 dict 格式,然後將所有 dict 放進列表並呼叫一次 update 方法即可;如若要更新資料,需要首先從資料庫中獲取帶有完整欄位的記錄,CouchDB 提供了一種功能強大的檢視功能,藉助檢視就可以將需要的記錄完整的取出,下面我們介紹檢視相關的方法。
使用 query() 和 view() 查詢檢視
CouchDB 中的 view 使得使用者可以靈活快速地查詢資料,實現類似 SQL 中 select 的功能,同時 view 也是 CouchDB 中實現資料 index 的一個過程,通過 JavaScript 語言編寫的的 map function 來實現。view 分為 temporary view 和 predefined view 兩種。
temporary view
由於 temporary view 所定義的 map function 和資料的 index 檔案並沒有真正儲存在資料庫中,使用者可在程式中即寫即用。因此它常用來快速地驗證 map function 的功能。但正因為如此,每次呼叫 temporary view 都將對資料臨時建立一次 index,在驗證資料量比較大的資料庫時,temporary view 的查詢時間將會很慢。
test 資料庫中現在有 4 條記錄,其中有兩條記錄的 hometown 欄位為“Shenzhen”,我們可以在 Python 中編寫如下的 map function 來構建一個簡單的 temporary view。儘管是在 Python 中,但 map function 的部分仍然需要使用 JavaScript 的語法:
1 2 3 4 5 |
map_fun ='''function(doc){ if(doc.hometown=="Shenzhen"){ emit(doc.age, doc); } }''' |
Map function 以 doc 作為唯一的引數,代表資料庫中的一條記錄。函式將檢視記錄中的 hometown 欄位是否為“Shenzhen”,如果是,將呼叫內建的 emit(arg1,arg2) 方法。emit() 函式的兩個引數中,第一個為 key,也即 index,可以是單一欄位,也可以是多個欄位組成的陣列,這裡我們以 age 欄位作為 key;第二個為 value,即將要 emit 出的結果,如果設為 doc,將 emit 整條記錄;如果設為 doc 的某個欄位,如 doc.name, 將只 emit 該欄位。這裡我們將 emit 出整條記錄 doc。
Couchdb-python 中用來執行 temporary view 的方法是 query(), 其引數如下:
query(map_fun, reduce_fun=None, language=’javascript’, wrapper=None, **options)
map_fun :map 函式名
reduce_fun:reduce 函式名 (可選)
language :函式語言,預設為 JavaScript
wrapper : 一個可選的引數,用來包裹查詢結果,預設為空
options : 可選的查詢引數,如 key=’yourkey’,descending=True
返回型別:List
此時就可以使用 query() 方法來獲取 temporary view 的結果了
1 2 3 4 |
for row in db.query(map_fun,descending=True): print row.key print row.value print row |
這裡我們遍歷 temporary view 的結果並依次列印出 key,value 和整條記錄。options 引數使用 descending =True 將結果進行降序排序。
列印結果如下:
1 2 3 4 5 6 |
25 {u'name': u'Johnson', u'hometown': u'Shenzhen', u'age': u'25', u'_id': u'bd50bad62946f07e202112a04b00bb52', u'company': u'IBM', u'_rev': u'5-6b8e641ab94b58c7782461f2d657de3d'} <Row id=u'bd50bad62946f07e202112a04b00bb52', key=u'25', value={u'name': u'Johnson', u'hometown': u'Shenzhen', u'age': u'25', u'_id': u'bd50bad62946f07e202112a04b00bb52', u'company': u'IBM', u'_rev': u'5-6b8e641ab94b58c7782461f2d657de3d'}> 20 {u'name': u'Mary', u'hometown': u'Shenzhen', u'age': u'20', u'_id': u'bd50bad62946f07e202112a04b00d85e', u'company': u'IBM China', u'_rev': u'2-6f8727ba21418735f944663667b2421c'} <Row id=u'bd50bad62946f07e202112a04b00d85e', key=u'20', value={u'name': u'Mary', u'hometown': u'Shenzhen', u'age': u'20', u'_id': u'bd50bad62946f07e202112a04b00d85e', u'company': u'IBM China', u'_rev': u'2-6f8727ba21418735f944663667b2421c'}> |
Predefined view
Predefined view 需要使用者先在資料庫中建立對應的 design doc,map function 作為 design doc 的一部分寫在 design doc 中。design doc 的結構如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
design_doc = {'_id': '_design/yourDisgnName, 'views': { 'yourViewName1': { 'map': 'function(doc){emit(doc.age,doc);' }, 'yourViewName2': { 'map': 'function(doc){emit(doc.hometown,doc);' } ... } } |
每個 design doc 必須包括‘_id’和‘view’欄位,且‘_id’欄位的值必須為‘_design/yourDesignName’的格式,‘view’欄位中可定義多個 map function, 每個 map function 擁有自己的名字,map function 的具體定義寫在 map 欄位的值中。這裡我們定義了兩個 map function, 分別在 age 和 hometown 欄位建立了 index,emit 出所有的記錄。之後只需使用 db.save(design_doc)方法即可將寫好的 design doc 儲存在資料庫中。如圖 5 所示。
圖 5.design document 樣例
Couchdb-python 中用來執行 predefined view 的方法是 view(),
其引數如下:
view(name, wrapper=None, **options)
map_fun – design_docid/viewname
wrapper – 一個可選的引數,用來包裹查詢結果,預設為空
options – 可選的查詢引數,如 key=’yourkey’,descending=True 等
返回型別:List
使用 view() 來查詢 hometown 為”Shenzhen”和”Wuhan”的記錄:
1 2 3 4 |
results=db.view('yourDisgnName/yourViewName2',keys=['Shenzhen','Wuhan']) for row in results: dic=row.value print dic |
這裡的 options 引數我們使用了 keys=[key1,key2,…],在CouchDB HTTP API中列舉了所有其他可選的查詢引數,讀者可靈活選擇。
使用 changes() 監聽資料庫更新
changes() 方法即是對 CouchDB API 中_changes 的封裝,它提供了一種監聽資料庫中資料變更的操作,並以時間順序返回變更資訊列表,可以用於構建類似訊息通知和推送的服務。其常用方法引數列表如下:
- doc_ids (array) – 記錄的 ID 列表,用於監聽指定記錄的改變。
- conflicts (boolean) –在返回值中包含衝突資訊,預設值為 false。
- descending (boolean) – 以時間降序順序返回資料變更結果,預設為 false。
- feed (string) –預設為 normal,這裡我們設定為 continuous。
- include_docs (boolean) –在返回結果中包含每條記錄的內容,預設為 false。
- limit (number) – 指定返回結果的數量。
- view (string) – 允許使用檢視作為搜尋條件。
1 2 3 4 5 6 7 8 9 |
ch = db.changes(feed='continuous', include_docs=True) counter=0 for each in ch: counter+=1 if (counter > 20): print each T=datetime.datetime.now print T counter=0 |
除非人為終止程式的執行,否則 feed=’continuous’將一直保持一個與 CouchDB 的連線(CouchDB 的併發機制可以很容易地在不影響效率的前提下支援這種長連線),只要有資料更新(包括刪除)都將返回給 ch 列表,include_docs=True 將返回已更新的資料本身。Counter 計數器的作用是,在資料庫中的更新操作大於 20 時返回被更新的記錄。
總結:
通過本文的介紹相信讀者可以在實際的開發工作中快速地編寫 Python 指令碼操作 CouchDB,希望文中提到的概念和示例程式碼對讀者的開發工作提供一定的幫助。