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
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, )
56 def set_project_name(self, projId, name):
57 rsp = self._rtm.lists.setName(
58 timeline=self._timeline,
62 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
65 def set_project_visibility(self, projId, visibility):
66 action = self._rtm.lists.unarchive if visibility else self._rtm.lists.archive
68 timeline=self._timeline,
71 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
74 def get_projects(self):
75 if len(self._lists) == 0:
76 self._populate_projects()
78 for list in self._lists:
81 def get_project(self, projId):
82 projs = [proj for proj in self.get_projects() if projId == proj["id"]]
83 assert len(projs) == 1, "%r: %r / %r" % (projId, projs, self._lists)
86 def lookup_project(self, projName):
88 From a project's name, returns the project's details
90 todoList = [list for list in self.get_projects() if list["name"] == projName]
91 assert len(todoList) == 1, "Wrong number of lists found for %s, in %r" % (projName, todoList)
94 def get_locations(self):
95 rsp = self._rtm.locations.getList()
96 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
101 ("longitude", t.longitude),
102 ("latitude", t.latitude),
103 ("address", t.address),
105 for t in rsp.locations
109 def get_tasks_with_details(self, projId):
110 for realProjId, taskSeries in self._get_taskseries(projId):
111 for task in self._get_tasks(taskSeries):
112 taskId = self._pack_ids(realProjId, taskSeries.id, task.id)
116 "name": taskSeries.name,
117 "url": taskSeries.url,
118 "locationId": taskSeries.location_id,
120 "isCompleted": task.completed,
121 "completedDate": task.completed,
122 "priority": task.priority,
123 "estimate": task.estimate,
126 for note in self._get_notes(taskId, taskSeries.notes)
129 taskDetails = self._parse_task_details(rawTaskDetails)
132 def get_task_details(self, taskId):
133 projId, rtmSeriesId, rtmTaskId = self._unpack_ids(taskId)
134 for task in self.get_tasks_with_details(projId):
135 if task["id"] == taskId:
139 def add_task(self, projId, taskName):
140 rsp = self._rtm.tasks.add(
141 timeline=self._timeline,
145 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
146 seriesId = rsp.list.taskseries.id
147 taskId = rsp.list.taskseries.task.id
148 name = rsp.list.taskseries.name
150 return self._pack_ids(projId, seriesId, taskId)
152 def set_project(self, taskId, newProjId):
153 projId, seriesId, taskId = self._unpack_ids(taskId)
154 rsp = self._rtm.tasks.moveTo(
155 timeline=self._timeline,
157 to_list_id=newProjId,
158 taskseries_id=seriesId,
161 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
163 def set_name(self, taskId, name):
164 projId, seriesId, taskId = self._unpack_ids(taskId)
165 rsp = self._rtm.tasks.setName(
166 timeline=self._timeline,
168 taskseries_id=seriesId,
172 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
174 def set_duedate(self, taskId, dueDate):
175 assert isinstance(dueDate, toolbox.Optional), (
176 "Date being set too definitively: %r" % dueDate
179 projId, seriesId, taskId = self._unpack_ids(taskId)
180 rsp = self._rtm.tasks.setDueDate(
181 timeline=self._timeline,
183 taskseries_id=seriesId,
188 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
190 def set_priority(self, taskId, priority):
191 assert isinstance(priority, toolbox.Optional), (
192 "Priority being set too definitively: %r" % priority
194 priority = str(priority.get_nothrow("N"))
195 projId, seriesId, taskId = self._unpack_ids(taskId)
197 rsp = self._rtm.tasks.setPriority(
198 timeline=self._timeline,
200 taskseries_id=seriesId,
204 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
206 def complete_task(self, taskId):
207 projId, seriesId, taskId = self._unpack_ids(taskId)
209 rsp = self._rtm.tasks.complete(
210 timeline=self._timeline,
212 taskseries_id=seriesId,
215 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
217 def add_note(self, taskId, noteTitle, noteBody):
218 projId, seriesId, taskId = self._unpack_ids(taskId)
220 rsp = self._rtm.tasks.notes.add(
221 timeline=self._timeline,
223 taskseries_id=seriesId,
225 note_title=noteTitle,
228 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
230 def update_note(self, noteId, noteTitle, noteBody):
231 projId, seriesId, taskId, note = self._unpack_ids(noteId)
233 rsp = self._rtm.tasks.notes.edit(
234 timeline=self._timeline,
236 note_title=noteTitle,
239 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
241 def delete_note(self, noteId):
242 projId, seriesId, taskId, noteId = self._unpack_ids(noteId)
244 rsp = self._rtm.tasks.notes.delete(
245 timeline=self._timeline,
248 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
253 >>> RtMilkManager._pack_ids(123, 456)
256 return "-".join((str(id) for id in ids))
259 def _unpack_ids(ids):
261 >>> RtMilkManager._unpack_ids("123-456")
264 return ids.split("-")
266 def _get_taskseries(self, projId):
267 rsp = self._rtm.tasks.getList(
270 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
271 # @note Meta-projects return lists for each project (I think)
272 rspTasksList = rsp.tasks.list
274 if not isinstance(rspTasksList, list):
275 rspTasksList = (rspTasksList, )
277 for something in rspTasksList:
278 realProjId = something.id
281 except AttributeError:
284 if isinstance(something.taskseries, list):
285 somethingsTaskseries = something.taskseries
287 somethingsTaskseries = (something.taskseries, )
289 for taskSeries in somethingsTaskseries:
290 yield realProjId, taskSeries
292 def _get_tasks(self, taskSeries):
293 if isinstance(taskSeries.task, list):
294 tasks = taskSeries.task
296 tasks = (taskSeries.task, )
300 def _parse_task_details(self, rawTaskDetails):
302 taskDetails["id"] = rawTaskDetails["id"]
303 taskDetails["projId"] = rawTaskDetails["projId"]
304 taskDetails["name"] = rawTaskDetails["name"]
305 taskDetails["url"] = fix_url(rawTaskDetails["url"])
307 rawLocationId = rawTaskDetails["locationId"]
309 locationId = toolbox.Optional(rawLocationId)
311 locationId = toolbox.Optional()
312 taskDetails["locationId"] = locationId
314 if rawTaskDetails["dueDate"]:
315 dueDate = datetime.datetime.strptime(
316 rawTaskDetails["dueDate"],
317 "%Y-%m-%dT%H:%M:%SZ",
319 dueDate = toolbox.Optional(dueDate)
321 dueDate = toolbox.Optional()
322 taskDetails["dueDate"] = dueDate
324 taskDetails["isCompleted"] = len(rawTaskDetails["isCompleted"]) != 0
326 if rawTaskDetails["completedDate"]:
327 completedDate = datetime.datetime.strptime(
328 rawTaskDetails["completedDate"],
329 "%Y-%m-%dT%H:%M:%SZ",
331 completedDate = toolbox.Optional(completedDate)
333 completedDate = toolbox.Optional()
334 taskDetails["completedDate"] = completedDate
337 priority = toolbox.Optional(int(rawTaskDetails["priority"]))
339 priority = toolbox.Optional()
340 taskDetails["priority"] = priority
342 if rawTaskDetails["estimate"]:
343 estimate = rawTaskDetails["estimate"]
344 estimate = toolbox.Optional(estimate)
346 estimate = toolbox.Optional()
347 taskDetails["estimate"] = estimate
349 taskDetails["notes"] = rawTaskDetails["notes"]
351 rawKeys = list(rawTaskDetails.iterkeys())
353 parsedKeys = list(taskDetails.iterkeys())
355 assert rawKeys == parsedKeys, "Missing some, %r != %r" % (rawKeys, parsedKeys)
359 def _get_notes(self, taskId, notes):
362 elif isinstance(notes.note, list):
365 notes = (notes.note, )
367 projId, rtmSeriesId, rtmTaskId = self._unpack_ids(taskId)
370 noteId = self._pack_ids(projId, rtmSeriesId, rtmTaskId, note.id)
372 body = getattr(note, "$t")
379 def _populate_projects(self):
380 rsp = self._rtm.lists.getList()
381 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
387 ("isVisible", not int(t.archived)),
388 ("isMeta", not not int(t.smart)),
390 for t in rsp.lists.list