diff --git a/homeassistant/components/seventeentrack/__init__.py b/homeassistant/components/seventeentrack/__init__.py index 1f9879cdcbc..40c9c8d58d1 100644 --- a/homeassistant/components/seventeentrack/__init__.py +++ b/homeassistant/components/seventeentrack/__init__.py @@ -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 diff --git a/homeassistant/components/seventeentrack/const.py b/homeassistant/components/seventeentrack/const.py index fc7ca7b2e7f..39932d31935 100644 --- a/homeassistant/components/seventeentrack/const.py +++ b/homeassistant/components/seventeentrack/const.py @@ -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" diff --git a/homeassistant/components/seventeentrack/coordinator.py b/homeassistant/components/seventeentrack/coordinator.py index 84bdf1e1359..4da4969ed92 100644 --- a/homeassistant/components/seventeentrack/coordinator.py +++ b/homeassistant/components/seventeentrack/coordinator.py @@ -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: diff --git a/homeassistant/components/seventeentrack/icons.json b/homeassistant/components/seventeentrack/icons.json index 05323a69743..78ca65edc4d 100644 --- a/homeassistant/components/seventeentrack/icons.json +++ b/homeassistant/components/seventeentrack/icons.json @@ -26,5 +26,8 @@ "default": "mdi:package" } } + }, + "services": { + "get_packages": "mdi:package" } } diff --git a/homeassistant/components/seventeentrack/services.yaml b/homeassistant/components/seventeentrack/services.yaml new file mode 100644 index 00000000000..41cb66ada5f --- /dev/null +++ b/homeassistant/components/seventeentrack/services.yaml @@ -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 diff --git a/homeassistant/components/seventeentrack/strings.json b/homeassistant/components/seventeentrack/strings.json index 8d91f926d50..626af29e856 100644 --- a/homeassistant/components/seventeentrack/strings.json +++ b/homeassistant/components/seventeentrack/strings.json @@ -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%]" + } + } } } diff --git a/tests/components/seventeentrack/snapshots/test_services.ambr b/tests/components/seventeentrack/snapshots/test_services.ambr new file mode 100644 index 00000000000..185a1d44fe0 --- /dev/null +++ b/tests/components/seventeentrack/snapshots/test_services.ambr @@ -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=), + '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=), + '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=), + '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=), + '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=), + 'tracking_number': '789', + }), + ]), + }) +# --- diff --git a/tests/components/seventeentrack/test_services.py b/tests/components/seventeentrack/test_services.py new file mode 100644 index 00000000000..cbd7132bf67 --- /dev/null +++ b/tests/components/seventeentrack/test_services.py @@ -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, + ]