megaplan-sdk 0.2.2__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {megaplan_sdk-0.2.2/src/megaplan_sdk.egg-info → megaplan_sdk-0.3.0}/PKG-INFO +124 -2
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/README.md +123 -1
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/pyproject.toml +1 -1
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/__init__.py +12 -1
- megaplan_sdk-0.3.0/src/megaplan_sdk/_knowledge_links.py +26 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/client.py +6 -0
- megaplan_sdk-0.3.0/src/megaplan_sdk/constants.py +44 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/models/__init__.py +8 -0
- megaplan_sdk-0.3.0/src/megaplan_sdk/models/knowledge.py +56 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/models/task.py +7 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/resources/base.py +2 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/resources/comments.py +61 -4
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/resources/deals.py +7 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/resources/full_details.py +15 -0
- megaplan_sdk-0.3.0/src/megaplan_sdk/resources/knowledge_article.py +28 -0
- megaplan_sdk-0.3.0/src/megaplan_sdk/resources/knowledge_base.py +149 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/resources/projects.py +7 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/resources/tasks.py +27 -1
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0/src/megaplan_sdk.egg-info}/PKG-INFO +124 -2
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk.egg-info/SOURCES.txt +4 -0
- megaplan_sdk-0.2.2/src/megaplan_sdk/constants.py +0 -17
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/LICENSE +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/setup.cfg +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/auth.py +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/cache.py +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/exceptions.py +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/filter_builder.py +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/helpers.py +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/http_client.py +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/logging_config.py +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/models/base.py +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/models/comment.py +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/models/common.py +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/models/contractor.py +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/models/deal.py +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/models/department.py +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/models/employee.py +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/models/filter.py +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/models/group.py +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/models/milestone.py +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/models/participant.py +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/models/project.py +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/resources/__init__.py +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/resources/auth.py +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/resources/contractors.py +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/resources/departments.py +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/resources/employees.py +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/resources/filters.py +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/types.py +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk.egg-info/dependency_links.txt +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk.egg-info/requires.txt +0 -0
- {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: megaplan-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Professional Python SDK for Megaplan API v3
|
|
5
5
|
Author-email: Maxim Borzov <max@borzov.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -67,12 +67,14 @@ Dynamic: license-file
|
|
|
67
67
|
- [Задачи](#работа-с-задачами)
|
|
68
68
|
- [Проекты](#работа-с-проектами)
|
|
69
69
|
- [Сделки](#работа-со-сделками)
|
|
70
|
+
- [База знаний](#работа-с-базой-знаний)
|
|
70
71
|
|
|
71
72
|
### Продвинутые возможности
|
|
72
73
|
- [Кэширование сущностей](#кэширование-сущностей)
|
|
73
74
|
- [Глобальные дефолтные лимиты](#глобальные-дефолтные-лимиты)
|
|
74
75
|
- [Автоматическая подгрузка связанных сущностей](#автоматическая-подгрузка-связанных-сущностей)
|
|
75
76
|
- [Работа с фильтрами](#работа-с-фильтрами)
|
|
77
|
+
- [Работа с комментариями](#работа-с-комментариями)
|
|
76
78
|
- [Настройка HTTP-клиента](#настройка-http-клиента)
|
|
77
79
|
- [Работа через прокси](#работа-через-прокси)
|
|
78
80
|
- [Ручное управление токенами](#ручное-управление-токенами)
|
|
@@ -429,7 +431,7 @@ print(details.deal.name) # для сделок
|
|
|
429
431
|
# Связанные данные
|
|
430
432
|
if details.comments:
|
|
431
433
|
for comment in details.comments:
|
|
432
|
-
print(comment.
|
|
434
|
+
print(comment.content)
|
|
433
435
|
|
|
434
436
|
if details.history:
|
|
435
437
|
print(f"Записей в истории: {len(details.history)}")
|
|
@@ -519,6 +521,37 @@ tasks = await client.tasks.list(filter=filter_obj)
|
|
|
519
521
|
- `todos: list[BaseEntity]` - Подзадачи-чеклисты
|
|
520
522
|
- `time_created: str` - Дата создания (API поле `timeCreated`)
|
|
521
523
|
- `time_updated: str` - Дата обновления (API поле `timeUpdated`)
|
|
524
|
+
- `activity: str | None` - Дата последней активности (API поле `activity`)
|
|
525
|
+
- `last_comment_time_created: str | None` - Время последнего комментария (API поле `lastCommentTimeCreated`)
|
|
526
|
+
- `status_change_time: str | None` - Время смены статуса (API поле `statusChangeTime`)
|
|
527
|
+
- `actual_start: str | None` - Фактическое время начала (API поле `actualStart`)
|
|
528
|
+
- `last_view: str | None` - Время последнего просмотра (API поле `lastView`)
|
|
529
|
+
|
|
530
|
+
### Фильтрация задач по временным полям
|
|
531
|
+
|
|
532
|
+
Временны́е поля (`activity`, `lastCommentTimeCreated` и др.) не возвращаются в `tasks.list()` по умолчанию.
|
|
533
|
+
Используйте константу `DEFAULT_TASK_LIST_FIELDS`, чтобы запросить их явно:
|
|
534
|
+
|
|
535
|
+
```python
|
|
536
|
+
from megaplan_sdk import DEFAULT_TASK_LIST_FIELDS
|
|
537
|
+
|
|
538
|
+
# Запросить задачи с временными полями (activity, lastCommentTimeCreated и др.)
|
|
539
|
+
tasks = await client.tasks.list(
|
|
540
|
+
limit=50,
|
|
541
|
+
fields=list(DEFAULT_TASK_LIST_FIELDS),
|
|
542
|
+
)
|
|
543
|
+
for task in tasks:
|
|
544
|
+
print(f"{task.name}: активность {task.activity}")
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
> **Примечание:** Сортировка по `"timeUpdated"` не поддерживается API — используйте `"activity"`:
|
|
548
|
+
>
|
|
549
|
+
> ```python
|
|
550
|
+
> tasks = await client.tasks.list(
|
|
551
|
+
> sort_by=[{"fieldName": "activity", "order": "desc"}],
|
|
552
|
+
> fields=list(DEFAULT_TASK_LIST_FIELDS),
|
|
553
|
+
> )
|
|
554
|
+
> ```
|
|
522
555
|
|
|
523
556
|
### Упрощенные методы создания
|
|
524
557
|
|
|
@@ -1091,6 +1124,45 @@ details = await client.deals.get_full_details(
|
|
|
1091
1124
|
|
|
1092
1125
|
> **Примечание:** Общее описание метода `get_full_details()` и примеры использования см. в разделе [Общие паттерны работы с сущностями](#общие-паттерны-работы-с-сущностями).
|
|
1093
1126
|
|
|
1127
|
+
## Работа с базой знаний
|
|
1128
|
+
|
|
1129
|
+
SDK предоставляет доступ к разделам и статьям Базы знаний Мегаплана через два ресурса: `client.knowledge_base` (разделы) и `client.knowledge_article` (статьи).
|
|
1130
|
+
|
|
1131
|
+
```python
|
|
1132
|
+
# Список разделов Базы знаний (плоский)
|
|
1133
|
+
sections = await client.knowledge_base.list()
|
|
1134
|
+
for s in sections:
|
|
1135
|
+
print(s.id, s.name)
|
|
1136
|
+
|
|
1137
|
+
# Один раздел с HTML-содержимым
|
|
1138
|
+
section = await client.knowledge_base.get(11)
|
|
1139
|
+
print(section.content)
|
|
1140
|
+
|
|
1141
|
+
# Итерация по всем разделам с автопагинацией
|
|
1142
|
+
async for section in client.knowledge_base.iterate():
|
|
1143
|
+
print(section.id, section.name)
|
|
1144
|
+
|
|
1145
|
+
# Статья по ID (parent всегда None — используйте base)
|
|
1146
|
+
article = await client.knowledge_article.get(33)
|
|
1147
|
+
print(article.name, "→ раздел:", article.base.name if article.base else None)
|
|
1148
|
+
|
|
1149
|
+
# Экспериментально: раздел вместе со статьями (через парсинг HTML-ссылок)
|
|
1150
|
+
bundle = await client.knowledge_base.get_with_articles(2)
|
|
1151
|
+
for a in bundle.articles:
|
|
1152
|
+
print(a.id, a.name)
|
|
1153
|
+
```
|
|
1154
|
+
|
|
1155
|
+
> **Известные ограничения API (серверная сторона):**
|
|
1156
|
+
>
|
|
1157
|
+
> - **Нет листинга статей:** эндпоинт `GET /api/v3/knowledgeArticle` отсутствует (возвращает 404).
|
|
1158
|
+
> Единственный способ обнаружения статей в разделе — метод `get_with_articles()`, который
|
|
1159
|
+
> парсит HTML-ссылки из поля `content` раздела. Это **экспериментальный** и хрупкий метод —
|
|
1160
|
+
> формат ссылок может измениться на стороне сервера.
|
|
1161
|
+
> - **Фильтр `parent` не работает:** `knowledge_base.list()` всегда возвращает плоский список
|
|
1162
|
+
> всех разделов; передача `parent` в фильтре игнорируется сервером. Иерархии разделов нет.
|
|
1163
|
+
> - **Поле `parent` у статьи всегда `null`:** для определения принадлежности статьи к разделу
|
|
1164
|
+
> используйте `article.base` (не `article.parent`).
|
|
1165
|
+
|
|
1094
1166
|
## Продвинутые возможности
|
|
1095
1167
|
|
|
1096
1168
|
### Кэширование сущностей
|
|
@@ -1502,6 +1574,56 @@ updated = await client.filters.update("task", filter_id=123, filter_config={...}
|
|
|
1502
1574
|
export_data = await client.filters.export("task", filter_id=123)
|
|
1503
1575
|
```
|
|
1504
1576
|
|
|
1577
|
+
### Работа с комментариями
|
|
1578
|
+
|
|
1579
|
+
Комментарии доступны для задач, проектов и сделок через `client.comments`.
|
|
1580
|
+
|
|
1581
|
+
#### Получение комментариев
|
|
1582
|
+
|
|
1583
|
+
```python
|
|
1584
|
+
# Комментарии задачи (entity_type по умолчанию "task")
|
|
1585
|
+
comments = await client.comments.list(entity_id=42)
|
|
1586
|
+
|
|
1587
|
+
# Комментарии проекта
|
|
1588
|
+
comments = await client.comments.list(entity_id=5, entity_type="project")
|
|
1589
|
+
|
|
1590
|
+
# Комментарии сделки
|
|
1591
|
+
comments = await client.comments.list(entity_id=200, entity_type="deal")
|
|
1592
|
+
|
|
1593
|
+
# Автоматическая пагинация
|
|
1594
|
+
async for comment in client.comments.iterate(entity_id=42):
|
|
1595
|
+
print(comment.content)
|
|
1596
|
+
```
|
|
1597
|
+
|
|
1598
|
+
#### Подгрузка авторов через expand
|
|
1599
|
+
|
|
1600
|
+
API Мегаплана не раскрывает поле `owner` (автор комментария) в списке комментариев.
|
|
1601
|
+
Используйте `expand=["owner"]`, чтобы SDK дозагрузил авторов отдельными запросами (с кэшированием):
|
|
1602
|
+
|
|
1603
|
+
```python
|
|
1604
|
+
# Комментарии задачи с именами авторов
|
|
1605
|
+
comments = await client.comments.list(entity_id=42, expand=["owner"])
|
|
1606
|
+
for comment in comments:
|
|
1607
|
+
author_name = comment.owner.name if comment.owner else "неизвестен"
|
|
1608
|
+
print(f"{author_name}: {comment.content}")
|
|
1609
|
+
```
|
|
1610
|
+
|
|
1611
|
+
#### Создание комментария
|
|
1612
|
+
|
|
1613
|
+
```python
|
|
1614
|
+
# Комментарий к задаче
|
|
1615
|
+
comment = await client.comments.create(entity_id=42, comment_data={"text": "Текст комментария"})
|
|
1616
|
+
|
|
1617
|
+
# Комментарий к проекту
|
|
1618
|
+
comment = await client.comments.create(entity_id=5, comment_data={"text": "Текст"}, entity_type="project")
|
|
1619
|
+
|
|
1620
|
+
# Комментарий к сделке
|
|
1621
|
+
comment = await client.comments.create(entity_id=200, comment_data={"text": "Текст"}, entity_type="deal")
|
|
1622
|
+
```
|
|
1623
|
+
|
|
1624
|
+
> **Ограничение:** Комментарии контрагентов не поддерживаются API (возвращает 500).
|
|
1625
|
+
> Используйте комментарии в связанных сделках или задачах.
|
|
1626
|
+
|
|
1505
1627
|
### Настройка HTTP-клиента
|
|
1506
1628
|
|
|
1507
1629
|
```python
|
|
@@ -35,12 +35,14 @@
|
|
|
35
35
|
- [Задачи](#работа-с-задачами)
|
|
36
36
|
- [Проекты](#работа-с-проектами)
|
|
37
37
|
- [Сделки](#работа-со-сделками)
|
|
38
|
+
- [База знаний](#работа-с-базой-знаний)
|
|
38
39
|
|
|
39
40
|
### Продвинутые возможности
|
|
40
41
|
- [Кэширование сущностей](#кэширование-сущностей)
|
|
41
42
|
- [Глобальные дефолтные лимиты](#глобальные-дефолтные-лимиты)
|
|
42
43
|
- [Автоматическая подгрузка связанных сущностей](#автоматическая-подгрузка-связанных-сущностей)
|
|
43
44
|
- [Работа с фильтрами](#работа-с-фильтрами)
|
|
45
|
+
- [Работа с комментариями](#работа-с-комментариями)
|
|
44
46
|
- [Настройка HTTP-клиента](#настройка-http-клиента)
|
|
45
47
|
- [Работа через прокси](#работа-через-прокси)
|
|
46
48
|
- [Ручное управление токенами](#ручное-управление-токенами)
|
|
@@ -397,7 +399,7 @@ print(details.deal.name) # для сделок
|
|
|
397
399
|
# Связанные данные
|
|
398
400
|
if details.comments:
|
|
399
401
|
for comment in details.comments:
|
|
400
|
-
print(comment.
|
|
402
|
+
print(comment.content)
|
|
401
403
|
|
|
402
404
|
if details.history:
|
|
403
405
|
print(f"Записей в истории: {len(details.history)}")
|
|
@@ -487,6 +489,37 @@ tasks = await client.tasks.list(filter=filter_obj)
|
|
|
487
489
|
- `todos: list[BaseEntity]` - Подзадачи-чеклисты
|
|
488
490
|
- `time_created: str` - Дата создания (API поле `timeCreated`)
|
|
489
491
|
- `time_updated: str` - Дата обновления (API поле `timeUpdated`)
|
|
492
|
+
- `activity: str | None` - Дата последней активности (API поле `activity`)
|
|
493
|
+
- `last_comment_time_created: str | None` - Время последнего комментария (API поле `lastCommentTimeCreated`)
|
|
494
|
+
- `status_change_time: str | None` - Время смены статуса (API поле `statusChangeTime`)
|
|
495
|
+
- `actual_start: str | None` - Фактическое время начала (API поле `actualStart`)
|
|
496
|
+
- `last_view: str | None` - Время последнего просмотра (API поле `lastView`)
|
|
497
|
+
|
|
498
|
+
### Фильтрация задач по временным полям
|
|
499
|
+
|
|
500
|
+
Временны́е поля (`activity`, `lastCommentTimeCreated` и др.) не возвращаются в `tasks.list()` по умолчанию.
|
|
501
|
+
Используйте константу `DEFAULT_TASK_LIST_FIELDS`, чтобы запросить их явно:
|
|
502
|
+
|
|
503
|
+
```python
|
|
504
|
+
from megaplan_sdk import DEFAULT_TASK_LIST_FIELDS
|
|
505
|
+
|
|
506
|
+
# Запросить задачи с временными полями (activity, lastCommentTimeCreated и др.)
|
|
507
|
+
tasks = await client.tasks.list(
|
|
508
|
+
limit=50,
|
|
509
|
+
fields=list(DEFAULT_TASK_LIST_FIELDS),
|
|
510
|
+
)
|
|
511
|
+
for task in tasks:
|
|
512
|
+
print(f"{task.name}: активность {task.activity}")
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
> **Примечание:** Сортировка по `"timeUpdated"` не поддерживается API — используйте `"activity"`:
|
|
516
|
+
>
|
|
517
|
+
> ```python
|
|
518
|
+
> tasks = await client.tasks.list(
|
|
519
|
+
> sort_by=[{"fieldName": "activity", "order": "desc"}],
|
|
520
|
+
> fields=list(DEFAULT_TASK_LIST_FIELDS),
|
|
521
|
+
> )
|
|
522
|
+
> ```
|
|
490
523
|
|
|
491
524
|
### Упрощенные методы создания
|
|
492
525
|
|
|
@@ -1059,6 +1092,45 @@ details = await client.deals.get_full_details(
|
|
|
1059
1092
|
|
|
1060
1093
|
> **Примечание:** Общее описание метода `get_full_details()` и примеры использования см. в разделе [Общие паттерны работы с сущностями](#общие-паттерны-работы-с-сущностями).
|
|
1061
1094
|
|
|
1095
|
+
## Работа с базой знаний
|
|
1096
|
+
|
|
1097
|
+
SDK предоставляет доступ к разделам и статьям Базы знаний Мегаплана через два ресурса: `client.knowledge_base` (разделы) и `client.knowledge_article` (статьи).
|
|
1098
|
+
|
|
1099
|
+
```python
|
|
1100
|
+
# Список разделов Базы знаний (плоский)
|
|
1101
|
+
sections = await client.knowledge_base.list()
|
|
1102
|
+
for s in sections:
|
|
1103
|
+
print(s.id, s.name)
|
|
1104
|
+
|
|
1105
|
+
# Один раздел с HTML-содержимым
|
|
1106
|
+
section = await client.knowledge_base.get(11)
|
|
1107
|
+
print(section.content)
|
|
1108
|
+
|
|
1109
|
+
# Итерация по всем разделам с автопагинацией
|
|
1110
|
+
async for section in client.knowledge_base.iterate():
|
|
1111
|
+
print(section.id, section.name)
|
|
1112
|
+
|
|
1113
|
+
# Статья по ID (parent всегда None — используйте base)
|
|
1114
|
+
article = await client.knowledge_article.get(33)
|
|
1115
|
+
print(article.name, "→ раздел:", article.base.name if article.base else None)
|
|
1116
|
+
|
|
1117
|
+
# Экспериментально: раздел вместе со статьями (через парсинг HTML-ссылок)
|
|
1118
|
+
bundle = await client.knowledge_base.get_with_articles(2)
|
|
1119
|
+
for a in bundle.articles:
|
|
1120
|
+
print(a.id, a.name)
|
|
1121
|
+
```
|
|
1122
|
+
|
|
1123
|
+
> **Известные ограничения API (серверная сторона):**
|
|
1124
|
+
>
|
|
1125
|
+
> - **Нет листинга статей:** эндпоинт `GET /api/v3/knowledgeArticle` отсутствует (возвращает 404).
|
|
1126
|
+
> Единственный способ обнаружения статей в разделе — метод `get_with_articles()`, который
|
|
1127
|
+
> парсит HTML-ссылки из поля `content` раздела. Это **экспериментальный** и хрупкий метод —
|
|
1128
|
+
> формат ссылок может измениться на стороне сервера.
|
|
1129
|
+
> - **Фильтр `parent` не работает:** `knowledge_base.list()` всегда возвращает плоский список
|
|
1130
|
+
> всех разделов; передача `parent` в фильтре игнорируется сервером. Иерархии разделов нет.
|
|
1131
|
+
> - **Поле `parent` у статьи всегда `null`:** для определения принадлежности статьи к разделу
|
|
1132
|
+
> используйте `article.base` (не `article.parent`).
|
|
1133
|
+
|
|
1062
1134
|
## Продвинутые возможности
|
|
1063
1135
|
|
|
1064
1136
|
### Кэширование сущностей
|
|
@@ -1470,6 +1542,56 @@ updated = await client.filters.update("task", filter_id=123, filter_config={...}
|
|
|
1470
1542
|
export_data = await client.filters.export("task", filter_id=123)
|
|
1471
1543
|
```
|
|
1472
1544
|
|
|
1545
|
+
### Работа с комментариями
|
|
1546
|
+
|
|
1547
|
+
Комментарии доступны для задач, проектов и сделок через `client.comments`.
|
|
1548
|
+
|
|
1549
|
+
#### Получение комментариев
|
|
1550
|
+
|
|
1551
|
+
```python
|
|
1552
|
+
# Комментарии задачи (entity_type по умолчанию "task")
|
|
1553
|
+
comments = await client.comments.list(entity_id=42)
|
|
1554
|
+
|
|
1555
|
+
# Комментарии проекта
|
|
1556
|
+
comments = await client.comments.list(entity_id=5, entity_type="project")
|
|
1557
|
+
|
|
1558
|
+
# Комментарии сделки
|
|
1559
|
+
comments = await client.comments.list(entity_id=200, entity_type="deal")
|
|
1560
|
+
|
|
1561
|
+
# Автоматическая пагинация
|
|
1562
|
+
async for comment in client.comments.iterate(entity_id=42):
|
|
1563
|
+
print(comment.content)
|
|
1564
|
+
```
|
|
1565
|
+
|
|
1566
|
+
#### Подгрузка авторов через expand
|
|
1567
|
+
|
|
1568
|
+
API Мегаплана не раскрывает поле `owner` (автор комментария) в списке комментариев.
|
|
1569
|
+
Используйте `expand=["owner"]`, чтобы SDK дозагрузил авторов отдельными запросами (с кэшированием):
|
|
1570
|
+
|
|
1571
|
+
```python
|
|
1572
|
+
# Комментарии задачи с именами авторов
|
|
1573
|
+
comments = await client.comments.list(entity_id=42, expand=["owner"])
|
|
1574
|
+
for comment in comments:
|
|
1575
|
+
author_name = comment.owner.name if comment.owner else "неизвестен"
|
|
1576
|
+
print(f"{author_name}: {comment.content}")
|
|
1577
|
+
```
|
|
1578
|
+
|
|
1579
|
+
#### Создание комментария
|
|
1580
|
+
|
|
1581
|
+
```python
|
|
1582
|
+
# Комментарий к задаче
|
|
1583
|
+
comment = await client.comments.create(entity_id=42, comment_data={"text": "Текст комментария"})
|
|
1584
|
+
|
|
1585
|
+
# Комментарий к проекту
|
|
1586
|
+
comment = await client.comments.create(entity_id=5, comment_data={"text": "Текст"}, entity_type="project")
|
|
1587
|
+
|
|
1588
|
+
# Комментарий к сделке
|
|
1589
|
+
comment = await client.comments.create(entity_id=200, comment_data={"text": "Текст"}, entity_type="deal")
|
|
1590
|
+
```
|
|
1591
|
+
|
|
1592
|
+
> **Ограничение:** Комментарии контрагентов не поддерживаются API (возвращает 500).
|
|
1593
|
+
> Используйте комментарии в связанных сделках или задачах.
|
|
1594
|
+
|
|
1473
1595
|
### Настройка HTTP-клиента
|
|
1474
1596
|
|
|
1475
1597
|
```python
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Megaplan Python SDK - Professional SDK for Megaplan API v3."""
|
|
2
2
|
|
|
3
3
|
from megaplan_sdk.client import MegaplanClient
|
|
4
|
+
from megaplan_sdk.constants import DEFAULT_TASK_LIST_FIELDS
|
|
4
5
|
from megaplan_sdk.exceptions import (
|
|
5
6
|
AuthenticationError,
|
|
6
7
|
AuthorizationError,
|
|
@@ -41,6 +42,11 @@ from megaplan_sdk.models.filter import (
|
|
|
41
42
|
UserSetting,
|
|
42
43
|
)
|
|
43
44
|
from megaplan_sdk.models.group import Group
|
|
45
|
+
from megaplan_sdk.models.knowledge import (
|
|
46
|
+
KnowledgeArticle,
|
|
47
|
+
KnowledgeBase,
|
|
48
|
+
KnowledgeSectionWithArticles,
|
|
49
|
+
)
|
|
44
50
|
from megaplan_sdk.models.milestone import Milestone
|
|
45
51
|
from megaplan_sdk.models.participant import Participant, parse_participant, parse_participants
|
|
46
52
|
from megaplan_sdk.models.project import Project, ProjectFullDetails
|
|
@@ -74,6 +80,9 @@ __all__ = [
|
|
|
74
80
|
"Department",
|
|
75
81
|
"Group",
|
|
76
82
|
"Milestone",
|
|
83
|
+
"KnowledgeBase",
|
|
84
|
+
"KnowledgeArticle",
|
|
85
|
+
"KnowledgeSectionWithArticles",
|
|
77
86
|
"Participant",
|
|
78
87
|
"parse_participant",
|
|
79
88
|
"parse_participants",
|
|
@@ -101,6 +110,8 @@ __all__ = [
|
|
|
101
110
|
"TaskFilterBuilder",
|
|
102
111
|
"TradeFilterBuilder",
|
|
103
112
|
"ProjectFilterBuilder",
|
|
113
|
+
# Constants
|
|
114
|
+
"DEFAULT_TASK_LIST_FIELDS",
|
|
104
115
|
]
|
|
105
116
|
|
|
106
|
-
__version__ = "0.
|
|
117
|
+
__version__ = "0.3.0"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""HTML link parsing for Knowledge Base article discovery.
|
|
2
|
+
|
|
3
|
+
EXPERIMENTAL / FRAGILE: this module is the single place coupled to Megaplan's
|
|
4
|
+
HTML format. The Megaplan API has no endpoint to list articles in a section,
|
|
5
|
+
so article IDs are discovered by parsing ``/knowledge/<section>/<article>``
|
|
6
|
+
links inside a section's HTML ``content``.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
|
|
11
|
+
# Matches /knowledge/<section_id> and /knowledge/<section_id>/<article_id>
|
|
12
|
+
# inside href values; scheme/host-agnostic (absolute or relative URLs).
|
|
13
|
+
_KNOWLEDGE_LINK_RE = re.compile(r"/knowledge/(\d+)(?:/(\d+))?")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def extract_article_ids(html: str | None) -> list[int]:
|
|
17
|
+
"""Extract article IDs from two-segment ``/knowledge/<section>/<article>`` links.
|
|
18
|
+
|
|
19
|
+
Single-segment ``/knowledge/<section>`` links (section references, e.g. a
|
|
20
|
+
table of contents) are ignored. Returns unique IDs in first-seen order.
|
|
21
|
+
"""
|
|
22
|
+
seen: dict[int, None] = {}
|
|
23
|
+
for _section, article in _KNOWLEDGE_LINK_RE.findall(html or ""):
|
|
24
|
+
if article:
|
|
25
|
+
seen.setdefault(int(article), None)
|
|
26
|
+
return list(seen)
|
|
@@ -13,6 +13,8 @@ from megaplan_sdk.resources.deals import DealsResource
|
|
|
13
13
|
from megaplan_sdk.resources.departments import DepartmentsResource
|
|
14
14
|
from megaplan_sdk.resources.employees import EmployeesResource
|
|
15
15
|
from megaplan_sdk.resources.filters import FiltersResource
|
|
16
|
+
from megaplan_sdk.resources.knowledge_article import KnowledgeArticleResource
|
|
17
|
+
from megaplan_sdk.resources.knowledge_base import KnowledgeBaseResource
|
|
16
18
|
from megaplan_sdk.resources.projects import ProjectsResource
|
|
17
19
|
from megaplan_sdk.resources.tasks import TasksResource
|
|
18
20
|
|
|
@@ -119,6 +121,10 @@ class MegaplanClient:
|
|
|
119
121
|
self.employees = EmployeesResource(self._http, cache=self._cache)
|
|
120
122
|
self.departments = DepartmentsResource(self._http, cache=self._cache)
|
|
121
123
|
self.filters = FiltersResource(self._http, cache=self._cache)
|
|
124
|
+
self.knowledge_article = KnowledgeArticleResource(self._http, cache=self._cache)
|
|
125
|
+
self.knowledge_base = KnowledgeBaseResource(
|
|
126
|
+
self._http, cache=self._cache, article_resource=self.knowledge_article
|
|
127
|
+
)
|
|
122
128
|
|
|
123
129
|
# Security: Store password only for initial authentication if provided
|
|
124
130
|
self._initial_password = password if (username and password) else None
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Constants for Megaplan SDK."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ContentType:
|
|
5
|
+
"""Content type constants for Megaplan API entities."""
|
|
6
|
+
|
|
7
|
+
TASK = "Task"
|
|
8
|
+
PROJECT = "Project"
|
|
9
|
+
DEAL = "Deal"
|
|
10
|
+
EMPLOYEE = "Employee"
|
|
11
|
+
CONTRACTOR = "Contractor"
|
|
12
|
+
CONTRACTOR_COMPANY = "ContractorCompany"
|
|
13
|
+
CONTRACTOR_HUMAN = "ContractorHuman"
|
|
14
|
+
CONTRACTOR_CATEGORY = "ContractorCategory"
|
|
15
|
+
DEPARTMENT = "Department"
|
|
16
|
+
COMMENT = "Comment"
|
|
17
|
+
GROUP = "Group"
|
|
18
|
+
KNOWLEDGE_BASE = "KnowledgeBase"
|
|
19
|
+
KNOWLEDGE_ARTICLE = "KnowledgeArticle"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Task fields users commonly try to sort by that the Megaplan API rejects (422).
|
|
23
|
+
# Maps the unsupported field name to the supported replacement to suggest.
|
|
24
|
+
UNSUPPORTED_TASK_SORT_FIELDS: dict[str, str] = {
|
|
25
|
+
"timeUpdated": "activity",
|
|
26
|
+
"updatedAt": "activity",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# Recommended `fields` set for tasks.list() so that date fields are populated.
|
|
30
|
+
# Megaplan list endpoints omit these unless explicitly requested, which makes
|
|
31
|
+
# client-side time-window filtering silently return nothing (#8).
|
|
32
|
+
# Only fields confirmed to exist on Task are included (no "timeUpdated" — see #7).
|
|
33
|
+
DEFAULT_TASK_LIST_FIELDS: tuple[str, ...] = (
|
|
34
|
+
"name",
|
|
35
|
+
"status",
|
|
36
|
+
"timeCreated",
|
|
37
|
+
"activity",
|
|
38
|
+
"lastCommentTimeCreated",
|
|
39
|
+
"statusChangeTime",
|
|
40
|
+
"actualStart",
|
|
41
|
+
"owner",
|
|
42
|
+
"responsible",
|
|
43
|
+
"commentsCount",
|
|
44
|
+
)
|
|
@@ -4,6 +4,11 @@ from megaplan_sdk.models.base import BaseEntity
|
|
|
4
4
|
from megaplan_sdk.models.common import File, Meta, Pagination, SortField
|
|
5
5
|
from megaplan_sdk.models.deal import Deal, ProgramState, TradeFilter
|
|
6
6
|
from megaplan_sdk.models.group import Group
|
|
7
|
+
from megaplan_sdk.models.knowledge import (
|
|
8
|
+
KnowledgeArticle,
|
|
9
|
+
KnowledgeBase,
|
|
10
|
+
KnowledgeSectionWithArticles,
|
|
11
|
+
)
|
|
7
12
|
from megaplan_sdk.models.participant import Participant, parse_participant, parse_participants
|
|
8
13
|
from megaplan_sdk.models.project import Project, ProjectFilter
|
|
9
14
|
from megaplan_sdk.models.task import Task, TaskFilter
|
|
@@ -25,4 +30,7 @@ __all__ = [
|
|
|
25
30
|
"Participant",
|
|
26
31
|
"parse_participant",
|
|
27
32
|
"parse_participants",
|
|
33
|
+
"KnowledgeBase",
|
|
34
|
+
"KnowledgeArticle",
|
|
35
|
+
"KnowledgeSectionWithArticles",
|
|
28
36
|
]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Knowledge Base models for Megaplan SDK."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
4
|
+
|
|
5
|
+
from megaplan_sdk.models.base import BaseEntity
|
|
6
|
+
from megaplan_sdk.models.common import DateTime
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class KnowledgeBase(BaseEntity):
|
|
10
|
+
"""Knowledge Base section.
|
|
11
|
+
|
|
12
|
+
The list endpoint returns a trimmed object (no ``content`` /
|
|
13
|
+
``last_updated`` / ``is_dropped``); ``knowledge_base.get(id)`` returns
|
|
14
|
+
the full object including the HTML ``content``.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
content_type: str = Field(alias="contentType", default="KnowledgeBase")
|
|
18
|
+
content: str | None = None # HTML, present only in the full get() response
|
|
19
|
+
access_role: str | None = Field(alias="accessRole", default=None)
|
|
20
|
+
last_updated: DateTime | None = Field(alias="lastUpdated", default=None)
|
|
21
|
+
last_update_by: BaseEntity | None = Field(alias="lastUpdateBy", default=None)
|
|
22
|
+
order_pos: int | None = Field(alias="orderPos", default=None)
|
|
23
|
+
expanded: bool | None = None
|
|
24
|
+
is_dropped: bool | None = Field(alias="isDropped", default=None)
|
|
25
|
+
|
|
26
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class KnowledgeArticle(BaseEntity):
|
|
30
|
+
"""Knowledge Base article.
|
|
31
|
+
|
|
32
|
+
NOTE: ``parent`` is ALWAYS null in the Megaplan API — use ``base`` (the
|
|
33
|
+
parent section reference) to determine which section the article belongs to.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
content_type: str = Field(alias="contentType", default="KnowledgeArticle")
|
|
37
|
+
content: str | None = None # HTML body
|
|
38
|
+
parent: BaseEntity | None = None # always null in API; use `base`
|
|
39
|
+
base: KnowledgeBase | None = None # the real parent-section link
|
|
40
|
+
access_role: str | None = Field(alias="accessRole", default=None)
|
|
41
|
+
last_updated: DateTime | None = Field(alias="lastUpdated", default=None)
|
|
42
|
+
last_update_by: BaseEntity | None = Field(alias="lastUpdateBy", default=None)
|
|
43
|
+
order_pos: int | None = Field(alias="orderPos", default=None)
|
|
44
|
+
expanded: bool | None = None
|
|
45
|
+
is_dropped: bool | None = Field(alias="isDropped", default=None)
|
|
46
|
+
|
|
47
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class KnowledgeSectionWithArticles(BaseModel):
|
|
51
|
+
"""Composite returned by the experimental ``get_with_articles`` helper."""
|
|
52
|
+
|
|
53
|
+
section: KnowledgeBase
|
|
54
|
+
articles: list[KnowledgeArticle]
|
|
55
|
+
|
|
56
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
@@ -44,6 +44,13 @@ class Task(TimestampMixin):
|
|
|
44
44
|
tags: list[BaseEntity] | None = None
|
|
45
45
|
attaches: list[BaseEntity] | None = None
|
|
46
46
|
todos: list[BaseEntity] | None = None
|
|
47
|
+
activity: str | DateTime | None = None
|
|
48
|
+
last_comment_time_created: str | DateTime | None = Field(
|
|
49
|
+
alias="lastCommentTimeCreated", default=None
|
|
50
|
+
)
|
|
51
|
+
status_change_time: str | DateTime | None = Field(alias="statusChangeTime", default=None)
|
|
52
|
+
actual_start: str | DateTime | None = Field(alias="actualStart", default=None)
|
|
53
|
+
last_view: str | DateTime | None = Field(alias="lastView", default=None)
|
|
47
54
|
|
|
48
55
|
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
49
56
|
|
|
@@ -521,6 +521,8 @@ class BaseResource:
|
|
|
521
521
|
"contractorCompany": ContentType.CONTRACTOR_COMPANY,
|
|
522
522
|
"contractorHuman": ContentType.CONTRACTOR_HUMAN,
|
|
523
523
|
"comment": ContentType.COMMENT,
|
|
524
|
+
"knowledgeBase": ContentType.KNOWLEDGE_BASE,
|
|
525
|
+
"knowledgeArticle": ContentType.KNOWLEDGE_ARTICLE,
|
|
524
526
|
}
|
|
525
527
|
|
|
526
528
|
result = mapping.get(entity_type)
|