Создание БД, установление и разрыв соединения при запросах

Продолжаем изучать фреймворк Flask и на этом занятии поговорим об использовании БД в WSGI-приложениях. Зачем они нужны, я думаю вы хорошо себе представляете. В двух словах, в БД сохраняется вся изменяемая информация, которая, затем, используется при формировании ответов на запросы пользователей. Например, мы можем сохранить в БД меню сайта и при формировании HTML-страниц, брать оттуда информацию и представлять ее в виде меню страницы.

В рамках наших занятий мы будем использовать СУБД SQLite, которая поставляется вместе с Python3. Но для реальных сайтов – это не лучший выбор. Гораздо продуктивнее будет использовать функционал модуля

SQLAlchemy

или СУБД

PostgreSQL

Однако, в качестве первого шага, вполне подойдет и SQLite. Если вы не знаете как работать с этой СУБД, то смотрите занятие по этой теме. Итак, первым делом в программе помимо Flask выполним импорт дополнительных пакетов:

import sqlite3
import os
from flask import Flask, render_template, request

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

# конфигурация
DATABASE = '/tmp/flsite.db'
DEBUG = True
SECRET_KEY = 'fdgfh78@#5?>gfhf89dx,v06k'
USERNAME = 'admin'
PASSWORD = '123'

Здесь все параметры, в принципе, понятны, кроме, может быть, одного –  SECRET_KEY. Он необходим для безопасной работы сессий на стороне клиента. С помощью этого секретного ключа выполняется шифрование данных, которые, затем, сохраняются в куках браузера. Поэтому, даже глядя на информацию, сохраненную в браузере, пользователь не сможет понять, что она означает. Разумеется, все шифрование Flask выполняет автоматически. От нас требуется только прописать этот ключ с как можно более непонятным набором символов.

Далее создаем само приложение и загружаем конфигурацию из текущего модуля:

app = Flask(__name__)
app.config.from_object(__name__)

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

Далее, мы переопределим в конфигурации значение DATABASE, расположив БД в текущем каталоге приложения:

app.config.update(dict(DATABASE=os.path.join(app.root_path,'flsite.db')))

Возможно, у вас возникнет вопрос: почему нельзя было сразу вначале определить DATABASE как нужно и не выполнять эту последнюю команду? Дело в том, что изначально у нас не было созданного приложения app и, соответственно, не могли обратиться к его свойству root_path, для определения корневого каталога. Конечно, можно было бы воспользоваться вот такой конструкцией для определения рабочего каталога:

os.path.dirname(os.path.abspath(__file__))

Однако, в случае использования нескольких WSGI-приложений рабочий каталог и каталог текущего приложения могут различаться. По этой причине в программе используется свойство root_path.

Создание БД

Итак, конфигурацию нашего приложения определили. Теперь пришло время создать БД. Общая концепция такая. Сначала отдельно создается БД с набором таблиц (без запуска веб-сервера). А уже потом, обработчики запросов обращаются к созданным таблицам БД, записывают и считывают из них информацию.

Как это сделать? В самом простом случае мы можем поступить так. Создадим общую функцию для установления соединения с БД:

def connect_db():
    conn = sqlite3.connect(app.config['DATABASE'])
    conn.row_factory = sqlite3.Row
    return conn

И, затем, объявим вспомогательную функцию, которая и будет создавать начальную БД с набором необходимых таблиц:

def create_db():
    """Вспомогательная функция для создания таблиц БД"""
    db = connect_db()
    with app.open_resource('sq_db.sql', mode='r') as f:
        db.cursor().executescript(f.read())
    db.commit()
    db.close()

Здесь мы используем метод open_resource, который открывает файл 'sq_db.sql' на чтение, расположенный в рабочем каталоге нашего приложения. Затем, для открытой БД выполняется скрипт, записанный в файле 'sq_db.sql'. В конце вызывается метод commit, чтобы изменения применились к текущей БД, и метод close закрывает установленное соединение.

Теперь, давайте посмотрим на содержимое файла 'sq_db.sql':

create table if not exists mainmenu (
id integer primary key autoincrement,
title text not null,
url text not null
);

Здесь записан SQL-запрос для формирования таблицы mainmenu с тремя полями: id, title и url. Их назначение вполне очевидно.

Как теперь воспользоваться этой функцией для создания начальной БД? Можно поступить так. Перейти в консоль Python и выполнить импорт функции create_db из файла flsite.py:

from flsite import create_db

И, затем, просто вызвать эту функцию:

create_db()

Все, в результате, в рабочем каталоге нашего приложения появится файл flsite.db с таблицей mainmenu. Мы в этом можем легко убедиться, если откроем эту БД с помощью специальной программы DB Browser SQLite.

Подключение к БД в запросах

После создания БД мы можем ее использовать при обработке запросов. И для оптимизации работы делают так: в момент прихода запроса, устанавливается соединение с БД, а в момент завершения обработки – разрывается соединение с БД.

Момент поступления запроса мы можем «поймать» непосредственно в обработчике. Добавим обработчик главной страницы:

@app.route("/")
def index():
    db = get_db()
    return render_template('index.html', menu = [])

Здесь вначале вызывается вспомогательная функция get_db, которая возвращает активное соединение с БД. Ее реализация, следующая:

def get_db():
    '''Соединение с БД, если оно еще не установлено'''
    if not hasattr(g, 'link_db'):
        g.link_db = connect_db()
    return g.link_db

Смотрите, мы здесь используем объект g контекста приложения, которое создается в момент поступления запроса. В этом объекте можно сохранять любую пользовательскую информацию, которая будет доступна в любой функции и шаблонах, в пределах этого запроса. Причем, для разных запросов, объект g будет разным, то есть, он уникален в пределах текущего запроса.

Далее, мы проверяем: было ли соединение уже установлено (существует ли атрибут link_db, который мы создаем в момент соединения с БД. Если соединения еще нет, то устанавливаем его и, затем, возвращаем. Как вы уже догадались, если где-либо в функциях (или шаблонах) будет повторное обращение к этой функции, то она просто возвратит ранее установленное соединение, что очень удобно.

Отлично, с БД связь установлена. Но как нам ее теперь разорвать в момент завершения запроса? Для в Flask есть специальный декоратор teardown_appcontext, который позволяет определять функцию, вызываемую в момент уничтожения контекста приложения. А это, обычно, происходит в момент завершения обработки запроса. В итоге получаем следующий обработчик для завершения соединения с БД:

@app.teardown_appcontext
def close_db(error):
    '''Закрываем соединение с БД, если оно было установлено'''
    if hasattr(g, 'link_db'):
        g.link_db.close()

Все, теперь в нашей программе существует БД, к которой можно получить доступ в момент возникновения запроса и автоматическое завершение соединения при окончании работы с запросом.

На следующем занятии мы продолжим работу с БД и посмотрим как можно добавлять и брать данные из таблицы для их отображения на страницах сайта.

Видео по теме