From a46a5e3a9e03dcdd8c84a92e4a5dbfa02ba461fa Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Fri, 26 Aug 2022 22:20:09 -0700 Subject: [PATCH] Improved code compilation pattern, closes #472 --- docs/cli.rst | 3 --- sqlite_utils/utils.py | 9 ++++----- tests/test_cli_convert.py | 20 ++++++++++++++++++++ 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/docs/cli.rst b/docs/cli.rst index 1632e2f..3c5c595 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -1359,12 +1359,9 @@ The following example adds a new ``score`` column, then updates it to list a ran random.seed(10) def convert(value): - global random return random.random() ' -Note the ``global random`` line here. Due to the way the tool compiles Python code, this is necessary to ensure the ``random`` module is available within the ``convert()`` function. If you were to omit this you would see a ``NameError: name 'random' is not defined`` error. - .. _cli_convert_recipes: sqlite-utils convert recipes diff --git a/sqlite_utils/utils.py b/sqlite_utils/utils.py index 64bba50..c0b7bf1 100644 --- a/sqlite_utils/utils.py +++ b/sqlite_utils/utils.py @@ -432,12 +432,11 @@ def progressbar(*args, **kwargs): def _compile_code(code, imports, variable="value"): - locals = {} globals = {"r": recipes, "recipes": recipes} # If user defined a convert() function, return that try: - exec(code, globals, locals) - return locals["convert"] + exec(code, globals) + return globals["convert"] except (AttributeError, SyntaxError, NameError, KeyError, TypeError): pass @@ -464,8 +463,8 @@ def _compile_code(code, imports, variable="value"): for import_ in imports: globals[import_.split(".")[0]] = __import__(import_) - exec(code_o, globals, locals) - return locals["fn"] + exec(code_o, globals) + return globals["fn"] def chunks(sequence: Iterable, size: int) -> Iterable[Iterable]: diff --git a/tests/test_cli_convert.py b/tests/test_cli_convert.py index 10b4563..d26ab8c 100644 --- a/tests/test_cli_convert.py +++ b/tests/test_cli_convert.py @@ -606,3 +606,23 @@ def test_convert_hyphen_workaround(fresh_db_and_path): assert list(db["names"].rows) == [ {"id": 1, "name": "-"}, ] + + +def test_convert_initialization_pattern(fresh_db_and_path): + db, db_path = fresh_db_and_path + db["names"].insert_all([{"id": 1, "name": "Cleo"}], pk="id") + result = CliRunner().invoke( + cli.cli, + [ + "convert", + db_path, + "names", + "name", + "-", + ], + input="import random\nrandom.seed(1)\ndef convert(value): return random.randint(0, 100)", + ) + assert 0 == result.exit_code, result.output + assert list(db["names"].rows) == [ + {"id": 1, "name": "17"}, + ]