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

Feat: Reconfiguration config flow #434

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
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
57 changes: 34 additions & 23 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,31 +1,42 @@
// See https://aka.ms/vscode-remote/devcontainer.json for format details.
{
"image": "ghcr.io/ludeeus/devcontainer/integration:stable",
"image": "mcr.microsoft.com/devcontainers/python:3.12",
"name": "Nordpool integration development",
"context": "..",
"appPort": [
"9123:8123"
],
"postCreateCommand": "container install",
"extensions": [
"ms-python.python",
"github.vscode-pull-request-github",
"ryanluker.vscode-coverage-gutters",
"ms-python.vscode-pylance"
// Mount the path to custom_components
// This let's us have the structure we want <root>/custom_components/integration_blueprint
// while at the same time have Home Assistant configuration inside <root>/config
// without resulting to symlinks.
"mounts": [
"source=${localWorkspaceFolder}/custom_components,target=${containerWorkspaceFolder}/config/custom_components,type=bind,consistency=cached"
],
"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",
"editor.formatOnPaste": false,
"editor.formatOnSave": true,
"editor.formatOnType": true,
"files.trimTrailingWhitespace": true
}

"postCreateCommand": "scripts/setup",
"customizations": {
"vscode": {
"extensions": [
"ms-python.python",
"github.vscode-pull-request-github",
"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",
"editor.formatOnPaste": false,
"editor.formatOnSave": true,
"editor.formatOnType": true,
"files.trimTrailingWhitespace": true
}
}
},
"remoteUser": "vscode",
"features": {}
}
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Home Assistant configuration
config/*
!config/configuration.yaml

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down
20 changes: 1 addition & 19 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,8 @@
{
"label": "Run Home Assistant on port 9123",
"type": "shell",
"command": "container start",
"command": "scripts/dev",
"problemMatcher": []
},
{
"label": "Run Home Assistant configuration against /config",
"type": "shell",
"command": "container check",
"problemMatcher": []
},
{
"label": "Upgrade Home Assistant to latest dev",
"type": "shell",
"command": "container install",
"problemMatcher": []
},
{
"label": "Install a specific version of Home Assistant",
"type": "shell",
"command": "container set-version",
"problemMatcher": []
}
]
}
File renamed without changes.
99 changes: 73 additions & 26 deletions custom_components/nordpool/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
"""Adds config flow for nordpool."""
import logging
import re
from typing import TYPE_CHECKING

import voluptuous as vol
from homeassistant import config_entries
from homeassistant.helpers.template import Template

if TYPE_CHECKING:
from typing import Any, Mapping

from . import DOMAIN
from .sensor import _PRICE_IN, _REGIONS, DEFAULT_TEMPLATE

Expand All @@ -24,6 +28,7 @@ class NordpoolFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
def __init__(self):
"""Initialize."""
self._errors = {}
_config_entry = None

async def async_step_user(
self, user_input=None
Expand All @@ -33,47 +38,89 @@ async def async_step_user(

if user_input is not None:
template_ok = False
if user_input["additional_costs"] in (None, ""):
user_input["additional_costs"] = DEFAULT_TEMPLATE
self._patch_template(user_input["additional_costs"])

template_ok = await self._valid_template(user_input["additional_costs"])
if template_ok:
return self.async_create_entry(title="Nordpool", data=user_input)
else:
# Lets try to remove the most common mistakes, this will still fail if the template
# was writte in notepad or something like that..
user_input["additional_costs"] = re.sub(
r"\s{2,}", "", user_input["additional_costs"]
)
self._errors["base"] = "invalid_template"

return self.async_show_form(
step_id="user",
**self._get_form_data(user_input),
errors=self._errors,
)

async def async_step_reconfigure(
self, entry_data: "Mapping[str, Any]"
) -> config_entries.ConfigFlowResult:
"""Handle a reconfiguration flow initialized by the user."""
config_entry = self.hass.config_entries.async_get_entry(
self.context["entry_id"]
)
self._config_entry = config_entry
return await self.async_step_reconfigure_confirm()

async def async_step_reconfigure_confirm(
self, user_input: "dict[str, Any] | None" = None
) -> config_entries.ConfigFlowResult:
"""Handle a reconfiguration flow initialized by the user."""

if user_input is not None:
template_ok = False
self._patch_template(user_input["additional_costs"])

template_ok = await self._valid_template(user_input["additional_costs"])
if template_ok:
return self.async_create_entry(title="Nordpool", data=user_input)
else:
self._errors["base"] = "invalid_template"

data_schema = {
vol.Required("region", default=None): vol.In(regions),
vol.Optional("currency", default=""): vol.In(currencys),
vol.Optional("VAT", default=True): bool,
vol.Optional("precision", default=3): vol.Coerce(int),
vol.Optional("low_price_cutoff", default=1.0): vol.Coerce(float),
vol.Optional("price_in_cents", default=False): bool,
vol.Optional("price_type", default="kWh"): vol.In(price_types),
vol.Optional("additional_costs", default=""): str,
}
return self.async_update_reload_and_abort(
self._config_entry,
data=user_input,
reason="reconfigure_successful",
)

return self.async_show_form(
step_id="reconfigure_confirm", **self._get_form_data(self._config_entry.data),
errors=self._errors
)

placeholders = {
"region": regions,
"currency": currencys,
"price_type": price_types,
def _get_form_data(self, user_input: "Mapping[str, Any] | None"):
"""Populate form data from user input and default values"""
if not user_input:
user_input = dict()

data_schema = vol.Schema({
vol.Required("region", default=user_input.get("region", None)): vol.In(regions),
vol.Required("currency", default=user_input.get("currency", None)): vol.In(currencys),
vol.Optional("VAT", default=user_input.get("VAT", True)): bool,
vol.Required("precision", default=user_input.get("precision", 3)): vol.Coerce(int),
vol.Required("low_price_cutoff", default=user_input.get("low_price_cutoff", 1.0)): vol.Coerce(float),
vol.Optional("price_in_cents", default=user_input.get("price_in_cents", False)): bool,
vol.Required("price_type", default=user_input.get("price_type", "kWh")): vol.In(price_types),
vol.Optional("additional_costs", default=user_input.get("additional_costs", "") or DEFAULT_TEMPLATE): str,
})

description_placeholders = {
"price_type": price_types[0],
"additional_costs": "{{0.0|float}}",
}

return self.async_show_form(
step_id="user",
data_schema=vol.Schema(data_schema),
description_placeholders=placeholders,
errors=self._errors,
return dict(description_placeholders=description_placeholders, data_schema=data_schema)

def _patch_template(self, user_template: str):
"""Fix common mistakes in template"""
# Lets try to remove the most common mistakes, this will still fail if the template
# was writte in notepad or something like that..
user_template = re.sub(
r"\s{2,}", "", user_template
)

async def _valid_template(self, user_template):
"""Validate template"""
try:
#
ut = Template(user_template, self.hass).async_render(
Expand Down
39 changes: 39 additions & 0 deletions custom_components/nordpool/strings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"config": {
"title": "Nordpool",
"step": {
"user": {
"title": "Nordpool Sensor",
"description": "Setup a Nordpool sensor",
"data": {
"region": "Region",
"currency": "Currency",
"VAT": "Include VAT",
"precision": "Decimal rounding precision",
"low_price_cutoff": "Low price percentage",
"price_in_cents": "Price in cents",
"price_type": "Energy scale",
"additional_costs": "Template for additional costs"
},
"reconfigure_confirm": {
"title": "Nordpool Sensor",
"description": "Reconfigure Nordpool sensor",
"data": {
"region": "Region",
"currency": "Currency",
"VAT": "Include VAT",
"precision": "Decimal rounding precision",
"low_price_cutoff": "Low price percentage",
"price_in_cents": "Price in cents",
"price_type": "Energy scale",
"additional_costs": "Template for additional costs"
}
}
}
},
"error": {
"name_exists": "Name already exists",
"invalid_template": "The template is invalid, check https://github.com/custom-components/nordpool"
}
}
}
16 changes: 15 additions & 1 deletion custom_components/nordpool/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,25 @@
"price_type": "Energy scale",
"additional_costs": "Template for additional costs"
}
},
"reconfigure_confirm": {
"title": "Nordpool Sensor",
"description": "Reconfigure a Nordpool sensor",
"data": {
"region": "Region",
"currency": "Currency",
"VAT": "Include VAT",
"precision": "Decimal rounding precision",
"low_price_cutoff": "Low price percentage",
"price_in_cents": "Price in cents",
"price_type": "Energy scale",
"additional_costs": "Template for additional costs"
}
}
},
"error": {
"name_exists": "Name already exists",
"invalid_template": "The template is invalid, check https://github.com/custom-components/nordpool"
}
}
}
}
21 changes: 18 additions & 3 deletions custom_components/nordpool/translations/sv.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,27 @@
"title": "Nord Pool",
"step": {
"user": {
"title": "Sensor",
"title": "Nordpool Sensor",
"description": "Konfigurera en Nord Pool-sensor",
"data": {
"region": "Region",
"friendly_name": "Visningsnamn",
"currency": "SEK",
"currency": "Valuta",
"VAT": "Inkludera moms",
"precision": "Hur många decimaler ska visas",
"low_price_cutoff": "Lägsta prisnivå",
"price_in_cents": "Pris i ören",
"price_type": "Prisformat",
"additional_costs": "Mall för ytterligare kostnader"
}
},
"reconfigure_confirm": {
"title": "Nordpool Sensor",
"description": "Konfigurera en Nord Pool-sensor",
"data": {
"region": "Region",
"friendly_name": "Visningsnamn",
"currency": "Valuta",
"VAT": "Inkludera moms",
"precision": "Hur många decimaler ska visas",
"low_price_cutoff": "Lägsta prisnivå",
Expand All @@ -23,4 +38,4 @@
"invalid_template": "Mallen är ogiltig, se https://github.com/custom-components/nordpool"
}
}
}
}
4 changes: 4 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
colorlog==6.8.2
homeassistant==2024.6.4
pip>=21.3.1
numpy
6 changes: 0 additions & 6 deletions scripts/dev
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,6 @@ if [[ ! -d "${PWD}/config" ]]; then
hass --config "${PWD}/config" --script ensure_config
fi

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this removed?

Copy link
Contributor Author

@bj00rn bj00rn Nov 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is from PR #431. I've replaced usage of pythonpath with mounting custom_components in the devcontainer. Downside is that this only works inside the devcontainer.

It seems that setting adding integration to pythonpath will cause the integration package to shadow nordpool dependency, since pythonpath has precedence over site-packages. Integration will not load. See discussion here ludeeus/integration_blueprint#130

import nordpool will point to the integration itself. I don't now if/why this was working in the earlier version of the devcontainer. Maybe some internal has changed in HASS, or if this relates to changes in python 3.12

# Set the path to custom_components
## This let's us have the structure we want <root>/custom_components/integration_blueprint
## while at the same time have Home Assistant configuration inside <root>/config
## without resulting to symlinks.
export PYTHONPATH="${PYTHONPATH}:${PWD}/custom_components"

echo $PWD

# Start Home Assistant
Expand Down
Empty file modified scripts/setup
100644 → 100755
Empty file.
Loading