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