mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-03-30 21:33:39 +00:00
Refactored config_flow.py (995 lines) into focused modules within config_flow/ package to improve maintainability and code organization. Changes: - Created config_flow/ package with 6 specialized modules (1,260 lines total) - Extracted validators to validators.py (95 lines) - pure, testable functions - Extracted schemas to schemas.py (577 lines) - centralized vol.Schema definitions - Split flow handlers into separate files: * user_flow.py (274 lines) - Main config flow (setup + reauth) * subentry_flow.py (124 lines) - Subentry flow (add homes) * options_flow.py (160 lines) - Options flow (6-step configuration wizard) - Package exports via __init__.py (50 lines) for backward compatibility - Deleted config_flow_legacy.py (no longer needed) Technical improvements: - Used Mapping[str, Any] for config_entry.options compatibility - Proper TYPE_CHECKING imports for circular dependency management - All 10 inline vol.Schema definitions replaced with reusable functions - Validators are pure functions (no side effects, easily testable) - Clear separation of concerns (validation, schemas, flows) Documentation: - Updated AGENTS.md with new package structure - Updated config flow patterns and examples - Added "Add a new config flow step" guide to Common Tasks - Marked refactoring plan as COMPLETED with lessons learned Verification: - All linting checks pass (./scripts/lint-check) - All flow handlers import successfully - Home Assistant loads integration without errors - All flow types functional (user, subentry, options, reauth) - No user-facing changes (backward compatible) Impact: Improves code maintainability by organizing 995 lines into 6 focused modules (avg 210 lines/module). Enables easier testing, future modifications, and onboarding of new contributors.
577 lines
18 KiB
Python
577 lines
18 KiB
Python
"""Schema definitions for tibber_prices config flow."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Mapping
|
|
|
|
import voluptuous as vol
|
|
|
|
from custom_components.tibber_prices.const import (
|
|
BEST_PRICE_MAX_LEVEL_OPTIONS,
|
|
CONF_BEST_PRICE_FLEX,
|
|
CONF_BEST_PRICE_MAX_LEVEL,
|
|
CONF_BEST_PRICE_MAX_LEVEL_GAP_COUNT,
|
|
CONF_BEST_PRICE_MIN_DISTANCE_FROM_AVG,
|
|
CONF_BEST_PRICE_MIN_PERIOD_LENGTH,
|
|
CONF_ENABLE_MIN_PERIODS_BEST,
|
|
CONF_ENABLE_MIN_PERIODS_PEAK,
|
|
CONF_EXTENDED_DESCRIPTIONS,
|
|
CONF_MIN_PERIODS_BEST,
|
|
CONF_MIN_PERIODS_PEAK,
|
|
CONF_PEAK_PRICE_FLEX,
|
|
CONF_PEAK_PRICE_MAX_LEVEL_GAP_COUNT,
|
|
CONF_PEAK_PRICE_MIN_DISTANCE_FROM_AVG,
|
|
CONF_PEAK_PRICE_MIN_LEVEL,
|
|
CONF_PEAK_PRICE_MIN_PERIOD_LENGTH,
|
|
CONF_PRICE_RATING_THRESHOLD_HIGH,
|
|
CONF_PRICE_RATING_THRESHOLD_LOW,
|
|
CONF_PRICE_TREND_THRESHOLD_FALLING,
|
|
CONF_PRICE_TREND_THRESHOLD_RISING,
|
|
CONF_RELAXATION_ATTEMPTS_BEST,
|
|
CONF_RELAXATION_ATTEMPTS_PEAK,
|
|
CONF_RELAXATION_STEP_BEST,
|
|
CONF_RELAXATION_STEP_PEAK,
|
|
CONF_VOLATILITY_THRESHOLD_HIGH,
|
|
CONF_VOLATILITY_THRESHOLD_MODERATE,
|
|
CONF_VOLATILITY_THRESHOLD_VERY_HIGH,
|
|
DEFAULT_BEST_PRICE_FLEX,
|
|
DEFAULT_BEST_PRICE_MAX_LEVEL,
|
|
DEFAULT_BEST_PRICE_MAX_LEVEL_GAP_COUNT,
|
|
DEFAULT_BEST_PRICE_MIN_DISTANCE_FROM_AVG,
|
|
DEFAULT_BEST_PRICE_MIN_PERIOD_LENGTH,
|
|
DEFAULT_ENABLE_MIN_PERIODS_BEST,
|
|
DEFAULT_ENABLE_MIN_PERIODS_PEAK,
|
|
DEFAULT_EXTENDED_DESCRIPTIONS,
|
|
DEFAULT_MIN_PERIODS_BEST,
|
|
DEFAULT_MIN_PERIODS_PEAK,
|
|
DEFAULT_PEAK_PRICE_FLEX,
|
|
DEFAULT_PEAK_PRICE_MAX_LEVEL_GAP_COUNT,
|
|
DEFAULT_PEAK_PRICE_MIN_DISTANCE_FROM_AVG,
|
|
DEFAULT_PEAK_PRICE_MIN_LEVEL,
|
|
DEFAULT_PEAK_PRICE_MIN_PERIOD_LENGTH,
|
|
DEFAULT_PRICE_RATING_THRESHOLD_HIGH,
|
|
DEFAULT_PRICE_RATING_THRESHOLD_LOW,
|
|
DEFAULT_PRICE_TREND_THRESHOLD_FALLING,
|
|
DEFAULT_PRICE_TREND_THRESHOLD_RISING,
|
|
DEFAULT_RELAXATION_ATTEMPTS_BEST,
|
|
DEFAULT_RELAXATION_ATTEMPTS_PEAK,
|
|
DEFAULT_RELAXATION_STEP_BEST,
|
|
DEFAULT_RELAXATION_STEP_PEAK,
|
|
DEFAULT_VOLATILITY_THRESHOLD_HIGH,
|
|
DEFAULT_VOLATILITY_THRESHOLD_MODERATE,
|
|
DEFAULT_VOLATILITY_THRESHOLD_VERY_HIGH,
|
|
PEAK_PRICE_MIN_LEVEL_OPTIONS,
|
|
)
|
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
|
from homeassistant.helpers.selector import (
|
|
BooleanSelector,
|
|
NumberSelector,
|
|
NumberSelectorConfig,
|
|
NumberSelectorMode,
|
|
SelectOptionDict,
|
|
SelectSelector,
|
|
SelectSelectorConfig,
|
|
SelectSelectorMode,
|
|
TextSelector,
|
|
TextSelectorConfig,
|
|
TextSelectorType,
|
|
)
|
|
|
|
|
|
def get_user_schema(access_token: str | None = None) -> vol.Schema:
|
|
"""Return schema for user step (API token input)."""
|
|
return vol.Schema(
|
|
{
|
|
vol.Required(
|
|
CONF_ACCESS_TOKEN,
|
|
default=access_token if access_token is not None else vol.UNDEFINED,
|
|
): TextSelector(
|
|
TextSelectorConfig(
|
|
type=TextSelectorType.TEXT,
|
|
),
|
|
),
|
|
}
|
|
)
|
|
|
|
|
|
def get_reauth_confirm_schema() -> vol.Schema:
|
|
"""Return schema for reauth confirmation step."""
|
|
return vol.Schema(
|
|
{
|
|
vol.Required(CONF_ACCESS_TOKEN): TextSelector(
|
|
TextSelectorConfig(type=TextSelectorType.TEXT),
|
|
),
|
|
}
|
|
)
|
|
|
|
|
|
def get_select_home_schema(home_options: list[SelectOptionDict]) -> vol.Schema:
|
|
"""Return schema for home selection step."""
|
|
return vol.Schema(
|
|
{
|
|
vol.Required("home_id"): SelectSelector(
|
|
SelectSelectorConfig(
|
|
options=home_options,
|
|
mode=SelectSelectorMode.DROPDOWN,
|
|
)
|
|
)
|
|
}
|
|
)
|
|
|
|
|
|
def get_subentry_init_schema(*, extended_descriptions: bool = DEFAULT_EXTENDED_DESCRIPTIONS) -> vol.Schema:
|
|
"""Return schema for subentry init step."""
|
|
return vol.Schema(
|
|
{
|
|
vol.Optional(
|
|
CONF_EXTENDED_DESCRIPTIONS,
|
|
default=extended_descriptions,
|
|
): BooleanSelector(),
|
|
}
|
|
)
|
|
|
|
|
|
def get_options_init_schema(options: Mapping[str, Any]) -> vol.Schema:
|
|
"""Return schema for options init step (general settings)."""
|
|
return vol.Schema(
|
|
{
|
|
vol.Optional(
|
|
CONF_EXTENDED_DESCRIPTIONS,
|
|
default=options.get(CONF_EXTENDED_DESCRIPTIONS, DEFAULT_EXTENDED_DESCRIPTIONS),
|
|
): BooleanSelector(),
|
|
}
|
|
)
|
|
|
|
|
|
def get_price_rating_schema(options: Mapping[str, Any]) -> vol.Schema:
|
|
"""Return schema for price rating thresholds configuration."""
|
|
return vol.Schema(
|
|
{
|
|
vol.Optional(
|
|
CONF_PRICE_RATING_THRESHOLD_LOW,
|
|
default=int(
|
|
options.get(
|
|
CONF_PRICE_RATING_THRESHOLD_LOW,
|
|
DEFAULT_PRICE_RATING_THRESHOLD_LOW,
|
|
)
|
|
),
|
|
): NumberSelector(
|
|
NumberSelectorConfig(
|
|
min=-100,
|
|
max=0,
|
|
unit_of_measurement="%",
|
|
step=1,
|
|
mode=NumberSelectorMode.SLIDER,
|
|
),
|
|
),
|
|
vol.Optional(
|
|
CONF_PRICE_RATING_THRESHOLD_HIGH,
|
|
default=int(
|
|
options.get(
|
|
CONF_PRICE_RATING_THRESHOLD_HIGH,
|
|
DEFAULT_PRICE_RATING_THRESHOLD_HIGH,
|
|
)
|
|
),
|
|
): NumberSelector(
|
|
NumberSelectorConfig(
|
|
min=0,
|
|
max=100,
|
|
unit_of_measurement="%",
|
|
step=1,
|
|
mode=NumberSelectorMode.SLIDER,
|
|
),
|
|
),
|
|
}
|
|
)
|
|
|
|
|
|
def get_volatility_schema(options: Mapping[str, Any]) -> vol.Schema:
|
|
"""Return schema for volatility thresholds configuration."""
|
|
return vol.Schema(
|
|
{
|
|
vol.Optional(
|
|
CONF_VOLATILITY_THRESHOLD_MODERATE,
|
|
default=float(
|
|
options.get(
|
|
CONF_VOLATILITY_THRESHOLD_MODERATE,
|
|
DEFAULT_VOLATILITY_THRESHOLD_MODERATE,
|
|
)
|
|
),
|
|
): NumberSelector(
|
|
NumberSelectorConfig(
|
|
min=0.0,
|
|
max=100.0,
|
|
step=0.1,
|
|
unit_of_measurement="%",
|
|
mode=NumberSelectorMode.BOX,
|
|
),
|
|
),
|
|
vol.Optional(
|
|
CONF_VOLATILITY_THRESHOLD_HIGH,
|
|
default=float(
|
|
options.get(
|
|
CONF_VOLATILITY_THRESHOLD_HIGH,
|
|
DEFAULT_VOLATILITY_THRESHOLD_HIGH,
|
|
)
|
|
),
|
|
): NumberSelector(
|
|
NumberSelectorConfig(
|
|
min=0.0,
|
|
max=100.0,
|
|
step=0.1,
|
|
unit_of_measurement="%",
|
|
mode=NumberSelectorMode.BOX,
|
|
),
|
|
),
|
|
vol.Optional(
|
|
CONF_VOLATILITY_THRESHOLD_VERY_HIGH,
|
|
default=float(
|
|
options.get(
|
|
CONF_VOLATILITY_THRESHOLD_VERY_HIGH,
|
|
DEFAULT_VOLATILITY_THRESHOLD_VERY_HIGH,
|
|
)
|
|
),
|
|
): NumberSelector(
|
|
NumberSelectorConfig(
|
|
min=0.0,
|
|
max=100.0,
|
|
step=0.1,
|
|
unit_of_measurement="%",
|
|
mode=NumberSelectorMode.BOX,
|
|
),
|
|
),
|
|
}
|
|
)
|
|
|
|
|
|
def get_best_price_schema(options: Mapping[str, Any]) -> vol.Schema:
|
|
"""Return schema for best price period configuration."""
|
|
return vol.Schema(
|
|
{
|
|
vol.Optional(
|
|
CONF_BEST_PRICE_MIN_PERIOD_LENGTH,
|
|
default=int(
|
|
options.get(
|
|
CONF_BEST_PRICE_MIN_PERIOD_LENGTH,
|
|
DEFAULT_BEST_PRICE_MIN_PERIOD_LENGTH,
|
|
)
|
|
),
|
|
): NumberSelector(
|
|
NumberSelectorConfig(
|
|
min=15,
|
|
max=240,
|
|
step=15,
|
|
unit_of_measurement="min",
|
|
mode=NumberSelectorMode.SLIDER,
|
|
),
|
|
),
|
|
vol.Optional(
|
|
CONF_BEST_PRICE_FLEX,
|
|
default=int(
|
|
options.get(
|
|
CONF_BEST_PRICE_FLEX,
|
|
DEFAULT_BEST_PRICE_FLEX,
|
|
)
|
|
),
|
|
): NumberSelector(
|
|
NumberSelectorConfig(
|
|
min=0,
|
|
max=100,
|
|
step=1,
|
|
unit_of_measurement="%",
|
|
mode=NumberSelectorMode.SLIDER,
|
|
),
|
|
),
|
|
vol.Optional(
|
|
CONF_BEST_PRICE_MIN_DISTANCE_FROM_AVG,
|
|
default=int(
|
|
options.get(
|
|
CONF_BEST_PRICE_MIN_DISTANCE_FROM_AVG,
|
|
DEFAULT_BEST_PRICE_MIN_DISTANCE_FROM_AVG,
|
|
)
|
|
),
|
|
): NumberSelector(
|
|
NumberSelectorConfig(
|
|
min=0,
|
|
max=50,
|
|
step=1,
|
|
unit_of_measurement="%",
|
|
mode=NumberSelectorMode.SLIDER,
|
|
),
|
|
),
|
|
vol.Optional(
|
|
CONF_BEST_PRICE_MAX_LEVEL,
|
|
default=options.get(
|
|
CONF_BEST_PRICE_MAX_LEVEL,
|
|
DEFAULT_BEST_PRICE_MAX_LEVEL,
|
|
),
|
|
): SelectSelector(
|
|
SelectSelectorConfig(
|
|
options=BEST_PRICE_MAX_LEVEL_OPTIONS,
|
|
mode=SelectSelectorMode.DROPDOWN,
|
|
translation_key="current_interval_price_level",
|
|
),
|
|
),
|
|
vol.Optional(
|
|
CONF_BEST_PRICE_MAX_LEVEL_GAP_COUNT,
|
|
default=int(
|
|
options.get(
|
|
CONF_BEST_PRICE_MAX_LEVEL_GAP_COUNT,
|
|
DEFAULT_BEST_PRICE_MAX_LEVEL_GAP_COUNT,
|
|
)
|
|
),
|
|
): NumberSelector(
|
|
NumberSelectorConfig(
|
|
min=0,
|
|
max=8,
|
|
step=1,
|
|
mode=NumberSelectorMode.SLIDER,
|
|
),
|
|
),
|
|
vol.Optional(
|
|
CONF_ENABLE_MIN_PERIODS_BEST,
|
|
default=options.get(
|
|
CONF_ENABLE_MIN_PERIODS_BEST,
|
|
DEFAULT_ENABLE_MIN_PERIODS_BEST,
|
|
),
|
|
): BooleanSelector(),
|
|
vol.Optional(
|
|
CONF_MIN_PERIODS_BEST,
|
|
default=int(
|
|
options.get(
|
|
CONF_MIN_PERIODS_BEST,
|
|
DEFAULT_MIN_PERIODS_BEST,
|
|
)
|
|
),
|
|
): NumberSelector(
|
|
NumberSelectorConfig(
|
|
min=1,
|
|
max=10,
|
|
step=1,
|
|
mode=NumberSelectorMode.SLIDER,
|
|
),
|
|
),
|
|
vol.Optional(
|
|
CONF_RELAXATION_STEP_BEST,
|
|
default=int(
|
|
options.get(
|
|
CONF_RELAXATION_STEP_BEST,
|
|
DEFAULT_RELAXATION_STEP_BEST,
|
|
)
|
|
),
|
|
): NumberSelector(
|
|
NumberSelectorConfig(
|
|
min=5,
|
|
max=50,
|
|
step=5,
|
|
unit_of_measurement="%",
|
|
mode=NumberSelectorMode.SLIDER,
|
|
),
|
|
),
|
|
vol.Optional(
|
|
CONF_RELAXATION_ATTEMPTS_BEST,
|
|
default=int(
|
|
options.get(
|
|
CONF_RELAXATION_ATTEMPTS_BEST,
|
|
DEFAULT_RELAXATION_ATTEMPTS_BEST,
|
|
)
|
|
),
|
|
): NumberSelector(
|
|
NumberSelectorConfig(
|
|
min=1,
|
|
max=12,
|
|
step=1,
|
|
mode=NumberSelectorMode.SLIDER,
|
|
),
|
|
),
|
|
}
|
|
)
|
|
|
|
|
|
def get_peak_price_schema(options: Mapping[str, Any]) -> vol.Schema:
|
|
"""Return schema for peak price period configuration."""
|
|
return vol.Schema(
|
|
{
|
|
vol.Optional(
|
|
CONF_PEAK_PRICE_MIN_PERIOD_LENGTH,
|
|
default=int(
|
|
options.get(
|
|
CONF_PEAK_PRICE_MIN_PERIOD_LENGTH,
|
|
DEFAULT_PEAK_PRICE_MIN_PERIOD_LENGTH,
|
|
)
|
|
),
|
|
): NumberSelector(
|
|
NumberSelectorConfig(
|
|
min=15,
|
|
max=240,
|
|
step=15,
|
|
unit_of_measurement="min",
|
|
mode=NumberSelectorMode.SLIDER,
|
|
),
|
|
),
|
|
vol.Optional(
|
|
CONF_PEAK_PRICE_FLEX,
|
|
default=int(
|
|
options.get(
|
|
CONF_PEAK_PRICE_FLEX,
|
|
DEFAULT_PEAK_PRICE_FLEX,
|
|
)
|
|
),
|
|
): NumberSelector(
|
|
NumberSelectorConfig(
|
|
min=-100,
|
|
max=0,
|
|
step=1,
|
|
unit_of_measurement="%",
|
|
mode=NumberSelectorMode.SLIDER,
|
|
),
|
|
),
|
|
vol.Optional(
|
|
CONF_PEAK_PRICE_MIN_DISTANCE_FROM_AVG,
|
|
default=int(
|
|
options.get(
|
|
CONF_PEAK_PRICE_MIN_DISTANCE_FROM_AVG,
|
|
DEFAULT_PEAK_PRICE_MIN_DISTANCE_FROM_AVG,
|
|
)
|
|
),
|
|
): NumberSelector(
|
|
NumberSelectorConfig(
|
|
min=0,
|
|
max=50,
|
|
step=1,
|
|
unit_of_measurement="%",
|
|
mode=NumberSelectorMode.SLIDER,
|
|
),
|
|
),
|
|
vol.Optional(
|
|
CONF_PEAK_PRICE_MIN_LEVEL,
|
|
default=options.get(
|
|
CONF_PEAK_PRICE_MIN_LEVEL,
|
|
DEFAULT_PEAK_PRICE_MIN_LEVEL,
|
|
),
|
|
): SelectSelector(
|
|
SelectSelectorConfig(
|
|
options=PEAK_PRICE_MIN_LEVEL_OPTIONS,
|
|
mode=SelectSelectorMode.DROPDOWN,
|
|
translation_key="current_interval_price_level",
|
|
),
|
|
),
|
|
vol.Optional(
|
|
CONF_PEAK_PRICE_MAX_LEVEL_GAP_COUNT,
|
|
default=int(
|
|
options.get(
|
|
CONF_PEAK_PRICE_MAX_LEVEL_GAP_COUNT,
|
|
DEFAULT_PEAK_PRICE_MAX_LEVEL_GAP_COUNT,
|
|
)
|
|
),
|
|
): NumberSelector(
|
|
NumberSelectorConfig(
|
|
min=0,
|
|
max=8,
|
|
step=1,
|
|
mode=NumberSelectorMode.SLIDER,
|
|
),
|
|
),
|
|
vol.Optional(
|
|
CONF_ENABLE_MIN_PERIODS_PEAK,
|
|
default=options.get(
|
|
CONF_ENABLE_MIN_PERIODS_PEAK,
|
|
DEFAULT_ENABLE_MIN_PERIODS_PEAK,
|
|
),
|
|
): BooleanSelector(),
|
|
vol.Optional(
|
|
CONF_MIN_PERIODS_PEAK,
|
|
default=int(
|
|
options.get(
|
|
CONF_MIN_PERIODS_PEAK,
|
|
DEFAULT_MIN_PERIODS_PEAK,
|
|
)
|
|
),
|
|
): NumberSelector(
|
|
NumberSelectorConfig(
|
|
min=1,
|
|
max=10,
|
|
step=1,
|
|
mode=NumberSelectorMode.SLIDER,
|
|
),
|
|
),
|
|
vol.Optional(
|
|
CONF_RELAXATION_STEP_PEAK,
|
|
default=int(
|
|
options.get(
|
|
CONF_RELAXATION_STEP_PEAK,
|
|
DEFAULT_RELAXATION_STEP_PEAK,
|
|
)
|
|
),
|
|
): NumberSelector(
|
|
NumberSelectorConfig(
|
|
min=5,
|
|
max=50,
|
|
step=5,
|
|
unit_of_measurement="%",
|
|
mode=NumberSelectorMode.SLIDER,
|
|
),
|
|
),
|
|
vol.Optional(
|
|
CONF_RELAXATION_ATTEMPTS_PEAK,
|
|
default=int(
|
|
options.get(
|
|
CONF_RELAXATION_ATTEMPTS_PEAK,
|
|
DEFAULT_RELAXATION_ATTEMPTS_PEAK,
|
|
)
|
|
),
|
|
): NumberSelector(
|
|
NumberSelectorConfig(
|
|
min=1,
|
|
max=12,
|
|
step=1,
|
|
mode=NumberSelectorMode.SLIDER,
|
|
),
|
|
),
|
|
}
|
|
)
|
|
|
|
|
|
def get_price_trend_schema(options: Mapping[str, Any]) -> vol.Schema:
|
|
"""Return schema for price trend thresholds configuration."""
|
|
return vol.Schema(
|
|
{
|
|
vol.Optional(
|
|
CONF_PRICE_TREND_THRESHOLD_RISING,
|
|
default=int(
|
|
options.get(
|
|
CONF_PRICE_TREND_THRESHOLD_RISING,
|
|
DEFAULT_PRICE_TREND_THRESHOLD_RISING,
|
|
)
|
|
),
|
|
): NumberSelector(
|
|
NumberSelectorConfig(
|
|
min=1,
|
|
max=50,
|
|
step=1,
|
|
unit_of_measurement="%",
|
|
mode=NumberSelectorMode.SLIDER,
|
|
),
|
|
),
|
|
vol.Optional(
|
|
CONF_PRICE_TREND_THRESHOLD_FALLING,
|
|
default=int(
|
|
options.get(
|
|
CONF_PRICE_TREND_THRESHOLD_FALLING,
|
|
DEFAULT_PRICE_TREND_THRESHOLD_FALLING,
|
|
)
|
|
),
|
|
): NumberSelector(
|
|
NumberSelectorConfig(
|
|
min=-50,
|
|
max=-1,
|
|
step=1,
|
|
unit_of_measurement="%",
|
|
mode=NumberSelectorMode.SLIDER,
|
|
),
|
|
),
|
|
}
|
|
)
|