2 Wrapper for Remember The Milk API
12 return "/".join(rturl.split(r"\/"))
15 class RtMilkManager(object):
17 Interface with rememberthemilk.com
19 @todo Decide upon an interface that will end up a bit less bloated
20 @todo Add interface for task tags
21 @todo Add interface for postponing tasks (have way for UI to specify how many days to postpone?)
22 @todo Add interface for task recurrence
23 @todo Add interface for task estimate
24 @todo Add interface for task location
25 @todo Add interface for task url
26 @todo Add undo support
28 API_KEY = '71f471f7c6ecdda6def341967686fe05'
29 SECRET = '7d3248b085f7efbe'
31 def __init__(self, username, password, token):
32 self._username = username
33 self._password = password
36 self._rtm = rtm_api.RTMapi(self._username, self.API_KEY, self.SECRET, token)
38 resp = self._rtm.timelines.create()
39 self._timeline = resp.timeline
42 def get_projects(self):
43 if len(self._lists) == 0:
44 self._populate_projects()
46 for list in self._lists:
49 def get_project(self, projId):
50 projs = [proj for proj in self.get_projects() if projId == proj["id"]]
51 assert len(projs) == 1, "%r: %r / %r" % (projId, projs, self._lists)
54 def get_project_names(self):
55 return (list["name"] for list in self.get_projects)
57 def lookup_project(self, projName):
59 From a project's name, returns the project's details
61 todoList = [list for list in self.get_projects() if list["name"] == projName]
62 assert len(todoList) == 1, "Wrong number of lists found for %s, in %r" % (projName, todoList)
65 def get_locations(self):
66 rsp = self._rtm.locations.getList()
67 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
72 ("longitude", t.longitude),
73 ("latitude", t.latitude),
74 ("address", t.address),
76 for t in rsp.locations
80 def get_tasks_with_details(self, projId):
81 for realProjId, taskSeries in self._get_taskseries(projId):
82 for task in self._get_tasks(taskSeries):
83 taskId = self._pack_ids(realProjId, taskSeries.id, task.id)
87 "name": taskSeries.name,
88 "url": taskSeries.url,
89 "locationId": taskSeries.location_id,
91 "isCompleted": task.completed,
92 "completedDate": task.completed,
93 "priority": task.priority,
94 "estimate": task.estimate,
95 "notes": list(self._get_notes(taskId, taskSeries.notes)),
97 taskDetails = self._parse_task_details(rawTaskDetails)
100 def get_task_details(self, taskId):
101 projId, rtmSeriesId, rtmTaskId = self._unpack_ids(taskId)
102 for task in self.get_tasks_with_details(projId):
103 if task["id"] == taskId:
107 def add_task(self, projId, taskName):
108 rsp = self._rtm.tasks.add(
109 timeline=self._timeline,
113 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
114 seriesId = rsp.list.taskseries.id
115 taskId = rsp.list.taskseries.task.id
116 name = rsp.list.taskseries.name
118 return self._pack_ids(projId, seriesId, taskId)
120 def set_project(self, taskId, newProjId):
121 projId, seriesId, taskId = self._unpack_ids(taskId)
122 rsp = self._rtm.tasks.moveTo(
123 timeline=self._timeline,
125 to_list_id=newProjId,
126 taskseries_id=seriesId,
129 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
131 def set_name(self, taskId, name):
132 projId, seriesId, taskId = self._unpack_ids(taskId)
133 rsp = self._rtm.tasks.setName(
134 timeline=self._timeline,
136 taskseries_id=seriesId,
140 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
142 def set_duedate(self, taskId, dueDate):
143 assert isinstance(dueDate, toolbox.Optional), (
144 "Date being set too definitively: %r" % dueDate
147 projId, seriesId, taskId = self._unpack_ids(taskId)
148 rsp = self._rtm.tasks.setDueDate(
149 timeline=self._timeline,
151 taskseries_id=seriesId,
156 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
158 def set_priority(self, taskId, priority):
159 assert isinstance(priority, toolbox.Optional), (
160 "Priority being set too definitively: %r" % priority
162 priority = str(priority.get_nothrow("N"))
163 projId, seriesId, taskId = self._unpack_ids(taskId)
165 rsp = self._rtm.tasks.setPriority(
166 timeline=self._timeline,
168 taskseries_id=seriesId,
172 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
174 def complete_task(self, taskId):
175 projId, seriesId, taskId = self._unpack_ids(taskId)
177 rsp = self._rtm.tasks.complete(
178 timeline=self._timeline,
180 taskseries_id=seriesId,
183 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
185 def add_note(self, taskId, noteTitle, noteBody):
186 projId, seriesId, taskId = self._unpack_ids(taskId)
188 rsp = self._rtm.tasks.notes.add(
189 timeline=self._timeline,
191 taskseries_id=seriesId,
193 note_title=noteTitle,
196 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
198 def update_note(self, noteId, noteTitle, noteBody):
199 projId, seriesId, taskId, note = self._unpack_ids(noteId)
201 rsp = self._rtm.tasks.notes.edit(
202 timeline=self._timeline,
204 note_title=noteTitle,
207 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
209 def delete_note(self, noteId):
210 projId, seriesId, taskId, noteId = self._unpack_ids(noteId)
212 rsp = self._rtm.tasks.notes.delete(
213 timeline=self._timeline,
216 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
221 >>> RtMilkManager._pack_ids(123, 456)
224 return "-".join((str(id) for id in ids))
227 def _unpack_ids(ids):
229 >>> RtMilkManager._unpack_ids("123-456")
232 return ids.split("-")
234 def _get_taskseries(self, projId):
235 rsp = self._rtm.tasks.getList(
238 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
239 # @note Meta-projects return lists for each project (I think)
240 rspTasksList = rsp.tasks.list
242 if not isinstance(rspTasksList, list):
243 rspTasksList = (rspTasksList, )
245 for something in rspTasksList:
246 realProjId = something.id
249 except AttributeError:
252 if isinstance(something.taskseries, list):
253 somethingsTaskseries = something.taskseries
255 somethingsTaskseries = (something.taskseries, )
257 for taskSeries in somethingsTaskseries:
258 yield realProjId, taskSeries
260 def _get_tasks(self, taskSeries):
261 if isinstance(taskSeries.task, list):
262 tasks = taskSeries.task
264 tasks = (taskSeries.task, )
268 def _parse_task_details(self, rawTaskDetails):
270 taskDetails["id"] = rawTaskDetails["id"]
271 taskDetails["projId"] = rawTaskDetails["projId"]
272 taskDetails["name"] = rawTaskDetails["name"]
273 taskDetails["url"] = fix_url(rawTaskDetails["url"])
275 rawLocationId = rawTaskDetails["locationId"]
277 locationId = toolbox.Optional(rawLocationId)
279 locationId = toolbox.Optional()
280 taskDetails["locationId"] = locationId
282 if rawTaskDetails["dueDate"]:
283 dueDate = datetime.datetime.strptime(
284 rawTaskDetails["dueDate"],
285 "%Y-%m-%dT%H:%M:%SZ",
287 dueDate = toolbox.Optional(dueDate)
289 dueDate = toolbox.Optional()
290 taskDetails["dueDate"] = dueDate
292 taskDetails["isCompleted"] = len(rawTaskDetails["isCompleted"]) != 0
294 if rawTaskDetails["completedDate"]:
295 completedDate = datetime.datetime.strptime(
296 rawTaskDetails["completedDate"],
297 "%Y-%m-%dT%H:%M:%SZ",
299 completedDate = toolbox.Optional(completedDate)
301 completedDate = toolbox.Optional()
302 taskDetails["completedDate"] = completedDate
305 priority = toolbox.Optional(int(rawTaskDetails["priority"]))
307 priority = toolbox.Optional()
308 taskDetails["priority"] = priority
310 if rawTaskDetails["estimate"]:
311 estimate = rawTaskDetails["estimate"]
312 estimate = toolbox.Optional(estimate)
314 estimate = toolbox.Optional()
315 taskDetails["estimate"] = estimate
317 taskDetails["notes"] = rawTaskDetails["notes"]
319 rawKeys = list(rawTaskDetails.iterkeys())
321 parsedKeys = list(taskDetails.iterkeys())
323 assert rawKeys == parsedKeys, "Missing some, %r != %r" % (rawKeys, parsedKeys)
327 def _get_notes(self, taskId, notes):
330 elif isinstance(notes.note, list):
333 notes = (notes.note, )
335 projId, rtmSeriesId, rtmTaskId = self._unpack_ids(taskId)
338 noteId = self._pack_ids(projId, rtmSeriesId, rtmTaskId, note.id)
340 body = getattr(note, "$t")
347 def _populate_projects(self):
348 rsp = self._rtm.lists.getList()
349 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
355 ("isVisible", not int(t.archived)),
356 ("isMeta", not not int(t.smart)),
358 for t in rsp.lists.list