Skip to content

[IMP] adapt code to new simplified safe_eval API #265

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

KangOl
Copy link
Contributor

@KangOl KangOl commented Apr 23, 2025

safe_eval loose the nocopy option. As we rely on it when evaluating expression, we need to reimplement safe_eval to do the evaluation using the SelfPrintEvalContext.

See odoo/odoo#206846
See odoo/enterprise#83818

@robodoo
Copy link
Contributor

robodoo commented Apr 23, 2025

Pull request status dashboard

@KangOl
Copy link
Contributor Author

KangOl commented Apr 23, 2025

upgradeci retry with always only hr in all versions

@KangOl KangOl requested a review from aj-fuentes April 23, 2025 12:36
Copy link
Contributor

@aj-fuentes aj-fuentes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Minimal comment.

@KangOl KangOl force-pushed the master-safe-eval-refactor-globals-roan branch from 963dd2a to decb2d7 Compare April 24, 2025 12:03
@KangOl KangOl force-pushed the master-safe-eval-refactor-globals-roan branch from decb2d7 to 5b158c9 Compare April 24, 2025 12:33
antonrom1 added a commit to odoo-dev/odoo that referenced this pull request May 5, 2025
This commit simplifies `safe_eval` which, up until now, allowed passing
global and local namespaces. There is no usecase for this anymore. We
want safe_eval exec mode to behave like a top-level module scope.

`nocopy` is not needed anymore either. It's a relic of when the context
could be dynamic (e.g. for already deleted `RecordDictWrapper` or
`QWebContext`).

Passed in context is always a dict. It will always be mutated with the
local eval namespace dict after evaluation.

This all makes `safe_eval` API less confusing.

See: odoo/enterprise#83818
See: odoo/upgrade-util#265

task-4378806
@KangOl KangOl force-pushed the master-safe-eval-refactor-globals-roan branch from af3d7ff to f0bf867 Compare May 5, 2025 10:40
antonrom1 added a commit to odoo-dev/odoo that referenced this pull request May 13, 2025

Verified

This commit was signed with the committer’s verified signature.
antonrom1 Anton Romanova
This commit simplifies `safe_eval` which, up until now, allowed passing
global and local namespaces. There is no usecase for this anymore. We
want safe_eval exec mode to behave like a top-level module scope.

`nocopy` is not needed anymore either. It's a relic of when the context
could be dynamic (e.g. for already deleted `RecordDictWrapper` or
`QWebContext`).

Passed in context is always a dict. It will always be mutated with the
local eval namespace dict after evaluation.

This all makes `safe_eval` API less confusing.

See: odoo/enterprise#83818
See: odoo/upgrade-util#265

task-4378806

Verified

This commit was signed with the committer’s verified signature.
antonrom1 Anton Romanova
`safe_eval` loose the `nocopy` option. As we rely on it when evaluating
expression, we need to reimplement `safe_eval` to do the evaluation
using the `SelfPrintEvalContext`.

See odoo/odoo#206846
See odoo/enterprise#83818
@antonrom1 antonrom1 force-pushed the master-safe-eval-refactor-globals-roan branch from f0bf867 to fc5d99f Compare May 13, 2025 15:22
@KangOl KangOl requested a review from aj-fuentes May 14, 2025 08:11
Comment on lines +536 to +567
if version_gte("saas~18.4"):
import odoo.tools.safe_eval as _safe_eval_mod

def safe_eval(expr, context=None):
if context is None:
context = SelfPrintEvalContext()

assert isinstance(expr, (str, bytes))
assert isinstance(context, SelfPrintEvalContext)

c = _safe_eval_mod.test_expr(expr, _safe_eval_mod._SAFE_OPCODES, mode="eval", filename=None)
context["__builtins__"] = dict(_safe_eval_mod._BUILTINS)
try:
return _safe_eval_mod.unsafe_eval(c, context, None)
except _safe_eval_mod._BUBBLEUP_EXCEPTIONS:
raise
except Exception as e:
raise ValueError("{!r} while evaluating\n{!r}".format(e, expr))
finally:
del context["__builtins__"]
else:
try:
from odoo.tools.safe_eval import safe_eval as _safe_eval_func
except ImportError:
from openerp.tools.safe_eval import safe_eval as _safe_eval_func

def safe_eval(expr, context=None):
if context is None:
context = SelfPrintEvalContext()
assert isinstance(context, SelfPrintEvalContext)

return _safe_eval_func(expr, context, nocopy=True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we assert in both cases that expr is str ot bytes?

If we can assert it in both then I'd suggest we move the shared part to a toplevel safe_eval that then calls _safe_eval_impl with bot expr and context mandatory. Sort of:

if version_gte("saas~18.4"):
    import odoo.tools.safe_eval as _safe_eval_mod

    def _safe_eval_impl(expr, context):
        c = _safe_eval_mod.test_expr(expr, _safe_eval_mod._SAFE_OPCODES, mode="eval", filename=None)
        context["__builtins__"] = dict(_safe_eval_mod._BUILTINS)
        try:
            return _safe_eval_mod.unsafe_eval(c, context, None)
        except _safe_eval_mod._BUBBLEUP_EXCEPTIONS:
            raise
        except Exception as e:
            raise ValueError("{!r} while evaluating\n{!r}".format(e, expr))
        finally:
            del context["__builtins__"]
else:
    try:
        from odoo.tools.safe_eval import safe_eval as _safe_eval_func
    except ImportError:
        from openerp.tools.safe_eval import safe_eval as _safe_eval_func

    def _safe_eval_impl(expr, context):
        return _safe_eval_func(expr, context, nocopy=True)

def safe_eval(expr, context=None):
    if context is None:
        context = SelfPrintEvalContext()

    assert isinstance(expr, (str, bytes))
    assert isinstance(context, SelfPrintEvalContext)
    return _safe_eval_impl(expr, context)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually compile already checks the type of the source here. It's can either be a string, bytesarray or AST. We already check that the source object is not a codeobject, so I think the assert isinstance(expr, (str, bytes)) is not really needed here

@@ -350,7 +349,7 @@ def adapter(leaf, is_or, negated):
)
for alias_id, defaults_s in cr.fetchall():
try:
defaults = dict(safe_eval(defaults_s)) # XXX literal_eval should works.
defaults = dict(literal_eval(defaults_s))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential change of behaviour, LGTM just flagging it it new errors start appearing after the merge.

Copy link
Contributor

@aj-fuentes aj-fuentes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is OK to merge. Optional comment for extra checks (isinstance) grouped in one place, the rest looks good to me.

@KangOl
Copy link
Contributor Author

KangOl commented May 14, 2025

@robodoo r+

robodoo pushed a commit to odoo/odoo that referenced this pull request May 14, 2025
This commit simplifies `safe_eval` which, up until now, allowed passing
global and local namespaces. There is no usecase for this anymore. We
want safe_eval exec mode to behave like a top-level module scope.

`nocopy` is not needed anymore either. It's a relic of when the context
could be dynamic (e.g. for already deleted `RecordDictWrapper` or
`QWebContext`).

Passed in context is always a dict. It will always be mutated with the
local eval namespace dict after evaluation.

This all makes `safe_eval` API less confusing.

See: odoo/enterprise#83818
See: odoo/upgrade-util#265

task-4378806

closes #206846

Signed-off-by: Denis Ledoux (dle) <dle@odoo.com>
@robodoo robodoo closed this in fa6ed2a May 14, 2025
@robodoo robodoo added the 18.4 label May 14, 2025
sjai-odoo pushed a commit to odoo-dev/odoo that referenced this pull request May 15, 2025
This commit simplifies `safe_eval` which, up until now, allowed passing
global and local namespaces. There is no usecase for this anymore. We
want safe_eval exec mode to behave like a top-level module scope.

`nocopy` is not needed anymore either. It's a relic of when the context
could be dynamic (e.g. for already deleted `RecordDictWrapper` or
`QWebContext`).

Passed in context is always a dict. It will always be mutated with the
local eval namespace dict after evaluation.

This all makes `safe_eval` API less confusing.

See: odoo/enterprise#83818
See: odoo/upgrade-util#265

task-4378806

closes odoo#206846

Signed-off-by: Denis Ledoux (dle) <dle@odoo.com>
AhmedElemary57 pushed a commit to odoo-dev/odoo that referenced this pull request May 18, 2025
This commit simplifies `safe_eval` which, up until now, allowed passing
global and local namespaces. There is no usecase for this anymore. We
want safe_eval exec mode to behave like a top-level module scope.

`nocopy` is not needed anymore either. It's a relic of when the context
could be dynamic (e.g. for already deleted `RecordDictWrapper` or
`QWebContext`).

Passed in context is always a dict. It will always be mutated with the
local eval namespace dict after evaluation.

This all makes `safe_eval` API less confusing.

See: odoo/enterprise#83818
See: odoo/upgrade-util#265

task-4378806

closes odoo#206846

Signed-off-by: Denis Ledoux (dle) <dle@odoo.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants