Code styling tweaks to core utils & YAML loader (#85433)
Code styling tweaks to core utils
This commit is contained in:
parent
aa1c539683
commit
7adb8d5ddc
11 changed files with 68 additions and 51 deletions
|
@ -23,8 +23,7 @@ RE_SANITIZE_PATH = re.compile(r"(~|\.(\.)+)")
|
||||||
|
|
||||||
|
|
||||||
def raise_if_invalid_filename(filename: str) -> None:
|
def raise_if_invalid_filename(filename: str) -> None:
|
||||||
"""
|
"""Check if a filename is valid.
|
||||||
Check if a filename is valid.
|
|
||||||
|
|
||||||
Raises a ValueError if the filename is invalid.
|
Raises a ValueError if the filename is invalid.
|
||||||
"""
|
"""
|
||||||
|
@ -33,8 +32,7 @@ def raise_if_invalid_filename(filename: str) -> None:
|
||||||
|
|
||||||
|
|
||||||
def raise_if_invalid_path(path: str) -> None:
|
def raise_if_invalid_path(path: str) -> None:
|
||||||
"""
|
"""Check if a path is valid.
|
||||||
Check if a path is valid.
|
|
||||||
|
|
||||||
Raises a ValueError if the path is invalid.
|
Raises a ValueError if the path is invalid.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -510,8 +510,7 @@ def color_temperature_to_hs(color_temperature_kelvin: float) -> tuple[float, flo
|
||||||
def color_temperature_to_rgb(
|
def color_temperature_to_rgb(
|
||||||
color_temperature_kelvin: float,
|
color_temperature_kelvin: float,
|
||||||
) -> tuple[float, float, float]:
|
) -> tuple[float, float, float]:
|
||||||
"""
|
"""Return an RGB color from a color temperature in Kelvin.
|
||||||
Return an RGB color from a color temperature in Kelvin.
|
|
||||||
|
|
||||||
This is a rough approximation based on the formula provided by T. Helland
|
This is a rough approximation based on the formula provided by T. Helland
|
||||||
http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/
|
http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/
|
||||||
|
@ -581,8 +580,7 @@ def _white_levels_to_color_temperature(
|
||||||
|
|
||||||
|
|
||||||
def _clamp(color_component: float, minimum: float = 0, maximum: float = 255) -> float:
|
def _clamp(color_component: float, minimum: float = 0, maximum: float = 255) -> float:
|
||||||
"""
|
"""Clamp the given color component value between the given min and max values.
|
||||||
Clamp the given color component value between the given min and max values.
|
|
||||||
|
|
||||||
The range defined by the minimum and maximum values is inclusive, i.e. given a
|
The range defined by the minimum and maximum values is inclusive, i.e. given a
|
||||||
color_component of 0 and a minimum of 10, the returned value is 10.
|
color_component of 0 and a minimum of 10, the returned value is 10.
|
||||||
|
@ -644,8 +642,7 @@ def get_distance_between_two_points(one: XYPoint, two: XYPoint) -> float:
|
||||||
|
|
||||||
|
|
||||||
def get_closest_point_to_line(A: XYPoint, B: XYPoint, P: XYPoint) -> XYPoint:
|
def get_closest_point_to_line(A: XYPoint, B: XYPoint, P: XYPoint) -> XYPoint:
|
||||||
"""
|
"""Find the closest point from P to a line defined by A and B.
|
||||||
Find the closest point from P to a line defined by A and B.
|
|
||||||
|
|
||||||
This point will be reproducible by the lamp
|
This point will be reproducible by the lamp
|
||||||
as it is on the edge of the gamut.
|
as it is on the edge of the gamut.
|
||||||
|
@ -667,8 +664,7 @@ def get_closest_point_to_line(A: XYPoint, B: XYPoint, P: XYPoint) -> XYPoint:
|
||||||
def get_closest_point_to_point(
|
def get_closest_point_to_point(
|
||||||
xy_tuple: tuple[float, float], Gamut: GamutType
|
xy_tuple: tuple[float, float], Gamut: GamutType
|
||||||
) -> tuple[float, float]:
|
) -> tuple[float, float]:
|
||||||
"""
|
"""Get the closest matching color within the gamut of the light.
|
||||||
Get the closest matching color within the gamut of the light.
|
|
||||||
|
|
||||||
Should only be used if the supplied color is outside of the color gamut.
|
Should only be used if the supplied color is outside of the color gamut.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -267,8 +267,7 @@ def parse_time(time_str: str) -> dt.time | None:
|
||||||
|
|
||||||
|
|
||||||
def get_age(date: dt.datetime) -> str:
|
def get_age(date: dt.datetime) -> str:
|
||||||
"""
|
"""Take a datetime and return its "age" as a string.
|
||||||
Take a datetime and return its "age" as a string.
|
|
||||||
|
|
||||||
The age can be in second, minute, hour, day, month or year. Only the
|
The age can be in second, minute, hour, day, month or year. Only the
|
||||||
biggest unit is considered, e.g. if it's 2 days and 3 hours, "2 days" will
|
biggest unit is considered, e.g. if it's 2 days and 3 hours, "2 days" will
|
||||||
|
@ -328,7 +327,9 @@ def parse_time_expression(parameter: Any, min_value: int, max_value: int) -> lis
|
||||||
def _dst_offset_diff(dattim: dt.datetime) -> dt.timedelta:
|
def _dst_offset_diff(dattim: dt.datetime) -> dt.timedelta:
|
||||||
"""Return the offset when crossing the DST barrier."""
|
"""Return the offset when crossing the DST barrier."""
|
||||||
delta = dt.timedelta(hours=24)
|
delta = dt.timedelta(hours=24)
|
||||||
return (dattim + delta).utcoffset() - (dattim - delta).utcoffset() # type: ignore[operator]
|
return (dattim + delta).utcoffset() - ( # type: ignore[operator]
|
||||||
|
dattim - delta
|
||||||
|
).utcoffset()
|
||||||
|
|
||||||
|
|
||||||
def _lower_bound(arr: list[int], cmp: int) -> int | None:
|
def _lower_bound(arr: list[int], cmp: int) -> int | None:
|
||||||
|
@ -360,7 +361,8 @@ def find_next_time_expression_time(
|
||||||
raise ValueError("Cannot find a next time: Time expression never matches!")
|
raise ValueError("Cannot find a next time: Time expression never matches!")
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
# Reset microseconds and fold; fold (for ambiguous DST times) will be handled later
|
# Reset microseconds and fold; fold (for ambiguous DST times) will be
|
||||||
|
# handled later.
|
||||||
result = now.replace(microsecond=0, fold=0)
|
result = now.replace(microsecond=0, fold=0)
|
||||||
|
|
||||||
# Match next second
|
# Match next second
|
||||||
|
@ -408,11 +410,12 @@ def find_next_time_expression_time(
|
||||||
# -> trigger on the next time that 1. matches the pattern and 2. does exist
|
# -> trigger on the next time that 1. matches the pattern and 2. does exist
|
||||||
# for example:
|
# for example:
|
||||||
# on 2021.03.28 02:00:00 in CET timezone clocks are turned forward an hour
|
# on 2021.03.28 02:00:00 in CET timezone clocks are turned forward an hour
|
||||||
# with pattern "02:30", don't run on 28 mar (such a wall time does not exist on this day)
|
# with pattern "02:30", don't run on 28 mar (such a wall time does not
|
||||||
# instead run at 02:30 the next day
|
# exist on this day) instead run at 02:30 the next day
|
||||||
|
|
||||||
# We solve this edge case by just iterating one second until the result exists
|
# We solve this edge case by just iterating one second until the result
|
||||||
# (max. 3600 operations, which should be fine for an edge case that happens once a year)
|
# exists (max. 3600 operations, which should be fine for an edge case that
|
||||||
|
# happens once a year)
|
||||||
now += dt.timedelta(seconds=1)
|
now += dt.timedelta(seconds=1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -420,29 +423,34 @@ def find_next_time_expression_time(
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# When leaving DST and clocks are turned backward.
|
# When leaving DST and clocks are turned backward.
|
||||||
# Then there are wall clock times that are ambiguous i.e. exist with DST and without DST
|
# Then there are wall clock times that are ambiguous i.e. exist with DST and
|
||||||
# The logic above does not take into account if a given pattern matches _twice_
|
# without DST. The logic above does not take into account if a given pattern
|
||||||
# in a day.
|
# matches _twice_ in a day.
|
||||||
# Example: on 2021.10.31 02:00:00 in CET timezone clocks are turned backward an hour
|
# Example: on 2021.10.31 02:00:00 in CET timezone clocks are turned
|
||||||
|
# backward an hour.
|
||||||
|
|
||||||
if _datetime_ambiguous(result):
|
if _datetime_ambiguous(result):
|
||||||
# `now` and `result` are both ambiguous, so the next match happens
|
# `now` and `result` are both ambiguous, so the next match happens
|
||||||
# _within_ the current fold.
|
# _within_ the current fold.
|
||||||
|
|
||||||
# Examples:
|
# Examples:
|
||||||
# 1. 2021.10.31 02:00:00+02:00 with pattern 02:30 -> 2021.10.31 02:30:00+02:00
|
# 1. 2021.10.31 02:00:00+02:00 with pattern 02:30
|
||||||
# 2. 2021.10.31 02:00:00+01:00 with pattern 02:30 -> 2021.10.31 02:30:00+01:00
|
# -> 2021.10.31 02:30:00+02:00
|
||||||
|
# 2. 2021.10.31 02:00:00+01:00 with pattern 02:30
|
||||||
|
# -> 2021.10.31 02:30:00+01:00
|
||||||
return result.replace(fold=now.fold)
|
return result.replace(fold=now.fold)
|
||||||
|
|
||||||
if now.fold == 0:
|
if now.fold == 0:
|
||||||
# `now` is in the first fold, but result is not ambiguous (meaning it no longer matches
|
# `now` is in the first fold, but result is not ambiguous (meaning it no
|
||||||
# within the fold).
|
# longer matches within the fold).
|
||||||
# -> Check if result matches in the next fold. If so, emit that match
|
# -> Check if result matches in the next fold. If so, emit that match
|
||||||
|
|
||||||
# Turn back the time by the DST offset, effectively run the algorithm on the first fold
|
# Turn back the time by the DST offset, effectively run the algorithm on
|
||||||
# If it matches on the first fold, that means it will also match on the second one.
|
# the first fold. If it matches on the first fold, that means it will also
|
||||||
|
# match on the second one.
|
||||||
|
|
||||||
# Example: 2021.10.31 02:45:00+02:00 with pattern 02:30 -> 2021.10.31 02:30:00+01:00
|
# Example: 2021.10.31 02:45:00+02:00 with pattern 02:30
|
||||||
|
# -> 2021.10.31 02:30:00+01:00
|
||||||
|
|
||||||
check_result = find_next_time_expression_time(
|
check_result = find_next_time_expression_time(
|
||||||
now + _dst_offset_diff(now), seconds, minutes, hours
|
now + _dst_offset_diff(now), seconds, minutes, hours
|
||||||
|
|
|
@ -124,7 +124,8 @@ def find_paths_unserializable_data(
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# We convert objects with as_dict to their dict values so we can find bad data inside it
|
# We convert objects with as_dict to their dict values
|
||||||
|
# so we can find bad data inside it
|
||||||
if hasattr(obj, "as_dict"):
|
if hasattr(obj, "as_dict"):
|
||||||
desc = obj.__class__.__name__
|
desc = obj.__class__.__name__
|
||||||
if isinstance(obj, State):
|
if isinstance(obj, State):
|
||||||
|
|
|
@ -79,8 +79,7 @@ def distance(
|
||||||
def vincenty(
|
def vincenty(
|
||||||
point1: tuple[float, float], point2: tuple[float, float], miles: bool = False
|
point1: tuple[float, float], point2: tuple[float, float], miles: bool = False
|
||||||
) -> float | None:
|
) -> float | None:
|
||||||
"""
|
"""Vincenty formula (inverse method) to calculate the distance.
|
||||||
Vincenty formula (inverse method) to calculate the distance.
|
|
||||||
|
|
||||||
Result in kilometers or miles between two points on the surface of a
|
Result in kilometers or miles between two points on the surface of a
|
||||||
spheroid.
|
spheroid.
|
||||||
|
|
|
@ -52,7 +52,9 @@ def is_installed(package: str) -> bool:
|
||||||
# was aborted while in progress see
|
# was aborted while in progress see
|
||||||
# https://github.com/home-assistant/core/issues/47699
|
# https://github.com/home-assistant/core/issues/47699
|
||||||
if installed_version is None:
|
if installed_version is None:
|
||||||
_LOGGER.error("Installed version for %s resolved to None", req.project_name) # type: ignore[unreachable]
|
_LOGGER.error( # type: ignore[unreachable]
|
||||||
|
"Installed version for %s resolved to None", req.project_name
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
return installed_version in req
|
return installed_version in req
|
||||||
except PackageNotFoundError:
|
except PackageNotFoundError:
|
||||||
|
|
|
@ -15,8 +15,7 @@ def draw_box(
|
||||||
text: str = "",
|
text: str = "",
|
||||||
color: tuple[int, int, int] = (255, 255, 0),
|
color: tuple[int, int, int] = (255, 255, 0),
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""Draw a bounding box on and image.
|
||||||
Draw a bounding box on and image.
|
|
||||||
|
|
||||||
The bounding box is defined by the tuple (y_min, x_min, y_max, x_max)
|
The bounding box is defined by the tuple (y_min, x_min, y_max, x_max)
|
||||||
where the coordinates are floats in the range [0.0, 1.0] and
|
where the coordinates are floats in the range [0.0, 1.0] and
|
||||||
|
|
|
@ -8,12 +8,12 @@ import certifi
|
||||||
def client_context() -> ssl.SSLContext:
|
def client_context() -> ssl.SSLContext:
|
||||||
"""Return an SSL context for making requests."""
|
"""Return an SSL context for making requests."""
|
||||||
|
|
||||||
# Reuse environment variable definition from requests, since it's already a requirement
|
# Reuse environment variable definition from requests, since it's already a
|
||||||
# If the environment variable has no value, fall back to using certs from certifi package
|
# requirement. If the environment variable has no value, fall back to using
|
||||||
|
# certs from certifi package.
|
||||||
cafile = environ.get("REQUESTS_CA_BUNDLE", certifi.where())
|
cafile = environ.get("REQUESTS_CA_BUNDLE", certifi.where())
|
||||||
|
|
||||||
context = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH, cafile=cafile)
|
return ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH, cafile=cafile)
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
def server_context_modern() -> ssl.SSLContext:
|
def server_context_modern() -> ssl.SSLContext:
|
||||||
|
|
|
@ -342,8 +342,8 @@ class TemperatureConverter(BaseUnitConverter):
|
||||||
For converting an interval between two temperatures, please use
|
For converting an interval between two temperatures, please use
|
||||||
`convert_interval` instead.
|
`convert_interval` instead.
|
||||||
"""
|
"""
|
||||||
# We cannot use the implementation from BaseUnitConverter here because the temperature
|
# We cannot use the implementation from BaseUnitConverter here because the
|
||||||
# units do not use the same floor: 0°C, 0°F and 0K do not align
|
# temperature units do not use the same floor: 0°C, 0°F and 0K do not align
|
||||||
if from_unit == to_unit:
|
if from_unit == to_unit:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
|
@ -195,7 +195,9 @@ class UnitSystem:
|
||||||
raise TypeError(f"{wind_speed!s} is not a numeric value.")
|
raise TypeError(f"{wind_speed!s} is not a numeric value.")
|
||||||
|
|
||||||
# type ignore: https://github.com/python/mypy/issues/7207
|
# type ignore: https://github.com/python/mypy/issues/7207
|
||||||
return SpeedConverter.convert(wind_speed, from_unit, self.wind_speed_unit) # type: ignore[unreachable]
|
return SpeedConverter.convert( # type: ignore[unreachable]
|
||||||
|
wind_speed, from_unit, self.wind_speed_unit
|
||||||
|
)
|
||||||
|
|
||||||
def volume(self, volume: float | None, from_unit: str) -> float:
|
def volume(self, volume: float | None, from_unit: str) -> float:
|
||||||
"""Convert the given volume to this unit system."""
|
"""Convert the given volume to this unit system."""
|
||||||
|
@ -203,7 +205,9 @@ class UnitSystem:
|
||||||
raise TypeError(f"{volume!s} is not a numeric value.")
|
raise TypeError(f"{volume!s} is not a numeric value.")
|
||||||
|
|
||||||
# type ignore: https://github.com/python/mypy/issues/7207
|
# type ignore: https://github.com/python/mypy/issues/7207
|
||||||
return VolumeConverter.convert(volume, from_unit, self.volume_unit) # type: ignore[unreachable]
|
return VolumeConverter.convert( # type: ignore[unreachable]
|
||||||
|
volume, from_unit, self.volume_unit
|
||||||
|
)
|
||||||
|
|
||||||
def as_dict(self) -> dict[str, str]:
|
def as_dict(self) -> dict[str, str]:
|
||||||
"""Convert the unit system to a dictionary."""
|
"""Convert the unit system to a dictionary."""
|
||||||
|
|
|
@ -18,7 +18,9 @@ try:
|
||||||
HAS_C_LOADER = True
|
HAS_C_LOADER = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
HAS_C_LOADER = False
|
HAS_C_LOADER = False
|
||||||
from yaml import SafeLoader as FastestAvailableSafeLoader # type: ignore[assignment]
|
from yaml import ( # type: ignore[assignment]
|
||||||
|
SafeLoader as FastestAvailableSafeLoader,
|
||||||
|
)
|
||||||
|
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
|
||||||
|
@ -132,10 +134,14 @@ class SafeLineLoader(yaml.SafeLoader):
|
||||||
super().__init__(stream)
|
super().__init__(stream)
|
||||||
self.secrets = secrets
|
self.secrets = secrets
|
||||||
|
|
||||||
def compose_node(self, parent: yaml.nodes.Node, index: int) -> yaml.nodes.Node: # type: ignore[override]
|
def compose_node( # type: ignore[override]
|
||||||
|
self, parent: yaml.nodes.Node, index: int
|
||||||
|
) -> yaml.nodes.Node:
|
||||||
"""Annotate a node with the first line it was seen."""
|
"""Annotate a node with the first line it was seen."""
|
||||||
last_line: int = self.line
|
last_line: int = self.line
|
||||||
node: yaml.nodes.Node = super().compose_node(parent, index) # type: ignore[assignment]
|
node: yaml.nodes.Node = super().compose_node( # type: ignore[assignment]
|
||||||
|
parent, index
|
||||||
|
)
|
||||||
node.__line__ = last_line + 1 # type: ignore[attr-defined]
|
node.__line__ = last_line + 1 # type: ignore[attr-defined]
|
||||||
return node
|
return node
|
||||||
|
|
||||||
|
@ -226,7 +232,9 @@ def _add_reference(obj: _DictT, loader: LoaderType, node: yaml.nodes.Node) -> _D
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
def _add_reference(obj, loader: LoaderType, node: yaml.nodes.Node): # type: ignore[no-untyped-def]
|
def _add_reference( # type: ignore[no-untyped-def]
|
||||||
|
obj, loader: LoaderType, node: yaml.nodes.Node
|
||||||
|
):
|
||||||
"""Add file reference information to an object."""
|
"""Add file reference information to an object."""
|
||||||
if isinstance(obj, list):
|
if isinstance(obj, list):
|
||||||
obj = NodeListClass(obj)
|
obj = NodeListClass(obj)
|
||||||
|
@ -337,7 +345,9 @@ def _ordered_dict(loader: LoaderType, node: yaml.nodes.MappingNode) -> OrderedDi
|
||||||
fname = loader.get_stream_name()
|
fname = loader.get_stream_name()
|
||||||
raise yaml.MarkedYAMLError(
|
raise yaml.MarkedYAMLError(
|
||||||
context=f'invalid key: "{key}"',
|
context=f'invalid key: "{key}"',
|
||||||
context_mark=yaml.Mark(fname, 0, line, -1, None, None), # type: ignore[arg-type]
|
context_mark=yaml.Mark(
|
||||||
|
fname, 0, line, -1, None, None # type: ignore[arg-type]
|
||||||
|
),
|
||||||
) from exc
|
) from exc
|
||||||
|
|
||||||
if key in seen:
|
if key in seen:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue