Repository: AppDaemon/appdaemon

Issue:
Request for non-existent domain or service in a call_service to throw an exception.
Currently, if a service_call that doesn't exist is called, a WARNING is displayed in logs saying "Unknown domain".

This request is to raise an exception if the domain or the service doesn't exist and specify in the exception if the domain doesn't exist or if it does, specify that the service doesn't exist.

Thank you!


Discussion:

Resolving patch:
diff --git a/appdaemon/adapi.py b/appdaemon/adapi.py
--- a/appdaemon/adapi.py
+++ b/appdaemon/adapi.py
@@ -1545,7 +1545,7 @@ async def set_state(self, entity, **kwargs):
     @staticmethod
     def _check_service(service: str) -> None:
         if service.find("/") == -1:
-            raise ValueError("Invalid Service Name: {}".format(service))
+            raise ValueError("Invalid Service Name: %s", service)
 
     def register_service(
         self, service: str, cb: Callable[[str, str, str, dict], Any], **kwargs: Optional[dict]
diff --git a/appdaemon/exceptions.py b/appdaemon/exceptions.py
--- a/appdaemon/exceptions.py
+++ b/appdaemon/exceptions.py
@@ -12,6 +12,14 @@ class NamespaceException(Exception):
     pass
 
 
+class DomainException(Exception):
+    pass
+
+
+class ServiceException(Exception):
+    pass
+
+
 class AppException(Exception):
     pass
 
diff --git a/appdaemon/services.py b/appdaemon/services.py
--- a/appdaemon/services.py
+++ b/appdaemon/services.py
@@ -5,7 +5,7 @@
 from typing import Any, Optional, Callable, Awaitable
 
 from appdaemon.appdaemon import AppDaemon
-from appdaemon.exceptions import NamespaceException
+from appdaemon.exceptions import NamespaceException, DomainException, ServiceException
 import appdaemon.utils as utils
 
 
@@ -31,10 +31,10 @@ def register_service(
             name = kwargs.get("__name")
             # first we confirm if the namespace exists
             if name and namespace not in self.AD.state.state:
-                raise NamespaceException(f"Namespace '{namespace}', doesn't exist")
+                raise NamespaceException("Namespace %s, doesn't exist", namespace)
 
             elif not callable(callback):
-                raise ValueError(f"The given callback {callback} is not a callable function")
+                raise ValueError("The given callback %s is not a callable function", callback)
 
             if namespace not in self.services:
                 self.services[namespace] = {}
@@ -149,18 +149,15 @@ async def call_service(self, namespace: str, domain: str, service: str, data: di
             name = data.pop("__name", None)
 
             if namespace not in self.services:
-                self.logger.warning("Unknown namespace (%s) in call_service from %s", namespace, name)
-                return None
+                raise NamespaceException("Unknown namespace (%s) in call_service from %s", namespace, name)
+
             if domain not in self.services[namespace]:
-                self.logger.warning(
-                    "Unknown domain (%s/%s) in call_service from %s", namespace, domain, name,
-                )
-                return None
+                raise DomainException("Unknown domain (%s/%s) in call_service from %s", namespace, domain, name)
+
             if service not in self.services[namespace][domain]:
-                self.logger.warning(
+                raise ServiceException(
                     "Unknown service (%s/%s/%s) in call_service from %s", namespace, domain, service, name,
                 )
-                return None
 
             # If we have namespace in data it's an override for the domain of the eventual service call, as distinct
             # from the namespace the call itself is executed from. e.g. set_state() is in the AppDaemon namespace but


Test patch: