From ce385af57dbd15a3622784b68dd6d0e8abc705b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Tue, 26 Jun 2018 19:08:57 +0200 Subject: [PATCH] Update to Python 3 issue7488 review48431002 --- CHANGELOG | 1 + bin/tryton | 2 +- doc/conf.py | 4 +- doc/installation.rst | 4 +- setup.py | 17 ++- tryton/action/__init__.py | 2 +- tryton/action/main.py | 2 +- tryton/client.py | 6 +- tryton/common/__init__.py | 10 +- tryton/common/button.py | 2 +- tryton/common/cellrendererbinary.py | 2 +- tryton/common/cellrendererfloat.py | 2 +- tryton/common/cellrendererinteger.py | 2 +- tryton/common/common.py | 122 +++++++++--------- tryton/common/completion.py | 6 +- tryton/common/datetime_.py | 24 ++-- tryton/common/domain_inversion.py | 28 ++-- tryton/common/domain_parser.py | 92 ++++++------- tryton/common/environment.py | 2 +- tryton/common/htmltextbuffer.py | 45 +++---- tryton/common/popup_menu.py | 6 +- tryton/common/selection.py | 12 +- tryton/common/timedelta.py | 8 +- tryton/config.py | 34 ++--- tryton/exceptions.py | 2 +- tryton/fingerprints.py | 4 +- tryton/gui/__init__.py | 2 +- tryton/gui/main.py | 33 ++--- tryton/gui/window/__init__.py | 6 +- tryton/gui/window/about.py | 2 +- tryton/gui/window/attachment.py | 10 +- tryton/gui/window/board.py | 5 +- tryton/gui/window/dblogin.py | 34 ++--- tryton/gui/window/form.py | 7 +- tryton/gui/window/limit.py | 2 +- tryton/gui/window/preference.py | 12 +- tryton/gui/window/view_board/__init__.py | 2 +- tryton/gui/window/view_form/__init__.py | 2 +- tryton/gui/window/view_form/model/field.py | 47 ++++--- tryton/gui/window/view_form/model/group.py | 14 +- tryton/gui/window/view_form/model/record.py | 48 +++---- .../gui/window/view_form/screen/__init__.py | 2 +- tryton/gui/window/view_form/screen/screen.py | 24 ++-- tryton/gui/window/view_form/view/calendar_.py | 2 +- .../view_form/view/calendar_gtk/calendar_.py | 2 +- tryton/gui/window/view_form/view/form.py | 14 +- .../view_form/view/form_gtk/dictionary.py | 20 +-- .../window/view_form/view/form_gtk/image.py | 10 +- .../view_form/view/form_gtk/many2many.py | 2 +- .../view_form/view/form_gtk/many2one.py | 4 +- .../view_form/view/form_gtk/one2many.py | 6 +- .../view_form/view/form_gtk/richtextbox.py | 2 +- .../view_form/view/form_gtk/state_widget.py | 2 +- .../window/view_form/view/form_gtk/textbox.py | 5 +- .../window/view_form/view/form_gtk/widget.py | 2 +- tryton/gui/window/view_form/view/graph.py | 5 +- .../window/view_form/view/graph_gtk/bar.py | 6 +- .../window/view_form/view/graph_gtk/graph.py | 8 +- .../window/view_form/view/graph_gtk/line.py | 4 +- .../window/view_form/view/graph_gtk/pie.py | 8 +- tryton/gui/window/view_form/view/list.py | 7 +- .../view_form/view/list_gtk/editabletree.py | 4 +- .../window/view_form/view/list_gtk/widget.py | 14 +- .../window/view_form/view/screen_container.py | 10 +- tryton/gui/window/win_export.py | 14 +- tryton/gui/window/win_form.py | 4 +- tryton/gui/window/win_import.py | 15 +-- tryton/gui/window/window.py | 6 +- tryton/gui/window/wizard.py | 4 +- tryton/ipc.py | 9 +- tryton/jsonrpc.py | 56 ++++---- tryton/plugins/__init__.py | 2 +- tryton/pyson.py | 30 ++--- tryton/rpc.py | 10 +- tryton/signal_event.py | 2 +- tryton/translate.py | 2 +- 76 files changed, 510 insertions(+), 474 deletions(-) mode change 100644 => 100755 setup.py diff --git a/CHANGELOG b/CHANGELOG index 3d430014a2..42492af1a1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Use Python 3 * Improve loading strategy by using views * Use non conflicting keyboard shortcuts in sync with sao * Use the context to get the suffix of window name diff --git a/bin/tryton b/bin/tryton index 4523016f6f..06cea2e45c 100755 --- a/bin/tryton +++ b/bin/tryton @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys import os try: diff --git a/doc/conf.py b/doc/conf.py index fc8d5ba0ed..140a219838 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -38,7 +38,7 @@ master_doc = 'index' # General information about the project. project = 'tryton' -copyright = u'2008-2011, Anne Krings, Bertrand Chenal, Cédric Krier, \ +copyright = '2008-2011, Anne Krings, Bertrand Chenal, Cédric Krier, \ Mathias Behrle, Tobias Paepke, Udo Spallek' # The version info for the project you're documenting, acts as replacement for @@ -174,7 +174,7 @@ htmlhelp_basename = 'trytonddoc' # (source start file, target name, title, author, documentclass [howto/manual]) latex_documents = [ ('index', 'tryton.tex', 'tryton Documentation', - u'Anne Krings, Bertrand Chenal, Cédric Krier, Mathias Behrle, ' + 'Anne Krings, Bertrand Chenal, Cédric Krier, Mathias Behrle, ' 'Tobias Paepke, Udo Spallek', 'manual'), ] diff --git a/doc/installation.rst b/doc/installation.rst index 99c1c4edb6..fe0601127b 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -4,12 +4,12 @@ Installing tryton Prerequisites ------------- - * Python 2.7.9 or later (http://www.python.org/) + * Python 3.4 or later (http://www.python.org/) * gtk+ 3.20 or later and py-gobject3 (http://www.gtk.org/) * librsvg (http://librsvg.sourceforge.net/) * python-dateutil (http://labix.org/python-dateutil) * Optional: cdecimal (http://www.bytereef.org/mpdecimal/index.html) - * Optional: GooCalendar (https://pypi.python.org/pypi/GooCalendar) + * Optional: GooCalendar 0.4 or later (https://pypi.python.org/pypi/GooCalendar) Installation ------------ diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index 29f99cf94f..670c90a524 --- a/setup.py +++ b/setup.py @@ -1,17 +1,20 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # 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 setuptools import setup, find_packages +import io import os import re 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() + args = {} - try: from babel.messages import frontend as babel @@ -45,6 +48,7 @@ def get_version(): init = read(os.path.join('tryton', '__init__.py')) return re.search('__version__ = "([0-9.]*)"', init).group(1) + version = get_version() major_version, minor_version, _ = version.split('.', 2) major_version = int(major_version) @@ -95,18 +99,21 @@ dist = setup(name=name, 'Natural Language :: Spanish', 'Natural Language :: Japanese', 'Operating System :: OS Independent', - 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', 'Topic :: Office/Business', ], platforms='any', license='GPL-3', + python_requires='>=3.4', install_requires=[ # "py-gobject3", "python-dateutil", ], extras_require={ 'cdecimal': ['cdecimal'], - 'calendar': ['GooCalendar'], + 'calendar': ['GooCalendar>=0.4'], }, zip_safe=False, **args diff --git a/tryton/action/__init__.py b/tryton/action/__init__.py index ed2a492011..0dad8d0854 100644 --- a/tryton/action/__init__.py +++ b/tryton/action/__init__.py @@ -1,3 +1,3 @@ # 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 main import * +from .main import * diff --git a/tryton/action/main.py b/tryton/action/main.py index 76f8047d90..90d730dddb 100644 --- a/tryton/action/main.py +++ b/tryton/action/main.py @@ -93,7 +93,7 @@ class Action(object): context=context) name_suffix = _(', ').join([x['rec_name'] for x in rec_names]) if len(data['ids']) > max_records: - name_suffix += _(u',\u2026') + name_suffix += _(',\u2026') return _('%s (%s)') % (name, name_suffix) data['action_id'] = action['id'] diff --git a/tryton/client.py b/tryton/client.py index ba8833bce1..4d2c57d5ed 100644 --- a/tryton/client.py +++ b/tryton/client.py @@ -189,7 +189,7 @@ try: except ImportError: pass gobject.threads_init() -from urlparse import urlparse +from urllib.parse import urlparse import threading import tryton.common as common @@ -217,7 +217,7 @@ if sys.platform == 'win32': return super(Dialog, self).run() gtk.Dialog = Dialog -CSS = """ +CSS = b""" .readonly entry { background-color: @insensitive_bg_color; } @@ -284,7 +284,7 @@ class TrytonClient(object): lambda signum, frame: main.sig_quit()) def excepthook(*args): - import common + from . import common import traceback detail = ''.join(traceback.format_exception(*args)) common.error(str(args[1]), detail) diff --git a/tryton/common/__init__.py b/tryton/common/__init__.py index 032fa6e6fb..bf2d9fabed 100644 --- a/tryton/common/__init__.py +++ b/tryton/common/__init__.py @@ -1,8 +1,8 @@ # 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 common import * -from datetime_strftime import * -from domain_inversion import domain_inversion, eval_domain, localize_domain, \ +from .common import * +from .datetime_strftime import * +from .domain_inversion import domain_inversion, eval_domain, localize_domain, \ merge, inverse_leaf, filter_leaf, concat, simplify, unique_value -from environment import EvalEnvironment -import timedelta +from .environment import EvalEnvironment +from . import timedelta diff --git a/tryton/common/button.py b/tryton/common/button.py index ce532f3ed0..19d586edf8 100644 --- a/tryton/common/button.py +++ b/tryton/common/button.py @@ -54,7 +54,7 @@ class Button(gtk.Button): label += ' (%s)' % len(clicks) if tip: tip += '\n' - tip += _('By: ') + _(', ').join(clicks.itervalues()) + tip += _('By: ') + _(', ').join(iter(clicks.values())) self.set_label(label) self.set_tooltip_text(tip) diff --git a/tryton/common/cellrendererbinary.py b/tryton/common/cellrendererbinary.py index 1f4efae990..1f18e0c4eb 100644 --- a/tryton/common/cellrendererbinary.py +++ b/tryton/common/cellrendererbinary.py @@ -70,7 +70,7 @@ class CellRendererBinary(gtk.GenericCellRenderer): return getattr(self, pspec.name) def button_width(self): - return (sum(width for n, (_, _, width, _) in self.images.iteritems() + return (sum(width for n, (_, _, width, _) in self.images.items() if n in self.buttons) + (2 * (BUTTON_BORDER + BUTTON_SPACING) * len(self.buttons)) - 2 * BUTTON_SPACING) diff --git a/tryton/common/cellrendererfloat.py b/tryton/common/cellrendererfloat.py index b12d28a17b..551920789a 100644 --- a/tryton/common/cellrendererfloat.py +++ b/tryton/common/cellrendererfloat.py @@ -3,7 +3,7 @@ import gtk import gobject import locale -from cellrendererinteger import CellRendererInteger +from .cellrendererinteger import CellRendererInteger class CellRendererFloat(CellRendererInteger): diff --git a/tryton/common/cellrendererinteger.py b/tryton/common/cellrendererinteger.py index a811c24f8d..fbc3445a05 100644 --- a/tryton/common/cellrendererinteger.py +++ b/tryton/common/cellrendererinteger.py @@ -1,7 +1,7 @@ # 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 gobject -from cellrenderertext import CellRendererText +from .cellrenderertext import CellRendererText import locale diff --git a/tryton/common/common.py b/tryton/common/common.py index d91d8bcc75..e394719c68 100644 --- a/tryton/common/common.py +++ b/tryton/common/common.py @@ -17,7 +17,8 @@ from functools import partial from tryton.config import CONFIG from tryton.config import TRYTON_ICON, PIXMAPS_DIR import sys -import xmlrpclib +import xmlrpc.client +from functools import reduce try: import hashlib except ImportError: @@ -27,10 +28,10 @@ import webbrowser import traceback import tryton.rpc as rpc import socket -import thread -import urllib -import urllib2 -import urlparse +import _thread +import urllib.request +import urllib.parse +import urllib.error from string import Template import shlex try: @@ -67,7 +68,7 @@ class TrytonIconFactory(gtk.IconFactory): continue try: pixbuf = gtk.gdk.pixbuf_new_from_file( - os.path.join(PIXMAPS_DIR, fname).decode('utf-8')) + os.path.join(PIXMAPS_DIR, fname)) except (IOError, glib.GError): continue finally: @@ -117,13 +118,14 @@ class TrytonIconFactory(gtk.IconFactory): except TrytonServerError: icons = [] for icon in icons: - pixbuf = _data2pixbuf(icon['icon']) + pixbuf = _data2pixbuf(icon['icon'].encode('utf-8')) self._tryton_icons.remove((icon['id'], icon['name'])) del self._name2id[icon['name']] self._loaded_icons.add(icon['name']) iconset = gtk.IconSet(pixbuf) self.add(icon['name'], iconset) + ICONFACTORY = TrytonIconFactory() ICONFACTORY.add_default() @@ -161,6 +163,7 @@ class ModelAccess(object): self._access.update(access) return self._access[model] + MODELACCESS = ModelAccess() @@ -178,6 +181,7 @@ class ModelHistory(object): def __contains__(self, model): return model in self._models + MODELHISTORY = ModelHistory() @@ -224,6 +228,7 @@ class ViewSearch(object): del self.searches[model][i] break + VIEW_SEARCH = ViewSearch() @@ -341,7 +346,7 @@ def selection(title, values, alwaysask=False): if not values or len(values) == 0: return None elif len(values) == 1 and (not alwaysask): - key = values.keys()[0] + key = list(values.keys())[0] return (key, values[key]) parent = get_toplevel_window() @@ -373,7 +378,7 @@ def selection(title, values, alwaysask=False): treeview.set_search_column(0) model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_INT) - keys = values.keys() + keys = list(values.keys()) keys.sort() i = 0 for val in keys: @@ -446,19 +451,13 @@ def file_selection(title, filename='', win.set_preview_widget(img_preview) win.connect('update-preview', update_preview_cb, img_preview) - if os.name == 'nt': - encoding = 'utf-8' - else: - encoding = sys.getfilesystemencoding() button = win.run() if button != gtk.RESPONSE_OK: result = None elif not multi: result = win.get_filename() - if result: - result = unicode(result, encoding) else: - result = [unicode(path, encoding) for path in win.get_filenames()] + result = win.get_filenames() parent.present() win.destroy() return result @@ -469,10 +468,10 @@ _slugify_hyphenate_re = re.compile(r'[-\s]+') def slugify(value): - if not isinstance(value, unicode): - value = unicode(value) - value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore') - value = unicode(_slugify_strip_re.sub('', value).strip()) + if not isinstance(value, str): + value = str(value) + value = unicodedata.normalize('NFKD', value) + value = str(_slugify_strip_re.sub('', value).strip()) return _slugify_hyphenate_re.sub('-', value) @@ -539,27 +538,17 @@ def mailto(to=None, cc=None, subject=None, body=None, attachment=None): # http://www.faqs.org/rfcs/rfc2368.html url = "mailto:" if to: - if isinstance(to, unicode): - to = to.encode('utf-8') - url += urllib.quote(to.strip(), "@,") + url += urllib.parse.quote(to.strip(), "@,") url += '?' if cc: - if isinstance(cc, unicode): - cc = cc.encode('utf-8') - url += "&cc=" + urllib.quote(cc, "@,") + url += "&cc=" + urllib.parse.quote(cc, "@,") if subject: - if isinstance(subject, unicode): - subject = subject.encode('utf-8') - url += "&subject=" + urllib.quote(subject, "") + url += "&subject=" + urllib.parse.quote(subject, "") if body: - if isinstance(body, unicode): - body = body.encode('utf-8') body = "\r\n".join(body.splitlines()) - url += "&body=" + urllib.quote(body, "") + url += "&body=" + urllib.parse.quote(body, "") if attachment: - if isinstance(attachment, unicode): - attachment = attachment.encode('utf-8') - url += "&attachment=" + urllib.quote(attachment, "") + url += "&attachment=" + urllib.parse.quote(attachment, "") webbrowser.open(url, new=1) @@ -598,6 +587,7 @@ class MessageDialog(UniqueDialog): def __call__(self, message, msg_type=gtk.MESSAGE_INFO): super(MessageDialog, self).__call__(message, msg_type) + message = MessageDialog() @@ -610,6 +600,7 @@ class WarningDialog(UniqueDialog): dialog.format_secondary_markup(to_xml(message)) return dialog + warning = WarningDialog() @@ -643,6 +634,7 @@ class UserWarningDialog(WarningDialog): return 'ok' return 'cancel' + userwarning = UserWarningDialog() @@ -680,6 +672,7 @@ class SurDialog(ConfirmationDialog): response = super(SurDialog, self).__call__(message) return response == gtk.RESPONSE_OK + sur = SurDialog() @@ -707,6 +700,7 @@ class Sur3BDialog(ConfirmationDialog): response = super(Sur3BDialog, self).__call__(message) return self.response_mapping.get(response, 'cancel') + sur_3b = Sur3BDialog() @@ -754,6 +748,7 @@ class AskDialog(UniqueDialog): self.running = False return result + ask = AskDialog() @@ -814,6 +809,7 @@ class ConcurrencyDialog(UniqueDialog): mode=['form', 'tree']) return False + concurrency = ConcurrencyDialog() @@ -898,6 +894,7 @@ class ErrorDialog(UniqueDialog): if response == gtk.RESPONSE_OK: send_bugtracker(title, details) + error = ErrorDialog() @@ -958,17 +955,18 @@ def send_bugtracker(title, msg): if response == gtk.RESPONSE_OK: try: msg = msg.encode('ascii', 'replace') + title = title.encode('ascii', 'replace') protocol = 'http' if ssl or hasattr(socket, 'ssl'): protocol = 'https' - quote = partial(urllib.quote, safe="!$&'()*+,;=:") - server = xmlrpclib.Server( + quote = partial(urllib.parse.quote, safe="!$&'()*+,;=:") + server = xmlrpc.client.Server( ('%s://%s:%s@' + CONFIG['roundup.xmlrpc']) % (protocol, quote(user), quote(password)), allow_none=True) if hashlib: - msg_md5 = hashlib.md5(msg + '\n' + title).hexdigest() + msg_md5 = hashlib.md5(msg + b'\n' + title).hexdigest() else: - msg_md5 = md5.new(msg + '\n' + title).hexdigest() + msg_md5 = md5.new(msg + b'\n' + title).hexdigest() if not title: title = '[no title]' issue_id = None @@ -1001,15 +999,15 @@ def send_bugtracker(title, msg): + 'issue%s' % issue_id) webbrowser.open(CONFIG['roundup.url'] + 'issue%s' % issue_id, new=2) - except (socket.error, xmlrpclib.Fault), exception: - if (isinstance(exception, xmlrpclib.Fault) + except (socket.error, xmlrpc.client.Fault) as exception: + if (isinstance(exception, xmlrpc.client.Fault) and 'roundup.cgi.exceptions.Unauthorised' in exception.faultString): message(_('Connection error.\nBad username or password.')) return send_bugtracker(title, msg) tb_s = reduce(lambda x, y: x + y, - traceback.format_exception(sys.exc_type, - sys.exc_value, sys.exc_traceback)) + traceback.format_exception(sys.exc_info()[0], + sys.exc_info()[1], sys.exc_info()[2])) message(_('Exception:') + '\n' + tb_s, msg_type=gtk.MESSAGE_ERROR) @@ -1019,7 +1017,7 @@ def check_version(box, version=__version__): webbrowser.open(url) box.remove(info_bar) - class HeadRequest(urllib2.Request): + class HeadRequest(urllib.request.Request): def get_method(self): return 'HEAD' @@ -1033,14 +1031,15 @@ def check_version(box, version=__version__): filename = 'tryton-setup-%s.exe' % version elif sys.platform == 'darwin': filename = 'tryton-%s.dmg' % version - url = list(urlparse.urlparse(CONFIG['download.url'])) + url = list(urllib.parse.urlparse(CONFIG['download.url'])) url[2] = '/%s/%s' % (series, filename) - url = urlparse.urlunparse(url) + url = urllib.parse.urlunparse(url) logger.info(_("Check URL: %s"), url) try: - urllib2.urlopen(HeadRequest(url), timeout=5, cafile=rpc._CA_CERTS) - except (urllib2.HTTPError, socket.timeout): + urllib.request.urlopen( + HeadRequest(url), timeout=5, cafile=rpc._CA_CERTS) + except (urllib.error.HTTPError, socket.timeout): return True except Exception: logger.error( @@ -1063,6 +1062,7 @@ def to_xml(string): return string.replace('&', '&' ).replace('<', '<').replace('>', '>') + PLOCK = Lock() @@ -1098,12 +1098,14 @@ def process_exception(exception, *args, **kwargs): from tryton.gui.main import Main if PLOCK.acquire(False): language = CONFIG['client.lang'] - func = lambda parameters: rpc.login( - rpc._HOST, rpc._PORT, rpc._DATABASE, rpc._USERNAME, - parameters, language) + + def login(parameters): + return rpc.login( + rpc._HOST, rpc._PORT, rpc._DATABASE, rpc._USERNAME, + parameters, language) try: - Login(func) - except TrytonError, exception: + Login(login) + except TrytonError as exception: if exception.faultCode == 'QueryCanceled': Main.get_main().sig_quit() raise @@ -1124,7 +1126,7 @@ class Login(object): while True: try: func(parameters) - except TrytonServerError, exception: + except TrytonServerError as exception: if exception.faultCode.startswith('403'): parameters.clear() continue @@ -1216,7 +1218,7 @@ class RPCProgress(object): def start(self): try: self.res = getattr(rpc, self.method)(*self.args) - except Exception, exception: + except Exception as exception: self.error = True self.res = False self.exception = exception @@ -1239,7 +1241,7 @@ class RPCProgress(object): if self.parent.get_window(): watch = gtk.gdk.Cursor(gtk.gdk.WATCH) self.parent.get_window().set_cursor(watch) - thread.start_new_thread(self.start, ()) + _thread.start_new_thread(self.start, ()) return else: self.start() @@ -1256,7 +1258,7 @@ class RPCProgress(object): try: return process_exception( self.exception, *self.args, rpc_execute=rpc_execute) - except RPCException, exception: + except RPCException as exception: self.exception = exception def return_(): @@ -1313,6 +1315,7 @@ class Tooltips(object): if self._tooltips: self._tooltips.disable() + COLOR_SCHEMES = { 'red': '#cf1d1d', 'green': '#3fb41b', @@ -1329,7 +1332,7 @@ def filter_domain(domain): ''' res = [] for arg in domain: - if isinstance(arg, basestring): + if isinstance(arg, str): if arg == 'OR': res = [] break @@ -1401,10 +1404,11 @@ def resize_pixbuf(pixbuf, width, height): def _data2pixbuf(data): loader = gtk.gdk.PixbufLoader() - loader.write(bytes(data)) + loader.write(data) loader.close() return loader.get_pixbuf() + BIG_IMAGE_SIZE = 10 ** 6 with open(os.path.join(PIXMAPS_DIR, 'tryton-noimage.png'), 'rb') as no_image: NO_IMG_PIXBUF = _data2pixbuf(no_image.read()) diff --git a/tryton/common/completion.py b/tryton/common/completion.py index dd1a35b16d..d6f56f7f00 100644 --- a/tryton/common/completion.py +++ b/tryton/common/completion.py @@ -33,7 +33,7 @@ def update_completion(entry, record, field, model, domain=None): def update(search_text, domain): if not entry.props.window: return False - if search_text != entry.get_text().decode('utf-8'): + if search_text != entry.get_text(): return False completion_model = entry.get_completion().get_model() if not search_text or not model: @@ -53,7 +53,7 @@ def update_completion(entry, record, field, model, domain=None): results = results() except (TrytonError, TrytonServerError): results = [] - if search_text != entry.get_text().decode('utf-8'): + if search_text != entry.get_text(): return False completion_model.clear() for result in results: @@ -70,5 +70,5 @@ def update_completion(entry, record, field, model, domain=None): _("Unable to search for completion of %s") % model, exc_info=True) return False - search_text = entry.get_text().decode('utf-8') + search_text = entry.get_text() gobject.timeout_add(300, update, search_text, domain) diff --git a/tryton/common/datetime_.py b/tryton/common/datetime_.py index 70d1d2fcaf..e947e02b4a 100644 --- a/tryton/common/datetime_.py +++ b/tryton/common/datetime_.py @@ -3,17 +3,13 @@ import gettext import datetime -import pygtk - -pygtk.require('2.0') - import gobject import gtk from dateutil.relativedelta import relativedelta from dateutil.parser import parse -from datetime_strftime import datetime_strftime as strftime +from .datetime_strftime import datetime_strftime as strftime __all__ = ['Date', 'CellRendererDate', 'Time', 'CellRendererTime', 'DateTime'] @@ -165,7 +161,7 @@ class Date(gtk.Entry): def do_set_property(self, prop, value): if prop.name == 'value': - if isinstance(value, basestring): + if isinstance(value, str): self.set_text(value) self.parse() value = self.__date @@ -185,6 +181,7 @@ class Date(gtk.Entry): elif prop.name == 'format': return self.__format + gobject.type_register(Date) @@ -226,6 +223,7 @@ class CellRendererDate(gtk.CellRendererText): widget.show() return widget + gobject.type_register(CellRendererDate) @@ -314,7 +312,7 @@ class Time(gtk.ComboBoxEntry): def do_set_property(self, prop, value): if prop.name == 'value': - if isinstance(value, basestring): + if isinstance(value, str): self.__entry.set_text(value) self.parse() value = self.__time @@ -334,6 +332,7 @@ class Time(gtk.ComboBoxEntry): elif prop.name == 'format': return self.__format + gobject.type_register(Time) @@ -377,6 +376,7 @@ class CellRendererTime(gtk.CellRendererText): widget.show() return widget + gobject.type_register(CellRendererTime) @@ -457,6 +457,7 @@ class DateTime(gtk.HBox): self.__date.modify_text(state, color) self.__time.child.modify_text(state, color) + gobject.type_register(DateTime) @@ -480,13 +481,13 @@ def popup_show(popup): (gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK | gtk.gdk.POINTER_MOTION_MASK), - None, cursor, 0L) + None, cursor, 0) def popup_hide(popup): popup.hide() popup.grab_remove() - gtk.gdk.pointer_ungrab(0L) + gtk.gdk.pointer_ungrab(0) def timelist_set_list(model, min_, max_, format_): @@ -496,10 +497,10 @@ def timelist_set_list(model, min_, max_, format_): model.append((time.strftime(format_), time)) hour = time.hour minute = time.minute + delta - hour += minute / 60 + hour, minute = divmod(minute, 60) + hour += time.hour if hour >= 24: break - minute = minute % 60 time = datetime.time(hour, minute) @@ -534,6 +535,7 @@ def add_operators(widget): editable.connect('key-press-event', key_press) return widget + OPERATORS = { gtk.keysyms.S: relativedelta(seconds=-1), gtk.keysyms.s: relativedelta(seconds=1), diff --git a/tryton/common/domain_inversion.py b/tryton/common/domain_inversion.py index c88540599d..c57eba6cdb 100644 --- a/tryton/common/domain_inversion.py +++ b/tryton/common/domain_inversion.py @@ -2,9 +2,9 @@ # this repository contains the full copyright notices and license terms. import operator -import types import datetime from collections import defaultdict +from functools import reduce def in_(a, b): @@ -16,6 +16,7 @@ def in_(a, b): else: return operator.contains(b, a) + OPERATORS = defaultdict(lambda: lambda a, b: True) OPERATORS.update({ '=': operator.eq, @@ -41,7 +42,7 @@ def locale_part(expression, field_name, locale_name='id'): def is_leaf(expression): return (isinstance(expression, (list, tuple)) and len(expression) > 2 - and isinstance(expression[1], basestring)) + and isinstance(expression[1], str)) def constrained_leaf(part, boolop=operator.and_): @@ -72,14 +73,14 @@ def eval_leaf(part, context, boolop=operator.and_): context_field = datetime.date.min if isinstance(context_field, (list, tuple)) and value is None: value = type(context_field)() - if (isinstance(context_field, basestring) + if (isinstance(context_field, str) and isinstance(value, (list, tuple))): try: value = '%s,%s' % tuple(value) except TypeError: pass elif (isinstance(context_field, (list, tuple)) - and isinstance(value, basestring)): + and isinstance(value, str)): try: context_field = '%s,%s' % tuple(context_field) except TypeError: @@ -90,7 +91,7 @@ def eval_leaf(part, context, boolop=operator.and_): value = list(value) if (operand in ('=', '!=') and isinstance(context_field, (list, tuple)) - and isinstance(value, (int, long))): + and isinstance(value, int)): operand = { '=': 'in', '!=': 'not in', @@ -112,7 +113,7 @@ def inverse_leaf(domain): return [domain[3]] + list(domain[1:]) return domain else: - return map(inverse_leaf, domain) + return list(map(inverse_leaf, domain)) def filter_leaf(domain, field, model): @@ -155,7 +156,7 @@ def localize_domain(domain, field_name=None, strip_target=False): else: return [domain[3]] + list(domain[1:-1]) locale_name = 'id' - if isinstance(domain[2], basestring): + if isinstance(domain[2], str): locale_name = 'rec_name' n = 3 if strip_target else 4 return [locale_part(domain[0], field_name, locale_name)] \ @@ -246,7 +247,7 @@ def domain_inversion(domain, symbol, context=None): class And(object): def __init__(self, expressions): - self.branches = map(parse, expressions) + self.branches = list(map(parse, expressions)) self.variables = set() for expression in self.branches: if is_leaf(expression): @@ -265,7 +266,7 @@ class And(object): for part in self.branches: if isinstance(part, And): part_inversion = part.inverse(symbol, context) - evaluated = isinstance(part_inversion, types.BooleanType) + evaluated = isinstance(part_inversion, bool) if not evaluated: result.append(part_inversion) elif part_inversion: @@ -284,7 +285,7 @@ class And(object): else: return False - result = filter(lambda e: e is not True, result) + result = [e for e in result if e is not True] if result == []: return True else: @@ -305,7 +306,7 @@ class Or(And): for part in self.branches: if isinstance(part, And): part_inversion = part.inverse(symbol, context) - evaluated = isinstance(part_inversion, types.BooleanType) + evaluated = isinstance(part_inversion, bool) if symbol not in part.variables: if evaluated and part_inversion: return True @@ -329,7 +330,7 @@ class Or(And): and not eval_leaf(part, context, operator.or_)): result.append(False) - result = filter(lambda e: e is not False, result) + result = [e for e in result if e is not False] if result == []: return False else: @@ -390,7 +391,7 @@ def test_or_inversion(): assert domain_inversion(domain, 'y', {'z': 4}) == [['y', '<', 3]] assert domain_inversion(domain, 'y', {'x': 3}) is True - domain = [u'OR', [u'length', u'>', 5], [u'language.code', u'=', u'de_DE']] + domain = ['OR', ['length', '>', 5], ['language.code', '=', 'de_DE']] assert domain_inversion(domain, 'length', {'length': 0, 'name': 'n'}) ==\ [['length', '>', 5]] @@ -636,6 +637,7 @@ def test_localize(): assert localize_domain(domain, 'x', False) == [['b.c', '=', 1, 'y', 'z']] assert localize_domain(domain, 'x', True) == [['b.c', '=', 1, 'z']] + if __name__ == '__main__': test_simple_inversion() test_and_inversion() diff --git a/tryton/common/domain_parser.py b/tryton/common/domain_parser.py index 74c68fd761..2d28e8581c 100644 --- a/tryton/common/domain_parser.py +++ b/tryton/common/domain_parser.py @@ -42,8 +42,8 @@ class udlex(shlex): class DummyWordchars(object): "Simulate str that contains all chars except somes" def __contains__(self, item): - return item not in (u':', u'>', u'<', u'=', u'!', u'"', u';', - u'(', u')') + return item not in (':', '>', '<', '=', '!', '"', ';', + '(', ')') self.wordchars = DummyWordchars() @@ -116,7 +116,7 @@ def likify(value): def quote(value): "Quote string if needed" - if not isinstance(value, basestring): + if not isinstance(value, str): return value if '\\' in value: value = value.replace('\\', '\\\\') @@ -196,7 +196,7 @@ def split_target_value(field, value): "Split the reference value into target and value" assert field['type'] == 'reference' target = None - if isinstance(value, basestring): + if isinstance(value, str): for key, text in field['selection']: if value.lower().startswith(text.lower() + ','): target = key @@ -230,8 +230,8 @@ def convert_value(field, value, context=None): context = {} def convert_boolean(): - if isinstance(value, basestring): - return any(test.decode('utf-8').lower().startswith(value.lower()) + if isinstance(value, str): + return any(test.lower().startswith(value.lower()) for test in ( _('y'), _('Yes'), _('True'), _('t'), '1')) else: @@ -256,7 +256,7 @@ def convert_value(field, value, context=None): return def convert_selection(): - if isinstance(value, basestring): + if isinstance(value, str): for key, text in field['selection']: if value.lower() == text.lower(): return key @@ -883,10 +883,10 @@ class DomainParser(object): def __init__(self, fields, context=None): self.fields = OrderedDict((name, f) - for name, f in fields.iteritems() + for name, f in fields.items() if f.get('searchable', True)) self.strings = dict((f['string'].lower(), f) - for f in fields.itervalues() + for f in fields.values() if f.get('searchable', True)) self.context = context @@ -901,8 +901,8 @@ class DomainParser(object): tokens = operatorize(tokens, 'and') tokens = self.parse_clause(tokens) return simplify(rlist(tokens)) - except ValueError, exception: - if exception.message == 'No closing quotation': + except ValueError as exception: + if str(exception) == 'No closing quotation': return self.parse(input_ + '"') def stringable(self, domain): @@ -930,7 +930,7 @@ class DomainParser(object): def string_(clause): if not clause: return '' - if (not isinstance(clause[0], basestring) + if (not isinstance(clause[0], str) or clause[0] in ('AND', 'OR')): return '(%s)' % self.string(clause) name, operator, value = clause[:3] @@ -995,7 +995,7 @@ class DomainParser(object): "Return completion for the input string" domain = self.parse(input_) closing = 0 - for i in xrange(1, len(input_)): + for i in range(1, len(input_)): if input_[-i] not in (')', ' '): break if input_[-i] == ')': @@ -1018,7 +1018,7 @@ class DomainParser(object): return if len(input_) >= 2 and input_[-2] == ':': return - for field in self.strings.itervalues(): + for field in self.strings.values(): operator = default_operator(field) value = '' if 'ilike' in operator: @@ -1052,7 +1052,7 @@ class DomainParser(object): name = '' if (name.lower() not in self.strings and name not in self.fields): - for field in self.strings.itervalues(): + for field in self.strings.values(): if field['string'].lower().startswith(name.lower()): operator = default_operator(field) value = '' @@ -1310,81 +1310,81 @@ def test_group(): 'string': '(Sur)Name', }, }) - assert rlist(dom.group(udlex(u'Name: Doe'))) == [('Name', None, 'Doe')] - assert rlist(dom.group(udlex(u'"(Sur)Name": Doe'))) == [ + assert rlist(dom.group(udlex('Name: Doe'))) == [('Name', None, 'Doe')] + assert rlist(dom.group(udlex('"(Sur)Name": Doe'))) == [ ('(Sur)Name', None, 'Doe'), ] - assert rlist(dom.group(udlex(u'Name: Doe Name: John'))) == [ + assert rlist(dom.group(udlex('Name: Doe Name: John'))) == [ ('Name', None, 'Doe'), ('Name', None, 'John')] - assert rlist(dom.group(udlex(u'Name: Name: John'))) == [ + assert rlist(dom.group(udlex('Name: Name: John'))) == [ ('Name', None, None), ('Name', None, 'John')] - assert rlist(dom.group(udlex(u'First Name: John'))) == [ + assert rlist(dom.group(udlex('First Name: John'))) == [ ('First Name', None, 'John'), ] - assert rlist(dom.group(udlex(u'Name: Doe First Name: John'))) == [ + assert rlist(dom.group(udlex('Name: Doe First Name: John'))) == [ ('Name', None, 'Doe'), ('First Name', None, 'John'), ] - assert rlist(dom.group(udlex(u'First Name: John Name: Doe'))) == [ + assert rlist(dom.group(udlex('First Name: John Name: Doe'))) == [ ('First Name', None, 'John'), ('Name', None, 'Doe'), ] - assert rlist(dom.group(udlex(u'First Name: John First Name: Jane'))) == [ + assert rlist(dom.group(udlex('First Name: John First Name: Jane'))) == [ ('First Name', None, 'John'), ('First Name', None, 'Jane'), ] - assert rlist(dom.group(udlex(u'Name: John Doe'))) == [ + assert rlist(dom.group(udlex('Name: John Doe'))) == [ ('Name', None, 'John'), ('Doe',), ] - assert rlist(dom.group(udlex(u'Name: "John Doe"'))) == [ + assert rlist(dom.group(udlex('Name: "John Doe"'))) == [ ('Name', None, 'John Doe'), ] - assert rlist(dom.group(udlex(u'Name: =Doe'))) == [('Name', '=', 'Doe')] - assert rlist(dom.group(udlex(u'Name: =Doe Name: >John'))) == [ + assert rlist(dom.group(udlex('Name: =Doe'))) == [('Name', '=', 'Doe')] + assert rlist(dom.group(udlex('Name: =Doe Name: >John'))) == [ ('Name', '=', 'Doe'), ('Name', '>', 'John'), ] - assert rlist(dom.group(udlex(u'First Name: =John First Name: =Jane'))) == [ + assert rlist(dom.group(udlex('First Name: =John First Name: =Jane'))) == [ ('First Name', '=', 'John'), ('First Name', '=', 'Jane'), ] - assert rlist(dom.group(udlex(u'Name: John;Jane'))) == [ + assert rlist(dom.group(udlex('Name: John;Jane'))) == [ ('Name', None, ['John', 'Jane']) ] - assert rlist(dom.group(udlex(u'Name: John;'))) == [ + assert rlist(dom.group(udlex('Name: John;'))) == [ ('Name', None, ['John']) ] - assert rlist(dom.group(udlex(u'Name: John;Jane Name: Doe'))) == [ + assert rlist(dom.group(udlex('Name: John;Jane Name: Doe'))) == [ ('Name', None, ['John', 'Jane']), ('Name', None, 'Doe'), ] - assert rlist(dom.group(udlex(u'Name: John; Name: Doe'))) == [ + assert rlist(dom.group(udlex('Name: John; Name: Doe'))) == [ ('Name', None, ['John']), ('Name', None, 'Doe'), ] - assert rlist(dom.group(udlex(u'Name:'))) == [ + assert rlist(dom.group(udlex('Name:'))) == [ ('Name', None, None), ] - assert rlist(dom.group(udlex(u'Name: ='))) == [ + assert rlist(dom.group(udlex('Name: ='))) == [ ('Name', '=', None), ] - assert rlist(dom.group(udlex(u'Name: =""'))) == [ + assert rlist(dom.group(udlex('Name: =""'))) == [ ('Name', '=', ''), ] - assert rlist(dom.group(udlex(u'Name: = ""'))) == [ + assert rlist(dom.group(udlex('Name: = ""'))) == [ ('Name', '=', ''), ] - assert rlist(dom.group(udlex(u'Name: = Name: Doe'))) == [ + assert rlist(dom.group(udlex('Name: = Name: Doe'))) == [ ('Name', '=', None), ('Name', None, 'Doe'), ] - assert rlist(dom.group(udlex(u'Name: \\"foo\\"'))) == [ + assert rlist(dom.group(udlex('Name: \\"foo\\"'))) == [ ('Name', None, '"foo"'), ] - assert rlist(dom.group(udlex(u'Name: "" <'))) == [ + assert rlist(dom.group(udlex('Name: "" <'))) == [ ('Name', '', '<'), ] @@ -1497,10 +1497,10 @@ def test_completion(): 'type': 'char', }, }) - assert list(dom.completion(u'Nam')) == ['Name: '] - assert list(dom.completion(u'Name:')) == ['Name: '] - assert list(dom.completion(u'Name: foo')) == [] - assert list(dom.completion(u'Name: !=')) == [] - assert list(dom.completion(u'Name: !=foo')) == [] - assert list(dom.completion(u'')) == ['Name: '] - assert list(dom.completion(u' ')) == ['', 'Name: '] + assert list(dom.completion('Nam')) == ['Name: '] + assert list(dom.completion('Name:')) == ['Name: '] + assert list(dom.completion('Name: foo')) == [] + assert list(dom.completion('Name: !=')) == [] + assert list(dom.completion('Name: !=foo')) == [] + assert list(dom.completion('')) == ['Name: '] + assert list(dom.completion(' ')) == ['', 'Name: '] diff --git a/tryton/common/environment.py b/tryton/common/environment.py index 042ab87b04..c0491e8a78 100644 --- a/tryton/common/environment.py +++ b/tryton/common/environment.py @@ -35,7 +35,7 @@ class EvalEnvironment(dict): pass return super(EvalEnvironment, self).get(item, default) - def __nonzero__(self): + def __bool__(self): return True def __str__(self): diff --git a/tryton/common/htmltextbuffer.py b/tryton/common/htmltextbuffer.py index b76f1f51b4..2f6cdcedd5 100644 --- a/tryton/common/htmltextbuffer.py +++ b/tryton/common/htmltextbuffer.py @@ -1,11 +1,11 @@ # 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 division + import sys -from io import BytesIO +from io import StringIO import xml.etree.ElementTree as ET from xml.sax.saxutils import escape, unescape -from HTMLParser import HTMLParser +from html.parser import HTMLParser from gi.repository import Gtk, Gdk, Pango @@ -16,7 +16,7 @@ use_serialize_func = False def _reverse_dict(dct): - return {j: i for i, j in dct.iteritems()} + return {j: i for i, j in dct.items()} SIZE2SCALE = { '1': 1 / (1.2 * 1.2 * 1.2), @@ -41,7 +41,7 @@ FAMILIES = ['normal', 'sans', 'serif', 'monospace'] def gdk_to_hex(gdk_color): "Convert color to 2 digit hex" colors = [gdk_color.red, gdk_color.green, gdk_color.blue] - return "#" + "".join(["%02x" % (color / 256) for color in colors]) + return "#" + "".join(["%02x" % (color // 256) for color in colors]) def _markup(text): @@ -49,7 +49,7 @@ def _markup(text): def _strip_markup(text): - return text[len(u''):-len(u'')] + return text[len(''):-len('')] def _strip_newline(text): @@ -101,7 +101,7 @@ class MarkupHTMLParse(HTMLParser): def parse_markup(markup_text): 'Return plain text and a list of start, end TextTag' - markup_text = BytesIO(_markup(markup_text)) + markup_text = StringIO(_markup(markup_text)) plain_text = '' tag_stack = [] tags = [] @@ -129,8 +129,6 @@ def parse_markup(markup_text): def normalize_markup(markup_text, method='html'): - if isinstance(markup_text, unicode): - markup_text = markup_text.encode('utf-8') parser = MarkupHTMLParse() parser.feed(_strip_newline(markup_text)) root = parser.root @@ -177,7 +175,8 @@ def normalize_markup(markup_text, method='html'): if em.text is None and len(em) == 0: em.append(ET.Element('br')) # TODO order attributes - return _strip_markup(ET.tostring(root, encoding='utf-8', method=method)) + return _strip_markup( + ET.tostring(root, encoding='utf-8', method=method).decode('utf-8')) def find_list_delta(old, new): @@ -268,7 +267,7 @@ def serialize(register, content, start, end, data): def deserialize(register, content, iter_, text, create_tags, data): - if not isinstance(text, unicode): + if not isinstance(text, str): for encoding in [sys.getfilesystemencoding(), 'utf-8', 'utf-16', 'utf-32']: try: @@ -278,14 +277,13 @@ def deserialize(register, content, iter_, text, create_tags, data): break else: text = text.decode('ascii', errors='replace') - text = text.encode('utf-8') text, tags = parse_markup(normalize_markup(text, method='xml')) offset = iter_.get_offset() content.insert(iter_, text) def sort_key(tag): start, end, element = tag - return start, -end, element + return start, -end, element.tag for start, end, element in sorted(tags, key=sort_key): for tag in _get_tags(content, element): @@ -306,9 +304,9 @@ _TAGS = [ _TAGS.extend([('family %s' % family, {'family': family}) for family in FAMILIES]) _TAGS.extend([('size %s' % size, {'scale': scale}) - for size, scale in SIZE2SCALE.iteritems()]) + for size, scale in SIZE2SCALE.items()]) _TAGS.extend([('justification %s' % align, {'justification': justification}) - for align, justification in ALIGN2JUSTIFICATION.iteritems()]) + for align, justification in ALIGN2JUSTIFICATION.items()]) def register_foreground(text_buffer, color): @@ -374,7 +372,7 @@ def _get_html(texttag, close=False, div_only=False): if close: return ''.join('' % t for t in tags) return ''.join('<%s %s>' % (t, ' '.join('%s="%s"' % (a, v) - for a, v in attrib.get(t, {}).iteritems())) + for a, v in attrib.get(t, {}).items())) for t in tags) @@ -403,7 +401,7 @@ def remove_tags(content, start, end, *names): if __name__ == '__main__': - html = u'''Bold + html = '''Bold Italic Underline

@@ -416,13 +414,13 @@ if __name__ == '__main__': def cb(window, event): if use_serialize_func: - print text_buffer.serialize( + print(text_buffer.serialize( text_buffer, MIME, text_buffer.get_start_iter(), - text_buffer.get_end_iter()) + text_buffer.get_end_iter())) else: - print serialize( + print(serialize( text_buffer, text_buffer, text_buffer.get_start_iter(), - text_buffer.get_end_iter(), None) + text_buffer.get_end_iter(), None)) Gtk.main_quit() win.connect('delete-event', cb) vbox = Gtk.VBox() @@ -439,8 +437,7 @@ if __name__ == '__main__': if use_serialize_func: text_buffer.deserialize( - text_buffer, MIME, text_buffer.get_start_iter(), - html.encode('utf-8')) + text_buffer, MIME, text_buffer.get_start_iter(), html) result = text_buffer.serialize( text_buffer, MIME, text_buffer.get_start_iter(), @@ -455,7 +452,7 @@ if __name__ == '__main__': text_buffer, text_buffer, text_buffer.get_start_iter(), text_buffer.get_end_iter(), None) - assert normalize_markup(html) == result + assert normalize_markup(html) == result, (normalize_markup(html), result) win.show_all() Gtk.main() diff --git a/tryton/common/popup_menu.py b/tryton/common/popup_menu.py index fbb87ccbf6..ee01f29587 100644 --- a/tryton/common/popup_menu.py +++ b/tryton/common/popup_menu.py @@ -19,21 +19,21 @@ def populate(menu, model, record, title='', field=None, context=None): ''' if record is None: return - elif isinstance(record, (int, long)): + elif isinstance(record, int): if record < 0: return elif record.id < 0: return def load(record): - if isinstance(record, (int, long)): + if isinstance(record, int): screen = Screen(model, context=context) screen.load([record]) record = screen.current_record return record def id_(record): - if not isinstance(record, (int, long)): + if not isinstance(record, int): return record.id return record diff --git a/tryton/common/selection.py b/tryton/common/selection.py index 02032195f3..037d6d7073 100644 --- a/tryton/common/selection.py +++ b/tryton/common/selection.py @@ -90,10 +90,12 @@ class SelectionMixin(object): def filter_selection(self, domain, record, field): if not domain: return - test = lambda value: eval_domain(domain, { - self.field_name: value[0], - }) - self.selection = filter(test, self.selection) + + def test(value): + return eval_domain(domain, { + self.field_name: value[0], + }) + self.selection = list(filter(test, self.selection)) def get_inactive_selection(self, value): if 'relation' not in self.attrs: @@ -125,7 +127,7 @@ def selection_shortcuts(entry): def freeze_value(value): if isinstance(value, dict): return tuple(sorted((k, freeze_value(v)) - for k, v in value.iteritems())) + for k, v in value.items())) elif isinstance(value, (list, set)): return tuple(freeze_value(v) for v in value) else: diff --git a/tryton/common/timedelta.py b/tryton/common/timedelta.py index b98b268b8c..9aa9d94757 100644 --- a/tryton/common/timedelta.py +++ b/tryton/common/timedelta.py @@ -1,6 +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. -from __future__ import division + import datetime import gettext import locale @@ -46,7 +46,7 @@ def format(value, converter=None): if value < 0: sign = '-' value = abs(value) - converter = sorted(converter.items(), key=operator.itemgetter(1), + converter = sorted(list(converter.items()), key=operator.itemgetter(1), reverse=True) values = [] for k, v in converter: @@ -77,7 +77,7 @@ def parse(text, converter=None): if not converter: converter = DEFAULT_CONVERTER - for separator in _get_separators().values(): + for separator in list(_get_separators().values()): text = text.replace(separator, separator + ' ') seconds = 0 @@ -90,7 +90,7 @@ def parse(text, converter=None): except ValueError: pass else: - for key, separator in _get_separators().items(): + for key, separator in list(_get_separators().items()): if part.endswith(separator): part = part[:-len(separator)] try: diff --git a/tryton/config.py b/tryton/config.py index c5f8389c0d..b1612d5472 100644 --- a/tryton/config.py +++ b/tryton/config.py @@ -1,7 +1,7 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. "Options" -import ConfigParser +import configparser import optparse import os import gettext @@ -19,14 +19,14 @@ _ = gettext.gettext def get_config_dir(): if os.name == 'nt': appdata = os.environ['APPDATA'] - if not isinstance(appdata, unicode): - appdata = unicode(appdata, sys.getfilesystemencoding()) + if not isinstance(appdata, str): + appdata = str(appdata, sys.getfilesystemencoding()) return os.path.join(appdata, '.config', 'tryton', __version__.rsplit('.', 1)[0]) return os.path.join(os.environ['HOME'], '.config', 'tryton', __version__.rsplit('.', 1)[0]) if not os.path.isdir(get_config_dir()): - os.makedirs(get_config_dir(), 0700) + os.makedirs(get_config_dir(), 0o700) class ConfigManager(object): @@ -119,15 +119,15 @@ class ConfigManager(object): def save(self): try: - configparser = ConfigParser.ConfigParser() - for entry in self.config.keys(): + parser = configparser.ConfigParser() + for entry in list(self.config.keys()): if not len(entry.split('.')) == 2: continue section, name = entry.split('.') - if not configparser.has_section(section): - configparser.add_section(section) - configparser.set(section, name, self.config[entry]) - configparser.write(open(self.rcfile, 'wb')) + if not parser.has_section(section): + parser.add_section(section) + parser.set(section, name, str(self.config[entry])) + parser.write(open(self.rcfile, 'w')) except IOError: logging.getLogger(__name__).warn( _('Unable to write config file %s.') @@ -136,10 +136,10 @@ class ConfigManager(object): return True def load(self): - configparser = ConfigParser.ConfigParser() - configparser.read([self.rcfile]) - for section in configparser.sections(): - for (name, value) in configparser.items(section): + parser = configparser.ConfigParser() + parser.read([self.rcfile]) + for section in parser.sections(): + for (name, value) in parser.items(section): if value.lower() == 'true': value = True elif value.lower() == 'false': @@ -164,8 +164,8 @@ CONFIG = ConfigManager() CURRENT_DIR = os.path.dirname(__file__) if hasattr(sys, 'frozen'): CURRENT_DIR = os.path.dirname(sys.executable) -if not isinstance(CURRENT_DIR, unicode): - CURRENT_DIR = unicode(CURRENT_DIR, sys.getfilesystemencoding()) +if not isinstance(CURRENT_DIR, str): + CURRENT_DIR = str(CURRENT_DIR, sys.getfilesystemencoding()) PIXMAPS_DIR = os.path.join(CURRENT_DIR, 'data', 'pixmaps', 'tryton') if not os.path.isdir(PIXMAPS_DIR): @@ -175,4 +175,4 @@ if not os.path.isdir(PIXMAPS_DIR): 'tryton', 'data/pixmaps/tryton') TRYTON_ICON = gtk.gdk.pixbuf_new_from_file( - os.path.join(PIXMAPS_DIR, 'tryton-icon.png').encode('utf-8')) + os.path.join(PIXMAPS_DIR, 'tryton-icon.png')) diff --git a/tryton/exceptions.py b/tryton/exceptions.py index eb50b4eeaf..3adcce7f1b 100644 --- a/tryton/exceptions.py +++ b/tryton/exceptions.py @@ -1,6 +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. -from jsonrpc import Fault +from .jsonrpc import Fault TrytonServerError = Fault diff --git a/tryton/fingerprints.py b/tryton/fingerprints.py index 04c7c40361..2913572c70 100644 --- a/tryton/fingerprints.py +++ b/tryton/fingerprints.py @@ -28,10 +28,10 @@ class Fingerprints(dict): def save(self): with open(KNOWN_HOSTS_PATH, 'w') as known_hosts: known_hosts.writelines('%s %s' % (host, sha1) - + os.linesep for host, sha1 in self.iteritems()) + + os.linesep for host, sha1 in self.items()) def __setitem__(self, key, value): - assert isinstance(key, basestring) + assert isinstance(key, str) if value: assert len(value) == 59 # len of formated sha1 else: diff --git a/tryton/gui/__init__.py b/tryton/gui/__init__.py index ed2a492011..0dad8d0854 100644 --- a/tryton/gui/__init__.py +++ b/tryton/gui/__init__.py @@ -1,3 +1,3 @@ # 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 main import * +from .main import * diff --git a/tryton/gui/main.py b/tryton/gui/main.py index f9a589dc00..70ffb5aa22 100644 --- a/tryton/gui/main.py +++ b/tryton/gui/main.py @@ -4,15 +4,14 @@ import os import sys import gettext -from urlparse import urlparse, parse_qsl -import urllib -import gobject -import gtk +from urllib.parse import urlparse, parse_qsl, unquote import json import webbrowser import threading import logging +import gobject +import gtk from gi.repository import GLib import tryton.rpc as rpc @@ -313,7 +312,7 @@ class Main(object): if callback: callback() return False - if search_text != widget.get_text().decode('utf-8'): + if search_text != widget.get_text(): return end() gmodel = global_search_completion.get_model() if not search_text or not gmodel: @@ -328,7 +327,7 @@ class Main(object): result = result() except RPCException: result = [] - if search_text != widget.get_text().decode('utf-8'): + if search_text != widget.get_text(): if callback: callback() return False @@ -357,7 +356,7 @@ class Main(object): return False def changed(widget): - search_text = widget.get_text().decode('utf-8') + search_text = widget.get_text() gobject.timeout_add(300, update, widget, search_text) def activate(widget): @@ -367,7 +366,7 @@ class Main(object): common.message(_('No result found.')) else: widget.emit('changed') - search_text = widget.get_text().decode('utf-8') + search_text = widget.get_text() update(widget, search_text, message) self.global_search_entry.connect('changed', changed) @@ -834,20 +833,22 @@ class Main(object): language = CONFIG['client.lang'] try: host, port, database, username = DBLogin().run() - except TrytonError, exception: + except TrytonError as exception: if exception.faultCode == 'QueryCanceled': return raise - func = lambda parameters: rpc.login( - host, port, database, username, parameters, language) + + def login(parameters): + return rpc.login( + host, port, database, username, parameters, language) self.set_title() # Adds username/profile while password is asked try: - common.Login(func) - except TrytonError, exception: + common.Login(login) + except TrytonError as exception: if exception.faultCode == 'QueryCanceled': return raise - except TrytonServerError, exception: + except TrytonServerError as exception: if exception.faultCode.startswith('404'): return self.sig_login() raise @@ -1223,8 +1224,8 @@ class Main(object): urlp = urlparse('http' + url[6:]) hostname = common.get_hostname(urlp.netloc) port = common.get_port(urlp.netloc) - database, path = map(urllib.unquote, - (urlp.path[1:].split('/', 1) + [''])[:2]) + database, path = list(map(unquote, + (urlp.path[1:].split('/', 1) + [''])[:2])) if (not path or hostname != rpc._HOST or int(port) != rpc._PORT or diff --git a/tryton/gui/window/__init__.py b/tryton/gui/window/__init__.py index 359c96fee4..c0947c1755 100644 --- a/tryton/gui/window/__init__.py +++ b/tryton/gui/window/__init__.py @@ -1,5 +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 window import * -from email import * -from limit import * +from .window import * +from .email import * +from .limit import * diff --git a/tryton/gui/window/about.py b/tryton/gui/window/about.py index 17f4995892..daf2016fdc 100644 --- a/tryton/gui/window/about.py +++ b/tryton/gui/window/about.py @@ -42,7 +42,7 @@ class About(object): self.win.set_authors(AUTHORS) self.win.set_translator_credits(_('translator-credits')) self.win.set_logo(GdkPixbuf.Pixbuf.new_from_file( - os.path.join(PIXMAPS_DIR, 'tryton.png').encode('utf-8'))) + os.path.join(PIXMAPS_DIR, 'tryton.png'))) self.win.run() parent.present() self.win.destroy() diff --git a/tryton/gui/window/attachment.py b/tryton/gui/window/attachment.py index 0bd7c18edc..b570bd9cb2 100644 --- a/tryton/gui/window/attachment.py +++ b/tryton/gui/window/attachment.py @@ -2,8 +2,8 @@ # this repository contains the full copyright notices and license terms. "Attachment" import os -import urllib -import urlparse +import urllib.request +import urllib.parse import sys import gettext @@ -42,9 +42,9 @@ class Attachment(WinForm): data_field = self.screen.group.fields['data'] name_field = self.screen.group.fields[data_field.attrs['filename']] new_record = self.screen.new() - file_name = os.path.basename(urlparse.urlparse(uri).path) + file_name = os.path.basename(urllib.parse.urlparse(uri).path) name_field.set_client(new_record, file_name) - uri = urllib.unquote(uri) + uri = urllib.parse.unquote(uri) uri = uri.decode('utf-8').encode(sys.getfilesystemencoding()) - data_field.set_client(new_record, urllib.urlopen(uri).read()) + data_field.set_client(new_record, urllib.request.urlopen(uri).read()) self.screen.display() diff --git a/tryton/gui/window/board.py b/tryton/gui/window/board.py index dcad939d9a..0b6083b326 100644 --- a/tryton/gui/window/board.py +++ b/tryton/gui/window/board.py @@ -7,7 +7,7 @@ from tryton.gui import Main from tryton.gui.window.view_board import ViewBoard from tryton.common import RPCExecute, RPCException -from tabcontent import TabContent +from .tabcontent import TabContent _ = gettext.gettext @@ -60,6 +60,9 @@ class Board(SignalEvent, TabContent): and self.board.context == value.board.context and self.name == value.name) + def __hash__(self): + return id(self) + def sig_win_close(self, widget): Main.get_main().sig_win_close(widget) diff --git a/tryton/gui/window/dblogin.py b/tryton/gui/window/dblogin.py index 44418c10d9..7f0dfeb14d 100644 --- a/tryton/gui/window/dblogin.py +++ b/tryton/gui/window/dblogin.py @@ -1,7 +1,7 @@ # 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 ConfigParser +import configparser import gtk import gobject import os @@ -29,7 +29,7 @@ class DBListEditor(object): # GTK Stuffs self.parent = parent - self.dialog = gtk.Dialog(title=_(u'Profile Editor'), parent=parent, + self.dialog = gtk.Dialog(title=_('Profile Editor'), parent=parent, flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT) self.ok_button = self.dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT) @@ -44,19 +44,19 @@ class DBListEditor(object): self.cell.connect('editing-started', self.edit_started) self.profile_tree = gtk.TreeView() self.profile_tree.set_model(profile_store) - self.profile_tree.insert_column_with_attributes(-1, _(u'Profile'), + self.profile_tree.insert_column_with_attributes(-1, _('Profile'), self.cell, text=0) self.profile_tree.connect('cursor-changed', self.profile_selected) scroll = gtk.ScrolledWindow() scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scroll.add(self.profile_tree) - self.add_button = gtk.Button(_(u'_Add'), use_underline=True) + self.add_button = gtk.Button(_('_Add'), use_underline=True) self.add_button.connect('clicked', self.profile_create) add_image = gtk.Image() add_image.set_from_stock('gtk-add', gtk.ICON_SIZE_BUTTON) self.add_button.set_image(add_image) self.add_button.set_always_show_image(True) - self.remove_button = gtk.Button(_(u'_Remove'), use_underline=True) + self.remove_button = gtk.Button(_('_Remove'), use_underline=True) self.remove_button.connect('clicked', self.profile_delete) remove_image = gtk.Image() remove_image.set_from_stock('gtk-remove', gtk.ICON_SIZE_BUTTON) @@ -70,7 +70,7 @@ class DBListEditor(object): table = gtk.Table(4, 2, homogeneous=False) table.set_row_spacings(3) table.set_col_spacings(3) - host = gtk.Label(set_underline(_(u'Host:'))) + host = gtk.Label(set_underline(_('Host:'))) host.set_use_underline(True) host.set_alignment(1, 0.5) host.set_padding(3, 3) @@ -81,7 +81,7 @@ class DBListEditor(object): host.set_mnemonic_widget(self.host_entry) table.attach(host, 0, 1, 1, 2, yoptions=False, xoptions=gtk.FILL) table.attach(self.host_entry, 1, 2, 1, 2, yoptions=False) - database = gtk.Label(set_underline(_(u'Database:'))) + database = gtk.Label(set_underline(_('Database:'))) database.set_use_underline(True) database.set_alignment(1, 0.5) database.set_padding(3, 3) @@ -101,7 +101,7 @@ class DBListEditor(object): self.database_combo.set_model(dbstore) self.database_combo.connect('changed', self.dbcombo_changed) self.database_progressbar = gtk.ProgressBar() - self.database_progressbar.set_text(_(u'Fetching databases list')) + self.database_progressbar.set_text(_('Fetching databases list')) image = gtk.Image() image.set_from_stock('tryton-new', gtk.ICON_SIZE_BUTTON) db_box = gtk.VBox(homogeneous=True) @@ -117,7 +117,7 @@ class DBListEditor(object): db_box.set_size_request(width, height) table.attach(database, 0, 1, 2, 3, yoptions=False, xoptions=gtk.FILL) table.attach(db_box, 1, 2, 2, 3, yoptions=False) - username = gtk.Label(set_underline(_(u'Username:'))) + username = gtk.Label(set_underline(_('Username:'))) username.set_use_underline(True) username.set_alignment(1, 0.5) username.set_padding(3, 3) @@ -214,7 +214,7 @@ class DBListEditor(object): try: entry_value = self.profiles.get(self.current_profile['name'], field) - except ConfigParser.NoOptionError: + except configparser.NoOptionError: entry_value = '' entry.set_text(entry_value) if field == 'database': @@ -304,9 +304,9 @@ class DBListEditor(object): label = None if self.test_server_version(host, port) is False: - label = _(u'Incompatible version of the server') + label = _('Incompatible version of the server') elif dbs is None: - label = _(u'Could not connect to the server') + label = _('Could not connect to the server') if label: self.database_label.set_label('%s' % label) self.database_label.show() @@ -433,7 +433,7 @@ class DBLogin(object): self.combo_profile.add_attribute(cell, 'sensitive', 1) self.combo_profile.set_model(self.profile_store) self.combo_profile.connect('changed', self.profile_changed) - self.profile_label = gtk.Label(set_underline(_(u'Profile:'))) + self.profile_label = gtk.Label(set_underline(_('Profile:'))) self.profile_label.set_use_underline(True) self.profile_label.set_justify(gtk.JUSTIFY_RIGHT) self.profile_label.set_alignment(1, 0.5) @@ -492,7 +492,7 @@ class DBLogin(object): # Profile informations self.profile_cfg = os.path.join(get_config_dir(), 'profiles.cfg') - self.profiles = ConfigParser.SafeConfigParser() + self.profiles = configparser.SafeConfigParser() if not os.path.exists(self.profile_cfg): short_version = '.'.join(__version__.split('.', 2)[:2]) name = 'demo%s.tryton.org' % short_version @@ -533,7 +533,7 @@ class DBLogin(object): profile = self.profile_store[position][0] try: username = self.profiles.get(profile, 'username') - except ConfigParser.NoOptionError: + except configparser.NoOptionError: username = '' host = self.profiles.get(profile, 'host') self.entry_host.set_text('%s' % host) @@ -624,10 +624,10 @@ class DBLogin(object): if not test: if test is False: common.warning('', - _(u'Incompatible version of the server')) + _('Incompatible version of the server')) else: common.warning('', - _(u'Could not connect to the server')) + _('Could not connect to the server')) continue database = self.entry_database.get_text() login = self.entry_login.get_text() diff --git a/tryton/gui/window/form.py b/tryton/gui/window/form.py index 29d8ca15f1..eb213d7247 100644 --- a/tryton/gui/window/form.py +++ b/tryton/gui/window/form.py @@ -20,7 +20,7 @@ from tryton.common import RPCExecute, RPCException from tryton.common.datetime_strftime import datetime_strftime from tryton import plugins -from tabcontent import TabContent +from .tabcontent import TabContent _ = gettext.gettext @@ -57,7 +57,7 @@ class Form(SignalEvent, TabContent): self.screen.signal_connect(self, 'unread-note', self._unread_note) if res_id not in (None, False): - if isinstance(res_id, (int, long)): + if isinstance(res_id, int): res_id = [res_id] self.screen.load(res_id) else: @@ -94,6 +94,9 @@ class Form(SignalEvent, TabContent): and self.screen.limit == value.screen.limit and self.screen.search_value == value.screen.search_value) + def __hash__(self): + return id(self) + def destroy(self): super(Form, self).destroy() self.screen.signal_unconnect(self) diff --git a/tryton/gui/window/limit.py b/tryton/gui/window/limit.py index 29302724ad..cc31ffb21c 100644 --- a/tryton/gui/window/limit.py +++ b/tryton/gui/window/limit.py @@ -30,7 +30,7 @@ class Limit(object): label = gtk.Label(_('Limit:')) hbox.pack_start(label, expand=True, fill=True) adjustment = gtk.Adjustment(value=CONFIG['client.limit'], - lower=1, upper=sys.maxint, step_incr=10, page_incr=100) + lower=1, upper=sys.maxsize, step_incr=10, page_incr=100) self.spin_limit = gtk.SpinButton() self.spin_limit.configure(adjustment, climb_rate=1, digits=0) self.spin_limit.set_numeric(False) diff --git a/tryton/gui/window/preference.py b/tryton/gui/window/preference.py index 462f54e197..e7bcf84780 100644 --- a/tryton/gui/window/preference.py +++ b/tryton/gui/window/preference.py @@ -87,12 +87,14 @@ class Preference(NoModal): if self.screen.current_record.validate(): vals = copy.copy(self.screen.get()) context = rpc.CONTEXT.copy() - func = lambda parameters: rpc.execute( - 'model', 'res.user', 'set_preferences', vals, parameters, - context) + + def login(parameters): + return rpc.execute( + 'model', 'res.user', 'set_preferences', + vals, parameters, context) try: - Login(func) - except TrytonError, exception: + Login(login) + except TrytonError as exception: if exception.faultCode == 'QueryCanceled': return raise diff --git a/tryton/gui/window/view_board/__init__.py b/tryton/gui/window/view_board/__init__.py index a4789b0a51..e33419253d 100644 --- a/tryton/gui/window/view_board/__init__.py +++ b/tryton/gui/window/view_board/__init__.py @@ -1,3 +1,3 @@ # 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 view_board import * +from .view_board import * diff --git a/tryton/gui/window/view_form/__init__.py b/tryton/gui/window/view_form/__init__.py index fdb85fd5a4..016355b5a9 100644 --- a/tryton/gui/window/view_form/__init__.py +++ b/tryton/gui/window/view_form/__init__.py @@ -1,3 +1,3 @@ # 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 screen +from . import screen diff --git a/tryton/gui/window/view_form/model/field.py b/tryton/gui/window/view_form/model/field.py index e57553cd31..89a253cdf9 100644 --- a/tryton/gui/window/view_form/model/field.py +++ b/tryton/gui/window/view_form/model/field.py @@ -287,7 +287,7 @@ class TimeDeltaField(Field): return group.context.get(self.attrs.get('converter')) def set_client(self, record, value, force_change=False): - if isinstance(value, basestring): + if isinstance(value, str): value = common.timedelta.parse(value, self.converter(record.group)) super(TimeDeltaField, self).set_client( record, value, force_change=force_change) @@ -320,10 +320,14 @@ class FloatField(Field): return self._default def set_client(self, record, value, force_change=False, factor=1): - if isinstance(value, basestring): + if isinstance(value, str): value = self.convert(value) if value is not None: - value /= factor + # Keep the same type + if isinstance(value, int): + value //= factor + else: + value /= factor super(FloatField, self).set_client(record, value, force_change=force_change) @@ -332,7 +336,7 @@ class FloatField(Field): if value is not None: digits = self.digits(record, factor=factor) if digits: - p = digits[1] + p = int(digits[1]) else: d = value * factor if not isinstance(d, Decimal): @@ -423,7 +427,7 @@ class M2OField(Field): def set(self, record, value): rec_name = record.value.get(self.name + '.rec_name') or '' - if not rec_name and value >= 0: + if not rec_name and value is not None and value >= 0: try: result, = RPCExecute('model', self.attrs['relation'], 'read', [value], ['rec_name']) @@ -496,7 +500,7 @@ class O2MField(Field): def _set_default_value(self, record, fields=None): if record.value.get(self.name) is not None: return - from group import Group + from .group import Group parent_name = self.attrs.get('relation_field', '') fields = fields or {} context = record.expr_eval(self.attrs.get('context', {})) @@ -593,7 +597,7 @@ class O2MField(Field): group = record.value[self.name] if value is None: value = [] - if not value or isinstance(value[0], (int, long)): + if not value or isinstance(value[0], int): mode = 'list ids' else: mode = 'list values' @@ -640,7 +644,7 @@ class O2MField(Field): fields = record.group.fields if fields: fields = dict((fname, field.attrs) - for fname, field in fields.iteritems()) + for fname, field in fields.items()) record.value[self.name] = None self._set_default_value(record, fields=fields) @@ -655,7 +659,7 @@ class O2MField(Field): if value is None: value = [] # domain inversion could try to set id as value - if isinstance(value, (int, long)): + if isinstance(value, int): value = [value] previous_ids = self.get_eval(record) @@ -801,7 +805,7 @@ class ReferenceField(Field): def set_client(self, record, value, force_change=False): if value: - if isinstance(value, basestring): + if isinstance(value, str): value = value.split(',') ref_model, ref_id = value if isinstance(ref_id, (tuple, list)): @@ -825,7 +829,7 @@ class ReferenceField(Field): if not value: record.value[self.name] = self._default return - if isinstance(value, basestring): + if isinstance(value, str): ref_model, ref_id = value.split(',') if not ref_id: ref_id = None @@ -888,14 +892,13 @@ class _FileCache(object): class BinaryField(Field): _default = None - cast = bytearray if bytes == str else bytes def get(self, record): result = record.value.get(self.name, self._default) if isinstance(result, _FileCache): try: with open(result.path, 'rb') as fp: - result = self.cast(fp.read()) + result = fp.read() except IOError: result = self.get_data(record) return result @@ -905,8 +908,11 @@ class BinaryField(Field): def set_client(self, record, value, force_change=False): _, filename = tempfile.mkstemp(prefix='tryton_') + data = value or b'' + if isinstance(data, str): + data = data.encode('utf-8') with open(filename, 'wb') as fp: - fp.write(value or '') + fp.write(data) self.set(record, _FileCache(filename)) record.modified_fields.setdefault(self.name) record.signal('record-modified') @@ -918,24 +924,27 @@ class BinaryField(Field): result = record.value.get(self.name) or 0 if isinstance(result, _FileCache): result = os.stat(result.path).st_size - elif isinstance(result, (basestring, bytes, bytearray)): + elif isinstance(result, (str, bytes)): result = len(result) return result def get_data(self, record): if not isinstance(record.value.get(self.name), - (basestring, bytes, bytearray, _FileCache)): + (str, bytes, _FileCache)): if record.id < 0: - return '' + return b'' context = record.get_context() try: values, = RPCExecute('model', record.model_name, 'read', [record.id], [self.name], context=context) except RPCException: - return '' + return b'' _, filename = tempfile.mkstemp(prefix='tryton_') + data = values[self.name] or b'' + if isinstance(data, str): + data = data.encode('utf-8') with open(filename, 'wb') as fp: - fp.write(values[self.name] or '') + fp.write(data) self.set(record, _FileCache(filename)) return self.get(record) diff --git a/tryton/gui/window/view_form/model/group.py b/tryton/gui/window/view_form/model/group.py index 93a1f053a1..d3bc06f401 100644 --- a/tryton/gui/window/view_form/model/group.py +++ b/tryton/gui/window/view_form/model/group.py @@ -1,7 +1,7 @@ # 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 record import Record -from field import Field, M2OField, ReferenceField +from .record import Record +from .field import Field, M2OField, ReferenceField from tryton import rpc from tryton.signal_event import SignalEvent from tryton.common.domain_inversion import is_leaf @@ -154,7 +154,7 @@ class Group(SignalEvent, list): return '' % (self.model_name, id(self)) def load_fields(self, fields): - for name, attr in fields.iteritems(): + for name, attr in fields.items(): field = Field.get_field(attr['type']) attr['name'] = name self.fields[name] = field(attr) @@ -184,7 +184,7 @@ class Group(SignalEvent, list): return root def written(self, ids): - if isinstance(ids, (int, long)): + if isinstance(ids, int): ids = [ids] ids = [x for x in self.on_write_ids(ids) or [] if x not in ids] if not ids: @@ -400,7 +400,7 @@ class Group(SignalEvent, list): return None return self[self.current_idx] - def next(self): + def __next__(self): if len(self) and self.current_idx is not None: self.current_idx = (self.current_idx + 1) % len(self) elif len(self): @@ -411,7 +411,7 @@ class Group(SignalEvent, list): def add_fields(self, fields): to_add = {} - for name, attr in fields.iteritems(): + for name, attr in fields.items(): if name not in self.fields: to_add[name] = attr else: @@ -429,7 +429,7 @@ class Group(SignalEvent, list): if len(new) and len(to_add): try: values = RPCExecute('model', self.model_name, 'default_get', - to_add.keys(), context=self.context) + list(to_add.keys()), context=self.context) except RPCException: return False for record in new: diff --git a/tryton/gui/window/view_form/model/record.py b/tryton/gui/window/view_form/model/record.py index e885b7b907..6942405485 100644 --- a/tryton/gui/window/view_form/model/record.py +++ b/tryton/gui/window/view_form/model/record.py @@ -3,7 +3,7 @@ from tryton.signal_event import SignalEvent import tryton.common as common from tryton.pyson import PYSONDecoder -import field as fields +from . import field as fields from tryton.common import RPCExecute, RPCException from tryton.config import CONFIG @@ -45,12 +45,12 @@ class Record(SignalEvent): if name == '*': loading = 'eager' views = set() - for field in self.group.fields.itervalues(): + for field in self.group.fields.values(): if field.attrs.get('loading', 'eager') == 'lazy': loading = 'lazy' views |= field.views # Set a valid name for next loaded check - for fname, field in self.group.fields.iteritems(): + for fname, field in self.group.fields.items(): if field.attrs.get('loading', 'eager') == loading: name = fname break @@ -60,10 +60,10 @@ class Record(SignalEvent): if loading == 'eager': fields = ((fname, field) - for fname, field in self.group.fields.iteritems() + for fname, field in self.group.fields.items() if field.attrs.get('loading', 'eager') == 'eager') else: - fields = self.group.fields.iteritems() + fields = self.group.fields.items() fnames = [fname for fname, field in fields if fname not in self._loaded @@ -113,20 +113,20 @@ class Record(SignalEvent): ctx = record_context.copy() ctx.update(dict(('%s.%s' % (self.model_name, fname), 'size') - for fname, field in self.group.fields.iteritems() + for fname, field in self.group.fields.items() if field.attrs['type'] == 'binary' and fname in fnames)) exception = None try: values = RPCExecute('model', self.model_name, 'read', - id2record.keys(), fnames, context=ctx) - except RPCException, exception: + list(id2record.keys()), fnames, context=ctx) + except RPCException as exception: values = [{'id': x} for x in id2record] default_values = dict((f, None) for f in fnames) for value in values: value.update(default_values) self.exception = True id2value = dict((value['id'], value) for value in values) - for id, record in id2record.iteritems(): + for id, record in id2record.items(): if not record.exception: record.exception = bool(exception) value = id2value.get(id) @@ -253,7 +253,7 @@ class Record(SignalEvent): def get(self): value = {} - for name, field in self.group.fields.iteritems(): + for name, field in self.group.fields.items(): if (field.attrs.get('readonly') and not isinstance(field, fields.O2MField)): continue @@ -267,7 +267,7 @@ class Record(SignalEvent): def get_eval(self): value = {} - for name, field in self.group.fields.iteritems(): + for name, field in self.group.fields.items(): if name not in self._loaded and self.id >= 0: continue value[name] = field.get_eval(self) @@ -276,7 +276,7 @@ class Record(SignalEvent): def get_on_change_value(self, skip=None): value = {} - for name, field in self.group.fields.iteritems(): + for name, field in self.group.fields.items(): if skip and name in skip: continue if (self.id >= 0 @@ -295,7 +295,7 @@ class Record(SignalEvent): def get_timestamp(self): result = {self.model_name + ',' + str(self.id): self._timestamp} - for name, field in self.group.fields.iteritems(): + for name, field in self.group.fields.items(): if name in self._loaded: result.update(field.get_timestamp(self)) return result @@ -376,7 +376,7 @@ class Record(SignalEvent): context.setdefault('default_rec_name', rec_name) try: vals = RPCExecute('model', self.model_name, 'default_get', - self.group.fields.keys(), context=context) + list(self.group.fields.keys()), context=context) except RPCException: return if (self.parent @@ -404,7 +404,7 @@ class Record(SignalEvent): elif fields is None: self._check_load() res = True - for field_name, field in self.group.fields.iteritems(): + for field_name, field in self.group.fields.items(): if fields is not None and field_name not in fields: continue if field.attrs.get('readonly'): @@ -417,7 +417,7 @@ class Record(SignalEvent): def _get_invalid_fields(self): fields = {} - for fname, field in self.group.fields.iteritems(): + for fname, field in self.group.fields.items(): invalid = field.get_state_attrs(self).get('invalid') if invalid: fields[fname] = invalid @@ -430,7 +430,7 @@ class Record(SignalEvent): def set_default(self, val, signal=True, validate=True): fieldnames = [] - for fieldname, value in val.items(): + for fieldname, value in list(val.items()): if fieldname not in self.group.fields: continue if fieldname == self.group.exclude_field: @@ -455,7 +455,7 @@ class Record(SignalEvent): def set(self, val, signal=True, validate=True): later = {} fieldnames = [] - for fieldname, value in val.iteritems(): + for fieldname, value in val.items(): if fieldname == '_timestamp': # Always keep the older timestamp if not self._timestamp: @@ -478,7 +478,7 @@ class Record(SignalEvent): self.group.fields[fieldname].set(self, value) self._loaded.add(fieldname) fieldnames.append(fieldname) - for fieldname, value in later.iteritems(): + for fieldname, value in later.items(): self.group.fields[fieldname].set(self, value) self._loaded.add(fieldname) if validate: @@ -487,7 +487,7 @@ class Record(SignalEvent): self.signal('record-changed') def set_on_change(self, values): - for fieldname, value in values.items(): + for fieldname, value in list(values.items()): if fieldname not in self.group.fields: continue if isinstance(self.group.fields[fieldname], (fields.M2OField, @@ -520,7 +520,7 @@ class Record(SignalEvent): self.signal('record-changed') def expr_eval(self, expr): - if not isinstance(expr, basestring): + if not isinstance(expr, str): return expr ctx = self.get_eval() ctx['context'] = self.get_context() @@ -605,7 +605,7 @@ class Record(SignalEvent): self.group.fields[fieldname].set_on_change(self, result) def autocomplete_with(self, field_name): - for fieldname, fieldinfo in self.group.fields.iteritems(): + for fieldname, fieldinfo in self.group.fields.items(): autocomplete = fieldinfo.attrs.get('autocomplete', []) if field_name not in autocomplete: continue @@ -625,7 +625,7 @@ class Record(SignalEvent): def set_field_context(self): from .group import Group - for name, field in self.group.fields.iteritems(): + for name, field in self.group.fields.items(): value = self.value.get(name) if not isinstance(value, Group): continue @@ -677,7 +677,7 @@ class Record(SignalEvent): return clicks def destroy(self): - for v in self.value.itervalues(): + for v in self.value.values(): if hasattr(v, 'destroy'): v.destroy() super(Record, self).destroy() diff --git a/tryton/gui/window/view_form/screen/__init__.py b/tryton/gui/window/view_form/screen/__init__.py index a33693ab54..70b9392183 100644 --- a/tryton/gui/window/view_form/screen/__init__.py +++ b/tryton/gui/window/view_form/screen/__init__.py @@ -1,3 +1,3 @@ # 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 screen import * +from .screen import * diff --git a/tryton/gui/window/view_form/screen/screen.py b/tryton/gui/window/view_form/screen/screen.py index 58ad9d0e40..dc311e857d 100644 --- a/tryton/gui/window/view_form/screen/screen.py +++ b/tryton/gui/window/view_form/screen/screen.py @@ -8,11 +8,11 @@ import datetime import calendar import json import collections -import urllib -import urlparse +import urllib.parse import xml.dom.minidom import gettext import logging +from operator import itemgetter import gtk @@ -170,7 +170,7 @@ class Screen(SignalEvent): view_tree = self.fields_view_tree[view_id] fields = copy.deepcopy(view_tree['fields']) - for name, props in fields.iteritems(): + for name, props in fields.items(): if props['type'] not in ('selection', 'reference'): continue if isinstance(props['selection'], (tuple, list)): @@ -202,7 +202,7 @@ class Screen(SignalEvent): ): if name not in fields: fields[name] = { - 'string': string.decode('utf-8'), + 'string': string, 'name': name, 'type': type_, } @@ -224,7 +224,7 @@ class Screen(SignalEvent): props['selection']) except RPCException: selection = [] - selection.sort(lambda x, y: cmp(x[1], y[1])) + selection.sort(key=itemgetter(1)) return selection def search_prev(self, search_string): @@ -355,7 +355,7 @@ class Screen(SignalEvent): fields_views = {} if self.group is not None: self.group.signal_unconnect(self) - for name, field in self.group.fields.iteritems(): + for name, field in self.group.fields.items(): fields[name] = field.attrs fields_views[name] = field.views self.tree_states_done.clear() @@ -375,7 +375,7 @@ class Screen(SignalEvent): self._record_modified) self.__group.signal_connect(self, 'group-changed', self._group_changed) self.__group.add_fields(fields) - for name, views in fields_views.iteritems(): + for name, views in fields_views.items(): self.__group.fields[name].views.update(views) self.__group.exclude_field = self.exclude_field @@ -827,7 +827,7 @@ class Screen(SignalEvent): timestamp = self.parent._timestamp if self.parent else None for view in self.views: if view.view_type == 'form': - for widgets in view.widgets.itervalues(): + for widgets in view.widgets.values(): for widget in widgets: if hasattr(widget, 'screen'): widget.screen.save_tree_state(store) @@ -1036,7 +1036,7 @@ class Screen(SignalEvent): record = self.current_record domain_string = _('"%s" is not valid according to its domain') domain_parser = DomainParser( - {n: f.attrs for n, f in record.group.fields.iteritems()}) + {n: f.attrs for n, f in record.group.fields.items()}) fields = [] for field, invalid in sorted(record.invalid_fields.items()): string = record.group.fields[field].attrs['string'] @@ -1134,7 +1134,7 @@ class Screen(SignalEvent): except RPCException: action = None self.reload(ids, written=True) - if isinstance(action, basestring): + if isinstance(action, str): self.client_action(action) elif action: Action.execute(action, { @@ -1208,7 +1208,7 @@ class Screen(SignalEvent): if view_ids: query_string.append(('views', json.dumps( view_ids, separators=(',', ':')))) - query_string = urllib.urlencode(query_string) - return urlparse.urlunparse(('tryton', + query_string = urllib.parse.urlencode(query_string) + return urllib.parse.urlunparse(('tryton', '%s:%s' % (rpc._HOST, rpc._PORT), '/'.join(path), query_string, '', '')) diff --git a/tryton/gui/window/view_form/view/calendar_.py b/tryton/gui/window/view_form/view/calendar_.py index de9bd606a9..e8e39f131d 100644 --- a/tryton/gui/window/view_form/view/calendar_.py +++ b/tryton/gui/window/view_form/view/calendar_.py @@ -11,7 +11,7 @@ from . import View try: from .calendar_gtk.calendar_ import Calendar_ from .calendar_gtk.toolbar import Toolbar -except ImportError, e: +except ImportError as e: Calendar_ = None Toolbar = None diff --git a/tryton/gui/window/view_form/view/calendar_gtk/calendar_.py b/tryton/gui/window/view_form/view/calendar_gtk/calendar_.py index f1d8212163..471513e380 100644 --- a/tryton/gui/window/view_form/view/calendar_gtk/calendar_.py +++ b/tryton/gui/window/view_form/view/calendar_gtk/calendar_.py @@ -3,7 +3,7 @@ import calendar import datetime import goocalendar -from dates_period import DatesPeriod +from .dates_period import DatesPeriod class Calendar_(goocalendar.Calendar): diff --git a/tryton/gui/window/view_form/view/form.py b/tryton/gui/window/view_form/view/form.py index f4a35707e8..4569d46e1d 100644 --- a/tryton/gui/window/view_form/view/form.py +++ b/tryton/gui/window/view_form/view/form.py @@ -466,13 +466,13 @@ class ViewForm(View): return cls.WIDGETS[name] def get_fields(self): - return self.widgets.keys() + return list(self.widgets.keys()) def __getitem__(self, name): return self.widgets[name][0] def destroy(self): - for widget_name in self.widgets.keys(): + for widget_name in list(self.widgets.keys()): for widget in self.widgets[widget_name]: widget.destroy() self.widget.destroy() @@ -480,7 +480,7 @@ class ViewForm(View): def set_value(self, focused_widget=False): record = self.screen.current_record if record: - for name, widgets in self.widgets.iteritems(): + for name, widgets in self.widgets.items(): if name in record.group.fields: field = record.group.fields[name] for widget in widgets: @@ -498,7 +498,7 @@ class ViewForm(View): @property def modified(self): - return any(w.modified for widgets in self.widgets.itervalues() + return any(w.modified for widgets in self.widgets.values() for w in widgets) def get_buttons(self): @@ -507,7 +507,7 @@ class ViewForm(View): def reset(self): record = self.screen.current_record if record: - for name, widgets in self.widgets.iteritems(): + for name, widgets in self.widgets.items(): field = record.group.fields.get(name) if field and 'invalid' in field.get_state_attrs(record): for widget in widgets: @@ -523,13 +523,13 @@ class ViewForm(View): (name, field.attrs.get('loading', 'eager') == 'eager', len(field.views)) - for name, field in record.group.fields.iteritems() + for name, field in record.group.fields.items() if self.view_id in field.views) fields = sorted(fields, key=operator.itemgetter(1, 2)) for field, _, _ in fields: record[field].get(record) focused_widget = find_focused_child(self.widget) - for name, widgets in self.widgets.iteritems(): + for name, widgets in self.widgets.items(): field = None if record: field = record.group.fields.get(name) diff --git a/tryton/gui/window/view_form/view/form_gtk/dictionary.py b/tryton/gui/window/view_form/view/form_gtk/dictionary.py index b841d1e7cf..520e6c9e5b 100644 --- a/tryton/gui/window/view_form/view/form_gtk/dictionary.py +++ b/tryton/gui/window/view_form/view/form_gtk/dictionary.py @@ -126,7 +126,7 @@ class DictSelectionEntry(DictEntry): text = child.get_text() value = None if text: - for txt, val in self._selection.items(): + for txt, val in list(self._selection.items()): if not val: continue if txt[:len(text)].lower() == text.lower(): @@ -390,7 +390,7 @@ class DictWidget(Widget): def _sig_add(self, *args): context = self.field.get_context(self.record) - value = self.wid_text.get_text().decode('utf-8') + value = self.wid_text.get_text() domain = self.field.domain_get(self.record) def callback(result): @@ -439,20 +439,20 @@ class DictWidget(Widget): def get_value(self): return dict((key, widget.get_value()) - for key, widget in self.fields.items()) + for key, widget in list(self.fields.items())) @property def modified(self): if self.record and self.field: value = self.field.get_client(self.record) return any(widget.modified(value) - for widget in self.fields.itervalues()) + for widget in self.fields.values()) return False def _readonly_set(self, readonly): self._readonly = readonly self._set_button_sensitive() - for widget in self.fields.values(): + for widget in list(self.fields.values()): widget.set_readonly(readonly) self.wid_text.set_sensitive(not readonly) self.wid_text.set_editable(not readonly) @@ -461,7 +461,7 @@ class DictWidget(Widget): self.but_add.set_sensitive(bool( not self._readonly and self.attrs.get('create', True))) - for button in self.buttons.itervalues(): + for button in self.buttons.values(): button.set_sensitive(bool( not self._readonly and self.attrs.get('delete', True))) @@ -507,7 +507,7 @@ class DictWidget(Widget): context = self.field.get_context(self.record) domain = self.field.domain_get(self.record) batchlen = min(10, CONFIG['client.limit']) - for i in xrange(0, len(keys), batchlen): + for i in range(0, len(keys), batchlen): sub_keys = keys[i:i + batchlen] try: key_ids = RPCExecute('model', self.schema_model, 'search', @@ -534,15 +534,15 @@ class DictWidget(Widget): record_id = record.id if record else None if record_id != self._record_id: - for key in self.fields.keys(): + for key in list(self.fields.keys()): self._sig_remove(None, key, modified=False) self._record_id = record_id value = field.get_client(record) if field else {} - new_key_names = set(value.iterkeys()) - set(self.keys) + new_key_names = set(value.keys()) - set(self.keys) if new_key_names: self.add_keys(list(new_key_names)) - for key, val in sorted(value.iteritems()): + for key, val in sorted(value.items()): if key not in self.keys: continue if key not in self.fields: diff --git a/tryton/gui/window/view_form/view/form_gtk/image.py b/tryton/gui/window/view_form/view/form_gtk/image.py index 93e61fba4c..67dfb76028 100644 --- a/tryton/gui/window/view_form/view/form_gtk/image.py +++ b/tryton/gui/window/view_form/view/form_gtk/image.py @@ -2,7 +2,7 @@ # this repository contains the full copyright notices and license terms. import gtk import gettext -import urllib +import urllib.request from tryton.common import resize_pixbuf, data2pixbuf, BIG_IMAGE_SIZE from .widget import Widget @@ -76,12 +76,14 @@ class Image(BinaryMixin, Widget): if info == 0: uri = selection.get_text().split('\n')[0] if uri: - self.field.set_client(self.record, urllib.urlopen(uri).read()) + self.field.set_client( + self.record, urllib.request.urlopen(uri).read()) self.update_img() elif info == 1: uri = selection.data.split('\r\n')[0] if uri: - self.field.set_client(self.record, urllib.urlopen(uri).read()) + self.field.set_client( + self.record, urllib.request.urlopen(uri).read()) self.update_img() elif info == 2: data = selection.get_pixbuf() @@ -93,7 +95,7 @@ class Image(BinaryMixin, Widget): value = None if self.field: value = self.field.get_client(self.record) - if isinstance(value, (int, long)): + if isinstance(value, int): if value > BIG_IMAGE_SIZE: value = False else: diff --git a/tryton/gui/window/view_form/view/form_gtk/many2many.py b/tryton/gui/window/view_form/view/form_gtk/many2many.py index 63f290d689..eee926b888 100644 --- a/tryton/gui/window/view_form/view/form_gtk/many2many.py +++ b/tryton/gui/window/view_form/view/form_gtk/many2many.py @@ -151,7 +151,7 @@ class Many2Many(Widget): domain = [domain, add_remove] context = self.field.get_search_context(self.record) order = self.field.get_search_order(self.record) - value = self.wid_text.get_text().decode('utf-8') + value = self.wid_text.get_text() self.focus_out = False diff --git a/tryton/gui/window/view_form/view/form_gtk/many2one.py b/tryton/gui/window/view_form/view/form_gtk/many2one.py index 845f4cd5f1..5ab8c668be 100644 --- a/tryton/gui/window/view_form/view/form_gtk/many2one.py +++ b/tryton/gui/window/view_form/view/form_gtk/many2one.py @@ -121,7 +121,7 @@ class Many2One(Widget): domain = self.field.domain_get(self.record) context = self.field.get_search_context(self.record) order = self.field.get_search_order(self.record) - text = self.wid_text.get_text().decode('utf-8') + text = self.wid_text.get_text() def callback(result): if result: @@ -212,7 +212,7 @@ class Many2One(Widget): domain = self.field.domain_get(self.record) context = self.field.get_search_context(self.record) order = self.field.get_search_order(self.record) - text = self.wid_text.get_text().decode('utf-8') + text = self.wid_text.get_text() def callback(result): if result: diff --git a/tryton/gui/window/view_form/view/form_gtk/one2many.py b/tryton/gui/window/view_form/view/form_gtk/one2many.py index b913abe493..863b25a818 100644 --- a/tryton/gui/window/view_form/view/form_gtk/one2many.py +++ b/tryton/gui/window/view_form/view/form_gtk/one2many.py @@ -413,8 +413,8 @@ class One2Many(Widget): self.screen.group.remove(first, remove=True) return - fields = product.keys() - for values in itertools.product(*product.values()): + fields = list(product.keys()) + for values in itertools.product(*list(product.values())): if first: record = first first = None @@ -478,7 +478,7 @@ class One2Many(Widget): domain = [domain, self.record.expr_eval(self.attrs.get('add_remove'))] removed_ids = self.field.get_removed_ids(self.record) domain = ['OR', domain, ('id', 'in', removed_ids)] - text = self.wid_text.get_text().decode('utf-8') + text = self.wid_text.get_text() self.focus_out = False diff --git a/tryton/gui/window/view_form/view/form_gtk/richtextbox.py b/tryton/gui/window/view_form/view/form_gtk/richtextbox.py index e74e5a12ee..b54a3de088 100644 --- a/tryton/gui/window/view_form/view/form_gtk/richtextbox.py +++ b/tryton/gui/window/view_form/view/form_gtk/richtextbox.py @@ -259,7 +259,7 @@ class RichTextBox(TextBox): start.backward_chars(length) end = iter_.copy() # Apply tags activated from toolbar - for name, widget in self.tag_widgets[textview].iteritems(): + for name, widget in self.tag_widgets[textview].items(): self._apply_tool(text_buffer, name, widget, start, end) def _apply_tool(self, text_buffer, name, tool, start, end): diff --git a/tryton/gui/window/view_form/view/form_gtk/state_widget.py b/tryton/gui/window/view_form/view/form_gtk/state_widget.py index 6913f97bea..6c8b7cc570 100644 --- a/tryton/gui/window/view_form/view/form_gtk/state_widget.py +++ b/tryton/gui/window/view_form/view/form_gtk/state_widget.py @@ -93,7 +93,7 @@ class Notebook(StateMixin, gtk.Notebook): else: state_changes = {} if state_changes.get('readonly', self.attrs.get('readonly')): - for widgets in self.widgets.itervalues(): + for widgets in self.widgets.values(): for widget in widgets: widget._readonly_set(True) diff --git a/tryton/gui/window/view_form/view/form_gtk/textbox.py b/tryton/gui/window/view_form/view/form_gtk/textbox.py index 8a2503ff6e..c36824a8dd 100644 --- a/tryton/gui/window/view_form/view/form_gtk/textbox.py +++ b/tryton/gui/window/view_form/view/form_gtk/textbox.py @@ -111,7 +111,7 @@ class TextBox(Widget, TranslateMixin): buf = textview.get_buffer() iter_start = buf.get_start_iter() iter_end = buf.get_end_iter() - return buf.get_text(iter_start, iter_end, False).decode('utf-8') + return buf.get_text(iter_start, iter_end, False) def display(self, record, field): super(TextBox, self).display(record, field) @@ -152,7 +152,6 @@ class TextBufferLimitSize(gtk.TextBuffer): def do_insert_text(self, iter, text, length): free_chars = self.max_length - self.get_char_count() - # Slice operation needs an unicode string to work as expected - text = text.decode('utf-8')[0:free_chars].encode('utf-8') + text = text[0:free_chars] length = len(text) return gtk.TextBuffer.do_insert_text(self, iter, text, length) diff --git a/tryton/gui/window/view_form/view/form_gtk/widget.py b/tryton/gui/window/view_form/view/form_gtk/widget.py index bbb1712220..ccb78d7f2e 100644 --- a/tryton/gui/window/view_form/view/form_gtk/widget.py +++ b/tryton/gui/window/view_form/view/form_gtk/widget.py @@ -220,7 +220,7 @@ class TranslateDialog(NoModal): def response(self, win, response): if response == gtk.RESPONSE_OK: - for code, widget in self.widgets.iteritems(): + for code, widget in self.widgets.items(): widget, editing, fuzzy = widget if not editing.get_active(): continue diff --git a/tryton/gui/window/view_form/view/graph.py b/tryton/gui/window/view_form/view/graph.py index 8912c4e500..d37c5f20db 100644 --- a/tryton/gui/window/view_form/view/graph.py +++ b/tryton/gui/window/view_form/view/graph.py @@ -111,7 +111,7 @@ class ViewGraph(View): hbox.pack_start(gtk.Label(_('Width:')), False, True) spinwidth = gtk.SpinButton() - spinwidth.configure(gtk.Adjustment(400.0, 0.0, sys.maxint, 1.0, 10.0), + spinwidth.configure(gtk.Adjustment(400.0, 0.0, sys.maxsize, 1.0, 10.0), climb_rate=1, digits=0) spinwidth.set_numeric(True) spinwidth.set_activates_default(True) @@ -119,7 +119,8 @@ class ViewGraph(View): hbox.pack_start(gtk.Label(_('Height:')), False, True) spinheight = gtk.SpinButton() - spinheight.configure(gtk.Adjustment(200.0, 0.0, sys.maxint, 1.0, 10.0), + spinheight.configure(gtk.Adjustment( + 200.0, 0.0, sys.maxsize, 1.0, 10.0), climb_rate=1, digits=0) spinheight.set_numeric(True) spinheight.set_activates_default(True) diff --git a/tryton/gui/window/view_form/view/graph_gtk/bar.py b/tryton/gui/window/view_form/view/graph_gtk/bar.py index 04e990f69f..164d516e66 100644 --- a/tryton/gui/window/view_form/view/graph_gtk/bar.py +++ b/tryton/gui/window/view_form/view/graph_gtk/bar.py @@ -8,7 +8,7 @@ import datetime import cairo -from graph import Graph +from .graph import Graph import tryton.common as common import tryton.rpc as rpc @@ -142,7 +142,7 @@ class VerticalBar(Bar): self.bars = [] i = 0 - keys = self.datas.keys() + keys = list(self.datas.keys()) keys.sort() for xfield in keys: j = 0 @@ -198,7 +198,7 @@ class HorizontalBar(Bar): self.bars = [] i = 0 - keys = self.datas.keys() + keys = list(self.datas.keys()) keys.sort() for xfield in keys: j = 0 diff --git a/tryton/gui/window/view_form/view/graph_gtk/graph.py b/tryton/gui/window/view_form/view/graph_gtk/graph.py index c192a744cc..d29b24ad1e 100644 --- a/tryton/gui/window/view_form/view/graph_gtk/graph.py +++ b/tryton/gui/window/view_form/view/graph_gtk/graph.py @@ -127,7 +127,7 @@ class Graph(gtk.DrawingArea): self.drawGraph(cx, width, height) self.drawAxis(cx, width, height) self.drawLegend(cx, width, height) - surface.write_to_png(filename.encode('utf-8')) + surface.write_to_png(filename) self.queue_draw() @@ -176,7 +176,7 @@ class Graph(gtk.DrawingArea): base = 1 else: base = 10 ** int(math.log(self.yrange, 10)) - for i in xrange(int(self.yrange / base) + 1): + for i in range(int(self.yrange / base) + 1): val = int(self.minyval / base) * base + i * base h = (val - self.minyval) * self.yscale label = locale.format('%.2f', val, True) @@ -186,7 +186,7 @@ class Graph(gtk.DrawingArea): def XLabels(self): xlabels = [] i = 0.0 - keys = self.datas.keys() + keys = list(self.datas.keys()) keys.sort() for key in keys: if self.xrange == 0: @@ -439,7 +439,7 @@ class Graph(gtk.DrawingArea): else: self.xscale = 1.0 / self.xrange - if not self.datas.values(): + if not list(self.datas.values()): self.maxyval = 0.0 self.minyval = 0.0 else: diff --git a/tryton/gui/window/view_form/view/graph_gtk/line.py b/tryton/gui/window/view_form/view/graph_gtk/line.py index dc3842f6ad..36deceee8b 100644 --- a/tryton/gui/window/view_form/view/graph_gtk/line.py +++ b/tryton/gui/window/view_form/view/graph_gtk/line.py @@ -8,7 +8,7 @@ import datetime import cairo -from graph import Graph +from .graph import Graph import tryton.common as common import tryton.rpc as rpc @@ -23,7 +23,7 @@ class Line(Graph): self.points = [] i = 0 - keys = self.datas.keys() + keys = list(self.datas.keys()) keys.sort() for xfield in keys: j = 0 diff --git a/tryton/gui/window/view_form/view/graph_gtk/pie.py b/tryton/gui/window/view_form/view/graph_gtk/pie.py index aa7469a6e2..ccca4d563f 100644 --- a/tryton/gui/window/view_form/view/graph_gtk/pie.py +++ b/tryton/gui/window/view_form/view/graph_gtk/pie.py @@ -8,7 +8,7 @@ import datetime import cairo -from graph import Graph, Area +from .graph import Graph, Area import tryton.common as common import tryton.rpc as rpc @@ -16,7 +16,7 @@ import tryton.rpc as rpc class Pie(Graph): def _getDatasKeys(self): - return self.datas.keys() + return list(self.datas.keys()) def drawLegend(self, cr, width, height): pass @@ -71,7 +71,7 @@ class Pie(Graph): def updateGraph(self): self.sum = 0.0 - for xkey in self.datas.iterkeys(): + for xkey in self.datas.keys(): key = self.yfields[0].get('key', self.yfields[0]['name']) if self.datas[xkey][key] > 0: self.sum += self.datas[xkey][key] @@ -79,7 +79,7 @@ class Pie(Graph): fraction = angle = 0.0 self.slices = [] - for xkey in self.datas.iterkeys(): + for xkey in self.datas.keys(): key = self.yfields[0].get('key', self.yfields[0]['name']) value = self.datas[xkey][key] if value > 0: diff --git a/tryton/gui/window/view_form/view/list.py b/tryton/gui/window/view_form/view/list.py index dc947c646f..562cf73f7e 100644 --- a/tryton/gui/window/view_form/view/list.py +++ b/tryton/gui/window/view_form/view/list.py @@ -158,8 +158,7 @@ class AdaptModelGroup(gtk.GenericTreeModel): pos += 1 except KeyError: continue - self.group.sort(lambda x, y: - cmp(new_order[ids2pos[x.id]], new_order[ids2pos[y.id]])) + self.group.sort(key=lambda x: new_order[ids2pos[x.id]]) prev = None for record in self.group: if prev: @@ -561,7 +560,7 @@ class ViewTree(View): column.arrow.set(gtk.ARROW_NONE, gtk.SHADOW_NONE) model = self.treeview.get_model() unsaved_records = [x for x in model.group if x.id < 0] - search_string = self.screen.screen_container.get_text() or u'' + search_string = self.screen.screen_container.get_text() or '' if (self.screen.search_count == len(model) or unsaved_records or self.screen.parent): @@ -969,7 +968,7 @@ class ViewTree(View): if last_col and last_col.name in fields: del fields[last_col.name] - if fields and any(fields.itervalues()): + if fields and any(fields.values()): model_name = self.screen.model_name try: RPCExecute('model', 'ir.ui.view_tree_width', 'set_width', diff --git a/tryton/gui/window/view_form/view/list_gtk/editabletree.py b/tryton/gui/window/view_form/view/list_gtk/editabletree.py index 76a93d135d..1e1af35c22 100644 --- a/tryton/gui/window/view_form/view/list_gtk/editabletree.py +++ b/tryton/gui/window/view_form/view/list_gtk/editabletree.py @@ -142,7 +142,7 @@ class EditableTreeView(TreeView): if event.keyval == gtk.keysyms.Right: if isinstance(entry, gtk.Entry): if entry.get_position() >= \ - len(entry.get_text().decode('utf-8')) \ + len(entry.get_text()) \ and not entry.get_selection_bounds(): leaving = True else: @@ -180,7 +180,7 @@ class EditableTreeView(TreeView): gobject.idle_add(self.set_cursor, path, self.prev_column(path, column), True) elif keyval in self.leaving_record_events: - fields = self.view.widgets.keys() + fields = list(self.view.widgets.keys()) if not record.validate(fields): invalid_fields = record.invalid_fields col = None diff --git a/tryton/gui/window/view_form/view/list_gtk/widget.py b/tryton/gui/window/view_form/view/list_gtk/widget.py index 6cc14dc9f1..33370fda1a 100644 --- a/tryton/gui/window/view_form/view/list_gtk/widget.py +++ b/tryton/gui/window/view_form/view/list_gtk/widget.py @@ -480,9 +480,12 @@ class Binary(GenericText): file_path = os.path.join(dtemp, filename) with open(file_path, 'wb') as fp: if hasattr(field, 'get_data'): - fp.write(field.get_data(record)) + data = field.get_data(record) else: - fp.write(field.get(record)) + data = field.get(record) + if isinstance(data, str): + data = data.encode('utf-8') + fp.write(data) root, type_ = os.path.splitext(filename) if type_: type_ = type_[1:] @@ -527,7 +530,7 @@ class Image(GenericText): record = store.get_value(iter_, 0) field = record[self.field_name] value = field.get_client(record) - if isinstance(value, (int, long)): + if isinstance(value, int): if value > common.BIG_IMAGE_SIZE: value = None else: @@ -665,7 +668,7 @@ class M2O(GenericText): context=context, domain=domain, order=order, view_ids=self.attrs.get('view_ids', '').split(','), new=create_access, title=self.attrs.get('string')) - win.screen.search_filter(quote(text.decode('utf-8'))) + win.screen.search_filter(quote(text)) return win def set_completion(self, entry, path): @@ -819,7 +822,8 @@ class Selection(GenericText, SelectionMixin, PopdownMixin): record = store.get_value(store.get_iter(path), 0) field = record[self.attrs['name']] - set_value = lambda *a: self.set_value(editable, record, field) + def set_value(*a): + return self.set_value(editable, record, field) editable.get_child().connect('activate', set_value) editable.get_child().connect('focus-out-event', set_value) editable.connect('changed', set_value) diff --git a/tryton/gui/window/view_form/view/screen_container.py b/tryton/gui/window/view_form/view/screen_container.py index 9e62994428..3752e04aea 100644 --- a/tryton/gui/window/view_form/view/screen_container.py +++ b/tryton/gui/window/view_form/view/screen_container.py @@ -344,7 +344,7 @@ class ScreenContainer(object): model.append([r.strip()]) def get_text(self): - return self.search_entry.get_text().decode('utf-8') + return self.search_entry.get_text() def set_text(self, value): self.search_entry.set_text(value) @@ -370,7 +370,7 @@ class ScreenContainer(object): bool(current_text)) for id_, name, domain in self.bookmarks(): text = self.screen.domain_parser.string(domain) - domain = self.screen.domain_parser.parse(text.decode('utf-8')) + domain = self.screen.domain_parser.parse(text) if (text == current_text or domain == current_domain): if icon_stock != 'tryton-star': @@ -549,7 +549,7 @@ class ScreenContainer(object): self.search_window.handler_unblock_by_func(window_hide) vbox = gtk.VBox() - fields = [f for f in self.screen.domain_parser.fields.itervalues() + fields = [f for f in self.screen.domain_parser.fields.values() if f.get('searchable', True)] self.search_table = gtk.Table(rows=len(fields), columns=2) self.search_table.set_homogeneous(False) @@ -617,8 +617,8 @@ class ScreenContainer(object): self.search_window.add(vbox) vbox.show_all() - new_size = map(sum, zip(self.search_table.size_request(), - scrolled.size_request())) + new_size = list(map(sum, list(zip(self.search_table.size_request(), + scrolled.size_request())))) self.search_window.set_default_size(*new_size) parent = widget.get_toplevel() diff --git a/tryton/gui/window/win_export.py b/tryton/gui/window/win_export.py index 777adab9d8..398fae9cbb 100644 --- a/tryton/gui/window/win_export.py +++ b/tryton/gui/window/win_export.py @@ -3,7 +3,6 @@ import csv import os import tempfile -import types import gtk import gobject @@ -94,8 +93,10 @@ class WinExport(WinCSV): def model_populate(self, fields, parent_node=None, prefix_field='', prefix_name=''): - key = lambda (n, f): f.get('string') or n - for name, field in sorted(fields.items(), key=key, reverse=True): + + def key(n_f): + return n_f[1].get('string') or n_f[0] + for name, field in sorted(list(fields.items()), key=key, reverse=True): string_ = field['string'] or name @@ -314,7 +315,7 @@ class WinExport(WinCSV): try: writer = csv.writer( - open(fname, 'wb+'), + open(fname, 'w+', encoding=encoding), quotechar=self.get_quotechar(), delimiter=self.get_delimiter()) if self.add_field_names.get_active(): @@ -322,9 +323,6 @@ class WinExport(WinCSV): for line in data: row = [] for val in line: - if isinstance(type(val), types.StringType): - val = val.replace('\n', ' ').replace('\t', ' ') - val = val.encode(encoding) row.append(val) writer.writerow(row) if popup: @@ -333,7 +331,7 @@ class WinExport(WinCSV): else: common.message(_('%d records saved.') % len(data)) return True - except IOError, exception: + except IOError as exception: common.warning(_("Operation failed.\nError message:\n%s") % exception, _('Error')) return False diff --git a/tryton/gui/window/win_form.py b/tryton/gui/window/win_form.py index f1f21cf8d6..1b0b08a364 100644 --- a/tryton/gui/window/win_form.py +++ b/tryton/gui/window/win_form.py @@ -316,7 +316,7 @@ class WinForm(NoModal, InfoBar): from tryton.gui.window.win_search import WinSearch domain = self.domain[:] model_name = self.screen.model_name - value = self.wid_text.get_text().decode('utf-8') + value = self.wid_text.get_text() def callback(result): if result: @@ -390,7 +390,7 @@ class WinForm(NoModal, InfoBar): validate = False elif validate and self.screen.current_view.view_type == 'form': view = self.screen.current_view - for widgets in view.widgets.itervalues(): + for widgets in view.widgets.values(): for widget in widgets: if (hasattr(widget, 'screen') and widget.screen.pre_validate): diff --git a/tryton/gui/window/win_import.py b/tryton/gui/window/win_import.py index 11828eea04..57ddf92740 100644 --- a/tryton/gui/window/win_import.py +++ b/tryton/gui/window/win_import.py @@ -56,9 +56,9 @@ class WinImport(WinCSV): def model_populate(self, fields, parent_node=None, prefix_field='', prefix_name=''): - fields_order = fields.keys() - fields_order.sort(lambda x, y: -cmp(fields[x].get('string', ''), - fields[y].get('string', ''))) + fields_order = list(fields.keys()) + fields_order.sort( + key=lambda x: fields[x].get('string', ''), reverse=True) for field in fields_order: if not fields[field].get('readonly', False): self.fields_data[prefix_field + field] = fields[field] @@ -101,21 +101,20 @@ class WinImport(WinCSV): common.message(_('You must select an import file first.')) return True + encoding = self.get_encoding() self.csv_skip.set_value(1) try: data = csv.reader( - open(fname, 'rb'), + open(fname, 'r', encoding=encoding), quotechar=self.get_quotechar(), delimiter=self.get_delimiter()) except IOError: common.warning(_('Error opening CSV file'), _('Error')) return True self.sig_unsel_all() - encoding = self.get_encoding() word = '' for line in data: for word in line: - word = word.decode(encoding) if word not in self.fields_invert and word not in self.fields: iter = self.model1.get_iter_first() prefix = '' @@ -181,14 +180,14 @@ class WinImport(WinCSV): skip = self.csv_skip.get_value_as_int() encoding = self.get_encoding() reader = csv.reader( - open(fname, 'rb'), + open(fname, 'r', encoding=encoding), quotechar=self.get_quotechar(), delimiter=self.get_delimiter()) data = [] for i, line in enumerate(reader): if i < skip or not line: continue - data.append([x.decode(encoding).encode('utf-8') for x in line]) + data.append([x for x in line]) try: count = RPCExecute( 'model', self.model, 'import_data', fields, data, diff --git a/tryton/gui/window/window.py b/tryton/gui/window/window.py index a7ea86c248..3095277d13 100644 --- a/tryton/gui/window/window.py +++ b/tryton/gui/window/window.py @@ -22,10 +22,10 @@ class Window(object): def create(model, **attributes): from tryton.gui import Main if model: - from form import Form + from .form import Form win = Form(model, **attributes) else: - from board import Board + from .board import Board win = Board(model, **attributes) win.icon = attributes.get('icon') Main.get_main().win_add(win, hide_current=Window.hide_current, @@ -35,7 +35,7 @@ class Window(object): def create_wizard(action, data, direct_print=False, email_print=False, email=None, name='', context=None, icon=None, window=False): from tryton.gui import Main - from wizard import WizardForm, WizardDialog + from .wizard import WizardForm, WizardDialog if window: win = WizardForm(name=name) win.icon = icon diff --git a/tryton/gui/window/wizard.py b/tryton/gui/window/wizard.py index 78b6799c16..bca960ca72 100644 --- a/tryton/gui/window/wizard.py +++ b/tryton/gui/window/wizard.py @@ -93,7 +93,7 @@ class Wizard(InfoBar): def callback(result): try: result = result() - except RPCException, rpc_exception: + except RPCException as rpc_exception: if (not isinstance(rpc_exception.exception, TrytonServerError) or not self.screen): @@ -180,7 +180,7 @@ class Wizard(InfoBar): self.update_buttons(record) def update_buttons(self, record): - for button in self.states.itervalues(): + for button in self.states.values(): button.state_set(record) def update(self, view, defaults, buttons): diff --git a/tryton/ipc.py b/tryton/ipc.py index 17847f606f..45150c6c69 100644 --- a/tryton/ipc.py +++ b/tryton/ipc.py @@ -58,7 +58,7 @@ class FileServer(IPCServer): def setup(self): config = open(self.config, 'w') - print >> config, self.tmpdir + print(self.tmpdir, file=config) def clean(self): try: @@ -101,7 +101,7 @@ class FIFOServer(IPCServer): def setup(self): self.filename = os.path.join(self.tmpdir, 'Socket') - os.mkfifo(self.filename, 0600) + os.mkfifo(self.filename, 0o600) if os.path.lexists(self.config): os.remove(self.config) os.symlink(self.filename, self.config) @@ -160,7 +160,7 @@ class FileClient(IPCClient): tmpdir = open(self.filename, 'r').readline().strip() _, tmpfile = tempfile.mkstemp(dir=tmpdir, text=True) with open(tmpfile, 'w') as tmpfile: - print >> tmpfile, message + print(message, file=tmpfile) return True @@ -173,9 +173,10 @@ class FIFOClient(IPCClient): if not os.path.lexists(self.filename): return False fifo = open(self.filename, 'w') - print >> fifo, message + print(message, file=fifo) return True + if hasattr(os, 'mkfifo'): Server = FIFOServer Client = FIFOClient diff --git a/tryton/jsonrpc.py b/tryton/jsonrpc.py index b7ce78545c..87d1cb380c 100644 --- a/tryton/jsonrpc.py +++ b/tryton/jsonrpc.py @@ -1,21 +1,21 @@ # 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 xmlrpclib +import xmlrpc.client import json import ssl -import httplib +import http.client from decimal import Decimal import datetime import socket import gzip -import StringIO +import io import hashlib import base64 import threading import errno from functools import partial from contextlib import contextmanager -import string +from functools import reduce __all__ = ["ResponseError", "Fault", "ProtocolError", "Transport", "ServerProxy", "ServerPool"] @@ -23,11 +23,11 @@ CONNECT_TIMEOUT = 5 DEFAULT_TIMEOUT = None -class ResponseError(xmlrpclib.ResponseError): +class ResponseError(xmlrpc.client.ResponseError): pass -class Fault(xmlrpclib.Fault): +class Fault(xmlrpc.client.Fault): def __init__(self, faultCode, faultString='', **extra): super(Fault, self).__init__(faultCode, faultString, **extra) @@ -40,7 +40,7 @@ class Fault(xmlrpclib.Fault): ) -class ProtocolError(xmlrpclib.ProtocolError): +class ProtocolError(xmlrpc.client.ProtocolError): pass @@ -57,8 +57,7 @@ def object_hook(dct): elif dct['__class__'] == 'timedelta': return datetime.timedelta(seconds=dct['seconds']) elif dct['__class__'] == 'bytes': - cast = bytearray if bytes == str else bytes - return cast(base64.decodestring(dct['base64'])) + return base64.decodestring(dct['base64'].encode('utf-8')) elif dct['__class__'] == 'Decimal': return Decimal(dct['decimal']) return dct @@ -94,9 +93,9 @@ class JSONEncoder(json.JSONEncoder): return {'__class__': 'timedelta', 'seconds': obj.total_seconds(), } - elif isinstance(obj, (bytes, bytearray)): + elif isinstance(obj, bytes): return {'__class__': 'bytes', - 'base64': base64.encodestring(obj), + 'base64': base64.encodestring(obj).decode('utf-8'), } elif isinstance(obj, Decimal): return {'__class__': 'Decimal', @@ -122,19 +121,19 @@ class JSONUnmarshaller(object): self.data = [] def feed(self, data): - self.data.append(data) + self.data.append(data.decode('utf-8')) def close(self): return json.loads(''.join(self.data), object_hook=object_hook) -class Transport(xmlrpclib.Transport, xmlrpclib.SafeTransport): +class Transport(xmlrpc.client.SafeTransport): accept_gzip_encoding = True encode_threshold = 1400 # common MTU def __init__(self, fingerprints=None, ca_certs=None, session=None): - xmlrpclib.Transport.__init__(self) + xmlrpc.client.Transport.__init__(self) self._connection = (None, None) self.__fingerprints = fingerprints self.__ca_certs = ca_certs @@ -146,13 +145,14 @@ class Transport(xmlrpclib.Transport, xmlrpclib.SafeTransport): return parser, target def get_host_info(self, host): - host, extra_headers, x509 = xmlrpclib.Transport.get_host_info( + host, extra_headers, x509 = xmlrpc.client.Transport.get_host_info( self, host) if extra_headers is None: extra_headers = [] if self.session: - auth = base64.encodestring(self.session) - auth = string.join(string.split(auth), "") # get rid of whitespace + auth = base64.encodestring( + self.session.encode('utf-8')).decode('ascii') + auth = ''.join(auth.split()) # get rid of whitespace extra_headers.append( ('Authorization', 'Session ' + auth), ) @@ -165,7 +165,7 @@ class Transport(xmlrpclib.Transport, xmlrpclib.SafeTransport): self.encode_threshold < len(request_body) and gzip): connection.putheader("Content-Encoding", "gzip") - buffer = StringIO.StringIO() + buffer = io.BytesIO() output = gzip.GzipFile(mode='wb', fileobj=buffer) output.write(request_body) output.close() @@ -183,7 +183,7 @@ class Transport(xmlrpclib.Transport, xmlrpclib.SafeTransport): ssl_ctx = ssl.create_default_context(cafile=self.__ca_certs) - class HTTPSConnection(httplib.HTTPSConnection): + class HTTPSConnection(http.client.HTTPSConnection): def connect(self): sock = socket.create_connection((self.host, self.port), @@ -195,7 +195,7 @@ class Transport(xmlrpclib.Transport, xmlrpclib.SafeTransport): sock, server_hostname=self.host) def http_connection(): - self._connection = host, httplib.HTTPConnection(host, + self._connection = host, http.client.HTTPConnection(host, timeout=CONNECT_TIMEOUT) self._connection[1].connect() sock = self._connection[1].sock @@ -242,7 +242,7 @@ class Transport(xmlrpclib.Transport, xmlrpclib.SafeTransport): return self._connection[1] -class ServerProxy(xmlrpclib.ServerProxy): +class ServerProxy(xmlrpc.client.ServerProxy): __id = 0 def __init__(self, host, port, database='', verbose=0, @@ -262,7 +262,7 @@ class ServerProxy(xmlrpclib.ServerProxy): 'id': id_, 'method': methodname, 'params': params, - }, cls=JSONEncoder, separators=(',', ':')) + }, cls=JSONEncoder, separators=(',', ':')).encode('utf-8') try: try: @@ -272,7 +272,7 @@ class ServerProxy(xmlrpclib.ServerProxy): request, verbose=self.__verbose ) - except (socket.error, httplib.HTTPException), v: + except (socket.error, http.client.HTTPException) as v: if (isinstance(v, socket.error) and v.args[0] == errno.EPIPE): raise @@ -284,9 +284,9 @@ class ServerProxy(xmlrpclib.ServerProxy): request, verbose=self.__verbose ) - except xmlrpclib.ProtocolError, e: + except xmlrpc.client.ProtocolError as e: raise Fault(str(e.errcode), e.errmsg) - except: + except Exception: self.__transport.close() raise @@ -303,7 +303,7 @@ class ServerProxy(xmlrpclib.ServerProxy): @property def ssl(self): return isinstance(self.__transport.make_connection(self.__host), - httplib.HTTPSConnection) + http.client.HTTPSConnection) class ServerPool(object): @@ -337,14 +337,14 @@ class ServerPool(object): def close(self): with self._lock: - for conn in self._pool + self._used.values(): + for conn in self._pool + list(self._used.values()): conn.close() self._pool = [] self._used.clear() @property def ssl(self): - for conn in self._pool + self._used.values(): + for conn in self._pool + list(self._used.values()): return conn.ssl return False diff --git a/tryton/plugins/__init__.py b/tryton/plugins/__init__.py index b15fab3623..8ba24dd64d 100644 --- a/tryton/plugins/__init__.py +++ b/tryton/plugins/__init__.py @@ -19,7 +19,7 @@ def register(): os.path.join(get_config_dir(), 'plugins'), os.path.join(CURRENT_DIR, 'plugins'), ] - paths = filter(os.path.isdir, paths) + paths = list(filter(os.path.isdir, paths)) imported = set() for path in paths: diff --git a/tryton/pyson.py b/tryton/pyson.py index 9a451097be..199e839110 100644 --- a/tryton/pyson.py +++ b/tryton/pyson.py @@ -9,7 +9,7 @@ from functools import reduce, wraps def reduced_type(types): types = types.copy() - for k, r in [(long, int), (str, basestring), (unicode, basestring)]: + for k, r in [(int, int), (str, str), (str, str)]: if k in types: types.remove(k) types.add(r) @@ -313,10 +313,10 @@ class Greater(PYSON): super(Greater, self).__init__() for i in (statement1, statement2): if isinstance(i, PYSON): - assert i.types().issubset({int, long, float, type(None)}), \ + assert i.types().issubset({int, int, float, type(None)}), \ 'statement must be an integer or a float' else: - assert isinstance(i, (int, long, float, type(None))), \ + assert isinstance(i, (int, float, type(None))), \ 'statement must be an integer or a float' if isinstance(equal, PYSON): assert equal.types() == set([bool]) @@ -346,7 +346,7 @@ class Greater(PYSON): for i in ('s1', 's2'): if dct[i] is None: dct[i] = 0.0 - if not isinstance(dct[i], (int, long, float)): + if not isinstance(dct[i], (int, float)): dct = dct.copy() dct[i] = float(dct[i]) return dct @@ -438,9 +438,9 @@ class Get(PYSON): assert isinstance(obj, dict), 'obj must be a dict' self._obj = obj if isinstance(key, PYSON): - assert key.types() == set([basestring]), 'key must be a string' + assert key.types() == set([str]), 'key must be a string' else: - assert isinstance(key, basestring), 'key must be a string' + assert isinstance(key, str), 'key must be a string' self._key = key self._default = default @@ -474,20 +474,20 @@ class In(PYSON): key, obj = k, v super(In, self).__init__() if isinstance(key, PYSON): - assert key.types().issubset(set([basestring, int])), \ + assert key.types().issubset(set([str, int])), \ 'key must be a string or an integer or a long' else: - assert isinstance(key, (basestring, int, long)), \ + assert isinstance(key, (str, int)), \ 'key must be a string or an integer or a long' if isinstance(obj, PYSON): assert obj.types().issubset(set([dict, list])), \ 'obj must be a dict or a list' if obj.types() == set([dict]): - assert isinstance(key, basestring), 'key must be a string' + assert isinstance(key, str), 'key must be a string' else: assert isinstance(obj, (dict, list)) if isinstance(obj, dict): - assert isinstance(key, basestring), 'key must be a string' + assert isinstance(key, str), 'key must be a string' self._key = key self._obj = obj @@ -523,10 +523,10 @@ class Date(PYSON): super(Date, self).__init__() for i in (year, month, day, delta_years, delta_months, delta_days): if isinstance(i, PYSON): - assert i.types().issubset(set([int, long, type(None)])), \ + assert i.types().issubset(set([int, int, type(None)])), \ '%s must be an integer or None' % (i,) else: - assert isinstance(i, (int, long, type(None))), \ + assert isinstance(i, (int, type(None))), \ '%s must be an integer or None' % (i,) self._year = year self._month = month @@ -590,7 +590,7 @@ class DateTime(Date): assert i.types() == set([int, type(None)]), \ '%s must be an integer or None' % (i,) else: - assert isinstance(i, (int, long, type(None))), \ + assert isinstance(i, (int, type(None))), \ '%s must be an integer or None' % (i,) self._hour = hour self._minute = minute @@ -651,10 +651,10 @@ class Len(PYSON): def __init__(self, v): super(Len, self).__init__() if isinstance(v, PYSON): - assert v.types().issubset(set([dict, list, basestring])), \ + assert v.types().issubset(set([dict, list, str])), \ 'value must be a dict or a list or a string' else: - assert isinstance(v, (dict, list, basestring)), \ + assert isinstance(v, (dict, list, str)), \ 'value must be a dict or list or a string' self._value = v diff --git a/tryton/rpc.py b/tryton/rpc.py index d899a73431..8f6560b10d 100644 --- a/tryton/rpc.py +++ b/tryton/rpc.py @@ -1,6 +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 httplib +import http.client import logging import socket import ssl @@ -41,7 +41,7 @@ def db_list(host, port): result = connection.common.db.list() logging.getLogger(__name__).debug(repr(result)) return result - except Fault, exception: + except Fault as exception: if exception.faultCode == 'AccessDenied': logging.getLogger(__name__).debug('AccessDenied') return [] @@ -58,7 +58,7 @@ def server_version(host, port): result = connection.common.server.version() logging.getLogger(__name__).debug(repr(result)) return result - except (Fault, socket.error, ssl.SSLError, ssl.CertificateError), e: + except (Fault, socket.error, ssl.SSLError, ssl.CertificateError) as e: logging.getLogger(__name__).error(e) return None @@ -96,7 +96,7 @@ def logout(): logging.getLogger(__name__).info('common.db.logout()') with CONNECTION() as conn: conn.common.db.logout() - except (Fault, socket.error, httplib.CannotSendRequest): + except (Fault, socket.error, http.client.CannotSendRequest): pass CONNECTION.close() CONNECTION = None @@ -136,7 +136,7 @@ def _execute(blocking, *args): logging.getLogger(__name__).info('%s%s' % (name, args)) with CONNECTION() as conn: result = getattr(conn, name)(*args) - except (httplib.CannotSendRequest, socket.error), exception: + except (http.client.CannotSendRequest, socket.error) as exception: raise TrytonServerUnavailable(*exception.args) if not CONFIG['dev']: if key and method == 'fields_view_get': diff --git a/tryton/signal_event.py b/tryton/signal_event.py index 6cb6107021..43ebf2abb8 100644 --- a/tryton/signal_event.py +++ b/tryton/signal_event.py @@ -25,7 +25,7 @@ class SignalEvent(object): def signal_unconnect(self, key, signal=None): if signal is None: - signal = self.__connects.keys() + signal = list(self.__connects.keys()) else: signal = [signal] for sig in signal: diff --git a/tryton/translate.py b/tryton/translate.py index 651641697a..eea44f2f3d 100644 --- a/tryton/translate.py +++ b/tryton/translate.py @@ -190,7 +190,7 @@ def setlang(lang=None, locale_dict=None): if locale_dict: conv = locale.localeconv() - for field in locale_dict.keys(): + for field in list(locale_dict.keys()): if field == 'date': continue conv[field] = locale_dict[field]