Python 數據庫框架
大多數的數據庫引擎都有對應的 Python 包,包括開源包和商業包。Flask 並不限制你使用何種類型的數據庫包,因此可以根據自己的喜好選擇使用 MySQL、Postgres、SQLite、Redis、MongoDB 或者 CouchDB。
如果這些都無法滿足需求,還有一些數據庫抽象層代碼包供選擇,例如SQLAlchemy和MongoEngine。你可以使用這些抽象包直接處理高等級的 Python 對象,而不用處理如表、文檔或查詢語言此類的數據庫實體。
選擇數據庫框架的因素:
- 易用性。抽象層,也稱為對象關系映 射(Object-Relational Mapper,ORM) 或 對 象 文 檔 映 射(Object-Document Mapper,ODM),在用戶不知覺的情況下把高層的面向對象操作轉換成低層的數據庫指令。
- 性能。ORM 和 ODM 把對象業務轉換成數據庫業務會有一定的損耗。真正的關鍵點在於如何選擇一個能直接操作低層數據庫的抽象層,以防特定的操作需要直接使用數據庫原生指令優化。
- 可移植性。必須考慮其是否能在你的開發平台和生產平台中使用。
- Flask集成度
Flask-SQLAlchemy 管理數據庫
- 安裝
pip install flask-sqlalchemy
- 1
使用URL制定數據庫
數據庫引擎 | URL |
---|---|
MySQL | mysql://username:password@hostname/database |
Postgres | postgresql://username:password@hostname/database |
SQLite(Unix) | sqlite:////absolute/path/to/database |
SQLite(Windows) | sqlite:///c:/absolute/path/to/database |
SQLite 數 據 庫 不 需 要 使 用 服 務 器, 因 此 不 用 指 定 hostname 、 username 和 password 。URL 中的 database 是硬盤上文件的文件名。
- 配置
程序使用的數據庫 URL 必須保存到 Flask 配置對象的 SQLALCHEMY_DATABASE_URI 鍵中
配置對象中還有一個很有用的選項,即 SQLALCHEMY_COMMIT_ON_TEARDOWN 鍵,將其設為 True時,每次請求結束后都會自動提交數據庫中的變動
from flask.ext.sqlalchemy import SQLAlchemy basedir = os.path.abspath(os.path.dirname(__file__)) app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] =\ 'sqlite:///' + os.path.join(basedir, 'data.sqlite') app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True db = SQLAlchemy(app)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 定義模型
class Role(db.Model): __tablename__ = 'roles'#__tablename__ 定義在數據庫中使用的表名 id = db.Column(db.Integer, primary_key=True)#primary_key如果設為 True ,這列就是表的主鍵.如果沒有定義 __tablename__ ,SQLAlchemy 會使用一個默認名字 name = db.Column(db.String(64), unique=True) def __repr__(self): return '<Role % r>' % self.name class User(db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), unique=True, index=True) def __repr__(self): return '<User % r>' % self.username
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
最常用的SQLAlchemy列類型
類型名 | Python類型 | 說 明 |
---|---|---|
Integer | int | 普通整數,一般是 32 位 |
SmallInteger | int | 取值范圍小的整數,一般是 16 位 |
BigInteger | int 或 long | 不限制精度的整數 |
Float | float | 浮點數 |
Numeric | decimal.Decimal | 定點數 |
String | str | 變長字符串 |
Text | str | 變長字符串,對較長或不限長度的字符串做了優化 |
Unicode | unicode | 變長 Unicode 字符串 |
UnicodeText | unicode | 變長 Unicode 字符串,對較長或不限長度的字符串做了優化 |
Boolean | bool | 布爾值 |
Date | datetime.date | 日期 |
Time | datetime.time | 時間 |
DateTime | datetime.datetime | 日期和時間 |
Interval | datetime.timedelta | 時間間隔 |
Enum | str | 一組字符串 |
PickleType | 任何 Python 對象 | 自動使用 Pickle 序列化 |
LargeBinary | str | 二進制文件 |
最常使用的SQLAlchemy列選項
選項名 | 說 明 |
---|---|
primary_key | 如果設為 True ,這列就是表的主鍵 |
unique | 如果設為 True ,這列不允許出現重復的值 |
index | 如果設為 True ,為這列創建索引,提升查詢效率 |
nullable | 如果設為 True ,這列允許使用空值;如果設為 False ,這列不允許使用空值 |
default | 為這列定義默認值 |
關系表達
關系型數據庫使用關系把不同表中的行聯系起來。
- 一對多
class Role(db.Model): # ... users = db.relationship('User', backref='role')#添加到 Role 模型中的 users 屬性代表這個關系的面向對象視角。對於一個 Role 類的實例,其 users 屬性將返回與角色相關聯的用戶組成的列表。db.relationship() 的第一個參數表,如果模型類尚未定義,可使用字符串形式指定。db.relationship() 中的 backref 參數向 User 模型中添加一個 role 屬性,從而定義反向關系。這一屬性可替代 role_id 訪問 Role 模型,此時獲取的是模型對象 class User(db.Model): # ... role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))#關系使用 users 表中的外鍵連接了兩行。添加到 User 模型中的 role_id 列被定義為外鍵,就是這個外鍵建立起了關系。傳給 db.ForeignKey() 的參數 'roles.id' 表明,這列的值是 roles 表中行的 id 值。
- 1
- 2
- 3
- 4
- 5
- 6
db.relationship() 都能自行找到關系中的外鍵,但有時卻無法決定把哪一列作為外鍵。如果 User 模型中有兩個或以上的列定義為 Role 模型的外鍵,SQLAlchemy 就不知道該使用哪列。如果無法決定外鍵,你就要為 db.relationship() 提供額外參數,從而確定所用外鍵
常用的SQLAlchemy關系選項
選項名 | 說 明 |
---|---|
backref | 在關系的另一個模型中添加反向引用 |
primaryjoin | 明確指定兩個模型之間使用的聯結條件。只在模棱兩可的關系中需要指定 |
lazy | 指定如何加載相關記錄。可選值有 select (首次訪問時按需加載)、 immediate (源對象加載后就加載)、 joined (加載記錄,但使用聯結)、 subquery (立即加載,但使用子查詢),noload (永不加載)和 dynamic (不加載記錄,但提供加載記錄的查詢) |
uselist | 如果設為 Fales ,不使用列表,而使用標量值 |
order_by | 指定關系中記錄的排序方式 |
secondary | 指定 多對多 關系中關系表的名字 |
secondaryjoin | SQLAlchemy 無法自行決定時,指定多對多關系中的二級聯結條件 |
-
一對一
一對一關系可以用前面介紹的一對多關系表示,但調用 db.relationship() 時要把 uselist 設為 False ,把“多”變成“一”。 -
多對多
tags = db.Table('tags', db.Column('tag_id', db.Integer, db.ForeignKey('tag.id')), db.Column('page_id', db.Integer, db.ForeignKey('page.id')) ) class Page(db.Model): id = db.Column(db.Integer, primary_key=True) tags = db.relationship('Tag', secondary=tags, backref=db.backref('pages', lazy='dynamic')) class Tag(db.Model): id = db.Column(db.Integer, primary_key=True)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
數據庫操作
- 創建表
python hello.py shell
>>> from hello import db >>> db.create_all()
- 1
- 2
- 3
- 刪除表
db.drop_all()
- 1
- 插入行
#創建對象,模型的構造函數接受的參數是使用關鍵字參數指定的模型屬性初始值。 admin_role = Role(name='Admin') user_role = Role(name='User') user_susan = User(username='susan', role=user_role)#role 屬性也可使用,雖然它不是真正的數據庫列,但卻是一對多關系的高級表示。 user_john = User(username='john', role=admin_role) #這些新建對象的 id 屬性並沒有明確設定,因為主鍵是由 Flask-SQLAlchemy 管理的。 print(admin_role.id)#None #通過數據庫會話管理對數據庫所做的改動,在 Flask-SQLAlchemy 中,會話由 db.session 表示。 ##首先,將對象添加到會話中 db.session.add(admin_role) db.session.add(user_role) db.session.add(user_susan) db.session.add(user_john) #簡寫:db.session.add_all([admin_role, user_role, user_john, user_susan]) ##通過提交會話(事務),將對象寫入數據庫 db.session.commit()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
會話提交:
數據庫會話能保證數據庫的一致性。提交操作使用原子方式把會話中的對象全部寫入數據庫。如果在寫入會話的過程中發生了錯誤,整個會話都會失效。
數據庫會話也可 回滾 。調用 db.session.rollback() 后,添加到數據庫會話中的所有對象都會還原到它們在數據庫時的狀態。
- 修改行
admin_role.name = 'Administrator' db.session.add(admin_role) session.commit()
- 1
- 2
- 3
- 刪除行
db.session.delete(mod_role)
session.commit()
- 1
- 2
-
查詢行
- 查詢全部。Role.query.all()
- 條件查詢(使用過濾器)。User.query.filter_by(role=user_role).all()
user_role = Role.query.filter_by(name='User').first()#filter_by() 等過濾器在 query 對象上調用,返回一個更精確的 query 對象。
- 1
常用過濾器
過濾器 | 說 明 |
---|---|
filter() | 把過濾器添加到原查詢上,返回一個新查詢 |
filter_by() | 把等值過濾器添加到原查詢上,返回一個新查詢 |
limit() | 使用指定的值限制原查詢返回的結果數量,返回一個新查詢 |
offset() | 偏移原查詢返回的結果,返回一個新查詢 |
order_by() | 根據指定條件對原查詢結果進行排序,返回一個新查詢 |
group_by() | 根據指定條件對原查詢結果進行分組,返回一個新查詢 |
最常使用的SQLAlchemy查詢執行函數
方 法 | 說 明 |
---|---|
all() | 以列表形式返回查詢的所有結果 |
first() | 返回查詢的第一個結果,如果沒有結果,則返回 None |
first_or_404() | 返回查詢的第一個結果,如果沒有結果,則終止請求,返回 404 錯誤響應 |
get() | 返回指定主鍵對應的行,如果沒有對應的行,則返回 None |
get_or_404() | 返回指定主鍵對應的行,如果沒找到指定的主鍵,則終止請求,返回 404 錯誤響應 |
count() | 返回查詢結果的數量 |
paginate() | 返回一個 Paginate 對象,它包含指定范圍內的結果 |
-
關系查詢
#執行 user_role.users 表達式時,隱含的查詢會調用 all() 返回一個用戶列表。 query 對象是隱藏的,因此無法指定更精確的查詢過濾器。 users = user_role.users #修改了關系的設置,加入了 lazy = 'dynamic' 參數,從而禁止自動執行查詢 class Role(db.Model): users = db.relationship('User', backref='role', lazy='dynamic') #順序排列 user_role.users.order_by(User.username).all()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
在視圖函數中操作數據庫
@app.route('/', methods=['GET', 'POST']) def index(): form = NameForm() if form.validate_on_submit(): user = User.query.filter_by(username=form.name.data).first() if user is None: user = User(username = form.name.data) db.session.add(user) session['known'] = False else: session['known'] = True session['name'] = form.name.data form.name.data = '' return redirect(url_for('index')) return render_template('index.html', form = form, name = session.get('name'), known = session.get('known', False))
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
修改模板
{ % extends "base.html" % }
{ % import "bootstrap/wtf.html" as wtf % }
{ % block title % }Flasky{ % endblock % }
{ % block page_content % }
<div class="page-header"> <h1>Hello, { % if name % }{{ name }}{ % else % }Stranger{ % endif % }!</h1> { % if not known % } <p>Pleased to meet you!</p> { % else % } <p>Happy to see you again!</p> { % endif % } </div> {{ wtf.quick_form(form) }} { % endblock % }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
集成 Python shell
讓 Flask-Script 的 shell 命令自動導入特定的對象
from flask.ext.script import Shell def make_shell_context(): return dict(app=app, db=db, User=User, Role=Role) manager.add_command("shell", Shell(make_context=make_shell_context))
- 1
- 2
- 3
- 4
make_shell_context() 函數注冊了程序、數據庫實例以及模型,因此這些對象能直接導入 shell
使用 Flask-Migrate 實現數據庫遷移
創建遷移倉庫
pip install flask-migrate
- 1
配置
from flask.ext.migrate import Migrate, MigrateCommand # ... migrate = Migrate(app, db) manager.add_command('db', MigrateCommand)
- 1
- 2
- 3
- 4
在維護數據庫遷移之前,要使用 init 子命令創建遷移倉庫
python hello.py db init
- 1
創建遷移腳本
python hello.py db migrate -m "initial migration"
- 1
更新數據庫
python hello.py db upgrade
轉自:https://blog.csdn.net/sun_dragon/article/details/51719753