[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.
This commit is contained in:
Calum Lind 2017-03-05 09:29:51 +00:00
commit 481f779349
27 changed files with 216 additions and 145 deletions

View file

@ -9,32 +9,39 @@
# License. # License.
# Written by Petru Paler # 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): class BTFailure(Exception):
pass 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): def decode_int(x, f):
f += 1 f += 1
newf = x.index('e', f) newf = x.index(END_DELIM, f)
n = int(x[f:newf]) n = int(x[f:newf])
if x[f] == '-': if x[f:f+1] == b'-' and x[f+1:f+2] == b'0':
if x[f + 1] == '0': raise ValueError
raise ValueError elif x[f:f+1] == b'0' and newf != f + 1:
elif x[f] == '0' and newf != f + 1:
raise ValueError raise ValueError
return (n, newf + 1) return (n, newf + 1)
def decode_string(x, f): def decode_string(x, f):
colon = x.index(':', f) colon = x.index(BYTE_SEP, f)
n = int(x[f:colon]) 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 raise ValueError
colon += 1 colon += 1
return (x[colon:colon + n], colon + n) return (x[colon:colon + n], colon + n)
@ -42,43 +49,43 @@ def decode_string(x, f):
def decode_list(x, f): def decode_list(x, f):
r, f = [], f + 1 r, f = [], f + 1
while x[f] != 'e': while x[f:f+1] != END_DELIM:
v, f = decode_func[x[f]](x, f) v, f = decode_func[x[f:f+1]](x, f)
r.append(v) r.append(v)
return (r, f + 1) return (r, f + 1)
def decode_dict(x, f): def decode_dict(x, f):
r, f = {}, f + 1 r, f = {}, f + 1
while x[f] != 'e': while x[f:f+1] != END_DELIM:
k, f = decode_string(x, f) 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) return (r, f + 1)
decode_func = {} decode_func = {}
decode_func['l'] = decode_list decode_func[LIST_DELIM] = decode_list
decode_func['d'] = decode_dict decode_func[DICT_DELIM] = decode_dict
decode_func['i'] = decode_int decode_func[INT_DELIM] = decode_int
decode_func['0'] = decode_string decode_func[b'0'] = decode_string
decode_func['1'] = decode_string decode_func[b'1'] = decode_string
decode_func['2'] = decode_string decode_func[b'2'] = decode_string
decode_func['3'] = decode_string decode_func[b'3'] = decode_string
decode_func['4'] = decode_string decode_func[b'4'] = decode_string
decode_func['5'] = decode_string decode_func[b'5'] = decode_string
decode_func['6'] = decode_string decode_func[b'6'] = decode_string
decode_func['7'] = decode_string decode_func[b'7'] = decode_string
decode_func['8'] = decode_string decode_func[b'8'] = decode_string
decode_func['9'] = decode_string decode_func[b'9'] = decode_string
def bdecode(x): def bdecode(x):
try: try:
r, l = decode_func[x[0]](x, 0) r, l = decode_func[x[0:1]](x, 0)
except (IndexError, KeyError, ValueError): except (IndexError, KeyError, ValueError):
raise BTFailure('not a valid bencoded string') raise BTFailure('Not a valid bencoded string')
else:
return r return r
class Bencached(object): class Bencached(object):
@ -94,53 +101,52 @@ def encode_bencached(x, r):
def encode_int(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): def encode_bool(x, r):
if x: encode_int(1 if x else 0, r)
encode_int(1, r)
else:
encode_int(0, r)
def encode_string(x, 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): def encode_list(x, r):
r.append('l') r.append(LIST_DELIM)
for i in x: for i in x:
encode_func[type(i)](i, r) encode_func[type(i)](i, r)
r.append('e') r.append(END_DELIM)
def encode_dict(x, r): def encode_dict(x, r):
r.append('d') r.append(DICT_DELIM)
ilist = sorted(x.items()) for k, v in sorted(x.items()):
for k, v in ilist: r.extend((str(len(k)).encode('utf8'), BYTE_SEP, k))
r.extend((str(len(k)), ':', k))
encode_func[type(v)](v, r) encode_func[type(v)](v, r)
r.append('e') r.append(END_DELIM)
encode_func = {} encode_func = {}
encode_func[Bencached] = encode_bencached encode_func[Bencached] = encode_bencached
encode_func[IntType] = encode_int encode_func[int] = encode_int
encode_func[LongType] = encode_int encode_func[list] = encode_list
encode_func[StringType] = encode_string encode_func[tuple] = encode_list
encode_func[ListType] = encode_list encode_func[dict] = encode_dict
encode_func[TupleType] = encode_list encode_func[bool] = encode_bool
encode_func[DictType] = encode_dict encode_func[str] = encode_string
encode_func[bytes] = encode_bytes
try: if PY2:
from types import BooleanType encode_func[long] = encode_int
encode_func[BooleanType] = encode_bool encode_func[str] = encode_bytes
except ImportError: encode_func[unicode] = encode_string
pass
def bencode(x): def bencode(x):
r = [] r = []
encode_func[type(x)](x, r) encode_func[type(x)](x, r)
return ''.join(r) return b''.join(r)

View file

@ -62,6 +62,9 @@ TORRENT_STATE = [
'Moving' 'Moving'
] ]
# The output formatting for json.dump
JSON_FORMAT = {'indent': 4, 'sort_keys': True, 'ensure_ascii': False}
PY2 = sys.version_info.major == 2 PY2 = sys.version_info.major == 2
@ -678,8 +681,12 @@ def create_magnet_uri(infohash, name=None, trackers=None):
str: A magnet uri string. 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: if name:
uri.extend(['&', DN_PARAM, name]) uri.extend(['&', DN_PARAM, name])
if trackers: if trackers:
@ -983,7 +990,7 @@ def create_localclient_account(append=False):
with open(auth_file, 'a' if append else 'w') as _file: with open(auth_file, 'a' if append else 'w') as _file:
_file.write(':'.join([ _file.write(':'.join([
'localclient', 'localclient',
sha(str(random.random())).hexdigest(), sha(str(random.random()).encode('utf8')).hexdigest(),
str(AUTH_LEVEL_ADMIN) str(AUTH_LEVEL_ADMIN)
]) + '\n') ]) + '\n')
_file.flush() _file.flush()
@ -1090,7 +1097,14 @@ def unicode_argv():
# As a last resort, just default to utf-8 # As a last resort, just default to utf-8
encoding = encoding or '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): def run_profiled(func, *args, **kwargs):
@ -1116,8 +1130,8 @@ def run_profiled(func, *args, **kwargs):
print('Profile stats saved to %s' % output_file) print('Profile stats saved to %s' % output_file)
else: else:
import pstats import pstats
import StringIO from io import StringIO
strio = StringIO.StringIO() strio = StringIO()
ps = pstats.Stats(profiler, stream=strio).sort_stats('cumulative') ps = pstats.Stats(profiler, stream=strio).sort_stats('cumulative')
ps.print_stats() ps.print_stats()
print(strio.getvalue()) print(strio.getvalue())

View file

@ -46,8 +46,10 @@ import json
import logging import logging
import os import os
import shutil 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__) log = logging.getLogger(__name__)
callLater = None # Necessary for the config tests callLater = None # Necessary for the config tests
@ -172,11 +174,6 @@ class Config(object):
5 5
""" """
try:
value = value.encode('utf8')
except AttributeError:
pass
if key not in self.__config: if key not in self.__config:
self.__config[key] = value self.__config[key] = value
log.debug('Setting key "%s" to: %s (of type: %s)', key, value, type(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) log.warning('Value Type "%s" invalid for key: %s', type(value), key)
raise 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 self.__config[key] = value
global callLater global callLater
@ -243,10 +242,7 @@ class Config(object):
5 5
""" """
try: return self.__config[key]
return self.__config[key].decode('utf8')
except AttributeError:
return self.__config[key]
def get(self, key, default=None): def get(self, key, default=None):
"""Gets the value of item 'key' if key is in the config, else default. """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 filename = self.__config_file
try: try:
with open(filename, 'rb') as _file: with open(filename, 'r', encoding='utf8') as _file:
data = _file.read() data = _file.read()
except IOError as ex: except IOError as ex:
log.warning('Unable to open config file %s: %s', filename, 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 # 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 # We will only write a new config file if there is a difference
try: try:
with open(filename, 'rb') as _file: with open(filename, 'r', encoding='utf8') as _file:
data = _file.read() data = _file.read()
objects = find_json_objects(data) objects = find_json_objects(data)
start, end = objects[0] start, end = objects[0]
@ -463,8 +459,8 @@ class Config(object):
try: try:
log.debug('Saving new config file %s', filename + '.new') log.debug('Saving new config file %s', filename + '.new')
with open(filename + '.new', 'wb') as _file: with open(filename + '.new', 'wb') as _file:
json.dump(self.__version, _file, indent=2) json.dump(self.__version, getwriter('utf8')(_file), **JSON_FORMAT)
json.dump(self.__config, _file, indent=2, sort_keys=True) json.dump(self.__config, getwriter('utf8')(_file), **JSON_FORMAT)
_file.flush() _file.flush()
os.fsync(_file.fileno()) os.fsync(_file.fileno())
except IOError as ex: except IOError as ex:

View file

@ -13,6 +13,7 @@ from __future__ import unicode_literals
import logging import logging
import os import os
import shutil import shutil
from io import open
import deluge.component as component import deluge.component as component
import deluge.configmanager as configmanager import deluge.configmanager as configmanager
@ -178,7 +179,7 @@ class AuthManager(component.Component):
else: else:
log.info('Saving the %s at: %s', filename, filepath) log.info('Saving the %s at: %s', filename, filepath)
try: try:
with open(filepath_tmp, 'wb') as _file: with open(filepath_tmp, 'w', encoding='utf8') as _file:
for account in self.__auth.values(): for account in self.__auth.values():
_file.write('%(username)s:%(password)s:%(authlevel_int)s\n' % account.data()) _file.write('%(username)s:%(password)s:%(authlevel_int)s\n' % account.data())
_file.flush() _file.flush()
@ -213,7 +214,7 @@ class AuthManager(component.Component):
for _filepath in (auth_file, auth_file_bak): for _filepath in (auth_file, auth_file_bak):
log.info('Opening %s for load: %s', filename, _filepath) log.info('Opening %s for load: %s', filename, _filepath)
try: try:
with open(_filepath, 'rb') as _file: with open(_filepath, 'r', encoding='utf8') as _file:
file_data = _file.readlines() file_data = _file.readlines()
except IOError as ex: except IOError as ex:
log.warning('Unable to load %s: %s', _filepath, ex) log.warning('Unable to load %s: %s', _filepath, ex)

View file

@ -45,7 +45,7 @@ def is_daemon_running(pid_file):
try: try:
with open(pid_file) as _file: with open(pid_file) as _file:
pid, port = [int(x) for x in _file.readline().strip().split(';')] pid, port = [int(x) for x in _file.readline().strip().split(';')]
except EnvironmentError: except (EnvironmentError, ValueError):
return False return False
if is_process_running(pid): 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. # Create pid file to track if deluged is running, also includes the port number.
pid = os.getpid() pid = os.getpid()
log.debug('Storing pid %s & port %s in: %s', pid, self.port, self.pid_file) 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)) _file.write('%s;%s\n' % (pid, self.port))
component.start() component.start()

View file

@ -563,7 +563,9 @@ def generate_ssl_keys():
""" """
This method generates a new SSL key/cert. 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 # Generate key pair
pkey = crypto.PKey() pkey = crypto.PKey()
pkey.generate_key(crypto.TYPE_RSA, 2048) pkey.generate_key(crypto.TYPE_RSA, 2048)
@ -587,9 +589,9 @@ def generate_ssl_keys():
# Write out files # Write out files
ssl_dir = deluge.configmanager.get_config_dir('ssl') 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)) _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)) _file.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
# Make the files only readable by this user # Make the files only readable by this user
for f in ('daemon.pkey', 'daemon.cert'): for f in ('daemon.pkey', 'daemon.cert'):

View file

@ -19,8 +19,6 @@ from __future__ import division, unicode_literals
import logging import logging
import os import os
import socket import socket
from future_builtins import zip
from urlparse import urlparse
from twisted.internet.defer import Deferred, DeferredList 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.decorators import deprecated
from deluge.event import TorrentFolderRenamedEvent, TorrentStateChangedEvent, TorrentTrackerStatusEvent 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__) log = logging.getLogger(__name__)
LT_TORRENT_STATE_MAP = { LT_TORRENT_STATE_MAP = {
@ -95,9 +105,14 @@ def convert_lt_files(files):
""" """
filelist = [] filelist = []
for index, _file in enumerate(files): for index, _file in enumerate(files):
try:
file_path = _file.path.decode('utf8')
except AttributeError:
file_path = _file.path
filelist.append({ filelist.append({
'index': index, 'index': index,
'path': _file.path.decode('utf8').replace('\\', '/'), 'path': file_path.replace('\\', '/'),
'size': _file.size, 'size': _file.size,
'offset': _file.offset 'offset': _file.offset
}) })

View file

@ -12,7 +12,6 @@ from __future__ import unicode_literals
import logging import logging
import os.path import os.path
import zlib import zlib
from urlparse import urljoin
from twisted.internet import reactor from twisted.internet import reactor
from twisted.python.failure import Failure 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 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__) log = logging.getLogger(__name__)
@ -52,7 +57,7 @@ class HTTPDownloader(client.HTTPDownloader):
self.force_filename = force_filename self.force_filename = force_filename
self.allow_compression = allow_compression self.allow_compression = allow_compression
self.code = None 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) client.HTTPDownloader.__init__(self, url, filename, headers=headers, agent=agent)

View file

@ -82,7 +82,7 @@ class Logging(LoggingLoggerClass):
def exception(self, msg, *args, **kwargs): def exception(self, msg, *args, **kwargs):
yield LoggingLoggerClass.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 f = logging.currentframe().f_back
rv = '(unknown file)', 0, '(unknown function)' rv = '(unknown file)', 0, '(unknown function)'
while hasattr(f, 'f_code'): while hasattr(f, 'f_code'):

View file

@ -16,7 +16,6 @@ import shutil
import time import time
from datetime import datetime, timedelta from datetime import datetime, timedelta
from email.utils import formatdate from email.utils import formatdate
from urlparse import urljoin
from twisted.internet import defer, threads from twisted.internet import defer, threads
from twisted.internet.task import LoopingCall 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 .detect import UnknownFormatError, create_reader, detect_compression, detect_format
from .readers import ReaderParseError 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: check return values for deferred callbacks
# TODO: review class attributes for redundancy # TODO: review class attributes for redundancy

View file

@ -19,12 +19,7 @@ def Zipped(reader): # NOQA: N802
"""Blocklist reader for zipped blocklists""" """Blocklist reader for zipped blocklists"""
def open(self): def open(self):
z = zipfile.ZipFile(self.file) z = zipfile.ZipFile(self.file)
if hasattr(z, 'open'): f = z.open(z.namelist()[0])
f = z.open(z.namelist()[0])
else:
# Handle python 2.5
import cStringIO
f = cStringIO.StringIO(z.read(z.namelist()[0]))
return f return f
reader.open = open reader.open = open
return reader return reader

View file

@ -60,9 +60,14 @@ same rencode version throughout your project.
import struct import struct
import sys import sys
from future_builtins import zip
from threading import Lock from threading import Lock
try:
from future_builtins import zip
except ImportError:
# Ignore on Py3.
pass
__version__ = ('Python', 1, 0, 4) __version__ = ('Python', 1, 0, 4)
__all__ = ['dumps', 'loads'] __all__ = ['dumps', 'loads']

View file

@ -185,6 +185,7 @@ class ProcessOutputHandler(protocol.ProcessProtocol):
def outReceived(self, data): # NOQA: N802 def outReceived(self, data): # NOQA: N802
"""Process output from stdout""" """Process output from stdout"""
data = data.decode('utf8')
self.log_output += data self.log_output += data
if self.check_callbacks(data): if self.check_callbacks(data):
pass pass
@ -193,6 +194,7 @@ class ProcessOutputHandler(protocol.ProcessProtocol):
def errReceived(self, data): # NOQA: N802 def errReceived(self, data): # NOQA: N802
"""Process output from stderr""" """Process output from stderr"""
data = data.decode('utf8')
self.log_output += data self.log_output += data
self.stderr_out += data self.stderr_out += data
self.check_callbacks(data, cb_type='stderr') self.check_callbacks(data, cb_type='stderr')

View file

@ -8,11 +8,13 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import os import os
from codecs import getwriter
from twisted.internet import task from twisted.internet import task
from twisted.trial import unittest from twisted.trial import unittest
import deluge.config import deluge.config
from deluge.common import JSON_FORMAT
from deluge.config import Config from deluge.config import Config
from .common import set_tmp_config_dir from .common import set_tmp_config_dir
@ -88,17 +90,10 @@ class ConfigTestCase(unittest.TestCase):
self.assertEqual(config['string'], 'foobar') self.assertEqual(config['string'], 'foobar')
self.assertEqual(config['float'], 0.435) 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 # Test opening a previous 1.2 config file of just a json object
import json import json
with open(os.path.join(self.config_dir, 'test.conf'), 'wb') as _file: 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() check_config()
@ -107,15 +102,15 @@ class ConfigTestCase(unittest.TestCase):
with open(os.path.join(self.config_dir, 'test.conf'), 'wb') as _file: with open(os.path.join(self.config_dir, 'test.conf'), 'wb') as _file:
_file.write(str(1) + '\n') _file.write(str(1) + '\n')
_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() check_config()
# Test the 1.2 config format # Test the 1.2 config format
version = {'format': 1, 'file': 1} version = {'format': 1, 'file': 1}
with open(os.path.join(self.config_dir, 'test.conf'), 'wb') as _file: with open(os.path.join(self.config_dir, 'test.conf'), 'wb') as _file:
json.dump(version, _file, indent=2) json.dump(version, getwriter('utf8')(_file), **JSON_FORMAT)
json.dump(DEFAULTS, _file, indent=2) json.dump(DEFAULTS, getwriter('utf8')(_file), **JSON_FORMAT)
check_config() check_config()

View file

@ -42,7 +42,7 @@ class CookieResource(Resource):
return return
request.setHeader(b'Content-Type', b'application/x-bittorrent') 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() data = _file.read()
return data return data
@ -50,7 +50,7 @@ class CookieResource(Resource):
class PartialDownload(Resource): class PartialDownload(Resource):
def render(self, request): 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() data = _file.read()
request.setHeader(b'Content-Type', len(data)) request.setHeader(b'Content-Type', len(data))
request.setHeader(b'Content-Type', b'application/x-bittorrent') request.setHeader(b'Content-Type', b'application/x-bittorrent')
@ -119,7 +119,7 @@ class CoreTestCase(BaseTestCase):
files_to_add = [] files_to_add = []
for f in filenames: for f in filenames:
filename = common.get_test_data_file(f) filename = common.get_test_data_file(f)
with open(filename) as _file: with open(filename, 'rb') as _file:
filedump = base64.encodestring(_file.read()) filedump = base64.encodestring(_file.read())
files_to_add.append((filename, filedump, options)) files_to_add.append((filename, filedump, options))
errors = yield self.core.add_torrent_files(files_to_add) errors = yield self.core.add_torrent_files(files_to_add)
@ -132,7 +132,7 @@ class CoreTestCase(BaseTestCase):
files_to_add = [] files_to_add = []
for f in filenames: for f in filenames:
filename = common.get_test_data_file(f) filename = common.get_test_data_file(f)
with open(filename) as _file: with open(filename, 'rb') as _file:
filedump = base64.encodestring(_file.read()) filedump = base64.encodestring(_file.read())
files_to_add.append((filename, filedump, options)) files_to_add.append((filename, filedump, options))
errors = yield self.core.add_torrent_files(files_to_add) errors = yield self.core.add_torrent_files(files_to_add)
@ -143,15 +143,15 @@ class CoreTestCase(BaseTestCase):
def test_add_torrent_file(self): def test_add_torrent_file(self):
options = {} options = {}
filename = common.get_test_data_file('test.torrent') filename = common.get_test_data_file('test.torrent')
with open(filename) as _file: with open(filename, 'rb') as _file:
filedump = base64.encodestring(_file.read()) filedump = base64.encodestring(_file.read())
torrent_id = yield self.core.add_torrent_file(filename, filedump, options) torrent_id = yield self.core.add_torrent_file(filename, filedump, options)
# Get the info hash from the test.torrent # Get the info hash from the test.torrent
from deluge.bencode import bdecode, bencode from deluge.bencode import bdecode, bencode
with open(filename) as _file: with open(filename, 'rb') as _file:
info_hash = sha(bencode(bdecode(_file.read())['info'])).hexdigest() info_hash = sha(bencode(bdecode(_file.read())[b'info'])).hexdigest()
self.assertEqual(torrent_id, info_hash) self.assertEquals(torrent_id, info_hash)
def test_add_torrent_file_invalid_filedump(self): def test_add_torrent_file_invalid_filedump(self):
options = {} options = {}
@ -211,7 +211,7 @@ class CoreTestCase(BaseTestCase):
def test_remove_torrent(self): def test_remove_torrent(self):
options = {} options = {}
filename = common.get_test_data_file('test.torrent') filename = common.get_test_data_file('test.torrent')
with open(filename) as _file: with open(filename, 'rb') as _file:
filedump = base64.encodestring(_file.read()) filedump = base64.encodestring(_file.read())
torrent_id = yield self.core.add_torrent_file(filename, filedump, options) torrent_id = yield self.core.add_torrent_file(filename, filedump, options)
removed = self.core.remove_torrent(torrent_id, True) removed = self.core.remove_torrent(torrent_id, True)
@ -225,12 +225,12 @@ class CoreTestCase(BaseTestCase):
def test_remove_torrents(self): def test_remove_torrents(self):
options = {} options = {}
filename = common.get_test_data_file('test.torrent') filename = common.get_test_data_file('test.torrent')
with open(filename) as _file: with open(filename, 'rb') as _file:
filedump = base64.encodestring(_file.read()) filedump = base64.encodestring(_file.read())
torrent_id = yield self.core.add_torrent_file(filename, filedump, options) torrent_id = yield self.core.add_torrent_file(filename, filedump, options)
filename2 = common.get_test_data_file('unicode_filenames.torrent') 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()) filedump = base64.encodestring(_file.read())
torrent_id2 = yield self.core.add_torrent_file(filename2, filedump, options) torrent_id2 = yield self.core.add_torrent_file(filename2, filedump, options)
d = self.core.remove_torrents([torrent_id, torrent_id2], True) d = self.core.remove_torrents([torrent_id, torrent_id2], True)
@ -248,7 +248,7 @@ class CoreTestCase(BaseTestCase):
def test_remove_torrents_invalid(self): def test_remove_torrents_invalid(self):
options = {} options = {}
filename = common.get_test_data_file('test.torrent') filename = common.get_test_data_file('test.torrent')
with open(filename) as _file: with open(filename, 'rb') as _file:
filedump = base64.encodestring(_file.read()) filedump = base64.encodestring(_file.read())
torrent_id = yield self.core.add_torrent_file(filename, filedump, options) torrent_id = yield self.core.add_torrent_file(filename, filedump, options)
val = yield self.core.remove_torrents(['invalidid1', 'invalidid2', torrent_id], False) val = yield self.core.remove_torrents(['invalidid1', 'invalidid2', torrent_id], False)

View file

@ -10,9 +10,8 @@
from __future__ import print_function, unicode_literals from __future__ import print_function, unicode_literals
import argparse import argparse
import exceptions
import StringIO
import sys import sys
from io import StringIO
import mock import mock
import pytest import pytest
@ -45,13 +44,14 @@ sys_stdout = sys.stdout
class StringFileDescriptor(object): class StringFileDescriptor(object):
"""File descriptor that writes to string buffer""" """File descriptor that writes to string buffer"""
def __init__(self, fd): def __init__(self, fd):
self.out = StringIO.StringIO() self.out = StringIO()
self.fd = fd self.fd = fd
for a in ['encoding']: for a in ['encoding']:
setattr(self, a, getattr(sys_stdout, a)) setattr(self, a, getattr(sys_stdout, a))
def write(self, *data, **kwargs): 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): def flush(self):
self.out.flush() self.out.flush()
@ -113,7 +113,7 @@ class DelugeEntryTestCase(BaseTestCase):
self.patch(argparse._sys, 'stdout', fd) self.patch(argparse._sys, 'stdout', fd)
with mock.patch('deluge.ui.console.main.ConsoleUI'): 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('usage: deluge' in fd.out.getvalue())
self.assertTrue('UI Options:' in fd.out.getvalue()) self.assertTrue('UI Options:' in fd.out.getvalue())
self.assertTrue('* console' in fd.out.getvalue()) self.assertTrue('* console' in fd.out.getvalue())
@ -292,7 +292,7 @@ class ConsoleUIBaseTestCase(UIBaseTestCase):
self.patch(argparse._sys, 'stdout', fd) self.patch(argparse._sys, 'stdout', fd)
with mock.patch('deluge.ui.console.main.ConsoleUI'): 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() std_output = fd.out.getvalue()
self.assertTrue(('usage: %s' % self.var['cmd_name']) in std_output) # Check command name self.assertTrue(('usage: %s' % self.var['cmd_name']) in std_output) # Check command name
self.assertTrue('Common Options:' in std_output) self.assertTrue('Common Options:' in std_output)
@ -314,7 +314,7 @@ class ConsoleUIBaseTestCase(UIBaseTestCase):
self.patch(argparse._sys, 'stdout', fd) self.patch(argparse._sys, 'stdout', fd)
with mock.patch('deluge.ui.console.main.ConsoleUI'): 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() std_output = fd.out.getvalue()
self.assertTrue('usage: info' in std_output) self.assertTrue('usage: info' in std_output)
self.assertTrue('Show information about the torrents' in std_output) self.assertTrue('Show information about the torrents' in std_output)
@ -324,7 +324,7 @@ class ConsoleUIBaseTestCase(UIBaseTestCase):
fd = StringFileDescriptor(sys.stdout) fd = StringFileDescriptor(sys.stdout)
self.patch(argparse._sys, 'stderr', fd) self.patch(argparse._sys, 'stderr', fd)
with mock.patch('deluge.ui.console.main.ConsoleUI'): 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()) self.assertTrue('unrecognized arguments: --ui' in fd.out.getvalue())

View file

@ -9,7 +9,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from StringIO import StringIO from io import BytesIO
from twisted.internet import defer, reactor from twisted.internet import defer, reactor
from twisted.python.failure import Failure 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, b'http://127.0.0.1:%s/json' % self.webserver_listen_port,
Headers({b'User-Agent': [b'Twisted Web Client Example'], Headers({b'User-Agent': [b'Twisted Web Client Example'],
b'Content-Type': [b'application/json']}), b'Content-Type': [b'application/json']}),
FileBodyProducer(StringIO(bad_body))) FileBodyProducer(BytesIO(bad_body)))
yield d yield d

View file

@ -10,7 +10,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import json as json_lib import json as json_lib
from StringIO import StringIO from io import BytesIO
import twisted.web.client import twisted.web.client
from twisted.internet import defer, reactor 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 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)), 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: try:
body = yield twisted.web.client.readBody(d) body = yield twisted.web.client.readBody(d)
except AttributeError: except AttributeError:

View file

@ -48,7 +48,6 @@ from __future__ import division, unicode_literals
import logging import logging
import struct import struct
from future_builtins import zip
import PIL.BmpImagePlugin import PIL.BmpImagePlugin
import PIL.Image import PIL.Image
@ -56,6 +55,12 @@ import PIL.ImageChops
import PIL.ImageFile import PIL.ImageFile
import PIL.PngImagePlugin import PIL.PngImagePlugin
try:
from future_builtins import zip
except ImportError:
# Ignore on Py3.
pass
_MAGIC = '\0\0\1\0' _MAGIC = '\0\0\1\0'
log = logging.getLogger(__name__) log = logging.getLogger(__name__)

View file

@ -166,7 +166,7 @@ del _
DEFAULT_HOST = '127.0.0.1' DEFAULT_HOST = '127.0.0.1'
DEFAULT_PORT = 58846 DEFAULT_PORT = 58846
DEFAULT_HOSTS = { 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. # The keys from session statistics for cache status.

View file

@ -10,9 +10,9 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import cStringIO
import logging import logging
import tokenize import tokenize
from io import StringIO
import deluge.component as component import deluge.component as component
import deluge.ui.console.utils.colors as colors import deluge.ui.console.utils.colors as colors
@ -58,7 +58,7 @@ def atom(src, token):
def simple_eval(source): def simple_eval(source):
""" evaluates the 'source' string into a combination of primitive python objects """ evaluates the 'source' string into a combination of primitive python objects
taken from http://effbot.org/zone/simple-iterator-parser.htm""" 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 = tokenize.generate_tokens(src)
src = (token for token in src if token[0] is not tokenize.NL) src = (token for token in src if token[0] is not tokenize.NL)
res = atom(src, next(src)) res = atom(src, next(src))

View file

@ -12,7 +12,6 @@ from __future__ import unicode_literals
import base64 import base64
import logging import logging
import os import os
from future_builtins import zip
import deluge.common import deluge.common
import deluge.component as component import deluge.component as component
@ -24,6 +23,12 @@ from deluge.ui.console.utils import curses_util as util
from deluge.ui.console.utils import format_utils from deluge.ui.console.utils import format_utils
from deluge.ui.console.widgets.popup import InputPopup, MessagePopup from deluge.ui.console.widgets.popup import InputPopup, MessagePopup
try:
from future_builtins import zip
except ImportError:
# Ignore on Py3.
pass
try: try:
import curses import curses
except ImportError: except ImportError:

View file

@ -14,7 +14,6 @@ import logging
import os import os
import time import time
from socket import gaierror, gethostbyname from socket import gaierror, gethostbyname
from urlparse import urlparse
import gtk import gtk
from twisted.internet import reactor 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.common import get_clipboard_text, get_deluge_icon, get_logo
from deluge.ui.gtkui.dialogs import AuthenticationDialog, ErrorDialog 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__) log = logging.getLogger(__name__)
DEFAULT_HOST = '127.0.0.1' DEFAULT_HOST = '127.0.0.1'

View file

@ -11,7 +11,6 @@ from __future__ import unicode_literals
import logging import logging
import os.path import os.path
from future_builtins import zip
from gtk import (TREE_VIEW_COLUMN_FIXED, Builder, CellRendererPixbuf, CellRendererProgress, CellRendererText, ListStore, from gtk import (TREE_VIEW_COLUMN_FIXED, Builder, CellRendererPixbuf, CellRendererProgress, CellRendererText, ListStore,
TreeViewColumn) 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.torrentdetails import Tab
from deluge.ui.gtkui.torrentview_data_funcs import cell_data_peer_progress, cell_data_speed_down, cell_data_speed_up 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__) log = logging.getLogger(__name__)

View file

@ -13,7 +13,6 @@ from __future__ import unicode_literals
import logging import logging
import os import os
from hashlib import sha1 as sha from hashlib import sha1 as sha
from urlparse import urlparse
import gtk import gtk
from gtk.gdk import Color from gtk.gdk import Color
@ -29,6 +28,12 @@ from deluge.ui.gtkui.dialogs import AccountDialog, ErrorDialog, InformationDialo
from deluge.ui.gtkui.path_chooser import PathChooser from deluge.ui.gtkui.path_chooser import PathChooser
from deluge.ui.translations_util import get_languages 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: try:
import appindicator import appindicator
except ImportError: except ImportError:

View file

@ -11,9 +11,7 @@ from __future__ import unicode_literals
import logging import logging
import os import os
from HTMLParser import HTMLParseError, HTMLParser
from tempfile import mkstemp from tempfile import mkstemp
from urlparse import urljoin, urlparse
from twisted.internet import defer, threads from twisted.internet import defer, threads
from twisted.web.error import PageRedirect from twisted.web.error import PageRedirect
@ -24,6 +22,14 @@ from deluge.configmanager import get_config_dir
from deluge.decorators import proxy from deluge.decorators import proxy
from deluge.httpdownloader import download_file 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: try:
import PIL.Image as Image import PIL.Image as Image
except ImportError: except ImportError:
@ -411,7 +417,7 @@ class TrackerIcons(Component):
callbackArgs=(host,), errbackArgs=(host,)) callbackArgs=(host,), errbackArgs=(host,))
elif f.check(NoResource, ForbiddenResource) and icons: elif f.check(NoResource, ForbiddenResource) and icons:
d = self.download_icon(icons, host) 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 # No icons, try favicon.ico as an act of desperation
d = self.download_icon([(urljoin(self.host_to_url(host), 'favicon.ico'), d = self.download_icon([(urljoin(self.host_to_url(host), 'favicon.ico'),
extension_to_mimetype('ico'))], host) extension_to_mimetype('ico'))], host)

View file

@ -20,8 +20,7 @@ known_third_party =
# Ignore Windows specific modules. # Ignore Windows specific modules.
bbfreeze, win32verstamp, bbfreeze, win32verstamp,
# Ignore gtk modules, primarily for tox testing. # Ignore gtk modules, primarily for tox testing.
pygtk, gtk, gobject, gtk.gdk, pango, cairo, pangocairo, pygtk, gtk, gobject, gtk.gdk, pango, cairo, pangocairo
six
known_first_party = msgfmt, deluge known_first_party = msgfmt, deluge
order_by_type = true order_by_type = true
line_length = 120 line_length = 120