Browse Source

[FIX] mis_builder: handle corner case errors raised by the test suite

Plus, handle even more corner cases related to NameError handling.
pull/189/head
Stéphane Bidoul 9 years ago
parent
commit
068cc14c63
  1. 4
      mis_builder/models/data_error.py
  2. 35
      mis_builder/models/mis_report.py
  3. 4
      mis_builder/models/mis_safe_eval.py
  4. 7
      mis_builder/tests/test_mis_safe_eval.py

4
mis_builder/models/data_error.py

@ -9,3 +9,7 @@ class DataError(Exception):
def __init__(self, name, msg): def __init__(self, name, msg):
self.name = name self.name = name
self.msg = msg self.msg = msg
class NameDataError(DataError):
pass

35
mis_builder/models/mis_report.py

@ -20,7 +20,7 @@ from .aep import AccountingExpressionProcessor as AEP
from .aggregate import _sum, _avg, _min, _max from .aggregate import _sum, _avg, _min, _max
from .accounting_none import AccountingNone from .accounting_none import AccountingNone
from .simple_array import SimpleArray from .simple_array import SimpleArray
from .mis_safe_eval import mis_safe_eval, DataError
from .mis_safe_eval import mis_safe_eval, DataError, NameDataError
from .mis_report_style import ( from .mis_report_style import (
TYPE_NUM, TYPE_PCT, TYPE_STR, CMP_DIFF, CMP_PCT, CMP_NONE TYPE_NUM, TYPE_PCT, TYPE_STR, CMP_DIFF, CMP_PCT, CMP_NONE
) )
@ -190,8 +190,9 @@ class KpiMatrix(object):
Invoke this and declare_comparison in display order. Invoke this and declare_comparison in display order.
""" """
self._cols[col_key] = KpiMatrixCol(label, description,
locals_dict, subkpis)
col = KpiMatrixCol(label, description, locals_dict, subkpis)
self._cols[col_key] = col
return col
def declare_comparison(self, col_key, base_col_key): def declare_comparison(self, col_key, base_col_key):
""" Declare a new comparison column. """ Declare a new comparison column.
@ -235,6 +236,7 @@ class KpiMatrix(object):
if isinstance(val, DataError): if isinstance(val, DataError):
val_rendered = val.name val_rendered = val.name
val_comment = val.msg val_comment = val.msg
val = None
else: else:
val_rendered = self._style_model.render( val_rendered = self._style_model.render(
self.lang, row.style_props, kpi.type, val) self.lang, row.style_props, kpi.type, val)
@ -915,7 +917,7 @@ class MisReport(models.Model):
if subkpi in subkpis_filter] if subkpi in subkpis_filter]
else: else:
subkpis = self.subkpi_ids subkpis = self.subkpi_ids
kpi_matrix.declare_col(col_key,
col = kpi_matrix.declare_col(col_key,
col_label, col_description, col_label, col_description,
locals_dict, subkpis) locals_dict, subkpis)
@ -934,11 +936,13 @@ class MisReport(models.Model):
vals = [] vals = []
drilldown_args = [] drilldown_args = []
try:
name_error = False
for expression in expressions: for expression in expressions:
replaced_expr = aep.replace_expr(expression) replaced_expr = aep.replace_expr(expression)
vals.append( vals.append(
mis_safe_eval(replaced_expr, locals_dict)) mis_safe_eval(replaced_expr, locals_dict))
if isinstance(vals[-1], NameDataError):
name_error = True
if replaced_expr != expression: if replaced_expr != expression:
drilldown_args.append({ drilldown_args.append({
'period_id': col_key, 'period_id': col_key,
@ -946,9 +950,8 @@ class MisReport(models.Model):
}) })
else: else:
drilldown_args.append(None) drilldown_args.append(None)
except NameError:
if name_error:
recompute_queue.append(kpi) recompute_queue.append(kpi)
break
else: else:
# no error, set it in locals_dict so it can be used # no error, set it in locals_dict so it can be used
# in computing other kpis # in computing other kpis
@ -957,10 +960,26 @@ class MisReport(models.Model):
else: else:
locals_dict[kpi.name] = SimpleArray(vals) locals_dict[kpi.name] = SimpleArray(vals)
# even in case of name error we set the result in the matrix
# so the name error will be displayed if it cannot be
# resolved by recomputing later
if len(expressions) == 1 and col.colspan > 1:
if isinstance(vals[0], tuple):
vals = vals[0]
assert len(vals) == col.colspan
elif isinstance(vals[0], NameDataError):
vals = (vals[0],) * col.colspan
else:
raise UserError("Probably not your fault... but I'm "
"really curious to know how you "
"managed to raise this error so "
"I can handle one more corner case!")
if len(drilldown_args) != len(vals):
drilldown_args = [None] * len(vals)
kpi_matrix.set_values( kpi_matrix.set_values(
kpi, col_key, vals, drilldown_args) kpi, col_key, vals, drilldown_args)
if not kpi.auto_expand_accounts:
if not kpi.auto_expand_accounts or name_error:
continue continue
for account_id, replaced_exprs in \ for account_id, replaced_exprs in \

4
mis_builder/models/mis_safe_eval.py

@ -6,7 +6,7 @@ import traceback
from openerp.tools.safe_eval import test_expr, _SAFE_OPCODES, _BUILTINS from openerp.tools.safe_eval import test_expr, _SAFE_OPCODES, _BUILTINS
from .data_error import DataError
from .data_error import DataError, NameDataError
__all__ = ['mis_safe_eval'] __all__ = ['mis_safe_eval']
@ -25,7 +25,7 @@ def mis_safe_eval(expr, locals_dict):
globals_dict = {'__builtins__': _BUILTINS} globals_dict = {'__builtins__': _BUILTINS}
val = eval(c, globals_dict, locals_dict) # pylint: disable=eval-used val = eval(c, globals_dict, locals_dict) # pylint: disable=eval-used
except NameError: except NameError:
raise
val = NameDataError('#NAME', traceback.format_exc())
except ZeroDivisionError: except ZeroDivisionError:
val = DataError('#DIV/0', traceback.format_exc()) val = DataError('#DIV/0', traceback.format_exc())
except: except:

7
mis_builder/tests/test_mis_safe_eval.py

@ -4,7 +4,7 @@
import openerp.tests.common as common import openerp.tests.common as common
from ..models.mis_safe_eval import mis_safe_eval, DataError
from ..models.mis_safe_eval import mis_safe_eval, DataError, NameDataError
class TestMisSafeEval(common.TransactionCase): class TestMisSafeEval(common.TransactionCase):
@ -22,5 +22,6 @@ class TestMisSafeEval(common.TransactionCase):
self.assertEqual(val.name, '#ERR') self.assertEqual(val.name, '#ERR')
def test_name_error(self): def test_name_error(self):
with self.assertRaises(NameError):
mis_safe_eval('a + 1', {})
val = mis_safe_eval('a + 1', {})
self.assertTrue(isinstance(val, NameDataError))
self.assertEqual(val.name, '#NAME')
Loading…
Cancel
Save