Понедельник вчера (03.02.25) оказался на редкость плодотворным. Я закончил дипломный проект по курсу Python advanced и сдал его на проверку. Утром смотрю в телефон - сообщение от куратора:
Ну и чудненько.
Итак, оглядываясь на проделанную работу:
Так же еще несколько публикаций на эту тему, не стал вставлять ссылки на них.
Самое сложное - начало
Сколько бы работ я ни делал, всегда новая работа очень трудно сдвигается с мертвой точки. Скачал архив с фронтендом, распаковал, запустил файл index.html в надежде чего-нибудь увидеть - а там пусто, белый экран! Фигасе! И чего тут делать?!
Примерно представляю что делать, а с чего начать - тупняк!
Спустя пару дней я разобрался что надо делать: кратко - архив распаковать в какую нибудь созданную директорию (например - static) и указать к ней путь в конфигурации nginx.conf:
А так же указать prox_pass с указанием на FasAPI приложение, которое и будет реагировать на эндпойнты.
Чтобы приложение минимально заработало нужен эндпойнт /api/users/me, который ничего не будет отрабатывать а только возвращать JSON определенного вида, скажем такого:
Это приложение уже сможет что-то вывести на странице браузера:
Но тут не будет твитов... Для этого нужен еще один эндпойнт /api/tweets. Тут так же можно вставить что-то типа JSON заглушки, как в коде выше, вот такого типа (написано в описании к проекту):
Если создать что-то типа этого JSON'a то в ленте твитов что-то появится...
Создание базы данных и планирование таблиц
Первым делом нужны .env файлы, один в корне проекта (для docker-compose.yaml файла), второй в src (для проекта), третий с добавленным именем test: .test.env (для выполнения тестов).
В самом начале проекта я использовал .env файл вот такого вида:
В конце проекта понял, что такой вариант .env файла не годится. Ведь при написании docker-compose.yaml файла указываются имя пользователя, пароль и имя базы данных, которые будет храниться в общем доступе. А надо их привести к такому виду чтобы эти данные брались из .env файла а не были как у меня, вот в таком виде, всем доступны из docker-compose.yaml:
Поэтому переделал .env в такой вид:
Теперь эта часть docker-compose.yaml выглядит так:
А затем уже в файле src/core/config.py из этих кусочков собирал URL для подключения к базе данных:
Пришлось избавиться от отдельного файла .test.env. Потому что выяснилось - при тестах, этот файл не подгружается, в отличии от версии этого приложения без использования докер-контейнеров.
И ни какие танцы с бубном мне не помогли сделать так, чтобы использовалась тестовая база данных и тестовый пользователь БД. В итоге я забил на это дело и вставил такую конструкцию в conftest.py:
Лишь имя докер-контейнера базы данных берется из .env файла.
Проектирование базы данных. Таблицы
Модели начинаются с "помощника баз данных (db_helper)" , задачей которого - создать движок, создать сессию исходя из параметров полученых в src/core/config.py
Структурная схема таблицы users и ее код, которая имеет связь один ко многим (один пользователь может иметь множество твитов, лайков, подписчиков и сам быть подписчиком):
Структурная схема таблицы tweets и ее код, которая так же имеет связи один к одному (у одного твита может быть только один автор, у одного твита может быть только одно изображение) и один ко многим (у одного твита может быть множество лайков)
Структурная схема таблицы likes и ее код, которая имеет связь один к одному (возможно ошибаюсь), так как один лайк может быть только от одного пользователя в отношении одного твита.
Структурная схема таблицы images и её код. Таблица имеет связь один к одному (только одна картинка может быть только у одного твита)
Структурная схема таблицы followers_tbl и её код. Таблица имеет связь многие ко многим и описана она в модели users:
Роуты
В данном приложении фактически только три роута: users, tweets, medias. Которые работают через префикс /api и далее каждый (кроме medias) дополняеется дополнительными постфиксами, необходимыми для конкретных ситуаций. Я так и поделил всю логику приложения на три файла:
routes_users.py
routes_tweets.py
routes_medias.py
Так как линтер flake8 сильно ругался на различные числа и переменные, то пришлось добавить несколько констант в файле src/core/config.py которые использовались в роутах:
Далее вся логика приложения переходит выполнению запросов к базе данных. Где так же как и роуты запросы к базе данных поделены на три типа: запрос картинок, пользователей, твитов:
crud_users.py
Самый сложный запрос - получить список твитов отсортированный по количеству лайков и только тех, кого читает текущий пользователь. А так же, от себя, добавил - видеть свои собственные твиты.
crud_tweets.py
Загрузка картинок работает не только с базой данных но и с жестким диском, куда эта картинка сохраняется. Появляется дополнительные функции оперирующие записью и удалением файлов с жесткого диска, а так же различными проверками, созданием директорий.
crud_images.py + utils/image_files.py
Схемы валидации
Вот мы написали роуты, роуты обратились к CRUD базе данных, получили какой-либо ответ, теперь этот ответ нужно прогнать через схемы валидации. Либо это правильный ответ, либо пришла ошибка и то и другое валидируем через pydantic.
- Базовая схема и схемы обработки ошибок:
- Схема обработки пользовательских данных
- Схема вывода твитов:
- Схема обработки лайков
- Схема обработки изображений:
Запуск FastAPI приложения
Понимая как это всё работает нужно объединить в одном main.py файле. Чтобы все таблицы создались при первом запуске приложенения, затем можно просто закомментировать несколько строк кода, выполняющих удаление, создание и наполнение некими данными для демонстрации работы приложения.
Вот уже хотел вставить скриншот своего main.py, и что-то мне показался он слишком избыточным в плане импортов и включения разных функций, в итоге вынес лишнее "под капот"
Конечено, наверняка можно и инклюды объединить в один, но сходу у меня не получилось, так что путь остается как есть. Было до модернизации так:
Под "капотом", куда вынес лишний код, стало так:
Ну вот осталось написать Dockerfile в котором будет прописана сборка докер-контейнера приложения:
А так же docker-compose.yaml файл где будут соединены FastAPI приложение, контейнер с postgresql и nginx:
Можно делать запуск:
Работа приложения в браузере
Посмотрим что у нас появилось в браузере по адресу http://0.0.0.0 (можно указать 127.0.0.1 кому как нравится):
И увидеть его в самом низу:
Если мы его лайкнем, то он переместится сразу же после твита с двумя лайками:
Если нажать на пользователя (иконку) то можно войти в его профиль и отписаться, если отпишешься, сразу появится кнопка подписаться:
Можно попытаться удалить чужой твит, только у нас этого не получится:
Свой же твит мы можем безпрепятственно удалить, твит с картинкой, который я вначале создан - удален, удалена и сама картинка с жесткого диска:
Можно поубирать все свои лайки (сердечки станут белыми и -1)
Можно открыть "свой" профиль:
Тестирование эндпойнтов pytest
Чуть не забыл про тестироание... Это очень важная часть работы. Первым делом - conftest.py который создает движок, сессию, наполняет базу какой-то тестовой информацией, тестовый клиент.
Вставка данных в таблицы:
Тесты разбиты на три файла, сравнение происходит с данными из тех данных что должны быть на выходе, файл good_response.py:
Ну и сами тесты:
Сорян... остальные тесты опубликовать не могу. ДЗЕН не позволяет много картинок постить...
Чтобы запустить тесты нужно запустить приложение командой:
docker compose up --build
Затем войти в контейнер Fastapi приложения:
docker exec -it clone_twitter_app_container /bin/bash
Ну вот вроде бы и всё, что знал - рассказал, может быть чего-то упустил, забыл, не заметил, спрашивайте - отвечу. Куратор, после того как принял работу, предложил добавить в проект Selery, Sentry, Prometehus + Grafana. А смысл?! Данный проект изначально мёртво-рожденный, кроме как для отработки полученных навыков, нигде использоваться не будет, смысла допиливать его не вижу, разве что чисто из "спортивного интереса". Может быть и допилю...
Кто осилил мою писанину - вот ссылка на версию без докер-контейнеров, и вторая ссылка на версию с докером.
На этом у меня всё. Всем здоровья и счастья и всего самого наилучшего.
Слава Богу!