Для работы понадобится python 3.6+, библиотеки SQLAlchemy и Flask. Код урока здесь.
Версии библиотек в файлеrequirements.txt
В этом материале речь пойдет об основах SQLAlchemy. Создадим веб-приложение на Flask, фреймворке языка Python. Это будет минималистичное приложение, которое ведет учет книг.
С его помощью можно будет добавлять новые книги, читать уже существующие, обновлять и удалять их. Эти операции — создание, чтение, обновление и удаление — также известны как «CRUD» и составляют основу почти всех веб-приложений. О них отдельно пойдет речь в статье.
Но прежде чем переходить к CRUD, разберемся с отдельными элементами приложения, начиная с SQLAlchemy.
Что такое SQLAlchemy?
Стоит отметить, что существует расширение для Flask под названием flask-sqlalchemy
, которое упрощает процесс использования SQLAlchemy с помощью некоторых значений по умолчанию и других элементов. Они в первую очередь облегчают выполнение базовых задач. Но в этом материале будет использоваться только чистый SQLAlchemy, чтобы разобраться в его основах без разных расширений.
Как написано на сайте библиотеки «SQLAlchemy — это набор SQL-инструментов для Python и инструмент объектно-реляционного отображения (ORM), который предоставляет разработчикам всю мощь и гибкость SQL».
При чтении этого определения в первую очередь возникает вопрос: а что же такое объектно-реляционное отображение? ORM — это техника, используемая для написания запросов к базам данных с помощью парадигм объектно-ориентированного программирования выбранного языка (Python в этом случае).
Если еще проще, ORM — это своеобразный переводчик, который переводит код с одного набора абстракций в другой. В этом случае — из Python в SQL.
Есть масса причин, почему стоит использовать ORM, а не вручную сооружать строки SQL. Вот некоторые из них:
- Ускорение веб-разработки, ведь пропадает необходимость переключаться между написанием кода Python и SQL
- Устранение повторяющегося кода
- Оптимизация рабочего процесса и более эффективные запросы к базе данных
- Абстрагирование системы базы данных, так что переключение между несколькими базами становится более плавным
- Генерация шаблонного кода для основных операций CRUD
Углубимся еще сильнее.
Зачем использовать ORM, когда можно писать сырой SQL? При написании запросов на сыром SQL, мы передаем их базе данных в виде строк. Следующий запрос написан на сыром SQL:
#импорт sqlite
import sqlite3
# подключаемся к базе данных коллекции книг
conn = sqlite3.connect('books-collection.db')
# создаем объект cursor, для работы с базой данных
c = conn.cursor()
# делаем запрос, который создает таблицу books с идентификатором и именем
c.execute('''
CREATE TABLE books
(id INTEGER PRIMARY KEY ASC,
name varchar(250) NOT NULL)
''' )
# выполняет запрос, который вставляет значения в таблицу
c.execute("INSERT INTO books VALUES(1, 'Чистый Python')")
# сохраняем работу
conn.commit()
# закрываем соединение
conn.close()
Нет ничего плохого в использовании чистого SQL для обращения к базам данных, только если вы не сделаете ошибку в запросе. Это может быть, например, опечатка в названии базы, к которой происходит обращение или неправильное название таблицы. Компилятор Python здесь ничем не поможет.
SQLAlchemy — один из множества ORM-инструментов для Python. При работе с маленькими приложения чистый SQL может сработать. Но если это большой сайт с массой данных, такой подход сильнее подвержен ошибкам и просто более сложен.
Создание базы данных с помощью SQLAlchemy
Создадим файл для настройки базы данных. Можете назвать его как угодно, но пусть это будет database_setup.py
.
import sys
# для настройки баз данных
from sqlalchemy import Column, ForeignKey, Integer, String
# для определения таблицы и модели
from sqlalchemy.ext.declarative import declarative_base
# для создания отношений между таблицами
from sqlalchemy.orm import relationship
# для настроек
from sqlalchemy import create_engine
# создание экземпляра declarative_base
Base = declarative_base()
# здесь добавим классы
# создает экземпляр create_engine в конце файла
engine = create_engine('sqlite:///books-collection.db')
Base.metadata.create_all(engine)
В верхней части файла импортируем все необходимые модули для настройки и создания баз данных. Для определения колонок в таблицах импортируем Column, ForeignKey, Integer
и String
.
Далее импортируем расширение declarative_base
. Base = declarative_base()
создает базовый класс для определения декларативного класса и присваивает его переменной Base
.
Согласно документации declarative_base()
возвращает новый базовый класс, который наследуют все связанные классы. Это таблица, mapper()
и объекты класса в пределах его определения.
Далее создаем экземпляр класса create_engine
, который указывает на базу данных с помощью engine = create_engine('sqlite:///books-collection.db')
. Можно назвать базу данных как угодно, но здесь пусть будет books-collection
.
Последний этап настройки — добавление Base.metadata.create_all(engine)
. Это добавит классы (напишем их чуть позже) в виде таблиц созданной базы данных.
После настройки базы данных создаем классы. В SQLAlchemy классы являются объектно-ориентированными или декларативными представлениями таблицы в базе данных.
# мы создаем класс Book наследуя его из класса Base.
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
. Она будет содержать 4 колонки: id
, title
, author
и genre
. Integer
и String
используются для определения типа значений, которые будут храниться в колонках. Колонка с названием, именем автора и жанром — это строки, а id
— число.
Есть много атрибутов класса, которые используются для определения колонок, но рассмотрим уже использованные:
primary_key
: при значенииTrue
указывает на значение, используемое для идентификации каждой уникальной строки таблицы.String(250)
: String — тип значения, а значение в скобках — максимальная длина строки.Integer
: указывает тип значения (целое число).nullable
: еслиFalse
, это значит, что для создания строки обязательно должно быть значение .
На этом процесс настройки заканчивается. Если сейчас использовать команду python database_setup.py
в командной строке, будет создана пустая база данных books-collection.db
. Теперь можно наполнять ее данными и пробовать обращаться.
CRUD с SQLAlchemy на примерах
В начале кратко была затронута тема операций CRUD. Пришло время их использовать.
Создадим еще один файл и назовем его populate.py
(или любым другим именем).
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
# импортируем классы Book и Base из файла database_setup.py
from database_setup import Book, Base
engine = create_engine('sqlite:///books-collection.db')
# Свяжим engine с метаданными класса Base,
# чтобы декларативы могли получить доступ через экземпляр DBSession
Base.metadata.bind = engine
DBSession = sessionmaker(bind=engine)
# Экземпляр DBSession() отвечает за все обращения к базе данных
# и представляет «промежуточную зону» для всех объектов,
# загруженных в объект сессии базы данных.
session = DBSession()
В первую очередь импортируем все зависимости и некоторые классы из файла database_setup.py
.
Затем сообщим программе, с какой базой данных хотим взаимодействовать. Это делается с помощью функции create_engine
.
Что бы создать соединение между определениями класса и таблицами в базе данных, используем команду Base.metadata.bind
.
Для создания, удаления, чтения или обновления записей в базе данных SQLAlchemy предоставляет интерфейс под названием Session
. Для выполнения запросов необходимо добавлять и фиксировать (делать комит) запроса. Используем метод flush()
. Он переносит изменения из памяти в буфер транзакции базы данных без фиксации изменения.
CREATE:
Стандартный процесс создания записи следующий:
entryName = ClassName(property="value", property="value" ... )
# Чтобы сохранить наш объект ClassName, мы добавляем его в наш сессию:
session.add(entryName)
'''
Чтобы сохранить изменения в нашу базу данных и зафиксировать
транзакцию, мы используем commit(). Любое изменение,
внесенное для объектов в сессии, не будет сохранено
в базу данных, пока вы не вызовете session.commit().
'''
session.commit()
Создать первую книгу можно с помощью следующей команды:
bookOne = Book(title="Чистый Python", author="Дэн Бейде", genre="компьютерная литература")
session.add(bookOne)
session.commit()
READ:
В зависимости от того, что нужно прочитать, используются разные функции. Рассмотрим два варианты их использования в приложении.
session.query(Book).all()
— вернет список всех книг
session.query(Book).first()
— вернет первый результат или None
, если строки нет
UPDATE:
Для обновления записей в базе данных, нужно проделать следующее:
- Найти запись
- Сбросить значения
- Добавить новую запись
- Зафиксировать сессию в базе данных (сделать комит)
Если еще не заметили, в записи bookOne
есть ошибка. Книгу «Чистый Python» написал Дэн Бейдер, а не «Дэн Бейде». Обновим имя автора с помощью 4 описанных шагов.
Для поиска записи используется filter()
, который фильтрует запросы на основе атрибутов записей. Следующий запрос выдаст книгу с id=1
(то есть, «Чистый Python»)
editedBook = session.query(Book).filter_by(id=1).one()
Чтобы сбросить и зафиксировать имя автора, нужны следующие команды:
editedBook.author = "Дэн Бейдер"
session.add(editedBook)
session.commit()
Можно использовать all()
, one()
или first()
для поиска записи в зависимости от ожидаемого результата. Но есть несколько нюансов, о которых важно помнить.
all()
— возвращает результаты запроса в виде спискаone()
— возвращает один результат или вызывает исключение. Вызовет исключениеsqlaclhemy.orm.exc.NoResultFoud
, если результат не найден илиsqlaclhemy.orm.exc.NoResultFoud
, если были возвращены несколько результатовfirst()
— вернет первый результат запроса илиNone
, если он не содержит строк, но без исключения
DELETE:
Удаление значений из базы данных — это почти то же самое, что и обновление:
- Находим запись
- Удаляем запись
- Фиксируем сессию
bookToDelete = session.query(Book).filter_by(title='Чистый Python').one()
session.delete(bookToDelete)
session.commit()
Теперь когда база данных настроена и есть базовое понимание CRUD-операций, пришло время написать небольшое приложение Flask. Но в сам фреймворк не будем углубляться. О нем можно подробнее почитать в других материалах.
Создадим новый файл app.py
в той же папке, что и database_setup.py
и populate.py
. Затем импортируем необходимые зависимости.
from flask import Flask, render_template, request, redirect, url_for
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from database_setup import Base, Book
app = Flask(__name__)
# Подключаемся и создаем сессию базы данных
engine = create_engine('sqlite:///books-collection.db?check_same_thread=False')
Base.metadata.bind = engine
DBSession = sessionmaker(bind=engine)
session = DBSession()
# страница, которая будет отображать все книги в базе данных
# Эта функция работает в режиме чтения.
@app.route('/')
@app.route('/books')
def showBooks():
books = session.query(Book).all()
return render_template("books.html", books=books)
# Эта функция позволит создать новую книгу и сохранить ее в базе данных.
@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')
# Эта функция позволит нам обновить книги и сохранить их в базе данных.
@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)
# Эта функция для удаления книг
@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(port=4996)
Наконец, нужно создать шаблоны: books.html
, newBook.html
, editBook.html
и deleteBook.html
. Для этого создадим папку с шаблонами во Flask templates
на том же уровне, где находится файл app.py
. Внутри него создадим четыре файла.
books.html
<html>
<body>
<h1>Books</h1>
<a href="{{url_for('newBook')}}">
<button>Добавить книгу</button>
</a>
<ol>
{% for book in books %}
<li> {{book.title}} by {{book.author}} </li>
<a href="{{url_for('editBook', book_id = book.id )}}">
Изменить
</a>
<a href="{{url_for('deleteBook', book_id = book.id )}}" style="margin-left: 10px;">
Удалить
</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">Название:</label>
<input type="text" maxlength="100" name="name" placeholder="Название книги">
<label for="author">Автор:</label>
<input maxlength="100" name="author" placeholder="Автор книги">
<label for="genre">Жанр:</label>
<input maxlength="100" name="genre" placeholder="Жанр книги">
<button type="submit">Добавить</button>
</div>
</form>
Дальше editBook.html
.
<form action="{{ url_for('editBook',book_id = book.id)}}" method="post">
<div class="form-group">
<label for="name">Название:</label>
<input type="text" class="form-control" name="name" value="{{book.title }}">
<button type="submit">Обновить</button>
</div>
</form>
<a href='{{ url_for('showBooks') }}'>
<button>Отменить</button>
</a>
И deleteBook.html
.
<h2>Вы уверены, что хотите удалить {{book.title}}?</h2>
<form action="#" method='post'>
<button type="submit">Удалить</button>
</form>
<a href='{{url_for('showBooks')}}'>
<button>Отменить</button>
</a>
Если запустить приложение app.py
и перейти в браузере на страницу https://localhost:4996/books, отобразится список книг. Добавьте несколько и если все работает, это выглядит вот так:
Расширение приложения и выводы
Если вы добрались до этого момента, то теперь знаете чуть больше о том, как работает SQLAlchemy. Это важная и объемная тема, а в этом материале речь шла только об основных вещах, поэтому поработайте с другими CRUD-операциями и добавьте в приложение новые функции.
Можете добавить таблицу Shelf
в базу данных, чтобы отслеживать свой прогресс чтения, или даже реализовать аутентификацию с авторизацией. Это сделает приложение более масштабируемым, а также позволит другим пользователям добавлять собственные книги.