Skip to content
Merged
Changes from all 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
85 changes: 43 additions & 42 deletions homeassistant/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1693,7 +1693,7 @@ def __init__(
class ServiceCall:
"""Representation of a call to a service."""

__slots__ = ["domain", "service", "data", "context", "return_response"]
__slots__ = ("domain", "service", "data", "context", "return_response")

def __init__(
self,
Expand All @@ -1704,8 +1704,8 @@ def __init__(
return_response: bool = False,
) -> None:
"""Initialize a service call."""
self.domain = domain.lower()
self.service = service.lower()
self.domain = domain
self.service = service
self.data = ReadOnlyDict(data or {})
self.context = context or Context()
self.return_response = return_response
Expand Down Expand Up @@ -1890,15 +1890,20 @@ async def async_call(

This method is a coroutine.
"""
domain = domain.lower()
service = service.lower()
context = context or Context()
service_data = service_data or {}

try:
handler = self._services[domain][service]
except KeyError:
raise ServiceNotFound(domain, service) from None
# Almost all calls are already lower case, so we avoid
# calling lower() on the arguments in the common case.
domain = domain.lower()
service = service.lower()
try:
handler = self._services[domain][service]
except KeyError:
raise ServiceNotFound(domain, service) from None

if return_response:
if not blocking:
Expand Down Expand Up @@ -1938,16 +1943,19 @@ async def async_call(
self._hass.bus.async_fire(
EVENT_CALL_SERVICE,
{
ATTR_DOMAIN: domain.lower(),
ATTR_SERVICE: service.lower(),
ATTR_DOMAIN: domain,
ATTR_SERVICE: service,
ATTR_SERVICE_DATA: service_data,
},
context=context,
)

coro = self._execute_service(handler, service_call)
if not blocking:
self._run_service_in_background(coro, service_call)
self._hass.async_create_task(
self._run_service_call_catch_exceptions(coro, service_call),
f"service call background {service_call.domain}.{service_call.service}",
)
return None

response_data = await coro
Expand All @@ -1959,49 +1967,42 @@ async def async_call(
)
return response_data

def _run_service_in_background(
async def _run_service_call_catch_exceptions(
self,
coro_or_task: Coroutine[Any, Any, Any] | asyncio.Task[Any],
service_call: ServiceCall,
) -> None:
"""Run service call in background, catching and logging any exceptions."""

async def catch_exceptions() -> None:
try:
await coro_or_task
except Unauthorized:
_LOGGER.warning(
"Unauthorized service called %s/%s",
service_call.domain,
service_call.service,
)
except asyncio.CancelledError:
_LOGGER.debug("Service was cancelled: %s", service_call)
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Error executing service: %s", service_call)

self._hass.async_create_task(
catch_exceptions(),
f"service call background {service_call.domain}.{service_call.service}",
)
try:
await coro_or_task
except Unauthorized:
_LOGGER.warning(
"Unauthorized service called %s/%s",
service_call.domain,
service_call.service,
)
except asyncio.CancelledError:
_LOGGER.debug("Service was cancelled: %s", service_call)
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Error executing service: %s", service_call)

async def _execute_service(
self, handler: Service, service_call: ServiceCall
) -> ServiceResponse:
"""Execute a service."""
if handler.job.job_type == HassJobType.Coroutinefunction:
return await cast(
Callable[[ServiceCall], Awaitable[ServiceResponse]],
handler.job.target,
)(service_call)
if handler.job.job_type == HassJobType.Callback:
return cast(Callable[[ServiceCall], ServiceResponse], handler.job.target)(
service_call
)
return await self._hass.async_add_executor_job(
cast(Callable[[ServiceCall], ServiceResponse], handler.job.target),
service_call,
)
job = handler.job
target = job.target
if job.job_type == HassJobType.Coroutinefunction:
if TYPE_CHECKING:
target = cast(Callable[..., Coroutine[Any, Any, _R]], target)
return await target(service_call)
if job.job_type == HassJobType.Callback:
if TYPE_CHECKING:
target = cast(Callable[..., _R], target)
return target(service_call)
if TYPE_CHECKING:
target = cast(Callable[..., _R], target)
return await self._hass.async_add_executor_job(target, service_call)


class Config:
Expand Down