From 33cbb398ad9873cc3ac7fc1f5d5c5b11b0721ccd Mon Sep 17 00:00:00 2001 From: Louis-Dominique Dubeau Date: Mon, 16 Dec 2019 03:39:20 -0500 Subject: [PATCH] Don't use the locals parameter on exec. (#29979) Using the locals parameter makes it so that the code of a Python script runs as if it were in the body of a ``class``. One effect of this is that functions defined as part of a script cannot call one another directly. Fixes: #24704, #13653 --- .../components/python_script/__init__.py | 8 ++++--- tests/components/python_script/test_init.py | 23 +++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/python_script/__init__.py b/homeassistant/components/python_script/__init__.py index e36ac397c0f..ddae8a81db1 100644 --- a/homeassistant/components/python_script/__init__.py +++ b/homeassistant/components/python_script/__init__.py @@ -175,6 +175,7 @@ def execute(hass, filename, source, data=None): builtins["sorted"] = sorted builtins["time"] = TimeWrapper() builtins["dt_util"] = dt_util + logger = logging.getLogger(f"{__name__}.{filename}") restricted_globals = { "__builtins__": builtins, "_print_": StubPrinter, @@ -184,14 +185,15 @@ def execute(hass, filename, source, data=None): "_getitem_": default_guarded_getitem, "_iter_unpack_sequence_": guarded_iter_unpack_sequence, "_unpack_sequence_": guarded_unpack_sequence, + "hass": hass, + "data": data or {}, + "logger": logger, } - logger = logging.getLogger(f"{__name__}.{filename}") - local = {"hass": hass, "data": data or {}, "logger": logger} try: _LOGGER.info("Executing %s: %s", filename, data) # pylint: disable=exec-used - exec(compiled.code, restricted_globals, local) + exec(compiled.code, restricted_globals) except ScriptError as err: logger.error("Error executing script: %s", err) except Exception as err: # pylint: disable=broad-except diff --git a/tests/components/python_script/test_init.py b/tests/components/python_script/test_init.py index 75616c7400a..b5879479837 100644 --- a/tests/components/python_script/test_init.py +++ b/tests/components/python_script/test_init.py @@ -258,6 +258,29 @@ hass.states.set('module.datetime', assert caplog.text == "" +@asyncio.coroutine +def test_execute_functions(hass, caplog): + """Test functions defined in script can call one another.""" + caplog.set_level(logging.ERROR) + source = """ +def a(): + hass.states.set('hello.a', 'one') + +def b(): + a() + hass.states.set('hello.b', 'two') + +b() +""" + hass.async_add_job(execute, hass, "test.py", source, {}) + yield from hass.async_block_till_done() + + assert hass.states.is_state("hello.a", "one") + assert hass.states.is_state("hello.b", "two") + # No errors logged = good + assert caplog.text == "" + + @asyncio.coroutine def test_reload(hass): """Test we can re-discover scripts."""