Skip to content
Snippets Groups Projects
Unverified Commit eb5c7a3e authored by Erwin Douna's avatar Erwin Douna Committed by GitHub
Browse files

Add Fastdotcom config flow (#98686)


* Adding config flow and tests

* Removing update and adding to integrations.json

* Updating hassfest

* Removing comments

* Removing unique ID

* Putting the setup_platform out of order

* Adding feedback on issues and importing

* Removing uniqueID (again)

* Adjusting unload and typo

* Updating manifest properly

* Minor patching

* Removing hass.data.setdefault(DOMAIN, {})

* Moving load_platform to __init__.py

* Update homeassistant/components/fastdotcom/config_flow.py

Co-authored-by: default avatarG Johansson <goran.johansson@shiftit.se>

* Update homeassistant/components/fastdotcom/strings.json

Co-authored-by: default avatarG Johansson <goran.johansson@shiftit.se>

* Update homeassistant/components/fastdotcom/__init__.py

Co-authored-by: default avatarG Johansson <goran.johansson@shiftit.se>

* Update homeassistant/components/fastdotcom/config_flow.py

Co-authored-by: default avatarG Johansson <goran.johansson@shiftit.se>

* Adding an unload function for the timer

* Adding issue on setup platform in sensor

* Update homeassistant/components/fastdotcom/config_flow.py

Co-authored-by: default avatarG Johansson <goran.johansson@shiftit.se>

* Removing platform

* Fixing strings.json

* Fine-tuning

* Putting back last_state

---------

Co-authored-by: default avatarG Johansson <goran.johansson@shiftit.se>
parent 5805601a
No related branches found
No related tags found
No related merge requests found
Showing with 206 additions and 32 deletions
......@@ -368,7 +368,8 @@ omit =
homeassistant/components/faa_delays/binary_sensor.py
homeassistant/components/faa_delays/coordinator.py
homeassistant/components/familyhub/camera.py
homeassistant/components/fastdotcom/*
homeassistant/components/fastdotcom/sensor.py
homeassistant/components/fastdotcom/__init__.py
homeassistant/components/ffmpeg/camera.py
homeassistant/components/fibaro/__init__.py
homeassistant/components/fibaro/binary_sensor.py
......
......@@ -373,7 +373,8 @@ build.json @home-assistant/supervisor
/tests/components/faa_delays/ @ntilley905
/homeassistant/components/fan/ @home-assistant/core
/tests/components/fan/ @home-assistant/core
/homeassistant/components/fastdotcom/ @rohankapoorcom
/homeassistant/components/fastdotcom/ @rohankapoorcom @erwindouna
/tests/components/fastdotcom/ @rohankapoorcom @erwindouna
/homeassistant/components/fibaro/ @rappenze
/tests/components/fibaro/ @rappenze
/homeassistant/components/file/ @fabaff
......
......@@ -8,23 +8,18 @@ from typing import Any
from fastdotcom import fast_com
import voluptuous as vol
from homeassistant.const import CONF_SCAN_INTERVAL, Platform
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_SCAN_INTERVAL
from homeassistant.core import HomeAssistant, ServiceCall
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.typing import ConfigType
DOMAIN = "fastdotcom"
DATA_UPDATED = f"{DOMAIN}_data_updated"
from .const import CONF_MANUAL, DATA_UPDATED, DEFAULT_INTERVAL, DOMAIN, PLATFORMS
_LOGGER = logging.getLogger(__name__)
CONF_MANUAL = "manual"
DEFAULT_INTERVAL = timedelta(hours=1)
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
......@@ -40,38 +35,61 @@ CONFIG_SCHEMA = vol.Schema(
)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def async_setup_platform(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Fast.com component. (deprecated)."""
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data=config[DOMAIN],
)
)
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up the Fast.com component."""
conf = config[DOMAIN]
data = hass.data[DOMAIN] = SpeedtestData(hass)
if not conf[CONF_MANUAL]:
async_track_time_interval(hass, data.update, conf[CONF_SCAN_INTERVAL])
entry.async_on_unload(
async_track_time_interval(hass, data.update, timedelta(hours=DEFAULT_INTERVAL))
)
# Run an initial update to get a starting state
await data.update()
def update(service_call: ServiceCall | None = None) -> None:
async def update(service_call: ServiceCall | None = None) -> None:
"""Service call to manually update the data."""
data.update()
await data.update()
hass.services.async_register(DOMAIN, "speedtest", update)
hass.async_create_task(
async_load_platform(hass, Platform.SENSOR, DOMAIN, {}, config)
await hass.config_entries.async_forward_entry_setups(
entry,
PLATFORMS,
)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload Fast.com config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data.pop(DOMAIN)
return unload_ok
class SpeedtestData:
"""Get the latest data from fast.com."""
"""Get the latest data from Fast.com."""
def __init__(self, hass: HomeAssistant) -> None:
"""Initialize the data object."""
self.data: dict[str, Any] | None = None
self._hass = hass
def update(self, now: datetime | None = None) -> None:
async def update(self, now: datetime | None = None) -> None:
"""Get the latest data from fast.com."""
_LOGGER.debug("Executing fast.com speedtest")
self.data = {"download": fast_com()}
_LOGGER.debug("Executing Fast.com speedtest")
fast_com_data = await self._hass.async_add_executor_job(fast_com)
self.data = {"download": fast_com_data}
_LOGGER.debug("Fast.com speedtest finished, with mbit/s: %s", fast_com_data)
dispatcher_send(self._hass, DATA_UPDATED)
"""Config flow for Fast.com integration."""
from __future__ import annotations
from typing import Any
from homeassistant.config_entries import ConfigFlow
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from .const import DEFAULT_NAME, DOMAIN
class FastdotcomConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Fast.com."""
VERSION = 1
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the initial step."""
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
if user_input is not None:
return self.async_create_entry(title=DEFAULT_NAME, data={})
return self.async_show_form(step_id="user")
async def async_step_import(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle a flow initiated by configuration file."""
async_create_issue(
self.hass,
HOMEASSISTANT_DOMAIN,
f"deprecated_yaml_{DOMAIN}",
breaks_in_ha_version="2024.6.0",
is_fixable=False,
issue_domain=DOMAIN,
severity=IssueSeverity.WARNING,
translation_key="deprecated_yaml",
translation_placeholders={
"domain": DOMAIN,
"integration_title": "Fast.com",
},
)
return await self.async_step_user(user_input)
"""Constants for the Fast.com integration."""
import logging
from homeassistant.const import Platform
LOGGER = logging.getLogger(__package__)
DOMAIN = "fastdotcom"
DATA_UPDATED = f"{DOMAIN}_data_updated"
CONF_MANUAL = "manual"
DEFAULT_NAME = "Fast.com"
DEFAULT_INTERVAL = 1
PLATFORMS: list[Platform] = [Platform.SENSOR]
{
"domain": "fastdotcom",
"name": "Fast.com",
"codeowners": ["@rohankapoorcom"],
"codeowners": ["@rohankapoorcom", "@erwindouna"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/fastdotcom",
"iot_class": "cloud_polling",
"loggers": ["fastdotcom"],
......
......@@ -8,29 +8,28 @@ from homeassistant.components.sensor import (
SensorEntity,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfDataRate
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import DATA_UPDATED, DOMAIN as FASTDOTCOM_DOMAIN
from .const import DATA_UPDATED, DOMAIN
async def async_setup_platform(
async def async_setup_entry(
hass: HomeAssistant,
config: ConfigType,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Fast.com sensor."""
async_add_entities([SpeedtestSensor(hass.data[FASTDOTCOM_DOMAIN])])
async_add_entities([SpeedtestSensor(hass.data[DOMAIN])])
# pylint: disable-next=hass-invalid-inheritance # needs fixing
class SpeedtestSensor(RestoreEntity, SensorEntity):
"""Implementation of a FAst.com sensor."""
"""Implementation of a Fast.com sensor."""
_attr_name = "Fast.com Download"
_attr_device_class = SensorDeviceClass.DATA_RATE
......
{
"config": {
"step": {
"user": {
"description": "Do you want to start the setup? The initial setup will take about 30-40 seconds."
}
},
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
}
},
"services": {
"speedtest": {
"name": "Speed test",
......
......@@ -141,6 +141,7 @@ FLOWS = {
"evil_genius_labs",
"ezviz",
"faa_delays",
"fastdotcom",
"fibaro",
"filesize",
"fireservicerota",
......
......@@ -1656,7 +1656,7 @@
"fastdotcom": {
"name": "Fast.com",
"integration_type": "hub",
"config_flow": false,
"config_flow": true,
"iot_class": "cloud_polling"
},
"feedreader": {
......
......@@ -634,6 +634,9 @@ eufylife-ble-client==0.1.8
# homeassistant.components.faa_delays
faadelays==2023.9.1
# homeassistant.components.fastdotcom
fastdotcom==0.0.3
# homeassistant.components.feedreader
feedparser==6.0.10
......
"""Fast.com integration tests."""
"""Test for the Fast.com config flow."""
from unittest.mock import patch
import pytest
from homeassistant import config_entries
from homeassistant.components.fastdotcom.const import DOMAIN
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from tests.common import MockConfigEntry
async def test_user_form(hass: HomeAssistant) -> None:
"""Test the full user configuration flow."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "user"
with patch(
"homeassistant.components.fastdotcom.async_setup_entry",
return_value=True,
) as mock_setup_entry:
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={},
)
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["title"] == "Fast.com"
assert result["data"] == {}
assert result["options"] == {}
assert len(mock_setup_entry.mock_calls) == 1
@pytest.mark.parametrize("source", [SOURCE_USER, SOURCE_IMPORT])
async def test_single_instance_allowed(
hass: HomeAssistant,
source: str,
) -> None:
"""Test we abort if already setup."""
mock_config_entry = MockConfigEntry(domain=DOMAIN)
mock_config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": source}
)
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "single_instance_allowed"
async def test_import_flow_success(hass: HomeAssistant) -> None:
"""Test import flow."""
with patch(
"homeassistant.components.fastdotcom.__init__.SpeedtestData",
return_value={"download": "50"},
), patch("homeassistant.components.fastdotcom.sensor.SpeedtestSensor"):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={},
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["title"] == "Fast.com"
assert result["data"] == {}
assert result["options"] == {}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment