Add support for Python 3

issue3211
review7311002
This commit is contained in:
Cédric Krier
2016-02-07 16:46:16 +01:00
parent da1be07832
commit 29129855ec
26 changed files with 195 additions and 141 deletions
+2 -2
View File
@@ -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
+1
View File
@@ -1,3 +1,4 @@
* Add Python3 support
* Make TestCase create and drop its database
* Add with_transaction decorator for tests
* Add note on resources
+8 -1
View File
@@ -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,
)
+2 -2
View File
@@ -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 =
-1
View File
@@ -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:
+2 -2
View File
@@ -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:
+17 -10
View File
@@ -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
+4 -7
View File
@@ -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:
+7 -4
View File
@@ -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
+2 -1
View File
@@ -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
+1 -2
View File
@@ -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
+1 -2
View File
@@ -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)
+1 -2
View File
@@ -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:
+3 -3
View File
@@ -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)
+7 -10
View File
@@ -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)
+4 -2
View File
@@ -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:
+20 -19
View File
@@ -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('<html')
content.write('<head>')
content.write('<meta http-equiv="Refresh" '
content = BytesIO()
def write(str_):
content.write(str_.encode('utf-8'))
write('<html')
write('<head>')
write('<meta http-equiv="Refresh" '
'content="0;url=tryton://%(hostname)s%(path)s"/>' % values)
content.write('<title>Moved</title>')
content.write('</head>')
content.write('<body>')
content.write('<h1>Moved</h1>')
content.write('<p>This page has moved to '
write('<title>Moved</title>')
write('</head>')
write('<body>')
write('<h1>Moved</h1>')
write('<p>This page has moved to '
'<a href="tryton://%(hostname)s%(path)s">'
'tryton://%(hostname)s%(path)s</a>.</p>' % values)
content.write('</body>')
content.write('</html>')
write('</body>')
write('</html>')
length = content.tell()
content.seek(0)
self.send_header('Location', 'tryton://%(hostname)s%(path)s' % values)
+23 -24
View File
@@ -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("<value><nil/></value>")
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)
+4 -1
View File
@@ -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
+8 -7
View File
@@ -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_)
+1 -1
View File
@@ -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')
+3 -3
View File
@@ -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)
+65 -28
View File
@@ -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')
+5 -2
View File
@@ -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':
+3 -3
View File
@@ -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):
+1 -2
View File
@@ -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)