Skip to content

[pull] dev from home-assistant:dev #645

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

Merged
merged 10 commits into from
May 1, 2025
Merged
11 changes: 1 addition & 10 deletions homeassistant/components/habitica/const.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Constants for the habitica integration."""

from homeassistant.const import APPLICATION_NAME, CONF_PATH, __version__
from homeassistant.const import APPLICATION_NAME, __version__

CONF_API_USER = "api_user"

Expand All @@ -13,15 +13,6 @@

DOMAIN = "habitica"

# service constants
SERVICE_API_CALL = "api_call"
ATTR_PATH = CONF_PATH
ATTR_ARGS = "args"

# event constants
EVENT_API_CALL_SUCCESS = f"{DOMAIN}_{SERVICE_API_CALL}_success"
ATTR_DATA = "data"

MANUFACTURER = "HabitRPG, Inc."
NAME = "Habitica"

Expand Down
62 changes: 1 addition & 61 deletions homeassistant/components/habitica/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

from homeassistant.components.todo import ATTR_RENAME
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ATTR_DATE, ATTR_NAME, CONF_NAME
from homeassistant.const import ATTR_DATE, ATTR_NAME
from homeassistant.core import (
HomeAssistant,
ServiceCall,
Expand All @@ -38,28 +38,24 @@
)
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.selector import ConfigEntrySelector
from homeassistant.util import dt as dt_util

from .const import (
ATTR_ADD_CHECKLIST_ITEM,
ATTR_ALIAS,
ATTR_ARGS,
ATTR_CLEAR_DATE,
ATTR_CLEAR_REMINDER,
ATTR_CONFIG_ENTRY,
ATTR_COST,
ATTR_COUNTER_DOWN,
ATTR_COUNTER_UP,
ATTR_DATA,
ATTR_DIRECTION,
ATTR_FREQUENCY,
ATTR_INTERVAL,
ATTR_ITEM,
ATTR_KEYWORD,
ATTR_NOTES,
ATTR_PATH,
ATTR_PRIORITY,
ATTR_REMINDER,
ATTR_REMOVE_CHECKLIST_ITEM,
Expand All @@ -78,10 +74,8 @@
ATTR_UNSCORE_CHECKLIST_ITEM,
ATTR_UP_DOWN,
DOMAIN,
EVENT_API_CALL_SUCCESS,
SERVICE_ABORT_QUEST,
SERVICE_ACCEPT_QUEST,
SERVICE_API_CALL,
SERVICE_CANCEL_QUEST,
SERVICE_CAST_SKILL,
SERVICE_CREATE_DAILY,
Expand All @@ -106,14 +100,6 @@
_LOGGER = logging.getLogger(__name__)


SERVICE_API_CALL_SCHEMA = vol.Schema(
{
vol.Required(ATTR_NAME): str,
vol.Required(ATTR_PATH): vol.All(cv.ensure_list, [str]),
vol.Optional(ATTR_ARGS): dict,
}
)

SERVICE_CAST_SKILL_SCHEMA = vol.Schema(
{
vol.Required(ATTR_CONFIG_ENTRY): ConfigEntrySelector({"integration": DOMAIN}),
Expand Down Expand Up @@ -266,46 +252,6 @@ def get_config_entry(hass: HomeAssistant, entry_id: str) -> HabiticaConfigEntry:
def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901
"""Set up services for Habitica integration."""

async def handle_api_call(call: ServiceCall) -> None:
async_create_issue(
hass,
DOMAIN,
"deprecated_api_call",
breaks_in_ha_version="2025.6.0",
is_fixable=False,
severity=IssueSeverity.WARNING,
translation_key="deprecated_api_call",
)
_LOGGER.warning(
"Deprecated action called: 'habitica.api_call' is deprecated and will be removed in Home Assistant version 2025.6.0"
)

name = call.data[ATTR_NAME]
path = call.data[ATTR_PATH]
entries: list[HabiticaConfigEntry] = hass.config_entries.async_entries(DOMAIN)

api = None
for entry in entries:
if entry.data[CONF_NAME] == name:
api = await entry.runtime_data.habitica.habitipy()
break
if api is None:
_LOGGER.error("API_CALL: User '%s' not configured", name)
return
try:
for element in path:
api = api[element]
except KeyError:
_LOGGER.error(
"API_CALL: Path %s is invalid for API on '{%s}' element", path, element
)
return
kwargs = call.data.get(ATTR_ARGS, {})
data = await api(**kwargs)
hass.bus.async_fire(
EVENT_API_CALL_SUCCESS, {ATTR_NAME: name, ATTR_PATH: path, ATTR_DATA: data}
)

async def cast_skill(call: ServiceCall) -> ServiceResponse:
"""Skill action."""
entry = get_config_entry(hass, call.data[ATTR_CONFIG_ENTRY])
Expand Down Expand Up @@ -928,12 +874,6 @@ async def create_tag(tag_name: str) -> UUID:
schema=SERVICE_CREATE_TASK_SCHEMA,
supports_response=SupportsResponse.ONLY,
)
hass.services.async_register(
DOMAIN,
SERVICE_API_CALL,
handle_api_call,
schema=SERVICE_API_CALL_SCHEMA,
)

hass.services.async_register(
DOMAIN,
Expand Down
16 changes: 0 additions & 16 deletions homeassistant/components/habitica/services.yaml
Original file line number Diff line number Diff line change
@@ -1,20 +1,4 @@
# Describes the format for Habitica service
api_call:
fields:
name:
required: true
example: "xxxNotAValidNickxxx"
selector:
text:
path:
required: true
example: '["tasks", "user", "post"]'
selector:
object:
args:
example: '{"text": "Use API from Home Assistant", "type": "todo"}'
selector:
object:
cast_skill:
fields:
config_entry: &config_entry
Expand Down
22 changes: 0 additions & 22 deletions homeassistant/components/habitica/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -526,31 +526,9 @@
"deprecated_entity": {
"title": "The Habitica {name} entity is deprecated",
"description": "The Habitica entity `{entity}` is deprecated and will be removed in a future release.\nPlease update your automations and scripts, disable `{entity}` and reload the integration/restart Home Assistant to fix this issue."
},
"deprecated_api_call": {
"title": "The Habitica action habitica.api_call is deprecated",
"description": "The Habitica action `habitica.api_call` is deprecated and will be removed in Home Assistant 2025.5.0.\n\nPlease update your automations and scripts to use other Habitica actions and entities."
}
},
"services": {
"api_call": {
"name": "API name",
"description": "Calls Habitica API.",
"fields": {
"name": {
"name": "[%key:common::config_flow::data::name%]",
"description": "Habitica's username to call for."
},
"path": {
"name": "[%key:common::config_flow::data::path%]",
"description": "Items from API URL in form of an array with method attached at the end. Consult https://habitica.com/apidoc/. Example uses https://habitica.com/apidoc/#api-Task-CreateUserTasks."
},
"args": {
"name": "Args",
"description": "Any additional JSON or URL parameter arguments. See apidoc mentioned for path. Example uses same API endpoint."
}
}
},
"cast_skill": {
"name": "Cast a skill",
"description": "Uses a skill or spell from your Habitica character on a specific task to affect its progress or status.",
Expand Down
12 changes: 3 additions & 9 deletions homeassistant/components/home_connect/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

from typing import Any

from aiohomeconnect.client import Client as HomeConnectClient

from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntry

Expand All @@ -14,7 +12,7 @@


async def _generate_appliance_diagnostics(
client: HomeConnectClient, appliance: HomeConnectApplianceData
appliance: HomeConnectApplianceData,
) -> dict[str, Any]:
return {
**appliance.info.to_dict(),
Expand All @@ -31,9 +29,7 @@ async def async_get_config_entry_diagnostics(
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
return {
appliance.info.ha_id: await _generate_appliance_diagnostics(
entry.runtime_data.client, appliance
)
appliance.info.ha_id: await _generate_appliance_diagnostics(appliance)
for appliance in entry.runtime_data.data.values()
}

Expand All @@ -45,6 +41,4 @@ async def async_get_device_diagnostics(
ha_id = next(
(identifier[1] for identifier in device.identifiers if identifier[0] == DOMAIN),
)
return await _generate_appliance_diagnostics(
entry.runtime_data.client, entry.runtime_data.data[ha_id]
)
return await _generate_appliance_diagnostics(entry.runtime_data.data[ha_id])
1 change: 0 additions & 1 deletion homeassistant/components/home_connect/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
class HomeConnectEntity(CoordinatorEntity[HomeConnectCoordinator]):
"""Generic Home Connect entity (base class)."""

_attr_should_poll = False
_attr_has_entity_name = True

def __init__(
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/home_connect/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@
class HomeConnectLightEntityDescription(LightEntityDescription):
"""Light entity description."""

brightness_key: SettingKey | None = None
brightness_key: SettingKey
brightness_scale: tuple[float, float]
color_key: SettingKey | None = None
enable_custom_color_value_key: str | None = None
custom_color_key: SettingKey | None = None
brightness_scale: tuple[float, float] = (0.0, 100.0)


LIGHTS: tuple[HomeConnectLightEntityDescription, ...] = (
Expand Down
3 changes: 2 additions & 1 deletion homeassistant/components/home_connect/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
NumberEntity,
NumberEntityDescription,
)
from homeassistant.const import PERCENTAGE
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
Expand Down Expand Up @@ -79,7 +80,7 @@
NumberEntityDescription(
key=SettingKey.COOKING_HOOD_COLOR_TEMPERATURE_PERCENT,
translation_key="color_temperature_percent",
native_unit_of_measurement="%",
native_unit_of_measurement=PERCENTAGE,
),
NumberEntityDescription(
key=SettingKey.LAUNDRY_CARE_WASHER_I_DOS_1_BASE_LEVEL,
Expand Down
1 change: 0 additions & 1 deletion homeassistant/components/home_connect/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,6 @@ class HomeConnectSensorEntityDescription(
HomeConnectSensorEntityDescription(
key=StatusKey.BSH_COMMON_BATTERY_LEVEL,
device_class=SensorDeviceClass.BATTERY,
translation_key="battery_level",
),
HomeConnectSensorEntityDescription(
key=StatusKey.BSH_COMMON_VIDEO_CAMERA_STATE,
Expand Down
3 changes: 0 additions & 3 deletions homeassistant/components/home_connect/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1585,9 +1585,6 @@
"name": "Ristretto espresso cups",
"unit_of_measurement": "[%key:component::home_connect::entity::sensor::hot_water_cups_counter::unit_of_measurement%]"
},
"battery_level": {
"name": "Battery level"
},
"camera_state": {
"name": "Camera state",
"state": {
Expand Down
6 changes: 4 additions & 2 deletions homeassistant/components/miele/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,10 @@ def __init__(
@property
def is_on(self) -> bool:
"""Return current on/off state."""
assert self.device.state_ventilation_step is not None
return self.device.state_ventilation_step > 0
return (
self.device.state_ventilation_step is not None
and self.device.state_ventilation_step > 0
)

@property
def speed_count(self) -> int:
Expand Down
51 changes: 3 additions & 48 deletions tests/components/habitica/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,9 @@
from freezegun.api import FrozenDateTimeFactory
import pytest

from homeassistant.components.habitica.const import (
ATTR_ARGS,
ATTR_DATA,
ATTR_PATH,
DOMAIN,
EVENT_API_CALL_SUCCESS,
SERVICE_API_CALL,
)
from homeassistant.components.habitica.const import DOMAIN
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
from homeassistant.const import ATTR_NAME
from homeassistant.core import Event, HomeAssistant
from homeassistant.core import HomeAssistant

from .conftest import (
ERROR_BAD_REQUEST,
Expand All @@ -27,13 +19,7 @@
ERROR_TOO_MANY_REQUESTS,
)

from tests.common import MockConfigEntry, async_capture_events, async_fire_time_changed


@pytest.fixture
def capture_api_call_success(hass: HomeAssistant) -> list[Event]:
"""Capture api_call events."""
return async_capture_events(hass, EVENT_API_CALL_SUCCESS)
from tests.common import MockConfigEntry, async_fire_time_changed


@pytest.mark.usefixtures("habitica")
Expand All @@ -53,37 +39,6 @@ async def test_entry_setup_unload(
assert config_entry.state is ConfigEntryState.NOT_LOADED


@pytest.mark.usefixtures("habitica")
async def test_service_call(
hass: HomeAssistant,
config_entry: MockConfigEntry,
capture_api_call_success: list[Event],
) -> None:
"""Test integration setup, service call and unload."""
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()

assert config_entry.state is ConfigEntryState.LOADED

assert len(capture_api_call_success) == 0

TEST_SERVICE_DATA = {
ATTR_NAME: "test-user",
ATTR_PATH: ["tasks", "user", "post"],
ATTR_ARGS: {"text": "Use API from Home Assistant", "type": "todo"},
}
await hass.services.async_call(
DOMAIN, SERVICE_API_CALL, TEST_SERVICE_DATA, blocking=True
)

assert len(capture_api_call_success) == 1
captured_data = capture_api_call_success[0].data
captured_data[ATTR_ARGS] = captured_data[ATTR_DATA]
del captured_data[ATTR_DATA]
assert captured_data == TEST_SERVICE_DATA


@pytest.mark.parametrize(
("exception"),
[ERROR_BAD_REQUEST, ERROR_TOO_MANY_REQUESTS, ClientError],
Expand Down
2 changes: 1 addition & 1 deletion tests/components/home_connect/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def mock_config_entry_v1_2(token_entry: dict[str, Any]) -> MockConfigEntry:
)


@pytest.fixture
@pytest.fixture(autouse=True)
async def setup_credentials(hass: HomeAssistant) -> None:
"""Fixture to setup credentials."""
assert await async_setup_component(hass, "application_credentials", {})
Expand Down
Loading