diff --git a/homeassistant/core.py b/homeassistant/core.py index a30aed22322..f01f8188bc0 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -1693,7 +1693,7 @@ class Service: 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, @@ -1704,8 +1704,8 @@ class ServiceCall: 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 @@ -1890,15 +1890,20 @@ class ServiceRegistry: 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: @@ -1938,8 +1943,8 @@ class ServiceRegistry: 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, @@ -1947,7 +1952,10 @@ class ServiceRegistry: 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 @@ -1959,49 +1967,42 @@ class ServiceRegistry: ) 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: