Skip to content
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

Optionally return response data when calling services through the API #115046

Merged
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion homeassistant/components/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,25 @@ async def post(
)

context = self.context(request)

if response_requested := "return_response" in request.query:
emontnemery marked this conversation as resolved.
Show resolved Hide resolved
if (
hass.services.supports_response(domain, service)
is ha.SupportsResponse.NONE
):
return self.json_message(
"Service does not support responses. Remove return_response from request.",
HTTPStatus.BAD_REQUEST,
)
elif (
hass.services.supports_response(domain, service) is ha.SupportsResponse.ONLY
):
return self.json_message(
"Service call requires responses but caller did not ask for responses. "
"Add ?return_response to query parameters.",
HTTPStatus.BAD_REQUEST,
)

changed_states: list[json_fragment] = []

@ha.callback
Expand All @@ -403,20 +422,26 @@ def _async_save_changed_entities(

try:
# shield the service call from cancellation on connection drop
await shield(
response = await shield(
hass.services.async_call(
domain,
service,
data, # type: ignore[arg-type]
blocking=True,
context=context,
return_response=response_requested,
)
)
except (vol.Invalid, ServiceNotFound) as ex:
raise HTTPBadRequest from ex
finally:
cancel_listen()

if response_requested:
return self.json(
{"changed_states": changed_states, "service_response": response}
)

return self.json(changed_states)


Expand Down
47 changes: 39 additions & 8 deletions tests/components/api/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,45 @@ def listener(service_call):
assert state["attributes"] == {"data": 1}


@pytest.mark.parametrize(
("supports_response", "requested_response", "expected_success"),
[
(ha.SupportsResponse.ONLY, True, True),
(ha.SupportsResponse.ONLY, False, False),
(ha.SupportsResponse.OPTIONAL, True, True),
(ha.SupportsResponse.OPTIONAL, False, True),
(ha.SupportsResponse.NONE, True, False),
(ha.SupportsResponse.NONE, False, True),
],
)
async def test_api_call_service_returns_response_requested_response(
hass: HomeAssistant,
mock_api_client: TestClient,
supports_response: ha.SupportsResponse,
requested_response: bool,
expected_success: bool,
) -> None:
"""Test if the API allows us to call a service."""
test_value = []

@ha.callback
def listener(service_call):
"""Record that our service got called."""
test_value.append(1)
return {}

hass.services.async_register(
"test_domain", "test_service", listener, supports_response=supports_response
)

await mock_api_client.post(
"/api/services/test_domain/test_service"
+ ("?return_response" if requested_response else "")
)
await hass.async_block_till_done()
assert len(test_value) == (1 if expected_success else 0)


emontnemery marked this conversation as resolved.
Show resolved Hide resolved
async def test_api_call_service_client_closed(
hass: HomeAssistant, mock_api_client: TestClient
) -> None:
Expand Down Expand Up @@ -728,14 +767,6 @@ async def test_rendering_template_admin(
assert resp.status == HTTPStatus.UNAUTHORIZED


async def test_api_call_service_not_found(
hass: HomeAssistant, mock_api_client: TestClient
) -> None:
"""Test if the API fails 400 if unknown service."""
resp = await mock_api_client.post("/api/services/test_domain/test_service")
assert resp.status == HTTPStatus.BAD_REQUEST


async def test_api_call_service_bad_data(
emontnemery marked this conversation as resolved.
Show resolved Hide resolved
hass: HomeAssistant, mock_api_client: TestClient
) -> None:
Expand Down