Updated packages and code to python3. Won't work with python 2
[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         json['user']['screen_name'] = str(json['user']['screen_name'].encode('ascii', 'ignore'))[2:-1]
82
83         setattr(status, '_json', json)
84         for k, v in json.items():
85             if k == 'user':
86                 user_model = getattr(api.parser.model_factory, 'user') if api else User
87                 user = user_model.parse(api, v)
88                 setattr(status, 'author', user)
89                 setattr(status, 'user', user)  # DEPRECIATED
90             elif k == 'created_at':
91                 setattr(status, k, parse_datetime(v))
92             elif k == 'source':
93                 if '<' in v:
94                     setattr(status, k, parse_html_value(v))
95                     setattr(status, 'source_url', parse_a_href(v))
96                 else:
97                     setattr(status, k, v)
98                     setattr(status, 'source_url', None)
99             elif k == 'retweeted_status':
100                 setattr(status, k, Status.parse(api, v))
101             elif k == 'place':
102                 if v is not None:
103                     setattr(status, k, Place.parse(api, v))
104                 else:
105                     setattr(status, k, None)
106             else:
107                 setattr(status, k, v)
108         return status
109
110     def destroy(self):
111         return self._api.destroy_status(self.id)
112
113     def retweet(self):
114         return self._api.retweet(self.id)
115
116     def retweets(self):
117         return self._api.retweets(self.id)
118
119     def favorite(self):
120         return self._api.create_favorite(self.id)
121
122     def __eq__(self, other):
123         if isinstance(other, Status):
124             return self.id == other.id
125
126         return NotImplemented
127
128     def __ne__(self, other):
129         result = self == other
130
131         if result is NotImplemented:
132             return result
133
134         return not result
135
136
137 class User(Model):
138
139     @classmethod
140     def parse(cls, api, json):
141         user = cls(api)
142         setattr(user, '_json', json)
143         for k, v in json.items():
144             if k == 'created_at':
145                 setattr(user, k, parse_datetime(v))
146             elif k == 'status':
147                 setattr(user, k, Status.parse(api, v))
148             elif k == 'following':
149                 # twitter sets this to null if it is false
150                 if v is True:
151                     setattr(user, k, True)
152                 else:
153                     setattr(user, k, False)
154             else:
155                 setattr(user, k, v)
156         return user
157
158     @classmethod
159     def parse_list(cls, api, json_list):
160         if isinstance(json_list, list):
161             item_list = json_list
162         else:
163             item_list = json_list['users']
164
165         results = ResultSet()
166         for obj in item_list:
167             results.append(cls.parse(api, obj))
168         return results
169
170     def timeline(self, **kargs):
171         return self._api.user_timeline(user_id=self.id, **kargs)
172
173     def friends(self, **kargs):
174         return self._api.friends(user_id=self.id, **kargs)
175
176     def followers(self, **kargs):
177         return self._api.followers(user_id=self.id, **kargs)
178
179     def follow(self):
180         self._api.create_friendship(user_id=self.id)
181         self.following = True
182
183     def unfollow(self):
184         self._api.destroy_friendship(user_id=self.id)
185         self.following = False
186
187     def lists_memberships(self, *args, **kargs):
188         return self._api.lists_memberships(user=self.screen_name,
189                                            *args,
190                                            **kargs)
191
192     def lists_subscriptions(self, *args, **kargs):
193         return self._api.lists_subscriptions(user=self.screen_name,
194                                              *args,
195                                              **kargs)
196
197     def lists(self, *args, **kargs):
198         return self._api.lists_all(user=self.screen_name,
199                                    *args,
200                                    **kargs)
201
202     def followers_ids(self, *args, **kargs):
203         return self._api.followers_ids(user_id=self.id,
204                                        *args,
205                                        **kargs)
206
207
208 class DirectMessage(Model):
209
210     @classmethod
211     def parse(cls, api, json):
212         dm = cls(api)
213         for k, v in json.items():
214             if k == 'sender' or k == 'recipient':
215                 setattr(dm, k, User.parse(api, v))
216             elif k == 'created_at':
217                 setattr(dm, k, parse_datetime(v))
218             else:
219                 setattr(dm, k, v)
220         return dm
221
222     def destroy(self):
223         return self._api.destroy_direct_message(self.id)
224
225
226 class Friendship(Model):
227
228     @classmethod
229     def parse(cls, api, json):
230         relationship = json['relationship']
231
232         # parse source
233         source = cls(api)
234         for k, v in relationship['source'].items():
235             setattr(source, k, v)
236
237         # parse target
238         target = cls(api)
239         for k, v in relationship['target'].items():
240             setattr(target, k, v)
241
242         return source, target
243
244
245 class Category(Model):
246
247     @classmethod
248     def parse(cls, api, json):
249         category = cls(api)
250         for k, v in json.items():
251             setattr(category, k, v)
252         return category
253
254
255 class SavedSearch(Model):
256
257     @classmethod
258     def parse(cls, api, json):
259         ss = cls(api)
260         for k, v in json.items():
261             if k == 'created_at':
262                 setattr(ss, k, parse_datetime(v))
263             else:
264                 setattr(ss, k, v)
265         return ss
266
267     def destroy(self):
268         return self._api.destroy_saved_search(self.id)
269
270
271 class SearchResults(ResultSet):
272
273     @classmethod
274     def parse(cls, api, json):
275         metadata = json['search_metadata']
276         results = SearchResults()
277         results.refresh_url = metadata.get('refresh_url')
278         results.completed_in = metadata.get('completed_in')
279         results.query = metadata.get('query')
280         results.count = metadata.get('count')
281         results.next_results = metadata.get('next_results')
282
283         status_model = getattr(api.parser.model_factory, 'status') if api else Status
284
285         for status in json['statuses']:
286             results.append(status_model.parse(api, status))
287         return results
288
289
290 class List(Model):
291
292     @classmethod
293     def parse(cls, api, json):
294         lst = List(api)
295         for k, v in json.items():
296             if k == 'user':
297                 setattr(lst, k, User.parse(api, v))
298             elif k == 'created_at':
299                 setattr(lst, k, parse_datetime(v))
300             else:
301                 setattr(lst, k, v)
302         return lst
303
304     @classmethod
305     def parse_list(cls, api, json_list, result_set=None):
306         results = ResultSet()
307         if isinstance(json_list, dict):
308             json_list = json_list['lists']
309         for obj in json_list:
310             results.append(cls.parse(api, obj))
311         return results
312
313     def update(self, **kargs):
314         return self._api.update_list(self.slug, **kargs)
315
316     def destroy(self):
317         return self._api.destroy_list(self.slug)
318
319     def timeline(self, **kargs):
320         return self._api.list_timeline(self.user.screen_name,
321                                        self.slug,
322                                        **kargs)
323
324     def add_member(self, id):
325         return self._api.add_list_member(self.slug, id)
326
327     def remove_member(self, id):
328         return self._api.remove_list_member(self.slug, id)
329
330     def members(self, **kargs):
331         return self._api.list_members(self.user.screen_name,
332                                       self.slug,
333                                       **kargs)
334
335     def is_member(self, id):
336         return self._api.is_list_member(self.user.screen_name,
337                                         self.slug,
338                                         id)
339
340     def subscribe(self):
341         return self._api.subscribe_list(self.user.screen_name, self.slug)
342
343     def unsubscribe(self):
344         return self._api.unsubscribe_list(self.user.screen_name, self.slug)
345
346     def subscribers(self, **kargs):
347         return self._api.list_subscribers(self.user.screen_name,
348                                           self.slug,
349                                           **kargs)
350
351     def is_subscribed(self, id):
352         return self._api.is_subscribed_list(self.user.screen_name,
353                                             self.slug,
354                                             id)
355
356
357 class Relation(Model):
358     @classmethod
359     def parse(cls, api, json):
360         result = cls(api)
361         for k, v in json.items():
362             if k == 'value' and json['kind'] in ['Tweet', 'LookedupStatus']:
363                 setattr(result, k, Status.parse(api, v))
364             elif k == 'results':
365                 setattr(result, k, Relation.parse_list(api, v))
366             else:
367                 setattr(result, k, v)
368         return result
369
370
371 class Relationship(Model):
372     @classmethod
373     def parse(cls, api, json):
374         result = cls(api)
375         for k, v in json.items():
376             if k == 'connections':
377                 setattr(result, 'is_following', 'following' in v)
378                 setattr(result, 'is_followed_by', 'followed_by' in v)
379             else:
380                 setattr(result, k, v)
381         return result
382
383
384 class JSONModel(Model):
385
386     @classmethod
387     def parse(cls, api, json):
388         return json
389
390
391 class IDModel(Model):
392
393     @classmethod
394     def parse(cls, api, json):
395         if isinstance(json, list):
396             return json
397         else:
398             return json['ids']
399
400
401 class BoundingBox(Model):
402
403     @classmethod
404     def parse(cls, api, json):
405         result = cls(api)
406         if json is not None:
407             for k, v in json.items():
408                 setattr(result, k, v)
409         return result
410
411     def origin(self):
412         """
413         Return longitude, latitude of southwest (bottom, left) corner of
414         bounding box, as a tuple.
415
416         This assumes that bounding box is always a rectangle, which
417         appears to be the case at present.
418         """
419         return tuple(self.coordinates[0][0])
420
421     def corner(self):
422         """
423         Return longitude, latitude of northeast (top, right) corner of
424         bounding box, as a tuple.
425
426         This assumes that bounding box is always a rectangle, which
427         appears to be the case at present.
428         """
429         return tuple(self.coordinates[0][2])
430
431
432 class Place(Model):
433
434     @classmethod
435     def parse(cls, api, json):
436         place = cls(api)
437         for k, v in json.items():
438             if k == 'bounding_box':
439                 # bounding_box value may be null (None.)
440                 # Example: "United States" (id=96683cc9126741d1)
441                 if v is not None:
442                     t = BoundingBox.parse(api, v)
443                 else:
444                     t = v
445                 setattr(place, k, t)
446             elif k == 'contained_within':
447                 # contained_within is a list of Places.
448                 setattr(place, k, Place.parse_list(api, v))
449             else:
450                 setattr(place, k, v)
451         return place
452
453     @classmethod
454     def parse_list(cls, api, json_list):
455         if isinstance(json_list, list):
456             item_list = json_list
457         else:
458             item_list = json_list['result']['places']
459
460         results = ResultSet()
461         for obj in item_list:
462             results.append(cls.parse(api, obj))
463         return results
464
465
466 class Media(Model):
467
468     @classmethod
469     def parse(cls, api, json):
470         media = cls(api)
471         for k, v in json.items():
472             setattr(media, k, v)
473         return media
474
475
476 class ModelFactory(object):
477     """
478     Used by parsers for creating instances
479     of models. You may subclass this factory
480     to add your own extended models.
481     """
482
483     status = Status
484     user = User
485     direct_message = DirectMessage
486     friendship = Friendship
487     saved_search = SavedSearch
488     search_results = SearchResults
489     category = Category
490     list = List
491     relation = Relation
492     relationship = Relationship
493     media = Media
494
495     json = JSONModel
496     ids = IDModel
497     place = Place
498     bounding_box = BoundingBox

Benjamin Mako Hill || Want to submit a patch?