Add support for Python 3
issue3211 review7311002
This commit is contained in:
+2
-2
@@ -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,3 +1,4 @@
|
||||
* Add Python3 support
|
||||
* Make TestCase create and drop its database
|
||||
* Add with_transaction decorator for tests
|
||||
* Add note on resources
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,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,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,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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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_)
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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
@@ -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')
|
||||
|
||||
@@ -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
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user