megaplan-sdk 0.1.0__py3-none-any.whl
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/__init__.py +67 -0
- megaplan_sdk/auth.py +185 -0
- megaplan_sdk/cache.py +192 -0
- megaplan_sdk/client.py +201 -0
- megaplan_sdk/constants.py +16 -0
- megaplan_sdk/exceptions.py +180 -0
- megaplan_sdk/helpers.py +108 -0
- megaplan_sdk/http_client.py +390 -0
- megaplan_sdk/logging_config.py +53 -0
- megaplan_sdk/models/__init__.py +22 -0
- megaplan_sdk/models/base.py +16 -0
- megaplan_sdk/models/comment.py +58 -0
- megaplan_sdk/models/common.py +107 -0
- megaplan_sdk/models/contractor.py +137 -0
- megaplan_sdk/models/deal.py +96 -0
- megaplan_sdk/models/department.py +40 -0
- megaplan_sdk/models/employee.py +117 -0
- megaplan_sdk/models/project.py +76 -0
- megaplan_sdk/models/task.py +75 -0
- megaplan_sdk/resources/__init__.py +15 -0
- megaplan_sdk/resources/auth.py +73 -0
- megaplan_sdk/resources/base.py +794 -0
- megaplan_sdk/resources/comments.py +148 -0
- megaplan_sdk/resources/contractors.py +173 -0
- megaplan_sdk/resources/deals.py +625 -0
- megaplan_sdk/resources/departments.py +70 -0
- megaplan_sdk/resources/employees.py +216 -0
- megaplan_sdk/resources/full_details.py +143 -0
- megaplan_sdk/resources/projects.py +854 -0
- megaplan_sdk/resources/tasks.py +932 -0
- megaplan_sdk/types.py +56 -0
- megaplan_sdk-0.1.0.dist-info/METADATA +1383 -0
- megaplan_sdk-0.1.0.dist-info/RECORD +36 -0
- megaplan_sdk-0.1.0.dist-info/WHEEL +5 -0
- megaplan_sdk-0.1.0.dist-info/licenses/LICENSE +21 -0
- megaplan_sdk-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,854 @@
|
|
|
1
|
+
"""Projects resource for Megaplan API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import AsyncIterator
|
|
6
|
+
from typing import Any, overload
|
|
7
|
+
|
|
8
|
+
from megaplan_sdk.constants import ContentType
|
|
9
|
+
from megaplan_sdk.models.comment import Comment
|
|
10
|
+
from megaplan_sdk.models.deal import Deal
|
|
11
|
+
from megaplan_sdk.models.project import Project, ProjectFullDetails
|
|
12
|
+
from megaplan_sdk.models.task import Task
|
|
13
|
+
from megaplan_sdk.resources.base import BaseResource
|
|
14
|
+
from megaplan_sdk.resources.full_details import FullDetailsMixin, RelatedDataConfig
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ProjectsResource(BaseResource, FullDetailsMixin):
|
|
18
|
+
"""Resource for working with projects."""
|
|
19
|
+
|
|
20
|
+
_full_details_config = [
|
|
21
|
+
RelatedDataConfig("deals", "include_deals", "get_deals"),
|
|
22
|
+
RelatedDataConfig("issues", "include_issues", "get_issues"),
|
|
23
|
+
RelatedDataConfig("actual_issues", "include_actual_issues", "get_actual_issues"),
|
|
24
|
+
RelatedDataConfig(
|
|
25
|
+
"comments", "include_comments", "get_comments", limit_param="comments_limit"
|
|
26
|
+
),
|
|
27
|
+
RelatedDataConfig("history", "include_history", "get_history", limit_param="history_limit"),
|
|
28
|
+
RelatedDataConfig("auditors", "include_auditors", "get_auditors"),
|
|
29
|
+
RelatedDataConfig("executors", "include_executors", "get_executors"),
|
|
30
|
+
RelatedDataConfig("milestones", "include_milestones", "get_milestones"),
|
|
31
|
+
RelatedDataConfig(
|
|
32
|
+
"responsible_details",
|
|
33
|
+
"include_responsible_details",
|
|
34
|
+
None,
|
|
35
|
+
entity_field="responsible",
|
|
36
|
+
entity_type="employee",
|
|
37
|
+
),
|
|
38
|
+
RelatedDataConfig(
|
|
39
|
+
"owner_details",
|
|
40
|
+
"include_owner_details",
|
|
41
|
+
None,
|
|
42
|
+
entity_field="owner",
|
|
43
|
+
entity_type="employee",
|
|
44
|
+
),
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
http_client,
|
|
50
|
+
cache=None,
|
|
51
|
+
default_comments_limit: int | None = None,
|
|
52
|
+
default_history_limit: int | None = None,
|
|
53
|
+
) -> None:
|
|
54
|
+
"""Initialize projects resource.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
http_client: HTTP client for making requests.
|
|
58
|
+
cache: Optional entity cache.
|
|
59
|
+
default_comments_limit: Default limit for comments in get_full_details().
|
|
60
|
+
default_history_limit: Default limit for history in get_full_details().
|
|
61
|
+
"""
|
|
62
|
+
super().__init__(
|
|
63
|
+
http_client,
|
|
64
|
+
cache=cache,
|
|
65
|
+
default_comments_limit=default_comments_limit,
|
|
66
|
+
default_history_limit=default_history_limit,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
async def create(
|
|
70
|
+
self, project_data: dict[str, Any], auto_fill_required: bool = True
|
|
71
|
+
) -> Project:
|
|
72
|
+
"""Create a new project.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
project_data: Project data dictionary.
|
|
76
|
+
auto_fill_required: Automatically fill required fields if not provided.
|
|
77
|
+
Default: True. Sets isTemplate=False if not specified.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Created project.
|
|
81
|
+
|
|
82
|
+
Examples:
|
|
83
|
+
>>> # Minimal project creation (auto-fills required fields)
|
|
84
|
+
>>> project = await client.projects.create({"name": "New project"})
|
|
85
|
+
>>>
|
|
86
|
+
>>> # With explicit required fields
|
|
87
|
+
>>> project = await client.projects.create({
|
|
88
|
+
... "name": "New project",
|
|
89
|
+
... "isTemplate": False
|
|
90
|
+
... })
|
|
91
|
+
"""
|
|
92
|
+
# Auto-fill required fields if not provided
|
|
93
|
+
if auto_fill_required:
|
|
94
|
+
if "isTemplate" not in project_data:
|
|
95
|
+
project_data["isTemplate"] = False
|
|
96
|
+
|
|
97
|
+
return await self._create_entity("project", project_data, Project)
|
|
98
|
+
|
|
99
|
+
async def create_simple(
|
|
100
|
+
self,
|
|
101
|
+
name: str,
|
|
102
|
+
owner_id: int | None = None,
|
|
103
|
+
responsible_id: int | None = None,
|
|
104
|
+
description: str | None = None,
|
|
105
|
+
employees_resource: Any | None = None,
|
|
106
|
+
) -> Project:
|
|
107
|
+
"""Create a project with minimal required parameters.
|
|
108
|
+
|
|
109
|
+
Automatically fills required fields (isTemplate) and optionally
|
|
110
|
+
determines owner/responsible from current user if not provided.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
name: Project name (required).
|
|
114
|
+
owner_id: Owner employee ID. If None and employees_resource
|
|
115
|
+
is provided, uses current user.
|
|
116
|
+
responsible_id: Responsible employee ID. If None and employees_resource
|
|
117
|
+
is provided, uses current user.
|
|
118
|
+
description: Project description.
|
|
119
|
+
employees_resource: EmployeesResource instance for auto-detecting current user.
|
|
120
|
+
If provided and owner_id/responsible_id are None, will use current user.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Created project.
|
|
124
|
+
|
|
125
|
+
Examples:
|
|
126
|
+
>>> # Simple project with current user as owner/responsible
|
|
127
|
+
>>> project = await client.projects.create_simple(
|
|
128
|
+
... "New project",
|
|
129
|
+
... employees_resource=client.employees
|
|
130
|
+
... )
|
|
131
|
+
>>>
|
|
132
|
+
>>> # Simple project with specific owner
|
|
133
|
+
>>> project = await client.projects.create_simple(
|
|
134
|
+
... "New project",
|
|
135
|
+
... owner_id=123,
|
|
136
|
+
... responsible_id=123
|
|
137
|
+
... )
|
|
138
|
+
"""
|
|
139
|
+
project_data: dict[str, Any] = {
|
|
140
|
+
"name": name,
|
|
141
|
+
"isTemplate": False,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if description:
|
|
145
|
+
project_data["description"] = description
|
|
146
|
+
|
|
147
|
+
# Auto-determine owner/responsible from current user if not provided
|
|
148
|
+
if employees_resource and (owner_id is None or responsible_id is None):
|
|
149
|
+
try:
|
|
150
|
+
current_user = await employees_resource.get_current()
|
|
151
|
+
if owner_id is None:
|
|
152
|
+
owner_id = current_user.id
|
|
153
|
+
if responsible_id is None:
|
|
154
|
+
responsible_id = current_user.id
|
|
155
|
+
except Exception:
|
|
156
|
+
# If we can't get current user, skip owner/responsible
|
|
157
|
+
# API will return error if required
|
|
158
|
+
pass
|
|
159
|
+
|
|
160
|
+
if owner_id:
|
|
161
|
+
project_data["owner"] = {"contentType": "Employee", "id": owner_id}
|
|
162
|
+
if responsible_id:
|
|
163
|
+
project_data["responsible"] = {"contentType": "Employee", "id": responsible_id}
|
|
164
|
+
|
|
165
|
+
return await self.create(project_data, auto_fill_required=False)
|
|
166
|
+
|
|
167
|
+
@overload
|
|
168
|
+
async def list(
|
|
169
|
+
self,
|
|
170
|
+
*,
|
|
171
|
+
limit: int | None = None,
|
|
172
|
+
page_after: dict[str, Any] | None = None,
|
|
173
|
+
page_before: dict[str, Any] | None = None,
|
|
174
|
+
page_with: dict[str, Any] | None = None,
|
|
175
|
+
fields: Any | None = None,
|
|
176
|
+
sort_by: list[dict[str, str]] | None = None,
|
|
177
|
+
only_requested_fields: bool | None = None,
|
|
178
|
+
expand: None = None,
|
|
179
|
+
) -> list[Project]: ...
|
|
180
|
+
|
|
181
|
+
@overload
|
|
182
|
+
async def list(
|
|
183
|
+
self,
|
|
184
|
+
*,
|
|
185
|
+
limit: int | None = None,
|
|
186
|
+
page_after: dict[str, Any] | None = None,
|
|
187
|
+
page_before: dict[str, Any] | None = None,
|
|
188
|
+
page_with: dict[str, Any] | None = None,
|
|
189
|
+
fields: Any | None = None,
|
|
190
|
+
sort_by: list[dict[str, str]] | None = None,
|
|
191
|
+
only_requested_fields: bool | None = None,
|
|
192
|
+
expand: list[str],
|
|
193
|
+
) -> list[ProjectFullDetails]: ...
|
|
194
|
+
|
|
195
|
+
async def list(
|
|
196
|
+
self,
|
|
197
|
+
limit: int | None = None,
|
|
198
|
+
page_after: dict[str, Any] | None = None,
|
|
199
|
+
page_before: dict[str, Any] | None = None,
|
|
200
|
+
page_with: dict[str, Any] | None = None,
|
|
201
|
+
fields: Any | None = None,
|
|
202
|
+
sort_by: list[dict[str, str]] | None = None,
|
|
203
|
+
only_requested_fields: bool | None = None,
|
|
204
|
+
expand: list[str] | None = None,
|
|
205
|
+
) -> list[Project] | list[ProjectFullDetails]:
|
|
206
|
+
"""Get list of projects.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
limit: Number of items per page.
|
|
210
|
+
page_after: Load page starting from this entity.
|
|
211
|
+
page_before: Load page strictly before this entity.
|
|
212
|
+
page_with: Load page containing this entity.
|
|
213
|
+
fields: Additional fields to include.
|
|
214
|
+
sort_by: Sort fields.
|
|
215
|
+
only_requested_fields: Return only requested fields.
|
|
216
|
+
expand: List of fields to expand (e.g., ["responsible", "owner"]).
|
|
217
|
+
Supported values: "responsible", "owner".
|
|
218
|
+
If provided, returns list[ProjectFullDetails] instead of list[Project].
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
List of projects (list[Project] if expand is None, list[ProjectFullDetails] otherwise).
|
|
222
|
+
|
|
223
|
+
Examples:
|
|
224
|
+
>>> # Get projects without expansion
|
|
225
|
+
>>> projects = await client.projects.list(limit=10)
|
|
226
|
+
>>>
|
|
227
|
+
>>> # Get projects with expanded responsible and owner
|
|
228
|
+
>>> projects_full = await client.projects.list(
|
|
229
|
+
... limit=10, expand=["responsible", "owner"]
|
|
230
|
+
... )
|
|
231
|
+
>>> for project_full in projects_full:
|
|
232
|
+
... if project_full.responsible_details:
|
|
233
|
+
... print(project_full.responsible_details.display_name())
|
|
234
|
+
"""
|
|
235
|
+
path = self._build_path("api", "v3", "project")
|
|
236
|
+
|
|
237
|
+
# Use base method to build params (DRY)
|
|
238
|
+
params = self._build_list_params(
|
|
239
|
+
limit=limit,
|
|
240
|
+
page_after=page_after,
|
|
241
|
+
page_before=page_before,
|
|
242
|
+
page_with=page_with,
|
|
243
|
+
fields=fields,
|
|
244
|
+
sort_by=sort_by,
|
|
245
|
+
only_requested_fields=only_requested_fields,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
# 1. Fetch projects
|
|
249
|
+
projects = await self._get_list(path, Project, params)
|
|
250
|
+
|
|
251
|
+
# 2. If no expand, return as is
|
|
252
|
+
if not expand or not projects:
|
|
253
|
+
return projects
|
|
254
|
+
|
|
255
|
+
# 3. Batch load related entities
|
|
256
|
+
from megaplan_sdk.models.employee import Employee
|
|
257
|
+
|
|
258
|
+
expand_config: dict[str, tuple[str, type, str]] = {
|
|
259
|
+
"responsible": ("employee", Employee, ContentType.EMPLOYEE),
|
|
260
|
+
"owner": ("employee", Employee, ContentType.EMPLOYEE),
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
expanded = await self._expand_list_entities(projects, expand, expand_config)
|
|
264
|
+
responsible_map = expanded.get("responsible", {})
|
|
265
|
+
owner_map = expanded.get("owner", {})
|
|
266
|
+
|
|
267
|
+
# 4. Build ProjectFullDetails objects
|
|
268
|
+
results = []
|
|
269
|
+
for project in projects:
|
|
270
|
+
resp_details = None
|
|
271
|
+
owner_details = None
|
|
272
|
+
|
|
273
|
+
if project.responsible and project.responsible.id in responsible_map:
|
|
274
|
+
resp_details = responsible_map[project.responsible.id]
|
|
275
|
+
|
|
276
|
+
if project.owner and project.owner.id in owner_map:
|
|
277
|
+
owner_details = owner_map[project.owner.id]
|
|
278
|
+
|
|
279
|
+
results.append(
|
|
280
|
+
ProjectFullDetails(
|
|
281
|
+
project=project,
|
|
282
|
+
responsible_details=resp_details,
|
|
283
|
+
owner_details=owner_details,
|
|
284
|
+
)
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
return results
|
|
288
|
+
|
|
289
|
+
async def get(self, project_id: int) -> Project:
|
|
290
|
+
"""Get project by ID.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
project_id: Project identifier.
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
Project details.
|
|
297
|
+
"""
|
|
298
|
+
return await self._get_entity("project", project_id, Project)
|
|
299
|
+
|
|
300
|
+
async def update(self, project_id: int, project_data: dict[str, Any]) -> Project:
|
|
301
|
+
"""Update project.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
project_id: Project identifier.
|
|
305
|
+
project_data: Updated project data.
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
Updated project.
|
|
309
|
+
"""
|
|
310
|
+
return await self._update_entity("project", project_id, project_data, Project)
|
|
311
|
+
|
|
312
|
+
async def delete(self, project_id: int) -> None:
|
|
313
|
+
"""Delete project.
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
project_id: Project identifier.
|
|
317
|
+
"""
|
|
318
|
+
await self._delete_entity("project", project_id)
|
|
319
|
+
|
|
320
|
+
async def get_deals(
|
|
321
|
+
self,
|
|
322
|
+
project_id: int,
|
|
323
|
+
limit: int | None = None,
|
|
324
|
+
page_after: dict[str, Any] | None = None,
|
|
325
|
+
page_before: dict[str, Any] | None = None,
|
|
326
|
+
page_with: dict[str, Any] | None = None,
|
|
327
|
+
fields: Any | None = None,
|
|
328
|
+
sort_by: list[dict[str, str]] | None = None,
|
|
329
|
+
only_requested_fields: bool | None = None,
|
|
330
|
+
) -> list[Deal]:
|
|
331
|
+
"""Get deals associated with project.
|
|
332
|
+
|
|
333
|
+
Args:
|
|
334
|
+
project_id: Project identifier.
|
|
335
|
+
limit: Number of items per page.
|
|
336
|
+
page_after: Load page starting from this entity.
|
|
337
|
+
page_before: Load page strictly before this entity.
|
|
338
|
+
page_with: Load page containing this entity.
|
|
339
|
+
fields: Additional fields to include.
|
|
340
|
+
sort_by: Sort fields.
|
|
341
|
+
only_requested_fields: Return only requested fields.
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
List of deals.
|
|
345
|
+
"""
|
|
346
|
+
path = self._build_path("api", "v3", "project", str(project_id), "deals")
|
|
347
|
+
|
|
348
|
+
# Use base method to build params (DRY)
|
|
349
|
+
params = self._build_list_params(
|
|
350
|
+
limit=limit,
|
|
351
|
+
page_after=page_after,
|
|
352
|
+
page_before=page_before,
|
|
353
|
+
page_with=page_with,
|
|
354
|
+
fields=fields,
|
|
355
|
+
sort_by=sort_by,
|
|
356
|
+
only_requested_fields=only_requested_fields,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
return await self._get_list(path, Deal, params)
|
|
360
|
+
|
|
361
|
+
async def get_issues(
|
|
362
|
+
self,
|
|
363
|
+
project_id: int,
|
|
364
|
+
limit: int | None = None,
|
|
365
|
+
page_after: dict[str, Any] | None = None,
|
|
366
|
+
page_before: dict[str, Any] | None = None,
|
|
367
|
+
page_with: dict[str, Any] | None = None,
|
|
368
|
+
fields: Any | None = None,
|
|
369
|
+
sort_by: list[dict[str, str]] | None = None,
|
|
370
|
+
only_requested_fields: bool | None = None,
|
|
371
|
+
) -> list[Task]:
|
|
372
|
+
"""Get tasks (issues) associated with project.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
project_id: Project identifier.
|
|
376
|
+
limit: Number of items per page.
|
|
377
|
+
page_after: Load page starting from this entity.
|
|
378
|
+
page_before: Load page strictly before this entity.
|
|
379
|
+
page_with: Load page containing this entity.
|
|
380
|
+
fields: Additional fields to include.
|
|
381
|
+
sort_by: Sort fields.
|
|
382
|
+
only_requested_fields: Return only requested fields.
|
|
383
|
+
|
|
384
|
+
Returns:
|
|
385
|
+
List of tasks.
|
|
386
|
+
"""
|
|
387
|
+
path = self._build_path("api", "v3", "project", str(project_id), "issues")
|
|
388
|
+
|
|
389
|
+
# Use base method to build params (DRY)
|
|
390
|
+
params = self._build_list_params(
|
|
391
|
+
limit=limit,
|
|
392
|
+
page_after=page_after,
|
|
393
|
+
page_before=page_before,
|
|
394
|
+
page_with=page_with,
|
|
395
|
+
fields=fields,
|
|
396
|
+
sort_by=sort_by,
|
|
397
|
+
only_requested_fields=only_requested_fields,
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
return await self._get_list(path, Task, params)
|
|
401
|
+
|
|
402
|
+
async def get_actual_issues(
|
|
403
|
+
self,
|
|
404
|
+
project_id: int,
|
|
405
|
+
limit: int | None = None,
|
|
406
|
+
page_after: dict[str, Any] | None = None,
|
|
407
|
+
page_before: dict[str, Any] | None = None,
|
|
408
|
+
page_with: dict[str, Any] | None = None,
|
|
409
|
+
fields: Any | None = None,
|
|
410
|
+
sort_by: list[dict[str, str]] | None = None,
|
|
411
|
+
only_requested_fields: bool | None = None,
|
|
412
|
+
) -> list[Task]:
|
|
413
|
+
"""Get actual tasks (issues) associated with project.
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
project_id: Project identifier.
|
|
417
|
+
limit: Number of items per page.
|
|
418
|
+
page_after: Load page starting from this entity.
|
|
419
|
+
page_before: Load page strictly before this entity.
|
|
420
|
+
page_with: Load page containing this entity.
|
|
421
|
+
fields: Additional fields to include.
|
|
422
|
+
sort_by: Sort fields.
|
|
423
|
+
only_requested_fields: Return only requested fields.
|
|
424
|
+
|
|
425
|
+
Returns:
|
|
426
|
+
List of actual tasks.
|
|
427
|
+
"""
|
|
428
|
+
path = self._build_path("api", "v3", "project", str(project_id), "actualIssues")
|
|
429
|
+
|
|
430
|
+
# Use base method to build params (DRY)
|
|
431
|
+
params = self._build_list_params(
|
|
432
|
+
limit=limit,
|
|
433
|
+
page_after=page_after,
|
|
434
|
+
page_before=page_before,
|
|
435
|
+
page_with=page_with,
|
|
436
|
+
fields=fields,
|
|
437
|
+
sort_by=sort_by,
|
|
438
|
+
only_requested_fields=only_requested_fields,
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
return await self._get_list(path, Task, params)
|
|
442
|
+
|
|
443
|
+
async def iterate(
|
|
444
|
+
self,
|
|
445
|
+
limit: int = 100,
|
|
446
|
+
**kwargs: Any,
|
|
447
|
+
) -> AsyncIterator[Project]:
|
|
448
|
+
"""Iterate over all projects with automatic pagination.
|
|
449
|
+
|
|
450
|
+
Args:
|
|
451
|
+
limit: Number of items per page.
|
|
452
|
+
**kwargs: Additional parameters to pass to list().
|
|
453
|
+
|
|
454
|
+
Yields:
|
|
455
|
+
Project objects.
|
|
456
|
+
"""
|
|
457
|
+
project: Project
|
|
458
|
+
async for project in self._iterate_generic( # type: ignore[valid-type]
|
|
459
|
+
ContentType.PROJECT,
|
|
460
|
+
self.list,
|
|
461
|
+
limit,
|
|
462
|
+
**kwargs,
|
|
463
|
+
):
|
|
464
|
+
yield project
|
|
465
|
+
|
|
466
|
+
async def get_comments(
|
|
467
|
+
self,
|
|
468
|
+
project_id: int,
|
|
469
|
+
limit: int | None = None,
|
|
470
|
+
page_after: dict[str, Any] | None = None,
|
|
471
|
+
page_before: dict[str, Any] | None = None,
|
|
472
|
+
page_with: dict[str, Any] | None = None,
|
|
473
|
+
) -> list[Comment]:
|
|
474
|
+
"""Get comments for a project.
|
|
475
|
+
|
|
476
|
+
Args:
|
|
477
|
+
project_id: Project identifier.
|
|
478
|
+
limit: Number of items per page.
|
|
479
|
+
page_after: Load page starting from this entity.
|
|
480
|
+
page_before: Load page strictly before this entity.
|
|
481
|
+
page_with: Load page containing this entity.
|
|
482
|
+
|
|
483
|
+
Returns:
|
|
484
|
+
List of comments.
|
|
485
|
+
|
|
486
|
+
Examples:
|
|
487
|
+
>>> comments = await client.projects.get_comments(project_id=123)
|
|
488
|
+
"""
|
|
489
|
+
return await self._get_entity_comments(
|
|
490
|
+
"project",
|
|
491
|
+
project_id,
|
|
492
|
+
limit,
|
|
493
|
+
page_after,
|
|
494
|
+
page_before,
|
|
495
|
+
page_with,
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
async def create_comment(
|
|
499
|
+
self,
|
|
500
|
+
project_id: int,
|
|
501
|
+
text: str,
|
|
502
|
+
attaches: list[dict[str, Any]] | None = None,
|
|
503
|
+
) -> Comment:
|
|
504
|
+
"""Create a comment for a project.
|
|
505
|
+
|
|
506
|
+
Args:
|
|
507
|
+
project_id: Project identifier.
|
|
508
|
+
text: Comment text.
|
|
509
|
+
attaches: List of file attachments.
|
|
510
|
+
|
|
511
|
+
Returns:
|
|
512
|
+
Created comment.
|
|
513
|
+
|
|
514
|
+
Examples:
|
|
515
|
+
>>> comment = await client.projects.create_comment(
|
|
516
|
+
... project_id=123,
|
|
517
|
+
... text="Project update"
|
|
518
|
+
... )
|
|
519
|
+
"""
|
|
520
|
+
return await self._create_entity_comment(
|
|
521
|
+
"project",
|
|
522
|
+
project_id,
|
|
523
|
+
text,
|
|
524
|
+
attaches,
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
async def get_auditors(
|
|
528
|
+
self,
|
|
529
|
+
project_id: int,
|
|
530
|
+
limit: int | None = None,
|
|
531
|
+
page_after: dict[str, Any] | None = None,
|
|
532
|
+
page_before: dict[str, Any] | None = None,
|
|
533
|
+
page_with: dict[str, Any] | None = None,
|
|
534
|
+
) -> list[Any]:
|
|
535
|
+
"""Get auditors for a project.
|
|
536
|
+
|
|
537
|
+
Args:
|
|
538
|
+
project_id: Project identifier.
|
|
539
|
+
limit: Number of items per page.
|
|
540
|
+
page_after: Load page starting from this entity.
|
|
541
|
+
page_before: Load page strictly before this entity.
|
|
542
|
+
page_with: Load page containing this entity.
|
|
543
|
+
|
|
544
|
+
Returns:
|
|
545
|
+
List of auditors (Employees).
|
|
546
|
+
|
|
547
|
+
Examples:
|
|
548
|
+
>>> auditors = await client.projects.get_auditors(project_id=123)
|
|
549
|
+
"""
|
|
550
|
+
return await self._get_entity_related_list(
|
|
551
|
+
"project", project_id, "auditors", limit, page_after, page_before, page_with
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
async def add_auditor(
|
|
555
|
+
self,
|
|
556
|
+
project_id: int,
|
|
557
|
+
auditor_id: int,
|
|
558
|
+
auditor_content_type: str = ContentType.EMPLOYEE,
|
|
559
|
+
) -> Any:
|
|
560
|
+
"""Add auditor to the project.
|
|
561
|
+
|
|
562
|
+
Args:
|
|
563
|
+
project_id: Project identifier.
|
|
564
|
+
auditor_id: Auditor ID (usually Employee ID).
|
|
565
|
+
auditor_content_type: Content type (usually "Employee").
|
|
566
|
+
|
|
567
|
+
Returns:
|
|
568
|
+
Added auditor.
|
|
569
|
+
|
|
570
|
+
Examples:
|
|
571
|
+
>>> auditor = await client.projects.add_auditor(
|
|
572
|
+
... project_id=123,
|
|
573
|
+
... auditor_id=456
|
|
574
|
+
... )
|
|
575
|
+
"""
|
|
576
|
+
return await self._add_entity_related(
|
|
577
|
+
"project", project_id, "auditors", auditor_id, auditor_content_type
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
async def remove_auditor(
|
|
581
|
+
self,
|
|
582
|
+
project_id: int,
|
|
583
|
+
auditor_id: int,
|
|
584
|
+
auditor_content_type: str = ContentType.EMPLOYEE,
|
|
585
|
+
) -> None:
|
|
586
|
+
"""Remove auditor from the project.
|
|
587
|
+
|
|
588
|
+
Args:
|
|
589
|
+
project_id: Project identifier.
|
|
590
|
+
auditor_id: Auditor ID.
|
|
591
|
+
auditor_content_type: Content type (usually "Employee").
|
|
592
|
+
|
|
593
|
+
Examples:
|
|
594
|
+
>>> await client.projects.remove_auditor(project_id=123, auditor_id=456)
|
|
595
|
+
"""
|
|
596
|
+
await self._remove_entity_related(
|
|
597
|
+
"project", project_id, "auditors", auditor_id, auditor_content_type
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
async def get_executors(
|
|
601
|
+
self,
|
|
602
|
+
project_id: int,
|
|
603
|
+
limit: int | None = None,
|
|
604
|
+
page_after: dict[str, Any] | None = None,
|
|
605
|
+
page_before: dict[str, Any] | None = None,
|
|
606
|
+
page_with: dict[str, Any] | None = None,
|
|
607
|
+
) -> list[Any]:
|
|
608
|
+
"""Get executors (co-performers) for a project.
|
|
609
|
+
|
|
610
|
+
Args:
|
|
611
|
+
project_id: Project identifier.
|
|
612
|
+
limit: Number of items per page.
|
|
613
|
+
page_after: Load page starting from this entity.
|
|
614
|
+
page_before: Load page strictly before this entity.
|
|
615
|
+
page_with: Load page containing this entity.
|
|
616
|
+
|
|
617
|
+
Returns:
|
|
618
|
+
List of executors (Employees).
|
|
619
|
+
|
|
620
|
+
Examples:
|
|
621
|
+
>>> executors = await client.projects.get_executors(project_id=123)
|
|
622
|
+
"""
|
|
623
|
+
return await self._get_entity_related_list(
|
|
624
|
+
"project", project_id, "executors", limit, page_after, page_before, page_with
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
async def add_executor(
|
|
628
|
+
self,
|
|
629
|
+
project_id: int,
|
|
630
|
+
executor_id: int,
|
|
631
|
+
executor_content_type: str = ContentType.EMPLOYEE,
|
|
632
|
+
) -> Any:
|
|
633
|
+
"""Add executor (co-performer) to the project.
|
|
634
|
+
|
|
635
|
+
Args:
|
|
636
|
+
project_id: Project identifier.
|
|
637
|
+
executor_id: Executor ID (usually Employee ID).
|
|
638
|
+
executor_content_type: Content type (usually "Employee").
|
|
639
|
+
|
|
640
|
+
Returns:
|
|
641
|
+
Added executor.
|
|
642
|
+
|
|
643
|
+
Examples:
|
|
644
|
+
>>> executor = await client.projects.add_executor(
|
|
645
|
+
... project_id=123,
|
|
646
|
+
... executor_id=456
|
|
647
|
+
... )
|
|
648
|
+
"""
|
|
649
|
+
return await self._add_entity_related(
|
|
650
|
+
"project", project_id, "executors", executor_id, executor_content_type
|
|
651
|
+
)
|
|
652
|
+
|
|
653
|
+
async def remove_executor(
|
|
654
|
+
self,
|
|
655
|
+
project_id: int,
|
|
656
|
+
executor_id: int,
|
|
657
|
+
executor_content_type: str = ContentType.EMPLOYEE,
|
|
658
|
+
) -> None:
|
|
659
|
+
"""Remove executor from the project.
|
|
660
|
+
|
|
661
|
+
Args:
|
|
662
|
+
project_id: Project identifier.
|
|
663
|
+
executor_id: Executor ID.
|
|
664
|
+
executor_content_type: Content type (usually "Employee").
|
|
665
|
+
|
|
666
|
+
Examples:
|
|
667
|
+
>>> await client.projects.remove_executor(project_id=123, executor_id=456)
|
|
668
|
+
"""
|
|
669
|
+
await self._remove_entity_related(
|
|
670
|
+
"project", project_id, "executors", executor_id, executor_content_type
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
async def get_milestones(
|
|
674
|
+
self,
|
|
675
|
+
project_id: int,
|
|
676
|
+
limit: int | None = None,
|
|
677
|
+
page_after: dict[str, Any] | None = None,
|
|
678
|
+
page_before: dict[str, Any] | None = None,
|
|
679
|
+
page_with: dict[str, Any] | None = None,
|
|
680
|
+
) -> list[Any]:
|
|
681
|
+
"""Get milestones for a project.
|
|
682
|
+
|
|
683
|
+
Args:
|
|
684
|
+
project_id: Project identifier.
|
|
685
|
+
limit: Number of items per page.
|
|
686
|
+
page_after: Load page starting from this entity.
|
|
687
|
+
page_before: Load page strictly before this entity.
|
|
688
|
+
page_with: Load page containing this entity.
|
|
689
|
+
|
|
690
|
+
Returns:
|
|
691
|
+
List of milestones.
|
|
692
|
+
|
|
693
|
+
Examples:
|
|
694
|
+
>>> milestones = await client.projects.get_milestones(project_id=123)
|
|
695
|
+
"""
|
|
696
|
+
return await self._get_entity_related_list(
|
|
697
|
+
"project", project_id, "milestones", limit, page_after, page_before, page_with
|
|
698
|
+
)
|
|
699
|
+
|
|
700
|
+
async def add_milestone(
|
|
701
|
+
self,
|
|
702
|
+
project_id: int,
|
|
703
|
+
milestone_data: dict[str, Any],
|
|
704
|
+
) -> Any:
|
|
705
|
+
"""Add milestone to the project.
|
|
706
|
+
|
|
707
|
+
Args:
|
|
708
|
+
project_id: Project identifier.
|
|
709
|
+
milestone_data: Milestone data (name, date, etc.).
|
|
710
|
+
|
|
711
|
+
Returns:
|
|
712
|
+
Added milestone.
|
|
713
|
+
|
|
714
|
+
Examples:
|
|
715
|
+
>>> milestone = await client.projects.add_milestone(
|
|
716
|
+
... project_id=123,
|
|
717
|
+
... milestone_data={"name": "Phase 1", "date": "2026-03-15"}
|
|
718
|
+
... )
|
|
719
|
+
"""
|
|
720
|
+
return await self._add_entity_related(
|
|
721
|
+
"project", project_id, "milestones", 0, "Milestone", data_override=milestone_data
|
|
722
|
+
)
|
|
723
|
+
|
|
724
|
+
async def get_history(
|
|
725
|
+
self,
|
|
726
|
+
project_id: int,
|
|
727
|
+
limit: int | None = None,
|
|
728
|
+
page_after: dict[str, Any] | None = None,
|
|
729
|
+
page_before: dict[str, Any] | None = None,
|
|
730
|
+
page_with: dict[str, Any] | None = None,
|
|
731
|
+
) -> list[dict[str, Any]]:
|
|
732
|
+
"""Get history log for a project.
|
|
733
|
+
|
|
734
|
+
Args:
|
|
735
|
+
project_id: Project identifier.
|
|
736
|
+
limit: Number of items per page.
|
|
737
|
+
page_after: Load page starting from this entity.
|
|
738
|
+
page_before: Load page strictly before this entity.
|
|
739
|
+
page_with: Load page containing this entity.
|
|
740
|
+
|
|
741
|
+
Returns:
|
|
742
|
+
List of history entries.
|
|
743
|
+
|
|
744
|
+
Examples:
|
|
745
|
+
>>> history = await client.projects.get_history(project_id=123, limit=10)
|
|
746
|
+
"""
|
|
747
|
+
return await self._get_entity_history(
|
|
748
|
+
"project", project_id, limit, page_after, page_before, page_with
|
|
749
|
+
)
|
|
750
|
+
|
|
751
|
+
async def search_history(
|
|
752
|
+
self,
|
|
753
|
+
project_id: int,
|
|
754
|
+
query: str,
|
|
755
|
+
limit: int | None = None,
|
|
756
|
+
page_after: dict[str, Any] | None = None,
|
|
757
|
+
page_before: dict[str, Any] | None = None,
|
|
758
|
+
page_with: dict[str, Any] | None = None,
|
|
759
|
+
) -> list[dict[str, Any]]:
|
|
760
|
+
"""Search in project history log.
|
|
761
|
+
|
|
762
|
+
Args:
|
|
763
|
+
project_id: Project identifier.
|
|
764
|
+
query: Search query.
|
|
765
|
+
limit: Number of items per page.
|
|
766
|
+
page_after: Load page starting from this entity.
|
|
767
|
+
page_before: Load page strictly before this entity.
|
|
768
|
+
page_with: Load page containing this entity.
|
|
769
|
+
|
|
770
|
+
Returns:
|
|
771
|
+
List of matching history entries.
|
|
772
|
+
|
|
773
|
+
Examples:
|
|
774
|
+
>>> results = await client.projects.search_history(project_id=123, query="milestone")
|
|
775
|
+
"""
|
|
776
|
+
return await self._search_entity_history(
|
|
777
|
+
"project", project_id, query, limit, page_after, page_before, page_with
|
|
778
|
+
)
|
|
779
|
+
|
|
780
|
+
async def get_full_details(
|
|
781
|
+
self,
|
|
782
|
+
project_id: int,
|
|
783
|
+
include_deals: bool = False,
|
|
784
|
+
include_issues: bool = False,
|
|
785
|
+
include_actual_issues: bool = False,
|
|
786
|
+
include_comments: bool = False,
|
|
787
|
+
include_history: bool = False,
|
|
788
|
+
include_auditors: bool = False,
|
|
789
|
+
include_executors: bool = False,
|
|
790
|
+
include_milestones: bool = False,
|
|
791
|
+
include_responsible_details: bool = False,
|
|
792
|
+
include_owner_details: bool = False,
|
|
793
|
+
comments_limit: int | None = None,
|
|
794
|
+
history_limit: int | None = None,
|
|
795
|
+
) -> ProjectFullDetails:
|
|
796
|
+
"""Get full project details with related entities.
|
|
797
|
+
|
|
798
|
+
This method fetches the project and optionally loads related data in parallel
|
|
799
|
+
for better performance.
|
|
800
|
+
|
|
801
|
+
Args:
|
|
802
|
+
project_id: Project identifier.
|
|
803
|
+
include_deals: Load associated deals.
|
|
804
|
+
include_issues: Load tasks/issues.
|
|
805
|
+
include_actual_issues: Load actual tasks/issues.
|
|
806
|
+
include_comments: Load project comments.
|
|
807
|
+
include_history: Load change history.
|
|
808
|
+
include_auditors: Load auditors list.
|
|
809
|
+
include_executors: Load executors/co-performers list.
|
|
810
|
+
include_milestones: Load milestones list.
|
|
811
|
+
include_responsible_details: Load full responsible (Employee) details.
|
|
812
|
+
include_owner_details: Load full owner (Employee) details.
|
|
813
|
+
comments_limit: Limit for comments (if included).
|
|
814
|
+
None = use global default (from MegaplanClient) or API default.
|
|
815
|
+
Explicit value overrides global default.
|
|
816
|
+
Example: comments_limit=50 returns max 50 comments.
|
|
817
|
+
history_limit: Limit for history (if included).
|
|
818
|
+
None = use global default (from MegaplanClient) or API default.
|
|
819
|
+
Explicit value overrides global default.
|
|
820
|
+
Example: history_limit=100 returns max 100 history entries.
|
|
821
|
+
|
|
822
|
+
Returns:
|
|
823
|
+
ProjectFullDetails object with all requested data.
|
|
824
|
+
|
|
825
|
+
Examples:
|
|
826
|
+
>>> # Get project with deals and tasks
|
|
827
|
+
>>> details = await client.projects.get_full_details(
|
|
828
|
+
... project_id=123,
|
|
829
|
+
... include_deals=True,
|
|
830
|
+
... include_issues=True,
|
|
831
|
+
... include_responsible_details=True
|
|
832
|
+
... )
|
|
833
|
+
>>> print(details.project.name)
|
|
834
|
+
>>> print(len(details.deals))
|
|
835
|
+
"""
|
|
836
|
+
return await self._get_full_details_generic(
|
|
837
|
+
entity_id=project_id,
|
|
838
|
+
entity_getter="get",
|
|
839
|
+
full_details_class=ProjectFullDetails,
|
|
840
|
+
config=self._full_details_config,
|
|
841
|
+
main_entity_field="project",
|
|
842
|
+
include_deals=include_deals,
|
|
843
|
+
include_issues=include_issues,
|
|
844
|
+
include_actual_issues=include_actual_issues,
|
|
845
|
+
include_comments=include_comments,
|
|
846
|
+
include_history=include_history,
|
|
847
|
+
include_auditors=include_auditors,
|
|
848
|
+
include_executors=include_executors,
|
|
849
|
+
include_milestones=include_milestones,
|
|
850
|
+
include_responsible_details=include_responsible_details,
|
|
851
|
+
include_owner_details=include_owner_details,
|
|
852
|
+
comments_limit=comments_limit,
|
|
853
|
+
history_limit=history_limit,
|
|
854
|
+
)
|