Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add configuration flow to Whois #63069

Merged
merged 5 commits into from
Dec 30, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -1268,6 +1268,7 @@ omit =
homeassistant/components/waze_travel_time/helpers.py
homeassistant/components/waze_travel_time/sensor.py
homeassistant/components/webostv/*
homeassistant/components/whois/__init__.py
homeassistant/components/whois/sensor.py
homeassistant/components/wiffi/*
homeassistant/components/wirelesstag/*
Expand Down
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -1035,6 +1035,7 @@ tests/components/wemo/* @esev
homeassistant/components/whirlpool/* @abmantis
tests/components/whirlpool/* @abmantis
homeassistant/components/whois/* @frenck
tests/components/whois/* @frenck
homeassistant/components/wiffi/* @mampfes
tests/components/wiffi/* @mampfes
homeassistant/components/wilight/* @leofig-rj
Expand Down
17 changes: 16 additions & 1 deletion homeassistant/components/whois/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,16 @@
"""The whois component."""
"""The Whois integration."""
frenck marked this conversation as resolved.
Show resolved Hide resolved
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant

from .const import PLATFORMS


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up from a config entry."""
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
52 changes: 52 additions & 0 deletions homeassistant/components/whois/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Config flow to configure the Whois integration."""
from __future__ import annotations

from typing import Any

import voluptuous as vol

from homeassistant.config_entries import ConfigFlow
from homeassistant.const import CONF_DOMAIN, CONF_NAME
from homeassistant.data_entry_flow import FlowResult

from .const import DOMAIN


class WhoisFlowHandler(ConfigFlow, domain=DOMAIN):
"""Config flow for Whois."""

VERSION = 1

imported_name: str | None = None

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle a flow initialized by the user."""
if user_input is not None:
await self.async_set_unique_id(user_input[CONF_DOMAIN].lower())
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=self.imported_name or user_input[CONF_DOMAIN],
data={
CONF_DOMAIN: user_input[CONF_DOMAIN].lower(),
},
)
frenck marked this conversation as resolved.
Show resolved Hide resolved

return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required(CONF_DOMAIN): str,
}
),
)

async def async_step_import(self, config: dict[str, Any]) -> FlowResult:
"""Handle a flow initialized by importing a config."""
self.imported_name = config[CONF_NAME]
return await self.async_step_user(
user_input={
CONF_DOMAIN: config[CONF_DOMAIN],
}
)
3 changes: 3 additions & 0 deletions homeassistant/components/whois/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
import logging
from typing import Final

from homeassistant.const import Platform

DOMAIN: Final = "whois"
PLATFORMS = [Platform.SENSOR]

LOGGER = logging.getLogger(__package__)

Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/whois/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"name": "Whois",
"documentation": "https://www.home-assistant.io/integrations/whois",
"requirements": ["python-whois==0.7.3"],
"config_flow": true,
"codeowners": ["@frenck"],
"iot_class": "cloud_polling"
}
47 changes: 34 additions & 13 deletions homeassistant/components/whois/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import whois

from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_DOMAIN, CONF_NAME, TIME_DAYS
from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv
Expand All @@ -19,10 +20,11 @@
ATTR_REGISTRAR,
ATTR_UPDATED,
DEFAULT_NAME,
DOMAIN,
LOGGER,
)

SCANTERVAL = timedelta(hours=24)
SCAN_INTERVAL = timedelta(hours=24)

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
Expand All @@ -39,33 +41,52 @@ def setup_platform(
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the WHOIS sensor."""
domain = config[CONF_DOMAIN]
name = config[CONF_NAME]

LOGGER.warning(
"Configuration of the Whois platform in YAML is deprecated and will be "
"removed in Home Assistant 2022.4; Your existing configuration "
"has been imported into the UI automatically and can be safely removed "
"from your configuration.yaml file"
)
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data={CONF_NAME: config[CONF_NAME]},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you not need to pass also the domain name?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ouch, nicely spotted! 🙏 thanks

Copy link
Member Author

@frenck frenck Dec 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added missing domain import in 90ada67

)
)


async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the platform from config_entry."""
domain = entry.data[CONF_DOMAIN]
try:
if "expiration_date" in whois.whois(domain):
add_entities([WhoisSensor(name, domain)], True)
else:
LOGGER.error(
"WHOIS lookup for %s didn't contain an expiration date", domain
)
return
info = await hass.async_add_executor_job(whois.whois, domain)
except whois.BaseException as ex: # pylint: disable=broad-except
frenck marked this conversation as resolved.
Show resolved Hide resolved
LOGGER.error("Exception %s occurred during WHOIS lookup for %s", ex, domain)
return

if "expiration_date" not in info:
LOGGER.error("WHOIS lookup for %s didn't contain an expiration date", domain)
return

async_add_entities([WhoisSensor(domain)], True)


class WhoisSensor(SensorEntity):
"""Implementation of a WHOIS sensor."""

_attr_icon = "mdi:calendar-clock"
_attr_native_unit_of_measurement = TIME_DAYS

def __init__(self, name: str, domain: str) -> None:
def __init__(self, domain: str) -> None:
"""Initialize the sensor."""
self._attr_name = domain
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are probably planning to do it in a follow-up PR, but since you are dropping the name from the config entry, does it not make sense to add the unique_id to the entity directly in this PR to be able to rename it in the UI?

Copy link
Member Author

@frenck frenck Dec 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that is the idea :)
I wanted to move that into the PR that adds more sensors/sensor entity descriptions to ensure a stable unique ID from the get-go (for all sensors)

self.whois = whois.whois
self._domain = domain
self._attr_name = name

def _empty_value_and_attributes(self) -> None:
"""Empty the state and attributes on an error."""
Expand Down
14 changes: 14 additions & 0 deletions homeassistant/components/whois/strings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"config": {
"step": {
"user": {
"data": {
"domain": "Domain name"
}
}
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]"
}
}
}
14 changes: 14 additions & 0 deletions homeassistant/components/whois/translations/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"config": {
"abort": {
"already_configured": "Service is already configured"
},
"step": {
"user": {
"data": {
"domain": "Domain name"
}
}
}
}
}
1 change: 1 addition & 0 deletions homeassistant/generated/config_flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@
"waze_travel_time",
"wemo",
"whirlpool",
"whois",
"wiffi",
"wilight",
"withings",
Expand Down
3 changes: 3 additions & 0 deletions requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1193,6 +1193,9 @@ python-tado==0.12.0
# homeassistant.components.twitch
python-twitch-client==0.6.0

# homeassistant.components.whois
python-whois==0.7.3

# homeassistant.components.awair
python_awair==0.2.1

Expand Down
1 change: 1 addition & 0 deletions tests/components/whois/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for the Whois integration."""
34 changes: 34 additions & 0 deletions tests/components/whois/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""Fixtures for Whois integration tests."""
from __future__ import annotations

from collections.abc import Generator
from unittest.mock import AsyncMock, patch

import pytest

from homeassistant.components.whois.const import DOMAIN
from homeassistant.const import CONF_DOMAIN

from tests.common import MockConfigEntry


@pytest.fixture
def mock_config_entry() -> MockConfigEntry:
"""Return the default mocked config entry."""
return MockConfigEntry(
title="Home Assistant",
domain=DOMAIN,
data={
CONF_DOMAIN: "Home-Assistant.io",
},
unique_id="home-assistant.io",
)


@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
"""Mock setting up a config entry."""
with patch(
"homeassistant.components.whois.async_setup_entry", return_value=True
) as mock_setup:
yield mock_setup
80 changes: 80 additions & 0 deletions tests/components/whois/test_config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"""Tests for the Whois config flow."""

from unittest.mock import AsyncMock

from homeassistant.components.whois.const import DOMAIN
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
from homeassistant.const import CONF_DOMAIN, CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import (
RESULT_TYPE_ABORT,
RESULT_TYPE_CREATE_ENTRY,
RESULT_TYPE_FORM,
)

from tests.common import MockConfigEntry


async def test_full_user_flow(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
) -> None:
"""Test the full user configuration flow."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)

assert result.get("type") == RESULT_TYPE_FORM
assert result.get("step_id") == SOURCE_USER
assert "flow_id" in result

result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_DOMAIN: "Example.com"},
)

assert result2.get("type") == RESULT_TYPE_CREATE_ENTRY
assert result2.get("title") == "Example.com"
assert result2.get("data") == {CONF_DOMAIN: "example.com"}

assert len(mock_setup_entry.mock_calls) == 1


async def test_already_configured(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test we abort if already configured."""
mock_config_entry.add_to_hass(hass)

result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
data={CONF_DOMAIN: "HOME-Assistant.io"},
)

assert result.get("type") == RESULT_TYPE_ABORT
assert result.get("reason") == "already_configured"

assert len(mock_setup_entry.mock_calls) == 0


async def test_import_flow(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
) -> None:
"""Test the import configuration flow."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data={CONF_DOMAIN: "Example.com", CONF_NAME: "My Example Domain"},
)

assert result.get("type") == RESULT_TYPE_CREATE_ENTRY
assert result.get("title") == "My Example Domain"
assert result.get("data") == {
CONF_DOMAIN: "example.com",
}

assert len(mock_setup_entry.mock_calls) == 1