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.
Files changed (47) hide show
  1. {megaplan_sdk-0.2.0/src/megaplan_sdk.egg-info → megaplan_sdk-0.2.1}/PKG-INFO +176 -4
  2. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/README.md +175 -3
  3. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/pyproject.toml +1 -1
  4. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/__init__.py +6 -0
  5. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/client.py +4 -0
  6. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/constants.py +1 -0
  7. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/http_client.py +6 -1
  8. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/__init__.py +6 -0
  9. megaplan_sdk-0.2.1/src/megaplan_sdk/models/group.py +40 -0
  10. megaplan_sdk-0.2.1/src/megaplan_sdk/models/participant.py +74 -0
  11. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/base.py +52 -0
  12. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/contractors.py +46 -0
  13. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/deals.py +52 -0
  14. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/projects.py +160 -0
  15. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/tasks.py +166 -0
  16. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1/src/megaplan_sdk.egg-info}/PKG-INFO +176 -4
  17. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk.egg-info/SOURCES.txt +2 -0
  18. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/LICENSE +0 -0
  19. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/setup.cfg +0 -0
  20. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/auth.py +0 -0
  21. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/cache.py +0 -0
  22. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/exceptions.py +0 -0
  23. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/filter_builder.py +0 -0
  24. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/helpers.py +0 -0
  25. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/logging_config.py +0 -0
  26. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/base.py +0 -0
  27. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/comment.py +0 -0
  28. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/common.py +0 -0
  29. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/contractor.py +0 -0
  30. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/deal.py +0 -0
  31. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/department.py +0 -0
  32. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/employee.py +0 -0
  33. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/filter.py +0 -0
  34. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/milestone.py +0 -0
  35. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/project.py +0 -0
  36. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/models/task.py +0 -0
  37. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/__init__.py +0 -0
  38. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/auth.py +0 -0
  39. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/comments.py +0 -0
  40. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/departments.py +0 -0
  41. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/employees.py +0 -0
  42. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/filters.py +0 -0
  43. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/resources/full_details.py +0 -0
  44. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk/types.py +0 -0
  45. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk.egg-info/dependency_links.txt +0 -0
  46. {megaplan_sdk-0.2.0 → megaplan_sdk-0.2.1}/src/megaplan_sdk.egg-info/requires.txt +0 -0
  47. {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.0
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.deals.list(
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.deals.list(
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:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "megaplan-sdk"
7
- version = "0.2.0"
7
+ version = "0.2.1"
8
8
  description = "Professional Python SDK for Megaplan API v3"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -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
 
@@ -14,3 +14,4 @@ class ContentType:
14
14
  CONTRACTOR_CATEGORY = "ContractorCategory"
15
15
  DEPARTMENT = "Department"
16
16
  COMMENT = "Comment"
17
+ GROUP = "Group"
@@ -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, # Follow redirects automatically
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]