22
33from __future__ import annotations
44
5- import asyncio
65import builtins
76from typing import TYPE_CHECKING
87
98from ...models import (
109 ArchivedGoalInfo ,
11- BulkCreateError ,
1210 BulkCreateResult ,
1311 CreatedGoalInfo ,
1412 GoalInfo ,
1513 GoalListResponse ,
1614 GoalStatus ,
1715)
1816from ...utils .async_base_operations import AsyncBaseOperations
17+ from ..mixins .goals_transform import GoalOperationsMixin
1918
2019if TYPE_CHECKING :
2120 from typing import Any
2221
23- import httpx
2422
25-
26- class AsyncGoalOperations (AsyncBaseOperations ):
23+ class AsyncGoalOperations (AsyncBaseOperations , GoalOperationsMixin ):
2724 """Async class to handle all operations related to goals (aka "rocks")."""
2825
29- def __init__ (self , client : httpx .AsyncClient ) -> None :
30- """Initialize the async goal operations.
31-
32- Args:
33- client: The async HTTP client to use for API requests.
34-
35- """
36- super ().__init__ (client )
37-
3826 async def list (
3927 self , user_id : int | None = None , archived : bool = False
4028 ) -> builtins .list [GoalInfo ] | GoalListResponse :
@@ -60,22 +48,7 @@ async def list(
6048 response .raise_for_status ()
6149 data = response .json ()
6250
63- active_goals : list [GoalInfo ] = [
64- GoalInfo (
65- id = goal ["Id" ],
66- user_id = goal ["Owner" ]["Id" ],
67- user_name = goal ["Owner" ]["Name" ],
68- title = goal ["Name" ],
69- created_at = goal ["CreateTime" ],
70- due_date = goal ["DueDate" ],
71- status = "Completed" if goal .get ("Complete" ) else "Incomplete" ,
72- meeting_id = goal ["Origins" ][0 ]["Id" ] if goal .get ("Origins" ) else None ,
73- meeting_title = (
74- goal ["Origins" ][0 ]["Name" ] if goal .get ("Origins" ) else None
75- ),
76- )
77- for goal in data
78- ]
51+ active_goals = self ._transform_goal_list (data )
7952
8053 if archived :
8154 archived_goals = await self ._get_archived_goals (user_id )
@@ -106,20 +79,7 @@ async def create(
10679 response .raise_for_status ()
10780 data = response .json ()
10881
109- # Map completion status
110- completion_map = {2 : "complete" , 1 : "on" , 0 : "off" }
111- status = completion_map .get (data .get ("Completion" , 0 ), "off" )
112-
113- return CreatedGoalInfo (
114- id = data ["Id" ],
115- user_id = user_id ,
116- user_name = data ["Owner" ]["Name" ],
117- title = title ,
118- meeting_id = meeting_id ,
119- meeting_title = data ["Origins" ][0 ]["Name" ],
120- status = status ,
121- created_at = data ["CreateTime" ],
122- )
82+ return self ._transform_created_goal (data , title , meeting_id , user_id )
12383
12484 async def delete (self , goal_id : int ) -> None :
12585 """Delete a goal.
@@ -148,29 +108,13 @@ async def update(
148108 status: The status value. Can be a GoalStatus enum member or string
149109 ('on', 'off', or 'complete'). Use GoalStatus.ON_TRACK,
150110 GoalStatus.AT_RISK, or GoalStatus.COMPLETE for type safety.
151-
152- Raises:
153- ValueError: If an invalid status value is provided
111+ Invalid values will raise ValueError via the update payload builder.
154112
155113 """
156114 if accountable_user is None :
157115 accountable_user = await self .get_user_id ()
158116
159- payload : dict [str , Any ] = {"accountableUserId" : accountable_user }
160-
161- if title is not None :
162- payload ["title" ] = title
163-
164- if status is not None :
165- valid_status = {"on" : "OnTrack" , "off" : "AtRisk" , "complete" : "Complete" }
166- # Handle both GoalStatus enum and string
167- status_value = status .value if isinstance (status , GoalStatus ) else status
168- status_key = status_value .lower ()
169- if status_key not in valid_status :
170- raise ValueError (
171- "Invalid status value. Must be 'on', 'off', or 'complete'."
172- )
173- payload ["completion" ] = valid_status [status_key ]
117+ payload = self ._build_goal_update_payload (accountable_user , title , status )
174118
175119 response = await self ._client .put (f"rocks/{ goal_id } " , json = payload )
176120 response .raise_for_status ()
@@ -214,16 +158,7 @@ async def _get_archived_goals(
214158 response .raise_for_status ()
215159 data = response .json ()
216160
217- return [
218- ArchivedGoalInfo (
219- id = goal ["Id" ],
220- title = goal ["Name" ],
221- created_at = goal ["CreateTime" ],
222- due_date = goal ["DueDate" ],
223- status = "Complete" if goal .get ("Complete" ) else "Incomplete" ,
224- )
225- for goal in data
226- ]
161+ return self ._transform_archived_goals (data )
227162
228163 async def create_many (
229164 self , goals : builtins .list [dict [str , Any ]], max_concurrent : int = 5
@@ -260,67 +195,17 @@ async def create_many(
260195 ```
261196
262197 """
263- # Create a semaphore to limit concurrent requests
264- semaphore = asyncio .Semaphore (max_concurrent )
265-
266- async def create_single_goal (
267- index : int , goal_data : dict [str , Any ]
268- ) -> tuple [int , CreatedGoalInfo | BulkCreateError ]:
269- """Create a single goal with error handling.
270-
271- Returns:
272- Tuple of (index, result) where result is either CreatedGoalInfo
273- or BulkCreateError.
274-
275- Raises:
276- ValueError: When required parameters are missing.
277-
278- """
279- async with semaphore :
280- try :
281- # Extract parameters from the goal data
282- title = goal_data .get ("title" )
283- meeting_id = goal_data .get ("meeting_id" )
284- user_id = goal_data .get ("user_id" )
285-
286- # Validate required parameters
287- if title is None :
288- raise ValueError ("title is required" )
289- if meeting_id is None :
290- raise ValueError ("meeting_id is required" )
291-
292- # Create the goal
293- created_goal = await self .create (
294- title = title , meeting_id = meeting_id , user_id = user_id
295- )
296- return (index , created_goal )
297-
298- except Exception as e :
299- error = BulkCreateError (
300- index = index , input_data = goal_data , error = str (e )
301- )
302- return (index , error )
303-
304- # Create tasks for all goals
305- tasks = [
306- create_single_goal (index , goal_data )
307- for index , goal_data in enumerate (goals )
308- ]
309-
310- # Execute all tasks concurrently
311- results = await asyncio .gather (* tasks )
312-
313- # Sort results to maintain order
314- results .sort (key = lambda x : x [0 ])
315-
316- # Separate successful and failed results
317- successful : builtins .list [CreatedGoalInfo ] = []
318- failed : builtins .list [BulkCreateError ] = []
319-
320- for _ , result in results :
321- if isinstance (result , CreatedGoalInfo ):
322- successful .append (result )
323- else :
324- failed .append (result )
325-
326- return BulkCreateResult (successful = successful , failed = failed )
198+
199+ async def _create_single (data : dict [str , Any ]) -> CreatedGoalInfo :
200+ return await self .create (
201+ title = data ["title" ],
202+ meeting_id = data ["meeting_id" ],
203+ user_id = data .get ("user_id" ),
204+ )
205+
206+ return await self ._process_bulk_async (
207+ goals ,
208+ _create_single ,
209+ required_fields = ["title" , "meeting_id" ],
210+ max_concurrent = max_concurrent ,
211+ )
0 commit comments