Handle content-type header charset value for streaming API
[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', 'skip_status', 'include_user_entities'
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', 'skip_status', 'include_user_entities']
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     @property
590     def get_settings(self):
591         """ :reference: https://dev.twitter.com/rest/reference/get/account/settings
592         """
593         return bind_api(
594             api=self,
595             path='/account/settings.json',
596             payload_type='json',
597             use_cache=False
598         )
599
600     @property
601     def set_settings(self):
602         """ :reference: https://dev.twitter.com/rest/reference/post/account/settings
603             :allowed_param:'sleep_time_enabled', 'start_sleep_time',
604             'end_sleep_time', 'time_zone', 'trend_location_woeid',
605             'allow_contributor_request', 'lang'
606         """
607         return bind_api(
608             api=self,
609             path='/account/settings.json',
610             method='POST',
611             payload_type='json',
612             allowed_param=['sleep_time_enabled', 'start_sleep_time',
613                            'end_sleep_time', 'time_zone',
614                            'trend_location_woeid', 'allow_contributor_request',
615                            'lang'],
616             use_cache=False
617         )
618
619     def verify_credentials(self, **kargs):
620         """ :reference: https://dev.twitter.com/rest/reference/get/account/verify_credentials
621             :allowed_param:'include_entities', 'skip_status', 'include_email'
622         """
623         try:
624             return bind_api(
625                 api=self,
626                 path='/account/verify_credentials.json',
627                 payload_type='user',
628                 require_auth=True,
629                 allowed_param=['include_entities', 'skip_status', 'include_email'],
630             )(**kargs)
631         except TweepError as e:
632             if e.response and e.response.status == 401:
633                 return False
634             raise
635
636     @property
637     def rate_limit_status(self):
638         """ :reference: https://dev.twitter.com/rest/reference/get/application/rate_limit_status
639             :allowed_param:'resources'
640         """
641         return bind_api(
642             api=self,
643             path='/application/rate_limit_status.json',
644             payload_type='json',
645             allowed_param=['resources'],
646             use_cache=False
647         )
648
649     @property
650     def set_delivery_device(self):
651         """ :reference: https://dev.twitter.com/rest/reference/post/account/update_delivery_device
652             :allowed_param:'device'
653         """
654         return bind_api(
655             api=self,
656             path='/account/update_delivery_device.json',
657             method='POST',
658             allowed_param=['device'],
659             payload_type='user',
660             require_auth=True
661         )
662
663     @property
664     def update_profile_colors(self):
665         """ :reference: https://dev.twitter.com/docs/api/1.1/post/account/update_profile_colors
666             :allowed_param:'profile_background_color', 'profile_text_color',
667              'profile_link_color', 'profile_sidebar_fill_color',
668              'profile_sidebar_border_color'],
669         """
670         return bind_api(
671             api=self,
672             path='/account/update_profile_colors.json',
673             method='POST',
674             payload_type='user',
675             allowed_param=['profile_background_color', 'profile_text_color',
676                            'profile_link_color', 'profile_sidebar_fill_color',
677                            'profile_sidebar_border_color'],
678             require_auth=True
679         )
680
681     def update_profile_image(self, filename, file_=None):
682         """ :reference: https://dev.twitter.com/rest/reference/post/account/update_profile_image
683             :allowed_param:'include_entities', 'skip_status'
684         """
685         headers, post_data = API._pack_image(filename, 700, f=file_)
686         return bind_api(
687             api=self,
688             path='/account/update_profile_image.json',
689             method='POST',
690             payload_type='user',
691             allowed_param=['include_entities', 'skip_status'],
692             require_auth=True
693         )(self, post_data=post_data, headers=headers)
694
695     def update_profile_background_image(self, filename, **kargs):
696         """ :reference: https://dev.twitter.com/rest/reference/post/account/update_profile_background_image
697             :allowed_param:'tile', 'include_entities', 'skip_status', 'use'
698         """
699         f = kargs.pop('file', None)
700         headers, post_data = API._pack_image(filename, 800, f=f)
701         bind_api(
702             api=self,
703             path='/account/update_profile_background_image.json',
704             method='POST',
705             payload_type='user',
706             allowed_param=['tile', 'include_entities', 'skip_status', 'use'],
707             require_auth=True
708         )(post_data=post_data, headers=headers)
709
710     def update_profile_banner(self, filename, **kargs):
711         """ :reference: https://dev.twitter.com/rest/reference/post/account/update_profile_banner
712             :allowed_param:'width', 'height', 'offset_left', 'offset_right'
713         """
714         f = kargs.pop('file', None)
715         headers, post_data = API._pack_image(filename, 700, form_field="banner", f=f)
716         bind_api(
717             api=self,
718             path='/account/update_profile_banner.json',
719             method='POST',
720             allowed_param=['width', 'height', 'offset_left', 'offset_right'],
721             require_auth=True
722         )(post_data=post_data, headers=headers)
723
724     @property
725     def update_profile(self):
726         """ :reference: https://dev.twitter.com/rest/reference/post/account/update_profile
727             :allowed_param:'name', 'url', 'location', 'description'
728         """
729         return bind_api(
730             api=self,
731             path='/account/update_profile.json',
732             method='POST',
733             payload_type='user',
734             allowed_param=['name', 'url', 'location', 'description'],
735             require_auth=True
736         )
737
738     @property
739     def favorites(self):
740         """ :reference: https://dev.twitter.com/rest/reference/get/favorites/list
741             :allowed_param:'screen_name', 'user_id', 'max_id', 'count', 'since_id', 'max_id'
742         """
743         return bind_api(
744             api=self,
745             path='/favorites/list.json',
746             payload_type='status', payload_list=True,
747             allowed_param=['screen_name', 'user_id', 'max_id', 'count', 'since_id', 'max_id']
748         )
749
750     @property
751     def create_favorite(self):
752         """ :reference:https://dev.twitter.com/rest/reference/post/favorites/create
753             :allowed_param:'id'
754         """
755         return bind_api(
756             api=self,
757             path='/favorites/create.json',
758             method='POST',
759             payload_type='status',
760             allowed_param=['id'],
761             require_auth=True
762         )
763
764     @property
765     def destroy_favorite(self):
766         """ :reference: https://dev.twitter.com/rest/reference/post/favorites/destroy
767             :allowed_param:'id'
768         """
769         return bind_api(
770             api=self,
771             path='/favorites/destroy.json',
772             method='POST',
773             payload_type='status',
774             allowed_param=['id'],
775             require_auth=True
776         )
777
778     @property
779     def create_block(self):
780         """ :reference: https://dev.twitter.com/rest/reference/post/blocks/create
781             :allowed_param:'id', 'user_id', 'screen_name'
782         """
783         return bind_api(
784             api=self,
785             path='/blocks/create.json',
786             method='POST',
787             payload_type='user',
788             allowed_param=['id', 'user_id', 'screen_name'],
789             require_auth=True
790         )
791
792     @property
793     def destroy_block(self):
794         """ :reference: https://dev.twitter.com/rest/reference/post/blocks/destroy
795             :allowed_param:'id', 'user_id', 'screen_name'
796         """
797         return bind_api(
798             api=self,
799             path='/blocks/destroy.json',
800             method='POST',
801             payload_type='user',
802             allowed_param=['id', 'user_id', 'screen_name'],
803             require_auth=True
804         )
805
806     @property
807     def blocks(self):
808         """ :reference: https://dev.twitter.com/rest/reference/get/blocks/list
809             :allowed_param:'cursor'
810         """
811         return bind_api(
812             api=self,
813             path='/blocks/list.json',
814             payload_type='user', payload_list=True,
815             allowed_param=['cursor'],
816             require_auth=True
817         )
818
819     @property
820     def blocks_ids(self):
821         """ :reference: https://dev.twitter.com/rest/reference/get/blocks/ids """
822         return bind_api(
823             api=self,
824             path='/blocks/ids.json',
825             payload_type='json',
826             require_auth=True
827         )
828
829     @property
830     def report_spam(self):
831         """ :reference: https://dev.twitter.com/rest/reference/post/users/report_spam
832             :allowed_param:'user_id', 'screen_name'
833         """
834         return bind_api(
835             api=self,
836             path='/users/report_spam.json',
837             method='POST',
838             payload_type='user',
839             allowed_param=['user_id', 'screen_name'],
840             require_auth=True
841         )
842
843     @property
844     def saved_searches(self):
845         """ :reference: https://dev.twitter.com/rest/reference/get/saved_searches/show/%3Aid """
846         return bind_api(
847             api=self,
848             path='/saved_searches/list.json',
849             payload_type='saved_search', payload_list=True,
850             require_auth=True
851         )
852
853     @property
854     def get_saved_search(self):
855         """ :reference: https://dev.twitter.com/rest/reference/get/saved_searches/show/%3Aid
856             :allowed_param:'id'
857         """
858         return bind_api(
859             api=self,
860             path='/saved_searches/show/{id}.json',
861             payload_type='saved_search',
862             allowed_param=['id'],
863             require_auth=True
864         )
865
866     @property
867     def create_saved_search(self):
868         """ :reference: https://dev.twitter.com/rest/reference/post/saved_searches/create
869             :allowed_param:'query'
870         """
871         return bind_api(
872             api=self,
873             path='/saved_searches/create.json',
874             method='POST',
875             payload_type='saved_search',
876             allowed_param=['query'],
877             require_auth=True
878         )
879
880     @property
881     def destroy_saved_search(self):
882         """ :reference: https://dev.twitter.com/rest/reference/post/saved_searches/destroy/%3Aid
883             :allowed_param:'id'
884         """
885         return bind_api(
886             api=self,
887             path='/saved_searches/destroy/{id}.json',
888             method='POST',
889             payload_type='saved_search',
890             allowed_param=['id'],
891             require_auth=True
892         )
893
894     @property
895     def create_list(self):
896         """ :reference: https://dev.twitter.com/rest/reference/post/lists/create
897             :allowed_param:'name', 'mode', 'description'
898         """
899         return bind_api(
900             api=self,
901             path='/lists/create.json',
902             method='POST',
903             payload_type='list',
904             allowed_param=['name', 'mode', 'description'],
905             require_auth=True
906         )
907
908     @property
909     def destroy_list(self):
910         """ :reference: https://dev.twitter.com/rest/reference/post/lists/destroy
911             :allowed_param:'owner_screen_name', 'owner_id', 'list_id', 'slug'
912         """
913         return bind_api(
914             api=self,
915             path='/lists/destroy.json',
916             method='POST',
917             payload_type='list',
918             allowed_param=['owner_screen_name', 'owner_id', 'list_id', 'slug'],
919             require_auth=True
920         )
921
922     @property
923     def update_list(self):
924         """ :reference: https://dev.twitter.com/rest/reference/post/lists/update
925             :allowed_param: list_id', 'slug', 'name', 'mode', 'description', 'owner_screen_name', 'owner_id'
926         """
927         return bind_api(
928             api=self,
929             path='/lists/update.json',
930             method='POST',
931             payload_type='list',
932             allowed_param=['list_id', 'slug', 'name', 'mode', 'description', 'owner_screen_name', 'owner_id'],
933             require_auth=True
934         )
935
936     @property
937     def lists_all(self):
938         """ :reference: https://dev.twitter.com/rest/reference/get/lists/list
939             :allowed_param:'screen_name', 'user_id'
940         """
941         return bind_api(
942             api=self,
943             path='/lists/list.json',
944             payload_type='list', payload_list=True,
945             allowed_param=['screen_name', 'user_id'],
946             require_auth=True
947         )
948
949     @property
950     def lists_memberships(self):
951         """ :reference: https://dev.twitter.com/rest/reference/get/lists/memberships
952             :allowed_param:'screen_name', 'user_id', 'filter_to_owned_lists', 'cursor'
953         """
954         return bind_api(
955             api=self,
956             path='/lists/memberships.json',
957             payload_type='list', payload_list=True,
958             allowed_param=['screen_name', 'user_id', 'filter_to_owned_lists', 'cursor'],
959             require_auth=True
960         )
961
962     @property
963     def lists_subscriptions(self):
964         """ :reference: https://dev.twitter.com/rest/reference/get/lists/subscriptions
965             :allowed_param:'screen_name', 'user_id', 'cursor'
966         """
967         return bind_api(
968             api=self,
969             path='/lists/subscriptions.json',
970             payload_type='list', payload_list=True,
971             allowed_param=['screen_name', 'user_id', 'cursor'],
972             require_auth=True
973         )
974
975     @property
976     def list_timeline(self):
977         """ :reference: https://dev.twitter.com/docs/api/1.1/get/lists/statuses
978             :allowed_param:'owner_screen_name', 'slug', 'owner_id', 'list_id',
979              'since_id', 'max_id', 'count', 'include_rts
980         """
981         return bind_api(
982             api=self,
983             path='/lists/statuses.json',
984             payload_type='status', payload_list=True,
985             allowed_param=['owner_screen_name', 'slug', 'owner_id',
986                            'list_id', 'since_id', 'max_id', 'count',
987                            'include_rts']
988         )
989
990     @property
991     def get_list(self):
992         """ :reference: https://dev.twitter.com/rest/reference/get/lists/show
993             :allowed_param:'owner_screen_name', 'owner_id', 'slug', 'list_id'
994         """
995         return bind_api(
996             api=self,
997             path='/lists/show.json',
998             payload_type='list',
999             allowed_param=['owner_screen_name', 'owner_id', 'slug', 'list_id']
1000         )
1001
1002     @property
1003     def add_list_member(self):
1004         """ :reference: https://dev.twitter.com/docs/api/1.1/post/lists/members/create
1005             :allowed_param:'screen_name', 'user_id', 'owner_screen_name',
1006              'owner_id', 'slug', 'list_id'
1007         """
1008         return bind_api(
1009             api=self,
1010             path='/lists/members/create.json',
1011             method='POST',
1012             payload_type='list',
1013             allowed_param=['screen_name', 'user_id', 'owner_screen_name',
1014                            'owner_id', 'slug', 'list_id'],
1015             require_auth=True
1016         )
1017
1018     @property
1019     def remove_list_member(self):
1020         """ :reference: https://dev.twitter.com/docs/api/1.1/post/lists/members/destroy
1021             :allowed_param:'screen_name', 'user_id', 'owner_screen_name',
1022              'owner_id', 'slug', 'list_id'
1023         """
1024         return bind_api(
1025             api=self,
1026             path='/lists/members/destroy.json',
1027             method='POST',
1028             payload_type='list',
1029             allowed_param=['screen_name', 'user_id', 'owner_screen_name',
1030                            'owner_id', 'slug', 'list_id'],
1031             require_auth=True
1032         )
1033
1034     def add_list_members(self, screen_name=None, user_id=None, slug=None,
1035                          list_id=None, owner_id=None, owner_screen_name=None):
1036         """ Perform bulk add of list members from user ID or screenname """
1037         return self._add_list_members(list_to_csv(screen_name),
1038                                       list_to_csv(user_id),
1039                                       slug, list_id, owner_id,
1040                                       owner_screen_name)
1041
1042     @property
1043     def _add_list_members(self):
1044         """ :reference: https://dev.twitter.com/docs/api/1.1/post/lists/members/create_all
1045             :allowed_param:'screen_name', 'user_id', 'slug', 'list_id',
1046             'owner_id', 'owner_screen_name'
1047
1048         """
1049         return bind_api(
1050             api=self,
1051             path='/lists/members/create_all.json',
1052             method='POST',
1053             payload_type='list',
1054             allowed_param=['screen_name', 'user_id', 'slug', 'list_id',
1055                            'owner_id', 'owner_screen_name'],
1056             require_auth=True
1057         )
1058
1059     def remove_list_members(self, screen_name=None, user_id=None, slug=None,
1060                             list_id=None, owner_id=None, owner_screen_name=None):
1061         """ Perform bulk remove of list members from user ID or screenname """
1062         return self._remove_list_members(list_to_csv(screen_name),
1063                                          list_to_csv(user_id),
1064                                          slug, list_id, owner_id,
1065                                          owner_screen_name)
1066
1067     @property
1068     def _remove_list_members(self):
1069         """ :reference: https://dev.twitter.com/docs/api/1.1/post/lists/members/destroy_all
1070             :allowed_param:'screen_name', 'user_id', 'slug', 'list_id',
1071             'owner_id', 'owner_screen_name'
1072
1073         """
1074         return bind_api(
1075             api=self,
1076             path='/lists/members/destroy_all.json',
1077             method='POST',
1078             payload_type='list',
1079             allowed_param=['screen_name', 'user_id', 'slug', 'list_id',
1080                            'owner_id', 'owner_screen_name'],
1081             require_auth=True
1082         )
1083
1084     @property
1085     def list_members(self):
1086         """ :reference: https://dev.twitter.com/docs/api/1.1/get/lists/members
1087             :allowed_param:'owner_screen_name', 'slug', 'list_id',
1088              'owner_id', 'cursor
1089         """
1090         return bind_api(
1091             api=self,
1092             path='/lists/members.json',
1093             payload_type='user', payload_list=True,
1094             allowed_param=['owner_screen_name', 'slug', 'list_id',
1095                            'owner_id', 'cursor']
1096         )
1097
1098     @property
1099     def show_list_member(self):
1100         """ :reference: https://dev.twitter.com/docs/api/1.1/get/lists/members/show
1101             :allowed_param:'list_id', 'slug', 'user_id', 'screen_name',
1102              'owner_screen_name', 'owner_id
1103         """
1104         return bind_api(
1105             api=self,
1106             path='/lists/members/show.json',
1107             payload_type='user',
1108             allowed_param=['list_id', 'slug', 'user_id', 'screen_name',
1109                            'owner_screen_name', 'owner_id']
1110         )
1111
1112     @property
1113     def subscribe_list(self):
1114         """ :reference: https://dev.twitter.com/docs/api/1.1/post/lists/subscribers/create
1115             :allowed_param:'owner_screen_name', 'slug', 'owner_id',
1116             'list_id'
1117         """
1118         return bind_api(
1119             api=self,
1120             path='/lists/subscribers/create.json',
1121             method='POST',
1122             payload_type='list',
1123             allowed_param=['owner_screen_name', 'slug', 'owner_id',
1124                            'list_id'],
1125             require_auth=True
1126         )
1127
1128     @property
1129     def unsubscribe_list(self):
1130         """ :reference: https://dev.twitter.com/docs/api/1.1/post/lists/subscribers/destroy
1131             :allowed_param:'owner_screen_name', 'slug', 'owner_id',
1132             'list_id'
1133         """
1134         return bind_api(
1135             api=self,
1136             path='/lists/subscribers/destroy.json',
1137             method='POST',
1138             payload_type='list',
1139             allowed_param=['owner_screen_name', 'slug', 'owner_id',
1140                            'list_id'],
1141             require_auth=True
1142         )
1143
1144     @property
1145     def list_subscribers(self):
1146         """ :reference: https://dev.twitter.com/docs/api/1.1/get/lists/subscribers
1147             :allowed_param:'owner_screen_name', 'slug', 'owner_id',
1148              'list_id', 'cursor
1149         """
1150         return bind_api(
1151             api=self,
1152             path='/lists/subscribers.json',
1153             payload_type='user', payload_list=True,
1154             allowed_param=['owner_screen_name', 'slug', 'owner_id',
1155                            'list_id', 'cursor']
1156         )
1157
1158     @property
1159     def show_list_subscriber(self):
1160         """ :reference: https://dev.twitter.com/docs/api/1.1/get/lists/subscribers/show
1161             :allowed_param:'owner_screen_name', 'slug', 'screen_name',
1162              'owner_id', 'list_id', 'user_id
1163         """
1164         return bind_api(
1165             api=self,
1166             path='/lists/subscribers/show.json',
1167             payload_type='user',
1168             allowed_param=['owner_screen_name', 'slug', 'screen_name',
1169                            'owner_id', 'list_id', 'user_id']
1170         )
1171
1172     @property
1173     def trends_available(self):
1174         """ :reference: https://dev.twitter.com/rest/reference/get/trends/available """
1175         return bind_api(
1176             api=self,
1177             path='/trends/available.json',
1178             payload_type='json'
1179         )
1180
1181     @property
1182     def trends_place(self):
1183         """ :reference: https://dev.twitter.com/rest/reference/get/trends/place
1184             :allowed_param:'id', 'exclude'
1185         """
1186         return bind_api(
1187             api=self,
1188             path='/trends/place.json',
1189             payload_type='json',
1190             allowed_param=['id', 'exclude']
1191         )
1192
1193     @property
1194     def trends_closest(self):
1195         """ :reference: https://dev.twitter.com/rest/reference/get/trends/closest
1196             :allowed_param:'lat', 'long'
1197         """
1198         return bind_api(
1199             api=self,
1200             path='/trends/closest.json',
1201             payload_type='json',
1202             allowed_param=['lat', 'long']
1203         )
1204
1205     @property
1206     def search(self):
1207         """ :reference: https://dev.twitter.com/rest/reference/get/search/tweets
1208             :allowed_param:'q', 'lang', 'locale', 'since_id', 'geocode',
1209              'max_id', 'since', 'until', 'result_type', 'count',
1210               'include_entities', 'from', 'to', 'source']
1211         """
1212         return bind_api(
1213             api=self,
1214             path='/search/tweets.json',
1215             payload_type='search_results',
1216             allowed_param=['q', 'lang', 'locale', 'since_id', 'geocode',
1217                            'max_id', 'since', 'until', 'result_type',
1218                            'count', 'include_entities', 'from',
1219                            'to', 'source']
1220         )
1221
1222     @property
1223     def reverse_geocode(self):
1224         """ :reference: https://dev.twitter.com/rest/reference/get/geo/reverse_geocode
1225             :allowed_param:'lat', 'long', 'accuracy', 'granularity', 'max_results'
1226         """
1227         return bind_api(
1228             api=self,
1229             path='/geo/reverse_geocode.json',
1230             payload_type='place', payload_list=True,
1231             allowed_param=['lat', 'long', 'accuracy', 'granularity',
1232                            'max_results']
1233         )
1234
1235     @property
1236     def geo_id(self):
1237         """ :reference: https://dev.twitter.com/rest/reference/get/geo/id/%3Aplace_id
1238             :allowed_param:'id'
1239         """
1240         return bind_api(
1241             api=self,
1242             path='/geo/id/{id}.json',
1243             payload_type='place',
1244             allowed_param=['id']
1245         )
1246
1247     @property
1248     def geo_search(self):
1249         """ :reference: https://dev.twitter.com/docs/api/1.1/get/geo/search
1250             :allowed_param:'lat', 'long', 'query', 'ip', 'granularity',
1251              'accuracy', 'max_results', 'contained_within
1252
1253         """
1254         return bind_api(
1255             api=self,
1256             path='/geo/search.json',
1257             payload_type='place', payload_list=True,
1258             allowed_param=['lat', 'long', 'query', 'ip', 'granularity',
1259                            'accuracy', 'max_results', 'contained_within']
1260         )
1261
1262     @property
1263     def geo_similar_places(self):
1264         """ :reference: https://dev.twitter.com/rest/reference/get/geo/similar_places
1265             :allowed_param:'lat', 'long', 'name', 'contained_within'
1266         """
1267         return bind_api(
1268             api=self,
1269             path='/geo/similar_places.json',
1270             payload_type='place', payload_list=True,
1271             allowed_param=['lat', 'long', 'name', 'contained_within']
1272         )
1273
1274     @property
1275     def supported_languages(self):
1276         """ :reference: https://dev.twitter.com/rest/reference/get/help/languages """
1277         return bind_api(
1278             api=self,
1279             path='/help/languages.json',
1280             payload_type='json',
1281             require_auth=True
1282         )
1283
1284     @property
1285     def configuration(self):
1286         """ :reference: https://dev.twitter.com/rest/reference/get/help/configuration """
1287         return bind_api(
1288             api=self,
1289             path='/help/configuration.json',
1290             payload_type='json',
1291             require_auth=True
1292         )
1293
1294     """ Internal use only """
1295
1296     @staticmethod
1297     def _pack_image(filename, max_size, form_field="image", f=None):
1298         """Pack image from file into multipart-formdata post body"""
1299         # image must be less than 700kb in size
1300         if f is None:
1301             try:
1302                 if os.path.getsize(filename) > (max_size * 1024):
1303                     raise TweepError('File is too big, must be less than %skb.' % max_size)
1304             except os.error as e:
1305                 raise TweepError('Unable to access file: %s' % e.strerror)
1306
1307             # build the mulitpart-formdata body
1308             fp = open(filename, 'rb')
1309         else:
1310             f.seek(0, 2)  # Seek to end of file
1311             if f.tell() > (max_size * 1024):
1312                 raise TweepError('File is too big, must be less than %skb.' % max_size)
1313             f.seek(0)  # Reset to beginning of file
1314             fp = f
1315
1316         # image must be gif, jpeg, or png
1317         file_type = mimetypes.guess_type(filename)
1318         if file_type is None:
1319             raise TweepError('Could not determine file type')
1320         file_type = file_type[0]
1321         if file_type not in ['image/gif', 'image/jpeg', 'image/png']:
1322             raise TweepError('Invalid file type for image: %s' % file_type)
1323
1324         if isinstance(filename, six.text_type):
1325             filename = filename.encode("utf-8")
1326
1327         BOUNDARY = b'Tw3ePy'
1328         body = list()
1329         body.append(b'--' + BOUNDARY)
1330         body.append('Content-Disposition: form-data; name="{0}";'
1331                     ' filename="{1}"'.format(form_field, filename)
1332                     .encode('utf-8'))
1333         body.append('Content-Type: {0}'.format(file_type).encode('utf-8'))
1334         body.append(b'')
1335         body.append(fp.read())
1336         body.append(b'--' + BOUNDARY + b'--')
1337         body.append(b'')
1338         fp.close()
1339         body = b'\r\n'.join(body)
1340
1341         # build headers
1342         headers = {
1343             'Content-Type': 'multipart/form-data; boundary=Tw3ePy',
1344             'Content-Length': str(len(body))
1345         }
1346
1347         return headers, body

Benjamin Mako Hill || Want to submit a patch?