YaTrackerApi


NameYaTrackerApi JSON
Version 2.1.2 PyPI version JSON
download
home_pageNone
SummaryАсинхронный Python клиент для работы с Yandex Tracker API с модульной архитектурой
upload_time2025-10-07 22:15:10
maintainerNone
docs_urlNone
authorNone
requires_python>=3.9
licenseMIT
keywords aiohttp api async project-management task-tracker tracker yandex
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # YaTrackerApi

> Асинхронный Python клиент для работы с Yandex Tracker API с модульной архитектурой

[![PyPI version](https://badge.fury.io/py/YaTrackerApi.svg)](https://badge.fury.io/py/YaTrackerApi)
[![Python](https://img.shields.io/pypi/pyversions/YaTrackerApi.svg)](https://pypi.org/project/YaTrackerApi/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## 📋 Содержание

- [Особенности](#-особенности)
- [Установка](#-установка)
- [Быстрый старт](#-быстрый-старт)
- [Основные возможности](#-основные-возможности)
  - [Работа с задачами](#работа-с-задачами)
  - [Связи между задачами](#связи-между-задачами)
  - [Управление статусами](#управление-статусами)
  - [Чеклисты](#чеклисты)
  - [Комментарии](#комментарии)
  - [Работа с полями](#работа-с-полями)
  - [Работа с сущностями](#работа-с-сущностями)
  - [Работа с пользователями](#работа-с-пользователями)
  - [Работа с очередями](#работа-с-очередями)
- [API Модули](#-api-модули)
- [Особенности и подводные камни](#-особенности-и-подводные-камни)
- [Требования](#-требования)
- [Лицензия](#-лицензия)
- [Контакты](#-контакты)

## ✨ Особенности

- **Асинхронность** - полная поддержка `async/await` на базе `aiohttp`
- **Модульная архитектура** - логическое разделение функциональности по модулям
- **Lazy Loading** - модули загружаются только при первом обращении
- **Типизация** - полная поддержка типов для всех методов
- **Гибкость** - множественные форматы для одного поля (строки, числа, объекты)
- **Валидация** - проверка данных на стороне клиента перед отправкой
- **Логирование** - система логов на русском языке с настраиваемым уровнем
- **Кастомные поля** - поддержка пользовательских полей любых типов
- **Безопасность** - настроенный SSL/TLS контекст для всех соединений

## 📦 Установка

```bash
pip install YaTrackerApi
```

Или через `uv`:

```bash
uv add YaTrackerApi
```

## 🚀 Быстрый старт

```python
import asyncio
from client import YandexTrackerClient

async def main():
    async with YandexTrackerClient(
        oauth_token="your_oauth_token",
        org_id="your_org_id",
        log_level="INFO"
    ) as client:
        # Создание задачи
        issue = await client.issues.create(
            summary="Новая задача",
            queue="TESTBOT",
            description="Подробное описание задачи"
        )
        print(f"Создана задача: {issue['key']}")

        # Получение задачи
        issue = await client.issues.get('TESTBOT-1')
        print(f"Задача: {issue['summary']}")

if __name__ == "__main__":
    asyncio.run(main())
```

### Настройка переменных окружения

Создайте файл `.env` в корне проекта:

```env
TRACKER_API_KEY=your_oauth_token
TRACKER_ORG_ID=your_org_id
```

И загрузите их:

```python
from dotenv import load_dotenv
import os

load_dotenv()

async with YandexTrackerClient(
    oauth_token=os.getenv("TRACKER_API_KEY"),
    org_id=os.getenv("TRACKER_ORG_ID")
) as client:
    # ваш код
```

## 💡 Основные возможности

### Работа с задачами

#### Создание задачи

```python
# Простое создание
issue = await client.issues.create(
    summary="Новая задача",
    queue="TESTBOT"
)

# С полными параметрами
issue = await client.issues.create(
    summary="Задача с полными параметрами",
    queue="TESTBOT",
    description="Подробное описание",
    assignee="username",
    priority={"id": "2", "key": "minor"},
    type="bug",
    tags=["backend", "urgent"]
)

# С кастомными полями
issue = await client.issues.create(
    summary="Задача с кастомными полями",
    queue="TESTBOT",
    localfields={
        "numberOfEmployees": 10,
        "contact_cl": "ООО Рога и Копыта",
        "customPriority": "Очень высокий"
    }
)
```

#### Получение задачи

```python
# Базовое получение
issue = await client.issues.get('TESTBOT-1')

# С расширенными полями
issue = await client.issues.get(
    'TESTBOT-1',
    expand=['transitions', 'attachments']
)
```

#### Обновление задачи

```python
# Простое обновление
await client.issues.update(
    issue_id='TESTBOT-1',
    summary="Обновленное название"
)

# Полное обновление
await client.issues.update(
    issue_id='TESTBOT-1',
    summary="Новое название",
    description="Новое описание",
    priority={"id": "1", "key": "critical"},
    assignee="new_assignee",
    tags={"add": ["new_tag"], "remove": ["old_tag"]}
)
```

#### Перенос задачи

```python
# Перенос в другую очередь
issue = await client.issues.move(
    issue_id='TESTBOT-1',
    queue='NEWQUEUE',
    move_all_fields=True,
    initial_status=True
)
```

#### Поиск задач

```python
# Поиск с фильтром
issues = await client.issues.search(
    filter={"queue": "TESTBOT", "assignee": "me()"},
    order="+status",
    expand=["transitions", "attachments"],
    per_page=50
)

# Поиск по текстовому запросу
issues = await client.issues.search(
    query="Queue: TESTBOT AND Status: Open"
)
```

#### Подсчет задач

```python
count = await client.issues.count(
    query="Queue: TESTBOT AND Status: New"
)
print(f"Найдено задач: {count}")
```

#### Получение приоритетов

```python
# Все приоритеты
priorities = await client.issues.priorities()

# Локализованные приоритеты
priorities = await client.issues.priorities(localized=True)
```

#### История изменений

```python
changelog = await client.issues.changelog('TESTBOT-1')
for change in changelog:
    print(f"{change['updatedAt']}: {change['updatedBy']['display']}")
```

### Связи между задачами

```python
# Получение связей
links = await client.issues.links.get('TESTBOT-1')

# Создание связи
await client.issues.links.create(
    issue_id='TESTBOT-1',
    relationship='relates',
    issue='TESTBOT-2'
)

# Удаление связи
await client.issues.links.delete(
    issue_id='TESTBOT-1',
    link_id='12345'
)
```

### Управление статусами

```python
# Получение доступных переходов
transitions = await client.issues.transitions.get('TESTBOT-1')

# Выполнение перехода
await client.issues.transitions.update(
    issue_id='TESTBOT-1',
    transition_id='resolve'
)
```

### Чеклисты

```python
# Получение чеклистов задачи
checklists = await client.issues.checklists.get('TESTBOT-1')

# Создание пункта чеклиста
await client.issues.checklists.create(
    issue_id='TESTBOT-1',
    text='Выполнить задачу',
    checked=False
)

# Создание с дедлайном
await client.issues.checklists.create(
    issue_id='TESTBOT-1',
    text='Подготовить релиз',
    checked=False,
    deadline={
        'date': '2025-12-31T23:59:59.000+0000',
        'deadlineType': 'date'
    }
)

# Обновление пункта чеклиста
await client.issues.checklists.item.update(
    issue_id='TESTBOT-1',
    checklist_item_id='item123',
    text='Обновленный текст',
    checked=True
)

# Удаление пункта
await client.issues.checklists.item.delete(
    issue_id='TESTBOT-1',
    checklist_item_id='item123'
)
```

### Комментарии

```python
# Получение комментариев
comments = await client.issues.comments.get('TESTBOT-1')

# Создание комментария
await client.issues.comments.create(
    issue_id='TESTBOT-1',
    text='Работа завершена',
    summonees=['user1', 'user2']
)

# Обновление комментария
await client.issues.comments.update(
    issue_id='TESTBOT-1',
    comment_id='123',
    text='Обновленный комментарий'
)

# Удаление комментария
await client.issues.comments.delete(
    issue_id='TESTBOT-1',
    comment_id='123'
)
```

### Работа с полями

```python
# Получение всех полей
fields = await client.issues.fields.get()

# Получение конкретного поля
field = await client.issues.fields.get(field_id='customField1')

# Создание кастомного поля
await client.fields.create(
    name="Приоритет проекта",
    key="projectPriority",
    category="0000000000000001",
    type="ru.yandex.startrek.core.fields.StringFieldType"
)

# Обновление поля
await client.fields.update(
    field_id='customField1',
    name="Новое название поля"
)

# Работа с локальными полями очереди
local_fields = await client.fields.local.get(queue_id='TESTBOT')

# Создание локального поля
await client.fields.local.create(
    queue_id='TESTBOT',
    name="Локальное поле",
    key="localField1"
)
```

### Работа с сущностями

Сущности в Yandex Tracker - это проекты, портфели и цели.

```python
# Создание проекта
project = await client.entities.create(
    entity_type="project",
    summary="Новый проект",
    lead="username"
)

# Получение сущности
entity = await client.entities.get(
    entity_id="project123",
    expand=["attachments"]
)

# Обновление сущности
await client.entities.update(
    entity_id="project123",
    summary="Обновленное название проекта"
)

# Поиск сущностей
entities = await client.entities.search(
    entity_type="project",
    filter={"lead": "username"},
    order="+createdAt"
)

# Удаление сущности
await client.entities.delete(
    entity_id="project123",
    withBoard=True  # Удалить вместе с доской
)

# Массовое обновление
await client.entities.bulk.update(
    entity_ids=["project1", "project2"],
    status="active"
)

# Работа со связями сущностей
await client.entities.links.create(
    entity_id="project1",
    relationship="relates",
    entity="project2"
)
```

### Работа с пользователями

```python
# Получение списка всех пользователей
users = await client.users.get()

# Анализ пользователей
for user in users:
    print(f"{user['display']} ({user['login']})")

# Фильтрация активных
active_users = [u for u in users if not u.get('dismissed', False)]

# Получение конкретного пользователя по логину
user = await client.users.get('username')

# Получение пользователя по ID
user = await client.users.get(123456)
```

### Работа с очередями

```python
# Получение списка всех очередей
queues = await client.queues.get()

# Получение конкретной очереди
queue = await client.queues.get('TREK', expand='all')

# Создание очереди
new_queue = await client.queues.create(
    key='DESIGN',
    name='Design Team',
    lead='username',
    default_type='task',
    default_priority='normal',
    issue_types_config=[
        {
            'issueType': 'task',
            'workflow': 'oicn',
            'resolutions': ['wontFix', 'fixed']
        }
    ],
    description='Очередь команды дизайна'
)

# Удаление очереди
await client.queues.delete('OLDQUEUE')

# Восстановление удаленной очереди
restored_queue = await client.queues.restore('OLDQUEUE')

# Работа с версиями очереди
versions = await client.queues.versions.get('TREK')

# Создание версии
version = await client.queues.versions.create(
    queue='TREK',
    name='v2.0.0',
    description='Релиз версии 2.0',
    start_date='2025-01-01',
    due_date='2025-12-31'
)

# Получение полей очереди
fields = await client.queues.fields.get('TREK')

# Получение тегов очереди
tags = await client.queues.tags.get('TREK')

# Удаление тега
await client.queues.tags.delete('TREK', 'deprecated')

# Массовое управление доступом к очереди
await client.queues.bulk.update(
    queue_id='TREK',
    create={'add': ['user1', 'user2']},
    write={'remove': ['user3']}
)

# Получение прав доступа пользователя к очереди
user_access = await client.queues.user.get('TREK', 'username')

# Получение прав доступа группы к очереди
group_access = await client.queues.group.get('TREK', 'group_id')
```

## 🔧 API Модули

Клиент состоит из следующих модулей:

### IssuesAPI (`client.issues`)
Работа с задачами:
- `get()` - получение задачи
- `create()` - создание задачи
- `update()` - обновление задачи
- `move()` - перенос задачи между очередями
- `search()` - поиск задач
- `count()` - подсчет задач
- `priorities()` - получение приоритетов
- `changelog()` - история изменений

**Подмодули:**
- `links` - связи между задачами
- `transitions` - переходы статусов
- `checklists` - чеклисты задач
- `comments` - комментарии
- `fields` - поля задач

### FieldsAPI (`client.fields`)
Управление полями:
- `get()` - получение списка полей
- `create()` - создание кастомного поля
- `update()` - обновление поля
- `create_category()` - создание категории полей

**Подмодуль:**
- `local` - локальные поля очередей

### EntitiesAPI (`client.entities`)
Работа с сущностями (проекты, портфели, цели):
- `get()` - получение сущности
- `create()` - создание сущности
- `update()` - обновление сущности
- `delete()` - удаление сущности
- `search()` - поиск сущностей
- `changelog()` - история изменений

**Подмодули:**
- `checklists` - чеклисты сущностей
- `bulk` - массовые операции
- `links` - связи между сущностями

### UsersAPI (`client.users`)
Работа с пользователями:
- `get()` - получение списка всех пользователей или конкретного пользователя

### QueuesAPI (`client.queues`)
Управление очередями:
- `get()` - получение очереди или списка очередей
- `create()` - создание новой очереди
- `delete()` - удаление очереди
- `restore()` - восстановление удаленной очереди

**Подмодули:**
- `versions` - версии очередей (create, get)
- `fields` - поля очередей (get)
- `tags` - теги очередей (get, delete)
- `bulk` - массовое управление доступом (update)
- `user` - права доступа пользователя (get)
- `group` - права доступа группы (get)

## ⚠️ Особенности и подводные камни

При работе с Yandex Tracker API есть ряд важных нюансов, которые необходимо учитывать для корректной работы.

### 1. Пагинация при поиске сущностей

**Проблема:** `entities.search()` возвращает пагинированный ответ в виде словаря, а не списка.

**Структура ответа:**
```python
{
    "hits": 125,      # Общее количество найденных сущностей
    "pages": 3,       # Количество страниц
    "values": [...]   # Массив сущностей на текущей странице (по умолчанию 50)
}
```

**Решение:**
```python
# Получаем первую страницу
projects_raw = await client.entities.search(
    entity_type="project",
    fields="summary,id"
)

# Проверяем тип ответа и загружаем все страницы
if isinstance(projects_raw, dict):
    pages = projects_raw.get("pages", 1)

    if isinstance(pages, int) and pages > 1:
        per_page = pages * 50
        projects_raw = await client.entities.search(
            entity_type="project",
            fields="summary,id",
            per_page=per_page
        )

    projects = projects_raw.get("values", [])
else:
    projects = projects_raw
```

### 2. Параметр fields при поиске

**Проблема:** Без явного указания полей в параметре `fields`, API может не возвращать важные данные.

**Неправильно:**
```python
projects = await client.entities.search(entity_type="project")
# summary может отсутствовать в ответе
```

**Правильно:**
```python
projects = await client.entities.search(
    entity_type="project",
    fields="summary,id,description"
)

# Извлекаем данные
for proj in projects["values"]:
    summary = proj.get("fields", {}).get("summary", "")
    if not summary:
        summary = proj.get("summary", "")
```

### 3. Формат поля project при создании задачи

**Проблема:** API ожидает `shortId` проекта (число), а не полный `id` (строка).

**Неправильно:**
```python
project = {
    "id": "68e5764ffa5085239cef5e94"  # ❌ Строковый ID
}
```

**Правильно:**
```python
# После создания проекта
new_project = await client.entities.create(
    entity_type="project",
    summary="Новый проект"
)

# Используйте shortId
project_short_id = new_project.get("shortId")

# Создаем задачу
issue = await client.issues.create(
    summary="Задача проекта",
    queue="TESTBOT",
    project={"primary": project_short_id}  # ✅ shortId как число
)
```

### 4. Формат полей type и priority

**Проблема:** API выдает ошибку 400, если передать полные объекты вместо ключей.

**Неправильно:**
```python
issue = await client.issues.get("TESTBOT-1")

new_issue = await client.issues.create(
    summary="Копия",
    queue="TESTBOT",
    type=issue["type"],        # ❌ Полный объект
    priority=issue["priority"]  # ❌ Полный объект
)
```

**Правильно:**
```python
def extract_field_value(field_data):
    """Извлечь значение поля для API запроса"""
    if isinstance(field_data, dict):
        return field_data.get("key") or field_data.get("id")
    return field_data

new_issue = await client.issues.create(
    summary="Копия",
    queue="TESTBOT",
    type=extract_field_value(issue.get("type")),        # ✅ "milestone"
    priority=extract_field_value(issue.get("priority"))  # ✅ "normal"
)
```

### 5. Получение сущностей с полным набором полей

**Проблема:** Без параметра `fields` API возвращает только базовые поля.

**Неправильно:**
```python
project = await client.entities.get(
    entity_id=project_id,
    entity_type="project"
)
# НЕТ: description, lead, teamUsers, parentEntity
```

**Правильно:**
```python
project = await client.entities.get(
    entity_id=project_id,
    entity_type="project",
    fields="summary,description,lead,teamUsers,teamAccess,parentEntity,clients,followers,start,end,tags"
)
# ✅ Все поля присутствуют
```

### 6. Ограничения на изменение системных полей

**Нельзя изменить:**
- ❌ `createdBy` - автор (устанавливается автоматически)
- ❌ `createdAt` - дата создания
- ❌ `author` - автор (алиас createdBy)

**Можно изменить:**
- ✅ `lead` - руководитель проекта
- ✅ `assignee` - исполнитель задачи
- ✅ `teamUsers` - участники
- ✅ `followers` - наблюдатели

```python
# Правильное обновление
await client.entities.update(
    entity_id=project_id,
    lead="user_login",
    teamUsers=["user1", "user2"]
)
```

### Дополнительные замечания

**Автоконвертация параметров:** Библиотека автоматически конвертирует snake_case в camelCase:
```python
per_page=50  # → perPage=50 в API
```

**Обработка ошибок:** Используйте debug логирование для диагностики:
```python
import logging
logging.getLogger("YaTrackerApi").setLevel(logging.DEBUG)
```

Полная документация: [context/gotchas.md](context/gotchas.md)

## 📋 Требования

- Python 3.9+
- aiohttp >= 3.12.15
- python-dotenv >= 1.1.1

## 📄 Лицензия

Проект распространяется под лицензией [MIT](LICENSE).

## 👤 Контакты

**Даниил**

- GitHub: [@imdeniil](https://github.com/imdeniil)
- Email: keemor821@gmail.com

## 🔗 Ссылки

- [PyPI](https://pypi.org/project/YaTrackerApi/)
- [GitHub Repository](https://github.com/imdeniil/YaTrackerApi)
- [Issues](https://github.com/imdeniil/YaTrackerApi/issues)
- [Официальная документация Yandex Tracker API](https://cloud.yandex.ru/docs/tracker/about-api)

---

**Создано с ❤️ для упрощения работы с Yandex Tracker API**

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "YaTrackerApi",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": "\u0414\u0430\u043d\u0438\u0438\u043b <keemor821@gmail.com>",
    "keywords": "aiohttp, api, async, project-management, task-tracker, tracker, yandex",
    "author": null,
    "author_email": "\u0414\u0430\u043d\u0438\u0438\u043b <keemor821@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/5c/3d/b6e3ab9a327e29b89733e915bc32f9d48f40c0112f8697838208b69ce4a4/yatrackerapi-2.1.2.tar.gz",
    "platform": null,
    "description": "# YaTrackerApi\n\n> \u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 Python \u043a\u043b\u0438\u0435\u043d\u0442 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 Yandex Tracker API \u0441 \u043c\u043e\u0434\u0443\u043b\u044c\u043d\u043e\u0439 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043e\u0439\n\n[![PyPI version](https://badge.fury.io/py/YaTrackerApi.svg)](https://badge.fury.io/py/YaTrackerApi)\n[![Python](https://img.shields.io/pypi/pyversions/YaTrackerApi.svg)](https://pypi.org/project/YaTrackerApi/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n## \ud83d\udccb \u0421\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435\n\n- [\u041e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u0438](#-\u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u0438)\n- [\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430](#-\u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430)\n- [\u0411\u044b\u0441\u0442\u0440\u044b\u0439 \u0441\u0442\u0430\u0440\u0442](#-\u0431\u044b\u0441\u0442\u0440\u044b\u0439-\u0441\u0442\u0430\u0440\u0442)\n- [\u041e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438](#-\u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0435-\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438)\n  - [\u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u0437\u0430\u0434\u0430\u0447\u0430\u043c\u0438](#\u0440\u0430\u0431\u043e\u0442\u0430-\u0441-\u0437\u0430\u0434\u0430\u0447\u0430\u043c\u0438)\n  - [\u0421\u0432\u044f\u0437\u0438 \u043c\u0435\u0436\u0434\u0443 \u0437\u0430\u0434\u0430\u0447\u0430\u043c\u0438](#\u0441\u0432\u044f\u0437\u0438-\u043c\u0435\u0436\u0434\u0443-\u0437\u0430\u0434\u0430\u0447\u0430\u043c\u0438)\n  - [\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u0442\u0430\u0442\u0443\u0441\u0430\u043c\u0438](#\u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435-\u0441\u0442\u0430\u0442\u0443\u0441\u0430\u043c\u0438)\n  - [\u0427\u0435\u043a\u043b\u0438\u0441\u0442\u044b](#\u0447\u0435\u043a\u043b\u0438\u0441\u0442\u044b)\n  - [\u041a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438](#\u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438)\n  - [\u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u043f\u043e\u043b\u044f\u043c\u0438](#\u0440\u0430\u0431\u043e\u0442\u0430-\u0441-\u043f\u043e\u043b\u044f\u043c\u0438)\n  - [\u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044f\u043c\u0438](#\u0440\u0430\u0431\u043e\u0442\u0430-\u0441-\u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044f\u043c\u0438)\n  - [\u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c\u0438](#\u0440\u0430\u0431\u043e\u0442\u0430-\u0441-\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c\u0438)\n  - [\u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u043e\u0447\u0435\u0440\u0435\u0434\u044f\u043c\u0438](#\u0440\u0430\u0431\u043e\u0442\u0430-\u0441-\u043e\u0447\u0435\u0440\u0435\u0434\u044f\u043c\u0438)\n- [API \u041c\u043e\u0434\u0443\u043b\u0438](#-api-\u043c\u043e\u0434\u0443\u043b\u0438)\n- [\u041e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u0438 \u0438 \u043f\u043e\u0434\u0432\u043e\u0434\u043d\u044b\u0435 \u043a\u0430\u043c\u043d\u0438](#-\u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u0438-\u0438-\u043f\u043e\u0434\u0432\u043e\u0434\u043d\u044b\u0435-\u043a\u0430\u043c\u043d\u0438)\n- [\u0422\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f](#-\u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f)\n- [\u041b\u0438\u0446\u0435\u043d\u0437\u0438\u044f](#-\u043b\u0438\u0446\u0435\u043d\u0437\u0438\u044f)\n- [\u041a\u043e\u043d\u0442\u0430\u043a\u0442\u044b](#-\u043a\u043e\u043d\u0442\u0430\u043a\u0442\u044b)\n\n## \u2728 \u041e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u0438\n\n- **\u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0441\u0442\u044c** - \u043f\u043e\u043b\u043d\u0430\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 `async/await` \u043d\u0430 \u0431\u0430\u0437\u0435 `aiohttp`\n- **\u041c\u043e\u0434\u0443\u043b\u044c\u043d\u0430\u044f \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430** - \u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u043f\u043e \u043c\u043e\u0434\u0443\u043b\u044f\u043c\n- **Lazy Loading** - \u043c\u043e\u0434\u0443\u043b\u0438 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u044e\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u0438 \u043f\u0435\u0440\u0432\u043e\u043c \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u0438\n- **\u0422\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u044f** - \u043f\u043e\u043b\u043d\u0430\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 \u0442\u0438\u043f\u043e\u0432 \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u043c\u0435\u0442\u043e\u0434\u043e\u0432\n- **\u0413\u0438\u0431\u043a\u043e\u0441\u0442\u044c** - \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 \u0444\u043e\u0440\u043c\u0430\u0442\u044b \u0434\u043b\u044f \u043e\u0434\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044f (\u0441\u0442\u0440\u043e\u043a\u0438, \u0447\u0438\u0441\u043b\u0430, \u043e\u0431\u044a\u0435\u043a\u0442\u044b)\n- **\u0412\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044f** - \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0430 \u0441\u0442\u043e\u0440\u043e\u043d\u0435 \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u043f\u0435\u0440\u0435\u0434 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u043e\u0439\n- **\u041b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435** - \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u043b\u043e\u0433\u043e\u0432 \u043d\u0430 \u0440\u0443\u0441\u0441\u043a\u043e\u043c \u044f\u0437\u044b\u043a\u0435 \u0441 \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c\u044b\u043c \u0443\u0440\u043e\u0432\u043d\u0435\u043c\n- **\u041a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u0435 \u043f\u043e\u043b\u044f** - \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0445 \u043f\u043e\u043b\u0435\u0439 \u043b\u044e\u0431\u044b\u0445 \u0442\u0438\u043f\u043e\u0432\n- **\u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u044c** - \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0439 SSL/TLS \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0439\n\n## \ud83d\udce6 \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430\n\n```bash\npip install YaTrackerApi\n```\n\n\u0418\u043b\u0438 \u0447\u0435\u0440\u0435\u0437 `uv`:\n\n```bash\nuv add YaTrackerApi\n```\n\n## \ud83d\ude80 \u0411\u044b\u0441\u0442\u0440\u044b\u0439 \u0441\u0442\u0430\u0440\u0442\n\n```python\nimport asyncio\nfrom client import YandexTrackerClient\n\nasync def main():\n    async with YandexTrackerClient(\n        oauth_token=\"your_oauth_token\",\n        org_id=\"your_org_id\",\n        log_level=\"INFO\"\n    ) as client:\n        # \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0437\u0430\u0434\u0430\u0447\u0438\n        issue = await client.issues.create(\n            summary=\"\u041d\u043e\u0432\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430\",\n            queue=\"TESTBOT\",\n            description=\"\u041f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0437\u0430\u0434\u0430\u0447\u0438\"\n        )\n        print(f\"\u0421\u043e\u0437\u0434\u0430\u043d\u0430 \u0437\u0430\u0434\u0430\u0447\u0430: {issue['key']}\")\n\n        # \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0437\u0430\u0434\u0430\u0447\u0438\n        issue = await client.issues.get('TESTBOT-1')\n        print(f\"\u0417\u0430\u0434\u0430\u0447\u0430: {issue['summary']}\")\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n```\n\n### \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f\n\n\u0421\u043e\u0437\u0434\u0430\u0439\u0442\u0435 \u0444\u0430\u0439\u043b `.env` \u0432 \u043a\u043e\u0440\u043d\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430:\n\n```env\nTRACKER_API_KEY=your_oauth_token\nTRACKER_ORG_ID=your_org_id\n```\n\n\u0418 \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u0435 \u0438\u0445:\n\n```python\nfrom dotenv import load_dotenv\nimport os\n\nload_dotenv()\n\nasync with YandexTrackerClient(\n    oauth_token=os.getenv(\"TRACKER_API_KEY\"),\n    org_id=os.getenv(\"TRACKER_ORG_ID\")\n) as client:\n    # \u0432\u0430\u0448 \u043a\u043e\u0434\n```\n\n## \ud83d\udca1 \u041e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438\n\n### \u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u0437\u0430\u0434\u0430\u0447\u0430\u043c\u0438\n\n#### \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0437\u0430\u0434\u0430\u0447\u0438\n\n```python\n# \u041f\u0440\u043e\u0441\u0442\u043e\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435\nissue = await client.issues.create(\n    summary=\"\u041d\u043e\u0432\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430\",\n    queue=\"TESTBOT\"\n)\n\n# \u0421 \u043f\u043e\u043b\u043d\u044b\u043c\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438\nissue = await client.issues.create(\n    summary=\"\u0417\u0430\u0434\u0430\u0447\u0430 \u0441 \u043f\u043e\u043b\u043d\u044b\u043c\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438\",\n    queue=\"TESTBOT\",\n    description=\"\u041f\u043e\u0434\u0440\u043e\u0431\u043d\u043e\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435\",\n    assignee=\"username\",\n    priority={\"id\": \"2\", \"key\": \"minor\"},\n    type=\"bug\",\n    tags=[\"backend\", \"urgent\"]\n)\n\n# \u0421 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u043c\u0438 \u043f\u043e\u043b\u044f\u043c\u0438\nissue = await client.issues.create(\n    summary=\"\u0417\u0430\u0434\u0430\u0447\u0430 \u0441 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u044b\u043c\u0438 \u043f\u043e\u043b\u044f\u043c\u0438\",\n    queue=\"TESTBOT\",\n    localfields={\n        \"numberOfEmployees\": 10,\n        \"contact_cl\": \"\u041e\u041e\u041e \u0420\u043e\u0433\u0430 \u0438 \u041a\u043e\u043f\u044b\u0442\u0430\",\n        \"customPriority\": \"\u041e\u0447\u0435\u043d\u044c \u0432\u044b\u0441\u043e\u043a\u0438\u0439\"\n    }\n)\n```\n\n#### \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0437\u0430\u0434\u0430\u0447\u0438\n\n```python\n# \u0411\u0430\u0437\u043e\u0432\u043e\u0435 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435\nissue = await client.issues.get('TESTBOT-1')\n\n# \u0421 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u044b\u043c\u0438 \u043f\u043e\u043b\u044f\u043c\u0438\nissue = await client.issues.get(\n    'TESTBOT-1',\n    expand=['transitions', 'attachments']\n)\n```\n\n#### \u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0437\u0430\u0434\u0430\u0447\u0438\n\n```python\n# \u041f\u0440\u043e\u0441\u0442\u043e\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435\nawait client.issues.update(\n    issue_id='TESTBOT-1',\n    summary=\"\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u043e\u0435 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\"\n)\n\n# \u041f\u043e\u043b\u043d\u043e\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435\nawait client.issues.update(\n    issue_id='TESTBOT-1',\n    summary=\"\u041d\u043e\u0432\u043e\u0435 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\",\n    description=\"\u041d\u043e\u0432\u043e\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435\",\n    priority={\"id\": \"1\", \"key\": \"critical\"},\n    assignee=\"new_assignee\",\n    tags={\"add\": [\"new_tag\"], \"remove\": [\"old_tag\"]}\n)\n```\n\n#### \u041f\u0435\u0440\u0435\u043d\u043e\u0441 \u0437\u0430\u0434\u0430\u0447\u0438\n\n```python\n# \u041f\u0435\u0440\u0435\u043d\u043e\u0441 \u0432 \u0434\u0440\u0443\u0433\u0443\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c\nissue = await client.issues.move(\n    issue_id='TESTBOT-1',\n    queue='NEWQUEUE',\n    move_all_fields=True,\n    initial_status=True\n)\n```\n\n#### \u041f\u043e\u0438\u0441\u043a \u0437\u0430\u0434\u0430\u0447\n\n```python\n# \u041f\u043e\u0438\u0441\u043a \u0441 \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u043c\nissues = await client.issues.search(\n    filter={\"queue\": \"TESTBOT\", \"assignee\": \"me()\"},\n    order=\"+status\",\n    expand=[\"transitions\", \"attachments\"],\n    per_page=50\n)\n\n# \u041f\u043e\u0438\u0441\u043a \u043f\u043e \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u043c\u0443 \u0437\u0430\u043f\u0440\u043e\u0441\u0443\nissues = await client.issues.search(\n    query=\"Queue: TESTBOT AND Status: Open\"\n)\n```\n\n#### \u041f\u043e\u0434\u0441\u0447\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\n\n```python\ncount = await client.issues.count(\n    query=\"Queue: TESTBOT AND Status: New\"\n)\nprint(f\"\u041d\u0430\u0439\u0434\u0435\u043d\u043e \u0437\u0430\u0434\u0430\u0447: {count}\")\n```\n\n#### \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u043f\u0440\u0438\u043e\u0440\u0438\u0442\u0435\u0442\u043e\u0432\n\n```python\n# \u0412\u0441\u0435 \u043f\u0440\u0438\u043e\u0440\u0438\u0442\u0435\u0442\u044b\npriorities = await client.issues.priorities()\n\n# \u041b\u043e\u043a\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u043f\u0440\u0438\u043e\u0440\u0438\u0442\u0435\u0442\u044b\npriorities = await client.issues.priorities(localized=True)\n```\n\n#### \u0418\u0441\u0442\u043e\u0440\u0438\u044f \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439\n\n```python\nchangelog = await client.issues.changelog('TESTBOT-1')\nfor change in changelog:\n    print(f\"{change['updatedAt']}: {change['updatedBy']['display']}\")\n```\n\n### \u0421\u0432\u044f\u0437\u0438 \u043c\u0435\u0436\u0434\u0443 \u0437\u0430\u0434\u0430\u0447\u0430\u043c\u0438\n\n```python\n# \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0441\u0432\u044f\u0437\u0435\u0439\nlinks = await client.issues.links.get('TESTBOT-1')\n\n# \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0441\u0432\u044f\u0437\u0438\nawait client.issues.links.create(\n    issue_id='TESTBOT-1',\n    relationship='relates',\n    issue='TESTBOT-2'\n)\n\n# \u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0441\u0432\u044f\u0437\u0438\nawait client.issues.links.delete(\n    issue_id='TESTBOT-1',\n    link_id='12345'\n)\n```\n\n### \u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u0442\u0430\u0442\u0443\u0441\u0430\u043c\u0438\n\n```python\n# \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u043e\u0432\ntransitions = await client.issues.transitions.get('TESTBOT-1')\n\n# \u0412\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430\nawait client.issues.transitions.update(\n    issue_id='TESTBOT-1',\n    transition_id='resolve'\n)\n```\n\n### \u0427\u0435\u043a\u043b\u0438\u0441\u0442\u044b\n\n```python\n# \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0447\u0435\u043a\u043b\u0438\u0441\u0442\u043e\u0432 \u0437\u0430\u0434\u0430\u0447\u0438\nchecklists = await client.issues.checklists.get('TESTBOT-1')\n\n# \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043f\u0443\u043d\u043a\u0442\u0430 \u0447\u0435\u043a\u043b\u0438\u0441\u0442\u0430\nawait client.issues.checklists.create(\n    issue_id='TESTBOT-1',\n    text='\u0412\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0437\u0430\u0434\u0430\u0447\u0443',\n    checked=False\n)\n\n# \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0441 \u0434\u0435\u0434\u043b\u0430\u0439\u043d\u043e\u043c\nawait client.issues.checklists.create(\n    issue_id='TESTBOT-1',\n    text='\u041f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c \u0440\u0435\u043b\u0438\u0437',\n    checked=False,\n    deadline={\n        'date': '2025-12-31T23:59:59.000+0000',\n        'deadlineType': 'date'\n    }\n)\n\n# \u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u0443\u043d\u043a\u0442\u0430 \u0447\u0435\u043a\u043b\u0438\u0441\u0442\u0430\nawait client.issues.checklists.item.update(\n    issue_id='TESTBOT-1',\n    checklist_item_id='item123',\n    text='\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044b\u0439 \u0442\u0435\u043a\u0441\u0442',\n    checked=True\n)\n\n# \u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u043f\u0443\u043d\u043a\u0442\u0430\nawait client.issues.checklists.item.delete(\n    issue_id='TESTBOT-1',\n    checklist_item_id='item123'\n)\n```\n\n### \u041a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438\n\n```python\n# \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0435\u0432\ncomments = await client.issues.comments.get('TESTBOT-1')\n\n# \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\nawait client.issues.comments.create(\n    issue_id='TESTBOT-1',\n    text='\u0420\u0430\u0431\u043e\u0442\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0430',\n    summonees=['user1', 'user2']\n)\n\n# \u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\nawait client.issues.comments.update(\n    issue_id='TESTBOT-1',\n    comment_id='123',\n    text='\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u044b\u0439 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439'\n)\n\n# \u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\nawait client.issues.comments.delete(\n    issue_id='TESTBOT-1',\n    comment_id='123'\n)\n```\n\n### \u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u043f\u043e\u043b\u044f\u043c\u0438\n\n```python\n# \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0432\u0441\u0435\u0445 \u043f\u043e\u043b\u0435\u0439\nfields = await client.issues.fields.get()\n\n# \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044f\nfield = await client.issues.fields.get(field_id='customField1')\n\n# \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044f\nawait client.fields.create(\n    name=\"\u041f\u0440\u0438\u043e\u0440\u0438\u0442\u0435\u0442 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\",\n    key=\"projectPriority\",\n    category=\"0000000000000001\",\n    type=\"ru.yandex.startrek.core.fields.StringFieldType\"\n)\n\n# \u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u044f\nawait client.fields.update(\n    field_id='customField1',\n    name=\"\u041d\u043e\u0432\u043e\u0435 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043f\u043e\u043b\u044f\"\n)\n\n# \u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u043c\u0438 \u043f\u043e\u043b\u044f\u043c\u0438 \u043e\u0447\u0435\u0440\u0435\u0434\u0438\nlocal_fields = await client.fields.local.get(queue_id='TESTBOT')\n\n# \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044f\nawait client.fields.local.create(\n    queue_id='TESTBOT',\n    name=\"\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u043b\u0435\",\n    key=\"localField1\"\n)\n```\n\n### \u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044f\u043c\u0438\n\n\u0421\u0443\u0449\u043d\u043e\u0441\u0442\u0438 \u0432 Yandex Tracker - \u044d\u0442\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u044b, \u043f\u043e\u0440\u0442\u0444\u0435\u043b\u0438 \u0438 \u0446\u0435\u043b\u0438.\n\n```python\n# \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\nproject = await client.entities.create(\n    entity_type=\"project\",\n    summary=\"\u041d\u043e\u0432\u044b\u0439 \u043f\u0440\u043e\u0435\u043a\u0442\",\n    lead=\"username\"\n)\n\n# \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438\nentity = await client.entities.get(\n    entity_id=\"project123\",\n    expand=[\"attachments\"]\n)\n\n# \u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438\nawait client.entities.update(\n    entity_id=\"project123\",\n    summary=\"\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u043e\u0435 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\"\n)\n\n# \u041f\u043e\u0438\u0441\u043a \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0435\u0439\nentities = await client.entities.search(\n    entity_type=\"project\",\n    filter={\"lead\": \"username\"},\n    order=\"+createdAt\"\n)\n\n# \u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438\nawait client.entities.delete(\n    entity_id=\"project123\",\n    withBoard=True  # \u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u0434\u043e\u0441\u043a\u043e\u0439\n)\n\n# \u041c\u0430\u0441\u0441\u043e\u0432\u043e\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435\nawait client.entities.bulk.update(\n    entity_ids=[\"project1\", \"project2\"],\n    status=\"active\"\n)\n\n# \u0420\u0430\u0431\u043e\u0442\u0430 \u0441\u043e \u0441\u0432\u044f\u0437\u044f\u043c\u0438 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0435\u0439\nawait client.entities.links.create(\n    entity_id=\"project1\",\n    relationship=\"relates\",\n    entity=\"project2\"\n)\n```\n\n### \u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c\u0438\n\n```python\n# \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0441\u043f\u0438\u0441\u043a\u0430 \u0432\u0441\u0435\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439\nusers = await client.users.get()\n\n# \u0410\u043d\u0430\u043b\u0438\u0437 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439\nfor user in users:\n    print(f\"{user['display']} ({user['login']})\")\n\n# \u0424\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u044f \u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0445\nactive_users = [u for u in users if not u.get('dismissed', False)]\n\n# \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043f\u043e \u043b\u043e\u0433\u0438\u043d\u0443\nuser = await client.users.get('username')\n\n# \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043f\u043e ID\nuser = await client.users.get(123456)\n```\n\n### \u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u043e\u0447\u0435\u0440\u0435\u0434\u044f\u043c\u0438\n\n```python\n# \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0441\u043f\u0438\u0441\u043a\u0430 \u0432\u0441\u0435\u0445 \u043e\u0447\u0435\u0440\u0435\u0434\u0435\u0439\nqueues = await client.queues.get()\n\n# \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0439 \u043e\u0447\u0435\u0440\u0435\u0434\u0438\nqueue = await client.queues.get('TREK', expand='all')\n\n# \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043e\u0447\u0435\u0440\u0435\u0434\u0438\nnew_queue = await client.queues.create(\n    key='DESIGN',\n    name='Design Team',\n    lead='username',\n    default_type='task',\n    default_priority='normal',\n    issue_types_config=[\n        {\n            'issueType': 'task',\n            'workflow': 'oicn',\n            'resolutions': ['wontFix', 'fixed']\n        }\n    ],\n    description='\u041e\u0447\u0435\u0440\u0435\u0434\u044c \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0434\u0438\u0437\u0430\u0439\u043d\u0430'\n)\n\n# \u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u043e\u0447\u0435\u0440\u0435\u0434\u0438\nawait client.queues.delete('OLDQUEUE')\n\n# \u0412\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u043e\u0439 \u043e\u0447\u0435\u0440\u0435\u0434\u0438\nrestored_queue = await client.queues.restore('OLDQUEUE')\n\n# \u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u0432\u0435\u0440\u0441\u0438\u044f\u043c\u0438 \u043e\u0447\u0435\u0440\u0435\u0434\u0438\nversions = await client.queues.versions.get('TREK')\n\n# \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0432\u0435\u0440\u0441\u0438\u0438\nversion = await client.queues.versions.create(\n    queue='TREK',\n    name='v2.0.0',\n    description='\u0420\u0435\u043b\u0438\u0437 \u0432\u0435\u0440\u0441\u0438\u0438 2.0',\n    start_date='2025-01-01',\n    due_date='2025-12-31'\n)\n\n# \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u0435\u0439 \u043e\u0447\u0435\u0440\u0435\u0434\u0438\nfields = await client.queues.fields.get('TREK')\n\n# \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0442\u0435\u0433\u043e\u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u0438\ntags = await client.queues.tags.get('TREK')\n\n# \u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0442\u0435\u0433\u0430\nawait client.queues.tags.delete('TREK', 'deprecated')\n\n# \u041c\u0430\u0441\u0441\u043e\u0432\u043e\u0435 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c \u043a \u043e\u0447\u0435\u0440\u0435\u0434\u0438\nawait client.queues.bulk.update(\n    queue_id='TREK',\n    create={'add': ['user1', 'user2']},\n    write={'remove': ['user3']}\n)\n\n# \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u043f\u0440\u0430\u0432 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043a \u043e\u0447\u0435\u0440\u0435\u0434\u0438\nuser_access = await client.queues.user.get('TREK', 'username')\n\n# \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u043f\u0440\u0430\u0432 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0433\u0440\u0443\u043f\u043f\u044b \u043a \u043e\u0447\u0435\u0440\u0435\u0434\u0438\ngroup_access = await client.queues.group.get('TREK', 'group_id')\n```\n\n## \ud83d\udd27 API \u041c\u043e\u0434\u0443\u043b\u0438\n\n\u041a\u043b\u0438\u0435\u043d\u0442 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u043c\u043e\u0434\u0443\u043b\u0435\u0439:\n\n### IssuesAPI (`client.issues`)\n\u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u0437\u0430\u0434\u0430\u0447\u0430\u043c\u0438:\n- `get()` - \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0437\u0430\u0434\u0430\u0447\u0438\n- `create()` - \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0437\u0430\u0434\u0430\u0447\u0438\n- `update()` - \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0437\u0430\u0434\u0430\u0447\u0438\n- `move()` - \u043f\u0435\u0440\u0435\u043d\u043e\u0441 \u0437\u0430\u0434\u0430\u0447\u0438 \u043c\u0435\u0436\u0434\u0443 \u043e\u0447\u0435\u0440\u0435\u0434\u044f\u043c\u0438\n- `search()` - \u043f\u043e\u0438\u0441\u043a \u0437\u0430\u0434\u0430\u0447\n- `count()` - \u043f\u043e\u0434\u0441\u0447\u0435\u0442 \u0437\u0430\u0434\u0430\u0447\n- `priorities()` - \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u043f\u0440\u0438\u043e\u0440\u0438\u0442\u0435\u0442\u043e\u0432\n- `changelog()` - \u0438\u0441\u0442\u043e\u0440\u0438\u044f \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439\n\n**\u041f\u043e\u0434\u043c\u043e\u0434\u0443\u043b\u0438:**\n- `links` - \u0441\u0432\u044f\u0437\u0438 \u043c\u0435\u0436\u0434\u0443 \u0437\u0430\u0434\u0430\u0447\u0430\u043c\u0438\n- `transitions` - \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u044b \u0441\u0442\u0430\u0442\u0443\u0441\u043e\u0432\n- `checklists` - \u0447\u0435\u043a\u043b\u0438\u0441\u0442\u044b \u0437\u0430\u0434\u0430\u0447\n- `comments` - \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438\n- `fields` - \u043f\u043e\u043b\u044f \u0437\u0430\u0434\u0430\u0447\n\n### FieldsAPI (`client.fields`)\n\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u044f\u043c\u0438:\n- `get()` - \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0441\u043f\u0438\u0441\u043a\u0430 \u043f\u043e\u043b\u0435\u0439\n- `create()` - \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044f\n- `update()` - \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u044f\n- `create_category()` - \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u0438 \u043f\u043e\u043b\u0435\u0439\n\n**\u041f\u043e\u0434\u043c\u043e\u0434\u0443\u043b\u044c:**\n- `local` - \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0435 \u043f\u043e\u043b\u044f \u043e\u0447\u0435\u0440\u0435\u0434\u0435\u0439\n\n### EntitiesAPI (`client.entities`)\n\u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044f\u043c\u0438 (\u043f\u0440\u043e\u0435\u043a\u0442\u044b, \u043f\u043e\u0440\u0442\u0444\u0435\u043b\u0438, \u0446\u0435\u043b\u0438):\n- `get()` - \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438\n- `create()` - \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438\n- `update()` - \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438\n- `delete()` - \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438\n- `search()` - \u043f\u043e\u0438\u0441\u043a \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0435\u0439\n- `changelog()` - \u0438\u0441\u0442\u043e\u0440\u0438\u044f \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439\n\n**\u041f\u043e\u0434\u043c\u043e\u0434\u0443\u043b\u0438:**\n- `checklists` - \u0447\u0435\u043a\u043b\u0438\u0441\u0442\u044b \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0435\u0439\n- `bulk` - \u043c\u0430\u0441\u0441\u043e\u0432\u044b\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438\n- `links` - \u0441\u0432\u044f\u0437\u0438 \u043c\u0435\u0436\u0434\u0443 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044f\u043c\u0438\n\n### UsersAPI (`client.users`)\n\u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c\u0438:\n- `get()` - \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0441\u043f\u0438\u0441\u043a\u0430 \u0432\u0441\u0435\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0438\u043b\u0438 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\n\n### QueuesAPI (`client.queues`)\n\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043e\u0447\u0435\u0440\u0435\u0434\u044f\u043c\u0438:\n- `get()` - \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u043e\u0447\u0435\u0440\u0435\u0434\u0438 \u0438\u043b\u0438 \u0441\u043f\u0438\u0441\u043a\u0430 \u043e\u0447\u0435\u0440\u0435\u0434\u0435\u0439\n- `create()` - \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043d\u043e\u0432\u043e\u0439 \u043e\u0447\u0435\u0440\u0435\u0434\u0438\n- `delete()` - \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u043e\u0447\u0435\u0440\u0435\u0434\u0438\n- `restore()` - \u0432\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u043e\u0439 \u043e\u0447\u0435\u0440\u0435\u0434\u0438\n\n**\u041f\u043e\u0434\u043c\u043e\u0434\u0443\u043b\u0438:**\n- `versions` - \u0432\u0435\u0440\u0441\u0438\u0438 \u043e\u0447\u0435\u0440\u0435\u0434\u0435\u0439 (create, get)\n- `fields` - \u043f\u043e\u043b\u044f \u043e\u0447\u0435\u0440\u0435\u0434\u0435\u0439 (get)\n- `tags` - \u0442\u0435\u0433\u0438 \u043e\u0447\u0435\u0440\u0435\u0434\u0435\u0439 (get, delete)\n- `bulk` - \u043c\u0430\u0441\u0441\u043e\u0432\u043e\u0435 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u043e\u043c (update)\n- `user` - \u043f\u0440\u0430\u0432\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f (get)\n- `group` - \u043f\u0440\u0430\u0432\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0433\u0440\u0443\u043f\u043f\u044b (get)\n\n## \u26a0\ufe0f \u041e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u0438 \u0438 \u043f\u043e\u0434\u0432\u043e\u0434\u043d\u044b\u0435 \u043a\u0430\u043c\u043d\u0438\n\n\u041f\u0440\u0438 \u0440\u0430\u0431\u043e\u0442\u0435 \u0441 Yandex Tracker API \u0435\u0441\u0442\u044c \u0440\u044f\u0434 \u0432\u0430\u0436\u043d\u044b\u0445 \u043d\u044e\u0430\u043d\u0441\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c \u0434\u043b\u044f \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u044b.\n\n### 1. \u041f\u0430\u0433\u0438\u043d\u0430\u0446\u0438\u044f \u043f\u0440\u0438 \u043f\u043e\u0438\u0441\u043a\u0435 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0435\u0439\n\n**\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430:** `entities.search()` \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043f\u0430\u0433\u0438\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043e\u0442\u0432\u0435\u0442 \u0432 \u0432\u0438\u0434\u0435 \u0441\u043b\u043e\u0432\u0430\u0440\u044f, \u0430 \u043d\u0435 \u0441\u043f\u0438\u0441\u043a\u0430.\n\n**\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043e\u0442\u0432\u0435\u0442\u0430:**\n```python\n{\n    \"hits\": 125,      # \u041e\u0431\u0449\u0435\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u0445 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0435\u0439\n    \"pages\": 3,       # \u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\n    \"values\": [...]   # \u041c\u0430\u0441\u0441\u0438\u0432 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0435\u0439 \u043d\u0430 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 (\u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e 50)\n}\n```\n\n**\u0420\u0435\u0448\u0435\u043d\u0438\u0435:**\n```python\n# \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u043f\u0435\u0440\u0432\u0443\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443\nprojects_raw = await client.entities.search(\n    entity_type=\"project\",\n    fields=\"summary,id\"\n)\n\n# \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0442\u0438\u043f \u043e\u0442\u0432\u0435\u0442\u0430 \u0438 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u0432\u0441\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b\nif isinstance(projects_raw, dict):\n    pages = projects_raw.get(\"pages\", 1)\n\n    if isinstance(pages, int) and pages > 1:\n        per_page = pages * 50\n        projects_raw = await client.entities.search(\n            entity_type=\"project\",\n            fields=\"summary,id\",\n            per_page=per_page\n        )\n\n    projects = projects_raw.get(\"values\", [])\nelse:\n    projects = projects_raw\n```\n\n### 2. \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 fields \u043f\u0440\u0438 \u043f\u043e\u0438\u0441\u043a\u0435\n\n**\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430:** \u0411\u0435\u0437 \u044f\u0432\u043d\u043e\u0433\u043e \u0443\u043a\u0430\u0437\u0430\u043d\u0438\u044f \u043f\u043e\u043b\u0435\u0439 \u0432 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0435 `fields`, API \u043c\u043e\u0436\u0435\u0442 \u043d\u0435 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c \u0432\u0430\u0436\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435.\n\n**\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e:**\n```python\nprojects = await client.entities.search(entity_type=\"project\")\n# summary \u043c\u043e\u0436\u0435\u0442 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0432 \u043e\u0442\u0432\u0435\u0442\u0435\n```\n\n**\u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e:**\n```python\nprojects = await client.entities.search(\n    entity_type=\"project\",\n    fields=\"summary,id,description\"\n)\n\n# \u0418\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u043c \u0434\u0430\u043d\u043d\u044b\u0435\nfor proj in projects[\"values\"]:\n    summary = proj.get(\"fields\", {}).get(\"summary\", \"\")\n    if not summary:\n        summary = proj.get(\"summary\", \"\")\n```\n\n### 3. \u0424\u043e\u0440\u043c\u0430\u0442 \u043f\u043e\u043b\u044f project \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u0437\u0430\u0434\u0430\u0447\u0438\n\n**\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430:** API \u043e\u0436\u0438\u0434\u0430\u0435\u0442 `shortId` \u043f\u0440\u043e\u0435\u043a\u0442\u0430 (\u0447\u0438\u0441\u043b\u043e), \u0430 \u043d\u0435 \u043f\u043e\u043b\u043d\u044b\u0439 `id` (\u0441\u0442\u0440\u043e\u043a\u0430).\n\n**\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e:**\n```python\nproject = {\n    \"id\": \"68e5764ffa5085239cef5e94\"  # \u274c \u0421\u0442\u0440\u043e\u043a\u043e\u0432\u044b\u0439 ID\n}\n```\n\n**\u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e:**\n```python\n# \u041f\u043e\u0441\u043b\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u0430\nnew_project = await client.entities.create(\n    entity_type=\"project\",\n    summary=\"\u041d\u043e\u0432\u044b\u0439 \u043f\u0440\u043e\u0435\u043a\u0442\"\n)\n\n# \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 shortId\nproject_short_id = new_project.get(\"shortId\")\n\n# \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0437\u0430\u0434\u0430\u0447\u0443\nissue = await client.issues.create(\n    summary=\"\u0417\u0430\u0434\u0430\u0447\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\",\n    queue=\"TESTBOT\",\n    project={\"primary\": project_short_id}  # \u2705 shortId \u043a\u0430\u043a \u0447\u0438\u0441\u043b\u043e\n)\n```\n\n### 4. \u0424\u043e\u0440\u043c\u0430\u0442 \u043f\u043e\u043b\u0435\u0439 type \u0438 priority\n\n**\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430:** API \u0432\u044b\u0434\u0430\u0435\u0442 \u043e\u0448\u0438\u0431\u043a\u0443 400, \u0435\u0441\u043b\u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u043f\u043e\u043b\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0432\u043c\u0435\u0441\u0442\u043e \u043a\u043b\u044e\u0447\u0435\u0439.\n\n**\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e:**\n```python\nissue = await client.issues.get(\"TESTBOT-1\")\n\nnew_issue = await client.issues.create(\n    summary=\"\u041a\u043e\u043f\u0438\u044f\",\n    queue=\"TESTBOT\",\n    type=issue[\"type\"],        # \u274c \u041f\u043e\u043b\u043d\u044b\u0439 \u043e\u0431\u044a\u0435\u043a\u0442\n    priority=issue[\"priority\"]  # \u274c \u041f\u043e\u043b\u043d\u044b\u0439 \u043e\u0431\u044a\u0435\u043a\u0442\n)\n```\n\n**\u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e:**\n```python\ndef extract_field_value(field_data):\n    \"\"\"\u0418\u0437\u0432\u043b\u0435\u0447\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u044f \u0434\u043b\u044f API \u0437\u0430\u043f\u0440\u043e\u0441\u0430\"\"\"\n    if isinstance(field_data, dict):\n        return field_data.get(\"key\") or field_data.get(\"id\")\n    return field_data\n\nnew_issue = await client.issues.create(\n    summary=\"\u041a\u043e\u043f\u0438\u044f\",\n    queue=\"TESTBOT\",\n    type=extract_field_value(issue.get(\"type\")),        # \u2705 \"milestone\"\n    priority=extract_field_value(issue.get(\"priority\"))  # \u2705 \"normal\"\n)\n```\n\n### 5. \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0435\u0439 \u0441 \u043f\u043e\u043b\u043d\u044b\u043c \u043d\u0430\u0431\u043e\u0440\u043e\u043c \u043f\u043e\u043b\u0435\u0439\n\n**\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u0430:** \u0411\u0435\u0437 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 `fields` API \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0431\u0430\u0437\u043e\u0432\u044b\u0435 \u043f\u043e\u043b\u044f.\n\n**\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e:**\n```python\nproject = await client.entities.get(\n    entity_id=project_id,\n    entity_type=\"project\"\n)\n# \u041d\u0415\u0422: description, lead, teamUsers, parentEntity\n```\n\n**\u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e:**\n```python\nproject = await client.entities.get(\n    entity_id=project_id,\n    entity_type=\"project\",\n    fields=\"summary,description,lead,teamUsers,teamAccess,parentEntity,clients,followers,start,end,tags\"\n)\n# \u2705 \u0412\u0441\u0435 \u043f\u043e\u043b\u044f \u043f\u0440\u0438\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0442\n```\n\n### 6. \u041e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f \u043d\u0430 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0445 \u043f\u043e\u043b\u0435\u0439\n\n**\u041d\u0435\u043b\u044c\u0437\u044f \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c:**\n- \u274c `createdBy` - \u0430\u0432\u0442\u043e\u0440 (\u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438)\n- \u274c `createdAt` - \u0434\u0430\u0442\u0430 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f\n- \u274c `author` - \u0430\u0432\u0442\u043e\u0440 (\u0430\u043b\u0438\u0430\u0441 createdBy)\n\n**\u041c\u043e\u0436\u043d\u043e \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c:**\n- \u2705 `lead` - \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c \u043f\u0440\u043e\u0435\u043a\u0442\u0430\n- \u2705 `assignee` - \u0438\u0441\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c \u0437\u0430\u0434\u0430\u0447\u0438\n- \u2705 `teamUsers` - \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u0438\n- \u2705 `followers` - \u043d\u0430\u0431\u043b\u044e\u0434\u0430\u0442\u0435\u043b\u0438\n\n```python\n# \u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435\nawait client.entities.update(\n    entity_id=project_id,\n    lead=\"user_login\",\n    teamUsers=[\"user1\", \"user2\"]\n)\n```\n\n### \u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0437\u0430\u043c\u0435\u0447\u0430\u043d\u0438\u044f\n\n**\u0410\u0432\u0442\u043e\u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0430\u0446\u0438\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432:** \u0411\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u0443\u0435\u0442 snake_case \u0432 camelCase:\n```python\nper_page=50  # \u2192 perPage=50 \u0432 API\n```\n\n**\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043e\u0448\u0438\u0431\u043e\u043a:** \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 debug \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0434\u043b\u044f \u0434\u0438\u0430\u0433\u043d\u043e\u0441\u0442\u0438\u043a\u0438:\n```python\nimport logging\nlogging.getLogger(\"YaTrackerApi\").setLevel(logging.DEBUG)\n```\n\n\u041f\u043e\u043b\u043d\u0430\u044f \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f: [context/gotchas.md](context/gotchas.md)\n\n## \ud83d\udccb \u0422\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f\n\n- Python 3.9+\n- aiohttp >= 3.12.15\n- python-dotenv >= 1.1.1\n\n## \ud83d\udcc4 \u041b\u0438\u0446\u0435\u043d\u0437\u0438\u044f\n\n\u041f\u0440\u043e\u0435\u043a\u0442 \u0440\u0430\u0441\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u044f\u0435\u0442\u0441\u044f \u043f\u043e\u0434 \u043b\u0438\u0446\u0435\u043d\u0437\u0438\u0435\u0439 [MIT](LICENSE).\n\n## \ud83d\udc64 \u041a\u043e\u043d\u0442\u0430\u043a\u0442\u044b\n\n**\u0414\u0430\u043d\u0438\u0438\u043b**\n\n- GitHub: [@imdeniil](https://github.com/imdeniil)\n- Email: keemor821@gmail.com\n\n## \ud83d\udd17 \u0421\u0441\u044b\u043b\u043a\u0438\n\n- [PyPI](https://pypi.org/project/YaTrackerApi/)\n- [GitHub Repository](https://github.com/imdeniil/YaTrackerApi)\n- [Issues](https://github.com/imdeniil/YaTrackerApi/issues)\n- [\u041e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u0430\u044f \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f Yandex Tracker API](https://cloud.yandex.ru/docs/tracker/about-api)\n\n---\n\n**\u0421\u043e\u0437\u0434\u0430\u043d\u043e \u0441 \u2764\ufe0f \u0434\u043b\u044f \u0443\u043f\u0440\u043e\u0449\u0435\u043d\u0438\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 Yandex Tracker API**\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "\u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 Python \u043a\u043b\u0438\u0435\u043d\u0442 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 Yandex Tracker API \u0441 \u043c\u043e\u0434\u0443\u043b\u044c\u043d\u043e\u0439 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u043e\u0439",
    "version": "2.1.2",
    "project_urls": {
        "Documentation": "https://github.com/imdeniil/YaTrackerApi#readme",
        "Homepage": "https://github.com/imdeniil/YaTrackerApi",
        "Issues": "https://github.com/imdeniil/YaTrackerApi/issues",
        "Repository": "https://github.com/imdeniil/YaTrackerApi"
    },
    "split_keywords": [
        "aiohttp",
        " api",
        " async",
        " project-management",
        " task-tracker",
        " tracker",
        " yandex"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "33589c12362479b7c65d93b5193303741c0127e9b0883601b1af933191c784ad",
                "md5": "88e932a439f344e2bf41ff3e5729762a",
                "sha256": "de498c89f716bc3a5415e3e3d7781b5c0badcdc7bdfe2d587c1017ea950818f7"
            },
            "downloads": -1,
            "filename": "yatrackerapi-2.1.2-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "88e932a439f344e2bf41ff3e5729762a",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9",
            "size": 86004,
            "upload_time": "2025-10-07T22:15:08",
            "upload_time_iso_8601": "2025-10-07T22:15:08.856732Z",
            "url": "https://files.pythonhosted.org/packages/33/58/9c12362479b7c65d93b5193303741c0127e9b0883601b1af933191c784ad/yatrackerapi-2.1.2-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "5c3db6e3ab9a327e29b89733e915bc32f9d48f40c0112f8697838208b69ce4a4",
                "md5": "76094a8e77b6f21efac0c0766457d60a",
                "sha256": "2381ddded70440b42c0719dd4f3aaee8198b85986357e738b0411e5ae3097989"
            },
            "downloads": -1,
            "filename": "yatrackerapi-2.1.2.tar.gz",
            "has_sig": false,
            "md5_digest": "76094a8e77b6f21efac0c0766457d60a",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 110583,
            "upload_time": "2025-10-07T22:15:10",
            "upload_time_iso_8601": "2025-10-07T22:15:10.927944Z",
            "url": "https://files.pythonhosted.org/packages/5c/3d/b6e3ab9a327e29b89733e915bc32f9d48f40c0112f8697838208b69ce4a4/yatrackerapi-2.1.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-10-07 22:15:10",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "imdeniil",
    "github_project": "YaTrackerApi#readme",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "yatrackerapi"
}
        
Elapsed time: 2.40119s