Skip to content
This repository was archived by the owner on Jul 6, 2026. It is now read-only.

Commit 4d3335c

Browse files
authored
Handle BOSer 25.01 config gaps (#403)
1 parent 29b7801 commit 4d3335c

3 files changed

Lines changed: 101 additions & 35 deletions

File tree

pyasic/config/fans.py

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# ------------------------------------------------------------------------------
1616
from __future__ import annotations
1717

18-
from typing import TypeVar
18+
from typing import Any, TypeVar
1919

2020
from pydantic import Field
2121

@@ -337,28 +337,45 @@ def from_vnish(cls, web_settings: dict):
337337

338338
@classmethod
339339
def from_boser(cls, grpc_miner_conf: dict):
340-
try:
341-
temperature_conf = grpc_miner_conf["temperature"]
342-
except LookupError:
340+
temperature_conf = grpc_miner_conf.get("temperature")
341+
if not isinstance(temperature_conf, dict):
343342
return cls.default()
344343

345-
keys = temperature_conf.keys()
346-
if "auto" in keys:
347-
if "minimumRequiredFans" in keys:
348-
return cls.normal(minimum_fans=temperature_conf["minimumRequiredFans"])
344+
def _maybe_int(value: Any) -> int | None:
345+
if isinstance(value, (int, float, str)) and value != "":
346+
try:
347+
return int(value)
348+
except (TypeError, ValueError):
349+
return None
350+
return None
351+
352+
min_fans = _maybe_int(temperature_conf.get("minimumRequiredFans"))
353+
354+
def _build_conf(conf_section: object) -> dict:
355+
conf: dict = {}
356+
if isinstance(conf_section, dict):
357+
speed = _maybe_int(conf_section.get("fanSpeedRatio"))
358+
if speed is not None:
359+
conf["speed"] = speed
360+
if min_fans is not None:
361+
conf["minimum_fans"] = min_fans
362+
return conf
363+
364+
if "auto" in temperature_conf:
365+
if min_fans is not None:
366+
return cls.normal(minimum_fans=min_fans)
349367
return cls.normal()
350-
if "manual" in keys:
351-
conf = {}
352-
if "fanSpeedRatio" in temperature_conf["manual"].keys():
353-
conf["speed"] = int(temperature_conf["manual"]["fanSpeedRatio"])
354-
if "minimumRequiredFans" in keys:
355-
conf["minimum_fans"] = int(temperature_conf["minimumRequiredFans"])
356-
return cls.manual(**conf)
357-
if "disabled" in keys:
358-
conf = {}
359-
if "fanSpeedRatio" in temperature_conf["disabled"].keys():
360-
conf["speed"] = int(temperature_conf["disabled"]["fanSpeedRatio"])
368+
369+
if "manual" in temperature_conf:
370+
conf = _build_conf(temperature_conf.get("manual"))
361371
return cls.manual(**conf)
372+
373+
if "disabled" in temperature_conf:
374+
conf = _build_conf(temperature_conf.get("disabled"))
375+
if conf.get("speed") is not None:
376+
return cls.manual(**conf)
377+
return cls.immersion()
378+
362379
return cls.default()
363380

364381
@classmethod

pyasic/config/temperature.py

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -114,32 +114,44 @@ def from_vnish(cls, web_settings: dict) -> TemperatureConfig:
114114

115115
@classmethod
116116
def from_boser(cls, grpc_miner_conf: dict) -> TemperatureConfig:
117-
try:
118-
temperature_conf = grpc_miner_conf["temperature"]
119-
except KeyError:
117+
temperature_conf = grpc_miner_conf.get("temperature")
118+
if not isinstance(temperature_conf, dict):
120119
return cls.default()
121120

122121
root_key = None
123122
for key in ["auto", "manual", "disabled"]:
124-
if key in temperature_conf.keys():
123+
if key in temperature_conf:
125124
root_key = key
126125
break
127126
if root_key is None:
128127
return cls.default()
129128

130-
conf = {}
131-
keys = temperature_conf[root_key].keys()
132-
if "targetTemperature" in keys:
133-
conf["target"] = int(
134-
temperature_conf[root_key]["targetTemperature"]["degreeC"]
135-
)
136-
if "hotTemperature" in keys:
137-
conf["hot"] = int(temperature_conf[root_key]["hotTemperature"]["degreeC"])
138-
if "dangerousTemperature" in keys:
139-
conf["danger"] = int(
140-
temperature_conf[root_key]["dangerousTemperature"]["degreeC"]
141-
)
129+
raw_conf = temperature_conf.get(root_key) or {}
130+
if not isinstance(raw_conf, dict):
131+
return cls.default()
142132

133+
def _read_temp(temp_block: object) -> int | None:
134+
if isinstance(temp_block, dict):
135+
temp_value = temp_block.get("degreeC")
136+
else:
137+
temp_value = temp_block
138+
try:
139+
return int(temp_value) if temp_value is not None else None
140+
except (TypeError, ValueError):
141+
return None
142+
143+
conf: dict = {}
144+
target_temp = _read_temp(raw_conf.get("targetTemperature"))
145+
if target_temp is not None:
146+
conf["target"] = target_temp
147+
hot_temp = _read_temp(raw_conf.get("hotTemperature"))
148+
if hot_temp is not None:
149+
conf["hot"] = hot_temp
150+
danger_temp = _read_temp(raw_conf.get("dangerousTemperature"))
151+
if danger_temp is not None:
152+
conf["danger"] = danger_temp
153+
154+
if conf:
143155
return cls(**conf)
144156
return cls.default()
145157

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import unittest
2+
3+
from pyasic.config import FanModeConfig, TemperatureConfig
4+
5+
6+
class TestBoserConfig25_01(unittest.TestCase):
7+
def test_fan_mode_handles_null_temperature(self):
8+
grpc_conf = {"temperature": None}
9+
self.assertEqual(FanModeConfig.default(), FanModeConfig.from_boser(grpc_conf))
10+
11+
def test_fan_mode_handles_empty_manual(self):
12+
grpc_conf = {"temperature": {"manual": None, "minimumRequiredFans": None}}
13+
self.assertEqual(FanModeConfig.manual(), FanModeConfig.from_boser(grpc_conf))
14+
15+
def test_fan_mode_auto_with_missing_min_fans(self):
16+
grpc_conf = {"temperature": {"auto": {}, "minimumRequiredFans": None}}
17+
self.assertEqual(FanModeConfig.normal(), FanModeConfig.from_boser(grpc_conf))
18+
19+
def test_fan_mode_disabled_without_speed_defaults_to_immersion(self):
20+
grpc_conf = {"temperature": {"disabled": {}, "minimumRequiredFans": 0}}
21+
self.assertEqual(FanModeConfig.immersion(), FanModeConfig.from_boser(grpc_conf))
22+
23+
def test_temperature_allows_partial_payload(self):
24+
grpc_conf = {"temperature": {"auto": {"targetTemperature": {"degreeC": 70}}}}
25+
self.assertEqual(
26+
TemperatureConfig(target=70), TemperatureConfig.from_boser(grpc_conf)
27+
)
28+
29+
def test_temperature_returns_default_on_null(self):
30+
grpc_conf = {"temperature": None}
31+
self.assertEqual(
32+
TemperatureConfig.default(), TemperatureConfig.from_boser(grpc_conf)
33+
)
34+
35+
36+
if __name__ == "__main__":
37+
unittest.main()

0 commit comments

Comments
 (0)