[FIX] web: add context in pyeval context
When the owl refactoring landed, we rewrote the pyjs evaluation system.
Then, we used the new system in the new code. In particular, the action
service uses it to evaluate the action domain.
But the previous code had a subtle behaviour in the special case of
evaluating domain: it added the context as a pydict object in itself, so
one could evaluate expression such as `context.get('a', 14)` to read the
value of a key in the context with a fallback.
With this commit, we uniformize this behaviour across all python
expression: the `context` key is now reserved, and used to access
anything in the current evaluation context.
closes odoo/odoo#73227
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
@@ -29,10 +29,10 @@ export function parseExpr(expr) {
|
||||
* Evaluates a python expression
|
||||
*
|
||||
* @param {string} expr
|
||||
* @param {Object} context
|
||||
* @param {Object} [context]
|
||||
* @returns {any}
|
||||
*/
|
||||
export function evaluateExpr(expr, context) {
|
||||
export function evaluateExpr(expr, context = {}) {
|
||||
const ast = parseExpr(expr);
|
||||
return evaluate(ast, context);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { BUILTINS } from "./py_builtin";
|
||||
import { PyDate, PyDateTime, PyRelativeDelta, PyTime } from "./py_date";
|
||||
import { parseArgs, PY_DICT } from "./py_utils";
|
||||
import { parseArgs, PY_DICT, toPyDict } from "./py_utils";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Types
|
||||
@@ -199,6 +199,16 @@ const DICT = {
|
||||
*/
|
||||
export function evaluate(ast, context = {}) {
|
||||
const dicts = new Set();
|
||||
let pyContext;
|
||||
const evalContext = Object.create(context);
|
||||
Object.defineProperty(evalContext, "context", {
|
||||
get() {
|
||||
if (!pyContext) {
|
||||
pyContext = toPyDict(context);
|
||||
}
|
||||
return pyContext;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {AST} ast
|
||||
@@ -209,8 +219,8 @@ export function evaluate(ast, context = {}) {
|
||||
case 1 /* String */:
|
||||
return ast.value;
|
||||
case 5 /* Name */:
|
||||
if (ast.value in context) {
|
||||
return context[ast.value];
|
||||
if (ast.value in evalContext) {
|
||||
return evalContext[ast.value];
|
||||
} else if (ast.value in BUILTINS) {
|
||||
return BUILTINS[ast.value];
|
||||
} else {
|
||||
@@ -221,9 +231,9 @@ export function evaluate(ast, context = {}) {
|
||||
case 2 /* Boolean */:
|
||||
return ast.value;
|
||||
case 6 /* UnaryOperator */:
|
||||
return applyUnaryOp(ast, context);
|
||||
return applyUnaryOp(ast, evalContext);
|
||||
case 7 /* BinaryOperator */:
|
||||
return applyBinaryOp(ast, context);
|
||||
return applyBinaryOp(ast, evalContext);
|
||||
case 14 /* BooleanOperator */:
|
||||
const left = _evaluate(ast.left);
|
||||
if (ast.op === "and") {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { evaluateExpr } from "@web/core/py_js/py";
|
||||
import { toPyDict } from "@web/core/py_js/py_utils";
|
||||
|
||||
QUnit.module("py", {}, () => {
|
||||
QUnit.module("interpreter", () => {
|
||||
@@ -138,11 +137,13 @@ QUnit.module("py", {}, () => {
|
||||
assert.deepEqual(evaluateExpr("foo", { foo: [1, 2, 3] }), [1, 2, 3]);
|
||||
});
|
||||
|
||||
QUnit.test("python values in context", (assert) => {
|
||||
const context = toPyDict({ b: 3 });
|
||||
assert.strictEqual(evaluateExpr("context.get('b', 54)", { context }), 3);
|
||||
assert.strictEqual(evaluateExpr("context.get('c', 54)", { context }), 54);
|
||||
});
|
||||
QUnit.test(
|
||||
"special case for context: the eval context can be accessed as 'context'",
|
||||
(assert) => {
|
||||
assert.strictEqual(evaluateExpr("context.get('b', 54)", { b: 3 }), 3);
|
||||
assert.strictEqual(evaluateExpr("context.get('c', 54)", { b: 3 }), 54);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("throw error if name is not defined", (assert) => {
|
||||
assert.throws(() => evaluateExpr("a"));
|
||||
|
||||
Reference in New Issue
Block a user