From fffaf735f58f1ac57781c095eb552de6d1fcdc47 Mon Sep 17 00:00:00 2001 From: xmo-odoo Date: Wed, 10 May 2017 09:39:55 +0200 Subject: [FIX] P3: list -> iterable builtins (#16811) In Python 3: * various builtins and dict methods were changed to return view/iterable objects rather than lists * and the separate Python 2 view/iterable builtins and methods were removed altogether This is problematic when using these items as list (which the happens repeatedly in Odoo), but more viciously when iterating *multiple times* over them (which also happens, which I've messed up multiple times while writing this, and which is a pain to debug even when you've just created the issue). Convert all code using these to semantics-matching cross-version helper functions to get the LCD behaviour between P2 and P3, and forbid the builtins via lint. issue #8530 --- odoo/addons/base/ir/ir_actions.py | 25 ++-- odoo/addons/base/ir/ir_attachment.py | 10 +- odoo/addons/base/ir/ir_config_parameter.py | 4 +- odoo/addons/base/ir/ir_fields.py | 16 +- odoo/addons/base/ir/ir_http.py | 5 +- odoo/addons/base/ir/ir_mail_server.py | 17 ++- odoo/addons/base/ir/ir_model.py | 30 ++-- odoo/addons/base/ir/ir_qweb/assetsbundle.py | 8 +- odoo/addons/base/ir/ir_qweb/ir_qweb.py | 8 +- odoo/addons/base/ir/ir_qweb/qweb.py | 55 +++---- odoo/addons/base/ir/ir_sequence.py | 3 +- odoo/addons/base/ir/ir_translation.py | 6 +- odoo/addons/base/ir/ir_ui_view.py | 38 +++-- odoo/addons/base/ir/ir_values.py | 6 +- odoo/addons/base/module/module.py | 16 +- .../module/report/ir_module_reference_print.py | 4 +- odoo/addons/base/res/ir_property.py | 8 +- odoo/addons/base/res/res_company.py | 3 +- odoo/addons/base/res/res_config.py | 14 +- odoo/addons/base/res/res_lang.py | 7 +- odoo/addons/base/res/res_partner.py | 2 +- odoo/addons/base/res/res_users.py | 34 +++-- odoo/addons/base/tests/test_api.py | 8 +- odoo/addons/base/tests/test_expression.py | 6 +- odoo/addons/base/tests/test_float.py | 6 +- odoo/addons/base/tests/test_ir_filters.py | 27 ++-- odoo/addons/base/tests/test_orm.py | 8 +- odoo/addons/base/tests/test_qweb.py | 3 +- odoo/addons/base/tests/test_search.py | 5 +- odoo/addons/base/tests/test_views.py | 2 +- odoo/addons/test_impex/tests/test_export.py | 5 +- odoo/addons/test_limits/models.py | 3 +- odoo/addons/test_new_api/models.py | 5 +- odoo/addons/test_new_api/tests/test_one2many.py | 2 +- odoo/addons/test_pylint/tests/test_pylint.py | 39 +++-- odoo/api.py | 15 +- odoo/cli/command.py | 2 +- odoo/fields.py | 20 +-- odoo/http.py | 22 ++- odoo/models.py | 163 ++++++++++----------- odoo/modules/db.py | 2 +- odoo/modules/graph.py | 19 ++- odoo/modules/loading.py | 11 +- odoo/modules/migration.py | 12 +- odoo/modules/module.py | 13 +- odoo/modules/registry.py | 19 +-- odoo/osv/expression.py | 4 +- odoo/osv/orm.py | 5 +- odoo/release.py | 4 +- odoo/service/db.py | 2 +- odoo/service/model.py | 2 +- odoo/service/server.py | 16 +- odoo/sql_db.py | 10 +- odoo/tests/common.py | 2 +- odoo/tools/amount_to_text_en.py | 2 +- odoo/tools/appdirs.py | 2 +- odoo/tools/cache.py | 10 +- odoo/tools/config.py | 17 ++- odoo/tools/convert.py | 10 +- odoo/tools/float_utils.py | 6 +- odoo/tools/func.py | 2 +- odoo/tools/graph.py | 19 ++- odoo/tools/lru.py | 7 +- odoo/tools/mail.py | 7 +- odoo/tools/mimetypes.py | 4 +- odoo/tools/misc.py | 26 ++-- odoo/tools/osutil.py | 2 +- odoo/tools/parse_version.py | 4 +- odoo/tools/pycompat.py | 11 +- odoo/tools/safe_eval.py | 3 +- odoo/tools/test_reports.py | 2 +- odoo/tools/translate.py | 10 +- odoo/tools/yaml_import.py | 36 ++--- odoo/tools/yaml_tag.py | 5 +- 74 files changed, 521 insertions(+), 445 deletions(-) (limited to 'odoo') diff --git a/odoo/addons/base/ir/ir_actions.py b/odoo/addons/base/ir/ir_actions.py index 918e5d90880..4979c898e08 100644 --- a/odoo/addons/base/ir/ir_actions.py +++ b/odoo/addons/base/ir/ir_actions.py @@ -5,6 +5,7 @@ import odoo from odoo import api, fields, models, tools, SUPERUSER_ID, _ from odoo.exceptions import MissingError, UserError, ValidationError, AccessError from odoo.tools.safe_eval import safe_eval, test_python_expr +from odoo.tools import pycompat from odoo.http import request import datetime @@ -649,17 +650,19 @@ class IrActionsTodo(models.Model): """ user_groups = self.env.user.groups_id - def groups_match(todo): - """ Checks if the todo's groups match those of the current user """ - return not todo.groups_id or bool(todo.groups_id & user_groups) - - done = filter(groups_match, self.browse(self.search([('state', '!=', 'open')]))) - total = filter(groups_match, self.browse(self.search([]))) - + done_count = self.search_count([ + ('state', '!=', open), + '|', ('groups_id', '=', False), + ('groups_id', 'in', user_groups.ids), + ]) + total_count = self.search_count([ + '|', ('groups_id', '=', False), + ('groups_id', 'in', user_groups.ids), + ]) return { - 'done': len(done), - 'total': len(total), - 'todo': len(total) - len(done), + 'done': done_count, + 'total': total_count, + 'todo': total_count - done_count, } @@ -688,7 +691,7 @@ class IrActionsActClient(models.Model): @api.depends('params_store') def _compute_params(self): self_bin = self.with_context(bin_size=False, bin_size_params_store=False) - for record, record_bin in zip(self, self_bin): + for record, record_bin in pycompat.izip(self, self_bin): record.params = record_bin.params_store and safe_eval(record_bin.params_store, {'uid': self._uid}) def _inverse_params(self): diff --git a/odoo/addons/base/ir/ir_attachment.py b/odoo/addons/base/ir/ir_attachment.py index b6bd9a08ee7..9d19d076438 100644 --- a/odoo/addons/base/ir/ir_attachment.py +++ b/odoo/addons/base/ir/ir_attachment.py @@ -11,7 +11,7 @@ from collections import defaultdict from odoo import api, fields, models, tools, SUPERUSER_ID, _ from odoo.exceptions import AccessError -from odoo.tools import config, human_size, ustr, html_escape +from odoo.tools import config, human_size, ustr, html_escape, pycompat from odoo.tools.mimetypes import guess_mimetype _logger = logging.getLogger(__name__) @@ -167,7 +167,7 @@ class IrAttachment(models.Model): # remove garbage files, and clean up checklist removed = 0 - for fname, filepath in checklist.iteritems(): + for fname, filepath in pycompat.items(checklist): if fname not in whitelist: try: os.unlink(self._full_path(fname)) @@ -318,7 +318,7 @@ class IrAttachment(models.Model): model_ids[values['res_model']].add(values['res_id']) # check access rights on the records - for res_model, res_ids in model_ids.iteritems(): + for res_model, res_ids in pycompat.items(model_ids): # ignore attachments that are not attached to a resource anymore # when checking access rights (resource was deleted but attachment # was not) @@ -374,12 +374,12 @@ class IrAttachment(models.Model): # To avoid multiple queries for each attachment found, checks are # performed in batch as much as possible. - for res_model, targets in model_attachments.iteritems(): + for res_model, targets in pycompat.items(model_attachments): if res_model not in self.env: continue if not self.env[res_model].check_access_rights('read', False): # remove all corresponding attachment ids - ids.difference_update(itertools.chain(*targets.itervalues())) + ids.difference_update(itertools.chain(*pycompat.values(targets))) continue # filter ids according to what access rules permit target_ids = list(targets) diff --git a/odoo/addons/base/ir/ir_config_parameter.py b/odoo/addons/base/ir/ir_config_parameter.py index cbb44ca6457..8e4ea20b5a5 100644 --- a/odoo/addons/base/ir/ir_config_parameter.py +++ b/odoo/addons/base/ir/ir_config_parameter.py @@ -8,7 +8,7 @@ import uuid import logging from odoo import api, fields, models -from odoo.tools import config, ormcache, mute_logger +from odoo.tools import config, ormcache, mute_logger, pycompat _logger = logging.getLogger(__name__) @@ -42,7 +42,7 @@ class IrConfigParameter(models.Model): Initializes the parameters listed in _default_parameters. It overrides existing parameters if force is ``True``. """ - for key, func in _default_parameters.iteritems(): + for key, func in pycompat.items(_default_parameters): # force=True skips search and always performs the 'if' body (because ids=False) params = self.sudo().search([('key', '=', key)]) if force or not params: diff --git a/odoo/addons/base/ir/ir_fields.py b/odoo/addons/base/ir/ir_fields.py index 26c351d6fa8..204d4599425 100644 --- a/odoo/addons/base/ir/ir_fields.py +++ b/odoo/addons/base/ir/ir_fields.py @@ -8,13 +8,13 @@ import psycopg2 import pytz from odoo import api, fields, models, _ -from odoo.tools import ustr +from odoo.tools import ustr, pycompat REFERENCING_FIELDS = {None, 'id', '.id'} def only_ref_fields(record): - return {k: v for k, v in record.iteritems() if k in REFERENCING_FIELDS} + return {k: v for k, v in pycompat.items(record) if k in REFERENCING_FIELDS} def exclude_ref_fields(record): - return {k: v for k, v in record.iteritems() if k not in REFERENCING_FIELDS} + return {k: v for k, v in pycompat.items(record) if k not in REFERENCING_FIELDS} CREATE = lambda values: (0, False, values) UPDATE = lambda id, values: (1, id, values) @@ -43,9 +43,9 @@ class IrFieldsConverter(models.AbstractModel): if isinstance(error_params, basestring): error_params = sanitize(error_params) elif isinstance(error_params, dict): - error_params = {k: sanitize(v) for k, v in error_params.iteritems()} + error_params = {k: sanitize(v) for k, v in pycompat.items(error_params)} elif isinstance(error_params, tuple): - error_params = tuple(map(sanitize, error_params)) + error_params = tuple(sanitize(v) for v in error_params) return error_type(error_msg % error_params, error_args) @api.model @@ -64,12 +64,12 @@ class IrFieldsConverter(models.AbstractModel): converters = { name: self.to_field(model, field, fromtype) - for name, field in model._fields.iteritems() + for name, field in pycompat.items(model._fields) } def fn(record, log): converted = {} - for field, value in record.iteritems(): + for field, value in pycompat.items(record): if field in REFERENCING_FIELDS: continue if not value: @@ -369,7 +369,7 @@ class IrFieldsConverter(models.AbstractModel): :rtype: str, list """ # Can import by name_get, external id or database id - fieldset = set(record.iterkeys()) + fieldset = set(record) if fieldset - REFERENCING_FIELDS: raise ValueError( _(u"Can not create Many-To-One records indirectly, import the field separately")) diff --git a/odoo/addons/base/ir/ir_http.py b/odoo/addons/base/ir/ir_http.py index 3d9c72aec60..aa23531055e 100644 --- a/odoo/addons/base/ir/ir_http.py +++ b/odoo/addons/base/ir/ir_http.py @@ -21,6 +21,7 @@ import odoo from odoo import api, http, models, tools, SUPERUSER_ID from odoo.exceptions import AccessDenied, AccessError from odoo.http import request, STATIC_CACHE, content_disposition +from odoo.tools import pycompat from odoo.tools.mimetypes import guess_mimetype from odoo.modules.module import get_resource_path, get_module_path @@ -54,7 +55,7 @@ class ModelsConverter(werkzeug.routing.BaseConverter): def to_python(self, value): env = api.Environment(request.cr, UID_PLACEHOLDER, request.context) - return env[self.model].browse(map(int, value.split(','))) + return env[self.model].browse(int(v) for v in value.split(',')) def to_url(self, value): return ",".join(value.ids) @@ -202,7 +203,7 @@ class IrHttp(models.AbstractModel): @classmethod def _postprocess_args(cls, arguments, rule): """ post process arg to set uid on browse records """ - for name, arg in arguments.items(): + for name, arg in list(pycompat.items(arguments)): if isinstance(arg, models.BaseModel) and arg._uid is UID_PLACEHOLDER: arguments[name] = arg.sudo(request.uid) if not arg.exists(): diff --git a/odoo/addons/base/ir/ir_mail_server.py b/odoo/addons/base/ir/ir_mail_server.py index 22f78da6c18..737e9806d68 100644 --- a/odoo/addons/base/ir/ir_mail_server.py +++ b/odoo/addons/base/ir/ir_mail_server.py @@ -13,9 +13,11 @@ import re import smtplib import threading +import itertools + from odoo import api, fields, models, tools, _ from odoo.exceptions import except_orm, UserError -from odoo.tools import html2text, ustr +from odoo.tools import html2text, ustr, pycompat _logger = logging.getLogger(__name__) _test_logger = logging.getLogger('odoo.tests') @@ -110,7 +112,7 @@ def extract_rfc2822_addresses(text): if not text: return [] candidates = address_pattern.findall(ustr(text).encode('utf-8')) - return filter(try_coerce_ascii, candidates) + return [c for c in candidates if try_coerce_ascii(c)] def encode_rfc2822_address_header(header_text): @@ -127,7 +129,7 @@ def encode_rfc2822_address_header(header_text): return formataddr((name, email)) addresses = getaddresses([ustr(header_text).encode('utf-8')]) - return COMMASPACE.join(map(encode_addr, addresses)) + return COMMASPACE.join(encode_addr(a) for a in addresses) class IrMailServer(models.Model): @@ -333,7 +335,7 @@ class IrMailServer(models.Model): msg['Bcc'] = encode_rfc2822_address_header(COMMASPACE.join(email_bcc)) msg['Date'] = formatdate() # Custom headers may override normal headers or provide additional ones - for key, value in headers.iteritems(): + for key, value in pycompat.items(headers): msg[ustr(key).encode('utf-8')] = encode_header(value) if subtype == 'html' and not body_alternative and html2text: @@ -442,7 +444,12 @@ class IrMailServer(models.Model): email_cc = message['Cc'] email_bcc = message['Bcc'] - smtp_to_list = filter(None, tools.flatten(map(extract_rfc2822_addresses, [email_to, email_cc, email_bcc]))) + smtp_to_list = [ + address + for base in [email_to, email_cc, email_bcc] + for address in extract_rfc2822_addresses(base) + if address + ] assert smtp_to_list, self.NO_VALID_RECIPIENT x_forge_to = message['X-Forge-To'] diff --git a/odoo/addons/base/ir/ir_model.py b/odoo/addons/base/ir/ir_model.py index ad72694d410..9272a790fbc 100644 --- a/odoo/addons/base/ir/ir_model.py +++ b/odoo/addons/base/ir/ir_model.py @@ -45,7 +45,7 @@ def query_insert(cr, table, values): query = INSERT_QUERY.format( table=table, cols=",".join(values), - vals=",".join(map("%({0})s".format, values)), + vals=",".join("%({0})s".format(v) for v in values), ) cr.execute(query, values) @@ -53,8 +53,8 @@ def query_update(cr, table, values, selectors): setters = set(values) - set(selectors) query = UPDATE_QUERY.format( table=table, - assignment=",".join(map("{0}=%({0})s".format, setters)), - condition=" AND ".join(map("{0}=%({0})s".format, selectors)), + assignment=",".join("{0}=%({0})s".format(s) for s in setters), + condition=" AND ".join("{0}=%({0})s".format(s) for s in selectors), ) cr.execute(query, values) @@ -188,7 +188,7 @@ class IrModel(models.Model): @api.multi def write(self, vals): if '__last_update' in self._context: - self = self.with_context({k: v for k, v in self._context.iteritems() if k != '__last_update'}) + self = self.with_context({k: v for k, v in pycompat.items(self._context) if k != '__last_update'}) if 'model' in vals and any(rec.model != vals['model'] for rec in self): raise UserError(_('Field "Model" cannot be modified on models.')) if 'state' in vals and any(rec.state != vals['state'] for rec in self): @@ -559,8 +559,8 @@ class IrModelFields(models.Model): except Exception: raise UserError("\n".join([ _("Cannot rename/delete fields that are still present in views:"), - _("Fields:") + " " + ", ".join(map(str, fields)), - _("View:") + " " + view.name, + _("Fields: %s") % ", ".join(str(f) for f in fields), + _("View: %s") % view.name, ])) finally: # the registry has been modified, restore it @@ -760,7 +760,7 @@ class IrModelFields(models.Model): fields_data[field.name] = dict(params, id=record.id) return record - diff = {key for key, val in params.items() if field_data[key] != val} + diff = {key for key, val in pycompat.items(params) if field_data[key] != val} if diff: cr = self.env.cr # update the entry in this table @@ -778,7 +778,7 @@ class IrModelFields(models.Model): def _reflect_model(self, model): """ Reflect the given model's fields. """ self.clear_caches() - for field in model._fields.itervalues(): + for field in pycompat.values(model._fields): self._reflect_field(field) if not self.pool._init: @@ -1228,7 +1228,7 @@ class IrModelData(models.Model): @api.depends('module', 'name') def _compute_complete_name(self): for res in self: - res.complete_name = ".".join(filter(None, [res.module, res.name])) + res.complete_name = ".".join(n for n in [res.module, res.name] if n) @api.depends('model', 'res_id') def _compute_reference(self): @@ -1259,7 +1259,7 @@ class IrModelData(models.Model): model_id_name[xid.model][xid.res_id] = None # fill in model_id_name with name_get() of corresponding records - for model, id_name in model_id_name.iteritems(): + for model, id_name in pycompat.items(model_id_name): try: ng = self.env[model].browse(id_name).name_get() id_name.update(ng) @@ -1356,7 +1356,7 @@ class IrModelData(models.Model): record = self.get_object(module, xml_id) if record: self.loads[(module, xml_id)] = (model, record.id) - for parent_model, parent_field in self.env[model]._inherits.iteritems(): + for parent_model, parent_field in pycompat.items(self.env[model]._inherits): parent = record[parent_field] parent_xid = '%s_%s' % (xml_id, parent_model.replace('.', '_')) self.loads[(module, parent_xid)] = (parent_model, parent.id) @@ -1411,7 +1411,7 @@ class IrModelData(models.Model): elif record: record.write(values) if xml_id: - for parent_model, parent_field in record._inherits.iteritems(): + for parent_model, parent_field in pycompat.items(record._inherits): self.sudo().create({ 'name': xml_id + '_' + parent_model.replace('.', '_'), 'model': parent_model, @@ -1430,7 +1430,7 @@ class IrModelData(models.Model): elif mode == 'init' or (mode == 'update' and xml_id): existing_parents = set() # {parent_model, ...} if xml_id: - for parent_model, parent_field in record._inherits.iteritems(): + for parent_model, parent_field in pycompat.items(record._inherits): xid = self.sudo().search([ ('module', '=', module), ('name', '=', xml_id + '_' + parent_model.replace('.', '_')), @@ -1450,7 +1450,7 @@ class IrModelData(models.Model): inherit_models = [record] while inherit_models: current_model = inherit_models.pop() - for parent_model_name, parent_field in current_model._inherits.iteritems(): + for parent_model_name, parent_field in pycompat.items(current_model._inherits): inherit_models.append(self.env[parent_model_name]) if parent_model_name in existing_parents: continue @@ -1476,7 +1476,7 @@ class IrModelData(models.Model): if xml_id and record: self.loads[(module, xml_id)] = (model, record.id) - for parent_model, parent_field in record._inherits.iteritems(): + for parent_model, parent_field in pycompat.items(record._inherits): parent_xml_id = xml_id + '_' + parent_model.replace('.', '_') self.loads[(module, parent_xml_id)] = (parent_model, record[parent_field].id) diff --git a/odoo/addons/base/ir/ir_qweb/assetsbundle.py b/odoo/addons/base/ir/ir_qweb/assetsbundle.py index 5bc2dec8a9b..19902e78d1b 100644 --- a/odoo/addons/base/ir/ir_qweb/assetsbundle.py +++ b/odoo/addons/base/ir/ir_qweb/assetsbundle.py @@ -13,7 +13,7 @@ from odoo.http import request from odoo.modules.module import get_resource_path import psycopg2 import werkzeug -from odoo.tools import func, misc +from odoo.tools import func, misc, pycompat import logging _logger = logging.getLogger(__name__) @@ -308,7 +308,7 @@ class AssetsBundle(object): outdated = False assets = dict((asset.html_url, asset) for asset in self.stylesheets if isinstance(asset, atype)) if assets: - assets_domain = [('url', 'in', assets.keys())] + assets_domain = [('url', 'in', list(assets))] attachments = self.env['ir.attachment'].sudo().search(assets_domain) for attachment in attachments: asset = assets[attachment.url] @@ -320,7 +320,7 @@ class AssetsBundle(object): if not asset._content and attachment.file_size > 0: asset._content = None # file missing, force recompile - if any(asset._content is None for asset in assets.itervalues()): + if any(asset._content is None for asset in pycompat.values(assets)): outdated = True if outdated: @@ -452,7 +452,7 @@ class WebAsset(object): def stat(self): if not (self.inline or self._filename or self._ir_attach): - path = filter(None, self.url.split('/')) + path = (segment for segment in self.url.split('/') if segment) self._filename = get_resource_path(*path) if self._filename: return diff --git a/odoo/addons/base/ir/ir_qweb/ir_qweb.py b/odoo/addons/base/ir/ir_qweb/ir_qweb.py index 68c4bf048d7..5548fcd47d8 100644 --- a/odoo/addons/base/ir/ir_qweb/ir_qweb.py +++ b/odoo/addons/base/ir/ir_qweb/ir_qweb.py @@ -75,7 +75,7 @@ class IrQWeb(models.AbstractModel, QWeb): # apply ormcache_context decorator unless in dev mode... @tools.conditional( 'xml' not in tools.config['dev_mode'], - tools.ormcache('id_or_xml_id', 'tuple(map(options.get, self._get_template_cache_keys()))'), + tools.ormcache('id_or_xml_id', 'tuple(options.get(k) for k in self._get_template_cache_keys())'), ) def compile(self, id_or_xml_id, options): return super(IrQWeb, self).compile(id_or_xml_id, options=options) @@ -173,7 +173,7 @@ class IrQWeb(models.AbstractModel, QWeb): if field_options and 'monetary' in field_options: try: options = "{'widget': 'monetary'" - for k, v in json.loads(field_options).iteritems(): + for k, v in pycompat.items(json.loads(field_options)): if k in ('display_currency', 'from_currency'): options = "%s, '%s': %s" % (options, k, v) else: @@ -238,12 +238,12 @@ class IrQWeb(models.AbstractModel, QWeb): atype = 'text/less' if atype not in ('text/less', 'text/sass'): atype = 'text/css' - path = filter(None, href.split('/')) + path = [segment for segment in href.split('/') if segment] filename = get_resource_path(*path) if path else None files.append({'atype': atype, 'url': href, 'filename': filename, 'content': el.text, 'media': media}) elif el.tag == 'script': atype = 'text/javascript' - path = filter(None, src.split('/')) + path = [segment for segment in src.split('/') if segment] filename = get_resource_path(*path) if path else None files.append({'atype': atype, 'url': src, 'filename': filename, 'content': el.text, 'media': media}) else: diff --git a/odoo/addons/base/ir/ir_qweb/qweb.py b/odoo/addons/base/ir/ir_qweb/qweb.py index 14a0a54fbd9..06aba8ccdb4 100644 --- a/odoo/addons/base/ir/ir_qweb/qweb.py +++ b/odoo/addons/base/ir/ir_qweb/qweb.py @@ -6,14 +6,15 @@ import traceback from collections import OrderedDict, Sized, Mapping, defaultdict from functools import reduce -from itertools import chain, izip, tee, count +from itertools import tee, count from textwrap import dedent +import itertools from lxml import etree, html import werkzeug from werkzeug.utils import escape as _escape -from odoo.tools import pycompat +from odoo.tools import pycompat, freehash try: import builtins @@ -82,7 +83,7 @@ class Contextifier(ast.NodeTransformer): return ast.copy_location(ast.Lambda( args=ast.arguments( args=args.args, - defaults=map(self.visit, args.defaults), + defaults=[self.visit(default) for default in args.defaults], vararg=args.vararg, kwarg=args.kwarg, ), @@ -109,7 +110,7 @@ class Contextifier(ast.NodeTransformer): for field, value in ast.iter_fields(node): # map transformation of comprehensions if isinstance(value, list): - setattr(newnode, field, map(transformer.visit, value)) + setattr(newnode, field, [transformer.visit(v) for v in value]) else: # set transformation of key/value/expr fields setattr(newnode, field, transformer.visit(value)) return newnode @@ -168,14 +169,14 @@ def foreach_iterator(base_ctx, enum, name): if not enum: return if isinstance(enum, int): - enum = pycompat.range(enum) + enum = range(enum) size = None if isinstance(enum, Sized): ctx["%s_size" % name] = size = len(enum) if isinstance(enum, Mapping): - enum = enum.iteritems() + enum = pycompat.items(enum) else: - enum = izip(*tee(enum)) + enum = pycompat.izip(*tee(enum)) value_key = '%s_value' % name index_key = '%s_index' % name first_key = '%s_first' % name @@ -201,7 +202,7 @@ def foreach_iterator(base_ctx, enum, name): yield ctx # copy changed items back into source context (?) # FIXME: maybe values could provide a ChainMap-style clone? - for k in base_ctx.keys(): + for k in list(base_ctx): base_ctx[k] = ctx[k] _FORMAT_REGEX = re.compile( @@ -226,7 +227,7 @@ class frozendict(dict): def update(self, *args, **kwargs): raise NotImplementedError("'update' not supported on frozendict") def __hash__(self): - return hash(frozenset((key, freehash(val)) for key, val in self.iteritems())) + return hash(frozenset((key, freehash(val)) for key, val in pycompat.items(self))) #################################### @@ -510,6 +511,7 @@ class QWeb(object): """ return ast.parse(dedent(""" from collections import OrderedDict + from odoo.tools import pycompat from odoo.addons.base.ir.ir_qweb.qweb import escape, unicodifier, foreach_iterator """)) @@ -665,7 +667,7 @@ class QWeb(object): # all directives have been compiled, there should be none left if any(att.startswith('t-') for att in el.attrib): - raise "Unknown directive on %s" % ("', '".join(directives), etree.tostring(el)) + raise NameError("Unknown directive on %s" % etree.tostring(el)) return [] def _values_var(self, varname, ctx): @@ -726,7 +728,7 @@ class QWeb(object): attrib = {} # If `el` introduced new namespaces, write them as attribute by using the # `attrib` dict. - for ns_prefix, ns_definition in set(el.nsmap.items()) - set(options['nsmap'].items()): + for ns_prefix, ns_definition in set(pycompat.items(el.nsmap)) - set(pycompat.items(options['nsmap'])): if ns_prefix is None: attrib['xmlns'] = ns_definition else: @@ -735,8 +737,9 @@ class QWeb(object): # Etree will also remove the ns prefixes indirection in the attributes. As we only have # the namespace definition, we'll use an nsmap where the keys are the definitions and # the values the prefixes in order to get back the right prefix and restore it. - nsprefixmap = {v: k for k, v in options['nsmap'].items() + el.nsmap.items()} - for key, value in el.attrib.items(): + ns = itertools.chain(pycompat.items(options['nsmap']), pycompat.items(el.nsmap)) + nsprefixmap = {v: k for k, v in ns} + for key, value in pycompat.items(el.attrib): attrib_qname = etree.QName(key) if attrib_qname.namespace: attrib['%s:%s' % (nsprefixmap[attrib_qname.namespace], attrib_qname.localname)] = value @@ -754,7 +757,7 @@ class QWeb(object): if unqualified_el_tag == 't': return content - tag = u'<%s%s' % (el_tag, u''.join([u' %s="%s"' % (name, escape(unicodifier(value))) for name, value in attrib.iteritems()])) + tag = u'<%s%s' % (el_tag, u''.join([u' %s="%s"' % (name, escape(unicodifier(value))) for name, value in pycompat.items(attrib)])) if unqualified_el_tag in self._void_elements: return [self._append(ast.Str(tag + '/>'))] + content else: @@ -766,10 +769,10 @@ class QWeb(object): # Etree will also remove the ns prefixes indirection in the attributes. As we only have # the namespace definition, we'll use an nsmap where the keys are the definitions and # the values the prefixes in order to get back the right prefix and restore it. - nsprefixmap = {v: k for k, v in options['nsmap'].items() + el.nsmap.items()} + nsprefixmap = {v: k for k, v in itertools.chain(pycompat.items(options['nsmap']), pycompat.items(el.nsmap))} nodes = [] - for key, value in el.attrib.iteritems(): + for key, value in pycompat.items(el.attrib): if not key.startswith('t-'): attrib_qname = etree.QName(key) if attrib_qname.namespace: @@ -784,7 +787,7 @@ class QWeb(object): We do not support namespaced dynamic attributes. """ nodes = [] - for name, value in el.attrib.iteritems(): + for name, value in pycompat.items(el.attrib): if name.startswith('t-attf-'): nodes.append((name[7:], self._compile_format(value))) elif name.startswith('t-att-'): @@ -810,7 +813,7 @@ class QWeb(object): def _compile_all_attributes(self, el, options, attr_already_created=False): """ Compile the attributes of the given elements into a list of AST nodes. """ body = [] - if any(name.startswith('t-att') or not name.startswith('t-') for name, value in el.attrib.iteritems()): + if any(name.startswith('t-att') or not name.startswith('t-') for name, value in pycompat.items(el.attrib)): if not attr_already_created: attr_already_created = True body.append( @@ -851,7 +854,7 @@ class QWeb(object): ))) if attr_already_created: - # for name, value in t_attrs.iteritems(): + # for name, value in pycompat.items(t_attrs): # if value or isinstance(value, basestring)): # append(u' ') # append(name) @@ -862,11 +865,11 @@ class QWeb(object): target=ast.Tuple(elts=[ast.Name(id='name', ctx=ast.Store()), ast.Name(id='value', ctx=ast.Store())], ctx=ast.Store()), iter=ast.Call( func=ast.Attribute( - value=ast.Name(id='t_attrs', ctx=ast.Load()), - attr='iteritems', + value=ast.Name(id='pycompat', ctx=ast.Load()), + attr='items', ctx=ast.Load() ), - args=[], keywords=[], + args=[ast.Name(id='t_attrs', ctx=ast.Load())], keywords=[], starargs=None, kwargs=None ), body=[ast.If( @@ -923,7 +926,7 @@ class QWeb(object): # If `el` introduced new namespaces, write them as attribute by using the # `extra_attrib` dict. - for ns_prefix, ns_definition in set(el.nsmap.items()) - set(options['nsmap'].items()): + for ns_prefix, ns_definition in set(pycompat.items(el.nsmap)) - set(pycompat.items(options['nsmap'])): if ns_prefix is None: extra_attrib['xmlns'] = ns_definition else: @@ -932,7 +935,7 @@ class QWeb(object): if unqualified_el_tag == 't': return content - body = [self._append(ast.Str(u'<%s%s' % (el_tag, u''.join([u' %s="%s"' % (name, escape(unicodifier(value))) for name, value in extra_attrib.iteritems()]))))] + body = [self._append(ast.Str(u'<%s%s' % (el_tag, u''.join([u' %s="%s"' % (name, escape(unicodifier(value))) for name, value in pycompat.items(extra_attrib)]))))] body.extend(self._compile_all_attributes(el, options, attr_already_created)) if unqualified_el_tag in self._void_elements: body.append(self._append(ast.Str(u'/>'))) @@ -1054,7 +1057,7 @@ class QWeb(object): def _compile_directive_if(self, el, options): orelse = [] next_el = el.getnext() - if next_el is not None and {'t-else', 't-elif'} & set(next_el.attrib.keys()): + if next_el is not None and {'t-else', 't-elif'} & set(next_el.attrib): if el.tail and not el.tail.isspace(): raise ValueError("Unexpected non-whitespace characters between t-if and t-else directives") el.tail = None @@ -1437,7 +1440,7 @@ class QWeb(object): # make the nsmap an ast dict keys = [] values = [] - for key, value in options['nsmap'].items(): + for key, value in pycompat.items(options['nsmap']): if isinstance(key, basestring): keys.append(ast.Str(s=key)) elif key is None: diff --git a/odoo/addons/base/ir/ir_sequence.py b/odoo/addons/base/ir/ir_sequence.py index a46f5e95eca..58961e921ba 100644 --- a/odoo/addons/base/ir/ir_sequence.py +++ b/odoo/addons/base/ir/ir_sequence.py @@ -6,6 +6,7 @@ import pytz from odoo import api, fields, models, _ from odoo.exceptions import UserError +from odoo.tools import pycompat _logger = logging.getLogger(__name__) @@ -178,7 +179,7 @@ class IrSequence(models.Model): 'weekday': '%w', 'h24': '%H', 'h12': '%I', 'min': '%M', 'sec': '%S' } res = {} - for key, format in sequences.iteritems(): + for key, format in pycompat.items(sequences): res[key] = effective_date.strftime(format) res['range_' + key] = range_date.strftime(format) res['current_' + key] = now.strftime(format) diff --git a/odoo/addons/base/ir/ir_translation.py b/odoo/addons/base/ir/ir_translation.py index 7d73f40d65b..219ebcc471d 100644 --- a/odoo/addons/base/ir/ir_translation.py +++ b/odoo/addons/base/ir/ir_translation.py @@ -128,7 +128,7 @@ class IrTranslationImport(object): env = api.Environment(cr, SUPERUSER_ID, {}) src_relevant_fields = [] for model in env: - for field_name, field in env[model]._fields.items(): + for field_name, field in pycompat.items(env[model]._fields): if hasattr(field, 'translate') and callable(field.translate): src_relevant_fields.append("%s,%s" % (model, field_name)) @@ -530,7 +530,7 @@ class IrTranslation(models.Model): # check for read/write access on translated field records fmode = 'read' if mode == 'read' else 'write' - for mname, ids in model_ids.iteritems(): + for mname, ids in pycompat.items(model_ids): records = self.env[mname].browse(ids) records.check_access_rights(fmode) records.check_field_access_rights(fmode, model_fields[mname]) @@ -640,7 +640,7 @@ class IrTranslation(models.Model): return ['&', ('res_id', '=', rec.id), ('name', '=', name)] # insert missing translations, and extend domain for related fields - for name, fld in record._fields.items(): + for name, fld in pycompat.items(record._fields): if not fld.translate: continue diff --git a/odoo/addons/base/ir/ir_ui_view.py b/odoo/addons/base/ir/ir_ui_view.py index c557d15ee6b..0a709985ff6 100644 --- a/odoo/addons/base/ir/ir_ui_view.py +++ b/odoo/addons/base/ir/ir_ui_view.py @@ -8,6 +8,8 @@ import logging import os import re import time + +import itertools from dateutil.relativedelta import relativedelta from operator import itemgetter @@ -49,7 +51,7 @@ def keep_query(*keep_params, **additional_params): if not keep_params and not additional_params: keep_params = ('*',) params = additional_params.copy() - qs_keys = request.httprequest.args.keys() + qs_keys = list(request.httprequest.args) for keep_param in keep_params: for param in fnmatch.filter(qs_keys, keep_param): if param not in additional_params and param in qs_keys: @@ -251,11 +253,11 @@ actual arch. @api.depends('arch') def _compute_arch_base(self): # 'arch_base' is the same as 'arch' without translation - for view, view_wo_lang in zip(self, self.with_context(lang=None)): + for view, view_wo_lang in pycompat.izip(self, self.with_context(lang=None)): view.arch_base = view_wo_lang.arch def _inverse_arch_base(self): - for view, view_wo_lang in zip(self, self.with_context(lang=None)): + for view, view_wo_lang in pycompat.izip(self, self.with_context(lang=None)): view_wo_lang.arch = view.arch_base @api.depends('write_date') @@ -458,7 +460,7 @@ actual arch. # not required. The root cause is the INNER JOIN # used to implement it. views = self.search(conditions + [('model_ids.module', 'in', tuple(self.pool._init_modules))]) - views = self.search(conditions + [('id', 'in', list(self._context.get('check_view_ids') or (0,)) + map(int, views))]) + views = self.search(conditions + [('id', 'in', list(self._context.get('check_view_ids') or (0,)) + views.ids)]) else: views = self.search(conditions) @@ -581,10 +583,16 @@ actual arch. separator = child.get('separator', ',') if separator == ' ': separator = None # squash spaces - to_add = filter(bool, map(str.strip, child.get('add', '').split(separator))) - to_remove = map(str.strip, child.get('remove', '').split(separator)) - values = map(str.strip, node.get(attribute, '').split(separator)) - value = (separator or ' ').join(filter(lambda s: s not in to_remove, values) + to_add) + to_add = ( + s for s in (s.strip() for s in child.get('add', '').split(separator)) + if s + ) + to_remove = {s.strip() for s in child.get('remove', '').split(separator)} + values = (s.strip() for s in node.get(attribute, '').split(separator)) + value = (separator or ' ').join(itertools.chain( + (v for v in values if v not in to_remove), + to_add + )) if value: node.set(attribute, value) elif attribute in node.attrib: @@ -834,7 +842,7 @@ actual arch. collect(arch, self.env[model_name]) - for field, nodes in field_nodes.iteritems(): + for field, nodes in pycompat.items(field_nodes): # if field should trigger an onchange, add on_change="1" on the # nodes referring to field model = self.env[field.model_name] @@ -900,7 +908,7 @@ actual arch. node.set(action, 'false') arch = etree.tostring(node, encoding="utf-8").replace('\t', '') - for k in fields.keys(): + for k in list(fields): if k not in fields_def: del fields[k] for field in fields_def: @@ -924,7 +932,7 @@ actual arch. @tools.conditional( 'xml' not in config['dev_mode'], tools.ormcache('frozenset(self.env.user.groups_id.ids)', 'view_id', - 'tuple(map(self._context.get, self._read_template_keys()))'), + 'tuple(self._context.get(k) for k in self._read_template_keys())'), ) def _read_template(self, view_id): arch = self.browse(view_id).read_combined(['arch'])['arch'] @@ -1091,12 +1099,12 @@ actual arch. Model = self.env[model] Node = self.env[node_obj] - for model_key, model_value in Model._fields.iteritems(): + for model_key, model_value in pycompat.items(Model._fields): if model_value.type == 'one2many': if model_value.comodel_name == node_obj: _Node_Field = model_key _Model_Field = model_value.inverse_name - for node_key, node_value in Node._fields.iteritems(): + for node_key, node_value in pycompat.items(Node._fields): if node_value.type == 'one2many': if node_value.comodel_name == conn_obj: # _Source_Field = "Incoming Arrows" (connected via des_node) @@ -1159,7 +1167,7 @@ actual arch. GROUP BY coalesce(v.inherit_id, v.id)""" self._cr.execute(query, [model]) - rec = self.browse(map(itemgetter(0), self._cr.fetchall())) + rec = self.browse(it[0] for it in self._cr.fetchall()) return rec.with_context({'load_all_views': True})._check_xml() @api.model @@ -1173,7 +1181,7 @@ actual arch. xmlid_filter = "AND md.name IN %s" names = tuple( name - for (xmod, name), (model, res_id) in self.pool.model_data_reference_ids.items() + for (xmod, name), (model, res_id) in pycompat.items(self.pool.model_data_reference_ids) if xmod == module and model == self._name ) if not names: diff --git a/odoo/addons/base/ir/ir_values.py b/odoo/addons/base/ir/ir_values.py index 3672d7811c2..ace2998b2b3 100644 --- a/odoo/addons/base/ir/ir_values.py +++ b/odoo/addons/base/ir/ir_values.py @@ -5,7 +5,7 @@ from ast import literal_eval from odoo import api, fields, models, tools, _ from odoo.exceptions import AccessError, MissingError, ValidationError -from odoo.tools import pickle +from odoo.tools import pickle, pycompat import logging _logger = logging.getLogger(__name__) @@ -315,7 +315,7 @@ class IrValues(models.Model): for row in self._cr.dictfetchall(): value = pickle.loads(row['value'].encode('utf-8')) defaults.setdefault(row['name'], (row['id'], row['name'], value)) - return defaults.values() + return list(pycompat.values(defaults)) # use ormcache: this is called a lot by BaseModel.default_get()! @api.model @@ -426,4 +426,4 @@ class IrValues(models.Model): results[name] = (id, name, action_def) except (AccessError, MissingError): continue - return sorted(results.values()) + return sorted(pycompat.values(results)) diff --git a/odoo/addons/base/module/module.py b/odoo/addons/base/module/module.py index ae36ccd1727..c8a83a01bf1 100644 --- a/odoo/addons/base/module/module.py +++ b/odoo/addons/base/module/module.py @@ -10,6 +10,8 @@ import shutil import tempfile import zipfile +from odoo.tools import pycompat + try: from urllib import parse as urlparse from urllib.request import urlopen @@ -209,9 +211,9 @@ class Module(models.Model): def format_view(v): return '%s%s (%s)' % (v.inherit_id and '* INHERIT ' or '', v.name, v.type) - module.views_by_module = "\n".join(sorted(map(format_view, browse('ir.ui.view')))) - module.reports_by_module = "\n".join(sorted(map(attrgetter('name'), browse('ir.actions.report')))) - module.menus_by_module = "\n".join(sorted(map(attrgetter('complete_name'), browse('ir.ui.menu')))) + module.views_by_module = "\n".join(sorted(format_view(v) for v in browse('ir.ui.view'))) + module.reports_by_module = "\n".join(sorted(r.name for r in browse('ir.actions.report'))) + module.menus_by_module = "\n".join(sorted(m.complete_name for m in browse('ir.ui.menu'))) @api.depends('icon') def _get_icon_image(self): @@ -668,7 +670,7 @@ class Module(models.Model): _logger.debug('Install from url: %r', urls) try: # 1. Download & unzip missing modules - for module_name, url in urls.iteritems(): + for module_name, url in pycompat.items(urls): if not url: continue # nothing to download, local version is already the last one @@ -687,7 +689,7 @@ class Module(models.Model): assert os.path.isdir(os.path.join(tmp, module_name)) # 2a. Copy/Replace module source in addons path - for module_name, url in urls.iteritems(): + for module_name, url in pycompat.items(urls): if module_name == OPENERP or not url: continue # OPENERP is special case, handled below, and no URL means local module module_path = modules.get_module_path(module_name, downloaded=True, display_warning=False) @@ -719,11 +721,11 @@ class Module(models.Model): self.update_list() - with_urls = [module_name for module_name, url in urls.iteritems() if url] + with_urls = [module_name for module_name, url in pycompat.items(urls) if url] downloaded = self.search([('name', 'in', with_urls)]) installed = self.search([('id', 'in', downloaded.ids), ('state', '=', 'installed')]) - to_install = self.search([('name', 'in', urls.keys()), ('state', '=', 'uninstalled')]) + to_install = self.search([('name', 'in', list(urls)), ('state', '=', 'uninstalled')]) post_install_action = to_install.button_immediate_install() if installed or to_install: diff --git a/odoo/addons/base/module/report/ir_module_reference_print.py b/odoo/addons/base/module/report/ir_module_reference_print.py index 1f2dff53243..a933cdd9430 100644 --- a/odoo/addons/base/module/report/ir_module_reference_print.py +++ b/odoo/addons/base/module/report/ir_module_reference_print.py @@ -2,6 +2,8 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import api, models +from odoo.tools import pycompat + class IrModelReferenceReport(models.AbstractModel): _name = 'report.base.report_irmodulereference' @@ -21,7 +23,7 @@ class IrModelReferenceReport(models.AbstractModel): if data: res_ids = data.mapped('res_id') fnames = self.env['ir.model.fields'].browse(res_ids).mapped('name') - return sorted(self.env[model].fields_get(fnames).iteritems()) + return sorted(pycompat.items(self.env[model].fields_get(fnames))) return [] @api.model diff --git a/odoo/addons/base/res/ir_property.py b/odoo/addons/base/res/ir_property.py index 8e1b7eda860..ec086fb6c61 100644 --- a/odoo/addons/base/res/ir_property.py +++ b/odoo/addons/base/res/ir_property.py @@ -217,7 +217,7 @@ class Property(models.Model): prop.write({'value': value}) # create new properties for records that do not have one yet - for ref, id in refs.iteritems(): + for ref, id in pycompat.items(refs): value = clean(values[id]) if value != default_value: self.create({ @@ -248,13 +248,13 @@ class Property(models.Model): elif operator in ('!=', '<=', '<', '>', '>='): value = makeref(value) elif operator in ('in', 'not in'): - value = map(makeref, value) + value = [makeref(v) for v in value] elif operator in ('=like', '=ilike', 'like', 'not like', 'ilike', 'not ilike'): # most probably inefficient... but correct target = self.env[comodel] target_names = target.name_search(value, operator=operator, limit=None) - target_ids = map(itemgetter(0), target_names) - operator, value = 'in', map(makeref, target_ids) + target_ids = [n[0] for n in target_names] + operator, value = 'in', [makeref(v) for v in target_ids] elif field.type in ('integer', 'float'): # No record is created in ir.property if the field's type is float or integer with a value # equal to 0. Then to match with the records that are linked to a property field equal to 0, diff --git a/odoo/addons/base/res/res_company.py b/odoo/addons/base/res/res_company.py index 96e922f5e77..9dfdba96cba 100644 --- a/odoo/addons/base/res/res_company.py +++ b/odoo/addons/base/res/res_company.py @@ -6,6 +6,7 @@ import re from odoo import api, fields, models, tools, _ from odoo.exceptions import ValidationError +from odoo.tools import pycompat class Company(models.Model): @@ -146,7 +147,7 @@ class Company(models.Model): if self.country_id: res['domain']['state_id'] = [('country_id', '=', self.country_id.id)] values = self.on_change_country(self.country_id.id)['value'] - for fname, value in values.iteritems(): + for fname, value in pycompat.items(values): setattr(self, fname, value) return res diff --git a/odoo/addons/base/res/res_config.py b/odoo/addons/base/res/res_config.py index 59764fca1e8..a19c6406dcb 100644 --- a/odoo/addons/base/res/res_config.py +++ b/odoo/addons/base/res/res_config.py @@ -9,7 +9,7 @@ from lxml import etree from odoo import api, models, registry, SUPERUSER_ID, _ from odoo.exceptions import AccessError, RedirectWarning, UserError -from odoo.tools import ustr +from odoo.tools import ustr, pycompat _logger = logging.getLogger(__name__) @@ -257,7 +257,7 @@ class ResConfigInstaller(models.TransientModel, ResConfigModuleInstallationMixin installer :rtype: [str] """ - return map(attrgetter('name'), self._already_installed()) + return [m.name for m in self._already_installed()] def _already_installed(self): """ For each module (boolean fields in a res.config.installer), @@ -267,7 +267,7 @@ class ResConfigInstaller(models.TransientModel, ResConfigModuleInstallationMixin :returns: a list of all installed modules in this installer :rtype: recordset (collection of Record) """ - selectable = [name for name, field in self._fields.iteritems() + selectable = [name for name, field in pycompat.items(self._fields) if field.type == 'boolean'] return self.env['ir.module.module'].search([('name', 'in', selectable), ('state', 'in', ['to install', 'installed', 'to upgrade'])]) @@ -292,7 +292,7 @@ class ResConfigInstaller(models.TransientModel, ResConfigModuleInstallationMixin """ base = set(module_name for installer in self.read() - for module_name, to_install in installer.iteritems() + for module_name, to_install in pycompat.items(installer) if self._fields[module_name].type == 'boolean' and to_install) hooks_results = set() @@ -302,7 +302,7 @@ class ResConfigInstaller(models.TransientModel, ResConfigModuleInstallationMixin hooks_results.update(hook() or set()) additionals = set(module - for requirements, consequences in self._install_if.iteritems() + for requirements, consequences in pycompat.items(self._install_if) if base.issuperset(requirements) for module in consequences) @@ -462,13 +462,13 @@ class ResConfigSettings(models.TransientModel, ResConfigModuleInstallationMixin) ref = self.env.ref defaults, groups, modules, others = [], [], [], [] - for name, field in self._fields.iteritems(): + for name, field in pycompat.items(self._fields): if name.startswith('default_') and hasattr(field, 'default_model'): defaults.append((name, field.default_model, name[8:])) elif name.startswith('group_') and field.type in ('boolean', 'selection') and \ hasattr(field, 'implied_group'): field_group_xmlids = getattr(field, 'group', 'base.group_user').split(',') - field_groups = Groups.concat(*map(ref, field_group_xmlids)) + field_groups = Groups.concat(*(ref(it) for it in field_group_xmlids)) groups.append((name, field_groups, ref(field.implied_group))) elif name.startswith('module_') and field.type in ('boolean', 'selection'): module = IrModule.sudo().search([('name', '=', name[7:])], limit=1) diff --git a/odoo/addons/base/res/res_lang.py b/odoo/addons/base/res/res_lang.py index 19a5b9be339..f4e2d7a1578 100644 --- a/odoo/addons/base/res/res_lang.py +++ b/odoo/addons/base/res/res_lang.py @@ -8,6 +8,7 @@ import re from operator import itemgetter from odoo import api, fields, models, tools, _ +from odoo.tools import pycompat from odoo.tools.safe_eval import safe_eval from odoo.exceptions import UserError, ValidationError @@ -22,7 +23,7 @@ class Lang(models.Model): _description = "Languages" _order = "active desc,name" - _disallowed_datetime_patterns = tools.DATETIME_FORMATS_MAP.keys() + _disallowed_datetime_patterns = list(tools.DATETIME_FORMATS_MAP) _disallowed_datetime_patterns.remove('%y') # this one is in fact allowed, just not good practice name = fields.Char(required=True) @@ -125,7 +126,7 @@ class Lang(models.Model): # For some locales, nl_langinfo returns a D_FMT/T_FMT that contains # unsupported '%-' patterns, e.g. for cs_CZ format = format.replace('%-', '%') - for pattern, replacement in tools.DATETIME_FORMATS_MAP.iteritems(): + for pattern, replacement in pycompat.items(tools.DATETIME_FORMATS_MAP): format = format.replace(pattern, replacement) return str(format) @@ -318,5 +319,5 @@ def intersperse(string, counts, separator=''): left, rest, right = intersperse_pat.match(string).groups() def reverse(s): return s[::-1] splits = split(reverse(rest), counts) - res = separator.join(map(reverse, reverse(splits))) + res = separator.join(reverse(s) for s in reverse(splits)) return left + res + right, len(splits) > 0 and len(splits) -1 or 0 diff --git a/odoo/addons/base/res/res_partner.py b/odoo/addons/base/res/res_partner.py index fe758d36de1..17e6507a01e 100644 --- a/odoo/addons/base/res/res_partner.py +++ b/odoo/addons/base/res/res_partner.py @@ -680,7 +680,7 @@ class Partner(models.Model): query += ' limit %s' where_clause_params.append(limit) self.env.cr.execute(query, where_clause_params) - partner_ids = map(lambda x: x[0], self.env.cr.fetchall()) + partner_ids = [row[0] for row in self.env.cr.fetchall()] if partner_ids: return self.browse(partner_ids).name_get() diff --git a/odoo/addons/base/res/res_users.py b/odoo/addons/base/res/res_users.py index ae8863eee4d..106fc32d56f 100644 --- a/odoo/addons/base/res/res_users.py +++ b/odoo/addons/base/res/res_users.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import pytz import datetime +import itertools import logging from collections import defaultdict @@ -13,7 +14,7 @@ from odoo import api, fields, models, tools, SUPERUSER_ID, _ from odoo.exceptions import AccessDenied, AccessError, UserError, ValidationError from odoo.osv import expression from odoo.service.db import check_super -from odoo.tools import partition +from odoo.tools import partition, pycompat _logger = logging.getLogger(__name__) @@ -29,7 +30,7 @@ def name_boolean_group(id): return 'in_group_' + str(id) def name_selection_groups(ids): - return 'sel_groups_' + '_'.join(map(str, ids)) + return 'sel_groups_' + '_'.join(str(it) for it in ids) def is_boolean_group(name): return name.startswith('in_group_') @@ -44,7 +45,7 @@ def get_boolean_group(name): return int(name[9:]) def get_selection_groups(name): - return map(int, name[11:].split('_')) + return [int(v) for v in name[11:].split('_')] def parse_m2m(commands): "return a list of ids corresponding to a many2many value" @@ -92,7 +93,7 @@ class Groups(models.Model): @api.depends('category_id.name', 'name') def _compute_full_name(self): # Important: value must be stored in environment of group, not group1! - for group, group1 in zip(self, self.sudo()): + for group, group1 in pycompat.izip(self, self.sudo()): if group1.category_id: group.full_name = '%s / %s' % (group1.category_id.name, group1.name) else: @@ -111,7 +112,7 @@ class Groups(models.Model): operand = [operand] where = [] for group in operand: - values = filter(bool, group.split('/')) + values = [v for v in group.split('/') if v] group_name = values.pop().strip() category_name = values and '/'.join(values).strip() or group_name group_domain = [('name', operator, lst and [group_name] or group_name)] @@ -299,13 +300,11 @@ class Users(models.Model): canwrite = self.env['ir.model.access'].check('res.users', 'write', False) if not canwrite: - def override_password(vals): - if (vals['id'] != self._uid): + for vals in result: + if vals['id'] != self._uid: for key in USER_PRIVATE_FIELDS: if key in vals: vals[key] = '********' - return vals - result = map(override_password, result) return result @@ -343,7 +342,7 @@ class Users(models.Model): raise UserError(_("You cannot deactivate the user you're currently logged in as.")) if self == self.env.user: - for key in values.keys(): + for key in list(values): if not (key in self.SELF_WRITEABLE_FIELDS or key.startswith('context_')): break else: @@ -624,7 +623,7 @@ class GroupsImplied(models.Model): if values.get('users') or values.get('implied_ids'): # add all implied groups (to all users of each group) for group in self: - vals = {'users': zip(repeat(4), group.with_context(active_test=False).users.ids)} + vals = {'users': list(pycompat.izip(repeat(4), group.with_context(active_test=False).users.ids))} super(GroupsImplied, group.trans_implied_ids).write(vals) return res @@ -766,7 +765,7 @@ class GroupsView(models.Model): # determine sequence order: a group appears after its implied groups order = {g: len(g.trans_implied_ids & gs) for g in gs} # check whether order is total, i.e., sequence orders are distinct - if len(set(order.itervalues())) == len(gs): + if len(set(pycompat.values(order))) == len(gs): return (app, 'selection', gs.sorted(key=order.get)) else: return (app, 'boolean', gs) @@ -780,7 +779,7 @@ class GroupsView(models.Model): others += g # build the result res = [] - for app, gs in sorted(by_app.iteritems(), key=lambda it: it[0].sequence or 0): + for app, gs in sorted(pycompat.items(by_app), key=lambda it: it[0].sequence or 0): res.append(linearize(app, gs)) if others: res.append((self.env['ir.module.category'], 'boolean', others)) @@ -820,7 +819,7 @@ class UsersView(models.Model): add, rem = [], [] values1 = {} - for key, val in values.iteritems(): + for key, val in pycompat.items(values): if is_boolean_group(key): (add if val else rem).append(get_boolean_group(key)) elif is_selection_groups(key): @@ -832,7 +831,10 @@ class UsersView(models.Model): if 'groups_id' not in values and (add or rem): # remove group ids in `rem` and add group ids in `add` - values1['groups_id'] = zip(repeat(3), rem) + zip(repeat(4), add) + values1['groups_id'] = list(itertools.chain( + pycompat.izip(repeat(3), rem), + pycompat.izip(repeat(4), add) + )) return values1 @@ -847,7 +849,7 @@ class UsersView(models.Model): @api.multi def read(self, fields=None, load='_classic_read'): # determine whether reified groups fields are required, and which ones - fields1 = fields or self.fields_get().keys() + fields1 = fields or list(self.fields_get()) group_fields, other_fields = partition(is_reified_group, fields1) # read regular fields (other_fields); add 'groups_id' if necessary diff --git a/odoo/addons/base/tests/test_api.py b/odoo/addons/base/tests/test_api.py index 8270d6086d5..55cf1ff26b5 100644 --- a/odoo/addons/base/tests/test_api.py +++ b/odoo/addons/base/tests/test_api.py @@ -79,11 +79,11 @@ class TestAPI(common.TransactionCase): domain = [('name', 'ilike', 'j')] partners = self.env['res.partner'].search(domain) self.assertTrue(partners) - ids = map(int, partners) + ids = partners.ids # modify those partners, and check that partners has not changed partners.write({'active': False}) - self.assertEqual(ids, map(int, partners)) + self.assertEqual(ids, partners.ids) # redo the search, and check that the result is now empty partners2 = self.env['res.partner'].search(domain) @@ -98,7 +98,7 @@ class TestAPI(common.TransactionCase): self.assertIsRecordset(user.groups_id, 'res.groups') partners = self.env['res.partner'].search([]) - for name, field in partners._fields.iteritems(): + for name, field in pycompat.items(partners._fields): if field.type == 'many2one': for p in partners: self.assertIsRecord(p[name], field.comodel_name) @@ -296,7 +296,7 @@ class TestAPI(common.TransactionCase): self.assertItemsEqual(partners.ids, country_id_cache) # partners' countries are ready for prefetching - country_ids = set(cid for cids in country_id_cache.itervalues() for cid in cids) + country_ids = set(cid for cids in pycompat.values(country_id_cache) for cid in cids) self.assertTrue(len(country_ids) > 1) self.assertItemsEqual(country_ids, partners._prefetch['res.country']) diff --git a/odoo/addons/base/tests/test_expression.py b/odoo/addons/base/tests/test_expression.py index 4dea4797b0c..87099e78754 100644 --- a/odoo/addons/base/tests/test_expression.py +++ b/odoo/addons/base/tests/test_expression.py @@ -5,7 +5,7 @@ import psycopg2 from odoo.models import BaseModel from odoo.tests.common import TransactionCase -from odoo.tools import mute_logger +from odoo.tools import mute_logger, pycompat import odoo.osv.expression as expression @@ -85,10 +85,10 @@ class TestExpression(TransactionCase): 'b ab': [cids['B'], cids['AB']], } pids = {} - for name, cat_ids in partners_config.iteritems(): + for name, cat_ids in pycompat.items(partners_config): pids[name] = partners.create({'name': name, 'category_id': [(6, 0, cat_ids)]}).id - base_domain = [('id', 'in', pids.values())] + base_domain = [('id', 'in', list(pycompat.values(pids)))] def test(op, value, expected): found_ids = partners.search(base_domain + [('category_id', op, value)]).ids diff --git a/odoo/addons/base/tests/test_float.py b/odoo/addons/base/tests/test_float.py index 8c4ee380bd2..725ba48365b 100644 --- a/odoo/addons/base/tests/test_float.py +++ b/odoo/addons/base/tests/test_float.py @@ -98,10 +98,10 @@ class TestFloatPrecision(TransactionCase): precisions = [2, 2, 2, 2, 2, 2, 3, 4] # Note: max precision for double floats is 53 bits of precision or # 17 significant decimal digits - for magnitude in pycompat.range(7): - for frac, exp, prec in zip(fractions, expecteds, precisions): + for magnitude in range(7): + for frac, exp, prec in pycompat.izip(fractions, expecteds, precisions): for sign in [-1,1]: - for x in pycompat.range(0, 10000, 97): + for x in range(0, 10000, 97): n = x * 10 ** magnitude f = sign * (n + frac) f_exp = ('-' if f != 0 and sign == -1 else '') + str(n) + exp diff --git a/odoo/addons/base/tests/test_ir_filters.py b/odoo/addons/base/tests/test_ir_filters.py index d932f16be11..b8de7590ad2 100644 --- a/odoo/addons/base/tests/test_ir_filters.py +++ b/odoo/addons/base/tests/test_ir_filters.py @@ -4,11 +4,12 @@ from odoo import exceptions from odoo.tests.common import TransactionCase, ADMIN_USER_ID -def noid(d): +def noid(seq): """ Removes values that are not relevant for the test comparisons """ - d.pop('id', None) - d.pop('action_id', None) - return d + for d in seq: + d.pop('id', None) + d.pop('action_id', None) + return seq class FiltersCase(TransactionCase): @@ -34,7 +35,7 @@ class TestGetFilters(FiltersCase): filters = self.env['ir.filters'].sudo(self.USER_ID).get_filters('ir.filters') - self.assertItemsEqual(map(noid, filters), [ + self.assertItemsEqual(noid(filters), [ dict(name='a', is_default=False, user_id=self.USER_NG, domain='[]', context='{}', sort='[]'), dict(name='b', is_default=False, user_id=self.USER_NG, domain='[]', context='{}', sort='[]'), dict(name='c', is_default=False, user_id=self.USER_NG, domain='[]', context='{}', sort='[]'), @@ -52,7 +53,7 @@ class TestGetFilters(FiltersCase): filters = self.env['ir.filters'].sudo(self.USER_ID).get_filters('ir.filters') - self.assertItemsEqual(map(noid, filters), [ + self.assertItemsEqual(noid(filters), [ dict(name='a', is_default=False, user_id=False, domain='[]', context='{}', sort='[]'), dict(name='b', is_default=False, user_id=False, domain='[]', context='{}', sort='[]'), dict(name='c', is_default=False, user_id=False, domain='[]', context='{}', sort='[]'), @@ -69,7 +70,7 @@ class TestGetFilters(FiltersCase): filters = self.env['ir.filters'].sudo(self.USER_ID).get_filters('ir.filters') - self.assertItemsEqual(map(noid, filters), [ + self.assertItemsEqual(noid(filters), [ dict(name='a', is_default=False, user_id=False, domain='[]', context='{}', sort='[]'), dict(name='c', is_default=False, user_id=self.USER_NG, domain='[]', context='{}', sort='[]'), ]) @@ -95,7 +96,7 @@ class TestOwnDefaults(FiltersCase): }) filters = Filters.get_filters('ir.filters') - self.assertItemsEqual(map(noid, filters), [ + self.assertItemsEqual(noid(filters), [ dict(name='a', user_id=self.USER_NG, is_default=True, domain='[]', context='{}', sort='[]') ]) @@ -120,7 +121,7 @@ class TestOwnDefaults(FiltersCase): }) filters = Filters.get_filters('ir.filters') - self.assertItemsEqual(map(noid, filters), [ + self.assertItemsEqual(noid(filters), [ dict(name='a', user_id=self.USER_NG, is_default=False, domain='[]', context='{}', sort='[]'), dict(name='b', user_id=self.USER_NG, is_default=False, domain='[]', context='{}', sort='[]'), dict(name='c', user_id=self.USER_NG, is_default=True, domain='[]', context='{}', sort='[]'), @@ -146,7 +147,7 @@ class TestOwnDefaults(FiltersCase): }) filters = Filters.get_filters('ir.filters') - self.assertItemsEqual(map(noid, filters), [ + self.assertItemsEqual(noid(filters), [ dict(name='a', user_id=self.USER_NG, is_default=False, domain='[]', context='{}', sort='[]'), dict(name='b', user_id=self.USER_NG, is_default=False, domain='[]', context='{}', sort='[]'), dict(name='c', user_id=self.USER_NG, is_default=True, domain='[]', context='{}', sort='[]'), @@ -172,7 +173,7 @@ class TestOwnDefaults(FiltersCase): }) filters = Filters.get_filters('ir.filters') - self.assertItemsEqual(map(noid, filters), [ + self.assertItemsEqual(noid(filters), [ dict(name='a', user_id=self.USER_NG, is_default=True, domain='[]', context='{}', sort='[]'), dict(name='b', user_id=self.USER_NG, is_default=False, domain='[]', context='{}', sort='[]'), ]) @@ -204,7 +205,7 @@ class TestGlobalDefaults(FiltersCase): }) filters = Filters.get_filters('ir.filters') - self.assertItemsEqual(map(noid, filters), [ + self.assertItemsEqual(noid(filters), [ dict(name='a', user_id=False, is_default=False, domain='[]', context='{}', sort='[]'), dict(name='b', user_id=False, is_default=False, domain='[]', context='{}', sort='[]'), dict(name='c', user_id=False, is_default=True, domain='[]', context='{}', sort='[]'), @@ -271,7 +272,7 @@ class TestGlobalDefaults(FiltersCase): }) filters = Filters.get_filters('ir.filters') - self.assertItemsEqual(map(noid, filters), [ + self.assertItemsEqual(noid(filters), [ dict(name='a', user_id=False, is_default=False, domain='[]', context='{}', sort='[]'), dict(name='b', user_id=False, is_default=True, domain='[]', context=context_value, sort='[]'), ]) diff --git a/odoo/addons/base/tests/test_orm.py b/odoo/addons/base/tests/test_orm.py index 0da71a84ad5..bf95bee08f7 100644 --- a/odoo/addons/base/tests/test_orm.py +++ b/odoo/addons/base/tests/test_orm.py @@ -141,7 +141,7 @@ class TestORM(TransactionCase): partner_ids_by_year = defaultdict(list) partners = self.env['res.partner'] - for name, date in partners_data.items(): + for name, date in pycompat.items(partners_data): p = partners.create(dict(name=name, date=date)) partner_ids.append(p.id) partner_ids_by_day[date].append(p.id) @@ -297,7 +297,7 @@ class TestO2MSerialization(TransactionCase): def test_CREATE_commands(self): " returns the VALUES dict as-is " values = [{'foo': 'bar'}, {'foo': 'baz'}, {'foo': 'baq'}] - results = self.env['res.partner'].resolve_2many_commands('child_ids', map(CREATE, values)) + results = self.env['res.partner'].resolve_2many_commands('child_ids', [CREATE(v) for v in values]) self.assertEqual(results, values) def test_LINK_TO_command(self): @@ -307,7 +307,7 @@ class TestO2MSerialization(TransactionCase): self.env['res.partner'].create({'name': 'bar'}).id, self.env['res.partner'].create({'name': 'baz'}).id, ] - commands = map(LINK_TO, ids) + commands = [LINK_TO(v) for v in ids] results = self.env['res.partner'].resolve_2many_commands('child_ids', commands, ['name']) self.assertItemsEqual(results, [ @@ -356,7 +356,7 @@ class TestO2MSerialization(TransactionCase): self.env['res.partner'].create({'name': 'bar'}).id, self.env['res.partner'].create({'name': 'baz'}).id, ] - commands = map(DELETE, ids) + commands = [DELETE(v) for v in ids] results = self.env['res.partner'].resolve_2many_commands('child_ids', commands, ['name']) self.assertEqual(results, []) diff --git a/odoo/addons/base/tests/test_qweb.py b/odoo/addons/base/tests/test_qweb.py index dcf41b092d6..cd459fe7669 100644 --- a/odoo/addons/base/tests/test_qweb.py +++ b/odoo/addons/base/tests/test_qweb.py @@ -13,6 +13,7 @@ from itertools import chain from odoo.modules import get_module_resource from odoo.tests.common import TransactionCase from odoo.addons.base.ir.ir_qweb import QWebException +from odoo.tools import pycompat def dedent_and_strip(string): @@ -411,7 +412,7 @@ class TestQWebNS(TransactionCase): 'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2', 'cbc': 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2' } - self.assertSetEqual(set(expected_ns.items()) - set(result_etree.nsmap.items()), set()) + self.assertSetEqual(set(pycompat.items(expected_ns)) - set(pycompat.items(result_etree.nsmap)), set()) # check that the t-call did its work cac_lines = result_etree.findall('.//cac:line', namespaces={'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2'}) diff --git a/odoo/addons/base/tests/test_search.py b/odoo/addons/base/tests/test_search.py index d52cd9db9c4..97670fdd805 100644 --- a/odoo/addons/base/tests/test_search.py +++ b/odoo/addons/base/tests/test_search.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo.tests.common import TransactionCase +from odoo.tools import pycompat class test_search(TransactionCase): @@ -107,7 +108,7 @@ class test_search(TransactionCase): user_ids[u] = Users.create({'name': u, 'login': u}).id cron_ids[u] = Cron.create({'name': u, 'model_id': self.env.ref('base.model_res_partner').id, 'user_id': user_ids[u]}).id - ids = Cron.search([('id', 'in', cron_ids.values())], order='user_id').ids + ids = Cron.search([('id', 'in', list(pycompat.values(cron_ids)))], order='user_id').ids expected_ids = [cron_ids[l] for l in 'ABC'] self.assertEqual(ids, expected_ids) @@ -127,7 +128,7 @@ class test_search(TransactionCase): create('F', parent_id=cat_ids['D']) expected_ids = [cat_ids[x] for x in 'ADEFBC'] - found_ids = Cats.search([('id', 'in', cat_ids.values())]).ids + found_ids = Cats.search([('id', 'in', list(pycompat.values(cat_ids)))]).ids self.assertEqual(found_ids, expected_ids) def test_13_m2o_order_loop_multi(self): diff --git a/odoo/addons/base/tests/test_views.py b/odoo/addons/base/tests/test_views.py index bd462932e5f..40cd922340c 100644 --- a/odoo/addons/base/tests/test_views.py +++ b/odoo/addons/base/tests/test_views.py @@ -638,7 +638,7 @@ class TestViews(ViewCase): kw.setdefault('mode', 'extension' if kw.get('inherit_id') else 'primary') kw.setdefault('active', True) - keys = sorted(kw.keys()) + keys = sorted(kw) fields = ','.join('"%s"' % (k.replace('"', r'\"'),) for k in keys) params = ','.join('%%(%s)s' % (k,) for k in keys) diff --git a/odoo/addons/test_impex/tests/test_export.py b/odoo/addons/test_impex/tests/test_export.py index 226ba9943f6..72a3b2a6f75 100644 --- a/odoo/addons/test_impex/tests/test_export.py +++ b/odoo/addons/test_impex/tests/test_export.py @@ -4,6 +4,7 @@ import itertools from odoo.tests import common +from odoo.tools import pycompat class CreatorCase(common.TransactionCase): @@ -494,9 +495,9 @@ class test_o2m_multiple(CreatorCase): """ fields = ['const', 'child1/value', 'child2/value'] child1 = [(0, False, {'value': v, 'str': 'record%.02d' % index}) - for index, v in zip(itertools.count(), [4, 42, 36, 4, 13])] + for index, v in pycompat.izip(itertools.count(), [4, 42, 36, 4, 13])] child2 = [(0, False, {'value': v, 'str': 'record%.02d' % index}) - for index, v in zip(itertools.count(10), [8, 12, 8, 55, 33, 13])] + for index, v in pycompat.izip(itertools.count(10), [8, 12, 8, 55, 33, 13])] self.assertEqual( self.export(child1=child1, child2=False, fields=fields), diff --git a/odoo/addons/test_limits/models.py b/odoo/addons/test_limits/models.py index 506578a811b..957e3c3d6f9 100644 --- a/odoo/addons/test_limits/models.py +++ b/odoo/addons/test_limits/models.py @@ -5,7 +5,6 @@ import time import sys from odoo import models, api -from odoo.tools import pycompat class m(models.Model): """ This model exposes a few methods that will consume between 'almost no @@ -39,7 +38,7 @@ class m(models.Model): t0 = time.clock() t1 = time.clock() while t1 - t0 < seconds: - for i in pycompat.range(10000000): + for i in range(10000000): x = i * i t1 = time.clock() return True diff --git a/odoo/addons/test_new_api/models.py b/odoo/addons/test_new_api/models.py index c84827d47ce..2f288e89f74 100644 --- a/odoo/addons/test_new_api/models.py +++ b/odoo/addons/test_new_api/models.py @@ -5,6 +5,7 @@ import datetime from odoo import models, fields, api, _ from odoo.exceptions import AccessError, ValidationError +from odoo.tools import pycompat class Category(models.Model): @@ -45,7 +46,7 @@ class Category(models.Model): categories.append(category[0]) categories.append(self) # assign parents following sequence - for parent, child in zip(categories, categories[1:]): + for parent, child in pycompat.izip(categories, categories[1:]): if parent and child: child.parent = parent # assign name of last category, and reassign display_name (to normalize it) @@ -364,4 +365,4 @@ class ComputeRecursive(models.Model): if rec.parent: rec.display_name = rec.parent.display_name + " / " + rec.name else: - rec.display_name = rec.name \ No newline at end of file + rec.display_name = rec.name diff --git a/odoo/addons/test_new_api/tests/test_one2many.py b/odoo/addons/test_new_api/tests/test_one2many.py index 9ade2c0fe19..e1ad547e95e 100644 --- a/odoo/addons/test_new_api/tests/test_one2many.py +++ b/odoo/addons/test_new_api/tests/test_one2many.py @@ -45,7 +45,7 @@ class One2manyCase(TransactionCase): # Check the lines first self.assertItemsEqual( self.multi.lines.mapped('name'), - map(str, range(10))) + [str(i) for i in range(10)]) # Modify the first line and drop the last one self.multi.lines[0].name = "hello" self.multi.lines = self.multi.lines[:-1] diff --git a/odoo/addons/test_pylint/tests/test_pylint.py b/odoo/addons/test_pylint/tests/test_pylint.py index 1ae544e56a6..f98dcf88b3c 100644 --- a/odoo/addons/test_pylint/tests/test_pylint.py +++ b/odoo/addons/test_pylint/tests/test_pylint.py @@ -30,22 +30,33 @@ class TestPyLint(TransactionCase): 'relative-import', 'deprecated-module', 'import-star-module-level', - # 'bad-python3-import', # TODO: more stuff used in report + + 'bad-builtin', + + 'dict-iter-method', + 'dict-view-method', 'long-suffix', - 'apply-builtin', - 'cmp-builtin', - 'coerce-builtin', - 'execfile-builtin', - 'input-builtin', - 'intern-builtin', - 'long-builtin', - 'raw_input-builtin', - 'reload-builtin', - 'xrange-builtin', + ] + + BAD_FUNCTIONS = [ + 'apply', + 'cmp', + 'coerce', + 'execfile', + 'input', + 'intern', + 'long', + 'raw_input', + 'reload', + 'xrange', + 'long', + 'map', + 'filter', + 'zip', # TODO: enable once report has been removed - # 'file-builtin', - # 'reduce-builtin', + # 'file', + # 'reduce', ] def _skip_test(self, reason): @@ -69,6 +80,8 @@ class TestPyLint(TransactionCase): '--enable=%s' % ','.join(self.ENABLED_CODES), '--reports=n', "--msg-template='{msg} ({msg_id}) at {path}:{line}'", + '--load-plugins=pylint.extensions.bad_builtin', + '--bad-functions=%s' % ','.join(self.BAD_FUNCTIONS), ] try: diff --git a/odoo/api.py b/odoo/api.py index 1adbd08f6e5..dc51870f20a 100644 --- a/odoo/api.py +++ b/odoo/api.py @@ -55,7 +55,7 @@ from weakref import WeakSet from decorator import decorator from werkzeug.local import Local, release_local -from odoo.tools import frozendict, classproperty +from odoo.tools import frozendict, classproperty, pycompat _logger = logging.getLogger(__name__) @@ -85,7 +85,7 @@ class Params(object): params = [] for arg in self.args: params.append(repr(arg)) - for item in sorted(self.kwargs.iteritems()): + for item in sorted(pycompat.items(self.kwargs)): params.append("%s=%r" % item) return ', '.join(params) @@ -100,7 +100,7 @@ class Meta(type): # dummy parent class to catch overridden methods decorated with 'returns' parent = type.__new__(meta, name, bases, {}) - for key, value in attrs.items(): + for key, value in list(pycompat.items(attrs)): if not key.startswith('__') and callable(value): # make the method inherit from decorators value = propagate(getattr(parent, key, None), value) @@ -916,7 +916,7 @@ class Environment(Mapping): def remove_todo(self, field, records): """ Mark ``field`` as recomputed on ``records``. """ recs_list = [recs - records for recs in self.all.todo.pop(field, [])] - recs_list = filter(None, recs_list) + recs_list = [r for r in recs_list if r] if recs_list: self.all.todo[field] = recs_list @@ -938,15 +938,14 @@ class Environment(Mapping): # make a full copy of the cache, and invalidate it cache_dump = dict( (field, dict(field_cache)) - for field, field_cache in self.cache.iteritems() + for field, field_cache in pycompat.items(self.cache) ) self.invalidate_all() # re-fetch the records, and compare with their former cache invalids = [] - for field, field_dump in cache_dump.iteritems(): - ids = filter(None, field_dump) - records = self[field.model_name].browse(ids) + for field, field_dump in pycompat.items(cache_dump): + records = self[field.model_name].browse(f for f in field_dump if f) for record in records: try: cached = field_dump[record.id] diff --git a/odoo/cli/command.py b/odoo/cli/command.py index 2ab042f58e8..503ffcb360a 100644 --- a/odoo/cli/command.py +++ b/odoo/cli/command.py @@ -28,7 +28,7 @@ class Help(Command): """Display the list of available commands""" def run(self, args): print("Available commands:\n") - names = commands.keys() + names = list(commands) padding = max([len(k) for k in names]) + 2 for k in sorted(names): name = k.ljust(padding, ' ') diff --git a/odoo/fields.py b/odoo/fields.py index e200d7b32dc..8d5d865968b 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -335,7 +335,7 @@ class Field(object): def __init__(self, string=Default, **kwargs): kwargs['string'] = string - args = {key: val for key, val in kwargs.iteritems() if val is not Default} + args = {key: val for key, val in pycompat.items(kwargs) if val is not Default} self.args = args or EMPTY_DICT self._setup_done = None @@ -364,7 +364,7 @@ class Field(object): """ Set all field attributes at once (with slot defaults). """ # optimization: we assign slots only assign = object.__setattr__ - for key, val in self._slots.iteritems(): + for key, val in pycompat.items(self._slots): assign(self, key, attrs.pop(key, val)) if attrs: assign(self, '_attrs', attrs) @@ -542,7 +542,7 @@ class Field(object): if not getattr(self, attr): setattr(self, attr, getattr(field, prop)) - for attr, value in field._attrs.iteritems(): + for attr, value in pycompat.items(field._attrs): if attr not in self._attrs: setattr(self, attr, value) @@ -565,7 +565,7 @@ class Field(object): """ Compute the related field ``self`` on ``records``. """ # when related_sudo, bypass access rights checks when reading values others = records.sudo() if self.related_sudo else records - for record, other in zip(records, others): + for record, other in pycompat.izip(records, others): if not record.id and record.env != other.env: # draft records: copy record's cache to other's cache first copy_cache(record, other.env) @@ -650,7 +650,7 @@ class Field(object): model = model0.env.get(field.comodel_name) # add self's model dependencies - for mname, fnames in model0._depends.iteritems(): + for mname, fnames in pycompat.items(model0._depends): model = model0.env[mname] for fname in fnames: field = model._fields[fname] @@ -1002,7 +1002,7 @@ class Field(object): # HACK: if result is in the wrong cache, copy values if recs.env != env: computed = record._field_computed[self] - for source, target in zip(recs, recs.with_env(env)): + for source, target in pycompat.izip(recs, recs.with_env(env)): try: values = target._convert_to_cache({ f.name: source[f.name] for f in computed @@ -1071,8 +1071,8 @@ class Field(object): for field, path in records._field_triggers[self]: bymodel[field.model_name][path].append(field) - for model_name, bypath in bymodel.iteritems(): - for path, fields in bypath.iteritems(): + for model_name, bypath in pycompat.items(bymodel): + for path, fields in pycompat.items(bypath): if path and any(field.compute and field.store for field in fields): # process stored fields stored = set(field for field in fields if field.compute and field.store) @@ -1755,7 +1755,7 @@ class Selection(Field): if 'selection_add' in field.args: # use an OrderedDict to update existing values selection_add = field.args['selection_add'] - self.selection = OrderedDict(self.selection + selection_add).items() + self.selection = list(pycompat.items(OrderedDict(self.selection + selection_add))) def _description_selection(self, env): """ return the selection list (pairs (value, label)); labels are @@ -1934,7 +1934,7 @@ class Many2one(_Relational): super(Many2one, self)._setup_attrs(model, name) # determine self.delegate if not self.delegate: - self.delegate = name in model._inherits.values() + self.delegate = name in pycompat.values(model._inherits) def update_db(self, model, columns): comodel = model.env[self.comodel_name] diff --git a/odoo/http.py b/odoo/http.py index 0b3ad105e39..fa5b44d1226 100644 --- a/odoo/http.py +++ b/odoo/http.py @@ -52,7 +52,7 @@ import odoo from odoo.service.server import memory_info from odoo.service import security, model as service_model from odoo.tools.func import lazy_property -from odoo.tools import ustr, consteq, frozendict +from odoo.tools import ustr, consteq, frozendict, pycompat, unique from odoo.modules.module import module_manifest @@ -288,8 +288,8 @@ class WebRequest(object): def set_handler(self, endpoint, arguments, auth): # is this needed ? - arguments = dict((k, v) for k, v in arguments.iteritems() - if not k.startswith("_ignored_")) + arguments ={k: v for k, v in pycompat.items(arguments) + if not k.startswith("_ignored_")} self.endpoint_arguments = arguments self.endpoint = endpoint self.auth_method = auth @@ -837,7 +837,7 @@ more details. """ response = Response(data, headers=headers) if cookies: - for k, v in cookies.iteritems(): + for k, v in pycompat.items(cookies): response.set_cookie(k, v) return response @@ -878,7 +878,7 @@ class ControllerType(type): super(ControllerType, cls).__init__(name, bases, attrs) # flag old-style methods with req as first argument - for k, v in attrs.items(): + for k, v in pycompat.items(attrs): if inspect.isfunction(v) and hasattr(v, 'original_func'): # Set routing type on original functions routing_type = v.routing.get('type') @@ -941,14 +941,12 @@ def routing_map(modules, nodb_only, converters=None): result = [klass] return result - uniq = lambda it: collections.OrderedDict((id(x), x) for x in it).values() - for module in modules: if module not in controllers_per_module: continue for _, cls in controllers_per_module[module]: - subclasses = uniq(c for c in get_subclasses(cls) if c is not cls) + subclasses = list(unique(c for c in get_subclasses(cls) if c is not cls)) if subclasses: name = "%s (extended by %s)" % (cls.__name__, ', '.join(sub.__name__ for sub in subclasses)) cls = type(name, tuple(reversed(subclasses)), {}) @@ -1050,7 +1048,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session): security.check(self.db, self.uid, self.password) def logout(self, keep_db=False): - for k in self.keys(): + for k in list(self): if not (keep_db and k == 'db'): del self[k] self._default_values() @@ -1137,7 +1135,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session): # NOTE we do not store files in the session itself to avoid loading them in memory. # By storing them in the session store, we ensure every worker (even ones on other # servers) can access them. It also allow stale files to be deleted by `session_gc`. - for f in req.files.values(): + for f in pycompat.values(req.files): storename = 'werkzeug_%s_%s.file' % (self.sid, uuid.uuid4().hex) path = os.path.join(root.session_store.path, storename) with open(path, 'w') as fp: @@ -1155,7 +1153,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session): try: if data: # regenerate files filenames with the current session store - for name, (storename, filename, content_type) in data['files'].iteritems(): + for name, (storename, filename, content_type) in pycompat.items(data['files']): path = os.path.join(root.session_store.path, storename) files.add(name, (path, filename, content_type)) yield werkzeug.datastructures.CombinedMultiDict([data['form'], files]) @@ -1163,7 +1161,7 @@ class OpenERPSession(werkzeug.contrib.sessions.Session): yield None finally: # cleanup files - for f, _, _ in files.values(): + for f, _, _ in pycompat.values(files): try: os.unlink(f) except IOError: diff --git a/odoo/models.py b/odoo/models.py index a1535123c9d..a9448b3ca05 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -108,9 +108,6 @@ def check_method_name(name): if regex_private.match(name): raise AccessError(_('Private methods (such as %s) cannot be called remotely.') % (name,)) -def intersect(la, lb): - return filter(lambda x: x in lb, la) - def same_name(f, g): """ Test whether functions ``f`` and ``g`` are identical or have the same name """ return f == g or getattr(f, '__name__', 0) == getattr(g, '__name__', 1) @@ -150,7 +147,7 @@ class MetaModel(api.Meta): self.module_to_models[self._module].append(self) # check for new-api conversion error: leave comma after field definition - for key, val in attrs.iteritems(): + for key, val in pycompat.items(attrs): if type(val) is tuple and len(val) == 1 and isinstance(val[0], Field): _logger.error("Trailing comma after field definition: %s.%s", self, key) if isinstance(val, Field): @@ -515,7 +512,7 @@ class BaseModel(object): cls._inherits.update(base._inherits) - for mname, fnames in base._depends.iteritems(): + for mname, fnames in pycompat.items(base._depends): cls._depends[mname] = cls._depends.get(mname, []) + fnames for cons in base._constraints: @@ -525,7 +522,7 @@ class BaseModel(object): cls._sql_constraints += base._sql_constraints cls._sequence = cls._sequence or (cls._table + '_id_seq') - cls._constraints = cls._constraints.values() + cls._constraints = list(pycompat.values(cls._constraints)) # update _inherits_children of parent models for parent_name in cls._inherits: @@ -707,7 +704,7 @@ class BaseModel(object): This method is used when exporting data via client menu """ - fields_to_export = map(fix_import_export_id_paths, fields_to_export) + fields_to_export = [fix_import_export_id_paths(f) for f in fields_to_export] if raw_data: self = self.with_context(export_raw_data=True) return {'datas': self._export_rows(fields_to_export)} @@ -740,7 +737,7 @@ class BaseModel(object): cr = self._cr cr.execute('SAVEPOINT model_load') - fields = map(fix_import_export_id_paths, fields) + fields = [fix_import_export_id_paths(f) for f in fields] fg = self.fields_get() ids = [] @@ -835,7 +832,7 @@ class BaseModel(object): # copy non-relational fields to record dict record = {fnames[0]: value - for fnames, value in itertools.izip(fields_, row) + for fnames, value in pycompat.izip(fields_, row) if not is_relational(fnames[0])} # Get all following rows which have relational values attached to @@ -849,13 +846,13 @@ class BaseModel(object): # get only cells for this sub-field, should be strictly # non-empty, field path [None] is for name_get field - indices, subfields = zip(*((index, fnames[1:] or [None]) + indices, subfields = pycompat.izip(*((index, fnames[1:] or [None]) for index, fnames in enumerate(fields_) if fnames[0] == relfield)) # return all rows which have at least one value for the # subfields of relfield - relfield_data = filter(any, map(itemgetter_tuple(indices), record_span)) + relfield_data = [it for it in pycompat.imap(itemgetter_tuple(indices), record_span) if any(it)] record[relfield] = [ subrecord for subrecord, _subinfo in comodel._extract_records(subfields, relfield_data, log=log) @@ -876,7 +873,7 @@ class BaseModel(object): :returns: a list of triplets of (id, xid, record) :rtype: list((int|None, str|None, dict)) """ - field_names = {name: field.string for name, field in self._fields.iteritems()} + field_names = {name: field.string for name, field in pycompat.items(self._fields)} if self.env.lang: field_names.update(self.env['ir.translation'].get_field_string(self._name)) @@ -1007,7 +1004,7 @@ class BaseModel(object): defaults = self._convert_to_write(defaults) # add default values for inherited fields - for model, names in parent_fields.iteritems(): + for model, names in pycompat.items(parent_fields): defaults.update(self.env[model].default_get(names)) return defaults @@ -1083,7 +1080,7 @@ class BaseModel(object): :rtype: etree._Element """ group = E.group(col="4") - for fname, field in self._fields.iteritems(): + for fname, field in pycompat.items(self._fields): if field.automatic: continue elif field.type in ('one2many', 'many2many', 'text', 'html'): @@ -1500,14 +1497,14 @@ class BaseModel(object): # avoid overriding inherited values when parent is set avoid_models = { parent_model - for parent_model, parent_field in self._inherits.iteritems() + for parent_model, parent_field in pycompat.items(self._inherits) if parent_field in values } # compute missing fields missing_defaults = { name - for name, field in self._fields.iteritems() + for name, field in pycompat.items(self._fields) if name not in values if name not in MAGIC_COLUMNS if not (field.inherited and field.related_field.model_name in avoid_models) @@ -1518,7 +1515,7 @@ class BaseModel(object): # override defaults with the provided values, never allow the other way around defaults = self.default_get(list(missing_defaults)) - for name, value in defaults.iteritems(): + for name, value in pycompat.items(defaults): if self._fields[name].type == 'many2many' and value and isinstance(value[0], pycompat.integer_types): # convert a list of ids into a list of commands defaults[name] = [(6, 0, value)] @@ -1603,10 +1600,10 @@ class BaseModel(object): if field.relational and groups._fold_name in groups._fields: fold = {group.id: group[groups._fold_name] for group in groups.browse([key for key in result if key])} - for key, line in result.iteritems(): + for key, line in pycompat.items(result): line['__fold'] = fold.get(key, False) - return result.values() + return list(pycompat.values(result)) @api.model def _read_group_prepare(self, orderby, aggregated_fields, annotated_groupbys, query): @@ -1840,7 +1837,7 @@ class BaseModel(object): def _read_group_raw(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True): self.check_access_rights('read') query = self._where_calc(domain) - fields = fields or [f.name for f in self._fields.itervalues() if f.store] + fields = fields or [f.name for f in pycompat.values(self._fields) if f.store] groupby = [groupby] if isinstance(groupby, basestring) else list(OrderedSet(groupby)) groupby_list = groupby[:1] if lazy else groupby @@ -1914,7 +1911,7 @@ class BaseModel(object): self._read_group_resolve_many2one_fields(fetched_data, annotated_groupbys) - data = map(lambda r: {k: self._read_group_prepare_data(k,v, groupby_dict) for k,v in r.iteritems()}, fetched_data) + data = ({k: self._read_group_prepare_data(k,v, groupby_dict) for k,v in pycompat.items(r)} for r in fetched_data) result = [self._read_group_format_result(d, annotated_groupbys, groupby, domain) for d in data] if lazy: # Right now, read_group only fill results in lazy mode (by default). @@ -2020,7 +2017,7 @@ class BaseModel(object): # fields which were required but have been removed (or will be added by # another module) cr = self._cr - cols = [name for name, field in self._fields.iteritems() + cols = [name for name, field in pycompat.items(self._fields) if field.store and field.column_type] cr.execute("SELECT a.attname, a.attnotnull" " FROM pg_class c, pg_attribute a" @@ -2120,7 +2117,7 @@ class BaseModel(object): recs = self.with_context(active_test=False).search([]) recs._recompute_todo(field) - for field in self._fields.itervalues(): + for field in pycompat.values(self._fields): if not field.store: continue @@ -2208,9 +2205,9 @@ class BaseModel(object): """ Determine inherited fields. """ # determine candidate inherited fields fields = {} - for parent_model, parent_field in self._inherits.iteritems(): + for parent_model, parent_field in pycompat.items(self._inherits): parent = self.env[parent_model] - for name, field in parent._fields.iteritems(): + for name, field in pycompat.items(parent._fields): # inherited fields are implemented as related fields, with the # following specific properties: # - reading inherited fields should not bypass access rights @@ -2223,13 +2220,13 @@ class BaseModel(object): ) # add inherited fields that are not redefined locally - for name, field in fields.iteritems(): + for name, field in pycompat.items(fields): if name not in self._fields: self._add_field(name, field) @api.model def _inherits_check(self): - for table, field_name in self._inherits.items(): + for table, field_name in pycompat.items(self._inherits): field = self._fields.get(field_name) if not field: _logger.info('Missing many2one field definition for _inherits reference "%s" in "%s", using default one.', field_name, self._name) @@ -2242,7 +2239,7 @@ class BaseModel(object): field.ondelete = "cascade" # reflect fields with delegate=True in dictionary self._inherits - for field in self._fields.itervalues(): + for field in pycompat.values(self._fields): if field.type == 'many2one' and not field.related and field.delegate: if not field.required: _logger.warning("Field %s with delegate=True must be required.", field) @@ -2330,7 +2327,7 @@ class BaseModel(object): # set up fields bad_fields = [] - for name, field in cls._fields.iteritems(): + for name, field in pycompat.items(cls._fields): try: field.setup_full(self) except Exception: @@ -2349,11 +2346,11 @@ class BaseModel(object): # map each field to the fields computed with the same method groups = defaultdict(list) - for field in cls._fields.itervalues(): + for field in pycompat.values(cls._fields): if field.compute: cls._field_computed[field] = group = groups[field.compute] group.append(field) - for fields in groups.itervalues(): + for fields in pycompat.values(groups): compute_sudo = fields[0].compute_sudo if not all(field.compute_sudo == compute_sudo for field in fields): _logger.warning("%s: inconsistent 'compute_sudo' for computed fields: %s", @@ -2366,7 +2363,7 @@ class BaseModel(object): if isinstance(self, Model): # set up field triggers (on database-persisted models only) - for field in cls._fields.itervalues(): + for field in pycompat.values(cls._fields): # dependencies of custom fields may not exist; ignore that case exceptions = (Exception,) if field.manual else () with tools.ignore(*exceptions): @@ -2405,7 +2402,7 @@ class BaseModel(object): readonly = not (has_access('write') or has_access('create')) res = {} - for fname, field in self._fields.iteritems(): + for fname, field in pycompat.items(self._fields): if allfields and fname not in allfields: continue if field.groups and not self.user_has_groups(field.groups): @@ -2417,7 +2414,7 @@ class BaseModel(object): description['states'] = {} if attributes: description = {key: val - for key, val in description.iteritems() + for key, val in pycompat.items(description) if key in attributes} res[fname] = description @@ -2452,9 +2449,9 @@ class BaseModel(object): return True if not fields: - fields = filter(valid, self._fields) + fields = [name for name in self._fields if valid(name)] else: - invalid_fields = set(filter(lambda name: not valid(name), fields)) + invalid_fields = {name for name in fields if not valid(name)} if invalid_fields: _logger.info('Access Denied by ACLs for operation: %s, uid: %s, model: %s, fields: %s', operation, self._uid, self._name, ', '.join(invalid_fields)) @@ -2528,7 +2525,7 @@ class BaseModel(object): if self._context.get('prefetch_fields', True) and field.prefetch: fs.update( f - for f in self._fields.itervalues() + for f in pycompat.values(self._fields) # select fields that can be prefetched if f.prefetch # discard fields with groups that the user may not access @@ -2591,7 +2588,7 @@ class BaseModel(object): order_str = self._generate_order_by(None, query) # determine the fields that are stored as columns in tables; - fields = map(self._fields.get, field_names + inherited_field_names) + fields = [self._fields[n] for n in (field_names + inherited_field_names)] fields_pre = [ field for field in fields @@ -2608,7 +2605,7 @@ class BaseModel(object): res = 'pg_size_pretty(length(%s)::bigint)' % res return '%s as "%s"' % (res, col) - qual_names = map(qualify, set(fields_pre + [self._fields['id']])) + qual_names = [qualify(name) for name in set(fields_pre + [self._fields['id']])] # determine the actual query to execute from_clause, where_clause, params = query.get_sql() @@ -2666,8 +2663,7 @@ class BaseModel(object): if extras: raise AccessError( _("Database fetch misses ids ({}) and has extra ids ({}), may be caused by a type incoherence in a previous request").format( - ', '.join(map(repr, missing._ids)), - ', '.join(map(repr, extras._ids)), + missing._ids, extras._ids, )) # mark non-existing records in missing forbidden = missing.exists() @@ -2990,7 +2986,7 @@ class BaseModel(object): # split up fields into old-style and pure new-style ones old_vals, new_vals, unknown = {}, {}, [] - for key, val in vals.iteritems(): + for key, val in pycompat.items(vals): field = self._fields.get(key) if field: if field.store or field.inherited: @@ -3003,7 +2999,7 @@ class BaseModel(object): if unknown: _logger.warning("%s.write() with unknown fields: %s", self._name, ', '.join(sorted(unknown))) - protected_fields = map(self._fields.get, new_vals) + protected_fields = [self._fields[n] for n in new_vals] with self.env.protecting(protected_fields, self): # write old-style fields with (low-level) method _write if old_vals: @@ -3054,7 +3050,7 @@ class BaseModel(object): query = "SELECT id FROM %s WHERE id IN %%s AND (%s IS NOT NULL) ORDER BY %s" % \ (self._table, self._parent_name, self._parent_order) cr.execute(query, (tuple(self.ids),)) - parents_changed = map(operator.itemgetter(0), cr.fetchall()) + parents_changed = [x[0] for x in cr.fetchall()] updates = [] # list of (column, expr) or (column, pattern, value) upd_todo = [] # list of column names to set explicitly @@ -3062,7 +3058,7 @@ class BaseModel(object): direct = [] # list of direcly updated columns has_trans = self.env.lang and self.env.lang != 'en_US' single_lang = len(self.env['res.lang'].get_installed()) <= 1 - for name, val in vals.iteritems(): + for name, val in pycompat.items(vals): field = self._fields[name] if field and field.deprecated: _logger.warning('Field %s.%s is deprecated: %s', self._name, name, field.deprecated) @@ -3124,7 +3120,7 @@ class BaseModel(object): # defaults in context must be removed when call a one2many or many2many rel_context = {key: val - for key, val in self._context.iteritems() + for key, val in pycompat.items(self._context) if not key.startswith('default_')} # call the 'write' method of fields which are not columns @@ -3137,7 +3133,7 @@ class BaseModel(object): # write inherited fields on the corresponding parent records unknown_fields = set(updend) - for parent_model, parent_field in self._inherits.iteritems(): + for parent_model, parent_field in pycompat.items(self._inherits): parent_ids = [] for sub_ids in cr.split_for_in_conditions(self.ids): query = "SELECT DISTINCT %s FROM %s WHERE id IN %%s" % (parent_field, self._table) @@ -3258,7 +3254,7 @@ class BaseModel(object): # split up fields into old-style and pure new-style ones old_vals, new_vals, unknown = {}, {}, [] - for key, val in vals.iteritems(): + for key, val in pycompat.items(vals): field = self._fields.get(key) if field: if field.store or field.inherited: @@ -3274,7 +3270,7 @@ class BaseModel(object): # create record with old-style fields record = self.browse(self._create(old_vals)) - protected_fields = map(self._fields.get, new_vals) + protected_fields = [self._fields[n] for n in new_vals] with self.env.protecting(protected_fields, record): # put the values of pure new-style fields into cache, and inverse them record.modified(set(new_vals) - set(old_vals)) @@ -3299,7 +3295,7 @@ class BaseModel(object): # data of parent records to create or update, by model tocreate = { parent_model: {'id': vals.pop(parent_field, None)} - for parent_model, parent_field in self._inherits.iteritems() + for parent_model, parent_field in pycompat.items(self._inherits) } # list of column assignments defined as tuples like: @@ -3314,7 +3310,7 @@ class BaseModel(object): upd_todo = [] unknown_fields = [] protected_fields = [] - for name, val in vals.items(): + for name, val in list(pycompat.items(vals)): field = self._fields.get(name) if not field: unknown_fields.append(name) @@ -3330,7 +3326,7 @@ class BaseModel(object): _logger.warning('No such field(s) in model %s: %s.', self._name, ', '.join(unknown_fields)) # create or update parent records - for parent_model, parent_vals in tocreate.iteritems(): + for parent_model, parent_vals in pycompat.items(tocreate): parent_id = parent_vals.pop('id') if not parent_id: parent_id = self.env[parent_model].create(parent_vals).id @@ -3339,12 +3335,12 @@ class BaseModel(object): updates.append((self._inherits[parent_model], '%s', parent_id)) # set boolean fields to False by default (to make search more powerful) - for name, field in self._fields.iteritems(): + for name, field in pycompat.items(self._fields): if field.type == 'boolean' and field.store and name not in vals: vals[name] = False # determine SQL values - for name, val in vals.iteritems(): + for name, val in pycompat.items(vals): field = self._fields[name] if field.store and field.column_type: updates.append((name, field.column_format, field.convert_to_column(val, self))) @@ -3375,7 +3371,7 @@ class BaseModel(object): if self.env.lang and self.env.lang != 'en_US': # add translations for self.env.lang - for name, val in vals.iteritems(): + for name, val in pycompat.items(vals): field = self._fields[name] if field.store and field.column_type and field.translate is True: tname = "%s,%s" % (self._name, name) @@ -3423,7 +3419,7 @@ class BaseModel(object): # defaults in context must be removed when call a one2many or many2many rel_context = {key: val - for key, val in self._context.iteritems() + for key, val in pycompat.items(self._context) if not key.startswith('default_')} # call the 'write' method of fields which are not columns @@ -3728,11 +3724,11 @@ class BaseModel(object): # build a black list of fields that should not be copied blacklist = set(MAGIC_COLUMNS + ['parent_left', 'parent_right']) - whitelist = set(name for name, field in self._fields.iteritems() if not field.inherited) + whitelist = set(name for name, field in pycompat.items(self._fields) if not field.inherited) def blacklist_given_fields(model): # blacklist the fields that are given by inheritance - for parent_model, parent_field in model._inherits.items(): + for parent_model, parent_field in pycompat.items(model._inherits): blacklist.add(parent_field) if parent_field in default: # all the fields of 'parent_model' are given by the record: @@ -3741,17 +3737,17 @@ class BaseModel(object): else: blacklist_given_fields(self.env[parent_model]) # blacklist deprecated fields - for name, field in model._fields.iteritems(): + for name, field in pycompat.items(model._fields): if field.deprecated: blacklist.add(name) blacklist_given_fields(self) fields_to_copy = {name: field - for name, field in self._fields.iteritems() + for name, field in pycompat.items(self._fields) if field.copy and name not in default and name not in blacklist} - for name, field in fields_to_copy.iteritems(): + for name, field in pycompat.items(fields_to_copy): if field.type == 'one2many': # duplicate following the order of the ids because we'll rely on # it later for copying translations in copy_translation()! @@ -3789,7 +3785,7 @@ class BaseModel(object): old_wo_lang, new_wo_lang = (old + new).with_context(lang=None) Translation = old.env['ir.translation'] - for name, field in old._fields.iteritems(): + for name, field in pycompat.items(old._fields): if not field.copy: continue @@ -3799,7 +3795,7 @@ class BaseModel(object): # foreseen in copy_data() old_lines = old[name].sorted(key='id') new_lines = new[name].sorted(key='id') - for (old_line, new_line) in zip(old_lines, new_lines): + for (old_line, new_line) in pycompat.izip(old_lines, new_lines): old_line.copy_translations(new_line) elif field.translate: @@ -3969,7 +3965,7 @@ class BaseModel(object): """ results = self._get_external_ids() return {key: val[0] if val else '' - for key, val in results.iteritems()} + for key, val in pycompat.items(results)} # backwards compatibility get_xml_id = get_external_id @@ -4236,7 +4232,7 @@ class BaseModel(object): """ List of actual record ids in this recordset (ignores placeholder ids for records to create) """ - return filter(None, list(self._ids)) + return [it for it in self._ids if it] # backward-compatibility with former browse records _cr = property(lambda self: self.env.cr) @@ -4342,7 +4338,7 @@ class BaseModel(object): target = self if update else self.browse([], self._prefetch) return { name: fields[name].convert_to_cache(value, target, validate=validate) - for name, value in values.iteritems() + for name, value in pycompat.items(values) if name in fields } @@ -4352,14 +4348,14 @@ class BaseModel(object): """ return { name: self._fields[name].convert_to_record(value, self) - for name, value in values.iteritems() + for name, value in pycompat.items(values) } def _convert_to_write(self, values): """ Convert the ``values`` dictionary into the format of :meth:`write`. """ fields = self._fields result = {} - for name, value in values.iteritems(): + for name, value in pycompat.items(values): if name in fields: field = fields[name] value = field.convert_to_cache(value, self, validate=False) @@ -4423,7 +4419,7 @@ class BaseModel(object): """ if isinstance(func, basestring): name = func - func = lambda rec: filter(None, rec.mapped(name)) + func = lambda rec: any(rec.mapped(name)) return self.browse([rec.id for rec in self if func(rec)]) def sorted(self, key=None, reverse=False): @@ -4440,13 +4436,13 @@ class BaseModel(object): return self.browse(reversed(recs._ids)) if reverse else recs if isinstance(key, basestring): key = itemgetter(key) - return self.browse(map(attrgetter('id'), sorted(self, key=key, reverse=reverse))) + return self.browse(item.id for item in sorted(self, key=key, reverse=reverse)) @api.multi def update(self, values): """ Update the records in ``self`` with ``values``. """ for record in self: - for name, value in values.iteritems(): + for name, value in pycompat.items(values): record[name] = value # @@ -4667,7 +4663,7 @@ class BaseModel(object): the records of model ``self`` in cache that have no value for ``field`` (:class:`Field` instance). """ - ids = filter(None, self._prefetch[self._name] - set(self.env.cache[field])) + ids = [it for it in self._prefetch[self._name] - set(self.env.cache[field]) if it] return self.browse(ids) @api.model @@ -4690,9 +4686,9 @@ class BaseModel(object): if fnames is None: if ids is None: return self.env.invalidate_all() - fields = self._fields.values() + fields = list(pycompat.values(self._fields)) else: - fields = map(self._fields.__getitem__, fnames) + fields = [self._fields[n] for n in fnames] # invalidate fields and inverse fields, too spec = [(f, ids) for f in fields] + \ @@ -4746,10 +4742,11 @@ class BaseModel(object): updates[frozendict(vals)].add(rec.id) # update records in batch when possible with recs.env.norecompute(): - for vals, ids in updates.iteritems(): + for vals, ids in pycompat.items(updates): recs.browse(ids)._write(dict(vals)) # mark computed fields as done - map(recs._recompute_done, fs) + for f in fs: + recs._recompute_done(f) # # Generic onchange method @@ -4779,7 +4776,7 @@ class BaseModel(object): if not result.get(names): result[names] = node.attrib.get('on_change') # traverse the subviews included in relational fields - for subinfo in info['fields'][name].get('views', {}).itervalues(): + for subinfo in pycompat.values(info['fields'][name].get('views', {})): process(etree.fromstring(subinfo['arch']), subinfo, names) else: for child in node: @@ -4802,19 +4799,19 @@ class BaseModel(object): return if res.get('value'): res['value'].pop('id', None) - self.update({key: val for key, val in res['value'].iteritems() if key in self._fields}) + self.update({key: val for key, val in pycompat.items(res['value']) if key in self._fields}) if res.get('domain'): result.setdefault('domain', {}).update(res['domain']) if res.get('warning'): if result.get('warning'): # Concatenate multiple warnings warning = result['warning'] - warning['message'] = '\n\n'.join(filter(None, [ + warning['message'] = '\n\n'.join(s for s in [ warning.get('title'), warning.get('message'), res['warning'].get('title'), res['warning'].get('message'), - ])) + ] if s) warning['title'] = _('Warnings') else: result['warning'] = res['warning'] @@ -4931,7 +4928,7 @@ class BaseModel(object): record.mapped(field_seq) # determine which fields have been modified - for name, oldval in values.iteritems(): + for name, oldval in pycompat.items(values): field = self._fields[name] newval = record[name] if newval != oldval or ( @@ -5005,7 +5002,7 @@ class RecordCache(MutableMapping): """ if args and isinstance(args[0], SpecialValue): values = dict.fromkeys(self._recs._ids, args[0]) - for name, field in self._recs._fields.iteritems(): + for name, field in pycompat.items(self._recs._fields): if name != 'id': self._recs.env.cache[field].update(values) else: @@ -5023,7 +5020,7 @@ class RecordCache(MutableMapping): """ Iterate over the field names with a regular value in cache. """ cache, id = self._recs.env.cache, self._recs.id dummy = SpecialValue(None) - for name, field in self._recs._fields.iteritems(): + for name, field in pycompat.items(self._recs._fields): if name != 'id' and not isinstance(cache[field].get(id, dummy), SpecialValue): yield name diff --git a/odoo/modules/db.py b/odoo/modules/db.py index 7106ff82b69..bde759843cf 100644 --- a/odoo/modules/db.py +++ b/odoo/modules/db.py @@ -99,7 +99,7 @@ def create_categories(cr, categories): category = [] while categories: category.append(categories[0]) - xml_id = 'module_category_' + ('_'.join(map(lambda x: x.lower(), category))).replace('&', 'and').replace(' ', '_') + xml_id = 'module_category_' + ('_'.join(x.lower() for x in category)).replace('&', 'and').replace(' ', '_') # search via xml_id (because some categories are renamed) cr.execute("SELECT res_id FROM ir_model_data WHERE name=%s AND module=%s AND model=%s", (xml_id, "base", "ir.module.category")) diff --git a/odoo/modules/graph.py b/odoo/modules/graph.py index d962ea78bdc..731b6f6557a 100644 --- a/odoo/modules/graph.py +++ b/odoo/modules/graph.py @@ -8,7 +8,7 @@ import logging import odoo import odoo.tools as tools - +from odoo.tools import pycompat _logger = logging.getLogger(__name__) @@ -36,7 +36,7 @@ class Graph(dict): return # update the graph with values from the database (if exist) ## First, we set the default values for each package in graph - additional_data = dict((key, {'id': 0, 'state': 'uninstalled', 'dbdemo': False, 'installed_version': None}) for key in self.keys()) + additional_data = {key: {'id': 0, 'state': 'uninstalled', 'dbdemo': False, 'installed_version': None} for key in pycompat.keys(self)} ## Then we get the values from the database cr.execute('SELECT name, id, state, demo AS dbdemo, latest_version AS installed_version' ' FROM ir_module_module' @@ -46,8 +46,8 @@ class Graph(dict): ## and we update the default values with values from the database additional_data.update((x['name'], x) for x in cr.dictfetchall()) - for package in self.values(): - for k, v in additional_data[package.name].items(): + for package in pycompat.values(self): + for k, v in pycompat.items(additional_data[package.name]): setattr(package, k, v) def add_module(self, cr, module, force=None): @@ -94,7 +94,7 @@ class Graph(dict): self.update_from_db(cr) for package in later: - unmet_deps = filter(lambda p: p not in self, dependencies[package]) + unmet_deps = [p for p in dependencies[package] if p not in self] _logger.error('module %s: Unmet dependencies: %s', package, ', '.join(unmet_deps)) return len(self) - len_graph @@ -102,9 +102,9 @@ class Graph(dict): def __iter__(self): level = 0 - done = set(self.keys()) + done = set(pycompat.keys(self)) while done: - level_modules = sorted((name, module) for name, module in self.items() if module.depth==level) + level_modules = sorted((name, module) for name, module in pycompat.items(self) if module.depth==level) for name, module in level_modules: done.remove(name) yield module @@ -164,7 +164,10 @@ class Node(object): setattr(child, name, value + 1) def __iter__(self): - return itertools.chain(iter(self.children), *map(iter, self.children)) + return itertools.chain( + self.children, + itertools.chain.from_iterable(self.children) + ) def __str__(self): return self._pprint() diff --git a/odoo/modules/loading.py b/odoo/modules/loading.py index f8c65d40177..bd58376a7b9 100644 --- a/odoo/modules/loading.py +++ b/odoo/modules/loading.py @@ -20,6 +20,7 @@ import odoo.tools as tools from odoo import api, SUPERUSER_ID from odoo.modules.module import adapt_version, initialize_sys_path, load_openerp_module +from odoo.tools import pycompat _logger = logging.getLogger(__name__) _test_logger = logging.getLogger('odoo.tests') @@ -295,15 +296,15 @@ def load_modules(db, force_demo=False, status=None, update_module=False): _logger.info('updating modules list') Module.update_list() - _check_module_names(cr, itertools.chain(tools.config['init'].keys(), tools.config['update'].keys())) + _check_module_names(cr, itertools.chain(tools.config['init'], tools.config['update'])) - module_names = [k for k, v in tools.config['init'].items() if v] + module_names = [k for k, v in pycompat.items(tools.config['init']) if v] if module_names: modules = Module.search([('state', '=', 'uninstalled'), ('name', 'in', module_names)]) if modules: modules.button_install() - module_names = [k for k, v in tools.config['update'].items() if v] + module_names = [k for k, v in pycompat.items(tools.config['update']) if v] if module_names: modules = Module.search([('state', '=', 'installed'), ('name', 'in', module_names)]) if modules: @@ -391,7 +392,7 @@ def load_modules(db, force_demo=False, status=None, update_module=False): getattr(py_module, uninstall_hook)(cr, registry) Module = env['ir.module.module'] - Module.browse(modules_to_remove.values()).module_uninstall() + Module.browse(pycompat.values(modules_to_remove)).module_uninstall() # Recursive reload, should only happen once, because there should be no # modules to remove next time cr.commit() @@ -414,7 +415,7 @@ def load_modules(db, force_demo=False, status=None, update_module=False): _logger.info('Modules loaded.') # STEP 8: call _register_hook on every model - for model in env.values(): + for model in pycompat.values(env): model._register_hook() # STEP 9: save installed/updated modules for post-install tests diff --git a/odoo/modules/migration.py b/odoo/modules/migration.py index 5b1dcf09ab2..0fa6699cf24 100644 --- a/odoo/modules/migration.py +++ b/odoo/modules/migration.py @@ -13,6 +13,7 @@ from os.path import join as opj from odoo.modules.module import get_resource_path import odoo.release as release import odoo.tools as tools +from odoo.tools import pycompat from odoo.tools.parse_version import parse_version @@ -87,13 +88,12 @@ class MigrationManager(object): return "%s.%s" % (release.major_version, version) def _get_migration_versions(pkg): - versions = list(set( + versions = sorted({ ver - for lv in self.migrations[pkg.name].values() - for ver, lf in lv.items() + for lv in pycompat.values(self.migrations[pkg.name]) + for ver, lf in pycompat.items(lv) if lf - )) - versions.sort(key=lambda k: parse_version(convert_version(k))) + }, key=lambda k: parse_version(convert_version(k))) return versions def _get_migration_files(pkg, version, stage): @@ -107,7 +107,7 @@ class MigrationManager(object): 'maintenance': opj('base', 'maintenance', 'migrations', pkg.name), } - for x in mapping.keys(): + for x in mapping: if version in m.get(x): for f in m[x][version]: if not f.startswith(stage + '-'): diff --git a/odoo/modules/module.py b/odoo/modules/module.py index dd1485e1d8a..e6194a5fb10 100644 --- a/odoo/modules/module.py +++ b/odoo/modules/module.py @@ -23,6 +23,7 @@ import odoo import odoo.tools as tools import odoo.release as release from odoo import SUPERUSER_ID, api +from odoo.tools import pycompat MANIFEST_NAMES = ('__manifest__.py', '__openerp__.py') README = ['README.rst', 'README.md', 'README.txt'] @@ -323,7 +324,7 @@ def load_information_from_description_file(module, mod_path=None): 'sequence': 100, 'summary': '', } - info.update(itertools.izip( + info.update(pycompat.izip( 'depends data demo test init_xml update_xml demo_xml'.split(), iter(list, None))) @@ -395,7 +396,11 @@ def get_modules(): for mname in MANIFEST_NAMES: if os.path.isfile(opj(dir, name, mname)): return True - return map(clean, filter(is_really_module, os.listdir(dir))) + return [ + clean(it) + for it in os.listdir(dir) + if is_really_module(it) + ] plist = [] initialize_sys_path() @@ -492,7 +497,7 @@ def run_unit_tests(module_name, dbname, position=runs_at_install): r = True for m in mods: tests = unwrap_suite(unittest.TestLoader().loadTestsFromModule(m)) - suite = unittest.TestSuite(itertools.ifilter(position, tests)) + suite = unittest.TestSuite(t for t in tests if position(t)) if suite.countTestCases(): t0 = time.time() @@ -532,5 +537,5 @@ def unwrap_suite(test): return for item in itertools.chain.from_iterable( - itertools.imap(unwrap_suite, subtests)): + unwrap_suite(t) for t in subtests): yield item diff --git a/odoo/modules/registry.py b/odoo/modules/registry.py index e0cfb532fd1..71d7b5d9fbe 100644 --- a/odoo/modules/registry.py +++ b/odoo/modules/registry.py @@ -16,7 +16,8 @@ import threading import odoo from .. import SUPERUSER_ID from odoo.tools import (assertion_report, lazy_classproperty, config, - lazy_property, table_exists, topological_sort, OrderedSet) + lazy_property, table_exists, topological_sort, + OrderedSet, pycompat) from odoo.tools.lru import LRU _logger = logging.getLogger(__name__) @@ -156,7 +157,7 @@ class Registry(Mapping): def delete_all(cls): """ Delete all the registries. """ with cls._lock: - for db_name in cls.registries.keys(): + for db_name in list(pycompat.keys(cls.registries)): cls.delete(db_name) # @@ -192,8 +193,8 @@ class Registry(Mapping): # map fields on their dependents dependents = { field: set(dep for dep, _ in model._field_triggers[field] if dep != field) - for model in self.itervalues() - for field in model._fields.itervalues() + for model in pycompat.values(self) + for field in pycompat.values(model._fields) } # sort them topologically, and associate a sequence number to each field mapping = { @@ -261,7 +262,7 @@ class Registry(Mapping): env['ir.model']._add_manual_models() # prepare the setup on all models - models = env.values() + models = list(pycompat.values(env)) for model in models: model._prepare_setup() @@ -310,18 +311,18 @@ class Registry(Mapping): # make sure all tables are present missing = [name - for name, model in env.items() + for name, model in pycompat.items(env) if not model._abstract and not table_exists(cr, model._table)] if missing: _logger.warning("Models have no table: %s.", ", ".join(missing)) # recreate missing tables following model dependencies - deps = {name: model._depends for name, model in env.items()} + deps = {name: model._depends for name, model in pycompat.items(env)} for name in topological_sort(deps): if name in missing: _logger.info("Recreate table of model %s.", name) env[name].init() # check again, and log errors if tables are still missing - for name, model in env.items(): + for name, model in pycompat.items(env): if not model._abstract and not table_exists(cr, model._table): _logger.error("Model %s has no table.", name) @@ -340,7 +341,7 @@ class Registry(Mapping): """ Clear the caches associated to methods decorated with ``tools.ormcache`` or ``tools.ormcache_multi`` for all the models. """ - for model in self.models.itervalues(): + for model in pycompat.values(self.models): model.clear_caches() def setup_signaling(self): diff --git a/odoo/osv/expression.py b/odoo/osv/expression.py index b98fca44b39..8df300a2bd4 100644 --- a/odoo/osv/expression.py +++ b/odoo/osv/expression.py @@ -1018,7 +1018,7 @@ class expression(object): subop = 'not inselect' if operator in NEGATIVE_TERM_OPERATORS else 'inselect' subquery = 'SELECT "%s" FROM "%s" WHERE "%s" IN %%s' % (rel_id1, rel_table, rel_id2) # avoid flattening of argument in to_sql() - subquery = cr.mogrify(subquery, [tuple(filter(None, res_ids))]) + subquery = cr.mogrify(subquery, [tuple(it for it in res_ids if it)]) push(create_substitution_leaf(leaf, ('id', subop, (subquery, [])), internal=True)) if call_null_m2m: @@ -1200,7 +1200,7 @@ class expression(object): else: field = model._fields[left] instr = ','.join([field.column_format] * len(params)) - params = map(partial(field.convert_to_column, record=model), params) + params = [field.convert_to_column(p, record=model) for p in params] query = '(%s."%s" %s (%s))' % (table_alias, left, operator, instr) else: # The case for (left, 'in', []) or (left, 'not in', []). diff --git a/odoo/osv/orm.py b/odoo/osv/orm.py index 6a815c9577a..c06c4e66496 100644 --- a/odoo/osv/orm.py +++ b/odoo/osv/orm.py @@ -1,6 +1,7 @@ import json from lxml import etree +from odoo.tools import pycompat from ..exceptions import except_orm from ..models import ( MetaModel, @@ -35,12 +36,12 @@ def transfer_field_to_modifiers(field, modifiers): for attr in ('invisible', 'readonly', 'required'): state_exceptions[attr] = [] default_values[attr] = bool(field.get(attr)) - for state, modifs in (field.get("states",{})).items(): + for state, modifs in pycompat.items(field.get("states",{})): for modif in modifs: if default_values[modif[0]] != modif[1]: state_exceptions[modif[0]].append(state) - for attr, default_value in default_values.items(): + for attr, default_value in pycompat.items(default_values): if state_exceptions[attr]: modifiers[attr] = [("state", "not in" if default_value else "in", state_exceptions[attr])] else: diff --git a/odoo/release.py b/odoo/release.py index 51385a7a02b..c563d9ff4cd 100644 --- a/odoo/release.py +++ b/odoo/release.py @@ -13,8 +13,8 @@ RELEASE_LEVELS_DISPLAY = {ALPHA: ALPHA, # (6,1,0,'beta',0) < (6,1,0,'candidate',1) < (6,1,0,'candidate',2) # (6,1,0,'candidate',2) < (6,1,0,'final',0) < (6,1,2,'final',0) version_info = (11, 0, 0, ALPHA, 1, '') -version = '.'.join(map(str, version_info[:2])) + RELEASE_LEVELS_DISPLAY[version_info[3]] + str(version_info[4] or '') + version_info[5] -series = serie = major_version = '.'.join(map(str, version_info[:2])) +version = '.'.join(str(s) for s in version_info[:2]) + RELEASE_LEVELS_DISPLAY[version_info[3]] + str(version_info[4] or '') + version_info[5] +series = serie = major_version = '.'.join(str(s) for s in version_info[:2]) product_name = 'Odoo' description = 'Odoo Server' diff --git a/odoo/service/db.py b/odoo/service/db.py index e20dcb7442a..e451b151d31 100644 --- a/odoo/service/db.py +++ b/odoo/service/db.py @@ -351,7 +351,7 @@ def list_db_incompatible(databases): :return: A list of databases that are incompatible """ incompatible_databases = [] - server_version = '.'.join(map(str, version_info[:2])) + server_version = '.'.join(str(v) for v in version_info[:2]) for database_name in databases: with closing(db_connect(database_name).cursor()) as cr: if odoo.tools.table_exists(cr, 'ir_module_module'): diff --git a/odoo/service/model.py b/odoo/service/model.py index ba243bf432c..78a07460a38 100644 --- a/odoo/service/model.py +++ b/odoo/service/model.py @@ -137,7 +137,7 @@ def check(f): time.sleep(wait_time) except IntegrityError as inst: registry = odoo.registry(dbname) - for key in registry._sql_error.keys(): + for key in pycompat.keys(registry._sql_error): if key in inst[0]: raise ValidationError(tr(registry._sql_error[key], 'sql_constraint') or inst[0]) if inst.pgcode in (errorcodes.NOT_NULL_VIOLATION, errorcodes.FOREIGN_KEY_VIOLATION, errorcodes.RESTRICT_VIOLATION): diff --git a/odoo/service/server.py b/odoo/service/server.py index 5de54d8188e..fa6ed422f81 100644 --- a/odoo/service/server.py +++ b/odoo/service/server.py @@ -40,7 +40,7 @@ from odoo.modules.module import run_unit_tests, runs_post_install from odoo.modules.registry import Registry from odoo.release import nt_service_name import odoo.tools.config as config -from odoo.tools import stripped_sys_argv, dumpstacks, log_ormcache_stats +from odoo.tools import stripped_sys_argv, dumpstacks, log_ormcache_stats, pycompat _logger = logging.getLogger(__name__) @@ -216,7 +216,7 @@ class ThreadedServer(CommonServer): time.sleep(SLEEP_INTERVAL + number) # Steve Reich timing style registries = odoo.modules.registry.Registry.registries _logger.debug('cron%d polling for jobs', number) - for db_name, registry in registries.iteritems(): + for db_name, registry in pycompat.items(registries): while registry.ready: try: acquired = odoo.addons.base.ir.ir_cron.ir_cron._acquire_job(db_name) @@ -513,7 +513,7 @@ class PreforkServer(CommonServer): def process_timeout(self): now = time.time() - for (pid, worker) in self.workers.items(): + for (pid, worker) in pycompat.items(self.workers): if worker.watchdog_timeout is not None and \ (now - worker.watchdog_time) >= worker.watchdog_timeout: _logger.error("%s (%s) timeout after %ss", @@ -534,8 +534,8 @@ class PreforkServer(CommonServer): def sleep(self): try: # map of fd -> worker - fds = dict([(w.watchdog_pipe[0], w) for k, w in self.workers.items()]) - fd_in = fds.keys() + [self.pipe[0]] + fds = {w.watchdog_pipe[0]: w for k, w in pycompat.items(self.workers)} + fd_in = list(pycompat.keys(fds)) + [self.pipe[0]] # check for ping or internal wakeups ready = select.select(fd_in, [], [], self.beat) # update worker watchdogs @@ -584,7 +584,7 @@ class PreforkServer(CommonServer): if graceful: _logger.info("Stopping gracefully") limit = time.time() + self.timeout - for pid in self.workers.keys(): + for pid in self.workers: self.worker_kill(pid, signal.SIGINT) while self.workers and time.time() < limit: try: @@ -596,7 +596,7 @@ class PreforkServer(CommonServer): time.sleep(0.1) else: _logger.info("Stopping forcefully") - for pid in self.workers.keys(): + for pid in self.workers: self.worker_kill(pid, signal.SIGTERM) if self.socket: self.socket.close() @@ -874,7 +874,7 @@ def load_test_file_yml(registry, test_file): def load_test_file_py(registry, test_file): # Locate python module based on its filename and run the tests test_path, _ = os.path.splitext(os.path.abspath(test_file)) - for mod_name, mod_mod in sys.modules.items(): + for mod_name, mod_mod in pycompat.items(sys.modules): if mod_mod: mod_path, _ = os.path.splitext(getattr(mod_mod, '__file__', '')) if test_path == mod_path: diff --git a/odoo/sql_db.py b/odoo/sql_db.py index 9a438d387de..a722c3ea41b 100644 --- a/odoo/sql_db.py +++ b/odoo/sql_db.py @@ -47,7 +47,7 @@ def undecimalize(symb, cr): return None return float(symb) -for name, typeoid in types_mapping.items(): +for name, typeoid in pycompat.items(types_mapping): psycopg2.extensions.register_type(psycopg2.extensions.new_type(typeoid, name, lambda x, cr: x)) psycopg2.extensions.register_type(psycopg2.extensions.new_type((700, 701, 1700,), 'float', undecimalize)) @@ -190,9 +190,9 @@ class Cursor(object): row = self._obj.fetchone() return row and self.__build_dict(row) def dictfetchmany(self, size): - return map(self.__build_dict, self._obj.fetchmany(size)) + return [self.__build_dict(row) for row in self._obj.fetchmany(size)] def dictfetchall(self): - return map(self.__build_dict, self._obj.fetchall()) + return [self.__build_dict(row) for row in self._obj.fetchall()] def __del__(self): if not self._closed and not self._cnx.closed: @@ -260,7 +260,7 @@ class Cursor(object): sqllogs = {'from': self.sql_from_log, 'into': self.sql_into_log} sum = 0 if sqllogs[type]: - sqllogitems = sqllogs[type].items() + sqllogitems = pycompat.items(sqllogs[type]) _logger.debug("SQL LOG %s:", type) for r in sorted(sqllogitems, key=lambda k: k[1]): delay = timedelta(microseconds=r[1][1]) @@ -473,7 +473,7 @@ class LazyCursor(object): if cr is None: from odoo import registry cr = self._cursor = registry(self.dbname).cursor() - for _ in pycompat.range(self._depth): + for _ in range(self._depth): cr.__enter__() return getattr(cr, name) diff --git a/odoo/tests/common.py b/odoo/tests/common.py index 78cfbfbdbf9..a491c23ef76 100644 --- a/odoo/tests/common.py +++ b/odoo/tests/common.py @@ -135,7 +135,7 @@ class BaseCase(unittest.TestCase): def shortDescription(self): doc = self._testMethodDoc - return doc and ' '.join(filter(None, map(str.strip, doc.splitlines()))) or None + return doc and ' '.join(l.strip() for l in doc.splitlines() if not l.isspace()) or None class TransactionCase(BaseCase): diff --git a/odoo/tools/amount_to_text_en.py b/odoo/tools/amount_to_text_en.py index 6a8e7020b4a..b1854b1c4d6 100644 --- a/odoo/tools/amount_to_text_en.py +++ b/odoo/tools/amount_to_text_en.py @@ -71,7 +71,7 @@ def amount_to_text(number, currency): cents_number = int(list[1]) cents_name = (cents_number > 1) and 'Cents' or 'Cent' - return ' '.join(filter(None, [start_word, units_name, (start_word or units_name) and (end_word or cents_name) and 'and', end_word, cents_name])) + return ' '.join(w for w in [start_word, units_name, (start_word or units_name) and (end_word or cents_name) and 'and', end_word, cents_name] if w) #------------------------------------------------------------- diff --git a/odoo/tools/appdirs.py b/odoo/tools/appdirs.py index f98fa1f8ae3..8617dabfa4f 100644 --- a/odoo/tools/appdirs.py +++ b/odoo/tools/appdirs.py @@ -15,7 +15,7 @@ from __future__ import print_function # - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html __version_info__ = (1, 3, 0) -__version__ = '.'.join(map(str, __version_info__)) +__version__ = '.'.join(str(v) for v in __version_info__) import sys diff --git a/odoo/tools/cache.py b/odoo/tools/cache.py index 48dc42ba04c..acc01895a48 100644 --- a/odoo/tools/cache.py +++ b/odoo/tools/cache.py @@ -8,6 +8,8 @@ from decorator import decorator from inspect import formatargspec, getargspec import logging +from odoo.tools import pycompat + unsafe_eval = eval _logger = logging.getLogger(__name__) @@ -112,7 +114,7 @@ class ormcache_context(ormcache): spec = getargspec(self.method) args = formatargspec(*spec)[1:-1] cont_expr = "(context or {})" if 'context' in spec.args else "self._context" - keys_expr = "tuple(map(%s.get, %r))" % (cont_expr, self.keys) + keys_expr = "tuple(%s.get(k) for k in %r)" % (cont_expr, self.keys) if self.args: code = "lambda %s: (%s, %s)" % (args, ", ".join(self.args), keys_expr) else: @@ -200,10 +202,10 @@ def log_ormcache_stats(sig=None, frame=None): me = threading.currentThread() me_dbname = me.dbname entries = defaultdict(int) - for dbname, reg in Registry.registries.iteritems(): - for key in reg.cache.iterkeys(): + for dbname, reg in pycompat.items(Registry.registries): + for key in reg.cache: entries[(dbname,) + key[:2]] += 1 - for key, count in sorted(entries.items()): + for key, count in sorted(pycompat.items(entries)): dbname, model_name, method = key me.dbname = dbname stat = STAT[key] diff --git a/odoo/tools/config.py b/odoo/tools/config.py index 3a227fb5142..0f51fbcb269 100644 --- a/odoo/tools/config.py +++ b/odoo/tools/config.py @@ -13,7 +13,8 @@ import odoo.conf import odoo.loglevels as loglevels import logging import odoo.release as release -from . import appdirs +from . import appdirs, pycompat + class MyOption (optparse.Option, object): """ optparse Option with two additional attributes. @@ -55,7 +56,7 @@ def _deduplicate_loggers(loggers): # there are no duplicates within the output sequence return ( '{}:{}'.format(logger, level) - for logger, level in dict(it.split(':') for it in loggers).iteritems() + for logger, level in pycompat.items(dict(it.split(':') for it in loggers)) ) @@ -453,10 +454,10 @@ class configmanager(object): self.options['demo'] = (dict(self.options['init']) if not self.options['without_demo'] else {}) self.options['update'] = opt.update and dict.fromkeys(opt.update.split(','), 1) or {} - self.options['translate_modules'] = opt.translate_modules and map(lambda m: m.strip(), opt.translate_modules.split(',')) or ['all'] + self.options['translate_modules'] = opt.translate_modules and [m.strip() for m in opt.translate_modules.split(',')] or ['all'] self.options['translate_modules'].sort() - dev_split = opt.dev_mode and map(str.strip, opt.dev_mode.split(',')) or [] + dev_split = opt.dev_mode and [s.strip() for s in opt.dev_mode.split(',')] or [] self.options['dev_mode'] = 'all' in dev_split and dev_split + ['pdb', 'reload', 'qweb', 'werkzeug', 'xml'] or dev_split if opt.pg_path: @@ -527,9 +528,9 @@ class configmanager(object): def save(self): p = ConfigParser.ConfigParser() - loglevelnames = dict(zip(self._LOGLEVELS.values(), self._LOGLEVELS.keys())) + loglevelnames = dict(pycompat.izip(pycompat.values(self._LOGLEVELS), pycompat.keys(self._LOGLEVELS))) p.add_section('options') - for opt in sorted(self.options.keys()): + for opt in sorted(pycompat.keys(self.options)): if opt in ('version', 'language', 'translate_out', 'translate_in', 'overwrite_existing_translations', 'init', 'update'): continue if opt in self.blacklist_for_save: @@ -541,9 +542,9 @@ class configmanager(object): else: p.set('options', opt, self.options[opt]) - for sec in sorted(self.misc.keys()): + for sec in sorted(pycompat.keys(self.misc)): p.add_section(sec) - for opt in sorted(self.misc[sec].keys()): + for opt in sorted(pycompat.keys(self.misc[sec])): p.set(sec,opt,self.misc[sec][opt]) # try to create the directories and write the file diff --git a/odoo/tools/convert.py b/odoo/tools/convert.py index 815ee9a64f1..7e8e9f6e3ce 100644 --- a/odoo/tools/convert.py +++ b/odoo/tools/convert.py @@ -98,7 +98,7 @@ def _eval_xml(self, node, env): q = safe_eval(f_search, idref2) ids = env[f_model].search(q).ids if f_use != 'id': - ids = map(lambda x: x[f_use], env[f_model].browse(ids).read([f_use])) + ids = [x[f_use] for x in env[f_model].browse(ids).read([f_use])] _fields = env[f_model]._fields if (f_name in _fields) and _fields[f_name].type == 'many2many': return ids @@ -659,7 +659,7 @@ form: module.record_id""" % (xml_id,) _fields = self.env[rec_model]._fields # if the current field is many2many if (f_name in _fields) and _fields[f_name].type == 'many2many': - f_val = [(6, 0, map(lambda x: x[f_use], s))] + f_val = [(6, 0, [x[f_use] for x in s])] elif len(s): # otherwise (we are probably in a many2one field), # take the first element of the search @@ -707,7 +707,7 @@ form: module.record_id""" % (xml_id,) 'model': 'ir.ui.view', } for att in ['forcecreate', 'context']: - if att in el.keys(): + if att in el.attrib: record_attrs[att] = el.attrib.pop(att) Field = builder.E.field @@ -733,7 +733,7 @@ form: module.record_id""" % (xml_id,) record.append(Field(name='customize_show', eval=el.get('customize_show'))) groups = el.attrib.pop('groups', None) if groups: - grp_lst = map(lambda x: "ref('%s')" % x, groups.split(',')) + grp_lst = [("ref('%s')" % x) for x in groups.split(',')] record.append(Field(name="groups_id", eval="[(6, 0, ["+', '.join(grp_lst)+"])]")) if el.attrib.pop('page', None) == 'True': record.append(Field(name="page", eval="True")) @@ -854,7 +854,7 @@ def convert_csv_import(cr, module, fname, csvcontent, idref=None, mode='init', if not (line and any(line)): continue try: - datas.append(map(ustr, line)) + datas.append([ustr(v) for v in line]) except Exception: _logger.error("Cannot import the line: %s", line) diff --git a/odoo/tools/float_utils.py b/odoo/tools/float_utils.py index 8c71d7a29b2..fa150a1d902 100644 --- a/odoo/tools/float_utils.py +++ b/odoo/tools/float_utils.py @@ -217,10 +217,10 @@ if __name__ == "__main__": fractions = [.0, .015, .01499, .675, .67499, .4555, .4555, .45555] expecteds = ['.00', '.02', '.01', '.68', '.67', '.46', '.456', '.4556'] precisions = [2, 2, 2, 2, 2, 2, 3, 4] - for magnitude in pycompat.range(7): - for frac, exp, prec in zip(fractions, expecteds, precisions): + for magnitude in range(7): + for frac, exp, prec in pycompat.izip(fractions, expecteds, precisions): for sign in [-1,1]: - for x in pycompat.range(0, 10000, 97): + for x in range(0, 10000, 97): n = x * 10**magnitude f = sign * (n + frac) f_exp = ('-' if f != 0 and sign == -1 else '') + str(n) + exp diff --git a/odoo/tools/func.py b/odoo/tools/func.py index 8f49864eda2..2e204206f04 100644 --- a/odoo/tools/func.py +++ b/odoo/tools/func.py @@ -33,7 +33,7 @@ class lazy_property(object): """ Reset all lazy properties on the instance `obj`. """ cls = type(obj) obj_dict = vars(obj) - for name in obj_dict.keys(): + for name in list(obj_dict): if isinstance(getattr(cls, name, None), lazy_property): obj_dict.pop(name) diff --git a/odoo/tools/graph.py b/odoo/tools/graph.py index 82fc0e0978a..5e3edabf05a 100755 --- a/odoo/tools/graph.py +++ b/odoo/tools/graph.py @@ -5,6 +5,9 @@ import operator import math +from odoo.tools import pycompat + + class graph(object): def __init__(self, nodes, transitions, no_ancester=None): """Initialize graph's object @@ -256,7 +259,7 @@ class graph(object): """The ranks are normalized by setting the least rank to zero. """ - least_rank = min(map(lambda x: x['x'], self.result.values())) + least_rank = min(x['x'] for x in pycompat.values(self.result.values)) if least_rank!=0: for node in self.result: @@ -380,7 +383,7 @@ class graph(object): """Finds actual-order of the nodes with respect to maximum number of nodes in a rank in component """ mid_pos = 0.0 - max_level = max(map(lambda x: len(x), self.levels.values())) + max_level = max(len(x) for x in pycompat.values(self.levels.values)) for level in self.levels: if level: @@ -471,7 +474,7 @@ class graph(object): """ if self.Is_Cyclic: - max_level = max(map(lambda x: len(x), self.levels.values())) + max_level = max(len(x) for x in pycompat.values(self.levels.values)) if max_level%2: self.result[self.start]['y'] = (max_level+1)/2 + self.max_order + (self.max_order and 1) @@ -483,7 +486,7 @@ class graph(object): else: self.result[self.start]['y'] = 0 self.tree_order(self.start, 0) - min_order = math.fabs(min(map(lambda x: x['y'], self.result.values()))) + min_order = math.fabs(min(x['y'] for x in pycompat.values(self.result.values))) index = self.start_nodes.index(self.start) same = False @@ -537,7 +540,7 @@ class graph(object): self.result[start]['y'] = base + factor factor += 1 - self.max_order = max(map(lambda x: x['y'], self.result.values())) + self.max_order = max(x['y'] for x in pycompat.values(self.result.values)) def find_starts(self): """Finds other start nodes of the graph in the case when graph is disconneted @@ -626,7 +629,7 @@ class graph(object): self.make_chain() self.preprocess_order() self.order = {} - max_rank = max(map(lambda x: x, self.levels.keys())) + max_rank = max(x for x in self.levels) for i in range(max_rank+1): self.order[i] = 0 @@ -657,7 +660,7 @@ class graph(object): for node in self.no_ancester: for sec_node in self.transitions.get(node, []): - if sec_node in self.partial_order.keys(): + if sec_node in pycompat.keys(self.partial_order): self.transitions[self.start_nodes[0]].append(node) break @@ -743,7 +746,7 @@ if __name__=='__main__': for node in nodes: node_res[node] = result[node] - for name,node in node_res.items(): + for name,node in pycompat.items(node_res): draw.arc( (int(node['y']-radius), int(node['x']-radius),int(node['y']+radius), int(node['x']+radius) ), 0, 360, (128,128,128)) draw.text( (int(node['y']), int(node['x'])), str(name), (128,128,128)) diff --git a/odoo/tools/lru.py b/odoo/tools/lru.py index 7c485a039d3..a9e4a8b8f18 100644 --- a/odoo/tools/lru.py +++ b/odoo/tools/lru.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- # taken from http://code.activestate.com/recipes/252524-length-limited-o1-lru-cache-implementation/ import threading + +from odoo.tools import pycompat from .func import synchronized __all__ = ['LRU'] @@ -92,6 +94,7 @@ class LRU(object): def __len__(self): return len(self.d) + # FIXME: should this have a P2 and a P3 version or something? @synchronized() def iteritems(self): cur = self.first @@ -106,12 +109,12 @@ class LRU(object): @synchronized() def itervalues(self): - for i,j in self.iteritems(): + for i,j in pycompat.items(self): yield j @synchronized() def keys(self): - return self.d.keys() + return list(pycompat.keys(self.d)) @synchronized() def pop(self,key): diff --git a/odoo/tools/mail.py b/odoo/tools/mail.py index 490006082d5..9689512df00 100644 --- a/odoo/tools/mail.py +++ b/odoo/tools/mail.py @@ -16,6 +16,7 @@ from lxml import etree import odoo from odoo.loglevels import ustr +from odoo.tools import pycompat _logger = logging.getLogger(__name__) @@ -84,7 +85,7 @@ class _Cleaner(clean.Cleaner): new_node.text = text new_node.tail = tail if attrs: - for key, val in attrs.iteritems(): + for key, val in pycompat.items(attrs): new_node.set(key, val) return new_node @@ -153,7 +154,7 @@ class _Cleaner(clean.Cleaner): if style[0].lower() in self._style_whitelist: valid_styles[style[0].lower()] = style[1] if valid_styles: - el.attrib['style'] = '; '.join('%s: %s' % (key, val) for (key, val) in valid_styles.iteritems()) + el.attrib['style'] = '; '.join('%s: %s' % (key, val) for (key, val) in pycompat.items(valid_styles)) else: del el.attrib['style'] @@ -530,4 +531,4 @@ def decode_smtp_header(smtp_header): # was mail_thread.decode_header() def decode_message_header(message, header, separator=' '): - return separator.join(map(decode_smtp_header, filter(None, message.get_all(header, [])))) + return separator.join(decode_smtp_header(h) for h in message.get_all(header, []) if h) diff --git a/odoo/tools/mimetypes.py b/odoo/tools/mimetypes.py index 370c4b2e85b..3abdc22963d 100644 --- a/odoo/tools/mimetypes.py +++ b/odoo/tools/mimetypes.py @@ -10,6 +10,8 @@ import logging import re import zipfile +from odoo.tools import pycompat + __all__ = ['guess_mimetype'] _logger = logging.getLogger(__name__) @@ -33,7 +35,7 @@ def _check_ooxml(data): # then there is a directory whose name denotes the type of the file: # word, pt (powerpoint) or xl (excel) - for dirname, mime in _ooxml_dirs.iteritems(): + for dirname, mime in pycompat.items(_ooxml_dirs): if any(entry.startswith(dirname) for entry in filenames): return mime diff --git a/odoo/tools/misc.py b/odoo/tools/misc.py index 492be9951e4..81c27105ff1 100644 --- a/odoo/tools/misc.py +++ b/odoo/tools/misc.py @@ -22,7 +22,7 @@ import time import werkzeug.utils import zipfile from collections import defaultdict, Iterable, Mapping, MutableSet, OrderedDict -from itertools import islice, izip, groupby, repeat +from itertools import islice, groupby, repeat from lxml import etree from .which import which @@ -282,7 +282,7 @@ def flatten(list): r = [] for e in list: if isiterable(e): - map(r.append, flatten(e)) + r.extend(flatten(e)) else: r.append(e) return r @@ -304,7 +304,7 @@ def reverse_enumerate(l): File "", line 1, in StopIteration """ - return izip(pycompat.range(len(l)-1, -1, -1), reversed(l)) + return pycompat.izip(range(len(l)-1, -1, -1), reversed(l)) def partition(pred, elems): """ Return a pair equivalent to: @@ -337,10 +337,12 @@ def topological_sort(elems): visited.add(n) if n in elems: # first visit all dependencies of n, then append n to result - map(visit, elems[n]) + for it in elems[n]: + visit(it) result.append(n) - map(visit, elems) + for el in elems: + visit(el) return result @@ -458,7 +460,7 @@ def logged(f): vector = ['Call -> function: %r' % f] for i, arg in enumerate(args): vector.append(' arg %02d: %s' % (i, pformat(arg))) - for key, value in kwargs.items(): + for key, value in pycompat.items(kwargs): vector.append(' kwarg %10s: %s' % (key, pformat(value))) timeb4 = time.time() @@ -523,9 +525,9 @@ def detect_ip_addr(): # try 32 bit kernel: if ip_addr is None: - ifaces = filter(None, [namestr[i:i+32].split('\0', 1)[0] for i in range(0, outbytes, 32)]) + ifaces = [namestr[i:i+32].split('\0', 1)[0] for i in range(0, outbytes, 32)] - for ifname in [iface for iface in ifaces if iface != 'lo']: + for ifname in [iface for iface in ifaces if iface if iface != 'lo']: ip_addr = socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, pack('256s', ifname[:15]))[20:24]) break @@ -909,7 +911,7 @@ def dumpstacks(sig=None, frame=None): 'uid': getattr(th, 'uid', 'n/a'), 'dbname': getattr(th, 'dbname', 'n/a')} for th in threading.enumerate()} - for threadId, stack in sys._current_frames().items(): + for threadId, stack in pycompat.items(sys._current_frames()): thread_info = threads_info.get(threadId, {}) code.append("\n# Thread: %s (id:%s) (db:%s) (uid:%s)" % (thread_info.get('name', 'n/a'), @@ -939,7 +941,7 @@ def freehash(arg): if isinstance(arg, Mapping): return hash(frozendict(arg)) elif isinstance(arg, Iterable): - return hash(frozenset(map(freehash, arg))) + return hash(frozenset(freehash(item) for item in arg)) else: return id(arg) @@ -960,7 +962,7 @@ class frozendict(dict): def update(self, *args, **kwargs): raise NotImplementedError("'update' not supported on frozendict") def __hash__(self): - return hash(frozenset((key, freehash(val)) for key, val in self.iteritems())) + return hash(frozenset((key, freehash(val)) for key, val in pycompat.items(self))) class Collector(Mapping): """ A mapping from keys to lists. This is essentially a space optimization @@ -1122,7 +1124,7 @@ def format_date(env, value, lang_code=False, date_format=False): def _consteq(str1, str2): """ Constant-time string comparison. Suitable to compare bytestrings of fixed, known length only, because length difference is optimized. """ - return len(str1) == len(str2) and sum(ord(x)^ord(y) for x, y in zip(str1, str2)) == 0 + return len(str1) == len(str2) and sum(ord(x)^ord(y) for x, y in pycompat.izip(str1, str2)) == 0 consteq = getattr(passlib.utils, 'consteq', _consteq) diff --git a/odoo/tools/osutil.py b/odoo/tools/osutil.py index af12c6558b2..2bb180a1d17 100644 --- a/odoo/tools/osutil.py +++ b/odoo/tools/osutil.py @@ -39,7 +39,7 @@ def walksymlinks(top, topdown=True, onerror=None): if topdown: yield dirpath, dirnames, filenames - symlinks = filter(lambda dirname: os.path.islink(os.path.join(dirpath, dirname)), dirnames) + symlinks = (dirname for dirname in dirnames if os.path.islink(os.path.join(dirpath, dirname))) for s in symlinks: for x in walksymlinks(os.path.join(dirpath, s), topdown, onerror): yield x diff --git a/odoo/tools/parse_version.py b/odoo/tools/parse_version.py index 348fb3664a6..e9ff071c0b6 100644 --- a/odoo/tools/parse_version.py +++ b/odoo/tools/parse_version.py @@ -7,6 +7,8 @@ from __future__ import print_function import re +from odoo.tools import pycompat + component_re = re.compile(r'(\d+ | [a-z]+ | \.| -)', re.VERBOSE) replace = {'pre':'c', 'preview':'c','-':'final-','_':'final-','rc':'c','dev':'@','saas':'','~':''}.get @@ -72,7 +74,7 @@ if __name__ == '__main__': if verbose: print(v, pv) - for a, b in zip(pvs, pvs[1:]): + for a, b in pycompat.izip(pvs, pvs[1:]): assert a < b, '%s < %s == %s' % (a, b, a < b) chk(('0', '4.2', '4.2.3.4', '5.0.0-alpha', '5.0.0-rc1', '5.0.0-rc1.1', '5.0.0_rc2', '5.0.0_rc3', '5.0.0'), False) diff --git a/odoo/tools/pycompat.py b/odoo/tools/pycompat.py index 50b6f492a59..3fbb847a9fa 100644 --- a/odoo/tools/pycompat.py +++ b/odoo/tools/pycompat.py @@ -6,10 +6,12 @@ import sys PY2 = sys.version_info[0] == 2 if PY2: - # pylint: disable=long-builtin,xrange-builtin + # pylint: disable=long-builtin,dict-iter-method integer_types = (int, long) - range = xrange + keys = lambda d: iter(d.iterkeys()) + values = lambda d: iter(d.itervalues()) + items = lambda d: iter(d.iteritems()) # noinspection PyUnresolvedReferences from itertools import imap, izip, ifilter @@ -19,9 +21,12 @@ if PY2: del cls.__next__ return cls else: + # pylint: disable=bad-functions integer_types = (int,) - range = range + keys = lambda d: iter(d.keys()) + values = lambda d: iter(d.values()) + items = lambda d: iter(d.items()) imap = map izip = zip diff --git a/odoo/tools/safe_eval.py b/odoo/tools/safe_eval.py index ebe97619e35..1897486eb82 100644 --- a/odoo/tools/safe_eval.py +++ b/odoo/tools/safe_eval.py @@ -23,7 +23,6 @@ from types import CodeType import logging import werkzeug -from odoo.tools import pycompat from .misc import ustr import odoo @@ -251,7 +250,7 @@ _BUILTINS = { 'divmod': divmod, 'isinstance': isinstance, 'range': range, - 'xrange': pycompat.range, + 'xrange': range, 'zip': zip, 'Exception': Exception, } diff --git a/odoo/tools/test_reports.py b/odoo/tools/test_reports.py index 17065848129..cad44431c1d 100644 --- a/odoo/tools/test_reports.py +++ b/odoo/tools/test_reports.py @@ -174,7 +174,7 @@ def try_report_action(cr, uid, action_id, active_model=None, active_ids=None, view_data.update(wiz_data) _logger.debug("View data is: %r", view_data) - for fk, field in view_res.get('fields',{}).items(): + for fk, field in pycompat.items(view_res.get('fields',{})): # Default fields returns list of int, while at create() # we need to send a [(6,0,[int,..])] if field['type'] in ('one2many', 'many2many') \ diff --git a/odoo/tools/translate.py b/odoo/tools/translate.py index dcdb95d0955..b2996af6ae3 100644 --- a/odoo/tools/translate.py +++ b/odoo/tools/translate.py @@ -255,7 +255,7 @@ def translate_xml_node(node, callback, method, parser=None): append_content(result, translate_content(todo) if todo_has_text else todo) # translate the required attributes - for name, value in result.items(): + for name, value in pycompat.items(result.attrib): if name in TRANSLATED_ATTRS: result.set(name, translate_text(value) or value) @@ -660,7 +660,7 @@ def trans_export(lang, modules, buffer, format, cr): row.setdefault('tnrs', []).append((type, name, res_id)) row.setdefault('comments', set()).update(comments) - for src, row in sorted(grouped_rows.items()): + for src, row in sorted(pycompat.items(grouped_rows)): if not lang: # translation template, so no translation value row['translation'] = '' @@ -674,7 +674,7 @@ def trans_export(lang, modules, buffer, format, cr): module = row[0] rows_by_module.setdefault(module, []).append(row) tmpdir = tempfile.mkdtemp() - for mod, modrows in rows_by_module.items(): + for mod, modrows in pycompat.items(rows_by_module): tmpmoddir = join(tmpdir, mod, 'i18n') os.makedirs(tmpmoddir) pofilename = (lang if lang else mod) + ".po" + ('t' if not lang else '') @@ -1073,7 +1073,7 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True, dic = dict.fromkeys(('type', 'name', 'res_id', 'src', 'value', 'comments', 'imd_model', 'imd_name', 'module')) dic['lang'] = lang - dic.update(zip(fields, row)) + dic.update(pycompat.izip(fields, row)) # discard the target from the POT targets. src = dic['src'] @@ -1111,7 +1111,7 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True, # Then process the entries implied by the POT file (which is more # correct w.r.t. the targets) if some of them remain. pot_rows = [] - for src, target in pot_targets.iteritems(): + for src, target in pycompat.items(pot_targets): if target.value: for type, name, res_id in target.targets: pot_rows.append((type, name, res_id, src, target.value, target.comments)) diff --git a/odoo/tools/yaml_import.py b/odoo/tools/yaml_import.py index 36c6c248249..4cb2e7ea184 100644 --- a/odoo/tools/yaml_import.py +++ b/odoo/tools/yaml_import.py @@ -13,7 +13,7 @@ import yaml import odoo from . import assertion_report -from . import yaml_tag +from . import yaml_tag, pycompat from .config import config from .misc import file_open, DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT from odoo import SUPERUSER_ID @@ -36,8 +36,8 @@ class YamlImportAbortion(Exception): def _is_yaml_mapping(node, tag_constructor): value = isinstance(node, dict) \ - and len(node.keys()) == 1 \ - and isinstance(node.keys()[0], tag_constructor) + and len(node) == 1 \ + and isinstance(next(iter(node)), tag_constructor) return value def is_comment(node): @@ -202,7 +202,7 @@ class YamlInterpreter(object): def process_assert(self, node): if isinstance(node, dict): - assertion, expressions = node.items()[0] + assertion, expressions = next(pycompat.items(node)) else: assertion, expressions = node, [] @@ -275,7 +275,7 @@ class YamlInterpreter(object): return record_dict def process_record(self, node): - record, fields = node.items()[0] + record, fields = next(pycompat.items(node)) model = self.env[record.model] view_id = record.view if view_id and (view_id is not True) and isinstance(view_id, basestring): @@ -377,7 +377,7 @@ class YamlInterpreter(object): def process_vals(fg, vals): """ sanitize the given field values """ result = {} - for field_name, field_value in vals.iteritems(): + for field_name, field_value in pycompat.items(vals): if field_name not in fg: continue if fg[field_name]['type'] == 'many2one' and isinstance(field_value, (tuple, list)): @@ -391,14 +391,14 @@ class YamlInterpreter(object): elif isinstance(command, dict): return process_vals(sub_fg, command) return command - field_value = map(process, field_value or []) + field_value = [process(v) for v in (field_value or [])] result[field_name] = field_value return result def post_process(fg, elems, vals): """ filter out readonly fields from vals """ result = {} - for field_name, field_value in vals.iteritems(): + for field_name, field_value in pycompat.items(vals): if is_readonly(elems[field_name]): continue if fg[field_name]['type'] in ('one2many', 'many2many'): @@ -412,7 +412,7 @@ class YamlInterpreter(object): elif isinstance(command, dict): return (0, 0, post_process(sub_fg, sub_elems, command)) return command - field_value = map(process, field_value or []) + field_value = [process(v) for v in (field_value or [])] result[field_name] = field_value return result @@ -444,7 +444,7 @@ class YamlInterpreter(object): record_dict.update(process_vals(fg, result.get('value', {}))) # fill in fields, and execute onchange where necessary - for field_name, field_elem in elems.iteritems(): + for field_name, field_elem in pycompat.items(elems): assert field_name in fg, "The field '%s' is defined in the form view but not on the object '%s'!" % (field_name, model._name) if is_readonly(field_elem): # skip readonly fields @@ -474,7 +474,7 @@ class YamlInterpreter(object): result = recs.onchange(dict(record_dict, **parent_values), field_name, onchange_spec) record_dict.update(process_vals(fg, { key: val - for key, val in result.get('value', {}).iteritems() + for key, val in pycompat.items(result.get('value', {})) if key not in fields # do not shadow values explicitly set in yaml })) @@ -483,7 +483,7 @@ class YamlInterpreter(object): else: record_dict = {} - for field_name, expression in fields.iteritems(): + for field_name, expression in pycompat.items(fields): if record_dict.get(field_name): continue field_value = self._eval_field(model, field_name, expression, parent=record_dict, default=False, context=context) @@ -494,7 +494,7 @@ class YamlInterpreter(object): # should not be sent to create. This bug appears with not stored function fields in the new API. return { key: val - for key, val in record_dict.iteritems() + for key, val in pycompat.items(record_dict) for field in [model._fields[key].base_field] if field.store or field.inverse } @@ -580,7 +580,7 @@ class YamlInterpreter(object): self.sudo_env = self.env(user=SUPERUSER_ID) def process_python(self, node): - python, statements = node.items()[0] + python, statements = next(pycompat.items(node)) assert python.model or python.id, "!python node must have attribute `model` or `id`" if python.id is None: record = self.env[python.model] @@ -640,7 +640,7 @@ class YamlInterpreter(object): return args def process_function(self, node): - function, params = node.items()[0] + function, params = next(pycompat.items(node)) if self.isnoupdate(function) and self.mode != 'init': return model = self.env[function.model] @@ -770,9 +770,9 @@ class YamlInterpreter(object): def process_ir_set(self, node): if not self.mode == 'init': return False - _, fields = node.items()[0] + _, fields = next(pycompat.items(node)) res = {} - for fieldname, expression in fields.items(): + for fieldname, expression in pycompat.items(fields): if is_eval(expression): value = safe_eval(expression.expression, self.eval_context) else: @@ -878,7 +878,7 @@ class YamlInterpreter(object): elif not is_preceded_by_comment: if isinstance(node, dict): msg = "Creating %s\n with %s" - args = node.items()[0] + args = next(pycompat.items(node)) self._log(msg, *args) else: self._log(node) diff --git a/odoo/tools/yaml_tag.py b/odoo/tools/yaml_tag.py index f921c598f30..2b7141b6693 100644 --- a/odoo/tools/yaml_tag.py +++ b/odoo/tools/yaml_tag.py @@ -1,6 +1,9 @@ import yaml import logging +from . import pycompat + + class YamlTag(object): """ Superclass for constructors of custom tags defined in yaml file. @@ -13,7 +16,7 @@ class YamlTag(object): def __getattr__(self, attr): return None def __repr__(self): - return "<%s %s>" % (self.__class__.__name__, sorted(self.__dict__.items())) + return "<%s %s>" % (self.__class__.__name__, sorted(pycompat.items(self.__dict__))) class Assert(YamlTag): def __init__(self, model, id=None, severity=logging.WARNING, string="NONAME", **kwargs): -- cgit v1.2.3