Тестовое задание для поступления в Яндекс школу бэкенд-разработки
Для развёрки на сервере необходимо:
- установить Docker
- установить Docker Compose
- скопировать репозиторий
- выполнить из корневой папки репозитория команду docker-compose up -d
В данной реализации были использованы:
- Docker и Docker Compose для сборки проекта
- Flask и Flask_restful для обеспечения обработки запросов
- Gunicorn для обеспечения одновременной обработки нескольких запросов
- SQLAlchemy и Flask_SQLAlchemy для обеспечения взаимодействия с базой данных
- MySQL bd (стандартный Docker-образ) в качестве базы данных
- Cerberus для валидации данных
База данных состоит из трёх таблиц:
- Import таблица с id импортов, единственный столбец:
- import_id
- Citizen таблица с информацией о жителях, столбцы:
- import_id
- citizen_id
- town
- street
- building
- apartment
- name
- birth_date
- gender
- Tie таблица, в которой хранятся родственные связи, столбцы:
- import_id
- first_citizen_id
- second_citizen_id
Все ответы имеют вид
{
"data": "
}
Definition
Post /imports
Arguments
[
{
"citizen_id":int Уникальный идентификатор жителя, неотрицательное число, в разных выгрузках citizen_id не уникален и может
повторяться у разных жителей
"town":string Название города. Непустая строка, содержащая хотя бы 1 букву или цифру, не более 256 символов.
"street":string Название улицы. Непустая строка, содержащая хотя бы 1 букву или цифру, не более 256 символов.
"building":string Номер дома, корпус и строение. Непустая строка, содержащая хотя бы 1 букву или цифру, не более 256 символов.
"apartment":int Номер квартиры, неотрицательное число.
"name":string Непустая строка, не более 256 символов.
"birth_date":string Дата рождения в формате ДД.ММ.ГГГГ (UTC +0). Должна быть меньше текущей даты и должна быть существующей датой
"gender":string Значения male, female.
"relatives":list of ints Ближайшие родственники, уникальные значения существующих citizen_id жителей из этой же выгрузки.
}
]
Response
-
400 Bad Request
в наборе есть неописанные поля / в наборе присутствуют не все поля / значения каких-то полей некорректны / какие-то значения полей null / в данных присутствуют неккоректные родственные связи -
201 Created
в случае успеха
{
"data": {
"import_id":1
}
}
Definition
PATCH /imports/$import_id/citizens/$citizen_id
Arguments
"import_id":int
идентификатор импорта"citizen_id"
идентификатор жителя
{
должно быть хотя бы одно из полей, значения не могут быть null
"town":string Название города. Непустая строка, содержащая хотя бы 1 букву или цифру, не более 256 символов.
"street":string Название улицы. Непустая строка, содержащая хотя бы 1 букву или цифру, не более 256 символов.
"building":string Номер дома, корпус и строение. Непустая строка, содержащая хотя бы 1 букву или цифру, не более 256 символов.
"apartment":int Номер квартиры, неотрицательное число.
"name":string Непустая строка, не более 256 символов.
"birth_date":string Дата рождения в формате ДД.ММ.ГГГГ (UTC +0). Должна быть меньше текущей даты и должна быть существующей датой
"gender":string Значения male, female.
"relatives":list of ints Ближайшие родственники, уникальные значения существующих citizen_id жителей из этой же выгрузки. Изменение должно быть двусторонним
}
Response
-
400 Bad Request
в наборе есть неописанные поля / в наборе присутствуют не все поля / значения каких-то полей некорректны / какие-то значения полей null / в данных присутствуют неккоректные родственные связи -
200
в случае успеха возращается актуальная информация об указанном жителе
{
"data": {
"citizen_id": 3,
"town": "Москва",
"street": "Льва Толстого",
"building": "16к7стр5",
"apartment": 7,
"name": "Иванова Мария Леонидовна",
"birth_date": "23.11.1986",
"gender": "female",
"relatives": []
}
}
Definition
GET /imports/$import_id/citizens
Arguments
"import_id":int
идентификатор импорта
Response
-
400 Bad Request
не корректный идентификатор импорта -
200
в случае успеха возвращает список всех жителей для указанного набора данных
{
"data": [
{
"citizen_id": 1,
"town": "Москва",
"street": "Льва Толстого",
"building": "16к7стр5",
"apartment": 7,
"name": "Иванов Иван Иванович",
"birth_date": "26.12.1986",
"gender": "male",
"relatives": [2,3] // id родственников
},
{
"citizen_id": 2,
"town": "Москва",
"street": "Льва Толстого",
"building": "16к7стр5",
"apartment": 7,
"name": "Иванов Сергей Иванович",
"birth_date": "01.04.1997",
"gender": "male",
"relatives": [1] // id родственников
},
{
"citizen_id": 3,
"town": "Москва",
"street": "Льва Толстого",
"building": "16к7стр5",
"apartment": 7,
"name": "Иванова Мария Леонидовна",
"birth_date": "23.11.1986",
"gender": "female",
"relatives": [1]
},
...
]
}
Definition
GET /imports/$import_id/citizens/birthdays
Arguments
"import_id":int
идентификатор импорта
Response
-
400 Bad Request
не корректный идентификатор импорта -
200
в случае успеха возвращает жителей и количество подарков, которые они будут покупать своим ближайшим родственникам (1-го порядка), сгруппированных по месяцам из указанного набора данных. Ключом должен быть месяц (нумерация должна начинаться с единицы, "1" - январь, "2" - февраль и т.п.). Если в импорте в каком-либо месяце нет ни одного жителя с днями рождения ближайших родственников, значением такого ключа должен быть пустой список.
{
"data": {
"1": [],
"2": [],
"3": [],
"4": [
{
"citizen_id": 1,
"presents": 1,
}
],
"5": [],
"6": [],
"7": [],
"8": [],
"9": [],
"10": [],
"11": [
{
"citizen_id": 1,
"presents": 1
}
],
"12": [
{
"citizen_id": 2,
"presents": 1
},
{
"citizen_id": 3,
"presents": 1
}
]
}
}
Definition
GET /imports/$import_id/towns/stat/percentile/age
Arguments
"import_id":int
идентификатор импорта
Response
-
400 Bad Request
не корректный идентификатор импорта -
200
в случае успеха возвращает статистику по городам для указанного набора данных в разрезе возраста (полных лет) жителей: p50, p75, p99, где число - это значение перцентиля. Расчеты производятся используя текущую дату (UTC). Значения перцентилей округляются до 2х знаков после запятой.
{
"data": [
{
"town": "Москва",
"p50": 35.0,
"p75": 47.5,
"p99": 59.5
},
{
"town": "Санкт-Петербург",
"p50": 45.0,
"p75": 52.5,
"p99": 97.15
}
]
}
Что означает:
"p50": 35.0, - 50% жителей меньше 35 лет
"p75": 47.5, - 75% жителей меньше 45 с половиной лет
Для тестирования написан скрипт test.py
Он генерирует валидные данные для post-запроса с помощью библиотеки Faker, а затем различными способами портит некоторую часть этих данных и проверяет, чтобы сервер правильно реагировал и на валидные, и на невалидные запросы.
Запросы можно посылать в том числе и одновременно.
Для запуска необходимо помимо стандартных библиотек иметь:
- Faker
- futures
Запуск тестов осуществляется с помощью команды python test.py (необходимые параметры в help)