1 # Python library for Remember The Milk API
3 __author__
= 'Sridhar Ratnakumar <http://nearfar.org/>'
15 _use_simplejson
= False
18 _use_simplejson
= True
23 SERVICE_URL
= 'http://api.rememberthemilk.com/services/rest/'
24 AUTH_SERVICE_URL
= 'http://www.rememberthemilk.com/services/auth/'
28 class RTMError(Exception): pass
30 class RTMAPIError(RTMError
): pass
32 class AuthStateMachine(object):
34 class NoData(RTMError
): pass
36 def __init__(self
, states
):
40 def dataReceived(self
, state
, datum
):
41 if state
not in self
.states
:
42 raise RTMError
, "Invalid state <%s>" % state
43 self
.data
[state
] = datum
46 if state
in self
.data
:
47 return self
.data
[state
]
49 raise AuthStateMachine
.NoData
, 'No data for <%s>' % state
54 def __init__(self
, apiKey
, secret
, token
=None, DEBUG
=False):
57 self
.authInfo
= AuthStateMachine(['frob', 'token'])
60 # this enables one to do 'rtm.tasks.getList()', for example
61 for prefix
, methods
in API
.items():
63 RTMAPICategory(self
, prefix
, methods
))
66 self
.authInfo
.dataReceived('token', token
)
68 def _sign(self
, params
):
69 "Sign the parameters with MD5 hash"
70 pairs
= ''.join(['%s%s' % (k
,v
) for k
,v
in sortedItems(params
)])
71 return md5(self
.secret
+pairs
).hexdigest()
73 def get(self
, **params
):
74 "Get the XML response for the passed `params`."
75 params
['api_key'] = self
.apiKey
76 params
['format'] = 'json'
77 params
['api_sig'] = self
._sign
(params
)
79 json
= openURL(SERVICE_URL
, params
).read()
83 data
= dottedDict('ROOT', simplejson
.loads(json
))
85 data
= dottedJSON(json
)
88 if rsp
.stat
== 'fail':
89 raise RTMAPIError
, 'API call failed - %s (%s)' % (
90 rsp
.err
.msg
, rsp
.err
.code
)
95 rsp
= self
.get(method
='rtm.auth.getFrob')
96 self
.authInfo
.dataReceived('frob', rsp
.frob
)
101 frob
= self
.authInfo
.get('frob')
102 except AuthStateMachine
.NoData
:
103 frob
= self
.getNewFrob()
106 'api_key': self
.apiKey
,
110 params
['api_sig'] = self
._sign
(params
)
111 return AUTH_SERVICE_URL
+ '?' + urllib
.urlencode(params
)
114 frob
= self
.authInfo
.get('frob')
115 rsp
= self
.get(method
='rtm.auth.getToken', frob
=frob
)
116 self
.authInfo
.dataReceived('token', rsp
.auth
.token
)
117 return rsp
.auth
.token
119 class RTMAPICategory
:
120 "See the `API` structure and `RTM.__init__`"
122 def __init__(self
, rtm
, prefix
, methods
):
125 self
.methods
= methods
127 def __getattr__(self
, attr
):
128 if attr
in self
.methods
:
129 rargs
, oargs
= self
.methods
[attr
]
130 aname
= 'rtm.%s.%s' % (self
.prefix
, attr
)
131 return lambda **params
: self
.callMethod(
132 aname
, rargs
, oargs
, **params
)
134 raise AttributeError, 'No such attribute: %s' % attr
136 def callMethod(self
, aname
, rargs
, oargs
, **params
):
138 for requiredArg
in rargs
:
139 if requiredArg
not in params
:
140 raise TypeError, 'Required parameter (%s) missing' % requiredArg
143 if param
not in rargs
+ oargs
:
144 warnings
.warn('Invalid parameter (%s)' % param
)
146 return self
.rtm
.get(method
=aname
,
147 auth_token
=self
.rtm
.authInfo
.get('token'),
154 def sortedItems(dictionary
):
155 "Return a list of (key, value) sorted based on keys"
156 keys
= dictionary
.keys()
159 yield key
, dictionary
[key
]
161 def openURL(url
, queryArgs
=None):
163 url
= url
+ '?' + urllib
.urlencode(queryArgs
)
166 return urllib
.urlopen(url
)
168 class dottedDict(object):
169 "Make dictionary items accessible via the object-dot notation."
171 def __init__(self
, name
, dictionary
):
174 if type(dictionary
) is dict:
175 for key
, value
in dictionary
.items():
176 if type(value
) is dict:
177 value
= dottedDict(key
, value
)
178 elif type(value
) in (list, tuple):
179 value
= [dottedDict('%s_%d' % (key
, i
), item
)
180 for i
, item
in indexed(value
)]
181 setattr(self
, key
, value
)
184 children
= [c
for c
in dir(self
) if not c
.startswith('_')]
185 return 'dotted <%s> : %s' % (
190 def safeEval(string
):
191 return eval(string
, {}, {})
193 def dottedJSON(json
):
194 return dottedDict('ROOT', safeEval(json
))
208 [('auth_token'), ()],
216 [('timeline', 'contact'), ()],
218 [('timeline', 'contact_id'), ()],
224 [('timeline', 'group'), ()],
226 [('timeline', 'group_id', 'contact_id'), ()],
228 [('timeline', 'group_id'), ()],
232 [('timeline', 'group_id', 'contact_id'), ()],
236 [('timeline', 'name'), ('filter'), ()],
238 [('timeline', 'list_id'), ()],
240 [('timeline', 'list_id'), ()],
244 [('timeline'), ('list_id'), ()],
246 [('timeline', 'list_id', 'name'), ()],
248 [('timeline'), ('list_id'), ()],
256 [('methodName',), ()],
266 [('timeline', 'name',), ('list_id', 'parse',)],
268 [('timeline', 'list_id', 'taskseries_id', 'task_id', 'tags'),
271 [('timeline', 'list_id', 'taskseries_id', 'task_id',), ()],
273 [('timeline', 'list_id', 'taskseries_id', 'task_id'), ()],
276 ('list_id', 'filter', 'last_sync')],
278 [('timeline', 'list_id', 'taskseries_id', 'task_id', 'direction'),
281 [('timeline', 'from_list_id', 'to_list_id', 'taskseries_id', 'task_id'),
284 [('timeline', 'list_id', 'taskseries_id', 'task_id'),
287 [('timeline', 'list_id', 'taskseries_id', 'task_id', 'tags'),
290 [('timeline', 'list_id', 'taskseries_id', 'task_id'),
291 ('due', 'has_due_time', 'parse')],
293 [('timeline', 'list_id', 'taskseries_id', 'task_id'),
296 [('timeline', 'list_id', 'taskseries_id', 'task_id'),
299 [('timeline', 'list_id', 'taskseries_id', 'task_id', 'name'),
302 [('timeline', 'list_id', 'taskseries_id', 'task_id'),
305 [('timeline', 'list_id', 'taskseries_id', 'task_id'),
308 [('timeline', 'list_id', 'taskseries_id', 'task_id'),
311 [('timeline', 'list_id', 'taskseries_id', 'task_id'),
314 [('timeline', 'list_id', 'taskseries_id', 'task_id'),
319 [('timeline', 'list_id', 'taskseries_id', 'task_id', 'note_title', 'note_text'), ()],
321 [('timeline', 'note_id'), ()],
323 [('timeline', 'note_id', 'note_title', 'note_text'), ()]
333 [('to_timezone',), ('from_timezone', 'to_timezone', 'time')],
335 [('text',), ('timezone', 'dateformat')]
347 [('timeline', 'transaction_id'), ()]
351 def createRTM(apiKey
, secret
, token
=None, DEBUG
=DEBUG
):
352 rtm
= RTM(apiKey
, secret
, token
, DEBUG
)
355 print 'No token found'
356 print 'Give me access here:', rtm
.getAuthURL()
357 raw_input('Press enter once you gave access')
358 print 'Note down this token for future use:', rtm
.getToken()
362 def test(apiKey
, secret
, token
=None):
363 rtm
= createRTM(apiKey
, secret
, token
)
365 rspTasks
= rtm
.tasks
.getList(filter='dueWithin:"1 week of today"')
366 print [t
.name
for t
in rspTasks
.tasks
.list.taskseries
]
367 print rspTasks
.tasks
.list.id
369 rspLists
= rtm
.lists
.getList()
370 # print rspLists.lists.list
371 print [(x
.name
, x
.id) for x
in rspLists
.lists
.list]