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

Benjamin Mako Hill || Want to submit a patch?