Hello community, here is the log from the commit of package python-tweepy for openSUSE:Factory checked in at 2016-03-02 14:21:21 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-tweepy (Old) and /work/SRC/openSUSE:Factory/.python-tweepy.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "python-tweepy" Changes: -------- --- /work/SRC/openSUSE:Factory/python-tweepy/python-tweepy.changes 2015-06-12 20:30:17.000000000 +0200 +++ /work/SRC/openSUSE:Factory/.python-tweepy.new/python-tweepy.changes 2016-03-02 14:21:24.000000000 +0100 @@ -1,0 +2,23 @@ +Thu Feb 18 13:42:14 UTC 2016 - eshmarnev@suse.com + +- update to version 3.5.0: + New features: + * Allow 'full_text' param when getting direct messages. + * Explicitly return api code when parsing error. + * Remove deprecated function and clean up codes. + Bug Fixes + * update_status: first positional argument should be 'status'. + * Fix "TypeError: Can't convert 'bytes' object to str implicitly". + * Fix duplicate raise in auth.py. + Additional changes from version 3.4.0: + New features: + * Add API for account/settings. + * Added RateLimitError for easily working with the rate limit. + * Allow include_email param for verify_credentials API. + * Added support for the "filter_level" parameter for the streaming API. + Bug Fixes: + * Streaming: don't decode stream bytes until json.decode. + * Typo fix on _add_list_members, _remove_list_members properties. + * Change raise in streaming.py to raise exception. + +------------------------------------------------------------------- Old: ---- tweepy-3.3.0.tar.gz New: ---- tweepy-3.5.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-tweepy.spec ++++++ --- /var/tmp/diff_new_pack.f4oYQW/_old 2016-03-02 14:21:25.000000000 +0100 +++ /var/tmp/diff_new_pack.f4oYQW/_new 2016-03-02 14:21:25.000000000 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-tweepy # -# Copyright (c) 2015 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2016 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,7 +17,7 @@ Name: python-tweepy -Version: 3.3.0 +Version: 3.5.0 Release: 0 Url: http://github.com/joshthecoder/tweepy Summary: Twitter library for python ++++++ tweepy-3.3.0.tar.gz -> tweepy-3.5.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tweepy-3.3.0/PKG-INFO new/tweepy-3.5.0/PKG-INFO --- old/tweepy-3.3.0/PKG-INFO 2015-02-21 20:31:46.000000000 +0100 +++ new/tweepy-3.5.0/PKG-INFO 2015-11-20 06:54:32.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: tweepy -Version: 3.3.0 +Version: 3.5.0 Summary: Twitter library for python Home-page: http://github.com/tweepy/tweepy Author: Joshua Roesslein diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tweepy-3.3.0/examples/oauth.py new/tweepy-3.5.0/examples/oauth.py --- old/tweepy-3.3.0/examples/oauth.py 2015-02-21 20:30:49.000000000 +0100 +++ new/tweepy-3.5.0/examples/oauth.py 2015-11-20 06:53:28.000000000 +0100 @@ -31,4 +31,4 @@ # If the application settings are set for "Read and Write" then # this line should tweet out the message to your account's # timeline. The "Read and Write" setting is on https://dev.twitter.com/apps -api.update_status('Updating using OAuth authentication via Tweepy!') +api.update_status(status='Updating using OAuth authentication via Tweepy!') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tweepy-3.3.0/examples/streaming.py new/tweepy-3.5.0/examples/streaming.py --- old/tweepy-3.3.0/examples/streaming.py 2015-02-21 20:30:49.000000000 +0100 +++ new/tweepy-3.5.0/examples/streaming.py 2015-11-20 06:53:28.000000000 +0100 @@ -15,7 +15,7 @@ access_token_secret="" class StdOutListener(StreamListener): - """ A listener handles tweets are the received from the stream. + """ A listener handles tweets that are received from the stream. This is a basic listener that just prints received tweets to stdout. """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tweepy-3.3.0/tweepy/__init__.py new/tweepy-3.5.0/tweepy/__init__.py --- old/tweepy-3.3.0/tweepy/__init__.py 2015-02-21 20:30:49.000000000 +0100 +++ new/tweepy-3.5.0/tweepy/__init__.py 2015-11-20 06:53:28.000000000 +0100 @@ -5,12 +5,12 @@ """ Tweepy Twitter API library """ -__version__ = '3.3.0' +__version__ = '3.5.0' __author__ = 'Joshua Roesslein' __license__ = 'MIT' from tweepy.models import Status, User, DirectMessage, Friendship, SavedSearch, SearchResults, ModelFactory, Category -from tweepy.error import TweepError +from tweepy.error import TweepError, RateLimitError from tweepy.api import API from tweepy.cache import Cache, MemoryCache, FileCache from tweepy.auth import OAuthHandler, AppAuthHandler diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tweepy-3.3.0/tweepy/api.py new/tweepy-3.5.0/tweepy/api.py --- old/tweepy-3.3.0/tweepy/api.py 2015-02-21 20:30:49.000000000 +0100 +++ new/tweepy-3.5.0/tweepy/api.py 2015-11-20 06:53:28.000000000 +0100 @@ -175,14 +175,15 @@ allowed_param=['id'] ) - def update_status(self, media_ids=None, *args, **kwargs): + def update_status(self, *args, **kwargs): """ :reference: https://dev.twitter.com/rest/reference/post/statuses/update :allowed_param:'status', 'in_reply_to_status_id', 'lat', 'long', 'source', 'place_id', 'display_coordinates', 'media_ids' """ post_data = {} + media_ids = kwargs.pop("media_ids", None) if media_ids is not None: post_data["media_ids"] = list_to_csv(media_ids) - + return bind_api( api=self, path='/statuses/update.json', @@ -391,39 +392,39 @@ @property def direct_messages(self): """ :reference: https://dev.twitter.com/rest/reference/get/direct_messages - :allowed_param:'since_id', 'max_id', 'count' + :allowed_param:'since_id', 'max_id', 'count', 'full_text' """ return bind_api( api=self, path='/direct_messages.json', payload_type='direct_message', payload_list=True, - allowed_param=['since_id', 'max_id', 'count'], + allowed_param=['since_id', 'max_id', 'count', 'full_text'], require_auth=True ) @property def get_direct_message(self): """ :reference: https://dev.twitter.com/rest/reference/get/direct_messages/show - :allowed_param:'id' + :allowed_param:'id', 'full_text' """ return bind_api( api=self, path='/direct_messages/show/{id}.json', payload_type='direct_message', - allowed_param=['id'], + allowed_param=['id', 'full_text'], require_auth=True ) @property def sent_direct_messages(self): """ :reference: https://dev.twitter.com/rest/reference/get/direct_messages/sent - :allowed_param:'since_id', 'max_id', 'count', 'page' + :allowed_param:'since_id', 'max_id', 'count', 'page', 'full_text' """ return bind_api( api=self, path='/direct_messages/sent.json', payload_type='direct_message', payload_list=True, - allowed_param=['since_id', 'max_id', 'count', 'page'], + allowed_param=['since_id', 'max_id', 'count', 'page', 'full_text'], require_auth=True ) @@ -528,13 +529,13 @@ @property def friends(self): """ :reference: https://dev.twitter.com/rest/reference/get/friends/list - :allowed_param:'id', 'user_id', 'screen_name', 'cursor' + :allowed_param:'id', 'user_id', 'screen_name', 'cursor', 'skip_status', 'include_user_entities' """ return bind_api( api=self, path='/friends/list.json', payload_type='user', payload_list=True, - allowed_param=['id', 'user_id', 'screen_name', 'cursor'] + allowed_param=['id', 'user_id', 'screen_name', 'cursor', 'skip_status', 'include_user_entities'] ) @property @@ -586,9 +587,39 @@ 'skip_status', 'include_user_entities'] ) + @property + def get_settings(self): + """ :reference: https://dev.twitter.com/rest/reference/get/account/settings + """ + return bind_api( + api=self, + path='/account/settings.json', + payload_type='json', + use_cache=False + ) + + @property + def set_settings(self): + """ :reference: https://dev.twitter.com/rest/reference/post/account/settings + :allowed_param:'sleep_time_enabled', 'start_sleep_time', + 'end_sleep_time', 'time_zone', 'trend_location_woeid', + 'allow_contributor_request', 'lang' + """ + return bind_api( + api=self, + path='/account/settings.json', + method='POST', + payload_type='json', + allowed_param=['sleep_time_enabled', 'start_sleep_time', + 'end_sleep_time', 'time_zone', + 'trend_location_woeid', 'allow_contributor_request', + 'lang'], + use_cache=False + ) + def verify_credentials(self, **kargs): """ :reference: https://dev.twitter.com/rest/reference/get/account/verify_credentials - :allowed_param:'include_entities', 'skip_status' + :allowed_param:'include_entities', 'skip_status', 'include_email' """ try: return bind_api( @@ -596,7 +627,7 @@ path='/account/verify_credentials.json', payload_type='user', require_auth=True, - allowed_param=['include_entities', 'skip_status'], + allowed_param=['include_entities', 'skip_status', 'include_email'], )(**kargs) except TweepError as e: if e.response and e.response.status == 401: @@ -1012,7 +1043,7 @@ @property def _add_list_members(self): """ :reference: https://dev.twitter.com/docs/api/1.1/post/lists/members/create_all - :allowed_param:'screen_name', 'user_id', 'slug', 'lit_id', + :allowed_param:'screen_name', 'user_id', 'slug', 'list_id', 'owner_id', 'owner_screen_name' """ @@ -1021,7 +1052,7 @@ path='/lists/members/create_all.json', method='POST', payload_type='list', - allowed_param=['screen_name', 'user_id', 'slug', 'lit_id', + allowed_param=['screen_name', 'user_id', 'slug', 'list_id', 'owner_id', 'owner_screen_name'], require_auth=True ) @@ -1037,7 +1068,7 @@ @property def _remove_list_members(self): """ :reference: https://dev.twitter.com/docs/api/1.1/post/lists/members/destroy_all - :allowed_param:'screen_name', 'user_id', 'slug', 'lit_id', + :allowed_param:'screen_name', 'user_id', 'slug', 'list_id', 'owner_id', 'owner_screen_name' """ @@ -1046,7 +1077,7 @@ path='/lists/members/destroy_all.json', method='POST', payload_type='list', - allowed_param=['screen_name', 'user_id', 'slug', 'lit_id', + allowed_param=['screen_name', 'user_id', 'slug', 'list_id', 'owner_id', 'owner_screen_name'], require_auth=True ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tweepy-3.3.0/tweepy/auth.py new/tweepy-3.5.0/tweepy/auth.py --- old/tweepy-3.3.0/tweepy/auth.py 2015-02-21 20:30:49.000000000 +0100 +++ new/tweepy-3.5.0/tweepy/auth.py 2015-11-20 06:53:28.000000000 +0100 @@ -85,7 +85,6 @@ self.request_token = self._get_request_token(access_type=access_type) return self.oauth.authorization_url(url) except Exception as e: - raise raise TweepError(e) def get_access_token(self, verifier=None): @@ -124,7 +123,6 @@ 'x_auth_username': username, 'x_auth_password': password}) - print(r.content) credentials = parse_qs(r.content) return credentials.get('oauth_token')[0], credentials.get('oauth_token_secret')[0] except Exception as e: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tweepy-3.3.0/tweepy/binder.py new/tweepy-3.5.0/tweepy/binder.py --- old/tweepy-3.3.0/tweepy/binder.py 2015-02-21 20:30:49.000000000 +0100 +++ new/tweepy-3.5.0/tweepy/binder.py 2015-11-20 06:53:28.000000000 +0100 @@ -12,7 +12,7 @@ import logging -from tweepy.error import TweepError +from tweepy.error import TweepError, RateLimitError, is_rate_limit_error_message from tweepy.utils import convert_to_utf8_str from tweepy.models import Model @@ -217,10 +217,16 @@ self.api.last_response = resp if resp.status_code and not 200 <= resp.status_code < 300: try: - error_msg = self.parser.parse_error(resp.text) + error_msg, api_error_code = \ + self.parser.parse_error(resp.text) except Exception: error_msg = "Twitter error response: status code = %s" % resp.status_code - raise TweepError(error_msg, resp) + api_error_code = None + + if is_rate_limit_error_message(error_msg): + raise RateLimitError(error_msg, resp) + else: + raise TweepError(error_msg, resp, api_code=api_error_code) # Parse the response payload result = self.parser.parse(self, resp.text) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tweepy-3.3.0/tweepy/error.py new/tweepy-3.5.0/tweepy/error.py --- old/tweepy-3.3.0/tweepy/error.py 2015-02-21 20:30:49.000000000 +0100 +++ new/tweepy-3.5.0/tweepy/error.py 2015-11-20 06:53:28.000000000 +0100 @@ -6,14 +6,29 @@ import six - class TweepError(Exception): """Tweepy exception""" - def __init__(self, reason, response=None): + def __init__(self, reason, response=None, api_code=None): self.reason = six.text_type(reason) self.response = response + self.api_code = api_code Exception.__init__(self, reason) def __str__(self): return self.reason + + +def is_rate_limit_error_message(message): + """Check if the supplied error message belongs to a rate limit error.""" + return isinstance(message, list) \ + and len(message) > 0 \ + and 'code' in message[0] \ + and message[0]['code'] == 88 + + +class RateLimitError(TweepError): + """Exception for Tweepy hitting the rate limit.""" + # RateLimitError has the exact same properties and inner workings + # as TweepError for backwards compatibility reasons. + pass diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tweepy-3.3.0/tweepy/parsers.py new/tweepy-3.5.0/tweepy/parsers.py --- old/tweepy-3.3.0/tweepy/parsers.py 2015-02-21 20:30:49.000000000 +0100 +++ new/tweepy-3.5.0/tweepy/parsers.py 2015-11-20 06:53:28.000000000 +0100 @@ -21,9 +21,9 @@ def parse_error(self, payload): """ - Parse the error message from payload. - If unable to parse the message, throw an exception - and default error message will be used. + Parse the error message and api error code from payload. + Return them as an (error_msg, error_code) tuple. If unable to parse the + message, throw an exception and default error message will be used. """ raise NotImplementedError @@ -63,11 +63,18 @@ return json def parse_error(self, payload): - error = self.json_lib.loads(payload) - if error.has_key('error'): - return error['error'] + error_object = self.json_lib.loads(payload) + + if 'error' in error_object: + reason = error_object['error'] + api_code = error_object.get('code') else: - return error['errors'] + reason = error_object['errors'] + api_code = [error.get('code') for error in + reason if error.get('code')] + api_code = api_code[0] if len(api_code) == 1 else api_code + + return reason, api_code class ModelParser(JSONParser): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tweepy-3.3.0/tweepy/streaming.py new/tweepy-3.5.0/tweepy/streaming.py --- old/tweepy-3.3.0/tweepy/streaming.py 2015-02-21 20:30:49.000000000 +0100 +++ new/tweepy-3.5.0/tweepy/streaming.py 2015-11-20 06:53:28.000000000 +0100 @@ -7,6 +7,7 @@ from __future__ import absolute_import, print_function import logging +import re import requests from requests.exceptions import Timeout from threading import Thread @@ -148,19 +149,26 @@ use small chunks so it can read the length and the tweet in 2 read calls. """ - def __init__(self, stream, chunk_size): + def __init__(self, stream, chunk_size, encoding='utf-8'): self._stream = stream - self._buffer = u"" + self._buffer = six.b('') self._chunk_size = chunk_size + self._encoding = encoding def read_len(self, length): while not self._stream.closed: if len(self._buffer) >= length: return self._pop(length) read_len = max(self._chunk_size, length - len(self._buffer)) - self._buffer += self._stream.read(read_len).decode("ascii") + self._buffer += self._stream.read(read_len) - def read_line(self, sep='\n'): + def read_line(self, sep=six.b('\n')): + """Read the data stream until a given separator is found (default \n) + + :param sep: Separator to read until. Must by of the bytes type (str in python 2, + bytes in python 3) + :return: The str of the data read until sep + """ start = 0 while not self._stream.closed: loc = self._buffer.find(sep, start) @@ -168,12 +176,12 @@ return self._pop(loc + len(sep)) else: start = len(self._buffer) - self._buffer += self._stream.read(self._chunk_size).decode("ascii") + self._buffer += self._stream.read(self._chunk_size) def _pop(self, length): r = self._buffer[:length] self._buffer = self._buffer[length:] - return r + return r.decode(self._encoding) class Stream(object): @@ -283,14 +291,21 @@ if exception: # call a handler first so that the exception can be logged. self.listener.on_exception(exception) - raise + raise exception def _data(self, data): if self.listener.on_data(data) is False: self.running = False def _read_loop(self, resp): - buf = ReadBuffer(resp.raw, self.chunk_size) + charset = resp.headers.get('content-type', default='') + enc_search = re.search('charset=(?P<enc>\S*)', charset) + if enc_search is not None: + encoding = enc_search.group('enc') + else: + encoding = 'utf-8' + + buf = ReadBuffer(resp.raw, self.chunk_size, encoding=encoding) while self.running and not resp.raw.closed: length = 0 @@ -404,7 +419,7 @@ self._start(async) def filter(self, follow=None, track=None, async=False, locations=None, - stall_warnings=False, languages=None, encoding='utf8'): + stall_warnings=False, languages=None, encoding='utf8', filter_level=None): self.body = {} self.session.headers['Content-type'] = "application/x-www-form-urlencoded" if self.running: @@ -423,6 +438,8 @@ self.body['stall_warnings'] = stall_warnings if languages: self.body['language'] = u','.join(map(str, languages)) + if filter_level: + self.body['filter_level'] = unicode(filter_level, encoding) self.session.params = {'delimited': 'length'} self.host = 'stream.twitter.com' self._start(async) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/tweepy-3.3.0/tweepy.egg-info/PKG-INFO new/tweepy-3.5.0/tweepy.egg-info/PKG-INFO --- old/tweepy-3.3.0/tweepy.egg-info/PKG-INFO 2015-02-21 20:31:46.000000000 +0100 +++ new/tweepy-3.5.0/tweepy.egg-info/PKG-INFO 2015-11-20 06:54:32.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: tweepy -Version: 3.3.0 +Version: 3.5.0 Summary: Twitter library for python Home-page: http://github.com/tweepy/tweepy Author: Joshua Roesslein