Skip to content

Commit

Permalink
Add Fastdotcom config flow (home-assistant#98686)
Browse files Browse the repository at this point in the history
* 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: G Johansson <goran.johansson@shiftit.se>

* Update homeassistant/components/fastdotcom/strings.json

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

* Update homeassistant/components/fastdotcom/__init__.py

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

* Update homeassistant/components/fastdotcom/config_flow.py

Co-authored-by: G 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: G Johansson <goran.johansson@shiftit.se>

* Removing platform

* Fixing strings.json

* Fine-tuning

* Putting back last_state

---------

Co-authored-by: G Johansson <goran.johansson@shiftit.se>
  • Loading branch information
erwindouna and gjohansson-ST authored Nov 21, 2023
1 parent 5805601 commit eb5c7a3
Show file tree
Hide file tree
Showing 13 changed files with 206 additions and 32 deletions.
3 changes: 2 additions & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
60 changes: 39 additions & 21 deletions homeassistant/components/fastdotcom/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,18 @@
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(
Expand All @@ -40,38 +35,61 @@
)


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)
50 changes: 50 additions & 0 deletions homeassistant/components/fastdotcom/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""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)
15 changes: 15 additions & 0 deletions homeassistant/components/fastdotcom/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""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]
3 changes: 2 additions & 1 deletion homeassistant/components/fastdotcom/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"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"],
Expand Down
13 changes: 6 additions & 7 deletions homeassistant/components/fastdotcom/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,28 @@
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
Expand Down
10 changes: 10 additions & 0 deletions homeassistant/components/fastdotcom/strings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
{
"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",
Expand Down
1 change: 1 addition & 0 deletions homeassistant/generated/config_flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@
"evil_genius_labs",
"ezviz",
"faa_delays",
"fastdotcom",
"fibaro",
"filesize",
"fireservicerota",
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/generated/integrations.json
Original file line number Diff line number Diff line change
Expand Up @@ -1656,7 +1656,7 @@
"fastdotcom": {
"name": "Fast.com",
"integration_type": "hub",
"config_flow": false,
"config_flow": true,
"iot_class": "cloud_polling"
},
"feedreader": {
Expand Down
3 changes: 3 additions & 0 deletions requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions tests/components/fastdotcom/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Fast.com integration tests."""
74 changes: 74 additions & 0 deletions tests/components/fastdotcom/test_config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""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 comments on commit eb5c7a3

Please sign in to comment.