Введение в Flask SQLAlchemy

Оглавление

Введение

В этой статье мы познакомимся с основами SQLAlchemy, создав управляемое данными веб-приложение с помощью Flask, фреймворка Python. Мы создадим минимальное приложение на Flask, которое будет следить за вашей коллекцией книг.

В самом простом виде приложение позволит пользователям создавать новые книги, читать все существующие книги, обновлять их и удалять. Эти операции - создание, чтение, обновление и удаление - более известны как "CRUD" и составляют основу практически всех веб-приложений. Подробнее о CRUD-операциях мы поговорим далее в статье.

Но прежде чем мы начнем играть с CRUD, давайте разберемся с другими частями приложения, начиная с SQLAlchemy.

Что такое SQLAlchemy?

Обратите внимание, что у нас есть расширение Flask flask-sqlalchemy, которое упрощает использование SQLAlchemy, предоставляя полезные настройки по умолчанию и дополнительные помощники, облегчающие выполнение общих задач. В этой статье мы будем использовать только обычную SQLAlchemy - просто для того, чтобы иметь базовое понимание SQLAlchemy перед добавлением каких-либо расширений.

Согласно информации с сайта компании, "SQLAlchemy - это инструментарий для работы с SQL на языке Python и объектно-реляционный картограф, который предоставляет разработчикам приложений всю мощь и гибкость SQL".

После прочтения приведенного выше определения первый вопрос, который возникает, - что такое объектно-реляционное отображение (Object Relational Mapper). Object Relational Mapper, также известный как ORM, - это техника, используемая для написания запросов к базе данных с использованием объектно-ориентированной парадигмы предпочитаемого языка (в данном случае Python).

В еще более простых терминах ORM можно представить как транслятор, переводящий код из одного набора абстракций в другой. В нашем случае - с языка Python на язык SQL.

Существует множество различных причин для использования ORM, помимо отсутствия необходимости создавать SQL-строки. Вот некоторые из них:

  • Ускорение веб-разработки, поскольку нам не нужно переключаться между написанием Python и SQL
  • Устранение повторяющегося кода
  • Упрощение рабочего процесса и более эффективный запрос данных
  • Абстрагирование от системы баз данных, что позволяет плавно переключаться между различными базами данных
  • Генерация шаблонного кода для основных операций CRUD

Давайте копнем чуть глубже.

Зачем использовать ORM, если можно писать запросы к базе данных, используя сырой SQL? Когда мы пишем запросы с использованием "сырого" SQL, мы передаем их в базу данных в виде строк. Следующий запрос написан на языке SQL:

#imports sqlite
import sqlite3

#connects it to the books-collection database
conn = sqlite3.connect('books-collection.db')

#creates the cursor
c = conn.cursor()

#execute the query which creates the table called books with id and name
#as the columns
c.execute('''
          CREATE TABLE books
          (id INTEGER PRIMARY KEY ASC,
	     name varchar(250) NOT NULL)
          ''' )

#executes the query which inserts values in the table
c.execute("INSERT INTO books VALUES(1, 'The Bell Jar')")

#commits the executions
conn.commit()

#closes the connection
conn.close()

Итак, нет ничего плохого в использовании "сырого" SQL для общения с базами данных, если только мы не допустим ошибку в запросе, например, опечатку или подключение к несуществующей базе данных, или попытку доступа к несуществующей таблице. Компилятор Python не сможет нам помочь.

SQLAlchemy - одна из многочисленных реализаций объектно-реляционного картографа на языке Python. Если мы работаем над небольшим приложением, то использование необработанного SQL может сработать, но если мы работаем над крупным сайтом, управляемым данными, то использование необработанного SQL может оказаться сложным и чреватым ошибками.

Чтобы обойти эту проблему, мы можем писать запросы не в виде строк, а в виде объектов, используя ORM. ORM преобразует наш код, написанный на Python (или любом другом языке), в операторы SQL. Все просто!

Достаточно теории. Давайте перейдем к делу и начнем писать код!

Создание базы данных с помощью SQLAlchemy

Создадим файл, который будет устанавливать и настраивать нашу базу данных. Мы можем назвать этот файл как угодно, но для данной статьи назовем его database_setup.py.

import sys
#for creating the mapper code
from sqlalchemy import Column, ForeignKey, Integer, String

#for configuration and class code
from sqlalchemy.ext.declarative import declarative_base

#for creating foreign key relationship between the tables
from sqlalchemy.orm import relationship

#for configuration
from sqlalchemy import create_engine

#create declarative_base instance
Base = declarative_base()

#we'll add classes here

#creates a create_engine instance at the bottom of the file
engine = create_engine('sqlite:///books-collection.db')

Base.metadata.create_all(engine)

В верхней части файла мы импортируем все необходимые модули для настройки и создания нашей базы данных. Как вы увидите, мы импортировали Column, ForeignKey, Integer и String для определения столбцов таблицы нашей базы данных.

Далее мы импортируем декларативную базу. Base = declarative_base() конструирует базовый класс для декларативного определения класса и присваивает его переменной Base.

Как сказано в документации, declarative_base() возвращает новый базовый класс, от которого должны наследоваться все отображаемые классы. В объявлении класса одновременно выражаются Table, mapper() и объекты класса.

Далее мы создаем экземпляр нашего класса create engine, который указывает на базу данных, добавляя engine = create_engine('sqlite:///books-collection.db'). Мы можем назвать нашу базу данных как угодно, но здесь мы назвали ее books-collection.

Последним шагом в нашей конфигурации является добавление Base.metadata.create_all(engine). Оно добавит классы (мы напишем их чуть позже) как новые таблицы в только что созданную базу данных.

После настройки базы данных создадим классы. В SQLAlchemy классы являются объектно-ориентированным или декларативным представлением таблиц в нашей базе данных.

#we create the class Book and extend it from the Base Class.
class Book(Base):
   __tablename__ = 'book'

   id = Column(Integer, primary_key=True)
   title = Column(String(250), nullable=False)
   author = Column(String(250), nullable=False)
   genre = Column(String(250))

В данном учебном пособии нам необходимо создать только одну таблицу: Book. Наша таблица Book имеет четыре столбца: id, title, author и genre. Для определения типа значений, хранящихся в столбце, используются Integer и String: столбцы title, author и genre являются строками, а столбец id имеет тип integer.

Существует множество атрибутов класса, которые мы можем использовать для определения наших колонок, но давайте рассмотрим некоторые из атрибутов класса, которые мы использовали здесь.

  1. primary_key: При значении true указывает на значение, которое может быть использовано для уникальной идентификации каждой строки нашей таблицы.
  2. String(250): В то время как строка определяет тип значения, заключенное в нее число обозначает максимальное количество строк.
  3. Integer: Integer устанавливает тип значения.
  4. nullable: При значении false указывает, что для создания строки необходимо иметь значение.

На этом установка и настройка базы данных завершена. Если мы выполним команду python database_setup.py в терминале, то будет создана пустая база данных с именем books-collection.db. Теперь, когда наша пустая база данных готова, давайте заполним ее и попробуем с ней пообщаться.

CRUD с SQLAlchemy на примере

Помните, как в самом начале мы кратко коснулись операций CRUD? Давайте используем их сейчас.

Создадим еще один файл и назовем его populate.py (или любое другое имя, которое вы захотите дать).

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
#Let’s import our Book and Base classes from our database_setup.py file
from database_setup import Book, Base

engine = create_engine('sqlite:///books-collection.db')
# Bind the engine to the metadata of the Base class so that the
# declaratives can be accessed through a DBSession instance
Base.metadata.bind = engine

DBSession = sessionmaker(bind=engine)
# A DBSession() instance establishes all conversations with the database
# and represents a "staging zone" for all the objects loaded into the
# database session object.
session = DBSession()

Сначала импортируем некоторые зависимости и некоторые классы из нашего database_setup.py файла.

Затем мы сообщаем нашей программе, с каким движком базы данных мы хотим взаимодействовать. Для этого мы используем функцию create_engine.

Для того чтобы установить связи между определениями классов и таблицами в базе данных, мы используем команду Base.metadata.bind.

Для создания, удаления, чтения или обновления записей в нашей базе данных SQLAlchemy предоставляет интерфейс, называемый Session. Для выполнения наших запросов нам необходимо добавить и зафиксировать нашу запись. Кроме того, интерфейс предоставляет нам метод flush(). Flush переносит наши изменения из памяти в буфер транзакций нашей базы данных без фиксации изменений.

CREATE:

Общая процедура создания записи такова:

entryName = ClassName(property="value", property="value" ... )

#To persist our ClassName object, we add() it to our Session:
session.add(entryName)

#To issue the changes to our database and commit the transaction we use commit(). #Any change made against the objects in the session won't be persisted into the #database until you call session.commit().

session.commit()

Мы можем создать нашу первую книгу, выполнив следующую команду:

bookOne = Book(title="The Bell Jar", author="Sylvia Pla", genre="roman à clef")
session.add(bookOne)
session.commit()

READ:

В зависимости от того, что мы хотим прочитать, мы можем использовать различные функции. Рассмотрим два варианта их потенциального использования в нашем приложении.

session.query(Book).all() - вернется список всех книг
session.query(Book).first() - возвращается первый результат или 'None', если результат не содержит ни одной строки.

UPDATE:

Для обновления записей в нашей базе данных необходимо выполнить следующие действия:

  1. Найти запись
  2. Сбросить значения
  3. Добавить новую запись
  4. Зафиксировать сессию в нашей базе данных

Если вы еще не заметили, то в нашей записи на BookOne есть ошибка. Книга The Bell Jar была написана Сильвией Плат, а не какой-то "Сильвией Пла". Давайте обновим имя автора, используя четыре шага, которые мы только что рассмотрели.

Для поиска записи можно использовать filter_by(), который позволяет фильтровать запросы на основе записей атрибутов. Следующий запрос даст нам книгу с id=1 (т.е. The Bell Jar)

editedBook = session.query(Book).filter_by(id=1).one()

Для сброса и фиксации имени автора я могу выполнить следующие команды:

editedBook.author = "Sylvia Plath"
session.add(editedBook)
session.commit()

Для поиска записи мы можем использовать all(), one() или first() в зависимости от ожидаемого результата. Однако здесь есть несколько проблем, с которыми следует быть осторожными.

  1. all() - возвращает результаты, представленные запросом, в виде списка
  2. one() - возвращает ровно один результат или вызывает исключение. Вызывает исключение sqlalchemy.orm.exc.NoResultFound, если результат не найден, или исключение sqlalchemy.orm.exc.NoResultFound, если возвращено несколько результатов
  3. .
  4. first() - возвращает первый результат запроса или 'None', если результат не содержит ни одной строки, но возбуждается исключение

DELETE:

Удаление значений из нашей базы данных практически не отличается от их обновления. Вместо обновления мы удаляем значения. Давайте посмотрим:

  1. Найти запись
  2. Удалить запись
  3. Зафиксировать сессию
bookToDelete = session.query(Book).filter_by(name='The Bell Jar').one()
session.delete(bookToDelete)
session.commit()

Теперь, когда у нас настроена база данных и мы знаем, как использовать CRUD-операции, давайте напишем минимальное Flask-приложение. В этой статье мы не будем углубляться в тему Flask, но если вам нужно освежить ее в памяти, вы можете прочитать о ней подробнее здесь.

Создадим новый файл app.py в том же каталоге, что и database_setup.py и populate.py. Затем мы импортируем некоторые из необходимых зависимостей.

from flask import Flask, render_template, request, redirect, url_for
app = Flask(__name__)

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from database_setup import Base, Book

#Connect to Database and create database session
engine = create_engine('sqlite:///books-collection.db')
Base.metadata.bind = engine

DBSession = sessionmaker(bind=engine)
session = DBSession()


#landing page that will display all the books in our database
#This function operate on the Read operation.
@app.route('/')
@app.route('/books')
def showBooks():
   books = session.query(Book).all()
   return render_template("books.html", books=books)



#This will let us Create a new book and save it in our database
@app.route('/books/new/',methods=['GET','POST'])
def newBook():
   if request.method == 'POST':
       newBook = Book(title = request.form['name'], author = request.form['author'], genre = request.form['genre'])
       session.add(newBook)
       session.commit()
       return redirect(url_for('showBooks'))
   else:
       return render_template('newBook.html')


#This will let us Update our books and save it in our database
@app.route("/books/<int:book_id>/edit/", methods = ['GET', 'POST'])
def editBook(book_id):
   editedBook = session.query(Book).filter_by(id=book_id).one()
   if request.method == 'POST':
       if request.form['name']:
           editedBook.title = request.form['name']
           return redirect(url_for('showBooks'))
   else:
       return render_template('editBook.html', book = editedBook)

#This will let us Delete our book
@app.route('/books/<int:book_id>/delete/', methods = ['GET','POST'])
def deleteBook(book_id):
   bookToDelete = session.query(Book).filter_by(id=book_id).one()
   if request.method == 'POST':
       session.delete(bookToDelete)
       session.commit()
       return redirect(url_for('showBooks', book_id=book_id))
   else:
       return render_template('deleteBook.html',book = bookToDelete)


if __name__ == '__main__':
   app.debug = True
   app.run(host='0.0.0.0', port=4996)

Наконец, нам необходимо создать шаблоны, т.е. books.html, newBook.html, editBook.html и deleteBook.html. Для этого создадим папку template на том же уровне, что и наш файл app.py. В этой папке мы создадим эти четыре файла.

#books.html

<html>
<body>
   <h1>Books</h1>
   <a href="{{url_for('newBook')}}">
       <button>Add Boo</button>
   </a>
   <ol>
       {% for book in books %}
       <li> {{book.title}} by {{book.author}} </li>
       <a href="{{url_for('editBook', book_id = book.id )}}">
           Edit
       </a>
       <a href="{{url_for('deleteBook', book_id = book.id )}}" style="margin-left: 10px;">
           Delete
       </a>
       <br> <br>
       {% endfor %}
   </ol>
</body>
</html>

Затем создадим файл newBook.html.

<h1>Add a Book</h1>
<form action="#" method="post">
   <div class="form-group">
       <label for="name">Title:</label>
       <input type="text" maxlength="100" name="name" placeholder="Name of the book">

       <label for="author">Author:</label>
       <input maxlength="100" name="author" placeholder="Author of the book">

       <label for="genre">Genre:</label>
       <input maxlength="100" name="genre" placeholder="Genre of the book">

       <button type="submit">Create</button>
   </div>
</form>

Далее следует editBook.html.

<form action="{{ url_for('editBook',book_id = book.id)}}" method="post">
   <div class="form-group">
       <label for="name">Title:</label>
       <input type="text" class="form-control" name="name" value="{{book.title }}">
       <button type="submit"> SAVE</button>
       <a href='{{url_for('showBooks')}}'>
           <button>Cancel</button>
       </a>
   </div>
</form>

Затем удалитьBook.html

<form action="{{ url_for('editBook',book_id = book.id)}}" method="post">
   <div class="form-group">
       <label for="name">Title:</label>
       <input type="text" class="form-control" name="name" value="{{book.title }}">
       <button type="submit"> SAVE</button>
       <a href='{{url_for('showBooks')}}'>
           <button>Cancel</button>
       </a>
   </div>
</form>

Если выполнить команду python app.py и направить браузер по адресу http://localhost:4996/books, то на экране должен появиться список книг. Если все работает, то на экране должно появиться что-то вроде этого:

Расширение приложения & Заключение

Если вы дошли до этого момента, то, надеюсь, узнали кое-что о том, как работает SQLAlchemy! SQLAlchemy - это огромная тема, и мы рассмотрели только ее основы, поэтому если вы хотите узнать больше, попробуйте сделать другое CRUD-приложение или усовершенствовать это приложение, добавив новые возможности. Если вы хотите продолжить работу над этим приложением, то можете попробовать добавить в базу данных таблицу Shelf для отслеживания прогресса в чтении, или, если вы хотите пойти дальше, попробуйте использовать Flask-Login для добавления в приложение функции аутентификации и авторизации. Добавление аутентификации и авторизации может сделать ваше приложение более масштабируемым. Вместо того чтобы каждый пользователь мог применять CRUD-операции к вашему книжному приложению, он может настроить его и обновлять только свои книги.

Вернуться на верх