mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-03-29 21:03:40 +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.
122 lines
3.1 KiB
Python
122 lines
3.1 KiB
Python
"""Validation functions for Tibber Prices config flow."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
from custom_components.tibber_prices.api import (
|
|
TibberPricesApiClient,
|
|
TibberPricesApiClientAuthenticationError,
|
|
TibberPricesApiClientCommunicationError,
|
|
TibberPricesApiClientError,
|
|
)
|
|
from custom_components.tibber_prices.const import DOMAIN
|
|
from homeassistant.exceptions import HomeAssistantError
|
|
from homeassistant.helpers.aiohttp_client import async_create_clientsession
|
|
from homeassistant.loader import async_get_integration
|
|
|
|
if TYPE_CHECKING:
|
|
from homeassistant.core import HomeAssistant
|
|
|
|
# Constants for validation
|
|
MAX_FLEX_PERCENTAGE = 100.0
|
|
MAX_MIN_PERIODS = 10 # Arbitrary upper limit for sanity
|
|
|
|
|
|
class InvalidAuthError(HomeAssistantError):
|
|
"""Error to indicate invalid authentication."""
|
|
|
|
|
|
class CannotConnectError(HomeAssistantError):
|
|
"""Error to indicate we cannot connect."""
|
|
|
|
|
|
async def validate_api_token(hass: HomeAssistant, token: str) -> dict:
|
|
"""
|
|
Validate Tibber API token.
|
|
|
|
Args:
|
|
hass: Home Assistant instance
|
|
token: Tibber API access token
|
|
|
|
Returns:
|
|
dict with viewer data on success
|
|
|
|
Raises:
|
|
InvalidAuthError: Invalid token
|
|
CannotConnectError: API connection failed
|
|
|
|
"""
|
|
try:
|
|
integration = await async_get_integration(hass, DOMAIN)
|
|
client = TibberPricesApiClient(
|
|
access_token=token,
|
|
session=async_create_clientsession(hass),
|
|
version=str(integration.version) if integration.version else "unknown",
|
|
)
|
|
result = await client.async_get_viewer_details()
|
|
return result["viewer"]
|
|
except TibberPricesApiClientAuthenticationError as exception:
|
|
raise InvalidAuthError from exception
|
|
except TibberPricesApiClientCommunicationError as exception:
|
|
raise CannotConnectError from exception
|
|
except TibberPricesApiClientError as exception:
|
|
raise CannotConnectError from exception
|
|
|
|
|
|
def validate_threshold_range(value: float, min_val: float, max_val: float) -> bool:
|
|
"""
|
|
Validate threshold is within allowed range.
|
|
|
|
Args:
|
|
value: Value to validate
|
|
min_val: Minimum allowed value
|
|
max_val: Maximum allowed value
|
|
|
|
Returns:
|
|
True if value is within range
|
|
|
|
"""
|
|
return min_val <= value <= max_val
|
|
|
|
|
|
def validate_period_length(minutes: int) -> bool:
|
|
"""
|
|
Validate period length is multiple of 15 minutes.
|
|
|
|
Args:
|
|
minutes: Period length in minutes
|
|
|
|
Returns:
|
|
True if length is valid
|
|
|
|
"""
|
|
return minutes > 0 and minutes % 15 == 0
|
|
|
|
|
|
def validate_flex_percentage(flex: float) -> bool:
|
|
"""
|
|
Validate flexibility percentage is within bounds.
|
|
|
|
Args:
|
|
flex: Flexibility percentage
|
|
|
|
Returns:
|
|
True if percentage is valid
|
|
|
|
"""
|
|
return 0.0 <= flex <= MAX_FLEX_PERCENTAGE
|
|
|
|
|
|
def validate_min_periods(count: int) -> bool:
|
|
"""
|
|
Validate minimum periods count is reasonable.
|
|
|
|
Args:
|
|
count: Number of minimum periods
|
|
|
|
Returns:
|
|
True if count is valid
|
|
|
|
"""
|
|
return count > 0 and count <= MAX_MIN_PERIODS
|