Skip to content

Commit f7eea1f

Browse files
committed
[IMP] util/misc: new literal_replace
Avoid replacing as string. closes #214 Signed-off-by: Christophe Simonis (chs) <[email protected]>
1 parent d2de0a8 commit f7eea1f

File tree

2 files changed

+40
-0
lines changed

2 files changed

+40
-0
lines changed

src/base/tests/test_util.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1609,6 +1609,24 @@ def test_SelfPrint_failure(self, value):
16091609
with self.assertRaises(ValueError):
16101610
safe_eval(value, util.SelfPrintEvalContext(), nocopy=True)
16111611

1612+
@parametrize(
1613+
[
1614+
("[('company_id','in',allowed_company_ids)]", "[('company_id', 'in', companies.active_ids)]"),
1615+
(
1616+
"[('company_id','in',allowed_company_ids or [False])]",
1617+
"[('company_id', 'in', companies.active_ids or [False])]",
1618+
),
1619+
(
1620+
"[('company_id','in', user.other.allowed_company_ids)]",
1621+
"[('company_id', 'in', user.other.allowed_company_ids)]",
1622+
),
1623+
]
1624+
)
1625+
@unittest.skipUnless(util.ast_unparse is not None, "`ast.unparse` available from Python3.9")
1626+
def test_literal_replace(self, orig, expected):
1627+
repl = util.literal_replace(orig, {"allowed_company_ids": "companies.active_ids"})
1628+
self.assertEqual(repl, expected)
1629+
16121630

16131631
def not_doing_anything_converter(el):
16141632
return True

src/util/misc.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import collections
55
import datetime
66
import functools
7+
import logging
78
import os
89
import re
910
import uuid
@@ -35,6 +36,8 @@
3536
except ImportError:
3637
ast_unparse = None
3738

39+
_logger = logging.getLogger(__name__)
40+
3841

3942
def _cached(func):
4043
sentinel = object()
@@ -510,3 +513,22 @@ def visit_UnaryOp(self, node):
510513
root = ast.parse(expr.strip(), mode="eval").body
511514
visited = replacer.visit(root)
512515
return (ast_unparse(visited).strip(), SelfPrintEvalContext(replacer.replaces))
516+
517+
518+
class _Replacer(ast.NodeTransformer):
519+
"""Replace literal nodes in an AST."""
520+
521+
def __init__(self, mapping):
522+
self.mapping = mapping
523+
524+
def visit_Name(self, node):
525+
return ast.Name(id=self.mapping[node.id], ctx=ast.Load()) if node.id in self.mapping else node
526+
527+
528+
def literal_replace(expr, mapping):
529+
if ast_unparse is None:
530+
_logger.critical("AST unparse unavailable")
531+
return expr
532+
root = ast.parse(expr.strip(), mode="eval").body
533+
visited = _Replacer(mapping).visit(root)
534+
return ast_unparse(visited).strip()

0 commit comments

Comments
 (0)