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

Benjamin Mako Hill || Want to submit a patch?