bloomy-python 0.12.1__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.
- bloomy/__init__.py +78 -0
- bloomy/client.py +95 -0
- bloomy/configuration.py +137 -0
- bloomy/exceptions.py +27 -0
- bloomy/models.py +371 -0
- bloomy/operations/__init__.py +19 -0
- bloomy/operations/goals.py +270 -0
- bloomy/operations/headlines.py +199 -0
- bloomy/operations/issues.py +187 -0
- bloomy/operations/meetings.py +304 -0
- bloomy/operations/scorecard.py +152 -0
- bloomy/operations/todos.py +229 -0
- bloomy/operations/users.py +154 -0
- bloomy/py.typed +0 -0
- bloomy/utils/__init__.py +1 -0
- bloomy/utils/base_operations.py +43 -0
- bloomy_python-0.12.1.dist-info/METADATA +253 -0
- bloomy_python-0.12.1.dist-info/RECORD +20 -0
- bloomy_python-0.12.1.dist-info/WHEEL +4 -0
- bloomy_python-0.12.1.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"""Meeting operations for the Bloomy SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import builtins
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ..exceptions import APIError
|
|
9
|
+
from ..models import (
|
|
10
|
+
Issue,
|
|
11
|
+
MeetingAttendee,
|
|
12
|
+
MeetingDetails,
|
|
13
|
+
MeetingListItem,
|
|
14
|
+
ScorecardMetric,
|
|
15
|
+
Todo,
|
|
16
|
+
)
|
|
17
|
+
from ..utils.base_operations import BaseOperations
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class MeetingOperations(BaseOperations):
|
|
21
|
+
"""Class to handle all operations related to meetings.
|
|
22
|
+
|
|
23
|
+
Note:
|
|
24
|
+
This class is already initialized via the client and usable as
|
|
25
|
+
`client.meeting.method`
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def list(self, user_id: int | None = None) -> builtins.list[MeetingListItem]:
|
|
29
|
+
"""List all meetings for a specific user.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
user_id: The ID of the user (default is the initialized user ID)
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
A list of MeetingListItem model instances
|
|
36
|
+
|
|
37
|
+
Example:
|
|
38
|
+
```python
|
|
39
|
+
client.meeting.list()
|
|
40
|
+
# Returns: [MeetingListItem(id=123, name="Team Meeting", ...), ...]
|
|
41
|
+
```
|
|
42
|
+
"""
|
|
43
|
+
if user_id is None:
|
|
44
|
+
user_id = self.user_id
|
|
45
|
+
|
|
46
|
+
response = self._client.get(f"L10/{user_id}/list")
|
|
47
|
+
response.raise_for_status()
|
|
48
|
+
data: Any = response.json()
|
|
49
|
+
|
|
50
|
+
return [MeetingListItem.model_validate(meeting) for meeting in data]
|
|
51
|
+
|
|
52
|
+
def attendees(self, meeting_id: int) -> builtins.list[MeetingAttendee]:
|
|
53
|
+
"""List all attendees for a specific meeting.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
meeting_id: The ID of the meeting
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
A list of MeetingAttendee model instances
|
|
60
|
+
|
|
61
|
+
Example:
|
|
62
|
+
```python
|
|
63
|
+
client.meeting.attendees(1)
|
|
64
|
+
# Returns: [MeetingAttendee(user_id=1, name='John Doe',
|
|
65
|
+
# image_url='...'), ...]
|
|
66
|
+
```
|
|
67
|
+
"""
|
|
68
|
+
response = self._client.get(f"L10/{meeting_id}/attendees")
|
|
69
|
+
response.raise_for_status()
|
|
70
|
+
data: Any = response.json()
|
|
71
|
+
|
|
72
|
+
return [
|
|
73
|
+
MeetingAttendee(
|
|
74
|
+
UserId=attendee["Id"],
|
|
75
|
+
Name=attendee["Name"],
|
|
76
|
+
ImageUrl=attendee.get("ImageUrl", ""),
|
|
77
|
+
)
|
|
78
|
+
for attendee in data
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
def issues(
|
|
82
|
+
self, meeting_id: int, include_closed: bool = False
|
|
83
|
+
) -> builtins.list[Issue]:
|
|
84
|
+
"""List all issues for a specific meeting.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
meeting_id: The ID of the meeting
|
|
88
|
+
include_closed: Whether to include closed issues (default: False)
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
A list of Issue model instances
|
|
92
|
+
|
|
93
|
+
Example:
|
|
94
|
+
```python
|
|
95
|
+
client.meeting.issues(1)
|
|
96
|
+
# Returns: [Issue(id=1, name='Issue Title',
|
|
97
|
+
# created_at='2024-06-10', ...), ...]
|
|
98
|
+
```
|
|
99
|
+
"""
|
|
100
|
+
response = self._client.get(
|
|
101
|
+
f"L10/{meeting_id}/issues",
|
|
102
|
+
params={"include_resolved": include_closed},
|
|
103
|
+
)
|
|
104
|
+
response.raise_for_status()
|
|
105
|
+
data: Any = response.json()
|
|
106
|
+
|
|
107
|
+
return [
|
|
108
|
+
Issue(
|
|
109
|
+
Id=issue["Id"],
|
|
110
|
+
Name=issue["Name"],
|
|
111
|
+
DetailsUrl=issue["DetailsUrl"],
|
|
112
|
+
CreateDate=issue["CreateTime"],
|
|
113
|
+
ClosedDate=issue["CloseTime"],
|
|
114
|
+
CompletionDate=issue["CloseTime"],
|
|
115
|
+
OwnerId=issue.get("Owner", {}).get("Id", 0),
|
|
116
|
+
OwnerName=issue.get("Owner", {}).get("Name", ""),
|
|
117
|
+
OwnerImageUrl=issue.get("Owner", {}).get("ImageUrl", ""),
|
|
118
|
+
MeetingId=meeting_id,
|
|
119
|
+
MeetingName=issue["Origin"],
|
|
120
|
+
)
|
|
121
|
+
for issue in data
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
def todos(
|
|
125
|
+
self, meeting_id: int, include_closed: bool = False
|
|
126
|
+
) -> builtins.list[Todo]:
|
|
127
|
+
"""List all todos for a specific meeting.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
meeting_id: The ID of the meeting
|
|
131
|
+
include_closed: Whether to include closed todos (default: False)
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
A list of Todo model instances
|
|
135
|
+
|
|
136
|
+
Example:
|
|
137
|
+
```python
|
|
138
|
+
client.meeting.todos(1)
|
|
139
|
+
# Returns: [Todo(id=1, name='Todo Title', due_date='2024-06-12', ...), ...]
|
|
140
|
+
```
|
|
141
|
+
"""
|
|
142
|
+
response = self._client.get(
|
|
143
|
+
f"L10/{meeting_id}/todos",
|
|
144
|
+
params={"INCLUDE_CLOSED": include_closed},
|
|
145
|
+
)
|
|
146
|
+
response.raise_for_status()
|
|
147
|
+
data: Any = response.json()
|
|
148
|
+
|
|
149
|
+
return [Todo.model_validate(todo) for todo in data]
|
|
150
|
+
|
|
151
|
+
def metrics(self, meeting_id: int) -> builtins.list[ScorecardMetric]:
|
|
152
|
+
"""List all metrics for a specific meeting.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
meeting_id: The ID of the meeting
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
A list of ScorecardMetric model instances
|
|
159
|
+
|
|
160
|
+
Example:
|
|
161
|
+
```python
|
|
162
|
+
client.meeting.metrics(1)
|
|
163
|
+
# Returns: [ScorecardMetric(id=1, title='Sales', target=100.0,
|
|
164
|
+
# metric_type='>', unit='currency', ...), ...]
|
|
165
|
+
```
|
|
166
|
+
"""
|
|
167
|
+
response = self._client.get(f"L10/{meeting_id}/measurables")
|
|
168
|
+
response.raise_for_status()
|
|
169
|
+
raw_data = response.json()
|
|
170
|
+
|
|
171
|
+
if not isinstance(raw_data, list):
|
|
172
|
+
return []
|
|
173
|
+
|
|
174
|
+
metrics: list[ScorecardMetric] = []
|
|
175
|
+
# Type the list explicitly
|
|
176
|
+
data_list: list[Any] = raw_data # type: ignore[assignment]
|
|
177
|
+
for item in data_list:
|
|
178
|
+
if not isinstance(item, dict):
|
|
179
|
+
continue
|
|
180
|
+
|
|
181
|
+
# Cast to Any dict to satisfy type checker
|
|
182
|
+
item_dict: dict[str, Any] = item # type: ignore[assignment]
|
|
183
|
+
measurable_id = item_dict.get("Id")
|
|
184
|
+
measurable_name = item_dict.get("Name")
|
|
185
|
+
|
|
186
|
+
if not measurable_id or not measurable_name:
|
|
187
|
+
continue
|
|
188
|
+
|
|
189
|
+
owner_data = item_dict.get("Owner", {})
|
|
190
|
+
if not isinstance(owner_data, dict):
|
|
191
|
+
owner_data = {}
|
|
192
|
+
owner_dict: dict[str, Any] = owner_data # type: ignore[assignment]
|
|
193
|
+
|
|
194
|
+
metrics.append(
|
|
195
|
+
ScorecardMetric(
|
|
196
|
+
Id=int(measurable_id),
|
|
197
|
+
Title=str(measurable_name).strip(),
|
|
198
|
+
Target=float(item_dict.get("Target", 0)),
|
|
199
|
+
Unit=str(item_dict.get("Modifiers", "")),
|
|
200
|
+
WeekNumber=0, # Not provided in this endpoint
|
|
201
|
+
Value=None,
|
|
202
|
+
MetricType=str(item_dict.get("Direction", "")),
|
|
203
|
+
AccountableUserId=int(owner_dict.get("Id") or 0),
|
|
204
|
+
AccountableUserName=str(owner_dict.get("Name") or ""),
|
|
205
|
+
IsInverse=False,
|
|
206
|
+
)
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
return metrics
|
|
210
|
+
|
|
211
|
+
def details(self, meeting_id: int, include_closed: bool = False) -> MeetingDetails:
|
|
212
|
+
"""Retrieve details of a specific meeting.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
meeting_id: The ID of the meeting
|
|
216
|
+
include_closed: Whether to include closed issues and todos (default: False)
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
A MeetingDetails model instance with comprehensive meeting information
|
|
220
|
+
|
|
221
|
+
Example:
|
|
222
|
+
```python
|
|
223
|
+
client.meeting.details(1)
|
|
224
|
+
# Returns: MeetingDetails(id=1, name='Team Meeting', attendees=[...],
|
|
225
|
+
# issues=[...], todos=[...], metrics=[...])
|
|
226
|
+
```
|
|
227
|
+
"""
|
|
228
|
+
meetings = self.list()
|
|
229
|
+
meeting = next((m for m in meetings if m.id == meeting_id), None)
|
|
230
|
+
|
|
231
|
+
if not meeting:
|
|
232
|
+
raise APIError(f"Meeting with ID {meeting_id} not found", status_code=404)
|
|
233
|
+
|
|
234
|
+
return MeetingDetails(
|
|
235
|
+
id=meeting.id,
|
|
236
|
+
name=meeting.name,
|
|
237
|
+
start_date_utc=getattr(meeting, "start_date_utc", None),
|
|
238
|
+
created_date=getattr(meeting, "created_date", None),
|
|
239
|
+
organization_id=getattr(meeting, "organization_id", None),
|
|
240
|
+
attendees=self.attendees(meeting_id),
|
|
241
|
+
issues=self.issues(meeting_id, include_closed=include_closed),
|
|
242
|
+
todos=self.todos(meeting_id, include_closed=include_closed),
|
|
243
|
+
metrics=self.metrics(meeting_id),
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
def create(
|
|
247
|
+
self,
|
|
248
|
+
title: str,
|
|
249
|
+
add_self: bool = True,
|
|
250
|
+
attendees: builtins.list[int] | None = None,
|
|
251
|
+
) -> dict[str, Any]:
|
|
252
|
+
"""Create a new meeting.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
title: The title of the new meeting
|
|
256
|
+
add_self: Whether to add the current user as an attendee (default: True)
|
|
257
|
+
attendees: A list of user IDs to add as attendees
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
A dictionary containing meeting_id, title and attendees array
|
|
261
|
+
|
|
262
|
+
Example:
|
|
263
|
+
```python
|
|
264
|
+
client.meeting.create("New Meeting", attendees=[2, 3])
|
|
265
|
+
# Returns: {"meeting_id": 1, "title": "New Meeting", "attendees": [2, 3]}
|
|
266
|
+
```
|
|
267
|
+
"""
|
|
268
|
+
if attendees is None:
|
|
269
|
+
attendees = []
|
|
270
|
+
|
|
271
|
+
payload = {"title": title, "addSelf": add_self}
|
|
272
|
+
response = self._client.post("L10/create", json=payload)
|
|
273
|
+
response.raise_for_status()
|
|
274
|
+
data: Any = response.json()
|
|
275
|
+
|
|
276
|
+
meeting_id = data["meetingId"]
|
|
277
|
+
|
|
278
|
+
# Add attendees
|
|
279
|
+
for attendee_id in attendees:
|
|
280
|
+
attendee_response = self._client.post(
|
|
281
|
+
f"L10/{meeting_id}/attendees/{attendee_id}"
|
|
282
|
+
)
|
|
283
|
+
attendee_response.raise_for_status()
|
|
284
|
+
|
|
285
|
+
return {"meeting_id": meeting_id, "title": title, "attendees": attendees}
|
|
286
|
+
|
|
287
|
+
def delete(self, meeting_id: int) -> bool:
|
|
288
|
+
"""Delete a meeting.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
meeting_id: The ID of the meeting to delete
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
True if deletion was successful
|
|
295
|
+
|
|
296
|
+
Example:
|
|
297
|
+
```python
|
|
298
|
+
client.meeting.delete(1)
|
|
299
|
+
# Returns: True
|
|
300
|
+
```
|
|
301
|
+
"""
|
|
302
|
+
response = self._client.delete(f"L10/{meeting_id}")
|
|
303
|
+
response.raise_for_status()
|
|
304
|
+
return True
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""Scorecard operations for the Bloomy SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import builtins
|
|
6
|
+
|
|
7
|
+
from ..models import ScorecardItem, ScorecardWeek
|
|
8
|
+
from ..utils.base_operations import BaseOperations
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ScorecardOperations(BaseOperations):
|
|
12
|
+
"""Class to handle all operations related to scorecards.
|
|
13
|
+
|
|
14
|
+
Note:
|
|
15
|
+
This class is already initialized via the client and usable as
|
|
16
|
+
`client.scorecard.method`
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def current_week(self) -> ScorecardWeek:
|
|
20
|
+
"""Retrieve the current week details.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
A ScorecardWeek model instance containing current week details
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
```python
|
|
27
|
+
client.scorecard.current_week()
|
|
28
|
+
# Returns: ScorecardWeek(id=123, week_number=24, week_start='2024-06-10',
|
|
29
|
+
# week_end='2024-06-16')
|
|
30
|
+
```
|
|
31
|
+
"""
|
|
32
|
+
response = self._client.get("weeks/current")
|
|
33
|
+
response.raise_for_status()
|
|
34
|
+
data = response.json()
|
|
35
|
+
|
|
36
|
+
return ScorecardWeek(
|
|
37
|
+
id=data["Id"],
|
|
38
|
+
week_number=data["ForWeekNumber"],
|
|
39
|
+
week_start=data["LocalDate"]["Date"],
|
|
40
|
+
week_end=data["ForWeek"],
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
def list(
|
|
44
|
+
self,
|
|
45
|
+
user_id: int | None = None,
|
|
46
|
+
meeting_id: int | None = None,
|
|
47
|
+
show_empty: bool = False,
|
|
48
|
+
week_offset: int | None = None,
|
|
49
|
+
) -> builtins.list[ScorecardItem]:
|
|
50
|
+
"""Retrieve the scorecards for a user or a meeting.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
user_id: The ID of the user (defaults to initialized user_id)
|
|
54
|
+
meeting_id: The ID of the meeting
|
|
55
|
+
show_empty: Whether to include scores with None values (default: False)
|
|
56
|
+
week_offset: Offset for the week number to filter scores
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
A list of ScorecardItem model instances
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
ValueError: If both user_id and meeting_id are provided
|
|
63
|
+
|
|
64
|
+
Example:
|
|
65
|
+
```python
|
|
66
|
+
# Fetch scorecards for the current user
|
|
67
|
+
client.scorecard.list()
|
|
68
|
+
|
|
69
|
+
# Fetch scorecards for a specific user
|
|
70
|
+
client.scorecard.list(user_id=42)
|
|
71
|
+
|
|
72
|
+
# Fetch scorecards for a specific meeting
|
|
73
|
+
client.scorecard.list(meeting_id=99)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Note:
|
|
77
|
+
The week_offset parameter is useful when fetching scores for
|
|
78
|
+
previous or future weeks.
|
|
79
|
+
For example, to fetch scores for the previous week, you can set
|
|
80
|
+
week_offset to -1.
|
|
81
|
+
To fetch scores for a future week, you can set week_offset to a
|
|
82
|
+
positive value.
|
|
83
|
+
"""
|
|
84
|
+
if user_id and meeting_id:
|
|
85
|
+
raise ValueError(
|
|
86
|
+
"Please provide either `user_id` or `meeting_id`, not both."
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if meeting_id:
|
|
90
|
+
response = self._client.get(f"scorecard/meeting/{meeting_id}")
|
|
91
|
+
else:
|
|
92
|
+
if user_id is None:
|
|
93
|
+
user_id = self.user_id
|
|
94
|
+
response = self._client.get(f"scorecard/user/{user_id}")
|
|
95
|
+
|
|
96
|
+
response.raise_for_status()
|
|
97
|
+
data = response.json()
|
|
98
|
+
|
|
99
|
+
scorecards: list[ScorecardItem] = [
|
|
100
|
+
ScorecardItem(
|
|
101
|
+
id=scorecard["Id"],
|
|
102
|
+
measurable_id=scorecard["MeasurableId"],
|
|
103
|
+
accountable_user_id=scorecard["AccountableUserId"],
|
|
104
|
+
title=scorecard["MeasurableName"],
|
|
105
|
+
target=scorecard["Target"],
|
|
106
|
+
value=scorecard["Measured"],
|
|
107
|
+
week=scorecard["Week"],
|
|
108
|
+
week_id=scorecard["ForWeek"],
|
|
109
|
+
updated_at=scorecard["DateEntered"],
|
|
110
|
+
)
|
|
111
|
+
for scorecard in data["Scores"]
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
# Filter by week offset if provided
|
|
115
|
+
if week_offset is not None:
|
|
116
|
+
week_data = self.current_week()
|
|
117
|
+
target_week_id = week_data.week_number + week_offset
|
|
118
|
+
scorecards = [s for s in scorecards if s.week_id == target_week_id]
|
|
119
|
+
|
|
120
|
+
# Filter out empty values unless show_empty is True
|
|
121
|
+
if not show_empty:
|
|
122
|
+
scorecards = [s for s in scorecards if s.value is not None]
|
|
123
|
+
|
|
124
|
+
return scorecards
|
|
125
|
+
|
|
126
|
+
def score(self, measurable_id: int, score: float, week_offset: int = 0) -> bool:
|
|
127
|
+
"""Update the score for a measurable item for a specific week.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
measurable_id: The ID of the measurable item
|
|
131
|
+
score: The score to be assigned to the measurable item
|
|
132
|
+
week_offset: The number of weeks to offset from the current week
|
|
133
|
+
(default: 0)
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
True if the score was successfully updated
|
|
137
|
+
|
|
138
|
+
Example:
|
|
139
|
+
```python
|
|
140
|
+
client.scorecard.score(measurable_id=123, score=5)
|
|
141
|
+
# Returns: True
|
|
142
|
+
```
|
|
143
|
+
"""
|
|
144
|
+
week_data = self.current_week()
|
|
145
|
+
week_id = week_data.week_number + week_offset
|
|
146
|
+
|
|
147
|
+
response = self._client.put(
|
|
148
|
+
f"measurables/{measurable_id}/week/{week_id}",
|
|
149
|
+
json={"value": score},
|
|
150
|
+
)
|
|
151
|
+
response.raise_for_status()
|
|
152
|
+
return response.is_success
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""Todo operations for the Bloomy SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import builtins
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from ..models import Todo
|
|
10
|
+
from ..utils.base_operations import BaseOperations
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TodoOperations(BaseOperations):
|
|
17
|
+
"""Class to handle all operations related to todos.
|
|
18
|
+
|
|
19
|
+
Note:
|
|
20
|
+
This class is already initialized via the client and usable as
|
|
21
|
+
`client.todo.method`
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def list(
|
|
25
|
+
self, user_id: int | None = None, meeting_id: int | None = None
|
|
26
|
+
) -> builtins.list[Todo]:
|
|
27
|
+
"""List all todos for a specific user or meeting.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
user_id: The ID of the user (default is the initialized user ID)
|
|
31
|
+
meeting_id: The ID of the meeting
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
A list of Todo model instances
|
|
35
|
+
|
|
36
|
+
Raises:
|
|
37
|
+
ValueError: If both user_id and meeting_id are provided
|
|
38
|
+
|
|
39
|
+
Example:
|
|
40
|
+
```python
|
|
41
|
+
# Fetch todos for the current user
|
|
42
|
+
client.todo.list()
|
|
43
|
+
# Returns: [Todo(id=1, name='New Todo', due_date='2024-06-15', ...)]
|
|
44
|
+
```
|
|
45
|
+
"""
|
|
46
|
+
if user_id is not None and meeting_id is not None:
|
|
47
|
+
raise ValueError(
|
|
48
|
+
"Please provide either `user_id` or `meeting_id`, not both."
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
if meeting_id is not None:
|
|
52
|
+
response = self._client.get(f"l10/{meeting_id}/todos")
|
|
53
|
+
else:
|
|
54
|
+
if user_id is None:
|
|
55
|
+
user_id = self.user_id
|
|
56
|
+
response = self._client.get(f"todo/user/{user_id}")
|
|
57
|
+
|
|
58
|
+
response.raise_for_status()
|
|
59
|
+
data = response.json()
|
|
60
|
+
|
|
61
|
+
return [Todo.model_validate(todo) for todo in data]
|
|
62
|
+
|
|
63
|
+
def create(
|
|
64
|
+
self,
|
|
65
|
+
title: str,
|
|
66
|
+
meeting_id: int,
|
|
67
|
+
due_date: str | None = None,
|
|
68
|
+
user_id: int | None = None,
|
|
69
|
+
notes: str | None = None,
|
|
70
|
+
) -> Todo:
|
|
71
|
+
"""Create a new todo.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
title: The title of the new todo
|
|
75
|
+
meeting_id: The ID of the meeting associated with the todo
|
|
76
|
+
due_date: The due date of the todo (optional)
|
|
77
|
+
user_id: The ID of the user responsible for the todo
|
|
78
|
+
(default: initialized user ID)
|
|
79
|
+
notes: Additional notes for the todo (optional)
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
A Todo model instance representing the newly created todo
|
|
83
|
+
|
|
84
|
+
Example:
|
|
85
|
+
```python
|
|
86
|
+
client.todo.create(
|
|
87
|
+
title="New Todo", meeting_id=1, due_date="2024-06-15"
|
|
88
|
+
)
|
|
89
|
+
# Returns: Todo(id=1, name='New Todo', due_date='2024-06-15', ...)
|
|
90
|
+
```
|
|
91
|
+
"""
|
|
92
|
+
if user_id is None:
|
|
93
|
+
user_id = self.user_id
|
|
94
|
+
|
|
95
|
+
payload: dict[str, Any] = {
|
|
96
|
+
"title": title,
|
|
97
|
+
"accountableUserId": user_id,
|
|
98
|
+
"notes": notes,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if due_date is not None:
|
|
102
|
+
payload["dueDate"] = due_date
|
|
103
|
+
|
|
104
|
+
response = self._client.post(f"L10/{meeting_id}/todos", json=payload)
|
|
105
|
+
response.raise_for_status()
|
|
106
|
+
data = response.json()
|
|
107
|
+
|
|
108
|
+
# Add default values for fields that might be missing in create response
|
|
109
|
+
todo_data = {
|
|
110
|
+
"Id": data["Id"],
|
|
111
|
+
"Name": data["Name"],
|
|
112
|
+
"DetailsUrl": data.get("DetailsUrl"),
|
|
113
|
+
"DueDate": data.get("DueDate"),
|
|
114
|
+
"CompleteTime": None,
|
|
115
|
+
"CreateTime": datetime.now().isoformat(),
|
|
116
|
+
"OriginId": meeting_id,
|
|
117
|
+
"Origin": None,
|
|
118
|
+
"Complete": False,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return Todo.model_validate(todo_data)
|
|
122
|
+
|
|
123
|
+
def complete(self, todo_id: int) -> bool:
|
|
124
|
+
"""Mark a todo as complete.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
todo_id: The ID of the todo to complete
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
True if the operation was successful
|
|
131
|
+
|
|
132
|
+
Example:
|
|
133
|
+
```python
|
|
134
|
+
client.todo.complete(1)
|
|
135
|
+
# Returns: True
|
|
136
|
+
```
|
|
137
|
+
"""
|
|
138
|
+
response = self._client.post(f"todo/{todo_id}/complete?status=true")
|
|
139
|
+
response.raise_for_status()
|
|
140
|
+
return response.is_success
|
|
141
|
+
|
|
142
|
+
def update(
|
|
143
|
+
self,
|
|
144
|
+
todo_id: int,
|
|
145
|
+
title: str | None = None,
|
|
146
|
+
due_date: str | None = None,
|
|
147
|
+
) -> Todo:
|
|
148
|
+
"""Update an existing todo.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
todo_id: The ID of the todo to update
|
|
152
|
+
title: The new title of the todo (optional)
|
|
153
|
+
due_date: The new due date of the todo (optional)
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
A Todo model instance containing the updated todo details
|
|
157
|
+
|
|
158
|
+
Raises:
|
|
159
|
+
ValueError: If no update fields are provided
|
|
160
|
+
RuntimeError: If the update request fails
|
|
161
|
+
|
|
162
|
+
Example:
|
|
163
|
+
```python
|
|
164
|
+
client.todo.update(
|
|
165
|
+
todo_id=1, title="Updated Todo", due_date="2024-11-01"
|
|
166
|
+
)
|
|
167
|
+
# Returns: Todo(id=1, name='Updated Todo', due_date='2024-11-01', ...)
|
|
168
|
+
```
|
|
169
|
+
"""
|
|
170
|
+
payload: dict[str, Any] = {}
|
|
171
|
+
|
|
172
|
+
if title is not None:
|
|
173
|
+
payload["title"] = title
|
|
174
|
+
|
|
175
|
+
if due_date is not None:
|
|
176
|
+
payload["dueDate"] = due_date
|
|
177
|
+
|
|
178
|
+
if not payload:
|
|
179
|
+
raise ValueError("At least one field must be provided")
|
|
180
|
+
|
|
181
|
+
response = self._client.put(f"todo/{todo_id}", json=payload)
|
|
182
|
+
|
|
183
|
+
if response.status_code != 200:
|
|
184
|
+
raise RuntimeError(f"Failed to update todo. Status: {response.status_code}")
|
|
185
|
+
|
|
186
|
+
# Construct todo data for validation
|
|
187
|
+
todo_data = {
|
|
188
|
+
"Id": todo_id,
|
|
189
|
+
"Name": title or "",
|
|
190
|
+
"DetailsUrl": "",
|
|
191
|
+
"DueDate": due_date,
|
|
192
|
+
"CompleteTime": None,
|
|
193
|
+
"CreateTime": datetime.now().isoformat(),
|
|
194
|
+
"OriginId": None,
|
|
195
|
+
"Origin": None,
|
|
196
|
+
"Complete": False,
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return Todo.model_validate(todo_data)
|
|
200
|
+
|
|
201
|
+
def details(self, todo_id: int) -> Todo:
|
|
202
|
+
"""Retrieve the details of a specific todo item by its ID.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
todo_id: The ID of the todo item to retrieve
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
A Todo model instance containing the todo details
|
|
209
|
+
|
|
210
|
+
Raises:
|
|
211
|
+
RuntimeError: If the request to retrieve the todo details fails
|
|
212
|
+
|
|
213
|
+
Example:
|
|
214
|
+
```python
|
|
215
|
+
client.todo.details(1)
|
|
216
|
+
# Returns: Todo(id=1, name='Updated Todo', due_date='2024-11-01', ...)
|
|
217
|
+
```
|
|
218
|
+
"""
|
|
219
|
+
response = self._client.get(f"todo/{todo_id}")
|
|
220
|
+
|
|
221
|
+
if not response.is_success:
|
|
222
|
+
raise RuntimeError(
|
|
223
|
+
f"Failed to get todo details. Status: {response.status_code}"
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
response.raise_for_status()
|
|
227
|
+
todo = response.json()
|
|
228
|
+
|
|
229
|
+
return Todo.model_validate(todo)
|