1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-19 18:38:58 +00:00

Bump pysma to 1.0.2 and enable type checking (#154977)

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Johann Kellerman
2025-10-22 23:01:51 +02:00
committed by GitHub
parent a3d760156f
commit b116619af1
14 changed files with 83 additions and 68 deletions

View File

@@ -477,6 +477,7 @@ homeassistant.components.skybell.*
homeassistant.components.slack.*
homeassistant.components.sleep_as_android.*
homeassistant.components.sleepiq.*
homeassistant.components.sma.*
homeassistant.components.smhi.*
homeassistant.components.smlight.*
homeassistant.components.smtp.*

View File

@@ -4,7 +4,7 @@ from __future__ import annotations
import logging
from pysma import SMA
from pysma import SMAWebConnect
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
@@ -35,7 +35,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: SMAConfigEntry) -> bool:
protocol = "https" if entry.data[CONF_SSL] else "http"
url = f"{protocol}://{entry.data[CONF_HOST]}"
sma = SMA(
sma = SMAWebConnect(
session=async_get_clientsession(
hass=hass, verify_ssl=entry.data[CONF_VERIFY_SSL]
),

View File

@@ -6,7 +6,13 @@ from collections.abc import Mapping
import logging
from typing import Any
import pysma
import attrs
from pysma import (
SmaAuthenticationException,
SmaConnectionException,
SmaReadException,
SMAWebConnect,
)
import voluptuous as vol
from yarl import URL
@@ -42,7 +48,7 @@ async def validate_input(
host = data[CONF_HOST] if data is not None else user_input[CONF_HOST]
url = URL.build(scheme=protocol, host=host)
sma = pysma.SMA(
sma = SMAWebConnect(
session, str(url), user_input[CONF_PASSWORD], group=user_input[CONF_GROUP]
)
@@ -51,7 +57,7 @@ async def validate_input(
device_info = await sma.device_info()
await sma.close_session()
return device_info
return attrs.asdict(device_info)
class SmaConfigFlow(ConfigFlow, domain=DOMAIN):
@@ -90,11 +96,11 @@ class SmaConfigFlow(ConfigFlow, domain=DOMAIN):
device_info = await validate_input(
self.hass, user_input=user_input, data=self._data
)
except pysma.exceptions.SmaConnectionException:
except SmaConnectionException:
errors["base"] = "cannot_connect"
except pysma.exceptions.SmaAuthenticationException:
except SmaAuthenticationException:
errors["base"] = "invalid_auth"
except pysma.exceptions.SmaReadException:
except SmaReadException:
errors["base"] = "cannot_retrieve_device_info"
except Exception:
_LOGGER.exception("Unexpected exception")

View File

@@ -6,13 +6,14 @@ from dataclasses import dataclass
from datetime import timedelta
import logging
from pysma import SMA
from pysma.exceptions import (
from pysma import (
SmaAuthenticationException,
SmaConnectionException,
SmaReadException,
SMAWebConnect,
)
from pysma.sensor import Sensor
from pysma.helpers import DeviceInfo
from pysma.sensor import Sensors
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_SCAN_INTERVAL
@@ -29,8 +30,8 @@ _LOGGER = logging.getLogger(__name__)
class SMACoordinatorData:
"""Data class for SMA sensors."""
sma_device_info: dict[str, str]
sensors: list[Sensor]
sma_device_info: DeviceInfo
sensors: Sensors
class SMADataUpdateCoordinator(DataUpdateCoordinator[SMACoordinatorData]):
@@ -42,7 +43,7 @@ class SMADataUpdateCoordinator(DataUpdateCoordinator[SMACoordinatorData]):
self,
hass: HomeAssistant,
config_entry: ConfigEntry,
sma: SMA,
sma: SMAWebConnect,
) -> None:
"""Initialize the SMA Data Update Coordinator."""
super().__init__(
@@ -57,8 +58,8 @@ class SMADataUpdateCoordinator(DataUpdateCoordinator[SMACoordinatorData]):
),
)
self.sma = sma
self._sma_device_info: dict[str, str] = {}
self._sensors: list[Sensor] = []
self._sma_device_info = DeviceInfo()
self._sensors = Sensors()
async def _async_setup(self) -> None:
"""Setup the SMA Data Update Coordinator."""

View File

@@ -13,5 +13,5 @@
"documentation": "https://www.home-assistant.io/integrations/sma",
"iot_class": "local_polling",
"loggers": ["pysma"],
"requirements": ["pysma==0.7.5"]
"requirements": ["pysma==1.0.2"]
}

View File

@@ -2,7 +2,7 @@
from __future__ import annotations
import pysma
from pysma.sensor import Sensor
from homeassistant.components.sensor import (
SensorDeviceClass,
@@ -859,7 +859,7 @@ class SMAsensor(CoordinatorEntity[SMADataUpdateCoordinator], SensorEntity):
self,
coordinator: SMADataUpdateCoordinator,
description: SensorEntityDescription | None,
pysma_sensor: pysma.sensor.Sensor,
pysma_sensor: Sensor,
entry: SMAConfigEntry,
) -> None:
"""Initialize the sensor."""
@@ -873,17 +873,17 @@ class SMAsensor(CoordinatorEntity[SMADataUpdateCoordinator], SensorEntity):
url = f"{protocol}://{entry.data[CONF_HOST]}"
self._sensor = pysma_sensor
self._serial = coordinator.data.sma_device_info["serial"]
self._serial = coordinator.data.sma_device_info.serial
assert entry.unique_id
self._attr_device_info = DeviceInfo(
configuration_url=url,
identifiers={(DOMAIN, entry.unique_id)},
manufacturer=coordinator.data.sma_device_info["manufacturer"],
model=coordinator.data.sma_device_info["type"],
name=coordinator.data.sma_device_info["name"],
sw_version=coordinator.data.sma_device_info["sw_version"],
serial_number=coordinator.data.sma_device_info["serial"],
manufacturer=coordinator.data.sma_device_info.manufacturer,
model=coordinator.data.sma_device_info.type,
name=coordinator.data.sma_device_info.name,
sw_version=coordinator.data.sma_device_info.sw_version,
serial_number=coordinator.data.sma_device_info.serial,
)
self._attr_unique_id = (
f"{entry.unique_id}-{pysma_sensor.key}_{pysma_sensor.key_idx}"
@@ -908,7 +908,7 @@ class SMAsensor(CoordinatorEntity[SMADataUpdateCoordinator], SensorEntity):
"""Return if the device is available."""
return (
super().available
and self._serial == self.coordinator.data.sma_device_info["serial"]
and self._serial == self.coordinator.data.sma_device_info.serial
)
@property

10
mypy.ini generated
View File

@@ -4526,6 +4526,16 @@ disallow_untyped_defs = true
warn_return_any = true
warn_unreachable = true
[mypy-homeassistant.components.sma.*]
check_untyped_defs = true
disallow_incomplete_defs = true
disallow_subclassing_any = true
disallow_untyped_calls = true
disallow_untyped_decorators = true
disallow_untyped_defs = true
warn_return_any = true
warn_unreachable = true
[mypy-homeassistant.components.smhi.*]
check_untyped_defs = true
disallow_incomplete_defs = true

2
requirements_all.txt generated
View File

@@ -2387,7 +2387,7 @@ pysignalclirestapi==0.3.24
pyskyqhub==0.1.4
# homeassistant.components.sma
pysma==0.7.5
pysma==1.0.2
# homeassistant.components.smappee
pysmappee==0.2.29

View File

@@ -1993,7 +1993,7 @@ pysiaalarm==3.1.1
pysignalclirestapi==0.3.24
# homeassistant.components.sma
pysma==0.7.5
pysma==1.0.2
# homeassistant.components.smappee
pysmappee==0.2.29

View File

@@ -1,5 +1,7 @@
"""Tests for the sma integration."""
from pysma.helpers import DeviceInfo
from homeassistant.components.sma.const import CONF_GROUP
from homeassistant.const import (
CONF_HOST,
@@ -12,13 +14,14 @@ from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
MOCK_DEVICE = {
"manufacturer": "SMA",
"name": "SMA Device Name",
"type": "Sunny Boy 3.6",
"serial": 123456789,
"sw_version": "1.0.0",
}
MOCK_DEVICE = DeviceInfo(
manufacturer="SMA",
name="SMA Device Name",
type="Sunny Boy 3.6",
serial=123456789,
sw_version="1.0.0",
)
MOCK_USER_INPUT = {
CONF_HOST: "1.1.1.1",

View File

@@ -8,7 +8,7 @@ from pysma.const import (
GENERIC_SENSORS,
OPTIMIZERS_VIA_INVERTER,
)
from pysma.definitions import sensor_map
from pysma.definitions.webconnect import sensor_map
from pysma.sensor import Sensors
import pytest
@@ -26,8 +26,8 @@ def mock_config_entry(hass: HomeAssistant) -> MockConfigEntry:
return MockConfigEntry(
domain=DOMAIN,
title=MOCK_DEVICE["name"],
unique_id=str(MOCK_DEVICE["serial"]),
title=MOCK_DEVICE.name,
unique_id=str(MOCK_DEVICE.serial),
data=MOCK_USER_INPUT,
minor_version=2,
entry_id="sma_entry_123",
@@ -47,7 +47,7 @@ def mock_setup_entry() -> Generator[AsyncMock]:
def mock_sma_client() -> Generator[MagicMock]:
"""Mock the SMA client."""
with patch(
"homeassistant.components.sma.coordinator.SMA", autospec=True
"homeassistant.components.sma.coordinator.SMAWebConnect", autospec=True
) as sma_cls:
sma_instance: MagicMock = sma_cls.return_value
sma_instance.device_info = AsyncMock(return_value=MOCK_DEVICE)
@@ -80,8 +80,11 @@ def mock_sma_client() -> Generator[MagicMock]:
sma_instance.read = AsyncMock(side_effect=_async_mock_read)
with (
patch("homeassistant.components.sma.config_flow.pysma.SMA", new=sma_cls),
patch("homeassistant.components.sma.SMA", new=sma_cls),
patch("pysma.SMA", new=sma_cls),
patch(
"homeassistant.components.sma.config_flow.SMAWebConnect",
new=sma_cls,
),
patch("homeassistant.components.sma.SMAWebConnect", new=sma_cls),
patch("pysma.SMAWebConnect", new=sma_cls),
):
yield sma_instance

View File

@@ -2,11 +2,7 @@
from unittest.mock import AsyncMock, MagicMock, patch
from pysma.exceptions import (
SmaAuthenticationException,
SmaConnectionException,
SmaReadException,
)
from pysma import SmaAuthenticationException, SmaConnectionException, SmaReadException
import pytest
from homeassistant.components.sma.const import DOMAIN
@@ -91,7 +87,7 @@ async def test_form_exceptions(
)
with patch(
"homeassistant.components.sma.config_flow.pysma.SMA.new_session",
"homeassistant.components.sma.config_flow.SMAWebConnect.new_session",
side_effect=exception,
):
result = await hass.config_entries.flow.async_configure(
@@ -211,7 +207,7 @@ async def test_dhcp_exceptions(
data=DHCP_DISCOVERY,
)
with patch("homeassistant.components.sma.config_flow.pysma.SMA") as mock_sma:
with patch("homeassistant.components.sma.config_flow.SMAWebConnect") as mock_sma:
mock_sma_instance = mock_sma.return_value
mock_sma_instance.new_session = AsyncMock(side_effect=exception)
@@ -223,7 +219,7 @@ async def test_dhcp_exceptions(
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": error}
with patch("homeassistant.components.sma.config_flow.pysma.SMA") as mock_sma:
with patch("homeassistant.components.sma.config_flow.SMAWebConnect") as mock_sma:
mock_sma_instance = mock_sma.return_value
mock_sma_instance.new_session = AsyncMock(return_value=True)
mock_sma_instance.device_info = AsyncMock(return_value=MOCK_DEVICE)
@@ -291,7 +287,7 @@ async def test_reauth_flow_exceptions(
result = await entry.start_reauth_flow(hass)
with patch("homeassistant.components.sma.config_flow.pysma.SMA") as mock_sma:
with patch("homeassistant.components.sma.config_flow.SMAWebConnect") as mock_sma:
mock_sma_instance = mock_sma.return_value
mock_sma_instance.new_session = AsyncMock(side_effect=exception)
result = await hass.config_entries.flow.async_configure(

View File

@@ -1,12 +1,9 @@
"""Test the sma init file."""
from collections.abc import AsyncGenerator, Generator
from collections.abc import AsyncGenerator
from unittest.mock import MagicMock
from pysma.exceptions import (
SmaAuthenticationException,
SmaConnectionException,
SmaReadException,
)
from pysma import SmaAuthenticationException, SmaConnectionException, SmaReadException
import pytest
from homeassistant.components.sma.const import DOMAIN
@@ -26,8 +23,8 @@ async def test_migrate_entry_minor_version_1_2(
"""Test migrating a 1.1 config entry to 1.2."""
entry = MockConfigEntry(
domain=DOMAIN,
title=MOCK_DEVICE["name"],
unique_id=MOCK_DEVICE["serial"], # Not converted to str
title=MOCK_DEVICE.name,
unique_id=MOCK_DEVICE.serial,
data=MOCK_USER_INPUT,
source=SOURCE_IMPORT,
minor_version=1,
@@ -36,7 +33,8 @@ async def test_migrate_entry_minor_version_1_2(
assert await hass.config_entries.async_setup(entry.entry_id)
assert entry.version == 1
assert entry.minor_version == 2
assert entry.unique_id == str(MOCK_DEVICE["serial"])
assert isinstance(MOCK_DEVICE.serial, str)
assert entry.unique_id == MOCK_DEVICE.serial
@pytest.mark.parametrize(
@@ -49,7 +47,7 @@ async def test_migrate_entry_minor_version_1_2(
)
async def test_setup_exceptions(
hass: HomeAssistant,
mock_sma_client: Generator,
mock_sma_client: MagicMock,
mock_config_entry: MockConfigEntry,
exception: Exception,
expected_state: ConfigEntryState,

View File

@@ -1,14 +1,10 @@
"""Test the SMA sensor platform."""
from collections.abc import Generator
from unittest.mock import patch
from unittest.mock import MagicMock, patch
from freezegun.api import FrozenDateTimeFactory
from pysma.exceptions import (
SmaAuthenticationException,
SmaConnectionException,
SmaReadException,
)
from pysma import SmaAuthenticationException, SmaConnectionException, SmaReadException
import pytest
from syrupy.assertion import SnapshotAssertion
@@ -56,7 +52,7 @@ async def test_all_entities(
async def test_refresh_exceptions(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_sma_client: Generator,
mock_sma_client: MagicMock,
freezer: FrozenDateTimeFactory,
exception: Exception,
) -> None:
@@ -71,4 +67,5 @@ async def test_refresh_exceptions(
await hass.async_block_till_done()
state = hass.states.get("sensor.sma_device_name_battery_capacity_a")
assert state
assert state.state == STATE_UNAVAILABLE