summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCalum Lind <calumlind@gmail.com>2017-03-05 09:29:51 +0000
committerCalum Lind <calumlind@gmail.com>2017-06-05 22:25:29 +0100
commit481f77934983284b67b7546470f52c9799bc6184 (patch)
tree6efeac6d6c038809f989385ec2e743ede0b2a4e1
parentede0f710f814c62f9e6625c7b166fb7b1fcf5942 (diff)
downloaddeluge-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.
-rw-r--r--deluge/bencode.py118
-rw-r--r--deluge/common.py24
-rw-r--r--deluge/config.py26
-rw-r--r--deluge/core/authmanager.py5
-rw-r--r--deluge/core/daemon.py4
-rw-r--r--deluge/core/rpcserver.py8
-rw-r--r--deluge/core/torrent.py21
-rw-r--r--deluge/httpdownloader.py9
-rw-r--r--deluge/log.py2
-rw-r--r--deluge/plugins/Blocklist/deluge/plugins/blocklist/core.py7
-rw-r--r--deluge/plugins/Blocklist/deluge/plugins/blocklist/decompressers.py7
-rw-r--r--deluge/rencode.py7
-rw-r--r--deluge/tests/common.py2
-rw-r--r--deluge/tests/test_config.py17
-rw-r--r--deluge/tests/test_core.py24
-rw-r--r--deluge/tests/test_ui_entry.py16
-rw-r--r--deluge/tests/test_web_api.py4
-rw-r--r--deluge/tests/test_webserver.py4
-rw-r--r--deluge/ui/Win32IconImagePlugin.py7
-rw-r--r--deluge/ui/common.py2
-rw-r--r--deluge/ui/console/cmdline/commands/config.py4
-rw-r--r--deluge/ui/console/modes/addtorrents.py7
-rw-r--r--deluge/ui/gtkui/connectionmanager.py7
-rw-r--r--deluge/ui/gtkui/peers_tab.py7
-rw-r--r--deluge/ui/gtkui/preferences.py7
-rw-r--r--deluge/ui/tracker_icons.py12
-rw-r--r--setup.cfg3
27 files changed, 216 insertions, 145 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)
diff --git a/setup.cfg b/setup.cfg
index 7066be629..b7924ac3d 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -20,8 +20,7 @@ known_third_party =
# Ignore Windows specific modules.
bbfreeze, win32verstamp,
# Ignore gtk modules, primarily for tox testing.
- pygtk, gtk, gobject, gtk.gdk, pango, cairo, pangocairo,
- six
+ pygtk, gtk, gobject, gtk.gdk, pango, cairo, pangocairo
known_first_party = msgfmt, deluge
order_by_type = true
line_length = 120