Migrate Enphase envoy from httpx to aiohttp (#146283)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Arie Catsman 2025-06-07 17:52:54 +02:00 committed by GitHub
parent 7f9f106729
commit ae5606aa2f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 22 additions and 35 deletions

View File

@ -2,7 +2,6 @@
from __future__ import annotations from __future__ import annotations
import httpx
from pyenphase import Envoy from pyenphase import Envoy
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@ -10,14 +9,9 @@ from homeassistant.const import CONF_HOST
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.httpx_client import get_async_client from homeassistant.helpers.aiohttp_client import async_create_clientsession
from .const import ( from .const import DOMAIN, PLATFORMS
DOMAIN,
OPTION_DISABLE_KEEP_ALIVE,
OPTION_DISABLE_KEEP_ALIVE_DEFAULT_VALUE,
PLATFORMS,
)
from .coordinator import EnphaseConfigEntry, EnphaseUpdateCoordinator from .coordinator import EnphaseConfigEntry, EnphaseUpdateCoordinator
@ -25,19 +19,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: EnphaseConfigEntry) -> b
"""Set up Enphase Envoy from a config entry.""" """Set up Enphase Envoy from a config entry."""
host = entry.data[CONF_HOST] host = entry.data[CONF_HOST]
options = entry.options session = async_create_clientsession(hass, verify_ssl=False)
envoy = ( envoy = Envoy(host, session)
Envoy(
host,
httpx.AsyncClient(
verify=False, limits=httpx.Limits(max_keepalive_connections=0)
),
)
if options.get(
OPTION_DISABLE_KEEP_ALIVE, OPTION_DISABLE_KEEP_ALIVE_DEFAULT_VALUE
)
else Envoy(host, get_async_client(hass, verify_ssl=False))
)
coordinator = EnphaseUpdateCoordinator(hass, envoy, entry) coordinator = EnphaseUpdateCoordinator(hass, envoy, entry)
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()

View File

@ -24,7 +24,7 @@ from homeassistant.const import (
CONF_USERNAME, CONF_USERNAME,
) )
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.httpx_client import get_async_client from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
from homeassistant.helpers.typing import VolDictType from homeassistant.helpers.typing import VolDictType
@ -63,7 +63,7 @@ async def validate_input(
description_placeholders: dict[str, str], description_placeholders: dict[str, str],
) -> Envoy: ) -> Envoy:
"""Validate the user input allows us to connect.""" """Validate the user input allows us to connect."""
envoy = Envoy(host, get_async_client(hass, verify_ssl=False)) envoy = Envoy(host, async_get_clientsession(hass, verify_ssl=False))
try: try:
await envoy.setup() await envoy.setup()
await envoy.authenticate(username=username, password=password) await envoy.authenticate(username=username, password=password)

View File

@ -6,6 +6,7 @@ import copy
from datetime import datetime from datetime import datetime
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING, Any
from aiohttp import ClientResponse
from attr import asdict from attr import asdict
from pyenphase.envoy import Envoy from pyenphase.envoy import Envoy
from pyenphase.exceptions import EnvoyError from pyenphase.exceptions import EnvoyError
@ -69,14 +70,14 @@ async def _get_fixture_collection(envoy: Envoy, serial: str) -> dict[str, Any]:
for end_point in end_points: for end_point in end_points:
try: try:
response = await envoy.request(end_point) response: ClientResponse = await envoy.request(end_point)
fixture_data[end_point] = response.text.replace("\n", "").replace( fixture_data[end_point] = (
serial, CLEAN_TEXT (await response.text()).replace("\n", "").replace(serial, CLEAN_TEXT)
) )
fixture_data[f"{end_point}_log"] = json_dumps( fixture_data[f"{end_point}_log"] = json_dumps(
{ {
"headers": dict(response.headers.items()), "headers": dict(response.headers.items()),
"code": response.status_code, "code": response.status,
} }
) )
except EnvoyError as err: except EnvoyError as err:

View File

@ -5,7 +5,7 @@ from __future__ import annotations
from collections.abc import Callable, Coroutine from collections.abc import Callable, Coroutine
from typing import Any, Concatenate from typing import Any, Concatenate
from httpx import HTTPError from aiohttp import ClientError
from pyenphase import EnvoyData from pyenphase import EnvoyData
from pyenphase.exceptions import EnvoyError from pyenphase.exceptions import EnvoyError
@ -16,7 +16,7 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN from .const import DOMAIN
from .coordinator import EnphaseUpdateCoordinator from .coordinator import EnphaseUpdateCoordinator
ACTIONERRORS = (EnvoyError, HTTPError) ACTIONERRORS = (EnvoyError, ClientError)
class EnvoyBaseEntity(CoordinatorEntity[EnphaseUpdateCoordinator]): class EnvoyBaseEntity(CoordinatorEntity[EnphaseUpdateCoordinator]):

View File

@ -7,7 +7,7 @@
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["pyenphase"], "loggers": ["pyenphase"],
"quality_scale": "platinum", "quality_scale": "platinum",
"requirements": ["pyenphase==1.26.1"], "requirements": ["pyenphase==2.0.1"],
"zeroconf": [ "zeroconf": [
{ {
"type": "_enphase-envoy._tcp.local." "type": "_enphase-envoy._tcp.local."

2
requirements_all.txt generated
View File

@ -1958,7 +1958,7 @@ pyeiscp==0.0.7
pyemoncms==0.1.1 pyemoncms==0.1.1
# homeassistant.components.enphase_envoy # homeassistant.components.enphase_envoy
pyenphase==1.26.1 pyenphase==2.0.1
# homeassistant.components.envisalink # homeassistant.components.envisalink
pyenvisalink==4.7 pyenvisalink==4.7

View File

@ -1630,7 +1630,7 @@ pyeiscp==0.0.7
pyemoncms==0.1.1 pyemoncms==0.1.1
# homeassistant.components.enphase_envoy # homeassistant.components.enphase_envoy
pyenphase==1.26.1 pyenphase==2.0.1
# homeassistant.components.everlights # homeassistant.components.everlights
pyeverlights==0.1.0 pyeverlights==0.1.0

View File

@ -5,6 +5,7 @@ from typing import Any
from unittest.mock import AsyncMock, Mock, patch from unittest.mock import AsyncMock, Mock, patch
import jwt import jwt
import multidict
from pyenphase import ( from pyenphase import (
EnvoyACBPower, EnvoyACBPower,
EnvoyBatteryAggregate, EnvoyBatteryAggregate,
@ -101,9 +102,11 @@ async def mock_envoy(
mock_envoy.auth = EnvoyTokenAuth("127.0.0.1", token=token, envoy_serial="1234") mock_envoy.auth = EnvoyTokenAuth("127.0.0.1", token=token, envoy_serial="1234")
mock_envoy.serial_number = "1234" mock_envoy.serial_number = "1234"
mock = Mock() mock = Mock()
mock.status_code = 200 mock.status = 200
mock.text = "Testing request \nreplies." aiohttp_text = AsyncMock()
mock.headers = {"Hello": "World"} aiohttp_text.return_value = "Testing request \nreplies."
mock.text = aiohttp_text
mock.headers = multidict.MultiDict([("Hello", "World")])
mock_envoy.request.return_value = mock mock_envoy.request.return_value = mock
# determine fixture file name, default envoy if no request passed # determine fixture file name, default envoy if no request passed