chore: rename integration and references in unit tests

This commit is contained in:
2021-12-11 12:03:56 +00:00
parent d51d010901
commit 349d6c6437
19 changed files with 20 additions and 20 deletions

View File

@ -0,0 +1,109 @@
"""
Custom integration to integrate integration_blueprint with Home Assistant.
For more details about this integration, please refer to
https://github.com/custom-components/integration_blueprint
"""
import asyncio
from datetime import timedelta
import logging
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 .api import IntegrationBlueprintApiClient
from .const import (
CONF_PASSWORD,
CONF_USERNAME,
DOMAIN,
PLATFORMS,
STARTUP_MESSAGE,
)
SCAN_INTERVAL = timedelta(seconds=30)
_LOGGER: logging.Logger = logging.getLogger(__package__)
async def async_setup(hass: HomeAssistant, config: Config):
"""Set up this integration using YAML is not supported."""
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Set up this integration using UI."""
if hass.data.get(DOMAIN) is None:
hass.data.setdefault(DOMAIN, {})
_LOGGER.info(STARTUP_MESSAGE)
username = entry.data.get(CONF_USERNAME)
password = entry.data.get(CONF_PASSWORD)
session = async_get_clientsession(hass)
client = IntegrationBlueprintApiClient(username, password, session)
coordinator = BlueprintDataUpdateCoordinator(hass, client=client)
await coordinator.async_refresh()
if not coordinator.last_update_success:
raise ConfigEntryNotReady
hass.data[DOMAIN][entry.entry_id] = coordinator
for platform in PLATFORMS:
if entry.options.get(platform, True):
coordinator.platforms.append(platform)
hass.async_add_job(
hass.config_entries.async_forward_entry_setup(entry, platform)
)
entry.add_update_listener(async_reload_entry)
return True
class BlueprintDataUpdateCoordinator(DataUpdateCoordinator):
"""Class to manage fetching data from the API."""
def __init__(
self, hass: HomeAssistant, client: IntegrationBlueprintApiClient
) -> None:
"""Initialize."""
self.api = client
self.platforms = []
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL)
async def _async_update_data(self):
"""Update data via library."""
try:
return await self.api.async_get_data()
except Exception as exception:
raise UpdateFailed() from exception
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Handle removal of an entry."""
coordinator = hass.data[DOMAIN][entry.entry_id]
unloaded = all(
await asyncio.gather(
*[
hass.config_entries.async_forward_entry_unload(entry, platform)
for platform in PLATFORMS
if platform in coordinator.platforms
]
)
)
if unloaded:
hass.data[DOMAIN].pop(entry.entry_id)
return unloaded
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)

View File

@ -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 IntegrationBlueprintApiClient:
def __init__(
self, username: str, password: str, session: aiohttp.ClientSession
) -> None:
"""Sample API Client."""
self._username = username
self._password = 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):
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 happened! - %s", exception)

View File

@ -0,0 +1,35 @@
"""Binary sensor platform for integration_blueprint."""
from homeassistant.components.binary_sensor import BinarySensorEntity
from .const import (
BINARY_SENSOR,
BINARY_SENSOR_DEVICE_CLASS,
DEFAULT_NAME,
DOMAIN,
)
from .entity import IntegrationBlueprintEntity
async def async_setup_entry(hass, entry, async_add_devices):
"""Setup binary_sensor platform."""
coordinator = hass.data[DOMAIN][entry.entry_id]
async_add_devices([IntegrationBlueprintBinarySensor(coordinator, entry)])
class IntegrationBlueprintBinarySensor(IntegrationBlueprintEntity, BinarySensorEntity):
"""integration_blueprint binary_sensor class."""
@property
def name(self):
"""Return the name of the binary_sensor."""
return f"{DEFAULT_NAME}_{BINARY_SENSOR}"
@property
def device_class(self):
"""Return the class of this binary_sensor."""
return BINARY_SENSOR_DEVICE_CLASS
@property
def is_on(self):
"""Return true if the binary_sensor is on."""
return self.coordinator.data.get("title", "") == "foo"

View File

@ -0,0 +1,116 @@
"""Adds config flow for Blueprint."""
from homeassistant import config_entries
from homeassistant.core import callback
from homeassistant.helpers.aiohttp_client import async_create_clientsession
import voluptuous as vol
from .api import IntegrationBlueprintApiClient
from .const import (
CONF_PASSWORD,
CONF_USERNAME,
DOMAIN,
PLATFORMS,
)
class BlueprintFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Config flow for Blueprint."""
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
def __init__(self):
"""Initialize."""
self._errors = {}
async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
self._errors = {}
# Uncomment the next 2 lines if only a single instance of the integration is allowed:
# if self._async_current_entries():
# return self.async_abort(reason="single_instance_allowed")
if user_input is not None:
valid = await self._test_credentials(
user_input[CONF_USERNAME], user_input[CONF_PASSWORD]
)
if valid:
return self.async_create_entry(
title=user_input[CONF_USERNAME], data=user_input
)
else:
self._errors["base"] = "auth"
return await self._show_config_form(user_input)
user_input = {}
# Provide defaults for form
user_input[CONF_USERNAME] = ""
user_input[CONF_PASSWORD] = ""
return await self._show_config_form(user_input)
@staticmethod
@callback
def async_get_options_flow(config_entry):
return BlueprintOptionsFlowHandler(config_entry)
async def _show_config_form(self, user_input): # pylint: disable=unused-argument
"""Show the configuration form to edit location data."""
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required(CONF_USERNAME, default=user_input[CONF_USERNAME]): str,
vol.Required(CONF_PASSWORD, default=user_input[CONF_PASSWORD]): str,
}
),
errors=self._errors,
)
async def _test_credentials(self, username, password):
"""Return true if credentials is valid."""
try:
session = async_create_clientsession(self.hass)
client = IntegrationBlueprintApiClient(username, password, session)
await client.async_get_data()
return True
except Exception: # pylint: disable=broad-except
pass
return False
class BlueprintOptionsFlowHandler(config_entries.OptionsFlow):
"""Blueprint config flow options handler."""
def __init__(self, config_entry):
"""Initialize HACS options flow."""
self.config_entry = config_entry
self.options = dict(config_entry.options)
async def async_step_init(self, user_input=None): # pylint: disable=unused-argument
"""Manage the options."""
return await self.async_step_user()
async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
if user_input is not None:
self.options.update(user_input)
return await self._update_options()
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required(x, default=self.options.get(x, True)): bool
for x in sorted(PLATFORMS)
}
),
)
async def _update_options(self):
"""Update config entry options."""
return self.async_create_entry(
title=self.config_entry.data.get(CONF_USERNAME), data=self.options
)

View File

@ -0,0 +1,40 @@
"""Constants for integration_blueprint."""
# Base component constants
NAME = "Integration blueprint"
DOMAIN = "integration_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/integration_blueprint/issues"
# Icons
ICON = "mdi:format-quote-close"
# Device classes
BINARY_SENSOR_DEVICE_CLASS = "connectivity"
# Platforms
BINARY_SENSOR = "binary_sensor"
SENSOR = "sensor"
SWITCH = "switch"
PLATFORMS = [BINARY_SENSOR, SENSOR, SWITCH]
# Configuration and options
CONF_ENABLED = "enabled"
CONF_USERNAME = "username"
CONF_PASSWORD = "password"
# Defaults
DEFAULT_NAME = DOMAIN
STARTUP_MESSAGE = f"""
-------------------------------------------------------------------
{NAME}
Version: {VERSION}
This is a custom integration!
If you have any issues with this you need to open an issue here:
{ISSUE_URL}
-------------------------------------------------------------------
"""

View File

@ -0,0 +1,33 @@
"""BlueprintEntity class"""
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN, NAME, VERSION, ATTRIBUTION
class IntegrationBlueprintEntity(CoordinatorEntity):
def __init__(self, coordinator, config_entry):
super().__init__(coordinator)
self.config_entry = config_entry
@property
def unique_id(self):
"""Return a unique ID to use for this entity."""
return self.config_entry.entry_id
@property
def device_info(self):
return {
"identifiers": {(DOMAIN, self.unique_id)},
"name": NAME,
"model": VERSION,
"manufacturer": NAME,
}
@property
def extra_state_attributes(self):
"""Return the state attributes."""
return {
"attribution": ATTRIBUTION,
"id": str(self.coordinator.data.get("id")),
"integration": DOMAIN,
}

View File

@ -0,0 +1,12 @@
{
"domain": "integration_blueprint",
"name": "Integration blueprint",
"documentation": "https://github.com/custom-components/integration_blueprint",
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/custom-components/integration_blueprint/issues",
"version": "0.0.0",
"config_flow": true,
"codeowners": [
"@ludeeus"
]
}

View File

@ -0,0 +1,28 @@
"""Sensor platform for integration_blueprint."""
from .const import DEFAULT_NAME, DOMAIN, ICON, SENSOR
from .entity import IntegrationBlueprintEntity
async def async_setup_entry(hass, entry, async_add_devices):
"""Setup sensor platform."""
coordinator = hass.data[DOMAIN][entry.entry_id]
async_add_devices([IntegrationBlueprintSensor(coordinator, entry)])
class IntegrationBlueprintSensor(IntegrationBlueprintEntity):
"""integration_blueprint Sensor class."""
@property
def name(self):
"""Return the name of the sensor."""
return f"{DEFAULT_NAME}_{SENSOR}"
@property
def state(self):
"""Return the state of the sensor."""
return self.coordinator.data.get("body")
@property
def icon(self):
"""Return the icon of the sensor."""
return ICON

View File

@ -0,0 +1,40 @@
"""Switch platform for integration_blueprint."""
from homeassistant.components.switch import SwitchEntity
from .const import DEFAULT_NAME, DOMAIN, ICON, SWITCH
from .entity import IntegrationBlueprintEntity
async def async_setup_entry(hass, entry, async_add_devices):
"""Setup sensor platform."""
coordinator = hass.data[DOMAIN][entry.entry_id]
async_add_devices([IntegrationBlueprintBinarySwitch(coordinator, entry)])
class IntegrationBlueprintBinarySwitch(IntegrationBlueprintEntity, SwitchEntity):
"""integration_blueprint switch class."""
async def async_turn_on(self, **kwargs): # pylint: disable=unused-argument
"""Turn on the switch."""
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_set_title("foo")
await self.coordinator.async_request_refresh()
@property
def name(self):
"""Return the name of the switch."""
return f"{DEFAULT_NAME}_{SWITCH}"
@property
def icon(self):
"""Return the icon of this switch."""
return ICON
@property
def is_on(self):
"""Return true if the switch is on."""
return self.coordinator.data.get("title", "") == "foo"

View File

@ -0,0 +1,31 @@
{
"config": {
"step": {
"user": {
"title": "Blueprint",
"description": "If you need help with the configuration have a look here: https://github.com/custom-components/integration_blueprint",
"data": {
"username": "Username",
"password": "Password"
}
}
},
"error": {
"auth": "Username/Password is wrong."
},
"abort": {
"single_instance_allowed": "Only a single instance is allowed."
}
},
"options": {
"step": {
"user": {
"data": {
"binary_sensor": "Binary sensor enabled",
"sensor": "Sensor enabled",
"switch": "Switch enabled"
}
}
}
}
}

View File

@ -0,0 +1,31 @@
{
"config": {
"step": {
"user": {
"title": "Blueprint",
"description": "Si vous avez besoin d'aide pour la configuration, regardez ici: https://github.com/custom-components/integration_blueprint",
"data": {
"username": "Identifiant",
"password": "Mot de Passe"
}
}
},
"error": {
"auth": "Identifiant ou mot de passe erroné."
},
"abort": {
"single_instance_allowed": "Une seule instance est autorisée."
}
},
"options": {
"step": {
"user": {
"data": {
"binary_sensor": "Capteur binaire activé",
"sensor": "Capteur activé",
"switch": "Interrupteur activé"
}
}
}
}
}

View File

@ -0,0 +1,31 @@
{
"config": {
"step": {
"user": {
"title": "Blueprint",
"description": "Hvis du trenger hjep til konfigurasjon ta en titt her: https://github.com/custom-components/integration_blueprint",
"data": {
"username": "Brukernavn",
"password": "Passord"
}
}
},
"error": {
"auth": "Brukernavn/Passord er feil."
},
"abort": {
"single_instance_allowed": "Denne integrasjonen kan kun konfigureres en gang."
}
},
"options": {
"step": {
"user": {
"data": {
"binary_sensor": "Binær sensor aktivert",
"sensor": "Sensor aktivert",
"switch": "Bryter aktivert"
}
}
}
}
}