[core] Add missing exception chaining (raise from) across codebase (#15648)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
This commit is contained in:
SaVi
2026-04-16 20:49:33 +05:30
committed by GitHub
parent d8329dba22
commit 0b051289f5
18 changed files with 54 additions and 54 deletions
+3 -4
View File
@@ -199,11 +199,10 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
return cv.Schema([schema])(value)
except cv.Invalid as err2:
if "extra keys not allowed" in str(err2) and len(err2.path) == 2:
# pylint: disable=raise-missing-from
raise err
raise err from None
if "Unable to find action" in str(err):
raise err2
raise cv.MultipleInvalid([err, err2])
raise err2 from None
raise cv.MultipleInvalid([err, err2]) from None
elif isinstance(value, dict):
if CONF_THEN in value:
return [schema(value)]
+2 -2
View File
@@ -83,7 +83,7 @@ def angle_to_position(value, min=-360, max=360):
value = angle(min=min, max=max)(value)
return (RESOLUTION + round(value * ANGLE_TO_POSITION)) % RESOLUTION
except cv.Invalid as e:
raise cv.Invalid(f"When using angle, {e.error_message}")
raise cv.Invalid(f"When using angle, {e.error_message}") from e
def percent_to_position(value):
@@ -164,7 +164,7 @@ def has_valid_range_config():
except cv.Invalid as e:
raise cv.Invalid(
f"The range between start and end position is invalid. It was was {range} but {e.error_message}"
)
) from e
return validator
+1 -1
View File
@@ -116,7 +116,7 @@ def read_audio_file_and_type(file_config: ConfigType) -> tuple[bytes, MockObj]:
raise cv.Invalid(
f"Unable to determine audio file type of '{path}'. "
f"Try re-encoding the file into a supported format. Details: {e}"
)
) from e
media_file_type = audio.AUDIO_FILE_TYPE_ENUM["NONE"]
if file_type == "wav":
+12 -5
View File
@@ -332,8 +332,9 @@ def parse_multi_click_timing_str(value):
try:
state = cv.boolean(parts[0])
except cv.Invalid:
# pylint: disable=raise-missing-from
raise cv.Invalid(f"First word must either be ON or OFF, not {parts[0]}")
raise cv.Invalid(
f"First word must either be ON or OFF, not {parts[0]}"
) from None
if parts[1] != "for":
raise cv.Invalid(f"Second word must be 'for', got {parts[1]}")
@@ -350,7 +351,9 @@ def parse_multi_click_timing_str(value):
try:
length = cv.positive_time_period_milliseconds(parts[4])
except cv.Invalid as err:
raise cv.Invalid(f"Multi Click Grammar Parsing length failed: {err}")
raise cv.Invalid(
f"Multi Click Grammar Parsing length failed: {err}"
) from err
return {CONF_STATE: state, key: str(length)}
if parts[3] != "to":
@@ -359,12 +362,16 @@ def parse_multi_click_timing_str(value):
try:
min_length = cv.positive_time_period_milliseconds(parts[2])
except cv.Invalid as err:
raise cv.Invalid(f"Multi Click Grammar Parsing minimum length failed: {err}")
raise cv.Invalid(
f"Multi Click Grammar Parsing minimum length failed: {err}"
) from err
try:
max_length = cv.positive_time_period_milliseconds(parts[4])
except cv.Invalid as err:
raise cv.Invalid(f"Multi Click Grammar Parsing minimum length failed: {err}")
raise cv.Invalid(
f"Multi Click Grammar Parsing maximum length failed: {err}"
) from err
return {
CONF_STATE: state,
+3 -1
View File
@@ -171,7 +171,9 @@ async def to_code_base(config):
with open(path, encoding="utf-8") as f:
bsec2_iaq_config = f.read()
except Exception as e:
raise core.EsphomeError(f"Could not open binary configuration file {path}: {e}")
raise core.EsphomeError(
f"Could not open binary configuration file {path}: {e}"
) from e
# Convert retrieved BSEC2 config to an array of ints
rhs = [int(x) for x in bsec2_iaq_config.split(",")]
+1 -1
View File
@@ -325,7 +325,7 @@ def download_gfont(value):
raise cv.Invalid(
f"Could not download font at {url}, please check the fonts exists "
f"at google fonts ({e})"
)
) from e
match = re.search(r"src:\s+url\((.+)\)\s+format\('truetype'\);", req.text)
if match is None:
raise cv.Invalid(
+1 -1
View File
@@ -283,7 +283,7 @@ async def to_code(config):
try:
return Image.open(path)
except Exception as e:
raise core.EsphomeError(f"Could not load image file {path}: {e}")
raise core.EsphomeError(f"Could not load image file {path}: {e}") from e
# make a wide horizontal combined image.
images = [load_image(x) for x in config[CONF_COLOR_PALETTE_IMAGES]]
+1 -1
View File
@@ -84,7 +84,7 @@ def get_firmware(value):
req = requests.get(url, timeout=30)
req.raise_for_status()
except requests.exceptions.RequestException as e:
raise cv.Invalid(f"Could not download firmware file ({url}): {e}")
raise cv.Invalid(f"Could not download firmware file ({url}): {e}") from e
h = hashlib.new("sha256")
h.update(req.content)
@@ -173,7 +173,7 @@ def _read_audio_file_and_type(file_config):
raise cv.Invalid(
f"Unable to determine audio file type of '{path}'. "
f"Try re-encoding the file into a supported format. Details: {e}"
)
) from e
media_file_type = audio.AUDIO_FILE_TYPE_ENUM["NONE"]
if file_type in ("wav"):
+6 -4
View File
@@ -35,8 +35,9 @@ def validate_acceleration(value):
try:
value = float(value)
except ValueError:
# pylint: disable=raise-missing-from
raise cv.Invalid(f"Expected acceleration as floating point number, got {value}")
raise cv.Invalid(
f"Expected acceleration as floating point number, got {value}"
) from None
if value <= 0:
raise cv.Invalid("Acceleration must be larger than 0 steps/s^2!")
@@ -55,8 +56,9 @@ def validate_speed(value):
try:
value = float(value)
except ValueError:
# pylint: disable=raise-missing-from
raise cv.Invalid(f"Expected speed as floating point number, got {value}")
raise cv.Invalid(
f"Expected speed as floating point number, got {value}"
) from None
if value <= 0:
raise cv.Invalid("Speed must be larger than 0 steps/s!")
+1 -1
View File
@@ -188,7 +188,7 @@ def _expand_substitutions(
f"\nRelevant context:\n{err.context_trace_str()}"
f"\nSee {'->'.join(str(x) for x in path)}",
path,
)
) from err
else:
if isinstance(orig_value, ESPHomeDataBase):
value = _restore_data_base(value, orig_value)
+2 -4
View File
@@ -109,8 +109,7 @@ def _parse_cron_int(value, special_mapping, message):
try:
return int(value)
except ValueError:
# pylint: disable=raise-missing-from
raise cv.Invalid(message.format(value))
raise cv.Invalid(message.format(value)) from None
def _parse_cron_part(part, min_value, max_value, special_mapping):
@@ -134,10 +133,9 @@ def _parse_cron_part(part, min_value, max_value, special_mapping):
try:
repeat_n = int(repeat)
except ValueError:
# pylint: disable=raise-missing-from
raise cv.Invalid(
f"Repeat for '/' time expression must be an integer, got {repeat}"
)
) from None
return set(range(offset_n, max_value + 1, repeat_n))
if "-" in part:
data = part.split("-")
+3 -3
View File
@@ -67,7 +67,7 @@ def _validate_load_certificate(value):
contents = read_relative_config_path(value)
return wrapped_load_pem_x509_certificate(contents)
except ValueError as err:
raise cv.Invalid(f"Invalid certificate: {err}")
raise cv.Invalid(f"Invalid certificate: {err}") from err
def validate_certificate(value):
@@ -86,9 +86,9 @@ def _validate_load_private_key(key, cert_pw):
except ValueError as e:
raise cv.Invalid(
f"There was an error with the EAP 'password:' provided for 'key' {e}"
)
) from e
except TypeError as e:
raise cv.Invalid(f"There was an error with the EAP 'key:' provided: {e}")
raise cv.Invalid(f"There was an error with the EAP 'key:' provided: {e}") from e
def _check_private_key_cert_match(key, cert):
+1 -1
View File
@@ -53,7 +53,7 @@ def _cidr_network(value):
try:
ipaddress.ip_network(value, strict=False)
except ValueError as err:
raise cv.Invalid(f"Invalid network in CIDR notation: {err}")
raise cv.Invalid(f"Invalid network in CIDR notation: {err}") from err
return value
+13 -19
View File
@@ -544,8 +544,9 @@ def int_(value):
try:
return int(value, base)
except ValueError:
# pylint: disable=raise-missing-from
raise Invalid(f"Expected integer, but cannot parse {value} as an integer")
raise Invalid(
f"Expected integer, but cannot parse {value} as an integer"
) from None
def int_range(min=None, max=None, min_included=True, max_included=True):
@@ -844,8 +845,7 @@ def time_period_str_colon(value):
try:
parsed = [int(x) for x in value.split(":")]
except ValueError:
# pylint: disable=raise-missing-from
raise Invalid(TIME_PERIOD_ERROR.format(value))
raise Invalid(TIME_PERIOD_ERROR.format(value)) from None
if len(parsed) == 2:
hour, minute = parsed
@@ -1047,8 +1047,7 @@ def date_time(date: bool, time: bool):
try:
date_obj = datetime.strptime(value, format)
except ValueError as err:
# pylint: disable=raise-missing-from
raise Invalid(f"Invalid {exc_message}: {err}")
raise Invalid(f"Invalid {exc_message}: {err}") from err
return_value = {}
if date:
@@ -1078,8 +1077,9 @@ def mac_address(value):
try:
parts_int.append(int(part, 16))
except ValueError:
# pylint: disable=raise-missing-from
raise Invalid("MAC Address parts must be hexadecimal values from 00 to FF")
raise Invalid(
"MAC Address parts must be hexadecimal values from 00 to FF"
) from None
return core.MACAddress(*parts_int)
@@ -1096,8 +1096,7 @@ def bind_key(value, *, name="Bind key"):
try:
parts_int.append(int(part, 16))
except ValueError:
# pylint: disable=raise-missing-from
raise Invalid(f"{name} must be hex values from 00 to FF")
raise Invalid(f"{name} must be hex values from 00 to FF") from None
return "".join(f"{part:02X}" for part in parts_int)
@@ -1425,8 +1424,7 @@ def mqtt_qos(value):
try:
value = int(value)
except (TypeError, ValueError):
# pylint: disable=raise-missing-from
raise Invalid(f"MQTT Quality of Service must be integer, got {value}")
raise Invalid(f"MQTT Quality of Service must be integer, got {value}") from None
return one_of(0, 1, 2)(value)
@@ -1518,8 +1516,7 @@ def _parse_percentage(value: object) -> float:
else:
value = float(value)
except ValueError:
# pylint: disable=raise-missing-from
raise Invalid("invalid number")
raise Invalid("invalid number") from None
try:
if not has_percent_sign and (value > 1 or value < -1):
raise Invalid(
@@ -1527,9 +1524,7 @@ def _parse_percentage(value: object) -> float:
"outside -1.0 to 1.0. Please put a percent sign after the number!"
)
except TypeError:
raise Invalid( # pylint: disable=raise-missing-from
"Expected percentage or float"
)
raise Invalid("Expected percentage or float") from None
return float(value)
@@ -1702,8 +1697,7 @@ def dimensions(value):
try:
width, height = int(value[0]), int(value[1])
except ValueError:
# pylint: disable=raise-missing-from
raise Invalid("Width and height dimensions must be integers")
raise Invalid("Width and height dimensions must be integers") from None
if width <= 0 or height <= 0:
raise Invalid("Width and height must at least be 1")
return [width, height]
+1 -1
View File
@@ -107,7 +107,7 @@ def download_content(url: str, path: Path, timeout=NETWORK_TIMEOUT) -> bytes:
e,
)
return path.read_bytes()
raise cv.Invalid(f"Could not download from {url}: {e}")
raise cv.Invalid(f"Could not download from {url}: {e}") from e
path.parent.mkdir(parents=True, exist_ok=True)
data = req.content
+1 -2
View File
@@ -39,8 +39,7 @@ class _Schema(vol.Schema):
try:
res = extra(res)
except vol.Invalid as err:
# pylint: disable=raise-missing-from
raise ensure_multiple_invalid(err)
raise ensure_multiple_invalid(err) from err
return res
def _compile_mapping(self, schema, invalid_msg=None):
+1 -2
View File
@@ -338,10 +338,9 @@ class ESPHomeLoaderMixin:
try:
hash(key)
except TypeError:
# pylint: disable=raise-missing-from
raise yaml.constructor.ConstructorError(
f'Invalid key "{key}" (not hashable)', key_node.start_mark
)
) from None
key = make_data_base(str(key))
key.from_node(key_node)