Flask:sqlalchemy模組的使用

lcc發表於2021-09-11

Flask:sqlalchemy模組的使用

一、安裝

$ pip install flask-sqlalchemy

二、配置

配置選項列表 :

079fb4ec821840690a4d821c774c23a.png

SQLALCHEMY_NATIVE_UNICODE | 可以用於顯式禁用原生 unicode 支援。當使用 不合適的指定無編碼的資料庫預設值時,這對於 一些資料庫介面卡是必須的(比如 Ubuntu 上某些版本的 PostgreSQL )。|

| SQLALCHEMY_POOL_SIZE | 資料庫連線池的大小。預設是引擎預設值(通常 是 5 ) |

| SQLALCHEMY_POOL_TIMEOUT | 設定連線池的連線超時時間。預設是 10 。 |

| SQLALCHEMY_POOL_RECYCLE | 多少秒後自動回收連線。這對 MySQL 是必要的, 它預設移除閒置多於 8 小時的連線。注意如果 使用了 MySQL , Flask-SQLALchemy 自動設定這個值為 2 小時。|

app.config["SQLALCHEMY_DATABASE_URI"] = DATABASE_URI
app.config["SQLALCHEMY_COMMIT_ON_TEARDOWN"] = True/False  # 每次請求結束後都會自動提交資料庫中的變動.
 
app.config[""] = 
app.config[""] = 
app.config[""] = 
app.config[""] = 
 
DATABASE_URI :
    mysql : mysql://username:password@hostname/database
 
    pgsql : postgresql://username:password@hostname/database
 
    sqlite(linux)  : sqlite:////absolute/path/to/database
 
    sqlite(windows) : sqlite:///c:/absolute/path/to/database

三、初始化示例

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
base_dir = os.path.abspath(os.path.dirname(__file__))
 
app = Flask(__name__)
 
app.config["SQLALCHEMY_DATABASE_URI"] = 'sqlite:///' + os.path.join(base_dir, 'data.sqlite')
app.config["SQLALCHEMY_COMMIT_ON_TEARDOWN"] = True
 
db = SQLAlchemy(app)

四、定義模型

模型 表示程式使用的持久化實體. 在 ORM 中, 模型一般是一個 Python 類, 類中的屬性對應資料庫中的表.

Flaks-SQLAlchemy 建立的資料庫例項為模型提供了一個基類以及一些列輔助類和輔助函式, 可用於定義模型的結構.

db.Model    # 建立模型,
db.Column   # 建立模型屬性.

模型屬性型別 :

399a658ef75bf10fae409d8869d7c0f.png

常用 SQLAlchemy 列選項

919f70ea385668133ed126b9da21e07.png

示例 :

class Role(db.Model):
    __tablename__ = "roles"
    id = db.Column(db.Integer, primary_key=True)
    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 '<Role %r>' % self.username

五、關係

關係型資料庫使用關係把不同表中的行聯絡起來。

常用 SQLAlchemy 關係選項:

382d9ce2f7174867837ff1d37e9b901.png

(1)一對多

原理 : 在 “多” 這一側加入一個外來鍵, 指定 “一” 這一側聯結的記錄.

示例程式碼 : 一個角色可屬於多個使用者, 而每個使用者只能有一個角色.

class Role(db.Model):
    # ...
    users = db.relationship('User', backref='role')
 
class User(db.Model):
    # ...
    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))  # 外來鍵關係.
 
 
###############
db.ForeignKey('roles.id') : 外來鍵關係,
 
Role.users = db.relationship('User', backref='role') : 代表 外來鍵關係的 物件導向視角. 對於一個 Role 類的例項, 
其 users 屬性將返回與角色相關聯的使用者組成的列表. 
    db.relationship() 第一個參數列示這個關係的另一端是哪個模型. 
    backref 引數, 向 User 模型新增了一個 role 資料屬性, 從而定義反向關係.  這一屬性可替代 role_id 訪問 Role 模型, 
    此時獲取的是模型物件, 而不是外來鍵的值.

(2)多對多

最複雜的關係型別,需要用到第三章表,即關聯表,這樣多對多關係可以分解成原表和關聯表之間的兩個一對多關係。

查詢多對多關係分兩步 : 遍歷兩個關係來獲取查詢結果。

程式碼示例:

registrations = db.Table("registrations",
                         db.Column("student_id", db.Integer, db.ForeignKey("students.id")),
                         db.Column("class_id", db.Integer, db.ForeignKey("classes.id"))
                         )
 
class Student(db.Model):
    __tablename__ = "students"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)
    classes = db.relationship("Class",
                              secondary=registrations,
                              backref=db.backref("students", lazy="dynamic"),
                              lazy="dynamic")
 
class Class(db.Model):
    __tablename__ = "classes"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)

多對多關係仍然使用定義一對多關係的 db.relationship() 方法進行定義, 但在多對多關係中, 必須把 secondary 引數設為關聯表。

多對多關係可以在任何一個類中定義, backref 引數會處理好關係的另一側。

關聯表就是一個簡單的表, 不是模型, SQLAlchemy 會自動接管這個表。

classes 關係使用列表語義, 這樣處理多對多關係比較簡單。

Class 模型的 students 關係有 引數 db.backref() 定義. 這個關係還指定了 lazy 引數, 所以, 關係兩側返回的查詢都可接受額外的過濾器。

自引用關係

自引用關係可以理解為多對多關係的特殊形式 : 多對多關係的兩邊由兩個實體變為一個實體。

高階多對多關係

使用多對多關係時,往往需要儲存所聯兩個實體之間的額外資訊。這種資訊只能儲存在關聯表中,對使用者之間的關注來說,可以儲存使用者關注另一個使用者的日期,這樣就能按照時間順序列出所有關注者。

為了能在關係中處理自定義的資料,必須提升關聯表的地位,使其變成程式可訪問的模型。

關注關聯表模型實現:

class Follow(db.Model):
    __tablename__ = "follows"
    follower_id = db.Column(db.Integer, db.ForeignKey("users.id"), primary_key=True)
    followed_id = db.Column(db.Integer, db.ForeignKey("users.id"), primary_key=True)
    timestamp = db.Column(db.DateTime, default=datetime.utcnow)
 
# SQLAlchemy 不能直接使用這個關聯表, 因為如果這個做程式就無法訪問其中的自定義欄位. 相反的, 要把這個多對多關係的左右
兩側拆分成兩個基本的一對多關係, 而且要定義成標準的關係。

使用兩個一對多關係實現的多對多關係:

class User(UserMixin, db.Model):
    # ...
    followd = db.relationship("Follow",
                              foreign_keys=[Follow.follower_id],
                              backref=db.backref("follower", lazy="joined"),
                              lazy="dynamic",
                              cascade="all, delete-orphan")
    followrs = db.relationship("Follow",
                              foreign_keys=[Follow.followed_id],
                              backref=db.backref("followed", lazy="joined"),
                              lazy="dynamic",
                              cascade="all, delete-orphan")
# 這段程式碼中, followed 和 follower 關係都定義為 單獨的 一對多關係. 
# 注意: 為了消除外來鍵歧義, 定義關係是必須使用可選引數 foreign_keys 指定的外來鍵. 而且 db.backref() 引數並不是指定這兩個
關係之間的引用關係, 而是回引 Follow 模型. 回引中的 lazy="joined" , 該模式可以實現立即從連線查詢中載入相關物件.
# 這兩個關係中, user 一側設定的 lazy 引數作用不一樣. lazy 引數都在 "一" 這一側設定, 返回的結果是 "多" 這一側中的記錄. 
dynamic 引數, 返回的是查詢物件.
# cascade 引數配置在父物件上執行的操作相關物件的影響. 比如, 層疊物件可設定為: 將使用者新增到資料庫會話後, 要自定把所有
關係的物件都新增到會話中. 刪除物件時, 預設的層疊行為是把物件聯結的所有相關物件的外來鍵設為空值. 但在關聯表中, 刪除記錄
後正確的行為是把執行該記錄的實體也刪除, 因為這樣才能有效銷燬聯結. 這就是 層疊選項值 delete-orphan 的作用. 設為 all, 
delete-orphan 的意思是啟動所有預設層疊選項, 並且還要刪除孤兒記錄。

(3)一對一

可以看做特殊的 一對多 關係. 但呼叫 db.relationship() 時 要把 uselist 設定 False, 把 多變為 一。

(4)多對一

將一對多關係,反過來即可,也是一對多關係。

六、資料庫操作

(1)建立資料庫及資料表

建立資料庫

db.create_all()

示例 :

$ python myflask.py shell
> from myflask import db
> db.create_all()

如果使用 sqlite , 會在 SQLALCHEMY_DATABASE_URI 指定的目錄下 多一個檔案,檔名為該配置中的檔名。

如果資料庫表已經存在於資料庫中, 那麼 db.create_all() 不會建立或更新這個表。

更新資料庫

方法一 :

先刪除, 在建立 –> 原有資料庫中的資料, 都會消失.

> db.drop_all()
> db.create_all()

方法二 :

資料庫遷移框架 : 可以跟自動資料庫模式的變化,然後增量式的把變化應用到資料庫中。

SQLAlchemy 的主力開發人員編寫了一個 遷移框架 Alembic, 除了直接使用 Alembic wait, Flask 程式還可使用 Flask-Migrate 擴充套件, 該擴充套件對 Alembic 做了輕量級包裝, 並整合到 Flask-Script 中, 所有操作都透過 Flaks-Script 命令完成。

① 安裝 Flask-Migrate

$ pip install flask-migrate

② 配置

from flask_migrate import Migrate, MigrateCommand
 
# ...
migrate = Migrate(app, db)
manager.add_command('db', MigrateCommand)

③ 資料庫遷移

a. 使用 init 自命令建立遷移倉庫.

$ python myflask.py db init # 該命令會建立 migrations 資料夾, 所有遷移指令碼都存在其中.

b. 建立資料路遷移指令碼.
    $ python myflask.py db revision     # 手動建立 Alemic 遷移
    建立的遷移只是一個骨架, upgrade() 和 downgrade() 函式都是空的. 開發者需要使用 Alembic 提供的 Operations 物件
    指令實現具體操作.
 
    $ python myflask.py db migrate -m COMMONT    # 自動建立遷移.
    自動建立的遷移會根據模型定義和資料庫當前的狀態之間的差異生成 upgrade() 和 downgrade() 函式的內容.
 
    ** 自動建立的遷移不一定總是正確的, 有可能漏掉一些細節, 自動生成遷移指令碼後一定要進行檢查.
 
 
c. 更新資料庫
    $ python myflask.py db upgrade     # 將遷移應用到資料庫中.

(2)插入行

模型的建構函式,接收的引數是使用關鍵字引數指定的模型屬性初始值。注意,role 屬性也可使用,雖然他不是真正的資料庫列,但卻是一對多關係的高階表示。這些新建物件的 id 屬性並沒有明確設定,因為主鍵是由 Flask-SQLAlchemy 管理的。現在這些物件只存在於 Python 直譯器中,尚未寫入資料庫。

>> from myflask import db, User, Role
>> db.create_all()
>> admin_role = Role(name="Admin")
>> mod_role = Role(name="Moderator")
>> user_role = Role(name="User")
>> user_john = User(username="john", role=admin_role)
>> user_susan = User(username="susan", role=mod_role)
>> user_david = User(username="david", role=user_role)
>> admin_role.name
'Admin'
>> admin_role.id
None
---------
>> db.session.add_all([admin_role, mod_role, user_role, user_john, user_susan, user_david])  # 把物件新增到會話中.
>> db.session.commit()      # 把物件寫入資料庫, 使用 commit() 提交會話.

(3)修改行

>> admin_role = "Administrator"
>> db.session.add(admin_role)
>> db.session.commit()

(4)刪除行

>> db.session.delete(mod_role)
>> db.session.commit()

(5)查詢行

Flask-SQLAlchemy 為每個模型類都提供了 query 物件.

獲取表中的所有記錄

>> Role.query.all()
  [<Role u'Admin'>, <Role u'Moderator'>, <Role u'User'>]
>> User.query.all()
  [<Role u'john'>, <Role u'susan'>, <Role u'david'>]

查詢過濾器

filter_by() 等過濾器在 query 物件上呼叫, 返回一個更精確的 query 物件. 多個過濾器可以一起呼叫, 直到獲取到所需的結果.

>> User.query.filter_by(role=user_role).all()   # 以列表形式,返回所有結果,
>> User.query.filter_by(role=user_role).first() # 返回結果中的第一個.

filter() 對查詢結果過濾,比”filter_by()”方法更強大,引數是布林表示式

# WHERE age<20
users = User.query.filter(User.age<20)
# WHERE name LIKE 'J%' AND age<20
users = User.query.filter(User.name.startswith('J'), User.age<20)

查詢過濾器 :

9aa0b57ca4cf3c2a42ae2d192868d28.png

查詢執行函式 :

d841847d7b639283a944c01d93c0985.png

first_or_404() | 返回查詢的第一個結果,如果沒有結果,則終止請求,返回 404 錯誤響應 | |

| get() | 返回指定主鍵對應的行,如果沒有對應的行,則返回 None |

get_or_404() | 返回指定主鍵對應的行,如果沒找到指定的主鍵,則終止請求,返回 404 | |錯誤響應

| count() | 返回查詢結果的數量 |

| paginate() | 返回一個 Paginate 物件,它包含指定範圍內的結果 |

(6)會話管理,事務管理

單個提交

>> db.session.add(ONE)
>> db.session.commit()

多個提交

>> db.session.add_all([LIST_OF_MEMBER])
>> db.session.commit()

刪除會話

>> db.session.delete(mod_role)
>> db.session.commit()

事務回滾 : 新增到資料庫會話中的所有物件都會還原到他們在資料庫時的狀態.

>> db.session.rollback()

七、檢視函式中運算元據庫

@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 = ""             # why empty it ?
        return redirect(url_for("index"))
    return render_template("index.html", current_time=datetime.utcnow(), form=form, name=session.get("name"), 
    known=session.get("known"))

八、分頁物件 Pagination

1. paginate() 方法

paginate() 方法的返回值是一個 Pagination 類物件,該類在 Flask-SQLAlchemy 中定義,用於在模板中生成分頁連結。

paginate(頁數[,per_page=20, error_out=True])
    頁數 : 唯一必須指定的引數,
    per_page : 指定每頁現實的記錄數量, 預設 20.
    error_out : True 如果請求的頁數超出了返回, 返回 404 錯誤; False 頁數超出範圍時返回一個,空列表.

示例程式碼:

@main.route("/", methods=["GET", "POST"])
def index():
    # ...
    page = request.args.get('page', 1, type=int)    # 渲染的頁數, 預設第一頁, type=int 保證引數無法轉換成整數時, 
    返回預設值.
    pagination = Post.query.order_by(Post.timestamp.desc()).paginate(page, per_page=current_app.config
    ["FLASKY_POSTS_PER_PAGE"], error_out=False)
    posts = pagination.items
    return render_template('index.html', form=form, posts=posts,pagination=pagination)

2. 分頁物件的屬性及方法:

Flask_SQLAlchemy 分頁物件的屬性:

90c14ada21d20a79faa109517566bfc.png

在分頁物件可呼叫的方法:

d3159e3de99f2207f61b04b1c86d2f1.png

3. 在模板中與 BootStrap 結合使用示例

使用 Flaks-SQLAlchemy 的分頁物件與 Bootstrap 中的分頁 CSS, 可以輕鬆的構造出一個 分頁導航.

分頁模板宏 _macros.html : 建立一個 Bootstrap 分頁元素, 即一個有特殊樣式的無序列表.

{% macro pagination_widget(pagination,endpoint) %}
<ul class="pagination">
    <li {% if not pagination.has_prev %} class="disabled" {% endif %}>
        <a href="{% if pagination.has_prev %}{{url_for(endpoint, page=paginatin.page - 1, **kwargs)}}{% else %}
        #{% endif %}">
            &laquo;
        </a>
    </li>
    {% for p in pagination,.iter_pages() %}
        {% if p %}
            {% if p == pagination.page %}
            <li class="active">
                <a href="{{ url_for(endpoint, page=p, **kwargs) }}">{{p}}</a>
            </li>
            {% else %}
            <li>
                <a href="{{ url_for(endpoint, page = p, **kwargs) }}">{{p}}</a>
            </li>
            {% endif %}
        {% else %}
        <li class="disabled"><a href="#">&hellip;</a> </li>
        {% endif %}
    {% endfor %}
    <li {% if not pagination.has_next %} class="disabled" {% endif%}>
        <a href="{% if paginatin.has_next %}{{ url_for(endpoint, page=pagination.page+1, **kwargs) }}{% else %}
        #{% endif %}">
            &raquo;
        </a>
    </li>
</ul>
{% endmacro %}

匯入使用分頁導航

{% extends "base.html" %}
{% import "_macros.html" as macros %}
...
<div class="pagination">
    {{ macro.pagination_widget(pagination, ".index")}}
</div>

九、監聽事件

1. set 事件

示例程式碼 :

from markdown import markdown
import bleach
class Post(db.Model):
    # ...
    body = db.Colume(db.Text)
    body_html = db.Column(db.Text)
    # ...
    @staticmethod
    def on_changeed_body(target, value, oldvalue, initiator):
        allowed_tags = ["a", "abbr", "acronym", "b", "blockquote", "code", "em",
                        "i", "li", "ol", "pre", "strong", "ul", "h1", "h2","h3","h4","p"]
        target.body_html = bleach.linkify(bleach.clean(markdown(value, output_format="html"), tags=allowed_tags, 
        strip=True))
db.event.listen(Post.body, "set", Post.on_changeed_body) 
# on_changed_body 函式註冊在 body 欄位上, 是 SQLIAlchemy "set" 事件的監聽程式, 
# 這意味著只要這個類例項的 body 欄位設了新值, 函式就會自動被呼叫. 
# on_changed_body 函式把 body 欄位中的文字渲染成 HTML 格式, 
# 結果儲存在 body_html 中, 自動高效的完成 Markdown 文字到 HTML 的轉換

十、記錄慢查詢

十一、Binds 操作多個資料庫

十二、其他

1. ORM 在查詢時做初始化操作

當 SQLIAlchemy ORM 從資料庫查詢資料時, 預設不呼叫__init__ 方法, 其底層實現了 Python 類的 __new__() 方法, 直接實現 物件例項化, 而不是透過 __init__ 來例項化物件.

如果需要在查詢時, 依舊希望實現一些初始化操作, 可以使用 orm.reconstructor() 裝飾器或 實現 InstanceEvents.load() 監聽事件。

# orm.reconstructor
from sqlalchemy import orm
class MyMappedClass(object):
    def __init__(self, data):
        self.data = data
        # we need stuff on all instances, but not in the database.
        self.stuff = []
    @orm.reconstructor
    def init_on_load(self):
        self.stuff = []
# InstanceEvents.load()
from sqlalchemy import event
## standard decorator style
@event.listens_for(SomeClass, 'load')
def receive_load(target, context):
    "listen for the 'load' event"
    # ... (event handling logic) ...

如果只是希望在從資料庫查詢生成的物件中包含某些屬性, 也可以使用 property 實現:

class AwsRegions(db.Model):
    name=db.Column(db.String(64))
    ...
    @property
    def zabbix_api(self):
        return ZabbixObj(zabbix_url)
    @zabbix_api.setter
    def zabbix_api(self):
        raise ValueError("zabbix can not be setted!")

python學習網,大量的免費,歡迎線上學習!

本文轉自:https://blog.csdn.net/LYLLOAD/article/details/81664322

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2331/viewspace-2835384/,如需轉載,請註明出處,否則將追究法律責任。

相關文章