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.
Files changed (52) hide show
  1. {megaplan_sdk-0.2.2/src/megaplan_sdk.egg-info → megaplan_sdk-0.3.0}/PKG-INFO +124 -2
  2. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/README.md +123 -1
  3. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/pyproject.toml +1 -1
  4. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/__init__.py +12 -1
  5. megaplan_sdk-0.3.0/src/megaplan_sdk/_knowledge_links.py +26 -0
  6. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/client.py +6 -0
  7. megaplan_sdk-0.3.0/src/megaplan_sdk/constants.py +44 -0
  8. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/models/__init__.py +8 -0
  9. megaplan_sdk-0.3.0/src/megaplan_sdk/models/knowledge.py +56 -0
  10. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/models/task.py +7 -0
  11. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/resources/base.py +2 -0
  12. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/resources/comments.py +61 -4
  13. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/resources/deals.py +7 -0
  14. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/resources/full_details.py +15 -0
  15. megaplan_sdk-0.3.0/src/megaplan_sdk/resources/knowledge_article.py +28 -0
  16. megaplan_sdk-0.3.0/src/megaplan_sdk/resources/knowledge_base.py +149 -0
  17. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/resources/projects.py +7 -0
  18. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/resources/tasks.py +27 -1
  19. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0/src/megaplan_sdk.egg-info}/PKG-INFO +124 -2
  20. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk.egg-info/SOURCES.txt +4 -0
  21. megaplan_sdk-0.2.2/src/megaplan_sdk/constants.py +0 -17
  22. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/LICENSE +0 -0
  23. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/setup.cfg +0 -0
  24. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/auth.py +0 -0
  25. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/cache.py +0 -0
  26. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/exceptions.py +0 -0
  27. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/filter_builder.py +0 -0
  28. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/helpers.py +0 -0
  29. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/http_client.py +0 -0
  30. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/logging_config.py +0 -0
  31. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/models/base.py +0 -0
  32. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/models/comment.py +0 -0
  33. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/models/common.py +0 -0
  34. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/models/contractor.py +0 -0
  35. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/models/deal.py +0 -0
  36. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/models/department.py +0 -0
  37. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/models/employee.py +0 -0
  38. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/models/filter.py +0 -0
  39. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/models/group.py +0 -0
  40. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/models/milestone.py +0 -0
  41. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/models/participant.py +0 -0
  42. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/models/project.py +0 -0
  43. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/resources/__init__.py +0 -0
  44. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/resources/auth.py +0 -0
  45. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/resources/contractors.py +0 -0
  46. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/resources/departments.py +0 -0
  47. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/resources/employees.py +0 -0
  48. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/resources/filters.py +0 -0
  49. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk/types.py +0 -0
  50. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk.egg-info/dependency_links.txt +0 -0
  51. {megaplan_sdk-0.2.2 → megaplan_sdk-0.3.0}/src/megaplan_sdk.egg-info/requires.txt +0 -0
  52. {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.2.2
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.text)
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.text)
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "megaplan-sdk"
7
- version = "0.2.2"
7
+ version = "0.3.0"
8
8
  description = "Professional Python SDK for Megaplan API v3"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -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.2.2"
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)