2 Wrapper for Remember The Milk API
12 return "/".join(rturl.split(r"\/"))
15 class RtmBackend(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
48 def add_project(self, name):
49 rsp = self._rtm.lists.add(
50 timeline=self._timeline,
53 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
57 def set_project_name(self, projId, name):
58 rsp = self._rtm.lists.setName(
59 timeline=self._timeline,
63 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
66 def set_project_visibility(self, projId, visibility):
67 action = self._rtm.lists.unarchive if visibility else self._rtm.lists.archive
69 timeline=self._timeline,
72 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
75 def get_projects(self):
76 if len(self._lists) == 0:
77 self._populate_projects()
79 for list in self._lists:
82 def get_project(self, projId):
83 projs = [proj for proj in self.get_projects() if projId == proj["id"]]
84 assert len(projs) == 1, "%r: %r / %r" % (projId, projs, self._lists)
87 def lookup_project(self, projName):
89 From a project's name, returns the project's details
91 todoList = [list for list in self.get_projects() if list["name"] == projName]
92 assert len(todoList) == 1, "Wrong number of lists found for %s, in %r" % (projName, todoList)
95 def get_locations(self):
96 rsp = self._rtm.locations.getList()
97 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
102 ("longitude", t.longitude),
103 ("latitude", t.latitude),
104 ("address", t.address),
106 for t in rsp.locations
110 def get_tasks_with_details(self, projId):
111 for realProjId, taskSeries in self._get_taskseries(projId):
112 for task in self._get_tasks(taskSeries):
113 taskId = self._pack_ids(realProjId, taskSeries.id, task.id)
117 "name": taskSeries.name,
118 "url": taskSeries.url,
119 "locationId": taskSeries.location_id,
121 "isCompleted": task.completed,
122 "completedDate": task.completed,
123 "priority": task.priority,
124 "estimate": task.estimate,
127 for note in self._get_notes(taskId, taskSeries.notes)
130 taskDetails = self._parse_task_details(rawTaskDetails)
133 def get_task_details(self, taskId):
134 projId, rtmSeriesId, rtmTaskId = self._unpack_ids(taskId)
135 for task in self.get_tasks_with_details(projId):
136 if task["id"] == taskId:
140 def add_task(self, projId, taskName):
141 rsp = self._rtm.tasks.add(
142 timeline=self._timeline,
146 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
147 seriesId = rsp.list.taskseries.id
148 taskId = rsp.list.taskseries.task.id
149 name = rsp.list.taskseries.name
151 return self._pack_ids(projId, seriesId, taskId)
153 def set_project(self, taskId, newProjId):
154 projId, seriesId, taskId = self._unpack_ids(taskId)
155 rsp = self._rtm.tasks.moveTo(
156 timeline=self._timeline,
158 to_list_id=newProjId,
159 taskseries_id=seriesId,
162 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
164 def set_name(self, taskId, name):
165 projId, seriesId, taskId = self._unpack_ids(taskId)
166 rsp = self._rtm.tasks.setName(
167 timeline=self._timeline,
169 taskseries_id=seriesId,
173 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
175 def set_duedate(self, taskId, dueDate):
176 assert isinstance(dueDate, toolbox.Optional), (
177 "Date being set too definitively: %r" % dueDate
180 projId, seriesId, taskId = self._unpack_ids(taskId)
181 rsp = self._rtm.tasks.setDueDate(
182 timeline=self._timeline,
184 taskseries_id=seriesId,
189 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
191 def set_priority(self, taskId, priority):
192 assert isinstance(priority, toolbox.Optional), (
193 "Priority being set too definitively: %r" % priority
195 priority = str(priority.get_nothrow("N"))
196 projId, seriesId, taskId = self._unpack_ids(taskId)
198 rsp = self._rtm.tasks.setPriority(
199 timeline=self._timeline,
201 taskseries_id=seriesId,
205 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
207 def complete_task(self, taskId):
208 projId, seriesId, taskId = self._unpack_ids(taskId)
210 rsp = self._rtm.tasks.complete(
211 timeline=self._timeline,
213 taskseries_id=seriesId,
216 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
218 def add_note(self, taskId, noteTitle, noteBody):
219 projId, seriesId, taskId = self._unpack_ids(taskId)
221 rsp = self._rtm.tasks.notes.add(
222 timeline=self._timeline,
224 taskseries_id=seriesId,
226 note_title=noteTitle,
229 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
232 return self._pack_ids(projId, seriesId, taskId, noteId)
234 def update_note(self, noteId, noteTitle, noteBody):
235 projId, seriesId, taskId, note = self._unpack_ids(noteId)
237 rsp = self._rtm.tasks.notes.edit(
238 timeline=self._timeline,
240 note_title=noteTitle,
243 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
245 def delete_note(self, noteId):
246 projId, seriesId, taskId, noteId = self._unpack_ids(noteId)
248 rsp = self._rtm.tasks.notes.delete(
249 timeline=self._timeline,
252 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
257 >>> RtmBackend._pack_ids(123, 456)
260 return "-".join((str(id) for id in ids))
263 def _unpack_ids(ids):
265 >>> RtmBackend._unpack_ids("123-456")
268 return ids.split("-")
270 def _get_taskseries(self, projId):
271 rsp = self._rtm.tasks.getList(
274 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
275 # @note Meta-projects return lists for each project (I think)
276 rspTasksList = rsp.tasks.list
278 if not isinstance(rspTasksList, list):
279 rspTasksList = (rspTasksList, )
281 for something in rspTasksList:
282 realProjId = something.id
285 except AttributeError:
288 if isinstance(something.taskseries, list):
289 somethingsTaskseries = something.taskseries
291 somethingsTaskseries = (something.taskseries, )
293 for taskSeries in somethingsTaskseries:
294 yield realProjId, taskSeries
296 def _get_tasks(self, taskSeries):
297 if isinstance(taskSeries.task, list):
298 tasks = taskSeries.task
300 tasks = (taskSeries.task, )
304 def _parse_task_details(self, rawTaskDetails):
306 taskDetails["id"] = rawTaskDetails["id"]
307 taskDetails["projId"] = rawTaskDetails["projId"]
308 taskDetails["name"] = rawTaskDetails["name"]
309 taskDetails["url"] = fix_url(rawTaskDetails["url"])
311 rawLocationId = rawTaskDetails["locationId"]
313 locationId = toolbox.Optional(rawLocationId)
315 locationId = toolbox.Optional()
316 taskDetails["locationId"] = locationId
318 if rawTaskDetails["dueDate"]:
319 dueDate = datetime.datetime.strptime(
320 rawTaskDetails["dueDate"],
321 "%Y-%m-%dT%H:%M:%SZ",
323 dueDate = toolbox.Optional(dueDate)
325 dueDate = toolbox.Optional()
326 taskDetails["dueDate"] = dueDate
328 taskDetails["isCompleted"] = len(rawTaskDetails["isCompleted"]) != 0
330 if rawTaskDetails["completedDate"]:
331 completedDate = datetime.datetime.strptime(
332 rawTaskDetails["completedDate"],
333 "%Y-%m-%dT%H:%M:%SZ",
335 completedDate = toolbox.Optional(completedDate)
337 completedDate = toolbox.Optional()
338 taskDetails["completedDate"] = completedDate
341 priority = toolbox.Optional(int(rawTaskDetails["priority"]))
343 priority = toolbox.Optional()
344 taskDetails["priority"] = priority
346 if rawTaskDetails["estimate"]:
347 estimate = rawTaskDetails["estimate"]
348 estimate = toolbox.Optional(estimate)
350 estimate = toolbox.Optional()
351 taskDetails["estimate"] = estimate
353 taskDetails["notes"] = rawTaskDetails["notes"]
355 rawKeys = list(rawTaskDetails.iterkeys())
357 parsedKeys = list(taskDetails.iterkeys())
359 assert rawKeys == parsedKeys, "Missing some, %r != %r" % (rawKeys, parsedKeys)
363 def _get_notes(self, taskId, notes):
366 elif isinstance(notes.note, list):
369 notes = (notes.note, )
371 projId, rtmSeriesId, rtmTaskId = self._unpack_ids(taskId)
374 noteId = self._pack_ids(projId, rtmSeriesId, rtmTaskId, note.id)
376 body = getattr(note, "$t")
383 def _populate_projects(self):
384 rsp = self._rtm.lists.getList()
385 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
391 ("isVisible", not int(t.archived)),
392 ("isMeta", not not int(t.smart)),
394 for t in rsp.lists.list