МИНИСТЕРСТВО ЦИФРОВОГО РАЗВИТИЯ, СВЯЗИ И МАССОВЫХ КОММУНИКАЦИЙ РФ Федеральное учреждение высшего профессионального образования Московский технический университет связи и информатики
Кафедра математическая кибернетика и информационные технологии
Курсовая работа по теме
«Разработка REST API сервиса»
по дисциплине:
Web- программирование
Выполнил студент
Проверила:
Полянцева Ксения Андреевна
Москва 2021
Оглавление
Цель 3
Разработка 6
Setup.py 7
База данных. Проектируем схему 10
Описываем схему в SQLAlchemy 13
Настраиваем Alembic 14
Генерируем миграции 18
Приложение 20
Сериализация данных 23
Обработчики 25
POST /imports 25
GET /imports/$import_id/citizens 30
PATCH /imports/$import_id/citizens/$citizen_id 32
GET /imports/$import_id/citizens/birthdays 36
GET /imports/$import_id/towns/stat/percentile/age 38
Тестирование 39
Обработчики 41
GET /imports/$import_id/citizens 45
POST /imports 48
PATCH /imports/$import_id/citizens/$citizen_id 52
GET /imports/$import_id/citizens/birthdays 55
GET /imports/$import_id/towns/stat/percentile/age 57
Миграции 59
Сборка 61
CI 63
Результаты работы 64
Вывод: 70
Список использованных источников 70
Цель
Разработать REST API-сервис посредством языка Python, протестировать, упаковать его в Docker-контейнер, а также развернуть с помощью Ansible.
Рис. 1 – структура проекта
Представим, что интернет-магазин подарков планирует запустить акцию в разных регионах. Чтобы стратегия продаж была эффективной, необходим анализ рынка. У магазина есть поставщик, регулярно присылающий (например, на почту) выгрузки данных с информацией о жителях.
Давайте разработаем REST API-сервис на Python, который будет анализировать предоставленные данные и выявлять спрос на подарки у жителей разных возрастных групп в разных городах по месяцам.
В сервисе реализуем следующие обработчики:
POST /imports
Добавляет новую выгрузку с данными;
GET /imports/$import_id/citizens
Возвращает жителей указанной выгрузки;
PATCH /imports/$import_id/citizens/$citizen_id
Изменяет информацию о жителе (и его родственниках) в указанной выгрузке;
GET /imports/$import_id/citizens/birthdays
Вычисляет число подарков, которое приобретет каждый житель выгрузки своим родственникам (первого порядка), сгруппированное по месяцам;
GET /imports/$import_id/towns/stat/percentile/age
Вычисляет 50-й, 75-й и 99-й перцентили возрастов (полных лет) жителей по городам в указанной выборке.
Для реализации предлагается выбрать СУБД PostgreSQL (или другую, по вашему усмотрению), зарекомендовавшую себя как надежное решение c отличной документацией на русском языке, сильным русским сообществом (всегда можно найти ответ на вопрос на русском языке). Реляционная модель достаточно универсальна и хорошо понятна многим разработчикам.
Основная задача сервиса — передача данных по сети между БД и клиентами — не предполагает большой нагрузки на процессор, но требует возможности обрабатывать несколько запросов в один момент времени. Асинхронный подход позволяет эффективно обслуживать нескольких клиентов в рамках одного процесса ОС (в отличие, например, от используемой во Flask/Django pre-fork-модели, которая создает несколько процессов для обработки запросов от пользователей, каждый из них потребляет память, но простаивает большую часть времени). Поэтому в качестве библиотеки для написания сервиса предлагается выбрать асинхронный aiohttp.
Рис 2. – Схема обработки запросов
SQLAlchemy позволяет декомпозировать сложные запросы на части, переиспользовать их, генерировать запросы с динамическим набором полей (например, PATCH-обработчик позволяет частичное обновление жителя с произвольными полями) и сосредоточиться непосредственно на бизнес-логике. С выполнением этих запросов и передачей данных быстрее всех справится драйвер asyncpg, а подружить их поможет asyncpgsa. Один из инструментов для управления состоянием БД и работы с миграциями — Alembic.
Логику валидации можно лаконично описать схемами Marshmallow (включая проверки на родственные связи). С помощью модуля aiohttp-spec связать aiohttp - обработчики и схемы для валидации данных, а бонусом сгенерировать документацию в формате Swagger и отобразить ее в графическом интерфейсе.
Для написания тестов используйте pytest. Для отладки и профилирования можно использовать отладчик PyCharm.
На любом компьютере с Docker (и даже на разных ОС) можно запускать упакованное приложение без необходимости настраивать окружение для запуска и легко устанавливать/обновлять/удалять приложение на сервере.
Для деплоя воспользуйтесь Ansible. Он позволяет декларативно описывать желаемое состояние сервера и его сервисов, работает по ssh и не требует специального софта.
Разработка
Задайте Python-пакету название analyzer и используйте следующую структуру:
Рис. 3 – структура пакета
В файле analyzer/ init .py разместите общую информацию о пакете: описание (docstring), версию, лицензию, контакты разработчиков.
Затем ее можно посмотреть встроенной командой help
$ python
>>> import analyzer
>>> help(analyzer)
Пример:
Рисунок 4 -Результат работы
Пакет имеет две входных точки — REST API-сервис (analyzer/api/__main__.py) и утилита управления состоянием БД (analyzer/db/__main__.py). Благодаря этому подходу к входным точкам можно обращаться с помощью команды python -m:
Рисунок 5 – входные точки
Setup.py
Главная цель файла setup.py — описать пакет с приложением для distutils/setuptools. В файле необходимо указать общую информацию о пакете (название, версию, автора и т. д.), но также в нем можно указать требуемые для работы модули, «экстра»-зависимости (например для тестирования), точки входа (например, исполняемые команды) и требования к интерпретатору. Плагины setuptools позволяют собирать из описанного пакета артефакт. Есть встроенные плагины: zip, egg, rpm, macOS pkg. Остальные плагины распространяются через PyPI: wheel, xar, pex. В сухом остатке, описав один файл, мы получаем огромные возможности. Именно поэтому разработку нового проекта нужно начинать с setup.py. В функции setup() зависимые модули указываются списком:
Но необходимо описать зависимости в отдельных файлах requirements.txt и requirements.dev.txt, содержимое которых используется в setup.py.
Это кажется более гибким, плюс тут есть секрет: впоследствии это позволит собирать Docker-образ быстрее. Зависимости будут ставиться отдельным шагом до установки самого приложения, а при пересборке Docker-контейнера попадать в кеш. Чтобы setup.py смог прочитать зависимости из файлов requirements.txt и requirements.dev.txt, написана функция:
Рисунок 6- функция для прочтения зависимостей
Стоит отметить, что setuptools при сборке source distribution по умолчанию включает в сборку только файлы .py, .c, .cpp и .h. Чтобы файлы с зависимостями requirements.txt и requirements.dev.txt попали в пакет, их необходимо явно указать в файле MANIFEST.in. setup.py целиком
Рисунок 7 – Программный код
Рисунок 8 – Продолжение программного кода
Установить проект в режиме разработки можно следующей командой (в editable режиме Python не установит пакет целиком в папку site-packages, а только создаст ссылки, поэтому любые изменения, вносимые в файлы пакета, будут видны сразу):
Рисунок 9 – Устанавливаю в режим разработки
База данных. Проектируем схему
В описании обработчика POST /imports приведен пример выгрузки с информацией о жителях:
Рисунок 10 – пример выгрузки с данных жителей
Реализация структуры данных:
Рисунок 11 -Реализация структуры данных
Таблица imports состоит из автоматически инкрементируемого столбца import_id. Он нужен для создания проверки по внешнему ключу в таблице citizens.
В таблице citizens хранятся скалярные данные о жителе (все поля за исключением информации о родственных связях). В качестве первичного ключа используется пара (import_id, citizen_id), гарантирующая уникальность жителей citizen_id в рамках import_id. Внешний ключ citizens.import_id -> imports.import_id гарантирует, что поле citizens.import_id будет содержать только существующие выгрузки.
Таблица relations содержит информацию о родственных связях
Одна родственная связь представлена двумя записями (от жителя к родственнику и обратно): эта избыточность позволяет использовать более простое условие при слиянии таблиц citizens и relations и получать информацию более эффективно. Первичный ключ состоит из столбцов (import_id, citizen_id, relative_id) и гарантирует, что в рамках одной выгрузки import_id у жителя citizen_id будут родственники c уникальными relative_id. Также в таблице используются два составных внешних ключа: (relations.import_id, relations.citizen_id) -> (citizens.import_id, citizens.citizen_id) и (relations.import_id, relations.relative_id) -> (citizens.import_id, citizens.citizen_id), гарантирующие, что в таблице будут указаны существующие житель citizen_id и родственник relative_id из одной выгрузки. Такая структура обеспечивает целостность данных средствами PostgreSQL, позволяет эффективно получать жителей с родственниками из базы данных, но подвержена состоянию гонки во время обновления информации о жителях конкурентными запросами (подробнее рассмотрим при реализации обработчика PATCH).