[FIX] P3: API changes
This commit is contained in:
@@ -13,7 +13,10 @@ import pytz
|
||||
import re
|
||||
import socket
|
||||
import time
|
||||
import xmlrpclib
|
||||
try:
|
||||
from xmlrpc import client as xmlrpclib
|
||||
except ImportError:
|
||||
import xmlrpclib
|
||||
|
||||
from collections import namedtuple
|
||||
from email.message import Message
|
||||
|
||||
@@ -9,7 +9,10 @@ import cgitb
|
||||
import time
|
||||
import optparse
|
||||
import sys
|
||||
import xmlrpclib
|
||||
try:
|
||||
from xmlrpc import client as xmlrpclib
|
||||
except ImportError:
|
||||
import xmlrpclib
|
||||
import smtplib
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.base import MIMEBase
|
||||
|
||||
@@ -33,6 +33,18 @@ unsafe_eval = eval
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
# in Python 2, arguments (within the ast.arguments structure) are expressions
|
||||
# (since they can be tuples), generally
|
||||
# ast.Name(id: identifyer, ctx=ast.Param()), whereas in Python 3 they are
|
||||
# ast.arg(arg: identifier, annotation: expr?) provide a toplevel arg()
|
||||
# function which matches ast.arg producing the relevant ast.Name in Python 2.
|
||||
arg = getattr(ast, 'arg', lambda arg, annotation: ast.Name(id=arg, ctx=ast.Param()))
|
||||
# also Python 3's arguments has grown *2* new mandatory arguments, kwonlyargs
|
||||
# and kw_defaults for keyword-only arguments and their default values (if any)
|
||||
# so add a shim for *that* based on the signature of Python 3 I guess?
|
||||
arguments = ast.arguments
|
||||
if pycompat.PY2:
|
||||
arguments = lambda args, vararg, kwonlyargs, kw_defaults, kwarg, defaults: ast.arguments(args=args, vararg=vararg, kwarg=kwarg, defaults=defaults)
|
||||
####################################
|
||||
### qweb tools ###
|
||||
####################################
|
||||
@@ -76,16 +88,23 @@ class Contextifier(ast.NodeTransformer):
|
||||
def visit_Lambda(self, node):
|
||||
args = node.args
|
||||
# assume we don't have any tuple parameter, just names
|
||||
names = [arg.id for arg in args.args]
|
||||
if pycompat.PY2:
|
||||
names = [arg.id for arg in args.args]
|
||||
else:
|
||||
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
|
||||
return ast.copy_location(ast.Lambda(
|
||||
args=ast.arguments(
|
||||
args=arguments(
|
||||
args=args.args,
|
||||
defaults=[self.visit(default) for default in args.defaults],
|
||||
vararg=args.vararg,
|
||||
kwarg=args.kwarg,
|
||||
# assume we don't have any, not sure it's even possible to
|
||||
# handle that cross-version
|
||||
kwonlyargs=[],
|
||||
kw_defaults=[],
|
||||
),
|
||||
body=Contextifier(self._safe_names + tuple(names)).visit(node.body)
|
||||
), node)
|
||||
@@ -296,7 +315,7 @@ class QWeb(object):
|
||||
except Exception as e:
|
||||
path = _options['last_path_node']
|
||||
node = element.getroottree().xpath(path)
|
||||
raise QWebException("Error when compiling AST", e, path, etree.tostring(node[0]), name)
|
||||
raise QWebException("Error when compiling AST", e, path, etree.tostring(node[0], encoding='unicode'), name)
|
||||
astmod.body.extend(_options['ast_calls'])
|
||||
|
||||
if 'profile' in options:
|
||||
@@ -316,28 +335,29 @@ class QWeb(object):
|
||||
except Exception as e:
|
||||
path = _options['last_path_node']
|
||||
node = element.getroottree().xpath(path)
|
||||
raise QWebException("Error when compiling AST", e, path, node and etree.tostring(node[0]), name)
|
||||
raise QWebException("Error when compiling AST", e, path, node and etree.tostring(node[0], encoding='unicode'), name)
|
||||
|
||||
# return the wrapped function
|
||||
|
||||
def _compiled_fn(self, append, values):
|
||||
log = {'last_path_node': None}
|
||||
values = dict(self.default_values(), **values)
|
||||
new = self.default_values()
|
||||
new.update(values)
|
||||
try:
|
||||
return compiled(self, append, values, options, log)
|
||||
return compiled(self, append, new, options, log)
|
||||
except QWebException as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
path = log['last_path_node']
|
||||
element, document = self.get_template(template, options)
|
||||
node = element.getroottree().xpath(path)
|
||||
raise QWebException("Error to render compiling AST", e, path, node and etree.tostring(node[0]), name)
|
||||
raise QWebException("Error to render compiling AST", e, path, node and etree.tostring(node[0], encoding='unicode'), name)
|
||||
|
||||
return _compiled_fn
|
||||
|
||||
def default_values(self):
|
||||
""" Return attributes added to the values for each computed template. """
|
||||
return dict(format=self.format)
|
||||
return {'format': self.format}
|
||||
|
||||
def get_template(self, template, options):
|
||||
""" Retrieve the given template, and return it as a pair ``(element,
|
||||
@@ -528,13 +548,13 @@ class QWeb(object):
|
||||
# def $name(self, append, values, options, log)
|
||||
fn = ast.FunctionDef(
|
||||
name=name,
|
||||
args=ast.arguments(args=[
|
||||
ast.Name(id='self', ctx=ast.Param()),
|
||||
ast.Name(id='append', ctx=ast.Param()),
|
||||
ast.Name(id='values', ctx=ast.Param()),
|
||||
ast.Name(id='options', ctx=ast.Param()),
|
||||
ast.Name(id='log', ctx=ast.Param()),
|
||||
], defaults=[], vararg=None, kwarg=None),
|
||||
args=arguments(args=[
|
||||
arg(arg='self', annotation=None),
|
||||
arg(arg='append', annotation=None),
|
||||
arg(arg='values', annotation=None),
|
||||
arg(arg='options', annotation=None),
|
||||
arg(arg='log', annotation=None),
|
||||
], defaults=[], vararg=None, kwarg=None, kwonlyargs=[], kw_defaults=[]),
|
||||
body=body or [ast.Return()],
|
||||
decorator_list=[])
|
||||
if lineno is not None:
|
||||
@@ -601,7 +621,7 @@ class QWeb(object):
|
||||
]
|
||||
),
|
||||
# append(escape($content))
|
||||
body=body,
|
||||
body=body or [ast.Pass()],
|
||||
# append(body default value)
|
||||
orelse=orelse,
|
||||
)
|
||||
@@ -667,7 +687,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 NameError("Unknown directive on %s" % etree.tostring(el))
|
||||
raise NameError("Unknown directive on %s" % etree.tostring(el, encoding='unicode'))
|
||||
return []
|
||||
|
||||
def _values_var(self, varname, ctx):
|
||||
@@ -750,8 +770,8 @@ class QWeb(object):
|
||||
# since `options['nsmap']` is a dict (and therefore mutable) and we do **not**
|
||||
# want changes done in deeper recursion to bevisible in earlier ones, we'll pass
|
||||
# a copy before continuing the recursion and restore the original afterwards.
|
||||
original_nsmap = options['nsmap']
|
||||
options['nsmap'] = dict(options['nsmap'], **el.nsmap)
|
||||
original_nsmap = dict(options['nsmap'])
|
||||
options['nsmap'].update(el.nsmap)
|
||||
content = self._compile_directive_content(el, options)
|
||||
options['nsmap'] = original_nsmap
|
||||
|
||||
@@ -964,9 +984,9 @@ class QWeb(object):
|
||||
# since `options['nsmap']` is a dict (and therefore mutable) and we do **not**
|
||||
# want changes done in deeper recursion to bevisible in earlier ones, we'll pass
|
||||
# a copy before continuing the recursion and restore the original afterwards.
|
||||
original_nsmap = dict(options['nsmap'])
|
||||
if el.nsmap:
|
||||
original_nsmap = options['nsmap']
|
||||
options['nsmap'] = dict(options['nsmap'], **el.nsmap)
|
||||
options['nsmap'].update(el.nsmap)
|
||||
content = self._compile_directives(el, options)
|
||||
if el.nsmap:
|
||||
options['nsmap'] = original_nsmap
|
||||
@@ -1069,7 +1089,7 @@ class QWeb(object):
|
||||
# $t-else
|
||||
ast.If(
|
||||
test=self._compile_expr(el.attrib.pop('t-if')),
|
||||
body=self._compile_directives(el, options),
|
||||
body=self._compile_directives(el, options) or [ast.Pass()],
|
||||
orelse=orelse
|
||||
)
|
||||
]
|
||||
@@ -1088,7 +1108,7 @@ class QWeb(object):
|
||||
args=[ast.Str(el.attrib.pop('t-groups'))], keywords=[],
|
||||
starargs=None, kwargs=None
|
||||
),
|
||||
body=self._compile_directives(el, options),
|
||||
body=self._compile_directives(el, options) or [ast.Pass()],
|
||||
orelse=[]
|
||||
)
|
||||
]
|
||||
@@ -1262,7 +1282,7 @@ class QWeb(object):
|
||||
# display the tag without content
|
||||
orelse = [ast.If(
|
||||
test=ast.Name(id='force_display', ctx=ast.Load()),
|
||||
body=self._compile_tag(el, [], options, True),
|
||||
body=self._compile_tag(el, [], options, True) or [ast.Pass()],
|
||||
orelse=[],
|
||||
)]
|
||||
|
||||
@@ -1306,7 +1326,7 @@ class QWeb(object):
|
||||
# display the tag without content
|
||||
ast.If(
|
||||
test=ast.Name(id=default_content, ctx=ast.Load()),
|
||||
body=self._compile_tag(el, [self._append(ast.Name(id=default_content, ctx=ast.Load()))], options, True),
|
||||
body=self._compile_tag(el, [self._append(ast.Name(id=default_content, ctx=ast.Load()))], options, True) or [ast.Pass()],
|
||||
orelse=orelse,
|
||||
)
|
||||
]
|
||||
|
||||
@@ -7,10 +7,15 @@ import unittest
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.tools import mute_logger
|
||||
from odoo.tools.safe_eval import safe_eval
|
||||
from odoo.tools.safe_eval import safe_eval, const_eval
|
||||
|
||||
|
||||
class TestSafeEval(unittest.TestCase):
|
||||
def test_const(self):
|
||||
# NB: True and False are names in Python 2 not consts
|
||||
expected = (1, {"a": {2.5}}, [None, u"foo"])
|
||||
actual = const_eval('(1, {"a": {2.5}}, [None, u"foo"])')
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_01_safe_eval(self):
|
||||
""" Try a few common expressions to verify they work with safe_eval """
|
||||
|
||||
+9
-3
@@ -22,6 +22,8 @@
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
import collections
|
||||
import dateutil
|
||||
import functools
|
||||
import itertools
|
||||
@@ -167,8 +169,9 @@ class MetaModel(api.Meta):
|
||||
|
||||
class NewId(object):
|
||||
""" Pseudo-ids for new records. """
|
||||
def __nonzero__(self):
|
||||
def __bool__(self):
|
||||
return False
|
||||
__nonzero__ = __bool__
|
||||
|
||||
IdType = pycompat.integer_types + (str, unicode, NewId)
|
||||
|
||||
@@ -4499,9 +4502,10 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
|
||||
# "Dunder" methods
|
||||
#
|
||||
|
||||
def __nonzero__(self):
|
||||
def __bool__(self):
|
||||
""" Test whether ``self`` is nonempty. """
|
||||
return bool(getattr(self, '_ids', True))
|
||||
__nonzero__ = __bool__
|
||||
|
||||
def __len__(self):
|
||||
""" Return the size of ``self``. """
|
||||
@@ -4952,7 +4956,9 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
collections.Set.register(BaseModel)
|
||||
# not exactly true as BaseModel doesn't have __reversed__, index or count
|
||||
collections.Sequence.register(BaseModel)
|
||||
|
||||
class RecordCache(MutableMapping):
|
||||
""" Implements a proxy dictionary to read/update the cache of a record.
|
||||
|
||||
+3
-9
@@ -642,15 +642,9 @@ class Connection(object):
|
||||
# serialized_cursor is deprecated - cursors are serialized by default
|
||||
serialized_cursor = cursor
|
||||
|
||||
def __nonzero__(self):
|
||||
"""Check if connection is possible"""
|
||||
try:
|
||||
_logger.info("__nonzero__() is deprecated. (It is too expensive to test a connection.)")
|
||||
cr = self.cursor()
|
||||
cr.close()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
def __bool__(self):
|
||||
raise NotImplementedError()
|
||||
__nonzero__ = __bool__
|
||||
|
||||
def connection_info_for(db_or_uri):
|
||||
""" parse the given `db_or_uri` and return a 2-tuple (dbname, connection_params)
|
||||
|
||||
@@ -23,6 +23,8 @@ from pprint import pformat
|
||||
|
||||
import requests
|
||||
|
||||
from odoo.tools import pycompat
|
||||
|
||||
try:
|
||||
from itertools import zip_longest as izip_longest
|
||||
except ImportError:
|
||||
@@ -158,6 +160,10 @@ class BaseCase(TreeCase):
|
||||
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)
|
||||
|
||||
class TransactionCase(BaseCase):
|
||||
""" TestCase in which each test method is run in its own transaction,
|
||||
|
||||
+5
-3
@@ -1104,10 +1104,12 @@ def _consteq(str1, str2):
|
||||
|
||||
consteq = getattr(passlib.utils, 'consteq', _consteq)
|
||||
|
||||
# forbid globals entirely: str/unicode, int/long, float, bool, tuple, list, dict, None
|
||||
class Unpickler(pickle_.Unpickler, object):
|
||||
find_global = None # Python 2
|
||||
find_class = None # Python 3
|
||||
def _pickle_load(stream, errors=False):
|
||||
unpickler = pickle_.Unpickler(stream)
|
||||
# pickle builtins: str/unicode, int/long, float, bool, tuple, list, dict, None
|
||||
unpickler.find_global = None
|
||||
unpickler = Unpickler(stream)
|
||||
try:
|
||||
return unpickler.load()
|
||||
except Exception:
|
||||
|
||||
+71
-28
@@ -14,8 +14,8 @@ condition/math builtins.
|
||||
# - http://code.activestate.com/recipes/286134/
|
||||
# - safe_eval in lp:~xrg/openobject-server/optimize-5.0
|
||||
# - safe_eval in tryton http://hg.tryton.org/hgwebdir.cgi/trytond/rev/bbb5f73319ad
|
||||
|
||||
from opcode import HAVE_ARGUMENT, opmap
|
||||
import dis
|
||||
from opcode import HAVE_ARGUMENT, opmap, opname
|
||||
|
||||
import functools
|
||||
from psycopg2 import OperationalError
|
||||
@@ -40,12 +40,40 @@ _ALLOWED_MODULES = ['_strptime', 'math', 'time']
|
||||
|
||||
_UNSAFE_ATTRIBUTES = ['f_builtins', 'f_globals', 'f_locals', 'gi_frame',
|
||||
'co_code', 'func_globals']
|
||||
_POSSIBLE_OPCODES_P3 = [
|
||||
# opcodes for `with` statement cleanup process
|
||||
'WITH_CLEANUP_START', 'WITH_CLEANUP_FINISH',
|
||||
# f-strings
|
||||
'FORMAT_VALUE', 'BUILD_STRING',
|
||||
# extended iterable unpacking: LHS has * e.g. `a, *b, c = thing()`
|
||||
'UNPACK_EX',
|
||||
# collection literals with unpacking e.g. [*a, *b]
|
||||
'BUILD_LIST_UNPACK', 'BUILD_TUPLE_UNPACK', 'BUILD_SET_UNPACK', 'BUILD_MAP_UNPACK',
|
||||
# packs args/kwargs for calls with multiple unpacks e.g. foo(*a, *b, *c)
|
||||
'BUILD_TUPLE_UNPACK_WITH_CALL', 'BUILD_MAP_UNPACK_WITH_CALL',
|
||||
# ???
|
||||
'GET_YIELD_FROM_ITER',
|
||||
# matrix operator
|
||||
'BINARY_MATRIX_MULTIPLY', 'INPLACE_MATRIX_MULTIPLY',
|
||||
]
|
||||
|
||||
# opcodes necessary to build literal values
|
||||
_CONST_OPCODES = set(opmap[x] for x in [
|
||||
# stack manipulations
|
||||
'POP_TOP', 'ROT_TWO', 'ROT_THREE', 'ROT_FOUR', 'DUP_TOP', 'DUP_TOPX',
|
||||
'POP_BLOCK','SETUP_LOOP', 'BUILD_LIST', 'BUILD_MAP', 'BUILD_TUPLE',
|
||||
'LOAD_CONST', 'RETURN_VALUE', 'STORE_SUBSCR', 'STORE_MAP'] if x in opmap)
|
||||
'DUP_TOP_TWO', # replaces DUP_TOPX in P3
|
||||
'LOAD_CONST',
|
||||
'RETURN_VALUE', # return the result of the literal/expr evaluation
|
||||
# literal collections
|
||||
'BUILD_LIST', 'BUILD_MAP', 'BUILD_TUPLE', 'BUILD_SET',
|
||||
# 3.6: literal map with constant keys https://bugs.python.org/issue27140
|
||||
'BUILD_CONST_KEY_MAP',
|
||||
# until Python 3.5, literal maps are compiled to creating an empty map
|
||||
# (pre-sized) then filling it key by key
|
||||
'STORE_MAP',
|
||||
] if x in opmap)
|
||||
|
||||
# operations on literal values
|
||||
_EXPR_OPCODES = _CONST_OPCODES.union(set(opmap[x] for x in [
|
||||
'UNARY_POSITIVE', 'UNARY_NEGATIVE', 'UNARY_NOT',
|
||||
'UNARY_INVERT', 'BINARY_POWER', 'BINARY_MULTIPLY',
|
||||
@@ -55,43 +83,57 @@ _EXPR_OPCODES = _CONST_OPCODES.union(set(opmap[x] for x in [
|
||||
'BINARY_OR', 'INPLACE_ADD', 'INPLACE_SUBTRACT', 'INPLACE_MULTIPLY',
|
||||
'INPLACE_DIVIDE', 'INPLACE_REMAINDER', 'INPLACE_POWER',
|
||||
'INPLACE_LEFTSHIFT', 'INPLACE_RIGHTSHIFT', 'INPLACE_AND',
|
||||
'INPLACE_XOR','INPLACE_OR'
|
||||
'INPLACE_XOR','INPLACE_OR', 'STORE_SUBSCR',
|
||||
# slice operations (Python 3 only has BUILD_SLICE)
|
||||
'SLICE+0', 'SLICE+1', 'SLICE+2', 'SLICE+3', 'BUILD_SLICE',
|
||||
# comprehensions
|
||||
'LIST_APPEND', 'MAP_ADD', 'SET_ADD',
|
||||
'COMPARE_OP',
|
||||
] if x in opmap))
|
||||
|
||||
_SAFE_OPCODES = _EXPR_OPCODES.union(set(opmap[x] for x in [
|
||||
'LOAD_NAME', 'CALL_FUNCTION', 'COMPARE_OP', 'LOAD_ATTR',
|
||||
'STORE_NAME', 'GET_ITER', 'FOR_ITER', 'LIST_APPEND', 'DELETE_NAME',
|
||||
'POP_BLOCK', 'POP_EXCEPT', # Seems to be a special-case of POP_BLOCK for P3
|
||||
'SETUP_LOOP', 'BREAK_LOOP', 'CONTINUE_LOOP',
|
||||
'MAKE_FUNCTION', 'CALL_FUNCTION',
|
||||
# P3: https://bugs.python.org/issue27213
|
||||
'CALL_FUNCTION_EX',
|
||||
# Already in P2 but apparently the first one is used more aggressively in P3
|
||||
'CALL_FUNCTION_KW', 'CALL_FUNCTION_VAR', 'CALL_FUNCTION_VAR_KW',
|
||||
'GET_ITER', 'FOR_ITER', 'YIELD_VALUE',
|
||||
'JUMP_FORWARD', 'JUMP_IF_TRUE', 'JUMP_IF_FALSE', 'JUMP_ABSOLUTE',
|
||||
'MAKE_FUNCTION', 'SLICE+0', 'SLICE+1', 'SLICE+2', 'SLICE+3', 'BREAK_LOOP',
|
||||
'CONTINUE_LOOP', 'RAISE_VARARGS', 'YIELD_VALUE',
|
||||
# New in Python 2.7 - http://bugs.python.org/issue4715 :
|
||||
'JUMP_IF_FALSE_OR_POP', 'JUMP_IF_TRUE_OR_POP', 'POP_JUMP_IF_FALSE',
|
||||
'POP_JUMP_IF_TRUE', 'SETUP_EXCEPT', 'END_FINALLY',
|
||||
'POP_JUMP_IF_TRUE', 'SETUP_EXCEPT', 'END_FINALLY', 'RAISE_VARARGS',
|
||||
'LOAD_NAME', 'STORE_NAME', 'DELETE_NAME', 'LOAD_ATTR',
|
||||
'LOAD_FAST', 'STORE_FAST', 'DELETE_FAST', 'UNPACK_SEQUENCE',
|
||||
'LOAD_GLOBAL', # Only allows access to restricted globals
|
||||
] if x in opmap))
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
def _get_opcodes(codeobj):
|
||||
"""_get_opcodes(codeobj) -> [opcodes]
|
||||
if hasattr(dis, 'get_instructions'):
|
||||
def _get_opcodes(codeobj):
|
||||
"""_get_opcodes(codeobj) -> [opcodes]
|
||||
|
||||
Extract the actual opcodes as a list from a code object
|
||||
Extract the actual opcodes as an iterator from a code object
|
||||
|
||||
>>> c = compile("[1 + 2, (1,2)]", "", "eval")
|
||||
>>> _get_opcodes(c)
|
||||
[100, 100, 23, 100, 100, 102, 103, 83]
|
||||
"""
|
||||
i = 0
|
||||
byte_codes = codeobj.co_code
|
||||
while i < len(byte_codes):
|
||||
code = ord(byte_codes[i])
|
||||
yield code
|
||||
>>> c = compile("[1 + 2, (1,2)]", "", "eval")
|
||||
>>> list(_get_opcodes(c))
|
||||
[100, 100, 23, 100, 100, 102, 103, 83]
|
||||
"""
|
||||
return (i.opcode for i in dis.get_instructions(codeobj))
|
||||
else:
|
||||
def _get_opcodes(codeobj):
|
||||
i = 0
|
||||
byte_codes = codeobj.co_code
|
||||
while i < len(byte_codes):
|
||||
code = ord(byte_codes[i:i+1])
|
||||
yield code
|
||||
|
||||
if code >= HAVE_ARGUMENT:
|
||||
i += 3
|
||||
else:
|
||||
i += 1
|
||||
if code >= HAVE_ARGUMENT:
|
||||
i += 3
|
||||
else:
|
||||
i += 1
|
||||
|
||||
def assert_no_dunder_name(code_obj, expr):
|
||||
""" assert_no_dunder_name(code_obj, expr) -> None
|
||||
@@ -138,8 +180,9 @@ def assert_valid_codeobj(allowed_codes, code_obj, expr):
|
||||
|
||||
# almost twice as fast as a manual iteration + condition when loading
|
||||
# /web according to line_profiler
|
||||
if set(_get_opcodes(code_obj)) - allowed_codes:
|
||||
raise ValueError("forbidden opcode(s) in %r" % expr)
|
||||
codes = set(_get_opcodes(code_obj)) - allowed_codes
|
||||
if codes:
|
||||
raise ValueError("forbidden opcode(s) in %r: %s" % (expr, ', '.join(opname[x] for x in codes)))
|
||||
|
||||
for const in code_obj.co_consts:
|
||||
if isinstance(const, CodeType):
|
||||
|
||||
+4
-1
@@ -10,7 +10,10 @@ import signal
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
import xmlrpclib
|
||||
try:
|
||||
from xmlrpc import client as xmlrpclib
|
||||
except ImportError:
|
||||
import xmlrpclib
|
||||
from contextlib import contextmanager
|
||||
from glob import glob
|
||||
from os.path import abspath, dirname, join
|
||||
|
||||
Reference in New Issue
Block a user