diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..5e83e5e --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +TOKEN= +PACKAGE_VERSION=<ВЕРИЯ ПАКЕТА> +TWINE_USERNAME=<ИМЯ для testPyPi> +TWINE_API_TOKEN= \ No newline at end of file diff --git a/.github/workflows/style_check.yml b/.github/workflows/style_check.yml new file mode 100644 index 0000000..389c911 --- /dev/null +++ b/.github/workflows/style_check.yml @@ -0,0 +1,26 @@ +name: sytle_check + +on: + pull_request: + push: + branches: + - develop + - master + - main +jobs: + check_style: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set-up python + uses: actions/setup-python@v5 + with: + python-version: 3.11 + + - name: Install dependies + run: | + python -m pip install --upgrade pip + pip install ruff==0.7.1 + + - name: Test style + run: python -m ruff check ./src diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6b08980 --- /dev/null +++ b/.gitignore @@ -0,0 +1,167 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +.bot_venv +.backend_venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +.idea/ +.vscode/ + +pachca-api-open-api-3-0-client/ \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..7ea41c3 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,17 @@ +repos: + +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + - id: check-merge-conflict + +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.7.1 + hooks: + - id: ruff + args: [--fix, ./src] + - id: ruff-format diff --git a/README.md b/README.md new file mode 100644 index 0000000..3cdc3d9 --- /dev/null +++ b/README.md @@ -0,0 +1,395 @@ +# Парсер OpenAPI для мессенджера "Пачка" + +### Библиотека для работы с API мессенджера "Пачка", автоматически генерируемая на Python. 🐍 + +## 🔖 Цели и задачи проекта + +Основная цель проекта — создание инструмента для преобразования спецификации OpenAPI в Python-пакет. 📋 +Этот пакет предоставляет структурированный и удобный интерфейс для взаимодействия с открытым API мессенджера "Пачка". 🌐 + +### ✅ Проект включает: +- **Конвертацию файлов OpenAPI**: Автоматизация генерации Python-кода, содержащего методы и классы для работы с API. +- **Подготовку Python-пакета**: Упаковка сгенерированного кода для распространения через менеджеры пакетов, такие как pip и poetry. + +### 🔧 Основные функции: +- **Обработка HTTP-запросов**: Сгенерированный код выполняет авторизованные запросы к серверу с использованием токенов. +- **Структуры данных**: Входные и выходные данные представлены в виде Python-классов. +- **Методы для эндпоинтов**: Методы названы в соответствии с эндпоинтами API, что обеспечивает ясность и соответствие. +- **Полная документация**: Сгенерированные классы и методы включают подробные docstring’и, созданные на основе OpenAPI. + +--- + +## 🔨 Установка генератора + +1. **В корне проекта, в файле .env указать:** + - TWINE_USERNAME=<Имя пользвателя сервиса TestPyPI> + - TWINE_API_TOKEN=<Токен пользвателя сервиса TestPyPI> + - TOKEN=<Токен пространства Пачки> + +2. **Установка библиотеки с сериса TestPyPI:** + + ```bash + pip install --no-cache-dir -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ PachcaAPI + ``` + +3. **Запуск генератора и тестов:** + + - run_generate_and_test - запуск генератора и тестов + - run_generator - запуск генератора + - run_test - запуск тестов + +### Примечание: +Пример тест-запросов, которые выполняются командой `run_generate_and_test` и `run_test`, можно найти по пути: +**Дирректория Виртуального Окружения** -> **Дирректория generator1** -> **Файл pachca.py** + +--- + +## 📜 Детали реализации + +### 📄 Подготовка OpenAPI-файла: +- Формат: JSON или YAML. +- Версия OpenAPI: 3.0. + +**Обязательные секции:** +- `openapi`: Версия файла OpenAPI. +- `info`: Общая информация об API. +- `servers`: Данные о серверах для выполнения запросов. +- `paths`: Описание эндпоинтов с параметрами `operationId` и `tags` для генерации методов. +- `components`: Схемы данных для структур входных и выходных параметров. + +## 💡 Команда написания спецификации + +- [Денис Лопин](https://github.com/fantyissues) +- [Александр Аполинаров](https://github.com/Alexander-Klp) +- [Александр Тогузов](https://github.com/Imuntouchable) + +### 🔨 Генерация кода: +- **Основной объект**: Центральный класс (`Pachca` - для 1 генератора, `Bot` - для 2 генератора), инициализируемый токеном авторизации. +- **Методы эндпоинтов**: Генерируются на основе параметра `operationId` из OpenAPI. +- **Модели данных**: Определены с использованием `pydantic` для входных и выходных схем. +- **Обработка ошибок**: Пользовательские исключения для API-ошибок на основе описания OpenAPI. + +--- + +# 🔧 ***generator1*** + +### 📚 Используемый стек и технологии: +- **Python 3.12+**. +- **httpx (0.28.1)**: Для выполнения асинхронных HTTP-запросов. +- **Jinja2 (3.1.4)**: Для генерации кода на основе шаблонов. +- **ruamel.yaml (0.18.6)**: Для работы с YAML файлами. +- **openapi-python-client (0.22.0)**: Для автоматической генерации клиентского кода на основе OpenAPI спецификаций. + +## 📂 Структура генератора + +### 🛠️ Основные компоненты +_src/generator1/_ + +#### 🛠️ pachca-api-open-api-3-0-client/client.py +- Автоматически генерируемый файл (появляется после запуска генерации) +- Представлена моделями и методами для работы с API на основе OpenAPI спецификации +- Содержит "динамическую" часть (основной класс для работы с API) после запуска скрипта-генератора +- Передает запрос пользователя в "статическую" часть, преобразовав из формата Python в JSON +- Передает ответ пользователю от "статической" части, преобразовав из формата JSON в Python + +#### 🧩 templates +- Директория хранения основных шаблонов для автоматической генерации + +#### 🔧 client_servis.py +- "Статическая" часть +- Отвечает за отправку/приемку данных на/от сервер(а) +- Полученные данные передает в "динамическую" часть (генерируемую) +- Содержит логику обеспечении безопасности и управления доступом + +#### 💾 script.py +- Создает "динамическую" часть в client.py +- Собирает главный и основной класс Pachca для работы с API + +#### 📜 requirements.txt +- Список зависимостей генератора с версиями + +#### 🧪 pachca.py +- Пример использования сгенерированного API клиента +- Тестовые вызовы различных методов API + +#### 📄 openapi.yaml: +- OpenAPI спецификации API +- Описание эндпоинтов, схем, параметров + +#### 📁 pachca-api-open-api-3-0-client/ +- директория автоматически сгенерированного кода + +#### 📄 config_pachca_api.py +- файл имен проекта и пакета + +#### 📄 config_pachca_api.py**** +- вспомогательный файл для запуска генератора и тестов + +--- + +## 🚀 Установка и использование + +### 🛠️ Инструкция (работать в папке `generator1` при активированном `venv`): +1. **Создайте файл `.env`** в директории `generator1`, с токеном для работы с API "Пачка". +Пример файла: + ``` + TOKEN=ваштокен + ``` +2. **Создайте и активируйте виртуальное окружение, установите зависимости**: + - Для Linux/gitBash: + ```bash + python3 -m venv venv + source venv/scripts/activate + pip install -r requirements.txt + ``` + - Для Windows cmd: + ```bash + python -m venv venv + .\venv\scripts\activate + pip install -r requirements.txt + ``` +3. **Запустите генерацию клиента:**: + ```bash + python generator.py generate + ``` +4. **Запустите пример запроса**: + ```bash + python generator.py test + ``` + +## 💡 Команда генератора + +- [Алексей Малков](https://github.com/shft1) +- [Владимир Кулаков](https://github.com/VladimirPulse) +- [Даниил Колчак](https://github.com/Daniil-Kolchak) +- [Данил Чирков](https://github.com/Dan1lChirkov) + +--- + +# 🔧 ***generator2*** + +### 📚 Используемый стек и технологии: +- **Python 3.12+** +- **httpx (0.28.1)**: Для выполнения асинхронных HTTP-запросов. +- **pydantic (2.10.4)**: Для определения моделей данных ввода и вывода. +- **ruamel.yaml (0.18.6)**: Для работы с YAML файлами. +- **openapi3-parser (1.1.19)**: Для парсинга OpenAPI спецификации. +- **ruff (0.7.1)**: Для автоматического исправления стилизации кода. +- **black (24.10.0)**: Для автоматического исправления стилизации кода. + +## 📂 Структура генератора + +### 🛠️ Основные компоненты + +_src/generator2/generator2_full_ + +#### 📁 models/ +- models_response_ # Модели ответов API +- models_reqBod_ # Модели запросов API + +#### 🧩 bot.py +- Основной класс для работы с API +- Метакласс RequestMethodsCollector для сбора методов +- Базовая функциональность для HTTP-запросов +- Форматирование URL и параметров запросов + +#### 🔧 constants.py +- Константы клиента +- Константы логгера + +#### 🧪 pachca.py +- Пример использования сгенерированного API клиента +- Тестовые вызовы различных методов API + +#### 📁 logger_setup.py +- Подготовка объекта логгера для логирования результатов работы + +#### 🌐 request_methods.py +- Содержит асинхронные методы для работы с API +- Импортирует сгенерированные Pydantic модели +- Реализует логику HTTP-запросов + +_src/generator2/services_ + +#### 🔧 constants.py +- Константы проекта +- Маппинги типов данных +- Пути к файлам +- HTTP методы + +#### 💾 file_writer.py +- Обеспечивает безопасную запись файлов +- Создает необходимые директории +- Управляет генерацией выходных файлов + +#### 📂 yaml_loader.py +- Загрузка YAML файла спецификации OpenAPI +- Создание глобального объекта YAML_DICT + +#### 📁 logger_setup.py +- Подготовка объекта логгера для логирования результатов работы + +_src/generator2/_ + +#### 📝 yaml_processor.py +- Обрабатывает YAML спецификацию +- Генерирует модели запросов и ответов + - Функция get_all_endpoints для извлечения эндпоинтов из YAML документации + - Функция process_endpoints для обработки эндпоинтов и генерации моделей для requestBody и response + +#### 🔗 generate_pydantic_model.py +- Создает модели pydantic для конкретного эндпоинта + - create_model для генерации текста модели pydantic + - create_enum для создания класса Enum + - look_into_schema_new для рекурсивного прохода по модели спецификации и создания всех необходимых моделей + - check_error_field для подмены тайпхинта в модели ошибок API + +#### 🔗 schema_link_processor.py +- Обрабатывает ссылки на схемы в YAML спецификации +- Генерирует модели для ссылок на схемы + - unite_schemas для объединения схем + - load_schema для загрузки схемы по ссылке + - new_replace_ref_with_schema для замены ссылок на схемы + +#### 📜 requirements.txt +- Список зависимостей генератора с версиями + +#### 🛠️ request_methods_generator.py +- Генерация методов для работы с API на основе OpenAPI спецификации +- Функции форматирования URL, параметров и обработки ответов + +#### 📄 openapi.yaml: +- OpenAPI спецификации API +- Описание эндпоинтов, схем, параметров + +#### 🛠️ generator_starter.py +- Основной запуск генерации необходимых файлов для клиента +- Форматтинг и правка сгенерированного кода в автоматическом режиме + +--- + +## 🚀 Установка и использование + +### 🛠️ Инструкция (работать в папке `src` при активированном `venv`): +1. **Создайте файл `.env`** в директории `generator2`, с токеном для работы с API "Пачка". +Пример файла .env.example: + ``` + TOKEN=ваштокен + ``` +2. **Создайте и активируйте виртуальное окружение, установите зависимости**: + - Для Linux/gitBash: + ```bash + python3 -m venv venv + source venv/scripts/activate + pip install -r requirements.txt + ``` + - Для Windows cmd: + ```bash + python -m venv venv + .\venv\scripts\activate + pip install -r requirements.txt + ``` +3. **Запустите генерацию клиента**: + ```bash + python -m generator2.generator_starter + ``` +4. **Запустите пример запроса**: + ```bash + python -m generator2.generator2_full.pachca + ``` + Или перейдите в `generator2` и запустите модуль: + ```bash + cd generator2 + python -m generator2_full.pachca + ``` + +## 💡 Команда генератора +- [Алексей Малков](https://github.com/shft1) +- [Дмитрий Костин](https://github.com/k0sdm1) +- [Дмитрий Бурмистров](https://github.com/bura09906) +- [Павел Колесников](https://github.com/Mrclive7406) + +--- + +# 🔧 ***builder*** + +## 🛠️ Инструменты для генерации библиотеки + +### 📋 Порядок действий (работать в папке `builder` при активированном `venv`): + +1. **Для запуска MakeFile командой make из VSCode нужно установить через PowerShell или cmd:** + + - winget install GnuWin32.Make. + +2. **Создание зависимостей для работы сборки библиотеки:** + + ```bash + pip install -r requirements_builder.txt + ``` + +3. **Создание зависимостей Pipfile и Pipfile.lock для библиотеки (в случае отсутствия или создания новых зависимостей):** + + ```bash + pipenv install requirements.txt + ``` + +4. **В папке проекта pachca_code_gen_team2 в файле .env указать:** + - PACKAGE_VERSION=<Версия пакета> + - TWINE_USERNAME=<Имя пользвателя сервиса TestPyPI> + - TWINE_API_TOKEN=<Токен пользвателя сервиса TestPyPI> + +5. **Запуск создания и загрузки библиотеки на сервис TestPyPI при помощи команды:** + + ```bash + make upload + ``` +6. **Установка библиотеки с сериса TestPyPI:** + + ```bash + pip install --no-cache-dir -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ PachcaAPI + ``` + +7. **Запуск генератора и тестов:** + + - run_generate_and_test - запуск генератора и тестов + - run_generator - запуск генератора + - run_test - запуск тестов + +## 📂 Структура генератора + +_src/builder/_ + +#### 📜 requirements_builder.txt +- файл со списком пакетов или библиотек, необходимых для работы упаковщика библиотеки. + +#### 📜 requirements.txt +- файл со списком пакетов или библиотек, необходимых для работы над библиотек. + +#### 📄 Pipfile +- файл, используемый виртуальной средой Pipenv для управления зависимостями библиотек. + +#### 🔒 Pipfile.lock +- файл в формате JSON хранит контрольные суммы пакетов, которые устанавливаются в проект, что даёт гарантию, что развёрнутые на разных машинах окружения будут идентичны друг другу. + +#### 🛠️ Makefile +- файл с инструкциями для утилиты make, которая нужна для автоматической сборки проекта. + +#### 📦 setup.py +- файл с описанием, каким именно образом будет упакован код для медотов генерации + +#### 🔗 MANIFEST.in +- файл с указанием, какие файлы следует включить в сборку пакета + +--- + +## 💡 Команда упаковки генераторов + +- [Алексей Малков](https://github.com/shft1) +- [Александр Малыгин](https://github.com/SanyM2007) +- [Александр Гора](https://github.com/MrAlexg82) + +--- + +**📄 Документация API**: [Pachca API Documentation](https://crm.pachca.com/dev/getting-started/requests-and-responses/) + +--- diff --git a/openapi.yaml b/openapi.yaml new file mode 100644 index 0000000..939326f --- /dev/null +++ b/openapi.yaml @@ -0,0 +1,2365 @@ +openapi: 3.0.3 +info: + title: PachcaAPI - OpenAPI 3.0 + description: Документация к открытому API пачки + version: 3.0.3 +servers: + - url: https://api.pachca.com/api/shared/v1 + +tags: + - name: common methods + description: Everything about common methods + - name: employees + description: Everything about employees + - name: status + description: Everything about + status + - name: tags + description: Everything about + tags + - name: chats and channels + description: Everything about + chats and channels + - name: talk and channel participants + description: Everything about + talk and channel participants + - name: comments + description: Everything about + comments + - name: messages + description: Everything about + messages + - name: reactions to messages + description: Everything about + reactions to messages + - name: reminders + description: Everything about + reminders + +paths: + /custom_properties: + get: + tags: + - common methods + summary: получение списка актульных полей сущности + description: | + Метод для получения актуального списка дополнительных полей участников и напоминаний в вашей компании. Тело запроса отсутствует, параметры передаются в URL (например, /custom_properties?entity_type=User) + operationId: getCommonMethods + parameters: + - name: entity_type + in: query + description: Тип сущности + required: true + schema: + type: string + responses: + '200': + description: Успешный запрос + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/CommonMethods' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Поле не может быть пустым + value: + errors: + - key: string + value: string + message: message + code: blank + payload: {} + inclusion: + description: Поле имеет непредусмотренное значение + value: + errors: + - key: string + value: string + message: message + code: inclusion + payload: {} + /uploads: + post: + tags: + - common methods + summary: получения подписи и ключа для загрузки файла + description: | + Данный метод необходимо использовать для загрузки каждого файла. + + Данный метод позволяет получить уникальный набор параметров для загрузки файла. Параметры запроса отсутствуют. + operationId: getUploads + responses: + '200': + description: Успешный ответ. + content: + application/json: + schema: + $ref: '#/components/schemas/FileResponse' + /direct_url: + post: + tags: + - common methods + summary: (полученный в ответе на запрос /uploads) загрузка файла + description: | + Данный метод не требует авторизации. + + Получив все параметры, вам необходимо сделать POST запрос в формате multipart/form-data на адрес, который был указан в поле direct_url, отправив полученные параметры и сам файл. + operationId: getDirectUrl + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/DirectResponse' + responses: + '201': + description: При безошибочном выполнении запроса тело ответа отсутствует. + /users: + get: + tags: + - employees + summary: получение актуального списка всех сотрудников компании + description: | + Метод для получения актуального списка сотрудников вашей компании. + Тело запроса отсутствует, параметры передаются в URL (например, /users?per=50&page=2&query=example.com) + operationId: getEmployees + parameters: + - name: per + in: query + description: Количество возвращаемых сущностей за один запрос (по умолчанию 50, максимум 50) + required: false + schema: + type: integer + default: 50 + maximum: 50 + - name: page + in: query + description: Страница выборки (по умолчанию 1) + required: false + schema: + type: integer + default: 1 + - name: query + in: query + description: | + Поисковая фраза для фильтрации результатов (поиск идет по полям first_name (имя), last_name (фамилия), email (электронная почта), phone_number (телефон) и nickname (никнейм)) + required: false + schema: + type: string + responses: + '200': + description: Успешный запрос + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Employee' + /users/{id}: + get: + tags: + - employees + summary: получение информации о сотруднике + description: | + Метод для получения информации о сотруднике. + Для получения сотрудника вам необходимо знать его id и указать его в URL запроса. + operationId: getEmployee + parameters: + - name: id + in: path + description: Уникальный идентификатор сотрудкика + required: true + schema: + type: integer + responses: + '200': + description: Успешный запрос + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Employee' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + not_found: + description: Поле не может быть пустым + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + /profile/status: + get: + tags: + - status + summary: получение информации о своем статусе + description: | + Метод для получения информации о своем статусе. Параметры запроса отсутствуют. + operationId: getStatus + responses: + '200': + description: Успешный запрос + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Status' + put: + tags: + - status + summary: новый статус + description: | + Метод для установки себе нового статуса. + operationId: putStatus + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + status: + $ref: '#/components/schemas/QueryStatus' + responses: + '200': + description: Объект создан + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Status' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Обязательное поле (не может быть пустым) + value: + errors: + - key: string + value: string + message: message + code: blank + payload: {} + too_long: + description: Слишком длинное значение (пояснения вы получите в поле message) + value: + errors: + - key: string + value: string + message: message + code: too_long + payload: {} + invalid: + description: Поле не соответствует правилам (пояснения вы получите в поле message) + value: + errors: + - key: string + value: string + message: message + code: invalid + payload: {} + wrong_emoji: + description: Emoji статуса не может содержать значения отличные от Emoji символа + value: + errors: + - key: string + value: string + message: message + code: wrong_emoji + payload: {} + delete: + tags: + - status + summary: удаление своего статуса + description: | + Метод для удаления своего статуса. Параметры запроса отсутствуют. + operationId: delStatus + responses: + '204': + description: При безошибочном выполнении запроса тело ответа отсутствует + content: {} + /group_tags/{id}: + get: + tags: + - tags + summary: получение информации о теге + description: | + Метод для получения информации о теге. Названия тегов являются уникальными в компании. + + Для получения тега вам необходимо знать его id и указать его в URL запроса. Параметры запроса отсутствуют + operationId: getTag + parameters: + - name: id + in: path + description: Уникальный идентификатор тега + required: true + schema: + type: integer + responses: + '200': + description: Успешный запрос + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Tag' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + /group_tags: + get: + tags: + - tags + summary: получение актуального списка тегов сотрудников + description: | + Метод для получения актуального списка тегов сотрудников. + + Названия тегов являются уникальными в компании. Тело запроса отсутствует, параметры передаются в URL (например, /group_tags?per=10&page=2) + operationId: getTags + parameters: + - name: per + in: query + description: Количество возвращаемых сущностей за один запрос (по умолчанию 50, максимум 50) + required: false + schema: + type: integer + default: 50 + maximum: 50 + - name: page + in: query + description: Страница выборки (по умолчанию 1) + required: false + schema: + type: integer + default: 1 + responses: + '200': + description: Успешный запрос + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Tag' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + exclusion: + description: Поле имеет непредусмотренное значение + value: + errors: + - key: string + value: string + message: message + code: exclusion + payload: {} + /group_tags/{id}/users: + get: + tags: + - tags + operationId: getTagsEmployees + summary: получение актуального списка сотрудников тега + description: | + Метод для получения актуального списка сотрудников тега. + + Идентификатор тега, список сотрудников которого необходимо получить, и другие параметры передаются в URL (например, /group_tags/877650/users?per=3&page=2) + parameters: + - name: id + in: path + description: Уникальный идентификатор сотрудкика + required: true + schema: + type: integer + - name: per + in: query + description: Количество возвращаемых сущностей за один запрос (по умолчанию 25, максимум 50) + required: false + schema: + type: integer + default: 25 + maximum: 50 + - name: page + in: query + description: Страница выборки (по умолчанию 1) + required: false + schema: + type: integer + default: 1 + responses: + '200': + description: Успешный запрос + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/BaseEmployee' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + exclusion: + description: Поле имеет непредусмотренное значение + value: + errors: + - key: string + value: string + message: message + code: exclusion + payload: {} + /chats: + post: + tags: + - chats and channels + operationId: createChat + summary: создание новой беседы или канала + description: | + Метод для создания новой беседы или нового канала. + При создании беседы или канала вы автоматически становитесь участником. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + chat: + $ref: '#/components/schemas/BaseChat' + responses: + '201': + description: Запрос отработал успешно, сущность создана + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Chat' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Обязательное поле (не может быть пустым) + value: + errors: + - key: name + value: '' + message: message + code: blank + payload: {} + too_long: + description: Слишком длинное значение (пояснения вы получите в поле message) + value: + errors: + - key: name + value: long_name + message: message + code: too_long + payload: {} + invalid: + description: Поле не соответствует правилам (пояснения вы получите в поле message) + value: + errors: + - key: name + value: 1234 + message: message + code: invalid + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + '422': + description: С запросом все хорошо, но правила сервиса не позволяют его обработать (например, при попытке создания контакта с уже существующим номером телефона в базе) + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + invalid: + description: Поле имеет неверный формат (идентификатор поля вы получите в поле payload) + value: + errors: + - key: name + value: name + message: message + code: invalid + payload: {} + get: + tags: + - chats and channels + operationId: getChats + summary: получение списка бесед и каналов + description: | + Метод для получения списка бесед и каналов по заданным параметрам. + + Тело запроса отсутствует, параметры передаются в URL (например, /chats?per=2&sort[id]=desc) + parameters: + - name: 'sort[id]' + in: query + required: false + description: | + Составной параметр сортировки сущностей выборки. + Варианты значений: по умолчанию desc (по убыванию) или asc (по возрастанию). + На данный момент сортировка доступна только по полю ({field}) id (идентификатор бесед и каналов). + schema: + type: string + enum: + - desc + - asc + default: desc + - name: per + in: query + description: Количество возвращаемых сущностей за один запрос (по умолчанию 25, максимум 50) + required: false + schema: + type: integer + default: 25 + maximum: 50 + - name: page + in: query + description: Страница выборки (по умолчанию 1) + required: false + schema: + type: integer + default: 1 + - name: availability + in: query + required: false + description: | + Параметр, который отвечает за доступность и выборку бесед и каналов для пользователя. + Варианты значений: по умолчанию is_member (беседы и каналы, где пользователь является участником) + или public (все открытые беседы и каналы компании, вне зависимости от участия в них пользователя). + schema: + type: string + enum: + - is_member + - public + default: is_member + - name: last_message_at_after + in: query + required: false + description: | + Фильтрация по времени создания последнего сообщения. + Будут возвращены те беседы/каналы, время последнего созданного сообщения в которых не раньше чем указанное (в формате YYYY-MM-DDThh:mm:ss.sssZ). + schema: + type: string + format: date-time + - name: last_message_at_before + in: query + required: false + description: | + Фильтрация по времени создания последнего сообщения. + Будут возвращены те беседы/каналы, время последнего созданного сообщения в которых не позже чем указанное (в формате YYYY-MM-DDThh:mm:ss.sssZ). + schema: + type: string + format: date-time + responses: + '200': + description: Запрос отработал как положено, без ошибок + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Chat' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + too_long: + description: Слишком длинное значение (пояснения вы получите в поле message) + value: + errors: + - key: name + value: long_name + message: message + code: too_long + payload: {} + invalid: + description: Поле не соответствует правилам (пояснения вы получите в поле message) + value: + errors: + - key: name + value: 1234 + message: message + code: invalid + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + '422': + description: С запросом все хорошо, но правила сервиса не позволяют его обработать (например, при попытке создания контакта с уже существующим номером телефона в базе) + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + invalid: + description: Поле имеет неверный формат (идентификатор поля вы получите в поле payload) + value: + errors: + - key: name + value: name + message: message + code: invalid + payload: {} + /chats/{id}: + get: + tags: + - chats and channels + operationId: getChat + summary: получение информации о беседе или канале + description: | + Получения информации о беседе или канале. + Для получения беседы или канала вам необходимо знать её id и указать его в URL запроса. + parameters: + - name: id + description: Идентификатор беседы или канала + in: path + required: true + schema: + type: integer + responses: + '200': + description: Успешный запрос + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Chat' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + /chats/{id}/members: + post: + tags: + - talk and channel participants + summary: добавление пользователей в состав участников + description: | + Метод для добавления пользователей в состав участников беседы или канала. + operationId: postMembersToChats + parameters: + - name: id + in: path + description: Идентификатор беседы/канала + required: true + schema: + type: integer + format: int64 + example: 533 + requestBody: + description: | + Идентификатор беседы/канала передаётся в URL (например, /chats/553/members) + Массив идентификаторов пользователей, которые станут участниками, передается в теле запроса + content: + application/json: + schema: + $ref: '#/components/schemas/MembersChat' + responses: + '204': + description: Пользователи добавлены + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Обязательное поле (не может быть пустым) + value: + errors: + - key: name + value: '' + message: message + code: blank + payload: {} + too_long: + description: Слишком длинное значение (пояснения вы получите в поле message) + value: + errors: + - key: name + value: long_name + message: message + code: too_long + payload: {} + invalid: + description: Поле не соответствует правилам (пояснения вы получите в поле message) + value: + errors: + - key: name + value: 1234 + message: message + code: invalid + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + '422': + description: С запросом все хорошо, но правила сервиса не позволяют его обработать (например, при попытке создания контакта с уже существующим номером телефона в базе) + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + invalid: + description: Поле имеет неверный формат (идентификатор поля вы получите в поле payload) + value: + errors: + - key: name + value: name + message: message + code: invalid + payload: {} + /chats/{id}/group_tags: + post: + tags: + - talk and channel participants + summary: добавление тегов в состав участников беседы или канала + description: | + Метод для добавления тегов в состав участников беседы или канала. + operationId: postTagsToChats + parameters: + - name: id + in: path + description: Идентификатор беседы/канала + required: true + schema: + type: integer + format: int64 + example: 533 + requestBody: + description: | + Идентификатор беседы/канала передаётся в URL (например, /chats/553/group_tags) + Массив идентификаторов тегов, которые станут участниками, передается в теле запроса + content: + application/json: + schema: + $ref: '#/components/schemas/GroupTag' + responses: + '204': + description: Тег(и) добавлен(ы) + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Обязательное поле (не может быть пустым) + value: + errors: + - key: name + value: '' + message: message + code: blank + payload: {} + too_long: + description: Слишком длинное значение (пояснения вы получите в поле message) + value: + errors: + - key: name + value: long_name + message: message + code: too_long + payload: {} + invalid: + description: Поле не соответствует правилам (пояснения вы получите в поле message) + value: + errors: + - key: name + value: 1234 + message: message + code: invalid + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + '422': + description: С запросом все хорошо, но правила сервиса не позволяют его обработать (например, при попытке создания контакта с уже существующим номером телефона в базе) + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + invalid: + description: Поле имеет неверный формат (идентификатор поля вы получите в поле payload) + value: + errors: + - key: name + value: name + message: message + code: invalid + payload: {} + /chats/{id}/leave: + delete: + tags: + - talk and channel participants + operationId: leaveChat + summary: выход из беседы или канала + description: |- + Метод для самостоятельного выхода из беседы или канала. Параметры запроса отсутствуют/ + parameters: + - name: id + in: path + required: true + description: Уникальный идентификатор беседы или канала. + schema: + type: integer + responses: + '204': + description: При безошибочном выполнении запроса тело ответа отсутствуе + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + personal_chat: + description: Нельзя покинуть персональный чат + value: + errors: + - key: string + value: string + message: message + code: personal_chat + payload: {} + /messages/{id}/thread: + post: + tags: + - comments + summary: создание нового треда + description: | + Метод для создания нового треда к сообщению. Если у сообщения уже был создан тред, то в ответе вернётся информация об уже созданном ранее треде. + operationId: createThread + parameters: + - name: id + in: path + required: true + description: Уникальный идентификатор сообщения, к которому создается тред. + schema: + type: integer + responses: + '201': + description: Тред успешно создан или возвращены данные существующего треда. + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Thread' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Поле не может быть пустым + value: + errors: + - key: name + value: '' + message: message + code: blank + payload: {} + exclusion: + description: Поле имеет недопустимое значение + value: + errors: + - key: name + value: 1234 + message: message + code: exclusion + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + /messages: + post: + tags: + - messages + summary: создание нового сообщения + description: | + Метод для отправки сообщения в беседу или канал, + личного сообщения пользователю или комментария в тред. + + При использовании entity_type: "discussion" (или просто без указания entity_type) + допускается отправка любого chat_id в поле entity_id. + То есть, сообщение можно отправить зная только идентификатор чата. + При этом, вы имеете возможность отправить сообщение в тред по его идентификатору + или личное сообщение по идентификатору пользователя. + + Для отправки личного сообщения пользователю создавать чат не требуется. + Достаточно указать entity_type: "user" и идентификатор пользователя. + Чат будет создан автоматически, если между вами ещё не было переписки. + Между двумя пользователями может быть только один личный чат. + operationId: createMessage + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + message: + $ref: '#/components/schemas/CreateMessage' + responses: + '201': + description: Successful + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Message' + example: + data: + id: 194275 + entity_type: discussion + entity_id: 198 + chat_id: 198 + content: Вчера мы продали 756 футболок (что на 10% больше, чем в прошлое воскресенье) + user_id: 12 + created_at: 2020-06-08T09:32:57.000Z + files: [] + buttons: [] + thread: null + forwarding: null + parent_message_id: null + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Поле не может быть пустым + value: + errors: + - key: name + value: '' + message: message + code: blank + payload: {} + exclusion: + description: Поле имеет недопустимое значение + value: + errors: + - key: name + value: 1234 + message: message + code: exclusion + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + get: + tags: + - messages + summary: получение списка сообщений чата + description: | + Метод для получения списка сообщений бесед, каналов, тредов и личных сообщений. + + Для получения сообщений вам необходимо знать chat_id требуемой беседы, канала, + треда или диалога, и указать его в URL запроса. Сообщения будут возвращены + в порядке убывания даты отправки (то есть, сначала будут идти последние сообщения чата). + Для получения более ранних сообщений чата доступны параметры per и page. + Тело запроса отсутствует, параметры передаются в URL (например, /messages?chat_id=198&per=3) + operationId: getListMessage + parameters: + - name: chat_id + in: query + description: Идентификатор чата (беседа, канал, диалог или чат треда) + required: true + schema: + title: chat_id + type: integer + - name: per + in: query + description: Количество возвращаемых сущностей за один запрос (по умолчанию 25, максимум 50) + required: false + schema: + type: integer + default: 25 + maximum: 50 + - name: page + in: query + description: Страница выборки (по умолчанию 1) + required: false + schema: + type: integer + default: 1 + responses: + '200': + description: Successful + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Message' + example: + data: + - id: 1194277 + entity_type: discussion + entity_id: 198 + chat_id: 198 + content: Это сообщение тоже попадёт в экспорт + user_id: 12 + created_at: 2023-09-18T13:43:32.000Z + files: [] + buttons: [] + thread: + id: 2633 + chat_id: 44997 + forwarding: null + parent_message_id: null + - id: 1194276 + entity_type: discussion + entity_id: 198 + chat_id: 198 + content: "**Andrew** добавил **Export bot** в беседу" + user_id: 12 + created_at: 2023-09-18T13:43:27.000Z + files: [] + buttons: [] + thread: null + forwarding: null + parent_message_id: null + - id: 1194275 + entity_type: discussion + entity_id: 198 + chat_id: 198 + content: "**Andrew** создал беседу" + user_id: 12 + created_at: 2023-09-18T13:43:19.000Z + files: [] + buttons: [] + thread: null + forwarding: null + parent_message_id: null + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Поле не может быть пустым + value: + errors: + - key: name + value: '' + message: message + code: blank + payload: {} + exclusion: + description: Поле имеет недопустимое значение + value: + errors: + - key: name + value: 1234 + message: message + code: exclusion + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + /messages/{id}: + get: + tags: + - messages + summary: получение информации о сообщении + description: | + Метод для получения информации о сообщении. + + Для получения сообщения вам необходимо знать его id и указать его в URL запроса. + operationId: getMessage + parameters: + - name: id + in: path + required: true + schema: + title: id + type: integer + responses: + '200': + description: Successfull + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Message' + example: + data: + id: 194275 + entity_type: discussion + entity_id: 198 + chat_id: 198 + content: Вчера мы продали 756 футболок (что на 10% больше, чем в прошлое воскресенье) + user_id: 12 + created_at: 2020-06-08T09:32:57.000Z + files: + - id: 3560 + key: attaches/files/12/21zu7934-02e1-44d9-8df2-0f970c259796/congrat.png + name: congrat.png + file_type: file + url: | + https://pachca-prod-uploads.s3.storage.selcloud.ru/attaches/files/12/21zu7934- + 02e1-44d9-8df2-0f970c259796/congrat.png?response-cache-control=max- + age%3D3600%3B&response-content-disposition=attachment&X-Amz-Algorithm=AWS4-HMAC + -SHA256&X-Amz-Credential=142155_staply%2F20231107%2Fru-1a%2Fs3%2Faws4_ + request&X-Amz-Date=20231107T160412Z&X-Amz-Expires=604800&X-Amz-SignedHeaders= + host&X-Amz-Signature=98765asgfadsfdsaDSd4sdfg35asdf67sadf8 + buttons: [] + thread: + id: 29873 + chat_id: 1949863 + forwarding: null + parent_message_id: 194274 + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + put: + tags: + - messages + operationId: editMessage + summary: редактирование сообщения по указанному идентификатору + description: Метод для редактирования сообщения или комментария. + parameters: + - name: id + in: path + required: true + description: Уникальный идентификатор беседы или канала. + schema: + type: integer + requestBody: + description: Массив идентификаторов тегов, которые станут участниками + content: + application/json: + schema: + type: object + properties: + message: + $ref: '#/components/schemas/EditMessages' + responses: + '200': + description: Успешно отредактировано + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Message' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Поле не может быть пустым + value: + errors: + - key: name + value: '' + message: message + code: blank + payload: {} + exclusion: + description: Поле имеет недопустимое значение + value: + errors: + - key: name + value: 1234 + message: message + code: exclusion + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + /messages/{id}/reactions: + post: + tags: + - reactions to messages + operationId: postMessageReactions + summary: добавление реакции + description: > + Метод для добавления реакции на сообщение. + **Лимиты реакций:** + - Каждый пользователь может установить не более 20 уникальных реакций на сообщение. + - Сообщение может иметь не более 30 уникальных реакций. + - Сообщение может иметь не более 1000 реакций. + parameters: + - name: id + in: path + required: true + description: Уникальный идентификатор сообщения. + schema: + type: integer + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CodeReaction' + responses: + "201": + description: Успешное выполнение запроса, тело ответа отсутствует. + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Поле не может быть пустым + value: + errors: + - key: name + value: '' + message: message + code: blank + payload: {} + exclusion: + description: Поле имеет недопустимое значение + value: + errors: + - key: name + value: 1234 + message: message + code: exclusion + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + user_limit: + description: Превышен лимит уникальных реакций пользователя + value: + errors: + - key: string + value: string + message: Вы можете добавить не более 20 уникальных реакций. + code: user_limit + payload: {} + unique_limit: + description: Превышен лимит уникальных реакций на сообщение + value: + errors: + - key: string + value: string + message: Сообщение может содержать не более 30 уникальных реакций. + code: unique_limit + payload: {} + general_limit: + description: Превышен общий лимит реакций на сообщение + value: + errors: + - key: string + value: string + message: Сообщение может содержать не более 1000 реакций. + code: general_limit + payload: {} + delete: + tags: + - reactions to messages + operationId: deleteMessageReactions + summary: удаление реакции + description: > + Метод для удаления реакции на сообщение. + Удалить можно только те реакции, которые были поставлены авторизованным пользователем. + parameters: + - name: id + in: path + required: true + description: Уникальный идентификатор сообщения. + schema: + type: integer + - name: code + in: query + description: Emoji в строковом формате для добавления реакции. + schema: + type: string + example: "👍" + responses: + "204": + description: При безошибочном выполнении запроса тело ответа отсутствует + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Поле не может быть пустым + value: + errors: + - key: name + value: '' + message: message + code: blank + payload: {} + exclusion: + description: Поле имеет недопустимое значение + value: + errors: + - key: name + value: 1234 + message: message + code: exclusion + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + get: + tags: + - reactions to messages + operationId: getMessageReactions + summary: получение актуального списка реакций + description: | + Метод для получения актуального списка реакций на сообщение. + + Идентификатор сообщения, список реакций на которое необходимо получить, передается в URL (например, /messages/7231942/reactions). Количество возвращаемых сущностей и страница выборки указываются в теле запроса + parameters: + - name: id + in: path + description: Уникальный идентификатор сообщения + required: true + schema: + type: integer + - name: per + in: query + description: Количество возвращаемых сущностей за один запрос (по умолчанию 50, максимум 50) + required: false + schema: + type: integer + default: 50 + maximum: 50 + - name: page + in: query + description: Страница выборки (по умолчанию 1) + required: false + schema: + type: integer + default: 1 + responses: + '200': + description: Список реакций успешно получен. + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Reaction' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + exclusion: + description: Поле имеет недопустимое значение + value: + errors: + - key: name + value: 1234 + message: message + code: exclusion + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + /tasks: + post: + tags: + - reminders + operationId: createTask + summary: создание нового напоминания + description: | + Метод для создания нового напоминания. + + При создании напоминания обязательным условием является указания типа напоминания: звонок, встреча, простое напоминание, событие или письмо. + При этом не требуется дополнительное описание - вы просто создадите напоминание с соответствующим текстом. + Если вы укажите описание напоминания - то именно оно и станет текстом напоминания. + У напоминания должны быть ответственные, если их не указывать - ответственным назначаетесь вы. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + task: + type: object + required: + - kind + - content + - due_at + properties: + kind: + type: string + description: Тип напоминания (call, meeting, reminder, event, email) + content: + type: string + description: Описание напоминания + due_at: + type: string + format: date-time + description: Срок выполнения напоминания (ISO-8601) + priority: + type: integer + description: Приоритет (1 - по умолчанию, 2 - важно, 3 - очень важно) + performer_ids: + type: array + items: + type: integer + description: Массив идентификаторов пользователей + custom_properties: + type: array + items: + type: object + properties: + id: + type: integer + description: Идентификатор поля + value: + type: string + description: Значение поля + responses: + '201': + description: Напоминание успешно создано + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Task' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Поле не может быть пустым + value: + errors: + - key: string + value: string + message: message + code: blank + payload: {} + too_long: + description: Слишком длинное значение (пояснения вы получите в поле message) + value: + errors: + - key: name + value: long_name + message: message + code: too_long + payload: {} + inclusion: + description: Поле имеет непредусмотренное значение + value: + errors: + - key: string + value: string + message: message + code: inclusion + payload: {} + invalid: + description: Поле имеет неверное значение (например, указаны недопустимые ответственные) + value: + errors: + - key: name + value: 1234 + message: message + code: invalid + payload: {} +components: + schemas: + MembersChat: + title: Members Chat + required: + - member_ids + type: object + properties: + member_ids: + type: array + description: Массив идентификаторов пользователей, которые станут участниками + minItems: 1 + items: + type: integer + format: int64 + example: [186, 187] + silent: + type: boolean + description: Не создавать в чате системное сообщение о добавлении участника + GroupTag: + title: Group Tag + required: + - group_tag_ids + type: object + properties: + group_tag_ids: + type: array + minItems: 1 + items: + type: integer + format: int64 + example: [86, 18] + description: Массив идентификаторов тегов, которые станут участниками + CodeReaction: + title: Code Reaction + type: object + properties: + code: + type: string + example: "👍" + description: Emoji в строковом формате для добавления реакции. + required: + - code + BaseEmployee: + title: Base Employee + type: object + properties: + id: + type: integer + example: 1 + description: Идентификатор пользователя + first_name: + type: string + description: Имя + last_name: + type: string + description: Фамилия + nickname: + type: string + description: Имя пользователя + email: + type: string + description: Электронная почта + phone_number: + type: string + description: Телефон + department: + type: string + description: Департамент + role: + type: string + enum: + - admin + - user + - multi_guest + description: | + Уровень доступа: admin (администратор), user (сотрудник), multi_guest (мульти-гость) + suspended: + type: boolean + description: | + Деактивация пользователя. При значении true пользователь является деактивированным. + invite_status: + type: string + enum: + - confirmed + - sent + description: | + Статус приглашения: confirmed (принято), sent (отправлено) + list_tags: + type: array + items: + type: string + description: Массив тегов, привязанных к сотруднику + custom_properties: + type: array + description: Дополнительные поля сотрудника + items: + type: object + properties: + id: + type: integer + description: Идентификатор поля + name: + type: string + description: Название поля + data_type: + type: string + enum: + - string + - number + - date + - link + description: Тип поля (string, number, date или link) + value: + type: string + description: Значение + bot: + type: boolean + description: | + Тип: пользователь (false) или бот (true) + description: Базовый класс сотрудника. + Employee: + allOf: + - $ref: '#/components/schemas/BaseEmployee' + - type: object + properties: + user_status: + $ref: '#/components/schemas/Status' + title: + type: string + description: Должность + created_at: + type: string + format: date-time + description: | + Дата создания (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ + time_zone: + type: string + description: Часовой пояс пользователя + image_url: + type: string + nullable: true + description: Ссылка на скачивание аватарки + description: Расширенный класс сотрудника. + BaseResponse: + title: Base Response + type: object + properties: + Content-Disposition: + type: string + description: Используемый заголовок + default: attachment + acl: + type: string + description: Уровень безопасности + default: private + policy: + type: string + description: Уникальный policy для загрузки файла + x-amz-credential: + type: string + description: x-amz-credential для загрузки файла + x-amz-algorithm: + type: string + description: Используемый алгоритм + default: AWS4-HMAC-SHA256 + x-amz-date: + type: string + description: Уникальный x-amz-date для загрузки файла + x-amz-signature: + type: string + description: Уникальная подпись для загрузки файла + key: + type: string + description: Уникальный ключ для загрузки файла + FileResponse: + title: File Response + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object + properties: + direct_url: + type: string + description: Адрес для загрузки файла + DirectResponse: + title: Direct Response + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object + properties: + file: + type: string + description: Адрес для загрузки файла + Status: + type: object + nullable: true + description: Статус. Возвращается как null, если статус не установлен. + properties: + emoji: + type: string + description: Emoji символ статуса + title: + type: string + description: Текст статуса + expires_at: + type: string + format: date-time + nullable: true + description: | + Срок жизни статуса (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ. Возвращается как null, если срок не установлен. + QueryStatus: + type: object + properties: + status: + type: object + description: Собранный объект параметров нового статуса + required: + - emoji + - title + properties: + emoji: + type: string + description: Emoji символ статуса + title: + type: string + description: Текст статуса + expires_at: + type: string + format: date-time + description: Срок действия статуса (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ + nullable: true + CommonMethods: + title: Common Methods + type: object + description: получение списка актульных полей сущности. + properties: + id: + type: integer + example: 1 + description: Название поля + name: + type: string + example: Дата рождения + description: Идентификатор поля + data_type: + type: string + enum: + - string + - number + - date + - link + example: number + description: тип поля + Errors: + type: object + properties: + errors: + type: array + items: + key: + title: key + type: string + description: Ключ параметра, в котором произошла ошибка + value: + title: value + type: string + description: Значение ключа, которое вызвало ошибку + message: + title: message + type: string + description: Ошибка текстом, который вы можете вывести пользователю + code: + title: code + type: string + description: Внутренний код ошибки (коды ошибок представлены в описании каждого метода) + payload: + title: payload + type: object + description: Объект, который предоставляет любую дополнительную информацию (возможные дополнения представлены в описании каждого метода) + Buttons: + title: Message Buttons + type: array + maxItems: 100 + items: + title: Row Buttons + type: array + maxItems: 8 + items: + type: object + title: Button + required: + - text + minProperties: 2 + properties: + text: + title: Text + type: string + maxLength: 255 + url: + title: Url + type: string + data: + title: Data + type: string + maxLength: 255 + BaseThread: + title: Base Thread + type: object + properties: + id: + type: integer + description: Идентификатор поля + chat_id: + type: integer + description: Идентификатор поля чата + Thread: + allOf: + - $ref: '#/components/schemas/BaseThread' + - type: object + properties: + message_id: + type: integer + description: Идентификатор сообщения, к которому был создан тред. + message_chat_id: + type: integer + description: Идентификатор чата сообщения. + updated_at: + type: string + format: date-time + description: | + Дата и время обновления треда (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ. + BaseFiles: + title: Base Files + type: object + required: + - key + - name + - file_type + properties: + key: + type: string + description: Путь к файлу, полученный в результате загрузки файла (каждый файл в каждом сообщении должен иметь свой уникальный key, не допускается использование одного и того же key в разных сообщениях) + name: + type: string + description: Название файла, которое вы хотите отображать пользователю (рекомендуется писать вместе с расширением) + file_type: + type: string + enum: + - file + - image + CreateEditFiles: + type: array + items: + allOf: + - $ref: '#/components/schemas/BaseFiles' + - type: object + title: Create&Edit Files + required: + - size + properties: + size: + type: integer + description: Размер файла в байтах, отображаемый пользователю + Files: + type: array + items: + allOf: + - $ref: '#/components/schemas/BaseFiles' + - type: object + title: Files + properties: + id: + type: integer + description: Идентификатор поля + url: + type: string + description: Прямая временная ссылка на скачивание файла + BeforeBaseMessages: + title: Before Base Messages + type: object + description: Для получения сообщения вам необходимо знать его id и указать его в URL запроса. + required: + - content + properties: + content: + type: string + description: Текст сообщения + default: Текст сообщения + buttons: + allOf: + - $ref: '#/components/schemas/Buttons' + title: buttons + EditMessages: + title: Edit Messages + allOf: + - $ref: '#/components/schemas/BeforeBaseMessages' + - type: object + description: Для получения сообщения вам необходимо знать его id и указать его в URL запроса. + required: + - content + properties: + files: + allOf: + - $ref: '#/components/schemas/CreateEditFiles' + title: files + BaseMessages: + title: Base Messages + allOf: + - $ref: '#/components/schemas/BeforeBaseMessages' + - type: object + required: + - entity_id + properties: + entity_type: + title: Entity Type + type: string + enum: + - discussion + - user + - thread + default: discussion + entity_id: + title: Entity Id + type: integer + parent_message_id: + title: Parent Massage Id + type: integer + nullable: true + default: null + description: Идентификатор сообщения, к которому написан ответ. Возвращается как null, если сообщение не является ответом. + CreateMessage: + title: Create Messages + allOf: + - $ref: '#/components/schemas/BaseMessages' + - type: object + properties: + files: + allOf: + - $ref: '#/components/schemas/CreateEditFiles' + title: files + skip_invite_mentions: + title: Skip Invite Mentions + type: boolean + default: false + link_preview: + title: Link Preview + type: boolean + default: false + Message: + type: object + properties: + entity_type: + title: Entity Type + type: string + enum: + - 'discussion' + - 'user' + - 'thread' + default: 'discussion' + entity_id: + title: Entity Id + type: integer + content: + title: Content + type: string + id: + title: Id + type: integer + chat_id: + title: Chat Id + type: integer + user_id: + title: User Id + type: integer + created_at: + title: Created At + type: string + format: date-time + files: + title: Files + type: array + items: + type: object + properties: + id: + title: Id + type: integer + key: + title: Key + type: string + description: Путь к файлу, полученный в результате загрузки файла (каждый файл в каждом сообщении должен иметь свой уникальный key, не допускается использование одного и того же key в разных сообщениях) + name: + title: Name + type: string + description: Название файла, которое вы хотите отображать пользователю (рекомендуется писать вместе с расширением) + file_type: + title: File Type + type: string + enum: + - 'file' + - 'image' + url: + title: Url + type: string + description: Размер файла в байтах, отображаемый пользователю + Reaction: + type: object + properties: + user_id: + type: integer + description: | + Идентификатор пользователя, оставившего реакцию. + created_at: + type: string + format: date-time + description: | + Дата и время добавления реакции (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ. + code: + type: string + description: | + Emoji символ реакции. + BaseChat: + title: Base Chat + type: object + description: Собранный объект параметров создаваемой беседы или канала + required: + - name + properties: + name: + type: string + description: Название + example: 🤿 aqua + member_ids: + type: array + description: Массив идентификаторов пользователей, которые станут участниками + items: + type: integer + example: + - 186 + - 187 + group_tag_ids: + type: array + description: Массив идентификаторов тегов, участников + items: + type: integer + example: [] + channel: + type: boolean + description: 'Тип: беседа (по умолчанию, false) или канал (true)' + example: true + public: + type: boolean + description: 'Доступ: закрытый (по умолчанию, false) или открытый (true)' + example: false + Chat: + allOf: + - type: object + properties: + id: + type: integer + description: Идентификатор беседы или канала + example: 334 + owner_id: + type: integer + description: Идентификатор пользователя, создавшего беседу или канал + example: 185 + created_at: + type: string + format: date-time + description: Дата и время создания беседы или канала (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ + example: '2021-08-28T15:56:53.000Z' + last_message_at: + type: string + format: date-time + description: Дата и время создания последнего сообщения в беседе/канале (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ + example: '2021-08-28T15:58:13.000Z' + meet_room_url: + type: string + description: Ссылка на Видеочат + example: 'https://meet.pachca.com/aqua-94bb21b5' + - $ref: '#/components/schemas/BaseChat' + Tag: + type: object + description: Для получения тега вам необходимо знать его id и указать его в URL запроса. + properties: + id: + type: integer + description: Идентификатор тега + name: + type: string + description: Название тега + users_count: + description: Количество сотрудников, которые имеют этот тег + type: integer + BaseCustomProperties: + title: Base Custom Properties + description: Задаваемые дополнительные поля + type: object + properties: + id: + type: integer + description: Идентификатор поля + value: + type: string + description: Значение поля + CustomProperties: + type: array + items: + allOf: + - $ref: '#/components/schemas/BaseCustomProperties' + - type: object + title: Custom Properties + properties: + name: + type: string + description: Название поля + data_type: + type: string + enum: + - string + - number + - date + - link + description: Тип поля (string, number, date или link) + BaseTask: + title: Base Task + type: object + required: + - kind + - content + - due_at + properties: + kind: + type: string + description: Тип напоминания + enum: + - call + - meeting + - reminder + - event + - email + content: + type: string + description: Описание напоминания + due_at: + type: string + format: date-time + description: Срок выполнения напоминания (ISO-8601) + priority: + type: integer + description: Приоритет (1 - по умолчанию, 2 - важно, 3 - очень важно) + enum: + - 1 + - 2 + - 3 + performer_ids: + type: array + items: + type: integer + description: Массив идентификаторов пользователей + custom_properties: + type: array + items: + allOf: + - $ref: '#/components/schemas/BaseCustomProperties' + Task: + allOf: + - $ref: '#/components/schemas/BaseTask' + - type: object + properties: + id: + type: integer + description: Идентификатор созданного напоминания + user_id: + type: integer + description: Идентификатор пользователя-создателя + status: + type: string + description: Статус напоминания + created_at: + type: string + format: date-time + description: Дата и время создания + custom_properties: + allOf: + - $ref: '#/components/schemas/CustomProperties' + securitySchemes: + bearerAuth: + type: http + scheme: bearer +security: + - bearerAuth: [] diff --git a/requirements_style.txt b/requirements_style.txt new file mode 100644 index 0000000..92b963a --- /dev/null +++ b/requirements_style.txt @@ -0,0 +1,2 @@ +ruff==0.7.1 +pre-commit==3.8.0 diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..3a5ba86 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,43 @@ +indent-width = 4 +line-length = 79 +required-version = ">=0.6.1" +target-version = "py311" +# Enable preview features. +preview = true + +[format] +# Enable reformatting of code snippets in docstrings. +docstring-code-format = true +# Format all docstring code snippets with a line length of 60. +docstring-code-line-length = 60 +# Use `\n` line endings for all files +line-ending = "lf" +# Prefer single quotes over double quotes. +quote-style = "single" + +[lint] +ignore=["E265", "F811", "D100", "D105", "D104", "D203", "D211", "D213", "N818", "W505", "ANN401"] +select=["E3", "E5", "E4", "E7", "E9", "W", "C90", "I", "N", "D", "F", "ANN", "ASYNC", "A", "COM", "RET", ] + +[lint.extend-per-file-ignores] +# Also ignore in all files. +"__init__.py" = ["E402", "E401"] + +[lint.flake8-annotations] +allow-star-arg-any = true + +[lint.flake8-quotes] +inline-quotes = "single" + +[lint.isort] +case-sensitive = true +known-local-folder = ["src"] + +[lint.mccabe] +# Flag errors (`C901`) whenever the complexity level exceeds 5. +max-complexity = 10 + +[lint.pycodestyle] +max-doc-length = 79 +# E501 reports lines that exceed the length of 79. +max-line-length = 79 diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000..060543c Binary files /dev/null and b/src/.DS_Store differ diff --git a/src/builder/MANIFEST.in b/src/builder/MANIFEST.in new file mode 100644 index 0000000..9d1fa63 --- /dev/null +++ b/src/builder/MANIFEST.in @@ -0,0 +1 @@ +recursive-include ../generator1/templates * \ No newline at end of file diff --git a/src/builder/Makefile b/src/builder/Makefile new file mode 100644 index 0000000..c813423 --- /dev/null +++ b/src/builder/Makefile @@ -0,0 +1,26 @@ +ifneq (,$(wildcard ../../.env)) + include ../../.env + export +endif + +.PHONY: help +help: + @echo "Команды:" + @echo " help - Список всех команд" + @echo " build - Создание библиотеки" + @echo " clean - Очистка временных файлов" + @echo " upload - Загрузка библиотеки на testPyPi" + +build: clean + + python setup.py bdist_wheel --dist-dir=../repository/ + +clean: + @rm -rf .pytest_cache/ .mypy_cache/ junit/ build/ ../repository/ ../*.egg-info/ + +.PHONY: upload +upload: build + @echo "library пакет" + @echo $$TWINE_USERNAME + @echo $$TWINE_API_TOKEN + TWINE_USERNAME=$$TWINE_USERNAME TWINE_PASSWORD=$$TWINE_API_TOKEN twine upload --repository testpypi ../repository/* --verbose \ No newline at end of file diff --git a/src/builder/Pipfile b/src/builder/Pipfile new file mode 100644 index 0000000..82faf0d --- /dev/null +++ b/src/builder/Pipfile @@ -0,0 +1,27 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +anyio = "==4.7.0" +attrs = "==24.3.0" +httpcore = "==1.0.7" +httpx = "==0.27.2" +jinja2 = "==3.1.4" +openapi-python-client = "==0.22.0" +pyyaml = "==6.0.2" +"ruamel.yaml" = "==0.18.6" +"ruamel.yaml.clib" = "==0.2.12" +typing-extensions = "==4.12.2" +black = "==24.10.0" +isort = "==5.13.2" +pydantic = "==2.10.4" +pydantic-core = "==2.27.2" +requests = "==2.32.3" +python-dotenv = "==1.0.1" + +[dev-packages] + +[requires] +python_version = "3.11" diff --git a/src/builder/Pipfile.lock b/src/builder/Pipfile.lock new file mode 100644 index 0000000..3e652d9 --- /dev/null +++ b/src/builder/Pipfile.lock @@ -0,0 +1,725 @@ +{ + "_meta": { + "hash": { + "sha256": "d74747f4b36f5701f892b052e2e03554226c99eac8761289805a434f76846737" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.11" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "annotated-types": { + "hashes": [ + "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", + "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89" + ], + "markers": "python_version >= '3.8'", + "version": "==0.7.0" + }, + "anyio": { + "hashes": [ + "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48", + "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==4.7.0" + }, + "attrs": { + "hashes": [ + "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff", + "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==24.3.0" + }, + "black": { + "hashes": [ + "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f", + "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd", + "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea", + "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981", + "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b", + "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7", + "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8", + "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175", + "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d", + "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392", + "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad", + "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f", + "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f", + "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b", + "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875", + "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3", + "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800", + "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65", + "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2", + "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812", + "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50", + "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==24.10.0" + }, + "certifi": { + "hashes": [ + "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", + "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe" + ], + "markers": "python_version >= '3.6'", + "version": "==2025.1.31" + }, + "charset-normalizer": { + "hashes": [ + "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537", + "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa", + "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a", + "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294", + "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b", + "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", + "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", + "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", + "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4", + "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", + "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2", + "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", + "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", + "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", + "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", + "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", + "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", + "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496", + "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", + "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", + "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e", + "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a", + "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4", + "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca", + "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78", + "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", + "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5", + "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", + "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", + "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", + "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765", + "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6", + "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", + "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", + "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", + "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd", + "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c", + "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", + "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", + "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", + "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770", + "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824", + "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f", + "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf", + "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487", + "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d", + "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd", + "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", + "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534", + "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", + "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", + "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", + "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd", + "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", + "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9", + "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", + "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", + "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d", + "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", + "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", + "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", + "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7", + "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", + "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", + "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8", + "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41", + "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", + "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", + "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", + "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", + "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", + "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", + "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", + "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", + "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", + "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", + "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", + "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e", + "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6", + "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", + "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", + "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e", + "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", + "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", + "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c", + "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", + "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", + "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089", + "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", + "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e", + "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", + "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616" + ], + "markers": "python_version >= '3.7'", + "version": "==3.4.1" + }, + "click": { + "hashes": [ + "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", + "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a" + ], + "markers": "python_version >= '3.7'", + "version": "==8.1.8" + }, + "colorama": { + "hashes": [ + "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", + "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", + "version": "==0.4.6" + }, + "h11": { + "hashes": [ + "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", + "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761" + ], + "markers": "python_version >= '3.7'", + "version": "==0.14.0" + }, + "httpcore": { + "hashes": [ + "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", + "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==1.0.7" + }, + "httpx": { + "hashes": [ + "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", + "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==0.27.2" + }, + "idna": { + "hashes": [ + "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", + "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" + ], + "markers": "python_version >= '3.6'", + "version": "==3.10" + }, + "isort": { + "hashes": [ + "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", + "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6" + ], + "index": "pypi", + "markers": "python_full_version >= '3.8.0'", + "version": "==5.13.2" + }, + "jinja2": { + "hashes": [ + "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", + "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==3.1.4" + }, + "markdown-it-py": { + "hashes": [ + "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", + "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb" + ], + "markers": "python_version >= '3.8'", + "version": "==3.0.0" + }, + "markupsafe": { + "hashes": [ + "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", + "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", + "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", + "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", + "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", + "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", + "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", + "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", + "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", + "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", + "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", + "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", + "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", + "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", + "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", + "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", + "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", + "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", + "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", + "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", + "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", + "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", + "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", + "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", + "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", + "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", + "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", + "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", + "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", + "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", + "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", + "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", + "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", + "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", + "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", + "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", + "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", + "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", + "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", + "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", + "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", + "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", + "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", + "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", + "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", + "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", + "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", + "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", + "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", + "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", + "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", + "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", + "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", + "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", + "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", + "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", + "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", + "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", + "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", + "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", + "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50" + ], + "markers": "python_version >= '3.9'", + "version": "==3.0.2" + }, + "mdurl": { + "hashes": [ + "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", + "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" + ], + "markers": "python_version >= '3.7'", + "version": "==0.1.2" + }, + "mypy-extensions": { + "hashes": [ + "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", + "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.0" + }, + "openapi-python-client": { + "hashes": [ + "sha256:b0ef2398c6b7f754f5a177ed9db77dfc244562cd1c62e6abb4cb10571e417f76", + "sha256:f2eb66b207bfcb8ef195ebe90bef90a8c0ec40a49ee7ad5cc71c0c45ea736ead" + ], + "index": "pypi", + "markers": "python_version >= '3.9' and python_version < '4.0'", + "version": "==0.22.0" + }, + "packaging": { + "hashes": [ + "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", + "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f" + ], + "markers": "python_version >= '3.8'", + "version": "==24.2" + }, + "pathspec": { + "hashes": [ + "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", + "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712" + ], + "markers": "python_version >= '3.8'", + "version": "==0.12.1" + }, + "platformdirs": { + "hashes": [ + "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", + "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb" + ], + "markers": "python_version >= '3.8'", + "version": "==4.3.6" + }, + "pydantic": { + "hashes": [ + "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d", + "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==2.10.4" + }, + "pydantic-core": { + "hashes": [ + "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278", + "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50", + "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9", + "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f", + "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", + "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", + "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54", + "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630", + "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9", + "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236", + "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", + "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", + "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", + "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", + "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", + "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", + "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", + "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd", + "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", + "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", + "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7", + "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", + "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e", + "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa", + "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", + "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962", + "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", + "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f", + "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", + "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5", + "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", + "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf", + "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a", + "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c", + "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76", + "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362", + "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4", + "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", + "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320", + "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118", + "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96", + "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", + "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046", + "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3", + "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", + "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af", + "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", + "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67", + "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a", + "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", + "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35", + "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", + "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151", + "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b", + "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", + "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133", + "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", + "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145", + "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", + "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", + "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", + "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", + "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", + "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", + "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5", + "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", + "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", + "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8", + "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", + "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da", + "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", + "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc", + "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993", + "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656", + "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4", + "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", + "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb", + "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d", + "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", + "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e", + "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", + "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc", + "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a", + "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9", + "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506", + "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b", + "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1", + "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", + "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", + "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", + "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31", + "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c", + "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", + "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", + "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308", + "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2", + "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228", + "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b", + "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", + "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==2.27.2" + }, + "pygments": { + "hashes": [ + "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", + "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c" + ], + "markers": "python_version >= '3.8'", + "version": "==2.19.1" + }, + "python-dateutil": { + "hashes": [ + "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", + "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "version": "==2.9.0.post0" + }, + "python-dotenv": { + "hashes": [ + "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", + "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==1.0.1" + }, + "pyyaml": { + "hashes": [ + "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", + "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", + "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", + "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", + "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", + "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", + "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", + "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", + "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", + "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", + "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", + "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", + "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", + "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", + "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", + "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", + "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", + "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", + "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", + "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", + "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", + "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", + "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", + "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", + "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", + "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", + "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", + "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", + "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", + "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", + "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", + "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", + "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", + "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", + "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", + "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", + "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", + "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", + "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", + "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", + "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", + "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", + "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", + "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", + "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", + "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", + "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", + "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", + "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", + "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", + "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", + "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", + "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==6.0.2" + }, + "requests": { + "hashes": [ + "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", + "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==2.32.3" + }, + "rich": { + "hashes": [ + "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", + "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90" + ], + "markers": "python_full_version >= '3.8.0'", + "version": "==13.9.4" + }, + "ruamel.yaml": { + "hashes": [ + "sha256:57b53ba33def16c4f3d807c0ccbc00f8a6081827e81ba2491691b76882d0c636", + "sha256:8b27e6a217e786c6fbe5634d8f3f11bc63e0f80f6a5890f28863d9c45aac311b" + ], + "markers": "python_version >= '3.7'", + "version": "==0.18.6" + }, + "ruamel.yaml.clib": { + "hashes": [ + "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b", + "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4", + "sha256:0b7e75b4965e1d4690e93021adfcecccbca7d61c7bddd8e22406ef2ff20d74ef", + "sha256:11f891336688faf5156a36293a9c362bdc7c88f03a8a027c2c1d8e0bcde998e5", + "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3", + "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632", + "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6", + "sha256:2c59aa6170b990d8d2719323e628aaf36f3bfbc1c26279c0eeeb24d05d2d11c7", + "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680", + "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf", + "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da", + "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6", + "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a", + "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01", + "sha256:5a0e060aace4c24dcaf71023bbd7d42674e3b230f7e7b97317baf1e953e5b519", + "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6", + "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f", + "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd", + "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2", + "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52", + "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd", + "sha256:943f32bc9dedb3abff9879edc134901df92cfce2c3d5c9348f172f62eb2d771d", + "sha256:95c3829bb364fdb8e0332c9931ecf57d9be3519241323c5274bd82f709cebc0c", + "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6", + "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb", + "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a", + "sha256:a606ef75a60ecf3d924613892cc603b154178ee25abb3055db5062da811fd969", + "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28", + "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d", + "sha256:bb43a269eb827806502c7c8efb7ae7e9e9d0573257a46e8e952f4d4caba4f31e", + "sha256:bc5f1e1c28e966d61d2519f2a3d451ba989f9ea0f2307de7bc45baa526de9e45", + "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4", + "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12", + "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31", + "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642", + "sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e", + "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285", + "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed", + "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1", + "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7", + "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3", + "sha256:e7e3736715fbf53e9be2a79eb4db68e4ed857017344d697e8b9749444ae57475", + "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5", + "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76", + "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987", + "sha256:fd5415dded15c3822597455bc02bcd66e81ef8b7a48cb71a33628fc9fdde39df" + ], + "markers": "python_version >= '3.9'", + "version": "==0.2.12" + }, + "ruff": { + "hashes": [ + "sha256:0509e8da430228236a18a677fcdb0c1f102dd26d5520f71f79b094963322ed25", + "sha256:0c000a471d519b3e6cfc9c6680025d923b4ca140ce3e4612d1a2ef58e11f11fe", + "sha256:248b1fb3f739d01d528cc50b35ee9c4812aa58cc5935998e776bf8ed5b251e75", + "sha256:45a56f61b24682f6f6709636949ae8cc82ae229d8d773b4c76c09ec83964a95a", + "sha256:496dd38a53aa173481a7d8866bcd6451bd934d06976a2505028a50583e001b76", + "sha256:52d587092ab8df308635762386f45f4638badb0866355b2b86760f6d3c076188", + "sha256:54799ca3d67ae5e0b7a7ac234baa657a9c1784b48ec954a094da7c206e0365b1", + "sha256:61323159cf21bc3897674e5adb27cd9e7700bab6b84de40d7be28c3d46dc67cf", + "sha256:7ae4478b1471fc0c44ed52a6fb787e641a2ac58b1c1f91763bafbc2faddc5117", + "sha256:7d7fc2377a04b6e04ffe588caad613d0c460eb2ecba4c0ccbbfe2bc973cbc162", + "sha256:91a7ddb221779871cf226100e677b5ea38c2d54e9e2c8ed847450ebbdf99b32d", + "sha256:9257aa841e9e8d9b727423086f0fa9a86b6b420fbf4bf9e1465d1250ce8e4d8d", + "sha256:bc3c083c50390cf69e7e1b5a5a7303898966be973664ec0c4a4acea82c1d4315", + "sha256:dcad24b81b62650b0eb8814f576fc65cfee8674772a6e24c9b747911801eeaa5", + "sha256:defed167955d42c68b407e8f2e6f56ba52520e790aba4ca707a9c88619e580e3", + "sha256:e169ea1b9eae61c99b257dc83b9ee6c76f89042752cb2d83486a7d6e48e8f764", + "sha256:e88b8f6d901477c41559ba540beeb5a671e14cd29ebd5683903572f4b40a9807", + "sha256:f1d70bef3d16fdc897ee290d7d20da3cbe4e26349f62e8a0274e7a3f4ce7a905" + ], + "markers": "python_version >= '3.7'", + "version": "==0.8.6" + }, + "shellingham": { + "hashes": [ + "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", + "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de" + ], + "markers": "python_version >= '3.7'", + "version": "==1.5.4" + }, + "six": { + "hashes": [ + "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", + "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "version": "==1.17.0" + }, + "sniffio": { + "hashes": [ + "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", + "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" + ], + "markers": "python_version >= '3.7'", + "version": "==1.3.1" + }, + "typer": { + "hashes": [ + "sha256:5b59580fd925e89463a29d363e0a43245ec02765bde9fb77d39e5d0f29dd7157", + "sha256:9d444cb96cc268ce6f8b94e13b4335084cef4c079998a9f4851a90229a3bd25c" + ], + "markers": "python_version >= '3.7'", + "version": "==0.13.1" + }, + "typing-extensions": { + "hashes": [ + "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", + "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==4.12.2" + }, + "urllib3": { + "hashes": [ + "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", + "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d" + ], + "markers": "python_version >= '3.9'", + "version": "==2.3.0" + } + }, + "develop": {} +} diff --git a/src/builder/README.md b/src/builder/README.md new file mode 100644 index 0000000..7dde17a --- /dev/null +++ b/src/builder/README.md @@ -0,0 +1,68 @@ +# 🔧 ***builder*** + +## 🛠️ Инструменты для генерации библиотеки + +### 📋 Порядок действий (работать в папке `builder` при активированном `venv`): + +1. **Для запуска MakeFile командой make из VSCode нужно установить через PowerShell или cmd:** + + - winget install GnuWin32.Make. + +2. **Создание зависимостей для работы сборки библиотеки:** + + ```bash + pip install -r requirements_builder.txt + ``` + +3. **Создание зависимостей Pipfile и Pipfile.lock для библиотеки (в случае отсутствия или создания новых зависимостей):** + + ```bash + pipenv install requirements.txt + ``` + +4. **В папке проекта pachca_code_gen_team2 в файле .env указать:** + - PACKAGE_VERSION=<Версия пакета> + - TWINE_USERNAME=<Имя пользвателя сервиса TestPyPI> + - TWINE_API_TOKEN=<Токен пользвателя сервиса TestPyPI> + +5. **Запуск создания и загрузки библиотеки на сервис TestPyPI при помощи команды:** + + ```bash + make upload + ``` +6. **Установка библиотеки с сериса TestPyPI:** + + ```bash + pip install --no-cache-dir -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ PachcaAPI==0.1.1 + ``` + +7. **Запуск генератора и тестов:** + + - run_generate_and_test - запуск генератора и тестов + - run_generator - запуск генератора + - run_test - запуск тестов + +## 📂 Структура генератора + +_src/builder/_ + +#### 📜 requirements_builder.txt +- файл со списком пакетов или библиотек, необходимых для работы упаковщика библиотеки. + +#### 📜 requirements.txt +- файл со списком пакетов или библиотек, необходимых для работы над библиотек. + +#### 📄 Pipfile +- файл, используемый виртуальной средой Pipenv для управления зависимостями библиотек. + +#### 🔒 Pipfile.lock +- файл в формате JSON хранит контрольные суммы пакетов, которые устанавливаются в проект, что даёт гарантию, что развёрнутые на разных машинах окружения будут идентичны друг другу. + +#### 🛠️ Makefile +- файл с инструкциями для утилиты make, которая нужна для автоматической сборки проекта. + +#### 📦 setup.py +- файл с описанием, каким именно образом будет упакован код для медотов генерации + +#### 🔗 MANIFEST.in +- файл с указанием, какие файлы следует включить в сборку пакета diff --git a/src/builder/requirements.txt b/src/builder/requirements.txt new file mode 100644 index 0000000..8e512c7 --- /dev/null +++ b/src/builder/requirements.txt @@ -0,0 +1,17 @@ +anyio==4.7.0 +attrs==24.3.0 +httpcore==1.0.7 +httpx==0.27.2 +Jinja2==3.1.4 +openapi-python-client==0.22.0 +python-dotenv==1.0.1 +PyYAML==6.0.2 +ruamel.yaml==0.18.6 +ruamel.yaml.clib==0.2.12 +typing_extensions==4.12.2 +black==24.10.0 +isort==5.13.2 +pydantic==2.10.4 +pydantic_core==2.27.2 +requests==2.32.3 +python-dotenv==1.0.1 \ No newline at end of file diff --git a/src/builder/requirements_builder.txt b/src/builder/requirements_builder.txt new file mode 100644 index 0000000..1a7acdd --- /dev/null +++ b/src/builder/requirements_builder.txt @@ -0,0 +1,5 @@ +python-dotenv==1.0.1 +pipenv==2024.4.0 +setuptools==75.6.0 +twine==6.0.1 +wheel==0.45.1 \ No newline at end of file diff --git a/src/builder/setup.py b/src/builder/setup.py new file mode 100644 index 0000000..7daa663 --- /dev/null +++ b/src/builder/setup.py @@ -0,0 +1,48 @@ +import json +import os +from pathlib import Path + +from dotenv import load_dotenv +from setuptools import find_packages, setup + +PACKAGE_VERSION = 'Версия из YAML' +GENERATOR_NAME = 'generator1' + +load_dotenv() + +this_directory = Path(__file__).parent +long_description = ( + this_directory / "../../README.md" +).read_text(encoding='utf-8') + + +def read_pipenv_dependencies(fname): + """Получаем из Pipfile.lock зависимости по умолчанию.""" + filepath = os.path.join(os.path.dirname(__file__), fname) + with open(filepath) as lockfile: + lockjson = json.load(lockfile) + return [dependency for dependency in lockjson.get('default')] + + +if __name__ == '__main__': + setup( + name='PachcaAPI', + long_description=long_description, + long_description_content_type='text/markdown', + version=os.getenv('PACKAGE_VERSION', PACKAGE_VERSION), + package_dir={'': '..'}, + packages=find_packages('..', include=[ + f'{GENERATOR_NAME}*']), + include_package_data=True, + description=f'A pachca_api package {GENERATOR_NAME}.', + install_requires=[ + *read_pipenv_dependencies('Pipfile.lock'), + ], + entry_points={ + 'console_scripts': [ + f'run_generate_and_test={GENERATOR_NAME}.api_generator:gen_and_test', # noqa + f'run_generator={GENERATOR_NAME}.api_generator:generate', + f'run_test={GENERATOR_NAME}.api_generator:test', + ], + }, + ) diff --git a/src/generator1/README.md b/src/generator1/README.md new file mode 100644 index 0000000..488eb81 --- /dev/null +++ b/src/generator1/README.md @@ -0,0 +1,21 @@ +Генерация Клиента через openapi-python-client + +Инструкция (работать в папке generator1): +1. В venv прописать + +pip install -r requirements.txt + +2. Сгенерировать клиент командой + +python generator.py generate + +2.1 По желанию можно указать именованный аргумент --url, +с указанием ссылки на спецификацию, по которой будет происходить генерация клиента. +Если не указывать, то будет взята официальная спецификация от Пачки по адресу: +https://raw.githubusercontent.com/pachca/openapi/main/openapi.yaml + +python generator.py generate --url *ссылка на YAML спецификацию* + +4. Запустить скрипт-пример запроса + +python generator.py test diff --git a/src/generator1/__init__.py b/src/generator1/__init__.py new file mode 100644 index 0000000..be8848f --- /dev/null +++ b/src/generator1/__init__.py @@ -0,0 +1,7 @@ +from .generator import BASE_DIR, PACKAGE_NAME, PROJECT_NAME + +__all__ = ( + "BASE_DIR", + "PACKAGE_NAME", + "PROJECT_NAME", +) diff --git a/src/generator1/api_generator.py b/src/generator1/api_generator.py new file mode 100644 index 0000000..958d771 --- /dev/null +++ b/src/generator1/api_generator.py @@ -0,0 +1,32 @@ +import os +import subprocess +import sys + +from generator1.generator import BASE_DIR + +generator_script_path = os.path.join(BASE_DIR, "generator.py") + + +def generate(): + command = [sys.executable, generator_script_path, "generate"] + result = subprocess.run(command, check=True, shell=False, text=True) + + if result.returncode == 0: + print("Команда выполнена успешно.") + else: + print("Ошибка выполнения команды:", result.returncode) + + +def test(): + command = [sys.executable, generator_script_path, "test"] + result = subprocess.run(command, check=True, shell=False, text=True) + + if result.returncode == 0: + print("Команда выполнена успешно.") + else: + print("Ошибка выполнения команды:", result.returncode) + + +def gen_and_test(): + generate() + test() diff --git a/src/generator1/client_servis.py b/src/generator1/client_servis.py new file mode 100644 index 0000000..d465483 --- /dev/null +++ b/src/generator1/client_servis.py @@ -0,0 +1,184 @@ +import ssl +from typing import Any, Optional, Union + +import httpx +from attrs import define, evolve, field + + +@define +class AuthenticatedClient: + """A Client which has been authenticated for use on secured endpoints + + The following are accepted as keyword arguments and will be used to construct httpx Clients internally: + + ``base_url``: The base URL for the API, all requests are made to a relative path to this URL + + ``cookies``: A dictionary of cookies to be sent with every request + + ``headers``: A dictionary of headers to be sent with every request + + ``timeout``: The maximum amount of a time a request can take. API functions will raise + httpx.TimeoutException if this is exceeded. + + ``verify_ssl``: Whether or not to verify the SSL certificate of the API server. This should be True in production, + but can be set to False for testing purposes. + + ``follow_redirects``: Whether or not to follow redirects. Default value is False. + + ``httpx_args``: A dictionary of additional arguments to be passed to the ``httpx.Client`` and ``httpx.AsyncClient`` constructor. + + + """ + + raise_on_unexpected_status: bool = field(default=False, kw_only=True) + _base_url: str = field( + default='https://api.pachca.com/api/shared/v1', + kw_only=True, + alias='base_url', + ) + _cookies: dict[str, str] = field( + factory=dict, + kw_only=True, + alias='cookies', + ) + _headers: dict[str, str] = field( + factory=dict, + kw_only=True, + alias='headers', + ) + _timeout: Optional[httpx.Timeout] = field( + default=None, + kw_only=True, + alias='timeout', + ) + _verify_ssl: Union[str, bool, ssl.SSLContext] = field( + default=True, + kw_only=True, + alias='verify_ssl', + ) + _follow_redirects: bool = field( + default=False, + kw_only=True, + alias='follow_redirects', + ) + _httpx_args: dict[str, Any] = field( + factory=dict, + kw_only=True, + alias='httpx_args', + ) + _client: Optional[httpx.Client] = field(default=None, init=False) + _async_client: Optional[httpx.AsyncClient] = field( + default=None, + init=False, + ) + + token: str + prefix: str = 'Bearer' + auth_header_name: str = 'Authorization' + + async def with_headers( + self, + headers: dict[str, str], + ) -> 'AuthenticatedClient': + """Get a new client matching this one with additional headers""" + if self._client is not None: + self._client.headers.update(headers) + if self._async_client is not None: + self._async_client.headers.update(headers) + return evolve(self, headers={**self._headers, **headers}) + + async def with_cookies( + self, + cookies: dict[str, str], + ) -> 'AuthenticatedClient': + """Get a new client matching this one with additional cookies""" + if self._client is not None: + self._client.cookies.update(cookies) + if self._async_client is not None: + self._async_client.cookies.update(cookies) + return evolve(self, cookies={**self._cookies, **cookies}) + + async def with_timeout( + self, + timeout: httpx.Timeout, + ) -> 'AuthenticatedClient': + """Get a new client matching this one with a new timeout (in seconds)""" + if self._client is not None: + self._client.timeout = timeout + if self._async_client is not None: + self._async_client.timeout = timeout + return evolve(self, timeout=timeout) + + async def set_httpx_client( + self, + client: httpx.Client, + ) -> 'AuthenticatedClient': + """Manually set the underlying httpx.Client + + **NOTE**: This will override any other settings on the client, including cookies, headers, and timeout. + """ + self._client = client + return self + + async def get_httpx_client(self) -> httpx.Client: + """Get the underlying httpx.Client, constructing a new one if not previously set""" + if self._client is None: + self._headers[self.auth_header_name] = ( + f'{self.prefix} {self.token}' if self.prefix else self.token + ) + self._client = httpx.Client( + base_url=self._base_url, + cookies=self._cookies, + headers=self._headers, + timeout=self._timeout, + verify=self._verify_ssl, + follow_redirects=self._follow_redirects, + **self._httpx_args, + ) + return self._client + + async def __enter__(self) -> 'AuthenticatedClient': + """Enter a context manager for self.client—you cannot enter twice (see httpx docs)""" + await self.get_httpx_client().__enter__() + return self + + async def __exit__(self, *args: Any, **kwargs: Any) -> None: + """Exit a context manager for internal httpx.Client (see httpx docs)""" + await self.get_httpx_client().__exit__(*args, **kwargs) + + async def set_async_httpx_client( + self, + async_client: httpx.AsyncClient, + ) -> 'AuthenticatedClient': + """Manually the underlying httpx.AsyncClient + + **NOTE**: This will override any other settings on the client, including cookies, headers, and timeout. + """ + self._async_client = async_client + return self + + async def get_async_httpx_client(self) -> httpx.AsyncClient: + """Get the underlying httpx.AsyncClient, constructing a new one if not previously set""" + if self._async_client is None: + self._headers[self.auth_header_name] = ( + f'{self.prefix} {self.token}' if self.prefix else self.token + ) + self._async_client = httpx.AsyncClient( + base_url=self._base_url, + cookies=self._cookies, + headers=self._headers, + timeout=self._timeout, + verify=self._verify_ssl, + follow_redirects=self._follow_redirects, + **self._httpx_args, + ) + return self._async_client + + async def __aenter__(self) -> 'AuthenticatedClient': + """Enter a context manager for underlying httpx.AsyncClient—you cannot enter twice (see httpx docs)""" + await self.get_async_httpx_client().__aenter__() + return self + + async def __aexit__(self, *args: Any, **kwargs: Any) -> None: + """Exit a context manager for underlying httpx.AsyncClient (see httpx docs)""" + await self.get_async_httpx_client().__aexit__(*args, **kwargs) diff --git a/src/generator1/config_pachca_api.py b/src/generator1/config_pachca_api.py new file mode 100644 index 0000000..6cb0487 --- /dev/null +++ b/src/generator1/config_pachca_api.py @@ -0,0 +1,2 @@ +project_name_override: PachcaAPIClient +package_name_override: pachca_api \ No newline at end of file diff --git a/src/generator1/generator.py b/src/generator1/generator.py new file mode 100644 index 0000000..77d3aea --- /dev/null +++ b/src/generator1/generator.py @@ -0,0 +1,94 @@ +import os +import os.path +import subprocess +import sys + +import requests + +PROJECT_NAME = 'PachcaAPIClient' +PACKAGE_NAME = 'pachca_api' +MIN_ARGS = 2 +COMMAND_INDEX = 1 +GENERATE_COMMAND = "generate" +INSTALL_TEST_COMMAND = "test" +DEFAULT_YAML_URL = "https://raw.githubusercontent.com/pachca/openapi/main/openapi.yaml" # noqa +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +OPENAPI_FILE_PATH = os.path.join(BASE_DIR, "openapi.yaml") + + +def run_command(command): + """Функция для выполнения команды в терминале.""" + try: + subprocess.run(command, check=True, shell=True, text=True) + print(f"Команда выполнена: {command}") + except subprocess.CalledProcessError as e: + print(f"Ошибка при выполнении команды: {command}\nКод ошибки: " + f"{e.returncode}\nВывод:\n{e.stderr}") + + +def download_yaml(url): + """Загрузка YAML файла по URL и сохранение в нужную директорию.""" + print(f"Загрузка YAML файла с {url}...") + try: + response = requests.get(url, timeout=10) + response.raise_for_status() + os.makedirs(BASE_DIR, exist_ok=True) + with open(OPENAPI_FILE_PATH, "wb") as file: + file.write(response.content) + print(f"Файл сохранён тут: {OPENAPI_FILE_PATH}") + except requests.RequestException as e: + print(f"Ошибка при загрузке YAML файла: {e}") + sys.exit(1) + + +def generate_client(yaml_url): + """Генерация клиента и запуск скрипта-генератора.""" + download_yaml(yaml_url) + print("Генерация клиента...") + openapi_python_client = ( + f"openapi-python-client generate --path {OPENAPI_FILE_PATH} " + f"--custom-template-path={os.path.join(BASE_DIR, 'templates')} " + f"--overwrite --output-path {os.path.join(BASE_DIR, PROJECT_NAME)} " + f"--config {os.path.join(BASE_DIR, 'config_pachca_api.py')}" + ) + run_command(openapi_python_client) + + pydantic_script_path = os.path.join(BASE_DIR, "pydantic_script.py") + script_path = os.path.join(BASE_DIR, "script.py") + run_command(f"{sys.executable} {pydantic_script_path}") + run_command(f"{sys.executable} {script_path}") + + +def install_and_run_tests(): + """Установка пакета и запуск тест-запросов.""" + pachca_path = os.path.join(BASE_DIR, "pachca.py") + print("Установка пакета и запуск тест-запросов...") + run_command(f"pip install {os.path.join(BASE_DIR, PROJECT_NAME)}") + run_command(f"{sys.executable} {pachca_path}") + + +if __name__ == "__main__": + if len(sys.argv) < MIN_ARGS: + print("Пример команды: python generator.py [generate|test] " + "[--url <ссылка на .yaml>]") + sys.exit(COMMAND_INDEX) + + command = sys.argv[COMMAND_INDEX] + yaml_url = DEFAULT_YAML_URL + if "--url" in sys.argv: + url_index = sys.argv.index("--url") + 1 + if url_index < len(sys.argv): + yaml_url = sys.argv[url_index] + else: + print("Ошибка: не указан url после --url") + sys.exit(1) + commands = { + GENERATE_COMMAND: lambda: generate_client(yaml_url), + INSTALL_TEST_COMMAND: install_and_run_tests, + } + if command in commands: + commands[command]() + else: + print("Некорректный аргумент. Введите 'generate' для генерации " + "клиента или 'test' для запуска тестов.") + sys.exit(COMMAND_INDEX) diff --git a/src/generator1/logger_setup.py b/src/generator1/logger_setup.py new file mode 100644 index 0000000..123e21d --- /dev/null +++ b/src/generator1/logger_setup.py @@ -0,0 +1,16 @@ +import logging + + +def setup_logging( + logger_name: str, + file_name: str = 'client_generator.log', +) -> logging.Logger: + logger = logging.getLogger(logger_name) + logger.setLevel(logging.DEBUG) + file_handler = logging.FileHandler(file_name, encoding='utf-8') + formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s', + ) + file_handler.setFormatter(formatter) + logger.addHandler(file_handler) + return logger diff --git a/src/generator1/openapi.yaml b/src/generator1/openapi.yaml new file mode 100644 index 0000000..939326f --- /dev/null +++ b/src/generator1/openapi.yaml @@ -0,0 +1,2365 @@ +openapi: 3.0.3 +info: + title: PachcaAPI - OpenAPI 3.0 + description: Документация к открытому API пачки + version: 3.0.3 +servers: + - url: https://api.pachca.com/api/shared/v1 + +tags: + - name: common methods + description: Everything about common methods + - name: employees + description: Everything about employees + - name: status + description: Everything about + status + - name: tags + description: Everything about + tags + - name: chats and channels + description: Everything about + chats and channels + - name: talk and channel participants + description: Everything about + talk and channel participants + - name: comments + description: Everything about + comments + - name: messages + description: Everything about + messages + - name: reactions to messages + description: Everything about + reactions to messages + - name: reminders + description: Everything about + reminders + +paths: + /custom_properties: + get: + tags: + - common methods + summary: получение списка актульных полей сущности + description: | + Метод для получения актуального списка дополнительных полей участников и напоминаний в вашей компании. Тело запроса отсутствует, параметры передаются в URL (например, /custom_properties?entity_type=User) + operationId: getCommonMethods + parameters: + - name: entity_type + in: query + description: Тип сущности + required: true + schema: + type: string + responses: + '200': + description: Успешный запрос + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/CommonMethods' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Поле не может быть пустым + value: + errors: + - key: string + value: string + message: message + code: blank + payload: {} + inclusion: + description: Поле имеет непредусмотренное значение + value: + errors: + - key: string + value: string + message: message + code: inclusion + payload: {} + /uploads: + post: + tags: + - common methods + summary: получения подписи и ключа для загрузки файла + description: | + Данный метод необходимо использовать для загрузки каждого файла. + + Данный метод позволяет получить уникальный набор параметров для загрузки файла. Параметры запроса отсутствуют. + operationId: getUploads + responses: + '200': + description: Успешный ответ. + content: + application/json: + schema: + $ref: '#/components/schemas/FileResponse' + /direct_url: + post: + tags: + - common methods + summary: (полученный в ответе на запрос /uploads) загрузка файла + description: | + Данный метод не требует авторизации. + + Получив все параметры, вам необходимо сделать POST запрос в формате multipart/form-data на адрес, который был указан в поле direct_url, отправив полученные параметры и сам файл. + operationId: getDirectUrl + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/DirectResponse' + responses: + '201': + description: При безошибочном выполнении запроса тело ответа отсутствует. + /users: + get: + tags: + - employees + summary: получение актуального списка всех сотрудников компании + description: | + Метод для получения актуального списка сотрудников вашей компании. + Тело запроса отсутствует, параметры передаются в URL (например, /users?per=50&page=2&query=example.com) + operationId: getEmployees + parameters: + - name: per + in: query + description: Количество возвращаемых сущностей за один запрос (по умолчанию 50, максимум 50) + required: false + schema: + type: integer + default: 50 + maximum: 50 + - name: page + in: query + description: Страница выборки (по умолчанию 1) + required: false + schema: + type: integer + default: 1 + - name: query + in: query + description: | + Поисковая фраза для фильтрации результатов (поиск идет по полям first_name (имя), last_name (фамилия), email (электронная почта), phone_number (телефон) и nickname (никнейм)) + required: false + schema: + type: string + responses: + '200': + description: Успешный запрос + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Employee' + /users/{id}: + get: + tags: + - employees + summary: получение информации о сотруднике + description: | + Метод для получения информации о сотруднике. + Для получения сотрудника вам необходимо знать его id и указать его в URL запроса. + operationId: getEmployee + parameters: + - name: id + in: path + description: Уникальный идентификатор сотрудкика + required: true + schema: + type: integer + responses: + '200': + description: Успешный запрос + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Employee' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + not_found: + description: Поле не может быть пустым + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + /profile/status: + get: + tags: + - status + summary: получение информации о своем статусе + description: | + Метод для получения информации о своем статусе. Параметры запроса отсутствуют. + operationId: getStatus + responses: + '200': + description: Успешный запрос + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Status' + put: + tags: + - status + summary: новый статус + description: | + Метод для установки себе нового статуса. + operationId: putStatus + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + status: + $ref: '#/components/schemas/QueryStatus' + responses: + '200': + description: Объект создан + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Status' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Обязательное поле (не может быть пустым) + value: + errors: + - key: string + value: string + message: message + code: blank + payload: {} + too_long: + description: Слишком длинное значение (пояснения вы получите в поле message) + value: + errors: + - key: string + value: string + message: message + code: too_long + payload: {} + invalid: + description: Поле не соответствует правилам (пояснения вы получите в поле message) + value: + errors: + - key: string + value: string + message: message + code: invalid + payload: {} + wrong_emoji: + description: Emoji статуса не может содержать значения отличные от Emoji символа + value: + errors: + - key: string + value: string + message: message + code: wrong_emoji + payload: {} + delete: + tags: + - status + summary: удаление своего статуса + description: | + Метод для удаления своего статуса. Параметры запроса отсутствуют. + operationId: delStatus + responses: + '204': + description: При безошибочном выполнении запроса тело ответа отсутствует + content: {} + /group_tags/{id}: + get: + tags: + - tags + summary: получение информации о теге + description: | + Метод для получения информации о теге. Названия тегов являются уникальными в компании. + + Для получения тега вам необходимо знать его id и указать его в URL запроса. Параметры запроса отсутствуют + operationId: getTag + parameters: + - name: id + in: path + description: Уникальный идентификатор тега + required: true + schema: + type: integer + responses: + '200': + description: Успешный запрос + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Tag' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + /group_tags: + get: + tags: + - tags + summary: получение актуального списка тегов сотрудников + description: | + Метод для получения актуального списка тегов сотрудников. + + Названия тегов являются уникальными в компании. Тело запроса отсутствует, параметры передаются в URL (например, /group_tags?per=10&page=2) + operationId: getTags + parameters: + - name: per + in: query + description: Количество возвращаемых сущностей за один запрос (по умолчанию 50, максимум 50) + required: false + schema: + type: integer + default: 50 + maximum: 50 + - name: page + in: query + description: Страница выборки (по умолчанию 1) + required: false + schema: + type: integer + default: 1 + responses: + '200': + description: Успешный запрос + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Tag' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + exclusion: + description: Поле имеет непредусмотренное значение + value: + errors: + - key: string + value: string + message: message + code: exclusion + payload: {} + /group_tags/{id}/users: + get: + tags: + - tags + operationId: getTagsEmployees + summary: получение актуального списка сотрудников тега + description: | + Метод для получения актуального списка сотрудников тега. + + Идентификатор тега, список сотрудников которого необходимо получить, и другие параметры передаются в URL (например, /group_tags/877650/users?per=3&page=2) + parameters: + - name: id + in: path + description: Уникальный идентификатор сотрудкика + required: true + schema: + type: integer + - name: per + in: query + description: Количество возвращаемых сущностей за один запрос (по умолчанию 25, максимум 50) + required: false + schema: + type: integer + default: 25 + maximum: 50 + - name: page + in: query + description: Страница выборки (по умолчанию 1) + required: false + schema: + type: integer + default: 1 + responses: + '200': + description: Успешный запрос + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/BaseEmployee' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + exclusion: + description: Поле имеет непредусмотренное значение + value: + errors: + - key: string + value: string + message: message + code: exclusion + payload: {} + /chats: + post: + tags: + - chats and channels + operationId: createChat + summary: создание новой беседы или канала + description: | + Метод для создания новой беседы или нового канала. + При создании беседы или канала вы автоматически становитесь участником. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + chat: + $ref: '#/components/schemas/BaseChat' + responses: + '201': + description: Запрос отработал успешно, сущность создана + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Chat' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Обязательное поле (не может быть пустым) + value: + errors: + - key: name + value: '' + message: message + code: blank + payload: {} + too_long: + description: Слишком длинное значение (пояснения вы получите в поле message) + value: + errors: + - key: name + value: long_name + message: message + code: too_long + payload: {} + invalid: + description: Поле не соответствует правилам (пояснения вы получите в поле message) + value: + errors: + - key: name + value: 1234 + message: message + code: invalid + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + '422': + description: С запросом все хорошо, но правила сервиса не позволяют его обработать (например, при попытке создания контакта с уже существующим номером телефона в базе) + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + invalid: + description: Поле имеет неверный формат (идентификатор поля вы получите в поле payload) + value: + errors: + - key: name + value: name + message: message + code: invalid + payload: {} + get: + tags: + - chats and channels + operationId: getChats + summary: получение списка бесед и каналов + description: | + Метод для получения списка бесед и каналов по заданным параметрам. + + Тело запроса отсутствует, параметры передаются в URL (например, /chats?per=2&sort[id]=desc) + parameters: + - name: 'sort[id]' + in: query + required: false + description: | + Составной параметр сортировки сущностей выборки. + Варианты значений: по умолчанию desc (по убыванию) или asc (по возрастанию). + На данный момент сортировка доступна только по полю ({field}) id (идентификатор бесед и каналов). + schema: + type: string + enum: + - desc + - asc + default: desc + - name: per + in: query + description: Количество возвращаемых сущностей за один запрос (по умолчанию 25, максимум 50) + required: false + schema: + type: integer + default: 25 + maximum: 50 + - name: page + in: query + description: Страница выборки (по умолчанию 1) + required: false + schema: + type: integer + default: 1 + - name: availability + in: query + required: false + description: | + Параметр, который отвечает за доступность и выборку бесед и каналов для пользователя. + Варианты значений: по умолчанию is_member (беседы и каналы, где пользователь является участником) + или public (все открытые беседы и каналы компании, вне зависимости от участия в них пользователя). + schema: + type: string + enum: + - is_member + - public + default: is_member + - name: last_message_at_after + in: query + required: false + description: | + Фильтрация по времени создания последнего сообщения. + Будут возвращены те беседы/каналы, время последнего созданного сообщения в которых не раньше чем указанное (в формате YYYY-MM-DDThh:mm:ss.sssZ). + schema: + type: string + format: date-time + - name: last_message_at_before + in: query + required: false + description: | + Фильтрация по времени создания последнего сообщения. + Будут возвращены те беседы/каналы, время последнего созданного сообщения в которых не позже чем указанное (в формате YYYY-MM-DDThh:mm:ss.sssZ). + schema: + type: string + format: date-time + responses: + '200': + description: Запрос отработал как положено, без ошибок + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Chat' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + too_long: + description: Слишком длинное значение (пояснения вы получите в поле message) + value: + errors: + - key: name + value: long_name + message: message + code: too_long + payload: {} + invalid: + description: Поле не соответствует правилам (пояснения вы получите в поле message) + value: + errors: + - key: name + value: 1234 + message: message + code: invalid + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + '422': + description: С запросом все хорошо, но правила сервиса не позволяют его обработать (например, при попытке создания контакта с уже существующим номером телефона в базе) + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + invalid: + description: Поле имеет неверный формат (идентификатор поля вы получите в поле payload) + value: + errors: + - key: name + value: name + message: message + code: invalid + payload: {} + /chats/{id}: + get: + tags: + - chats and channels + operationId: getChat + summary: получение информации о беседе или канале + description: | + Получения информации о беседе или канале. + Для получения беседы или канала вам необходимо знать её id и указать его в URL запроса. + parameters: + - name: id + description: Идентификатор беседы или канала + in: path + required: true + schema: + type: integer + responses: + '200': + description: Успешный запрос + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Chat' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + /chats/{id}/members: + post: + tags: + - talk and channel participants + summary: добавление пользователей в состав участников + description: | + Метод для добавления пользователей в состав участников беседы или канала. + operationId: postMembersToChats + parameters: + - name: id + in: path + description: Идентификатор беседы/канала + required: true + schema: + type: integer + format: int64 + example: 533 + requestBody: + description: | + Идентификатор беседы/канала передаётся в URL (например, /chats/553/members) + Массив идентификаторов пользователей, которые станут участниками, передается в теле запроса + content: + application/json: + schema: + $ref: '#/components/schemas/MembersChat' + responses: + '204': + description: Пользователи добавлены + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Обязательное поле (не может быть пустым) + value: + errors: + - key: name + value: '' + message: message + code: blank + payload: {} + too_long: + description: Слишком длинное значение (пояснения вы получите в поле message) + value: + errors: + - key: name + value: long_name + message: message + code: too_long + payload: {} + invalid: + description: Поле не соответствует правилам (пояснения вы получите в поле message) + value: + errors: + - key: name + value: 1234 + message: message + code: invalid + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + '422': + description: С запросом все хорошо, но правила сервиса не позволяют его обработать (например, при попытке создания контакта с уже существующим номером телефона в базе) + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + invalid: + description: Поле имеет неверный формат (идентификатор поля вы получите в поле payload) + value: + errors: + - key: name + value: name + message: message + code: invalid + payload: {} + /chats/{id}/group_tags: + post: + tags: + - talk and channel participants + summary: добавление тегов в состав участников беседы или канала + description: | + Метод для добавления тегов в состав участников беседы или канала. + operationId: postTagsToChats + parameters: + - name: id + in: path + description: Идентификатор беседы/канала + required: true + schema: + type: integer + format: int64 + example: 533 + requestBody: + description: | + Идентификатор беседы/канала передаётся в URL (например, /chats/553/group_tags) + Массив идентификаторов тегов, которые станут участниками, передается в теле запроса + content: + application/json: + schema: + $ref: '#/components/schemas/GroupTag' + responses: + '204': + description: Тег(и) добавлен(ы) + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Обязательное поле (не может быть пустым) + value: + errors: + - key: name + value: '' + message: message + code: blank + payload: {} + too_long: + description: Слишком длинное значение (пояснения вы получите в поле message) + value: + errors: + - key: name + value: long_name + message: message + code: too_long + payload: {} + invalid: + description: Поле не соответствует правилам (пояснения вы получите в поле message) + value: + errors: + - key: name + value: 1234 + message: message + code: invalid + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + '422': + description: С запросом все хорошо, но правила сервиса не позволяют его обработать (например, при попытке создания контакта с уже существующим номером телефона в базе) + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + invalid: + description: Поле имеет неверный формат (идентификатор поля вы получите в поле payload) + value: + errors: + - key: name + value: name + message: message + code: invalid + payload: {} + /chats/{id}/leave: + delete: + tags: + - talk and channel participants + operationId: leaveChat + summary: выход из беседы или канала + description: |- + Метод для самостоятельного выхода из беседы или канала. Параметры запроса отсутствуют/ + parameters: + - name: id + in: path + required: true + description: Уникальный идентификатор беседы или канала. + schema: + type: integer + responses: + '204': + description: При безошибочном выполнении запроса тело ответа отсутствуе + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + personal_chat: + description: Нельзя покинуть персональный чат + value: + errors: + - key: string + value: string + message: message + code: personal_chat + payload: {} + /messages/{id}/thread: + post: + tags: + - comments + summary: создание нового треда + description: | + Метод для создания нового треда к сообщению. Если у сообщения уже был создан тред, то в ответе вернётся информация об уже созданном ранее треде. + operationId: createThread + parameters: + - name: id + in: path + required: true + description: Уникальный идентификатор сообщения, к которому создается тред. + schema: + type: integer + responses: + '201': + description: Тред успешно создан или возвращены данные существующего треда. + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Thread' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Поле не может быть пустым + value: + errors: + - key: name + value: '' + message: message + code: blank + payload: {} + exclusion: + description: Поле имеет недопустимое значение + value: + errors: + - key: name + value: 1234 + message: message + code: exclusion + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + /messages: + post: + tags: + - messages + summary: создание нового сообщения + description: | + Метод для отправки сообщения в беседу или канал, + личного сообщения пользователю или комментария в тред. + + При использовании entity_type: "discussion" (или просто без указания entity_type) + допускается отправка любого chat_id в поле entity_id. + То есть, сообщение можно отправить зная только идентификатор чата. + При этом, вы имеете возможность отправить сообщение в тред по его идентификатору + или личное сообщение по идентификатору пользователя. + + Для отправки личного сообщения пользователю создавать чат не требуется. + Достаточно указать entity_type: "user" и идентификатор пользователя. + Чат будет создан автоматически, если между вами ещё не было переписки. + Между двумя пользователями может быть только один личный чат. + operationId: createMessage + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + message: + $ref: '#/components/schemas/CreateMessage' + responses: + '201': + description: Successful + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Message' + example: + data: + id: 194275 + entity_type: discussion + entity_id: 198 + chat_id: 198 + content: Вчера мы продали 756 футболок (что на 10% больше, чем в прошлое воскресенье) + user_id: 12 + created_at: 2020-06-08T09:32:57.000Z + files: [] + buttons: [] + thread: null + forwarding: null + parent_message_id: null + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Поле не может быть пустым + value: + errors: + - key: name + value: '' + message: message + code: blank + payload: {} + exclusion: + description: Поле имеет недопустимое значение + value: + errors: + - key: name + value: 1234 + message: message + code: exclusion + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + get: + tags: + - messages + summary: получение списка сообщений чата + description: | + Метод для получения списка сообщений бесед, каналов, тредов и личных сообщений. + + Для получения сообщений вам необходимо знать chat_id требуемой беседы, канала, + треда или диалога, и указать его в URL запроса. Сообщения будут возвращены + в порядке убывания даты отправки (то есть, сначала будут идти последние сообщения чата). + Для получения более ранних сообщений чата доступны параметры per и page. + Тело запроса отсутствует, параметры передаются в URL (например, /messages?chat_id=198&per=3) + operationId: getListMessage + parameters: + - name: chat_id + in: query + description: Идентификатор чата (беседа, канал, диалог или чат треда) + required: true + schema: + title: chat_id + type: integer + - name: per + in: query + description: Количество возвращаемых сущностей за один запрос (по умолчанию 25, максимум 50) + required: false + schema: + type: integer + default: 25 + maximum: 50 + - name: page + in: query + description: Страница выборки (по умолчанию 1) + required: false + schema: + type: integer + default: 1 + responses: + '200': + description: Successful + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Message' + example: + data: + - id: 1194277 + entity_type: discussion + entity_id: 198 + chat_id: 198 + content: Это сообщение тоже попадёт в экспорт + user_id: 12 + created_at: 2023-09-18T13:43:32.000Z + files: [] + buttons: [] + thread: + id: 2633 + chat_id: 44997 + forwarding: null + parent_message_id: null + - id: 1194276 + entity_type: discussion + entity_id: 198 + chat_id: 198 + content: "**Andrew** добавил **Export bot** в беседу" + user_id: 12 + created_at: 2023-09-18T13:43:27.000Z + files: [] + buttons: [] + thread: null + forwarding: null + parent_message_id: null + - id: 1194275 + entity_type: discussion + entity_id: 198 + chat_id: 198 + content: "**Andrew** создал беседу" + user_id: 12 + created_at: 2023-09-18T13:43:19.000Z + files: [] + buttons: [] + thread: null + forwarding: null + parent_message_id: null + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Поле не может быть пустым + value: + errors: + - key: name + value: '' + message: message + code: blank + payload: {} + exclusion: + description: Поле имеет недопустимое значение + value: + errors: + - key: name + value: 1234 + message: message + code: exclusion + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + /messages/{id}: + get: + tags: + - messages + summary: получение информации о сообщении + description: | + Метод для получения информации о сообщении. + + Для получения сообщения вам необходимо знать его id и указать его в URL запроса. + operationId: getMessage + parameters: + - name: id + in: path + required: true + schema: + title: id + type: integer + responses: + '200': + description: Successfull + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Message' + example: + data: + id: 194275 + entity_type: discussion + entity_id: 198 + chat_id: 198 + content: Вчера мы продали 756 футболок (что на 10% больше, чем в прошлое воскресенье) + user_id: 12 + created_at: 2020-06-08T09:32:57.000Z + files: + - id: 3560 + key: attaches/files/12/21zu7934-02e1-44d9-8df2-0f970c259796/congrat.png + name: congrat.png + file_type: file + url: | + https://pachca-prod-uploads.s3.storage.selcloud.ru/attaches/files/12/21zu7934- + 02e1-44d9-8df2-0f970c259796/congrat.png?response-cache-control=max- + age%3D3600%3B&response-content-disposition=attachment&X-Amz-Algorithm=AWS4-HMAC + -SHA256&X-Amz-Credential=142155_staply%2F20231107%2Fru-1a%2Fs3%2Faws4_ + request&X-Amz-Date=20231107T160412Z&X-Amz-Expires=604800&X-Amz-SignedHeaders= + host&X-Amz-Signature=98765asgfadsfdsaDSd4sdfg35asdf67sadf8 + buttons: [] + thread: + id: 29873 + chat_id: 1949863 + forwarding: null + parent_message_id: 194274 + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + put: + tags: + - messages + operationId: editMessage + summary: редактирование сообщения по указанному идентификатору + description: Метод для редактирования сообщения или комментария. + parameters: + - name: id + in: path + required: true + description: Уникальный идентификатор беседы или канала. + schema: + type: integer + requestBody: + description: Массив идентификаторов тегов, которые станут участниками + content: + application/json: + schema: + type: object + properties: + message: + $ref: '#/components/schemas/EditMessages' + responses: + '200': + description: Успешно отредактировано + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Message' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Поле не может быть пустым + value: + errors: + - key: name + value: '' + message: message + code: blank + payload: {} + exclusion: + description: Поле имеет недопустимое значение + value: + errors: + - key: name + value: 1234 + message: message + code: exclusion + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + /messages/{id}/reactions: + post: + tags: + - reactions to messages + operationId: postMessageReactions + summary: добавление реакции + description: > + Метод для добавления реакции на сообщение. + **Лимиты реакций:** + - Каждый пользователь может установить не более 20 уникальных реакций на сообщение. + - Сообщение может иметь не более 30 уникальных реакций. + - Сообщение может иметь не более 1000 реакций. + parameters: + - name: id + in: path + required: true + description: Уникальный идентификатор сообщения. + schema: + type: integer + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CodeReaction' + responses: + "201": + description: Успешное выполнение запроса, тело ответа отсутствует. + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Поле не может быть пустым + value: + errors: + - key: name + value: '' + message: message + code: blank + payload: {} + exclusion: + description: Поле имеет недопустимое значение + value: + errors: + - key: name + value: 1234 + message: message + code: exclusion + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + user_limit: + description: Превышен лимит уникальных реакций пользователя + value: + errors: + - key: string + value: string + message: Вы можете добавить не более 20 уникальных реакций. + code: user_limit + payload: {} + unique_limit: + description: Превышен лимит уникальных реакций на сообщение + value: + errors: + - key: string + value: string + message: Сообщение может содержать не более 30 уникальных реакций. + code: unique_limit + payload: {} + general_limit: + description: Превышен общий лимит реакций на сообщение + value: + errors: + - key: string + value: string + message: Сообщение может содержать не более 1000 реакций. + code: general_limit + payload: {} + delete: + tags: + - reactions to messages + operationId: deleteMessageReactions + summary: удаление реакции + description: > + Метод для удаления реакции на сообщение. + Удалить можно только те реакции, которые были поставлены авторизованным пользователем. + parameters: + - name: id + in: path + required: true + description: Уникальный идентификатор сообщения. + schema: + type: integer + - name: code + in: query + description: Emoji в строковом формате для добавления реакции. + schema: + type: string + example: "👍" + responses: + "204": + description: При безошибочном выполнении запроса тело ответа отсутствует + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Поле не может быть пустым + value: + errors: + - key: name + value: '' + message: message + code: blank + payload: {} + exclusion: + description: Поле имеет недопустимое значение + value: + errors: + - key: name + value: 1234 + message: message + code: exclusion + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + get: + tags: + - reactions to messages + operationId: getMessageReactions + summary: получение актуального списка реакций + description: | + Метод для получения актуального списка реакций на сообщение. + + Идентификатор сообщения, список реакций на которое необходимо получить, передается в URL (например, /messages/7231942/reactions). Количество возвращаемых сущностей и страница выборки указываются в теле запроса + parameters: + - name: id + in: path + description: Уникальный идентификатор сообщения + required: true + schema: + type: integer + - name: per + in: query + description: Количество возвращаемых сущностей за один запрос (по умолчанию 50, максимум 50) + required: false + schema: + type: integer + default: 50 + maximum: 50 + - name: page + in: query + description: Страница выборки (по умолчанию 1) + required: false + schema: + type: integer + default: 1 + responses: + '200': + description: Список реакций успешно получен. + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Reaction' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + exclusion: + description: Поле имеет недопустимое значение + value: + errors: + - key: name + value: 1234 + message: message + code: exclusion + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + /tasks: + post: + tags: + - reminders + operationId: createTask + summary: создание нового напоминания + description: | + Метод для создания нового напоминания. + + При создании напоминания обязательным условием является указания типа напоминания: звонок, встреча, простое напоминание, событие или письмо. + При этом не требуется дополнительное описание - вы просто создадите напоминание с соответствующим текстом. + Если вы укажите описание напоминания - то именно оно и станет текстом напоминания. + У напоминания должны быть ответственные, если их не указывать - ответственным назначаетесь вы. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + task: + type: object + required: + - kind + - content + - due_at + properties: + kind: + type: string + description: Тип напоминания (call, meeting, reminder, event, email) + content: + type: string + description: Описание напоминания + due_at: + type: string + format: date-time + description: Срок выполнения напоминания (ISO-8601) + priority: + type: integer + description: Приоритет (1 - по умолчанию, 2 - важно, 3 - очень важно) + performer_ids: + type: array + items: + type: integer + description: Массив идентификаторов пользователей + custom_properties: + type: array + items: + type: object + properties: + id: + type: integer + description: Идентификатор поля + value: + type: string + description: Значение поля + responses: + '201': + description: Напоминание успешно создано + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Task' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Поле не может быть пустым + value: + errors: + - key: string + value: string + message: message + code: blank + payload: {} + too_long: + description: Слишком длинное значение (пояснения вы получите в поле message) + value: + errors: + - key: name + value: long_name + message: message + code: too_long + payload: {} + inclusion: + description: Поле имеет непредусмотренное значение + value: + errors: + - key: string + value: string + message: message + code: inclusion + payload: {} + invalid: + description: Поле имеет неверное значение (например, указаны недопустимые ответственные) + value: + errors: + - key: name + value: 1234 + message: message + code: invalid + payload: {} +components: + schemas: + MembersChat: + title: Members Chat + required: + - member_ids + type: object + properties: + member_ids: + type: array + description: Массив идентификаторов пользователей, которые станут участниками + minItems: 1 + items: + type: integer + format: int64 + example: [186, 187] + silent: + type: boolean + description: Не создавать в чате системное сообщение о добавлении участника + GroupTag: + title: Group Tag + required: + - group_tag_ids + type: object + properties: + group_tag_ids: + type: array + minItems: 1 + items: + type: integer + format: int64 + example: [86, 18] + description: Массив идентификаторов тегов, которые станут участниками + CodeReaction: + title: Code Reaction + type: object + properties: + code: + type: string + example: "👍" + description: Emoji в строковом формате для добавления реакции. + required: + - code + BaseEmployee: + title: Base Employee + type: object + properties: + id: + type: integer + example: 1 + description: Идентификатор пользователя + first_name: + type: string + description: Имя + last_name: + type: string + description: Фамилия + nickname: + type: string + description: Имя пользователя + email: + type: string + description: Электронная почта + phone_number: + type: string + description: Телефон + department: + type: string + description: Департамент + role: + type: string + enum: + - admin + - user + - multi_guest + description: | + Уровень доступа: admin (администратор), user (сотрудник), multi_guest (мульти-гость) + suspended: + type: boolean + description: | + Деактивация пользователя. При значении true пользователь является деактивированным. + invite_status: + type: string + enum: + - confirmed + - sent + description: | + Статус приглашения: confirmed (принято), sent (отправлено) + list_tags: + type: array + items: + type: string + description: Массив тегов, привязанных к сотруднику + custom_properties: + type: array + description: Дополнительные поля сотрудника + items: + type: object + properties: + id: + type: integer + description: Идентификатор поля + name: + type: string + description: Название поля + data_type: + type: string + enum: + - string + - number + - date + - link + description: Тип поля (string, number, date или link) + value: + type: string + description: Значение + bot: + type: boolean + description: | + Тип: пользователь (false) или бот (true) + description: Базовый класс сотрудника. + Employee: + allOf: + - $ref: '#/components/schemas/BaseEmployee' + - type: object + properties: + user_status: + $ref: '#/components/schemas/Status' + title: + type: string + description: Должность + created_at: + type: string + format: date-time + description: | + Дата создания (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ + time_zone: + type: string + description: Часовой пояс пользователя + image_url: + type: string + nullable: true + description: Ссылка на скачивание аватарки + description: Расширенный класс сотрудника. + BaseResponse: + title: Base Response + type: object + properties: + Content-Disposition: + type: string + description: Используемый заголовок + default: attachment + acl: + type: string + description: Уровень безопасности + default: private + policy: + type: string + description: Уникальный policy для загрузки файла + x-amz-credential: + type: string + description: x-amz-credential для загрузки файла + x-amz-algorithm: + type: string + description: Используемый алгоритм + default: AWS4-HMAC-SHA256 + x-amz-date: + type: string + description: Уникальный x-amz-date для загрузки файла + x-amz-signature: + type: string + description: Уникальная подпись для загрузки файла + key: + type: string + description: Уникальный ключ для загрузки файла + FileResponse: + title: File Response + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object + properties: + direct_url: + type: string + description: Адрес для загрузки файла + DirectResponse: + title: Direct Response + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object + properties: + file: + type: string + description: Адрес для загрузки файла + Status: + type: object + nullable: true + description: Статус. Возвращается как null, если статус не установлен. + properties: + emoji: + type: string + description: Emoji символ статуса + title: + type: string + description: Текст статуса + expires_at: + type: string + format: date-time + nullable: true + description: | + Срок жизни статуса (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ. Возвращается как null, если срок не установлен. + QueryStatus: + type: object + properties: + status: + type: object + description: Собранный объект параметров нового статуса + required: + - emoji + - title + properties: + emoji: + type: string + description: Emoji символ статуса + title: + type: string + description: Текст статуса + expires_at: + type: string + format: date-time + description: Срок действия статуса (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ + nullable: true + CommonMethods: + title: Common Methods + type: object + description: получение списка актульных полей сущности. + properties: + id: + type: integer + example: 1 + description: Название поля + name: + type: string + example: Дата рождения + description: Идентификатор поля + data_type: + type: string + enum: + - string + - number + - date + - link + example: number + description: тип поля + Errors: + type: object + properties: + errors: + type: array + items: + key: + title: key + type: string + description: Ключ параметра, в котором произошла ошибка + value: + title: value + type: string + description: Значение ключа, которое вызвало ошибку + message: + title: message + type: string + description: Ошибка текстом, который вы можете вывести пользователю + code: + title: code + type: string + description: Внутренний код ошибки (коды ошибок представлены в описании каждого метода) + payload: + title: payload + type: object + description: Объект, который предоставляет любую дополнительную информацию (возможные дополнения представлены в описании каждого метода) + Buttons: + title: Message Buttons + type: array + maxItems: 100 + items: + title: Row Buttons + type: array + maxItems: 8 + items: + type: object + title: Button + required: + - text + minProperties: 2 + properties: + text: + title: Text + type: string + maxLength: 255 + url: + title: Url + type: string + data: + title: Data + type: string + maxLength: 255 + BaseThread: + title: Base Thread + type: object + properties: + id: + type: integer + description: Идентификатор поля + chat_id: + type: integer + description: Идентификатор поля чата + Thread: + allOf: + - $ref: '#/components/schemas/BaseThread' + - type: object + properties: + message_id: + type: integer + description: Идентификатор сообщения, к которому был создан тред. + message_chat_id: + type: integer + description: Идентификатор чата сообщения. + updated_at: + type: string + format: date-time + description: | + Дата и время обновления треда (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ. + BaseFiles: + title: Base Files + type: object + required: + - key + - name + - file_type + properties: + key: + type: string + description: Путь к файлу, полученный в результате загрузки файла (каждый файл в каждом сообщении должен иметь свой уникальный key, не допускается использование одного и того же key в разных сообщениях) + name: + type: string + description: Название файла, которое вы хотите отображать пользователю (рекомендуется писать вместе с расширением) + file_type: + type: string + enum: + - file + - image + CreateEditFiles: + type: array + items: + allOf: + - $ref: '#/components/schemas/BaseFiles' + - type: object + title: Create&Edit Files + required: + - size + properties: + size: + type: integer + description: Размер файла в байтах, отображаемый пользователю + Files: + type: array + items: + allOf: + - $ref: '#/components/schemas/BaseFiles' + - type: object + title: Files + properties: + id: + type: integer + description: Идентификатор поля + url: + type: string + description: Прямая временная ссылка на скачивание файла + BeforeBaseMessages: + title: Before Base Messages + type: object + description: Для получения сообщения вам необходимо знать его id и указать его в URL запроса. + required: + - content + properties: + content: + type: string + description: Текст сообщения + default: Текст сообщения + buttons: + allOf: + - $ref: '#/components/schemas/Buttons' + title: buttons + EditMessages: + title: Edit Messages + allOf: + - $ref: '#/components/schemas/BeforeBaseMessages' + - type: object + description: Для получения сообщения вам необходимо знать его id и указать его в URL запроса. + required: + - content + properties: + files: + allOf: + - $ref: '#/components/schemas/CreateEditFiles' + title: files + BaseMessages: + title: Base Messages + allOf: + - $ref: '#/components/schemas/BeforeBaseMessages' + - type: object + required: + - entity_id + properties: + entity_type: + title: Entity Type + type: string + enum: + - discussion + - user + - thread + default: discussion + entity_id: + title: Entity Id + type: integer + parent_message_id: + title: Parent Massage Id + type: integer + nullable: true + default: null + description: Идентификатор сообщения, к которому написан ответ. Возвращается как null, если сообщение не является ответом. + CreateMessage: + title: Create Messages + allOf: + - $ref: '#/components/schemas/BaseMessages' + - type: object + properties: + files: + allOf: + - $ref: '#/components/schemas/CreateEditFiles' + title: files + skip_invite_mentions: + title: Skip Invite Mentions + type: boolean + default: false + link_preview: + title: Link Preview + type: boolean + default: false + Message: + type: object + properties: + entity_type: + title: Entity Type + type: string + enum: + - 'discussion' + - 'user' + - 'thread' + default: 'discussion' + entity_id: + title: Entity Id + type: integer + content: + title: Content + type: string + id: + title: Id + type: integer + chat_id: + title: Chat Id + type: integer + user_id: + title: User Id + type: integer + created_at: + title: Created At + type: string + format: date-time + files: + title: Files + type: array + items: + type: object + properties: + id: + title: Id + type: integer + key: + title: Key + type: string + description: Путь к файлу, полученный в результате загрузки файла (каждый файл в каждом сообщении должен иметь свой уникальный key, не допускается использование одного и того же key в разных сообщениях) + name: + title: Name + type: string + description: Название файла, которое вы хотите отображать пользователю (рекомендуется писать вместе с расширением) + file_type: + title: File Type + type: string + enum: + - 'file' + - 'image' + url: + title: Url + type: string + description: Размер файла в байтах, отображаемый пользователю + Reaction: + type: object + properties: + user_id: + type: integer + description: | + Идентификатор пользователя, оставившего реакцию. + created_at: + type: string + format: date-time + description: | + Дата и время добавления реакции (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ. + code: + type: string + description: | + Emoji символ реакции. + BaseChat: + title: Base Chat + type: object + description: Собранный объект параметров создаваемой беседы или канала + required: + - name + properties: + name: + type: string + description: Название + example: 🤿 aqua + member_ids: + type: array + description: Массив идентификаторов пользователей, которые станут участниками + items: + type: integer + example: + - 186 + - 187 + group_tag_ids: + type: array + description: Массив идентификаторов тегов, участников + items: + type: integer + example: [] + channel: + type: boolean + description: 'Тип: беседа (по умолчанию, false) или канал (true)' + example: true + public: + type: boolean + description: 'Доступ: закрытый (по умолчанию, false) или открытый (true)' + example: false + Chat: + allOf: + - type: object + properties: + id: + type: integer + description: Идентификатор беседы или канала + example: 334 + owner_id: + type: integer + description: Идентификатор пользователя, создавшего беседу или канал + example: 185 + created_at: + type: string + format: date-time + description: Дата и время создания беседы или канала (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ + example: '2021-08-28T15:56:53.000Z' + last_message_at: + type: string + format: date-time + description: Дата и время создания последнего сообщения в беседе/канале (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ + example: '2021-08-28T15:58:13.000Z' + meet_room_url: + type: string + description: Ссылка на Видеочат + example: 'https://meet.pachca.com/aqua-94bb21b5' + - $ref: '#/components/schemas/BaseChat' + Tag: + type: object + description: Для получения тега вам необходимо знать его id и указать его в URL запроса. + properties: + id: + type: integer + description: Идентификатор тега + name: + type: string + description: Название тега + users_count: + description: Количество сотрудников, которые имеют этот тег + type: integer + BaseCustomProperties: + title: Base Custom Properties + description: Задаваемые дополнительные поля + type: object + properties: + id: + type: integer + description: Идентификатор поля + value: + type: string + description: Значение поля + CustomProperties: + type: array + items: + allOf: + - $ref: '#/components/schemas/BaseCustomProperties' + - type: object + title: Custom Properties + properties: + name: + type: string + description: Название поля + data_type: + type: string + enum: + - string + - number + - date + - link + description: Тип поля (string, number, date или link) + BaseTask: + title: Base Task + type: object + required: + - kind + - content + - due_at + properties: + kind: + type: string + description: Тип напоминания + enum: + - call + - meeting + - reminder + - event + - email + content: + type: string + description: Описание напоминания + due_at: + type: string + format: date-time + description: Срок выполнения напоминания (ISO-8601) + priority: + type: integer + description: Приоритет (1 - по умолчанию, 2 - важно, 3 - очень важно) + enum: + - 1 + - 2 + - 3 + performer_ids: + type: array + items: + type: integer + description: Массив идентификаторов пользователей + custom_properties: + type: array + items: + allOf: + - $ref: '#/components/schemas/BaseCustomProperties' + Task: + allOf: + - $ref: '#/components/schemas/BaseTask' + - type: object + properties: + id: + type: integer + description: Идентификатор созданного напоминания + user_id: + type: integer + description: Идентификатор пользователя-создателя + status: + type: string + description: Статус напоминания + created_at: + type: string + format: date-time + description: Дата и время создания + custom_properties: + allOf: + - $ref: '#/components/schemas/CustomProperties' + securitySchemes: + bearerAuth: + type: http + scheme: bearer +security: + - bearerAuth: [] diff --git a/src/generator1/pachca.py b/src/generator1/pachca.py new file mode 100644 index 0000000..2d28ac1 --- /dev/null +++ b/src/generator1/pachca.py @@ -0,0 +1,224 @@ +import asyncio +import datetime +import os + +from dotenv import load_dotenv +from pachca_api.client import Pachca +from pachca_api.logger_setup import setup_logging +from pachca_api.models import (CreateTaskBodyTask, EditMessageBody, + EditMessages, GroupTag, MembersChat, + QueryStatusStatus) +from pachca_api.models.base_chat import BaseChat +from pachca_api.models.code_reaction import CodeReaction +from pachca_api.models.create_chat_body import CreateChatBody +from pachca_api.models.create_message_body import CreateMessageBody +from pachca_api.models.create_messages import CreateMessages +from pachca_api.models.create_task_body import CreateTaskBody + +load_dotenv() +pachca = Pachca(os.getenv('TOKEN')) + +logger = setup_logging( + 'test_requests_logging', + 'pachca_testresults.log', +) + + +async def main() -> None: + """Функция теста эндпоинтов""" + + # подготовка запроса на создание беседы --> + query_chat = BaseChat(name='test500_2') + chat_body = CreateChatBody(chat=query_chat) + # <-- + # запрос на создание беседы + chat_create = asyncio.create_task(pachca.createChat(body=chat_body)) + chat_response = await chat_create + + # запрос на получение списка бесед и каналов + create_task = await asyncio.create_task(pachca.getChats()) + + # запрос на получение информации о беседе + getChat = await asyncio.create_task( + pachca.getChat(id=chat_response.data.id), + ) + # подготовка запроса на создание сообщения в созданную беседу --> + create_message = CreateMessages( + entity_id=chat_response.data.id, + content='Super puper', + ) + message_body = CreateMessageBody(message=create_message) + # <-- + # запрос на создание сообщения в созданную беседу + message_create = asyncio.create_task( + pachca.createMessage(body=message_body), + ) + message_response = await message_create + # создание треда к созданному сообщению + thread_create = asyncio.create_task( + pachca.createThread(id=message_response.data.id), + ) + # запрос на получение списка сообщений + getListMessage = await asyncio.create_task( + pachca.getListMessage(chat_id=chat_response.data.id), + ) + + # запрос на получение сообщения + getMessage = await asyncio.create_task( + pachca.getMessage(id=message_response.data.id), + ) + + # подготовка запроса на редактирование сообщения --> + edit_meassage = EditMessages(content='NOT SUPER PUPER') + edit_message_body = EditMessageBody(message=edit_meassage) + # <-- + # запрос на редактирование сообщения + editMessage = await asyncio.create_task( + pachca.editMessage( + id=message_response.data.id, + body=edit_message_body, + ), + ) + + # подготовка запроса на добавление реакции к сообщению --> + post_reactions = CodeReaction(code='😭') + # <-- + # запрос на добавление реакции к сообщению + postMessageReactions = await pachca.postMessageReactions( + id=message_response.data.id, + body=post_reactions, + ) + + # подготовка запроса на получение списка реакций к сообщению --> + # <-- + # запрос на получение списка реакций + getMessageReactions = await pachca.getMessageReactions( + id=message_response.data.id, + ) + + # запрос на удаление реакции + deleteMessageReactions = await pachca.deleteMessageReactions( + id=message_response.data.id, + code='😭', + ) + + # подготовка запроса на создание напоминания --> + create_body_task = CreateTaskBodyTask( + kind='call', + content='Звонок другу', + due_at=datetime.datetime.now(), + ) + body_task = CreateTaskBody(task=create_body_task) + # <-- + # запрос на создание напоминания + createtaskbody = await asyncio.create_task( + pachca.createTask(body=body_task), + ) + + # запрос на получения списка пользователей + # users_response = await asyncio.create_task(pachca.getEmployees()) + + # запрос на получение информации о пользователе + # getEmployee = await asyncio.create_task( + # pachca.getEmployee(id=users_response.data[0].id) + # ) + + # подготовка запроса на добавление участника --> + # chats_body = MembersChat(member_ids=[users_response.data[0].id]) + # <-- + # запрос на добавление участника в беседу + # postMembersToChats = await asyncio.create_task(pachca.postMembersToChats( + # id=chat_response.data.id, body=chats_body) + # ) + + # подготовка запроса на добавление статуса --> + query_status = QueryStatusStatus( + emoji='😭', + title='Я не плачу это просто слезы', + expires_at=None, + ) + # <-- + # запрос на добавление статуса + putStatus = await asyncio.create_task(pachca.putStatus(body=query_status)) + + # запрос на получение информации о своем статусе + getStatus = await asyncio.create_task(pachca.getStatus()) + + # запрос на удаление статуса + delStatus = await asyncio.create_task(pachca.delStatus()) + + # запрос на получение тегов сотрудников + getTags = await asyncio.create_task(pachca.getTags()) + + if len(getTags.data) > 0: + tag_id = getTags.data[0].id + else: + tag_id = 0 + # запрос получение информации о теге + getTag = await asyncio.create_task(pachca.getTag(id=tag_id)) + + # запрос получение информации о теге участников + getTagsEmployees = await asyncio.create_task( + pachca.getTagsEmployees(id=tag_id), + ) + + # подготовка запроса на добавление участника --> + tags_body = GroupTag(group_tag_ids=[tag_id]) + # <-- + # запрос на добавление тегов в состав участников беседы или канала + postTagsToChats = await asyncio.create_task( + pachca.postTagsToChats(id=chat_response.data.id, body=tags_body), + ) + + # запрос на получение списка актуальных полей сущности + getCommonMethods = await asyncio.create_task( + pachca.getCommonMethods(entity_type='User'), + ) + + # запрос получения подписи и ключа для загрузки файла + getUploads = await asyncio.create_task(pachca.getUploads()) + + # запрос исключение участника из беседы + leaveChat = await asyncio.create_task( + pachca.leaveChat(id=chat_response.data.id), + ) + + logger.debug(await pachca.getUploads()) + + for task in ( + chat_response, + create_task, + getChat, + message_response, + thread_create, + getListMessage, + getMessage, + editMessage, + create_task, + postMessageReactions, + getMessageReactions, + deleteMessageReactions, + createtaskbody, + # users_response, + # getEmployee, + # postMembersToChats, + putStatus, + getStatus, + delStatus, + getTags, + getTag, + getTagsEmployees, + postTagsToChats, + getCommonMethods, + getUploads, + leaveChat, + ): + result = task + logger.debug( + f'{task}: data={result} \n***', + ) + logger.debug('Tests ended ' + '*' * 100) + + +if __name__ == '__main__': + asyncio.run(main()) diff --git a/src/generator1/pydantic_script.py b/src/generator1/pydantic_script.py new file mode 100644 index 0000000..5a68401 --- /dev/null +++ b/src/generator1/pydantic_script.py @@ -0,0 +1,49 @@ +import os +import sys +from pathlib import Path + +sys.path.append(str(Path(__file__).parent.parent)) + +from generator1 import BASE_DIR, PACKAGE_NAME, PROJECT_NAME # noqa + + +def correcting_imports_in_model_files(file_path): + with open(file_path, encoding="utf-8") as file: + content = file.readlines() + + new_content = [] + skip_next_lines = False + + for line in content: + # Проверяем, является ли строка условием TYPE_CHECKING + if line.strip() == "if TYPE_CHECKING:": + skip_next_lines = True + continue # Пропускаем эту строку + + # Если мы пропускаем строки, значит это импорты + if skip_next_lines: + if line.strip(): # Если строка не пустая + new_content.append(line.lstrip()) # Сдвигаем влево + else: + new_content.append(line) # Добавляем строку как есть + + # Если встретили пустую строку, значит закончили с импортами + if line.strip() == "": + skip_next_lines = False + + # Записываем изменения обратно в файл + with open(file_path, "w", encoding="utf-8") as file: + file.writelines(new_content) + + +def changes_all_model_files(directory): + for filename in os.listdir(directory): + if filename.endswith(".py"): + correcting_imports_in_model_files( + os.path.join(directory, filename) + ) + + +changes_all_model_files(os.path.join( + BASE_DIR, PROJECT_NAME, PACKAGE_NAME, 'models' +)) diff --git a/src/generator1/requirements.txt b/src/generator1/requirements.txt new file mode 100644 index 0000000..8b2158e --- /dev/null +++ b/src/generator1/requirements.txt @@ -0,0 +1,17 @@ +anyio==4.7.0 +attrs==24.3.0 +httpcore==1.0.7 +httpx==0.27.2 +Jinja2==3.1.4 +openapi-python-client==0.22.0 +python-dotenv==1.0.1 +PyYAML==6.0.2 +ruamel.yaml==0.18.6 +ruamel.yaml.clib==0.2.12 +typing_extensions==4.12.2 +black==24.10.0 +isort==5.13.2 +pydantic==2.10.4 +pydantic_core==2.27.2 +requests==2.32.3 +python-dotenv==1.0.1 diff --git a/src/generator1/script.py b/src/generator1/script.py new file mode 100644 index 0000000..5c2e455 --- /dev/null +++ b/src/generator1/script.py @@ -0,0 +1,230 @@ +import ast +import os +import shutil +import subprocess +import sys +from pathlib import Path + +import yaml + +sys.path.append(str(Path(__file__).parent.parent)) + +from jinja2 import Environment, FileSystemLoader # noqa + +from generator1 import BASE_DIR, PACKAGE_NAME, PROJECT_NAME # noqa + + +def extract_functions_and_imports_from_file(file_path) -> None: + with open(file_path, encoding='utf-8') as file: + tree = ast.parse(file.read()) + + functions = [] + imports = [] + + for node in ast.walk(tree): + if isinstance(node, ast.AsyncFunctionDef) or isinstance( + node, + ast.FunctionDef, + ): + functions.append(ast.unparse(node)) + elif isinstance(node, ast.ImportFrom): + module = node.module if node.module else '.' + for alias in node.names: + if module == 'typing': + imports.append(f'from typing import {alias.name}') + elif module.startswith('models'): + imports.append(f'from .models import {alias.name}') + elif module == 'types': + imports.append(f'from .types import {alias.name}') + elif module == 'client_serv': + imports.append(f'from .client_serv import {alias.name}') + else: + imports.append(f'from {module} import {alias.name}') + + return functions, imports + + +def get_all_api_functions_and_imports(api_dir): + all_functions = [] + all_imports = [] + for root, _, files in os.walk(api_dir): + for file in files: + if file.endswith('.py'): + file_path = os.path.join(root, file) + functions, imports = extract_functions_and_imports_from_file( + file_path, + ) + all_functions.extend(functions) + all_imports.extend(imports) + return all_functions, all_imports + + +def get_base_url_from_yaml(openapi_yaml): + with open(os.path.join(BASE_DIR, openapi_yaml), encoding='utf-8') as file: + data = yaml.safe_load(file) + return data['servers'][0]['url'] + + +api_dir = os.path.join( + BASE_DIR, + PROJECT_NAME, + PACKAGE_NAME, + 'api', +) +openapi_yaml = 'openapi.yaml' +endpoints, imports = get_all_api_functions_and_imports(api_dir) +base_url = get_base_url_from_yaml(openapi_yaml) +env = Environment( + loader=FileSystemLoader(os.path.join(BASE_DIR, 'templates')), +) + +client_template = env.get_template('client.py.jinja') + +client_path = os.path.join( + BASE_DIR, + PROJECT_NAME, + PACKAGE_NAME, + 'client.py', +) + +with open(client_path, mode='w', encoding='utf-8') as file: + unique_imports = list(set(imports)) + models_imports = sorted( + [ + model + for model in unique_imports + if model.startswith( + 'from .models', + ) + ], + ) + typing_imports = sorted( + [ + model + for model in unique_imports + if model.startswith( + 'from typing import', + ) + ], + ) + types_imports = sorted( + [ + model + for model in unique_imports + if model.startswith( + 'from .types import', + ) + ], + ) + other_imports = sorted( + list( + set(unique_imports) + - set(typing_imports) + - set(types_imports) + - set(models_imports), + ), + ) + if models_imports: + models_imports_str = ( + 'from .models import (\n ' + + ',\n '.join( + [ + model.split( + 'from .models import ', + )[-1] + for model in models_imports + ], + ) + + '\n)' + ) + file.write(models_imports_str + '\n\n') + if typing_imports: + typing_imports_str = ( + 'from typing import (\n ' + + ',\n '.join( + [ + model.split( + 'from typing import ', + )[-1] + for model in typing_imports + ], + ) + + '\n)' + ) + file.write(typing_imports_str + '\n\n') + if types_imports: + types_imports_str = ( + 'from .types import (\n ' + + ',\n '.join( + [ + model.split( + 'from .types import ', + )[-1] + for model in types_imports + ], + ) + + '\n)' + ) + file.write(types_imports_str + '\n\n') + if other_imports: + file.write('\n'.join(other_imports) + '\n\n') + file.write(client_template.render(endpoints=endpoints, base_url=base_url)) + +cli_servis_path = os.path.join( + BASE_DIR, + PROJECT_NAME, + PACKAGE_NAME, + 'client_serv.py', +) + +logger_setup_path = os.path.join( + BASE_DIR, + PROJECT_NAME, + PACKAGE_NAME, + 'logger_setup.py', +) + +source_file_serv = os.path.join( + BASE_DIR, + 'client_servis.py', +) +shutil.copy(source_file_serv, cli_servis_path) + +source_file_log = os.path.join( + BASE_DIR, + 'logger_setup.py', +) +shutil.copy(source_file_log, logger_setup_path) + +try: + subprocess.run( + [ + 'black', + ( + os.path.join( + BASE_DIR, + PROJECT_NAME, + PACKAGE_NAME, + 'client.py', + ) + ), + '--line-length', + '79', + ], + check=True, + ) + subprocess.run( + [ + 'isort', + ( + os.path.join( + BASE_DIR, + PROJECT_NAME, + PACKAGE_NAME, + 'client.py', + ) + ), + ], + ) +except subprocess.CalledProcessError as e: + print('Автолинтер не сработал!', e) diff --git a/src/generator1/templates/__init__.py b/src/generator1/templates/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/generator1/templates/client.py.jinja b/src/generator1/templates/client.py.jinja new file mode 100644 index 0000000..b44908b --- /dev/null +++ b/src/generator1/templates/client.py.jinja @@ -0,0 +1,24 @@ +import datetime +import logging +import ssl +from typing import Any, Union, Optional, cast + +from attrs import define, field, evolve +import httpx +from .client_serv import AuthenticatedClient +from .logger_setup import setup_logging + +{% from "macros/client_macros.py.jinja" import httpx_args_docstring %} + + +class Pachca: + """Главный класс библиотеки.""" + + def __init__(self, token): + self.client = AuthenticatedClient(token=token) + self.logger = setup_logging(__name__) + {% if endpoints %} + {% for endpoint in endpoints %} + {{ endpoint | indent(4, first=Fasle) }} + {% endfor %} + {% endif %} diff --git a/src/generator1/templates/endpoint_macros.py.jinja b/src/generator1/templates/endpoint_macros.py.jinja new file mode 100644 index 0000000..8abe5ad --- /dev/null +++ b/src/generator1/templates/endpoint_macros.py.jinja @@ -0,0 +1,174 @@ +{% from "property_templates/helpers.jinja" import guarded_statement %} +{% from "helpers.jinja" import safe_docstring %} + +{% macro header_params(endpoint) %} +{% if endpoint.header_parameters or endpoint.bodies | length > 0 %} +headers: dict[str, Any] = {} +{% if endpoint.header_parameters %} + {% for parameter in endpoint.header_parameters %} + {% import "property_templates/" + parameter.template as param_template %} + {% if param_template.transform_header %} + {% set expression = param_template.transform_header(parameter.python_name) %} + {% else %} + {% set expression = parameter.python_name %} + {% endif %} + {% set statement = 'headers["' + parameter.name + '"]' + " = " + expression %} +{{ guarded_statement(parameter, parameter.python_name, statement) }} + {% endfor %} +{% endif %} +{% endif %} +{% endmacro %} + +{% macro cookie_params(endpoint) %} +{% if endpoint.cookie_parameters %} +cookies = {} + {% for parameter in endpoint.cookie_parameters %} + {% if parameter.required %} +cookies["{{ parameter.name}}"] = {{ parameter.python_name }} + {% else %} +if {{ parameter.python_name }} is not UNSET: + cookies["{{ parameter.name}}"] = {{ parameter.python_name }} + {% endif %} + + {% endfor %} +{% endif %} +{% endmacro %} + + +{% macro query_params(endpoint) %} +{% if endpoint.query_parameters %} +params: dict[str, Any] = {} + +{% for property in endpoint.query_parameters %} + {% set destination = property.python_name %} + {% import "property_templates/" + property.template as prop_template %} + {% if prop_template.transform %} + {% set destination = "json_" + property.python_name %} +{{ prop_template.transform(property, property.python_name, destination) }} + {% endif %} + {%- if not property.json_is_dict %} +params["{{ property.name }}"] = {{ destination }} + {% else %} +{{ guarded_statement(property, destination, "params.update(" + destination + ")") }} + {% endif %} + +{% endfor %} + +params = {k: v for k, v in params.items() if v is not UNSET and v is not None} +{% endif %} +{% endmacro %} + +{% macro body_to_kwarg(body, destination) %} +{% if body.body_type == "data" %} +{{ destination }} = body.to_dict() +{% elif body.body_type == "files"%} +{{ multipart_body(body, destination) }} +{% elif body.body_type == "json" %} +{{ json_body(body, destination) }} +{% elif body.body_type == "content" %} +{{ destination }} = body.payload +{% endif %} +{% endmacro %} + +{% macro json_body(body, destination) %} +{% set property = body.prop %} +{% import "property_templates/" + property.template as prop_template %} +{% if prop_template.transform %} +{{ prop_template.transform(property, property.python_name, destination) }} +{% else %} +{{ destination }} = {{ property.python_name }} +{% endif %} +{% endmacro %} + +{% macro multipart_body(body, destination) %} +{% set property = body.prop %} +{% import "property_templates/" + property.template as prop_template %} +{% if prop_template.transform_multipart_body %} +{{ prop_template.transform_multipart_body(property, property.python_name, destination) }} +{% endif %} +{% endmacro %} + +{# The all the kwargs passed into an endpoint (and variants thereof)) #} +{% macro arguments(endpoint, include_client=True) %} +{# path parameters #} +{% for parameter in endpoint.path_parameters %} +{{ parameter.to_string() }}, +{% endfor %} +{% if include_client or ((endpoint.list_all_parameters() | length) > (endpoint.path_parameters | length)) %} +{% endif %} +{# Any allowed bodies #} +{% if endpoint.bodies | length == 1 %} +body: {{ endpoint.bodies[0].prop.get_type_string() }}, +{% elif endpoint.bodies | length > 1 %} +body: Union[ + {% for body in endpoint.bodies %} + {{ body.prop.get_type_string() }}, + {% endfor %} +], +{% endif %} +{# query parameters #} +{% for parameter in endpoint.query_parameters %} +{{ parameter.to_string() }}, +{% endfor %} +{% for parameter in endpoint.header_parameters %} +{{ parameter.to_string() }}, +{% endfor %} +{# cookie parameters #} +{% for parameter in endpoint.cookie_parameters %} +{{ parameter.to_string() }}, +{% endfor %} +{% endmacro %} + +{# Just lists all kwargs to endpoints as name=name for passing to other functions #} +{% macro kwargs(endpoint, include_client=True) %} +{% for parameter in endpoint.path_parameters %} +{{ parameter.python_name }}={{ parameter.python_name }}, +{% endfor %} +{% if endpoint.bodies | length > 0 %} +body=body, +{% endif %} +{% for parameter in endpoint.query_parameters %} +{{ parameter.python_name }}={{ parameter.python_name }}, +{% endfor %} +{% for parameter in endpoint.header_parameters %} +{{ parameter.python_name }}={{ parameter.python_name }}, +{% endfor %} +{% for parameter in endpoint.cookie_parameters %} +{{ parameter.python_name }}={{ parameter.python_name }}, +{% endfor %} +{% endmacro %} + +{% macro docstring_content(endpoint, return_string, is_detailed) %} +{% if endpoint.summary %}{{ endpoint.summary | wordwrap(100)}} + +{% endif -%} +{%- if endpoint.description %} {{ endpoint.description | wordwrap(100) }} + +{% endif %} +{% if not endpoint.summary and not endpoint.description %} +{# Leave extra space so that Args or Returns isn't at the top #} + +{% endif %} +{% set all_parameters = endpoint.list_all_parameters() %} +{% if all_parameters %} +Args: + {% for parameter in all_parameters %} + {{ parameter.to_docstring() | wordwrap(90) | indent(8) }} + {% endfor %} + +{% endif %} +Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + +Returns: +{% if is_detailed %} + Response[{{ return_string }}] +{% else %} + {{ return_string }} +{% endif %} +{% endmacro %} + +{% macro docstring(endpoint, return_string, is_detailed) %} +{{ safe_docstring(docstring_content(endpoint, return_string, is_detailed)) }} +{% endmacro %} diff --git a/src/generator1/templates/endpoint_module.py.jinja b/src/generator1/templates/endpoint_module.py.jinja new file mode 100644 index 0000000..69c4c0d --- /dev/null +++ b/src/generator1/templates/endpoint_module.py.jinja @@ -0,0 +1,122 @@ +from http import HTTPStatus +from typing import Any, Optional, Union, cast + +import httpx + +import Errors.from_dict +from ...types import Response, UNSET +from ... import errors +from .client_serv import AuthenticatedClient +from .logger_setup import setup_logging + +{% for relative in endpoint.relative_imports | sort %} +{{ relative }} +{% endfor %} + +{% from "endpoint_macros.py.jinja" import header_params, cookie_params, query_params, + arguments, client, kwargs, parse_response, docstring, body_to_kwarg %} + +{% set return_string = endpoint.response_type() %} +{% set parsed_responses = (endpoint.responses | length > 0) and return_string != "Any" %} + +async def _get_kwargs_{{ endpoint.name }}(self, + {{ arguments(endpoint, include_client=False) | indent(4) }} +) -> dict[str, Any]: + {{ header_params(endpoint) | indent(4) }} + + {{ cookie_params(endpoint) | indent(4) }} + + {{ query_params(endpoint) | indent(4) }} + + _kwargs: dict[str, Any] = { + "method": "{{ endpoint.method }}", + {% if endpoint.path_parameters %} + "url": "{{ endpoint.path }}".format( + {%- for parameter in endpoint.path_parameters -%} + {{parameter.python_name}}={{parameter.python_name}}, + {%- endfor -%} + ), + {% else %} + "url": "{{ endpoint.path }}", + {% endif %} + {% if endpoint.query_parameters %} + "params": params, + {% endif %} + {% if endpoint.cookie_parameters %} + "cookies": cookies, + {% endif %} + } + +{% if endpoint.bodies | length > 1 %} +{% for body in endpoint.bodies %} + if isinstance(body, {{body.prop.get_type_string() }}): + {% set destination = "_" + body.body_type + "_body" %} + {{ body_to_kwarg(body, destination) | indent(8) }} + _kwargs["{{ body.body_type.value }}"] = {{ destination }} + headers["Content-Type"] = "{{ body.content_type }}" +{% endfor %} +{% elif endpoint.bodies | length == 1 %} +{% set body = endpoint.bodies[0] %} + {{ body_to_kwarg(body, "_body") | indent(4) }} + _kwargs["{{ body.body_type.value }}"] = _body + {% if body.content_type != "multipart/form-data" %}{# Need httpx to set the boundary automatically #} + headers["Content-Type"] = "{{ body.content_type }}" + {% endif %} +{% endif %} + +{% if endpoint.header_parameters or endpoint.bodies | length > 0 %} + _kwargs["headers"] = headers +{% endif %} + self.logger.debug("Создание параметров {{ endpoint.name }}.") + return _kwargs + + +async def _parse_response_{{ endpoint.name }}(self, response: httpx.Response) -> Optional[{{ return_string }}]: + {% for response in endpoint.responses %} + self.logger.info(f"Получен ответ с кодом: {response.status_code} для {{ endpoint.name }}") + if response.status_code == {{ response.status_code.value }}: + {% if parsed_responses %}{% import "property_templates/" + response.prop.template as prop_template %} + {% if prop_template.construct %} + {{ prop_template.construct(response.prop, response.source.attribute) | indent(8) }} + {% elif response.source.return_type == response.prop.get_type_string() %} + {{ response.prop.python_name }} = {{ response.source.attribute }} + {% else %} + {{ response.prop.python_name }} = cast({{ response.prop.get_type_string() }}, {{ response.source.attribute }}) + {% endif %} + return {{ response.prop.python_name }} + {% else %} + return None + {% endif %} + {% endfor %} + else: + response_error = Errors.from_dict(response.json()) + return response_error + + +async def _build_response_{{ endpoint.name }}(self, response: httpx.Response) -> Response[{{ return_string }}]: + self.logger.debug("Преобразование JSON в Python для {{ endpoint.name }}.") + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed= await self._parse_response_{{ endpoint.name }}(response=response), + ) + + +async def {{ endpoint.name }}(self, + {{ arguments(endpoint) | indent(4) }} +) -> Optional[{{ return_string }}]: + {{ docstring(endpoint, return_string, is_detailed=false) | indent(4) }} + self.logger.info("Начинаем создание ответа на запрос.") + + kwargs = await self._get_kwargs_{{ endpoint.name }}( + {{ kwargs(endpoint, include_client=False) }} + ) + + response = await ( + await self.client.get_async_httpx_client() + ).request(**kwargs) + + return (await self._build_response_{{ endpoint.name }}( + response=response)).parsed + diff --git a/src/generator1/templates/errors.py.jinja b/src/generator1/templates/errors.py.jinja new file mode 100644 index 0000000..4a2b798 --- /dev/null +++ b/src/generator1/templates/errors.py.jinja @@ -0,0 +1,17 @@ +"""Contains shared errors types that can be raised from API functions""" + + +class UnexpectedStatus(Exception): + """Raised by api functions when the response status an undocumented status and Client.raise_on_unexpected_status is True""" + + def __init__(self, status_code: int, content: bytes): + self.status_code = status_code + self.content = content + + super().__init__( + f"Unexpected status code: {status_code}\n\n" + f"Response content:\n{content.decode(errors='ignore')}" + ) + + +__all__ = ["UnexpectedStatus"] \ No newline at end of file diff --git a/src/generator1/templates/macros/__init__.py b/src/generator1/templates/macros/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/generator1/templates/macros/client_macros.py.jinja b/src/generator1/templates/macros/client_macros.py.jinja new file mode 100644 index 0000000..35f0b76 --- /dev/null +++ b/src/generator1/templates/macros/client_macros.py.jinja @@ -0,0 +1,25 @@ +{% macro httpx_args_docstring() %} + The following are accepted as keyword arguments and will be used to construct httpx Clients internally: + + ``base_url``: The base URL for the API, all requests are made to a relative path to this URL + + ``cookies``: A dictionary of cookies to be sent with every request + + ``headers``: A dictionary of headers to be sent with every request + + ``timeout``: The maximum amount of a time a request can take. API functions will raise + httpx.TimeoutException if this is exceeded. + + ``verify_ssl``: Whether or not to verify the SSL certificate of the API server. This should be True in production, + but can be set to False for testing purposes. + + ``follow_redirects``: Whether or not to follow redirects. Default value is False. + + ``httpx_args``: A dictionary of additional arguments to be passed to the ``httpx.Client`` and ``httpx.AsyncClient`` constructor. + + ``token``: The token to use for authentication + + ``prefix``: The prefix to use for the Authorization header + + ``auth_header_name``: The name of the Authorization header +{% endmacro %} \ No newline at end of file diff --git a/src/generator1/templates/model.py.jinja b/src/generator1/templates/model.py.jinja new file mode 100644 index 0000000..f535384 --- /dev/null +++ b/src/generator1/templates/model.py.jinja @@ -0,0 +1,181 @@ +from typing import Any, TypeVar, Optional, BinaryIO, TextIO, TYPE_CHECKING, Dict + +from pydantic import BaseModel, Field +{% if model.is_multipart_body %} +import json +{% endif %} + +from ..types import UNSET, Unset + +{% for relative in model.relative_imports | sort %} +{{ relative }} +{% endfor %} + +{% for lazy_import in model.lazy_imports %} +{% if loop.first %} +if TYPE_CHECKING: +{% endif %} + {{ lazy_import }} +{% endfor %} + +{% if model.additional_properties %} +{% set additional_property_type = 'Any' if model.additional_properties == True else model.additional_properties.get_type_string(quoted=not model.additional_properties.is_base_type) %} +{% endif %} + +{% set class_name = model.class_info.name %} +{% set module_name = model.class_info.module_name %} + +{% from "helpers.jinja" import safe_docstring %} + +T = TypeVar("T", bound="{{ class_name }}") + +{% macro class_docstring_content(model) %} + {% if model.title %}{{ model.title | wordwrap(116) }} + + {% endif -%} + {%- if model.description %}{{ model.description | wordwrap(116) }} + + {% endif %} + {% if not model.title and not model.description %} + {# Leave extra space so that a section doesn't start on the first line #} + + {% endif %} + {% if model.example %} + Example: + {{ model.example | string | wordwrap(112) | indent(12) }} + + {% endif %} + {% if model.required_properties or model.optional_properties %} + Attributes: + {% for property in model.required_properties + model.optional_properties %} + {{ property.to_docstring() | wordwrap(112) | indent(12) }} + {% endfor %}{% endif %} +{% endmacro %} + +class {{ class_name }}(BaseModel): + {{ safe_docstring(class_docstring_content(model)) | indent(4) }} + + {% for property in model.required_properties + model.optional_properties %} + {{ property.to_string() }} + {% endfor %} + {% if model.additional_properties %} + additional_properties: dict[str, {{ additional_property_type }}] = Field(default_factory=dict) + {% endif %} + class Config: + arbitrary_types_allowed = True + +{% macro _to_dict(multipart=False) %} +{% for property in model.required_properties + model.optional_properties %} +{% import "property_templates/" + property.template as prop_template %} +{% if multipart %} +{{ prop_template.transform_multipart(property, "self." + property.python_name, property.python_name) }} +{% elif prop_template.transform %} +{{ prop_template.transform(property=property, source="self." + property.python_name, destination=property.python_name) }} +{% else %} +{{ property.python_name }} = self.{{ property.python_name }} +{% endif %} + +{% endfor %} + +field_dict: dict[str, Any] = {} +{% if model.additional_properties %} +{% import "property_templates/" + model.additional_properties.template as prop_template %} +{% if multipart %} +for prop_name, prop in self.additional_properties.items(): + {{ prop_template.transform_multipart(model.additional_properties, "prop", "field_dict[prop_name]") | indent(4) }} +{% elif prop_template.transform %} +for prop_name, prop in self.additional_properties.items(): + {{ prop_template.transform(model.additional_properties, "prop", "field_dict[prop_name]", declare_type=false) | indent(4) }} +{% else %} +field_dict.update(self.additional_properties) +{% endif %} +{% endif %} + +{% if model.required_properties | length > 0 or model.optional_properties | length > 0 %} +field_dict.update({ + {% for property in model.required_properties + model.optional_properties %} + {% if property.required %} + "{{ property.name }}": {{ property.python_name }}, + {% endif %} + {% endfor %} +}) +{% endif %} +{% for property in model.optional_properties %} +{% if not property.required %} +if {{ property.python_name }} is not UNSET: + field_dict["{{ property.name }}"] = {{ property.python_name }} +{% endif %} +{% endfor %} + +return field_dict +{% endmacro %} + + def to_dict(self) -> dict[str, Any]: + {% for lazy_import in model.lazy_imports %} + {{ lazy_import }} + {% endfor %} + {{ _to_dict() | indent(8) }} + +{% if model.is_multipart_body %} + def to_multipart(self) -> dict: + {{ _to_dict(multipart=True) | indent(8) }} +{% endif %} + + @classmethod + def from_dict(cls, src_dict: dict) -> "{{ class_name }}": + {% for lazy_import in model.lazy_imports %} + {{ lazy_import }} + {% endfor %} +{% if (model.required_properties or model.optional_properties or model.additional_properties) %} + d = src_dict.copy() +{% for property in model.required_properties + model.optional_properties %} + {% if property.required %} + {% set property_source = 'd.pop("' + property.name + '")' %} + {% else %} + {% set property_source = 'd.pop("' + property.name + '", UNSET)' %} + {% endif %} + {% import "property_templates/" + property.template as prop_template %} + {% if prop_template.construct %} + {{ prop_template.construct(property, property_source) | indent(8) }} + {% else %} + {{ property.python_name }} = {{ property_source }} + {% endif %} + +{% endfor %} +{% endif %} + {{ module_name }} = cls( +{% for property in model.required_properties + model.optional_properties %} + {{ property.python_name }}={{ property.python_name }}, +{% endfor %} + ) + +{% if model.additional_properties %} + {% if model.additional_properties.template %}{# Can be a bool instead of an object #} + {% import "property_templates/" + model.additional_properties.template as prop_template %} + +{% if model.additional_properties.lazy_imports %} + {% for lazy_import in model.additional_properties.lazy_imports %} + {{ lazy_import }} + {% endfor %} +{% endif %} + {% else %} + {% set prop_template = None %} + {% endif %} + {% if prop_template and prop_template.construct %} + additional_properties = {} + for prop_name, prop_dict in d.items(): + {{ prop_template.construct(model.additional_properties, "prop_dict") | indent(12) }} + additional_properties[prop_name] = {{ model.additional_properties.python_name }} + + {{ module_name }}.additional_properties = additional_properties + {% else %} + {{ module_name }}.additional_properties = d + {% endif %} +{% endif %} + return {{ module_name }} + + {% if model.additional_properties %} + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + {% endif %} diff --git a/src/generator1/templates/package_init.py.jinja b/src/generator1/templates/package_init.py.jinja new file mode 100644 index 0000000..8139c11 --- /dev/null +++ b/src/generator1/templates/package_init.py.jinja @@ -0,0 +1,6 @@ +{% from "helpers.jinja" import safe_docstring %} + +{{ safe_docstring(package_description) }} +from .client import AuthenticatedClient, Pachca + +__all__ = ("AuthenticatedClient", "Pachca") diff --git a/src/generator2/.env.example b/src/generator2/.env.example new file mode 100644 index 0000000..0f17c0f --- /dev/null +++ b/src/generator2/.env.example @@ -0,0 +1 @@ +TOKEN=ваштокен \ No newline at end of file diff --git a/src/generator2/README.md b/src/generator2/README.md new file mode 100644 index 0000000..68b129b --- /dev/null +++ b/src/generator2/README.md @@ -0,0 +1,32 @@ +### Инструкция (работать в папке `src` при активированном venv): + +1. Создать файл .env в директории generator2, с токеном для работы с API пачки. Пример файла .env.example: + +``` +TOKEN=ваштокен +``` + +2. Создать и активировать виртуальное окружение, установить зависимости: + +``` +python -m venv venv +source venv/sctipts/activate +pip install -r requirements.txt +``` + +3. Важно - текущая версия работает только через модули python: python -m generator2.имяфайла + +4. Запустить единый генератор клиента: + +``` +python -m generator2.generator_starter +``` + +5. Дождаться генерации кода, в результате будут получены (обновлены) модели и эндпоинты. + + +6. Запустить скрипт-пример запроса + +``` +python -m generator2.generator2_full.pachca +``` diff --git a/src/generator2/__init__.py b/src/generator2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/generator2/generate_pydantic_model.py b/src/generator2/generate_pydantic_model.py new file mode 100644 index 0000000..4a63c7a --- /dev/null +++ b/src/generator2/generate_pydantic_model.py @@ -0,0 +1,134 @@ +from .schema_link_processor import load_schema, new_replace_ref_with_schema +from .services.constants import ENUM_TYPES, PYTHON_TYPES +from .services.file_writer import write_to_file + + +def check_error_field(model_name: str, field_name: str, field_type: str): + """Заменяет для специфичных полей тайпхинт на Any.""" + if model_name != 'Errors': + return field_type + if field_name != 'value': + return field_type + return 'Any' + + +def create_model(name: str, fields: list) -> str: + """Генерирует код модели Pydantic.""" + model_code = f'class {name}(BaseModel):\n' + alias = '' + for field in fields: + field_name = field[0] + if '-' in field[0]: + alias = f', alilas=\'{field[0]}\'' + field_name = field_name.replace('-', '_') + else: + alias = '' + + if field[2]: + model_code += ( + f' {field_name}: ' + f'{check_error_field(name, field_name, field[1])} ' + f'= Field(..., description=\'{field[3]}\'{alias})\n' + ) + else: + model_code += ( + f' {field_name}: ' + f'Optional[{check_error_field(name, field_name, field[1])}] ' + f'= Field(None, description=\'{field[3]}\'{alias})\n' + ) + return model_code + + +def create_enum(name: str, enum_type: str, fields: list): + """Создает классы Enum.""" + enum_class_code = f'class enum_{name}({ENUM_TYPES.get(enum_type)}):\n' + if enum_type == 'string': + for field in fields: + enum_class_code += f' {field} = \'{field}\'\n' + if enum_type == 'integer': + for field in fields: + enum_class_code += f' {name}_{field} = {field}\n' + return enum_class_code + + +def look_into_schema_new(schema: dict, file_name: str): + """Разбирает схемы и вызывает генерацию моделей.""" + schema = new_replace_ref_with_schema(schema) + list_of_properties = [] + nested_properties = [] + enum_properties = [] + required_properties = [] + upper_schema_name = list(schema.keys())[0] + inner_schema = ( + schema.get(upper_schema_name).get('properties') + or schema.get(upper_schema_name).get('items', {}).get('properties') + or schema.get(upper_schema_name).get( + 'items',{}).get('items', {}).get('properties')) + required_properties = schema.get(upper_schema_name).get('required', []) + for property in inner_schema: + inner_body = new_replace_ref_with_schema(inner_schema.get(property)) + if 'enum' in inner_body: + enum_properties.append( + (property, inner_body.get('type'), inner_body['enum']), + ) + inner_schema[property] = inner_body + + if inner_body.get('items', {}).get('$ref'): + inner_schema[property] = load_schema(inner_body.get('items', {}).get('$ref')) + description = inner_body.get('description', 'No docstring provided') + property_type = ( + PYTHON_TYPES.get(inner_body.get('type')) or inner_body.get('type')) + if property_type is None: + property_type = ( + [item.get('type') for item + in inner_body.get('allOf') + if '$ref' not in item][0] + ) + if 'enum' in inner_body: + property_type = f'enum_{property}' + if property_type == 'object': + property_type = property.capitalize() + if property_type == 'array': + list_type = inner_body.get("items").get("type") + list_type = PYTHON_TYPES.get(list_type, list_type) + if list_type is None: + list_type = next(iter(inner_schema.keys())).capitalize() + if list_type == 'object' or list_type == 'array': + list_type = property.capitalize() + if inner_body.get("items").get("items"): + list_type = f'List[{list_type}]' + property_type = (f'List[{list_type}]') + if property_type == 'Payload': + property_type = 'Dict' + list_of_properties.append( + ( + property, + property_type, + True if property in required_properties else False, + description.replace('\n', ''), + ), + ) + if ('allOf' in inner_body + or inner_body.get('type') == 'object' + and inner_body.get('properties') + or inner_body.get('type') == 'array' + and inner_body.get('items', {}).get('properties') + or inner_body.get('items', {}).get('$ref') + or inner_body.get('items', {}).get('items')): + nested_properties.append(property) + + for nested in nested_properties: + nested_obj = new_replace_ref_with_schema(inner_schema) + if nested in nested_obj: + look_into_schema_new( + {nested.capitalize(): nested_obj[nested]}, + file_name=file_name + ) + else: + look_into_schema_new(nested_obj, file_name=file_name) + + for enum_class in enum_properties: + write_to_file(file_name, create_enum(*enum_class) + '\n\n') + write_to_file( + file_name, + create_model(upper_schema_name, list_of_properties) + '\n\n') diff --git a/src/generator2/generator2_full/__init__.py b/src/generator2/generator2_full/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/generator2/generator2_full/bot.py b/src/generator2/generator2_full/bot.py new file mode 100644 index 0000000..c66f838 --- /dev/null +++ b/src/generator2/generator2_full/bot.py @@ -0,0 +1,36 @@ +import httpx + +from .constants import PARAM_NAME_SORT, PARAM_NAME_SORT_FIELD, TOKEN_TYPE, URL +from .request_methods import RequestMethods + + +class Bot(RequestMethods): + base_url = URL + token_type = TOKEN_TYPE + + def __init__(self, token): + self.token = f'{self.token_type} {token}' + + async def get_client(self): + return httpx.AsyncClient( + base_url=self.base_url, + headers={'Authorization': self.token}, + ) + + async def format_url( + self, + url_template: str, + path_param: dict[str, int] = None, + ): + return url_template.format(**path_param) + + async def filter_query_params(self, **kwargs): + if PARAM_NAME_SORT in kwargs or PARAM_NAME_SORT_FIELD in kwargs: + sort = kwargs.pop(PARAM_NAME_SORT) + sort_field = kwargs.pop(PARAM_NAME_SORT_FIELD) + kwargs[f'sort[{sort_field}]'] = sort + + return { + str(key): value + for key, value in kwargs.items() if value is not None + } diff --git a/src/generator2/generator2_full/logger_setup.py b/src/generator2/generator2_full/logger_setup.py new file mode 100644 index 0000000..5626a8f --- /dev/null +++ b/src/generator2/generator2_full/logger_setup.py @@ -0,0 +1,23 @@ +import logging +import os +from logging.handlers import RotatingFileHandler +from pathlib import Path + +from .constants import BACKUP_COUNT, LOG_FILE_NAME, MAX_FILE_SIZE + + +def setup_logging(logger_name: str) -> logging.Logger: + logger = logging.getLogger(logger_name) + logger.setLevel(logging.DEBUG) + file_handler = RotatingFileHandler( + Path(os.path.dirname(os.path.abspath( + __file__))).parent / LOG_FILE_NAME, + maxBytes=MAX_FILE_SIZE, + backupCount=BACKUP_COUNT, + encoding='utf-8' + ) + formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s') + file_handler.setFormatter(formatter) + logger.addHandler(file_handler) + return logger diff --git a/src/generator2/generator2_full/pachca.py b/src/generator2/generator2_full/pachca.py new file mode 100644 index 0000000..677c4e4 --- /dev/null +++ b/src/generator2/generator2_full/pachca.py @@ -0,0 +1,214 @@ +import asyncio +import os + +from dotenv import load_dotenv + +from .bot import Bot +from .logger_setup import setup_logging +from .models.models_reqBod_createChat import Createchat +from .models.models_reqBod_createMessage import Createmessage, Message +from .models.models_reqBod_createTask import Createtask +from .models.models_reqBod_editMessage import Editmessage +from .models.models_reqBod_postMembersToChats import Postmemberstochats +from .models.models_reqBod_postMessageReactions import Postmessagereactions +from .models.models_reqBod_putStatus import Putstatus + +load_dotenv() + +logger = setup_logging('pachca_log') + +if __name__ == '__main__': + pachca = Bot(token=f'{os.environ.get("TOKEN", "LOOKUP FAILED!")}') + + message_test = Createmessage(message=Message( + entity_type="discussion", + entity_id=17579010, + content=("Вчера мы продали 756 футболок " + "(что на 10% больше, чем в прошлое воскресенье)"), + )) + async def run_pachca(): + + # Получение common methods + logger.debug(f'get_common_methods: {await pachca.get_common_methods()}') + # Создание беседы. + created_chat = await pachca.create_chat( + data=Createchat( + chat={ + 'name': 'Тестовая беседа 31', + 'channel': False, + 'public': True, + }, + ), + ) + logger.debug(f'create_chat: {created_chat}') + + # Получение всех бесед данного рабочего пространства + all_chats = await pachca.get_chats() + logger.debug(f'get_chats: {all_chats}') + + # Получение конкретной беседы по id + chat = await pachca.get_chat(created_chat.data.id) + logger.debug(f'get_chat: {chat}') + + # Подключение пользователей к беседе. + response_post_members = await pachca.post_members_to_chats( + id=chat.data.id, + data=Postmemberstochats( + member_ids=[518863], + silent=False, + ), + ) + logger.debug(f'post_members_to_chats: {response_post_members}') + + # Получение всех бесед данного рабочего пространства (на одну больше) + all_chats = await pachca.get_chats() + logger.debug(f'get_chats: {all_chats}') + + # Создание нового сообщения в беседе. + response_create_message = await pachca.create_message( + data=Createmessage( + message={ + 'content': f'Запощеное сообщение в беседу {chat.data.id}', + 'entity_type': 'discussion', + 'entity_id': chat.data.id, + }, + ), + ) + logger.debug(f'create_message: {response_create_message}') + + # Получение списка тегов + logger.debug(f'get_tags: {await pachca.get_tags()}') + + # Создание треда к конкретному сообщению с id. + response_create_thread = await pachca.create_thread( + id=response_create_message.data.id, + ) + logger.debug(f'create_thread: {response_create_thread}') + + # Создание комментария в треде другого сообщения + response_create_message_in_thread = await pachca.create_message( + data=Createmessage( + message={ + 'content': ( + 'Новое сообщение в тред' + f'{response_create_thread.data.id}' + ), + 'entity_type': 'thread', + 'entity_id': response_create_thread.data.id, + }, + ), + ) + logger.debug(f'create_message_in_thread: {response_create_message_in_thread}') + + # Получение списка всех сообщений конкретного треда или беседы с пагинацией + response_list_messages = await pachca.get_list_message( + chat_id=response_create_thread.data.chat_id, per=10, page=1, + ) + logger.debug(f'get_list_message: {response_list_messages}') + + # Получение конкретного сообщения по id + response_get_message = await pachca.get_message( + id=response_create_message_in_thread.data.id, + ) + logger.debug(f'get_message: {response_get_message}') + + # Редактирование конкретного сообщения по его id + response_edit_message = await pachca.edit_message( + id=response_create_message_in_thread.data.id, + data=Editmessage(message={ + 'content': ('РЕДАКТИРОВАНИЕ СООБЩЕНИЯ ' + f'{response_create_message_in_thread.data.id} ' + 'ЧЕРЕЗ АПИ СОВЕРШЕНО УСПЕШНО'), + }), + ) + logger.debug(f'edit_message: {response_edit_message}') + + # Добавление реакции к сообщению с id + response_add_reaction = await pachca.post_message_reactions( + id=response_edit_message.data.id, + data=Postmessagereactions(code='👍'), + ) + response_add_reaction = await pachca.post_message_reactions( + id=response_edit_message.data.id, + data=Postmessagereactions(code='😱'), + ) + logger.debug(f'post_message_reactions: {response_add_reaction}') + + # Получение списка всех реакций конкретного сообщения. + response_message_reactions = await pachca.get_message_reactions( + id=response_edit_message.data.id, + ) + logger.debug(f'get_message_reactions: {response_message_reactions}') + + # Удаение конкретной реакции у конкретного сообщения. + response_delete_reaction = await pachca.delete_message_reactions( + id=response_edit_message.data.id, + code='😱', + ) + logger.debug(f'delete_message_reactions: {response_delete_reaction}') + + # Создание напоминания + response_create_task = await pachca.create_task( + data=Createtask( + task={ + 'kind': 'reminder', + 'content': 'дата в прошлом', + 'priority': 3, + 'due_at': '2025-12-24T18:00:29.000Z', + }, + ), + ) + logger.debug(f'create_task: {response_create_task}') + + # Метод для того чтобы покинуть конкретный чат (беседу) + response_leave_chat = await pachca.leave_chat( + id=created_chat.data.id + ) + logger.debug(f'leave_chat: {response_leave_chat}') + + response_get_users = await pachca.get_employees() + logger.debug(f'One user from list: {response_get_users.data[0].id}') + + # Получить конкретного сотрудника рабочего простанства. + response_get_user = await pachca.get_employee( + response_get_users.data[0].id, + ) + logger.debug(f'get_employee: {response_get_user}') + + # Добавить статус текущему пользователю, обладателю токена. + response_put_status = await pachca.put_status( + data=Putstatus( + status={ + 'emoji': '😱', + 'expires_at': '2025-12-24T17:47:25.000Z', + 'title': 'Статус из клиента АПИ', + }, + ), + ) + logger.debug(f'put_status: {response_put_status}') + + # Получить статус текущего пользователя, обладателя токена. + response_get_status = await pachca.get_status() + logger.debug(f'get_status: {response_get_status}') + + # Удалить статус текущему пользователю, обладателю токена. + response_del_status = await pachca.del_status() + logger.debug(f'del_status: {response_del_status}') + + # Получения подписи и ключа для загрузки файла + response_get_uploads = await pachca.get_uploads() + logger.debug(f'del_status: {response_get_uploads}') + + response_get_tags_employees = await pachca.get_tags_employees( + id=1234 + ) + logger.debug(f'get_tags_employees: {response_get_tags_employees}') + + response_get_tag = await pachca.get_tag( + id=1234 + ) + logger.debug(f'get_tag: {response_get_tag}') + + logger.debug('*' * 60) + + asyncio.run(run_pachca()) diff --git a/src/generator2/generator_starter.py b/src/generator2/generator_starter.py new file mode 100644 index 0000000..3f7e926 --- /dev/null +++ b/src/generator2/generator_starter.py @@ -0,0 +1,68 @@ +import os +import subprocess + +from .request_methods_generator import generate +from .services.constants import GENERATED_CLIENT_FOLDER +from .services.logger_setup import setup_logging +from .yaml_processor import process_endpoints + + +def generate_client(): + logger = setup_logging('client_generator') + + try: + process_endpoints() + except Exception as ex: + logger.critical('Unable to create pydantic models! ' + f'Error: {ex}') + + try: + generate() + except Exception as ex: + logger.critical('Unable to create endpoints! ' + f'Error: {ex}') + + dir_path = ( + os.path.dirname(os.path.realpath(__file__)) + + f'/{GENERATED_CLIENT_FOLDER}' + ) + logger.debug(f'Working directory: {dir_path}') + try: + subprocess.run( + ['black', f'{dir_path}/models', '--line-length', '79'], check=True + ) + subprocess.run( + [ + 'black', + f'{dir_path}/request_methods.py', + '--line-length', + '79' + ], + check=True + ) + logger.debug('Finished code formatting!') + subprocess.run( + [ + 'ruff', + 'check', + f'{dir_path}/models', + '--fix', + '--silent', + ] + ) + subprocess.run( + [ + 'ruff', + 'check', + f'{dir_path}/request_methods.py', + '--fix', + '--silent' + ] + ) + logger.debug('Finished code fix with ruff!') + except Exception as ex: + logger.error(f'Unable to format or fix code: {ex}') + + +if __name__ == '__main__': + generate_client() diff --git a/src/generator2/openapi.yaml b/src/generator2/openapi.yaml new file mode 100644 index 0000000..7425458 --- /dev/null +++ b/src/generator2/openapi.yaml @@ -0,0 +1,2361 @@ +openapi: 3.0.3 +info: + title: PachcaAPI - OpenAPI 3.0 + description: Документация к открытому API пачки + version: 3.0.3 +servers: + - url: https://api.pachca.com/api/shared/v1 + +tags: + - name: common methods + description: Everything about common methods + - name: employees + description: Everything about employees + - name: status + description: Everything about + status + - name: tags + description: Everything about + tags + - name: chats and channels + description: Everything about + chats and channels + - name: talk and channel participants + description: Everything about + talk and channel participants + - name: comments + description: Everything about + comments + - name: messages + description: Everything about + messages + - name: reactions to messages + description: Everything about + reactions to messages + - name: reminders + description: Everything about + reminders + +paths: + /custom_properties: + get: + tags: + - common methods + summary: получение списка актульных полей сущности + description: | + Метод для получения актуального списка дополнительных полей участников и напоминаний в вашей компании. Тело запроса отсутствует, параметры передаются в URL (например, /custom_properties?entity_type=User) + operationId: getCommonMethods + parameters: + - name: entity_type + in: query + description: Тип сущности + required: true + schema: + type: string + enum: + - User + - Task + responses: + '200': + description: Успешный запрос + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/CommonMethods' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + type: object + properties: + errors: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Поле не может быть пустым + value: + errors: + - key: string + value: string + message: message + code: blank + payload: {} + inclusion: + description: Поле имеет непредусмотренное значение + value: + errors: + - key: string + value: string + message: message + code: inclusion + payload: {} + /uploads: + post: + tags: + - common methods + summary: получения подписи и ключа для загрузки файла + description: | + Данный метод необходимо использовать для загрузки каждого файла. + + Данный метод позволяет получить уникальный набор параметров для загрузки файла. Параметры запроса отсутствуют. + operationId: getUploads + responses: + '200': + description: Успешный ответ. + content: + application/json: + schema: + $ref: '#/components/schemas/FileResponse' + /direct_url: + post: + tags: + - common methods + summary: (полученный в ответе на запрос /uploads) загрузка файла + description: | + Данный метод не требует авторизации. + + Получив все параметры, вам необходимо сделать POST запрос в формате multipart/form-data на адрес, который был указан в поле direct_url, отправив полученные параметры и сам файл. + operationId: getDirectUrl + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/DirectResponse' + responses: + '204': + description: При безошибочном выполнении запроса тело ответа отсутствует. + /users: + get: + tags: + - employees + summary: получение актуального списка всех сотрудников компании + description: | + Метод для получения актуального списка сотрудников вашей компании. + Тело запроса отсутствует, параметры передаются в URL (например, /users?per=50&page=2&query=example.com) + operationId: getEmployees + parameters: + - $ref: '#/components/parameters/perParameter50' + - $ref: '#/components/parameters/pageParameter' + - $ref: '#/components/parameters/queryParameter' + responses: + '200': + description: Успешный запрос + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Employee' + /users/{id}: + get: + tags: + - employees + summary: получение информации о сотруднике + description: | + Метод для получения информации о сотруднике. + Для получения сотрудника вам необходимо знать его id и указать его в URL запроса. + operationId: getEmployee + parameters: + - name: id + in: path + description: Уникальный идентификатор сотрудкика + required: true + schema: + type: integer + responses: + '200': + description: Успешный запрос + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Employee' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + type: object + properties: + errors: + $ref: '#/components/schemas/Errors' + examples: + not_found: + description: Поле не может быть пустым + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + /profile/status: + get: + tags: + - status + summary: получение информации о своем статусе + description: | + Метод для получения информации о своем статусе. Параметры запроса отсутствуют. + operationId: getStatus + responses: + '200': + description: Успешный запрос + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Status' + put: + tags: + - status + summary: новый статус + description: | + Метод для установки себе нового статуса. + operationId: putStatus + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + status: + $ref: '#/components/schemas/Status' + responses: + '201': + description: Объект создан + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Status' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + type: object + properties: + errors: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Обязательное поле (не может быть пустым) + value: + errors: + - key: string + value: string + message: message + code: blank + payload: {} + too_long: + description: Слишком длинное значение (пояснения вы получите в поле message) + value: + errors: + - key: string + value: string + message: message + code: too_long + payload: {} + invalid: + description: Поле не соответствует правилам (пояснения вы получите в поле message) + value: + errors: + - key: string + value: string + message: message + code: invalid + payload: {} + wrong_emoji: + description: Emoji статуса не может содержать значения отличные от Emoji символа + value: + errors: + - key: string + value: string + message: message + code: wrong_emoji + payload: {} + delete: + tags: + - status + summary: удаление своего статуса + description: | + Метод для удаления своего статуса. Параметры запроса отсутствуют. + operationId: delStatus + responses: + '204': + description: При безошибочном выполнении запроса тело ответа отсутствует + content: {} + /group_tags/{id}: + get: + tags: + - tags + summary: получение информации о теге + description: | + Метод для получения информации о теге. Названия тегов являются уникальными в компании. + + Для получения тега вам необходимо знать его id и указать его в URL запроса. Параметры запроса отсутствуют + operationId: getTag + parameters: + - name: id + in: path + description: Уникальный идентификатор тега + required: true + schema: + type: integer + responses: + '200': + description: Успешный запрос + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Tag' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + type: object + properties: + errors: + $ref: '#/components/schemas/Errors' + examples: + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + /group_tags: + get: + tags: + - tags + summary: получение актуального списка тегов сотрудников + description: | + Метод для получения актуального списка тегов сотрудников. + + Названия тегов являются уникальными в компании. Тело запроса отсутствует, параметры передаются в URL (например, /group_tags?per=10&page=2) + operationId: getTags + parameters: + - $ref: '#/components/parameters/perParameter50' + - $ref: '#/components/parameters/pageParameter' + responses: + '200': + description: Успешный запрос + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Tag' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + type: object + properties: + errors: + $ref: '#/components/schemas/Errors' + examples: + exclusion: + description: Поле имеет непредусмотренное значение + value: + errors: + - key: string + value: string + message: message + code: exclusion + payload: {} + /group_tags/{id}/users: + get: + tags: + - tags + operationId: getTagsEmployees + summary: получение актуального списка сотрудников тега + description: | + Метод для получения актуального списка сотрудников тега. + + Идентификатор тега, список сотрудников которого необходимо получить, и другие параметры передаются в URL (например, /group_tags/877650/users?per=3&page=2) + parameters: + - name: id + in: path + description: Уникальный идентификатор сотрудкика + required: true + schema: + type: integer + - $ref: '#/components/parameters/perParameter25' + - $ref: '#/components/parameters/pageParameter' + responses: + '200': + description: Успешный запрос + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/BaseEmployee' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + type: object + properties: + errors: + $ref: '#/components/schemas/Errors' + examples: + exclusion: + description: Поле имеет непредусмотренное значение + value: + errors: + - key: string + value: string + message: message + code: exclusion + payload: {} + /chats: + post: + tags: + - chats and channels + operationId: createChat + summary: создание новой беседы или канала + description: | + Метод для создания новой беседы или нового канала. + При создании беседы или канала вы автоматически становитесь участником. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + chat: + $ref: '#/components/schemas/BaseChat' + responses: + '201': + description: Запрос отработал успешно, сущность создана + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Chat' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + type: object + properties: + errors: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Обязательное поле (не может быть пустым) + value: + errors: + - key: name + value: '' + message: message + code: blank + payload: {} + too_long: + description: Слишком длинное значение (пояснения вы получите в поле message) + value: + errors: + - key: name + value: long_name + message: message + code: too_long + payload: {} + invalid: + description: Поле не соответствует правилам (пояснения вы получите в поле message) + value: + errors: + - key: name + value: 1234 + message: message + code: invalid + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + '422': + description: С запросом все хорошо, но правила сервиса не позволяют его обработать (например, при попытке создания контакта с уже существующим номером телефона в базе) + content: + application/json: + schema: + type: object + properties: + errors: + $ref: '#/components/schemas/Errors' + examples: + invalid: + description: Поле имеет неверный формат (идентификатор поля вы получите в поле payload) + value: + errors: + - key: name + value: name + message: message + code: invalid + payload: {} + get: + tags: + - chats and channels + operationId: getChats + summary: получение списка бесед и каналов + description: | + Метод для получения списка бесед и каналов по заданным параметрам. + + Тело запроса отсутствует, параметры передаются в URL (например, /chats?per=2&sort[id]=desc) + parameters: + - $ref: '#/components/parameters/sortParameter' + - $ref: '#/components/parameters/perParameter25' + - $ref: '#/components/parameters/pageParameter' + - $ref: '#/components/parameters/availabilityParameter' + - $ref: '#/components/parameters/last_message_at_afterParameter' + - $ref: '#/components/parameters/last_message_at_beforeParameter' + responses: + '200': + description: Запрос отработал как положено, без ошибок + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Chat' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + type: object + properties: + errors: + $ref: '#/components/schemas/Errors' + examples: + too_long: + description: Слишком длинное значение (пояснения вы получите в поле message) + value: + errors: + - key: name + value: long_name + message: message + code: too_long + payload: {} + invalid: + description: Поле не соответствует правилам (пояснения вы получите в поле message) + value: + errors: + - key: name + value: 1234 + message: message + code: invalid + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + '422': + description: С запросом все хорошо, но правила сервиса не позволяют его обработать (например, при попытке создания контакта с уже существующим номером телефона в базе) + content: + application/json: + schema: + type: object + properties: + errors: + $ref: '#/components/schemas/Errors' + examples: + invalid: + description: Поле имеет неверный формат (идентификатор поля вы получите в поле payload) + value: + errors: + - key: name + value: name + message: message + code: invalid + payload: {} + /chats/{id}: + get: + tags: + - chats and channels + operationId: getChat + summary: получение информации о беседе или канале + description: | + Получения информации о беседе или канале. + Для получения беседы или канала вам необходимо знать её id и указать его в URL запроса. + parameters: + - name: id + description: Идентификатор беседы или канала + in: path + required: true + schema: + type: integer + responses: + '200': + description: Успешный запрос + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Chat' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + type: object + properties: + errors: + $ref: '#/components/schemas/Errors' + examples: + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + /chats/{id}/members: + post: + tags: + - talk and channel participants + summary: добавление пользователей в состав участников + description: | + Метод для добавления пользователей в состав участников беседы или канала. + operationId: postMembersToChats + parameters: + - name: id + in: path + description: Идентификатор беседы/канала + required: true + schema: + type: integer + format: int64 + example: 533 + requestBody: + description: | + Идентификатор беседы/канала передаётся в URL (например, /chats/553/members) + Массив идентификаторов пользователей, которые станут участниками, передается в теле запроса + content: + application/json: + schema: + $ref: '#/components/schemas/MembersChat' + responses: + '201': + description: Пользователи добавлены + '400': + description: Пояснения ошибки + content: + application/json: + schema: + type: object + properties: + errors: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Обязательное поле (не может быть пустым) + value: + errors: + - key: name + value: '' + message: message + code: blank + payload: {} + too_long: + description: Слишком длинное значение (пояснения вы получите в поле message) + value: + errors: + - key: name + value: long_name + message: message + code: too_long + payload: {} + invalid: + description: Поле не соответствует правилам (пояснения вы получите в поле message) + value: + errors: + - key: name + value: 1234 + message: message + code: invalid + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + '422': + description: С запросом все хорошо, но правила сервиса не позволяют его обработать (например, при попытке создания контакта с уже существующим номером телефона в базе) + content: + application/json: + schema: + type: object + properties: + errors: + $ref: '#/components/schemas/Errors' + examples: + invalid: + description: Поле имеет неверный формат (идентификатор поля вы получите в поле payload) + value: + errors: + - key: name + value: name + message: message + code: invalid + payload: {} + /chats/{id}/group_tags: + post: + tags: + - talk and channel participants + summary: добавление тегов в состав участников беседы или канала + description: | + Метод для добавления тегов в состав участников беседы или канала. + operationId: postTagsToChats + parameters: + - name: id + in: path + description: Идентификатор беседы/канала + required: true + schema: + type: integer + format: int64 + example: 533 + requestBody: + description: | + Идентификатор беседы/канала передаётся в URL (например, /chats/553/group_tags) + Массив идентификаторов тегов, которые станут участниками, передается в теле запроса + content: + application/json: + schema: + $ref: '#/components/schemas/GroupTag' + responses: + '201': + description: Тег(и) добавлен(ы) + '400': + description: Пояснения ошибки + content: + application/json: + schema: + type: object + properties: + errors: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Обязательное поле (не может быть пустым) + value: + errors: + - key: name + value: '' + message: message + code: blank + payload: {} + too_long: + description: Слишком длинное значение (пояснения вы получите в поле message) + value: + errors: + - key: name + value: long_name + message: message + code: too_long + payload: {} + invalid: + description: Поле не соответствует правилам (пояснения вы получите в поле message) + value: + errors: + - key: name + value: 1234 + message: message + code: invalid + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + '422': + description: С запросом все хорошо, но правила сервиса не позволяют его обработать (например, при попытке создания контакта с уже существующим номером телефона в базе) + content: + application/json: + schema: + type: object + properties: + errors: + $ref: '#/components/schemas/Errors' + examples: + invalid: + description: Поле имеет неверный формат (идентификатор поля вы получите в поле payload) + value: + errors: + - key: name + value: name + message: message + code: invalid + payload: {} + /chats/{id}/leave: + delete: + tags: + - talk and channel participants + operationId: leaveChat + summary: выход из беседы или канала + description: |- + Метод для самостоятельного выхода из беседы или канала. Параметры запроса отсутствуют/ + parameters: + - name: id + in: path + required: true + description: Уникальный идентификатор беседы или канала. + schema: + type: integer + responses: + '200': + description: При безошибочном выполнении запроса тело ответа отсутствуе + '400': + description: Пояснения ошибки + content: + application/json: + schema: + type: object + properties: + errors: + $ref: '#/components/schemas/Errors' + examples: + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + personal_chat: + description: Нельзя покинуть персональный чат + value: + errors: + - key: string + value: string + message: message + code: personal_chat + payload: {} + /messages/{id}/thread: + post: + tags: + - comments + summary: создание нового треда + description: | + Метод для создания нового треда к сообщению. Если у сообщения уже был создан тред, то в ответе вернётся информация об уже созданном ранее треде. + operationId: createThread + parameters: + - name: id + in: path + required: true + description: Уникальный идентификатор сообщения, к которому создается тред. + schema: + type: integer + responses: + '200': + description: Тред успешно создан или возвращены данные существующего треда. + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Thread' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + type: object + properties: + errors: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Поле не может быть пустым + value: + errors: + - key: name + value: '' + message: message + code: blank + payload: {} + exclusion: + description: Поле имеет недопустимое значение + value: + errors: + - key: name + value: 1234 + message: message + code: exclusion + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + /messages: + post: + tags: + - messages + summary: создание нового сообщения + description: | + Метод для отправки сообщения в беседу или канал, + личного сообщения пользователю или комментария в тред. + + При использовании entity_type: "discussion" (или просто без указания entity_type) + допускается отправка любого chat_id в поле entity_id. + То есть, сообщение можно отправить зная только идентификатор чата. + При этом, вы имеете возможность отправить сообщение в тред по его идентификатору + или личное сообщение по идентификатору пользователя. + + Для отправки личного сообщения пользователю создавать чат не требуется. + Достаточно указать entity_type: "user" и идентификатор пользователя. + Чат будет создан автоматически, если между вами ещё не было переписки. + Между двумя пользователями может быть только один личный чат. + operationId: createMessage + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + message: + $ref: '#/components/schemas/CreateMessage' + responses: + '201': + description: Successful + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Message' + example: + data: + id: 194275 + entity_type: discussion + entity_id: 198 + chat_id: 198 + content: Вчера мы продали 756 футболок (что на 10% больше, чем в прошлое воскресенье) + user_id: 12 + created_at: 2020-06-08T09:32:57.000Z + files: [] + buttons: [] + thread: null + forwarding: null + parent_message_id: null + '400': + description: Пояснения ошибки + content: + application/json: + schema: + type: object + properties: + errors: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Поле не может быть пустым + value: + errors: + - key: name + value: '' + message: message + code: blank + payload: {} + exclusion: + description: Поле имеет недопустимое значение + value: + errors: + - key: name + value: 1234 + message: message + code: exclusion + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + get: + tags: + - messages + summary: получение списка сообщений чата + description: | + Метод для получения списка сообщений бесед, каналов, тредов и личных сообщений. + + Для получения сообщений вам необходимо знать chat_id требуемой беседы, канала, + треда или диалога, и указать его в URL запроса. Сообщения будут возвращены + в порядке убывания даты отправки (то есть, сначала будут идти последние сообщения чата). + Для получения более ранних сообщений чата доступны параметры per и page. + Тело запроса отсутствует, параметры передаются в URL (например, /messages?chat_id=198&per=3) + operationId: getListMessage + parameters: + - $ref: '#/components/parameters/chatParameter' + - $ref: '#/components/parameters/perParameter25' + - $ref: '#/components/parameters/pageParameter' + responses: + '200': + description: Successful + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Message' + example: + data: + - id: 1194277 + entity_type: discussion + entity_id: 198 + chat_id: 198 + content: Это сообщение тоже попадёт в экспорт + user_id: 12 + created_at: 2023-09-18T13:43:32.000Z + files: [] + buttons: [] + thread: + id: 2633 + chat_id: 44997 + forwarding: null + parent_message_id: null + - id: 1194276 + entity_type: discussion + entity_id: 198 + chat_id: 198 + content: "**Andrew** добавил **Export bot** в беседу" + user_id: 12 + created_at: 2023-09-18T13:43:27.000Z + files: [] + buttons: [] + thread: null + forwarding: null + parent_message_id: null + - id: 1194275 + entity_type: discussion + entity_id: 198 + chat_id: 198 + content: "**Andrew** создал беседу" + user_id: 12 + created_at: 2023-09-18T13:43:19.000Z + files: [] + buttons: [] + thread: null + forwarding: null + parent_message_id: null + '400': + description: Пояснения ошибки + content: + application/json: + schema: + type: object + properties: + errors: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Поле не может быть пустым + value: + errors: + - key: name + value: '' + message: message + code: blank + payload: {} + exclusion: + description: Поле имеет недопустимое значение + value: + errors: + - key: name + value: 1234 + message: message + code: exclusion + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + /messages/{id}: + get: + tags: + - messages + summary: получение информации о сообщении + description: | + Метод для получения информации о сообщении. + + Для получения сообщения вам необходимо знать его id и указать его в URL запроса. + operationId: getMessage + parameters: + - name: id + in: path + required: true + schema: + title: id + type: integer + responses: + '200': + description: Successfull + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Message' + example: + data: + id: 194275 + entity_type: discussion + entity_id: 198 + chat_id: 198 + content: Вчера мы продали 756 футболок (что на 10% больше, чем в прошлое воскресенье) + user_id: 12 + created_at: 2020-06-08T09:32:57.000Z + files: + - id: 3560 + key: attaches/files/12/21zu7934-02e1-44d9-8df2-0f970c259796/congrat.png + name: congrat.png + file_type: file + url: | + https://pachca-prod-uploads.s3.storage.selcloud.ru/attaches/files/12/21zu7934- + 02e1-44d9-8df2-0f970c259796/congrat.png?response-cache-control=max- + age%3D3600%3B&response-content-disposition=attachment&X-Amz-Algorithm=AWS4-HMAC + -SHA256&X-Amz-Credential=142155_staply%2F20231107%2Fru-1a%2Fs3%2Faws4_ + request&X-Amz-Date=20231107T160412Z&X-Amz-Expires=604800&X-Amz-SignedHeaders= + host&X-Amz-Signature=98765asgfadsfdsaDSd4sdfg35asdf67sadf8 + buttons: [] + thread: + id: 29873 + chat_id: 1949863 + forwarding: null + parent_message_id: 194274 + '400': + description: Пояснения ошибки + content: + application/json: + schema: + type: object + properties: + errors: + $ref: '#/components/schemas/Errors' + examples: + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + put: + tags: + - messages + operationId: editMessage + summary: редактирование сообщения по указанному идентификатору + description: Метод для редактирования сообщения или комментария. + parameters: + - name: id + in: path + required: true + description: Уникальный идентификатор беседы или канала. + schema: + type: integer + requestBody: + description: Массив идентификаторов тегов, которые станут участниками + content: + application/json: + schema: + type: object + properties: + message: + $ref: '#/components/schemas/EditMessages' + responses: + '200': + description: Успешно отредактировано + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Message' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + type: object + properties: + errors: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Поле не может быть пустым + value: + errors: + - key: name + value: '' + message: message + code: blank + payload: {} + exclusion: + description: Поле имеет недопустимое значение + value: + errors: + - key: name + value: 1234 + message: message + code: exclusion + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + /messages/{id}/reactions: + post: + tags: + - reactions to messages + operationId: postMessageReactions + summary: добавление реакции + description: > + Метод для добавления реакции на сообщение. + **Лимиты реакций:** + - Каждый пользователь может установить не более 20 уникальных реакций на сообщение. + - Сообщение может иметь не более 30 уникальных реакций. + - Сообщение может иметь не более 1000 реакций. + parameters: + - name: id + in: path + required: true + description: Уникальный идентификатор сообщения. + schema: + type: integer + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CodeReaction' + responses: + "204": + description: Успешное выполнение запроса, тело ответа отсутствует. + '400': + description: Пояснения ошибки + content: + application/json: + schema: + type: object + properties: + errors: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Поле не может быть пустым + value: + errors: + - key: name + value: '' + message: message + code: blank + payload: {} + exclusion: + description: Поле имеет недопустимое значение + value: + errors: + - key: name + value: 1234 + message: message + code: exclusion + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + user_limit: + description: Превышен лимит уникальных реакций пользователя + value: + errors: + - key: string + value: string + message: Вы можете добавить не более 20 уникальных реакций. + code: user_limit + payload: {} + unique_limit: + description: Превышен лимит уникальных реакций на сообщение + value: + errors: + - key: string + value: string + message: Сообщение может содержать не более 30 уникальных реакций. + code: unique_limit + payload: {} + general_limit: + description: Превышен общий лимит реакций на сообщение + value: + errors: + - key: string + value: string + message: Сообщение может содержать не более 1000 реакций. + code: general_limit + payload: {} + delete: + tags: + - reactions to messages + operationId: deleteMessageReactions + summary: удаление реакции + description: > + Метод для удаления реакции на сообщение. + Удалить можно только те реакции, которые были поставлены авторизованным пользователем. + parameters: + - name: id + in: path + required: true + description: Уникальный идентификатор сообщения. + schema: + type: integer + - $ref: '#/components/parameters/reactionParameter' + responses: + "204": + description: При безошибочном выполнении запроса тело ответа отсутствует + '400': + description: Пояснения ошибки + content: + application/json: + schema: + type: object + properties: + errors: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Поле не может быть пустым + value: + errors: + - key: name + value: '' + message: message + code: blank + payload: {} + exclusion: + description: Поле имеет недопустимое значение + value: + errors: + - key: name + value: 1234 + message: message + code: exclusion + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + get: + tags: + - reactions to messages + operationId: getMessageReactions + summary: получение актуального списка реакций + description: | + Метод для получения актуального списка реакций на сообщение. + + Идентификатор сообщения, список реакций на которое необходимо получить, передается в URL (например, /messages/7231942/reactions). Количество возвращаемых сущностей и страница выборки указываются в теле запроса + parameters: + - name: id + in: path + description: Уникальный идентификатор сообщения + required: true + schema: + type: integer + - $ref: '#/components/parameters/perParameter50' + - $ref: '#/components/parameters/pageParameter' + responses: + '200': + description: Список реакций успешно получен. + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Reaction' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + type: object + properties: + errors: + $ref: '#/components/schemas/Errors' + examples: + exclusion: + description: Поле имеет недопустимое значение + value: + errors: + - key: name + value: 1234 + message: message + code: exclusion + payload: {} + not_found: + description: Не удалось найти + value: + errors: + - key: string + value: string + message: message + code: not_found + payload: {} + /tasks: + post: + tags: + - reminders + operationId: createTask + summary: создание нового напоминания + description: | + Метод для создания нового напоминания. + + При создании напоминания обязательным условием является указания типа напоминания: звонок, встреча, простое напоминание, событие или письмо. + При этом не требуется дополнительное описание - вы просто создадите напоминание с соответствующим текстом. + Если вы укажите описание напоминания - то именно оно и станет текстом напоминания. + У напоминания должны быть ответственные, если их не указывать - ответственным назначаетесь вы. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + task: + $ref: '#/components/schemas/BaseTask' + responses: + '201': + description: Напоминание успешно создано + content: + application/json: + schema: + type: object + properties: + data: + $ref: '#/components/schemas/Task' + '400': + description: Пояснения ошибки + content: + application/json: + schema: + type: object + properties: + errors: + $ref: '#/components/schemas/Errors' + examples: + blank: + description: Поле не может быть пустым + value: + errors: + - key: string + value: string + message: message + code: blank + payload: {} + too_long: + description: Слишком длинное значение (пояснения вы получите в поле message) + value: + errors: + - key: name + value: long_name + message: message + code: too_long + payload: {} + inclusion: + description: Поле имеет непредусмотренное значение + value: + errors: + - key: string + value: string + message: message + code: inclusion + payload: {} + invalid: + description: Поле имеет неверное значение (например, указаны недопустимые ответственные) + value: + errors: + - key: name + value: 1234 + message: message + code: invalid + payload: {} +components: + parameters: + perParameter50: + name: per + in: query + description: Количество возвращаемых сущностей за один запрос (по умолчанию 50, максимум 50) + required: false + schema: + type: integer + default: 50 + maximum: 50 + perParameter25: + name: per + in: query + description: Количество возвращаемых сущностей за один запрос (по умолчанию 25, максимум 50) + required: false + schema: + type: integer + default: 25 + maximum: 50 + pageParameter: + name: page + in: query + description: Страница выборки (по умолчанию 1) + required: false + schema: + type: integer + default: 1 + queryParameter: + name: query + in: query + description: | + Поисковая фраза для фильтрации результатов (поиск идет по полям first_name (имя), last_name (фамилия), email (электронная почта), phone_number (телефон) и nickname (никнейм)) + required: false + schema: + type: string + chatParameter: + name: chat_id + in: query + description: Идентификатор чата (беседа, канал, диалог или чат треда) + required: true + schema: + title: chat_id + type: integer + sortParameter: + name: 'sort[id]' + in: query + required: false + description: | + Составной параметр сортировки сущностей выборки. + Варианты значений: по умолчанию desc (по убыванию) или asc (по возрастанию). + На данный момент сортировка доступна только по полю ({field}) id (идентификатор бесед и каналов). + schema: + type: string + enum: + - desc + - asc + default: desc + availabilityParameter: + name: availability + in: query + required: false + description: | + Параметр, который отвечает за доступность и выборку бесед и каналов для пользователя. + Варианты значений: по умолчанию is_member (беседы и каналы, где пользователь является участником) + или public (все открытые беседы и каналы компании, вне зависимости от участия в них пользователя). + schema: + type: string + enum: + - is_member + - public + default: is_member + last_message_at_afterParameter: + name: last_message_at_after + in: query + required: false + description: | + Фильтрация по времени создания последнего сообщения. + Будут возвращены те беседы/каналы, время последнего созданного сообщения в которых не раньше чем указанное (в формате YYYY-MM-DDThh:mm:ss.sssZ). + schema: + type: string + format: date-time + last_message_at_beforeParameter: + name: last_message_at_before + in: query + required: false + description: | + Фильтрация по времени создания последнего сообщения. + Будут возвращены те беседы/каналы, время последнего созданного сообщения в которых не позже чем указанное (в формате YYYY-MM-DDThh:mm:ss.sssZ). + schema: + type: string + format: date-time + reactionParameter: + name: code + in: query + description: Emoji в строковом формате для добавления реакции. + schema: + type: string + example: "👍" + schemas: + MembersChat: + title: Members Chat + required: + - member_ids + type: object + properties: + member_ids: + type: array + description: Массив идентификаторов пользователей, которые станут участниками + minItems: 1 + items: + type: integer + format: int64 + example: [186, 187] + silent: + type: boolean + description: Не создавать в чате системное сообщение о добавлении участника + GroupTag: + title: Group Tag + required: + - group_tag_ids + type: object + properties: + group_tag_ids: + type: array + minItems: 1 + items: + type: integer + format: int64 + example: [86, 18] + description: Массив идентификаторов тегов, которые станут участниками + CodeReaction: + title: Code Reaction + type: object + properties: + code: + type: string + example: "👍" + description: Emoji в строковом формате для добавления реакции. + required: + - code + BaseEmployee: + title: Base Employee + type: object + properties: + id: + type: integer + example: 1 + description: Идентификатор пользователя + first_name: + type: string + description: Имя + last_name: + type: string + description: Фамилия + nickname: + type: string + description: Имя пользователя + email: + type: string + description: Электронная почта + phone_number: + type: string + description: Телефон + department: + type: string + description: Департамент + role: + type: string + enum: + - admin + - user + - multi_guest + description: | + Уровень доступа: admin (администратор), user (сотрудник), multi_guest (мульти-гость) + suspended: + type: boolean + description: | + Деактивация пользователя. При значении true пользователь является деактивированным. + invite_status: + type: string + enum: + - confirmed + - sent + description: | + Статус приглашения: confirmed (принято), sent (отправлено) + list_tags: + type: array + items: + type: string + description: Массив тегов, привязанных к сотруднику + custom_properties: + type: array + description: Дополнительные поля сотрудника + items: + type: object + properties: + id: + type: integer + description: Идентификатор поля + name: + type: string + description: Название поля + data_type: + type: string + enum: + - string + - number + - date + - link + description: Тип поля (string, number, date или link) + value: + type: string + description: Значение + bot: + type: boolean + description: | + Тип: пользователь (false) или бот (true) + description: Базовый класс сотрудника. + Employee: + allOf: + - $ref: '#/components/schemas/BaseEmployee' + - type: object + properties: + user_status: + $ref: '#/components/schemas/Status' + title: + type: string + description: Должность + created_at: + type: string + format: date-time + description: | + Дата создания (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ + time_zone: + type: string + description: Часовой пояс пользователя + image_url: + type: string + nullable: true + description: Ссылка на скачивание аватарки + description: Расширенный класс сотрудника. + BaseResponse: + title: Base Response + type: object + properties: + Content-Disposition: + type: string + description: Используемый заголовок + default: attachment + acl: + type: string + description: Уровень безопасности + default: private + policy: + type: string + description: Уникальный policy для загрузки файла + x-amz-credential: + type: string + description: x-amz-credential для загрузки файла + x-amz-algorithm: + type: string + description: Используемый алгоритм + default: AWS4-HMAC-SHA256 + x-amz-date: + type: string + description: Уникальный x-amz-date для загрузки файла + x-amz-signature: + type: string + description: Уникальная подпись для загрузки файла + key: + type: string + description: Уникальный ключ для загрузки файла + FileResponse: + title: File Response + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object + properties: + direct_url: + type: string + description: Адрес для загрузки файла + DirectResponse: + title: Direct Response + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object + properties: + file: + type: string + description: Адрес для загрузки файла + Status: + type: object + nullable: true + description: Статус. Возвращается как null, если статус не установлен. + properties: + emoji: + type: string + description: Emoji символ статуса + title: + type: string + description: Текст статуса + expires_at: + type: string + format: date-time + nullable: true + description: | + Срок жизни статуса (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ. Возвращается как null, если срок не установлен. + CommonMethods: + title: Common Methods + type: object + description: получение списка актульных полей сущности. + properties: + id: + type: integer + example: 1 + description: Название поля + name: + type: string + example: Дата рождения + description: Идентификатор поля + data_type: + type: string + enum: + - string + - number + - date + - link + example: number + description: тип поля + Errors: + type: array + items: + title: Error + type: object + properties: + key: + title: key + type: string + description: Ключ параметра, в котором произошла ошибка + value: + title: value + type: string + description: Значение ключа, которое вызвало ошибку + message: + title: message + type: string + description: Ошибка текстом, который вы можете вывести пользователю + code: + title: code + type: string + description: Внутренний код ошибки (коды ошибок представлены в описании каждого метода) + payload: + title: payload + type: object + description: Объект, который предоставляет любую дополнительную информацию (возможные дополнения представлены в описании каждого метода) + Buttons: + title: Message Buttons + type: array + maxItems: 100 + items: + title: Row Buttons + type: array + maxItems: 8 + items: + type: object + title: Button + required: + - text + minProperties: 2 + properties: + text: + title: Text + type: string + maxLength: 255 + url: + title: Url + type: string + data: + title: Data + type: string + maxLength: 255 + BaseThread: + title: Base Thread + type: object + nullable: true + properties: + id: + title: Id + type: integer + chat_id: + title: Chat Id + type: integer + Thread: + allOf: + - $ref: '#/components/schemas/BaseThread' + - type: object + properties: + message_id: + type: integer + description: Идентификатор сообщения, к которому был создан тред. + message_chat_id: + type: integer + description: Идентификатор чата сообщения. + updated_at: + type: string + format: date-time + description: | + Дата и время обновления треда (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ. + BaseFiles: + title: Base Files + type: array + items: + type: object + required: + - key + - name + - file_type + - size + properties: + key: + title: Key + type: string + description: Путь к файлу, полученный в результате загрузки файла (каждый файл в каждом сообщении должен иметь свой уникальный key, не допускается использование одного и того же key в разных сообщениях) + name: + title: Name + type: string + description: Название файла, которое вы хотите отображать пользователю (рекомендуется писать вместе с расширением) + file_type: + title: File Type + type: string + enum: + - 'file' + - 'image' + CreateEditFiles: + title: Create&Edit Files + allOf: + - $ref: '#/components/schemas/BaseFiles' + - type: array + items: + type: object + properties: + size: + title: Size + type: integer + description: Размер файла в байтах, отображаемый пользователю + Files: + allOf: + - $ref: '#/components/schemas/BaseFiles' + - type: array + items: + type: object + properties: + id: + title: Id + type: integer + url: + title: Url + type: string + description: Прямая временная ссылка на скачивание файла + BeforeBaseMessages: + title: Before Base Messages + type: object + description: Для получения сообщения вам необходимо знать его id и указать его в URL запроса. + required: + - content + properties: + content: + type: string + description: Текст сообщения + default: Текст сообщения + buttons: + allOf: + - $ref: '#/components/schemas/Buttons' + title: buttons + EditMessages: + title: Edit Messages + allOf: + - $ref: '#/components/schemas/BeforeBaseMessages' + - type: object + description: Для получения сообщения вам необходимо знать его id и указать его в URL запроса. + required: + - content + properties: + files: + allOf: + - $ref: '#/components/schemas/CreateEditFiles' + title: files + BaseMessages: + title: Base Messages + allOf: + - $ref: '#/components/schemas/BeforeBaseMessages' + - type: object + required: + - entity_id + properties: + entity_type: + title: Entity Type + type: string + enum: + - 'discussion' + - 'user' + - 'thread' + default: 'discussion' + entity_id: + title: Entity Id + type: integer + parent_message_id: + title: Parent Massage Id + type: integer + nullable: true + default: null + description: Идентификатор сообщения, к которому написан ответ. Возвращается как null, если сообщение не является ответом. + CreateMessage: + title: Create Messages + allOf: + - $ref: '#/components/schemas/BaseMessages' + - type: object + properties: + files: + allOf: + - $ref: '#/components/schemas/CreateEditFiles' + title: files + skip_invite_mentions: + title: Skip Invite Mentions + type: boolean + default: false + link_preview: + title: Link Preview + type: boolean + default: false + Message: + allOf: + - $ref: '#/components/schemas/BaseMessages' + - type: object + properties: + id: + title: Id + type: integer + chat_id: + title: Chat Id + type: integer + user_id: + title: User Id + type: integer + created_at: + title: Created At + type: string + format: date-time + files: + allOf: + - $ref: '#/components/schemas/Files' + title: files + thread: + allOf: + - $ref: '#/components/schemas/BaseThread' + forwarding: + title: Forwarding + type: object + nullable: true + default: null + properties: + original_message_id: + title: Origin Message Id + type: integer + description: Идентификатор оригинального сообщения + original_chat_id: + title: Original Chat Id + type: integer + description: Идентификатор чата, в котором находится оригинальное сообщение + author_id: + title: Author Id + type: integer + description: Идентификатор чата, в котором находится оригинальное сообщение + original_created_at: + title: Original Created At + type: integer + description: Дата и время создания оригинального сообщения (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ + original_thread_id: + title: Original Thread Id + type: integer + nullable: true + description: Идентификатор треда, в котором находится оригинальное сообщение. Возвращается как null, если оригинальное сообщение не является комментарием в треде. + original_thread_message_id: + title: Original Thread Message Id + type: integer + nullable: true + description: Идентификатор сообщения, к которому был создан тред, в котором находится оригинальное сообщение. Возвращается как null, если оригинальное сообщение не является комментарием в треде. + original_thread_parent_chat_id: + title: Original Thread Parent Chat Id + type: integer + nullable: true + description: Идентификатор чата сообщения, к которому был создан тред, в котором находится оригинальное сообщение. Возвращается как null, если оригинальное сообщение не является комментарием в треде. + Reaction: + type: object + properties: + user_id: + type: integer + description: | + Идентификатор пользователя, оставившего реакцию. + created_at: + type: string + format: date-time + description: | + Дата и время добавления реакции (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ. + code: + type: string + description: | + Emoji символ реакции. + BaseChat: + title: Base Chat + type: object + description: Собранный объект параметров создаваемой беседы или канала + required: + - name + properties: + name: + type: string + description: Название + example: 🤿 aqua + member_ids: + type: array + description: Массив идентификаторов пользователей, которые станут участниками + items: + type: integer + example: + - 186 + - 187 + group_tag_ids: + type: array + description: Массив идентификаторов тегов, участников + items: + type: integer + example: [] + channel: + type: boolean + description: 'Тип: беседа (по умолчанию, false) или канал (true)' + example: true + public: + type: boolean + description: 'Доступ: закрытый (по умолчанию, false) или открытый (true)' + example: false + Chat: + allOf: + - type: object + properties: + id: + type: integer + description: Идентификатор беседы или канала + example: 334 + owner_id: + type: integer + description: Идентификатор пользователя, создавшего беседу или канал + example: 185 + created_at: + type: string + format: date-time + description: Дата и время создания беседы или канала (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ + example: '2021-08-28T15:56:53.000Z' + last_message_at: + type: string + format: date-time + description: Дата и время создания последнего сообщения в беседе/канале (ISO-8601, UTC+0) в формате YYYY-MM-DDThh:mm:ss.sssZ + example: '2021-08-28T15:58:13.000Z' + meet_room_url: + type: string + description: Ссылка на Видеочат + example: 'https://meet.pachca.com/aqua-94bb21b5' + - $ref: '#/components/schemas/BaseChat' + Tag: + type: object + description: Для получения тега вам необходимо знать его id и указать его в URL запроса. + properties: + id: + type: integer + description: Идентификатор тега + name: + type: string + description: Название тега + users_count: + description: Количество сотрудников, которые имеют этот тег + type: integer + BaseCustomProperties: + title: Base Custom Properties + type: array + items: + type: object + properties: + id: + type: integer + description: Идентификатор поля + value: + type: string + description: Значение поля + CustomProperties: + title: Custom Properties + allOf: + - $ref: '#/components/schemas/BaseCustomProperties' + - type: array + items: + type: object + properties: + name: + type: string + description: Название поля + data_type: + type: string + enum: + - string + - number + - date + - link + description: Тип поля (string, number, date или link) + BaseTask: + title: Base Task + type: object + required: + - kind + - content + - due_at + properties: + kind: + type: string + description: Тип напоминания + enum: + - call + - meeting + - reminder + - event + - email + content: + type: string + description: Описание напоминания + due_at: + type: string + format: date-time + description: Срок выполнения напоминания (ISO-8601) + priority: + type: integer + description: Приоритет (1 - по умолчанию, 2 - важно, 3 - очень важно) + enum: + - 1 + - 2 + - 3 + performer_ids: + type: array + items: + type: integer + description: Массив идентификаторов пользователей + custom_properties: + allOf: + - $ref: '#/components/schemas/BaseCustomProperties' + Task: + allOf: + - $ref: '#/components/schemas/BaseTask' + - type: object + properties: + id: + type: integer + description: Идентификатор созданного напоминания + user_id: + type: integer + description: Идентификатор пользователя-создателя + status: + type: string + description: Статус напоминания + created_at: + type: string + format: date-time + description: Дата и время создания + custom_properties: + allOf: + - $ref: '#/components/schemas/CustomProperties' + securitySchemes: + bearerAuth: + type: http + scheme: bearer +security: + - bearerAuth: [] \ No newline at end of file diff --git a/src/generator2/request_methods_generator.py b/src/generator2/request_methods_generator.py new file mode 100644 index 0000000..586593f --- /dev/null +++ b/src/generator2/request_methods_generator.py @@ -0,0 +1,404 @@ +import os +import re +import textwrap +from typing import Union + +from httpx import codes +from openapi_parser import parse +from openapi_parser.specification import (DataType, Operation, Parameter, Path, + Specification) + +from .services.constants import (DEFAULT_VALUE_SORT_FIELD, + GENERATED_CLIENT_FOLDER, PARAM_DEFAULT_KEY, + PARAM_LOCATION_PATH, PARAM_LOCATION_QUERY, + PARAM_NAME_SORT, PARAM_NAME_SORT_FIELD, + PARAM_TYPE_KEY, PREFIX_REQUEST, + PREFIX_RESPONSE, SCHEMA_SORT_ID, + SPECIFICATION_FILE_NAME, + TEMPLATE_CLASS_REQUEST_METHODS, + TYPE_SORT_FIELD) + + +def format_path_params(param_path: dict[str, Union[str, dict]]) -> str: + """Форматирует параметры пути для использования в функции.""" + return ( + ", ".join( + [f"'{name}': {name}" for name in param_path] + ) if param_path else "" + ) + + +def format_query_params(param_query: dict[str, Union[str, dict]]) -> str: + """Форматирует параметры запроса для использования в функции.""" + return ", ".join([ + f"{name}={name}" for name in param_query.keys() + ]) if param_query else "" + + +def generate_url_template( + url: str, param_path: dict[str, Union[str, dict]] +) -> str: + """Генерирует строку с форматированным URL для функции.""" + if param_path: + path_params = format_path_params(param_path) + return f"url = await self.format_url('{url}', {{{path_params}}})" + return f"url = '{url}'" + + +def generate_function_params( + param_path: dict[str, Union[str, dict]] = None, + param_query: dict[str, Union[str, dict]] = None, + name_request_scheme: str = None, +) -> str: + """Генерирует список параметров для функции.""" + function_params = [] + + if param_path: + function_params.extend( + [ + f'{name}: {type_param}' + for name, type_param in param_path.items() + ] + ) + if param_query: + for name, data_param in param_query.items(): + type_param = data_param['type'] + default_value = data_param['default'] + if name in {'sort', 'sort_field'}: + default_value = f"'{default_value}'" + function_params.append(f'{name}: {type_param} = {default_value}') + + if name_request_scheme: + return ", ".join( + ["self", f"data: {name_request_scheme}"] + function_params + ) + return ", ".join(["self"] + function_params) + + +def generate_request_handling( + method_request: str, + name_request_scheme: str = None, + param_query: dict[str, Union[str, dict]] = None, +) -> str: + """Генерирует логику отправки запроса в зависимости от параметров.""" + if name_request_scheme: + return ( + f'response = await client.{method_request}' + '(url, json=data.model_dump())' + ) + if param_query: + return ( + f'response = await client.{method_request}' + '(url, params=query_params)' + ) + return f"response = await client.{method_request}(url)" + + +def generate_response_handling( + name_response_scheme: str = None, + name_error_scheme: str = None, +) -> str: + """Генерирует логику обработки ответа от сервера.""" + response_handling = "" + if name_response_scheme: + response_handling += ( + '\n if response.is_success:\n' + f' return {name_response_scheme}' + '.model_validate_json(response.text)' + ) + if name_error_scheme: + response_handling += ( + '\n if response.is_client_error:\n' + f' return {name_error_scheme}' + '.model_validate_json(response.text)' + ) + return response_handling + + +def get_template_methods( + name_func: str, + url: str, + method_request: str, + docstring: str, + param_path: dict[str, Union[str, dict]] = None, + param_query: dict[str, Union[str, dict]] = None, + name_request_scheme: str = None, + name_response_scheme: str = None, + name_error_scheme: str = None, +) -> str: + """Возвращает шаблон генерируемой функции.""" + function_params = generate_function_params( + param_path, param_query, name_request_scheme + ) + format_url = generate_url_template(url, param_path) + filter_params = format_query_params(param_query) + request_handling = generate_request_handling( + method_request, name_request_scheme, param_query + ) + response_handling = generate_response_handling( + name_response_scheme, name_error_scheme + ) + response_annotation = ( + f" -> {name_response_scheme}" if name_response_scheme else "" + ) + filter_params_code = ( + f"\n query_params = await self.filter_query_params" + f"({filter_params})" + if filter_params else "" + ) + + return f""" + + async def {name_func}({function_params}){response_annotation}: + {docstring} + client = await self.get_client() + async with client: + {format_url}{filter_params_code} + {request_handling}{response_handling} + return None +""" + + +def format_docstring(summary: str, description: str, max_width: int = 79): + """Редактирует длины строк докстринг + генерируемых функций в соответствиие с PEP8 + """ + formatted_summary = "\n".join(textwrap.wrap(summary, width=max_width)) + formatted_description = "\n".join( + textwrap.wrap(description, width=max_width), + ) + return f'"""{formatted_summary}\n\n{formatted_description}"""' + + +def format_name_func(operation_id: str): + """Возвращает название генерируемой функции запроса в требуемом формате""" + return '_'.join( + re.findall(r'[a-z]+|[A-Z][^A-Z]*', operation_id), + ).lower() + + +def get_python_type(schema_type: DataType): + """Сопоставляет текущий тип данных параметра с типом данных Python + и возвращает его + """ + type_mapping = { + DataType.STRING: "str", + DataType.INTEGER: "int", + DataType.NUMBER: "float", + DataType.BOOLEAN: "bool", + DataType.ARRAY: "list", + DataType.OBJECT: "dict", + } + + return type_mapping.get(schema_type, None) + + +def import_string_generation( + prefix: str, + operation_id: str, + schema: str, + code: str = None, + method: str = None +) -> str: + """Возвращает шаблон строки импорта""" + if prefix == PREFIX_RESPONSE: + return ( + f'from .models.{prefix}{operation_id}' + f'{method}{code} import {schema}' + ) + if prefix == PREFIX_REQUEST: + return f'from .models.{prefix}{operation_id} import {schema}' + + +def process_operation(operation: Operation) -> tuple[str]: + """Обрабатывает объект Operation и возвращает кортеж значений: + - method метод запроса операции + - operation_id уникальный идентификатор операции + - operation.summary краткое описание операции + - operation.description развернутое описание операции + - name_request_scheme сгенерированое название схемы объекта передаваемого + в запросе + - name_response_scheme сгенерированое название схемы возвращаемого объекта + - name_error_scheme сгенерированое название схемы объекта ошибки + - import_template Список строк для импортов используемых моделей схем.""" + import_template = [] + name_request_scheme = None + name_response_scheme = None + name_error_scheme = None + + method = operation.method.value + operation_id = operation.operation_id + + if operation.request_body: + name_request_scheme = operation_id.capitalize() + import_template.append( + import_string_generation( + prefix=PREFIX_REQUEST, + operation_id=operation_id, + schema=name_request_scheme + ) + ) + for response in operation.responses: + if codes.is_success(response.code) and response.content: + name_response_scheme = ( + f'Response{operation_id.capitalize()}' + f'{operation.method.value.capitalize()}' + f'{str(response.code).capitalize()}' + ) + import_template.append( + import_string_generation( + prefix=PREFIX_RESPONSE, + operation_id=operation_id, + schema=name_response_scheme, + code=response.code, + method=method, + ) + ) + if codes.is_client_error(response.code): + name_error_scheme = ( + f'Response{operation_id.capitalize()}' + f'{operation.method.value.capitalize()}' + f'{str(response.code).capitalize()}' + ) + import_template.append( + import_string_generation( + prefix=PREFIX_RESPONSE, + operation_id=operation_id, + schema=name_error_scheme, + code=response.code, + method=method, + ) + ) + + return ( + method, operation_id, operation.summary, operation.description, + name_request_scheme, name_response_scheme, name_error_scheme, + import_template + ) + + +def process_parameters(parameters: list[Parameter]) -> tuple[dict, dict]: + """Обрабатывает параметры запроса и возвращает два словаря: + - param_path: параметры, относящиеся к пути (path). + - param_query: параметры, относящиеся к строке запроса (query)""" + param_path = {} + param_query = {} + + for param in parameters: + if param.location.value == PARAM_LOCATION_PATH: + schema_type = get_python_type(param.schema.type) + param_path[param.name] = schema_type + if param.location.value == PARAM_LOCATION_QUERY: + schema_type = get_python_type(param.schema.type) + if param.name == SCHEMA_SORT_ID: + default_value_sort = param.schema.default + param.name = PARAM_NAME_SORT + param_query[PARAM_NAME_SORT_FIELD] = { + PARAM_TYPE_KEY: TYPE_SORT_FIELD, + PARAM_DEFAULT_KEY: DEFAULT_VALUE_SORT_FIELD + } + param_query[param.name] = { + PARAM_TYPE_KEY: schema_type, + PARAM_DEFAULT_KEY: default_value_sort + } + continue + param_query[param.name] = { + PARAM_TYPE_KEY: schema_type, + PARAM_DEFAULT_KEY: None, + } + + return param_path, param_query + + +def template_generation(paths: list[Path]) -> tuple[list[str]]: + """Собирает параметры запроса всех paths спецификации + передает их в функицю get_template_methods + Возвращает кортеж состоящий из: + - templates список шаблонов методов запроса + - import_templates список шаблонов импортов + """ + templates = [] + import_templates = [] + + for path in paths: + url = path.url + for operation in path.operations: + ( + method, + operation_id, + summary, + description, + name_request_scheme, + name_response_scheme, + name_error_scheme, + import_template, + ) = process_operation(operation) + + import_templates.extend(import_template) + + method_request = method.lower() + function_name = format_name_func(operation_id) + docstring = format_docstring(summary, description) + + param_path, param_query = process_parameters(operation.parameters) + + templates.append( + get_template_methods( + function_name, + url, + method_request, + docstring, + param_path, + param_query, + name_request_scheme, + name_response_scheme, + name_error_scheme + ), + ) + + return templates, import_templates + + +def get_obj_openapi_spec( + path_to_file=SPECIFICATION_FILE_NAME, +) -> Specification: + """Читает спецификацию openapi из файла openapi.yaml и возвращает + спецификацию в виде объекта Specification библиотеки openapi_parser + """ + base_dir = os.path.dirname(os.path.abspath(__file__)) + full_path = os.path.join(base_dir, path_to_file) + + with open(full_path, 'r', encoding='utf-8') as file: + spec_openapi = file.read() + + return parse(spec_string=spec_openapi) + + +def generation_class_bot( + templates: list, + import_templates: list, + file=f'./{__package__}/{GENERATED_CLIENT_FOLDER}/request_methods.py', +): + with open( + file, 'w', encoding='utf-8', + ) as file: + for module_name in import_templates: + file.write(f'{module_name}\n') + file.write(f'{TEMPLATE_CLASS_REQUEST_METHODS}') + for template in templates: + file.write(template) + + +def generate(): + spec: Specification = get_obj_openapi_spec() + paths: list[Path] = spec.paths + + templates, import_templates = template_generation(paths) + + generation_class_bot( + templates=templates, import_templates=import_templates + ) + + +if __name__ == "__main__": + generate() diff --git a/src/generator2/requirements.txt b/src/generator2/requirements.txt new file mode 100644 index 0000000..43fea68 --- /dev/null +++ b/src/generator2/requirements.txt @@ -0,0 +1,50 @@ +annotated-types==0.7.0 +anyio==4.7.0 +attrs==24.3.0 +black==24.10.0 +certifi==2024.12.14 +cfgv==3.4.0 +chardet==5.2.0 +charset-normalizer==3.4.1 +click==8.1.8 +colorama==0.4.6 +distlib==0.3.9 +exceptiongroup==1.2.2 +filelock==3.16.1 +h11==0.14.0 +httpcore==1.0.7 +httpx==0.28.1 +identify==2.6.4 +idna==3.10 +jsonschema==4.23.0 +jsonschema-spec==0.2.4 +jsonschema-specifications==2023.7.1 +lazy-object-proxy==1.10.0 +mypy-extensions==1.0.0 +nodeenv==1.9.1 +openapi-schema-validator==0.6.2 +openapi-spec-validator==0.6.0 +openapi3-parser==1.1.19 +packaging==24.2 +pathable==0.4.3 +pathspec==0.12.1 +platformdirs==4.3.6 +prance==23.6.21.0 +pre-commit==3.8.0 +pydantic==2.10.4 +pydantic_core==2.27.2 +python-dotenv==1.0.1 +PyYAML==6.0.2 +referencing==0.30.2 +requests==2.32.3 +rfc3339-validator==0.1.4 +rpds-py==0.22.3 +ruamel.yaml==0.18.6 +ruamel.yaml.clib==0.2.12 +ruff==0.7.1 +six==1.17.0 +sniffio==1.3.1 +tomli==2.2.1 +typing_extensions==4.12.2 +urllib3==2.3.0 +virtualenv==20.28.1 diff --git a/src/generator2/schema_link_processor.py b/src/generator2/schema_link_processor.py new file mode 100644 index 0000000..7058061 --- /dev/null +++ b/src/generator2/schema_link_processor.py @@ -0,0 +1,52 @@ +from .services.yaml_loader import YAML_DICT + + +def unite_schemas(schemas: list[dict], schema2: dict): + for schema in schemas: + schema2['type'] = schema2.get('type') or schema.get('type') + required_proprties = schema2.get('required', []) + required_proprties.extend(schema.get('required', [])) + schema2['required'] = list(set(required_proprties)) + if schema2['type'] == 'object': + schema2['properties'] = ( + schema.get('properties', {}) | schema2.get('properties', {}) + ) + if schema2['type'] == 'array': + if 'items' in schema2 and schema2['items'].get('properties'): + required_proprties = schema2['items'].get('required', []) + required_proprties.extend(schema['items'].get('required', [])) + schema2['required'] = list(set(required_proprties)) + schema2['items']['properties'] = ( + schema.get('items').get('properties') | schema2.get('items').get('properties') + ) + else: + schema2['items'] = ( + schema.get('items', {}) | schema2.get('items', {}) + ) + return schema2 + + +def load_schema(path_to_schema: str, is_parameter: bool = False) -> dict: + """Возвращает схему из ссылки.""" + schema_name = path_to_schema.split('/')[-1] + if is_parameter: + return YAML_DICT.get('components').get('parameters').get(schema_name) + return YAML_DICT.get('components').get('schemas').get(schema_name) + + +def new_replace_ref_with_schema(schema: dict): + if '$ref' in schema: + return load_schema(schema['$ref']) + + if 'allOf' in schema: + all_inherits = [new_replace_ref_with_schema(load_schema(ingerit['$ref'])) for ingerit in schema['allOf'] if '$ref' in ingerit] + all_non_inherits = [ingerit for ingerit in schema['allOf'] if '$ref' not in ingerit] + if not all_non_inherits: + all_non_inherits = [{}] + temp = all_non_inherits[0] + temp = unite_schemas(all_inherits, temp) + schema = temp + schema_name = next(iter(schema.keys())) + if 'allOf' in schema[schema_name]: + schema[schema_name] = new_replace_ref_with_schema(schema[schema_name]) + return schema diff --git a/src/generator2/services/__init__.py b/src/generator2/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/generator2/services/constants.py b/src/generator2/services/constants.py new file mode 100644 index 0000000..f0557a5 --- /dev/null +++ b/src/generator2/services/constants.py @@ -0,0 +1,58 @@ +import os +import sys +from pathlib import Path + +# PATH_TO_YAML = './openapi_test.yaml' +PATH_TO_YAML = ( + f'{Path(__file__).parent.parent.resolve()}/openapi.yaml') +SPECIFICATION_FILE_NAME = ( + f'{Path(__file__).parent.parent.resolve()}/openapi.yaml') + +PYTHON_TYPES = { + 'string': 'str', + 'integer': 'int', + 'boolean': 'bool', +} + +ENUM_TYPES = { + 'string': 'str, Enum' if sys.version_info[1] < 11 else 'StrEnum', + 'integer': 'IntEnum', +} + +HTTP_METHODS = ( + 'get', 'post', 'put', 'update', 'patch', 'delete', +) + +PARAM_TYPE_KEY = 'type' +PARAM_DEFAULT_KEY = 'default' + +SCHEMA_SORT_ID = 'sort[id]' +PARAM_NAME_SORT = 'sort' +PARAM_NAME_SORT_FIELD = 'sort_field' + +PARAM_LOCATION_QUERY = 'query' +PARAM_LOCATION_PATH = 'path' + +PREFIX_RESPONSE = 'models_response_' +PREFIX_REQUEST = 'models_reqBod_' + +DEFAULT_VALUE_SORT_FIELD = 'id' +TYPE_SORT_FIELD = 'str' + +BASE_DIR = Path(os.path.dirname(os.path.abspath(__file__))).parent +LOG_FILE_NAME = os.path.join(BASE_DIR, "client_generator.log") + +GENERATED_CLIENT_FOLDER = 'generator2_full' + +TEMPLATE_CLASS_REQUEST_METHODS = """ +class RequestMethods: + + async def get_client(self): + pass + + async def format_url(self): + pass + + async def filter_query_params(self): + pass +""" diff --git a/src/generator2/services/file_writer.py b/src/generator2/services/file_writer.py new file mode 100644 index 0000000..bab3277 --- /dev/null +++ b/src/generator2/services/file_writer.py @@ -0,0 +1,38 @@ +import errno +import os +import os.path +import pathlib + +from .constants import GENERATED_CLIENT_FOLDER + + +def mkdir_p(path): + try: + os.makedirs(path) + except OSError as exc: # Python >2.5 + if exc.errno == errno.EEXIST and os.path.isdir(path): + pass + + +def safe_open_w(path, mode: str): + ''' Open "path" for writing, creating any parent directories as needed. + ''' + mkdir_p(os.path.dirname(path)) + return open(path, mode, encoding='utf-8') + + +def write_to_file( + file_name: str, text_to_write: str, folder_name: str = 'models', + open_file_mode: str = 'a' +): + """Записывает текст в файл.""" + with safe_open_w( + (pathlib.Path(__file__).parent.parent.resolve() + / GENERATED_CLIENT_FOLDER / folder_name / f'{file_name}.py'), + open_file_mode + ) as f: + f.write(text_to_write) + + +if __name__ == '__main__': + write_to_file('model_users.py', '\n\nANOTHER CODE HERE') diff --git a/src/generator2/services/logger_setup.py b/src/generator2/services/logger_setup.py new file mode 100644 index 0000000..36c8a59 --- /dev/null +++ b/src/generator2/services/logger_setup.py @@ -0,0 +1,14 @@ +import logging + +from .constants import LOG_FILE_NAME + + +def setup_logging(logger_name: str) -> logging.Logger: + logger = logging.getLogger(logger_name) + logger.setLevel(logging.DEBUG) + file_handler = logging.FileHandler(LOG_FILE_NAME, encoding='utf-8') + formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s') + file_handler.setFormatter(formatter) + logger.addHandler(file_handler) + return logger diff --git a/src/generator2/services/yaml_loader.py b/src/generator2/services/yaml_loader.py new file mode 100644 index 0000000..2fe2d94 --- /dev/null +++ b/src/generator2/services/yaml_loader.py @@ -0,0 +1,7 @@ +from pathlib import Path + +from ruamel.yaml import YAML + +from .constants import PATH_TO_YAML + +YAML_DICT = YAML(typ='rt').load(Path(PATH_TO_YAML)) diff --git a/src/generator2/yaml_processor.py b/src/generator2/yaml_processor.py new file mode 100644 index 0000000..f1a983f --- /dev/null +++ b/src/generator2/yaml_processor.py @@ -0,0 +1,148 @@ +import sys + +from .generate_pydantic_model import look_into_schema_new +from .schema_link_processor import load_schema +from .services.constants import HTTP_METHODS, PREFIX_REQUEST, PREFIX_RESPONSE +from .services.file_writer import write_to_file +from .services.logger_setup import setup_logging +from .services.yaml_loader import YAML_DICT + +logger = setup_logging('yaml_processor') + + +def create_constants_for_client(yaml_dict: dict) -> str: + """Записывает файл констант для клиента.""" + write_to_file( + 'constants', + ( + "# Client constants\n" + f"URL = '{yaml_dict['servers'][0]['url']}'\n" + "PARAM_NAME_SORT = 'sort'\n" + "PARAM_NAME_SORT_FIELD = 'sort_field'\n" + "TOKEN_TYPE = 'Bearer'\n\n" + "# Logger constants\n" + "LOG_FILE_NAME = 'pachca_log.log'\n" + "MAX_FILE_SIZE = 1 * 1024 * 1024 # 1 MB\n" + "BACKUP_COUNT = 3\n" + ), + folder_name='', + open_file_mode='w' + ) + + +def get_all_endpoints(yaml_dict: dict): + """Получает все эндпоинты из path документации.""" + endpoints = yaml_dict.get('paths') + method = {} + for path, body in endpoints.items(): + for method_name in HTTP_METHODS: + method_body = body.get(method_name) + if method_body: + method[method_name] = method_body + for name, body in method.items(): + yield path, name, body + method.clear() + + +def process_endpoints() -> tuple[list, list]: + """Обрабатывает эндпоинты. + + Проходит по каждому эндпоинту в openapi файле и генерирует модели для + каждой схемы в requestBody и resopnse. + """ + create_constants_for_client(YAML_DICT) + body: dict + for endpoint, method, body in get_all_endpoints(YAML_DICT): + logger.debug(f'Working on: {endpoint}, {method}') + operation_id = body.get('operationId') + parameters = body.get('parameters') + path_parameters = [] + query_parameters = [] + if parameters: + for parameter in parameters: + if '$ref' in parameter: + parameter = load_schema(parameter['$ref'], is_parameter=1) + required = parameter.get('required', False) + if required: + path_parameters.append( + ( + parameter.get('name'), + parameter.get('schema').get('type'), + ), + ) + else: + query_parameters.append( + ( + parameter.get('name'), + parameter.get('schema').get('type'), + ), + ) + request_body = body.get('requestBody') + if request_body: + write_to_file( + f'{PREFIX_REQUEST}{operation_id}', + ( + 'from enum import Enum, IntEnum' + f'{"" if sys.version_info[1] < 11 else ", StrEnum"}\n' + 'from typing import Any, Dict, Optional, List\n' + 'from pydantic import Field, BaseModel\n\n\n' + ), + open_file_mode='w' + ) + schema = ( + request_body.get('content').get('application/json') + or request_body.get('content').get('multipart/form-data')) + if not schema: + continue + schema_has_link = schema.get('schema').get('$ref', False) + schema = ( + {operation_id.capitalize(): load_schema(schema_has_link)} + if schema_has_link + else {operation_id.capitalize(): schema.get('schema')} + ) + look_into_schema_new(schema, PREFIX_REQUEST + operation_id) + try: + responses = body.get('responses', False) + if responses: + for code, response in responses.items(): + if 'content' not in response: + continue + schema = ( + response.get('content').get('application/json') + or response.get('content').get('multipart/form-data')) + if not schema: + continue + write_to_file( + f'{PREFIX_RESPONSE}{operation_id}{method}{code}', + ( + 'from enum import Enum, IntEnum' + f'{"" if sys.version_info[1] < 11 else ", StrEnum"}\n' + 'from typing import Any, Dict, Optional, List\n' + 'from pydantic import Field, BaseModel\n\n\n' + ), + open_file_mode='w' + ) + schema_has_link = schema.get('schema').get('$ref', False) + model_name = ( + f'Response{operation_id.capitalize()}' + f'{method.capitalize()}{code}') + schema = ( + {model_name: load_schema(schema_has_link)} + if schema_has_link + else {model_name: schema.get('schema')} + ) + look_into_schema_new( + schema, + f'{PREFIX_RESPONSE}{operation_id}{method}{code}' + ) + except Exception as e: + logger.error( + 'Unable to create responses for ' + f'{operation_id}, {method, code}!' + f'Error: {e}' + ) + return path_parameters, query_parameters + + +if __name__ == '__main__': + process_endpoints() diff --git a/src/repository/PachcaAPI-4.0-py3-none-any.whl b/src/repository/PachcaAPI-4.0-py3-none-any.whl new file mode 100644 index 0000000..38f1336 Binary files /dev/null and b/src/repository/PachcaAPI-4.0-py3-none-any.whl differ