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
- Rewrite as a single expression using allowed names: compute intermediate values inline or with min/max nesting instead of assignments.
- 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.
- 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
- Write formulas as one expression; avoid assignment to inputs entirely.
- Inline intermediate calculations instead of mutating base/product/uom.
- Validate read-only-ness in onchange on the tax form to fail before save.
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
- Invalid AST node: %s
- Only int, float or None are allowed as constant values
- Unknown identifier: %s
- Unknown function call
- Invalid formula for expression '%(label)s' of line '%(line)s
AI-assisted analysis of odoo/odoo@1e661df964 (2026-08-15).
Data as JSON: /api/errors/49312703269f13af.
Report an issue: GitHub.