Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/bear/python-twitter
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonathan Schultz committed Mar 28, 2017
2 parents e8313a9 + e02e9b8 commit c71c197
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 73 deletions.
2 changes: 1 addition & 1 deletion examples/tweet.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

__author__ = 'dewitt@google.com'

import ConfigParser
import configparser
import getopt
import os
import sys
Expand Down
19 changes: 19 additions & 0 deletions tests/test_api_30.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@
from __future__ import unicode_literals, print_function

import json
import os
import re
import sys
from tempfile import NamedTemporaryFile
import unittest
try:
from unittest.mock import patch
except ImportError:
from mock import patch
import warnings

import twitter
Expand Down Expand Up @@ -1713,3 +1719,16 @@ def test_UpdateBackgroundImage_deprecation(self):
with warnings.catch_warnings(record=True) as w:
resp = self.api.UpdateBackgroundImage(image='testdata/168NQ.jpg')
self.assertTrue(issubclass(w[0].category, DeprecationWarning))

@responses.activate
@patch('twitter.api.Api.UploadMediaChunked')
def test_UploadSmallVideoUsesChunkedData(self, mocker):
responses.add(POST, DEFAULT_URL, body='{}')
video = NamedTemporaryFile(suffix='.mp4')
video.write(b'10' * 1024)
video.seek(0, 0)

resp = self.api.PostUpdate('test', media=video)
assert os.path.getsize(video.name) <= 1024 * 1024
assert isinstance(resp, twitter.Status)
assert twitter.api.Api.UploadMediaChunked.called
130 changes: 61 additions & 69 deletions twitter/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,6 @@
)


warnings.simplefilter('always', DeprecationWarning)

CHARACTER_LIMIT = 140

# A singleton representing a lazily instantiated FileCache.
Expand Down Expand Up @@ -158,7 +156,8 @@ def __init__(self,
debugHTTP=False,
timeout=None,
sleep_on_rate_limit=False,
tweet_mode='compat'):
tweet_mode='compat',
proxies=None):
"""Instantiate a new twitter.Api object.
Args:
Expand Down Expand Up @@ -209,12 +208,15 @@ def __init__(self,
tweet_mode (str, optional):
Whether to use the new (as of Sept. 2016) extended tweet mode. See docs for
details. Choices are ['compatibility', 'extended'].
proxies (dict, optional):
A dictionary of proxies for the request to pass through, if not specified
allows requests lib to use environmental variables for proxy if any.
"""

# check to see if the library is running on a Google App Engine instance
# see GAE.rst for more information
if os.environ:
if 'Google App Engine' in os.environ.get('SERVER_SOFTWARE', ''):
if 'APPENGINE_RUNTIME' in os.environ.keys():
import requests_toolbelt.adapters.appengine # Adapter ensures requests use app engine's urlfetch
requests_toolbelt.adapters.appengine.monkeypatch()
cache = None # App Engine does not like this caching strategy, disable caching
Expand All @@ -235,6 +237,7 @@ def __init__(self,
self.rate_limit = RateLimit()
self.sleep_on_rate_limit = sleep_on_rate_limit
self.tweet_mode = tweet_mode
self.proxies = proxies

if base_url is None:
self.base_url = 'https://api.twitter.com/1.1'
Expand Down Expand Up @@ -1055,6 +1058,7 @@ def PostUpdate(self,
parameters['attachment_url'] = attachment_url

if media:
chunked_types = ['video/mp4', 'video/quicktime', 'image/gif']
media_ids = []
if isinstance(media, int):
media_ids.append(media)
Expand All @@ -1070,9 +1074,8 @@ def PostUpdate(self,
_, _, file_size, media_type = parse_media_file(media_file)
if media_type == 'image/gif' or media_type == 'video/mp4':
raise TwitterError(
'You cannot post more than 1 GIF or 1 video in a '
'single status.')
if file_size > self.chunk_size:
'You cannot post more than 1 GIF or 1 video in a single status.')
if file_size > self.chunk_size or media_type in chunked_types:
media_id = self.UploadMediaChunked(
media=media_file,
additional_owners=media_additional_owners,
Expand All @@ -1084,13 +1087,11 @@ def PostUpdate(self,
media_category=media_category)
media_ids.append(media_id)
else:
_, _, file_size, _ = parse_media_file(media)
if file_size > self.chunk_size:
media_ids.append(
self.UploadMediaChunked(media, media_additional_owners))
_, _, file_size, media_type = parse_media_file(media)
if file_size > self.chunk_size or media_type in chunked_types:
media_ids.append(self.UploadMediaChunked(media, media_additional_owners))
else:
media_ids.append(
self.UploadMediaSimple(media, media_additional_owners))
media_ids.append(self.UploadMediaSimple(media, media_additional_owners))
parameters['media_ids'] = ','.join([str(mid) for mid in media_ids])

if latitude is not None and longitude is not None:
Expand Down Expand Up @@ -1292,7 +1293,7 @@ def _UploadMediaChunkedAppend(self,

try:
media_fp.close()
except:
except Exception as e:
pass

return True
Expand Down Expand Up @@ -1731,7 +1732,7 @@ def GetRetweeters(self,
"""
url = '%s/statuses/retweeters/ids.json' % (self.base_url)
parameters = {
'status_id': enf_type('status_id', int, status_id),
'id': enf_type('id', int, status_id),
'stringify_ids': enf_type('stringify_ids', bool, stringify_ids)
}

Expand All @@ -1741,7 +1742,7 @@ def GetRetweeters(self,
while True:
if cursor:
try:
parameters['count'] = int(cursor)
parameters['cursor'] = int(cursor)
except ValueError:
raise TwitterError({'message': "cursor must be an integer"})
resp = self._RequestUrl(url, 'GET', data=parameters)
Expand Down Expand Up @@ -2858,47 +2859,38 @@ def UsersLookup(self,
are queried is the union of all specified parameters.
Args:
user_id:
A list of user_ids to retrieve extended information. [Optional]
screen_name:
A list of screen_names to retrieve extended information. [Optional]
users:
user_id (int, list, optional):
A list of user_ids to retrieve extended information.
screen_name (str, optional):
A list of screen_names to retrieve extended information.
users (list, optional):
A list of twitter.User objects to retrieve extended information.
[Optional]
include_entities:
include_entities (bool, optional):
The entities node that may appear within embedded statuses will be
disincluded when set to False. [Optional]
excluded when set to False.
Returns:
A list of twitter.User objects for the requested users
"""
if not user_id and not screen_name and not users:
raise TwitterError({'message': "Specify at least one of user_id, screen_name, or users."})
if not any([user_id, screen_name, users]):
raise TwitterError("Specify at least one of user_id, screen_name, or users.")

url = '%s/users/lookup.json' % self.base_url
parameters = {}
parameters = {
'include_entities': include_entities
}
uids = list()
if user_id:
uids.extend(user_id)
if users:
uids.extend([u.id for u in users])
if len(uids):
parameters['user_id'] = ','.join(["%s" % u for u in uids])
parameters['user_id'] = ','.join([str(u) for u in uids])
if screen_name:
parameters['screen_name'] = ','.join(screen_name)
if not include_entities:
parameters['include_entities'] = 'false'

resp = self._RequestUrl(url, 'GET', data=parameters)
try:
data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
except TwitterError as e:
_, e, _ = sys.exc_info()
t = e.args[0]
if len(t) == 1 and ('code' in t[0]) and (t[0]['code'] == 34):
data = []
else:
raise
data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
return [User.NewFromJsonDict(u) for u in data]

def GetUser(self,
Expand All @@ -2908,29 +2900,27 @@ def GetUser(self,
"""Returns a single user.
Args:
user_id:
The id of the user to retrieve. [Optional]
screen_name:
user_id (int, optional):
The id of the user to retrieve.
screen_name (str, optional):
The screen name of the user for whom to return results for.
Either a user_id or screen_name is required for this method.
[Optional]
include_entities:
include_entities (bool, optional):
The entities node will be omitted when set to False.
[Optional]
Returns:
A twitter.User instance representing that user
"""
url = '%s/users/show.json' % (self.base_url)
parameters = {}
parameters = {
'include_entities': include_entities
}
if user_id:
parameters['user_id'] = user_id
elif screen_name:
parameters['screen_name'] = screen_name
else:
raise TwitterError({'message': "Specify at least one of user_id or screen_name."})
if not include_entities:
parameters['include_entities'] = 'false'
raise TwitterError("Specify at least one of user_id or screen_name.")

resp = self._RequestUrl(url, 'GET', data=parameters)
data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
Expand Down Expand Up @@ -3621,8 +3611,8 @@ def CreateList(self, name, mode=None, description=None):
return List.NewFromJsonDict(data)

def DestroyList(self,
owner_screen_name=False,
owner_id=False,
owner_screen_name=None,
owner_id=None,
list_id=None,
slug=None):
"""Destroys the list identified by list_id or slug and one of
Expand Down Expand Up @@ -3660,8 +3650,8 @@ def DestroyList(self,
return List.NewFromJsonDict(data)

def CreateSubscription(self,
owner_screen_name=False,
owner_id=False,
owner_screen_name=None,
owner_id=None,
list_id=None,
slug=None):
"""Creates a subscription to a list by the authenticated user.
Expand Down Expand Up @@ -3697,8 +3687,8 @@ def CreateSubscription(self,
return User.NewFromJsonDict(data)

def DestroySubscription(self,
owner_screen_name=False,
owner_id=False,
owner_screen_name=None,
owner_id=None,
list_id=None,
slug=None):
"""Destroys the subscription to a list for the authenticated user.
Expand Down Expand Up @@ -3735,8 +3725,8 @@ def DestroySubscription(self,
return List.NewFromJsonDict(data)

def ShowSubscription(self,
owner_screen_name=False,
owner_id=False,
owner_screen_name=None,
owner_id=None,
list_id=None,
slug=None,
user_id=None,
Expand Down Expand Up @@ -4200,8 +4190,8 @@ def CreateListsMember(self,
def DestroyListsMember(self,
list_id=None,
slug=None,
owner_screen_name=False,
owner_id=False,
owner_screen_name=None,
owner_id=None,
user_id=None,
screen_name=None):
"""Destroys the subscription to a list for the authenticated user.
Expand All @@ -4219,10 +4209,10 @@ def DestroyListsMember(self,
owner_id (int, optional):
The user ID of the user who owns the list being requested by a slug.
user_id (int, optional):
The user_id or a list of user_id's to add to the list.
The user_id or a list of user_id's to remove from the list.
If not given, then screen_name is required.
screen_name (str, optional):
The screen_name or a list of Screen_name's to add to the list.
The screen_name or a list of Screen_name's to remove from the list.
If not given, then user_id is required.
Returns:
Expand Down Expand Up @@ -4916,7 +4906,8 @@ def _RequestChunkedUpload(self, url, headers, data):
headers=headers,
data=data,
auth=self.__auth,
timeout=self._timeout
timeout=self._timeout,
proxies=self.proxies
)
except requests.RequestException as e:
raise TwitterError(str(e))
Expand Down Expand Up @@ -4953,20 +4944,20 @@ def _RequestUrl(self, url, verb, data=None, json=None):
if data:
if 'media_ids' in data:
url = self._BuildUrl(url, extra_params={'media_ids': data['media_ids']})
resp = requests.post(url, data=data, auth=self.__auth, timeout=self._timeout)
resp = requests.post(url, data=data, auth=self.__auth, timeout=self._timeout, proxies=self.proxies)
elif 'media' in data:
resp = requests.post(url, files=data, auth=self.__auth, timeout=self._timeout)
resp = requests.post(url, files=data, auth=self.__auth, timeout=self._timeout, proxies=self.proxies)
else:
resp = requests.post(url, data=data, auth=self.__auth, timeout=self._timeout)
resp = requests.post(url, data=data, auth=self.__auth, timeout=self._timeout, proxies=self.proxies)
elif json:
resp = requests.post(url, json=json, auth=self.__auth, timeout=self._timeout)
resp = requests.post(url, json=json, auth=self.__auth, timeout=self._timeout, proxies=self.proxies)
else:
resp = 0 # POST request, but without data or json

elif verb == 'GET':
data['tweet_mode'] = self.tweet_mode
url = self._BuildUrl(url, extra_params=data)
resp = requests.get(url, auth=self.__auth, timeout=self._timeout)
resp = requests.get(url, auth=self.__auth, timeout=self._timeout, proxies=self.proxies)

else:
resp = 0 # if not a POST or GET request
Expand Down Expand Up @@ -4998,14 +4989,15 @@ def _RequestStream(self, url, verb, data=None):
try:
return requests.post(url, data=data, stream=True,
auth=self.__auth,
timeout=self._timeout)
timeout=self._timeout,
proxies=self.proxies)
except requests.RequestException as e:
raise TwitterError(str(e))
if verb == 'GET':
url = self._BuildUrl(url, extra_params=data)
try:
return requests.get(url, stream=True, auth=self.__auth,
timeout=self._timeout)
timeout=self._timeout, proxies=self.proxies)
except requests.RequestException as e:
raise TwitterError(str(e))
return 0 # if not a POST or GET request
6 changes: 3 additions & 3 deletions twitter/twitter_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,8 @@ def parse_media_file(passed_media):
# Otherwise, if a file object was passed in the first place,
# create the standard reference to media_file (i.e., rename it to fp).
else:
if passed_media.mode != 'rb':
raise TwitterError({'message': 'File mode must be "rb".'})
if passed_media.mode not in ['rb', 'rb+', 'w+b']:
raise TwitterError('File mode must be "rb" or "rb+"')
filename = os.path.basename(passed_media.name)
data_file = passed_media

Expand All @@ -233,7 +233,7 @@ def parse_media_file(passed_media):

try:
data_file.seek(0)
except:
except Exception as e:
pass

media_type = mimetypes.guess_type(os.path.basename(filename))[0]
Expand Down

0 comments on commit c71c197

Please sign in to comment.