Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sync with latest version of integration-blueprint #89

Merged
merged 6 commits into from
Dec 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ cookiecutter-homeassistant-custom-component
|

Cookiecutter_ template for a `Home Assistant`_ custom component based on the
blueprint_ template.
integration_blueprint_ template.

This project is the fusion of `cookiecutter-homeassistant-component`_, blueprint_
This project is the fusion of `cookiecutter-homeassistant-component`_, integration_blueprint_
and `cookiecutter-hypermodern-python`_ projects.

✨📚✨ `Read the full documentation`__
Expand Down Expand Up @@ -198,7 +198,7 @@ Known limitations
.. references-begin

.. _Black: https://github.com/psf/black
.. _blueprint: https://github.com/custom-components/blueprint
.. _integration_blueprint: https://github.com/custom-components/integration_blueprint
.. _Cookiecutter: https://github.com/cookiecutter/cookiecutter
.. _cookiecutter-homeassistant-component: https://github.com/boralyl/cookiecutter-homeassistant-component
.. _cookiecutter-homeassistant-custom-component-instance: https://github.com/oncleben31/cookiecutter-homeassistant-custom-component-instance
Expand Down
4 changes: 2 additions & 2 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ Home Assistant custom component Cookiecutter
:end-before: badges-end

Cookiecutter_ template for a `Home Assistant`_ custom component based
on the blueprint_ template.
on the integration_blueprint_ template.

This project is the fusion of `cookiecutter-homeassistant-component`_,
blueprint_
integration_blueprint_
and `cookiecutter-hypermodern-python`_ projects.


Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
default_config:

logger:
default: error
default: info
logs:
custom_components.{{cookiecutter.domain_name}}: debug
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
// See https://aka.ms/vscode-remote/devcontainer.json for format details.
{
"image": "ludeeus/container:integration",
"image": "ludeeus/container:integration-debian",
"name": "{{cookiecutter.friendly_name}} integration development",
"context": "..",
"appPort": ["9123:8123"],
"postCreateCommand": "container install",
"runArgs": ["-v", "${env:HOME}${env:USERPROFILE}/.ssh:/tmp/.ssh"],
"extensions": [
"ms-python.python",
"github.vscode-pull-request-github",
"tabnine.tabnine-vscode"
"ryanluker.vscode-coverage-gutters",
"ms-python.vscode-pylance"
],
"settings": {
"files.eol": "\n",
"editor.tabSize": 4,
"terminal.integrated.shell.linux": "/bin/bash",
"python.pythonPath": "/usr/bin/python3",
"python.analysis.autoSearchPaths": false,
"python.linting.pylintEnabled": true,
"python.linting.enabled": true,
"python.formatting.provider": "black",
Expand Down
2 changes: 1 addition & 1 deletion {{cookiecutter.project_name}}/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__pycache__
pythonenv*
.python-version
.vscode/settings.json
5 changes: 5 additions & 0 deletions {{cookiecutter.project_name}}/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"python.linting.pylintEnabled": true,
"python.linting.enabled": true,
"python.pythonPath": "/usr/local/bin/python"
}
4 changes: 2 additions & 2 deletions {{cookiecutter.project_name}}/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@ Or use the `pre-commit` settings implemented in this repository

## Test your code modification

This custom component is based on [blueprint template](https://github.com/custom-components/blueprint).
This custom component is based on [integration_blueprint template](https://github.com/custom-components/integration_blueprint).

It comes with development environment in a container, easy to launch
if you use Visual Studio Code. With this container you will have a stand alone
Home Assistant instance running and already configured with the included
[`.devcontainer/configuration.yaml`](https://github.com/oncleben31/ha-pool_pump/blob/master/.devcontainer/configuration.yaml)
[`.devcontainer/configuration.yaml`](./.devcontainer/configuration.yaml)
file.

You can use the `pre-commit` settings implemented in this repository to have
Expand Down
30 changes: 17 additions & 13 deletions {{cookiecutter.project_name}}/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand All @@ -29,6 +27,7 @@ This repository contains multiple files, here is a overview:
| `.vscode/tasks.json` | Tasks for the devcontainer. |
| `custom_components/{{cookiecutter.domain_name}}/translations/*` | [Translation files.](https://developers.home-assistant.io/docs/internationalization/custom_integration) |
| `custom_components/{{cookiecutter.domain_name}}/__init__.py` | The component file for the integration. |
| `custom_components/{{cookiecutter.domain_name}}/api.py` | This is a sample API client. |
| `custom_components/{{cookiecutter.domain_name}}/binary_sensor.py` | Binary sensor platform for the integration. |
| `custom_components/{{cookiecutter.domain_name}}/config_flow.py` | Config flow file, this adds the UI configuration possibilities. |
| `custom_components/{{cookiecutter.domain_name}}/const.py` | A file to hold shared variables/constants for the entire integration. |
Expand Down Expand Up @@ -103,16 +102,21 @@ README content if this was a published component:
Using your HA configuration directory (folder) as a starting point you should now also have this:

```text
custom_components/{{ cookiecutter.domain_name }}/.translations/en.json
custom_components/{{ cookiecutter.domain_name }}/.translations/nb.json
custom_components/{{ cookiecutter.domain_name }}/.translations/sensor.nb.json
custom_components/{{ cookiecutter.domain_name }}/__init__.py
custom_components/{{ cookiecutter.domain_name }}/binary_sensor.py
custom_components/{{ cookiecutter.domain_name }}/config_flow.py
custom_components/{{ cookiecutter.domain_name }}/const.py
custom_components/{{ cookiecutter.domain_name }}/manifest.json
custom_components/{{ cookiecutter.domain_name }}/sensor.py
custom_components/{{ cookiecutter.domain_name }}/switch.py
custom_components/{{cookiecutter.domain_name}}/translations/en.json
custom_components/{{cookiecutter.domain_name}}/translations/fr.json
custom_components/{{cookiecutter.domain_name}}/translations/nb.json
custom_components/{{cookiecutter.domain_name}}/translations/sensor.en.json
custom_components/{{cookiecutter.domain_name}}/translations/sensor.fr.json
custom_components/{{cookiecutter.domain_name}}/translations/sensor.nb.json
custom_components/{{cookiecutter.domain_name}}/translations/sensor.nb.json
custom_components/{{cookiecutter.domain_name}}/__init__.py
custom_components/{{cookiecutter.domain_name}}/api.py
custom_components/{{cookiecutter.domain_name}}/binary_sensor.py
custom_components/{{cookiecutter.domain_name}}/config_flow.py
custom_components/{{cookiecutter.domain_name}}/const.py
custom_components/{{cookiecutter.domain_name}}/manifest.json
custom_components/{{cookiecutter.domain_name}}/sensor.py
custom_components/{{cookiecutter.domain_name}}/switch.py
```

## Configuration is done in the UI
Expand All @@ -131,7 +135,7 @@ Code template was mainly taken from [@Ludeeus](https://github.com/ludeeus)'s [bl

---

[blueprint]: https://github.com/custom-components/blueprint
[integration_blueprint]: https://github.com/custom-components/integration_blueprint
[buymecoffee]: https://www.buymeacoffee.com/{{cookiecutter.github_user}}
[buymecoffeebadge]: https://img.shields.io/badge/buy%20me%20a%20coffee-donate-yellow.svg?style=for-the-badge
[commits-shield]: https://img.shields.io/github/commit-activity/y/{{cookiecutter.github_user}}/{{cookiecutter.project_name}}.svg?style=for-the-badge
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
from homeassistant.core import Config
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from homeassistant.helpers.update_coordinator import UpdateFailed
from sampleclient.client import Client

from .api import {{cookiecutter.class_name_prefix}}ApiClient
from .const import CONF_PASSWORD
from .const import CONF_USERNAME
from .const import DOMAIN
Expand All @@ -24,7 +25,7 @@

SCAN_INTERVAL = timedelta(seconds=30)

_LOGGER = logging.getLogger(__name__)
_LOGGER: logging.Logger = logging.getLogger(__package__)


async def async_setup(hass: HomeAssistant, config: Config):
Expand All @@ -41,8 +42,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
username = entry.data.get(CONF_USERNAME)
password = entry.data.get(CONF_PASSWORD)

session = async_get_clientsession(hass)
client = {{cookiecutter.class_name_prefix}}ApiClient(
username, password, session
)

coordinator = {{cookiecutter.class_name_prefix}}DataUpdateCoordinator(
hass, username=username, password=password
hass, client=client
)
await coordinator.async_refresh()

Expand All @@ -67,23 +73,26 @@ class {{cookiecutter.class_name_prefix}}DataUpdateCoordinator(
):
"""Class to manage fetching data from the API."""

def __init__(self, hass, username, password):
def __init__(
self,
hass: HomeAssistant,
client: {{cookiecutter.class_name_prefix}}ApiClient,
) -> None:
"""Initialize."""
self.api = Client(username, password)
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:
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(
Expand All @@ -101,7 +110,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)
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""Sample API Client."""
import asyncio
import logging
import socket

import aiohttp
import async_timeout

TIMEOUT = 10


_LOGGER: logging.Logger = logging.getLogger(__package__)

HEADERS = {"Content-type": "application/json; charset=UTF-8"}


class {{cookiecutter.class_name_prefix}}ApiClient:
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)
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""Binary sensor platform for {{cookiecutter.friendly_name}}."""
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.binary_sensor import BinarySensorEntity

from .const import BINARY_SENSOR
from .const import BINARY_SENSOR_DEVICE_CLASS
Expand All @@ -21,7 +21,7 @@ async def async_setup_entry(hass, entry, async_add_devices):


class {{cookiecutter.class_name_prefix}}BinarySensor(
{{cookiecutter.class_name_prefix}}Entity, BinarySensorDevice
{{cookiecutter.class_name_prefix}}Entity, BinarySensorEntity
):
"""{{cookiecutter.domain_name}} binary_sensor class."""

Expand All @@ -38,4 +38,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"
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.core import callback
from sampleclient.client import Client
from homeassistant.helpers.aiohttp_client import async_create_clientsession

from .api import {{cookiecutter.class_name_prefix}}ApiClient
from .const import CONF_PASSWORD
from .const import CONF_USERNAME
from .const import DOMAIN
Expand All @@ -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 = {}

Expand Down Expand Up @@ -67,7 +66,10 @@ 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 = {{cookiecutter.class_name_prefix}}ApiClient(
username, password, session
)
await client.async_get_data()
return True
except Exception: # pylint: disable=broad-except
Expand All @@ -78,7 +80,7 @@ async def _test_credentials(self, username, password):
class {{cookiecutter.class_name_prefix}}OptionsFlowHandler(
config_entries.OptionsFlow
):
"""{{cookiecutter.domain_name}} config flow options handler."""
"""Config flow options handler for {{cookiecutter.domain_name}}."""

def __init__(self, config_entry):
"""Initialize HACS options flow."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
DOMAIN_DATA = f"{DOMAIN}_data"
VERSION = "{{ cookiecutter.version }}"

ATTRIBUTION = "Data provided by http://jsonplaceholder.typicode.com/"
ISSUE_URL = "https://github.com/{{cookiecutter.github_user}}/{{cookiecutter.project_name}}/issues"

# Icons
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""{{cookiecutter.class_name_prefix}}Entity class"""
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import ATTRIBUTION
from .const import DOMAIN
from .const import NAME
from .const import VERSION
Expand Down Expand Up @@ -29,6 +30,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,
}
Loading