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,
97 for note in self._get_notes(taskId, taskSeries.notes)
100 taskDetails = self._parse_task_details(rawTaskDetails)
103 def get_task_details(self, taskId):
104 projId, rtmSeriesId, rtmTaskId = self._unpack_ids(taskId)
105 for task in self.get_tasks_with_details(projId):
106 if task["id"] == taskId:
110 def add_task(self, projId, taskName):
111 rsp = self._rtm.tasks.add(
112 timeline=self._timeline,
116 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
117 seriesId = rsp.list.taskseries.id
118 taskId = rsp.list.taskseries.task.id
119 name = rsp.list.taskseries.name
121 return self._pack_ids(projId, seriesId, taskId)
123 def set_project(self, taskId, newProjId):
124 projId, seriesId, taskId = self._unpack_ids(taskId)
125 rsp = self._rtm.tasks.moveTo(
126 timeline=self._timeline,
128 to_list_id=newProjId,
129 taskseries_id=seriesId,
132 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
134 def set_name(self, taskId, name):
135 projId, seriesId, taskId = self._unpack_ids(taskId)
136 rsp = self._rtm.tasks.setName(
137 timeline=self._timeline,
139 taskseries_id=seriesId,
143 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
145 def set_duedate(self, taskId, dueDate):
146 assert isinstance(dueDate, toolbox.Optional), (
147 "Date being set too definitively: %r" % dueDate
150 projId, seriesId, taskId = self._unpack_ids(taskId)
151 rsp = self._rtm.tasks.setDueDate(
152 timeline=self._timeline,
154 taskseries_id=seriesId,
159 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
161 def set_priority(self, taskId, priority):
162 assert isinstance(priority, toolbox.Optional), (
163 "Priority being set too definitively: %r" % priority
165 priority = str(priority.get_nothrow("N"))
166 projId, seriesId, taskId = self._unpack_ids(taskId)
168 rsp = self._rtm.tasks.setPriority(
169 timeline=self._timeline,
171 taskseries_id=seriesId,
175 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
177 def complete_task(self, taskId):
178 projId, seriesId, taskId = self._unpack_ids(taskId)
180 rsp = self._rtm.tasks.complete(
181 timeline=self._timeline,
183 taskseries_id=seriesId,
186 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
188 def add_note(self, taskId, noteTitle, noteBody):
189 projId, seriesId, taskId = self._unpack_ids(taskId)
191 rsp = self._rtm.tasks.notes.add(
192 timeline=self._timeline,
194 taskseries_id=seriesId,
196 note_title=noteTitle,
199 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
201 def update_note(self, noteId, noteTitle, noteBody):
202 projId, seriesId, taskId, note = self._unpack_ids(noteId)
204 rsp = self._rtm.tasks.notes.edit(
205 timeline=self._timeline,
207 note_title=noteTitle,
210 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
212 def delete_note(self, noteId):
213 projId, seriesId, taskId, noteId = self._unpack_ids(noteId)
215 rsp = self._rtm.tasks.notes.delete(
216 timeline=self._timeline,
219 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
224 >>> RtMilkManager._pack_ids(123, 456)
227 return "-".join((str(id) for id in ids))
230 def _unpack_ids(ids):
232 >>> RtMilkManager._unpack_ids("123-456")
235 return ids.split("-")
237 def _get_taskseries(self, projId):
238 rsp = self._rtm.tasks.getList(
241 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
242 # @note Meta-projects return lists for each project (I think)
243 rspTasksList = rsp.tasks.list
245 if not isinstance(rspTasksList, list):
246 rspTasksList = (rspTasksList, )
248 for something in rspTasksList:
249 realProjId = something.id
252 except AttributeError:
255 if isinstance(something.taskseries, list):
256 somethingsTaskseries = something.taskseries
258 somethingsTaskseries = (something.taskseries, )
260 for taskSeries in somethingsTaskseries:
261 yield realProjId, taskSeries
263 def _get_tasks(self, taskSeries):
264 if isinstance(taskSeries.task, list):
265 tasks = taskSeries.task
267 tasks = (taskSeries.task, )
271 def _parse_task_details(self, rawTaskDetails):
273 taskDetails["id"] = rawTaskDetails["id"]
274 taskDetails["projId"] = rawTaskDetails["projId"]
275 taskDetails["name"] = rawTaskDetails["name"]
276 taskDetails["url"] = fix_url(rawTaskDetails["url"])
278 rawLocationId = rawTaskDetails["locationId"]
280 locationId = toolbox.Optional(rawLocationId)
282 locationId = toolbox.Optional()
283 taskDetails["locationId"] = locationId
285 if rawTaskDetails["dueDate"]:
286 dueDate = datetime.datetime.strptime(
287 rawTaskDetails["dueDate"],
288 "%Y-%m-%dT%H:%M:%SZ",
290 dueDate = toolbox.Optional(dueDate)
292 dueDate = toolbox.Optional()
293 taskDetails["dueDate"] = dueDate
295 taskDetails["isCompleted"] = len(rawTaskDetails["isCompleted"]) != 0
297 if rawTaskDetails["completedDate"]:
298 completedDate = datetime.datetime.strptime(
299 rawTaskDetails["completedDate"],
300 "%Y-%m-%dT%H:%M:%SZ",
302 completedDate = toolbox.Optional(completedDate)
304 completedDate = toolbox.Optional()
305 taskDetails["completedDate"] = completedDate
308 priority = toolbox.Optional(int(rawTaskDetails["priority"]))
310 priority = toolbox.Optional()
311 taskDetails["priority"] = priority
313 if rawTaskDetails["estimate"]:
314 estimate = rawTaskDetails["estimate"]
315 estimate = toolbox.Optional(estimate)
317 estimate = toolbox.Optional()
318 taskDetails["estimate"] = estimate
320 taskDetails["notes"] = rawTaskDetails["notes"]
322 rawKeys = list(rawTaskDetails.iterkeys())
324 parsedKeys = list(taskDetails.iterkeys())
326 assert rawKeys == parsedKeys, "Missing some, %r != %r" % (rawKeys, parsedKeys)
330 def _get_notes(self, taskId, notes):
333 elif isinstance(notes.note, list):
336 notes = (notes.note, )
338 projId, rtmSeriesId, rtmTaskId = self._unpack_ids(taskId)
341 noteId = self._pack_ids(projId, rtmSeriesId, rtmTaskId, note.id)
343 body = getattr(note, "$t")
350 def _populate_projects(self):
351 rsp = self._rtm.lists.getList()
352 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
358 ("isVisible", not int(t.archived)),
359 ("isMeta", not not int(t.smart)),
361 for t in rsp.lists.list