Skip to content

Commit a384e98

Browse files
committed
better validation error formatting
1 parent dbe57fe commit a384e98

File tree

1 file changed

+39
-14
lines changed

1 file changed

+39
-14
lines changed

appdaemon/exceptions.py

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"""
2-
Exceptions used by appdaemon
3-
2+
Custom exceptions used by AppDaemon and helper functions to format them in the logs.
43
"""
54

65
import asyncio
@@ -20,6 +19,7 @@
2019

2120
from aiohttp.client_exceptions import ClientConnectorError, ConnectionTimeoutError
2221
from pydantic import ValidationError
22+
from pytz import UnknownTimeZoneError
2323

2424
if TYPE_CHECKING:
2525
from .appdaemon import AppDaemon
@@ -79,19 +79,11 @@ def user_exception_block(logger: Logger, exception: Exception, app_dir: Path | N
7979
case AssertionError():
8080
logger.error(f"{indent}{exc.__class__.__name__}: {exc}")
8181
continue
82+
case UnknownTimeZoneError() if exc == chain[0]:
83+
logger.error(f"{indent}{exc.__class__.__name__}: {exc}")
84+
logger.error(f"{indent} The specified time zone is not recognized. Check your 'time_zone' setting.")
8285
case ValidationError():
83-
for error in exc.errors():
84-
match error:
85-
case {"type": "missing", "msg": msg, "loc": loc}:
86-
app_name = loc[0]
87-
field = loc[-1]
88-
logger.error(f"{indent}App '{app_name}' has an assertion error in field '{field}': {msg}")
89-
case {"type": "assertion_error", "msg": msg, "loc": loc}:
90-
app_name = loc[0]
91-
field = loc[-1]
92-
logger.error(f"{indent}Assertion error in app '{app_name}' field '{field}': {msg}")
93-
case _:
94-
pass
86+
validation_block(chain[0], exc, logger, indent)
9587
case AppDaemonException():
9688
assert app_dir is not None, "app_dir is required to format exception block"
9789
for i, line in enumerate(str(exc).splitlines()):
@@ -140,6 +132,39 @@ def user_exception_block(logger: Logger, exception: Exception, app_dir: Path | N
140132
logger.error("=" * width)
141133

142134

135+
def validation_block(root: BaseException, exc: ValidationError, logger: Logger, indent: str = " ") -> None:
136+
"""Generate a user-friendly block of text for a ValidationError."""
137+
for error in exc.errors():
138+
match error:
139+
case {"msg": str(msg), "type": str(type_), "loc": loc}:
140+
match root:
141+
case BadAppConfigFile():
142+
app_name, *_, field = loc
143+
if type_ == "missing" and field in ("module", "class"):
144+
logger.error(f"{indent}App config error in '{app_name}', missing field '{field}'")
145+
# There's a bunch of other types of validation errors that could come up here, but
146+
# most of them are just confusing to the user, so we skip them.
147+
case _:
148+
# This is an error with appdaemon config, not app config
149+
field_name = '.'.join(map(str, loc))
150+
input_ = error.get("input")
151+
match type_:
152+
case "missing":
153+
logger.error(f"{indent}Missing required field: {field_name}")
154+
case "extra_forbidden":
155+
logger.error(f"{indent}Unknown field: {field_name}")
156+
case "float_parsing" | "int_parsing":
157+
logger.error(f"{indent}Invalid value for {field_name}: {input_}")
158+
case "value_error":
159+
logger.error(f"{indent}{msg}")
160+
case "url_parsing" | "url_scheme":
161+
logger.error(f"{indent}Invalid URL for {field_name}: {input_}")
162+
case _:
163+
logger.error(f"{indent}'{type_}' error from {loc}")
164+
case _:
165+
logger.error(f"{indent}{error}")
166+
167+
143168
def unexpected_block(logger: Logger, exception: Exception):
144169
logger.error("=" * 75)
145170
logger.error(f"Unexpected error: {exception}")

0 commit comments

Comments
 (0)