Fixed key error in some tweets
[twitter-api-cdsw] / tweepy / models.py
1 # Tweepy
2 # Copyright 2009-2010 Joshua Roesslein
3 # See LICENSE for details.
4
5 from __future__ import absolute_import, print_function
6
7 from tweepy.utils import parse_datetime, parse_html_value, parse_a_href
8
9
10 class ResultSet(list):
11     """A list like object that holds results from a Twitter API query."""
12     def __init__(self, max_id=None, since_id=None):
13         super(ResultSet, self).__init__()
14         self._max_id = max_id
15         self._since_id = since_id
16
17     @property
18     def max_id(self):
19         if self._max_id:
20             return self._max_id
21         ids = self.ids()
22         # Max_id is always set to the *smallest* id, minus one, in the set
23         return (min(ids) - 1) if ids else None
24
25     @property
26     def since_id(self):
27         if self._since_id:
28             return self._since_id
29         ids = self.ids()
30         # Since_id is always set to the *greatest* id in the set
31         return max(ids) if ids else None
32
33     def ids(self):
34         return [item.id for item in self if hasattr(item, 'id')]
35
36
37 class Model(object):
38
39     def __init__(self, api=None):
40         self._api = api
41
42     def __getstate__(self):
43         # pickle
44         pickle = dict(self.__dict__)
45         try:
46             del pickle['_api']  # do not pickle the API reference
47         except KeyError:
48             pass
49         return pickle
50
51     @classmethod
52     def parse(cls, api, json):
53         """Parse a JSON object into a model instance."""
54         raise NotImplementedError
55
56     @classmethod
57     def parse_list(cls, api, json_list):
58         """
59             Parse a list of JSON objects into
60             a result set of model instances.
61         """
62         results = ResultSet()
63         for obj in json_list:
64             if obj:
65                 results.append(cls.parse(api, obj))
66         return results
67
68     def __repr__(self):
69         state = ['%s=%s' % (k, repr(v)) for (k, v) in vars(self).items()]
70         return '%s(%s)' % (self.__class__.__name__, ', '.join(state))
71
72
73 class Status(Model):
74
75     @classmethod
76     def parse(cls, api, json):
77         status = cls(api)
78
79         # I'm not proud. Blame billg.
80         json['text'] = str(json['text'].encode('ascii', 'ignore'))[2:-1]
81         if 'user' in json:
82             json['user']['screen_name'] = str(json['user']['screen_name'].encode('ascii', 'ignore'))[2:-1]
83
84         setattr(status, '_json', json)
85         for k, v in json.items():
86             if k == 'user':
87                 user_model = getattr(api.parser.model_factory, 'user') if api else User
88                 user = user_model.parse(api, v)
89                 setattr(status, 'author', user)
90                 setattr(status, 'user', user)  # DEPRECIATED
91             elif k == 'created_at':
92                 setattr(status, k, parse_datetime(v))
93             elif k == 'source':
94                 if '<' in v:
95                     setattr(status, k, parse_html_value(v))
96                     setattr(status, 'source_url', parse_a_href(v))
97                 else:
98                     setattr(status, k, v)
99                     setattr(status, 'source_url', None)
100             elif k == 'retweeted_status':
101                 setattr(status, k, Status.parse(api, v))
102             elif k == 'place':
103                 if v is not None:
104                     setattr(status, k, Place.parse(api, v))
105                 else:
106                     setattr(status, k, None)
107             else:
108                 setattr(status, k, v)
109         return status
110
111     def destroy(self):
112         return self._api.destroy_status(self.id)
113
114     def retweet(self):
115         return self._api.retweet(self.id)
116
117     def retweets(self):
118         return self._api.retweets(self.id)
119
120     def favorite(self):
121         return self._api.create_favorite(self.id)
122
123     def __eq__(self, other):
124         if isinstance(other, Status):
125             return self.id == other.id
126
127         return NotImplemented
128
129     def __ne__(self, other):
130         result = self == other
131
132         if result is NotImplemented:
133             return result
134
135         return not result
136
137
138 class User(Model):
139
140     @classmethod
141     def parse(cls, api, json):
142         user = cls(api)
143         setattr(user, '_json', json)
144         for k, v in json.items():
145             if k == 'created_at':
146                 setattr(user, k, parse_datetime(v))
147             elif k == 'status':
148                 setattr(user, k, Status.parse(api, v))
149             elif k == 'following':
150                 # twitter sets this to null if it is false
151                 if v is True:
152                     setattr(user, k, True)
153                 else:
154                     setattr(user, k, False)
155             else:
156                 setattr(user, k, v)
157         return user
158
159     @classmethod
160     def parse_list(cls, api, json_list):
161         if isinstance(json_list, list):
162             item_list = json_list
163         else:
164             item_list = json_list['users']
165
166         results = ResultSet()
167         for obj in item_list:
168             results.append(cls.parse(api, obj))
169         return results
170
171     def timeline(self, **kargs):
172         return self._api.user_timeline(user_id=self.id, **kargs)
173
174     def friends(self, **kargs):
175         return self._api.friends(user_id=self.id, **kargs)
176
177     def followers(self, **kargs):
178         return self._api.followers(user_id=self.id, **kargs)
179
180     def follow(self):
181         self._api.create_friendship(user_id=self.id)
182         self.following = True
183
184     def unfollow(self):
185         self._api.destroy_friendship(user_id=self.id)
186         self.following = False
187
188     def lists_memberships(self, *args, **kargs):
189         return self._api.lists_memberships(user=self.screen_name,
190                                            *args,
191                                            **kargs)
192
193     def lists_subscriptions(self, *args, **kargs):
194         return self._api.lists_subscriptions(user=self.screen_name,
195                                              *args,
196                                              **kargs)
197
198     def lists(self, *args, **kargs):
199         return self._api.lists_all(user=self.screen_name,
200                                    *args,
201                                    **kargs)
202
203     def followers_ids(self, *args, **kargs):
204         return self._api.followers_ids(user_id=self.id,
205                                        *args,
206                                        **kargs)
207
208
209 class DirectMessage(Model):
210
211     @classmethod
212     def parse(cls, api, json):
213         dm = cls(api)
214         for k, v in json.items():
215             if k == 'sender' or k == 'recipient':
216                 setattr(dm, k, User.parse(api, v))
217             elif k == 'created_at':
218                 setattr(dm, k, parse_datetime(v))
219             else:
220                 setattr(dm, k, v)
221         return dm
222
223     def destroy(self):
224         return self._api.destroy_direct_message(self.id)
225
226
227 class Friendship(Model):
228
229     @classmethod
230     def parse(cls, api, json):
231         relationship = json['relationship']
232
233         # parse source
234         source = cls(api)
235         for k, v in relationship['source'].items():
236             setattr(source, k, v)
237
238         # parse target
239         target = cls(api)
240         for k, v in relationship['target'].items():
241             setattr(target, k, v)
242
243         return source, target
244
245
246 class Category(Model):
247
248     @classmethod
249     def parse(cls, api, json):
250         category = cls(api)
251         for k, v in json.items():
252             setattr(category, k, v)
253         return category
254
255
256 class SavedSearch(Model):
257
258     @classmethod
259     def parse(cls, api, json):
260         ss = cls(api)
261         for k, v in json.items():
262             if k == 'created_at':
263                 setattr(ss, k, parse_datetime(v))
264             else:
265                 setattr(ss, k, v)
266         return ss
267
268     def destroy(self):
269         return self._api.destroy_saved_search(self.id)
270
271
272 class SearchResults(ResultSet):
273
274     @classmethod
275     def parse(cls, api, json):
276         metadata = json['search_metadata']
277         results = SearchResults()
278         results.refresh_url = metadata.get('refresh_url')
279         results.completed_in = metadata.get('completed_in')
280         results.query = metadata.get('query')
281         results.count = metadata.get('count')
282         results.next_results = metadata.get('next_results')
283
284         status_model = getattr(api.parser.model_factory, 'status') if api else Status
285
286         for status in json['statuses']:
287             results.append(status_model.parse(api, status))
288         return results
289
290
291 class List(Model):
292
293     @classmethod
294     def parse(cls, api, json):
295         lst = List(api)
296         for k, v in json.items():
297             if k == 'user':
298                 setattr(lst, k, User.parse(api, v))
299             elif k == 'created_at':
300                 setattr(lst, k, parse_datetime(v))
301             else:
302                 setattr(lst, k, v)
303         return lst
304
305     @classmethod
306     def parse_list(cls, api, json_list, result_set=None):
307         results = ResultSet()
308         if isinstance(json_list, dict):
309             json_list = json_list['lists']
310         for obj in json_list:
311             results.append(cls.parse(api, obj))
312         return results
313
314     def update(self, **kargs):
315         return self._api.update_list(self.slug, **kargs)
316
317     def destroy(self):
318         return self._api.destroy_list(self.slug)
319
320     def timeline(self, **kargs):
321         return self._api.list_timeline(self.user.screen_name,
322                                        self.slug,
323                                        **kargs)
324
325     def add_member(self, id):
326         return self._api.add_list_member(self.slug, id)
327
328     def remove_member(self, id):
329         return self._api.remove_list_member(self.slug, id)
330
331     def members(self, **kargs):
332         return self._api.list_members(self.user.screen_name,
333                                       self.slug,
334                                       **kargs)
335
336     def is_member(self, id):
337         return self._api.is_list_member(self.user.screen_name,
338                                         self.slug,
339                                         id)
340
341     def subscribe(self):
342         return self._api.subscribe_list(self.user.screen_name, self.slug)
343
344     def unsubscribe(self):
345         return self._api.unsubscribe_list(self.user.screen_name, self.slug)
346
347     def subscribers(self, **kargs):
348         return self._api.list_subscribers(self.user.screen_name,
349                                           self.slug,
350                                           **kargs)
351
352     def is_subscribed(self, id):
353         return self._api.is_subscribed_list(self.user.screen_name,
354                                             self.slug,
355                                             id)
356
357
358 class Relation(Model):
359     @classmethod
360     def parse(cls, api, json):
361         result = cls(api)
362         for k, v in json.items():
363             if k == 'value' and json['kind'] in ['Tweet', 'LookedupStatus']:
364                 setattr(result, k, Status.parse(api, v))
365             elif k == 'results':
366                 setattr(result, k, Relation.parse_list(api, v))
367             else:
368                 setattr(result, k, v)
369         return result
370
371
372 class Relationship(Model):
373     @classmethod
374     def parse(cls, api, json):
375         result = cls(api)
376         for k, v in json.items():
377             if k == 'connections':
378                 setattr(result, 'is_following', 'following' in v)
379                 setattr(result, 'is_followed_by', 'followed_by' in v)
380             else:
381                 setattr(result, k, v)
382         return result
383
384
385 class JSONModel(Model):
386
387     @classmethod
388     def parse(cls, api, json):
389         return json
390
391
392 class IDModel(Model):
393
394     @classmethod
395     def parse(cls, api, json):
396         if isinstance(json, list):
397             return json
398         else:
399             return json['ids']
400
401
402 class BoundingBox(Model):
403
404     @classmethod
405     def parse(cls, api, json):
406         result = cls(api)
407         if json is not None:
408             for k, v in json.items():
409                 setattr(result, k, v)
410         return result
411
412     def origin(self):
413         """
414         Return longitude, latitude of southwest (bottom, left) corner of
415         bounding box, as a tuple.
416
417         This assumes that bounding box is always a rectangle, which
418         appears to be the case at present.
419         """
420         return tuple(self.coordinates[0][0])
421
422     def corner(self):
423         """
424         Return longitude, latitude of northeast (top, right) corner of
425         bounding box, as a tuple.
426
427         This assumes that bounding box is always a rectangle, which
428         appears to be the case at present.
429         """
430         return tuple(self.coordinates[0][2])
431
432
433 class Place(Model):
434
435     @classmethod
436     def parse(cls, api, json):
437         place = cls(api)
438         for k, v in json.items():
439             if k == 'bounding_box':
440                 # bounding_box value may be null (None.)
441                 # Example: "United States" (id=96683cc9126741d1)
442                 if v is not None:
443                     t = BoundingBox.parse(api, v)
444                 else:
445                     t = v
446                 setattr(place, k, t)
447             elif k == 'contained_within':
448                 # contained_within is a list of Places.
449                 setattr(place, k, Place.parse_list(api, v))
450             else:
451                 setattr(place, k, v)
452         return place
453
454     @classmethod
455     def parse_list(cls, api, json_list):
456         if isinstance(json_list, list):
457             item_list = json_list
458         else:
459             item_list = json_list['result']['places']
460
461         results = ResultSet()
462         for obj in item_list:
463             results.append(cls.parse(api, obj))
464         return results
465
466
467 class Media(Model):
468
469     @classmethod
470     def parse(cls, api, json):
471         media = cls(api)
472         for k, v in json.items():
473             setattr(media, k, v)
474         return media
475
476
477 class ModelFactory(object):
478     """
479     Used by parsers for creating instances
480     of models. You may subclass this factory
481     to add your own extended models.
482     """
483
484     status = Status
485     user = User
486     direct_message = DirectMessage
487     friendship = Friendship
488     saved_search = SavedSearch
489     search_results = SearchResults
490     category = Category
491     list = List
492     relation = Relation
493     relationship = Relationship
494     media = Media
495
496     json = JSONModel
497     ids = IDModel
498     place = Place
499     bounding_box = BoundingBox

Benjamin Mako Hill || Want to submit a patch?