Merge pull request #3 from guyrt/master
[twitter-api-cdsw] / tweepy / api.py
1 # Tweepy
2 # Copyright 2009-2010 Joshua Roesslein
3 # See LICENSE for details.
4
5 from __future__ import print_function
6
7 import os
8 import mimetypes
9
10 import six
11
12 from tweepy.binder import bind_api
13 from tweepy.error import TweepError
14 from tweepy.parsers import ModelParser, Parser
15 from tweepy.utils import list_to_csv
16
17
18 class API(object):
19     """Twitter API"""
20
21     def __init__(self, auth_handler=None,
22                  host='api.twitter.com', search_host='search.twitter.com',
23                  upload_host='upload.twitter.com', cache=None, api_root='/1.1',
24                  search_root='', upload_root='/1.1', retry_count=0,
25                  retry_delay=0, retry_errors=None, timeout=60, parser=None,
26                  compression=False, wait_on_rate_limit=False,
27                  wait_on_rate_limit_notify=False, proxy=''):
28         """ Api instance Constructor
29
30         :param auth_handler:
31         :param host:  url of the server of the rest api, default:'api.twitter.com'
32         :param search_host: url of the search server, default:'search.twitter.com'
33         :param upload_host: url of the upload server, default:'upload.twitter.com'
34         :param cache: Cache to query if a GET method is used, default:None
35         :param api_root: suffix of the api version, default:'/1.1'
36         :param search_root: suffix of the search version, default:''
37         :param upload_root: suffix of the upload version, default:'/1.1'
38         :param retry_count: number of allowed retries, default:0
39         :param retry_delay: delay in second between retries, default:0
40         :param retry_errors: default:None
41         :param timeout: delay before to consider the request as timed out in seconds, default:60
42         :param parser: ModelParser instance to parse the responses, default:None
43         :param compression: If the response is compressed, default:False
44         :param wait_on_rate_limit: If the api wait when it hits the rate limit, default:False
45         :param wait_on_rate_limit_notify: If the api print a notification when the rate limit is hit, default:False
46         :param proxy: Url to use as proxy during the HTTP request, default:''
47
48         :raise TypeError: If the given parser is not a ModelParser instance.
49         """
50         self.auth = auth_handler
51         self.host = host
52         self.search_host = search_host
53         self.upload_host = upload_host
54         self.api_root = api_root
55         self.search_root = search_root
56         self.upload_root = upload_root
57         self.cache = cache
58         self.compression = compression
59         self.retry_count = retry_count
60         self.retry_delay = retry_delay
61         self.retry_errors = retry_errors
62         self.timeout = timeout
63         self.wait_on_rate_limit = wait_on_rate_limit
64         self.wait_on_rate_limit_notify = wait_on_rate_limit_notify
65         self.parser = parser or ModelParser()
66         self.proxy = {}
67         if proxy:
68             self.proxy['https'] = proxy
69
70         # Attempt to explain more clearly the parser argument requirements
71         # https://github.com/tweepy/tweepy/issues/421
72         #
73         parser_type = Parser
74         if not isinstance(self.parser, parser_type):
75             raise TypeError(
76                 '"parser" argument has to be an instance of "{required}".'
77                 ' It is currently a {actual}.'.format(
78                     required=parser_type.__name__,
79                     actual=type(self.parser)
80                 )
81             )
82
83     @property
84     def home_timeline(self):
85         """ :reference: https://dev.twitter.com/rest/reference/get/statuses/home_timeline
86             :allowed_param:'since_id', 'max_id', 'count'
87         """
88         return bind_api(
89             api=self,
90             path='/statuses/home_timeline.json',
91             payload_type='status', payload_list=True,
92             allowed_param=['since_id', 'max_id', 'count'],
93             require_auth=True
94         )
95
96     def statuses_lookup(self, id_, include_entities=None,
97                         trim_user=None, map_=None):
98         return self._statuses_lookup(list_to_csv(id_), include_entities,
99                                      trim_user, map_)
100
101     @property
102     def _statuses_lookup(self):
103         """ :reference: https://dev.twitter.com/rest/reference/get/statuses/lookup
104             :allowed_param:'id', 'include_entities', 'trim_user', 'map'
105         """
106         return bind_api(
107             api=self,
108             path='/statuses/lookup.json',
109             payload_type='status', payload_list=True,
110             allowed_param=['id', 'include_entities', 'trim_user', 'map'],
111             require_auth=True
112         )
113
114     @property
115     def user_timeline(self):
116         """ :reference: https://dev.twitter.com/rest/reference/get/statuses/user_timeline
117             :allowed_param:'id', 'user_id', 'screen_name', 'since_id'
118         """
119         return bind_api(
120             api=self,
121             path='/statuses/user_timeline.json',
122             payload_type='status', payload_list=True,
123             allowed_param=['id', 'user_id', 'screen_name', 'since_id',
124                            'max_id', 'count', 'include_rts']
125         )
126
127     @property
128     def mentions_timeline(self):
129         """ :reference: https://dev.twitter.com/rest/reference/get/statuses/mentions_timeline
130             :allowed_param:'since_id', 'max_id', 'count'
131         """
132         return bind_api(
133             api=self,
134             path='/statuses/mentions_timeline.json',
135             payload_type='status', payload_list=True,
136             allowed_param=['since_id', 'max_id', 'count'],
137             require_auth=True
138         )
139
140     @property
141     def related_results(self):
142         """ :reference: https://dev.twitter.com/docs/api/1.1/get/related_results/show/%3id.format
143             :allowed_param:'id'
144         """
145         return bind_api(
146             api=self,
147             path='/related_results/show/{id}.json',
148             payload_type='relation', payload_list=True,
149             allowed_param=['id'],
150             require_auth=False
151         )
152
153     @property
154     def retweets_of_me(self):
155         """ :reference: https://dev.twitter.com/rest/reference/get/statuses/retweets_of_me
156             :allowed_param:'since_id', 'max_id', 'count'
157         """
158         return bind_api(
159             api=self,
160             path='/statuses/retweets_of_me.json',
161             payload_type='status', payload_list=True,
162             allowed_param=['since_id', 'max_id', 'count'],
163             require_auth=True
164         )
165
166     @property
167     def get_status(self):
168         """ :reference: https://dev.twitter.com/rest/reference/get/statuses/show/%3Aid
169             :allowed_param:'id'
170         """
171         return bind_api(
172             api=self,
173             path='/statuses/show.json',
174             payload_type='status',
175             allowed_param=['id']
176         )
177
178     def update_status(self, media_ids=None, *args, **kwargs):
179         """ :reference: https://dev.twitter.com/rest/reference/post/statuses/update
180             :allowed_param:'status', 'in_reply_to_status_id', 'lat', 'long', 'source', 'place_id', 'display_coordinates', 'media_ids'
181         """
182         post_data = {}
183         if media_ids is not None:
184             post_data["media_ids"] = list_to_csv(media_ids)
185         
186         return bind_api(
187             api=self,
188             path='/statuses/update.json',
189             method='POST',
190             payload_type='status',
191             allowed_param=['status', 'in_reply_to_status_id', 'lat', 'long', 'source', 'place_id', 'display_coordinates'],
192             require_auth=True
193         )(post_data=post_data, *args, **kwargs)
194
195     def media_upload(self, filename, *args, **kwargs):
196         """ :reference: https://dev.twitter.com/rest/reference/post/media/upload
197             :allowed_param:
198         """
199         f = kwargs.pop('file', None)
200         headers, post_data = API._pack_image(filename, 3072, form_field='media', f=f)
201         kwargs.update({'headers': headers, 'post_data': post_data})
202
203         return bind_api(
204             api=self,
205             path='/media/upload.json',
206             method='POST',
207             payload_type='media',
208             allowed_param=[],
209             require_auth=True,
210             upload_api=True
211         )(*args, **kwargs)
212
213     def update_with_media(self, filename, *args, **kwargs):
214         """ :reference: https://dev.twitter.com/rest/reference/post/statuses/update_with_media
215             :allowed_param:'status', 'possibly_sensitive', 'in_reply_to_status_id', 'lat', 'long', 'place_id', 'display_coordinates'
216         """
217         f = kwargs.pop('file', None)
218         headers, post_data = API._pack_image(filename, 3072, form_field='media[]', f=f)
219         kwargs.update({'headers': headers, 'post_data': post_data})
220
221         return bind_api(
222             api=self,
223             path='/statuses/update_with_media.json',
224             method='POST',
225             payload_type='status',
226             allowed_param=[
227                 'status', 'possibly_sensitive', 'in_reply_to_status_id', 'lat', 'long',
228                 'place_id', 'display_coordinates'
229             ],
230             require_auth=True
231         )(*args, **kwargs)
232
233     @property
234     def destroy_status(self):
235         """ :reference: https://dev.twitter.com/rest/reference/post/statuses/destroy/%3Aid
236             :allowed_param:'id'
237         """
238         return bind_api(
239             api=self,
240             path='/statuses/destroy/{id}.json',
241             method='POST',
242             payload_type='status',
243             allowed_param=['id'],
244             require_auth=True
245         )
246
247     @property
248     def retweet(self):
249         """ :reference: https://dev.twitter.com/rest/reference/post/statuses/retweet/%3Aid
250             :allowed_param:'id'
251         """
252         return bind_api(
253             api=self,
254             path='/statuses/retweet/{id}.json',
255             method='POST',
256             payload_type='status',
257             allowed_param=['id'],
258             require_auth=True
259         )
260
261     @property
262     def retweets(self):
263         """ :reference: https://dev.twitter.com/rest/reference/get/statuses/retweets/%3Aid
264             :allowed_param:'id', 'count'
265         """
266         return bind_api(
267             api=self,
268             path='/statuses/retweets/{id}.json',
269             payload_type='status', payload_list=True,
270             allowed_param=['id', 'count'],
271             require_auth=True
272         )
273
274     @property
275     def retweeters(self):
276         """ :reference: https://dev.twitter.com/rest/reference/get/statuses/retweeters/ids
277             :allowed_param:'id', 'cursor', 'stringify_ids
278         """
279         return bind_api(
280             api=self,
281             path='/statuses/retweeters/ids.json',
282             payload_type='ids',
283             allowed_param=['id', 'cursor', 'stringify_ids']
284         )
285
286     @property
287     def get_user(self):
288         """ :reference: https://dev.twitter.com/rest/reference/get/users/show
289             :allowed_param:'id', 'user_id', 'screen_name'
290         """
291         return bind_api(
292             api=self,
293             path='/users/show.json',
294             payload_type='user',
295             allowed_param=['id', 'user_id', 'screen_name']
296         )
297
298     @property
299     def get_oembed(self):
300         """ :reference: https://dev.twitter.com/rest/reference/get/statuses/oembed
301             :allowed_param:'id', 'url', 'maxwidth', 'hide_media', 'omit_script', 'align', 'related', 'lang'
302         """
303         return bind_api(
304             api=self,
305             path='/statuses/oembed.json',
306             payload_type='json',
307             allowed_param=['id', 'url', 'maxwidth', 'hide_media', 'omit_script', 'align', 'related', 'lang']
308         )
309
310     def lookup_users(self, user_ids=None, screen_names=None, include_entities=None):
311         """ Perform bulk look up of users from user ID or screenname """
312         post_data = {}
313         if include_entities is not None:
314             include_entities = 'true' if include_entities else 'false'
315             post_data['include_entities'] = include_entities
316         if user_ids:
317             post_data['user_id'] = list_to_csv(user_ids)
318         if screen_names:
319             post_data['screen_name'] = list_to_csv(screen_names)
320
321         return self._lookup_users(post_data=post_data)
322
323     @property
324     def _lookup_users(self):
325         """ :reference: https://dev.twitter.com/rest/reference/get/users/lookup
326             allowed_param='user_id', 'screen_name', 'include_entities'
327         """
328         return bind_api(
329             api=self,
330             path='/users/lookup.json',
331             payload_type='user', payload_list=True,
332             method='POST',
333         )
334
335     def me(self):
336         """ Get the authenticated user """
337         return self.get_user(screen_name=self.auth.get_username())
338
339     @property
340     def search_users(self):
341         """ :reference: https://dev.twitter.com/rest/reference/get/users/search
342             :allowed_param:'q', 'count', 'page'
343         """
344         return bind_api(
345             api=self,
346             path='/users/search.json',
347             payload_type='user', payload_list=True,
348             require_auth=True,
349             allowed_param=['q', 'count', 'page']
350         )
351
352     @property
353     def suggested_users(self):
354         """ :reference: https://dev.twitter.com/rest/reference/get/users/suggestions/%3Aslug
355             :allowed_param:'slug', 'lang'
356         """
357         return bind_api(
358             api=self,
359             path='/users/suggestions/{slug}.json',
360             payload_type='user', payload_list=True,
361             require_auth=True,
362             allowed_param=['slug', 'lang']
363         )
364
365     @property
366     def suggested_categories(self):
367         """ :reference: https://dev.twitter.com/rest/reference/get/users/suggestions
368             :allowed_param:'lang'
369         """
370         return bind_api(
371             api=self,
372             path='/users/suggestions.json',
373             payload_type='category', payload_list=True,
374             allowed_param=['lang'],
375             require_auth=True
376         )
377
378     @property
379     def suggested_users_tweets(self):
380         """ :reference: https://dev.twitter.com/rest/reference/get/users/suggestions/%3Aslug/members
381             :allowed_param:'slug'
382         """
383         return bind_api(
384             api=self,
385             path='/users/suggestions/{slug}/members.json',
386             payload_type='status', payload_list=True,
387             allowed_param=['slug'],
388             require_auth=True
389         )
390
391     @property
392     def direct_messages(self):
393         """ :reference: https://dev.twitter.com/rest/reference/get/direct_messages
394             :allowed_param:'since_id', 'max_id', 'count'
395         """
396         return bind_api(
397             api=self,
398             path='/direct_messages.json',
399             payload_type='direct_message', payload_list=True,
400             allowed_param=['since_id', 'max_id', 'count'],
401             require_auth=True
402         )
403
404     @property
405     def get_direct_message(self):
406         """ :reference: https://dev.twitter.com/rest/reference/get/direct_messages/show
407             :allowed_param:'id'
408         """
409         return bind_api(
410             api=self,
411             path='/direct_messages/show/{id}.json',
412             payload_type='direct_message',
413             allowed_param=['id'],
414             require_auth=True
415         )
416
417     @property
418     def sent_direct_messages(self):
419         """ :reference: https://dev.twitter.com/rest/reference/get/direct_messages/sent
420             :allowed_param:'since_id', 'max_id', 'count', 'page'
421         """
422         return bind_api(
423             api=self,
424             path='/direct_messages/sent.json',
425             payload_type='direct_message', payload_list=True,
426             allowed_param=['since_id', 'max_id', 'count', 'page'],
427             require_auth=True
428         )
429
430     @property
431     def send_direct_message(self):
432         """ :reference: https://dev.twitter.com/rest/reference/post/direct_messages/new
433             :allowed_param:'user', 'screen_name', 'user_id', 'text'
434         """
435         return bind_api(
436             api=self,
437             path='/direct_messages/new.json',
438             method='POST',
439             payload_type='direct_message',
440             allowed_param=['user', 'screen_name', 'user_id', 'text'],
441             require_auth=True
442         )
443
444     @property
445     def destroy_direct_message(self):
446         """ :reference: https://dev.twitter.com/rest/reference/post/direct_messages/destroy
447             :allowed_param:'id'
448         """
449         return bind_api(
450             api=self,
451             path='/direct_messages/destroy.json',
452             method='POST',
453             payload_type='direct_message',
454             allowed_param=['id'],
455             require_auth=True
456         )
457
458     @property
459     def create_friendship(self):
460         """ :reference: https://dev.twitter.com/rest/reference/post/friendships/create
461             :allowed_param:'id', 'user_id', 'screen_name', 'follow'
462         """
463         return bind_api(
464             api=self,
465             path='/friendships/create.json',
466             method='POST',
467             payload_type='user',
468             allowed_param=['id', 'user_id', 'screen_name', 'follow'],
469             require_auth=True
470         )
471
472     @property
473     def destroy_friendship(self):
474         """ :reference: https://dev.twitter.com/rest/reference/post/friendships/destroy
475             :allowed_param:'id', 'user_id', 'screen_name'
476         """
477         return bind_api(
478             api=self,
479             path='/friendships/destroy.json',
480             method='POST',
481             payload_type='user',
482             allowed_param=['id', 'user_id', 'screen_name'],
483             require_auth=True
484         )
485
486     @property
487     def show_friendship(self):
488         """ :reference: https://dev.twitter.com/rest/reference/get/friendships/show
489             :allowed_param:'source_id', 'source_screen_name'
490         """
491         return bind_api(
492             api=self,
493             path='/friendships/show.json',
494             payload_type='friendship',
495             allowed_param=['source_id', 'source_screen_name',
496                            'target_id', 'target_screen_name']
497         )
498
499     def lookup_friendships(self, user_ids=None, screen_names=None):
500         """ Perform bulk look up of friendships from user ID or screenname """
501         return self._lookup_friendships(list_to_csv(user_ids), list_to_csv(screen_names))
502
503     @property
504     def _lookup_friendships(self):
505         """ :reference: https://dev.twitter.com/rest/reference/get/friendships/lookup
506             :allowed_param:'user_id', 'screen_name'
507         """
508         return bind_api(
509             api=self,
510             path='/friendships/lookup.json',
511             payload_type='relationship', payload_list=True,
512             allowed_param=['user_id', 'screen_name'],
513             require_auth=True
514         )
515
516     @property
517     def friends_ids(self):
518         """ :reference: https://dev.twitter.com/rest/reference/get/friends/ids
519             :allowed_param:'id', 'user_id', 'screen_name', 'cursor'
520         """
521         return bind_api(
522             api=self,
523             path='/friends/ids.json',
524             payload_type='ids',
525             allowed_param=['id', 'user_id', 'screen_name', 'cursor']
526         )
527
528     @property
529     def friends(self):
530         """ :reference: https://dev.twitter.com/rest/reference/get/friends/list
531             :allowed_param:'id', 'user_id', 'screen_name', 'cursor'
532         """
533         return bind_api(
534             api=self,
535             path='/friends/list.json',
536             payload_type='user', payload_list=True,
537             allowed_param=['id', 'user_id', 'screen_name', 'cursor']
538         )
539
540     @property
541     def friendships_incoming(self):
542         """ :reference: https://dev.twitter.com/rest/reference/get/friendships/incoming
543             :allowed_param:'cursor'
544         """
545         return bind_api(
546             api=self,
547             path='/friendships/incoming.json',
548             payload_type='ids',
549             allowed_param=['cursor']
550         )
551
552     @property
553     def friendships_outgoing(self):
554         """ :reference: https://dev.twitter.com/rest/reference/get/friendships/outgoing
555             :allowed_param:'cursor'
556         """
557         return bind_api(
558             api=self,
559             path='/friendships/outgoing.json',
560             payload_type='ids',
561             allowed_param=['cursor']
562         )
563
564     @property
565     def followers_ids(self):
566         """ :reference: https://dev.twitter.com/rest/reference/get/followers/ids
567             :allowed_param:'id', 'user_id', 'screen_name', 'cursor', 'count'
568         """
569         return bind_api(
570             api=self,
571             path='/followers/ids.json',
572             payload_type='ids',
573             allowed_param=['id', 'user_id', 'screen_name', 'cursor', 'count']
574         )
575
576     @property
577     def followers(self):
578         """ :reference: https://dev.twitter.com/rest/reference/get/followers/list
579             :allowed_param:'id', 'user_id', 'screen_name', 'cursor', 'count', 'skip_status', 'include_user_entities'
580         """
581         return bind_api(
582             api=self,
583             path='/followers/list.json',
584             payload_type='user', payload_list=True,
585             allowed_param=['id', 'user_id', 'screen_name', 'cursor', 'count',
586                            'skip_status', 'include_user_entities']
587         )
588
589     def verify_credentials(self, **kargs):
590         """ :reference: https://dev.twitter.com/rest/reference/get/account/verify_credentials
591             :allowed_param:'include_entities', 'skip_status'
592         """
593         try:
594             return bind_api(
595                 api=self,
596                 path='/account/verify_credentials.json',
597                 payload_type='user',
598                 require_auth=True,
599                 allowed_param=['include_entities', 'skip_status'],
600             )(**kargs)
601         except TweepError as e:
602             if e.response and e.response.status == 401:
603                 return False
604             raise
605
606     @property
607     def rate_limit_status(self):
608         """ :reference: https://dev.twitter.com/rest/reference/get/application/rate_limit_status
609             :allowed_param:'resources'
610         """
611         return bind_api(
612             api=self,
613             path='/application/rate_limit_status.json',
614             payload_type='json',
615             allowed_param=['resources'],
616             use_cache=False
617         )
618
619     @property
620     def set_delivery_device(self):
621         """ :reference: https://dev.twitter.com/rest/reference/post/account/update_delivery_device
622             :allowed_param:'device'
623         """
624         return bind_api(
625             api=self,
626             path='/account/update_delivery_device.json',
627             method='POST',
628             allowed_param=['device'],
629             payload_type='user',
630             require_auth=True
631         )
632
633     @property
634     def update_profile_colors(self):
635         """ :reference: https://dev.twitter.com/docs/api/1.1/post/account/update_profile_colors
636             :allowed_param:'profile_background_color', 'profile_text_color',
637              'profile_link_color', 'profile_sidebar_fill_color',
638              'profile_sidebar_border_color'],
639         """
640         return bind_api(
641             api=self,
642             path='/account/update_profile_colors.json',
643             method='POST',
644             payload_type='user',
645             allowed_param=['profile_background_color', 'profile_text_color',
646                            'profile_link_color', 'profile_sidebar_fill_color',
647                            'profile_sidebar_border_color'],
648             require_auth=True
649         )
650
651     def update_profile_image(self, filename, file_=None):
652         """ :reference: https://dev.twitter.com/rest/reference/post/account/update_profile_image
653             :allowed_param:'include_entities', 'skip_status'
654         """
655         headers, post_data = API._pack_image(filename, 700, f=file_)
656         return bind_api(
657             api=self,
658             path='/account/update_profile_image.json',
659             method='POST',
660             payload_type='user',
661             allowed_param=['include_entities', 'skip_status'],
662             require_auth=True
663         )(self, post_data=post_data, headers=headers)
664
665     def update_profile_background_image(self, filename, **kargs):
666         """ :reference: https://dev.twitter.com/rest/reference/post/account/update_profile_background_image
667             :allowed_param:'tile', 'include_entities', 'skip_status', 'use'
668         """
669         f = kargs.pop('file', None)
670         headers, post_data = API._pack_image(filename, 800, f=f)
671         bind_api(
672             api=self,
673             path='/account/update_profile_background_image.json',
674             method='POST',
675             payload_type='user',
676             allowed_param=['tile', 'include_entities', 'skip_status', 'use'],
677             require_auth=True
678         )(post_data=post_data, headers=headers)
679
680     def update_profile_banner(self, filename, **kargs):
681         """ :reference: https://dev.twitter.com/rest/reference/post/account/update_profile_banner
682             :allowed_param:'width', 'height', 'offset_left', 'offset_right'
683         """
684         f = kargs.pop('file', None)
685         headers, post_data = API._pack_image(filename, 700, form_field="banner", f=f)
686         bind_api(
687             api=self,
688             path='/account/update_profile_banner.json',
689             method='POST',
690             allowed_param=['width', 'height', 'offset_left', 'offset_right'],
691             require_auth=True
692         )(post_data=post_data, headers=headers)
693
694     @property
695     def update_profile(self):
696         """ :reference: https://dev.twitter.com/rest/reference/post/account/update_profile
697             :allowed_param:'name', 'url', 'location', 'description'
698         """
699         return bind_api(
700             api=self,
701             path='/account/update_profile.json',
702             method='POST',
703             payload_type='user',
704             allowed_param=['name', 'url', 'location', 'description'],
705             require_auth=True
706         )
707
708     @property
709     def favorites(self):
710         """ :reference: https://dev.twitter.com/rest/reference/get/favorites/list
711             :allowed_param:'screen_name', 'user_id', 'max_id', 'count', 'since_id', 'max_id'
712         """
713         return bind_api(
714             api=self,
715             path='/favorites/list.json',
716             payload_type='status', payload_list=True,
717             allowed_param=['screen_name', 'user_id', 'max_id', 'count', 'since_id', 'max_id']
718         )
719
720     @property
721     def create_favorite(self):
722         """ :reference:https://dev.twitter.com/rest/reference/post/favorites/create
723             :allowed_param:'id'
724         """
725         return bind_api(
726             api=self,
727             path='/favorites/create.json',
728             method='POST',
729             payload_type='status',
730             allowed_param=['id'],
731             require_auth=True
732         )
733
734     @property
735     def destroy_favorite(self):
736         """ :reference: https://dev.twitter.com/rest/reference/post/favorites/destroy
737             :allowed_param:'id'
738         """
739         return bind_api(
740             api=self,
741             path='/favorites/destroy.json',
742             method='POST',
743             payload_type='status',
744             allowed_param=['id'],
745             require_auth=True
746         )
747
748     @property
749     def create_block(self):
750         """ :reference: https://dev.twitter.com/rest/reference/post/blocks/create
751             :allowed_param:'id', 'user_id', 'screen_name'
752         """
753         return bind_api(
754             api=self,
755             path='/blocks/create.json',
756             method='POST',
757             payload_type='user',
758             allowed_param=['id', 'user_id', 'screen_name'],
759             require_auth=True
760         )
761
762     @property
763     def destroy_block(self):
764         """ :reference: https://dev.twitter.com/rest/reference/post/blocks/destroy
765             :allowed_param:'id', 'user_id', 'screen_name'
766         """
767         return bind_api(
768             api=self,
769             path='/blocks/destroy.json',
770             method='POST',
771             payload_type='user',
772             allowed_param=['id', 'user_id', 'screen_name'],
773             require_auth=True
774         )
775
776     @property
777     def blocks(self):
778         """ :reference: https://dev.twitter.com/rest/reference/get/blocks/list
779             :allowed_param:'cursor'
780         """
781         return bind_api(
782             api=self,
783             path='/blocks/list.json',
784             payload_type='user', payload_list=True,
785             allowed_param=['cursor'],
786             require_auth=True
787         )
788
789     @property
790     def blocks_ids(self):
791         """ :reference: https://dev.twitter.com/rest/reference/get/blocks/ids """
792         return bind_api(
793             api=self,
794             path='/blocks/ids.json',
795             payload_type='json',
796             require_auth=True
797         )
798
799     @property
800     def report_spam(self):
801         """ :reference: https://dev.twitter.com/rest/reference/post/users/report_spam
802             :allowed_param:'user_id', 'screen_name'
803         """
804         return bind_api(
805             api=self,
806             path='/users/report_spam.json',
807             method='POST',
808             payload_type='user',
809             allowed_param=['user_id', 'screen_name'],
810             require_auth=True
811         )
812
813     @property
814     def saved_searches(self):
815         """ :reference: https://dev.twitter.com/rest/reference/get/saved_searches/show/%3Aid """
816         return bind_api(
817             api=self,
818             path='/saved_searches/list.json',
819             payload_type='saved_search', payload_list=True,
820             require_auth=True
821         )
822
823     @property
824     def get_saved_search(self):
825         """ :reference: https://dev.twitter.com/rest/reference/get/saved_searches/show/%3Aid
826             :allowed_param:'id'
827         """
828         return bind_api(
829             api=self,
830             path='/saved_searches/show/{id}.json',
831             payload_type='saved_search',
832             allowed_param=['id'],
833             require_auth=True
834         )
835
836     @property
837     def create_saved_search(self):
838         """ :reference: https://dev.twitter.com/rest/reference/post/saved_searches/create
839             :allowed_param:'query'
840         """
841         return bind_api(
842             api=self,
843             path='/saved_searches/create.json',
844             method='POST',
845             payload_type='saved_search',
846             allowed_param=['query'],
847             require_auth=True
848         )
849
850     @property
851     def destroy_saved_search(self):
852         """ :reference: https://dev.twitter.com/rest/reference/post/saved_searches/destroy/%3Aid
853             :allowed_param:'id'
854         """
855         return bind_api(
856             api=self,
857             path='/saved_searches/destroy/{id}.json',
858             method='POST',
859             payload_type='saved_search',
860             allowed_param=['id'],
861             require_auth=True
862         )
863
864     @property
865     def create_list(self):
866         """ :reference: https://dev.twitter.com/rest/reference/post/lists/create
867             :allowed_param:'name', 'mode', 'description'
868         """
869         return bind_api(
870             api=self,
871             path='/lists/create.json',
872             method='POST',
873             payload_type='list',
874             allowed_param=['name', 'mode', 'description'],
875             require_auth=True
876         )
877
878     @property
879     def destroy_list(self):
880         """ :reference: https://dev.twitter.com/rest/reference/post/lists/destroy
881             :allowed_param:'owner_screen_name', 'owner_id', 'list_id', 'slug'
882         """
883         return bind_api(
884             api=self,
885             path='/lists/destroy.json',
886             method='POST',
887             payload_type='list',
888             allowed_param=['owner_screen_name', 'owner_id', 'list_id', 'slug'],
889             require_auth=True
890         )
891
892     @property
893     def update_list(self):
894         """ :reference: https://dev.twitter.com/rest/reference/post/lists/update
895             :allowed_param: list_id', 'slug', 'name', 'mode', 'description', 'owner_screen_name', 'owner_id'
896         """
897         return bind_api(
898             api=self,
899             path='/lists/update.json',
900             method='POST',
901             payload_type='list',
902             allowed_param=['list_id', 'slug', 'name', 'mode', 'description', 'owner_screen_name', 'owner_id'],
903             require_auth=True
904         )
905
906     @property
907     def lists_all(self):
908         """ :reference: https://dev.twitter.com/rest/reference/get/lists/list
909             :allowed_param:'screen_name', 'user_id'
910         """
911         return bind_api(
912             api=self,
913             path='/lists/list.json',
914             payload_type='list', payload_list=True,
915             allowed_param=['screen_name', 'user_id'],
916             require_auth=True
917         )
918
919     @property
920     def lists_memberships(self):
921         """ :reference: https://dev.twitter.com/rest/reference/get/lists/memberships
922             :allowed_param:'screen_name', 'user_id', 'filter_to_owned_lists', 'cursor'
923         """
924         return bind_api(
925             api=self,
926             path='/lists/memberships.json',
927             payload_type='list', payload_list=True,
928             allowed_param=['screen_name', 'user_id', 'filter_to_owned_lists', 'cursor'],
929             require_auth=True
930         )
931
932     @property
933     def lists_subscriptions(self):
934         """ :reference: https://dev.twitter.com/rest/reference/get/lists/subscriptions
935             :allowed_param:'screen_name', 'user_id', 'cursor'
936         """
937         return bind_api(
938             api=self,
939             path='/lists/subscriptions.json',
940             payload_type='list', payload_list=True,
941             allowed_param=['screen_name', 'user_id', 'cursor'],
942             require_auth=True
943         )
944
945     @property
946     def list_timeline(self):
947         """ :reference: https://dev.twitter.com/docs/api/1.1/get/lists/statuses
948             :allowed_param:'owner_screen_name', 'slug', 'owner_id', 'list_id',
949              'since_id', 'max_id', 'count', 'include_rts
950         """
951         return bind_api(
952             api=self,
953             path='/lists/statuses.json',
954             payload_type='status', payload_list=True,
955             allowed_param=['owner_screen_name', 'slug', 'owner_id',
956                            'list_id', 'since_id', 'max_id', 'count',
957                            'include_rts']
958         )
959
960     @property
961     def get_list(self):
962         """ :reference: https://dev.twitter.com/rest/reference/get/lists/show
963             :allowed_param:'owner_screen_name', 'owner_id', 'slug', 'list_id'
964         """
965         return bind_api(
966             api=self,
967             path='/lists/show.json',
968             payload_type='list',
969             allowed_param=['owner_screen_name', 'owner_id', 'slug', 'list_id']
970         )
971
972     @property
973     def add_list_member(self):
974         """ :reference: https://dev.twitter.com/docs/api/1.1/post/lists/members/create
975             :allowed_param:'screen_name', 'user_id', 'owner_screen_name',
976              'owner_id', 'slug', 'list_id'
977         """
978         return bind_api(
979             api=self,
980             path='/lists/members/create.json',
981             method='POST',
982             payload_type='list',
983             allowed_param=['screen_name', 'user_id', 'owner_screen_name',
984                            'owner_id', 'slug', 'list_id'],
985             require_auth=True
986         )
987
988     @property
989     def remove_list_member(self):
990         """ :reference: https://dev.twitter.com/docs/api/1.1/post/lists/members/destroy
991             :allowed_param:'screen_name', 'user_id', 'owner_screen_name',
992              'owner_id', 'slug', 'list_id'
993         """
994         return bind_api(
995             api=self,
996             path='/lists/members/destroy.json',
997             method='POST',
998             payload_type='list',
999             allowed_param=['screen_name', 'user_id', 'owner_screen_name',
1000                            'owner_id', 'slug', 'list_id'],
1001             require_auth=True
1002         )
1003
1004     def add_list_members(self, screen_name=None, user_id=None, slug=None,
1005                          list_id=None, owner_id=None, owner_screen_name=None):
1006         """ Perform bulk add of list members from user ID or screenname """
1007         return self._add_list_members(list_to_csv(screen_name),
1008                                       list_to_csv(user_id),
1009                                       slug, list_id, owner_id,
1010                                       owner_screen_name)
1011
1012     @property
1013     def _add_list_members(self):
1014         """ :reference: https://dev.twitter.com/docs/api/1.1/post/lists/members/create_all
1015             :allowed_param:'screen_name', 'user_id', 'slug', 'lit_id',
1016             'owner_id', 'owner_screen_name'
1017
1018         """
1019         return bind_api(
1020             api=self,
1021             path='/lists/members/create_all.json',
1022             method='POST',
1023             payload_type='list',
1024             allowed_param=['screen_name', 'user_id', 'slug', 'lit_id',
1025                            'owner_id', 'owner_screen_name'],
1026             require_auth=True
1027         )
1028
1029     def remove_list_members(self, screen_name=None, user_id=None, slug=None,
1030                             list_id=None, owner_id=None, owner_screen_name=None):
1031         """ Perform bulk remove of list members from user ID or screenname """
1032         return self._remove_list_members(list_to_csv(screen_name),
1033                                          list_to_csv(user_id),
1034                                          slug, list_id, owner_id,
1035                                          owner_screen_name)
1036
1037     @property
1038     def _remove_list_members(self):
1039         """ :reference: https://dev.twitter.com/docs/api/1.1/post/lists/members/destroy_all
1040             :allowed_param:'screen_name', 'user_id', 'slug', 'lit_id',
1041             'owner_id', 'owner_screen_name'
1042
1043         """
1044         return bind_api(
1045             api=self,
1046             path='/lists/members/destroy_all.json',
1047             method='POST',
1048             payload_type='list',
1049             allowed_param=['screen_name', 'user_id', 'slug', 'lit_id',
1050                            'owner_id', 'owner_screen_name'],
1051             require_auth=True
1052         )
1053
1054     @property
1055     def list_members(self):
1056         """ :reference: https://dev.twitter.com/docs/api/1.1/get/lists/members
1057             :allowed_param:'owner_screen_name', 'slug', 'list_id',
1058              'owner_id', 'cursor
1059         """
1060         return bind_api(
1061             api=self,
1062             path='/lists/members.json',
1063             payload_type='user', payload_list=True,
1064             allowed_param=['owner_screen_name', 'slug', 'list_id',
1065                            'owner_id', 'cursor']
1066         )
1067
1068     @property
1069     def show_list_member(self):
1070         """ :reference: https://dev.twitter.com/docs/api/1.1/get/lists/members/show
1071             :allowed_param:'list_id', 'slug', 'user_id', 'screen_name',
1072              'owner_screen_name', 'owner_id
1073         """
1074         return bind_api(
1075             api=self,
1076             path='/lists/members/show.json',
1077             payload_type='user',
1078             allowed_param=['list_id', 'slug', 'user_id', 'screen_name',
1079                            'owner_screen_name', 'owner_id']
1080         )
1081
1082     @property
1083     def subscribe_list(self):
1084         """ :reference: https://dev.twitter.com/docs/api/1.1/post/lists/subscribers/create
1085             :allowed_param:'owner_screen_name', 'slug', 'owner_id',
1086             'list_id'
1087         """
1088         return bind_api(
1089             api=self,
1090             path='/lists/subscribers/create.json',
1091             method='POST',
1092             payload_type='list',
1093             allowed_param=['owner_screen_name', 'slug', 'owner_id',
1094                            'list_id'],
1095             require_auth=True
1096         )
1097
1098     @property
1099     def unsubscribe_list(self):
1100         """ :reference: https://dev.twitter.com/docs/api/1.1/post/lists/subscribers/destroy
1101             :allowed_param:'owner_screen_name', 'slug', 'owner_id',
1102             'list_id'
1103         """
1104         return bind_api(
1105             api=self,
1106             path='/lists/subscribers/destroy.json',
1107             method='POST',
1108             payload_type='list',
1109             allowed_param=['owner_screen_name', 'slug', 'owner_id',
1110                            'list_id'],
1111             require_auth=True
1112         )
1113
1114     @property
1115     def list_subscribers(self):
1116         """ :reference: https://dev.twitter.com/docs/api/1.1/get/lists/subscribers
1117             :allowed_param:'owner_screen_name', 'slug', 'owner_id',
1118              'list_id', 'cursor
1119         """
1120         return bind_api(
1121             api=self,
1122             path='/lists/subscribers.json',
1123             payload_type='user', payload_list=True,
1124             allowed_param=['owner_screen_name', 'slug', 'owner_id',
1125                            'list_id', 'cursor']
1126         )
1127
1128     @property
1129     def show_list_subscriber(self):
1130         """ :reference: https://dev.twitter.com/docs/api/1.1/get/lists/subscribers/show
1131             :allowed_param:'owner_screen_name', 'slug', 'screen_name',
1132              'owner_id', 'list_id', 'user_id
1133         """
1134         return bind_api(
1135             api=self,
1136             path='/lists/subscribers/show.json',
1137             payload_type='user',
1138             allowed_param=['owner_screen_name', 'slug', 'screen_name',
1139                            'owner_id', 'list_id', 'user_id']
1140         )
1141
1142     @property
1143     def trends_available(self):
1144         """ :reference: https://dev.twitter.com/rest/reference/get/trends/available """
1145         return bind_api(
1146             api=self,
1147             path='/trends/available.json',
1148             payload_type='json'
1149         )
1150
1151     @property
1152     def trends_place(self):
1153         """ :reference: https://dev.twitter.com/rest/reference/get/trends/place
1154             :allowed_param:'id', 'exclude'
1155         """
1156         return bind_api(
1157             api=self,
1158             path='/trends/place.json',
1159             payload_type='json',
1160             allowed_param=['id', 'exclude']
1161         )
1162
1163     @property
1164     def trends_closest(self):
1165         """ :reference: https://dev.twitter.com/rest/reference/get/trends/closest
1166             :allowed_param:'lat', 'long'
1167         """
1168         return bind_api(
1169             api=self,
1170             path='/trends/closest.json',
1171             payload_type='json',
1172             allowed_param=['lat', 'long']
1173         )
1174
1175     @property
1176     def search(self):
1177         """ :reference: https://dev.twitter.com/rest/reference/get/search/tweets
1178             :allowed_param:'q', 'lang', 'locale', 'since_id', 'geocode',
1179              'max_id', 'since', 'until', 'result_type', 'count',
1180               'include_entities', 'from', 'to', 'source']
1181         """
1182         return bind_api(
1183             api=self,
1184             path='/search/tweets.json',
1185             payload_type='search_results',
1186             allowed_param=['q', 'lang', 'locale', 'since_id', 'geocode',
1187                            'max_id', 'since', 'until', 'result_type',
1188                            'count', 'include_entities', 'from',
1189                            'to', 'source']
1190         )
1191
1192     @property
1193     def reverse_geocode(self):
1194         """ :reference: https://dev.twitter.com/rest/reference/get/geo/reverse_geocode
1195             :allowed_param:'lat', 'long', 'accuracy', 'granularity', 'max_results'
1196         """
1197         return bind_api(
1198             api=self,
1199             path='/geo/reverse_geocode.json',
1200             payload_type='place', payload_list=True,
1201             allowed_param=['lat', 'long', 'accuracy', 'granularity',
1202                            'max_results']
1203         )
1204
1205     @property
1206     def geo_id(self):
1207         """ :reference: https://dev.twitter.com/rest/reference/get/geo/id/%3Aplace_id
1208             :allowed_param:'id'
1209         """
1210         return bind_api(
1211             api=self,
1212             path='/geo/id/{id}.json',
1213             payload_type='place',
1214             allowed_param=['id']
1215         )
1216
1217     @property
1218     def geo_search(self):
1219         """ :reference: https://dev.twitter.com/docs/api/1.1/get/geo/search
1220             :allowed_param:'lat', 'long', 'query', 'ip', 'granularity',
1221              'accuracy', 'max_results', 'contained_within
1222
1223         """
1224         return bind_api(
1225             api=self,
1226             path='/geo/search.json',
1227             payload_type='place', payload_list=True,
1228             allowed_param=['lat', 'long', 'query', 'ip', 'granularity',
1229                            'accuracy', 'max_results', 'contained_within']
1230         )
1231
1232     @property
1233     def geo_similar_places(self):
1234         """ :reference: https://dev.twitter.com/rest/reference/get/geo/similar_places
1235             :allowed_param:'lat', 'long', 'name', 'contained_within'
1236         """
1237         return bind_api(
1238             api=self,
1239             path='/geo/similar_places.json',
1240             payload_type='place', payload_list=True,
1241             allowed_param=['lat', 'long', 'name', 'contained_within']
1242         )
1243
1244     @property
1245     def supported_languages(self):
1246         """ :reference: https://dev.twitter.com/rest/reference/get/help/languages """
1247         return bind_api(
1248             api=self,
1249             path='/help/languages.json',
1250             payload_type='json',
1251             require_auth=True
1252         )
1253
1254     @property
1255     def configuration(self):
1256         """ :reference: https://dev.twitter.com/rest/reference/get/help/configuration """
1257         return bind_api(
1258             api=self,
1259             path='/help/configuration.json',
1260             payload_type='json',
1261             require_auth=True
1262         )
1263
1264     """ Internal use only """
1265
1266     @staticmethod
1267     def _pack_image(filename, max_size, form_field="image", f=None):
1268         """Pack image from file into multipart-formdata post body"""
1269         # image must be less than 700kb in size
1270         if f is None:
1271             try:
1272                 if os.path.getsize(filename) > (max_size * 1024):
1273                     raise TweepError('File is too big, must be less than %skb.' % max_size)
1274             except os.error as e:
1275                 raise TweepError('Unable to access file: %s' % e.strerror)
1276
1277             # build the mulitpart-formdata body
1278             fp = open(filename, 'rb')
1279         else:
1280             f.seek(0, 2)  # Seek to end of file
1281             if f.tell() > (max_size * 1024):
1282                 raise TweepError('File is too big, must be less than %skb.' % max_size)
1283             f.seek(0)  # Reset to beginning of file
1284             fp = f
1285
1286         # image must be gif, jpeg, or png
1287         file_type = mimetypes.guess_type(filename)
1288         if file_type is None:
1289             raise TweepError('Could not determine file type')
1290         file_type = file_type[0]
1291         if file_type not in ['image/gif', 'image/jpeg', 'image/png']:
1292             raise TweepError('Invalid file type for image: %s' % file_type)
1293
1294         if isinstance(filename, six.text_type):
1295             filename = filename.encode("utf-8")
1296
1297         BOUNDARY = b'Tw3ePy'
1298         body = list()
1299         body.append(b'--' + BOUNDARY)
1300         body.append('Content-Disposition: form-data; name="{0}";'
1301                     ' filename="{1}"'.format(form_field, filename)
1302                     .encode('utf-8'))
1303         body.append('Content-Type: {0}'.format(file_type).encode('utf-8'))
1304         body.append(b'')
1305         body.append(fp.read())
1306         body.append(b'--' + BOUNDARY + b'--')
1307         body.append(b'')
1308         fp.close()
1309         body = b'\r\n'.join(body)
1310
1311         # build headers
1312         headers = {
1313             'Content-Type': 'multipart/form-data; boundary=Tw3ePy',
1314             'Content-Length': str(len(body))
1315         }
1316
1317         return headers, body

Benjamin Mako Hill || Want to submit a patch?