From d148c30b0b61cb30447684be1839b67cd652afa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Mon, 27 Oct 2014 23:17:02 +0100 Subject: [PATCH] Replace safe_eval by simpleeval and use decistmt from tools issue4274 review10571003 --- CHANGELOG | 2 + INSTALL | 1 + price_list.py | 117 +++++++++++++------------------------------------- setup.py | 2 +- 4 files changed, 33 insertions(+), 89 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d070caa465..94d6d5c609 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Replace safe_eval by simpleeval + Version 3.4.0 - 2014-10-20 * Bug fixes (see mercurial logs for details) diff --git a/INSTALL b/INSTALL index e53d450cb2..aa8481087d 100644 --- a/INSTALL +++ b/INSTALL @@ -5,6 +5,7 @@ Prerequisites ------------- * Python 2.7 or later (http://www.python.org/) + * simpleeval (https://github.com/danthedeckie/simpleeval) * trytond (http://www.tryton.org/) * trytond_product (http://www.tryton.org/) * trytond_party (http://www.tryton.org/) diff --git a/price_list.py b/price_list.py index 36df54a846..eaa3405e80 100644 --- a/price_list.py +++ b/price_list.py @@ -1,10 +1,11 @@ #This file is part of Tryton. The COPYRIGHT file at the top level of #this repository contains the full copyright notices and license terms. from decimal import Decimal -import tokenize -from StringIO import StringIO + +from simpleeval import simple_eval + from trytond.model import ModelView, ModelSQL, MatchMixin, fields -from trytond.tools import safe_eval +from trytond.tools import decistmt from trytond.pyson import If, Eval from trytond.transaction import Transaction from trytond.pool import Pool @@ -13,37 +14,6 @@ from trytond import backend __all__ = ['PriceList', 'PriceListLine'] -# code snippet taken from http://docs.python.org/library/tokenize.html -def decistmt(s): - """Substitute Decimals for floats in a string of statements. - - >>> from decimal import Decimal - >>> s = 'print +21.3e-5*-.1234/81.7' - >>> decistmt(s) - "print +Decimal ('21.3e-5')*-Decimal ('.1234')/Decimal ('81.7')" - - >>> exec(s) - -3.21716034272e-007 - >>> exec(decistmt(s)) - -3.217160342717258261933904529E-7 - """ - result = [] - # tokenize the string - g = tokenize.generate_tokens(StringIO(s).readline) - for toknum, tokval, _, _, _ in g: - # replace NUMBER tokens - if toknum == tokenize.NUMBER and '.' in tokval: - result.extend([ - (tokenize.NAME, 'Decimal'), - (tokenize.OP, '('), - (tokenize.STRING, repr(tokval)), - (tokenize.OP, ')') - ]) - else: - result.append((toknum, tokval)) - return tokenize.untokenize(result) - - class PriceList(ModelSQL, ModelView): 'Price List' __name__ = 'product.price_list' @@ -60,36 +30,16 @@ class PriceList(ModelSQL, ModelView): def default_company(): return Transaction().context.get('company') - def _get_context_price_list_line(self, party, product, unit_price, - quantity, uom): - ''' - Get price list context for unit price - - :param party: the BrowseRecord of the party.party - :param product: the BrowseRecord of the product.product - :param unit_price: a Decimal for the default unit price in the - company's currency and default uom of the product - :param quantity: the quantity of product - :param uom: the BrowseRecord of the product.uom - :return: a dictionary - ''' + def get_context_formula(self, party, product, unit_price, quantity, uom): return { - 'unit_price': unit_price, - } + 'names': { + 'unit_price': unit_price, + }, + } def compute(self, party, product, unit_price, quantity, uom, pattern=None): - ''' - Compute price based on price list of party - - :param unit_price: a Decimal for the default unit price in the - company's currency and default uom of the product - :param quantity: the quantity of product - :param uom: a instance of the product.uom - :param pattern: a dictionary with price list field as key - and match value as value - :return: the computed unit price - ''' + 'Compute price based on price list of party' Uom = Pool().get('product.uom') @@ -101,12 +51,11 @@ class PriceList(ModelSQL, ModelView): pattern['quantity'] = Uom.compute_qty(uom, quantity, product.default_uom, round=False) if product else quantity + context = self.get_context_formula( + party, product, unit_price, quantity, uom) for line in self.lines: if line.match(pattern): - with Transaction().set_context( - self._get_context_price_list_line(party, product, - unit_price, quantity, uom)): - return line.get_unit_price() + return line.get_unit_price(**context) return unit_price @@ -133,7 +82,7 @@ class PriceListLine(ModelSQL, ModelView, MatchMixin): cls._order.insert(0, ('sequence', 'ASC')) cls._error_messages.update({ 'invalid_formula': ('Invalid formula "%(formula)s" in price ' - 'list line "%(line)s".'), + 'list line "%(line)s" with exception "%(exception)s".'), }) @classmethod @@ -172,23 +121,18 @@ class PriceListLine(ModelSQL, ModelView, MatchMixin): ''' Check formula ''' - pool = Pool() - PriceList = pool.get('product.price_list') - context = PriceList()._get_context_price_list_line(None, None, - Decimal('0.0'), 0, None) + context = self.price_list.get_context_formula( + None, None, Decimal('0'), 0, None) - with Transaction().set_context(**context): - try: - if not isinstance(self.get_unit_price(), Decimal): - self.raise_user_error('invalid_formula', { - 'formula': self.formula, - 'line': self.rec_name, - }) - except Exception: - self.raise_user_error('invalid_formula', { - 'formula': self.formula, - 'line': self.rec_name, - }) + try: + if not isinstance(self.get_unit_price(**context), Decimal): + raise ValueError + except Exception, exception: + self.raise_user_error('invalid_formula', { + 'formula': self.formula, + 'line': self.rec_name, + 'exception': exception, + }) def match(self, pattern): if 'quantity' in pattern: @@ -197,10 +141,7 @@ class PriceListLine(ModelSQL, ModelView, MatchMixin): return False return super(PriceListLine, self).match(pattern) - def get_unit_price(self): - ''' - Return unit price (as Decimal) - ''' - context = Transaction().context.copy() - context['Decimal'] = Decimal - return safe_eval(decistmt(self.formula), context) + def get_unit_price(self, **context): + 'Return unit price (as Decimal)' + context.setdefault('functions', {})['Decimal'] = Decimal + return simple_eval(decistmt(self.formula), **context) diff --git a/setup.py b/setup.py index c6d0c97295..38e8e69895 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ if minor_version % 2: 'hg+http://hg.tryton.org/modules/%s#egg=%s-%s' % ( name[8:], name, version)) -requires = [] +requires = ['simpleeval'] for dep in info.get('depends', []): if not re.match(r'(ir|res|webdav)(\W|$)', dep): requires.append(get_require_version('trytond_%s' % dep))