Remove deprecated 17track package sensor (#136389)

This commit is contained in:
G Johansson 2025-01-24 08:43:18 +01:00 committed by GitHub
parent 90d95d935e
commit e44cfa00af
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 3 additions and 476 deletions

View File

@ -47,6 +47,3 @@ SERVICE_ARCHIVE_PACKAGE = "archive_package"
ATTR_PACKAGE_STATE = "package_state"
ATTR_PACKAGE_TRACKING_NUMBER = "package_tracking_number"
ATTR_CONFIG_ENTRY_ID = "config_entry_id"
DEPRECATED_KEY = "deprecated"

View File

@ -1,49 +0,0 @@
"""Repairs for the SeventeenTrack integration."""
import voluptuous as vol
from homeassistant.components.repairs import ConfirmRepairFlow, RepairsFlow
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResult
from .const import DEPRECATED_KEY
class SensorDeprecationRepairFlow(RepairsFlow):
"""Handler for an issue fixing flow."""
def __init__(self, entry: ConfigEntry) -> None:
"""Create flow."""
self.entry = entry
async def async_step_init(
self, user_input: dict[str, str] | None = None
) -> FlowResult:
"""Handle the first step of a fix flow."""
return await self.async_step_confirm()
async def async_step_confirm(
self, user_input: dict[str, str] | None = None
) -> FlowResult:
"""Handle the confirm step of a fix flow."""
if user_input is not None:
data = {**self.entry.data, DEPRECATED_KEY: True}
self.hass.config_entries.async_update_entry(self.entry, data=data)
return self.async_create_entry(data={})
return self.async_show_form(
step_id="confirm",
data_schema=vol.Schema({}),
)
async def async_create_fix_flow(
hass: HomeAssistant, issue_id: str, data: dict
) -> RepairsFlow:
"""Create flow."""
if issue_id.startswith("deprecate_sensor_") and (
entry := hass.config_entries.async_get_entry(data["entry_id"])
):
return SensorDeprecationRepairFlow(entry)
return ConfirmRepairFlow()

View File

@ -4,12 +4,10 @@ from __future__ import annotations
from typing import Any
from homeassistant.components import persistent_notification
from homeassistant.components.sensor import SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_LOCATION
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er, issue_registry as ir
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
@ -17,23 +15,13 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import SeventeenTrackCoordinator
from .const import (
ATTR_DESTINATION_COUNTRY,
ATTR_INFO_TEXT,
ATTR_ORIGIN_COUNTRY,
ATTR_PACKAGE_TYPE,
ATTR_PACKAGES,
ATTR_STATUS,
ATTR_TIMESTAMP,
ATTR_TRACKING_INFO_LANGUAGE,
ATTR_TRACKING_NUMBER,
ATTRIBUTION,
DEPRECATED_KEY,
DOMAIN,
LOGGER,
NOTIFICATION_DELIVERED_MESSAGE,
NOTIFICATION_DELIVERED_TITLE,
UNIQUE_ID_TEMPLATE,
VALUE_DELIVERED,
)
@ -45,63 +33,12 @@ async def async_setup_entry(
"""Set up a 17Track sensor entry."""
coordinator: SeventeenTrackCoordinator = hass.data[DOMAIN][config_entry.entry_id]
previous_tracking_numbers: set[str] = set()
# This has been deprecated in 2024.8, will be removed in 2025.2
@callback
def _async_create_remove_entities():
if config_entry.data.get(DEPRECATED_KEY):
remove_packages(hass, coordinator.account_id, previous_tracking_numbers)
return
live_tracking_numbers = set(coordinator.data.live_packages.keys())
new_tracking_numbers = live_tracking_numbers - previous_tracking_numbers
old_tracking_numbers = previous_tracking_numbers - live_tracking_numbers
previous_tracking_numbers.update(live_tracking_numbers)
packages_to_add = [
coordinator.data.live_packages[tracking_number]
for tracking_number in new_tracking_numbers
]
for package_data in coordinator.data.live_packages.values():
if (
package_data.status == VALUE_DELIVERED
and not coordinator.show_delivered
):
old_tracking_numbers.add(package_data.tracking_number)
notify_delivered(
hass,
package_data.friendly_name,
package_data.tracking_number,
)
remove_packages(hass, coordinator.account_id, old_tracking_numbers)
async_add_entities(
SeventeenTrackPackageSensor(
coordinator,
package_data.tracking_number,
)
for package_data in packages_to_add
if not (
not coordinator.show_delivered and package_data.status == "Delivered"
)
)
async_add_entities(
SeventeenTrackSummarySensor(status, coordinator)
for status, summary_data in coordinator.data.summary.items()
)
if not config_entry.data.get(DEPRECATED_KEY):
deprecate_sensor_issue(hass, config_entry.entry_id)
_async_create_remove_entities()
config_entry.async_on_unload(
coordinator.async_add_listener(_async_create_remove_entities)
)
class SeventeenTrackSensor(CoordinatorEntity[SeventeenTrackCoordinator], SensorEntity):
"""Define a 17Track sensor."""
@ -163,96 +100,3 @@ class SeventeenTrackSummarySensor(SeventeenTrackSensor):
for package in packages
]
}
# The dynamic package sensors have been replaced by the seventeentrack.get_packages service
class SeventeenTrackPackageSensor(SeventeenTrackSensor):
"""Define an individual package sensor."""
_attr_translation_key = "package"
def __init__(
self,
coordinator: SeventeenTrackCoordinator,
tracking_number: str,
) -> None:
"""Initialize the sensor."""
super().__init__(coordinator)
self._tracking_number = tracking_number
self._previous_status = coordinator.data.live_packages[tracking_number].status
self._attr_unique_id = UNIQUE_ID_TEMPLATE.format(
coordinator.account_id, tracking_number
)
package = coordinator.data.live_packages[tracking_number]
if not (name := package.friendly_name):
name = tracking_number
self._attr_translation_placeholders = {"name": name}
@property
def available(self) -> bool:
"""Return whether the entity is available."""
return self._tracking_number in self.coordinator.data.live_packages
@property
def native_value(self) -> StateType:
"""Return the state."""
return self.coordinator.data.live_packages[self._tracking_number].status
@property
def extra_state_attributes(self) -> dict[str, Any] | None:
"""Return the state attributes."""
package = self.coordinator.data.live_packages[self._tracking_number]
return {
ATTR_DESTINATION_COUNTRY: package.destination_country,
ATTR_INFO_TEXT: package.info_text,
ATTR_TIMESTAMP: package.timestamp,
ATTR_LOCATION: package.location,
ATTR_ORIGIN_COUNTRY: package.origin_country,
ATTR_PACKAGE_TYPE: package.package_type,
ATTR_TRACKING_INFO_LANGUAGE: package.tracking_info_language,
ATTR_TRACKING_NUMBER: package.tracking_number,
}
def remove_packages(hass: HomeAssistant, account_id: str, packages: set[str]) -> None:
"""Remove entity itself."""
reg = er.async_get(hass)
for package in packages:
entity_id = reg.async_get_entity_id(
"sensor",
"seventeentrack",
UNIQUE_ID_TEMPLATE.format(account_id, package),
)
if entity_id:
reg.async_remove(entity_id)
def notify_delivered(hass: HomeAssistant, friendly_name: str, tracking_number: str):
"""Notify when package is delivered."""
LOGGER.debug("Package delivered: %s", tracking_number)
identification = friendly_name if friendly_name else tracking_number
message = NOTIFICATION_DELIVERED_MESSAGE.format(identification, tracking_number)
title = NOTIFICATION_DELIVERED_TITLE.format(identification)
notification_id = NOTIFICATION_DELIVERED_TITLE.format(tracking_number)
persistent_notification.create(
hass, message, title=title, notification_id=notification_id
)
@callback
def deprecate_sensor_issue(hass: HomeAssistant, entry_id: str) -> None:
"""Ensure an issue is registered."""
ir.async_create_issue(
hass,
DOMAIN,
f"deprecate_sensor_{entry_id}",
breaks_in_ha_version="2025.2.0",
issue_domain=DOMAIN,
is_fixable=True,
is_persistent=True,
translation_key="deprecate_sensor",
severity=ir.IssueSeverity.WARNING,
data={"entry_id": entry_id},
)

View File

@ -37,19 +37,6 @@
}
}
},
"issues": {
"deprecate_sensor": {
"title": "17Track package sensors are being deprecated",
"fix_flow": {
"step": {
"confirm": {
"title": "[%key:component::seventeentrack::issues::deprecate_sensor::title%]",
"description": "17Track package sensors are deprecated and will be removed.\nPlease update your automations and scripts to get data using the `seventeentrack.get_packages` action."
}
}
}
}
},
"entity": {
"sensor": {
"not_found": {

View File

@ -1,86 +0,0 @@
"""Tests for the seventeentrack repair flow."""
from unittest.mock import AsyncMock
from freezegun.api import FrozenDateTimeFactory
from homeassistant.components.repairs import DOMAIN as REPAIRS_DOMAIN
from homeassistant.components.seventeentrack import DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.helpers import issue_registry as ir
from homeassistant.setup import async_setup_component
from . import goto_future, init_integration
from .conftest import DEFAULT_SUMMARY_LENGTH, get_package
from tests.common import MockConfigEntry
from tests.components.repairs import process_repair_fix_flow, start_repair_fix_flow
from tests.typing import ClientSessionGenerator
async def test_repair(
hass: HomeAssistant,
mock_seventeentrack: AsyncMock,
issue_registry: ir.IssueRegistry,
hass_client: ClientSessionGenerator,
mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Ensure everything starts correctly."""
await init_integration(hass, mock_config_entry) # 2
assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH
assert len(issue_registry.issues) == 1
package = get_package()
mock_seventeentrack.return_value.profile.packages.return_value = [package]
await goto_future(hass, freezer)
assert hass.states.get("sensor.17track_package_friendly_name_1")
assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH + 1
assert "deprecated" not in mock_config_entry.data
repair_issue = issue_registry.async_get_issue(
domain=DOMAIN, issue_id=f"deprecate_sensor_{mock_config_entry.entry_id}"
)
assert await async_setup_component(hass, REPAIRS_DOMAIN, {REPAIRS_DOMAIN: {}})
client = await hass_client()
data = await start_repair_fix_flow(client, DOMAIN, repair_issue.issue_id)
flow_id = data["flow_id"]
assert data == {
"type": "form",
"flow_id": flow_id,
"handler": DOMAIN,
"step_id": "confirm",
"data_schema": [],
"errors": None,
"description_placeholders": None,
"last_step": None,
"preview": None,
}
data = await process_repair_fix_flow(client, flow_id)
flow_id = data["flow_id"]
assert data == {
"type": "create_entry",
"handler": DOMAIN,
"flow_id": flow_id,
"description": None,
"description_placeholders": None,
}
assert mock_config_entry.data["deprecated"]
repair_issue = issue_registry.async_get_issue(
domain=DOMAIN, issue_id="deprecate_sensor"
)
assert repair_issue is None
await goto_future(hass, freezer)
assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH

View File

@ -2,7 +2,7 @@
from __future__ import annotations
from unittest.mock import AsyncMock, patch
from unittest.mock import AsyncMock
from freezegun.api import FrozenDateTimeFactory
from pyseventeentrack.errors import SeventeenTrackError
@ -63,87 +63,6 @@ async def test_login_exception(
assert not hass.states.async_entity_ids("sensor")
async def test_add_package(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
mock_seventeentrack: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Ensure package is added correctly when user add a new package."""
package = get_package()
mock_seventeentrack.return_value.profile.packages.return_value = [package]
await init_integration(hass, mock_config_entry)
assert hass.states.get("sensor.17track_package_friendly_name_1")
assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH + 1
package2 = get_package(
tracking_number="789",
friendly_name="friendly name 2",
info_text="info text 2",
location="location 2",
timestamp="2020-08-10 14:25",
)
mock_seventeentrack.return_value.profile.packages.return_value = [package, package2]
await goto_future(hass, freezer)
assert hass.states.get("sensor.17track_package_friendly_name_1") is not None
assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH + 2
async def test_add_package_default_friendly_name(
hass: HomeAssistant,
mock_seventeentrack: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Ensure package is added correctly with default friendly name when user add a new package without his own friendly name."""
package = get_package(friendly_name=None)
mock_seventeentrack.return_value.profile.packages.return_value = [package]
await init_integration(hass, mock_config_entry)
state_456 = hass.states.get("sensor.17track_package_456")
assert state_456 is not None
assert state_456.attributes["friendly_name"] == "17Track Package 456"
assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH + 1
async def test_remove_package(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
mock_seventeentrack: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Ensure entity is not there anymore if package is not there."""
package1 = get_package()
package2 = get_package(
tracking_number="789",
friendly_name="friendly name 2",
info_text="info text 2",
location="location 2",
timestamp="2020-08-10 14:25",
)
mock_seventeentrack.return_value.profile.packages.return_value = [
package1,
package2,
]
await init_integration(hass, mock_config_entry)
assert hass.states.get("sensor.17track_package_friendly_name_1") is not None
assert hass.states.get("sensor.17track_package_friendly_name_2") is not None
assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH + 2
mock_seventeentrack.return_value.profile.packages.return_value = [package2]
await goto_future(hass, freezer)
assert hass.states.get("sensor.17track_package_friendly_name_1") is None
assert hass.states.get("sensor.17track_package_friendly_name_2") is not None
assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH + 1
async def test_package_error(
hass: HomeAssistant,
mock_seventeentrack: AsyncMock,
@ -159,72 +78,6 @@ async def test_package_error(
assert hass.states.get("sensor.17track_package_friendly_name_1") is None
async def test_delivered_not_shown(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
mock_seventeentrack: AsyncMock,
mock_config_entry_with_default_options: MockConfigEntry,
) -> None:
"""Ensure delivered packages are not shown."""
package = get_package(status=40)
mock_seventeentrack.return_value.profile.packages.return_value = [package]
with patch(
"homeassistant.components.seventeentrack.sensor.persistent_notification"
) as persistent_notification_mock:
await init_integration(hass, mock_config_entry_with_default_options)
await goto_future(hass, freezer)
assert hass.states.get("sensor.17track_package_friendly_name_1") is None
persistent_notification_mock.create.assert_called()
async def test_delivered_shown(
hass: HomeAssistant,
mock_seventeentrack: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Ensure delivered packages are show when user choose to show them."""
package = get_package(status=40)
mock_seventeentrack.return_value.profile.packages.return_value = [package]
with patch(
"homeassistant.components.seventeentrack.sensor.persistent_notification"
) as persistent_notification_mock:
await init_integration(hass, mock_config_entry)
assert hass.states.get("sensor.17track_package_friendly_name_1") is not None
assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH + 1
persistent_notification_mock.create.assert_not_called()
async def test_becomes_delivered_not_shown_notification(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
mock_seventeentrack: AsyncMock,
mock_config_entry_with_default_options: MockConfigEntry,
) -> None:
"""Ensure notification is triggered when package becomes delivered."""
package = get_package()
mock_seventeentrack.return_value.profile.packages.return_value = [package]
await init_integration(hass, mock_config_entry_with_default_options)
assert hass.states.get("sensor.17track_package_friendly_name_1") is not None
assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH + 1
package_delivered = get_package(status=40)
mock_seventeentrack.return_value.profile.packages.return_value = [package_delivered]
with patch(
"homeassistant.components.seventeentrack.sensor.persistent_notification"
) as persistent_notification_mock:
await goto_future(hass, freezer)
persistent_notification_mock.create.assert_called()
assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH
async def test_summary_correctly_updated(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
@ -237,7 +90,7 @@ async def test_summary_correctly_updated(
await init_integration(hass, mock_config_entry)
assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH + 1
assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH
state_ready_picked = hass.states.get("sensor.17track_ready_to_be_picked_up")
assert state_ready_picked is not None
@ -278,25 +131,6 @@ async def test_summary_error(
)
async def test_utc_timestamp(
hass: HomeAssistant,
mock_seventeentrack: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Ensure package timestamp is converted correctly from HA-defined time zone to UTC."""
package = get_package(tz="Asia/Jakarta")
mock_seventeentrack.return_value.profile.packages.return_value = [package]
await init_integration(hass, mock_config_entry)
assert hass.states.get("sensor.17track_package_friendly_name_1") is not None
assert len(hass.states.async_entity_ids()) == DEFAULT_SUMMARY_LENGTH + 1
state_456 = hass.states.get("sensor.17track_package_friendly_name_1")
assert state_456 is not None
assert str(state_456.attributes.get("timestamp")) == "2020-08-10 03:32:00+00:00"
async def test_non_valid_platform_config(
hass: HomeAssistant, mock_seventeentrack: AsyncMock
) -> None: