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 add_project(self, name):
43 rsp = self._rtm.lists.add(
44 timeline=self._timeline,
47 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
50 def set_project_name(self, projId, name):
51 rsp = self._rtm.lists.setName(
52 timeline=self._timeline,
56 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
59 def set_project_visibility(self, projId, visibility):
60 action = self._rtm.lists.unarchive if visibility else self._rtm.lists.archive
62 timeline=self._timeline,
65 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
68 def get_projects(self):
69 if len(self._lists) == 0:
70 self._populate_projects()
72 for list in self._lists:
75 def get_project(self, projId):
76 projs = [proj for proj in self.get_projects() if projId == proj["id"]]
77 assert len(projs) == 1, "%r: %r / %r" % (projId, projs, self._lists)
80 def lookup_project(self, projName):
82 From a project's name, returns the project's details
84 todoList = [list for list in self.get_projects() if list["name"] == projName]
85 assert len(todoList) == 1, "Wrong number of lists found for %s, in %r" % (projName, todoList)
88 def get_locations(self):
89 rsp = self._rtm.locations.getList()
90 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
95 ("longitude", t.longitude),
96 ("latitude", t.latitude),
97 ("address", t.address),
99 for t in rsp.locations
103 def get_tasks_with_details(self, projId):
104 for realProjId, taskSeries in self._get_taskseries(projId):
105 for task in self._get_tasks(taskSeries):
106 taskId = self._pack_ids(realProjId, taskSeries.id, task.id)
110 "name": taskSeries.name,
111 "url": taskSeries.url,
112 "locationId": taskSeries.location_id,
114 "isCompleted": task.completed,
115 "completedDate": task.completed,
116 "priority": task.priority,
117 "estimate": task.estimate,
120 for note in self._get_notes(taskId, taskSeries.notes)
123 taskDetails = self._parse_task_details(rawTaskDetails)
126 def get_task_details(self, taskId):
127 projId, rtmSeriesId, rtmTaskId = self._unpack_ids(taskId)
128 for task in self.get_tasks_with_details(projId):
129 if task["id"] == taskId:
133 def add_task(self, projId, taskName):
134 rsp = self._rtm.tasks.add(
135 timeline=self._timeline,
139 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
140 seriesId = rsp.list.taskseries.id
141 taskId = rsp.list.taskseries.task.id
142 name = rsp.list.taskseries.name
144 return self._pack_ids(projId, seriesId, taskId)
146 def set_project(self, taskId, newProjId):
147 projId, seriesId, taskId = self._unpack_ids(taskId)
148 rsp = self._rtm.tasks.moveTo(
149 timeline=self._timeline,
151 to_list_id=newProjId,
152 taskseries_id=seriesId,
155 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
157 def set_name(self, taskId, name):
158 projId, seriesId, taskId = self._unpack_ids(taskId)
159 rsp = self._rtm.tasks.setName(
160 timeline=self._timeline,
162 taskseries_id=seriesId,
166 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
168 def set_duedate(self, taskId, dueDate):
169 assert isinstance(dueDate, toolbox.Optional), (
170 "Date being set too definitively: %r" % dueDate
173 projId, seriesId, taskId = self._unpack_ids(taskId)
174 rsp = self._rtm.tasks.setDueDate(
175 timeline=self._timeline,
177 taskseries_id=seriesId,
182 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
184 def set_priority(self, taskId, priority):
185 assert isinstance(priority, toolbox.Optional), (
186 "Priority being set too definitively: %r" % priority
188 priority = str(priority.get_nothrow("N"))
189 projId, seriesId, taskId = self._unpack_ids(taskId)
191 rsp = self._rtm.tasks.setPriority(
192 timeline=self._timeline,
194 taskseries_id=seriesId,
198 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
200 def complete_task(self, taskId):
201 projId, seriesId, taskId = self._unpack_ids(taskId)
203 rsp = self._rtm.tasks.complete(
204 timeline=self._timeline,
206 taskseries_id=seriesId,
209 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
211 def add_note(self, taskId, noteTitle, noteBody):
212 projId, seriesId, taskId = self._unpack_ids(taskId)
214 rsp = self._rtm.tasks.notes.add(
215 timeline=self._timeline,
217 taskseries_id=seriesId,
219 note_title=noteTitle,
222 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
224 def update_note(self, noteId, noteTitle, noteBody):
225 projId, seriesId, taskId, note = self._unpack_ids(noteId)
227 rsp = self._rtm.tasks.notes.edit(
228 timeline=self._timeline,
230 note_title=noteTitle,
233 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
235 def delete_note(self, noteId):
236 projId, seriesId, taskId, noteId = self._unpack_ids(noteId)
238 rsp = self._rtm.tasks.notes.delete(
239 timeline=self._timeline,
242 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
247 >>> RtMilkManager._pack_ids(123, 456)
250 return "-".join((str(id) for id in ids))
253 def _unpack_ids(ids):
255 >>> RtMilkManager._unpack_ids("123-456")
258 return ids.split("-")
260 def _get_taskseries(self, projId):
261 rsp = self._rtm.tasks.getList(
264 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
265 # @note Meta-projects return lists for each project (I think)
266 rspTasksList = rsp.tasks.list
268 if not isinstance(rspTasksList, list):
269 rspTasksList = (rspTasksList, )
271 for something in rspTasksList:
272 realProjId = something.id
275 except AttributeError:
278 if isinstance(something.taskseries, list):
279 somethingsTaskseries = something.taskseries
281 somethingsTaskseries = (something.taskseries, )
283 for taskSeries in somethingsTaskseries:
284 yield realProjId, taskSeries
286 def _get_tasks(self, taskSeries):
287 if isinstance(taskSeries.task, list):
288 tasks = taskSeries.task
290 tasks = (taskSeries.task, )
294 def _parse_task_details(self, rawTaskDetails):
296 taskDetails["id"] = rawTaskDetails["id"]
297 taskDetails["projId"] = rawTaskDetails["projId"]
298 taskDetails["name"] = rawTaskDetails["name"]
299 taskDetails["url"] = fix_url(rawTaskDetails["url"])
301 rawLocationId = rawTaskDetails["locationId"]
303 locationId = toolbox.Optional(rawLocationId)
305 locationId = toolbox.Optional()
306 taskDetails["locationId"] = locationId
308 if rawTaskDetails["dueDate"]:
309 dueDate = datetime.datetime.strptime(
310 rawTaskDetails["dueDate"],
311 "%Y-%m-%dT%H:%M:%SZ",
313 dueDate = toolbox.Optional(dueDate)
315 dueDate = toolbox.Optional()
316 taskDetails["dueDate"] = dueDate
318 taskDetails["isCompleted"] = len(rawTaskDetails["isCompleted"]) != 0
320 if rawTaskDetails["completedDate"]:
321 completedDate = datetime.datetime.strptime(
322 rawTaskDetails["completedDate"],
323 "%Y-%m-%dT%H:%M:%SZ",
325 completedDate = toolbox.Optional(completedDate)
327 completedDate = toolbox.Optional()
328 taskDetails["completedDate"] = completedDate
331 priority = toolbox.Optional(int(rawTaskDetails["priority"]))
333 priority = toolbox.Optional()
334 taskDetails["priority"] = priority
336 if rawTaskDetails["estimate"]:
337 estimate = rawTaskDetails["estimate"]
338 estimate = toolbox.Optional(estimate)
340 estimate = toolbox.Optional()
341 taskDetails["estimate"] = estimate
343 taskDetails["notes"] = rawTaskDetails["notes"]
345 rawKeys = list(rawTaskDetails.iterkeys())
347 parsedKeys = list(taskDetails.iterkeys())
349 assert rawKeys == parsedKeys, "Missing some, %r != %r" % (rawKeys, parsedKeys)
353 def _get_notes(self, taskId, notes):
356 elif isinstance(notes.note, list):
359 notes = (notes.note, )
361 projId, rtmSeriesId, rtmTaskId = self._unpack_ids(taskId)
364 noteId = self._pack_ids(projId, rtmSeriesId, rtmTaskId, note.id)
366 body = getattr(note, "$t")
373 def _populate_projects(self):
374 rsp = self._rtm.lists.getList()
375 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
381 ("isVisible", not int(t.archived)),
382 ("isMeta", not not int(t.smart)),
384 for t in rsp.lists.list