Skip to content

Commit 4dd6a7e

Browse files
Fix mypy type errors, failing tests, and add safety warnings
- Add ConfigEntry None checks in coordinator.py to satisfy mypy - Add proper type annotations for select entities list in select.py - Add dict[str, Any] annotation for attrs in extra_state_attributes - Remove unused type:ignore comment from climate.py - Update coordinator tests to pass config_entry parameter - Update options flow test to expect experimental_features in result - Add detailed safety warnings in strings.json about remote temperature risks
1 parent dba4a27 commit 4dd6a7e

6 files changed

Lines changed: 36 additions & 22 deletions

File tree

custom_components/mitsubishi/climate.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ class MitsubishiClimate(MitsubishiEntity, ClimateEntity):
123123
| ClimateEntityFeature.TARGET_TEMPERATURE
124124
| ClimateEntityFeature.FAN_MODE
125125
| ClimateEntityFeature.SWING_MODE
126-
| ClimateEntityFeature.SWING_HORIZONTAL_MODE # type: ignore[attr-defined]
126+
| ClimateEntityFeature.SWING_HORIZONTAL_MODE
127127
)
128128

129129
def __init__(

custom_components/mitsubishi/coordinator.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,13 @@ def set_remote_temp_mode(self, enabled: bool) -> None:
5353
_LOGGER.info("Remote temperature mode set to: %s", enabled)
5454

5555
# Persist to config entry options
56-
new_options = dict(self.config_entry.options)
57-
new_options[CONF_REMOTE_TEMP_MODE] = enabled
58-
self.hass.config_entries.async_update_entry(
59-
self.config_entry,
60-
options=new_options,
61-
)
56+
if self.config_entry is not None:
57+
new_options = dict(self.config_entry.options)
58+
new_options[CONF_REMOTE_TEMP_MODE] = enabled
59+
self.hass.config_entries.async_update_entry(
60+
self.config_entry,
61+
options=new_options,
62+
)
6263

6364
@property
6465
def remote_temp_mode(self) -> bool:
@@ -68,6 +69,8 @@ def remote_temp_mode(self) -> bool:
6869
@property
6970
def experimental_features_enabled(self) -> bool:
7071
"""Return whether experimental features are enabled."""
72+
if self.config_entry is None:
73+
return False
7174
return self.config_entry.options.get(CONF_EXPERIMENTAL_FEATURES, False)
7275

7376
async def get_unit_info(self) -> dict:
@@ -103,6 +106,8 @@ async def _async_update_data(self) -> ParsedDeviceState:
103106

104107
async def _send_remote_temperature(self) -> None:
105108
"""Send remote temperature to AC if configured and available."""
109+
if self.config_entry is None:
110+
return
106111
external_entity_id = self.config_entry.options.get(CONF_EXTERNAL_TEMP_ENTITY)
107112

108113
if not external_entity_id:

custom_components/mitsubishi/select.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ async def async_setup_entry(
3030
"""Set up Mitsubishi select entities."""
3131
coordinator = hass.data[DOMAIN][config_entry.entry_id]
3232

33-
entities = [MitsubishiPowerSavingSelect(coordinator, config_entry)]
33+
entities: list[SelectEntity] = [MitsubishiPowerSavingSelect(coordinator, config_entry)]
3434

3535
# Only add Temperature Source selector if experimental features are enabled
3636
if config_entry.options.get(CONF_EXPERIMENTAL_FEATURES, False):
@@ -153,7 +153,7 @@ async def async_select_option(self, option: str) -> None:
153153
@property
154154
def extra_state_attributes(self) -> dict[str, Any]:
155155
"""Return entity specific state attributes."""
156-
attrs = {"source": "Mitsubishi AC"}
156+
attrs: dict[str, Any] = {"source": "Mitsubishi AC"}
157157
external_entity_id = self._config_entry.options.get(CONF_EXTERNAL_TEMP_ENTITY)
158158
if external_entity_id:
159159
attrs["external_temperature_entity"] = external_entity_id

custom_components/mitsubishi/strings.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@
3838
"external_temperature_entity": "External Temperature Sensor"
3939
},
4040
"data_description": {
41-
"experimental_features": "Enable experimental features like remote temperature sensor support. These features may change or be removed in future versions.",
42-
"external_temperature_entity": "Select a temperature sensor to use as the room temperature when Remote mode is selected. Leave empty to only use the internal sensor."
41+
"experimental_features": "Enable experimental features like remote temperature sensor support. These features may change or be removed in future versions. WARNING: The remote temperature feature should NOT be used for critical heating/cooling needs. If your Home Assistant server goes offline, your external sensor becomes unavailable, or provides invalid readings, the AC will NOT automatically revert to its internal sensor. You must manually switch back to Internal mode.",
42+
"external_temperature_entity": "Select a temperature sensor to use as the room temperature when Remote mode is selected. Leave empty to only use the internal sensor. The AC must receive regular temperature updates - if updates stop, the AC continues using the last received value indefinitely."
4343
}
4444
}
4545
},

tests/test_config_flow.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,8 @@ async def test_options_flow_successful_update(self, hass: HomeAssistant, mock_co
370370
result = await options_flow.async_step_init(new_data)
371371

372372
assert result["type"] == FlowResultType.CREATE_ENTRY
373-
assert result["data"] == {}
373+
# Options flow now returns experimental_features setting
374+
assert result["data"] == {"experimental_features": False}
374375
hass.config_entries.async_update_entry.assert_called_once_with(
375376
mock_config_entry, data=new_data
376377
)

tests/test_coordinator.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111

1212

1313
@pytest.mark.asyncio
14-
async def test_coordinator_init(hass, mock_mitsubishi_controller):
14+
async def test_coordinator_init(hass, mock_mitsubishi_controller, mock_config_entry):
1515
"""Test coordinator initialization."""
1616
coordinator = MitsubishiDataUpdateCoordinator(
17-
hass, mock_mitsubishi_controller, DEFAULT_SCAN_INTERVAL
17+
hass, mock_mitsubishi_controller, mock_config_entry, DEFAULT_SCAN_INTERVAL
1818
)
1919

2020
assert coordinator.controller == mock_mitsubishi_controller
@@ -24,18 +24,22 @@ async def test_coordinator_init(hass, mock_mitsubishi_controller):
2424

2525

2626
@pytest.mark.asyncio
27-
async def test_coordinator_init_custom_interval(hass, mock_mitsubishi_controller):
27+
async def test_coordinator_init_custom_interval(hass, mock_mitsubishi_controller, mock_config_entry):
2828
"""Test coordinator initialization with custom scan interval."""
2929
custom_interval = 60
30-
coordinator = MitsubishiDataUpdateCoordinator(hass, mock_mitsubishi_controller, custom_interval)
30+
coordinator = MitsubishiDataUpdateCoordinator(
31+
hass, mock_mitsubishi_controller, mock_config_entry, custom_interval
32+
)
3133

3234
assert coordinator.update_interval == timedelta(seconds=custom_interval)
3335

3436

3537
@pytest.mark.asyncio
36-
async def test_fetch_unit_info_success(hass, mock_mitsubishi_controller):
38+
async def test_fetch_unit_info_success(hass, mock_mitsubishi_controller, mock_config_entry):
3739
"""Test successful unit info fetching."""
38-
coordinator = MitsubishiDataUpdateCoordinator(hass, mock_mitsubishi_controller)
40+
coordinator = MitsubishiDataUpdateCoordinator(
41+
hass, mock_mitsubishi_controller, mock_config_entry
42+
)
3943

4044
# Mock unit info fetch
4145
expected_unit_info = {"Adapter Information": {"Adaptor name": "MAC-577IF-2E"}}
@@ -47,9 +51,11 @@ async def test_fetch_unit_info_success(hass, mock_mitsubishi_controller):
4751

4852

4953
@pytest.mark.asyncio
50-
async def test_async_update_data_success(hass, mock_mitsubishi_controller):
54+
async def test_async_update_data_success(hass, mock_mitsubishi_controller, mock_config_entry):
5155
"""Test successful data update."""
52-
coordinator = MitsubishiDataUpdateCoordinator(hass, mock_mitsubishi_controller)
56+
coordinator = MitsubishiDataUpdateCoordinator(
57+
hass, mock_mitsubishi_controller, mock_config_entry
58+
)
5359

5460
with patch.object(
5561
hass,
@@ -63,9 +69,11 @@ async def test_async_update_data_success(hass, mock_mitsubishi_controller):
6369

6470

6571
@pytest.mark.asyncio
66-
async def test_async_update_data_fetch_status_fails(hass, mock_mitsubishi_controller):
72+
async def test_async_update_data_fetch_status_fails(hass, mock_mitsubishi_controller, mock_config_entry):
6773
"""Test data update when fetch_status fails."""
68-
coordinator = MitsubishiDataUpdateCoordinator(hass, mock_mitsubishi_controller)
74+
coordinator = MitsubishiDataUpdateCoordinator(
75+
hass, mock_mitsubishi_controller, mock_config_entry
76+
)
6977

7078
mock_mitsubishi_controller.fetch_status.side_effect = UpdateFailed("foobar")
7179

0 commit comments

Comments
 (0)