|
1 | 1 | """ |
2 | | -Exceptions used by appdaemon |
3 | | -
|
| 2 | +Custom exceptions used by AppDaemon and helper functions to format them in the logs. |
4 | 3 | """ |
5 | 4 |
|
6 | 5 | import asyncio |
|
20 | 19 |
|
21 | 20 | from aiohttp.client_exceptions import ClientConnectorError, ConnectionTimeoutError |
22 | 21 | from pydantic import ValidationError |
| 22 | +from pytz import UnknownTimeZoneError |
23 | 23 |
|
24 | 24 | if TYPE_CHECKING: |
25 | 25 | from .appdaemon import AppDaemon |
@@ -79,19 +79,11 @@ def user_exception_block(logger: Logger, exception: Exception, app_dir: Path | N |
79 | 79 | case AssertionError(): |
80 | 80 | logger.error(f"{indent}{exc.__class__.__name__}: {exc}") |
81 | 81 | 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.") |
82 | 85 | 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) |
95 | 87 | case AppDaemonException(): |
96 | 88 | assert app_dir is not None, "app_dir is required to format exception block" |
97 | 89 | for i, line in enumerate(str(exc).splitlines()): |
@@ -140,6 +132,39 @@ def user_exception_block(logger: Logger, exception: Exception, app_dir: Path | N |
140 | 132 | logger.error("=" * width) |
141 | 133 |
|
142 | 134 |
|
| 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 | + |
143 | 168 | def unexpected_block(logger: Logger, exception: Exception): |
144 | 169 | logger.error("=" * 75) |
145 | 170 | logger.error(f"Unexpected error: {exception}") |
|
0 commit comments