Add config flow to Moon (#67444)
parent
81a509e69e
commit
02391663c1
|
@ -127,10 +127,11 @@ homeassistant.components.lookin.*
|
|||
homeassistant.components.luftdaten.*
|
||||
homeassistant.components.mailbox.*
|
||||
homeassistant.components.media_player.*
|
||||
homeassistant.components.media_source.*
|
||||
homeassistant.components.mjpeg.*
|
||||
homeassistant.components.modbus.*
|
||||
homeassistant.components.modem_callerid.*
|
||||
homeassistant.components.media_source.*
|
||||
homeassistant.components.moon.*
|
||||
homeassistant.components.mysensors.*
|
||||
homeassistant.components.nam.*
|
||||
homeassistant.components.nanoleaf.*
|
||||
|
|
|
@ -616,8 +616,8 @@ homeassistant/components/moehlenhoff_alpha2/* @j-a-n
|
|||
tests/components/moehlenhoff_alpha2/* @j-a-n
|
||||
homeassistant/components/monoprice/* @etsinko @OnFreund
|
||||
tests/components/monoprice/* @etsinko @OnFreund
|
||||
homeassistant/components/moon/* @fabaff
|
||||
tests/components/moon/* @fabaff
|
||||
homeassistant/components/moon/* @fabaff @frenck
|
||||
tests/components/moon/* @fabaff @frenck
|
||||
homeassistant/components/motion_blinds/* @starkillerOG
|
||||
tests/components/motion_blinds/* @starkillerOG
|
||||
homeassistant/components/motioneye/* @dermotduffy
|
||||
|
|
|
@ -1 +1,16 @@
|
|||
"""The moon component."""
|
||||
"""The Moon integration."""
|
||||
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)
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
"""Config flow to configure the Moon integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
|
||||
from .const import DEFAULT_NAME, DOMAIN
|
||||
|
||||
|
||||
class MoonConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Config flow for Moon."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle a flow initialized by the user."""
|
||||
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=user_input.get(CONF_NAME, DEFAULT_NAME),
|
||||
data={},
|
||||
)
|
||||
|
||||
return self.async_show_form(step_id="user", data_schema=vol.Schema({}))
|
||||
|
||||
async def async_step_import(self, user_input: dict[str, Any]) -> FlowResult:
|
||||
"""Handle import from configuration.yaml."""
|
||||
return await self.async_step_user(user_input)
|
|
@ -0,0 +1,9 @@
|
|||
"""Constants for the Moon integration."""
|
||||
from typing import Final
|
||||
|
||||
from homeassistant.const import Platform
|
||||
|
||||
DOMAIN: Final = "moon"
|
||||
PLATFORMS: Final = [Platform.SENSOR]
|
||||
|
||||
DEFAULT_NAME: Final = "Moon"
|
|
@ -2,7 +2,8 @@
|
|||
"domain": "moon",
|
||||
"name": "Moon",
|
||||
"documentation": "https://www.home-assistant.io/integrations/moon",
|
||||
"codeowners": ["@fabaff"],
|
||||
"codeowners": ["@fabaff", "@frenck"],
|
||||
"quality_scale": "internal",
|
||||
"iot_class": "local_polling"
|
||||
"iot_class": "local_polling",
|
||||
"config_flow": true
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ from homeassistant.components.sensor import (
|
|||
PLATFORM_SCHEMA as PARENT_PLATFORM_SCHEMA,
|
||||
SensorEntity,
|
||||
)
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
@ -15,7 +16,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
DEFAULT_NAME = "Moon"
|
||||
from .const import DEFAULT_NAME, DOMAIN
|
||||
|
||||
STATE_FIRST_QUARTER = "first_quarter"
|
||||
STATE_FULL_MOON = "full_moon"
|
||||
|
@ -49,23 +50,37 @@ async def async_setup_platform(
|
|||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the Moon sensor."""
|
||||
name: str = config[CONF_NAME]
|
||||
|
||||
async_add_entities([MoonSensor(name)], True)
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_IMPORT},
|
||||
data=config,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class MoonSensor(SensorEntity):
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the platform from config_entry."""
|
||||
async_add_entities([MoonSensorEntity(entry)], True)
|
||||
|
||||
|
||||
class MoonSensorEntity(SensorEntity):
|
||||
"""Representation of a Moon sensor."""
|
||||
|
||||
_attr_device_class = "moon__phase"
|
||||
|
||||
def __init__(self, name: str) -> None:
|
||||
def __init__(self, entry: ConfigEntry) -> None:
|
||||
"""Initialize the moon sensor."""
|
||||
self._attr_name = name
|
||||
self._attr_name = entry.title
|
||||
self._attr_unique_id = entry.entry_id
|
||||
|
||||
async def async_update(self):
|
||||
async def async_update(self) -> None:
|
||||
"""Get the time and updates the states."""
|
||||
today = dt_util.as_local(dt_util.utcnow()).date()
|
||||
today = dt_util.now().date()
|
||||
state = moon.phase(today)
|
||||
|
||||
if state < 0.5 or state > 27.5:
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"title": "Moon",
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "[%key:common::config_flow::description::confirm_setup%]"
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"single_instance_allowed": "Already configured. Only a single configuration possible."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "Do you want to start set up?"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Moon"
|
||||
}
|
|
@ -203,6 +203,7 @@ FLOWS = [
|
|||
"modern_forms",
|
||||
"moehlenhoff_alpha2",
|
||||
"monoprice",
|
||||
"moon",
|
||||
"motion_blinds",
|
||||
"motioneye",
|
||||
"mqtt",
|
||||
|
|
13
mypy.ini
13
mypy.ini
|
@ -1198,6 +1198,17 @@ no_implicit_optional = true
|
|||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.media_source.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
disallow_subclassing_any = true
|
||||
disallow_untyped_calls = true
|
||||
disallow_untyped_decorators = true
|
||||
disallow_untyped_defs = true
|
||||
no_implicit_optional = true
|
||||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.mjpeg.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
|
@ -1231,7 +1242,7 @@ no_implicit_optional = true
|
|||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.media_source.*]
|
||||
[mypy-homeassistant.components.moon.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
disallow_subclassing_any = true
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
"""Fixtures for Moon integration tests."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.moon.const import DOMAIN
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config_entry() -> MockConfigEntry:
|
||||
"""Return the default mocked config entry."""
|
||||
return MockConfigEntry(
|
||||
title="Moon",
|
||||
domain=DOMAIN,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_setup_entry() -> Generator[None, None, None]:
|
||||
"""Mock setting up a config entry."""
|
||||
with patch("homeassistant.components.moon.async_setup_entry", return_value=True):
|
||||
yield
|
|
@ -0,0 +1,72 @@
|
|||
"""Tests for the Moon config flow."""
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.moon.const import DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
|
||||
from homeassistant.const import 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: MagicMock,
|
||||
) -> 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={},
|
||||
)
|
||||
|
||||
assert result2.get("type") == RESULT_TYPE_CREATE_ENTRY
|
||||
assert result2.get("title") == "Moon"
|
||||
assert result2.get("data") == {}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("source", [SOURCE_USER, SOURCE_IMPORT])
|
||||
async def test_single_instance_allowed(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
source: str,
|
||||
) -> None:
|
||||
"""Test we abort if already setup."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": source}
|
||||
)
|
||||
|
||||
assert result.get("type") == RESULT_TYPE_ABORT
|
||||
assert result.get("reason") == "single_instance_allowed"
|
||||
|
||||
|
||||
async def test_import_flow(
|
||||
hass: HomeAssistant,
|
||||
mock_setup_entry: MagicMock,
|
||||
) -> None:
|
||||
"""Test the import configuration flow."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_IMPORT},
|
||||
data={CONF_NAME: "My Moon"},
|
||||
)
|
||||
|
||||
assert result.get("type") == RESULT_TYPE_CREATE_ENTRY
|
||||
assert result.get("title") == "My Moon"
|
||||
assert result.get("data") == {}
|
|
@ -0,0 +1,55 @@
|
|||
"""Tests for the Moon integration."""
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from homeassistant.components.moon.const import DOMAIN
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_load_unload_config_entry(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test the Moon configuration entry loading/unloading."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
await hass.config_entries.async_unload(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert not hass.data.get(DOMAIN)
|
||||
assert mock_config_entry.state is ConfigEntryState.NOT_LOADED
|
||||
|
||||
|
||||
async def test_import_config(
|
||||
hass: HomeAssistant,
|
||||
mock_setup_entry: AsyncMock,
|
||||
) -> None:
|
||||
"""Test Moon being set up from config via import."""
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
SENSOR_DOMAIN,
|
||||
{
|
||||
SENSOR_DOMAIN: {
|
||||
"platform": DOMAIN,
|
||||
CONF_NAME: "My Moon",
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
config_entries = hass.config_entries.async_entries(DOMAIN)
|
||||
assert len(config_entries) == 1
|
||||
|
||||
entry = config_entries[0]
|
||||
assert entry.title == "My Moon"
|
||||
assert entry.unique_id is None
|
||||
assert entry.data == {}
|
|
@ -5,10 +5,6 @@ from unittest.mock import patch
|
|||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.homeassistant import (
|
||||
DOMAIN as HA_DOMAIN,
|
||||
SERVICE_UPDATE_ENTITY,
|
||||
)
|
||||
from homeassistant.components.moon.sensor import (
|
||||
MOON_ICONS,
|
||||
STATE_FIRST_QUARTER,
|
||||
|
@ -20,9 +16,11 @@ from homeassistant.components.moon.sensor import (
|
|||
STATE_WAXING_CRESCENT,
|
||||
STATE_WAXING_GIBBOUS,
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
from homeassistant.const import ATTR_ICON
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -39,33 +37,27 @@ from homeassistant.setup import async_setup_component
|
|||
],
|
||||
)
|
||||
async def test_moon_day(
|
||||
hass: HomeAssistant, moon_value: float, native_value: str, icon: str
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
moon_value: float,
|
||||
native_value: str,
|
||||
icon: str,
|
||||
) -> None:
|
||||
"""Test the Moon sensor."""
|
||||
config = {"sensor": {"platform": "moon"}}
|
||||
|
||||
await async_setup_component(hass, HA_DOMAIN, {})
|
||||
assert await async_setup_component(hass, "sensor", config)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("sensor.moon")
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.moon.sensor.moon.phase", return_value=moon_value
|
||||
):
|
||||
await async_update_entity(hass, "sensor.moon")
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("sensor.moon")
|
||||
assert state
|
||||
assert state.state == native_value
|
||||
assert state.attributes["icon"] == icon
|
||||
assert state.attributes[ATTR_ICON] == icon
|
||||
|
||||
|
||||
async def async_update_entity(hass: HomeAssistant, entity_id: str) -> None:
|
||||
"""Run an update action for an entity."""
|
||||
await hass.services.async_call(
|
||||
HA_DOMAIN,
|
||||
SERVICE_UPDATE_ENTITY,
|
||||
{ATTR_ENTITY_ID: entity_id},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
entity_registry = er.async_get(hass)
|
||||
entry = entity_registry.async_get("sensor.moon")
|
||||
assert entry
|
||||
assert entry.unique_id == mock_config_entry.entry_id
|
||||
|
|
Loading…
Reference in New Issue