Update Twython to 3.5.0.

This commit is contained in:
Bill Dengler 2017-07-08 06:47:31 +00:00
parent 6c6784bd86
commit 90410a72fa
3 changed files with 103 additions and 41 deletions

View File

@ -19,7 +19,7 @@ Questions, comments? ryan@venodesigns.net
""" """
__author__ = 'Ryan McGrath <ryan@venodesigns.net>' __author__ = 'Ryan McGrath <ryan@venodesigns.net>'
__version__ = '3.3.0' __version__ = '3.5.0'
from .api import Twython from .api import Twython
from .streaming import TwythonStreamer from .streaming import TwythonStreamer

View File

@ -140,11 +140,8 @@ class Twython(EndpointsMixin, object):
params = params or {} params = params or {}
func = getattr(self.client, method) func = getattr(self.client, method)
if type(params) is dict: params, files = _transparent_params(params)
params, files = _transparent_params(params)
else:
params = params
files = list()
requests_args = {} requests_args = {}
for k, v in self.client_args.items(): for k, v in self.client_args.items():
# Maybe this should be set as a class variable and only done once? # Maybe this should be set as a class variable and only done once?
@ -202,10 +199,8 @@ class Twython(EndpointsMixin, object):
else: else:
content = response.json() content = response.json()
except ValueError: except ValueError:
# Send the response as is for working with /media/metadata/create.json. raise TwythonError('Response was not valid JSON. \
content = response.content Unable to decode.')
# raise TwythonError('Response was not valid JSON. \
# Unable to decode.')
return content return content
@ -258,8 +253,10 @@ class Twython(EndpointsMixin, object):
url = endpoint url = endpoint
else: else:
url = '%s/%s.json' % (self.api_url % version, endpoint) url = '%s/%s.json' % (self.api_url % version, endpoint)
content = self._request(url, method=method, params=params, content = self._request(url, method=method, params=params,
api_call=url) api_call=url)
return content return content
def get(self, endpoint, params=None, version='1.1'): def get(self, endpoint, params=None, version='1.1'):
@ -531,7 +528,7 @@ class Twython(EndpointsMixin, object):
@staticmethod @staticmethod
def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False, expand_quoted_status=False): def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False, expand_quoted_status=False):
"""Return HTML for a tweet (urls, mentions, hashtags replaced with links) """Return HTML for a tweet (urls, mentions, hashtags, symbols replaced with links)
:param tweet: Tweet object from received from Twitter API :param tweet: Tweet object from received from Twitter API
:param use_display_url: Use display URL to represent link :param use_display_url: Use display URL to represent link
@ -547,8 +544,18 @@ class Twython(EndpointsMixin, object):
if 'retweeted_status' in tweet: if 'retweeted_status' in tweet:
tweet = tweet['retweeted_status'] tweet = tweet['retweeted_status']
if 'extended_tweet' in tweet:
tweet = tweet['extended_tweet']
orig_tweet_text = tweet.get('full_text') or tweet['text']
display_text_range = tweet.get('display_text_range') or [0, len(orig_tweet_text)]
display_text_start, display_text_end = display_text_range[0], display_text_range[1]
display_text = orig_tweet_text[display_text_start:display_text_end]
prefix_text = orig_tweet_text[0:display_text_start]
suffix_text = orig_tweet_text[display_text_end:len(orig_tweet_text)]
if 'entities' in tweet: if 'entities' in tweet:
text = tweet['text']
entities = tweet['entities'] entities = tweet['entities']
# Mentions # Mentions
@ -556,9 +563,13 @@ class Twython(EndpointsMixin, object):
key=lambda mention: len(mention['screen_name']), reverse=True): key=lambda mention: len(mention['screen_name']), reverse=True):
start, end = entity['indices'][0], entity['indices'][1] start, end = entity['indices'][0], entity['indices'][1]
mention_html = '<a href="https://twitter.com/%(screen_name)s" class="twython-mention">@%(screen_name)s</a>' mention_html = '<a href="https://twitter.com/%(screen_name)s" ' \
text = re.sub(r'(?<!>)' + tweet['text'][start:end] + '(?!</a>)', 'class="twython-mention">@%(screen_name)s</a>' % {'screen_name': entity['screen_name']}
mention_html % {'screen_name': entity['screen_name']}, text) sub_expr = r'(?<!>)' + orig_tweet_text[start:end] + '(?!</a>)'
if display_text_start <= start <= display_text_end:
display_text = re.sub(sub_expr, mention_html, display_text)
else:
prefix_text = re.sub(sub_expr, mention_html, prefix_text)
# Hashtags # Hashtags
for entity in sorted(entities['hashtags'], for entity in sorted(entities['hashtags'],
@ -566,8 +577,17 @@ class Twython(EndpointsMixin, object):
start, end = entity['indices'][0], entity['indices'][1] start, end = entity['indices'][0], entity['indices'][1]
hashtag_html = '<a href="https://twitter.com/search?q=%%23%(hashtag)s" class="twython-hashtag">#%(hashtag)s</a>' hashtag_html = '<a href="https://twitter.com/search?q=%%23%(hashtag)s" class="twython-hashtag">#%(hashtag)s</a>'
text = re.sub(r'(?<!>)' + tweet['text'][start:end] + '(?!</a>)', display_text = re.sub(r'(?<!>)' + orig_tweet_text[start:end] + '(?!</a>)',
hashtag_html % {'hashtag': entity['text']}, text) hashtag_html % {'hashtag': entity['text']}, display_text)
# Symbols
for entity in sorted(entities['symbols'],
key=lambda symbol: len(symbol['text']), reverse=True):
start, end = entity['indices'][0], entity['indices'][1]
symbol_html = '<a href="https://twitter.com/search?q=%%24%(symbol)s" class="twython-symbol">$%(symbol)s</a>'
display_text = re.sub(r'(?<!>)' + re.escape(orig_tweet_text[start:end]) + r'\b(?!</a>)',
symbol_html % {'symbol': entity['text']}, display_text)
# Urls # Urls
for entity in entities['urls']: for entity in entities['urls']:
@ -580,9 +600,11 @@ class Twython(EndpointsMixin, object):
else: else:
shown_url = entity['url'] shown_url = entity['url']
url_html = '<a href="%s" class="twython-url">%s</a>' url_html = '<a href="%s" class="twython-url">%s</a>' % (entity['url'], shown_url)
text = text.replace(tweet['text'][start:end], if display_text_start <= start <= display_text_end:
url_html % (entity['url'], shown_url)) display_text = display_text.replace(orig_tweet_text[start:end], url_html)
else:
suffix_text = suffix_text.replace(orig_tweet_text[start:end], url_html)
# Media # Media
if 'media' in entities: if 'media' in entities:
@ -596,13 +618,17 @@ class Twython(EndpointsMixin, object):
else: else:
shown_url = entity['url'] shown_url = entity['url']
url_html = '<a href="%s" class="twython-media">%s</a>' url_html = '<a href="%s" class="twython-media">%s</a>' % (entity['url'], shown_url)
text = text.replace(tweet['text'][start:end], if display_text_start <= start <= display_text_end:
url_html % (entity['url'], shown_url)) # for compatibility with pre-extended tweets
display_text = display_text.replace(orig_tweet_text[start:end], url_html)
else:
suffix_text = suffix_text.replace(orig_tweet_text[start:end], url_html)
if expand_quoted_status and tweet.get('is_quote_status'): quote_text = ''
if expand_quoted_status and tweet.get('is_quote_status') and tweet.get('quoted_status'):
quoted_status = tweet['quoted_status'] quoted_status = tweet['quoted_status']
text += '<blockquote class="twython-quote">%(quote)s<cite><a href="%(quote_tweet_link)s">' \ quote_text += '<blockquote class="twython-quote">%(quote)s<cite><a href="%(quote_tweet_link)s">' \
'<span class="twython-quote-user-name">%(quote_user_name)s</span>' \ '<span class="twython-quote-user-name">%(quote_user_name)s</span>' \
'<span class="twython-quote-user-screenname">@%(quote_user_screen_name)s</span></a>' \ '<span class="twython-quote-user-screenname">@%(quote_user_screen_name)s</span></a>' \
'</cite></blockquote>' % \ '</cite></blockquote>' % \
@ -612,4 +638,9 @@ class Twython(EndpointsMixin, object):
'quote_user_name': quoted_status['user']['name'], 'quote_user_name': quoted_status['user']['name'],
'quote_user_screen_name': quoted_status['user']['screen_name']} 'quote_user_screen_name': quoted_status['user']['screen_name']}
return text return '%(prefix)s%(display)s%(suffix)s%(quote)s' % {
'prefix': '<span class="twython-tweet-prefix">%s</span>' % prefix_text if prefix_text else '',
'display': display_text,
'suffix': '<span class="twython-tweet-suffix">%s</span>' % suffix_text if suffix_text else '',
'quote': quote_text
}

View File

@ -14,13 +14,14 @@ This map is organized the order functions are documented at:
https://dev.twitter.com/docs/api/1.1 https://dev.twitter.com/docs/api/1.1
""" """
import json
import os import os
import warnings import warnings
try: from io import BytesIO
from StringIO import StringIO from time import sleep
except ImportError: #try:
from io import StringIO #from StringIO import StringIO
#except ImportError:
#from io import StringIO
from .advisory import TwythonDeprecationWarning from .advisory import TwythonDeprecationWarning
@ -143,15 +144,13 @@ class EndpointsMixin(object):
Docs: Docs:
https://dev.twitter.com/rest/reference/post/media/upload https://dev.twitter.com/rest/reference/post/media/upload
""" """
# https://dev.twitter.com/rest/reference/get/media/upload-status
if params and params.get('command', '') == 'STATUS':
return self.get('https://upload.twitter.com/1.1/media/upload.json', params=params)
return self.post('https://upload.twitter.com/1.1/media/upload.json', params=params) return self.post('https://upload.twitter.com/1.1/media/upload.json', params=params)
def set_description(self, **params): def upload_video(self, media, media_type, media_category=None, size=None, check_progress=False):
""" Adds a description to an image."""
# This method only accepts strings, no dictionaries.
params = json.dumps(params)
return self.post("media/metadata/create", params=params)
def upload_video(self, media, media_type, size=None):
"""Uploads video file to Twitter servers in chunks. The file will be available to be attached """Uploads video file to Twitter servers in chunks. The file will be available to be attached
to a status for 60 minutes. To attach to a update, pass a list of returned media ids to a status for 60 minutes. To attach to a update, pass a list of returned media ids
to the 'update_status' method using the 'media_ids' param. to the 'update_status' method using the 'media_ids' param.
@ -176,7 +175,8 @@ class EndpointsMixin(object):
params = { params = {
'command': 'INIT', 'command': 'INIT',
'media_type': media_type, 'media_type': media_type,
'total_bytes': size 'total_bytes': size,
'media_category': media_category
} }
response_init = self.post(upload_url, params=params) response_init = self.post(upload_url, params=params)
media_id = response_init['media_id'] media_id = response_init['media_id']
@ -187,7 +187,7 @@ class EndpointsMixin(object):
data = media.read(1*1024*1024) data = media.read(1*1024*1024)
if not data: if not data:
break break
media_chunk = StringIO() media_chunk = BytesIO()
media_chunk.write(data) media_chunk.write(data)
media_chunk.seek(0) media_chunk.seek(0)
@ -205,7 +205,38 @@ class EndpointsMixin(object):
'command': 'FINALIZE', 'command': 'FINALIZE',
'media_id': media_id 'media_id': media_id
} }
return self.post(upload_url, params=params)
response = self.post(upload_url, params=params)
# Only get the status if explicity asked to
# Default to False
if check_progress:
# Stage 4: STATUS call if still processing
params = {
'command': 'STATUS',
'media_id': media_id
}
# added code to handle if media_category is NOT set and check_progress=True
# the API will return a NoneType object in this case
try:
processing_state = response.get('processing_info').get('state')
except AttributeError:
return response
if processing_state:
while (processing_state == 'pending' or processing_state == 'in_progress') :
# get the secs to wait
check_after_secs = response.get('processing_info').get('check_after_secs')
if check_after_secs:
sleep(check_after_secs)
response = self.get(upload_url, params=params)
# get new state after waiting
processing_state = response.get('processing_info').get('state')
return response
def get_oembed_tweet(self, **params): def get_oembed_tweet(self, **params):
"""Returns information allowing the creation of an embedded """Returns information allowing the creation of an embedded