Skip to main content

bursar.expr module

Safe expression evaluator using Python’s ast module.

Allows mathematical expressions with whitelisted variables and functions. Rejects any AST node type not in the allowlist (no eval/exec).

Money safety (REFACTOR_CONTRACT §1):

  • All arithmetic is performed in decimal.Decimal. Numeric literals are

parsed as exact decimals from their source text (never via float()), so 0.1 + 0.2 is exactly 0.3.

  • Exponentiation (** / ast.Pow) is rejected entirely at validate time – the simplest acceptable DoS fix per the contract (no constant-exponent carve-out). See ALLOWED_NODES (ast.Pow is intentionally absent).
  • Division / modulo by zero raise ExpressionError (never inf/nan).
  • After evaluation the result is asserted finite; non-finite -> ExpressionError.
  • OverflowError / ValueError / InvalidOperation are converted to ExpressionError.

exception bursar.expr.ExpressionError

Bases: Exception

Raised on invalid or unsafe expressions.

bursar.expr.evaluate_expression(expr: str, variables: dict[str, Any]) → Decimal

Safely evaluate a validated expression in exact Decimal arithmetic.

Args: : expr: Expression string to evaluate. variables: Mapping of variable names to their numeric values.

Returns: : Exact Decimal result of the expression evaluation. Not quantized – callers (engine/breakdown) quantize at the cost boundary.

Raises: : ExpressionError: If the expression is invalid, references unknown : variables, divides/mods by zero, overflows, or produces a non-finite result.

bursar.expr.validate_expression(expr: str, known_variables: set[str] | None = None) → None

Validate that an expression string is safe and syntactically valid.

Args: : expr: Expression string to validate. known_variables: Optional canonical set of allowed variable names

(the engine’s metric set). When provided, any identifier that is neither a known variable nor an allowed function raises ExpressionError – so config-author typos fail at config-load time rather than at first runtime evaluation (M5).

Raises: : ExpressionError: If the expression contains disallowed constructs or : references an unknown variable.