mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
Add sensor platform to Mealie (#122280)
* Bump aiomealie to 0.7.0 * Add sensor platform to Mealie * Fix
This commit is contained in:
parent
7f82fb8cb8
commit
874b1ae873
@ -22,11 +22,12 @@ from .coordinator import (
|
||||
MealieData,
|
||||
MealieMealplanCoordinator,
|
||||
MealieShoppingListCoordinator,
|
||||
MealieStatisticsCoordinator,
|
||||
)
|
||||
from .services import setup_services
|
||||
from .utils import create_version
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.CALENDAR, Platform.TODO]
|
||||
PLATFORMS: list[Platform] = [Platform.CALENDAR, Platform.SENSOR, Platform.TODO]
|
||||
|
||||
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
|
||||
|
||||
@ -75,12 +76,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: MealieConfigEntry) -> bo
|
||||
|
||||
mealplan_coordinator = MealieMealplanCoordinator(hass, client)
|
||||
shoppinglist_coordinator = MealieShoppingListCoordinator(hass, client)
|
||||
statistics_coordinator = MealieStatisticsCoordinator(hass, client)
|
||||
|
||||
await mealplan_coordinator.async_config_entry_first_refresh()
|
||||
await shoppinglist_coordinator.async_config_entry_first_refresh()
|
||||
await statistics_coordinator.async_config_entry_first_refresh()
|
||||
|
||||
entry.runtime_data = MealieData(
|
||||
client, mealplan_coordinator, shoppinglist_coordinator
|
||||
client, mealplan_coordinator, shoppinglist_coordinator, statistics_coordinator
|
||||
)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
@ -13,6 +13,7 @@ from aiomealie import (
|
||||
MealplanEntryType,
|
||||
ShoppingItem,
|
||||
ShoppingList,
|
||||
Statistics,
|
||||
)
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@ -33,6 +34,7 @@ class MealieData:
|
||||
client: MealieClient
|
||||
mealplan_coordinator: MealieMealplanCoordinator
|
||||
shoppinglist_coordinator: MealieShoppingListCoordinator
|
||||
statistics_coordinator: MealieStatisticsCoordinator
|
||||
|
||||
|
||||
type MealieConfigEntry = ConfigEntry[MealieData]
|
||||
@ -139,3 +141,26 @@ class MealieShoppingListCoordinator(
|
||||
except MealieConnectionError as error:
|
||||
raise UpdateFailed(error) from error
|
||||
return shopping_list_items
|
||||
|
||||
|
||||
class MealieStatisticsCoordinator(MealieDataUpdateCoordinator[Statistics]):
|
||||
"""Class to manage fetching Mealie Statistics data."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, client: MealieClient) -> None:
|
||||
"""Initialize coordinator."""
|
||||
super().__init__(
|
||||
hass,
|
||||
name="MealieStatistics",
|
||||
client=client,
|
||||
update_interval=timedelta(minutes=15),
|
||||
)
|
||||
|
||||
async def _async_update_data(
|
||||
self,
|
||||
) -> Statistics:
|
||||
try:
|
||||
return await self.client.get_statistics()
|
||||
except MealieAuthenticationError as error:
|
||||
raise ConfigEntryAuthFailed from error
|
||||
except MealieConnectionError as error:
|
||||
raise UpdateFailed(error) from error
|
||||
|
@ -4,6 +4,23 @@
|
||||
"shopping_list": {
|
||||
"default": "mdi:basket"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"recipes": {
|
||||
"default": "mdi:food"
|
||||
},
|
||||
"users": {
|
||||
"default": "mdi:account-multiple"
|
||||
},
|
||||
"categories": {
|
||||
"default": "mdi:shape"
|
||||
},
|
||||
"tags": {
|
||||
"default": "mdi:tag-multiple"
|
||||
},
|
||||
"tools": {
|
||||
"default": "mdi:tools"
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
|
94
homeassistant/components/mealie/sensor.py
Normal file
94
homeassistant/components/mealie/sensor.py
Normal file
@ -0,0 +1,94 @@
|
||||
"""Support for Mealie sensors."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
|
||||
from aiomealie import Statistics
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
|
||||
from .coordinator import MealieConfigEntry, MealieStatisticsCoordinator
|
||||
from .entity import MealieEntity
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class MealieStatisticsSensorEntityDescription(SensorEntityDescription):
|
||||
"""Describes Mealie Statistics sensor entity."""
|
||||
|
||||
value_fn: Callable[[Statistics], StateType]
|
||||
|
||||
|
||||
SENSOR_TYPES: tuple[MealieStatisticsSensorEntityDescription, ...] = (
|
||||
MealieStatisticsSensorEntityDescription(
|
||||
key="recipes",
|
||||
native_unit_of_measurement="recipes",
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
value_fn=lambda statistics: statistics.total_recipes,
|
||||
),
|
||||
MealieStatisticsSensorEntityDescription(
|
||||
key="users",
|
||||
native_unit_of_measurement="users",
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
value_fn=lambda statistics: statistics.total_users,
|
||||
),
|
||||
MealieStatisticsSensorEntityDescription(
|
||||
key="categories",
|
||||
native_unit_of_measurement="categories",
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
value_fn=lambda statistics: statistics.total_categories,
|
||||
),
|
||||
MealieStatisticsSensorEntityDescription(
|
||||
key="tags",
|
||||
native_unit_of_measurement="tags",
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
value_fn=lambda statistics: statistics.total_tags,
|
||||
),
|
||||
MealieStatisticsSensorEntityDescription(
|
||||
key="tools",
|
||||
native_unit_of_measurement="tools",
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
value_fn=lambda statistics: statistics.total_tools,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: MealieConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Mealie sensors based on a config entry."""
|
||||
coordinator = entry.runtime_data.statistics_coordinator
|
||||
|
||||
async_add_entities(
|
||||
MealieStatisticSensors(coordinator, description) for description in SENSOR_TYPES
|
||||
)
|
||||
|
||||
|
||||
class MealieStatisticSensors(MealieEntity, SensorEntity):
|
||||
"""Defines a Mealie sensor."""
|
||||
|
||||
entity_description: MealieStatisticsSensorEntityDescription
|
||||
coordinator: MealieStatisticsCoordinator
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: MealieStatisticsCoordinator,
|
||||
description: MealieStatisticsSensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize Mealie sensor."""
|
||||
super().__init__(coordinator, description.key)
|
||||
self.entity_description = description
|
||||
self._attr_translation_key = description.key
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the state of the sensor."""
|
||||
return self.entity_description.value_fn(self.coordinator.data)
|
@ -53,6 +53,23 @@
|
||||
"side": {
|
||||
"name": "Side"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"recipes": {
|
||||
"name": "Recipes"
|
||||
},
|
||||
"users": {
|
||||
"name": "Users"
|
||||
},
|
||||
"categories": {
|
||||
"name": "Categories"
|
||||
},
|
||||
"tags": {
|
||||
"name": "Tags"
|
||||
},
|
||||
"tools": {
|
||||
"name": "Tools"
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
|
@ -10,6 +10,7 @@ from aiomealie import (
|
||||
Recipe,
|
||||
ShoppingItemsResponse,
|
||||
ShoppingListsResponse,
|
||||
Statistics,
|
||||
UserInfo,
|
||||
)
|
||||
from mashumaro.codecs.orjson import ORJSONDecoder
|
||||
@ -70,6 +71,9 @@ def mock_mealie_client() -> Generator[AsyncMock]:
|
||||
client.get_shopping_items.return_value = ShoppingItemsResponse.from_json(
|
||||
load_fixture("get_shopping_items.json", DOMAIN)
|
||||
)
|
||||
client.get_statistics.return_value = Statistics.from_json(
|
||||
load_fixture("statistics.json", DOMAIN)
|
||||
)
|
||||
yield client
|
||||
|
||||
|
||||
|
7
tests/components/mealie/fixtures/statistics.json
Normal file
7
tests/components/mealie/fixtures/statistics.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"totalRecipes": 765,
|
||||
"totalUsers": 3,
|
||||
"totalCategories": 24,
|
||||
"totalTags": 454,
|
||||
"totalTools": 11
|
||||
}
|
251
tests/components/mealie/snapshots/test_sensor.ambr
Normal file
251
tests/components/mealie/snapshots/test_sensor.ambr
Normal file
@ -0,0 +1,251 @@
|
||||
# serializer version: 1
|
||||
# name: test_entities[sensor.mealie_categories-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.mealie_categories',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Categories',
|
||||
'platform': 'mealie',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'categories',
|
||||
'unique_id': 'bf1c62fe-4941-4332-9886-e54e88dbdba0_categories',
|
||||
'unit_of_measurement': 'categories',
|
||||
})
|
||||
# ---
|
||||
# name: test_entities[sensor.mealie_categories-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mealie Categories',
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
'unit_of_measurement': 'categories',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.mealie_categories',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '24',
|
||||
})
|
||||
# ---
|
||||
# name: test_entities[sensor.mealie_recipes-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.mealie_recipes',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Recipes',
|
||||
'platform': 'mealie',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'recipes',
|
||||
'unique_id': 'bf1c62fe-4941-4332-9886-e54e88dbdba0_recipes',
|
||||
'unit_of_measurement': 'recipes',
|
||||
})
|
||||
# ---
|
||||
# name: test_entities[sensor.mealie_recipes-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mealie Recipes',
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
'unit_of_measurement': 'recipes',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.mealie_recipes',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '765',
|
||||
})
|
||||
# ---
|
||||
# name: test_entities[sensor.mealie_tags-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.mealie_tags',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Tags',
|
||||
'platform': 'mealie',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'tags',
|
||||
'unique_id': 'bf1c62fe-4941-4332-9886-e54e88dbdba0_tags',
|
||||
'unit_of_measurement': 'tags',
|
||||
})
|
||||
# ---
|
||||
# name: test_entities[sensor.mealie_tags-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mealie Tags',
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
'unit_of_measurement': 'tags',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.mealie_tags',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '454',
|
||||
})
|
||||
# ---
|
||||
# name: test_entities[sensor.mealie_tools-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.mealie_tools',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Tools',
|
||||
'platform': 'mealie',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'tools',
|
||||
'unique_id': 'bf1c62fe-4941-4332-9886-e54e88dbdba0_tools',
|
||||
'unit_of_measurement': 'tools',
|
||||
})
|
||||
# ---
|
||||
# name: test_entities[sensor.mealie_tools-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mealie Tools',
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
'unit_of_measurement': 'tools',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.mealie_tools',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '11',
|
||||
})
|
||||
# ---
|
||||
# name: test_entities[sensor.mealie_users-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.mealie_users',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Users',
|
||||
'platform': 'mealie',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'users',
|
||||
'unique_id': 'bf1c62fe-4941-4332-9886-e54e88dbdba0_users',
|
||||
'unit_of_measurement': 'users',
|
||||
})
|
||||
# ---
|
||||
# name: test_entities[sensor.mealie_users-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mealie Users',
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
'unit_of_measurement': 'users',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.mealie_users',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '3',
|
||||
})
|
||||
# ---
|
27
tests/components/mealie/test_sensor.py
Normal file
27
tests/components/mealie/test_sensor.py
Normal file
@ -0,0 +1,27 @@
|
||||
"""Tests for the Mealie sensors."""
|
||||
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
|
||||
|
||||
async def test_entities(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
entity_registry: er.EntityRegistry,
|
||||
mock_mealie_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test the sensor entities."""
|
||||
with patch("homeassistant.components.mealie.PLATFORMS", [Platform.SENSOR]):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
Loading…
x
Reference in New Issue
Block a user