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