diff --git a/homeassistant/components/seventeentrack/__init__.py b/homeassistant/components/seventeentrack/__init__.py index 56d87b1935d..695ca179966 100644 --- a/homeassistant/components/seventeentrack/__init__.py +++ b/homeassistant/components/seventeentrack/__init__.py @@ -1,136 +1,30 @@ """The seventeentrack component.""" -from typing import Final - from pyseventeentrack import Client as SeventeenTrackClient from pyseventeentrack.errors import SeventeenTrackError -from pyseventeentrack.package import PACKAGE_STATUS_MAP -import voluptuous as vol -from homeassistant.config_entries import ConfigEntry, ConfigEntryState -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, ServiceValidationError -from homeassistant.helpers import config_validation as cv, selector +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform +from homeassistant.core import HomeAssistant +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 ( - ATTR_CONFIG_ENTRY_ID, - ATTR_DESTINATION_COUNTRY, - ATTR_INFO_TEXT, - ATTR_ORIGIN_COUNTRY, - ATTR_PACKAGE_STATE, - ATTR_PACKAGE_TYPE, - ATTR_STATUS, - ATTR_TIMESTAMP, - ATTR_TRACKING_INFO_LANGUAGE, - ATTR_TRACKING_NUMBER, - DOMAIN, - SERVICE_GET_PACKAGES, -) +from .const import DOMAIN from .coordinator import SeventeenTrackCoordinator +from .services import setup_services PLATFORMS: list[Platform] = [Platform.SENSOR] CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN) -SERVICE_SCHEMA: Final = vol.Schema( - { - vol.Required(ATTR_CONFIG_ENTRY_ID): selector.ConfigEntrySelector( - { - "integration": DOMAIN, - } - ), - vol.Optional(ATTR_PACKAGE_STATE): selector.SelectSelector( - selector.SelectSelectorConfig( - multiple=True, - options=[ - value.lower().replace(" ", "_") - for value in PACKAGE_STATUS_MAP.values() - ], - mode=selector.SelectSelectorMode.DROPDOWN, - translation_key=ATTR_PACKAGE_STATE, - ) - ), - } -) - 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, []) + setup_services(hass) - entry: ConfigEntry | None = hass.config_entries.async_get_entry(config_entry_id) - - if not entry: - raise ServiceValidationError( - translation_domain=DOMAIN, - translation_key="invalid_config_entry", - translation_placeholders={ - "config_entry_id": config_entry_id, - }, - ) - if entry.state != ConfigEntryState.LOADED: - raise ServiceValidationError( - translation_domain=DOMAIN, - translation_key="unloaded_config_entry", - translation_placeholders={ - "config_entry_id": entry.title, - }, - ) - - 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_DESTINATION_COUNTRY: package.destination_country, - 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, - 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, - schema=SERVICE_SCHEMA, - supports_response=SupportsResponse.ONLY, - ) return True diff --git a/homeassistant/components/seventeentrack/const.py b/homeassistant/components/seventeentrack/const.py index 584eca507e9..6b888590600 100644 --- a/homeassistant/components/seventeentrack/const.py +++ b/homeassistant/components/seventeentrack/const.py @@ -42,8 +42,11 @@ NOTIFICATION_DELIVERED_MESSAGE = ( VALUE_DELIVERED = "Delivered" SERVICE_GET_PACKAGES = "get_packages" +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" diff --git a/homeassistant/components/seventeentrack/icons.json b/homeassistant/components/seventeentrack/icons.json index 94ca8cd535a..a5cac0a9f84 100644 --- a/homeassistant/components/seventeentrack/icons.json +++ b/homeassistant/components/seventeentrack/icons.json @@ -30,6 +30,9 @@ "services": { "get_packages": { "service": "mdi:package" + }, + "archive_package": { + "service": "mdi:archive" } } } diff --git a/homeassistant/components/seventeentrack/services.py b/homeassistant/components/seventeentrack/services.py new file mode 100644 index 00000000000..9a7a4d2d4b6 --- /dev/null +++ b/homeassistant/components/seventeentrack/services.py @@ -0,0 +1,145 @@ +"""Services for the seventeentrack integration.""" + +from typing import Final + +from pyseventeentrack.package import PACKAGE_STATUS_MAP +import voluptuous as vol + +from homeassistant.config_entries import ConfigEntry, ConfigEntryState +from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_LOCATION +from homeassistant.core import ( + HomeAssistant, + ServiceCall, + ServiceResponse, + SupportsResponse, +) +from homeassistant.exceptions import ServiceValidationError +from homeassistant.helpers import config_validation as cv, selector +from homeassistant.util import slugify + +from . import SeventeenTrackCoordinator +from .const import ( + ATTR_CONFIG_ENTRY_ID, + ATTR_DESTINATION_COUNTRY, + ATTR_INFO_TEXT, + ATTR_ORIGIN_COUNTRY, + ATTR_PACKAGE_STATE, + ATTR_PACKAGE_TRACKING_NUMBER, + ATTR_PACKAGE_TYPE, + ATTR_STATUS, + ATTR_TIMESTAMP, + ATTR_TRACKING_INFO_LANGUAGE, + ATTR_TRACKING_NUMBER, + DOMAIN, + SERVICE_ARCHIVE_PACKAGE, + SERVICE_GET_PACKAGES, +) + +SERVICE_ADD_PACKAGES_SCHEMA: Final = vol.Schema( + { + vol.Required(ATTR_CONFIG_ENTRY_ID): cv.string, + vol.Optional(ATTR_PACKAGE_STATE): selector.SelectSelector( + selector.SelectSelectorConfig( + multiple=True, + options=[ + value.lower().replace(" ", "_") + for value in PACKAGE_STATUS_MAP.values() + ], + mode=selector.SelectSelectorMode.DROPDOWN, + translation_key=ATTR_PACKAGE_STATE, + ) + ), + } +) + +SERVICE_ARCHIVE_PACKAGE_SCHEMA: Final = vol.Schema( + { + vol.Required(ATTR_CONFIG_ENTRY_ID): cv.string, + vol.Required(ATTR_PACKAGE_TRACKING_NUMBER): cv.string, + } +) + + +def setup_services(hass: HomeAssistant) -> None: + """Set up the services for the seventeentrack integration.""" + + 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, []) + + await _validate_service(config_entry_id) + + 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_DESTINATION_COUNTRY: package.destination_country, + 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, + 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 == [] + ] + } + + async def archive_package(call: ServiceCall) -> None: + config_entry_id = call.data[ATTR_CONFIG_ENTRY_ID] + tracking_number = call.data[ATTR_PACKAGE_TRACKING_NUMBER] + + await _validate_service(config_entry_id) + + seventeen_coordinator: SeventeenTrackCoordinator = hass.data[DOMAIN][ + config_entry_id + ] + + await seventeen_coordinator.client.profile.archive_package(tracking_number) + + async def _validate_service(config_entry_id): + entry: ConfigEntry | None = hass.config_entries.async_get_entry(config_entry_id) + if not entry: + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="invalid_config_entry", + translation_placeholders={ + "config_entry_id": config_entry_id, + }, + ) + if entry.state != ConfigEntryState.LOADED: + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="unloaded_config_entry", + translation_placeholders={ + "config_entry_id": entry.title, + }, + ) + + hass.services.async_register( + DOMAIN, + SERVICE_GET_PACKAGES, + get_packages, + schema=SERVICE_ADD_PACKAGES_SCHEMA, + supports_response=SupportsResponse.ONLY, + ) + + hass.services.async_register( + DOMAIN, + SERVICE_ARCHIVE_PACKAGE, + archive_package, + schema=SERVICE_ARCHIVE_PACKAGE_SCHEMA, + ) diff --git a/homeassistant/components/seventeentrack/services.yaml b/homeassistant/components/seventeentrack/services.yaml index 41cb66ada5f..d4592dc8aab 100644 --- a/homeassistant/components/seventeentrack/services.yaml +++ b/homeassistant/components/seventeentrack/services.yaml @@ -18,3 +18,14 @@ get_packages: selector: config_entry: integration: seventeentrack +archive_package: + fields: + package_tracking_number: + required: true + selector: + text: + 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 0fbac13736e..fda5575ff95 100644 --- a/homeassistant/components/seventeentrack/strings.json +++ b/homeassistant/components/seventeentrack/strings.json @@ -100,6 +100,20 @@ "description": "The packages will be retrieved for the selected service." } } + }, + "archive_package": { + "name": "Archive package", + "description": "Archive a package", + "fields": { + "package_tracking_number": { + "name": "Package tracking number", + "description": "The package will be archived for the specified tracking number." + }, + "config_entry_id": { + "name": "[%key:component::seventeentrack::services::get_packages::fields::config_entry_id::name%]", + "description": "The package will be archived for the selected service." + } + } } }, "selector": { diff --git a/tests/components/seventeentrack/conftest.py b/tests/components/seventeentrack/conftest.py index e2493319b69..0d02a7ab5f1 100644 --- a/tests/components/seventeentrack/conftest.py +++ b/tests/components/seventeentrack/conftest.py @@ -40,6 +40,11 @@ NEW_SUMMARY_DATA = { "Returned": 1, } +ARCHIVE_PACKAGE_NUMBER = "123" +CONFIG_ENTRY_ID_KEY = "config_entry_id" +PACKAGE_TRACKING_NUMBER_KEY = "package_tracking_number" +PACKAGE_STATE_KEY = "package_state" + VALID_CONFIG = { CONF_USERNAME: "test", CONF_PASSWORD: "test", diff --git a/tests/components/seventeentrack/test_services.py b/tests/components/seventeentrack/test_services.py index 4347189a5c0..54c9349c121 100644 --- a/tests/components/seventeentrack/test_services.py +++ b/tests/components/seventeentrack/test_services.py @@ -5,14 +5,24 @@ from unittest.mock import AsyncMock import pytest from syrupy import SnapshotAssertion -from homeassistant.components.seventeentrack import DOMAIN, SERVICE_GET_PACKAGES +from homeassistant.components.seventeentrack import DOMAIN +from homeassistant.components.seventeentrack.const import ( + SERVICE_ARCHIVE_PACKAGE, + SERVICE_GET_PACKAGES, +) from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import device_registry as dr from . import init_integration -from .conftest import get_package +from .conftest import ( + ARCHIVE_PACKAGE_NUMBER, + CONFIG_ENTRY_ID_KEY, + PACKAGE_STATE_KEY, + PACKAGE_TRACKING_NUMBER_KEY, + get_package, +) from tests.common import MockConfigEntry @@ -30,8 +40,8 @@ async def test_get_packages_from_list( DOMAIN, SERVICE_GET_PACKAGES, { - "config_entry_id": mock_config_entry.entry_id, - "package_state": ["in_transit", "delivered"], + CONFIG_ENTRY_ID_KEY: mock_config_entry.entry_id, + PACKAGE_STATE_KEY: ["in_transit", "delivered"], }, blocking=True, return_response=True, @@ -53,7 +63,7 @@ async def test_get_all_packages( DOMAIN, SERVICE_GET_PACKAGES, { - "config_entry_id": mock_config_entry.entry_id, + CONFIG_ENTRY_ID_KEY: mock_config_entry.entry_id, }, blocking=True, return_response=True, @@ -76,7 +86,7 @@ async def test_service_called_with_unloaded_entry( DOMAIN, SERVICE_GET_PACKAGES, { - "config_entry_id": mock_config_entry.entry_id, + CONFIG_ENTRY_ID_KEY: mock_config_entry.entry_id, }, blocking=True, return_response=True, @@ -110,13 +120,36 @@ async def test_service_called_with_non_17track_device( DOMAIN, SERVICE_GET_PACKAGES, { - "config_entry_id": device_entry.id, + CONFIG_ENTRY_ID_KEY: device_entry.id, }, blocking=True, return_response=True, ) +async def test_archive_package( + hass: HomeAssistant, + mock_seventeentrack: AsyncMock, + mock_config_entry: MockConfigEntry, + snapshot: SnapshotAssertion, +) -> None: + """Ensure service archives package.""" + await _mock_packages(mock_seventeentrack) + await init_integration(hass, mock_config_entry) + await hass.services.async_call( + DOMAIN, + SERVICE_ARCHIVE_PACKAGE, + { + CONFIG_ENTRY_ID_KEY: mock_config_entry.entry_id, + PACKAGE_TRACKING_NUMBER_KEY: ARCHIVE_PACKAGE_NUMBER, + }, + blocking=True, + ) + mock_seventeentrack.return_value.profile.archive_package.assert_called_once_with( + ARCHIVE_PACKAGE_NUMBER + ) + + async def _mock_packages(mock_seventeentrack): package1 = get_package(status=10) package2 = get_package(