Cat

FastAPI 4. Модель пользователя и Alembic

В этом посте начнём работу по системе пользователей в проекте. Опишем модели базы данных и инициализируем Alembic для создания миграций.

Сервис на FastAPI proDream 17 Октябрь 2024 Просмотров: 106

Практически каждому проекту требуется модель пользователя — для регистрации, авторизации и работы с профилем. Наш проект не станет исключением.

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

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

 

Базовая модель

Начнём не с модели пользователя, а с создания базовой модели, от которой будут наследоваться все остальные. Это позволит нам избежать дублирования кода и организовать работу с базой данных более эффективно.

Если вы работали с моделями в Django, то знаете, что при создании модели не нужно явно указывать первичный ключ, если его не требуется настраивать — это поле автоматически добавляет базовая модель. Мы реализуем аналогичный подход, добавив свою базовую модель с полем первичного ключа.

В пакете core создадим подкаталог models, а в нём файл base.py.

Теперь создадим класс Base, который будет унаследован от DeclarativeBase из SQLAlchemy. Этот класс предназначен для декларативного стиля работы с таблицами базы данных, что означает, что мы описываем таблицы в виде Python-классов.

 

Поля класса

В классе Base будет два ключевых элемента:

  1. Абстрактный класс
    Мы добавим атрибут __abstract__ = True, чтобы указать SQLAlchemy, что этот класс не должен использоваться для создания таблицы в базе данных. Абстрактные классы используются как шаблоны для других моделей, которые наследуются от них.
  2. Поле id
    Поле id будет служить уникальным идентификатором для каждой записи в таблице. Оно будет иметь тип данных Mapped[UUID]. Вот что это значит:
  • Mapped — это специальный тип SQLAlchemy, который связывает поле класса с соответствующим столбцом в таблице базы данных.
  • UUID — тип данных, представляющий собой универсальный уникальный идентификатор.

 

Как работает UUID?

UUID (Universally Unique Identifier) — это стандарт для создания уникальных идентификаторов. Одной из самых популярных версий UUID является версия 4 (UUIDv4), которая генерируется случайным образом, используя криптографически безопасные методы. Это гарантирует уникальность каждого идентификатора даже при создании на разных устройствах или в разное время.

 

Использование mapped_column

Поле id будет объявлено через функцию mapped_column. Эта функция связывает атрибут класса с конкретным столбцом в базе данных. Мы укажем следующие аргументы:

  • Тип данных UUID(as_uuid=True), который будет хранить значение UUID в виде строки.
  • primary_key=True укажет, что это поле будет основным ключом таблицы, т.е. уникальным идентификатором.
  • Значение по умолчанию — uuid.uuid4, которое автоматически сгенерирует уникальный UUID для каждой новой записи.

 

Метод as_dict

Для удобства мы добавим метод as_dict, который преобразует объект модели в словарь. Это полезно, когда нужно передавать данные в JSON или другие форматы.

 

Код класса:

import uuid  

from sqlalchemy import UUID  
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column  


class Base(DeclarativeBase):  
    __abstract__ = True  

    id: Mapped[UUID] = mapped_column(  
        UUID(as_uuid=True), primary_key=True, default=uuid.uuid4  
    )  

    def as_dict(self):  
        return {c.name: getattr(self, c.name) for c in self.__table__.columns}

 

Модель пользователя

Теперь создадим саму модель пользователя. В пакете models, где мы уже разместили базовую модель, создадим новый файл user.py. В этом файле опишем класс User, который будет унаследован от базовой модели Base.

 

Поля модели

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

  1. __tablename__:
    • Это специальный dunder-атрибут, который указывает SQLAlchemy, как будет называться таблица в базе данных. В данном случае имя таблицы — user.
  2. email:
    • Поле для хранения электронной почты пользователя. Оно имеет строковый тип данных с максимальной длиной 100 символов.
    • Это поле должно быть уникальным (unique=True), так как каждому пользователю присваивается свой уникальный email, и обязательным (nullable=False), чтобы предотвратить создание записи без email.
  3. password:
    • Поле для хранения пароля пользователя в хешированном виде. Используем текстовый тип данных (Text), так как хеш пароля может быть достаточно длинным.
    • Это поле не уникально (unique=False), поскольку два пользователя могут иметь одинаковые пароли (но с разными email), и обязательно (nullable=False).
  4. is_active:
    • Булево поле, указывающее, активен ли пользователь или его аккаунт заблокирован. Это полезно для управления доступом.
    • По умолчанию значение — False, так как новый пользователь считается неактивным, пока, не подтвердит регистрацию.
  5. is_superuser:
    • Ещё одно булево поле, указывающее, является ли пользователь администратором или обладает ли он расширенными правами доступа.
    • По умолчанию значение — False.
  6. is_verified:
    • Булево поле, которое определяет, подтвердил ли пользователь свою электронную почту после регистрации. Это полезно для предотвращения фейковых регистраций и спама.
    • По умолчанию значение — False, что означает, что пользователь считается неподтверждённым, пока не выполнит верификацию.
  7. created_at:
    • Поле, которое хранит дату и время регистрации пользователя. Оно автоматически заполняется текущей датой и временем при создании записи.
    • Для этого используется комбинация server_default=func.now() (для работы на уровне базы данных) и default=datetime.datetime.now (для работы на уровне приложения).

 

Код модели:

import datetime  

from sqlalchemy import String, Boolean, func, Text  
from sqlalchemy.orm import Mapped, mapped_column  

from lkeep.core.models.base import Base  


class User(Base):  
    __tablename__ = "user"  

    email: Mapped[str] = mapped_column(String(100), unique=True, nullable=False)  
    password: Mapped[str] = mapped_column(Text, unique=False, nullable=False)  
    is_active: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)  
    is_superuser: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)  
    is_verified: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)  
    created_at: Mapped[datetime.datetime] = mapped_column(  
        server_default=func.now(), default=datetime.datetime.now  
    )

 

Видимость моделей для Alembic

Чтобы система миграций Alembic могла "увидеть" наши модели и корректно работать с ними при создании миграций, необходимо выполнить определённые действия. Это важный шаг, так как без него Alembic не сможет автоматически отслеживать изменения в моделях базы данных.

 

Добавление __all__ и импортов

  1. Открываем __init__.py в пакете models:
    • Этот файл нужен для того, чтобы Python воспринимал директорию как пакет. В данном случае мы будем использовать его для экспорта наших моделей, чтобы они стали доступными для Alembic.
  2. Добавляем dunder-атрибут __all__:
    • Атрибут __all__ — это специальная конструкция, которая указывает, какие имена (модели) должны экспортироваться, когда кто-то выполняет импорт всех объектов из пакета с помощью конструкции from models import *.
    • Мы перечислим все модели, которые должны быть доступны для Alembic.
  3. Импорты моделей:
    • После __all__ нужно явно импортировать все модели, которые мы перечислили, чтобы Alembic мог работать с ними при генерации миграций.

 

Пример кода:

from lkeep.core.models.base import Base
from lkeep.core.models.user import User


__all__ = ("Base", "User")

 

Теперь при создании миграций Alembic сможет видеть наши модели и корректно отслеживать изменения в базе данных.

 

Установка и инициализация Alembic

Для работы с базой данных и управления её миграциями мы будем использовать Alembic — инструмент для создания и применения изменений в структуре базы данных. Это удобно, так как не требует вручную изменять существующие таблицы или записывать их в отдельных скриптах. Процесс работы Alembic схож с миграциями в Django, что делает его понятным для тех, кто уже знаком с этим фреймворком.

 

Установка Alembic

Чтобы установить Alembic в проект, выполним следующую команду:

poetry add alembic black

 

  • alembic — сам инструмент для создания и применения миграций.
  • black — дополнительный инструмент для автоматического форматирования кода.

 

Инициализация Alembic

После установки нужно инициализировать Alembic в проекте. Для этого выполняем следующую команду:

alembic init --template async alembic

 

Здесь мы указываем:

  • --template async — указывает на использование асинхронного шаблона для работы с базой данных, что важно для проектов на FastAPI.
  • alembic — имя директории, где будут храниться настройки и файлы миграций.

 

Результат

После выполнения команды в корне вашего проекта появятся:

  • Директория alembic — в ней будут храниться все миграции и файлы конфигурации.
  • Файл alembic.ini — основной конфигурационный файл для Alembic, в котором можно настроить параметры миграций.

 

Настройка Alembic

После инициализации Alembic необходимо внести изменения в конфигурацию, чтобы корректно подключиться к базе данных и настроить форматирование миграций.

 

Шаг 1: Изменение alembic.ini

Откроем файл alembic.ini. В нем потребуется раскомментировать следующие строки:

  • 9-я строка — задаёт шаблон имени файлов для миграций:
# было
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s

# стало
file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s

 

  • Строки 70-73 — настройки для инструмента форматирования кода black. Они обеспечат автоматическое форматирование всех файлов миграций:
# было
# hooks = black  
# black.type = console_scripts  
# black.entrypoint = black  
# black.options = -l 79 REVISION_SCRIPT_FILENAME

# стало
hooks = black  
black.type = console_scripts  
black.entrypoint = black  
black.options = -l 79 REVISION_SCRIPT_FILENAME

 

Важно: будьте внимательны с пробелами! В начале строк не должно быть лишних пробелов.

Также закомментируем 61-ю строку, где указывается URL для подключения к базе данных:

# было
sqlalchemy.url = driver://user:pass@localhost/dbname

# стало
# sqlalchemy.url = driver://user:pass@localhost/dbname

 

В дальнейшем мы зададим свои параметры подключения.

 

Шаг 2: Изменение env.py

Теперь откроем файл env.py, который находится в директории alembic.

  1. Найдите строку config = context.config и после неё добавьте следующее:
from lkeep.core.settings import settings


config.set_main_option("sqlalchemy.url", settings.db_settings.db_url.unicode_string())

 

Этот код указывает Alembic, где взять URL для подключения к базе данных.

 

  1. Далее найдите строку target_metadata = None и измените её на:
from lkeep.core.models.base import Base


target_metadata = Base.metadata

 

Это позволит Alembic собирать все модели, унаследованные от базовой модели Base, для создания таблиц в базе данных.

 

Первые миграции

Теперь давайте проверим, корректно ли настроено подключение к базе данных и создание миграций.

 

Шаг 1: Создание миграции

Для этого откроем терминал и выполним команду:

alembic revision --autogenerate -m "Create User Table"

 

  • Флаг --autogenerate указывает Alembic автоматически сгенерировать файл миграции на основе изменений в моделях.
  • Флаг -m и строка после него используются для добавления описания миграции (по аналогии с описаниями коммитов в Git).

После выполнения команды, в директории versions должен появиться новый файл миграции. Откройте его и проверьте, все ли поля созданы корректно. Это важный шаг! Всегда проверяйте сгенерированные файлы миграций, чтобы убедиться, что автоматическая генерация не пропустила что-либо важное.

 

Шаг 2: Применение миграции

После того как вы убедились, что файл миграции создан корректно, применим его командой:

alembic upgrade head

 

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

 

Шаг 3: Проверка

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

 

Заключение

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

P.S. Мы также установили библиотеку black для автоматического форматирования файлов миграций. Вы можете запускать его вручную, выполнив в терминале команду:

black .

 

Эта команда проверит все .py-файлы в текущей директории на соответствие стандартам PEP-8 и при необходимости отформатирует их. Если хотите, вы можете указать конкретную директорию или файл вместо точки, чтобы сузить область проверки. Это хороший способ поддерживать код в порядке и избегать проблем с форматированием.

Автор

    Нет комментариев

    Реклама