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 interface for task notes
27 @todo Add undo support
29 API_KEY = '71f471f7c6ecdda6def341967686fe05'
30 SECRET = '7d3248b085f7efbe'
32 def __init__(self, username, password, token):
33 self._username = username
34 self._password = password
37 self._rtm = rtm_api.RTMapi(self._username, self.API_KEY, self.SECRET, token)
39 resp = self._rtm.timelines.create()
40 self._timeline = resp.timeline
43 def get_projects(self):
44 if len(self._lists) == 0:
45 self._populate_projects()
47 for list in self._lists:
50 def get_project(self, projId):
51 proj = [proj for proj in self.get_projects() if projId == proj["id"]]
52 assert len(proj) == 1, "%r / %r" % (proj, self._lists)
55 def get_project_names(self):
56 return (list["name"] for list in self.get_projects)
58 def lookup_project(self, projName):
60 From a project's name, returns the project's details
62 todoList = [list for list in self.get_projects() if list["name"] == projName]
63 assert len(todoList) == 1, "Wrong number of lists found for %s, in %r" % (projName, todoList)
66 def get_locations(self):
67 rsp = self._rtm.locations.getList()
68 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
73 ("longitude", t.longitude),
74 ("latitude", t.latitude),
75 ("address", t.address),
77 for t in rsp.locations
81 def get_tasks_with_details(self, projId):
82 for realProjId, taskSeries in self._get_taskseries(projId):
83 for task in self._get_tasks(taskSeries):
84 taskId = self._pack_ids(realProjId, taskSeries.id, task.id)
88 "name": taskSeries.name,
89 "url": taskSeries.url,
90 "locationId": taskSeries.location_id,
92 "isCompleted": task.completed,
93 "completedDate": task.completed,
94 "priority": task.priority,
95 "estimate": task.estimate,
96 "notes": list(self._get_notes(taskId, taskSeries.notes)),
98 taskDetails = self._parse_task_details(rawTaskDetails)
101 def get_task_details(self, taskId):
102 projId, rtmSeriesId, rtmTaskId = self._unpack_ids(taskId)
103 for task in self.get_tasks_with_details(projId):
104 if task["id"] == taskId:
108 def add_task(self, projId, taskName):
109 rsp = self._rtm.tasks.add(
110 timeline=self._timeline,
114 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
115 seriesId = rsp.list.taskseries.id
116 taskId = rsp.list.taskseries.task.id
117 name = rsp.list.taskseries.name
119 return self._pack_ids(projId, seriesId, taskId)
121 def set_project(self, taskId, newProjId):
122 projId, seriesId, taskId = self._unpack_ids(taskId)
123 rsp = self._rtm.tasks.moveTo(
124 timeline=self._timeline,
126 to_list_id=newProjId,
127 taskseries_id=seriesId,
130 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
132 def set_name(self, taskId, name):
133 projId, seriesId, taskId = self._unpack_ids(taskId)
134 rsp = self._rtm.tasks.setName(
135 timeline=self._timeline,
137 taskseries_id=seriesId,
141 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
143 def set_duedate(self, taskId, dueDate):
144 assert isinstance(dueDate, toolbox.Optional), (
145 "Date being set too definitively: %r" % dueDate
148 projId, seriesId, taskId = self._unpack_ids(taskId)
149 rsp = self._rtm.tasks.setDueDate(
150 timeline=self._timeline,
152 taskseries_id=seriesId,
157 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
159 def set_priority(self, taskId, priority):
160 assert isinstance(priority, toolbox.Optional), (
161 "Priority being set too definitively: %r" % priority
163 priority = str(priority.get_nothrow("N"))
164 projId, seriesId, taskId = self._unpack_ids(taskId)
166 rsp = self._rtm.tasks.setPriority(
167 timeline=self._timeline,
169 taskseries_id=seriesId,
173 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
175 def complete_task(self, taskId):
176 projId, seriesId, taskId = self._unpack_ids(taskId)
178 rsp = self._rtm.tasks.complete(
179 timeline=self._timeline,
181 taskseries_id=seriesId,
184 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
189 >>> RtMilkManager._pack_ids(123, 456)
192 return "-".join((str(id) for id in ids))
195 def _unpack_ids(ids):
197 >>> RtMilkManager._unpack_ids("123-456")
200 return ids.split("-")
202 def _get_taskseries(self, projId):
203 rsp = self._rtm.tasks.getList(
206 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
207 # @note Meta-projects return lists for each project (I think)
208 rspTasksList = rsp.tasks.list
210 if not isinstance(rspTasksList, list):
211 rspTasksList = (rspTasksList, )
213 for something in rspTasksList:
214 realProjId = something.id
217 except AttributeError:
220 if isinstance(something.taskseries, list):
221 somethingsTaskseries = something.taskseries
223 somethingsTaskseries = (something.taskseries, )
225 for taskSeries in somethingsTaskseries:
226 yield realProjId, taskSeries
228 def _get_tasks(self, taskSeries):
229 if isinstance(taskSeries.task, list):
230 tasks = taskSeries.task
232 tasks = (taskSeries.task, )
236 def _parse_task_details(self, rawTaskDetails):
238 taskDetails["id"] = rawTaskDetails["id"]
239 taskDetails["projId"] = rawTaskDetails["projId"]
240 taskDetails["name"] = rawTaskDetails["name"]
241 taskDetails["url"] = fix_url(rawTaskDetails["url"])
243 rawLocationId = rawTaskDetails["locationId"]
245 locationId = toolbox.Optional(rawLocationId)
247 locationId = toolbox.Optional()
248 taskDetails["locationId"] = locationId
250 if rawTaskDetails["dueDate"]:
251 dueDate = datetime.datetime.strptime(
252 rawTaskDetails["dueDate"],
253 "%Y-%m-%dT%H:%M:%SZ",
255 dueDate = toolbox.Optional(dueDate)
257 dueDate = toolbox.Optional()
258 taskDetails["dueDate"] = dueDate
260 taskDetails["isCompleted"] = len(rawTaskDetails["isCompleted"]) != 0
262 if rawTaskDetails["completedDate"]:
263 completedDate = datetime.datetime.strptime(
264 rawTaskDetails["completedDate"],
265 "%Y-%m-%dT%H:%M:%SZ",
267 completedDate = toolbox.Optional(completedDate)
269 completedDate = toolbox.Optional()
270 taskDetails["completedDate"] = completedDate
273 priority = toolbox.Optional(int(rawTaskDetails["priority"]))
275 priority = toolbox.Optional()
276 taskDetails["priority"] = priority
278 if rawTaskDetails["estimate"]:
279 estimate = rawTaskDetails["estimate"]
280 estimate = toolbox.Optional(estimate)
282 estimate = toolbox.Optional()
283 taskDetails["estimate"] = estimate
285 taskDetails["notes"] = rawTaskDetails["notes"]
287 rawKeys = list(rawTaskDetails.iterkeys())
289 parsedKeys = list(taskDetails.iterkeys())
291 assert rawKeys == parsedKeys, "Missing some, %r != %r" % (rawKeys, parsedKeys)
295 def _get_notes(self, taskId, notes):
298 elif isinstance(notes.note, list):
301 notes = (notes.note, )
303 projId, rtmSeriesId, rtmTaskId = self._unpack_ids(taskId)
306 noteId = self._pack_ids(projId, rtmSeriesId, rtmTaskId, note.id)
308 body = getattr(note, "$t")
315 def _populate_projects(self):
316 rsp = self._rtm.lists.getList()
317 assert rsp.stat == "ok", "Bad response: %r" % (rsp, )
323 ("isVisible", not int(t.archived)),
324 ("isMeta", not not int(t.smart)),
326 for t in rsp.lists.list