在上一篇測試中已完成 Firebase 資料庫的建立與線上資料新增與刪除等操作, 本篇主要是測試如何利用 python-firebase 套件連線 Firebase 即時資料庫並進行 CRUD (Create, Retrieve, Update, Delete) 操作.
本系列上一篇文章參考 :
六. 安裝 python-firebase 套件 :
python-firebase 為第三方套件, 須先安裝才能使用, 我先用 pip 指令安裝 :
pip install python-firebase
C:\Users\User>pip install python-firebase
Collecting python-firebase
Downloading python-firebase-1.2.tar.gz (10 kB)
Preparing metadata (setup.py) ... done
Requirement already satisfied: requests>=1.1.0 in c:\python37\lib\site-packages (from python-firebase) (2.21.0)
Requirement already satisfied: urllib3<1.25,>=1.21.1 in c:\python37\lib\site-packages (from requests>=1.1.0->python-firebase) (1.24.1)
Requirement already satisfied: chardet<3.1.0,>=3.0.2 in c:\python37\lib\site-packages (from requests>=1.1.0->python-firebase) (3.0.4)
Requirement already satisfied: idna<2.9,>=2.5 in c:\python37\lib\site-packages (from requests>=1.1.0->python-firebase) (2.8)
Requirement already satisfied: certifi>=2017.4.17 in c:\python37\lib\site-packages (from requests>=1.1.0->python-firebase) (2018.11.29)
Building wheels for collected packages: python-firebase
Building wheel for python-firebase (setup.py) ... done
Created wheel for python-firebase: filename=python_firebase-1.2-py3-none-any.whl size=11534 sha256=3f46bf6f132adf9c4e4dcab2eed24f211fc75ad48b807a4b7ee2eac16fa6cbfc
Stored in directory: c:\users\user\appdata\local\pip\cache\wheels\0f\6d\06\32ba434ed7b712403f944a537c25e5b9560d766b58d172c8e6
Successfully built python-firebase
Installing collected packages: python-firebase
Successfully installed python-firebase-1.2
但是匯入 firebase.firebase 模組時卻出現語法錯誤 :
>>> from firebase import firebase
Traceback (most recent call last):
File "<pyshell>", line 1, in <module>
File "C:\Python37\lib\site-packages\firebase\__init__.py", line 3
from .async import process_pool
^
SyntaxError: invalid syntax
經查詢找到下面這篇 Stackoverflow 文章 :
原來是 python-firebase 的作者似乎沒將修正過的版本同時更新到 PyPi 網站, 導致 pip 從 PyPi 下載安裝到有 bug 的套件, 解決辦法是用 pip 指令直接從 python-firebase 的 GitHub 寄存庫下載套件來安裝 :
C:\Users\User>pip install git+https://github.com/ozgur/python-firebase
Collecting git+https://github.com/ozgur/python-firebase
Cloning https://github.com/ozgur/python-firebase to c:\users\user\appdata\local\temp\pip-req-build-vtrzu0_q
Running command git clone --filter=blob:none -q https://github.com/ozgur/python-firebase 'C:\Users\User\AppData\Local\Temp\pip-req-build-vtrzu0_q'
Resolved https://github.com/ozgur/python-firebase to commit 0d79d7609844569ea1cec4ac71cb9038e834c355
Preparing metadata (setup.py) ... done
Requirement already satisfied: requests>=1.1.0 in c:\python37\lib\site-packages (from python-firebase==1.2.1) (2.21.0)
Requirement already satisfied: certifi>=2017.4.17 in c:\python37\lib\site-packages (from requests>=1.1.0->python-firebase==1.2.1) (2018.11.29)
Requirement already satisfied: idna<2.9,>=2.5 in c:\python37\lib\site-packages (from requests>=1.1.0->python-firebase==1.2.1) (2.8)
Requirement already satisfied: urllib3<1.25,>=1.21.1 in c:\python37\lib\site-packages (from requests>=1.1.0->python-firebase==1.2.1) (1.24.1)
Requirement already satisfied: chardet<3.1.0,>=3.0.2 in c:\python37\lib\site-packages (from requests>=1.1.0->python-firebase==1.2.1) (3.0.4)
Building wheels for collected packages: python-firebase
Building wheel for python-firebase (setup.py) ... done
Created wheel for python-firebase: filename=python_firebase-1.2.1-py3-none-any.whl size=12612 sha256=3ac6b887368946f2e6518e3c14ac262d76d90aa72521031ac6c1ce32292f2951
Stored in directory: C:\Users\User\AppData\Local\Temp\pip-ephem-wheel-cache-xqlzf6r5\wheels\e7\7e\19\4f87f6d9a85bccbedc016446ef2314ed1f3fff01879f8f2be9
Successfully built python-firebase
Installing collected packages: python-firebase
Attempting uninstall: python-firebase
Found existing installation: python-firebase 1.2
Uninstalling python-firebase-1.2:
Successfully uninstalled python-firebase-1.2
Successfully installed python-firebase-1.2.1
果然重新從 GitHub 安裝後就可以順利匯入了.
七. python-firebase 套件的 CRUD 操作 :
安裝好後先來看看此套件的內容, 我們會用到的是 firebase 套件裡的 firebase 模組 :
>>> from firebase import firebase
>>> type(firebase)
<class 'module'>
>>> dir(firebase)
['FirebaseApplication', 'FirebaseAuthentication', 'FirebaseTokenGenerator', 'FirebaseUser', 'JSONEncoder', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'http_connection', 'json', 'make_delete_request', 'make_get_request', 'make_patch_request', 'make_post_request', 'make_put_request', 'process_pool', 'urlparse']
可以用內建函式 dir(), type(), 以及 eval() 來過濾 firebase 模組中的成員 :
>>> members=dir(firebase)
>>> for mbr in members: # 走訪 execjs 模組成員
obj=eval('firebase.' + mbr) # 用 eval() 求值取得 firebase 成員之參考
if not mbr.startswith('_'): # 走訪所有不是 "_" 開頭的成員
print(mbr, type(obj))
FirebaseApplication <class 'type'>
FirebaseAuthentication <class 'type'>
FirebaseTokenGenerator <class 'type'>
FirebaseUser <class 'type'>
JSONEncoder <class 'type'>
http_connection <class 'function'>
json <class 'module'>
make_delete_request <class 'function'>
make_get_request <class 'function'>
make_patch_request <class 'function'>
make_post_request <class 'function'>
make_put_request <class 'function'>
process_pool <class 'firebase.lazy.LazyLoadProxy(function)'>
urlparse <class 'module'>
其中 FirebaseApplication 類別就是用來與 Firebase 即時資料庫建立連線的工具, 可以用它的建構子 FirebaseApplication() 來建立一個 FirebaseApplication 物件, 語法如下 :
firebase.FirebaseApplication(url, None)
第一參數 url 為資料庫的網址 (即 Firebase 即時資料庫主控台上方的資料庫根節點網址), 第二參數用來指定物件建立失敗時的傳回值, 預設為 None.
>>> url='https://tony1966test-default-rtdb.asia-southeast1.firebasedatabase.app/'
>>> fbapp=firebase.FirebaseApplication(url, None)
>>> type(fbapp)
<class 'firebase.firebase.FirebaseApplication'>
>>> dir(fbapp)
['NAME_EXTENSION', 'URL_SEPERATOR', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_authenticate', '_build_endpoint_url', 'authentication', 'delete', 'delete_async', 'dsn', 'get', 'get_async', 'patch', 'patch_async', 'post', 'post_async', 'put', 'put_async']
同樣可用內建函式 dir(), type(), 以及 eval() 來過濾 FirebaseApplication 物件中的成員 :
>>> members=dir(fbapp)
>>> for mbr in members: # 走訪 execjs 模組成員
obj=eval('fbapp.' + mbr) # 用 eval() 求值取得 FirebaseApplication 成員之參考
if not mbr.startswith('_'): # 走訪所有不是 "_" 開頭的成員
print(mbr, type(obj))
NAME_EXTENSION <class 'str'>
URL_SEPERATOR <class 'str'>
authentication <class 'NoneType'>
delete <class 'method'>
delete_async <class 'method'>
dsn <class 'str'>
get <class 'method'>
get_async <class 'method'>
patch <class 'method'>
patch_async <class 'method'>
post <class 'method'>
post_async <class 'method'>
put <class 'method'>
put_async <class 'method'>
其中常用的四個物件方法為 post(), get(), put(), 以及 delete(), 分別對應資料庫存取中最常用的 CRUD (Create, Retrieve, Update, Delete) 操作, 用法摘要如下表 :
物件方法 | 說明 |
get(url, None) | 讀取指定 url 節點之資料, 傳回值為 dict 物件 [Retrieve] |
post(url, data) | 在指定 url 節點新增一筆資料 data (dict 物件) [Create] |
delet(url + id, None) | 刪除指定 url 節點下鍵為 id 之資料 [Delete] |
put(url, name, data) | 於 url 節點新增或更新資料, 鍵=name 值=data [Create/Update] |
其中 put() 方法與 post() 方法都可以新增資料, 它們的差別有三個, 其一為 post() 無法指定 ID (即 key), 而是由系統指定一個隨機 ID, 而 put() 方法則可以自行指定 ID, 若該 ID 已存在則更新該 ID 內的資料, 所以 put() 方法兼具 Create 與 Update 功能, 這是它們的第二個差別. 其三是 post() 的傳回值是一個鍵為 'name', 值為所新增之資料的 ID; 而 put() 的傳回值為所新增的資料 (值).
1. 用 get() 方法讀取資料 :
呼叫 FirebaseApplication 物件的 get(url, None) 方法可讀取指定 url 節點下的全部資料, 成功就以 dict 型態傳回這些資料, 失敗就傳回第二個參數 (通常設為 None). 在上一篇測試中, 我們已經透過 Firebase 即時資料庫主控台在 tony1966test 這個資料庫中手動建立如下資料 :
在 get() 中傳入資料庫根節點將傳回此資料庫內所有資料, 例如 :
>>> from firebase import firebase
>>> import time
>>> url='https://tony1966test-default-rtdb.asia-southeast1.firebasedatabase.app/'
>>> fbapp=firebase.FirebaseApplication(url, None)
>>> for k, v in data.items():
print(f'id={k} value={v}')
time.sleep(1)
id=motto value={'motto1': '處事宜帶春風, 律己宜帶秋氣 (林則徐)', 'motto2': '物質生活向下看, 精神生活向上看 (胡適)'}
id=motto1 value=處事宜帶春風, 律己宜帶秋氣 (林則徐)
此處匯入 time 模組是為了讓每次存取 Firebase 有時間間隔 (例如每秒存取一次), 避免傳輸延遲問題. 上面是讀取資料庫根節點的結果, 可見 id=motto 因為底下還有一層, 因此其值為一個 dict 物件; 而 id=motto1 因為是直接放在根節點下, 所以其值是一個 str 物件. 由於 Firebase 資料庫支援 RESTful 存取, 因此如果將 url 指定為根節點的下一層節點, 則 get() 將傳回該節點下的所有資料, 例如 :
>>> from firebase import firebase
>>> import time
>>> url='https://tony1966test-default-rtdb.asia-southeast1.firebasedatabase.app/'
>>> fbapp=firebase.FirebaseApplication(url, None)
>>> data=fbapp.get(url + "/motto", None)
>>> data
{'motto1': '處事宜帶春風, 律己宜帶秋氣 (林則徐)', 'motto2': '物質生活向下看, 精神生活向上看 (胡適)'}
>>> for k, v in data.items():
print(f'id={k} value={v}')
time.sleep(1)
id=motto1 value=處事宜帶春風, 律己宜帶秋氣 (林則徐)
id=motto2 value=物質生活向下看, 精神生活向上看 (胡適)
可見傳入 get() 的 url 參數若加上鍵的路徑, 就可以取得該節點下的全部資料, 例如此處是取得根節點的下一層 id=motto 節點下的全部資料.
2. 用 post() 方法新增資料 :
呼叫 post(url, data) 方法可在指定節點 url 底下新增資料, 由於是純粹的 Create 操作, 新增的資料會被指定一個隨機 ID 以避免鍵的衝突. post() 的傳回值是一個 dict 物件, 其鍵固定為 'name', 其值為該新增資料的鍵 ID.
如果第二參數為一個基本類型物件 (數值或字串等), 則此新增資料會被放在根節點下, 例如 :
>>> from firebase import firebase
>>> import time
>>> url='https://tony1966test-default-rtdb.asia-southeast1.firebasedatabase.app/'
>>> fbapp=firebase.FirebaseApplication(url, None)
>>> data=fbapp.post(url, "初念淺, 轉念深")
>>> data
{'name': '-MsxZcjlmRCZQxXo9miT'} # post() 的傳回值為一個字典物件
這時去看主控台會發現在根節點下已經新增了一筆 ID 為自動指派的資料 :
如果傳入 post(url, data) 的第二參數 data 是一個字典, 則這筆資料不會放在根節點下, 而是放在下一層, Firebase 會先指派一個隨機 ID 建立一個目錄節點, 然後將這筆資料放在這節點下面, 其 key 就是 dict 物件的 key. 傳回值同樣是一個鍵為 'name' 的字典, 值為目錄節點之 ID, 例如 :
>>> data=fbapp.post(url, {'motto3': '初念淺, 轉念深'})
>>> data
{'name': '-MsxcUA65p_Q_kYtnd5S'}
這時在主控台就可以看到這筆掛在根節點的下一層, 自動指派 ID 目錄節點下的新增資料了 :
如果要將資料放在指定之目錄節點, 例如 /motto 節點下面, 則 post() 的 url 參數應該指定為 url + '/motto', 且 data 參數應該直接傳入字串, 而非 dict 物件, 例如 :
>>> data=fbapp.post(url + '/motto', '初念淺, 轉念深') # url 加掛 /motto, data=字串
>>> data
{'name': '-MszU-El7e99pqBDTFZC'}
這時在主控台就可以看到這筆新增資料是直接掛在 /motto 節點之下 :
如果 data 參數是傳入字典, 那麼這筆資料就不會直接放在 /motto 下, 而是 /motto 的下一層, 自動指派 ID 的目錄節點下, 例如 :
>>> data=fbapp.post(url + '/motto', {'motto3': '初念淺, 轉念深'})
>>> data
{'name': '-Mt0Uq9CEa93xZoyhrbn'}
新增後到主控台就可以看見 /motto 下新增了一個自動指派 ID 的目錄節點, 新增資料是掛在此節點下, 而不是直接掛在 /motto 下 :
3. 用 put() 方法新增或更新資料 :
put(url, name, data) 兼具新增 (Create) 與更新 (Update) 操作功能, 它有三個參數, name 是 ID (鍵), 而 data 是值. 注意, 使用關鍵字參數時 name 與 data 位置可互換, 但若使用位置參數時, 第二參數為 name, 第三參數為 data. 傳回值為所新增的值 (即 data), 例如 :
>>> from firebase import firebase
>>> import time
>>> url='https://tony1966test-default-rtdb.asia-southeast1.firebasedatabase.app/'
>>> fbapp=firebase.FirebaseApplication(url, None)
>>> data=fbapp.put(url + '/motto', name='motto3', data='初念淺, 轉念深')
>>> data
'初念淺, 轉念深'
此處呼叫 put() 時指定 url 為根節點的下一層目錄節點 /motto, 並以關鍵字參數 name 指定資料的 ID (鍵) 為 motto3, 以 data 指定資料的值, 由於 /motto 下面並沒有 ID=motto3 的資料, 故此為一新增資料的操作, put() 的傳回值為所新增資料的值.
這時到主控台就可看到 /motto 節點下這筆鍵為 motto3 的新增資料 :
上面若使用位置參數也是可以的, 結果完全一樣 :
>>> data=fbapp.put(url + '/motto', 'motto3', '初念淺, 轉念深')
>>> data
'初念淺, 轉念深'
由於前面已經用 put() 新增了 ID=motto3, 所以上面這個使用位置參數的 put() 其實是 Update 的更新動作, 因為值相同所以看不出有甚麼更新, 下面用不一樣的值就看得出來了 :
>>> data=fbapp.put(url + '/motto', name='motto3', data='天下沒有白吃的午餐')
>>> data
'天下沒有白吃的午餐'
這時去看主控台就會發現 /motto 下的 motto3 內容已經被更新了 :
在物聯網應用中, 若要保存歷史資料就用 post(), 新的資料不會覆蓋舊資料; 否則就用 post() 不斷更新一個固定的 ID.
4. 用 delete() 方法刪除資料 :
呼叫 delete(url + id, None) 可刪除指定 ID 的那筆資料, 其中第二參數為傳回值, 一般設為 None. 如果所指定的 ID 不存在也不會出現錯誤訊息, 一樣傳回 None, 例如刪除 /motto 下的 motto3 :
>>> from firebase import firebase
>>> import time
>>> url='https://tony1966test-default-rtdb.asia-southeast1.firebasedatabase.app/'
>>> fbapp=firebase.FirebaseApplication(url, None)
>>> data=fbapp.delete(url + '/motto/motto3', None)
>>> print(data)
None
這時查看主控台會發現 /motto 底下的 motto3 已經被刪除了 :
要刪除用 post() 新增的資料, 同樣也是將那看似亂數的隨機 ID 帶在 url 後面即可, 例如刪除根節點底下 ID=-MsxZcjlmRCZQxXo9miT 的這筆資料 :
>>> id='-MsxZcjlmRCZQxXo9miT'
>>> data=fbapp.delete(url + id, None)
>>> print(data)
None
檢視主控台可知 ID=-MsxZcjlmRCZQxXo9miT 這筆資料已被刪除 :
接下來 ID='-MsxcUA65p_Q_kYtnd5S' 這筆資料, 其值是一個鍵為 motto3 的 JSON, 刪除它會將整個資料刪除, 例如 :
>>> id='-MsxcUA65p_Q_kYtnd5S'
>>> data=fbapp.delete(url + id, None)
>>> print(data)
None
檢查主控台該筆資料已被刪除 :
如果刪除某個節點, 此節點底下不管有多少層, 所有資料都會被刪除. 例如上圖若刪除 /motto 這節點, 它底下全部資料都會全部刪除.
5. 用迴圈批次新增資料 :
最後來測試用迴圈批次新增資料的方法, 這些資料是放在一個字典串列中 :
motto=[{ "motto1" : "處事宜帶春風, 律己宜帶秋氣 (林則徐)"},
{ "motto2" : "物質生活向下看, 精神生活向上看 (胡適)"},
{ "motto3" : "初念淺, 轉念深"},
{ "motto4" : "天下沒有白吃的午餐"}]
做法是用迴圈依序讀取串列中的字典, 然後用 put() 或 post() 寫到資料庫裡. 首先用 delete() 或在主控台手動清空所有資料 :
首先用 post() 來新增資料, 如上所述, post() 無法自訂節點名稱 (name), 因此若將串列中的字典寫入 /motto 下的話, 會新增一層隨機名稱的節點來存放該鍵值對, 完整程式如下 :
# firbase-batch-create-data-post-1.py
from firebase import firebase
mottos=[{"motto1" : "處事宜帶春風, 律己宜帶秋氣 (林則徐)"},
{"motto2" : "物質生活向下看, 精神生活向上看 (胡適)"},
{"motto3" : "初念淺, 轉念深"},
{"motto4" : "天下沒有白吃的午餐"}]
url='https://tony1966test-default-rtdb.asia-southeast1.firebasedatabase.app/'
fbapp=firebase.FirebaseApplication(url, None)
for motto in mottos:
fbapp.post(url + '/motto', data=motto)
結果如下 :
可見資料如預期地被放在 /motto 的下一層隨機名稱節點下. 如果想去掉多出來的一層, 只好犧牲原始資料中字典的鍵 (motto1, motto2, ...), 直接使用隨機名稱為鍵, 這樣就不能將字典傳給 post() 的第二參數, 而是只取出字典中的值傳入即可. 同樣先清空資料庫, 然後再執行下列程式 :
# firbase-batch-create-data-post-2.py
from firebase import firebase
mottos=[{"motto1" : "處事宜帶春風, 律己宜帶秋氣 (林則徐)"},
{"motto2" : "物質生活向下看, 精神生活向上看 (胡適)"},
{"motto3" : "初念淺, 轉念深"},
{"motto4" : "天下沒有白吃的午餐"}]
url='https://tony1966test-default-rtdb.asia-southeast1.firebasedatabase.app/'
fbapp=firebase.FirebaseApplication(url, None)
for motto in mottos:
value=list(motto.values())[0]
fbapp.post(url + '/motto', data=value)
此處呼叫字典的 values() 方法會傳回一個狀似串列的 dict_values 物件, 此物件內容不能使用索引存取, 故必須先用 list() 將其轉成串列才行, 結果如下 :
如果想要自訂節點名稱, 則必須使用 put() 方法, 但必須先從原始資料中取出字典中的鍵與值, 分別傳給 put() 的參數 name 與 daya. 先將資料庫清空再執行下列程式 :
# firbase-batch-create-data-put.py
from firebase import firebase
mottos=[{"motto1" : "處事宜帶春風, 律己宜帶秋氣 (林則徐)"},
{"motto2" : "物質生活向下看, 精神生活向上看 (胡適)"},
{"motto3" : "初念淺, 轉念深"},
{"motto4" : "天下沒有白吃的午餐"}]
url='https://tony1966test-default-rtdb.asia-southeast1.firebasedatabase.app/'
fbapp=firebase.FirebaseApplication(url, None)
for motto in mottos:
key=list(motto.keys())[0]
value=list(motto.values())[0]
fbapp.put(url + '/motto', name=key, data=value)
結果如下 :
我把資料匯出如下 :
{
"motto" : {
"motto1" : "處事宜帶春風, 律己宜帶秋氣 (林則徐)",
"motto2" : "物質生活向下看, 精神生活向上看 (胡適)",
"motto3" : "初念淺, 轉念深",
"motto4" : "天下沒有白吃的午餐"
}
}
可見匯出的 Firebase 資料庫為 JSON 格式, 也可以用 URL 直接取得 (若存取權限 read=True 的話), 其 URL 結構為 :
https://App網址/資料庫名稱.json
例如上面的 motto 資料庫網址為 :
注意, 以前使用 firebaseio.com 網域 (很多網路資料都是舊的), 但後來改成 firebasedatabase.app 了, 若用舊網域會得到如下錯誤回應 :
{ "correctUrl" : "https://tony1966test-default-rtdb.asia-southeast1.firebasedatabase.app", "error" : "Database lives in a different region. Please change your database URL to https://tony1966test-default-rtdb.asia-southeast1.firebasedatabase.app" }
參考 :
沒有留言:
張貼留言