Вступление
Всем доброго дня! В предыдущей статье Kawai.Focus - приложение для фокусировки внимания (часть 4) я:
- Улучшил код
ReadJson(читалка JSON); - Написал класс для сообщений ошибок
ErrorMessageна основеEnum; - Написал валидатор для таймера
TimerValidatorна основеdataclasses; - Улучшил функцию
custom_timer(); - Написал тесты для
validators_tests.py.
Сегодня я подключу базу данных SQLite3, которая отлично подходит для хранения данных в небольших однопользовательских и синхронных приложениях. Это лёгкая база данных, не требующая отдельного сервера. Её часто используют в мобильных приложениях для работы в оффлайн-режиме.
Где используется SQLite3:
- 📝 Личные дневники и заметки — сохранение записей пользователя;
- 📅 Трекеры привычек — хранение прогресса и статистики;
- 📚 Офлайн-читалки — книги, статьи без интернета;
- 💰 Финансовые приложения — учет расходов без сети.
Также будут разработаны функции для CRUD-операций, включая получение таймера по id и создание нового таймера. Кроме того, я внедрю валидацию данных таймера с помощью pydantic.
Заваривайте чай, доставайте вкусняшки — пора “фаршировать помидор”! 🍅
База данных SQLite3
Взаимодействие с базой данных в Python удобнее реализовывать через ORM, а не с помощью непосредственных SQL-запросов. Я буду использовать популярную ORM-библиотеку SQLAlchemy, которая позволяет автоматически преобразовывать операции с Python-объектами в SQL-запросы.
Для начала я установлю SQLAlchemy:
poetry add sqlalchemyНастройки для базы данных
Для хранения настроек проекта, включая параметры базы данных, я буду использовать библиотеку pydantic_settings. Она позволяет удобно управлять конфигурацией приложения, обеспечивая простую валидацию и загрузку настроек из разных источников.
pydantic_settings позволяет:
- Загружать конфигурацию из
.env, переменных окружения, файловyamlилиjson; - Автоматически валидировать значения при загрузке настроек;
- Использовать аннотации типов, что делает код читаемым и безопасным;
- Применять дефолтные значения и обрабатывать отсутствующие параметры;
- Преобразовывать данные (например,
str → intилиbool); - Использовать вложенные модели, что удобно для сложных конфигураций.
Устанавливаю pydantic_settings:
poetry add pydantic_settingsДалее я создам файл settings.py, в котором напишу три класса:
ModelConfig— модель конфигурации с настройками для.env;SettingsDB— класс с настройками базыданных;Settings— основной класс настроек.
Импорты
from pydantic_settings import BaseSettings, SettingsConfigDictРазбор импортируемых модулей:
BaseSettings (pydantic_settings)
- Базовый класс для создания моделей конфигурации;
- Позволяет загружать настройки из
.env, переменных окружения и других источников; - Автоматически валидирует данные на основе аннотаций типов;
SettingsConfigDict (pydantic_settings)
- Позволяет задавать конфигурацию модели настроек;
- Например, можно указать
env_file=".env", чтобы загрузить данные из.env; - Используется для управления поведением модели конфигурации.
Класс ModelConfig
class ModelConfig(BaseSettings):
"""Модель конфигурации"""
model_config = SettingsConfigDict(
env_file='.env',
env_file_encoding='utf-8',
extra='ignore'
)Разбор класса:
Наследование от BaseSettings
ModelConfigполучает функциональность для работы с переменными окружения и.envфайлами.
Атрибут model_config
- Определяет параметры конфигурации через
SettingsConfigDict. env_file=".env"— указывает, что настройки загружаются из файла.env.env_file_encoding="utf-8"— устанавливает кодировку файла.extra="ignore"— игнорирует неизвестные переменные окружения, чтобы избежать ошибок.
Класс SettingsDB
class SettingsDB(ModelConfig):
"""Класс для данных БД"""
name_db: str
@property
def get_url_db(self) -> str:
"""Метод вернёт URL для подключения к БД"""
return f'sqlite:///{self.name_db}'Разбор класса:
Наследуется от ModelConfig:
- Получает возможность загружать настройки из
.envи использовать конфигурацию, определённую вModelConfig;
Атрибут name_db: str:
- Описывает имя базы данных в виде строки;
- Это имя будет использовано для формирования строки подключения;
Метод get_url_db (с декоратором @property):
- Автоматически формирует url для подключения к SQLite3;
- Использует шаблон
'sqlite:///{self.name_db}', гдеself.name_db— название файла БД; - Позволяет обращаться к
get_url_dbкак к атрибуту (settings.db_settings.get_url_db).
Класс Settings
class Settings(ModelConfig):
"""Класс для данных конфига"""
db_settings: SettingsDB = SettingsDB()Разбор класса:
Наследуется от ModelConfig:
- Получает доступ к настройкам, загружаемым из
.env; - Использует
SettingsConfigDictдля обработки переменных окружения;
Атрибут db_settings: SettingsDB:
- Определяет объект настроек БД, который создаётся из
SettingsDB(); - Позволяет обращаться к данным БД (
settings.db_settings.name_db); - Включает метод
get_url_db, который формирует URL для подключения к SQLite3.
Инициализация настроек
settings = Settings()Инициализация класса настроек для последующего использования.
Весь код settings.py
from pydantic_settings import BaseSettings, SettingsConfigDict
class ModelConfig(BaseSettings):
"""Модель конфигурации"""
model_config = SettingsConfigDict(
env_file='.env',
env_file_encoding='utf-8',
extra='ignore'
)
class SettingsDb(ModelConfig):
"""Класс для данных БД"""
name_db: str
@property
def get_url_db(self) -> str:
"""Метод вернёт URL для подключения к БД"""
return f"sqlite:///{self.name_db}"
class Settings(ModelConfig):
"""Класс для данных конфига"""
db_settings: SettingsDb = SettingsDb()
settings = Settings()
Сессия
Сессия базы данных представляет собой объект или механизм, управляющий взаимодействием с БД, обеспечивающий выполнение транзакций и запросов.
Для её реализации будет использоваться sessionmaker — фабрика сессий, основанная на шаблоне проектирования "Фабрика" (Factory Pattern). Этот паттерн применяется для создания объектов без явного указания их конкретного класса, что упрощает управление подключениями и повышает гибкость архитектуры.
Основные функции сессии:
- Открывает соединение с базой данных;
- Позволяет выполнять SQL-запросы;
- Отслеживает изменения объектов перед сохранением;
- Коммитит (сохраняет) или откатывает (rollback) транзакции;
- Закрывает соединение после завершения работы.
Импорты
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session
from kawai_focus.settings import settingsРазбор импортируемых модулей:
create_engine (sqlalchemy):
- Создаёт объект подключения к базе данных;
- Позволяет управлять соединением с БД;
- Используется для передачи данных в ORM;
sessionmaker (sqlalchemy.orm):
- Фабрика для создания сессий (Session);
- Позволяет управлять транзакциями, запросами и сохранением данных;
Session (sqlalchemy.orm):
- Класс сессии, который используется для выполнения операций с БД;
- Позволяет делать SQL-запросы, коммитить изменения и откатывать транзакции;
settings (kawai_focus.settings):
- Кастомный модуль настроек приложения;
- Используется для получения URL подключения к БД.
Класс SessionDB
class SessionDB:
"""Класс для управления подключением к базе данных."""
def __init__(self) -> None:
self._engine = create_engine(
url=settings.db_settings.get_url_db,
echo=settings.db_settings.echo_db
)
self._session_factory = sessionmaker(
bind=self._engine,
expire_on_commit=False,
autocommit=False
)
@property
def get_session(self) -> Session:
"""Метод для получения сессии"""
return self._session_factorySessionDB — класс для управления подключением к базе данных, который инкапсулирует создание движка (engine) и фабрики сессий (sessionmaker) в SQLAlchemy.
Разбор класса:
Конструктор __init__:
- Создаёт объект
engineс параметрами подключения (url=settings.db_settings.get_url_db); echo=settings.db_settings.echo_db— включает/отключает вывод SQL-запросов в консоль для дебага;- Создаёт фабрику сессий (
sessionmaker), привязанную кengine; expire_on_commit=False— делает так, чтобы объекты оставались активными послеcommit();autocommit=False— отключает автоматическое коммитирование, требуя явного вызоваcommit().
Свойство get_session:
- Возвращает готовую сессию.
Инициализация класса сессии
db = SessionDB()Разбор кода:
db = SessionDB():
- Создаёт экземпляр класса
SessionDB;
Весь код session.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session
from kawai_focus.settings import settings
class SessionDB:
"""Класс для управления подключением к базе данных."""
def __init__(self) -> None:
self._engine = create_engine(
url=settings.db_settings.get_url_db,
echo=settings.db_settings.echo_db
)
self._session_factory = sessionmaker(
bind=self._engine,
expire_on_commit=False,
autocommit=False
)
@property
def get_session(self) -> Session:
"""Метод для получения сессии"""
return self._session_factory
db = SessionDB()
Модели
Теперь я создам модель, которая будет отвечать за хранение настроек таймера. В пакете database уже подготовлены два файла: models.py и mixins.py.
mixins.py
Миксины позволяют переиспользовать общие поля моделей, исключая дублирование кода. Одним из таких полей является id, который присутствует почти в каждой таблице базы данных.
В файле mixins.py создан класс IDMixin, предназначенный для повторного использования id поля в различных моделях.
Содержимое mixins.py:
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy import Integer
class IDMixin:
"""Базовый класс для моделей с id"""
id: Mapped[int] = mapped_column(
Integer,
name="id",
primary_key=True,
autoincrement=True
)Разбор класса:
Наследование от Mapped[int]:
idобъявляется как типизированное поле, соответствующееint;- Использует
Mapped, который помогает работать с аннотированными типами в SQLAlchemy 2;
Использование mapped_column():
Integer— указывает, что полеidявляется целочисленным;name="id"— явно задаёт имя столбца в таблице;primary_key=True— делаетidпервичным ключом, который уникально идентифицирует запись;autoincrement=True— указывает, что значенияidбудут автоматически увеличиваться при добавлении новых записей.
models.py
В файле models.py будут находится модели Base и Timer:
Base— базовая модель;Timer— модель с данными таймера.
Импорты
from sqlalchemy.orm import DeclarativeBase, mapped_column, Mapped
from sqlalchemy import Integer, String
from kawai_focus.database.mixins import IDMixinРазбор импортов:
DeclarativeBase (sqlalchemy.orm):
- Базовый класс для декларативного объявления моделей в SQLAlchemy 2.0;
- Позволяет создавать ORM-модели, используя Python-аннотации типов;
mapped_column (sqlalchemy.orm):
- Функция для явного указания параметров столбца в ORM-модели;
- Используется внутри аннотированных
Mapped[...]полей;
Mapped (sqlalchemy.orm):
- Позволяет аннотировать поля в ORM-модели, указывая их типы (
Mapped[int],Mapped[str]); - Используется в SQLAlchemy 2.0 для улучшенной работы с типами данных;
Integer (sqlalchemy):
- Определяет целочисленный (
INTEGER) тип данных для столбца в БД;
String (sqlalchemy):
- Определяет строковый (
VARCHAR) тип данных для столбца в БД;
IDMixin (kawai_focus.database.mixins):
- Кастомный миксин, который, вероятно, добавляет в модели поле
idкак первичный ключ; - Упрощает создание моделей с уникальным идентификатором, избегая дублирования кода.
Модель Base
class Base(DeclarativeBase):
"""Класс для корректной работы аннотаций"""
passРазбор класса:
Наследование от DeclarativeBase:
DeclarativeBaseзаменяетdeclarative_base()и предоставляет удобный способ объявления моделей;- Позволяет корректно работать с аннотациями типов (
Mapped[...]); - Упрощает наследование для всех моделей БД;
Почему pass ?
- В этом классе нет дополнительных атрибутов, он просто определяет базу для всех моделей;
- В дальнейшем все модели могут наследоваться от
Base, получая доступ к механизмам ORM.
Модель Timer
class Timer(Base):
"""Модель настроек таймера"""
__tablename__ = 'timer'
title: Mapped[str] = mapped_column(
String(length=200),
name='название',
nullable=False
)
pomodoro_time: Mapped[int] = mapped_column(
Integer,
name='время помидора',
nullable=False
)
break_time: Mapped[int] = mapped_column(
Integer,
name='время перерыва',
nullable=False
)
break_long_time: Mapped[int] = mapped_column(
Integer,
name='время долгого перерыва',
nullable=False
)
count_pomodoro: Mapped[int] = mapped_column(
Integer,
name='количество помидоров',
nullable=False
)Разбор кода:
__tablename__ = 'timer':
- Определяет название таблицы в базе данных (
timer);
Поле title (название таймера):
Mapped[str]— аннотация типа, указывает, что поле хранит строку;mapped_column(String(length=200))— ограничение длины до 200 символов;name='название'— имя столбца в БД (явно указано на русском);nullable=False— поле обязательное, оно не может бытьNULL;
Поле pomodoro_time (время работы в Pomodoro);
Mapped[int]— тип данныхInteger;nullable=False— поле обязательно к заполнению;
Остальные поля break_time, break_long_time и count_pomodoro устроены так же как и поле pomodoro_time.
Весь код models.py
from sqlalchemy.orm import DeclarativeBase, mapped_column, Mapped
from sqlalchemy import Integer, String
from kawai_focus.database.mixins import IDMixin
class Base(DeclarativeBase):
"""Класс для корректной работы аннотаций"""
pass
class Timer(Base):
"""Модель настроек таймера"""
__tablename__ = 'timer'
title: Mapped[str] = mapped_column(
String(length=200),
name='название',
nullable=False
)
pomodoro_time: Mapped[int] = mapped_column(
Integer,
name='время помидора',
nullable=False
)
break_time: Mapped[int] = mapped_column(
Integer,
name='время перерыва',
nullable=False
)
break_long_time: Mapped[int] = mapped_column(
Integer,
name='время долгого перерыва',
nullable=False
)
count_pomodoro: Mapped[int] = mapped_column(
Integer,
name='количество помидоров',
nullable=False
)
Миграции
Теперь самое время настроить миграции. Для этого я буду использовать Alembic — мощный инструмент для управления изменениями в базе данных, работающий поверх SQLAlchemy. Он помогает отслеживать версионность схемы, вносить изменения и управлять ими эффективно.
Также я буду использовать Ruff — быстрый линтер и форматтер для Python, который помогает поддерживать чистый и структурированный код. В паре с Alembic он полезен для обеспечения качества миграционных скриптов и предотвращения ошибок.
Устанавливаю alembic и ruff:
poetry add alembic ruffИнициализация среды миграции:
alembic init kawai_focus/database/alembicОсновную информацию о настройке alembic можно найти в статье FastAPI 4. Модель пользователя, миксины и Alembic автора proDream на сайте Код на салфетке.
Создание миграции
alembic revision --autogenerate -m "migrration timer"Результат выполнения команды:
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected added table 'timer'
Generating /media/user_name/Files/Programming/Python/Projects/kawai-focus/kawai_focus/database/alembic/versions/2025_05_06_2026-e3e6ab4aede7_migrration_timer.py ... done
Running post write hook 'ruff' ...
1 file reformatted
doneРазбор выполнения команды:
Context impl SQLiteImpl:
- Alembic обнаружил, что используется SQLite3;
Will assume non-transactional DDL:
- В SQLite3 DDL-команды (изменения структуры БД) не поддерживают транзакции, поэтому Alembic не будет применять их внутри
BEGIN ... COMMIT; - У базы данных SQLite3 нет возможности откатывать изменения с помощью
ROLLBACK; - Нельзя удалять и изменять столбцы напрямую;
- Для глобальных изменений требуется создавать новую базу данных;
- В моём случае таблицы довольно простые, и я не планирую их изменять;
- Для добавления новых полей я буду создавать новую таблицу (конечно, не ради одного поля);
Detected added table 'timer':
- Alembic обнаружил, что в моделях появилась новая таблица
timer;
Generating ... migration_timer.py ... done:
- Сгенерирован новый файл миграции в
versions/, содержащий SQL-операции для добавления таблицы;
Running post write hook 'ruff':
- Запущен Ruff для автоформатирования кода миграции;
1 file reformatted:
- Ruff отформатировал файл, исправив стиль и возможные ошибки.
Применение миграции
alembic upgrade headРезультат работы команды:
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.runtime.migration] Running upgrade -> e3e6ab4aede7, migrration timerРазбор строки вывода INFO [alembic.runtime.migration] Running upgrade -> e3e6ab4aede7, migrration timer:
INFO [alembic.runtime.migration]→ Alembic сообщает, что выполняет процесс миграции;Running upgrade→ Применяется новая миграция (добавление/изменение таблиц, колонок);-> e3e6ab4aede7→ Это уникальный идентификатор миграции (revision ID);migrration timer→ Название миграции.
Валидация
Перед сохранением данных в модель необходимо проверять их типы и структуру. Поэтому я создал в пакете kawai_focus файл schemas.py, где теперь будут находиться все валидаторы приложения.
Внутри этого файла я создам класс TimerModel, который будет отвечать за проверку типов данных и определение схемы для модели Timer.
Содержимое schemas.py:
from pydantic import BaseModel
class TimerModel(BaseModel):
"""Модель для валидации данных таймера"""
id: int | None = None
title: str
pomodoro_time: int
break_time: int
break_long_time: int
count_pomodoro: intРазбор кода:
Наследование от BaseModel:
BaseModelиз Pydantic автоматически проверяет типы данных при создании объекта;
Поле id: int | None = None:
int | None→ Поле может быть целым числом илиNone(например, если объект ещё не сохранён в БД);= None-> Значение по умолчание дляid(нужно для создания нового таймера).
Далее идут остальные поля.
CRUD операции
CRUD-операция — это процесс управления данными в базе, включающий создание (Create), чтение (Read), обновление (Update) и удаление (Delete). Сейчас мне нужно написать две функции с crud операциями:
get_timer— получение данных таймера поid(Read -> Select в SQL);new_timer— создание нового таймера (Create -> Insert в SQL).
Импорты
from sqlalchemy import select, insert
from sqlalchemy.exc import SQLAlchemyError, OperationalError, NoResultFound
from pydantic import ValidationError
from kawai_focus.database.validators import TimerValidModel
from kawai_focus.database.session import db
from kawai_focus.database.models import Timer
from kawai_focus.main import LoggerРазбор импортов:
select, insert (sqlalchemy):
select— используется для выполнения SQL-запросов на чтение (SELECT * FROM ...);insert— позволяет вставлять данные в таблицу (INSERT INTO ...);
SQLAlchemyError, OperationalError, NoResultFound (sqlalchemy.exc):
SQLAlchemyError— базовый класс ошибок SQLAlchemy;OperationalError— ошибка, связанная с работой базы (например, проблемы с соединением);NoResultFound— возникает, еслиSELECTне нашёл нужных данных;
ValidationError (pydantic):
- Ошибка валидации данных, возникающая при проверке моделей Pydantic;
- Полезна для проверки корректности входных данных перед записью в БД;
TimerValidModel (kawai_focus.database.validators):
- Модель валидации таймера;
session (kawai_focus.database.session);
- Объект сессии SQLAlchemy, используемый для выполнения запросов к БД.
- Инкапсулирует логику работы с подключением и транзакциями;
Timer (kawai_focus.database.models):
- ORM-модель таймера, представляющая таблицу
timerв базе данных; - Содержит поля, такие как
title,pomodoro_time,break_timeи другие;
Logger (kawai_focus.main):
- Объект для логирования событий, ошибок или операций в приложении.
Функция get_timer()
def get_timer(timer_id: int) -> TimerValidModel:
"""Функция для получения данных таймера"""
try:
with db.get_session() as session:
timer_model = Timer
query = select(
timer_model.id,
timer_model.title,
timer_model.pomodoro_time,
timer_model.break_time,
timer_model.break_long_time,
timer_model.count_pomodoro
).where(timer_id timer_model.id)
result = session.execute(query)
timer = result.mappings().first()
return TimerValidModel.model_validate(obj=timer, from_attributes=True)
except (ConnectionError, SQLAlchemyError, TimeoutError, OperationalError, ValidationError, NoResultFound) as err:
Logger.error(f'{err.__class__.__name__}: {err}')Разбор кода:
Аргумент timer_id: int:
- Ожидает
int→ идентификатор таймера;
Открытие сессии (with db.get_session() as session:):
- Гарантирует, что соединение с БД будет корректно закрыто после выполнения запроса;
Формирование SQL-запроса (select):
timer_model = Timer→ получает ORM-модельTimer;select(...)→ выбираетid,title,pomodoro_timeи другие поля;.where(timer_id timer_model.id)→ фильтрует записи, оставляя только таймер с нужнымid;
Выполнение запроса (session.execute(query)):
- Запускает SQL-запрос, получая данные из таблицы
timer; .mappings().first()→ извлекает первую найденную запись (None, если таймера с такимidнет);
Валидация данных через Pydantic:
TimerValidModel.model_validate(obj=timer, from_attributes=True);- Преобразует объект таймера в модель Pydantic, проверяя типы и значения;
Обработка ошибок (try-except):
- Ловит ошибки подключения (
ConnectionError, OperationalError); - Ошибки в SQLAlchemy (
SQLAlchemyError, TimeoutError, NoResultFound); - Ошибка валидации (
ValidationError); - Логирует ошибку через
Logger.error(...).
Функция new_timer()
def new_timer(data: TimerValidModel) -> bool | None:
"""Функция для создания нового таймера"""
try:
with db.get_session() as session:
timer_model = Timer
query = insert(timer_model).values(**data.model_dump())
session.execute(query)
session.commit()
except (ConnectionError, SQLAlchemyError, TimeoutError, OperationalError, ValidationError) as err:
Logger.error(f'{err.__class__.__name__}: {err}')
else:
return TrueРазбор кода:
Аргумент data: TimerValidModel:
- Получает объект
TimerValidModel, который содержит проверенные данные таймера; model_dump()→ ПреобразуетPydantic-модель в словарь для SQLAlchemy;
Открытие сессии (with db.get_session() as session:):
- Гарантирует, что соединение с БД будет автоматически закрыто после выполнения запросов;
Создание SQL-запроса insert:
query = insert(timer_model).values(**data.model_dump());insert(timer_model)→ вставляет новую запись в таблицуtimer;.values(**data.model_dump())→ передаёт данные таймера в видеdict;
Выполнение SQL-запроса (session.execute(query)):
- Запускает команду
INSERT INTO timer (...) VALUES (...);
Сохранение изменений (session.commit()):
- Подтверждает транзакцию, записывая новый таймер в базу;
Обработка ошибок аналогична с функцией get_timer().
Улучшение TimerValidator
В моём проекте сейчас есть два файла: validators.py и schemas.py, которые находятся в разных частях программы. Однако их можно объединить в один, чтобы упростить структуру.
Класс TimerValidator написан с использованием декоратора @dataclass, тогда как TimerValidModel наследуется от BaseModel из pydantic.
Старый код класса TimerValidator
@dataclass
class TimerValidator:
"""Класс для валидации функции таймера."""
hh: int = 0
mm: int = 0
ss: int = 0
def __post_init__(self):
if not all(isinstance(value, int) for value in (self.hh, self.mm, self.ss)):
raise TypeError(ErrorMessage.NOT_INT_TYPE.value)
if self.ss 0 and self.mm 0 and self.hh 0:
raise ValueError(ErrorMessage.NO_TIME.value)
if self.ss < 0 or self.mm < 0 or self.hh < 0:
raise ValueError(ErrorMessage.NEGATIVE_TIME.value)
if self.ss > 59 or self.mm > 59:
raise ValueError(ErrorMessage.SS_MM_BIG.value)
if self.hh > 23:
raise ValueError(ErrorMessage.HH_BIG.value)Поскольку в проекте используется pydantic для валидации типов данных, применение dataclasses не имеет большого смысла. Логичнее переписать TimerValidator, чтобы использовать возможности pydantic и превратить его из валидатора в схему данных.
TimerTimeModel
Удаляю старый validators.py из пакета utils.py, поскольку его логичнее разместить в разделе схем, а не утилит. Создаю класс TimerTimeModel в kawai_focus/schemas.py. Название TimerTimeModel лучше отражает его назначение, так как он отвечает за структуру и валидацию данных времени таймера.
Я буду использовать класс Field, который позволит избавиться от множества валидаторов, сократить объем кода и задействовать встроенные механизмы валидации.
Содержимое класса TimerTimeModel:
class TimerTimeModel(BaseModel):
"""Модель для валидации времени таймера"""
hh: int = Field(0, ge=0, le=23)
mm: int = Field(0, ge=0, le=59)
ss: int = Field(0, ge=0, le=59)
@field_validator('hh', 'mm', 'ss')
@classmethod
def check_all_time_fields(cls, value: int) -> int:
"""Метод валидирует все поля времени"""
# гарантирует, что время не равно 00:00:00
if value 0:
raise ValueError(ErrorMessage.NO_TIME.value)
return valueРазбор кода:
1. Определение базовой модели:
TimerTimeModel наследуется от BaseModel (Pydantic), что позволяет автоматически проверять данные на соответствие типам;
Поля:
hh: int = Field(0, ge=0, le=23)— Определяет полеhh(часы) с начальным значением0. Задает границы допустимых значений: от0до23;mm: int = Field(0, ge=0, le=59)— Определяет полеmm(минуты) с начальным значением0. Ограничивает диапазон значений от0до59;ss: int = Field(0, ge=0, le=59)— Определяет полеss(секунды) с начальным значением0. Устанавливает допустимый диапазон от0до59;
2. Валидатор check_all_time_fields:
- Проверяет все поля времени (
hh,mm,ss); - Запрещает значение
00:00:00(ValueError→ErrorMessage.NO_TIME.value); - Проверяет, что переданные значения типа
int(TypeError).
Анонс на следующие статьи
Следующая моя статья выйдет 29 мая 2025 года. Прежде чем приступать к разработке экрана конструктора, я должен убедиться, что новый код работает корректно. Поэтому в следующей статье я планирую написать тесты для валидаторов, модели и CRUD-операций.
Если у вас есть мысли о том, как можно улучшить проект, пишите в комментариях — с удовольствием ознакомлюсь с вашими предложениями!
Читайте продолжение — не пропустите!
Заключение
- База данных SQLite3 подключена к проекту;
- Создана модель таймера;
- Подключены
alembicиruffдля миграций; - Созданы функции CRUD операций для создания и получения таймера;
- Написаны два валидатора на
pydantic; - Разобраны недостатки SQLIte3.
Ссылки к статье
- Мои статьи Arduinum628 на Код на салфетке;
- Репозиторий проекта Kawai.Focus.
Комментарии
Оставить комментарийВойдите, чтобы оставить комментарий.
Комментариев пока нет.