diff options
author | Calum Lind <calumlind@gmail.com> | 2017-03-05 09:29:51 +0000 |
---|---|---|
committer | Calum Lind <calumlind@gmail.com> | 2017-06-05 22:25:29 +0100 |
commit | 481f77934983284b67b7546470f52c9799bc6184 (patch) | |
tree | 6efeac6d6c038809f989385ec2e743ede0b2a4e1 /deluge | |
parent | ede0f710f814c62f9e6625c7b166fb7b1fcf5942 (diff) | |
download | deluge-481f77934983284b67b7546470f52c9799bc6184.tar.gz deluge-481f77934983284b67b7546470f52c9799bc6184.tar.bz2 deluge-481f77934983284b67b7546470f52c9799bc6184.zip |
[Python3] Fixes to make code backward compatible
* Continuation of updating code to Python 3 with Python 2 fallback.
* Using io.open allows files to be encoded and decoded automatically on write and read. This
maintains the python boundaries of unicode in code and bytes for output/files so less
explicit encoding or decoding.
* io.StringIO is the replacement for StringIO and will only accept unicode strings.
* io.BytesIO is used where bytes output is required by the enclosing method.
* Update bencode for full compatibility.
Diffstat (limited to 'deluge')
26 files changed, 215 insertions, 143 deletions
diff --git a/deluge/bencode.py b/deluge/bencode.py index 34bb15543..5adbc2ebe 100644 --- a/deluge/bencode.py +++ b/deluge/bencode.py @@ -9,32 +9,39 @@ # License. # Written by Petru Paler +# Updated by Calum Lind to support both Python 2 and Python 3. -# Minor modifications made by Andrew Resch to replace the BTFailure errors with Exceptions +from sys import version_info -from types import DictType, IntType, ListType, LongType, StringType, TupleType +PY2 = version_info.major == 2 class BTFailure(Exception): pass +DICT_DELIM = b'd' +END_DELIM = b'e' +INT_DELIM = b'i' +LIST_DELIM = b'l' +BYTE_SEP = b':' + + def decode_int(x, f): f += 1 - newf = x.index('e', f) + newf = x.index(END_DELIM, f) n = int(x[f:newf]) - if x[f] == '-': - if x[f + 1] == '0': - raise ValueError - elif x[f] == '0' and newf != f + 1: + if x[f:f+1] == b'-' and x[f+1:f+2] == b'0': + raise ValueError + elif x[f:f+1] == b'0' and newf != f + 1: raise ValueError return (n, newf + 1) def decode_string(x, f): - colon = x.index(':', f) + colon = x.index(BYTE_SEP, f) n = int(x[f:colon]) - if x[f] == '0' and colon != f + 1: + if x[f:f+1] == b'0' and colon != f + 1: raise ValueError colon += 1 return (x[colon:colon + n], colon + n) @@ -42,43 +49,43 @@ def decode_string(x, f): def decode_list(x, f): r, f = [], f + 1 - while x[f] != 'e': - v, f = decode_func[x[f]](x, f) + while x[f:f+1] != END_DELIM: + v, f = decode_func[x[f:f+1]](x, f) r.append(v) return (r, f + 1) def decode_dict(x, f): r, f = {}, f + 1 - while x[f] != 'e': + while x[f:f+1] != END_DELIM: k, f = decode_string(x, f) - r[k], f = decode_func[x[f]](x, f) + r[k], f = decode_func[x[f:f+1]](x, f) return (r, f + 1) decode_func = {} -decode_func['l'] = decode_list -decode_func['d'] = decode_dict -decode_func['i'] = decode_int -decode_func['0'] = decode_string -decode_func['1'] = decode_string -decode_func['2'] = decode_string -decode_func['3'] = decode_string -decode_func['4'] = decode_string -decode_func['5'] = decode_string -decode_func['6'] = decode_string -decode_func['7'] = decode_string -decode_func['8'] = decode_string -decode_func['9'] = decode_string +decode_func[LIST_DELIM] = decode_list +decode_func[DICT_DELIM] = decode_dict +decode_func[INT_DELIM] = decode_int +decode_func[b'0'] = decode_string +decode_func[b'1'] = decode_string +decode_func[b'2'] = decode_string +decode_func[b'3'] = decode_string +decode_func[b'4'] = decode_string +decode_func[b'5'] = decode_string +decode_func[b'6'] = decode_string +decode_func[b'7'] = decode_string +decode_func[b'8'] = decode_string +decode_func[b'9'] = decode_string def bdecode(x): try: - r, l = decode_func[x[0]](x, 0) + r, l = decode_func[x[0:1]](x, 0) except (IndexError, KeyError, ValueError): - raise BTFailure('not a valid bencoded string') - - return r + raise BTFailure('Not a valid bencoded string') + else: + return r class Bencached(object): @@ -94,53 +101,52 @@ def encode_bencached(x, r): def encode_int(x, r): - r.extend(('i', str(x), 'e')) + r.extend((INT_DELIM, str(x).encode('utf8'), END_DELIM)) def encode_bool(x, r): - if x: - encode_int(1, r) - else: - encode_int(0, r) + encode_int(1 if x else 0, r) def encode_string(x, r): - r.extend((str(len(x)), ':', x)) + encode_string(x.encode('utf8'), r) + + +def encode_bytes(x, r): + r.extend((str(len(x)).encode('utf8'), BYTE_SEP, x)) def encode_list(x, r): - r.append('l') + r.append(LIST_DELIM) for i in x: encode_func[type(i)](i, r) - r.append('e') + r.append(END_DELIM) def encode_dict(x, r): - r.append('d') - ilist = sorted(x.items()) - for k, v in ilist: - r.extend((str(len(k)), ':', k)) + r.append(DICT_DELIM) + for k, v in sorted(x.items()): + r.extend((str(len(k)).encode('utf8'), BYTE_SEP, k)) encode_func[type(v)](v, r) - r.append('e') + r.append(END_DELIM) encode_func = {} encode_func[Bencached] = encode_bencached -encode_func[IntType] = encode_int -encode_func[LongType] = encode_int -encode_func[StringType] = encode_string -encode_func[ListType] = encode_list -encode_func[TupleType] = encode_list -encode_func[DictType] = encode_dict - -try: - from types import BooleanType - encode_func[BooleanType] = encode_bool -except ImportError: - pass +encode_func[int] = encode_int +encode_func[list] = encode_list +encode_func[tuple] = encode_list +encode_func[dict] = encode_dict +encode_func[bool] = encode_bool +encode_func[str] = encode_string +encode_func[bytes] = encode_bytes +if PY2: + encode_func[long] = encode_int + encode_func[str] = encode_bytes + encode_func[unicode] = encode_string def bencode(x): r = [] encode_func[type(x)](x, r) - return ''.join(r) + return b''.join(r) diff --git a/deluge/common.py b/deluge/common.py index 371c567e0..2d71df04a 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -62,6 +62,9 @@ TORRENT_STATE = [ 'Moving' ] +# The output formatting for json.dump +JSON_FORMAT = {'indent': 4, 'sort_keys': True, 'ensure_ascii': False} + PY2 = sys.version_info.major == 2 @@ -678,8 +681,12 @@ def create_magnet_uri(infohash, name=None, trackers=None): str: A magnet uri string. """ + try: + infohash = infohash.decode('hex') + except AttributeError: + pass - uri = [MAGNET_SCHEME, XT_BTIH_PARAM, base64.b32encode(infohash.decode('hex'))] + uri = [MAGNET_SCHEME, XT_BTIH_PARAM, base64.b32encode(infohash)] if name: uri.extend(['&', DN_PARAM, name]) if trackers: @@ -983,7 +990,7 @@ def create_localclient_account(append=False): with open(auth_file, 'a' if append else 'w') as _file: _file.write(':'.join([ 'localclient', - sha(str(random.random())).hexdigest(), + sha(str(random.random()).encode('utf8')).hexdigest(), str(AUTH_LEVEL_ADMIN) ]) + '\n') _file.flush() @@ -1090,7 +1097,14 @@ def unicode_argv(): # As a last resort, just default to utf-8 encoding = encoding or 'utf-8' - return [arg.decode(encoding) for arg in sys.argv] + arg_list = [] + for arg in sys.argv: + try: + arg_list.append(arg.decode(encoding)) + except AttributeError: + arg_list.append(arg) + + return arg_list def run_profiled(func, *args, **kwargs): @@ -1116,8 +1130,8 @@ def run_profiled(func, *args, **kwargs): print('Profile stats saved to %s' % output_file) else: import pstats - import StringIO - strio = StringIO.StringIO() + from io import StringIO + strio = StringIO() ps = pstats.Stats(profiler, stream=strio).sort_stats('cumulative') ps.print_stats() print(strio.getvalue()) diff --git a/deluge/config.py b/deluge/config.py index 84f24f66a..5550182e7 100644 --- a/deluge/config.py +++ b/deluge/config.py @@ -46,8 +46,10 @@ import json import logging import os import shutil +from codecs import getwriter +from io import open -from deluge.common import get_default_config_dir +from deluge.common import JSON_FORMAT, get_default_config_dir log = logging.getLogger(__name__) callLater = None # Necessary for the config tests @@ -172,11 +174,6 @@ class Config(object): 5 """ - try: - value = value.encode('utf8') - except AttributeError: - pass - if key not in self.__config: self.__config[key] = value log.debug('Setting key "%s" to: %s (of type: %s)', key, value, type(value)) @@ -195,8 +192,10 @@ class Config(object): log.warning('Value Type "%s" invalid for key: %s', type(value), key) raise - log.debug('Setting key "%s" to: %s (of type: %s)', key, value, type(value)) + if isinstance(value, bytes): + value.decode('utf8') + log.debug('Setting key "%s" to: %s (of type: %s)', key, value, type(value)) self.__config[key] = value global callLater @@ -243,10 +242,7 @@ class Config(object): 5 """ - try: - return self.__config[key].decode('utf8') - except AttributeError: - return self.__config[key] + return self.__config[key] def get(self, key, default=None): """Gets the value of item 'key' if key is in the config, else default. @@ -394,7 +390,7 @@ class Config(object): filename = self.__config_file try: - with open(filename, 'rb') as _file: + with open(filename, 'r', encoding='utf8') as _file: data = _file.read() except IOError as ex: log.warning('Unable to open config file %s: %s', filename, ex) @@ -444,7 +440,7 @@ class Config(object): # Check to see if the current config differs from the one on disk # We will only write a new config file if there is a difference try: - with open(filename, 'rb') as _file: + with open(filename, 'r', encoding='utf8') as _file: data = _file.read() objects = find_json_objects(data) start, end = objects[0] @@ -463,8 +459,8 @@ class Config(object): try: log.debug('Saving new config file %s', filename + '.new') with open(filename + '.new', 'wb') as _file: - json.dump(self.__version, _file, indent=2) - json.dump(self.__config, _file, indent=2, sort_keys=True) + json.dump(self.__version, getwriter('utf8')(_file), **JSON_FORMAT) + json.dump(self.__config, getwriter('utf8')(_file), **JSON_FORMAT) _file.flush() os.fsync(_file.fileno()) except IOError as ex: diff --git a/deluge/core/authmanager.py b/deluge/core/authmanager.py index ae0a5c520..60452f828 100644 --- a/deluge/core/authmanager.py +++ b/deluge/core/authmanager.py @@ -13,6 +13,7 @@ from __future__ import unicode_literals import logging import os import shutil +from io import open import deluge.component as component import deluge.configmanager as configmanager @@ -178,7 +179,7 @@ class AuthManager(component.Component): else: log.info('Saving the %s at: %s', filename, filepath) try: - with open(filepath_tmp, 'wb') as _file: + with open(filepath_tmp, 'w', encoding='utf8') as _file: for account in self.__auth.values(): _file.write('%(username)s:%(password)s:%(authlevel_int)s\n' % account.data()) _file.flush() @@ -213,7 +214,7 @@ class AuthManager(component.Component): for _filepath in (auth_file, auth_file_bak): log.info('Opening %s for load: %s', filename, _filepath) try: - with open(_filepath, 'rb') as _file: + with open(_filepath, 'r', encoding='utf8') as _file: file_data = _file.readlines() except IOError as ex: log.warning('Unable to load %s: %s', _filepath, ex) diff --git a/deluge/core/daemon.py b/deluge/core/daemon.py index e84218141..842924b02 100644 --- a/deluge/core/daemon.py +++ b/deluge/core/daemon.py @@ -45,7 +45,7 @@ def is_daemon_running(pid_file): try: with open(pid_file) as _file: pid, port = [int(x) for x in _file.readline().strip().split(';')] - except EnvironmentError: + except (EnvironmentError, ValueError): return False if is_process_running(pid): @@ -130,7 +130,7 @@ class Daemon(object): # Create pid file to track if deluged is running, also includes the port number. pid = os.getpid() log.debug('Storing pid %s & port %s in: %s', pid, self.port, self.pid_file) - with open(self.pid_file, 'wb') as _file: + with open(self.pid_file, 'w') as _file: _file.write('%s;%s\n' % (pid, self.port)) component.start() diff --git a/deluge/core/rpcserver.py b/deluge/core/rpcserver.py index bb192a3fb..0b4b7d221 100644 --- a/deluge/core/rpcserver.py +++ b/deluge/core/rpcserver.py @@ -563,7 +563,9 @@ def generate_ssl_keys(): """ This method generates a new SSL key/cert. """ - digest = b'sha256' + from deluge.common import PY2 + digest = 'sha256' if not PY2 else b'sha256' + # Generate key pair pkey = crypto.PKey() pkey.generate_key(crypto.TYPE_RSA, 2048) @@ -587,9 +589,9 @@ def generate_ssl_keys(): # Write out files ssl_dir = deluge.configmanager.get_config_dir('ssl') - with open(os.path.join(ssl_dir, 'daemon.pkey'), 'w') as _file: + with open(os.path.join(ssl_dir, 'daemon.pkey'), 'wb') as _file: _file.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)) - with open(os.path.join(ssl_dir, 'daemon.cert'), 'w') as _file: + with open(os.path.join(ssl_dir, 'daemon.cert'), 'wb') as _file: _file.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert)) # Make the files only readable by this user for f in ('daemon.pkey', 'daemon.cert'): diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index dadec5b2f..a482eeba1 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -19,8 +19,6 @@ from __future__ import division, unicode_literals import logging import os import socket -from future_builtins import zip -from urlparse import urlparse from twisted.internet.defer import Deferred, DeferredList @@ -32,6 +30,18 @@ from deluge.core.authmanager import AUTH_LEVEL_ADMIN from deluge.decorators import deprecated from deluge.event import TorrentFolderRenamedEvent, TorrentStateChangedEvent, TorrentTrackerStatusEvent +try: + from urllib.parse import urlparse +except ImportError: + # PY2 fallback + from urlparse import urlparse # pylint: disable=ungrouped-imports + +try: + from future_builtins import zip +except ImportError: + # Ignore on Py3. + pass + log = logging.getLogger(__name__) LT_TORRENT_STATE_MAP = { @@ -95,9 +105,14 @@ def convert_lt_files(files): """ filelist = [] for index, _file in enumerate(files): + try: + file_path = _file.path.decode('utf8') + except AttributeError: + file_path = _file.path + filelist.append({ 'index': index, - 'path': _file.path.decode('utf8').replace('\\', '/'), + 'path': file_path.replace('\\', '/'), 'size': _file.size, 'offset': _file.offset }) diff --git a/deluge/httpdownloader.py b/deluge/httpdownloader.py index 10e8ce0ea..75000ced3 100644 --- a/deluge/httpdownloader.py +++ b/deluge/httpdownloader.py @@ -12,7 +12,6 @@ from __future__ import unicode_literals import logging import os.path import zlib -from urlparse import urljoin from twisted.internet import reactor from twisted.python.failure import Failure @@ -21,6 +20,12 @@ from twisted.web.error import PageRedirect from deluge.common import get_version, utf8_encode_structure +try: + from urllib.parse import urljoin +except ImportError: + # PY2 fallback + from urlparse import urljoin # pylint: disable=ungrouped-imports + log = logging.getLogger(__name__) @@ -52,7 +57,7 @@ class HTTPDownloader(client.HTTPDownloader): self.force_filename = force_filename self.allow_compression = allow_compression self.code = None - agent = b'Deluge/%s (http://deluge-torrent.org)' % get_version() + agent = b'Deluge/%s (http://deluge-torrent.org)' % get_version().encode('utf8') client.HTTPDownloader.__init__(self, url, filename, headers=headers, agent=agent) diff --git a/deluge/log.py b/deluge/log.py index 003c1be8a..bbf57f73f 100644 --- a/deluge/log.py +++ b/deluge/log.py @@ -82,7 +82,7 @@ class Logging(LoggingLoggerClass): def exception(self, msg, *args, **kwargs): yield LoggingLoggerClass.exception(self, msg, *args, **kwargs) - def findCaller(self): # NOQA: N802 + def findCaller(self, stack_info=False): # NOQA: N802 f = logging.currentframe().f_back rv = '(unknown file)', 0, '(unknown function)' while hasattr(f, 'f_code'): diff --git a/deluge/plugins/Blocklist/deluge/plugins/blocklist/core.py b/deluge/plugins/Blocklist/deluge/plugins/blocklist/core.py index aa21fed53..73f917a50 100644 --- a/deluge/plugins/Blocklist/deluge/plugins/blocklist/core.py +++ b/deluge/plugins/Blocklist/deluge/plugins/blocklist/core.py @@ -16,7 +16,6 @@ import shutil import time from datetime import datetime, timedelta from email.utils import formatdate -from urlparse import urljoin from twisted.internet import defer, threads from twisted.internet.task import LoopingCall @@ -33,6 +32,12 @@ from .common import IP, BadIP from .detect import UnknownFormatError, create_reader, detect_compression, detect_format from .readers import ReaderParseError +try: + from urllib.parse import urljoin +except ImportError: + # PY2 fallback + from urlparse import urljoin # pylint: disable=ungrouped-imports + # TODO: check return values for deferred callbacks # TODO: review class attributes for redundancy diff --git a/deluge/plugins/Blocklist/deluge/plugins/blocklist/decompressers.py b/deluge/plugins/Blocklist/deluge/plugins/blocklist/decompressers.py index c44b807ca..6096d9935 100644 --- a/deluge/plugins/Blocklist/deluge/plugins/blocklist/decompressers.py +++ b/deluge/plugins/Blocklist/deluge/plugins/blocklist/decompressers.py @@ -19,12 +19,7 @@ def Zipped(reader): # NOQA: N802 """Blocklist reader for zipped blocklists""" def open(self): z = zipfile.ZipFile(self.file) - if hasattr(z, 'open'): - f = z.open(z.namelist()[0]) - else: - # Handle python 2.5 - import cStringIO - f = cStringIO.StringIO(z.read(z.namelist()[0])) + f = z.open(z.namelist()[0]) return f reader.open = open return reader diff --git a/deluge/rencode.py b/deluge/rencode.py index b8c72a3ce..d9ac8fb89 100644 --- a/deluge/rencode.py +++ b/deluge/rencode.py @@ -60,9 +60,14 @@ same rencode version throughout your project. import struct import sys -from future_builtins import zip from threading import Lock +try: + from future_builtins import zip +except ImportError: + # Ignore on Py3. + pass + __version__ = ('Python', 1, 0, 4) __all__ = ['dumps', 'loads'] diff --git a/deluge/tests/common.py b/deluge/tests/common.py index b81edd5b2..364af6973 100644 --- a/deluge/tests/common.py +++ b/deluge/tests/common.py @@ -185,6 +185,7 @@ class ProcessOutputHandler(protocol.ProcessProtocol): def outReceived(self, data): # NOQA: N802 """Process output from stdout""" + data = data.decode('utf8') self.log_output += data if self.check_callbacks(data): pass @@ -193,6 +194,7 @@ class ProcessOutputHandler(protocol.ProcessProtocol): def errReceived(self, data): # NOQA: N802 """Process output from stderr""" + data = data.decode('utf8') self.log_output += data self.stderr_out += data self.check_callbacks(data, cb_type='stderr') diff --git a/deluge/tests/test_config.py b/deluge/tests/test_config.py index 6b6a38506..b7c009087 100644 --- a/deluge/tests/test_config.py +++ b/deluge/tests/test_config.py @@ -8,11 +8,13 @@ from __future__ import unicode_literals import os +from codecs import getwriter from twisted.internet import task from twisted.trial import unittest import deluge.config +from deluge.common import JSON_FORMAT from deluge.config import Config from .common import set_tmp_config_dir @@ -88,17 +90,10 @@ class ConfigTestCase(unittest.TestCase): self.assertEqual(config['string'], 'foobar') self.assertEqual(config['float'], 0.435) - # Test loading an old config from 1.1.x - import pickle - with open(os.path.join(self.config_dir, 'test.conf'), 'wb') as _file: - pickle.dump(DEFAULTS, _file) - - check_config() - # Test opening a previous 1.2 config file of just a json object import json with open(os.path.join(self.config_dir, 'test.conf'), 'wb') as _file: - json.dump(DEFAULTS, _file, indent=2) + json.dump(DEFAULTS, getwriter('utf8')(_file), **JSON_FORMAT) check_config() @@ -107,15 +102,15 @@ class ConfigTestCase(unittest.TestCase): with open(os.path.join(self.config_dir, 'test.conf'), 'wb') as _file: _file.write(str(1) + '\n') _file.write(str(1) + '\n') - json.dump(DEFAULTS, _file, indent=2) + json.dump(DEFAULTS, getwriter('utf8')(_file), **JSON_FORMAT) check_config() # Test the 1.2 config format version = {'format': 1, 'file': 1} with open(os.path.join(self.config_dir, 'test.conf'), 'wb') as _file: - json.dump(version, _file, indent=2) - json.dump(DEFAULTS, _file, indent=2) + json.dump(version, getwriter('utf8')(_file), **JSON_FORMAT) + json.dump(DEFAULTS, getwriter('utf8')(_file), **JSON_FORMAT) check_config() diff --git a/deluge/tests/test_core.py b/deluge/tests/test_core.py index 135e2462c..6a7aeda7a 100644 --- a/deluge/tests/test_core.py +++ b/deluge/tests/test_core.py @@ -42,7 +42,7 @@ class CookieResource(Resource): return request.setHeader(b'Content-Type', b'application/x-bittorrent') - with open(common.get_test_data_file('ubuntu-9.04-desktop-i386.iso.torrent')) as _file: + with open(common.get_test_data_file('ubuntu-9.04-desktop-i386.iso.torrent'), 'rb') as _file: data = _file.read() return data @@ -50,7 +50,7 @@ class CookieResource(Resource): class PartialDownload(Resource): def render(self, request): - with open(common.get_test_data_file('ubuntu-9.04-desktop-i386.iso.torrent')) as _file: + with open(common.get_test_data_file('ubuntu-9.04-desktop-i386.iso.torrent'), 'rb') as _file: data = _file.read() request.setHeader(b'Content-Type', len(data)) request.setHeader(b'Content-Type', b'application/x-bittorrent') @@ -119,7 +119,7 @@ class CoreTestCase(BaseTestCase): files_to_add = [] for f in filenames: filename = common.get_test_data_file(f) - with open(filename) as _file: + with open(filename, 'rb') as _file: filedump = base64.encodestring(_file.read()) files_to_add.append((filename, filedump, options)) errors = yield self.core.add_torrent_files(files_to_add) @@ -132,7 +132,7 @@ class CoreTestCase(BaseTestCase): files_to_add = [] for f in filenames: filename = common.get_test_data_file(f) - with open(filename) as _file: + with open(filename, 'rb') as _file: filedump = base64.encodestring(_file.read()) files_to_add.append((filename, filedump, options)) errors = yield self.core.add_torrent_files(files_to_add) @@ -143,15 +143,15 @@ class CoreTestCase(BaseTestCase): def test_add_torrent_file(self): options = {} filename = common.get_test_data_file('test.torrent') - with open(filename) as _file: + with open(filename, 'rb') as _file: filedump = base64.encodestring(_file.read()) torrent_id = yield self.core.add_torrent_file(filename, filedump, options) # Get the info hash from the test.torrent from deluge.bencode import bdecode, bencode - with open(filename) as _file: - info_hash = sha(bencode(bdecode(_file.read())['info'])).hexdigest() - self.assertEqual(torrent_id, info_hash) + with open(filename, 'rb') as _file: + info_hash = sha(bencode(bdecode(_file.read())[b'info'])).hexdigest() + self.assertEquals(torrent_id, info_hash) def test_add_torrent_file_invalid_filedump(self): options = {} @@ -211,7 +211,7 @@ class CoreTestCase(BaseTestCase): def test_remove_torrent(self): options = {} filename = common.get_test_data_file('test.torrent') - with open(filename) as _file: + with open(filename, 'rb') as _file: filedump = base64.encodestring(_file.read()) torrent_id = yield self.core.add_torrent_file(filename, filedump, options) removed = self.core.remove_torrent(torrent_id, True) @@ -225,12 +225,12 @@ class CoreTestCase(BaseTestCase): def test_remove_torrents(self): options = {} filename = common.get_test_data_file('test.torrent') - with open(filename) as _file: + with open(filename, 'rb') as _file: filedump = base64.encodestring(_file.read()) torrent_id = yield self.core.add_torrent_file(filename, filedump, options) filename2 = common.get_test_data_file('unicode_filenames.torrent') - with open(filename2) as _file: + with open(filename2, 'rb') as _file: filedump = base64.encodestring(_file.read()) torrent_id2 = yield self.core.add_torrent_file(filename2, filedump, options) d = self.core.remove_torrents([torrent_id, torrent_id2], True) @@ -248,7 +248,7 @@ class CoreTestCase(BaseTestCase): def test_remove_torrents_invalid(self): options = {} filename = common.get_test_data_file('test.torrent') - with open(filename) as _file: + with open(filename, 'rb') as _file: filedump = base64.encodestring(_file.read()) torrent_id = yield self.core.add_torrent_file(filename, filedump, options) val = yield self.core.remove_torrents(['invalidid1', 'invalidid2', torrent_id], False) diff --git a/deluge/tests/test_ui_entry.py b/deluge/tests/test_ui_entry.py index 2cabf1824..977b84dcc 100644 --- a/deluge/tests/test_ui_entry.py +++ b/deluge/tests/test_ui_entry.py @@ -10,9 +10,8 @@ from __future__ import print_function, unicode_literals import argparse -import exceptions -import StringIO import sys +from io import StringIO import mock import pytest @@ -45,13 +44,14 @@ sys_stdout = sys.stdout class StringFileDescriptor(object): """File descriptor that writes to string buffer""" def __init__(self, fd): - self.out = StringIO.StringIO() + self.out = StringIO() self.fd = fd for a in ['encoding']: setattr(self, a, getattr(sys_stdout, a)) def write(self, *data, **kwargs): - print(*data, file=self.out, end='') + # io.StringIO requires unicode strings. + print(unicode(*data), file=self.out, end='') def flush(self): self.out.flush() @@ -113,7 +113,7 @@ class DelugeEntryTestCase(BaseTestCase): self.patch(argparse._sys, 'stdout', fd) with mock.patch('deluge.ui.console.main.ConsoleUI'): - self.assertRaises(exceptions.SystemExit, ui_entry.start_ui) + self.assertRaises(SystemExit, ui_entry.start_ui) self.assertTrue('usage: deluge' in fd.out.getvalue()) self.assertTrue('UI Options:' in fd.out.getvalue()) self.assertTrue('* console' in fd.out.getvalue()) @@ -292,7 +292,7 @@ class ConsoleUIBaseTestCase(UIBaseTestCase): self.patch(argparse._sys, 'stdout', fd) with mock.patch('deluge.ui.console.main.ConsoleUI'): - self.assertRaises(exceptions.SystemExit, self.exec_command) + self.assertRaises(SystemExit, self.exec_command) std_output = fd.out.getvalue() self.assertTrue(('usage: %s' % self.var['cmd_name']) in std_output) # Check command name self.assertTrue('Common Options:' in std_output) @@ -314,7 +314,7 @@ class ConsoleUIBaseTestCase(UIBaseTestCase): self.patch(argparse._sys, 'stdout', fd) with mock.patch('deluge.ui.console.main.ConsoleUI'): - self.assertRaises(exceptions.SystemExit, self.exec_command) + self.assertRaises(SystemExit, self.exec_command) std_output = fd.out.getvalue() self.assertTrue('usage: info' in std_output) self.assertTrue('Show information about the torrents' in std_output) @@ -324,7 +324,7 @@ class ConsoleUIBaseTestCase(UIBaseTestCase): fd = StringFileDescriptor(sys.stdout) self.patch(argparse._sys, 'stderr', fd) with mock.patch('deluge.ui.console.main.ConsoleUI'): - self.assertRaises(exceptions.SystemExit, self.exec_command) + self.assertRaises(SystemExit, self.exec_command) self.assertTrue('unrecognized arguments: --ui' in fd.out.getvalue()) diff --git a/deluge/tests/test_web_api.py b/deluge/tests/test_web_api.py index ecec2991a..bc1330f90 100644 --- a/deluge/tests/test_web_api.py +++ b/deluge/tests/test_web_api.py @@ -9,7 +9,7 @@ from __future__ import unicode_literals -from StringIO import StringIO +from io import BytesIO from twisted.internet import defer, reactor from twisted.python.failure import Failure @@ -172,5 +172,5 @@ class WebAPITestCase(WebServerTestBase): b'http://127.0.0.1:%s/json' % self.webserver_listen_port, Headers({b'User-Agent': [b'Twisted Web Client Example'], b'Content-Type': [b'application/json']}), - FileBodyProducer(StringIO(bad_body))) + FileBodyProducer(BytesIO(bad_body))) yield d diff --git a/deluge/tests/test_webserver.py b/deluge/tests/test_webserver.py index a98b4c090..ef2c0a773 100644 --- a/deluge/tests/test_webserver.py +++ b/deluge/tests/test_webserver.py @@ -10,7 +10,7 @@ from __future__ import unicode_literals import json as json_lib -from StringIO import StringIO +from io import BytesIO import twisted.web.client from twisted.internet import defer, reactor @@ -47,7 +47,7 @@ class WebServerTestCase(WebServerTestBase, WebServerMockBase): url = 'http://127.0.0.1:%s/json' % self.webserver_listen_port d = yield agent.request(b'POST', url.encode('utf-8'), Headers(utf8_encode_structure(headers)), - FileBodyProducer(StringIO(input_file.encode('utf-8')))) + FileBodyProducer(BytesIO(input_file.encode('utf-8')))) try: body = yield twisted.web.client.readBody(d) except AttributeError: diff --git a/deluge/ui/Win32IconImagePlugin.py b/deluge/ui/Win32IconImagePlugin.py index 8d88ef430..b1a0cf6e1 100644 --- a/deluge/ui/Win32IconImagePlugin.py +++ b/deluge/ui/Win32IconImagePlugin.py @@ -48,7 +48,6 @@ from __future__ import division, unicode_literals import logging import struct -from future_builtins import zip import PIL.BmpImagePlugin import PIL.Image @@ -56,6 +55,12 @@ import PIL.ImageChops import PIL.ImageFile import PIL.PngImagePlugin +try: + from future_builtins import zip +except ImportError: + # Ignore on Py3. + pass + _MAGIC = '\0\0\1\0' log = logging.getLogger(__name__) diff --git a/deluge/ui/common.py b/deluge/ui/common.py index 7f1f09d73..ef7dda9af 100644 --- a/deluge/ui/common.py +++ b/deluge/ui/common.py @@ -166,7 +166,7 @@ del _ DEFAULT_HOST = '127.0.0.1' DEFAULT_PORT = 58846 DEFAULT_HOSTS = { - 'hosts': [(sha(str(time.time())).hexdigest(), DEFAULT_HOST, DEFAULT_PORT, '', '')] + 'hosts': [(sha(str(time.time()).encode('utf8')).hexdigest(), DEFAULT_HOST, DEFAULT_PORT, '', '')] } # The keys from session statistics for cache status. diff --git a/deluge/ui/console/cmdline/commands/config.py b/deluge/ui/console/cmdline/commands/config.py index cf63e0033..f3f9dfefa 100644 --- a/deluge/ui/console/cmdline/commands/config.py +++ b/deluge/ui/console/cmdline/commands/config.py @@ -10,9 +10,9 @@ from __future__ import unicode_literals -import cStringIO import logging import tokenize +from io import StringIO import deluge.component as component import deluge.ui.console.utils.colors as colors @@ -58,7 +58,7 @@ def atom(src, token): def simple_eval(source): """ evaluates the 'source' string into a combination of primitive python objects taken from http://effbot.org/zone/simple-iterator-parser.htm""" - src = cStringIO.StringIO(source).readline + src = StringIO(source).readline src = tokenize.generate_tokens(src) src = (token for token in src if token[0] is not tokenize.NL) res = atom(src, next(src)) diff --git a/deluge/ui/console/modes/addtorrents.py b/deluge/ui/console/modes/addtorrents.py index 2edd5d502..e46cd6d45 100644 --- a/deluge/ui/console/modes/addtorrents.py +++ b/deluge/ui/console/modes/addtorrents.py @@ -12,7 +12,6 @@ from __future__ import unicode_literals import base64 import logging import os -from future_builtins import zip import deluge.common import deluge.component as component @@ -25,6 +24,12 @@ from deluge.ui.console.utils import format_utils from deluge.ui.console.widgets.popup import InputPopup, MessagePopup try: + from future_builtins import zip +except ImportError: + # Ignore on Py3. + pass + +try: import curses except ImportError: pass diff --git a/deluge/ui/gtkui/connectionmanager.py b/deluge/ui/gtkui/connectionmanager.py index 0225a4667..095816f30 100644 --- a/deluge/ui/gtkui/connectionmanager.py +++ b/deluge/ui/gtkui/connectionmanager.py @@ -14,7 +14,6 @@ import logging import os import time from socket import gaierror, gethostbyname -from urlparse import urlparse import gtk from twisted.internet import reactor @@ -28,6 +27,12 @@ from deluge.ui.common import get_localhost_auth from deluge.ui.gtkui.common import get_clipboard_text, get_deluge_icon, get_logo from deluge.ui.gtkui.dialogs import AuthenticationDialog, ErrorDialog +try: + from urllib.parse import urlparse +except ImportError: + # PY2 fallback + from urlparse import urlparse # pylint: disable=ungrouped-imports + log = logging.getLogger(__name__) DEFAULT_HOST = '127.0.0.1' diff --git a/deluge/ui/gtkui/peers_tab.py b/deluge/ui/gtkui/peers_tab.py index 7d9bd9652..a989edef3 100644 --- a/deluge/ui/gtkui/peers_tab.py +++ b/deluge/ui/gtkui/peers_tab.py @@ -11,7 +11,6 @@ from __future__ import unicode_literals import logging import os.path -from future_builtins import zip from gtk import (TREE_VIEW_COLUMN_FIXED, Builder, CellRendererPixbuf, CellRendererProgress, CellRendererText, ListStore, TreeViewColumn) @@ -25,6 +24,12 @@ from deluge.ui.gtkui.common import icon_downloading, icon_seeding, load_pickled_ from deluge.ui.gtkui.torrentdetails import Tab from deluge.ui.gtkui.torrentview_data_funcs import cell_data_peer_progress, cell_data_speed_down, cell_data_speed_up +try: + from future_builtins import zip +except ImportError: + # Ignore on Py3. + pass + log = logging.getLogger(__name__) diff --git a/deluge/ui/gtkui/preferences.py b/deluge/ui/gtkui/preferences.py index c8323a754..ec94d51c9 100644 --- a/deluge/ui/gtkui/preferences.py +++ b/deluge/ui/gtkui/preferences.py @@ -13,7 +13,6 @@ from __future__ import unicode_literals import logging import os from hashlib import sha1 as sha -from urlparse import urlparse import gtk from gtk.gdk import Color @@ -30,6 +29,12 @@ from deluge.ui.gtkui.path_chooser import PathChooser from deluge.ui.translations_util import get_languages try: + from urllib.parse import urlparse +except ImportError: + # PY2 fallback + from urlparse import urlparse # pylint: disable=ungrouped-imports + +try: import appindicator except ImportError: appindicator = False diff --git a/deluge/ui/tracker_icons.py b/deluge/ui/tracker_icons.py index 7e69d810c..dd367ec3b 100644 --- a/deluge/ui/tracker_icons.py +++ b/deluge/ui/tracker_icons.py @@ -11,9 +11,7 @@ from __future__ import unicode_literals import logging import os -from HTMLParser import HTMLParseError, HTMLParser from tempfile import mkstemp -from urlparse import urljoin, urlparse from twisted.internet import defer, threads from twisted.web.error import PageRedirect @@ -25,6 +23,14 @@ from deluge.decorators import proxy from deluge.httpdownloader import download_file try: + from html.parser import HTMLParser + from urllib.parse import urljoin, urlparse +except ImportError: + # PY2 fallback + from HTMLParser import HTMLParser + from urlparse import urljoin, urlparse # pylint: disable=ungrouped-imports + +try: import PIL.Image as Image except ImportError: PIL_INSTALLED = False @@ -411,7 +417,7 @@ class TrackerIcons(Component): callbackArgs=(host,), errbackArgs=(host,)) elif f.check(NoResource, ForbiddenResource) and icons: d = self.download_icon(icons, host) - elif f.check(NoIconsError, HTMLParseError): + elif f.check(NoIconsError): # No icons, try favicon.ico as an act of desperation d = self.download_icon([(urljoin(self.host_to_url(host), 'favicon.ico'), extension_to_mimetype('ico'))], host) |