From 668bd9d0bc10276fb522054602b2881733558ebd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Sat, 14 Nov 2020 15:27:54 +0100 Subject: [PATCH] Remove sampleclient (#45) --- .vscode/settings.json | 5 ++ README.md | 10 +-- custom_components/blueprint/__init__.py | 28 ++++---- custom_components/blueprint/api.py | 75 ++++++++++++++++++++ custom_components/blueprint/binary_sensor.py | 6 +- custom_components/blueprint/config_flow.py | 12 ++-- custom_components/blueprint/const.py | 2 +- custom_components/blueprint/entity.py | 7 +- custom_components/blueprint/manifest.json | 5 +- custom_components/blueprint/sensor.py | 6 +- custom_components/blueprint/switch.py | 14 ++-- hacs.json | 4 +- info.md | 2 +- 13 files changed, 129 insertions(+), 47 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 custom_components/blueprint/api.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3c2faa3 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "python.linting.pylintEnabled": true, + "python.linting.enabled": true, + "python.pythonPath": "/usr/local/bin/python" +} \ No newline at end of file diff --git a/README.md b/README.md index 65b1d65..718f784 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,6 @@ The component and platforms in this repository are not meant to be used by a user, but as a "blueprint" that custom component developers can build upon, to make more awesome stuff. -This blueprint uses ['sampleclient'](https://github.com/ludeeus/sampleclient) to simulate what you actually might use in your integration. - HAVE FUN! 😎 ## Why? @@ -29,6 +27,7 @@ File | Purpose `.vscode/tasks.json` | Tasks for the devcontainer. `custom_components/blueprint/translations/*` | [Translation files.](https://developers.home-assistant.io/docs/internationalization/custom_integration) `custom_components/blueprint/__init__.py` | The component file for the integration. +`custom_components/blueprint/api.py` | This is a sample API client. `custom_components/blueprint/binary_sensor.py` | Binary sensor platform for the integration. `custom_components/blueprint/config_flow.py` | Config flow file, this adds the UI configuration possibilities. `custom_components/blueprint/const.py` | A file to hold shared variables/constants for the entire integration. @@ -104,10 +103,11 @@ Platform | Description Using your HA configuration directory (folder) as a starting point you should now also have this: ```text -custom_components/blueprint/.translations/en.json -custom_components/blueprint/.translations/nb.json -custom_components/blueprint/.translations/sensor.nb.json +custom_components/blueprint/translations/en.json +custom_components/blueprint/translations/nb.json +custom_components/blueprint/translations/sensor.nb.json custom_components/blueprint/__init__.py +custom_components/blueprint/api.py custom_components/blueprint/binary_sensor.py custom_components/blueprint/config_flow.py custom_components/blueprint/const.py diff --git a/custom_components/blueprint/__init__.py b/custom_components/blueprint/__init__.py index ae22b7f..8741507 100644 --- a/custom_components/blueprint/__init__.py +++ b/custom_components/blueprint/__init__.py @@ -11,10 +11,12 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import Config, HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from sampleclient.client import Client -from custom_components.blueprint.const import ( +from .api import BlueprintApiClient + +from .const import ( CONF_PASSWORD, CONF_USERNAME, DOMAIN, @@ -24,7 +26,7 @@ SCAN_INTERVAL = timedelta(seconds=30) -_LOGGER = logging.getLogger(__name__) +_LOGGER: logging.Logger = logging.getLogger(__package__) async def async_setup(hass: HomeAssistant, config: Config): @@ -41,9 +43,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): username = entry.data.get(CONF_USERNAME) password = entry.data.get(CONF_PASSWORD) - coordinator = BlueprintDataUpdateCoordinator( - hass, username=username, password=password - ) + session = async_get_clientsession(hass) + client = BlueprintApiClient(username, password, session) + + coordinator = BlueprintDataUpdateCoordinator(hass, client=client) await coordinator.async_refresh() if not coordinator.last_update_success: @@ -65,9 +68,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): class BlueprintDataUpdateCoordinator(DataUpdateCoordinator): """Class to manage fetching data from the API.""" - def __init__(self, hass, username, password): + def __init__(self, hass: HomeAssistant, client: BlueprintApiClient) -> None: """Initialize.""" - self.api = Client(username, password) + self.api: BlueprintApiClient = client self.platforms = [] super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL) @@ -75,13 +78,12 @@ def __init__(self, hass, username, password): async def _async_update_data(self): """Update data via library.""" try: - data = await self.api.async_get_data() - return data.get("data", {}) + return await self.api.async_get_data() except Exception as exception: - raise UpdateFailed(exception) + raise UpdateFailed() from exception -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Handle removal of an entry.""" coordinator = hass.data[DOMAIN][entry.entry_id] unloaded = all( @@ -99,7 +101,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): return unloaded -async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry): +async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: """Reload config entry.""" await async_unload_entry(hass, entry) await async_setup_entry(hass, entry) diff --git a/custom_components/blueprint/api.py b/custom_components/blueprint/api.py new file mode 100644 index 0000000..e45f2d4 --- /dev/null +++ b/custom_components/blueprint/api.py @@ -0,0 +1,75 @@ +"""Sample API Client.""" +import logging +import asyncio +import socket +from typing import Optional +import aiohttp +import async_timeout + +TIMEOUT = 10 + + +_LOGGER: logging.Logger = logging.getLogger(__package__) + +HEADERS = {"Content-type": "application/json; charset=UTF-8"} + + +class BlueprintApiClient: + def __init__( + self, username: str, password: str, session: aiohttp.ClientSession + ) -> None: + """Sample API Client.""" + self._username = username + self._passeword = password + self._session = session + + async def async_get_data(self) -> dict: + """Get data from the API.""" + url = "https://jsonplaceholder.typicode.com/posts/1" + return await self.api_wrapper("get", url) + + async def async_set_title(self, value: str) -> None: + """Get data from the API.""" + url = "https://jsonplaceholder.typicode.com/posts/1" + await self.api_wrapper("patch", url, data={"title": value}, headers=HEADERS) + + async def api_wrapper( + self, method: str, url: str, data: dict = {}, headers: dict = {} + ) -> dict: + """Get information from the API.""" + try: + async with async_timeout.timeout(TIMEOUT, loop=asyncio.get_event_loop()): + if method == "get": + response = await self._session.get(url, headers=headers) + return await response.json() + + elif method == "put": + await self._session.put(url, headers=headers, json=data) + + elif method == "patch": + await self._session.patch(url, headers=headers, json=data) + + elif method == "post": + await self._session.post(url, headers=headers, json=data) + + except asyncio.TimeoutError as exception: + _LOGGER.error( + "Timeout error fetching information from %s - %s", + url, + exception, + ) + + except (KeyError, TypeError) as exception: + _LOGGER.error( + "Error parsing information from %s - %s", + url, + exception, + ) + except (aiohttp.ClientError, socket.gaierror) as exception: + _LOGGER.error( + "Error fetching information from %s - %s", + url, + exception, + ) + except Exception as exception: # pylint: disable=broad-except + _LOGGER.error("Something really wrong happend! - %s", exception) diff --git a/custom_components/blueprint/binary_sensor.py b/custom_components/blueprint/binary_sensor.py index ebb8411..e108ce0 100644 --- a/custom_components/blueprint/binary_sensor.py +++ b/custom_components/blueprint/binary_sensor.py @@ -1,13 +1,13 @@ """Binary sensor platform for blueprint.""" from homeassistant.components.binary_sensor import BinarySensorDevice -from custom_components.blueprint.const import ( +from .const import ( BINARY_SENSOR, BINARY_SENSOR_DEVICE_CLASS, DEFAULT_NAME, DOMAIN, ) -from custom_components.blueprint.entity import BlueprintEntity +from .entity import BlueprintEntity async def async_setup_entry(hass, entry, async_add_devices): @@ -32,4 +32,4 @@ def device_class(self): @property def is_on(self): """Return true if the binary_sensor is on.""" - return self.coordinator.data.get("bool_on", False) + return self.coordinator.data.get("title", "") == "foo" diff --git a/custom_components/blueprint/config_flow.py b/custom_components/blueprint/config_flow.py index 731d17c..f52dd78 100644 --- a/custom_components/blueprint/config_flow.py +++ b/custom_components/blueprint/config_flow.py @@ -1,10 +1,11 @@ """Adds config flow for Blueprint.""" from homeassistant import config_entries from homeassistant.core import callback -from sampleclient.client import Client +from homeassistant.helpers.aiohttp_client import async_create_clientsession import voluptuous as vol -from custom_components.blueprint.const import ( # pylint: disable=unused-import +from .api import BlueprintApiClient +from .const import ( CONF_PASSWORD, CONF_USERNAME, DOMAIN, @@ -22,9 +23,7 @@ def __init__(self): """Initialize.""" self._errors = {} - async def async_step_user( - self, user_input=None # pylint: disable=bad-continuation - ): + async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" self._errors = {} @@ -65,7 +64,8 @@ async def _show_config_form(self, user_input): # pylint: disable=unused-argumen async def _test_credentials(self, username, password): """Return true if credentials is valid.""" try: - client = Client(username, password) + session = async_create_clientsession(self.hass) + client = BlueprintApiClient(username, password, session) await client.async_get_data() return True except Exception: # pylint: disable=broad-except diff --git a/custom_components/blueprint/const.py b/custom_components/blueprint/const.py index 86530cb..40e6d03 100644 --- a/custom_components/blueprint/const.py +++ b/custom_components/blueprint/const.py @@ -4,7 +4,7 @@ DOMAIN = "blueprint" DOMAIN_DATA = f"{DOMAIN}_data" VERSION = "0.0.1" - +ATTRIBUTION = "Data provided by http://jsonplaceholder.typicode.com/" ISSUE_URL = "https://github.com/custom-components/blueprint/issues" # Icons diff --git a/custom_components/blueprint/entity.py b/custom_components/blueprint/entity.py index 684f2d4..2e63801 100644 --- a/custom_components/blueprint/entity.py +++ b/custom_components/blueprint/entity.py @@ -1,7 +1,7 @@ """BlueprintEntity class""" from homeassistant.helpers.update_coordinator import CoordinatorEntity -from custom_components.blueprint.const import DOMAIN, NAME, VERSION +from .const import DOMAIN, NAME, VERSION, ATTRIBUTION class BlueprintEntity(CoordinatorEntity): @@ -27,6 +27,7 @@ def device_info(self): def device_state_attributes(self): """Return the state attributes.""" return { - "time": str(self.coordinator.data.get("time")), - "static": self.coordinator.data.get("static"), + "attribution": ATTRIBUTION, + "id": str(self.coordinator.data.get("id")), + "integration": DOMAIN, } diff --git a/custom_components/blueprint/manifest.json b/custom_components/blueprint/manifest.json index 5f5af27..8437a1c 100644 --- a/custom_components/blueprint/manifest.json +++ b/custom_components/blueprint/manifest.json @@ -2,12 +2,11 @@ "domain": "blueprint", "name": "Blueprint", "documentation": "https://github.com/custom-components/blueprint", + "issue_tracker": "https://github.com/custom-components/blueprint/issues", "dependencies": [], "config_flow": true, "codeowners": [ "@ludeeus" ], - "requirements": [ - "sampleclient" - ] + "requirements": [] } diff --git a/custom_components/blueprint/sensor.py b/custom_components/blueprint/sensor.py index db1891c..71a40a9 100644 --- a/custom_components/blueprint/sensor.py +++ b/custom_components/blueprint/sensor.py @@ -1,6 +1,6 @@ """Sensor platform for blueprint.""" -from custom_components.blueprint.const import DEFAULT_NAME, DOMAIN, ICON, SENSOR -from custom_components.blueprint.entity import BlueprintEntity +from .const import DEFAULT_NAME, DOMAIN, ICON, SENSOR +from .entity import BlueprintEntity async def async_setup_entry(hass, entry, async_add_devices): @@ -20,7 +20,7 @@ def name(self): @property def state(self): """Return the state of the sensor.""" - return self.coordinator.data.get("static") + return self.coordinator.data.get("body") @property def icon(self): diff --git a/custom_components/blueprint/switch.py b/custom_components/blueprint/switch.py index 8cd1bcc..6c78f47 100644 --- a/custom_components/blueprint/switch.py +++ b/custom_components/blueprint/switch.py @@ -1,8 +1,8 @@ """Switch platform for blueprint.""" -from homeassistant.components.switch import SwitchDevice +from homeassistant.components.switch import SwitchEntity -from custom_components.blueprint.const import DEFAULT_NAME, DOMAIN, ICON, SWITCH -from custom_components.blueprint.entity import BlueprintEntity +from .const import DEFAULT_NAME, DOMAIN, ICON, SWITCH +from .entity import BlueprintEntity async def async_setup_entry(hass, entry, async_add_devices): @@ -11,17 +11,17 @@ async def async_setup_entry(hass, entry, async_add_devices): async_add_devices([BlueprintBinarySwitch(coordinator, entry)]) -class BlueprintBinarySwitch(BlueprintEntity, SwitchDevice): +class BlueprintBinarySwitch(BlueprintEntity, SwitchEntity): """blueprint switch class.""" async def async_turn_on(self, **kwargs): # pylint: disable=unused-argument """Turn on the switch.""" - await self.coordinator.api.async_change_something(True) + await self.coordinator.api.async_set_title("bar") await self.coordinator.async_request_refresh() async def async_turn_off(self, **kwargs): # pylint: disable=unused-argument """Turn off the switch.""" - await self.coordinator.api.async_change_something(False) + await self.coordinator.api.async_set_title("foo") await self.coordinator.async_request_refresh() @property @@ -37,4 +37,4 @@ def icon(self): @property def is_on(self): """Return true if the switch is on.""" - return self.coordinator.api.something + return self.coordinator.data.get("title", "") == "foo" diff --git a/hacs.json b/hacs.json index c2a336b..8068370 100644 --- a/hacs.json +++ b/hacs.json @@ -1,11 +1,11 @@ { "name": "Blueprint", - "hacs": "0.24.0", + "hacs": "1.6.0", "domains": [ "binary_sensor", "sensor", "switch" ], "iot_class": "Cloud Polling", - "homeassistant": "0.115.0" + "homeassistant": "0.118.0" } diff --git a/info.md b/info.md index cd940fb..0090923 100644 --- a/info.md +++ b/info.md @@ -16,7 +16,7 @@ _Component to integrate with [blueprint][blueprint]._ Platform | Description -- | -- `binary_sensor` | Show something `True` or `False`. -`sensor` | Show info from blueprint API. +`sensor` | Show info from API. `switch` | Switch something `True` or `False`. ![example][exampleimg]