corneyl a848dc1155
Add service for adding products to a Picnic order (#67877)
* Add Picnic services for searching products and adding products to the cart

* Improve the Picnic services implementation and add unit tests

* Fix pre-commit check issues

* Fix comments and example product name

* Remove search service, update add_product service schema

* Fix pylint suggestion

* Add more tests and removed unused code

* Remove code needed for the removed service, clean tests from obvious comments and add type hints

* Remove unused import

* Remove unnecessary comments and simplify getting the config entry id

Co-authored-by: Allen Porter <allen.porter@gmail.com>

* Don't use hass.data in tests, make device id mandatory for service

* Rewrite all service tests so using lru.cache is not needed

* Add test for uncovered line in _product_search()

* Require a config entry id as service parameter instead of device id

* Use explicit check in get_api_client() and raise HomeAssistantError

* Fix HomeAssistantError import, fix services tests

* Change HomeAssistantError to ValueError when config entry is not found

Co-authored-by: Allen Porter <allen.porter@gmail.com>
2022-11-12 20:15:45 -08:00

91 lines
2.8 KiB
Python

"""Services for the Picnic integration."""
from __future__ import annotations
from python_picnic_api import PicnicAPI
import voluptuous as vol
from homeassistant.core import HomeAssistant, ServiceCall
import homeassistant.helpers.config_validation as cv
from .const import (
ATTR_AMOUNT,
ATTR_CONFIG_ENTRY_ID,
ATTR_PRODUCT_ID,
ATTR_PRODUCT_IDENTIFIERS,
ATTR_PRODUCT_NAME,
CONF_API,
DOMAIN,
SERVICE_ADD_PRODUCT_TO_CART,
)
class PicnicServiceException(Exception):
"""Exception for Picnic services."""
async def async_register_services(hass: HomeAssistant) -> None:
"""Register services for the Picnic integration, if not registered yet."""
if hass.services.has_service(DOMAIN, SERVICE_ADD_PRODUCT_TO_CART):
return
async def async_add_product_service(call: ServiceCall):
api_client = await get_api_client(hass, call.data[ATTR_CONFIG_ENTRY_ID])
await handle_add_product(hass, api_client, call)
hass.services.async_register(
DOMAIN,
SERVICE_ADD_PRODUCT_TO_CART,
async_add_product_service,
schema=vol.Schema(
{
vol.Required(ATTR_CONFIG_ENTRY_ID): cv.string,
vol.Exclusive(
ATTR_PRODUCT_ID, ATTR_PRODUCT_IDENTIFIERS
): cv.positive_int,
vol.Exclusive(ATTR_PRODUCT_NAME, ATTR_PRODUCT_IDENTIFIERS): cv.string,
vol.Optional(ATTR_AMOUNT): vol.All(vol.Coerce(int), vol.Range(min=1)),
}
),
)
async def get_api_client(hass: HomeAssistant, config_entry_id: str) -> PicnicAPI:
"""Get the right Picnic API client based on the device id, else get the default one."""
if config_entry_id not in hass.data[DOMAIN]:
raise ValueError(f"Config entry with id {config_entry_id} not found!")
return hass.data[DOMAIN][config_entry_id][CONF_API]
async def handle_add_product(
hass: HomeAssistant, api_client: PicnicAPI, call: ServiceCall
) -> None:
"""Handle the call for the add_product service."""
product_id = call.data.get("product_id")
if not product_id:
product_id = await hass.async_add_executor_job(
_product_search, api_client, call.data.get("product_name")
)
if not product_id:
raise PicnicServiceException("No product found or no product ID given!")
await hass.async_add_executor_job(
api_client.add_product, str(product_id), call.data.get("amount", 1)
)
def _product_search(api_client: PicnicAPI, product_name: str) -> None | str:
"""Query the api client for the product name."""
search_result = api_client.search(product_name)
if not search_result or "items" not in search_result[0]:
return None
# Return the first valid result
for item in search_result[0]["items"]:
if "name" in item:
return str(item["id"])
return None