Skip to content
Browse files

Merge pull request #319 from bear/alt-text_issue-316

Alt text - work for issue 316
  • Loading branch information...
2 parents 7e6d53b + 824461e commit c5e37b108bd6defd315995227481c5c112219189 @jeremylow jeremylow committed May 16, 2016
Showing with 166 additions and 73 deletions.
  1. +1 −0 testdata/get_status_ext_alt.json
  2. +100 −11 tests/test_api_30.py
  3. +64 −62 twitter/api.py
  4. +1 −0 twitter/models.py
View
1 testdata/get_status_ext_alt.json
@@ -0,0 +1 @@
+{"place": null, "favorite_count": 43, "possibly_sensitive": false, "retweet_count": 28, "id": 724441953534877696, "in_reply_to_user_id": null, "id_str": "724441953534877696", "coordinates": null, "favorited": false, "contributors": null, "in_reply_to_status_id": null, "text": "\u201cJon Snow is dead.\u201d\u200a\u2014\u200a@HBOPR https://t.co/8IK0pbcxIr https://t.co/jvZ3Koip6x", "user": {"default_profile_image": false, "screen_name": "Medium", "time_zone": "Pacific Time (US & Canada)", "notifications": false, "profile_use_background_image": false, "protected": false, "name": "Medium", "contributors_enabled": false, "followers_count": 1727713, "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme14/bg.gif", "listed_count": 10639, "profile_link_color": "00AB69", "profile_image_url_https": "https://pbs.twimg.com/profile_images/672905320751083521/DqQkNLfW_normal.png", "id": 571202103, "profile_background_tile": true, "is_translator": false, "id_str": "571202103", "profile_image_url": "http://pbs.twimg.com/profile_images/672905320751083521/DqQkNLfW_normal.png", "following": false, "has_extended_profile": false, "profile_sidebar_border_color": "FFFFFF", "lang": "en", "url": "http://t.co/39nrCKtdRI", "is_translation_enabled": false, "location": "San Francisco, CA, US", "default_profile": false, "entities": {"url": {"urls": [{"url": "http://t.co/39nrCKtdRI", "display_url": "medium.com", "expanded_url": "http://medium.com", "indices": [0, 22]}]}, "description": {"urls": []}}, "verified": true, "created_at": "Fri May 04 20:16:39 +0000 2012", "utc_offset": -25200, "profile_background_color": "FFFFFF", "favourites_count": 4430, "statuses_count": 14794, "profile_background_image_url": "http://abs.twimg.com/images/themes/theme14/bg.gif", "profile_banner_url": "https://pbs.twimg.com/profile_banners/571202103/1444267389", "description": "Move thinking forward.", "geo_enabled": false, "follow_request_sent": false, "friends_count": 145, "profile_sidebar_fill_color": "EFEFEF", "profile_text_color": "333333"}, "lang": "en", "truncated": false, "is_quote_status": false, "in_reply_to_status_id_str": null, "entities": {"hashtags": [], "media": [{"id": 724441951391604736, "id_str": "724441951391604736", "media_url": "http://pbs.twimg.com/media/Cg28HdUWYAAvmwn.jpg", "media_url_https": "https://pbs.twimg.com/media/Cg28HdUWYAAvmwn.jpg", "type": "photo", "display_url": "pic.twitter.com/jvZ3Koip6x", "url": "https://t.co/jvZ3Koip6x", "sizes": {"large": {"resize": "fit", "w": 750, "h": 748}, "small": {"resize": "fit", "w": 340, "h": 339}, "thumb": {"resize": "crop", "w": 150, "h": 150}, "medium": {"resize": "fit", "w": 600, "h": 598}}, "expanded_url": "http://twitter.com/Medium/status/724441953534877696/photo/1", "indices": [53, 76]}], "user_mentions": [{"name": "HBO PR", "id": 2153490764, "id_str": "2153490764", "screen_name": "HBOPR", "indices": [22, 28]}], "symbols": [], "urls": [{"url": "https://t.co/8IK0pbcxIr", "display_url": "medium.com/hbo-cinemax-pr\u2026", "expanded_url": "https://medium.com/hbo-cinemax-pr/game-of-thrones-season-6-episodes-53586437cbb3#---0-17.cvym8cqn4", "indices": [29, 52]}]}, "retweeted": false, "in_reply_to_user_id_str": null, "created_at": "Mon Apr 25 03:36:35 +0000 2016", "in_reply_to_screen_name": null, "extended_entities": {"media": [{"id": 724441951391604736, "id_str": "724441951391604736", "media_url": "http://pbs.twimg.com/media/Cg28HdUWYAAvmwn.jpg", "media_url_https": "https://pbs.twimg.com/media/Cg28HdUWYAAvmwn.jpg", "type": "photo", "display_url": "pic.twitter.com/jvZ3Koip6x", "ext_alt_text": "\u201cJon Snow is dead.\u2026\u201d from \u201cGAME OF THRONES SEASON 6 EPISODES\u201d by HBO PR.", "url": "https://t.co/jvZ3Koip6x", "sizes": {"large": {"resize": "fit", "w": 750, "h": 748}, "small": {"resize": "fit", "w": 340, "h": 339}, "thumb": {"resize": "crop", "w": 150, "h": 150}, "medium": {"resize": "fit", "w": 600, "h": 598}}, "expanded_url": "http://twitter.com/Medium/status/724441953534877696/photo/1", "indices": [53, 76]}]}, "possibly_sensitive_appealable": false, "geo": null, "source": "<a href=\"https://medium.com\" rel=\"nofollow\">Medium</a>"}
View
111 tests/test_api_30.py
@@ -1,17 +1,20 @@
# encoding: utf-8
+from __future__ import unicode_literals, print_function
import json
+import re
import sys
import unittest
+import warnings
import twitter
-import warnings
-
warnings.filterwarnings('ignore', category=DeprecationWarning)
import responses
+DEFAULT_URL = re.compile(r'https?://.*\.twitter.com/1\.1/.*')
+
class ErrNull(object):
""" Suppress output of tests while writing to stdout or stderr. This just
@@ -571,8 +574,7 @@ def testGetFollowersIDs(self):
resp_data = f.read()
responses.add(
responses.GET,
- '{base_url}/followers/ids.json?count=5000&cursor=-1&screen_name=GirlsMakeGames'.format(
- base_url=self.api.base_url),
+ 'https://api.twitter.com/1.1/followers/ids.json?cursor=-1&stringify_ids=False&count=5000&screen_name=GirlsMakeGames',
body=resp_data,
match_querystring=True,
status=200)
@@ -582,8 +584,7 @@ def testGetFollowersIDs(self):
resp_data = f.read()
responses.add(
responses.GET,
- '{base_url}/followers/ids.json?cursor=1482201362283529597&count=5000&screen_name=GirlsMakeGames'.format(
- base_url=self.api.base_url),
+ 'https://api.twitter.com/1.1/followers/ids.json?count=5000&screen_name=GirlsMakeGames&cursor=1482201362283529597&stringify_ids=False',
body=resp_data,
match_querystring=True,
status=200)
@@ -596,9 +597,6 @@ def testGetFollowersIDs(self):
# Error checking
self.assertRaises(
twitter.TwitterError,
- lambda: self.api.GetFollowerIDs(count='infinity'))
- self.assertRaises(
- twitter.TwitterError,
lambda: self.api.GetFollowerIDs(total_count='infinity'))
@responses.activate
@@ -653,8 +651,7 @@ def testGetFollowerIDsPaged(self):
resp_data = f.read()
responses.add(
responses.GET,
- '{base_url}/followers/ids.json?count=5000&stringify_ids=False&screen_name=himawari8bot&cursor=-1'.format(
- base_url=self.api.base_url),
+ 'https://api.twitter.com/1.1/followers/ids.json?count=5000&stringify_ids=False&cursor=-1&screen_name=himawari8bot',
body=resp_data,
match_querystring=True,
status=200)
@@ -1440,3 +1437,95 @@ def testLookupFriendshipBlockMute(self):
resp = self.api.LookupFriendship(screen_name='dickc')
self.assertEqual(resp[0].muting, True)
self.assertEqual(resp[0].blocking, True)
+
+ @responses.activate
+ def testPostMediaMetadata(self):
+ responses.add(
+ responses.POST,
+ 'https://upload.twitter.com/1.1/media/metadata/create.json',
+ body=b'',
+ status=200)
+ resp = self.api.PostMediaMetadata(media_id=718561981427396608, alt_text='test')
+
+ # At the moment, all we can do is test if the method call works. The response
+ # body should be blank, with a 200 status on success.
+ self.assertTrue(resp)
+
+ @responses.activate
+ def testGetStatusWithExtAltText(self):
+ with open('testdata/get_status_ext_alt.json') as f:
+ resp_data = f.read()
+ responses.add(responses.GET, DEFAULT_URL, body=resp_data, status=200)
+ resp = self.api.GetStatus(status_id=724441953534877696)
+ self.assertEqual(resp.media[0].ext_alt_text, "\u201cJon Snow is dead.\u2026\u201d from \u201cGAME OF THRONES SEASON 6 EPISODES\u201d by HBO PR.")
+
+
+ @responses.activate
+ def testGetStatus(self):
+ with open('testdata/get_status.json') as f:
+ resp_data = f.read()
+ responses.add(
+ responses.GET,
+ 'https://api.twitter.com/1.1/statuses/show.json?include_entities=True&include_my_retweet=True&include_ext_alt_text=True&id=397',
+ body=resp_data,
+ match_querystring=True,
+ status=200)
+ resp = self.api.GetStatus(status_id=397)
+
+ self.assertTrue(type(resp) is twitter.Status)
+ self.assertEqual(resp.id, 397)
+ self.assertEqual(resp.user.id, 12)
+ self.assertFalse(resp != resp)
+
+ self.assertRaises(
+ twitter.TwitterError,
+ lambda: self.api.GetStatus(status_id='test'))
+
+ @responses.activate
+ def testGetStatusExtraParams(self):
+ with open('testdata/get_status_extra_params.json') as f:
+ resp_data = f.read()
+ responses.add(
+ responses.GET,
+ 'https://api.twitter.com/1.1/statuses/show.json?include_ext_alt_text=True&id=397&include_my_retweet=True&trim_user=True',
+ body=resp_data,
+ match_querystring=True,
+ status=200)
+ resp = self.api.GetStatus(status_id=397, trim_user=True, include_entities=False)
+ self.assertFalse(resp.user.screen_name)
+
+
+ @responses.activate
+ def testGetStatusOembed(self):
+ with open('testdata/get_status_oembed.json') as f:
+ resp_data = f.read()
+ responses.add(
+ responses.GET,
+ 'https://api.twitter.com/1.1/statuses/oembed.json?id=397',
+ body=resp_data,
+ match_querystring=True,
+ status=200)
+ responses.add(
+ responses.GET,
+ 'https://api.twitter.com/1.1/statuses/oembed.json?url=https://twitter.com/jack/statuses/397',
+ body=resp_data,
+ match_querystring=True,
+ status=200)
+ resp_id = self.api.GetStatusOembed(status_id=397)
+ self.assertEqual(resp_id['url'], 'https://twitter.com/jack/statuses/397')
+ self.assertEqual(resp_id['provider_url'], 'https://twitter.com')
+ self.assertEqual(resp_id['provider_name'], 'Twitter')
+
+ self.assertRaises(
+ twitter.TwitterError,
+ lambda: self.api.GetStatusOembed(status_id='test'))
+
+ resp_url = self.api.GetStatusOembed(url="https://twitter.com/jack/statuses/397")
+ self.assertEqual(resp_id, resp_url)
+
+ self.assertRaises(
+ twitter.TwitterError,
+ lambda: self.api.GetStatusOembed(status_id=None, url=None))
+ self.assertRaises(
+ twitter.TwitterError,
+ lambda: self.api.GetStatusOembed(status_id=397, align='test'))
View
126 twitter/api.py
@@ -721,7 +721,8 @@ def GetStatus(self,
status_id,
trim_user=False,
include_my_retweet=True,
- include_entities=True):
+ include_entities=True,
+ include_ext_alt_text=True):
"""Returns a single status message, specified by the status_id parameter.
Args:
@@ -754,11 +755,13 @@ def GetStatus(self,
raise TwitterError({'message': "'status_id' must be an integer."})
if trim_user:
- parameters['trim_user'] = 1
+ parameters['trim_user'] = True
if include_my_retweet:
- parameters['include_my_retweet'] = 1
- if not include_entities:
- parameters['include_entities'] = 'none'
+ parameters['include_my_retweet'] = True
+ if include_entities:
+ parameters['include_entities'] = True
+ if include_ext_alt_text:
+ parameters['include_ext_alt_text'] = True
resp = self._RequestUrl(url, 'GET', data=parameters)
data = self._ParseAndCheckTwitter(resp.content.decode('utf-8'))
@@ -1053,6 +1056,29 @@ def UploadMediaSimple(self,
except KeyError:
raise TwitterError({'message': 'Media could not be uploaded.'})
+ def PostMediaMetadata(self,
+ media_id,
+ alt_text=None):
+ """Provide addtional data for uploaded media.
+
+ Args:
+ media_id:
+ ID of a previously uploaded media item.
+ alt_text:
+ Image Alternate Text.
+ """
+ url = '%s/media/metadata/create.json' % self.upload_url
+ parameters = {}
+
+ parameters['media_id'] = media_id
+
+ if alt_text:
+ parameters['alt_text'] = {"text": alt_text}
+
+ resp = self._RequestUrl(url, 'POST', json=parameters)
+
+ return resp
+
def UploadMediaChunked(self,
media,
additional_owners=None,
@@ -1864,12 +1890,12 @@ def GetFollowerIDsPaged(self,
one for each follower
"""
url = '%s/followers/ids.json' % self.base_url
- return self._GetIDsPaged(url,
- user_id,
- screen_name,
- cursor,
- stringify_ids,
- count)
+ return self._GetIDsPaged(url=url,
+ user_id=user_id,
+ screen_name=screen_name,
+ cursor=cursor,
+ stringify_ids=stringify_ids,
+ count=count)
def GetFriendIDsPaged(self,
user_id=None,
@@ -1922,22 +1948,12 @@ def _GetFriendFollowerIDs(self,
total_count=None):
""" Common method for GetFriendIDs and GetFollowerIDs """
- if cursor is not None or count is not None:
- warnings.warn(
- "Use of 'cursor' and 'count' parameters are deprecated as of "
- "python-twitter 3.0. Please use GetFriendIDsPaged or "
- "GetFollowerIDsPaged instead.",
- DeprecationWarning, stacklevel=2)
-
count = 5000
cursor = -1
result = []
if total_count:
- try:
- total_count = int(total_count)
- except ValueError:
- raise TwitterError({'message': "total_count must be an integer"})
+ total_count = enf_type('total_count', int, total_count)
if total_count and total_count < count:
count = total_count
@@ -1947,12 +1963,12 @@ def _GetFriendFollowerIDs(self,
break
next_cursor, previous_cursor, data = self._GetIDsPaged(
- url,
- user_id,
- screen_name,
- cursor,
- stringify_ids,
- count)
+ url=url,
+ user_id=user_id,
+ screen_name=screen_name,
+ cursor=cursor,
+ stringify_ids=stringify_ids,
+ count=count)
result.extend([x for x in data])
@@ -1998,13 +2014,13 @@ def GetFollowerIDs(self,
A list of integers, one for each user id.
"""
url = '%s/followers/ids.json' % self.base_url
- return self._GetFriendFollowerIDs(url,
- user_id,
- screen_name,
- cursor,
- stringify_ids,
- count,
- total_count)
+ return self._GetFriendFollowerIDs(url=url,
+ user_id=user_id,
+ screen_name=screen_name,
+ cursor=cursor,
+ stringify_ids=stringify_ids,
+ count=count,
+ total_count=total_count)
def GetFriendIDs(self,
user_id=None,
@@ -4432,7 +4448,7 @@ def _RequestChunkedUpload(self, url, headers, data):
except requests.RequestException as e:
raise TwitterError(str(e))
- def _RequestUrl(self, url, verb, data=None):
+ def _RequestUrl(self, url, verb, data=None, json=None):
"""Request a url.
Args:
@@ -4460,36 +4476,22 @@ def _RequestUrl(self, url, verb, data=None):
pass
if verb == 'POST':
- if 'media_ids' in data:
- url = self._BuildUrl(url, extra_params={'media_ids': data['media_ids']})
-
- if 'media' in data:
- try:
- resp = requests.post(url,
- files=data,
- auth=self.__auth,
- timeout=self._timeout)
- except requests.RequestException as e:
- raise TwitterError(str(e))
+ 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)
+ elif 'media' in data:
+ resp = requests.post(url, files=data, auth=self.__auth, timeout=self._timeout)
+ else:
+ resp = requests.post(url, data=data, auth=self.__auth, timeout=self._timeout)
+ elif json:
+ resp = requests.post(url, json=json, auth=self.__auth, timeout=self._timeout)
else:
- try:
- resp = requests.post(url,
- data=data,
- auth=self.__auth,
- timeout=self._timeout)
-
- except requests.RequestException as e:
- raise TwitterError(str(e))
+ resp = 0 # POST request, but without data or json
elif verb == 'GET':
url = self._BuildUrl(url, extra_params=data)
- try:
- resp = requests.get(url,
- auth=self.__auth,
- timeout=self._timeout)
-
- except requests.RequestException as e:
- raise TwitterError(str(e))
+ resp = requests.get(url, auth=self.__auth, timeout=self._timeout)
else:
resp = 0 # if not a POST or GET request
View
1 twitter/models.py
@@ -93,6 +93,7 @@ def __init__(self, **kwargs):
self.param_defaults = {
'display_url': None,
'expanded_url': None,
+ 'ext_alt_text': None,
'id': None,
'media_url': None,
'media_url_https': None,

0 comments on commit c5e37b1

Please sign in to comment.
Something went wrong with that request. Please try again.