[REM] pycompat: remove python 2 shims and helpers

Odoo no longer supports python 2, thus some of these helpers can and
have been replaced by python 3 built-ins, therefore there is no need for
them to stay defined.

The removed helpers are:
    * izip, imap and ifilter
    * unichr, text_type
    * implements_to_string, implements_iterator
    * string_types, integer_types
    * to_native

The python 2 shims have also been removed, and only the python 3 helpers
have been kept, because they can still be usable (i.e. accepting
both bytes and str for functions that can only accept one of the two)

[REM] pyjsparser: remove PY3 shims

They're no longer necessary as Odoo doesn't officially support python 2
anymore.

closes odoo/odoo#28519
This commit is contained in:
Adrian Torres
2018-11-29 09:28:17 +00:00
parent 52f5528cfb
commit 758382b3a7
14 changed files with 76 additions and 207 deletions
+1 -1
View File
@@ -95,7 +95,7 @@ class Web_Editor(http.Controller):
font_obj = ImageFont.truetype(addons_path + font, size)
# if received character is not a number, keep old behaviour (icon is character)
icon = pycompat.unichr(int(icon)) if icon.isdigit() else icon
icon = chr(int(icon)) if icon.isdigit() else icon
# Determine the dimensions of the icon
image = Image.new("RGBA", (size, size), color=(0, 0, 0, 0))
+15 -23
View File
@@ -27,16 +27,8 @@ __all__ = ['PyJsParser', 'parse', 'ENABLE_JS2PY_ERRORS', 'ENABLE_PYIMPORT', 'JsS
REGEXP_SPECIAL_SINGLE = ('\\', '^', '$', '*', '+', '?', '.', '[', ']', '(', ')', '{', '{', '|', '-')
ENABLE_PYIMPORT = False
ENABLE_JS2PY_ERRORS = False
PY3 = sys.version_info >= (3,0)
if PY3:
basestring = str
long = int
xrange = range
unicode = str
ESPRIMA_VERSION = '2.2.0'
DEBUG = False
# Small naming convention changes
# len -> leng
@@ -179,7 +171,7 @@ class PyJsParser:
def scanHexEscape(self, prefix):
code = 0
leng = 4 if (prefix == 'u') else 2
for i in xrange(leng):
for i in range(leng):
if self.index < self.length and isHexDigit(self.source[self.index]):
ch = self.source[self.index]
self.index += 1
@@ -442,7 +434,7 @@ class PyJsParser:
def isImplicitOctalLiteral(self):
# Implicit octal, unless there is a non-octal digit.
# (Annex B.1.1 on Numeric Literals)
for i in xrange(self.index + 1, self.length):
for i in range(self.index + 1, self.length):
ch = self.source[i];
if (ch == '8' or ch == '9'):
return False;
@@ -992,20 +984,20 @@ class PyJsParser:
except:
raise Exception("ENABLE_JS2PY_ERRORS was set to True, but Js2Py was not found!")
ENABLE_PYIMPORT = old_pyimport
error = ERRORS['SyntaxError']('Line ' + unicode(line) + ': ' + unicode(description))
error = ERRORS['SyntaxError']('Line ' + str(line) + ': ' + str(description))
error.put('index', Js(pos))
error.put('lineNumber', Js(line))
error.put('column', Js(pos - (self.lineStart if self.scanning else self.lastLineStart) + 1))
error.put('description', Js(description))
return JsToPyException(error)
else:
return JsSyntaxError('Line ' + unicode(line) + ': ' + unicode(description))
return JsSyntaxError('Line ' + str(line) + ': ' + str(description))
# Throw an exception
def throwError(self, messageFormat, *args):
msg = messageFormat % tuple(unicode(e) for e in args)
msg = messageFormat % tuple(str(e) for e in args)
raise self.createError(self.lastLineNumber, self.lastIndex, msg);
def tolerateError(self, messageFormat, *args):
@@ -1038,7 +1030,7 @@ class PyJsParser:
value = token['value']['raw'] if (typ == Token.Template) else token.get('value')
else:
value = 'ILLEGAL'
msg = msg.replace('%s', unicode(value))
msg = msg.replace('%s', str(value))
return (self.createError(token['lineNumber'], token['start'], msg) if (token and token.get('lineNumber')) else
self.createError(self.lineNumber if self.scanning else self.lastLineNumber,
@@ -1445,12 +1437,12 @@ class PyJsParser:
self.reinterpretExpressionAsPattern(expr.argument)
elif typ == Syntax.ArrayExpression:
expr['type'] = Syntax.ArrayPattern
for i in xrange(len(expr['elements'])):
for i in range(len(expr['elements'])):
if (expr['elements'][i] != null):
self.reinterpretExpressionAsPattern(expr['elements'][i])
elif typ == Syntax.ObjectExpression:
expr['type'] = Syntax.ObjectPattern
for i in xrange(len(expr['properties'])):
for i in range(len(expr['properties'])):
self.reinterpretExpressionAsPattern(expr['properties'][i]['value']);
elif Syntax.AssignmentExpression:
expr['type'] = Syntax.AssignmentPattern;
@@ -1526,7 +1518,7 @@ class PyJsParser:
if (not self.match('=>')):
self.expect('=>');
self.isBindingElement = false
for i in xrange(len(expressions)):
for i in range(len(expressions)):
self.reinterpretExpressionAsPattern(expressions[i])
return {
'type': PlaceHolders.ArrowParameterPlaceHolder,
@@ -1539,7 +1531,7 @@ class PyJsParser:
if (not self.isBindingElement):
self.throwUnexpectedToken(self.lookahead);
if (expr['type'] == Syntax.SequenceExpression):
for i in xrange(len(expr.expressions)):
for i in range(len(expr.expressions)):
self.reinterpretExpressionAsPattern(expr['expressions'][i])
else:
self.reinterpretExpressionAsPattern(expr);
@@ -1880,12 +1872,12 @@ class PyJsParser:
elif typ == Syntax.AssignmentPattern:
self.checkPatternParam(options, param.left)
elif typ == Syntax.ArrayPattern:
for i in xrange(len(param.elements)):
for i in range(len(param.elements)):
if (param.elements[i] != null):
self.checkPatternParam(options, param.elements[i]);
else:
assert typ == Syntax.ObjectPattern, 'Invalid type'
for i in xrange(len(param.properties)):
for i in range(len(param.properties)):
self.checkPatternParam(options, param.properties[i]['value']);
def reinterpretAsCoverFormalsList(self, expr):
@@ -1902,7 +1894,7 @@ class PyJsParser:
options = {
'paramSet': {}}
le = len(params)
for i in xrange(le):
for i in range(le):
param = params[i]
if param.type == Syntax.AssignmentPattern:
params[i] = param.left;
@@ -2870,7 +2862,7 @@ class PyJsParser:
raise NotImplementedError('Options not implemented! You can only use default settings.')
self.clean()
self.source = unicode(code) + ' \n ; //END' # I have to add it in order not to check for EOF every time
self.source = str(code) + ' \n ; //END' # I have to add it in order not to check for EOF every time
self.index = 0
self.lineNumber = 1 if len(self.source) > 0 else 0
self.lineStart = 0
+9 -15
View File
@@ -23,12 +23,6 @@ import sys
import unicodedata
from collections import defaultdict
PY3 = sys.version_info >= (3,0)
if PY3:
unichr = chr
xrange = range
unicode = str
token = {
'BooleanLiteral': 1,
@@ -221,7 +215,7 @@ LS = u'\u2028'
PS = u'\u2029'
U_CATEGORIES = defaultdict(list)
for c in map(unichr, range(sys.maxunicode + 1)):
for c in map(chr, range(sys.maxunicode + 1)):
U_CATEGORIES[unicodedata.category(c)].append(c)
UNICODE_LETTER = set(U_CATEGORIES['Lu']+U_CATEGORIES['Ll']+
U_CATEGORIES['Lt']+U_CATEGORIES['Lm']+
@@ -242,33 +236,33 @@ WHITE_SPACE = set((0x20, 0x09, 0x0B, 0x0C, 0xA0, 0x1680,
LINE_TERMINATORS = set((0x0A, 0x0D, 0x2028, 0x2029))
def isIdentifierStart(ch):
return (ch if isinstance(ch, unicode) else unichr(ch)) in IDENTIFIER_START
return (ch if isinstance(ch, str) else chr(ch)) in IDENTIFIER_START
def isIdentifierPart(ch):
return (ch if isinstance(ch, unicode) else unichr(ch)) in IDENTIFIER_PART
return (ch if isinstance(ch, str) else chr(ch)) in IDENTIFIER_PART
def isWhiteSpace(ch):
return (ord(ch) if isinstance(ch, unicode) else ch) in WHITE_SPACE
return (ord(ch) if isinstance(ch, str) else ch) in WHITE_SPACE
def isLineTerminator(ch):
return (ord(ch) if isinstance(ch, unicode) else ch) in LINE_TERMINATORS
return (ord(ch) if isinstance(ch, str) else ch) in LINE_TERMINATORS
OCTAL = set(('0', '1', '2', '3', '4', '5', '6', '7'))
DEC = set(('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'))
HEX = set('0123456789abcdefABCDEF')
HEX_CONV = dict(('0123456789abcdef'[n],n) for n in xrange(16))
HEX_CONV = dict(('0123456789abcdef'[n],n) for n in range(16))
for i,e in enumerate('ABCDEF', 10):
HEX_CONV[e] = i
def isDecimalDigit(ch):
return (ch if isinstance(ch, unicode) else unichr(ch)) in DEC
return (ch if isinstance(ch, str) else chr(ch)) in DEC
def isHexDigit(ch):
return (ch if isinstance(ch, unicode) else unichr(ch)) in HEX
return (ch if isinstance(ch, str) else chr(ch)) in HEX
def isOctalDigit(ch):
return (ch if isinstance(ch, unicode) else unichr(ch)) in OCTAL
return (ch if isinstance(ch, str) else chr(ch)) in OCTAL
def isFutureReservedWord(w):
return w in ('enum', 'export', 'import', 'super')
-10
View File
@@ -62,16 +62,6 @@ features whereas:
* Python 3.2 made ``range`` views more list-like (backported to 2.7)and
reintroduced ``callable``
.. warning::
While Python 3 adds plenty of great features (keyword-only parameters,
generator delegation, pathlib, ...), you must *not* use them in Odoo
until Python 2 support is dropped
.. note::
In the *very rare* cases where you *need* to differentiate between
Python 2 and Python 3, use the :data:`odoo.tools.pycompat.PY2` flag.
Semantics changes
=================
+1 -4
View File
@@ -78,10 +78,7 @@ class Contextifier(ast.NodeTransformer):
def visit_Lambda(self, node):
args = node.args
# assume we don't have any tuple parameter, just names
if pycompat.PY2:
names = [arg.id for arg in args.args]
else:
names = [arg.arg for arg in args.args]
names = [arg.arg for arg in args.args]
if args.vararg: names.append(args.vararg)
if args.kwarg: names.append(args.kwarg)
# remap defaults in case there's any
+1 -3
View File
@@ -1735,9 +1735,7 @@ class Datetime(Field):
# http://initd.org/psycopg/docs/usage.html#binary-adaptation
# Received data is returned as buffer (in Python 2) or memoryview (in Python 3).
_BINARY = memoryview
if pycompat.PY2:
#pylint: disable=buffer-builtin,undefined-variable
_BINARY = buffer
class Binary(Field):
type = 'binary'
-1
View File
@@ -193,7 +193,6 @@ LOG_ACCESS_COLUMNS = ['create_uid', 'create_date', 'write_uid', 'write_date']
MAGIC_COLUMNS = ['id'] + LOG_ACCESS_COLUMNS
@pycompat.implements_to_string
class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
""" Base class for Odoo models.
+10 -32
View File
@@ -5,6 +5,7 @@
from collections import defaultdict
import glob
import importlib.util
import logging
import os
from os.path import join as opj
@@ -14,40 +15,17 @@ import odoo.release as release
import odoo.tools as tools
from odoo.tools.parse_version import parse_version
if pycompat.PY2:
import imp
def load_script(path, module_name):
fp, fname = tools.file_open(path, pathinfo=True)
fp2 = None
# pylint: disable=file-builtin,undefined-variable
if not isinstance(fp, file):
# imp.load_source need a real file object, so we create
# one from the file-like object we get from file_open
fp2 = os.tmpfile()
fp2.write(fp.read())
fp2.seek(0)
try:
return imp.load_source(module_name, fname, fp2 or fp)
finally:
if fp:
fp.close()
if fp2:
fp2.close()
else:
import importlib.util
def load_script(path, module_name):
full_path = get_resource_path(*path.split(os.path.sep))
spec = importlib.util.spec_from_file_location(module_name, full_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
_logger = logging.getLogger(__name__)
def load_script(path, module_name):
full_path = get_resource_path(*path.split(os.path.sep))
spec = importlib.util.spec_from_file_location(module_name, full_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
class MigrationManager(object):
"""
This class manage the migration of modules
+2 -6
View File
@@ -434,12 +434,8 @@ def get_test_modules(module):
mod = importlib.import_module('.tests', modpath)
except ImportError as e: # will also catch subclass ModuleNotFoundError of P3.6
# Hide ImportErrors on `tests` sub-module, but display other exceptions
if pycompat.PY2:
if e.message.startswith('No module named') and e.message.endswith("tests"):
return []
else:
if e.name == modpath + '.tests' and e.msg.startswith('No module named'):
return []
if e.name == modpath + '.tests' and e.msg.startswith('No module named'):
return []
_logger.exception('Can not `import %s`.', module)
return []
except Exception as e:
+3 -4
View File
@@ -354,10 +354,9 @@ class BaseCase(TreeCase, MetaCase('DummyCase', (object,), {})):
doc = self._testMethodDoc
return doc and ' '.join(l.strip() for l in doc.splitlines() if not l.isspace()) or None
if not pycompat.PY2:
# turns out this thing may not be quite as useful as we thought...
def assertItemsEqual(self, a, b, msg=None):
self.assertCountEqual(a, b, msg=None)
# turns out this thing may not be quite as useful as we thought...
def assertItemsEqual(self, a, b, msg=None):
self.assertCountEqual(a, b, msg=None)
class TransactionCase(BaseCase):
+16 -19
View File
@@ -2,28 +2,25 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from __future__ import print_function
import builtins
import math
if not pycompat.PY2:
import builtins
def round(f):
# P3's builtin round differs from P2 in the following manner:
# * it rounds half to even rather than up (away from 0)
# * round(-0.) loses the sign (it returns -0 rather than 0)
# * round(x) returns an int rather than a float
#
# this compatibility shim implements Python 2's round in terms of
# Python 3's so that important rounding error under P3 can be
# trivially fixed, assuming the P2 behaviour to be debugged and
# correct.
roundf = builtins.round(f)
if builtins.round(f + 1) - roundf != 1:
return f + math.copysign(0.5, f)
# copysign ensures round(-0.) -> -0 *and* result is a float
return math.copysign(roundf, f)
else:
round = round
def round(f):
# P3's builtin round differs from P2 in the following manner:
# * it rounds half to even rather than up (away from 0)
# * round(-0.) loses the sign (it returns -0 rather than 0)
# * round(x) returns an int rather than a float
#
# this compatibility shim implements Python 2's round in terms of
# Python 3's so that important rounding error under P3 can be
# trivially fixed, assuming the P2 behaviour to be debugged and
# correct.
roundf = builtins.round(f)
if builtins.round(f + 1) - roundf != 1:
return f + math.copysign(0.5, f)
# copysign ensures round(-0.) -> -0 *and* result is a float
return math.copysign(roundf, f)
def _float_check_precision(precision_digits=None, precision_rounding=None):
assert (precision_digits is not None or precision_rounding is not None) and \
+1 -4
View File
@@ -545,10 +545,7 @@ def decode_smtp_header(smtp_header):
smtp_header = ustr(smtp_header)
if smtp_header:
text = decode_header(smtp_header.replace('\r', ''))
# The joining space will not be needed as of Python 3.3
# See https://github.com/python/cpython/commit/07ea53cb218812404cdbde820647ce6e4b2d0f8e
sep = ' ' if pycompat.PY2 else ''
return sep.join([ustr(x[0], x[1]) for x in text])
return ''.join([ustr(x[0], x[1]) for x in text])
return u''
# was mail_thread.decode_header()
-1
View File
@@ -1002,7 +1002,6 @@ class Collector(Mapping):
return len(self._map)
@pycompat.implements_to_string
class StackMap(MutableMapping):
""" A stack of mappings behaving as a single mapping, and used to implement
nested scopes. The lookups search the stack from top to bottom, and
+17 -84
View File
@@ -6,101 +6,34 @@ import csv
import codecs
import collections
import io
import sys
PY2 = sys.version_info[0] == 2
def reraise(tp, value, tb=None):
if value.__traceback__ != tb:
raise value.with_traceback(tb)
raise value
_Writer = collections.namedtuple('_Writer', 'writerow writerows')
if PY2:
# pylint: disable=long-builtin,unichr-builtin,unicode-builtin,undefined-variable
unichr = unichr
text_type = unicode
string_types = (str, unicode)
def to_native(source, encoding='utf-8', falsy_empty=False):
if not source and falsy_empty:
return ''
if isinstance(source, text_type):
return source.encode(encoding)
_reader = codecs.getreader('utf-8')
_writer = codecs.getwriter('utf-8')
return str(source)
integer_types = (int, long)
def csv_reader(stream, **params):
assert not isinstance(stream, io.TextIOBase),\
"For cross-compatibility purposes, csv_reader takes a bytes stream"
return csv.reader(_reader(stream), **params)
# noinspection PyUnresolvedReferences
from itertools import imap, izip, ifilter
def implements_to_string(cls):
cls.__unicode__ = cls.__str__
cls.__str__ = lambda x: x.__unicode__().encode('utf-8')
return cls
def csv_writer(stream, **params):
assert not isinstance(stream, io.TextIOBase), \
"For cross-compatibility purposes, csv_writer takes a bytes stream"
return csv.writer(_writer(stream), **params)
def implements_iterator(cls):
cls.next = cls.__next__
del cls.__next__
return cls
exec ('def reraise(tp, value, tb=None):\n raise tp, value, tb')
def csv_reader(stream, **params):
for row in csv.reader(stream, **params):
yield [c.decode('utf-8') for c in row]
def csv_writer(stream, **params):
w = csv.writer(stream, **params)
return _Writer(
writerow=lambda r: w.writerow([c.encode('utf-8') for c in r]),
writerows=lambda rs: w.writerows(
[c.encode('utf-8') for c in r]
for r in rs
)
)
else:
# pylint: disable=bad-functions
unichr = chr
text_type = str
string_types = (str,)
def to_native(source, encoding='utf-8', falsy_empty=False):
if not source and falsy_empty:
return ''
if isinstance(source, bytes):
return source.decode(encoding)
return str(source)
integer_types = (int,)
imap = map
izip = zip
ifilter = filter
def implements_to_string(cls):
return cls
def implements_iterator(cls):
return cls
def reraise(tp, value, tb=None):
if value.__traceback__ != tb:
raise value.with_traceback(tb)
raise value
_reader = codecs.getreader('utf-8')
_writer = codecs.getwriter('utf-8')
def csv_reader(stream, **params):
assert not isinstance(stream, io.TextIOBase),\
"For cross-compatibility purposes, csv_reader takes a bytes stream"
return csv.reader(_reader(stream), **params)
def csv_writer(stream, **params):
assert not isinstance(stream, io.TextIOBase), \
"For cross-compatibility purposes, csv_writer takes a bytes stream"
return csv.writer(_writer(stream), **params)
def to_text(source):
""" Generates a text value (an instance of text_type) from an arbitrary
""" Generates a text value (an instance of text_type) from an arbitrary
source.
* False and None are converted to empty strings
* text is passed through
* bytes are decoded as UTF-8
@@ -112,4 +45,4 @@ def to_text(source):
if isinstance(source, bytes):
return source.decode('utf-8')
return text_type(source)
return str(source)