odoo/odoo · error · ValidationError

Only read access to identifiers is allowed

Error message

Only read access to identifiers is allowed

What it means

Raised by `visit_Name` in addons/account_tax_python/tools/formula_utils.py:90 when a Name node has a context other than `ast.Load` — i.e. the formula tries to WRITE to an identifier (assignment, augmented assignment, del) instead of only reading it. The sandbox formulas are pure expressions; only reading allowed variables and assigning the final result through the dedicated mechanism is supported.

Source

Thrown at addons/account_tax_python/tools/formula_utils.py:90

    """
    def __init__(self, env):
        self.env = env
        super().__init__()

    def visit(self, node):
        if not isinstance(node, _NODE_WHITELIST):
            raise ValidationError(self.env._("Invalid AST node: %s", type(node).__name__))
        super().visit(node)

    def visit_Constant(self, node: ast.Constant):
        if not isinstance(node.value, _ALLOWED_CONSTANT_T):
            raise ValidationError(self.env._("Only int, float or None are allowed as constant values"))

    def visit_Name(self, node: ast.Name):
        if node.id not in _ALLOWED_NAMES:
            raise ValidationError(self.env._("Unknown identifier: %s", str(node.id)))
        if not isinstance(node.ctx, ast.Load):
            raise ValidationError(self.env._("Only read access to identifiers is allowed"))

    def visit_Call(self, node: ast.Call):
        if not (
            isinstance(node.func, ast.Name)
            and node.func.id in _ALLOWED_FUNCS
            and isinstance(node.func.ctx, ast.Load)
        ):
            raise ValidationError(self.env._("Unknown function call"))
        # don't visit node.func: it's already validated and min/max aren't allowed as normal Name identifiers
        for arg in node.args:
            self.visit(arg)
        if node.keywords:
            raise ValidationError(self.env._("Kwargs are not allowed"))

    def visit_Subscript(self, node: ast.Subscript):
        # Only allow string constants as subscripts (e.g., product["type"])
        # They are not allowed elsewhere in the formula
        if not (

View on GitHub (pinned to 1e661df964)

Solutions

  1. Rewrite as a single expression using allowed names: compute intermediate values inline or with min/max nesting instead of assignments.
  2. If a variable is genuinely needed, introduce it via `result = ...` style supported constructs only — check the module docs for the accepted assignment target in your version; otherwise inline it.
  3. Precompute derived inputs (discounted base, factors) in the tax's Python context/overrides, not inside the formula.

Example fix

# before (formula)
base = base * 0.9
result = base * 0.21

# after (formula) — no assignment to inputs
result = (base * 0.9) * 0.21
Defensive patterns

Strategy: validation

Validate before calling

import ast

def formula_is_read_only(formula: str) -> bool:
    return not any(
        isinstance(n.ctx, (ast.Store, ast.Del))
        for n in ast.walk(ast.parse(formula, mode='exec'))
        if isinstance(n, ast.Name)
    )

Prevention

When it happens

Trigger: A formula like `base = base * 2\nresult = base * 0.21` (reassignment of `base`), `product = 1`, or `del x` — the Store/Delete context on the Name node makes `isinstance(node.ctx, ast.Load)` false and the ValidationError fires.

Common situations: Users writing multi-statement python habits into the formula box; old Odoo python-tax formulas (pre-sandbox) that mutated `result` or inputs; documentation/examples copied from outside the sandbox era.

Related errors


AI-assisted analysis of odoo/odoo@1e661df964 (2026-08-15). Data as JSON: /api/errors/49312703269f13af. Report an issue: GitHub.