mirror of
https://github.com/esphome/esphome.git
synced 2026-05-21 15:15:11 +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)
|
||||
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)]
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(",")]
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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]]
|
||||
|
||||
@@ -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"):
|
||||
|
||||
@@ -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!")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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("-")
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user