使用 Python、SQLite 和 SQLAlchemy 進行數據管理2 |【生長吧!Python!】 【生長吧!Python】有獎征文火熱進行中:https://bbs.huaweicloud.com/blogs/278897(使用驅動器u盤之前需要格式化)
使用 SQLAlchemy 和 Python 對象
SQLAlchemy是一個強大的 Python 數據庫訪問工具包,其對象關系映射器 (ORM)是其最著名的組件之一,此處討論和使用了該組件。
當您使用面向對象的語言(如 Python)工作時,從對象的角度進行思考通常很有用。可以將 SQL 查詢返回的結果映射到對象,但這樣做與數據庫的工作方式背道而馳。堅持使用 SQL 提供的標量結果與 Python 開發人員的工作方式背道而馳。這個問題被稱為對象-關系阻抗失配。
SQLAlchemy 提供的 ORM 位于 SQLite 數據庫和 Python 程序之間,并轉換數據庫引擎和 Python 對象之間的數據流。SQLAlchemy 允許您從對象的角度進行思考,同時仍保留數據庫引擎的強大功能。
該模型
將 SQLAlchemy 連接到數據庫的基本要素之一是創建模型。該模型是一個 Python 類,用于定義作為數據庫查詢結果返回的 Python 對象與底層數據庫表之間的數據映射。
前面顯示的實體關系圖顯示了用箭頭連接的框。這些框是使用 SQL 命令構建的表,并且是 Python 類將建模的內容。箭頭是表之間的關系。
這些模型是從 SQLAlchemyBase類繼承的 Python類。本Base類提供了模型的實例和數據庫表之間的接口操作。
下面是models.py創建模型來表示author_book_publisher.db數據庫的文件:
from sqlalchemy import Column, Integer, String, ForeignKey, Table from sqlalchemy.orm import relationship, backref from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() author_publisher = Table( "author_publisher", Base.metadata, Column("author_id", Integer, ForeignKey("author.author_id")), Column("publisher_id", Integer, ForeignKey("publisher.publisher_id")), ) book_publisher = Table( "book_publisher", Base.metadata, Column("book_id", Integer, ForeignKey("book.book_id")), Column("publisher_id", Integer, ForeignKey("publisher.publisher_id")), ) class Author(Base): __tablename__ = "author" author_id = Column(Integer, primary_key=True) first_name = Column(String) last_name = Column(String) books = relationship("Book", backref=backref("author")) publishers = relationship( "Publisher", secondary=author_publisher, back_populates="authors" ) class Book(Base): __tablename__ = "book" book_id = Column(Integer, primary_key=True) author_id = Column(Integer, ForeignKey("author.author_id")) title = Column(String) publishers = relationship( "Publisher", secondary=book_publisher, back_populates="books" ) class Publisher(Base): __tablename__ = "publisher" publisher_id = Column(Integer, primary_key=True) name = Column(String) authors = relationship( "Author", secondary=author_publisher, back_populates="publishers" ) books = relationship( "Book", secondary=book_publisher, back_populates="publishers" )
這是這個模塊中發生的事情:
1個線的進口Column,Integer,String,ForeignKey,和Table來自SQLAlchemy的班,這是用來幫助定義模型的屬性。
第 2 行導入relationship()和backref對象,用于創建對象之間的關系。
第 3 行導入declarative_base對象,該對象將數據庫引擎連接到模型的 SQLAlchemy 功能。
第 5 行創建了Base類,它是所有模型繼承的類以及它們如何獲得 SQLAlchemy ORM 功能。
第 7 到 12 行創建author_publisher關聯表模型。
第 14 到 19 行創建book_publisher關聯表模型。
第 21 到 29 行定義Author了author數據庫表的類模型。
第 31 到 38 行定義Book了book數據庫表的類模型。
第 40 到 49 行定義Publisher了publisher數據庫表的類模型。
上面的描述顯示了author_book_publisher.db數據庫中五個表的映射。但它掩蓋了一些SQLAlchemy的ORM的功能,包括Table,ForeignKey,relationship(),和backref。讓我們現在進入這些。
Table?創建關聯
author_publisher和book_publisher都是Table創建多對多關聯表的類的實例,分別用于author與publisher表和book與publisher表之間。
SQLAlchemyTable類在數據庫中創建一個 ORM 映射表的唯一實例。第一個參數是數據庫中定義的表名,第二個參數是Base.metadata,它提供了 SQLAlchemy 功能和數據庫引擎之間的連接。
其余參數是Column按名稱、類型以及在上面的示例中定義表字段的類的實例ForeignKey。
ForeignKey?創建連接
SQLAlchemyForeignKey類定義了Column不同表中兩個字段之間的依賴關系。AForeignKey是如何讓 SQLAlchemy 了解表之間的關系。例如,author_publisher實例創建中的這一行建立了外鍵關系:
Column("author_id", Integer, ForeignKey("author.author_id"))
上面的語句告訴 SQLAlchemy 在author_publisher名為的表中有一個列author_id。該列的類型是Integer,并且author_id是與author表中的主鍵相關的外鍵。
在實例中同時定義author_id和會創建從表到表的連接,反之亦然,從而建立多對多關系。publisher_idauthor_publisher?Tableauthorpublisher
relationship()?建立一個集合
books = relationship("Book", backref=backref("author"))
上面的代碼定義了一個父子集合。該books屬性存在復數(這不是必須的,只是一個約定)是一個跡象,這是一個集合。
第一個參數relationship()是類名Book(不是表名book),是與books屬性相關的類。該relationship運籌學SQLAlchemy的有總之間的關系Author和Book階級。SQLAlchemy 會在Book類定義中找到關系:
author_id = Column(Integer, ForeignKey("author.author_id"))
SQLAlchemy 認識到這是ForeignKey兩個類之間的連接點。稍后您將獲得backref參數relationship()。
中的另一個關系Author是與Publisher班級的關系。這是使用Author類定義中的以下語句創建的:
publishers = relationship( "Publisher", secondary=author_publisher, back_populates="authors" )
secondary告訴 SQLAlchemy 與Publisher類的關系是通過一個輔助表,它是author_publisher之前在models.py.?該secondary參數使 SQLAlchemy 找到關聯表中publisher_id?ForeignKey定義的author_publisher。
back_populates是一個方便的配置,告訴 SQLAlchemy 在Publisher名為authors.
backref?鏡子屬性
集合的backref參數為每個實例創建一個屬性。這一屬性涉及到母公司的實例有關。booksrelationship()authorBookAuthorBook
例如,如果您執行以下 Python 代碼,則將Book從 SQLAlchemy 查詢返回一個實例。該Book實例具有可用于打印有關書籍信息的屬性:
book = session.query(Book).filter_by(Book.title == "The Stand").one_or_none() print(f"Authors name: {book.author.first_name} {book.author.last_name}")
上面的author屬性存在Book是因為backref定義。backref當您需要引用父級并且您擁有的只是一個子實例時,使用A可能非常方便。
查詢回答問題
您可以像這樣SELECT * FROM author;在 SQLAlchemy 中進行基本查詢:
results = session.query(Author).all()
的session是用于在Python示例程序的SQLite通信的SQLAlchemy的對象。在這里,您告訴會話您要對Author模型執行查詢并返回所有記錄。
在這一點上,使用 SQLAlchemy 代替普通 SQL 的優勢可能并不明顯,尤其是考慮到創建代表數據庫的模型所需的設置。該results查詢返回的神奇在哪里發生。您將返回具有Author與您定義的列名稱匹配的屬性的對象實例列表,而不是返回標量數據列表的列表。
在幕后,SQLAlchemy 將對象和方法調用轉換為 SQL 語句以針對 SQLite 數據庫引擎執行。SQLAlchemy 將 SQL 查詢返回的數據轉換為 Python 對象。
author_book_totals = ( session.query( Author.first_name, Author.last_name, func.count(Book.title).label("book_total") ) .join(Book) .group_by(Author.last_name) .order_by(desc("book_total")) .all() )
示例程序
示例程序examples/example_2/main.py具有相同的功能,examples/example_1/main.py但僅使用 SQLAlchemy 與author_book_publisher.dbSQLite 數據庫進行交互。程序被分解為main()函數和它調用的函數:
def main(): """Main entry point of program""" # Connect to the database using SQLAlchemy with resources.path( "project.data", "author_book_publisher.db" ) as sqlite_filepath: engine = create_engine(f"sqlite:///{sqlite_filepath}") Session = sessionmaker() Session.configure(bind=engine) session = Session() # Get the number of books printed by each publisher books_by_publisher = get_books_by_publishers(session, ascending=False) for row in books_by_publisher: print(f"Publisher: {row.name}, total books: {row.total_books}") print() # Get the number of authors each publisher publishes authors_by_publisher = get_authors_by_publishers(session) for row in authors_by_publisher: print(f"Publisher: {row.name}, total authors: {row.total_authors}") print() # Output hierarchical author data authors = get_authors(session) output_author_hierarchy(authors) # Add a new book add_new_book( session, author_name="Stephen King", book_title="The Stand", publisher_name="Random House", ) # Output the updated hierarchical author data authors = get_authors(session) output_author_hierarchy(authors)
該程序是examples/example_1/main.py.?讓我們來看看差異:
第 4 到 7 行首先將sqlite_filepath變量初始化為數據庫文件路徑。然后他們創建engine變量以與 SQLite 和author_book_publisher.db數據庫文件進行通信,這是 SQLAlchemy 對數據庫的訪問點。
8號線創建Session從SQLAlchemy的的類sessionmaker()。
第 9Session行將綁定到第 8 行中創建的引擎。
第 10 行創建session實例,程序使用該實例與 SQLAlchemy 進行通信。
該函數的其余部分類似,只是將datawith替換session為所有被 調用的函數的第一個參數main()。
get_books_by_publisher()?已重構為使用 SQLAlchemy 和您之前定義的模型來獲取請求的數據:
def get_books_by_publishers(session, ascending=True): 2 """Get a list of publishers and the number of books they've published""" 3 if not isinstance(ascending, bool): 4 raise ValueError(f"Sorting value invalid: {ascending}") 5 6 direction = asc if ascending else desc 7 8 return ( 9 session.query( 10 Publisher.name, func.count(Book.title).label("total_books") 11 ) 12 .join(Publisher.books) 13 .group_by(Publisher.name) 14 .order_by(direction("total_books")) 15 )
這是新函數get_books_by_publishers(), 正在執行的操作:
第 6 行創建direction變量并根據參數的值將其設置為等于 SQLAlchemydesc或asc函數ascending。
第 12 行連接到Publisher.books集合。
第 13 行按Publisher.name屬性聚合圖書計數。
第 14 行根據由 定義的運算符按圖書計數對輸出進行排序direction。
第 15 行關閉對象,執行查詢,并將結果返回給調用者。
上面的所有代碼都表達了想要的東西,而不是如何檢索它。現在,您不再使用 SQL 來描述所需內容,而是使用 Python 對象和方法。返回的是 Python 對象列表,而不是數據元組列表。
get_authors_by_publisher()也被修改為專門與 SQLAlchemy 一起使用。它的功能與前面的功能非常相似,因此省略了功能說明:
def get_authors_by_publishers(session, ascending=True): """Get a list of publishers and the number of authors they've published""" if not isinstance(ascending, bool): raise ValueError(f"Sorting value invalid: {ascending}") direction = asc if ascending else desc return ( session.query( Publisher.name, func.count(Author.first_name).label("total_authors"), ) .join(Publisher.authors) .group_by(Publisher.name) .order_by(direction("total_authors")) )
def get_authors(session): """Get a list of author objects sorted by last name""" return session.query(Author).order_by(Author.last_name).all()
def add_new_book(session, author_name, book_title, publisher_name): """Adds a new book to the system""" # Get the author's first and last names first_name, _, last_name = author_name.partition(" ") # Check if book exists book = ( session.query(Book) .join(Author) .filter(Book.title == book_title) .filter( and_( Author.first_name == first_name, Author.last_name == last_name ) ) .filter(Book.publishers.any(Publisher.name == publisher_name)) .one_or_none() ) # Does the book by the author and publisher already exist? if book is not None: return # Get the book by the author book = ( session.query(Book) .join(Author) .filter(Book.title == book_title) .filter( and_( Author.first_name == first_name, Author.last_name == last_name ) ) .one_or_none() ) # Create the new book if needed if book is None: book = Book(title=book_title) # Get the author author = ( session.query(Author) .filter( and_( Author.first_name == first_name, Author.last_name == last_name ) ) .one_or_none() ) # Do we need to create the author? if author is None: author = Author(first_name=first_name, last_name=last_name) session.add(author) # Get the publisher publisher = ( session.query(Publisher) .filter(Publisher.name == publisher_name) .one_or_none() ) # Do we need to create the publisher? if publisher is None: publisher = Publisher(name=publisher_name) session.add(publisher) # Initialize the book relationships book.author = author book.publishers.append(publisher) session.add(book) # Commit to the database session.commit()
上面的代碼比較長。讓我們將功能分解為可管理的部分:
第 20 和 21 行確定這本書是否已經存在,如果存在則返回。
第 55 到 63行將publisher變量設置為現有發布者(如果找到),或者Publisher根據傳入的發布者名稱創建新實例。
第 67行將publisher實例添加到book.publishers集合中。這在book和publisher表之間創建了多對多關系。SQLAlchemy 將在表以及book_publisher連接兩者的關聯表中創建引用。
第 68行將Book實例添加到會話中,使其成為會話工作單元的一部分。
第 71行將所有創建和更新提交到數據庫。
這里有幾點需要注意。首先,在查詢或創建和更新中都沒有提及author_publisher或book_publisher關聯表。由于您在models.py設置關系方面所做的工作,SQLAlchemy 可以處理將對象連接在一起并在創建和更新期間保持這些表同步。
其次,所有的創建和更新都發生在session對象的上下文中。這些活動都沒有觸及數據庫。只有當session.commit()語句執行時,會話才會通過其工作單元并將該工作提交給數據庫。
例如,如果Book創建了一個新實例(如上面的第 37 行),那么除了book_id主鍵和author_id外鍵之外,書的屬性都會被初始化。因為還沒有發生數據庫活動,所以book_id是未知的,并且在實例化中沒有做任何事情book來給它一個author_id.
當session.commit()被執行時,的事情會做一個是插入book到數據庫中,此時數據庫將創建book_id主鍵。然后會話將book.book_id使用數據庫引擎創建的主鍵值初始化該值。
session.commit()也知道Book實例在author.books集合中的插入。該author對象的author_id主鍵將被添加到Book附加到實例author.books集合作為author_id外鍵。
SECRET_KEY = "you-will-never-guess" SQLALCHEMY_TRACK_MODIFICATIONS = False SQLAlCHEMY_ECHO = False DEBUG = True
示例應用程序相當大,只有其中的一部分與本教程相關。出于這個原因,檢查和學習代碼留給讀者作為練習。也就是說,您可以查看下面應用程序的動畫屏幕截圖,然后是呈現主頁的 HTML 和提供動態數據的 Python Flask 路由。
這是運行中的應用程序,瀏覽各種菜單和功能:
動畫屏幕截圖從應用程序主頁開始,使用Bootstrap 4設計樣式。該頁面顯示數據庫中的藝術家,按升序排序。屏幕截圖的其余部分顯示單擊顯示的鏈接或從頂級菜單導航應用程序的結果。
這是生成應用程序主頁的Jinja2?HTML 模板:
{% extends "base.html" %} {% block content %}
Artist Name |
---|
{{ artist.name }} |
這是 Jinja2 模板代碼中發生的事情:
第 1 行使用 Jinja2 模板繼承從base.html模板構建此模板。該base.html模板包含所有 HTML5 樣板代碼以及在站點的所有頁面上一致的 Bootstrap 導航欄。
第 3 到 37 行包含頁面的塊內容,它被合并到base.html基本模板中的同名 Jinja2 宏中。
第 9 到 13 行渲染表單以創建新藝術家。這使用Flask-WTF的特性來生成表單。
第 24 到 32 行創建了一個for循環,用于呈現藝術家姓名表。
第 27 到 29行將藝術家姓名呈現為指向藝術家專輯頁面的鏈接,顯示與特定藝術家相關聯的歌曲。
這是呈現頁面的 Python 路由:
from flask import Blueprint, render_template, redirect, url_for from flask_wtf import FlaskForm from wtforms import StringField from wtforms.validators import InputRequired, ValidationError from app import db from app.models import Artist # Set up the blueprint artists_bp = Blueprint( "artists_bp", __name__, template_folder="templates", static_folder="static" ) def does_artist_exist(form, field): artist = ( db.session.query(Artist) .filter(Artist.name == field.data) .one_or_none() ) if artist is not None: raise ValidationError("Artist already exists", field.data) class CreateArtistForm(FlaskForm): name = StringField( label="Artist's Name", validators=[InputRequired(), does_artist_exist] ) @artists_bp.route("/") @artists_bp.route("/artists", methods=["GET", "POST"]) def artists(): form = CreateArtistForm() # Is the form valid? if form.validate_on_submit(): # Create new artist artist = Artist(name=form.name.data) db.session.add(artist) db.session.commit() return redirect(url_for("artists_bp.artists")) artists = db.session.query(Artist).order_by(Artist.name).all() return render_template("artists.html", artists=artists, form=form,)
讓我們回顧一下上面的代碼是做什么的:
第 1 到 6 行導入呈現頁面所需的所有模塊,并使用數據庫中的數據初始化表單。
第 9 到 11 行創建了藝術家頁面的藍圖。
第 13 到 20 行為Flask-WTF 表單創建了一個自定義驗證器函數,以確保創建新藝術家的請求不會與現有藝術家發生沖突。
第 22 到 25 行創建表單類來處理在瀏覽器中呈現的藝術家表單并提供表單字段輸入的驗證。
第 27 到 28行將兩條路由連接到artists()它們所裝飾的函數。
第 30 行創建了一個CreateArtistForm()類的實例。
第 33 行確定頁面是通過 HTTP 方法 GET 還是 POST(提交)請求的。如果它是 POST,那么它還會驗證表單的字段并通知用戶這些字段是否無效。
第 35 到 37 行創建了一個新的藝術家對象,將其添加到 SQLAlchemy 會話中,并將藝術家對象提交到數據庫中,并將其持久化。
第 38 行重定向回藝術家頁面,該頁面將使用新創建的藝術家重新呈現。
第 40行運行 SQLAlchemy 查詢以獲取數據庫中的所有藝術家并按Artist.name.
如果 HTTP 請求方法是 GET,則第41 行呈現藝術家頁面。
您可以看到大量的功能是由相當少的代碼創建的。
創建 REST API 服務器
您還可以創建一個提供REST?API的 Web 服務器。這種服務器提供 URL 端點響應數據,通常是JSON格式。JavaScript 單頁 Web 應用程序可以通過使用 AJAX HTTP 請求來使用提供 REST API 端點的服務器。
Flask 是創建 REST 應用程序的絕佳工具。有關使用 Flask、Connexion 和 SQLAlchemy 創建 REST 應用程序的多部分系列教程,請查看Python REST APIs With Flask、Connexion 和 SQLAlchemy。
如果您是 Django 的粉絲并且對創建 REST API 感興趣,請查看Django Rest Framework – 簡介和使用 Django Tastypie 創建超級基本 REST API。
注意:詢問 SQLite 作為 Web 應用程序的數據庫后端是否是正確的選擇是合理的。在SQLite的網站指出,SQLite是為滿足每天10萬左右的點擊網站一個不錯的選擇。如果您的網站獲得更多的每日點擊量,首先要說的是恭喜!
除此之外,如果您使用 SQLAlchemy 實現了您的網站,則可以將數據從 SQLite 移動到另一個數據庫,例如MySQL或 PostgreSQL。有關 SQLite、MySQL 和 PostgreSQL 的比較以幫助您決定哪一個最適合您的應用程序,請查看Python SQL 庫簡介。
為您的 Python 應用程序考慮 SQLite 是非常值得的,無論它是什么。使用數據庫可為您的應用程序提供多功能性,并且可能會為添加其他功能創造驚人的機會。
結論
您在本教程中涵蓋了很多關于數據庫、SQLite、SQL 和 SQLAlchemy 的基礎知識!您已經使用這些工具將平面文件中包含的數據移動到 SQLite 數據庫,使用 SQL 和 SQLAlchemy 訪問數據,并通過 Web 服務器提供該數據。
在本教程中,您學習了:
為什么SQLite 數據庫可以成為平面文件數據存儲的引人注目的替代品
如何規范化數據以減少數據冗余并提高數據完整性
如何使用SQLAlchemy以面向對象的方式處理數據庫
如何構建一個Web 應用程序來為多個用戶提供數據庫服務
使用數據庫是處理數據的強大抽象,它為您的 Python 程序增加了重要的功能,并允許您對數據提出有趣的問題。
您可以通過以下鏈接獲取在本教程中看到的所有代碼和數據:
進一步閱讀
本教程是對使用數據庫、SQL 和 SQLAlchemy 的介紹,但關于這些主題還有很多東西需要了解。這些是功能強大、復雜的工具,沒有一個教程可以充分涵蓋。以下是一些資源,可提供更多信息以擴展您的技能:
如果您的應用程序會將數據庫暴露給用戶,那么避免 SQL 注入攻擊是一項重要技能。有關更多信息,請查看使用 Python 防止 SQL 注入攻擊。
提供對數據庫的 Web 訪問在基于 Web 的單頁應用程序中很常見。要了解如何操作,請查看帶有 Flask、Connexion 和 SQLAlchemy 的 Python REST API – 第 2 部分。
準備數據工程工作面試會讓你在職業生涯中占上風。要開始,請查看Python 的數據工程師面試問題。
使用 Flask 與 Postgres 和 SQLAlchemy 遷移數據并能夠回滾是軟件開發生命周期 (SDLC) 不可或缺的一部分。您可以通過查看Flask by Example – 設置 Postgres、SQLAlchemy 和 Alembic了解更多信息。
【生長吧!Python】有獎征文火熱進行中:https://bbs.huaweicloud.com/blogs/278897
Python SQLite 數據庫 數據管理服務 DAS
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。