Skip to content

3. Работа с базами данных в Flask


Цель лекции: Изучить основы работы фремворка Flask с распространными базами данных

Вопросы

  1. Введение в Flask и базы данных

Краткий обзор Flask как веб-фреймворка

Необходимость интеграции баз данных в веб-приложения

Обзор популярных баз данных для использования с Flask

  1. Работа с реляционными базами данных

SQLite для разработки и тестирования

PostgreSQL для промышленной эксплуатации

MySQL/MariaDB и их особенности Примеры подключения и конфигурации

  1. SQLAlchemy и Flask-SQLAlchemy

Основы ORM (Object-Relational Mapping)

Установка и настройка Flask-SQLAlchemy Определение моделей данных

CRUD-операции (Create, Read, Update, Delete)

Миграции базы данных с Alembic и Flask-Migrate

  1. NoSQL базы данных и Flask

MongoDB и PyMongo

Flask-PyMongo для упрощения интеграции

Работа с документоориентированной моделью данных

Примеры CRUD-операций в MongoDB

  1. Продвинутые техники работы с базами данных

Транзакции и обработка ошибок

Оптимизация запросов и индексирование

Пагинация и сортировка результатов

Полнотекстовый поиск

  1. Безопасность при работе с базами данных

SQL-инъекции и их предотвращение

Управление соединениями и пулами соединений

Шифрование конфиденциальных данных

Аудит и логирование операций с базой данных

  1. Redis и кэширование в Flask

Базовые концепции Redis

Flask-Caching для интеграции с Redis

Кэширование запросов и результатов

Очереди и фоновые задачи с Celery и Redis


1 Введение в Flask и базы данных

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

+---------------------+
|       Flask         |
+---------------------+
|  - Маршрутизация    |
|  - Шаблонизатор     |
|  - Отладчик         |
|  - Сервер разработки|
+---------------------+
        ^
        |
+---------------------+
|     Расширения      |
+---------------------+
|  - Flask-SQLAlchemy |
|  - Flask-Login      |
|  - Flask-WTF        |
|  - Flask-RESTful    |
|  - И другие...      |
+---------------------+

Необходимость интеграции баз данных в веб-приложения

Современные веб-приложения редко обходятся без постоянного хранилища данных. Базы данных необходимы для:

    Пользователь
        ^
        |
+----------------+     +-----------------+
|  Веб-приложение| <--> |  База данных   |
|    (Flask)     |     |                 |
+----------------+     +-----------------+
    ^
    |
  Запрос/Ответ
  • Хранения пользовательских данных: профили, предпочтения, настройки
  • Сохранения состояния приложения: корзины покупок, сессии, временные данные
  • Управления контентом: статьи, товары, комментарии
  • Аналитики и отчетности: логи, статистика использования
  • Обеспечения целостности данных: проверки, ограничения, связи между сущностями

Обзор популярных баз данных для использования с Flask

Реляционные базы данных

+-------------------+     +-------------------+
|     SQLite        |     |    PostgreSQL     |
+-------------------+     +-------------------+
| - Файловая БД     |     | - Производитель-  |
| - Встроенная      |     |   ность           |
| - Без сервера     |     | - Расширяемость   |
| - Для разработки  |     | - Для production  |
+-------------------+     +-------------------+

+-------------------+     +-------------------+
|   MySQL/MariaDB   |     |      Oracle       |
+-------------------+     +-------------------+
| - Популярность    |     | - Корпоративный   |
| - Простота        |     |   стандарт        |
| - Для web-apps    |     | - Высокая         |
| - Оптимизирован   |     |   надежность      |
+-------------------+     +-------------------+

NoSQL базы данных

+-------------------+     +-------------------+
|     MongoDB       |     |       Redis       |
+-------------------+     +-------------------+
| - Документо-      |     | - Хранилище       |
|   ориентированная |     |   ключ-значение   |
| - JSON-подобные   |     | - Кэширование     |
|   документы       |     | - Быстродействие  |
| - Гибкие схемы    |     | - In-memory       |
+-------------------+     +-------------------+

+-------------------+     +-------------------+
|    Cassandra      |     |     Firebase      |
+-------------------+     +-------------------+
| - Распределенная  |     | - BaaS решение    |
| - Высокая         |     | - Realtime DB     |
|   доступность     |     | - Облачное        |
| - Масштабируемость|     |   хранилище       |
+-------------------+     +-------------------+

Сравнение подходов при интеграции с Flask

+---------------------------------------------+
|             Интеграция с Flask              |
+---------------------------------------------+
|                                             |
|  +----------------+    +----------------+   |
|  | Нативные драйверы|    |     ORM        |   |
|  +----------------+    +----------------+   |
|  | - psycopg2     |    | - SQLAlchemy   |   |
|  | - pymysql      |    | - Peewee       |   |
|  | - pymongo      |    | - Django ORM   |   |
|  +----------------+    +----------------+   |
|                                             |
|  +----------------+    +----------------+   |
|  | Flask-расширения|    |  API-клиенты   |   |
|  +----------------+    +----------------+   |
|  | - Flask-SQLAlchemy| | - Redis-py     |   |
|  | - Flask-PyMongo  | | - Elasticsearch |   |
|  | - Flask-Redis    | | - Neo4j-python  |   |
|  +----------------+    +----------------+   |
|                                             |
+---------------------------------------------+

Выбор базы данных зависит от: - Требований к производительности и масштабируемости - Структуры хранимых данных - Особенностей предметной области - Опыта разработчиков - Требований к целостности и согласованности данных


2. Работа с реляционными базами данных

SQLite для разработки и тестирования

SQLite — это легковесная встраиваемая реляционная база данных, которая хранит все данные в одном файле на диске.

+----------------------------+
|          SQLite            |
+----------------------------+
|                            |
|     Файл базы данных       |
|   +------------------+     |
|   |   Таблицы        |     |
|   |   Индексы        |     |
|   |   Триггеры       |     |
|   |   Представления  |     |
|   +------------------+     |
|                            |
+----------------------------+

Почему SQLite удобна для разработки: - Не требует установки отдельного сервера - Вся база данных — это один файл - Встроена в Python (не нужны дополнительные библиотеки) - Поддерживает большинство стандартных SQL-запросов - Быстрая для небольших приложений

Пример работы с SQLite в Flask:

import sqlite3
from flask import Flask, g

app = Flask(__name__)

DATABASE = 'database.db'  # Путь к файлу базы данных

def get_db():
    # Проверяем, есть ли уже соединение с БД в контексте запроса
    db = getattr(g, '_database', None)
    if db is None:
        # Если соединения нет, создаем новое
        db = g._database = sqlite3.connect(DATABASE)
        db.row_factory = sqlite3.Row  # Результаты в виде словарей, а не кортежей
    return db

@app.teardown_appcontext
def close_connection(exception):
    # Эта функция автоматически вызывается в конце запроса
    # и закрывает соединение с БД
    db = getattr(g, '_database', None)
    if db is not None:
        db.close()

@app.route('/users')
def get_users():
    db = get_db()  # Получаем соединение с БД
    cursor = db.cursor()  # Создаем курсор для выполнения SQL-запросов
    cursor.execute('SELECT id, name, email FROM users')  # Выполняем запрос
    users = cursor.fetchall()  # Получаем все результаты
    return {'users': [dict(user) for user in users]}  # Преобразуем в словари и возвращаем

В ручке /users о нет явного вызова функции close_connection, и это ключевой момент для понимания работы декоратора @app.teardown_appcontext.

Когда вы используете декоратор @app.teardown_appcontext, Flask автоматически вызывает декорированную функцию в конце жизненного цикла контекста приложения, который создается для каждого HTTP-запроса. Это происходит следующим образом:

  1. Когда поступает HTTP-запрос к эндпоинту /users, Flask создает контекст приложения.

  2. Функция get_users() выполняется и вызывает get_db() для получения соединения с базой данных, которое сохраняется в объекте g.

  3. После того как функция get_users() завершила работу и сформировала ответ, Flask начинает процесс очистки контекста приложения.

  4. В этот момент Flask автоматически вызывает все функции, зарегистрированные через декоратор @app.teardown_appcontext.

  5. Наша функция close_connection() выполняется, проверяет наличие соединения с БД и закрывает его, если оно существует.

Важно понимать, что этот механизм работает независимо от того, завершился ли запрос успешно или с ошибкой. Даже если в get_users() произойдет исключение, Flask все равно вызовет close_connection(), что гарантирует закрытие соединения с базой данных.

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

Курсор базы данных — это объект уровня базы данных, который позволяет запрашивать базу данных несколько раз.

В контексте Flask объект g - это специальный объект глобального контекста запроса. "g" означает "global", но является глобальным только в рамках одного запроса. Это временное хранилище, доступное во время обработки одного конкретного HTTP-запроса.

Ключевые особенности объекта g:

  1. Область видимости запроса: Данные, сохраненные в g, доступны только во время обработки текущего запроса и автоматически очищаются после его завершения.

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

  3. Безопасность в многопоточной среде: Каждый запрос имеет свой собственный объект g, что делает его безопасным для использования в многопоточных приложениях.

  4. Доступность во всех функциях представления: Объект g доступен в любой функции, обрабатывающей запрос, без необходимости передавать его как параметр.

PostgreSQL для промышленной эксплуатации

PostgreSQL — это объектно-реляционная система управления базами данных с открытым исходным кодом.

+--------------------------------------+
|             PostgreSQL               |
+--------------------------------------+
|                                      |
|  +------------------------------+    |
|  |        PostgreSQL сервер     |    |
|  |  +------------------------+  |    |
|  |  |      База данных       |  |    |
|  |  |  +----------------+    |  |    |
|  |  |  |    Схемы       |    |  |    |
|  |  |  |  +----------+  |    |  |    |
|  |  |  |  | Таблицы  |  |    |  |    |
|  |  |  |  +----------+  |    |  |    |
|  |  |  +----------------+    |  |    |
|  |  +------------------------+  |    |
|  +------------------------------+    |
|                                      |
+--------------------------------------+

Преимущества PostgreSQL: - Высокая производительность - Надежность и стабильность - Отличная поддержка транзакций - Расширяемость (функции, типы данных) - Продвинутые индексы и оптимизация запросов - Работа с JSON, геоданными и другими сложными типами

Пример работы с PostgreSQL в Flask:

import psycopg2
import psycopg2.extras
from flask import Flask, g

app = Flask(__name__)

DATABASE_URI = "postgresql://username:password@localhost/dbname"  # Строка подключения

def get_db():
    # Проверяем, есть ли уже соединение с БД в контексте запроса
    db = getattr(g, '_database', None)
    if db is None:
        # Если соединения нет, создаем новое
        db = g._database = psycopg2.connect(DATABASE_URI)
    return db

@app.teardown_appcontext
def close_connection(exception):
    # Автоматически закрываем соединение в конце запроса
    db = getattr(g, '_database', None)
    if db is not None:
        db.close()

@app.route('/products')
def get_products():
    db = get_db()  # Получаем соединение
    # Создаем курсор, который будет возвращать результаты в виде словарей
    cursor = db.cursor(cursor_factory=psycopg2.extras.DictCursor)
    cursor.execute('SELECT id, name, price FROM products')  # Выполняем запрос
    products = cursor.fetchall()  # Получаем все результаты
    return {'products': [dict(product) for product in products]}  # Возвращаем как JSON

MySQL/MariaDB и их особенности

MySQL — популярная система управления базами данных с открытым исходным кодом. MariaDB — это ответвление (форк) MySQL, созданное сообществом.

+--------------------------------------+
|           MySQL/MariaDB              |
+--------------------------------------+
|                                      |
|  +------------------------------+    |
|  |        MySQL сервер          |    |
|  |  +------------------------+  |    |
|  |  |      База данных       |  |    |
|  |  |  +----------------+    |  |    |
|  |  |  |    Таблицы     |    |  |    |
|  |  |  |  (InnoDB,      |    |  |    |
|  |  |  |   MyISAM и др.)|    |  |    |
|  |  |  +----------------+    |  |    |
|  |  +------------------------+  |    |
|  +------------------------------+    |
|                                      |
+--------------------------------------+

Особенности MySQL/MariaDB: - Высокая скорость для операций чтения - Легкая настройка и администрирование - Различные движки хранения данных (InnoDB, MyISAM) - Отличная документация и большое сообщество - Оптимизирована для веб-приложений

Пример работы с MySQL в Flask:

import pymysql
from flask import Flask, g

app = Flask(__name__)

DATABASE_CONFIG = {
    'host': 'localhost',  # Хост базы данных
    'user': 'username',   # Имя пользователя
    'password': 'password',  # Пароль
    'db': 'dbname',       # Имя базы данных
    'charset': 'utf8mb4',  # Кодировка (поддержка эмодзи и других Unicode символов)
    'cursorclass': pymysql.cursors.DictCursor  # Тип курсора (результаты в виде словарей)
}

def get_db():
    # Получаем существующее соединение или создаем новое
    db = getattr(g, '_database', None)
    if db is None:
        db = g._database = pymysql.connect(**DATABASE_CONFIG)
    return db

@app.teardown_appcontext
def close_connection(exception):
    # Закрываем соединение после завершения запроса
    db = getattr(g, '_database', None)
    if db is not None:
        db.close()

@app.route('/orders')
def get_orders():
    db = get_db()
    with db.cursor() as cursor:  # Используем менеджер контекста для автоматического закрытия курсора
        cursor.execute('SELECT id, customer_id, total_price, status FROM orders')
        orders = cursor.fetchall()  # Получаем все заказы
    return {'orders': orders}  # Возвращаем как JSON

Примеры

SQLite:

def init_sqlite_db():
    # Подключаемся к файлу базы данных
    conn = sqlite3.connect('database.db')
    c = conn.cursor()

    # Создаем таблицу users, если она не существует
    c.execute('''
    CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,  # Автоинкрементный первичный ключ
        username TEXT NOT NULL,                # Имя пользователя (не может быть NULL)
        email TEXT UNIQUE NOT NULL,            # Уникальный email
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP  # Время создания
    )
    ''')
    conn.commit()  # Сохраняем изменения
    conn.close()   # Закрываем соединение

PostgreSQL:

def init_postgres_db():
    # Подключаемся к серверу PostgreSQL
    conn = psycopg2.connect("postgresql://username:password@localhost/dbname")
    conn.autocommit = True  # Автоматический коммит изменений
    cursor = conn.cursor()

    # Создаем таблицу users, если она не существует
    cursor.execute('''
    CREATE TABLE IF NOT EXISTS users (
        id SERIAL PRIMARY KEY,                 # Автоинкрементный первичный ключ
        username VARCHAR(100) NOT NULL,        # Имя пользователя (строка фикс. длины)
        email VARCHAR(100) UNIQUE NOT NULL,    # Уникальный email
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP  # Время создания
    )
    ''')
    cursor.close()  # Закрываем курсор
    conn.close()    # Закрываем соединение

MySQL:

def init_mysql_db():
    # Подключаемся к серверу MySQL
    conn = pymysql.connect(
        host='localhost',
        user='username',
        password='password',
        db='dbname'
    )
    cursor = conn.cursor()

    # Создаем таблицу users, если она не существует
    cursor.execute('''
    CREATE TABLE IF NOT EXISTS users (
        id INT AUTO_INCREMENT PRIMARY KEY,     # Автоинкрементный первичный ключ
        username VARCHAR(100) NOT NULL,        # Имя пользователя
        email VARCHAR(100) UNIQUE NOT NULL,    # Уникальный email
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP  # Время создания
    )
    ''')
    conn.commit()   # Сохраняем изменения
    cursor.close()  # Закрываем курсор
    conn.close()    # Закрываем соединение

Сравнение баз данных в виде таблицы

+------------------+----------------+------------------+------------------+
|     Функция      |     SQLite     |    PostgreSQL    |   MySQL/MariaDB  |
+------------------+----------------+------------------+------------------+
| Тип базы данных  | Файловая       | Клиент-серверная | Клиент-серверная |
+------------------+----------------+------------------+------------------+
| Установка        | Не требуется   | Требуется        | Требуется        |
+------------------+----------------+------------------+------------------+
| Конфигурация     | Минимальная    | Сложная          | Средняя          |
+------------------+----------------+------------------+------------------+
| Производитель-   | Низкая для     | Высокая          | Средняя/Высокая  |
| ность            | больших данных |                  |                  |
+------------------+----------------+------------------+------------------+
| Параллельное     | Ограниченное   | Отличное         | Хорошее          |
| выполнение       |                |                  |                  |
+------------------+----------------+------------------+------------------+
| Сложные запросы  | Ограниченно    | Полная поддержка | Хорошая поддержка|
+------------------+----------------+------------------+------------------+
| Типичное         | Разработка,    | Промышленная     | Веб-приложения,  |
| применение       | тестирование,  | эксплуатация,    | CMS, блоги       |
|                  | мобильные      | корпоративные    |                  |
|                  | приложения     | приложения       |                  |
+------------------+----------------+------------------+------------------+

Выбор базы данных для приложений Flask зависит от конкретных требований и условий:

  • SQLite идеально подходит для:
  • Разработки и тестирования
  • Небольших приложений
  • Встраиваемых решений
  • Ситуаций, когда простота важнее масштабируемости

  • PostgreSQL рекомендуется для:

  • Корпоративных приложений
  • Систем с высокими требованиями к надежности
  • Приложений со сложной бизнес-логикой
  • Использования расширенных типов данных (JSON, геоданные)

  • MySQL/MariaDB хорошо подходит для:

  • Веб-сайтов с высокой посещаемостью
  • Приложений с преобладанием операций чтения
  • Систем управления контентом
  • Если у вашей команды есть опыт работы с MySQL

3. SQLAlchemy и Flask-SQLAlchemy

SQLAlchemy — это ORM (Object-Relational Mapping, объектно-реляционное отображение). Это библиотека для работы с базами данных в Python, которая позволяет взаимодействовать с реляционными базами данных через объекты Python, а не напрямую через SQL-запросы.

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

Пример:

  • Таблица в базе данных становится классом Python.
  • Строка в таблице становится экземпляром этого класса.
  • Столбцы таблицы становятся атрибутами класса.

SQLAlchemy состоит из двух основных компонентов:

  • Core : Низкоуровневый API, который предоставляет инструменты для работы с базами данных через SQL-выражения. Позволяет выполнять "сырые" SQL-запросы или строить запросы с помощью абстрактного синтаксиса Python. Подходит для разработчиков, которым нужен контроль над SQL.

  • ORM : Высокоуровневый API, который предоставляет объектно-ориентированный интерфейс для работы с базой данных. Позволяет работать с таблицами как с классами и строками как с объектами. Упрощает работу с базой данных за счет автоматического преобразования операций с объектами в SQL-запросы.

+----------------------------------+
|         Ваше приложение          |
|                                  |
|  +----------------------------+  |
|  |      Python-код           |  |
|  +----------------------------+  |
|              ↓                   |
|  +----------------------------+  |
|  |      SQLAlchemy           |  |
|  +----------------------------+  |
|              ↓                   |
+----------------------------------+
               ↓
+----------------------------------+
|         База данных              |
+----------------------------------+

Представьте, что SQLAlchemy — это переводчик: - Вы говорите на Python - База данных говорит на SQL - SQLAlchemy переводит ваши Python-команды в SQL-запросы

Зачем нужен Flask-SQLAlchemy?

Flask-SQLAlchemy — это расширение для Flask, которое упрощает использование SQLAlchemy в проектах на Flask.

+----------------------------------+
|    Flask-приложение              |
|  +----------------------------+  |
|  |      Веб-страницы         |  |
|  +----------------------------+  |
|              ↓                   |
|  +----------------------------+  |
|  |    Flask-SQLAlchemy       |  |
|  +----------------------------+  |
|              ↓                   |
|  +----------------------------+  |
|  |      SQLAlchemy           |  |
|  +----------------------------+  |
|              ↓                   |
+----------------------------------+
               ↓
+----------------------------------+
|         База данных              |
+----------------------------------+

Что Flask-SQLAlchemy делает проще: - Автоматически настраивает соединение с базой данных - Управляет сессиями - Интегрируется с системой запросов Flask - Предоставляет полезные вспомогательные функции

Установка и настройка

Установить Flask-SQLAlchemy очень просто:

pip install Flask-SQLAlchemy

Настройка в вашем приложении:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

# Создаем объект приложения
app = Flask(__name__)

# Говорим приложению, где находится база данных
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///students.db'

# Отключаем отслеживание изменений (для производительности)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

# Создаем объект для работы с базой данных
db = SQLAlchemy(app)

Модели данных — это описание таблиц вашей базы данных на языке Python. Каждая модель — это класс Python, который соответствует таблице в базе данных.

+----------------------------------+
|                                  |
|  Python-класс Student            |
|  +----------------------------+  |
|  | id                         |  |
|  | name                       |  |
|  | age                        |  |
|  | email                      |  |
|  +----------------------------+  |
|                ↓                 |
+----------------------------------+
                ↓
+----------------------------------+
|                                  |
|  Таблица students в базе данных  |
|  +----------------------------+  |
|  | id | name | age | email    |  |
|  |----+------+-----+----------|  |
|  | 1  | Анна | 20  | a@mail.ru|  |
|  | 2  | Иван | 21  | i@mail.ru|  |
|  +----------------------------+  |
|                                  |
+----------------------------------+

Пример модели студента:

class Student(db.Model):
    # Название таблицы (необязательно, по умолчанию - имя класса в нижнем регистре)
    __tablename__ = 'students'

    # Колонки таблицы
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), nullable=False)
    age = db.Column(db.Integer)
    email = db.Column(db.String(120), unique=True)

    # Метод для отображения объекта в виде строки
    def __repr__(self):
        return f'<Student {self.name}>'

Основные типы данных в SQLAlchemy:

+-------------------+----------------------------+
| Тип в SQLAlchemy  | Для чего используется      |
+-------------------+----------------------------+
| Integer           | Целые числа                |
| String(длина)     | Текст ограниченной длины   |
| Text              | Текст без ограничений      |
| Float             | Числа с плавающей точкой   |
| Boolean           | Логические значения        |
| DateTime          | Дата и время               |
+-------------------+----------------------------+

Связи между таблицами

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

+----------------------------------+      +----------------------------------+
|                                  |      |                                  |
|  Модель Student                  |      |  Модель Course                   |
|  +----------------------------+  |      |  +----------------------------+  |
|  | id                         |  |      |  | id                         |  |
|  | name                       |  |      |  | title                      |  |
|  | courses (связь)            |◆-------→|  | students (связь)           |  |
|  +----------------------------+  |      |  +----------------------------+  |
|                                  |      |                                  |
+----------------------------------+      +----------------------------------+

Пример связи "многие-ко-многим":

# Таблица для связи студентов и курсов
student_courses = db.Table('student_courses',
    db.Column('student_id', db.Integer, db.ForeignKey('students.id')),
    db.Column('course_id', db.Integer, db.ForeignKey('courses.id'))
)

class Student(db.Model):
    __tablename__ = 'students'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), nullable=False)

    # Связь с курсами
    courses = db.relationship('Course', secondary=student_courses, 
                             backref=db.backref('students', lazy='dynamic'))

class Course(db.Model):
    __tablename__ = 'courses'
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)

Основные операции с данными (CRUD)

CRUD — это аббревиатура от Create (создание), Read (чтение), Update (обновление) и Delete (удаление).

+----------------------------------+
|         CRUD-операции            |
+----------------------------------+
|                                  |
|   Create   →   Новая запись      |
|                                  |
|   Read     →   Получение данных  |
|                                  |
|   Update   →   Изменение данных  |
|                                  |
|   Delete   →   Удаление данных   |
|                                  |
+----------------------------------+

1. Создание записей (Create)

# Создаем нового студента
new_student = Student(name='Мария', age=19, email='maria@example.com')

# Добавляем его в сессию базы данных
db.session.add(new_student)

# Сохраняем изменения в базе данных
db.session.commit()

2. Чтение данных (Read)

# Получить всех студентов
all_students = Student.query.all()

# Найти студента по ID
student = Student.query.get(1)

# Найти студента по имени
maria = Student.query.filter_by(name='Мария').first()

# Сложный запрос: найти всех студентов старше 18 лет, отсортированных по имени
adult_students = Student.query.filter(Student.age > 18).order_by(Student.name).all()

3. Обновление данных (Update)

# Найти студента, которого хотим обновить
student = Student.query.get(1)

# Изменить его данные
student.age = 21
student.email = 'new_email@example.com'

# Сохранить изменения
db.session.commit()

4. Удаление данных (Delete)

# Найти студента, которого хотим удалить
student_to_delete = Student.query.get(2)

# Удалить его
db.session.delete(student_to_delete)

# Сохранить изменения
db.session.commit()

Миграции базы данных

Со временем структура вашей базы данных будет меняться: добавляются новые таблицы, изменяются существующие. Flask-Migrate помогает управлять этими изменениями.

+----------------------------------+
|   Процесс миграции базы данных   |
|                                  |
|  1. Изменение моделей Python    →|
|                ↓                 |
|  2. Создание файла миграции     →|
|                ↓                 |
|  3. Применение миграции к БД    →|
|                ↓                 |
|  4. Обновленная база данных      |
|                                  |
+----------------------------------+

Установка Flask-Migrate:

pip install Flask-Migrate

Настройка:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
db = SQLAlchemy(app)
migrate = Migrate(app, db)

# Ваши модели...

Основные команды для работы с миграциями:

# Инициализация системы миграций
flask db init

# Создание миграции после изменения моделей
flask db migrate -m "Добавлена таблица студентов"

# Применение миграции к базе данных
flask db upgrade

# Откат последней миграции (если что-то пошло не так)
flask db downgrade

Пример использования в Flask-приложении

Вот как все это выглядит в полноценном Flask-приложении:

from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///students.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

# Определение модели
class Student(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), nullable=False)
    age = db.Column(db.Integer)

    def __repr__(self):
        return f'<Student {self.name}>'

# Создание таблиц в базе данных
with app.app_context():
    db.create_all()

# Маршруты приложения
@app.route('/')
def index():
    students = Student.query.all()
    return render_template('index.html', students=students)

@app.route('/add', methods=['GET', 'POST'])
def add_student():
    if request.method == 'POST':
        name = request.form['name']
        age = request.form['age']

        new_student = Student(name=name, age=age)
        db.session.add(new_student)
        db.session.commit()

        return redirect(url_for('index'))

    return render_template('add.html')

if __name__ == '__main__':
    app.run(debug=True)

4. NoSQL базы данных и Flask

MongoDB — это популярная NoSQL база данных, которая хранит данные в формате JSON-подобных документов. В отличие от реляционных баз данных, MongoDB не требует строгой схемы для структуры данных.

+--------------------------------------+
|             MongoDB                  |
+--------------------------------------+
|                                      |
|  +------------------------------+    |
|  |        База данных           |    |
|  |  +------------------------+  |    |
|  |  |      Коллекции         |  |    |
|  |  |  +------------------+  |  |    |
|  |  |  |    Документы     |  |  |    |
|  |  |  |  (JSON-подобные) |  |  |    |
|  |  |  +------------------+  |  |    |
|  |  +------------------------+  |    |
|  +------------------------------+    |
|                                      |
+--------------------------------------+

Отличия MongoDB от реляционных баз данных: - Хранит документы вместо строк таблиц - Не требует фиксированной схемы - Естественная поддержка вложенных данных - Работает с BSON (бинарный JSON) - Горизонтальное масштабирование

Установка PyMongo:

# Установка библиотеки для работы с MongoDB
pip install pymongo

Простой пример подключения к MongoDB:

from pymongo import MongoClient
from flask import Flask, jsonify

app = Flask(__name__)

# Создаем соединение с MongoDB
client = MongoClient('mongodb://localhost:27017/')  # Стандартный адрес для локального сервера MongoDB

# Выбираем базу данных
db = client['flask_app']  # Имя базы данных

# Получаем коллекцию (аналог таблицы в реляционных БД)
users_collection = db['users']  # Коллекция пользователей

# Пример маршрута для получения всех пользователей
@app.route('/users')
def get_users():
    # Получаем всех пользователей из коллекции
    users = list(users_collection.find({}, {'_id': 0}))  # Исключаем поле _id из результатов
    return jsonify(users)

Flask-PyMongo для упрощения интеграции

Flask-PyMongo — это расширение Flask, которое упрощает работу с MongoDB.

+--------------------------------------+
|           Flask-приложение           |
+--------------------------------------+
|                                      |
|  +------------------------------+    |
|  |       Flask-PyMongo          |    |
|  +------------------------------+    |
|                ↓                     |
|  +------------------------------+    |
|  |         PyMongo              |    |
|  +------------------------------+    |
|                ↓                     |
+--------------------------------------+
                ↓
+--------------------------------------+
|           MongoDB сервер             |
+--------------------------------------+

Установка Flask-PyMongo:

pip install Flask-PyMongo

Настройка Flask-PyMongo:

from flask import Flask, jsonify, request
from flask_pymongo import PyMongo

app = Flask(__name__)

# Настройка подключения к MongoDB
app.config['MONGO_URI'] = 'mongodb://localhost:27017/flask_app'
mongo = PyMongo(app)  # Создаем экземпляр PyMongo

@app.route('/users')
def get_users():
    # Получаем всех пользователей (mongo.db автоматически указывает на нашу базу данных)
    users = list(mongo.db.users.find({}, {'_id': 0}))
    return jsonify(users)

Работа с документоориентированной моделью данных

В MongoDB данные хранятся в виде документов — гибких структур, похожих на JSON-объекты. Это позволяет хранить разнородные данные в одной коллекции.

+----------------------------------------+
|  Документ пользователя в MongoDB        |
+----------------------------------------+
|                                        |
|  {                                     |
|    "_id": ObjectId("5f8a53d3e8d1d"),   |
|    "username": "john_doe",             |
|    "email": "john@example.com",        |
|    "profile": {                        |
|      "first_name": "John",             |
|      "last_name": "Doe",               |
|      "age": 30                         |
|    },                                  |
|    "interests": ["coding", "music"],   |
|    "joined_at": ISODate("2023-05-15")  |
|  }                                     |
|                                        |
+----------------------------------------+

Преимущества документной модели: - Естественное представление объектов - Гибкость схемы (разные документы могут иметь разную структуру) - Возможность хранить вложенные структуры - Хорошая производительность для операций с целыми документами

Примеры CRUD-операций в MongoDB

1. Создание (Create)

@app.route('/users', methods=['POST'])
def create_user():
    # Получаем данные из запроса
    user_data = request.json

    # Проверяем, что пользователь с таким email не существует
    if mongo.db.users.find_one({'email': user_data['email']}):
        return jsonify({'error': 'Пользователь с таким email уже существует'}), 400

    # Добавляем пользователя в коллекцию
    result = mongo.db.users.insert_one(user_data)

    # Возвращаем ID созданного пользователя
    return jsonify({'message': 'Пользователь создан', 'id': str(result.inserted_id)}), 201

2. Чтение (Read)

# Получение всех пользователей
@app.route('/users', methods=['GET'])
def get_all_users():
    # Получаем параметры запроса (например, для фильтрации)
    age = request.args.get('age', type=int)

    # Формируем фильтр
    query = {}
    if age:
        query['profile.age'] = age  # Обращение к вложенному полю с помощью точки

    # Выполняем запрос с фильтром
    users = list(mongo.db.users.find(query, {'_id': 0}))
    return jsonify(users)

# Получение пользователя по имени пользователя
@app.route('/users/<username>', methods=['GET'])
def get_user(username):
    # Ищем пользователя по имени пользователя
    user = mongo.db.users.find_one({'username': username}, {'_id': 0})

    if user:
        return jsonify(user)
    else:
        return jsonify({'error': 'Пользователь не найден'}), 404

3. Обновление (Update)

@app.route('/users/<username>', methods=['PUT'])
def update_user(username):
    # Получаем данные для обновления
    update_data = request.json

    # Обновляем пользователя
    result = mongo.db.users.update_one(
        {'username': username},  # Фильтр - какой документ обновлять
        {'$set': update_data}    # Оператор $set устанавливает новые значения полей
    )

    if result.matched_count > 0:
        return jsonify({'message': 'Пользователь обновлен'})
    else:
        return jsonify({'error': 'Пользователь не найден'}), 404

4. Удаление (Delete)

@app.route('/users/<username>', methods=['DELETE'])
def delete_user(username):
    # Удаляем пользователя
    result = mongo.db.users.delete_one({'username': username})

    if result.deleted_count > 0:
        return jsonify({'message': 'Пользователь удален'})
    else:
        return jsonify({'error': 'Пользователь не найден'}), 404

Специальные операторы MongoDB

MongoDB предоставляет множество операторов для выполнения сложных запросов:

# Поиск пользователей старше 25 лет
users = mongo.db.users.find({'profile.age': {'$gt': 25}})

# Поиск пользователей с определенными интересами
users = mongo.db.users.find({'interests': {'$in': ['coding', 'music']}})

# Обновление с увеличением значения
result = mongo.db.users.update_one(
    {'username': 'john_doe'},
    {'$inc': {'visits': 1}}  # Увеличиваем счетчик посещений на 1
)

# Добавление элемента в массив
result = mongo.db.users.update_one(
    {'username': 'john_doe'},
    {'$push': {'interests': 'photography'}}  # Добавляем новый интерес
)

Полный пример Flask-приложения с MongoDB

from flask import Flask, jsonify, request
from flask_pymongo import PyMongo
from bson.objectid import ObjectId
import datetime

app = Flask(__name__)
app.config['MONGO_URI'] = 'mongodb://localhost:27017/task_manager'
mongo = PyMongo(app)

# Маршрут для создания новой задачи
@app.route('/tasks', methods=['POST'])
def create_task():
    # Получаем данные из запроса
    title = request.json.get('title')
    description = request.json.get('description', '')
    due_date = request.json.get('due_date')

    # Проверяем обязательные поля
    if not title:
        return jsonify({'error': 'Название задачи обязательно'}), 400

    # Создаем новый документ задачи
    new_task = {
        'title': title,
        'description': description,
        'completed': False,
        'created_at': datetime.datetime.now(),
        'due_date': datetime.datetime.fromisoformat(due_date) if due_date else None
    }

    # Вставляем задачу в коллекцию tasks
    result = mongo.db.tasks.insert_one(new_task)

    # Возвращаем ID созданной задачи
    return jsonify({
        'message': 'Задача создана',
        'id': str(result.inserted_id)
    }), 201

# Маршрут для получения всех задач
@app.route('/tasks', methods=['GET'])
def get_tasks():
    # Получаем параметры фильтрации
    completed = request.args.get('completed')

    # Строим запрос
    query = {}
    if completed is not None:
        query['completed'] = completed.lower() == 'true'

    # Получаем задачи с фильтром
    tasks = []
    for task in mongo.db.tasks.find(query):
        # Преобразуем ObjectId в строку для JSON
        task['_id'] = str(task['_id'])
        # Преобразуем даты в ISO формат
        if 'created_at' in task:
            task['created_at'] = task['created_at'].isoformat()
        if 'due_date' in task and task['due_date']:
            task['due_date'] = task['due_date'].isoformat()
        tasks.append(task)

    return jsonify({'tasks': tasks})

if __name__ == '__main__':
    app.run(debug=True)

Заключение

Сегодня мы рассмотрели комплексный подход к интеграции баз данных в веб-приложения с использованием фреймворка Flask. Позвольте подытожить ключевые моменты нашей лекции.

Flask, как легковесный и гибкий веб-фреймворк, предоставляет нам основу для разработки современных веб-приложений. Однако его истинная сила раскрывается при интеграции с системами хранения данных.

В контексте реляционных БД мы детально изучили SQLite – решение для быстрой разработки и тестирования, что было продемонстрировано в нашем практическом примере с использованием контекстного менеджера g. Мы также обсудили PostgreSQL как предпочтительный выбор для эксплуатации благодаря его надежности и поддержке сложных типов данных. MySQL/MariaDB представляют собой компромиссное решение с хорошими показателями производительности.

Особое внимание мы уделили SQLAlchemy – ORM-системе, которая абстрагирует работу с базой данных, позволяя оперировать объектами Python вместо написания SQL-запросов напрямую. Flask-SQLAlchemy значительно упрощает интеграцию, предоставляя элегантный API для определения моделей и выполнения CRUD-операций. Система миграций Alembic и Flask-Migrate позволяет контролировать эволюцию схемы данных, что критически важно при промышленной разработке.

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

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

Практические задания по работе с базами данных в Flask

Задание 1: Создание простого приложения с SQLite Создайте базовое Flask-приложение, которое подключается к SQLite базе данных и создает таблицу users с полями id, name и email. Реализуйте маршрут, который выводит сообщение "База данных успешно подключена".

Задание 2: Чтение данных из базы Расширьте предыдущее приложение, добавив в него несколько пользователей напрямую через SQL-запросы. Создайте маршрут /users, который выводит список всех пользователей в формате JSON.

Задание 3: Форма для добавления пользователей Создайте веб-форму для добавления новых пользователей в базу данных. Форма должна содержать поля для имени и email, данные должны сохраняться в базе данных SQLite через прямые SQL-запросы.

Работа с различными СУБД

Задание 4: Переход на PostgreSQL Возьмите приложение из задания 3 и модифицируйте его для работы с PostgreSQL вместо SQLite. Обеспечьте возможность переключения между разными базами данных через переменные окружения.

Задание 5: Создание универсального класса для работы с базами данных Разработайте класс DatabaseManager, который абстрагирует работу с разными СУБД (SQLite, PostgreSQL, MySQL) и позволяет выполнять базовые операции чтения и записи независимо от конкретной базы данных.

SQLAlchemy и Flask-SQLAlchemy

Задание 6: Миграция на Flask-SQLAlchemy Перепишите приложение из задания 5, используя Flask-SQLAlchemy вместо прямых SQL-запросов. Создайте модель User и реализуйте CRUD-операции с использованием ORM.

Задание 7: Связи между таблицами Расширьте приложение, добавив модель Post (посты пользователей) с полями id, title, content и user_id. Настройте связь "один-ко-многим" между пользователями и их постами. Реализуйте функциональность добавления постов для конкретных пользователей и отображения всех постов пользователя.

Задание 8: Миграции базы данных Добавьте в приложение поддержку миграций с помощью Flask-Migrate. Создайте миграцию для добавления поля created_at к модели Post и примените её к базе данных.

NoSQL базы данных и комбинированный подход

Задание 9: Работа с MongoDB Создайте новое Flask-приложение для управления задачами (todo-list), используя MongoDB и Flask-PyMongo. Реализуйте функциональность создания, просмотра, редактирования и удаления задач. Задачи должны иметь поля: название, описание, статус выполнения и дата создания.

Задание 10: Комбинированное использование SQL и NoSQL Разработайте приложение электронного магазина, которое использует PostgreSQL через SQLAlchemy для хранения информации о пользователях, заказах и товарах, а MongoDB для хранения отзывов о товарах и аналитических данных. Реализуйте: - Регистрацию и авторизацию пользователей - Каталог товаров с возможностью фильтрации - Корзину покупок и оформление заказа - Систему отзывов на товары - Страницу истории заказов пользователя