megaplan-sdk 0.2.0__tar.gz → 0.2.1__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.0/src/megaplan_sdk.egg-info → megaplan_sdk-0.2.1}/PKG-INFO +176 -4
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/README.md +175 -3
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/pyproject.toml +1 -1
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/__init__.py +6 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/client.py +4 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/constants.py +1 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/http_client.py +6 -1
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/__init__.py +6 -0
- megaplan_sdk-0.2.1/src/megaplan_sdk/models/group.py +40 -0
- megaplan_sdk-0.2.1/src/megaplan_sdk/models/participant.py +74 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/base.py +52 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/contractors.py +46 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/deals.py +52 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/projects.py +160 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/tasks.py +166 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1/src/megaplan_sdk.egg-info}/PKG-INFO +176 -4
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk.egg-info/SOURCES.txt +2 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/LICENSE +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/setup.cfg +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/auth.py +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/cache.py +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/exceptions.py +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/filter_builder.py +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/helpers.py +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/logging_config.py +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/base.py +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/comment.py +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/common.py +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/contractor.py +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/deal.py +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/department.py +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/employee.py +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/filter.py +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/milestone.py +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/project.py +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/task.py +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/__init__.py +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/auth.py +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/comments.py +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/departments.py +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/employees.py +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/filters.py +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/full_details.py +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/types.py +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk.egg-info/dependency_links.txt +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk.egg-info/requires.txt +0 -0
- {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/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.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Professional Python SDK for Megaplan API v3
|
|
5
5
|
Author-email: Maxim Borzov <max@borzov.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -74,6 +74,7 @@ Dynamic: license-file
|
|
|
74
74
|
- [Автоматическая подгрузка связанных сущностей](#автоматическая-подгрузка-связанных-сущностей)
|
|
75
75
|
- [Работа с фильтрами](#работа-с-фильтрами)
|
|
76
76
|
- [Настройка HTTP-клиента](#настройка-http-клиента)
|
|
77
|
+
- [Работа через прокси](#работа-через-прокси)
|
|
77
78
|
- [Ручное управление токенами](#ручное-управление-токенами)
|
|
78
79
|
|
|
79
80
|
### Справочная информация
|
|
@@ -570,6 +571,53 @@ actual_subtasks = await client.tasks.get_actual_sub_tasks(
|
|
|
570
571
|
# Возвращает: list[Task] - список актуальных подзадач
|
|
571
572
|
```
|
|
572
573
|
|
|
574
|
+
### Получение доступных родителей
|
|
575
|
+
|
|
576
|
+
Методы для получения доступных надзадач и надпроектов (для выбора родителя при создании или перемещении задачи):
|
|
577
|
+
|
|
578
|
+
```python
|
|
579
|
+
# Глобальный поиск доступных родителей для новой задачи
|
|
580
|
+
# Возвращает список Task и Project объектов
|
|
581
|
+
parents = await client.tasks.get_available_parents(
|
|
582
|
+
is_template=False, # bool: Фильтр по шаблонам
|
|
583
|
+
limit=10, # int: Количество элементов
|
|
584
|
+
)
|
|
585
|
+
for parent in parents:
|
|
586
|
+
print(f"{type(parent).__name__}: {parent.name}") # "Task: ..." или "Project: ..."
|
|
587
|
+
|
|
588
|
+
# Доступные родители для существующей задачи
|
|
589
|
+
# Исключает саму задачу и её потомков
|
|
590
|
+
parents = await client.tasks.get_available_parents_for(
|
|
591
|
+
task_id=123,
|
|
592
|
+
is_template=False,
|
|
593
|
+
limit=10,
|
|
594
|
+
)
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
**Примечание:** Методы возвращают смешанный список объектов `Task` и `Project`, так как задача может быть вложена как в другую задачу, так и в проект.
|
|
598
|
+
|
|
599
|
+
### Получение всех участников задачи
|
|
600
|
+
|
|
601
|
+
Метод `get_all_participants()` возвращает полный список участников задачи (ответственный, соисполнители, аудиторы, владелец) в одном запросе:
|
|
602
|
+
|
|
603
|
+
```python
|
|
604
|
+
participants = await client.tasks.get_all_participants(
|
|
605
|
+
task_id=123,
|
|
606
|
+
limit=None, # int: Количество элементов
|
|
607
|
+
# ... стандартные параметры пагинации
|
|
608
|
+
)
|
|
609
|
+
# Возвращает: list[Employee | ContractorHuman | Group]
|
|
610
|
+
|
|
611
|
+
for participant in participants:
|
|
612
|
+
if hasattr(participant, 'display_name'):
|
|
613
|
+
print(participant.display_name())
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
**Типы участников:**
|
|
617
|
+
- `Employee` — сотрудник организации
|
|
618
|
+
- `ContractorHuman` — контрагент-физлицо
|
|
619
|
+
- `Group` — группа участников (например, отдел)
|
|
620
|
+
|
|
573
621
|
### Получение задач на уровне дерева
|
|
574
622
|
|
|
575
623
|
```python
|
|
@@ -790,6 +838,45 @@ actual_issues = await client.projects.get_actual_issues(
|
|
|
790
838
|
# Возвращает: list[Task] - список актуальных задач проекта
|
|
791
839
|
```
|
|
792
840
|
|
|
841
|
+
### Получение доступных родителей
|
|
842
|
+
|
|
843
|
+
Методы для получения доступных родительских проектов (для выбора родителя при создании или перемещении проекта):
|
|
844
|
+
|
|
845
|
+
```python
|
|
846
|
+
# Глобальный поиск доступных родительских проектов
|
|
847
|
+
parents = await client.projects.get_available_parents(
|
|
848
|
+
is_template=False, # bool: Фильтр по шаблонам
|
|
849
|
+
limit=10, # int: Количество элементов
|
|
850
|
+
)
|
|
851
|
+
for parent in parents:
|
|
852
|
+
print(f"Project: {parent.name}")
|
|
853
|
+
|
|
854
|
+
# Доступные родители для существующего проекта
|
|
855
|
+
# Исключает сам проект и его потомков
|
|
856
|
+
parents = await client.projects.get_available_parents_for(
|
|
857
|
+
project_id=456,
|
|
858
|
+
is_template=False,
|
|
859
|
+
limit=10,
|
|
860
|
+
)
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
**Примечание:** В отличие от задач, проекты могут быть вложены только в другие проекты, поэтому возвращается список объектов `Project`.
|
|
864
|
+
|
|
865
|
+
### Получение всех участников проекта
|
|
866
|
+
|
|
867
|
+
Метод `get_all_participants()` возвращает полный список участников проекта в одном запросе:
|
|
868
|
+
|
|
869
|
+
```python
|
|
870
|
+
participants = await client.projects.get_all_participants(
|
|
871
|
+
project_id=123,
|
|
872
|
+
limit=None, # int: Количество элементов
|
|
873
|
+
)
|
|
874
|
+
# Возвращает: list[Employee | ContractorHuman | Group]
|
|
875
|
+
|
|
876
|
+
for participant in participants:
|
|
877
|
+
print(f"{type(participant).__name__}: {participant.display_name()}")
|
|
878
|
+
```
|
|
879
|
+
|
|
793
880
|
### Получение полной информации о проекте
|
|
794
881
|
|
|
795
882
|
Метод `get_full_details()` для проектов поддерживает следующие специфичные параметры:
|
|
@@ -917,6 +1004,23 @@ deal = await client.deals.apply_trigger(
|
|
|
917
1004
|
# Возвращает: Deal - обновленная сделка
|
|
918
1005
|
```
|
|
919
1006
|
|
|
1007
|
+
### Получение всех участников сделки
|
|
1008
|
+
|
|
1009
|
+
Метод `get_all_participants()` возвращает полный список участников сделки:
|
|
1010
|
+
|
|
1011
|
+
```python
|
|
1012
|
+
participants = await client.deals.get_all_participants(
|
|
1013
|
+
deal_id=200,
|
|
1014
|
+
limit=None, # int: Количество элементов
|
|
1015
|
+
)
|
|
1016
|
+
# Возвращает: list[Employee]
|
|
1017
|
+
|
|
1018
|
+
for employee in participants:
|
|
1019
|
+
print(employee.display_name())
|
|
1020
|
+
```
|
|
1021
|
+
|
|
1022
|
+
**Примечание:** В отличие от задач и проектов, сделки возвращают только сотрудников (`Employee`).
|
|
1023
|
+
|
|
920
1024
|
### Получение аудиторов сделки
|
|
921
1025
|
|
|
922
1026
|
```python
|
|
@@ -1402,6 +1506,48 @@ client = MegaplanClient(
|
|
|
1402
1506
|
)
|
|
1403
1507
|
```
|
|
1404
1508
|
|
|
1509
|
+
### Работа через прокси
|
|
1510
|
+
|
|
1511
|
+
SDK поддерживает работу через HTTP/HTTPS/SOCKS5 прокси-серверы. Это полезно для корпоративных сетей, где все запросы должны проходить через прокси.
|
|
1512
|
+
|
|
1513
|
+
```python
|
|
1514
|
+
# HTTP прокси с аутентификацией
|
|
1515
|
+
async with MegaplanClient(
|
|
1516
|
+
base_url="https://my.megaplan.ru",
|
|
1517
|
+
username="user@example.com",
|
|
1518
|
+
password="password",
|
|
1519
|
+
proxy="http://login:pass@proxy.corp.local:8080",
|
|
1520
|
+
) as client:
|
|
1521
|
+
tasks = await client.tasks.list()
|
|
1522
|
+
|
|
1523
|
+
# HTTP прокси без аутентификации
|
|
1524
|
+
client = MegaplanClient(
|
|
1525
|
+
base_url="https://my.megaplan.ru",
|
|
1526
|
+
access_token="token",
|
|
1527
|
+
proxy="http://proxy.corp.local:8080",
|
|
1528
|
+
)
|
|
1529
|
+
|
|
1530
|
+
# HTTPS прокси
|
|
1531
|
+
client = MegaplanClient(
|
|
1532
|
+
base_url="https://my.megaplan.ru",
|
|
1533
|
+
access_token="token",
|
|
1534
|
+
proxy="https://proxy.corp.local:8080",
|
|
1535
|
+
)
|
|
1536
|
+
|
|
1537
|
+
# SOCKS5 прокси (требует httpx[socks])
|
|
1538
|
+
client = MegaplanClient(
|
|
1539
|
+
base_url="https://my.megaplan.ru",
|
|
1540
|
+
access_token="token",
|
|
1541
|
+
proxy="socks5://user:pass@proxy.corp.local:1080",
|
|
1542
|
+
)
|
|
1543
|
+
```
|
|
1544
|
+
|
|
1545
|
+
**Поддерживаемые форматы прокси:**
|
|
1546
|
+
- `http://proxy:port` - HTTP прокси без аутентификации
|
|
1547
|
+
- `http://user:password@proxy:port` - HTTP прокси с аутентификацией
|
|
1548
|
+
- `https://proxy:port` - HTTPS прокси
|
|
1549
|
+
- `socks5://user:password@proxy:port` - SOCKS5 прокси (требует `pip install httpx[socks]`)
|
|
1550
|
+
|
|
1405
1551
|
### Ручное управление токенами
|
|
1406
1552
|
|
|
1407
1553
|
```python
|
|
@@ -1440,13 +1586,39 @@ API возвращает ошибку 500 при попытке получить
|
|
|
1440
1586
|
# comments = await client.contractors.get_comments(contractor_id=123)
|
|
1441
1587
|
|
|
1442
1588
|
# Вместо этого используйте комментарии в сделках контрагента
|
|
1443
|
-
deals = await client.
|
|
1444
|
-
base_on={"contentType": "Contractor", "id": 123}
|
|
1445
|
-
)
|
|
1589
|
+
deals = await client.contractors.get_deals(contractor_id=123)
|
|
1446
1590
|
for deal in deals:
|
|
1447
1591
|
comments = await client.deals.get_comments(deal.id)
|
|
1448
1592
|
```
|
|
1449
1593
|
|
|
1594
|
+
### Получение сделок контрагента
|
|
1595
|
+
|
|
1596
|
+
SDK предоставляет удобный метод `get_deals()` для получения сделок контрагента:
|
|
1597
|
+
|
|
1598
|
+
```python
|
|
1599
|
+
# Получить все сделки контрагента
|
|
1600
|
+
deals = await client.contractors.get_deals(
|
|
1601
|
+
contractor_id=123,
|
|
1602
|
+
limit=50 # Опционально
|
|
1603
|
+
)
|
|
1604
|
+
|
|
1605
|
+
for deal in deals:
|
|
1606
|
+
print(f"[{deal.id}] {deal.name}")
|
|
1607
|
+
if deal.state:
|
|
1608
|
+
print(f" Статус: {deal.state}")
|
|
1609
|
+
```
|
|
1610
|
+
|
|
1611
|
+
Это удобнее, чем использование `FilterBuilder`:
|
|
1612
|
+
|
|
1613
|
+
```python
|
|
1614
|
+
# Альтернатива через FilterBuilder (более сложный способ)
|
|
1615
|
+
from megaplan_sdk import TradeFilterBuilder
|
|
1616
|
+
filter_obj = TradeFilterBuilder().field("contractor").equals(
|
|
1617
|
+
{"contentType": "Contractor", "id": 123}
|
|
1618
|
+
).build()
|
|
1619
|
+
deals = await client.deals.list(filter=filter_obj)
|
|
1620
|
+
```
|
|
1621
|
+
|
|
1450
1622
|
### Поиск сотрудников
|
|
1451
1623
|
|
|
1452
1624
|
Поиск сотрудников по имени или телефону может работать некорректно и возвращать 0 результатов. Для надежного поиска используйте точный email:
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
- [Автоматическая подгрузка связанных сущностей](#автоматическая-подгрузка-связанных-сущностей)
|
|
43
43
|
- [Работа с фильтрами](#работа-с-фильтрами)
|
|
44
44
|
- [Настройка HTTP-клиента](#настройка-http-клиента)
|
|
45
|
+
- [Работа через прокси](#работа-через-прокси)
|
|
45
46
|
- [Ручное управление токенами](#ручное-управление-токенами)
|
|
46
47
|
|
|
47
48
|
### Справочная информация
|
|
@@ -538,6 +539,53 @@ actual_subtasks = await client.tasks.get_actual_sub_tasks(
|
|
|
538
539
|
# Возвращает: list[Task] - список актуальных подзадач
|
|
539
540
|
```
|
|
540
541
|
|
|
542
|
+
### Получение доступных родителей
|
|
543
|
+
|
|
544
|
+
Методы для получения доступных надзадач и надпроектов (для выбора родителя при создании или перемещении задачи):
|
|
545
|
+
|
|
546
|
+
```python
|
|
547
|
+
# Глобальный поиск доступных родителей для новой задачи
|
|
548
|
+
# Возвращает список Task и Project объектов
|
|
549
|
+
parents = await client.tasks.get_available_parents(
|
|
550
|
+
is_template=False, # bool: Фильтр по шаблонам
|
|
551
|
+
limit=10, # int: Количество элементов
|
|
552
|
+
)
|
|
553
|
+
for parent in parents:
|
|
554
|
+
print(f"{type(parent).__name__}: {parent.name}") # "Task: ..." или "Project: ..."
|
|
555
|
+
|
|
556
|
+
# Доступные родители для существующей задачи
|
|
557
|
+
# Исключает саму задачу и её потомков
|
|
558
|
+
parents = await client.tasks.get_available_parents_for(
|
|
559
|
+
task_id=123,
|
|
560
|
+
is_template=False,
|
|
561
|
+
limit=10,
|
|
562
|
+
)
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
**Примечание:** Методы возвращают смешанный список объектов `Task` и `Project`, так как задача может быть вложена как в другую задачу, так и в проект.
|
|
566
|
+
|
|
567
|
+
### Получение всех участников задачи
|
|
568
|
+
|
|
569
|
+
Метод `get_all_participants()` возвращает полный список участников задачи (ответственный, соисполнители, аудиторы, владелец) в одном запросе:
|
|
570
|
+
|
|
571
|
+
```python
|
|
572
|
+
participants = await client.tasks.get_all_participants(
|
|
573
|
+
task_id=123,
|
|
574
|
+
limit=None, # int: Количество элементов
|
|
575
|
+
# ... стандартные параметры пагинации
|
|
576
|
+
)
|
|
577
|
+
# Возвращает: list[Employee | ContractorHuman | Group]
|
|
578
|
+
|
|
579
|
+
for participant in participants:
|
|
580
|
+
if hasattr(participant, 'display_name'):
|
|
581
|
+
print(participant.display_name())
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
**Типы участников:**
|
|
585
|
+
- `Employee` — сотрудник организации
|
|
586
|
+
- `ContractorHuman` — контрагент-физлицо
|
|
587
|
+
- `Group` — группа участников (например, отдел)
|
|
588
|
+
|
|
541
589
|
### Получение задач на уровне дерева
|
|
542
590
|
|
|
543
591
|
```python
|
|
@@ -758,6 +806,45 @@ actual_issues = await client.projects.get_actual_issues(
|
|
|
758
806
|
# Возвращает: list[Task] - список актуальных задач проекта
|
|
759
807
|
```
|
|
760
808
|
|
|
809
|
+
### Получение доступных родителей
|
|
810
|
+
|
|
811
|
+
Методы для получения доступных родительских проектов (для выбора родителя при создании или перемещении проекта):
|
|
812
|
+
|
|
813
|
+
```python
|
|
814
|
+
# Глобальный поиск доступных родительских проектов
|
|
815
|
+
parents = await client.projects.get_available_parents(
|
|
816
|
+
is_template=False, # bool: Фильтр по шаблонам
|
|
817
|
+
limit=10, # int: Количество элементов
|
|
818
|
+
)
|
|
819
|
+
for parent in parents:
|
|
820
|
+
print(f"Project: {parent.name}")
|
|
821
|
+
|
|
822
|
+
# Доступные родители для существующего проекта
|
|
823
|
+
# Исключает сам проект и его потомков
|
|
824
|
+
parents = await client.projects.get_available_parents_for(
|
|
825
|
+
project_id=456,
|
|
826
|
+
is_template=False,
|
|
827
|
+
limit=10,
|
|
828
|
+
)
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
**Примечание:** В отличие от задач, проекты могут быть вложены только в другие проекты, поэтому возвращается список объектов `Project`.
|
|
832
|
+
|
|
833
|
+
### Получение всех участников проекта
|
|
834
|
+
|
|
835
|
+
Метод `get_all_participants()` возвращает полный список участников проекта в одном запросе:
|
|
836
|
+
|
|
837
|
+
```python
|
|
838
|
+
participants = await client.projects.get_all_participants(
|
|
839
|
+
project_id=123,
|
|
840
|
+
limit=None, # int: Количество элементов
|
|
841
|
+
)
|
|
842
|
+
# Возвращает: list[Employee | ContractorHuman | Group]
|
|
843
|
+
|
|
844
|
+
for participant in participants:
|
|
845
|
+
print(f"{type(participant).__name__}: {participant.display_name()}")
|
|
846
|
+
```
|
|
847
|
+
|
|
761
848
|
### Получение полной информации о проекте
|
|
762
849
|
|
|
763
850
|
Метод `get_full_details()` для проектов поддерживает следующие специфичные параметры:
|
|
@@ -885,6 +972,23 @@ deal = await client.deals.apply_trigger(
|
|
|
885
972
|
# Возвращает: Deal - обновленная сделка
|
|
886
973
|
```
|
|
887
974
|
|
|
975
|
+
### Получение всех участников сделки
|
|
976
|
+
|
|
977
|
+
Метод `get_all_participants()` возвращает полный список участников сделки:
|
|
978
|
+
|
|
979
|
+
```python
|
|
980
|
+
participants = await client.deals.get_all_participants(
|
|
981
|
+
deal_id=200,
|
|
982
|
+
limit=None, # int: Количество элементов
|
|
983
|
+
)
|
|
984
|
+
# Возвращает: list[Employee]
|
|
985
|
+
|
|
986
|
+
for employee in participants:
|
|
987
|
+
print(employee.display_name())
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
**Примечание:** В отличие от задач и проектов, сделки возвращают только сотрудников (`Employee`).
|
|
991
|
+
|
|
888
992
|
### Получение аудиторов сделки
|
|
889
993
|
|
|
890
994
|
```python
|
|
@@ -1370,6 +1474,48 @@ client = MegaplanClient(
|
|
|
1370
1474
|
)
|
|
1371
1475
|
```
|
|
1372
1476
|
|
|
1477
|
+
### Работа через прокси
|
|
1478
|
+
|
|
1479
|
+
SDK поддерживает работу через HTTP/HTTPS/SOCKS5 прокси-серверы. Это полезно для корпоративных сетей, где все запросы должны проходить через прокси.
|
|
1480
|
+
|
|
1481
|
+
```python
|
|
1482
|
+
# HTTP прокси с аутентификацией
|
|
1483
|
+
async with MegaplanClient(
|
|
1484
|
+
base_url="https://my.megaplan.ru",
|
|
1485
|
+
username="user@example.com",
|
|
1486
|
+
password="password",
|
|
1487
|
+
proxy="http://login:pass@proxy.corp.local:8080",
|
|
1488
|
+
) as client:
|
|
1489
|
+
tasks = await client.tasks.list()
|
|
1490
|
+
|
|
1491
|
+
# HTTP прокси без аутентификации
|
|
1492
|
+
client = MegaplanClient(
|
|
1493
|
+
base_url="https://my.megaplan.ru",
|
|
1494
|
+
access_token="token",
|
|
1495
|
+
proxy="http://proxy.corp.local:8080",
|
|
1496
|
+
)
|
|
1497
|
+
|
|
1498
|
+
# HTTPS прокси
|
|
1499
|
+
client = MegaplanClient(
|
|
1500
|
+
base_url="https://my.megaplan.ru",
|
|
1501
|
+
access_token="token",
|
|
1502
|
+
proxy="https://proxy.corp.local:8080",
|
|
1503
|
+
)
|
|
1504
|
+
|
|
1505
|
+
# SOCKS5 прокси (требует httpx[socks])
|
|
1506
|
+
client = MegaplanClient(
|
|
1507
|
+
base_url="https://my.megaplan.ru",
|
|
1508
|
+
access_token="token",
|
|
1509
|
+
proxy="socks5://user:pass@proxy.corp.local:1080",
|
|
1510
|
+
)
|
|
1511
|
+
```
|
|
1512
|
+
|
|
1513
|
+
**Поддерживаемые форматы прокси:**
|
|
1514
|
+
- `http://proxy:port` - HTTP прокси без аутентификации
|
|
1515
|
+
- `http://user:password@proxy:port` - HTTP прокси с аутентификацией
|
|
1516
|
+
- `https://proxy:port` - HTTPS прокси
|
|
1517
|
+
- `socks5://user:password@proxy:port` - SOCKS5 прокси (требует `pip install httpx[socks]`)
|
|
1518
|
+
|
|
1373
1519
|
### Ручное управление токенами
|
|
1374
1520
|
|
|
1375
1521
|
```python
|
|
@@ -1408,13 +1554,39 @@ API возвращает ошибку 500 при попытке получить
|
|
|
1408
1554
|
# comments = await client.contractors.get_comments(contractor_id=123)
|
|
1409
1555
|
|
|
1410
1556
|
# Вместо этого используйте комментарии в сделках контрагента
|
|
1411
|
-
deals = await client.
|
|
1412
|
-
base_on={"contentType": "Contractor", "id": 123}
|
|
1413
|
-
)
|
|
1557
|
+
deals = await client.contractors.get_deals(contractor_id=123)
|
|
1414
1558
|
for deal in deals:
|
|
1415
1559
|
comments = await client.deals.get_comments(deal.id)
|
|
1416
1560
|
```
|
|
1417
1561
|
|
|
1562
|
+
### Получение сделок контрагента
|
|
1563
|
+
|
|
1564
|
+
SDK предоставляет удобный метод `get_deals()` для получения сделок контрагента:
|
|
1565
|
+
|
|
1566
|
+
```python
|
|
1567
|
+
# Получить все сделки контрагента
|
|
1568
|
+
deals = await client.contractors.get_deals(
|
|
1569
|
+
contractor_id=123,
|
|
1570
|
+
limit=50 # Опционально
|
|
1571
|
+
)
|
|
1572
|
+
|
|
1573
|
+
for deal in deals:
|
|
1574
|
+
print(f"[{deal.id}] {deal.name}")
|
|
1575
|
+
if deal.state:
|
|
1576
|
+
print(f" Статус: {deal.state}")
|
|
1577
|
+
```
|
|
1578
|
+
|
|
1579
|
+
Это удобнее, чем использование `FilterBuilder`:
|
|
1580
|
+
|
|
1581
|
+
```python
|
|
1582
|
+
# Альтернатива через FilterBuilder (более сложный способ)
|
|
1583
|
+
from megaplan_sdk import TradeFilterBuilder
|
|
1584
|
+
filter_obj = TradeFilterBuilder().field("contractor").equals(
|
|
1585
|
+
{"contentType": "Contractor", "id": 123}
|
|
1586
|
+
).build()
|
|
1587
|
+
deals = await client.deals.list(filter=filter_obj)
|
|
1588
|
+
```
|
|
1589
|
+
|
|
1418
1590
|
### Поиск сотрудников
|
|
1419
1591
|
|
|
1420
1592
|
Поиск сотрудников по имени или телефону может работать некорректно и возвращать 0 результатов. Для надежного поиска используйте точный email:
|
|
@@ -40,7 +40,9 @@ from megaplan_sdk.models.filter import (
|
|
|
40
40
|
TradeFilter,
|
|
41
41
|
UserSetting,
|
|
42
42
|
)
|
|
43
|
+
from megaplan_sdk.models.group import Group
|
|
43
44
|
from megaplan_sdk.models.milestone import Milestone
|
|
45
|
+
from megaplan_sdk.models.participant import Participant, parse_participant, parse_participants
|
|
44
46
|
from megaplan_sdk.models.project import Project, ProjectFullDetails
|
|
45
47
|
from megaplan_sdk.models.task import Task, TaskFullDetails
|
|
46
48
|
from megaplan_sdk.resources.filters import FiltersResource
|
|
@@ -69,7 +71,11 @@ __all__ = [
|
|
|
69
71
|
"ContractorHuman",
|
|
70
72
|
"Employee",
|
|
71
73
|
"Department",
|
|
74
|
+
"Group",
|
|
72
75
|
"Milestone",
|
|
76
|
+
"Participant",
|
|
77
|
+
"parse_participant",
|
|
78
|
+
"parse_participants",
|
|
73
79
|
"DateTime",
|
|
74
80
|
# Filters
|
|
75
81
|
"FiltersResource",
|
|
@@ -38,6 +38,7 @@ class MegaplanClient:
|
|
|
38
38
|
cache_max_size: int = 1000,
|
|
39
39
|
default_comments_limit: int | None = None,
|
|
40
40
|
default_history_limit: int | None = None,
|
|
41
|
+
proxy: str | None = None,
|
|
41
42
|
) -> None:
|
|
42
43
|
"""Initialize Megaplan client.
|
|
43
44
|
|
|
@@ -60,6 +61,8 @@ class MegaplanClient:
|
|
|
60
61
|
default_history_limit: Default limit for history in get_full_details().
|
|
61
62
|
None = use Megaplan API default (no explicit limit).
|
|
62
63
|
This value is used only if history_limit is not specified in method call.
|
|
64
|
+
proxy: Proxy URL for HTTP requests (e.g., http://user:pass@proxy:8080).
|
|
65
|
+
Supports HTTP, HTTPS, and SOCKS5 proxies.
|
|
63
66
|
|
|
64
67
|
Security Note:
|
|
65
68
|
For production use, it's recommended to use refresh tokens or pre-obtained
|
|
@@ -78,6 +81,7 @@ class MegaplanClient:
|
|
|
78
81
|
timeout=timeout,
|
|
79
82
|
max_retries=max_retries,
|
|
80
83
|
allow_http=allow_http,
|
|
84
|
+
proxy=proxy,
|
|
81
85
|
)
|
|
82
86
|
self._auth_manager = AuthManager(self._http)
|
|
83
87
|
|
|
@@ -28,6 +28,7 @@ class HTTPClient:
|
|
|
28
28
|
timeout: float = 30.0,
|
|
29
29
|
max_retries: int = 3,
|
|
30
30
|
allow_http: bool = False,
|
|
31
|
+
proxy: str | None = None,
|
|
31
32
|
) -> None:
|
|
32
33
|
"""Initialize HTTP client.
|
|
33
34
|
|
|
@@ -37,6 +38,8 @@ class HTTPClient:
|
|
|
37
38
|
timeout: Request timeout in seconds.
|
|
38
39
|
max_retries: Maximum number of retry attempts for 5xx errors.
|
|
39
40
|
allow_http: Allow HTTP connections (insecure, only for dev/test).
|
|
41
|
+
proxy: Proxy URL for HTTP requests (e.g., http://user:pass@proxy:8080).
|
|
42
|
+
Supports HTTP, HTTPS, and SOCKS5 proxies.
|
|
40
43
|
|
|
41
44
|
Raises:
|
|
42
45
|
ValueError: If base_url is not HTTPS and allow_http is False.
|
|
@@ -52,6 +55,7 @@ class HTTPClient:
|
|
|
52
55
|
self._access_token: str | None = access_token
|
|
53
56
|
self.timeout = timeout
|
|
54
57
|
self.max_retries = max_retries
|
|
58
|
+
self._proxy = proxy
|
|
55
59
|
self._client: httpx.AsyncClient | None = None
|
|
56
60
|
|
|
57
61
|
@property
|
|
@@ -83,7 +87,8 @@ class HTTPClient:
|
|
|
83
87
|
timeout=self.timeout,
|
|
84
88
|
headers={"Content-Type": "application/json"},
|
|
85
89
|
limits=limits,
|
|
86
|
-
follow_redirects=True,
|
|
90
|
+
follow_redirects=True,
|
|
91
|
+
proxy=self._proxy,
|
|
87
92
|
)
|
|
88
93
|
|
|
89
94
|
async def close(self) -> None:
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
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
|
+
from megaplan_sdk.models.group import Group
|
|
7
|
+
from megaplan_sdk.models.participant import Participant, parse_participant, parse_participants
|
|
6
8
|
from megaplan_sdk.models.project import Project, ProjectFilter
|
|
7
9
|
from megaplan_sdk.models.task import Task, TaskFilter
|
|
8
10
|
|
|
@@ -19,4 +21,8 @@ __all__ = [
|
|
|
19
21
|
"Deal",
|
|
20
22
|
"TradeFilter",
|
|
21
23
|
"ProgramState",
|
|
24
|
+
"Group",
|
|
25
|
+
"Participant",
|
|
26
|
+
"parse_participant",
|
|
27
|
+
"parse_participants",
|
|
22
28
|
]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Group model for Megaplan SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import ConfigDict, Field
|
|
6
|
+
|
|
7
|
+
from megaplan_sdk.models.base import BaseEntity
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Group(BaseEntity):
|
|
11
|
+
"""Group entity for organizing participants.
|
|
12
|
+
|
|
13
|
+
Used to group entities by some attribute (e.g., department, role).
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
id: Group identifier.
|
|
17
|
+
content_type: Entity content type (always "Group").
|
|
18
|
+
name: Group name.
|
|
19
|
+
children: List of child entities in this group.
|
|
20
|
+
children_count: Number of entities in this group.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
content_type: str = Field(alias="contentType", default="Group")
|
|
24
|
+
name: str | None = None
|
|
25
|
+
children: list[BaseEntity] | None = None
|
|
26
|
+
children_count: int | None = Field(alias="childrenCount", default=None)
|
|
27
|
+
|
|
28
|
+
model_config = ConfigDict(populate_by_name=True, extra="ignore")
|
|
29
|
+
|
|
30
|
+
def display_name(self) -> str:
|
|
31
|
+
"""Get display name for the group.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Group name or fallback identifier.
|
|
35
|
+
"""
|
|
36
|
+
return self.name or f"Group#{self.id}"
|
|
37
|
+
|
|
38
|
+
def __str__(self) -> str:
|
|
39
|
+
"""Return display name for string representation."""
|
|
40
|
+
return self.display_name()
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Participant types for Megaplan SDK.
|
|
2
|
+
|
|
3
|
+
This module provides union types and parsing utilities for participant entities
|
|
4
|
+
returned by allParticipants endpoints.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from megaplan_sdk.constants import ContentType
|
|
12
|
+
from megaplan_sdk.models.contractor import ContractorHuman
|
|
13
|
+
from megaplan_sdk.models.employee import Employee
|
|
14
|
+
from megaplan_sdk.models.group import Group
|
|
15
|
+
|
|
16
|
+
Participant = Employee | ContractorHuman | Group
|
|
17
|
+
"""Union type for participants in tasks and projects.
|
|
18
|
+
|
|
19
|
+
Participants can be:
|
|
20
|
+
- Employee: An employee of the organization
|
|
21
|
+
- ContractorHuman: A human contractor (individual)
|
|
22
|
+
- Group: A group of participants (e.g., department)
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def parse_participant(data: dict[str, Any]) -> Participant:
|
|
27
|
+
"""Parse participant data into appropriate model based on contentType.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
data: Raw participant data from API response.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Parsed participant as Employee, ContractorHuman, or Group.
|
|
34
|
+
|
|
35
|
+
Raises:
|
|
36
|
+
ValueError: If contentType is unknown or missing.
|
|
37
|
+
|
|
38
|
+
Examples:
|
|
39
|
+
>>> data = {"contentType": "Employee", "id": 123, "firstName": "John"}
|
|
40
|
+
>>> participant = parse_participant(data)
|
|
41
|
+
>>> isinstance(participant, Employee)
|
|
42
|
+
True
|
|
43
|
+
"""
|
|
44
|
+
content_type = data.get("contentType")
|
|
45
|
+
|
|
46
|
+
if content_type == ContentType.EMPLOYEE:
|
|
47
|
+
return Employee(**data)
|
|
48
|
+
elif content_type == ContentType.CONTRACTOR_HUMAN:
|
|
49
|
+
return ContractorHuman(**data)
|
|
50
|
+
elif content_type == ContentType.GROUP:
|
|
51
|
+
return Group(**data)
|
|
52
|
+
else:
|
|
53
|
+
raise ValueError(f"Unknown participant contentType: {content_type}")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def parse_participants(data_list: list[dict[str, Any]]) -> list[Participant]:
|
|
57
|
+
"""Parse list of participant data into appropriate models.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
data_list: List of raw participant data from API response.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
List of parsed participants.
|
|
64
|
+
|
|
65
|
+
Examples:
|
|
66
|
+
>>> data = [
|
|
67
|
+
... {"contentType": "Employee", "id": 1, "firstName": "John"},
|
|
68
|
+
... {"contentType": "Group", "id": 2, "name": "Developers"},
|
|
69
|
+
... ]
|
|
70
|
+
>>> participants = parse_participants(data)
|
|
71
|
+
>>> len(participants)
|
|
72
|
+
2
|
|
73
|
+
"""
|
|
74
|
+
return [parse_participant(item) for item in data_list]
|