diff --git a/.drone.yml b/.drone.yml index 9d7c769d8c..c4fd423e94 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,9 +1,9 @@ -image: python2.7 +image: python:all env: - POSTGRESQL_URI=postgresql://postgres@127.0.0.1:5432/ - MYSQL_URI=mysql://root@127.0.0.1:3306/ script: - pip install tox - - tox -e "py27-{sqlite,postgresql}" + - tox -e "{py27,py33,py34,py35}-{sqlite,postgresql}" --skip-missing-interpreters services: - postgres diff --git a/CHANGELOG b/CHANGELOG index 4a281d831d..22a7a8f830 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Add Python3 support * Make TestCase create and drop its database * Add with_transaction decorator for tests * Add note on resources diff --git a/setup.py b/setup.py index c51c3a348a..4efa071aca 100644 --- a/setup.py +++ b/setup.py @@ -5,11 +5,14 @@ from setuptools import setup, find_packages import os import re +import io import platform def read(fname): - return open(os.path.join(os.path.dirname(__file__), fname)).read() + return io.open( + os.path.join(os.path.dirname(__file__), fname), + 'r', encoding='utf-8').read() def get_version(): @@ -78,6 +81,9 @@ setup(name=name, 'Natural Language :: Spanish', 'Operating System :: OS Independent', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Libraries :: Application Frameworks', @@ -106,4 +112,5 @@ setup(name=name, test_suite='trytond.tests', test_loader='trytond.test_loader:Loader', tests_require=['mock'], + use_2to3=True, ) diff --git a/tox.ini b/tox.ini index f8e91e9937..eee8cfdb68 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,11 @@ [tox] -envlist = py27-{sqlite,postgresql,mysql},pypy-{sqlite,postgresql} +envlist = {py27,py33,py34,py35}-{sqlite,postgresql,mysql},pypy-{sqlite,postgresql} [testenv] commands = {envpython} setup.py test deps = mock - py27-postgresql: psycopg2 >= 2.0 + {py27,py33,py34,py35}-postgresql: psycopg2 >= 2.0 pypy-postgresql: psycopg2cffi >= 2.5 mysql: MySQL-python setenv = diff --git a/trytond/backend/postgresql/database.py b/trytond/backend/postgresql/database.py index 7a8675c3ba..9d2fa11c2d 100644 --- a/trytond/backend/postgresql/database.py +++ b/trytond/backend/postgresql/database.py @@ -216,7 +216,6 @@ class Database(DatabaseInterface): 'WHERE datistemplate = false ORDER BY datname') res = [] for db_name, in cursor.fetchall(): - db_name = db_name.encode('utf-8') try: database = Database(db_name).connect() except Exception: diff --git a/trytond/backend/postgresql/table.py b/trytond/backend/postgresql/table.py index 66407f5f7b..10e62d7e7d 100644 --- a/trytond/backend/postgresql/table.py +++ b/trytond/backend/postgresql/table.py @@ -260,11 +260,11 @@ class TableHandler(TableHandlerInterface): and self._columns[column_name]['typname'] == 'varchar'): # Migrate size if field_size is None: - if self._columns[column_name]['size'] > 0: + if self._columns[column_name]['size']: self.alter_size(column_name, base_type) elif self._columns[column_name]['size'] == field_size: pass - elif (self._columns[column_name]['size'] > 0 + elif (self._columns[column_name]['size'] and self._columns[column_name]['size'] < field_size): self.alter_size(column_name, column_type[1]) else: diff --git a/trytond/ir/lang.py b/trytond/ir/lang.py index a6f8c24aa6..47d52dfdaf 100644 --- a/trytond/ir/lang.py +++ b/trytond/ir/lang.py @@ -1,7 +1,13 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. +from __future__ import absolute_import + +import sys import datetime import warnings +warnings.filterwarnings('ignore', "", ImportWarning) +from locale import CHAR_MAX +warnings.resetwarnings() from ast import literal_eval from ..model import ModelView, ModelSQL, fields, Check @@ -12,10 +18,6 @@ from ..pool import Pool from .time_locale import TIME_LOCALE from ..backend.database import CursorInterface -warnings.filterwarnings('ignore', "", ImportWarning) -from locale import CHAR_MAX -warnings.resetwarnings() - CursorInterface.cache_keys.add('translate_name') __all__ = [ @@ -154,9 +156,11 @@ class Lang(ModelSQL, ModelView): Check the date format ''' for lang in langs: + date = lang.date + if sys.version_info < (3,): + date = date.encode('utf-8') try: - datetime_strftime(datetime.datetime.now(), - lang.date.encode('utf-8')) + datetime_strftime(datetime.datetime.now(), date) except Exception: cls.raise_user_error('invalid_date', { 'format': lang.date, @@ -415,7 +419,10 @@ class Lang(ModelSQL, ModelView): TIME_LOCALE[code][f][datetime.timetuple()[i]]) format = format.replace('%p', TIME_LOCALE[code]['%p'][datetime.timetuple()[3] < 12 and 0 - or 1]).encode('utf-8') - else: - format = format.encode('utf-8') - return datetime_strftime(datetime, format).decode('utf-8') + or 1]) + if sys.version_info < (3,): + format.encode('utf-8') + result = datetime_strftime(datetime, format) + if sys.version_info < (3,): + result = result.decode('utf-8') + return result diff --git a/trytond/ir/translation.py b/trytond/ir/translation.py index 4e3f69fff7..675145d5a9 100644 --- a/trytond/ir/translation.py +++ b/trytond/ir/translation.py @@ -1,9 +1,5 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -try: - import cStringIO as StringIO -except ImportError: - import StringIO import zipfile import polib import xml.dom.minidom @@ -12,6 +8,7 @@ import os from hashlib import md5 from lxml import etree from itertools import izip +from io import BytesIO from sql import Column, Null from sql.functions import Substring, Position @@ -438,7 +435,7 @@ class Translation(ModelSQL, ModelView): fuzzy_sql = table.fuzzy == False if Transaction().context.get('fuzzy_translation', False): fuzzy_sql = None - in_max = cursor.IN_MAX / 7 + in_max = cursor.IN_MAX // 7 for sub_to_fetch in grouped_slice(to_fetch, in_max): red_sql = reduce_ids(table.res_id, sub_to_fetch) where = And(((table.lang == lang), @@ -673,7 +670,7 @@ class Translation(ModelSQL, ModelView): where &= table.src == source clause.append(where) if clause: - in_max = cursor.IN_MAX / 7 + in_max = cursor.IN_MAX // 7 for sub_clause in grouped_slice(clause, in_max): cursor.execute(*table.select( table.lang, table.type, table.name, table.src, @@ -1041,7 +1038,7 @@ class TranslationSet(Wizard): if not content: continue - content_io = StringIO.StringIO(content) + content_io = BytesIO(content) try: content_z = zipfile.ZipFile(content_io, mode='r') except zipfile.BadZipfile: diff --git a/trytond/ir/ui/view.py b/trytond/ir/ui/view.py index ac19d8b37f..24cfc3ac95 100644 --- a/trytond/ir/ui/view.py +++ b/trytond/ir/ui/view.py @@ -121,10 +121,13 @@ class View(ModelSQL, ModelView): key = (cls.__name__, type_) rng = cls._get_rng_cache.get(key) if rng is None: - rng_name = os.path.join(os.path.dirname( - unicode(__file__, sys.getfilesystemencoding())), - type_ + '.rng') - rng = etree.fromstring(open(rng_name).read()) + if sys.version_info < (3,): + filename = __file__.decode(sys.getfilesystemencoding()) + else: + filename = __file__ + rng_name = os.path.join(os.path.dirname(filename), type_ + '.rng') + with open(rng_name, 'rb') as fp: + rng = etree.fromstring(fp.read()) cls._get_rng_cache.set(key, rng) return rng diff --git a/trytond/model/fields/char.py b/trytond/model/fields/char.py index d2d6b611c4..83ba32370b 100644 --- a/trytond/model/fields/char.py +++ b/trytond/model/fields/char.py @@ -1,5 +1,6 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. +import sys import warnings from sql import Query, Expression @@ -53,7 +54,7 @@ class Char(FieldTranslate): return value if value is None: return None - elif isinstance(value, str): + elif isinstance(value, str) and sys.version_info < (3,): return unicode(value, 'utf-8') assert isinstance(value, unicode) return value diff --git a/trytond/model/fields/many2one.py b/trytond/model/fields/many2one.py index 3730dece72..7ed55598dd 100644 --- a/trytond/model/fields/many2one.py +++ b/trytond/model/fields/many2one.py @@ -1,6 +1,5 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -from types import NoneType from sql import Query, Expression, Literal from sql.operators import Or @@ -76,7 +75,7 @@ class Many2One(Field): value = Target(**value) elif isinstance(value, (int, long)): value = Target(value) - assert isinstance(value, (Target, NoneType)) + assert isinstance(value, (Target, type(None))) super(Many2One, self).__set__(inst, value) @staticmethod diff --git a/trytond/model/fields/one2one.py b/trytond/model/fields/one2one.py index 60139dca5a..3b9e322f5a 100644 --- a/trytond/model/fields/one2one.py +++ b/trytond/model/fields/one2one.py @@ -1,6 +1,5 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -from types import NoneType from trytond.model.fields.field import Field from trytond.model.fields.many2many import Many2Many @@ -59,5 +58,5 @@ class One2One(Many2Many): value = Target(*value) elif isinstance(value, (int, long)): value = Target(value) - assert isinstance(value, (Target, NoneType)) + assert isinstance(value, (Target, type(None))) Field.__set__(self, inst, value) diff --git a/trytond/model/fields/reference.py b/trytond/model/fields/reference.py index 970351ca41..8489a8ad6d 100644 --- a/trytond/model/fields/reference.py +++ b/trytond/model/fields/reference.py @@ -1,6 +1,5 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -from types import NoneType import warnings from sql import Cast, Literal, Query, Expression @@ -93,7 +92,7 @@ class Reference(Field): def __set__(self, inst, value): from ..model import Model - if not isinstance(value, (Model, NoneType)): + if not isinstance(value, (Model, type(None))): if isinstance(value, basestring): target, value = value.split(',') else: diff --git a/trytond/model/modelsql.py b/trytond/model/modelsql.py index 59917b5b50..284cd244eb 100644 --- a/trytond/model/modelsql.py +++ b/trytond/model/modelsql.py @@ -298,10 +298,10 @@ class ModelSQL(ModelStorage): cls.raise_user_error('foreign_model_missing', error_args=error_args) for name, _, error in cls._sql_constraints: - if name in str(exception[0]): + if name in unicode(exception): cls.raise_user_error(error) for name, error in cls._sql_error_messages.iteritems(): - if name in str(exception[0]): + if name in unicode(exception): cls.raise_user_error(error) @classmethod @@ -1411,7 +1411,7 @@ class ModelSQL(ModelStorage): if isinstance(sql, Unique): columns = [Column(table, c.name) for c in sql.columns] columns.insert(0, table.id) - in_max = cursor.IN_MAX / (len(columns) + 1) + in_max = cursor.IN_MAX // (len(columns) + 1) for sub_ids in grouped_slice(ids, in_max): red_sql = reduce_ids(table.id, sub_ids) diff --git a/trytond/model/modelstorage.py b/trytond/model/modelstorage.py index 0d447dceac..313941423e 100644 --- a/trytond/model/modelstorage.py +++ b/trytond/model/modelstorage.py @@ -5,10 +5,6 @@ import datetime import time import csv import warnings -try: - import cStringIO as StringIO -except ImportError: - import StringIO from decimal import Decimal from itertools import islice, ifilter, chain, izip @@ -569,7 +565,7 @@ class ModelStorage(Model): return None res = [] Relation = pool.get(relation) - for word in csv.reader(StringIO.StringIO(value), delimiter=',', + for word in csv.reader(value.splitlines(), delimiter=',', quoting=csv.QUOTE_NONE, escapechar='\\').next(): res2 = Relation.search([ ('rec_name', '=', word), @@ -618,7 +614,7 @@ class ModelStorage(Model): relation = None ftype = fields_def[field[-1][:-3]]['type'] if ftype == 'many2many': - value = csv.reader(StringIO.StringIO(value), delimiter=',', + value = csv.reader(value.splitlines(), delimiter=',', quoting=csv.QUOTE_NONE, escapechar='\\').next() elif ftype == 'reference': try: @@ -1191,7 +1187,7 @@ class ModelStorage(Model): try: return super(ModelStorage, self).__getattr__(name) except AttributeError: - if self.id < 0: + if self.id is None or self.id < 0: raise counter = Transaction().counter @@ -1373,7 +1369,8 @@ class ModelStorage(Model): field = self._fields[fname] if field._type in ('many2one', 'one2one', 'reference'): if value: - if value.id < 0 and field._type != 'reference': + if ((value.id is None or value.id < 0) + and field._type != 'reference'): value.save() if field._type == 'reference': value = str(value) @@ -1381,7 +1378,7 @@ class ModelStorage(Model): value = value.id if field._type in ('one2many', 'many2many'): targets = value - if self.id >= 0: + if self.id is not None and self.id >= 0: _values, self._values = self._values, None try: to_remove = [t.id for t in getattr(self, fname)] @@ -1393,7 +1390,7 @@ class ModelStorage(Model): to_create = [] to_write = [] for target in targets: - if target.id < 0: + if target.id is None or target.id < 0: if field._type == 'one2many': # Don't store old target link setattr(target, field.field, None) diff --git a/trytond/model/modelview.py b/trytond/model/modelview.py index d7ba1aea7f..1c4b278215 100644 --- a/trytond/model/modelview.py +++ b/trytond/model/modelview.py @@ -303,7 +303,8 @@ class ModelView(Model): continue tree_inherit = etree.fromstring(view.arch, parser=parser) tree = _inherit_apply(tree, tree_inherit) - result['arch'] = etree.tostring(tree, encoding='utf-8') + result['arch'] = etree.tostring( + tree, encoding='utf-8').decode('utf-8') # otherwise, build some kind of default view else: @@ -465,7 +466,8 @@ class ModelView(Model): if 'active' in cls._fields: fields_def.setdefault('active', {'name': 'active'}) - arch = etree.tostring(tree, encoding='utf-8', pretty_print=False) + arch = etree.tostring( + tree, encoding='utf-8', pretty_print=False).decode('utf-8') fields2 = cls.fields_get(fields_def.keys()) for field in fields_def: if field in fields2: diff --git a/trytond/protocols/jsonrpc.py b/trytond/protocols/jsonrpc.py index df9f46e697..edaa35571c 100644 --- a/trytond/protocols/jsonrpc.py +++ b/trytond/protocols/jsonrpc.py @@ -6,6 +6,7 @@ from trytond.config import config from trytond.protocols.common import daemon, RegisterHandlerMixin from trytond.exceptions import UserError, UserWarning, NotLogged, \ ConcurrencyException + import SimpleXMLRPCServer import SimpleHTTPServer import SocketServer @@ -27,10 +28,7 @@ except ImportError: import json import base64 import encodings -try: - from cStringIO import StringIO -except ImportError: - from StringIO import StringIO +from io import BytesIO class JSONDecoder(object): @@ -61,7 +59,7 @@ JSONDecoder.register('timedelta', def _bytes_decoder(dct): cast = bytearray if bytes == str else bytes - return cast(base64.decodestring(dct['base64'])) + return cast(base64.decodestring(dct['base64'].encode('utf-8'))) JSONDecoder.register('bytes', _bytes_decoder) JSONDecoder.register('Decimal', lambda dct: Decimal(dct['decimal'])) @@ -118,7 +116,7 @@ JSONEncoder.register(datetime.timedelta, }) _bytes_encoder = lambda o: { '__class__': 'bytes', - 'base64': base64.encodestring(o), + 'base64': base64.encodestring(o).decode('utf-8'), } JSONEncoder.register(bytes, _bytes_encoder) JSONEncoder.register(bytearray, _bytes_encoder) @@ -148,7 +146,7 @@ class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher): existing method through subclassing is the prefered means of changing method dispatch behavior. """ - rawreq = json.loads(data, object_hook=JSONDecoder()) + rawreq = json.loads(data.decode('utf-8'), object_hook=JSONDecoder()) req_id = rawreq.get('id', 0) method = rawreq['method'] @@ -172,7 +170,7 @@ class SimpleJSONRPCDispatcher(SimpleXMLRPCServer.SimpleXMLRPCDispatcher): # report exception back to server response['error'] = (str(sys.exc_value), tb_s) - return json.dumps(response, cls=JSONEncoder) + return json.dumps(response, cls=JSONEncoder).encode('utf-8') class GenericJSONRPCRequestHandler: @@ -263,20 +261,23 @@ class SimpleJSONRPCRequestHandler(RegisterHandlerMixin, 'hostname': hostname, 'path': path, } - content = StringIO() - content.write('') - content.write('') + write('' % values) - content.write('Moved') - content.write('') - content.write('') - content.write('

Moved

') - content.write('

This page has moved to ' + write('Moved') + write('') + write('') + write('

Moved

') + write('

This page has moved to ' '' 'tryton://%(hostname)s%(path)s.

' % values) - content.write('') - content.write('') + write('') + write('') length = content.tell() content.seek(0) self.send_header('Location', 'tryton://%(hostname)s%(path)s' % values) diff --git a/trytond/protocols/xmlrpc.py b/trytond/protocols/xmlrpc.py index 9febacf9d3..492e516ac4 100644 --- a/trytond/protocols/xmlrpc.py +++ b/trytond/protocols/xmlrpc.py @@ -8,11 +8,10 @@ from trytond.exceptions import UserError, UserWarning, NotLogged, \ from trytond import security import SimpleXMLRPCServer import SocketServer -import xmlrpclib +import xmlrpclib as client import socket import base64 import datetime -from types import DictType import logging # convert decimal to float before marshalling: @@ -30,7 +29,7 @@ def dump_decimal(self, value, write): def dump_bytes(self, value, write): self.write = write - value = xmlrpclib.Binary(value) + value = client.Binary(value) value.encode(self) del self.write @@ -60,18 +59,17 @@ def dump_timedelta(self, value, write): } self.dump_struct(value, write) -xmlrpclib.Marshaller.dispatch[Decimal] = dump_decimal -xmlrpclib.Marshaller.dispatch[type(None)] = \ +client.Marshaller.dispatch[Decimal] = dump_decimal +client.Marshaller.dispatch[type(None)] = \ lambda self, value, write: write("") -xmlrpclib.Marshaller.dispatch[datetime.date] = dump_date -xmlrpclib.Marshaller.dispatch[datetime.time] = dump_time -xmlrpclib.Marshaller.dispatch[datetime.timedelta] = dump_timedelta -if bytes != str: - xmlrpclib.Marshaller.dispatch[bytes] = dump_bytes -xmlrpclib.Marshaller.dispatch[bytearray] = dump_bytes +client.Marshaller.dispatch[datetime.date] = dump_date +client.Marshaller.dispatch[datetime.time] = dump_time +client.Marshaller.dispatch[datetime.timedelta] = dump_timedelta +if bytes == str: + client.Marshaller.dispatch[bytearray] = dump_bytes -def dump_struct(self, value, write, escape=xmlrpclib.escape): +def dump_struct(self, value, write, escape=client.escape): converted_value = {} for k, v in value.items(): if type(k) in (int, long): @@ -81,7 +79,7 @@ def dump_struct(self, value, write, escape=xmlrpclib.escape): converted_value[k] = v return self.dump_struct(converted_value, write, escape=escape) -xmlrpclib.Marshaller.dispatch[DictType] = dump_struct +client.Marshaller.dispatch[dict] = dump_struct class XMLRPCDecoder(object): @@ -114,29 +112,30 @@ def end_struct(self, data): dct = {} items = self._stack[mark:] for i in range(0, len(items), 2): - dct[xmlrpclib._stringify(items[i])] = items[i + 1] + dct[items[i]] = items[i + 1] dct = XMLRPCDecoder()(dct) self._stack[mark:] = [dct] self._value = 0 -xmlrpclib.Unmarshaller.dispatch['struct'] = end_struct +client.Unmarshaller.dispatch['struct'] = end_struct def _end_dateTime(self, data): - value = xmlrpclib.DateTime() + value = client.DateTime() value.decode(data) - value = xmlrpclib._datetime_type(data) + value = client._datetime_type(data) self.append(value) -xmlrpclib.Unmarshaller.dispatch["dateTime.iso8601"] = _end_dateTime +client.Unmarshaller.dispatch["dateTime.iso8601"] = _end_dateTime def _end_base64(self, data): - value = xmlrpclib.Binary() - value.decode(data) + value = client.Binary() + value.decode(data.encode('ascii')) cast = bytearray if bytes == str else bytes self.append(cast(value.data)) self._value = 0 -xmlrpclib.Unmarshaller.dispatch['base64'] = _end_base64 +if bytes == str: + client.Unmarshaller.dispatch['base64'] = _end_base64 class GenericXMLRPCRequestHandler: @@ -165,14 +164,14 @@ class GenericXMLRPCRequestHandler: session, object_type, object_name, method, *params) except (NotLogged, ConcurrencyException), exception: logger.debug(exception_message, exc_info=True) - raise xmlrpclib.Fault(exception.code, str(exception)) + raise client.Fault(exception.code, str(exception)) except (UserError, UserWarning), exception: logger.debug(exception_message, exc_info=True) error, description = exception.args - raise xmlrpclib.Fault(exception.code, str(exception)) + raise client.Fault(exception.code, str(exception)) except Exception, exception: logger.error(exception_message, exc_info=True) - raise xmlrpclib.Fault(255, str(exception)) + raise client.Fault(255, str(exception)) finally: security.logout(database_name, user, session) diff --git a/trytond/res/ir.py b/trytond/res/ir.py index ddebcab9f0..5ac6782cfc 100644 --- a/trytond/res/ir.py +++ b/trytond/res/ir.py @@ -11,7 +11,6 @@ __all__ = [ 'SequenceTypeGroup', 'Sequence', 'SequenceStrict', 'ModuleConfigWizardItem', ] -__metaclass__ = PoolMeta class UIMenuGroup(ModelSQL): @@ -220,6 +219,7 @@ class RuleGroupUser(ModelSQL): class Lang: + __metaclass__ = PoolMeta __name__ = 'ir.lang' @classmethod @@ -230,6 +230,7 @@ class Lang: class SequenceType: + __metaclass__ = PoolMeta __name__ = 'ir.sequence.type' groups = fields.Many2Many('ir.sequence.type-res.group', 'sequence_type', 'group', 'User Groups', @@ -268,6 +269,7 @@ class SequenceTypeGroup(ModelSQL): class Sequence: + __metaclass__ = PoolMeta __name__ = 'ir.sequence' groups = fields.Function(fields.Many2Many('res.group', None, None, 'User Groups'), 'get_groups', searcher='search_groups') @@ -306,6 +308,7 @@ class SequenceStrict(Sequence): class ModuleConfigWizardItem: + __metaclass__ = PoolMeta __name__ = 'ir.module.config_wizard.item' @classmethod diff --git a/trytond/res/user.py b/trytond/res/user.py index 0c4c355e14..e2ac62769d 100644 --- a/trytond/res/user.py +++ b/trytond/res/user.py @@ -494,20 +494,21 @@ class User(ModelSQL, ModelView): @classmethod def hash_sha1(cls, password): - if isinstance(password, unicode): - password = password.encode('utf-8') salt = ''.join(random.sample(string.ascii_letters + string.digits, 8)) - hash_ = hashlib.sha1(password + salt).hexdigest() + salted_password = password + salt + if isinstance(salted_password, unicode): + salted_password = salted_password.encode('utf-8') + hash_ = hashlib.sha1(salted_password).hexdigest() return '$'.join(['sha1', hash_, salt]) @classmethod def check_sha1(cls, password, hash_): if isinstance(password, unicode): password = password.encode('utf-8') - if isinstance(hash_, unicode): - hash_ = hash_.encode('utf-8') hash_method, hash_, salt = hash_.split('$', 2) salt = salt or '' + if isinstance(salt, unicode): + salt = salt.encode('utf-8') assert hash_method == 'sha1' return hash_ == hashlib.sha1(password + salt).hexdigest() @@ -515,16 +516,16 @@ class User(ModelSQL, ModelView): def hash_bcrypt(cls, password): if isinstance(password, unicode): password = password.encode('utf-8') - hash_ = bcrypt.hashpw(password, bcrypt.gensalt()) + hash_ = bcrypt.hashpw(password, bcrypt.gensalt()).decode('utf-8') return '$'.join(['bcrypt', hash_]) @classmethod def check_bcrypt(cls, password, hash_): if isinstance(password, unicode): password = password.encode('utf-8') + hash_method, hash_ = hash_.split('$', 1) if isinstance(hash_, unicode): hash_ = hash_.encode('utf-8') - hash_method, hash_ = hash_.split('$', 1) assert hash_method == 'bcrypt' return hash_ == bcrypt.hashpw(password, hash_) diff --git a/trytond/tests/export_data.py b/trytond/tests/export_data.py index e084c2bb2b..422e6b24b0 100644 --- a/trytond/tests/export_data.py +++ b/trytond/tests/export_data.py @@ -7,7 +7,6 @@ from trytond.pool import PoolMeta __all__ = [ 'ExportDataTarget', 'ExportData', 'ExportDataTarget2', 'ExportDataRelation'] -__metaclass__ = PoolMeta class ExportDataTarget(ModelSQL): @@ -46,6 +45,7 @@ class ExportData(ModelSQL): class ExportDataTarget2: 'Export Date Target' + __metaclass__ = PoolMeta __name__ = 'test.export_data.target' one2many = fields.Many2One('test.export_data', 'Export Data') diff --git a/trytond/tests/test_protocols.py b/trytond/tests/test_protocols.py index 28ae6660a4..70df4240f9 100644 --- a/trytond/tests/test_protocols.py +++ b/trytond/tests/test_protocols.py @@ -7,7 +7,7 @@ import datetime from decimal import Decimal from trytond.protocols.jsonrpc import JSONEncoder, JSONDecoder -from trytond.protocols.xmlrpc import xmlrpclib +from trytond.protocols.xmlrpc import client class JSONTestCase(unittest.TestCase): @@ -44,8 +44,8 @@ class XMLTestCase(unittest.TestCase): 'Test XML' def dumps_loads(self, value): - s = xmlrpclib.dumps((value,)) - result, _ = xmlrpclib.loads(s) + s = client.dumps((value,)) + result, _ = client.loads(s) result, = result self.assertEqual(value, result) diff --git a/trytond/tools/decimal_.py b/trytond/tools/decimal_.py index ee156c0584..08d36dd75f 100644 --- a/trytond/tools/decimal_.py +++ b/trytond/tools/decimal_.py @@ -1,36 +1,73 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -import tokenize -from io import StringIO +import sys +from tokenize import * +from io import BytesIO, StringIO # code snippet taken from http://docs.python.org/library/tokenize.html -def decistmt(s): - """Substitute Decimals for floats in a string of statements. +if sys.version_info < (3,): + def decistmt(s): + """Substitute Decimals for floats in a string of statements. - >>> from decimal import Decimal - >>> s = 'print +21.3e-5*-.1234/81.7' - >>> decistmt(s) - u"print +Decimal (u'21.3e-5')*-Decimal (u'.1234')/Decimal (u'81.7')" + >>> from decimal import Decimal + >>> s = 'print +21.3e-5*-.1234/81.7' + >>> decistmt(s) + u"print +Decimal (u'21.3e-5')*-Decimal (u'.1234')/Decimal (u'81.7')" - >>> exec(s) - -3.21716034272e-07 - >>> exec(decistmt(s)) - -3.217160342717258261933904529E-7 - """ - result = [] - # tokenize the string - g = tokenize.generate_tokens(StringIO(s.decode('utf-8')).readline) - for toknum, tokval, _, _, _ in g: - # replace NUMBER tokens - if toknum == tokenize.NUMBER and '.' in tokval: - result.extend([ - (tokenize.NAME, 'Decimal'), - (tokenize.OP, '('), - (tokenize.STRING, repr(tokval)), - (tokenize.OP, ')') - ]) - else: - result.append((toknum, tokval)) - return tokenize.untokenize(result) + >>> exec(s) + -3.21716034272e-07 + >>> exec(decistmt(s)) + -3.217160342717258261933904529E-7 + + """ + result = [] + g = generate_tokens(StringIO(s.decode('utf-8')).readline) # tokenize the string + for toknum, tokval, _, _, _ in g: + if toknum == NUMBER and '.' in tokval: # replace NUMBER tokens + result.extend([ + (NAME, 'Decimal'), + (OP, '('), + (STRING, repr(tokval)), + (OP, ')') + ]) + else: + result.append((toknum, tokval)) + return untokenize(result) +else: + def decistmt(s): + """Substitute Decimals for floats in a string of statements. + + >>> from decimal import Decimal + >>> s = 'print(+21.3e-5*-.1234/81.7)' + >>> decistmt(s) + "print (+Decimal ('21.3e-5')*-Decimal ('.1234')/Decimal ('81.7'))" + + The format of the exponent is inherited from the platform C library. + Known cases are "e-007" (Windows) and "e-07" (not Windows). Since + we're only showing 12 digits, and the 13th isn't close to 5, the + rest of the output should be platform-independent. + + >>> exec(s) #doctest: +ELLIPSIS + -3.217160342717258e-0...7 + + Output from calculations with Decimal should be identical across all + platforms. + + >>> exec(decistmt(s)) + -3.217160342717258261933904529E-7 + """ + result = [] + g = tokenize(BytesIO(s.encode('utf-8')).readline) # tokenize the string + for toknum, tokval, _, _, _ in g: + if toknum == NUMBER and '.' in tokval: # replace NUMBER tokens + result.extend([ + (NAME, 'Decimal'), + (OP, '('), + (STRING, repr(tokval)), + (OP, ')') + ]) + else: + result.append((toknum, tokval)) + return untokenize(result).decode('utf-8') diff --git a/trytond/tools/misc.py b/trytond/tools/misc.py index 42e62d3027..0660eff575 100644 --- a/trytond/tools/misc.py +++ b/trytond/tools/misc.py @@ -53,8 +53,11 @@ def exec_command_pipe(name, *args, **kwargs): def file_open(name, mode="r", subdir='modules'): """Open a file from the root dir, using a subdir folder.""" from trytond.modules import EGG_MODULES - root_path = os.path.dirname(os.path.dirname(os.path.abspath( - unicode(__file__, sys.getfilesystemencoding())))) + if sys.version_info < (3,): + filename = __file__.decode(sys.getfilesystemencoding()) + else: + filename = __file__ + root_path = os.path.dirname(os.path.dirname(os.path.abspath(filename))) egg_name = False if subdir == 'modules': diff --git a/trytond/url.py b/trytond/url.py index 2e40fada8d..b65cda0466 100644 --- a/trytond/url.py +++ b/trytond/url.py @@ -11,9 +11,9 @@ from trytond.transaction import Transaction __all__ = ['URLMixin'] HOSTNAME = (config.get('jsonrpc', 'hostname') - or unicode(socket.getfqdn(), 'utf8')) -HOSTNAME = '.'.join(encodings.idna.ToASCII(part) if part else '' - for part in HOSTNAME.split('.')) + or socket.getfqdn()) +HOSTNAME = '.'.join(encodings.idna.ToASCII(part).decode('ascii') + if part else '' for part in HOSTNAME.split('.')) class URLAccessor(object): diff --git a/trytond/wizard/wizard.py b/trytond/wizard/wizard.py index 4aa0df35a1..fda671a2f7 100644 --- a/trytond/wizard/wizard.py +++ b/trytond/wizard/wizard.py @@ -314,8 +314,7 @@ class Wizard(WarningErrorMixin, URLMixin, PoolBase): Session = pool.get('ir.session.wizard') self._session_id = session_id session = Session(session_id) - data = json.loads(session.data.encode('utf-8'), - object_hook=JSONDecoder()) + data = json.loads(session.data, object_hook=JSONDecoder()) for state_name, state in self.states.iteritems(): if isinstance(state, StateView): Target = pool.get(state.model_name)