Тестовое задание: Python middle
Спасибо большое за интерес к нашей вакансии!

Ниже — тестовое задание для позиции Python-разработчика.
Мы будем рады, если вы сможете отправить ответ в течение 3–5 рабочих дней с момента получения. Чистое время работы - до 8 часов.
Если вдруг понадобится больше времени — ничего страшного, просто дайте знать заранее 🙂

1.Общее

Необходимо использовать следующие инструменты и технологии:
  • Python (3.10+)
  • FastAPI
  • PostgreSQL или SQLite
  • SQLAlchemy
Необходимо реализовать API для каталога товаров со следующими endpoint-ами:
  • GET /catalog/ - постраничный вывод списка товаров с поиском/фильтрацией
  • GET /catalog/filter/ - вывод параметров для фильтрации
  • GET /product/{UID} - информация о товаре
  • POST /product/ - добавление товара
  • DELETE /product/{UID} - удаление товара
  • POST /properties/ - добавление свойства
  • DELETE /properties/{UID} - удаление свойства
1.1. Структура данных

Товар содержит в себе:
  • uid (строка) - ID
  • name - название
  • properties - значение свойств товара.
Свойства могут быть либо численные (int), либо списочные (list). Например, свойство "высота" может принимать любой int, а свойство "объём памяти" иметь на выбор значения "1GB", "2GB", "10GB" и так далее.
Свойства содержат в себе:
  • uid (строка) - ID
  • name - название
  • type - тип свойства
  • values - массив значений свойства (для типа list):
  • value_uid - ID значения
  • value - название значения
Тестовые данные предоставлены в формате json.
Ссылка для скачивания

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

1.2. Задание

GET /catalog/

Данный URL возвращает список товаров в формате json, и принимает на вход следующие необязательные query-параметры:
  • page - номер страницы. По-умолчанию первая.
  • page_size - размер страницы. По-умолчанию 10.
  • property_X - значение свойства товара, по которому фильтруется результат. Может быть передано несколько значений для одного свойства
  • name - подстрока для поиска по названию товара
  • sort - параметр сортировки, принимает name или uid. Сортировка идёт по-возрастанию. По-умолчанию uid
Примеры:
  • /catalog/ - первые 10 товаров, первая страница
  • /catalog/?name=abc&property_uid1=uid1&property_uid1=uid2&property_uid2=uid3 - первые 10 товаров, отфильтрованных по property uid1 со значениями [uid1, uid2] и property uid2 с значением uid3
  • /catalog/?name=abc&property_uid3_from=10&property_uid3_to=15 - первые 10 товаров, отфильтрованных по property uid3 (тип int) со значениями от 10 до 15
Пример вывода:
{
  products: [
     {
       uid: "uid1",
       name: "Товар 1",
       properties: [
           {uid: "uid1", name: "Свойство 1", value_uid: "uid1", value: "Значение 1"},
           {uid: "uid3", name: "Свойство 3", value: 15}
       ]
     },
     {
       uid: "uid2",
       name: "Товар 2",
       properties: [
           {uid: "uid1", name: "Свойство 1", value_uid: "uid2", value: "Значение 2"},
           {uid: "uid3", name: "Свойство 3", value: 13}
       ]
     },
     // и так далее
  ],
  count: 50 // общее количество товаров по запросу
}
GET /catalog/filter/

Данный URL нужен для построения фильтра по товарам. Он возвращает общее количество товаров, удовлетворяющих запросу, и количество по свойствам. В query принимаются ровно те же параметры, что и в /catalog/, за исключением номера и размера страницы.
Например, запрос по всей базе:

/catalog/filter/
{
  count: 50,
  property_uid1: {uid1: 10, uid2: 15}, // 10 товаров с значением uid1 и 15 товаров с значением uid2
  property_uid2: {uid3: 20}, // 20 товаров с значением uid3
  property_uid4: {min_value: 10, max_value: 20} // это числовое свойство, минимальное значение 10, максимальное 20.
}
Запрос, который уже содержит фильтр. Должны выводиться данные только по товарам, попавшему в фильтр. По сути это сгруппированная статистика по результатам аналогичного запроса на /catalog/.

/catalog/?name=abc&property_uid1=uid1&property_uid1=uid2&property_uid2=uid3
{
  count: 35,
  property_uid1: {uid1: 10, uid2: 15},
  property_uid2: {uid3: 2},
  property_uid4: {min_value: 15, max_value: 20}
}
В данном случае запрос с тем же query на /catalog/ вернёт те же товары, по которым посчитана эта статистика.

GET /product/{UID}

В данном случае необходимо просто вывести все данные по товару в том же формате, как и в каталоге.

POST /product/

Данный URL необходим для создания товара. В него передаются данные в том же формате, в котором товары выводятся в каталоге:
{
  uid: uid1,
  name: "Товар 1",
  properties: [
      // передавать name для свойства не нужно, т.к. оно уже есть в базе
      {uid: "uid1", value_uid: "uid1"}, // передавать value не нужно, достаточно value_uid
      {uid: "uid3", value: 15}
  ]
}
В случае, если какое-либо свойство или его значение (для типа "список") отсутствует, то должна выдаваться ошибка.

DELETE /product/{UID}

Данный метод необходим для удаления товара. После его вызова товар должен исчезнуть из базы данных, либо должна быть выдана ошибка, если товар не найден.

POST /properties/

Данный URL необходим для создания свойства. В него передаётся тип свойства и доступные значения (для типа "список"):
// свойство типа "список"
{
  uid: uid1,
  name: "Свойство 1",
  type: "list",
  values: [
      {"value_uid": "uid1", "value": "Значение 1"},
      {"value_uid": "uid2", "value": "Значение 2"}
  ]
}
// или для числового свойства 
{
  uid: uid1,
  name: "Свойство 1",
  type: "int"
}
В случае, если какое-либо свойство или его значение (для типа "список") отсутствует, то должна выдаваться ошибка.

DELETE /properties/{UID}

Данный метод необходим для удаления свойства. После его вызова свойство должно исчезнуть из базы данных, либо должна быть выдана ошибка, если свойство не найдено. Значения свойства (для типа "список") должны быть удалены.

1.3. Исполнение
Необходимо, чтобы была подробная инструкция для разворачивания и запуска проекта под linux. Использование docker не обязательно.
*Дополнение:

фильтрация по значению свойства.

Запрос должен работать так: у нас есть фильтр по свойству 9b58c43d-163d-41e5-a323-d997ecce3fbc (PROPERTY_UID1) со значениями e17704ef-3a8f-47b2-b7ea-9933f2d3d28d и 06946301-5485-44db-b278-842648814c80 (VALUE_UID1, VALUE_UID2) и фильтр по 79e0b026-48b4-4790-9983-a94e46f63d3c (PROPERTY_UID2) со значениями ad3ff88b-a8cc-4158-b988-27d15e5e4816 (VALUE_UID3)

В результат попадают все товары, у которых property UID1 равно либо VALUE_UID1, либо VALUE_UID2, и у которых при этом UID2 равно VALUE_UID3. Грубо говоря, (PROPERTY_UID1=VALUE_UID1 OR UID2=VALUE_UID2) AND (PROPERTY_UID2=VALUE_UID3).

В запросе должно быть ?property_9b58c43d-163d-41e5-a323-d997ecce3fbc=e17704ef-3a8f-47b2-b7ea-9933f2d3d28d&property_9b58c43d-163d-41e5-a323-d997ecce3fbc=06946301-5485-44db-b278-842648814c80&property_79e0b026-48b4-4790-9983-a94e46f63d3c=ad3ff88b-a8cc-4158-b988-27d15e5e4816

Интересно кандидатам ✅

=