diff --git a/pyasic/config/fans.py b/pyasic/config/fans.py index dc0ecee25..dc832029c 100644 --- a/pyasic/config/fans.py +++ b/pyasic/config/fans.py @@ -15,7 +15,7 @@ # ------------------------------------------------------------------------------ from __future__ import annotations -from typing import TypeVar +from typing import Any, TypeVar from pydantic import Field @@ -337,28 +337,45 @@ def from_vnish(cls, web_settings: dict): @classmethod def from_boser(cls, grpc_miner_conf: dict): - try: - temperature_conf = grpc_miner_conf["temperature"] - except LookupError: + temperature_conf = grpc_miner_conf.get("temperature") + if not isinstance(temperature_conf, dict): return cls.default() - keys = temperature_conf.keys() - if "auto" in keys: - if "minimumRequiredFans" in keys: - return cls.normal(minimum_fans=temperature_conf["minimumRequiredFans"]) + def _maybe_int(value: Any) -> int | None: + if isinstance(value, (int, float, str)) and value != "": + try: + return int(value) + except (TypeError, ValueError): + return None + return None + + min_fans = _maybe_int(temperature_conf.get("minimumRequiredFans")) + + def _build_conf(conf_section: object) -> dict: + conf: dict = {} + if isinstance(conf_section, dict): + speed = _maybe_int(conf_section.get("fanSpeedRatio")) + if speed is not None: + conf["speed"] = speed + if min_fans is not None: + conf["minimum_fans"] = min_fans + return conf + + if "auto" in temperature_conf: + if min_fans is not None: + return cls.normal(minimum_fans=min_fans) return cls.normal() - if "manual" in keys: - conf = {} - if "fanSpeedRatio" in temperature_conf["manual"].keys(): - conf["speed"] = int(temperature_conf["manual"]["fanSpeedRatio"]) - if "minimumRequiredFans" in keys: - conf["minimum_fans"] = int(temperature_conf["minimumRequiredFans"]) - return cls.manual(**conf) - if "disabled" in keys: - conf = {} - if "fanSpeedRatio" in temperature_conf["disabled"].keys(): - conf["speed"] = int(temperature_conf["disabled"]["fanSpeedRatio"]) + + if "manual" in temperature_conf: + conf = _build_conf(temperature_conf.get("manual")) return cls.manual(**conf) + + if "disabled" in temperature_conf: + conf = _build_conf(temperature_conf.get("disabled")) + if conf.get("speed") is not None: + return cls.manual(**conf) + return cls.immersion() + return cls.default() @classmethod diff --git a/pyasic/config/temperature.py b/pyasic/config/temperature.py index e16cc00c4..db5b1cb87 100644 --- a/pyasic/config/temperature.py +++ b/pyasic/config/temperature.py @@ -114,32 +114,44 @@ def from_vnish(cls, web_settings: dict) -> TemperatureConfig: @classmethod def from_boser(cls, grpc_miner_conf: dict) -> TemperatureConfig: - try: - temperature_conf = grpc_miner_conf["temperature"] - except KeyError: + temperature_conf = grpc_miner_conf.get("temperature") + if not isinstance(temperature_conf, dict): return cls.default() root_key = None for key in ["auto", "manual", "disabled"]: - if key in temperature_conf.keys(): + if key in temperature_conf: root_key = key break if root_key is None: return cls.default() - conf = {} - keys = temperature_conf[root_key].keys() - if "targetTemperature" in keys: - conf["target"] = int( - temperature_conf[root_key]["targetTemperature"]["degreeC"] - ) - if "hotTemperature" in keys: - conf["hot"] = int(temperature_conf[root_key]["hotTemperature"]["degreeC"]) - if "dangerousTemperature" in keys: - conf["danger"] = int( - temperature_conf[root_key]["dangerousTemperature"]["degreeC"] - ) + raw_conf = temperature_conf.get(root_key) or {} + if not isinstance(raw_conf, dict): + return cls.default() + def _read_temp(temp_block: object) -> int | None: + if isinstance(temp_block, dict): + temp_value = temp_block.get("degreeC") + else: + temp_value = temp_block + try: + return int(temp_value) if temp_value is not None else None + except (TypeError, ValueError): + return None + + conf: dict = {} + target_temp = _read_temp(raw_conf.get("targetTemperature")) + if target_temp is not None: + conf["target"] = target_temp + hot_temp = _read_temp(raw_conf.get("hotTemperature")) + if hot_temp is not None: + conf["hot"] = hot_temp + danger_temp = _read_temp(raw_conf.get("dangerousTemperature")) + if danger_temp is not None: + conf["danger"] = danger_temp + + if conf: return cls(**conf) return cls.default() diff --git a/tests/config_tests/test_boser_25_01.py b/tests/config_tests/test_boser_25_01.py new file mode 100644 index 000000000..640d1d7c2 --- /dev/null +++ b/tests/config_tests/test_boser_25_01.py @@ -0,0 +1,37 @@ +import unittest + +from pyasic.config import FanModeConfig, TemperatureConfig + + +class TestBoserConfig25_01(unittest.TestCase): + def test_fan_mode_handles_null_temperature(self): + grpc_conf = {"temperature": None} + self.assertEqual(FanModeConfig.default(), FanModeConfig.from_boser(grpc_conf)) + + def test_fan_mode_handles_empty_manual(self): + grpc_conf = {"temperature": {"manual": None, "minimumRequiredFans": None}} + self.assertEqual(FanModeConfig.manual(), FanModeConfig.from_boser(grpc_conf)) + + def test_fan_mode_auto_with_missing_min_fans(self): + grpc_conf = {"temperature": {"auto": {}, "minimumRequiredFans": None}} + self.assertEqual(FanModeConfig.normal(), FanModeConfig.from_boser(grpc_conf)) + + def test_fan_mode_disabled_without_speed_defaults_to_immersion(self): + grpc_conf = {"temperature": {"disabled": {}, "minimumRequiredFans": 0}} + self.assertEqual(FanModeConfig.immersion(), FanModeConfig.from_boser(grpc_conf)) + + def test_temperature_allows_partial_payload(self): + grpc_conf = {"temperature": {"auto": {"targetTemperature": {"degreeC": 70}}}} + self.assertEqual( + TemperatureConfig(target=70), TemperatureConfig.from_boser(grpc_conf) + ) + + def test_temperature_returns_default_on_null(self): + grpc_conf = {"temperature": None} + self.assertEqual( + TemperatureConfig.default(), TemperatureConfig.from_boser(grpc_conf) + ) + + +if __name__ == "__main__": + unittest.main()