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

Benjamin Mako Hill || Want to submit a patch?