web框架flask(4)——数据库
2019獨角獸企業重金招聘Python工程師標準>>>
Flask 中的數據庫
我們將使用?Flask-SQLAlchemy?擴展來管理我們應用程序的數據。這個擴展封裝了?SQLAlchemy?項目,這是一個?對象關系映射器?或者 ORM。
ORMs 允許數據庫應用程序與對象一起工作,而不是表以及 SQL。執行在對象的操作會被 ORM 翻譯成數據庫命令。這就意味著我們將不需要學習 SQL,我們將讓 Flask-SQLAlchemy 代替 SQL。
安裝:pip install Flask-SQLAlchemy
遷移
我見過的大多數數據庫教程會涉及到創建和使用一個數據庫,但沒有充分講述隨著應用程序擴大更新數據庫的問題。通常情況下,每次你需要進行更新,你最終不得不刪除舊的數據庫和創建一個新的數據庫,并且失去了所有的數據。如果數據不能容易地被重新創建,你可能會被迫自己編寫導出和導入腳本。
幸運地,我們還有一個更好的選擇。
我們將使用?SQLAlchemy-migrate?來跟蹤數據庫的更新。它只是在開始建立數據庫的時候多花費些工作,這只是很小的代價,以后就再不用擔心人工數據遷移了。
安裝:pip install SQLAlchemy-migrate
配置
針對我們小型的應用,我們將采用 sqlite 數據庫。sqlite 數據庫是小型應用的最方便的選擇,每一個數據庫都是存儲在單個文件里。
我們有許多新的配置項需要添加到配置文件中(文件?config.py):
import os basedir = os.path.abspath(os.path.dirname(__file__))SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db') SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'db_repository')SQLALCHEMY_DATABASE_URI 是 Flask-SQLAlchemy 擴展需要的。這是我們數據庫文件的路徑。
SQLALCHEMY_MIGRATE_REPO 是文件夾,我們將會把 SQLAlchemy-migrate 數據文件存儲在這里。
最后,當我們初始化應用程序的時候,我們也必須初始化數據庫。這是我們更新后的初始化文件(文件?app/__init__.py):
from flask import Flask from flask.ext.sqlalchemy import SQLAlchemyapp = Flask(__name__) app.config.from_object('config') db = SQLAlchemy(app)from app import views, models注意我們在初始化腳本中的兩個改變。創建了一個?db?對象,這是我們的數據庫,接著導入一個新的模塊,叫做?models。接下來我們將編寫這個模塊。
數據庫模型
我們存儲在數據庫中數據將會以類的集合來表示,我們稱之為數據庫模型。ORM 層需要做的翻譯就是將從這些類創建的對象映射到適合的數據庫表的行。
讓我們創建一個表示用戶的模型。使用?WWW SQL Designer?工具,我制作如下的圖來表示我們用戶的表:
id?字段通常會在所有模型中,并且用于作為主鍵。在數據庫的每一個用戶會被賦予一個不同的 id 值,存儲在這個字段中。幸好這是自動完成的,我們僅僅需要的是提供?id?這個字段。
nickname?以及?email?字段是被定義成字符串,并且指定了最大的長度以便數據庫可以優化空間占用。
現在我們已經決定用戶表的樣子,剩下的工作就是把它轉換成代碼(文件?app/models.py):
from app import dbclass User(db.Model):id = db.Column(db.Integer, primary_key = True)nickname = db.Column(db.String(64), index = True, unique = True)email = db.Column(db.String(120), index = True, unique = True)def __repr__(self):return '<User %r>' % (self.nickname)我們剛剛創建的?User?類包含一些字段,這些字段被定義成類的變量。字段是被作為?db.Column?類的實例創建的,db.Column?把字段的類型作為參數,并且還有一些其它可選的參數,比如表明字段是否唯一。
__repr__?方法告訴 Python 如何打印這個類的對象。我們將用它來調試。
創建數據庫
配置以及模型都已經到位了,是時候準備創建數據庫文件。SQLAlchemy-migrate 包自帶命令行和 APIs,這些 APIs 以一種將來允許容易升級的方式來創建數據庫。我發現命令行使用起來比較別扭,因此我們自己編寫一些 Python 腳本來調用遷移的 APIs。
這是創建數據庫的腳本(文件?db_create.py):
#!flask/bin/python from migrate.versioning import api from config import SQLALCHEMY_DATABASE_URI from config import SQLALCHEMY_MIGRATE_REPO from app import db import os.path db.create_all() if not os.path.exists(SQLALCHEMY_MIGRATE_REPO):api.create(SQLALCHEMY_MIGRATE_REPO, 'database repository')api.version_control(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) else:api.version_control(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, api.version(SQLALCHEMY_MIGRATE_REPO))為了創建數據庫,你需要運行這個腳本(記得如果在 Windows 上命令有些不同):
./db_create.py在運行上述命令之后你會發現一個新的?app.db?文件。這是一個空的 sqlite 數據庫,創建一開始就支持遷移。同樣你還將有一個?db_repository?文件夾,里面還有一些文件,這是?SQLAlchemy-migrate?存儲它的數據文件的地方。請注意,我們不會再生的存儲庫,如果它已經存在。這將使我們重新創建數據庫,同時保留現有的存儲庫,如果我們需要。
第一次遷移
現在,我們已經定義了我們的模型,我們可以將其合并到我們的數據庫中。我們會把應用程序數據庫的結構任何的改變看做成一次遷移,因此這是我們第一次遷移,我們將從一個空數據庫遷移到一個能存儲用戶的數據庫上。
為了實現遷移,我們需要編寫一小段 Python 代碼(文件?db_migrate.py):
#!flask/bin/python import imp from migrate.versioning import api from app import db from config import SQLALCHEMY_DATABASE_URI from config import SQLALCHEMY_MIGRATE_REPO v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) migration = SQLALCHEMY_MIGRATE_REPO + ('/versions/%03d_migration.py' % (v+1)) tmp_module = imp.new_module('old_model') old_model = api.create_model(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) exec(old_model, tmp_module.__dict__) script = api.make_update_script_for_model(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, tmp_module.meta, db.metadata) open(migration, "wt").write(script) api.upgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) print('New migration saved as ' + migration) print('Current database version: ' + str(v))腳本看起來很復雜,其實際上做的并不多。SQLAlchemy-migrate 遷移的方式就是比較數據庫(在本例中從?app.db?中獲取)與我們模型的結構(從文件?app/models.py?獲取)。兩者間的不同將會被記錄成一個遷移腳本存放在遷移倉庫中。遷移腳本知道如何去遷移或撤銷它,所以它始終是可能用于升級或降級一個數據庫。
然而在使用上面的腳本自動地完成遷移的時候也不是沒有問題的,我見過有時候它很難識別新老格式的變化。為了讓 SQLAlchemy-migrate 容易地識別出變化,我絕不會重命名存在的字段,我僅限于增加或者刪除模型或者字段,或者改變已存在字段的類型。當然我一直會檢查生成的遷移腳本,確保它是正確。
毋庸置疑你不應該在沒有備份下去嘗試遷移數據庫。當然也不能在生產環境下直接運行遷移腳本,必須在開發環境下確保遷移運轉正常。
因此讓我們繼續進行,記錄下遷移:
./db_migrate.py腳本的輸出如下:
New migration saved as db_repository/versions/001_migration.py Current database version: 1腳本會打印出遷移腳本存儲在哪里,也會打印出目前數據庫版本。空數據庫的版本是0,在我們遷移到包含用戶的數據庫后,版本為1.
數據庫升級和回退
到現在你可能想知道為什么完成記錄數據庫遷移的這項令人麻煩的事情是這么重要。
假設你有一個應用程序在開發機器上,同時有一個拷貝部署在到線上的生產機器上。在下一個版本中,你的數據模型有一個變化,比如新增了一個表。如果沒有遷移腳本,你可能必須要琢磨著如何修改數據庫格式在開發和生產機器上,這會花費很大的工作。
如果有數據庫遷移的支持,當你準備發布新版的時候,你只需要錄制一個新的遷移,拷貝遷移腳本到生產服務器上接著運行腳本,所有事情就完成了。數據庫升級也只需要一點 Python 腳本(文件?db_upgrade.py):
#!flask/bin/python from migrate.versioning import api from config import SQLALCHEMY_DATABASE_URI from config import SQLALCHEMY_MIGRATE_REPO api.upgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) print('Current database version: ' + str(v))當你運行上述腳本的時候,數據庫將會升級到最新版本。
通常情況下,沒有必要把數據庫降低到舊版本,但是,SQLAlchemy-migrate 支持這么做(文件?db_downgrade.py):
#!flask/bin/python from migrate.versioning import api from config import SQLALCHEMY_DATABASE_URI from config import SQLALCHEMY_MIGRATE_REPO v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) api.downgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, v - 1) v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO) print('Current database version: ' + str(v))這個腳本會回退數據庫一個版本。你可以運行多次來回退多個版本。
數據庫關系
關系型數據可以很好的存儲數據項之間的關系。考慮一個用戶寫了一篇 blog 的例子。在?users?表中有一條用戶的數據,在?posts?表中有一條 blog 數據。記錄是誰寫了這篇 blog 的最有效的方式就是連接這兩條相關的數據項。
一旦在用戶和文章(post)的聯系被建立,有兩種類型的查詢是我們可能需要使用的。最常用的查詢就是查詢 blog 的作者。復雜一點的查詢就是一個用戶的所有的 blog。Flask-SQLAlchemy 將會幫助我們完成這兩種查詢。
讓我們擴展數據庫以便存儲 blog。為此我們回到數據庫設計工具并且創建一個?posts?表。
我們的?posts?表中有必須得?id?字段,以及 blog 的?body?以及一個?timestamp。這里沒有多少新東西。只是對?user_id?字段需要解釋下。
我們說過想要連接用戶和他們寫的 blog。方式就是通過在?posts?增加一個字段,這個字段包含了編寫 blog 的用戶的?id。這個?id?稱為一個外鍵。我們的數據庫設計工具把外鍵顯示成一個連線,這根連線連接于?users?表中的?id?與?posts?表中的?user_id。這種關系稱為一對多,一個用戶編寫多篇 blog。
讓我們修改模型以反映這些變化(app/models.py):
from app import dbclass User(db.Model):id = db.Column(db.Integer, primary_key=True)nickname = db.Column(db.String(64), index=True, unique=True)email = db.Column(db.String(120), index=True, unique=True)posts = db.relationship('Post', backref='author', lazy='dynamic')def __repr__(self):return '<User %r>' % (self.nickname)class Post(db.Model):id = db.Column(db.Integer, primary_key = True)body = db.Column(db.String(140))timestamp = db.Column(db.DateTime)user_id = db.Column(db.Integer, db.ForeignKey('user.id'))def __repr__(self):return '<Post %r>' % (self.body)我們添加了一個?Post?類,這是用來表示用戶編寫的 blog。在?Post?類中的?user_id?字段初始化成外鍵,因此 Flask-SQLAlchemy 知道這個字段是連接到用戶上。
值得注意的是我們已經在?User?類中添加一個新的字段稱為?posts,它是被構建成一個?db.relationship?字段。這并不是一個實際的數據庫字段,因此是不會出現在上面的圖中。對于一個一對多的關系,db.relationship?字段通常是定義在“一”這一邊。在這種關系下,我們得到一個?user.posts?成員,它給出一個用戶所有的 blog。不用擔心很多細節不知道什么意思,以后我們會不斷地看到例子。
首先還是來運行遷移腳本:
./db_migrate.py輸出:
New migration saved as db_repository/versions/002_migration.py Current database version: 2編程時間
我們花了很多時間定義我們的數據庫,但是我們仍沒有看到它是如何工作的。因為我們的應用程序中還沒有關于數據庫的代碼,讓我們先在 Python 解釋器上試用下我們全新的數據庫。
讓我們先啟動 Python。在 Linux 或者 OS X 上:
flask/bin/python或者在 Windows 上:
flask\Scripts\python一旦啟動 Python,在 Python 提示符中輸入如下語句:
>>> from app import db, models >>>這將會把我們的數據庫和模型載入內存中。
首先創建一個新用戶:
>>> u = models.User(nickname='john', email='john@email.com') >>> db.session.add(u) >>> db.session.commit() >>>在會話的上下文中完成對數據庫的更改。多個的更改可以在一個會話中累積,當所有的更改已經提交,你可以發出一個?db.session.commit(),這能原子地寫入更改。如果在會話中出現錯誤的時候,?db.session.rollback()?可以是數據庫回到會話開始的狀態。如果即沒有?commit?也沒有?rollback?發生,系統默認情況下會回滾會話。會話保證數據庫將永遠保持一致的狀態。
讓我們添加另一個用戶:
>>> u = models.User(nickname='susan', email='susan@email.com') >>> db.session.add(u) >>> db.session.commit() >>>現在我們可以查詢用戶:
>>> users = models.User.query.all() >>> users [<User u'john'>, <User u'susan'>] >>> for u in users: ... print(u.id,u.nickname) ... 1 john 2 susan >>>對于查詢用戶,我們使用?query?成員,這是對所有模型類都是可用的。
這是另外一種查詢。如果你知道用戶的?id?,我們能夠找到這個用戶的數據像下面這樣:
>>> u = models.User.query.get(1) >>> u <User u'john'> >>>現在讓我們提交一篇 blog:
>>> import datetime >>> u = models.User.query.get(1) >>> p = models.Post(body='my first post!', timestamp=datetime.datetime.utcnow(), author=u) >>> db.session.add(p) >>> db.session.commit()這里我們設置我們的?timestamp?為 UTC 時區。所有存儲在數據庫的時間戳都會是 UTC。我們有來自世界上不同地方的用戶因此需要有個統一的時間單位。在后面的教程中會以當地的時間呈現這些時間在用戶面前。
你可能注意到了我們并沒有設置?user_id?字段。相反我們在?author?字段上存儲了一個 User 對象。ORM 層將會知道怎么完成?user_id?字段。
讓我們多做一些查詢:
# get all posts from a user >>> u = models.User.query.get(1) >>> u <User u'john'> >>> posts = u.posts.all() >>> posts [<Post u'my first post!'>]# obtain author of each post >>> for p in posts: ... print(p.id,p.author.nickname,p.body) ... 1 john my first post!# a user that has no posts >>> u = models.User.query.get(2) >>> u <User u'susan'> >>> u.posts.all() []# get all users in reverse alphabetical order >>> models.User.query.order_by('nickname desc').all() [<User u'susan'>, <User u'john'>] >>>Flask-SQLAlchemy?文檔可能會提供更多有幫助的信息。
在結束之前,需要清除一下剛才創建的數據,以便在下一章中會有一個干凈的數據庫:
>>> users = models.User.query.all() >>> for u in users: ... db.session.delete(u) ... >>> posts = models.Post.query.all() >>> for p in posts: ... db.session.delete(p) ... >>> db.session.commit() >>>結束語
這是一個漫長的教程。我們已經學會了使用數據庫的基本知識,但我們還沒有納入到我們的應用程序的數據庫。在下一章中,我們將會把我們所學到的所有關于數據庫的知識用于實踐。
轉載于:https://my.oschina.net/u/3767248/blog/1620990
總結
以上是生活随笔為你收集整理的web框架flask(4)——数据库的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 寒假作业--微信小程序开发1
- 下一篇: 基于Kubernetes的ESaaS架构