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.
@@ -0,0 +1,19 @@
1
+ """API operations for the Bloomy SDK."""
2
+
3
+ from .goals import GoalOperations
4
+ from .headlines import HeadlineOperations
5
+ from .issues import IssueOperations
6
+ from .meetings import MeetingOperations
7
+ from .scorecard import ScorecardOperations
8
+ from .todos import TodoOperations
9
+ from .users import UserOperations
10
+
11
+ __all__ = [
12
+ "GoalOperations",
13
+ "HeadlineOperations",
14
+ "IssueOperations",
15
+ "MeetingOperations",
16
+ "ScorecardOperations",
17
+ "TodoOperations",
18
+ "UserOperations",
19
+ ]
@@ -0,0 +1,270 @@
1
+ """Goal operations for the Bloomy SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import builtins
6
+ from typing import TYPE_CHECKING
7
+
8
+ from ..models import ArchivedGoalInfo, CreatedGoalInfo, GoalInfo, GoalListResponse
9
+ from ..utils.base_operations import BaseOperations
10
+
11
+ if TYPE_CHECKING:
12
+ from typing import Any
13
+
14
+
15
+ class GoalOperations(BaseOperations):
16
+ """Class to handle all the operations related to goals (also known as "rocks").
17
+
18
+ Note:
19
+ This class is already initialized via the client and usable as
20
+ `client.goal.method`
21
+ """
22
+
23
+ def list(
24
+ self, user_id: int | None = None, archived: bool = False
25
+ ) -> builtins.list[GoalInfo] | GoalListResponse:
26
+ """List all goals for a specific user.
27
+
28
+ Args:
29
+ user_id: The ID of the user (default is the initialized user ID)
30
+ archived: Whether to include archived goals (default: False)
31
+
32
+ Returns:
33
+ Either:
34
+ - A list of GoalInfo model instances if archived is false
35
+ - A GoalListResponse model with 'active' and 'archived' lists of
36
+ GoalInfo instances if archived is true
37
+
38
+ Examples:
39
+ List active goals:
40
+ ```python
41
+ client.goal.list()
42
+ # Returns: [GoalInfo(id=1, title='Complete project', ...)]
43
+ ```
44
+
45
+ List both active and archived goals:
46
+ ```python
47
+ client.goal.list(archived=True)
48
+ # Returns: GoalListResponse(
49
+ # active=[GoalInfo(id=1, ...)],
50
+ # archived=[ArchivedGoalInfo(id=2, ...)]
51
+ # )
52
+ ```
53
+ """
54
+ if user_id is None:
55
+ user_id = self.user_id
56
+
57
+ response = self._client.get(
58
+ f"rocks/user/{user_id}", params={"include_origin": True}
59
+ )
60
+ response.raise_for_status()
61
+ data = response.json()
62
+
63
+ active_goals: list[GoalInfo] = []
64
+ for goal in data:
65
+ active_goals.append(
66
+ GoalInfo(
67
+ id=goal["Id"],
68
+ user_id=goal["Owner"]["Id"],
69
+ user_name=goal["Owner"]["Name"],
70
+ title=goal["Name"],
71
+ created_at=goal["CreateTime"],
72
+ due_date=goal["DueDate"],
73
+ status="Completed" if goal.get("Complete") else "Incomplete",
74
+ meeting_id=goal["Origins"][0]["Id"]
75
+ if goal.get("Origins")
76
+ else None,
77
+ meeting_title=goal["Origins"][0]["Name"]
78
+ if goal.get("Origins")
79
+ else None,
80
+ )
81
+ )
82
+
83
+ if archived:
84
+ archived_goals = self._get_archived_goals(user_id)
85
+ return GoalListResponse(active=active_goals, archived=archived_goals)
86
+
87
+ return active_goals
88
+
89
+ def create(
90
+ self, title: str, meeting_id: int, user_id: int | None = None
91
+ ) -> CreatedGoalInfo:
92
+ """Create a new goal.
93
+
94
+ Args:
95
+ title: The title of the new goal
96
+ meeting_id: The ID of the meeting associated with the goal
97
+ user_id: The ID of the user responsible for the goal (default:
98
+ initialized user ID)
99
+
100
+ Returns:
101
+ A CreatedGoalInfo model instance representing the newly created goal
102
+
103
+ Example:
104
+ ```python
105
+ client.goal.create(title="New Goal", meeting_id=1)
106
+ # Returns: CreatedGoalInfo(id=1, title='New Goal', meeting_id=1, ...)
107
+ ```
108
+ """
109
+ if user_id is None:
110
+ user_id = self.user_id
111
+
112
+ payload = {"title": title, "accountableUserId": user_id}
113
+ response = self._client.post(f"L10/{meeting_id}/rocks", json=payload)
114
+ response.raise_for_status()
115
+ data = response.json()
116
+
117
+ # Map completion status
118
+ completion_map = {2: "complete", 1: "on", 0: "off"}
119
+ status = completion_map.get(data.get("Completion", 0), "off")
120
+
121
+ return CreatedGoalInfo(
122
+ id=data["Id"],
123
+ user_id=user_id,
124
+ user_name=data["Owner"]["Name"],
125
+ title=title,
126
+ meeting_id=meeting_id,
127
+ meeting_title=data["Origins"][0]["Name"],
128
+ status=status,
129
+ created_at=data["CreateTime"],
130
+ )
131
+
132
+ def delete(self, goal_id: int) -> bool:
133
+ """Delete a goal.
134
+
135
+ Args:
136
+ goal_id: The ID of the goal to delete
137
+
138
+ Returns:
139
+ True if deletion was successful
140
+
141
+ Example:
142
+ ```python
143
+ client.goal.delete(1)
144
+ # Returns: True
145
+ ```
146
+ """
147
+ response = self._client.delete(f"rocks/{goal_id}")
148
+ response.raise_for_status()
149
+ return True
150
+
151
+ def update(
152
+ self,
153
+ goal_id: int,
154
+ title: str | None = None,
155
+ accountable_user: int | None = None,
156
+ status: str | None = None,
157
+ ) -> bool:
158
+ """Update a goal.
159
+
160
+ Args:
161
+ goal_id: The ID of the goal to update
162
+ title: The new title of the goal
163
+ accountable_user: The ID of the user responsible for the goal
164
+ (default: initialized user ID)
165
+ status: The status value ('on', 'off', or 'complete')
166
+
167
+ Returns:
168
+ True if the update was successful
169
+
170
+ Raises:
171
+ ValueError: If an invalid status value is provided
172
+
173
+ Example:
174
+ ```python
175
+ client.goal.update(goal_id=1, title="Updated Goal", status='on')
176
+ # Returns: True
177
+ ```
178
+ """
179
+ if accountable_user is None:
180
+ accountable_user = self.user_id
181
+
182
+ payload: dict[str, Any] = {"accountableUserId": accountable_user}
183
+
184
+ if title is not None:
185
+ payload["title"] = title
186
+
187
+ if status is not None:
188
+ valid_status = {"on": "OnTrack", "off": "AtRisk", "complete": "Complete"}
189
+ status_key = status.lower()
190
+ if status_key not in valid_status:
191
+ raise ValueError(
192
+ "Invalid status value. Must be 'on', 'off', or 'complete'."
193
+ )
194
+ payload["completion"] = valid_status[status_key]
195
+
196
+ response = self._client.put(f"rocks/{goal_id}", json=payload)
197
+ response.raise_for_status()
198
+ return True
199
+
200
+ def archive(self, goal_id: int) -> bool:
201
+ """Archive a rock with the specified goal ID.
202
+
203
+ Args:
204
+ goal_id: The ID of the goal/rock to archive
205
+
206
+ Returns:
207
+ True if the archival was successful, False otherwise
208
+
209
+ Example:
210
+ ```python
211
+ goals.archive(123)
212
+ # Returns: True
213
+ ```
214
+ """
215
+ response = self._client.put(f"rocks/{goal_id}/archive")
216
+ response.raise_for_status()
217
+ return True
218
+
219
+ def restore(self, goal_id: int) -> bool:
220
+ """Restore a previously archived goal identified by the provided goal ID.
221
+
222
+ Args:
223
+ goal_id: The unique identifier of the goal to restore
224
+
225
+ Returns:
226
+ True if the restore operation was successful, False otherwise
227
+
228
+ Example:
229
+ ```python
230
+ goals.restore(123)
231
+ # Returns: True
232
+ ```
233
+ """
234
+ response = self._client.put(f"rocks/{goal_id}/restore")
235
+ response.raise_for_status()
236
+ return True
237
+
238
+ def _get_archived_goals(self, user_id: int | None = None) -> list[ArchivedGoalInfo]:
239
+ """Retrieve all archived goals for a specific user (private method).
240
+
241
+ Args:
242
+ user_id: The ID of the user (default is the initialized user ID)
243
+
244
+ Returns:
245
+ A list of ArchivedGoalInfo model instances containing archived goal details
246
+
247
+ Example:
248
+ ```python
249
+ goal._get_archived_goals()
250
+ # Returns: [ArchivedGoalInfo(id=1, title='Archived Goal',
251
+ # created_at='2024-06-10', ...), ...]
252
+ ```
253
+ """
254
+ if user_id is None:
255
+ user_id = self.user_id
256
+
257
+ response = self._client.get(f"archivedrocks/user/{user_id}")
258
+ response.raise_for_status()
259
+ data = response.json()
260
+
261
+ return [
262
+ ArchivedGoalInfo(
263
+ id=goal["Id"],
264
+ title=goal["Name"],
265
+ created_at=goal["CreateTime"],
266
+ due_date=goal["DueDate"],
267
+ status="Complete" if goal.get("Complete") else "Incomplete",
268
+ )
269
+ for goal in data
270
+ ]
@@ -0,0 +1,199 @@
1
+ """Headline operations for the Bloomy SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import builtins
6
+
7
+ from ..models import (
8
+ HeadlineDetails,
9
+ HeadlineInfo,
10
+ HeadlineListItem,
11
+ MeetingInfo,
12
+ OwnerDetails,
13
+ )
14
+ from ..utils.base_operations import BaseOperations
15
+
16
+
17
+ class HeadlineOperations(BaseOperations):
18
+ """Class to handle all operations related to headlines."""
19
+
20
+ def create(
21
+ self,
22
+ meeting_id: int,
23
+ title: str,
24
+ owner_id: int | None = None,
25
+ notes: str | None = None,
26
+ ) -> HeadlineInfo:
27
+ """Create a new headline.
28
+
29
+ Args:
30
+ meeting_id: The ID of the meeting
31
+ title: The title of the headline
32
+ owner_id: The ID of the headline owner (default: current user ID)
33
+ notes: Additional notes for the headline
34
+
35
+ Returns:
36
+ A HeadlineInfo model instance containing id, title, owner_details,
37
+ and notes_url
38
+
39
+ Raises:
40
+ httpx.HTTPStatusError: If the API request fails
41
+ """
42
+ if owner_id is None:
43
+ owner_id = self.user_id
44
+
45
+ payload = {"title": title, "ownerId": owner_id}
46
+ if notes is not None:
47
+ payload["notes"] = notes
48
+
49
+ response = self._client.post(f"L10/{meeting_id}/headlines", json=payload)
50
+ response.raise_for_status()
51
+ data = response.json()
52
+
53
+ return HeadlineInfo(
54
+ id=data["Id"],
55
+ title=data["Name"],
56
+ owner_details=OwnerDetails(id=owner_id, name=None),
57
+ notes_url=data.get("DetailsUrl", ""),
58
+ )
59
+
60
+ def update(self, headline_id: int, title: str) -> bool:
61
+ """Update a headline.
62
+
63
+ Args:
64
+ headline_id: The ID of the headline to update
65
+ title: The new title of the headline
66
+
67
+ Returns:
68
+ True if update was successful
69
+
70
+ Raises:
71
+ httpx.HTTPStatusError: If the API request fails
72
+ """
73
+ payload = {"title": title}
74
+ response = self._client.put(f"headline/{headline_id}", json=payload)
75
+ response.raise_for_status()
76
+ return True
77
+
78
+ def details(self, headline_id: int) -> HeadlineDetails:
79
+ """Get headline details.
80
+
81
+ Args:
82
+ headline_id: The ID of the headline
83
+
84
+ Returns:
85
+ A HeadlineDetails model instance containing id, title, notes_url,
86
+ meeting_details, owner_details, archived, created_at, and closed_at
87
+
88
+ Raises:
89
+ httpx.HTTPStatusError: If the API request fails
90
+ """
91
+ response = self._client.get(
92
+ f"headline/{headline_id}", params={"Include_Origin": "true"}
93
+ )
94
+ response.raise_for_status()
95
+ data = response.json()
96
+
97
+ return HeadlineDetails(
98
+ id=data["Id"],
99
+ title=data["Name"],
100
+ notes_url=data["DetailsUrl"],
101
+ meeting_details=MeetingInfo(
102
+ id=data["OriginId"],
103
+ title=data["Origin"],
104
+ ),
105
+ owner_details=OwnerDetails(
106
+ id=data["Owner"]["Id"],
107
+ name=data["Owner"]["Name"],
108
+ ),
109
+ archived=data["Archived"],
110
+ created_at=data["CreateTime"],
111
+ closed_at=data["CloseTime"],
112
+ )
113
+
114
+ def list(
115
+ self, user_id: int | None = None, meeting_id: int | None = None
116
+ ) -> builtins.list[HeadlineListItem]:
117
+ """Get headlines for a user or a meeting.
118
+
119
+ Args:
120
+ user_id: The ID of the user (defaults to initialized user_id)
121
+ meeting_id: The ID of the meeting
122
+
123
+ Returns:
124
+ A list of HeadlineListItem model instances containing:
125
+ - id
126
+ - title
127
+ - meeting_details
128
+ - owner_details
129
+ - archived
130
+ - created_at
131
+ - closed_at
132
+
133
+ Raises:
134
+ ValueError: If both user_id and meeting_id are provided
135
+ httpx.HTTPStatusError: If the API request fails
136
+
137
+ Example:
138
+ ```python
139
+ client.headline.list()
140
+ # Returns: [
141
+ # HeadlineListItem(
142
+ # id=1,
143
+ # title='Headline Title',
144
+ # meeting_details=MeetingInfo(id=1, title='Team Meeting'),
145
+ # owner_details=OwnerDetails(id=1, name='John Doe'),
146
+ # archived=False,
147
+ # created_at='2023-01-01',
148
+ # closed_at=None
149
+ # )
150
+ # ]
151
+ ```
152
+ """
153
+ if user_id and meeting_id:
154
+ raise ValueError("Please provide either user_id or meeting_id, not both.")
155
+
156
+ if meeting_id:
157
+ response = self._client.get(f"l10/{meeting_id}/headlines")
158
+ else:
159
+ if user_id is None:
160
+ user_id = self.user_id
161
+ response = self._client.get(f"headline/users/{user_id}")
162
+
163
+ response.raise_for_status()
164
+ data = response.json()
165
+
166
+ return [
167
+ HeadlineListItem(
168
+ id=headline["Id"],
169
+ title=headline["Name"],
170
+ meeting_details=MeetingInfo(
171
+ id=headline["OriginId"],
172
+ title=headline["Origin"],
173
+ ),
174
+ owner_details=OwnerDetails(
175
+ id=headline["Owner"]["Id"],
176
+ name=headline["Owner"]["Name"],
177
+ ),
178
+ archived=headline["Archived"],
179
+ created_at=headline["CreateTime"],
180
+ closed_at=headline["CloseTime"],
181
+ )
182
+ for headline in data
183
+ ]
184
+
185
+ def delete(self, headline_id: int) -> bool:
186
+ """Delete a headline.
187
+
188
+ Args:
189
+ headline_id: The ID of the headline to delete
190
+
191
+ Returns:
192
+ True if the deletion was successful
193
+
194
+ Raises:
195
+ httpx.HTTPStatusError: If the API request fails
196
+ """
197
+ response = self._client.delete(f"headline/{headline_id}")
198
+ response.raise_for_status()
199
+ return True
@@ -0,0 +1,187 @@
1
+ """Issue operations for the Bloomy SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import builtins
6
+
7
+ from ..models import CreatedIssue, IssueDetails, IssueListItem
8
+ from ..utils.base_operations import BaseOperations
9
+
10
+
11
+ class IssueOperations(BaseOperations):
12
+ """Class to handle all operations related to issues.
13
+
14
+ Provides functionality to create, retrieve, list, and solve issues
15
+ associated with meetings and users.
16
+ """
17
+
18
+ def details(self, issue_id: int) -> IssueDetails:
19
+ """Retrieve detailed information about a specific issue.
20
+
21
+ Args:
22
+ issue_id: Unique identifier of the issue
23
+
24
+ Returns:
25
+ An IssueDetails model instance containing detailed information
26
+ about the issue
27
+
28
+ Raises:
29
+ httpx.HTTPError: When the API request fails or returns invalid data
30
+
31
+ Example:
32
+ ```python
33
+ client.issue.details(123)
34
+ # Returns: IssueDetails(id=123, title='Issue Title',
35
+ # created_at='2024-06-10', ...)
36
+ ```
37
+ """
38
+ response = self._client.get(f"issues/{issue_id}")
39
+ response.raise_for_status()
40
+ data = response.json()
41
+
42
+ return IssueDetails(
43
+ id=data["Id"],
44
+ title=data["Name"],
45
+ notes_url=data["DetailsUrl"],
46
+ created_at=data["CreateTime"],
47
+ completed_at=data["CloseTime"],
48
+ meeting_id=data["OriginId"],
49
+ meeting_title=data["Origin"],
50
+ user_id=data["Owner"]["Id"],
51
+ user_name=data["Owner"]["Name"],
52
+ )
53
+
54
+ def list(
55
+ self, user_id: int | None = None, meeting_id: int | None = None
56
+ ) -> builtins.list[IssueListItem]:
57
+ """List issues filtered by user or meeting.
58
+
59
+ Args:
60
+ user_id: Unique identifier of the user (optional)
61
+ meeting_id: Unique identifier of the meeting (optional)
62
+
63
+ Returns:
64
+ A list of IssueListItem model instances matching the filter criteria
65
+
66
+ Raises:
67
+ ValueError: When both user_id and meeting_id are provided
68
+ httpx.HTTPError: When the API request fails or returns invalid data
69
+
70
+ Example:
71
+ ```python
72
+ # List issues for current user
73
+ client.issue.list()
74
+ # Returns: [IssueListItem(id=1, title='Issue 1', ...), ...]
75
+
76
+ # List issues for specific meeting
77
+ client.issue.list(meeting_id=456)
78
+ # Returns: [IssueListItem(id=2, title='Issue 2', ...), ...]
79
+ ```
80
+ """
81
+ if user_id and meeting_id:
82
+ raise ValueError(
83
+ "Please provide either `user_id` or `meeting_id`, not both."
84
+ )
85
+
86
+ if meeting_id:
87
+ response = self._client.get(f"l10/{meeting_id}/issues")
88
+ else:
89
+ if user_id is None:
90
+ user_id = self.user_id
91
+ response = self._client.get(f"issues/users/{user_id}")
92
+
93
+ response.raise_for_status()
94
+ data = response.json()
95
+
96
+ return [
97
+ IssueListItem(
98
+ id=issue["Id"],
99
+ title=issue["Name"],
100
+ notes_url=issue["DetailsUrl"],
101
+ created_at=issue["CreateTime"],
102
+ meeting_id=issue["OriginId"],
103
+ meeting_title=issue["Origin"],
104
+ )
105
+ for issue in data
106
+ ]
107
+
108
+ def solve(self, issue_id: int) -> bool:
109
+ """Mark an issue as completed/solved.
110
+
111
+ Args:
112
+ issue_id: Unique identifier of the issue to be solved
113
+
114
+ Returns:
115
+ True if issue was successfully solved
116
+
117
+ Raises:
118
+ httpx.HTTPError: When the API request fails
119
+
120
+ Example:
121
+ ```python
122
+ client.issue.solve(123)
123
+ # Returns: True
124
+ ```
125
+ """
126
+ response = self._client.post(
127
+ f"issues/{issue_id}/complete", json={"complete": True}
128
+ )
129
+ response.raise_for_status()
130
+ return True
131
+
132
+ def create(
133
+ self,
134
+ meeting_id: int,
135
+ title: str,
136
+ user_id: int | None = None,
137
+ notes: str | None = None,
138
+ ) -> CreatedIssue:
139
+ """Create a new issue in the system.
140
+
141
+ Args:
142
+ meeting_id: Unique identifier of the associated meeting
143
+ title: Title/name of the issue
144
+ user_id: Unique identifier of the issue owner (defaults to current user)
145
+ notes: Additional notes or description for the issue (optional)
146
+
147
+ Returns:
148
+ A CreatedIssue model instance containing the newly created issue details
149
+
150
+ Raises:
151
+ httpx.HTTPError: When the API request fails or returns invalid data
152
+ ValueError: When required parameters are missing or invalid
153
+
154
+ Example:
155
+ ```python
156
+ client.issue.create(
157
+ meeting_id=123,
158
+ title="New Issue",
159
+ notes="This is a detailed description"
160
+ )
161
+ # Returns: CreatedIssue(id=456, title='New Issue', meeting_id=123, ...)
162
+ ```
163
+ """
164
+ if user_id is None:
165
+ user_id = self.user_id
166
+
167
+ payload = {
168
+ "title": title,
169
+ "meetingid": meeting_id,
170
+ "ownerid": user_id,
171
+ }
172
+
173
+ if notes is not None:
174
+ payload["notes"] = notes
175
+
176
+ response = self._client.post("issues/create", json=payload)
177
+ response.raise_for_status()
178
+ data = response.json()
179
+
180
+ return CreatedIssue(
181
+ id=data["Id"],
182
+ meeting_id=data["OriginId"],
183
+ meeting_title=data["Origin"],
184
+ title=data["Name"],
185
+ user_id=data["Owner"]["Id"],
186
+ notes_url=data["DetailsUrl"],
187
+ )