Add python_script response (#97937)
* Add response to python_script
* Reset output to empty dict if not valid dict
* Add tests for python_script response
* Raise Exceptions on service execution
* Add info on exception type
* Raise ServiceValidationError instead of ValueError
* Raise only on return_response=True
* Fix exception logger if no service response
* Create issue if exception is not raised
* Revert "Create issue if exception is not raised"
This reverts commit a61dd8619f
.
---------
Co-authored-by: rikroe <rikroe@users.noreply.github.com>
This commit is contained in:
parent
8645d9c717
commit
e7573c3ed4
2 changed files with 188 additions and 8 deletions
|
@ -6,6 +6,7 @@ import pytest
|
|||
|
||||
from homeassistant.components.python_script import DOMAIN, FOLDER, execute
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||
from homeassistant.helpers.service import async_get_all_descriptions
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
|
@ -136,6 +137,19 @@ raise Exception('boom')
|
|||
assert "Error executing script: boom" in caplog.text
|
||||
|
||||
|
||||
async def test_execute_runtime_error_with_response(hass: HomeAssistant) -> None:
|
||||
"""Test compile error logs error."""
|
||||
source = """
|
||||
raise Exception('boom')
|
||||
"""
|
||||
|
||||
task = hass.async_add_executor_job(execute, hass, "test.py", source, {}, True)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert type(task.exception()) == HomeAssistantError
|
||||
assert "Error executing script (Exception): boom" in str(task.exception())
|
||||
|
||||
|
||||
async def test_accessing_async_methods(
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
|
@ -151,6 +165,19 @@ hass.async_stop()
|
|||
assert "Not allowed to access async methods" in caplog.text
|
||||
|
||||
|
||||
async def test_accessing_async_methods_with_response(hass: HomeAssistant) -> None:
|
||||
"""Test compile error logs error."""
|
||||
source = """
|
||||
hass.async_stop()
|
||||
"""
|
||||
|
||||
task = hass.async_add_executor_job(execute, hass, "test.py", source, {}, True)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert type(task.exception()) == ServiceValidationError
|
||||
assert "Not allowed to access async methods" in str(task.exception())
|
||||
|
||||
|
||||
async def test_using_complex_structures(
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
|
@ -186,6 +213,21 @@ async def test_accessing_forbidden_methods(
|
|||
assert f"Not allowed to access {name}" in caplog.text
|
||||
|
||||
|
||||
async def test_accessing_forbidden_methods_with_response(hass: HomeAssistant) -> None:
|
||||
"""Test compile error logs error."""
|
||||
for source, name in {
|
||||
"hass.stop()": "HomeAssistant.stop",
|
||||
"dt_util.set_default_time_zone()": "module.set_default_time_zone",
|
||||
"datetime.non_existing": "module.non_existing",
|
||||
"time.tzset()": "TimeWrapper.tzset",
|
||||
}.items():
|
||||
task = hass.async_add_executor_job(execute, hass, "test.py", source, {}, True)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert type(task.exception()) == ServiceValidationError
|
||||
assert f"Not allowed to access {name}" in str(task.exception())
|
||||
|
||||
|
||||
async def test_iterating(hass: HomeAssistant) -> None:
|
||||
"""Test compile error logs error."""
|
||||
source = """
|
||||
|
@ -449,3 +491,108 @@ time.sleep(5)
|
|||
await hass.async_block_till_done()
|
||||
|
||||
assert caplog.text.count("time.sleep") == 1
|
||||
|
||||
|
||||
async def test_execute_with_output(
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""Test executing a script with a return value."""
|
||||
caplog.set_level(logging.WARNING)
|
||||
|
||||
scripts = [
|
||||
"/some/config/dir/python_scripts/hello.py",
|
||||
]
|
||||
with patch(
|
||||
"homeassistant.components.python_script.os.path.isdir", return_value=True
|
||||
), patch("homeassistant.components.python_script.glob.iglob", return_value=scripts):
|
||||
await async_setup_component(hass, "python_script", {})
|
||||
|
||||
source = """
|
||||
output = {"result": f"hello {data.get('name', 'World')}"}
|
||||
"""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.python_script.open",
|
||||
mock_open(read_data=source),
|
||||
create=True,
|
||||
):
|
||||
response = await hass.services.async_call(
|
||||
"python_script",
|
||||
"hello",
|
||||
{"name": "paulus"},
|
||||
blocking=True,
|
||||
return_response=True,
|
||||
)
|
||||
|
||||
assert isinstance(response, dict)
|
||||
assert len(response) == 1
|
||||
assert response["result"] == "hello paulus"
|
||||
|
||||
# No errors logged = good
|
||||
assert caplog.text == ""
|
||||
|
||||
|
||||
async def test_execute_no_output(
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""Test executing a script without a return value."""
|
||||
caplog.set_level(logging.WARNING)
|
||||
|
||||
scripts = [
|
||||
"/some/config/dir/python_scripts/hello.py",
|
||||
]
|
||||
with patch(
|
||||
"homeassistant.components.python_script.os.path.isdir", return_value=True
|
||||
), patch("homeassistant.components.python_script.glob.iglob", return_value=scripts):
|
||||
await async_setup_component(hass, "python_script", {})
|
||||
|
||||
source = """
|
||||
no_output = {"result": f"hello {data.get('name', 'World')}"}
|
||||
"""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.python_script.open",
|
||||
mock_open(read_data=source),
|
||||
create=True,
|
||||
):
|
||||
response = await hass.services.async_call(
|
||||
"python_script",
|
||||
"hello",
|
||||
{"name": "paulus"},
|
||||
blocking=True,
|
||||
return_response=True,
|
||||
)
|
||||
|
||||
assert isinstance(response, dict)
|
||||
assert len(response) == 0
|
||||
|
||||
# No errors logged = good
|
||||
assert caplog.text == ""
|
||||
|
||||
|
||||
async def test_execute_wrong_output_type(hass: HomeAssistant) -> None:
|
||||
"""Test executing a script without a return value."""
|
||||
scripts = [
|
||||
"/some/config/dir/python_scripts/hello.py",
|
||||
]
|
||||
with patch(
|
||||
"homeassistant.components.python_script.os.path.isdir", return_value=True
|
||||
), patch("homeassistant.components.python_script.glob.iglob", return_value=scripts):
|
||||
await async_setup_component(hass, "python_script", {})
|
||||
|
||||
source = """
|
||||
output = f"hello {data.get('name', 'World')}"
|
||||
"""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.python_script.open",
|
||||
mock_open(read_data=source),
|
||||
create=True,
|
||||
), pytest.raises(ServiceValidationError):
|
||||
await hass.services.async_call(
|
||||
"python_script",
|
||||
"hello",
|
||||
{"name": "paulus"},
|
||||
blocking=True,
|
||||
return_response=True,
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue