web框架flask(4)——数据库
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
Flask 中的數(shù)據(jù)庫(kù)
我們將使用?Flask-SQLAlchemy?擴(kuò)展來(lái)管理我們應(yīng)用程序的數(shù)據(jù)。這個(gè)擴(kuò)展封裝了?SQLAlchemy?項(xiàng)目,這是一個(gè)?對(duì)象關(guān)系映射器?或者 ORM。
ORMs 允許數(shù)據(jù)庫(kù)應(yīng)用程序與對(duì)象一起工作,而不是表以及 SQL。執(zhí)行在對(duì)象的操作會(huì)被 ORM 翻譯成數(shù)據(jù)庫(kù)命令。這就意味著我們將不需要學(xué)習(xí) SQL,我們將讓 Flask-SQLAlchemy 代替 SQL。
安裝:pip install Flask-SQLAlchemy
遷移
我見(jiàn)過(guò)的大多數(shù)數(shù)據(jù)庫(kù)教程會(huì)涉及到創(chuàng)建和使用一個(gè)數(shù)據(jù)庫(kù),但沒(méi)有充分講述隨著應(yīng)用程序擴(kuò)大更新數(shù)據(jù)庫(kù)的問(wèn)題。通常情況下,每次你需要進(jìn)行更新,你最終不得不刪除舊的數(shù)據(jù)庫(kù)和創(chuàng)建一個(gè)新的數(shù)據(jù)庫(kù),并且失去了所有的數(shù)據(jù)。如果數(shù)據(jù)不能容易地被重新創(chuàng)建,你可能會(huì)被迫自己編寫(xiě)導(dǎo)出和導(dǎo)入腳本。
幸運(yùn)地,我們還有一個(gè)更好的選擇。
我們將使用?SQLAlchemy-migrate?來(lái)跟蹤數(shù)據(jù)庫(kù)的更新。它只是在開(kāi)始建立數(shù)據(jù)庫(kù)的時(shí)候多花費(fèi)些工作,這只是很小的代價(jià),以后就再不用擔(dān)心人工數(shù)據(jù)遷移了。
安裝:pip install SQLAlchemy-migrate
配置
針對(duì)我們小型的應(yīng)用,我們將采用 sqlite 數(shù)據(jù)庫(kù)。sqlite 數(shù)據(jù)庫(kù)是小型應(yīng)用的最方便的選擇,每一個(gè)數(shù)據(jù)庫(kù)都是存儲(chǔ)在單個(gè)文件里。
我們有許多新的配置項(xiàng)需要添加到配置文件中(文件?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 擴(kuò)展需要的。這是我們數(shù)據(jù)庫(kù)文件的路徑。
SQLALCHEMY_MIGRATE_REPO 是文件夾,我們將會(huì)把 SQLAlchemy-migrate 數(shù)據(jù)文件存儲(chǔ)在這里。
最后,當(dāng)我們初始化應(yīng)用程序的時(shí)候,我們也必須初始化數(shù)據(jù)庫(kù)。這是我們更新后的初始化文件(文件?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注意我們?cè)诔跏蓟_本中的兩個(gè)改變。創(chuàng)建了一個(gè)?db?對(duì)象,這是我們的數(shù)據(jù)庫(kù),接著導(dǎo)入一個(gè)新的模塊,叫做?models。接下來(lái)我們將編寫(xiě)這個(gè)模塊。
數(shù)據(jù)庫(kù)模型
我們存儲(chǔ)在數(shù)據(jù)庫(kù)中數(shù)據(jù)將會(huì)以類的集合來(lái)表示,我們稱之為數(shù)據(jù)庫(kù)模型。ORM 層需要做的翻譯就是將從這些類創(chuàng)建的對(duì)象映射到適合的數(shù)據(jù)庫(kù)表的行。
讓我們創(chuàng)建一個(gè)表示用戶的模型。使用?WWW SQL Designer?工具,我制作如下的圖來(lái)表示我們用戶的表:
id?字段通常會(huì)在所有模型中,并且用于作為主鍵。在數(shù)據(jù)庫(kù)的每一個(gè)用戶會(huì)被賦予一個(gè)不同的 id 值,存儲(chǔ)在這個(gè)字段中。幸好這是自動(dòng)完成的,我們僅僅需要的是提供?id?這個(gè)字段。
nickname?以及?email?字段是被定義成字符串,并且指定了最大的長(zhǎng)度以便數(shù)據(jù)庫(kù)可以優(yōu)化空間占用。
現(xiàn)在我們已經(jīng)決定用戶表的樣子,剩下的工作就是把它轉(zhuǎn)換成代碼(文件?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)我們剛剛創(chuàng)建的?User?類包含一些字段,這些字段被定義成類的變量。字段是被作為?db.Column?類的實(shí)例創(chuàng)建的,db.Column?把字段的類型作為參數(shù),并且還有一些其它可選的參數(shù),比如表明字段是否唯一。
__repr__?方法告訴 Python 如何打印這個(gè)類的對(duì)象。我們將用它來(lái)調(diào)試。
創(chuàng)建數(shù)據(jù)庫(kù)
配置以及模型都已經(jīng)到位了,是時(shí)候準(zhǔn)備創(chuàng)建數(shù)據(jù)庫(kù)文件。SQLAlchemy-migrate 包自帶命令行和 APIs,這些 APIs 以一種將來(lái)允許容易升級(jí)的方式來(lái)創(chuàng)建數(shù)據(jù)庫(kù)。我發(fā)現(xiàn)命令行使用起來(lái)比較別扭,因此我們自己編寫(xiě)一些 Python 腳本來(lái)調(diào)用遷移的 APIs。
這是創(chuàng)建數(shù)據(jù)庫(kù)的腳本(文件?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))為了創(chuàng)建數(shù)據(jù)庫(kù),你需要運(yùn)行這個(gè)腳本(記得如果在 Windows 上命令有些不同):
./db_create.py在運(yùn)行上述命令之后你會(huì)發(fā)現(xiàn)一個(gè)新的?app.db?文件。這是一個(gè)空的 sqlite 數(shù)據(jù)庫(kù),創(chuàng)建一開(kāi)始就支持遷移。同樣你還將有一個(gè)?db_repository?文件夾,里面還有一些文件,這是?SQLAlchemy-migrate?存儲(chǔ)它的數(shù)據(jù)文件的地方。請(qǐng)注意,我們不會(huì)再生的存儲(chǔ)庫(kù),如果它已經(jīng)存在。這將使我們重新創(chuàng)建數(shù)據(jù)庫(kù),同時(shí)保留現(xiàn)有的存儲(chǔ)庫(kù),如果我們需要。
第一次遷移
現(xiàn)在,我們已經(jīng)定義了我們的模型,我們可以將其合并到我們的數(shù)據(jù)庫(kù)中。我們會(huì)把應(yīng)用程序數(shù)據(jù)庫(kù)的結(jié)構(gòu)任何的改變看做成一次遷移,因此這是我們第一次遷移,我們將從一個(gè)空數(shù)據(jù)庫(kù)遷移到一個(gè)能存儲(chǔ)用戶的數(shù)據(jù)庫(kù)上。
為了實(shí)現(xiàn)遷移,我們需要編寫(xiě)一小段 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))腳本看起來(lái)很復(fù)雜,其實(shí)際上做的并不多。SQLAlchemy-migrate 遷移的方式就是比較數(shù)據(jù)庫(kù)(在本例中從?app.db?中獲取)與我們模型的結(jié)構(gòu)(從文件?app/models.py?獲取)。兩者間的不同將會(huì)被記錄成一個(gè)遷移腳本存放在遷移倉(cāng)庫(kù)中。遷移腳本知道如何去遷移或撤銷它,所以它始終是可能用于升級(jí)或降級(jí)一個(gè)數(shù)據(jù)庫(kù)。
然而在使用上面的腳本自動(dòng)地完成遷移的時(shí)候也不是沒(méi)有問(wèn)題的,我見(jiàn)過(guò)有時(shí)候它很難識(shí)別新老格式的變化。為了讓 SQLAlchemy-migrate 容易地識(shí)別出變化,我絕不會(huì)重命名存在的字段,我僅限于增加或者刪除模型或者字段,或者改變已存在字段的類型。當(dāng)然我一直會(huì)檢查生成的遷移腳本,確保它是正確。
毋庸置疑你不應(yīng)該在沒(méi)有備份下去嘗試遷移數(shù)據(jù)庫(kù)。當(dāng)然也不能在生產(chǎn)環(huán)境下直接運(yùn)行遷移腳本,必須在開(kāi)發(fā)環(huán)境下確保遷移運(yùn)轉(zhuǎn)正常。
因此讓我們繼續(xù)進(jìn)行,記錄下遷移:
./db_migrate.py腳本的輸出如下:
New migration saved as db_repository/versions/001_migration.py Current database version: 1腳本會(huì)打印出遷移腳本存儲(chǔ)在哪里,也會(huì)打印出目前數(shù)據(jù)庫(kù)版本。空數(shù)據(jù)庫(kù)的版本是0,在我們遷移到包含用戶的數(shù)據(jù)庫(kù)后,版本為1.
數(shù)據(jù)庫(kù)升級(jí)和回退
到現(xiàn)在你可能想知道為什么完成記錄數(shù)據(jù)庫(kù)遷移的這項(xiàng)令人麻煩的事情是這么重要。
假設(shè)你有一個(gè)應(yīng)用程序在開(kāi)發(fā)機(jī)器上,同時(shí)有一個(gè)拷貝部署在到線上的生產(chǎn)機(jī)器上。在下一個(gè)版本中,你的數(shù)據(jù)模型有一個(gè)變化,比如新增了一個(gè)表。如果沒(méi)有遷移腳本,你可能必須要琢磨著如何修改數(shù)據(jù)庫(kù)格式在開(kāi)發(fā)和生產(chǎn)機(jī)器上,這會(huì)花費(fèi)很大的工作。
如果有數(shù)據(jù)庫(kù)遷移的支持,當(dāng)你準(zhǔn)備發(fā)布新版的時(shí)候,你只需要錄制一個(gè)新的遷移,拷貝遷移腳本到生產(chǎn)服務(wù)器上接著運(yùn)行腳本,所有事情就完成了。數(shù)據(jù)庫(kù)升級(jí)也只需要一點(diǎn) 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))當(dāng)你運(yùn)行上述腳本的時(shí)候,數(shù)據(jù)庫(kù)將會(huì)升級(jí)到最新版本。
通常情況下,沒(méi)有必要把數(shù)據(jù)庫(kù)降低到舊版本,但是,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))這個(gè)腳本會(huì)回退數(shù)據(jù)庫(kù)一個(gè)版本。你可以運(yùn)行多次來(lái)回退多個(gè)版本。
數(shù)據(jù)庫(kù)關(guān)系
關(guān)系型數(shù)據(jù)可以很好的存儲(chǔ)數(shù)據(jù)項(xiàng)之間的關(guān)系。考慮一個(gè)用戶寫(xiě)了一篇 blog 的例子。在?users?表中有一條用戶的數(shù)據(jù),在?posts?表中有一條 blog 數(shù)據(jù)。記錄是誰(shuí)寫(xiě)了這篇 blog 的最有效的方式就是連接這兩條相關(guān)的數(shù)據(jù)項(xiàng)。
一旦在用戶和文章(post)的聯(lián)系被建立,有兩種類型的查詢是我們可能需要使用的。最常用的查詢就是查詢 blog 的作者。復(fù)雜一點(diǎn)的查詢就是一個(gè)用戶的所有的 blog。Flask-SQLAlchemy 將會(huì)幫助我們完成這兩種查詢。
讓我們擴(kuò)展數(shù)據(jù)庫(kù)以便存儲(chǔ) blog。為此我們回到數(shù)據(jù)庫(kù)設(shè)計(jì)工具并且創(chuàng)建一個(gè)?posts?表。
我們的?posts?表中有必須得?id?字段,以及 blog 的?body?以及一個(gè)?timestamp。這里沒(méi)有多少新東西。只是對(duì)?user_id?字段需要解釋下。
我們說(shuō)過(guò)想要連接用戶和他們寫(xiě)的 blog。方式就是通過(guò)在?posts?增加一個(gè)字段,這個(gè)字段包含了編寫(xiě) blog 的用戶的?id。這個(gè)?id?稱為一個(gè)外鍵。我們的數(shù)據(jù)庫(kù)設(shè)計(jì)工具把外鍵顯示成一個(gè)連線,這根連線連接于?users?表中的?id?與?posts?表中的?user_id。這種關(guān)系稱為一對(duì)多,一個(gè)用戶編寫(xiě)多篇 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)我們添加了一個(gè)?Post?類,這是用來(lái)表示用戶編寫(xiě)的 blog。在?Post?類中的?user_id?字段初始化成外鍵,因此 Flask-SQLAlchemy 知道這個(gè)字段是連接到用戶上。
值得注意的是我們已經(jīng)在?User?類中添加一個(gè)新的字段稱為?posts,它是被構(gòu)建成一個(gè)?db.relationship?字段。這并不是一個(gè)實(shí)際的數(shù)據(jù)庫(kù)字段,因此是不會(huì)出現(xiàn)在上面的圖中。對(duì)于一個(gè)一對(duì)多的關(guān)系,db.relationship?字段通常是定義在“一”這一邊。在這種關(guān)系下,我們得到一個(gè)?user.posts?成員,它給出一個(gè)用戶所有的 blog。不用擔(dān)心很多細(xì)節(jié)不知道什么意思,以后我們會(huì)不斷地看到例子。
首先還是來(lái)運(yùn)行遷移腳本:
./db_migrate.py輸出:
New migration saved as db_repository/versions/002_migration.py Current database version: 2編程時(shí)間
我們花了很多時(shí)間定義我們的數(shù)據(jù)庫(kù),但是我們?nèi)詻](méi)有看到它是如何工作的。因?yàn)槲覀兊膽?yīng)用程序中還沒(méi)有關(guān)于數(shù)據(jù)庫(kù)的代碼,讓我們先在 Python 解釋器上試用下我們?nèi)碌臄?shù)據(jù)庫(kù)。
讓我們先啟動(dòng) Python。在 Linux 或者 OS X 上:
flask/bin/python或者在 Windows 上:
flask\Scripts\python一旦啟動(dòng) Python,在 Python 提示符中輸入如下語(yǔ)句:
>>> from app import db, models >>>這將會(huì)把我們的數(shù)據(jù)庫(kù)和模型載入內(nèi)存中。
首先創(chuàng)建一個(gè)新用戶:
>>> u = models.User(nickname='john', email='john@email.com') >>> db.session.add(u) >>> db.session.commit() >>>在會(huì)話的上下文中完成對(duì)數(shù)據(jù)庫(kù)的更改。多個(gè)的更改可以在一個(gè)會(huì)話中累積,當(dāng)所有的更改已經(jīng)提交,你可以發(fā)出一個(gè)?db.session.commit(),這能原子地寫(xiě)入更改。如果在會(huì)話中出現(xiàn)錯(cuò)誤的時(shí)候,?db.session.rollback()?可以是數(shù)據(jù)庫(kù)回到會(huì)話開(kāi)始的狀態(tài)。如果即沒(méi)有?commit?也沒(méi)有?rollback?發(fā)生,系統(tǒng)默認(rèn)情況下會(huì)回滾會(huì)話。會(huì)話保證數(shù)據(jù)庫(kù)將永遠(yuǎn)保持一致的狀態(tài)。
讓我們添加另一個(gè)用戶:
>>> u = models.User(nickname='susan', email='susan@email.com') >>> db.session.add(u) >>> db.session.commit() >>>現(xiàn)在我們可以查詢用戶:
>>> 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 >>>對(duì)于查詢用戶,我們使用?query?成員,這是對(duì)所有模型類都是可用的。
這是另外一種查詢。如果你知道用戶的?id?,我們能夠找到這個(gè)用戶的數(shù)據(jù)像下面這樣:
>>> u = models.User.query.get(1) >>> u <User u'john'> >>>現(xiàn)在讓我們提交一篇 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()這里我們?cè)O(shè)置我們的?timestamp?為 UTC 時(shí)區(qū)。所有存儲(chǔ)在數(shù)據(jù)庫(kù)的時(shí)間戳都會(huì)是 UTC。我們有來(lái)自世界上不同地方的用戶因此需要有個(gè)統(tǒng)一的時(shí)間單位。在后面的教程中會(huì)以當(dāng)?shù)氐臅r(shí)間呈現(xiàn)這些時(shí)間在用戶面前。
你可能注意到了我們并沒(méi)有設(shè)置?user_id?字段。相反我們?cè)?author?字段上存儲(chǔ)了一個(gè) User 對(duì)象。ORM 層將會(huì)知道怎么完成?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?文檔可能會(huì)提供更多有幫助的信息。
在結(jié)束之前,需要清除一下剛才創(chuàng)建的數(shù)據(jù),以便在下一章中會(huì)有一個(gè)干凈的數(shù)據(jù)庫(kù):
>>> 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() >>>結(jié)束語(yǔ)
這是一個(gè)漫長(zhǎng)的教程。我們已經(jīng)學(xué)會(huì)了使用數(shù)據(jù)庫(kù)的基本知識(shí),但我們還沒(méi)有納入到我們的應(yīng)用程序的數(shù)據(jù)庫(kù)。在下一章中,我們將會(huì)把我們所學(xué)到的所有關(guān)于數(shù)據(jù)庫(kù)的知識(shí)用于實(shí)踐。
轉(zhuǎn)載于:https://my.oschina.net/u/3767248/blog/1620990
總結(jié)
以上是生活随笔為你收集整理的web框架flask(4)——数据库的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 寒假作业--微信小程序开发1
- 下一篇: day38--MySQL基础二