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 date platform #81948

Merged
merged 22 commits into from Apr 30, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Expand Up @@ -232,6 +232,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/cups/ @fabaff
/homeassistant/components/daikin/ @fredrike
/tests/components/daikin/ @fredrike
/homeassistant/components/date/ @home-assistant/core
/tests/components/date/ @home-assistant/core
/homeassistant/components/debugpy/ @frenck
/tests/components/debugpy/ @frenck
/homeassistant/components/deconz/ @Kane610
Expand Down
109 changes: 109 additions & 0 deletions homeassistant/components/date/__init__.py
@@ -0,0 +1,109 @@
"""Component to allow setting date as platforms."""
from __future__ import annotations

from dataclasses import dataclass
from datetime import date, timedelta
import logging
from typing import final

import voluptuous as vol

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_DATE
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.config_validation import ( # noqa: F401
PLATFORM_SCHEMA,
PLATFORM_SCHEMA_BASE,
)
from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.typing import ConfigType

from .const import DOMAIN, SERVICE_SET_VALUE

SCAN_INTERVAL = timedelta(seconds=30)

ENTITY_ID_FORMAT = DOMAIN + ".{}"

_LOGGER = logging.getLogger(__name__)

__all__ = ["DOMAIN", "DateEntity", "DateEntityDescription"]


async def _async_set_value(entity: DateEntity, service_call: ServiceCall) -> None:
"""Service call wrapper to set a new date."""
return await entity.async_set_value(service_call.data[ATTR_DATE])

frenck marked this conversation as resolved.
Show resolved Hide resolved

async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up Date entities."""
component = hass.data[DOMAIN] = EntityComponent[DateEntity](
_LOGGER, DOMAIN, hass, SCAN_INTERVAL
)
await component.async_setup(config)

component.async_register_entity_service(
SERVICE_SET_VALUE, {vol.Required(ATTR_DATE): cv.date}, _async_set_value
raman325 marked this conversation as resolved.
Show resolved Hide resolved
)

return True


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up a config entry."""
component: EntityComponent[DateEntity] = hass.data[DOMAIN]
return await component.async_setup_entry(entry)


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
component: EntityComponent[DateEntity] = hass.data[DOMAIN]
return await component.async_unload_entry(entry)

Check warning on line 62 in homeassistant/components/date/__init__.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/date/__init__.py#L61-L62

Added lines #L61 - L62 were not covered by tests


@dataclass
class DateEntityDescription(EntityDescription):
"""A class that describes date entities."""


class DateEntity(Entity):
raman325 marked this conversation as resolved.
Show resolved Hide resolved
"""Representation of a Date entity."""

entity_description: DateEntityDescription
raman325 marked this conversation as resolved.
Show resolved Hide resolved
_attr_device_class: None
_attr_native_value: date | None
raman325 marked this conversation as resolved.
Show resolved Hide resolved
_attr_state: None = None

@property
@final
def device_class(self) -> None:
"""Return the device class for the entity."""
return None

@property
@final
def state_attributes(self) -> None:
"""Return the state attributes."""
return None

@property
@final
def state(self) -> str | None:
"""Return the entity state."""
if self.native_value is None:
return None
return self.native_value.isoformat()

@property
def native_value(self) -> date | None:
"""Return the value reported by the date."""
return self._attr_native_value

def set_value(self, value: date) -> None:
"""Change the date."""
raise NotImplementedError()

async def async_set_value(self, value: date) -> None:
"""Change the date."""
await self.hass.async_add_executor_job(self.set_value, value)
5 changes: 5 additions & 0 deletions homeassistant/components/date/const.py
@@ -0,0 +1,5 @@
"""Provides the constants needed for the component."""

DOMAIN = "date"

SERVICE_SET_VALUE = "set_value"
8 changes: 8 additions & 0 deletions homeassistant/components/date/manifest.json
@@ -0,0 +1,8 @@
{
"domain": "date",
"name": "Date",
"codeowners": ["@home-assistant/core"],
"documentation": "https://www.home-assistant.io/integrations/date",
"integration_type": "entity",
"quality_scale": "internal"
raman325 marked this conversation as resolved.
Show resolved Hide resolved
}
14 changes: 14 additions & 0 deletions homeassistant/components/date/services.yaml
@@ -0,0 +1,14 @@
set_value:
name: Set Date
description: Set the date for a date entity.
target:
entity:
domain: date
fields:
date:
name: Date
description: The date to set.
required: true
example: "2022/11/01"
selector:
date:
8 changes: 8 additions & 0 deletions homeassistant/components/date/strings.json
@@ -0,0 +1,8 @@
{
"title": "Date",
"entity_component": {
"_": {
"name": "[%key:component::date::title%]"
}
}
}
1 change: 1 addition & 0 deletions homeassistant/components/demo/__init__.py
Expand Up @@ -27,6 +27,7 @@
Platform.CAMERA,
Platform.CLIMATE,
Platform.COVER,
Platform.DATE,
Platform.FAN,
Platform.HUMIDIFIER,
Platform.LIGHT,
Expand Down
73 changes: 73 additions & 0 deletions homeassistant/components/demo/date.py
@@ -0,0 +1,73 @@
"""Demo platform that offers a fake Date entity."""
from __future__ import annotations

from datetime import date

from homeassistant.components.date import DateEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import DEVICE_DEFAULT_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

from . import DOMAIN


async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Demo date entity."""
async_add_entities(
[
DemoDate(
"date",
"Date",
date(2020, 1, 1),
"mdi:calendar",
False,
),
]
)


async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Demo config entry."""
await async_setup_platform(hass, {}, async_add_entities)


class DemoDate(DateEntity):
"""Representation of a Demo date entity."""

_attr_should_poll = False

def __init__(
self,
unique_id: str,
name: str,
state: date,
icon: str,
assumed_state: bool,
) -> None:
"""Initialize the Demo date entity."""
self._attr_assumed_state = assumed_state
self._attr_icon = icon
self._attr_name = name or DEVICE_DEFAULT_NAME
self._attr_native_value = state
self._attr_unique_id = unique_id

self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, unique_id)}, name=self.name
)

async def async_set_value(self, value: date) -> None:
"""Update the date."""
self._attr_native_value = value
self.async_write_ha_state()
1 change: 1 addition & 0 deletions homeassistant/const.py
Expand Up @@ -31,6 +31,7 @@ class Platform(StrEnum):
CAMERA = "camera"
CLIMATE = "climate"
COVER = "cover"
DATE = "date"
DEVICE_TRACKER = "device_tracker"
FAN = "fan"
GEO_LOCATION = "geo_location"
Expand Down
1 change: 1 addition & 0 deletions tests/components/date/__init__.py
@@ -0,0 +1 @@
"""Tests for the date component."""
54 changes: 54 additions & 0 deletions tests/components/date/test_init.py
@@ -0,0 +1,54 @@
"""The tests for the date component."""
raman325 marked this conversation as resolved.
Show resolved Hide resolved
from datetime import date

from homeassistant.components.date import DOMAIN, SERVICE_SET_VALUE, DateEntity
from homeassistant.const import (
ATTR_DATE,
ATTR_ENTITY_ID,
ATTR_FRIENDLY_NAME,
CONF_PLATFORM,
)
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component


class MockDateEntity(DateEntity):
"""Mock date device to use in tests."""

_attr_name = "date"

def __init__(self, native_value=date(2020, 1, 1)) -> None:
"""Initialize mock date entity."""
self._attr_native_value = native_value

async def async_set_value(self, value: date) -> None:
"""Set the value of the date."""
self._attr_native_value = value


async def test_date(hass: HomeAssistant, enable_custom_integrations: None) -> None:
"""Test date entity."""
platform = getattr(hass.components, f"test.{DOMAIN}")
platform.init()

assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
await hass.async_block_till_done()

state = hass.states.get("date.test")
assert state.state == "2020-01-01"
assert state.attributes == {ATTR_FRIENDLY_NAME: "test"}

await hass.services.async_call(
DOMAIN,
SERVICE_SET_VALUE,
{ATTR_DATE: date(2021, 1, 1), ATTR_ENTITY_ID: "date.test"},
blocking=True,
)
await hass.async_block_till_done()

state = hass.states.get("date.test")
assert state.state == "2021-01-01"

date_entity = MockDateEntity(native_value=None)
assert date_entity.state is None
assert date_entity.state_attributes is None
34 changes: 34 additions & 0 deletions tests/components/demo/test_date.py
@@ -0,0 +1,34 @@
"""The tests for the demo date component."""
import pytest

from homeassistant.components.date import ATTR_DATE, DOMAIN, SERVICE_SET_VALUE
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component

ENTITY_DATE = "date.date"


@pytest.fixture(autouse=True)
async def setup_demo_date(hass: HomeAssistant) -> None:
"""Initialize setup demo date."""
assert await async_setup_component(hass, DOMAIN, {"date": {"platform": "demo"}})
await hass.async_block_till_done()


def test_setup_params(hass: HomeAssistant) -> None:
"""Test the initial parameters."""
state = hass.states.get(ENTITY_DATE)
assert state.state == "2020-01-01"


async def test_set_datetime(hass: HomeAssistant) -> None:
"""Test set datetime service."""
await hass.services.async_call(
DOMAIN,
SERVICE_SET_VALUE,
{ATTR_ENTITY_ID: ENTITY_DATE, ATTR_DATE: "2021-02-03"},
blocking=True,
)
state = hass.states.get(ENTITY_DATE)
assert state.state == "2021-02-03"
50 changes: 50 additions & 0 deletions tests/testing_config/custom_components/test/date.py
@@ -0,0 +1,50 @@
"""Provide a mock date platform.

Call init before using it in your tests to ensure clean test data.
"""
from datetime import date

from homeassistant.components.date import DateEntity

from tests.common import MockEntity

UNIQUE_DATE = "unique_date"

ENTITIES = []


class MockDateEntity(MockEntity, DateEntity):
"""Mock date class."""

@property
def native_value(self):
"""Return the native value of this date."""
return self._handle("native_value")

def set_value(self, value: date) -> None:
"""Change the date."""
self._values["native_value"] = value


def init(empty=False):
"""Initialize the platform with entities."""
global ENTITIES

ENTITIES = (
[]
if empty
else [
MockDateEntity(
name="test",
unique_id=UNIQUE_DATE,
native_value=date(2020, 1, 1),
),
]
)


async def async_setup_platform(
hass, config, async_add_entities_callback, discovery_info=None
):
"""Return mock entities."""
async_add_entities_callback(ENTITIES)