Add service to 17track to get packages (#116067)

* Add service to 17track

* Add service to 17track

change to select selector
add snapshot test

* Add service to 17track

use strings for the selector

* Add service to 17track

fix test
This commit is contained in:
Shai Ungar 2024-04-24 15:29:13 +03:00 committed by GitHub
parent 24a1f0712f
commit 1f4585cc9e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 262 additions and 11 deletions

View File

@ -4,16 +4,81 @@ from py17track import Client as SeventeenTrackClient
from py17track.errors import SeventeenTrackError
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.const import (
ATTR_FRIENDLY_NAME,
ATTR_LOCATION,
CONF_PASSWORD,
CONF_USERNAME,
Platform,
)
from homeassistant.core import (
HomeAssistant,
ServiceCall,
ServiceResponse,
SupportsResponse,
)
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import slugify
from .const import DOMAIN
from .const import (
ATTR_CONFIG_ENTRY_ID,
ATTR_INFO_TEXT,
ATTR_PACKAGE_STATE,
ATTR_STATUS,
ATTR_TIMESTAMP,
ATTR_TRACKING_NUMBER,
DOMAIN,
SERVICE_GET_PACKAGES,
)
from .coordinator import SeventeenTrackCoordinator
PLATFORMS: list[Platform] = [Platform.SENSOR]
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the 17Track component."""
async def get_packages(call: ServiceCall) -> ServiceResponse:
"""Get packages from 17Track."""
config_entry_id = call.data[ATTR_CONFIG_ENTRY_ID]
package_states = call.data.get(ATTR_PACKAGE_STATE, [])
seventeen_coordinator: SeventeenTrackCoordinator = hass.data[DOMAIN][
config_entry_id
]
live_packages = sorted(
await seventeen_coordinator.client.profile.packages(
show_archived=seventeen_coordinator.show_archived
)
)
return {
"packages": [
{
ATTR_TRACKING_NUMBER: package.tracking_number,
ATTR_LOCATION: package.location,
ATTR_STATUS: package.status,
ATTR_TIMESTAMP: package.timestamp,
ATTR_INFO_TEXT: package.info_text,
ATTR_FRIENDLY_NAME: package.friendly_name,
}
for package in live_packages
if slugify(package.status) in package_states or package_states == []
]
}
hass.services.async_register(
DOMAIN,
SERVICE_GET_PACKAGES,
get_packages,
supports_response=SupportsResponse.ONLY,
)
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up 17Track from a config entry."""
@ -26,10 +91,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
except SeventeenTrackError as err:
raise ConfigEntryNotReady from err
coordinator = SeventeenTrackCoordinator(hass, client)
seventeen_coordinator = SeventeenTrackCoordinator(hass, client)
await coordinator.async_config_entry_first_refresh()
await seventeen_coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = seventeen_coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True

View File

@ -40,3 +40,8 @@ NOTIFICATION_DELIVERED_MESSAGE = (
)
VALUE_DELIVERED = "Delivered"
SERVICE_GET_PACKAGES = "get_packages"
ATTR_PACKAGE_STATE = "package_state"
ATTR_CONFIG_ENTRY_ID = "config_entry_id"

View File

@ -45,19 +45,19 @@ class SeventeenTrackCoordinator(DataUpdateCoordinator[SeventeenTrackData]):
self.show_delivered = self.config_entry.options[CONF_SHOW_DELIVERED]
self.account_id = client.profile.account_id
self._show_archived = self.config_entry.options[CONF_SHOW_ARCHIVED]
self._client = client
self.show_archived = self.config_entry.options[CONF_SHOW_ARCHIVED]
self.client = client
async def _async_update_data(self) -> SeventeenTrackData:
"""Fetch data from 17Track API."""
try:
summary = await self._client.profile.summary(
show_archived=self._show_archived
summary = await self.client.profile.summary(
show_archived=self.show_archived
)
live_packages = set(
await self._client.profile.packages(show_archived=self._show_archived)
await self.client.profile.packages(show_archived=self.show_archived)
)
except SeventeenTrackError as err:

View File

@ -26,5 +26,8 @@
"default": "mdi:package"
}
}
},
"services": {
"get_packages": "mdi:package"
}
}

View File

@ -0,0 +1,20 @@
get_packages:
fields:
package_state:
selector:
select:
multiple: true
options:
- "not_found"
- "in_transit"
- "expired"
- "ready_to_be_picked_up"
- "undelivered"
- "delivered"
- "returned"
translation_key: package_state
config_entry_id:
required: true
selector:
config_entry:
integration: seventeentrack

View File

@ -66,5 +66,34 @@
"name": "Package {name}"
}
}
},
"services": {
"get_packages": {
"name": "Get packages",
"description": "Get packages from 17Track",
"fields": {
"package_state": {
"name": "Package states",
"description": "Only return packages with the specified states. Returns all packages if not specified."
},
"config_entry_id": {
"name": "17Track service",
"description": "The packages will be retrieved for the selected service."
}
}
}
},
"selector": {
"package_state": {
"options": {
"not_found": "[%key:component::seventeentrack::entity::sensor::not_found::name%]",
"in_transit": "[%key:component::seventeentrack::entity::sensor::in_transit::name%]",
"expired": "[%key:component::seventeentrack::entity::sensor::expired::name%]",
"ready_to_be_picked_up": "[%key:component::seventeentrack::entity::sensor::ready_to_be_picked_up::name%]",
"undelivered": "[%key:component::seventeentrack::entity::sensor::undelivered::name%]",
"delivered": "[%key:component::seventeentrack::entity::sensor::delivered::name%]",
"returned": "[%key:component::seventeentrack::entity::sensor::returned::name%]"
}
}
}
}

View File

@ -0,0 +1,53 @@
# serializer version: 1
# name: test_get_all_packages
dict({
'packages': list([
dict({
'friendly_name': 'friendly name 3',
'info_text': 'info text 1',
'location': 'location 1',
'status': 'Expired',
'timestamp': datetime.datetime(2020, 8, 10, 10, 32, tzinfo=<UTC>),
'tracking_number': '123',
}),
dict({
'friendly_name': 'friendly name 1',
'info_text': 'info text 1',
'location': 'location 1',
'status': 'In Transit',
'timestamp': datetime.datetime(2020, 8, 10, 10, 32, tzinfo=<UTC>),
'tracking_number': '456',
}),
dict({
'friendly_name': 'friendly name 2',
'info_text': 'info text 1',
'location': 'location 1',
'status': 'Delivered',
'timestamp': datetime.datetime(2020, 8, 10, 10, 32, tzinfo=<UTC>),
'tracking_number': '789',
}),
]),
})
# ---
# name: test_get_packages_from_list
dict({
'packages': list([
dict({
'friendly_name': 'friendly name 1',
'info_text': 'info text 1',
'location': 'location 1',
'status': 'In Transit',
'timestamp': datetime.datetime(2020, 8, 10, 10, 32, tzinfo=<UTC>),
'tracking_number': '456',
}),
dict({
'friendly_name': 'friendly name 2',
'info_text': 'info text 1',
'location': 'location 1',
'status': 'Delivered',
'timestamp': datetime.datetime(2020, 8, 10, 10, 32, tzinfo=<UTC>),
'tracking_number': '789',
}),
]),
})
# ---

View File

@ -0,0 +1,76 @@
"""Tests for the seventeentrack service."""
from unittest.mock import AsyncMock
from syrupy import SnapshotAssertion
from homeassistant.components.seventeentrack import DOMAIN, SERVICE_GET_PACKAGES
from homeassistant.core import HomeAssistant, SupportsResponse
from tests.common import MockConfigEntry
from tests.components.seventeentrack import init_integration
from tests.components.seventeentrack.conftest import get_package
async def test_get_packages_from_list(
hass: HomeAssistant,
mock_seventeentrack: AsyncMock,
mock_config_entry: MockConfigEntry,
snapshot: SnapshotAssertion,
) -> None:
"""Ensure service returns only the packages in the list."""
await _mock_packages(mock_seventeentrack)
await init_integration(hass, mock_config_entry)
service_response = await hass.services.async_call(
DOMAIN,
SERVICE_GET_PACKAGES,
{
"config_entry_id": mock_config_entry.entry_id,
"package_state": ["in_transit", "delivered"],
},
blocking=True,
return_response=SupportsResponse.ONLY,
)
assert service_response == snapshot
async def test_get_all_packages(
hass: HomeAssistant,
mock_seventeentrack: AsyncMock,
mock_config_entry: MockConfigEntry,
snapshot: SnapshotAssertion,
) -> None:
"""Ensure service returns all packages when non provided."""
await _mock_packages(mock_seventeentrack)
await init_integration(hass, mock_config_entry)
service_response = await hass.services.async_call(
DOMAIN,
SERVICE_GET_PACKAGES,
{
"config_entry_id": mock_config_entry.entry_id,
},
blocking=True,
return_response=SupportsResponse.ONLY,
)
assert service_response == snapshot
async def _mock_packages(mock_seventeentrack):
package1 = get_package(status=10)
package2 = get_package(
tracking_number="789",
friendly_name="friendly name 2",
status=40,
)
package3 = get_package(
tracking_number="123",
friendly_name="friendly name 3",
status=20,
)
mock_seventeentrack.return_value.profile.packages.return_value = [
package1,
package2,
package3,
]