From 1d065b11cdbcb46849658abb4d0994bdb2be70de Mon Sep 17 00:00:00 2001 From: Julian Pawlowski Date: Tue, 14 Apr 2026 19:33:24 +0000 Subject: [PATCH] fix(services): use injected now in resolve_search_range day offset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _resolve_time_with_day_offset() was calling dt_util.now() internally instead of using the injected now parameter. This caused incorrect date calculations in tests and any caller that passes a specific reference time. Also add missing price_rank_* sensor keys to TIME_SENSITIVE_ENTITY_KEYS in coordinator/constants.py so quarter-hour refresh is registered for all 11 price rank sensors (current/next/previous interval and hour variants). Rename dt as dt_utils → dt as dt_util (ICN001) across 11 files to follow the project-wide import alias convention. Apply ruff auto-fixes for import ordering and collapsing single-item imports throughout the codebase. Released-Bug: no --- custom_components/tibber_prices/api/client.py | 25 ++++++--------- .../tibber_prices/binary_sensor/attributes.py | 4 +-- .../tibber_prices/binary_sensor/core.py | 9 ++---- .../binary_sensor/definitions.py | 5 +-- .../tibber_prices/config_flow.py | 8 ++--- .../config_flow_handlers/__init__.py | 12 ++----- .../config_flow_handlers/schemas.py | 2 +- .../config_flow_handlers/user_flow.py | 31 +++++-------------- .../tibber_prices/coordinator/__init__.py | 6 +--- .../tibber_prices/coordinator/constants.py | 12 +++++++ .../tibber_prices/coordinator/core.py | 11 ++----- .../tibber_prices/coordinator/helpers.py | 2 +- .../coordinator/period_handlers/core.py | 22 ++++++------- .../period_handlers/day_pattern.py | 8 ++--- .../period_handlers/level_filtering.py | 2 +- .../period_handlers/outlier_filtering.py | 2 +- .../period_handlers/period_building.py | 10 ++---- .../period_handlers/period_statistics.py | 15 +++------ .../coordinator/period_handlers/relaxation.py | 27 ++++++---------- .../period_handlers/shape_extension.py | 13 +++----- .../tibber_prices/coordinator/periods.py | 7 ++--- .../coordinator/price_data_manager.py | 6 ++-- .../tibber_prices/coordinator/repairs.py | 2 +- .../tibber_prices/coordinator/time_service.py | 2 +- .../tibber_prices/diagnostics.py | 2 +- .../tibber_prices/entity_utils/__init__.py | 10 ++---- .../tibber_prices/entity_utils/attributes.py | 4 +-- .../tibber_prices/interval_pool/cache.py | 10 +++--- .../tibber_prices/interval_pool/fetcher.py | 10 +++--- .../interval_pool/garbage_collector.py | 2 +- .../tibber_prices/interval_pool/manager.py | 16 +++++----- .../tibber_prices/interval_pool/routing.py | 8 ++--- custom_components/tibber_prices/migrations.py | 3 +- .../tibber_prices/number/core.py | 10 ++---- .../tibber_prices/number/definitions.py | 5 +-- .../tibber_prices/sensor/__init__.py | 5 +-- .../sensor/attributes/__init__.py | 13 +++----- .../sensor/attributes/helpers.py | 2 +- .../sensor/attributes/lifecycle.py | 8 ++--- .../sensor/attributes/metadata.py | 4 +-- .../sensor/attributes/volatility.py | 6 ++-- .../sensor/attributes/window_24h.py | 6 ++-- .../tibber_prices/sensor/calculators/base.py | 8 ++--- .../tibber_prices/sensor/definitions.py | 15 ++------- .../tibber_prices/sensor/value_getters.py | 2 +- .../tibber_prices/services/__init__.py | 6 +--- .../services/debug_clear_tomorrow.py | 2 +- .../services/find_cheapest_block.py | 14 +++------ .../services/find_cheapest_hours.py | 21 +++++-------- .../services/find_cheapest_schedule.py | 14 +++------ .../tibber_prices/services/formatters.py | 10 +++--- .../services/get_apexcharts_yaml.py | 9 ++---- .../tibber_prices/services/get_chartdata.py | 17 +++------- .../tibber_prices/services/get_price.py | 6 ++-- .../tibber_prices/services/helpers.py | 16 +++++----- .../tibber_prices/switch/core.py | 10 ++---- .../tibber_prices/utils/price.py | 16 +++++----- .../tibber_prices/utils/price_window.py | 2 +- tests/services/test_find_service_responses.py | 8 +++-- tests/services/test_search_range.py | 15 +++------ tests/test_avg_none_fallback.py | 9 ++---- tests/test_best_price_e2e.py | 4 +-- tests/test_coordinator_shutdown.py | 4 +-- tests/test_interval_pool_memory_leak.py | 18 +++-------- tests/test_interval_pool_optimization.py | 20 ++++++------ tests/test_level_filtering.py | 8 ++--- tests/test_mean_median_display.py | 11 ++----- tests/test_midnight_handler.py | 4 +-- tests/test_midnight_periods.py | 4 +-- tests/test_midnight_turnover.py | 12 ++----- tests/test_minmax_none_fallback.py | 4 +-- tests/test_next_api_poll.py | 4 +-- tests/test_peak_price_e2e.py | 4 +-- tests/test_percentage_calculations.py | 4 +-- tests/test_price_calculations.py | 6 ++-- tests/test_rating_threshold_validation.py | 5 +-- tests/test_resource_cleanup.py | 28 +++++------------ tests/test_sensor_consistency.py | 13 ++------ tests/test_sensor_timer_assignment.py | 5 +-- tests/test_time_service.py | 4 +-- tests/test_timer_scheduling.py | 8 ++--- tests/test_tomorrow_data_refresh.py | 8 ++--- tests/test_user_data_validation.py | 8 ++--- 83 files changed, 250 insertions(+), 503 deletions(-) diff --git a/custom_components/tibber_prices/api/client.py b/custom_components/tibber_prices/api/client.py index 22c8a2e..768a41c 100644 --- a/custom_components/tibber_prices/api/client.py +++ b/custom_components/tibber_prices/api/client.py @@ -4,16 +4,16 @@ from __future__ import annotations import asyncio import base64 +from datetime import datetime, timedelta import logging import re import socket -from datetime import datetime, timedelta from typing import TYPE_CHECKING, Any from zoneinfo import ZoneInfo import aiohttp -from homeassistant.util import dt as dt_utils +from homeassistant.util import dt as dt_util from .exceptions import ( TibberPricesApiClientAuthenticationError, @@ -21,12 +21,7 @@ from .exceptions import ( TibberPricesApiClientError, TibberPricesApiClientPermissionError, ) -from .helpers import ( - flatten_price_info, - prepare_headers, - verify_graphql_response, - verify_response_or_raise, -) +from .helpers import flatten_price_info, prepare_headers, verify_graphql_response, verify_response_or_raise from .queries import TibberPricesQueryType if TYPE_CHECKING: @@ -163,9 +158,7 @@ class TibberPricesApiClient: """ # Import here to avoid circular dependency (interval_pool imports TibberPricesApiClient) - from custom_components.tibber_prices.interval_pool import ( # noqa: PLC0415 - get_price_intervals_for_range, - ) + from custom_components.tibber_prices.interval_pool import get_price_intervals_for_range # noqa: PLC0415 price_info = await get_price_intervals_for_range( api_client=self, @@ -581,7 +574,7 @@ class TibberPricesApiClient: """ Calculate day before yesterday midnight in home's timezone. - CRITICAL: Uses REAL TIME (dt_utils.now()), NOT TimeService.now(). + CRITICAL: Uses REAL TIME (dt_util.now()), NOT TimeService.now(). This ensures API boundary calculations are based on actual current time, not simulated time from TimeService. @@ -594,7 +587,7 @@ class TibberPricesApiClient: """ # Get current REAL time (not TimeService) - now = dt_utils.now() + now = dt_util.now() # Convert to home's timezone or fallback to HA system timezone if home_timezone: @@ -607,10 +600,10 @@ class TibberPricesApiClient: home_timezone, error, ) - now_in_home_tz = dt_utils.as_local(now) + now_in_home_tz = dt_util.as_local(now) else: # Fallback to HA system timezone - now_in_home_tz = dt_utils.as_local(now) + now_in_home_tz = dt_util.as_local(now) # Calculate day before yesterday midnight return (now_in_home_tz - timedelta(days=2)).replace(hour=0, minute=0, second=0, microsecond=0) @@ -640,7 +633,7 @@ class TibberPricesApiClient: Timezone-aware datetime object. """ - return dt_utils.parse_datetime(timestamp_str) or dt_utils.now() + return dt_util.parse_datetime(timestamp_str) or dt_util.now() def _calculate_cursor_for_home(self, home_timezone: str | None) -> str: """ diff --git a/custom_components/tibber_prices/binary_sensor/attributes.py b/custom_components/tibber_prices/binary_sensor/attributes.py index a057444..d464977 100644 --- a/custom_components/tibber_prices/binary_sensor/attributes.py +++ b/custom_components/tibber_prices/binary_sensor/attributes.py @@ -492,7 +492,7 @@ def build_final_attributes_simple( return result -async def build_async_extra_state_attributes( # noqa: PLR0913 +async def build_async_extra_state_attributes( entity_key: str, translation_key: str | None, hass: HomeAssistant, @@ -555,7 +555,7 @@ async def build_async_extra_state_attributes( # noqa: PLR0913 return attributes or None -def build_sync_extra_state_attributes( # noqa: PLR0913 +def build_sync_extra_state_attributes( entity_key: str, translation_key: str | None, hass: HomeAssistant, diff --git a/custom_components/tibber_prices/binary_sensor/core.py b/custom_components/tibber_prices/binary_sensor/core.py index 57c865f..ce5ecda 100644 --- a/custom_components/tibber_prices/binary_sensor/core.py +++ b/custom_components/tibber_prices/binary_sensor/core.py @@ -9,10 +9,7 @@ from custom_components.tibber_prices.coordinator.core import get_connection_stat from custom_components.tibber_prices.coordinator.helpers import get_intervals_for_day_offsets from custom_components.tibber_prices.entity import TibberPricesEntity from custom_components.tibber_prices.entity_utils import get_binary_sensor_icon -from homeassistant.components.binary_sensor import ( - BinarySensorEntity, - BinarySensorEntityDescription, -) +from homeassistant.components.binary_sensor import BinarySensorEntity, BinarySensorEntityDescription from homeassistant.core import callback from homeassistant.exceptions import ConfigEntryAuthFailed from homeassistant.helpers.restore_state import RestoreEntity @@ -27,9 +24,7 @@ from .attributes import ( if TYPE_CHECKING: from collections.abc import Callable - from custom_components.tibber_prices.coordinator import ( - TibberPricesDataUpdateCoordinator, - ) + from custom_components.tibber_prices.coordinator import TibberPricesDataUpdateCoordinator from custom_components.tibber_prices.coordinator.time_service import TibberPricesTimeService diff --git a/custom_components/tibber_prices/binary_sensor/definitions.py b/custom_components/tibber_prices/binary_sensor/definitions.py index 5c24868..9503949 100644 --- a/custom_components/tibber_prices/binary_sensor/definitions.py +++ b/custom_components/tibber_prices/binary_sensor/definitions.py @@ -2,10 +2,7 @@ from __future__ import annotations -from homeassistant.components.binary_sensor import ( - BinarySensorDeviceClass, - BinarySensorEntityDescription, -) +from homeassistant.components.binary_sensor import BinarySensorDeviceClass, BinarySensorEntityDescription from homeassistant.const import EntityCategory # Period lookahead removed - icons show "waiting" state if ANY future periods exist diff --git a/custom_components/tibber_prices/config_flow.py b/custom_components/tibber_prices/config_flow.py index 1ef6a66..20ed3ca 100644 --- a/custom_components/tibber_prices/config_flow.py +++ b/custom_components/tibber_prices/config_flow.py @@ -7,9 +7,7 @@ The actual implementation is in the config_flow_handlers package. from __future__ import annotations -from .config_flow_handlers.options_flow import ( - TibberPricesOptionsFlowHandler as OptionsFlowHandler, -) +from .config_flow_handlers.options_flow import TibberPricesOptionsFlowHandler as OptionsFlowHandler from .config_flow_handlers.schemas import ( get_best_price_schema, get_options_init_schema, @@ -23,9 +21,7 @@ from .config_flow_handlers.schemas import ( get_user_schema, get_volatility_schema, ) -from .config_flow_handlers.subentry_flow import ( - TibberPricesSubentryFlowHandler as SubentryFlowHandler, -) +from .config_flow_handlers.subentry_flow import TibberPricesSubentryFlowHandler as SubentryFlowHandler from .config_flow_handlers.user_flow import TibberPricesConfigFlowHandler as ConfigFlow from .config_flow_handlers.validators import ( TibberPricesCannotConnectError, diff --git a/custom_components/tibber_prices/config_flow_handlers/__init__.py b/custom_components/tibber_prices/config_flow_handlers/__init__.py index 45ef991..bf1b8fe 100644 --- a/custom_components/tibber_prices/config_flow_handlers/__init__.py +++ b/custom_components/tibber_prices/config_flow_handlers/__init__.py @@ -20,9 +20,7 @@ Supporting modules: from __future__ import annotations # Phase 3: Import flow handlers from their new modular structure -from custom_components.tibber_prices.config_flow_handlers.options_flow import ( - TibberPricesOptionsFlowHandler, -) +from custom_components.tibber_prices.config_flow_handlers.options_flow import TibberPricesOptionsFlowHandler from custom_components.tibber_prices.config_flow_handlers.schemas import ( get_best_price_schema, get_options_init_schema, @@ -36,12 +34,8 @@ from custom_components.tibber_prices.config_flow_handlers.schemas import ( get_user_schema, get_volatility_schema, ) -from custom_components.tibber_prices.config_flow_handlers.subentry_flow import ( - TibberPricesSubentryFlowHandler, -) -from custom_components.tibber_prices.config_flow_handlers.user_flow import ( - TibberPricesConfigFlowHandler, -) +from custom_components.tibber_prices.config_flow_handlers.subentry_flow import TibberPricesSubentryFlowHandler +from custom_components.tibber_prices.config_flow_handlers.user_flow import TibberPricesConfigFlowHandler from custom_components.tibber_prices.config_flow_handlers.validators import ( TibberPricesCannotConnectError, TibberPricesInvalidAuthError, diff --git a/custom_components/tibber_prices/config_flow_handlers/schemas.py b/custom_components/tibber_prices/config_flow_handlers/schemas.py index bb45eff..122fe3f 100644 --- a/custom_components/tibber_prices/config_flow_handlers/schemas.py +++ b/custom_components/tibber_prices/config_flow_handlers/schemas.py @@ -174,7 +174,7 @@ ConfigOverrides = dict[str, dict[str, Any]] def is_field_overridden( config_key: str, - config_section: str, # noqa: ARG001 - kept for API compatibility + config_section: str, overrides: ConfigOverrides | None, ) -> bool: """ diff --git a/custom_components/tibber_prices/config_flow_handlers/user_flow.py b/custom_components/tibber_prices/config_flow_handlers/user_flow.py index d55fdc9..e5eaa5d 100644 --- a/custom_components/tibber_prices/config_flow_handlers/user_flow.py +++ b/custom_components/tibber_prices/config_flow_handlers/user_flow.py @@ -7,9 +7,7 @@ from typing import TYPE_CHECKING, Any import voluptuous as vol -from custom_components.tibber_prices.config_flow_handlers.options_flow import ( - TibberPricesOptionsFlowHandler, -) +from custom_components.tibber_prices.config_flow_handlers.options_flow import TibberPricesOptionsFlowHandler from custom_components.tibber_prices.config_flow_handlers.schemas import ( get_reauth_confirm_schema, get_select_home_schema, @@ -20,26 +18,11 @@ from custom_components.tibber_prices.config_flow_handlers.validators import ( TibberPricesInvalidAuthError, validate_api_token, ) -from custom_components.tibber_prices.const import ( - DOMAIN, - LOGGER, - get_default_options, - get_translation, -) -from homeassistant.config_entries import ( - ConfigEntry, - ConfigFlow, - ConfigFlowResult, - OptionsFlow, -) +from custom_components.tibber_prices.const import DOMAIN, LOGGER, get_default_options, get_translation +from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult, OptionsFlow from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.core import callback -from homeassistant.helpers.selector import ( - SelectOptionDict, - SelectSelector, - SelectSelectorConfig, - SelectSelectorMode, -) +from homeassistant.helpers.selector import SelectOptionDict, SelectSelector, SelectSelectorConfig, SelectSelectorMode if TYPE_CHECKING: from homeassistant.config_entries import ConfigSubentryFlow @@ -65,7 +48,7 @@ class TibberPricesConfigFlowHandler(ConfigFlow, domain=DOMAIN): @callback def async_get_supported_subentry_types( cls, - config_entry: ConfigEntry, # noqa: ARG003 + config_entry: ConfigEntry, ) -> dict[str, type[ConfigSubentryFlow]]: """Return subentries supported by this integration.""" # Temporarily disabled: Time-travel feature not yet fully implemented @@ -85,7 +68,7 @@ class TibberPricesConfigFlowHandler(ConfigFlow, domain=DOMAIN): """Return True if match_dict matches this flow.""" return bool(other_flow.get("domain") == DOMAIN) - async def async_step_reauth(self, entry_data: dict[str, Any]) -> ConfigFlowResult: # noqa: ARG002 + async def async_step_reauth(self, entry_data: dict[str, Any]) -> ConfigFlowResult: """Handle reauth flow when access token becomes invalid.""" entry_id = self.context.get("entry_id") if entry_id: @@ -295,7 +278,7 @@ class TibberPricesConfigFlowHandler(ConfigFlow, domain=DOMAIN): description_placeholders={"tibber_url": "https://developer.tibber.com"}, ) - async def async_step_select_home(self, user_input: dict | None = None) -> ConfigFlowResult: # noqa: PLR0911 + async def async_step_select_home(self, user_input: dict | None = None) -> ConfigFlowResult: """Handle home selection during initial setup.""" homes = self._viewer.get("homes", []) if self._viewer else [] diff --git a/custom_components/tibber_prices/coordinator/__init__.py b/custom_components/tibber_prices/coordinator/__init__.py index fb4a1cb..f6d1ab9 100644 --- a/custom_components/tibber_prices/coordinator/__init__.py +++ b/custom_components/tibber_prices/coordinator/__init__.py @@ -16,11 +16,7 @@ Main components: - period_handlers/: Period calculation sub-package """ -from .constants import ( - MINUTE_UPDATE_ENTITY_KEYS, - STORAGE_VERSION, - TIME_SENSITIVE_ENTITY_KEYS, -) +from .constants import MINUTE_UPDATE_ENTITY_KEYS, STORAGE_VERSION, TIME_SENSITIVE_ENTITY_KEYS from .core import TibberPricesDataUpdateCoordinator from .time_service import TibberPricesTimeService diff --git a/custom_components/tibber_prices/coordinator/constants.py b/custom_components/tibber_prices/coordinator/constants.py index 531c9e1..da26011 100644 --- a/custom_components/tibber_prices/coordinator/constants.py +++ b/custom_components/tibber_prices/coordinator/constants.py @@ -93,6 +93,18 @@ TIME_SENSITIVE_ENTITY_KEYS = frozenset( "best_price_next_start_time", "peak_price_end_time", "peak_price_next_start_time", + # Price rank sensors (rank of current/next/previous interval within a day scope) + "current_interval_price_rank_today", + "current_interval_price_rank_tomorrow", + "current_interval_price_rank_today_tomorrow", + "current_hour_price_rank_today", + "current_hour_price_rank_today_tomorrow", + "next_interval_price_rank_today", + "next_interval_price_rank_today_tomorrow", + "next_hour_price_rank_today", + "next_hour_price_rank_today_tomorrow", + "previous_interval_price_rank_today", + "previous_interval_price_rank_today_tomorrow", # Lifecycle sensor needs quarter-hour precision for state transitions: # - 23:45: turnover_pending (last interval before midnight) # - 00:00: turnover complete (after midnight API update) diff --git a/custom_components/tibber_prices/coordinator/core.py b/custom_components/tibber_prices/coordinator/core.py index 50d3206..fc50cfe 100644 --- a/custom_components/tibber_prices/coordinator/core.py +++ b/custom_components/tibber_prices/coordinator/core.py @@ -2,8 +2,8 @@ from __future__ import annotations -import logging from datetime import timedelta +import logging from typing import TYPE_CHECKING, Any from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback @@ -24,16 +24,11 @@ from custom_components.tibber_prices.api import ( TibberPricesApiClientError, ) from custom_components.tibber_prices.const import DOMAIN -from custom_components.tibber_prices.utils.price import ( - find_price_data_for_interval, -) +from custom_components.tibber_prices.utils.price import find_price_data_for_interval from homeassistant.exceptions import ConfigEntryAuthFailed from . import helpers -from .constants import ( - STORAGE_VERSION, - UPDATE_INTERVAL, -) +from .constants import STORAGE_VERSION, UPDATE_INTERVAL from .data_transformation import TibberPricesDataTransformer from .listeners import TibberPricesListenerManager from .midnight_handler import TibberPricesMidnightHandler diff --git a/custom_components/tibber_prices/coordinator/helpers.py b/custom_components/tibber_prices/coordinator/helpers.py index bd8ce97..a6d07b7 100644 --- a/custom_components/tibber_prices/coordinator/helpers.py +++ b/custom_components/tibber_prices/coordinator/helpers.py @@ -2,8 +2,8 @@ from __future__ import annotations -import logging from datetime import timedelta +import logging from typing import TYPE_CHECKING, Any from homeassistant.util import dt as dt_util diff --git a/custom_components/tibber_prices/coordinator/period_handlers/core.py b/custom_components/tibber_prices/coordinator/period_handlers/core.py index 6087149..25ba7da 100644 --- a/custom_components/tibber_prices/coordinator/period_handlers/core.py +++ b/custom_components/tibber_prices/coordinator/period_handlers/core.py @@ -11,9 +11,7 @@ if TYPE_CHECKING: from .types import TibberPricesPeriodConfig -from .outlier_filtering import ( - filter_price_outliers, -) +from .outlier_filtering import filter_price_outliers from .period_building import ( add_interval_ends, build_periods, @@ -24,9 +22,7 @@ from .period_building import ( filter_superseded_periods, split_intervals_by_day, ) -from .period_statistics import ( - extract_period_summaries, -) +from .period_statistics import extract_period_summaries from .shape_extension import extend_periods_for_shape from .types import TibberPricesThresholdConfig @@ -81,7 +77,7 @@ def calculate_periods( from .types import INDENT_L0 # noqa: PLC0415 - _LOGGER = logging.getLogger(__name__) # noqa: N806 + _LOGGER = logging.getLogger(__name__) # Extract config values reverse_sort = config.reverse_sort @@ -141,7 +137,7 @@ def calculate_periods( # User's flex setting still applies to period criteria (in_flex check). # Import details logger locally (core.py imports logger locally in function) - _LOGGER_DETAILS = logging.getLogger(__name__ + ".details") # noqa: N806 + _LOGGER_DETAILS = logging.getLogger(__name__ + ".details") outlier_flex = min(abs(flex) * 100, MAX_OUTLIER_FLEX * 100) if abs(flex) * 100 > MAX_OUTLIER_FLEX * 100: @@ -298,7 +294,7 @@ def calculate_periods( def _period_belongs_to_side( period: list[dict], side_times: set, - time: "TibberPricesTimeService", + time: TibberPricesTimeService, ) -> bool: """Return True if the majority of a period's intervals are in side_times.""" if not period: @@ -307,14 +303,14 @@ def _period_belongs_to_side( return in_side * 2 >= len(period) -def _apply_segment_forcing( # noqa: PLR0913 +def _apply_segment_forcing( all_prices_smoothed: list[dict], periods: list[list[dict]], price_context: dict[str, Any], - config: "TibberPricesPeriodConfig", + config: TibberPricesPeriodConfig, *, day_patterns_by_date: dict, - time: "TibberPricesTimeService", + time: TibberPricesTimeService, ) -> list[list[dict]]: """ Force at least segment_min_periods periods per segment for W/M-shaped days. @@ -341,7 +337,7 @@ def _apply_segment_forcing( # noqa: PLR0913 from .period_building import build_periods # noqa: PLC0415 from .types import DAY_PATTERN_DOUBLE_PEAK, DAY_PATTERN_DOUBLE_VALLEY, INDENT_L1, INDENT_L2 # noqa: PLC0415 - _LOGGER = logging.getLogger(__name__) # noqa: N806 + _LOGGER = logging.getLogger(__name__) reverse_sort = config.reverse_sort target_pattern = DAY_PATTERN_DOUBLE_PEAK if reverse_sort else DAY_PATTERN_DOUBLE_VALLEY diff --git a/custom_components/tibber_prices/coordinator/period_handlers/day_pattern.py b/custom_components/tibber_prices/coordinator/period_handlers/day_pattern.py index 3f6573d..addcc4f 100644 --- a/custom_components/tibber_prices/coordinator/period_handlers/day_pattern.py +++ b/custom_components/tibber_prices/coordinator/period_handlers/day_pattern.py @@ -344,7 +344,7 @@ def _deduplicate_extrema(extrema: list[dict[str, Any]]) -> list[dict[str, Any]]: # ─── pattern classification ─────────────────────────────────────────────────── -def _classify_pattern( # noqa: PLR0911, PLR0912 +def _classify_pattern( extrema: list[dict[str, Any]], cv_pct: float, times: list[datetime], @@ -399,7 +399,7 @@ def _classify_pattern( # noqa: PLR0911, PLR0912 return DAY_PATTERN_PEAK, confidence # ── two extrema ───────────────────────────────────────────────────────────── - if n_extrema == 2: # noqa: PLR2004 + if n_extrema == 2: if types == ["max", "min"]: return DAY_PATTERN_FALLING, 0.7 if types == ["min", "max"]: @@ -410,7 +410,7 @@ def _classify_pattern( # noqa: PLR0911, PLR0912 return DAY_PATTERN_DOUBLE_PEAK, 0.65 # ── three extrema ──────────────────────────────────────────────────────────── - if n_extrema == 3: # noqa: PLR2004 + if n_extrema == 3: # min-max-min → W-shape if types == ["min", "max", "min"]: return DAY_PATTERN_DOUBLE_VALLEY, 0.75 @@ -498,7 +498,7 @@ def _find_knee_on_flank( # Normalise so that start=(0,0) and end=(1,1) px_range = float(length) py_range = p_end - p_start - if abs(py_range) < 1e-9: # noqa: PLR2004 + if abs(py_range) < 1e-9: return None # Flat flank - no knee max_dist = 0.0 diff --git a/custom_components/tibber_prices/coordinator/period_handlers/level_filtering.py b/custom_components/tibber_prices/coordinator/period_handlers/level_filtering.py index 810ed46..19a582d 100644 --- a/custom_components/tibber_prices/coordinator/period_handlers/level_filtering.py +++ b/custom_components/tibber_prices/coordinator/period_handlers/level_filtering.py @@ -204,7 +204,7 @@ def check_interval_criteria( if scale_factor < SCALE_FACTOR_WARNING_THRESHOLD: import logging # noqa: PLC0415 - _LOGGER = logging.getLogger(f"{__name__}.details") # noqa: N806 + _LOGGER = logging.getLogger(f"{__name__}.details") _LOGGER.debug( "High flex %.1f%% detected: Reducing min_distance %.1f%% → %.1f%% (scale %.2f)", flex_abs * 100, diff --git a/custom_components/tibber_prices/coordinator/period_handlers/outlier_filtering.py b/custom_components/tibber_prices/coordinator/period_handlers/outlier_filtering.py index 69d6356..7d08d75 100644 --- a/custom_components/tibber_prices/coordinator/period_handlers/outlier_filtering.py +++ b/custom_components/tibber_prices/coordinator/period_handlers/outlier_filtering.py @@ -14,8 +14,8 @@ Uses statistical methods: from __future__ import annotations -import logging from datetime import datetime +import logging from typing import NamedTuple from custom_components.tibber_prices.utils.price import calculate_coefficient_of_variation diff --git a/custom_components/tibber_prices/coordinator/period_handlers/period_building.py b/custom_components/tibber_prices/coordinator/period_handlers/period_building.py index 9b828d6..5f099ee 100644 --- a/custom_components/tibber_prices/coordinator/period_handlers/period_building.py +++ b/custom_components/tibber_prices/coordinator/period_handlers/period_building.py @@ -2,8 +2,8 @@ from __future__ import annotations -import logging from datetime import date, datetime, timedelta +import logging from typing import TYPE_CHECKING, Any from custom_components.tibber_prices.const import PRICE_LEVEL_MAPPING @@ -11,11 +11,7 @@ from custom_components.tibber_prices.const import PRICE_LEVEL_MAPPING if TYPE_CHECKING: from custom_components.tibber_prices.coordinator.time_service import TibberPricesTimeService -from .level_filtering import ( - apply_level_filter, - check_interval_criteria, - compute_geometric_flex_bonus, -) +from .level_filtering import apply_level_filter, check_interval_criteria, compute_geometric_flex_bonus from .types import TibberPricesIntervalCriteria _LOGGER = logging.getLogger(__name__) @@ -54,7 +50,7 @@ def calculate_reference_prices(intervals_by_day: dict[date, list[dict]], *, reve return ref_prices -def build_periods( # noqa: PLR0913, PLR0915, PLR0912 - Complex period building logic requires many arguments, statements, and branches +def build_periods( all_prices: list[dict], price_context: dict[str, Any], *, diff --git a/custom_components/tibber_prices/coordinator/period_handlers/period_statistics.py b/custom_components/tibber_prices/coordinator/period_handlers/period_statistics.py index dbd9289..d0bae3b 100644 --- a/custom_components/tibber_prices/coordinator/period_handlers/period_statistics.py +++ b/custom_components/tibber_prices/coordinator/period_handlers/period_statistics.py @@ -9,11 +9,7 @@ if TYPE_CHECKING: from custom_components.tibber_prices.coordinator.time_service import TibberPricesTimeService - from .types import ( - TibberPricesPeriodData, - TibberPricesPeriodStatistics, - TibberPricesThresholdConfig, - ) + from .types import TibberPricesPeriodData, TibberPricesPeriodStatistics, TibberPricesThresholdConfig from custom_components.tibber_prices.utils.average import calculate_median from custom_components.tibber_prices.utils.price import ( @@ -272,7 +268,7 @@ def _add_interval_flag_counts(summary: dict, period: list[dict], *, geo_extensio summary["segment_forced"] = True -def extract_period_summaries( # noqa: PLR0912, PLR0915 - CV pre-check for geo-extension adds necessary branches/statements +def extract_period_summaries( periods: list[list[dict]], all_prices: list[dict], price_context: dict[str, Any], @@ -302,10 +298,7 @@ def extract_period_summaries( # noqa: PLR0912, PLR0915 - CV pre-check for geo-e time: TibberPricesTimeService instance (required). """ - from .types import ( # noqa: PLC0415 - Avoid circular import - TibberPricesPeriodData, - TibberPricesPeriodStatistics, - ) + from .types import TibberPricesPeriodData, TibberPricesPeriodStatistics # noqa: PLC0415 - Avoid circular import # Build lookup dictionary for full price data by timestamp price_lookup: dict[str, dict] = {} @@ -344,7 +337,7 @@ def extract_period_summaries( # noqa: PLR0912, PLR0915 - CV pre-check for geo-e if cv_fails: base_period = _strip_geo_from_edges(period) if base_period: - period = base_period # noqa: PLW2901 - intentional period replacement + period = base_period geo_extension_status = "attempted" else: geo_extension_status = "active" diff --git a/custom_components/tibber_prices/coordinator/period_handlers/relaxation.py b/custom_components/tibber_prices/coordinator/period_handlers/relaxation.py index 4006a29..1b5be4d 100644 --- a/custom_components/tibber_prices/coordinator/period_handlers/relaxation.py +++ b/custom_components/tibber_prices/coordinator/period_handlers/relaxation.py @@ -2,8 +2,8 @@ from __future__ import annotations -import logging from datetime import timedelta +import logging from typing import TYPE_CHECKING, Any if TYPE_CHECKING: @@ -14,10 +14,7 @@ if TYPE_CHECKING: from custom_components.tibber_prices.utils.price import calculate_coefficient_of_variation, calculate_iqr_stats -from .period_overlap import ( - recalculate_period_metadata, - resolve_period_overlaps, -) +from .period_overlap import recalculate_period_metadata, resolve_period_overlaps from .types import ( INDENT_L0, INDENT_L1, @@ -486,7 +483,7 @@ def _compute_day_effective_min( price_values = [float(p["total"]) for p in day_prices if p.get("total") is not None] - if len(price_values) < 2: # noqa: PLR2004 - need at least 2 prices for any metric + if len(price_values) < 2: day_effective_min[day] = min_periods continue @@ -532,7 +529,7 @@ def _compute_day_effective_min( return day_effective_min, flat_day_count -def calculate_periods_with_relaxation( # noqa: PLR0912, PLR0913, PLR0915 - Per-day relaxation requires many parameters and branches +def calculate_periods_with_relaxation( all_prices: list[dict], *, config: TibberPricesPeriodConfig, @@ -584,12 +581,8 @@ def calculate_periods_with_relaxation( # noqa: PLR0912, PLR0913, PLR0915 - Per- """ # Import here to avoid circular dependency - from .core import ( # noqa: PLC0415 - calculate_periods, - ) - from .period_building import ( # noqa: PLC0415 - filter_superseded_periods, - ) + from .core import calculate_periods # noqa: PLC0415 + from .period_building import filter_superseded_periods # noqa: PLC0415 # Compact INFO-level summary period_type = "PEAK PRICE" if config.reverse_sort else "BEST PRICE" @@ -691,7 +684,7 @@ def calculate_periods_with_relaxation( # noqa: PLR0912, PLR0913, PLR0915 - Per- any_normal_day = False for day_prices in prices_by_day.values(): prices = [float(p["total"]) for p in day_prices if p.get("total") is not None] - if len(prices) >= 2: # noqa: PLR2004 + if len(prices) >= 2: day_min = min(prices) day_avg = sum(prices) / len(prices) span = abs(day_avg - day_min) @@ -877,7 +870,7 @@ def calculate_periods_with_relaxation( # noqa: PLR0912, PLR0913, PLR0915 - Per- return final_result -def relax_all_prices( # noqa: PLR0913 - Comprehensive filter relaxation requires many parameters and statements +def relax_all_prices( all_prices: list[dict], config: TibberPricesPeriodConfig, min_periods: int, @@ -914,9 +907,7 @@ def relax_all_prices( # noqa: PLR0913 - Comprehensive filter relaxation require """ # Import here to avoid circular dependency - from .core import ( # noqa: PLC0415 - calculate_periods, - ) + from .core import calculate_periods # noqa: PLC0415 flex_increment = 0.03 # 3% per step (hard-coded for reliability) base_flex = abs(config.flex) diff --git a/custom_components/tibber_prices/coordinator/period_handlers/shape_extension.py b/custom_components/tibber_prices/coordinator/period_handlers/shape_extension.py index 795ecee..9389401 100644 --- a/custom_components/tibber_prices/coordinator/period_handlers/shape_extension.py +++ b/custom_components/tibber_prices/coordinator/period_handlers/shape_extension.py @@ -20,8 +20,8 @@ created by this step. from __future__ import annotations -import statistics from datetime import timedelta +import statistics from typing import TYPE_CHECKING, Any from custom_components.tibber_prices.const import ( @@ -30,10 +30,7 @@ from custom_components.tibber_prices.const import ( PRICE_LEVEL_VERY_CHEAP, PRICE_LEVEL_VERY_EXPENSIVE, ) -from custom_components.tibber_prices.utils.price import ( - aggregate_period_levels, - aggregate_period_ratings, -) +from custom_components.tibber_prices.utils.price import aggregate_period_levels, aggregate_period_ratings from .period_statistics import ( calculate_aggregated_rating_difference, @@ -51,7 +48,7 @@ if TYPE_CHECKING: _INTERVAL_DURATION = timedelta(minutes=15) -def extend_periods_for_shape( # noqa: PLR0913 - Extension requires all context params +def extend_periods_for_shape( periods: list[dict[str, Any]], all_prices: list[dict[str, Any]], price_context: dict[str, Any], @@ -164,7 +161,7 @@ def _walk_contiguous( return additions -def _extend_period_edges( # noqa: PLR0913 - Period edge extension requires many args +def _extend_period_edges( period: dict[str, Any], interval_index: dict[datetime, dict[str, Any]], *, @@ -253,7 +250,7 @@ def _extend_period_edges( # noqa: PLR0913 - Period edge extension requires many # ── recalculate volatility (coefficient of variation) ──────────────────── prices_for_vol = [float(p["total"]) for p in all_period_intervals if "total" in p] cv_pct: float | None = None - if len(prices_for_vol) >= 2: # noqa: PLR2004 + if len(prices_for_vol) >= 2: mean_p = statistics.mean(prices_for_vol) if mean_p > 0: cv_pct = round(statistics.stdev(prices_for_vol) / mean_p * 100, 1) diff --git a/custom_components/tibber_prices/coordinator/periods.py b/custom_components/tibber_prices/coordinator/periods.py index 8236eec..9a4a986 100644 --- a/custom_components/tibber_prices/coordinator/periods.py +++ b/custom_components/tibber_prices/coordinator/periods.py @@ -7,8 +7,8 @@ gap tolerance, and coordination of the period_handlers calculation functions. from __future__ import annotations -import logging from datetime import date, timedelta +import logging from typing import TYPE_CHECKING, Any from custom_components.tibber_prices import const as _const @@ -20,10 +20,7 @@ if TYPE_CHECKING: from homeassistant.config_entries import ConfigEntry from .helpers import get_intervals_for_day_offsets -from .period_handlers import ( - TibberPricesPeriodConfig, - calculate_periods_with_relaxation, -) +from .period_handlers import TibberPricesPeriodConfig, calculate_periods_with_relaxation _LOGGER = logging.getLogger(__name__) diff --git a/custom_components/tibber_prices/coordinator/price_data_manager.py b/custom_components/tibber_prices/coordinator/price_data_manager.py index 8095633..a7fdb34 100644 --- a/custom_components/tibber_prices/coordinator/price_data_manager.py +++ b/custom_components/tibber_prices/coordinator/price_data_manager.py @@ -26,8 +26,8 @@ source of truth. This module only caches user_data for daily refresh cycle. from __future__ import annotations -import logging from datetime import timedelta +import logging from typing import TYPE_CHECKING, Any from custom_components.tibber_prices.api import ( @@ -71,7 +71,7 @@ class TibberPricesPriceDataManager: This class orchestrates WHEN to fetch and processes the results. """ - def __init__( # noqa: PLR0913 + def __init__( self, api: TibberPricesApiClient, store: Any, @@ -178,7 +178,7 @@ class TibberPricesPriceDataManager: ) await cache.save_cache(self._store, cache_data, self._log_prefix) - def _validate_user_data(self, user_data: dict, home_id: str) -> bool: # noqa: PLR0911 + def _validate_user_data(self, user_data: dict, home_id: str) -> bool: """ Validate user data completeness. diff --git a/custom_components/tibber_prices/coordinator/repairs.py b/custom_components/tibber_prices/coordinator/repairs.py index d0d903a..2be7e96 100644 --- a/custom_components/tibber_prices/coordinator/repairs.py +++ b/custom_components/tibber_prices/coordinator/repairs.py @@ -57,7 +57,7 @@ class TibberPricesRepairManager: async def check_tomorrow_data_availability( self, - has_tomorrow_data: bool, # noqa: FBT001 - Clear meaning in context + has_tomorrow_data: bool, current_time: datetime, ) -> None: """ diff --git a/custom_components/tibber_prices/coordinator/time_service.py b/custom_components/tibber_prices/coordinator/time_service.py index 503eb1d..09ef4e1 100644 --- a/custom_components/tibber_prices/coordinator/time_service.py +++ b/custom_components/tibber_prices/coordinator/time_service.py @@ -43,8 +43,8 @@ scheduling delays. It is NOT used for Timer #1's offset tracking. from __future__ import annotations -import math from datetime import datetime, timedelta +import math from typing import TYPE_CHECKING from homeassistant.util import dt as dt_util diff --git a/custom_components/tibber_prices/diagnostics.py b/custom_components/tibber_prices/diagnostics.py index f7cbb89..daf8d36 100644 --- a/custom_components/tibber_prices/diagnostics.py +++ b/custom_components/tibber_prices/diagnostics.py @@ -16,7 +16,7 @@ if TYPE_CHECKING: async def async_get_config_entry_diagnostics( - hass: HomeAssistant, # noqa: ARG001 + hass: HomeAssistant, entry: TibberPricesConfigEntry, ) -> dict[str, Any]: """Return diagnostics for a config entry.""" diff --git a/custom_components/tibber_prices/entity_utils/__init__.py b/custom_components/tibber_prices/entity_utils/__init__.py index 108611a..71e174b 100644 --- a/custom_components/tibber_prices/entity_utils/__init__.py +++ b/custom_components/tibber_prices/entity_utils/__init__.py @@ -18,15 +18,9 @@ For pure data transformation (no HA dependencies), see utils/ package. from __future__ import annotations -from .attributes import ( - add_description_attributes, - async_add_description_attributes, -) +from .attributes import add_description_attributes, async_add_description_attributes from .colors import add_icon_color_attribute, get_icon_color -from .helpers import ( - find_rolling_hour_center_index, - get_price_value, -) +from .helpers import find_rolling_hour_center_index, get_price_value from .icons import ( get_binary_sensor_icon, get_dynamic_icon, diff --git a/custom_components/tibber_prices/entity_utils/attributes.py b/custom_components/tibber_prices/entity_utils/attributes.py index f3aa78f..9f2af74 100644 --- a/custom_components/tibber_prices/entity_utils/attributes.py +++ b/custom_components/tibber_prices/entity_utils/attributes.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: from ..data import TibberPricesConfigEntry # noqa: TID252 -def add_description_attributes( # noqa: PLR0913, PLR0912 +def add_description_attributes( attributes: dict, platform: str, translation_key: str | None, @@ -113,7 +113,7 @@ def add_description_attributes( # noqa: PLR0913, PLR0912 attributes[key] = value -async def async_add_description_attributes( # noqa: PLR0913, PLR0912 +async def async_add_description_attributes( attributes: dict, platform: str, translation_key: str | None, diff --git a/custom_components/tibber_prices/interval_pool/cache.py b/custom_components/tibber_prices/interval_pool/cache.py index 838670c..249fb45 100644 --- a/custom_components/tibber_prices/interval_pool/cache.py +++ b/custom_components/tibber_prices/interval_pool/cache.py @@ -2,16 +2,14 @@ from __future__ import annotations -import logging from datetime import datetime, timedelta +import logging from typing import TYPE_CHECKING, Any -from homeassistant.util import dt as dt_utils +from homeassistant.util import dt as dt_util if TYPE_CHECKING: - from custom_components.tibber_prices.coordinator.time_service import ( - TibberPricesTimeService, - ) + from custom_components.tibber_prices.coordinator.time_service import TibberPricesTimeService _LOGGER = logging.getLogger(__name__) _LOGGER_DETAILS = logging.getLogger(__name__ + ".details") @@ -114,7 +112,7 @@ class TibberPricesIntervalPoolFetchGroupCache: """ # Use TimeService if available (Time Machine support), else real time - now = self._time_service.now() if self._time_service else dt_utils.now() + now = self._time_service.now() if self._time_service else dt_util.now() today_date_str = now.date().isoformat() # Check cache validity (invalidate daily) diff --git a/custom_components/tibber_prices/interval_pool/fetcher.py b/custom_components/tibber_prices/interval_pool/fetcher.py index 31a4c0e..1c7eb5e 100644 --- a/custom_components/tibber_prices/interval_pool/fetcher.py +++ b/custom_components/tibber_prices/interval_pool/fetcher.py @@ -2,11 +2,11 @@ from __future__ import annotations -import logging from datetime import UTC, datetime, timedelta +import logging from typing import TYPE_CHECKING, Any -from homeassistant.util import dt as dt_utils +from homeassistant.util import dt as dt_util if TYPE_CHECKING: from collections.abc import Callable @@ -287,11 +287,9 @@ class TibberPricesIntervalPoolFetcher: """ # Import here to avoid circular dependency - from custom_components.tibber_prices.interval_pool.routing import ( # noqa: PLC0415 - get_price_intervals_for_range, - ) + from custom_components.tibber_prices.interval_pool.routing import get_price_intervals_for_range # noqa: PLC0415 - fetch_time_iso = dt_utils.now().isoformat() + fetch_time_iso = dt_util.now().isoformat() all_fetched_intervals = [] for idx, (missing_start_iso, missing_end_iso) in enumerate(missing_ranges, start=1): diff --git a/custom_components/tibber_prices/interval_pool/garbage_collector.py b/custom_components/tibber_prices/interval_pool/garbage_collector.py index 323c40e..0e15362 100644 --- a/custom_components/tibber_prices/interval_pool/garbage_collector.py +++ b/custom_components/tibber_prices/interval_pool/garbage_collector.py @@ -2,8 +2,8 @@ from __future__ import annotations -import logging from datetime import datetime +import logging from typing import TYPE_CHECKING, Any if TYPE_CHECKING: diff --git a/custom_components/tibber_prices/interval_pool/manager.py b/custom_components/tibber_prices/interval_pool/manager.py index 92728df..97a4eb3 100644 --- a/custom_components/tibber_prices/interval_pool/manager.py +++ b/custom_components/tibber_prices/interval_pool/manager.py @@ -4,8 +4,8 @@ from __future__ import annotations import asyncio import contextlib -import logging from datetime import UTC, datetime, timedelta +import logging from typing import TYPE_CHECKING, Any from zoneinfo import ZoneInfo @@ -13,7 +13,7 @@ from custom_components.tibber_prices.api.exceptions import ( TibberPricesApiClientCommunicationError, TibberPricesApiClientError, ) -from homeassistant.util import dt as dt_utils +from homeassistant.util import dt as dt_util from .cache import TibberPricesIntervalPoolFetchGroupCache from .fetcher import TibberPricesIntervalPoolFetcher @@ -23,9 +23,7 @@ from .storage import async_save_pool_state if TYPE_CHECKING: from custom_components.tibber_prices.api.client import TibberPricesApiClient - from custom_components.tibber_prices.coordinator.time_service import ( - TibberPricesTimeService, - ) + from custom_components.tibber_prices.coordinator.time_service import TibberPricesTimeService _LOGGER = logging.getLogger(__name__) _LOGGER_DETAILS = logging.getLogger(__name__ + ".details") @@ -101,7 +99,7 @@ class TibberPricesIntervalPool: hass: HomeAssistant instance for auto-save (optional). entry_id: Config entry ID for auto-save (optional). time_service: TimeService for time-travel support (optional). - If None, uses real time (dt_utils.now()). + If None, uses real time (dt_util.now()). """ self._home_id = home_id @@ -206,7 +204,7 @@ class TibberPricesIntervalPool: # Fetch missing ranges from API api_fetch_failed = False if missing_ranges: - fetch_time_iso = dt_utils.now().isoformat() + fetch_time_iso = dt_util.now().isoformat() try: # Fetch with callback for immediate caching @@ -301,7 +299,7 @@ class TibberPricesIntervalPool: # Calculate range in home's timezone tz = ZoneInfo(tz_str) if tz_str else None - now = self._time_service.now() if self._time_service else dt_utils.now() + now = self._time_service.now() if self._time_service else dt_util.now() now_local = now.astimezone(tz) if tz else now # Day before yesterday 00:00 (start) - same for both fetch and return @@ -598,7 +596,7 @@ class TibberPricesIntervalPool: result = [] # Determine interval step (15 min post-2025-10-01, 60 min pre) - resolution_change_naive = datetime(2025, 10, 1) # noqa: DTZ001 + resolution_change_naive = datetime(2025, 10, 1) interval_minutes = INTERVAL_QUARTER_HOURLY if current_naive >= resolution_change_naive else INTERVAL_HOURLY fetch_groups = self._cache.get_fetch_groups() diff --git a/custom_components/tibber_prices/interval_pool/routing.py b/custom_components/tibber_prices/interval_pool/routing.py index 3ec2ca3..5996574 100644 --- a/custom_components/tibber_prices/interval_pool/routing.py +++ b/custom_components/tibber_prices/interval_pool/routing.py @@ -7,7 +7,7 @@ This module handles intelligent routing between different Tibber API endpoints: - PRICE_INFO_RANGE: Historical data (before "day before yesterday midnight") - Automatic splitting and merging when range spans the boundary -CRITICAL: Uses REAL TIME (dt_utils.now()) for API boundary calculation, +CRITICAL: Uses REAL TIME (dt_util.now()) for API boundary calculation, NOT TimeService.now() which may be shifted for internal simulation. """ @@ -17,7 +17,7 @@ import logging from typing import TYPE_CHECKING, Any from custom_components.tibber_prices.api.exceptions import TibberPricesApiClientError -from homeassistant.util import dt as dt_utils +from homeassistant.util import dt as dt_util if TYPE_CHECKING: from datetime import datetime @@ -43,7 +43,7 @@ async def get_price_intervals_for_range( - PRICE_INFO: For intervals from "day before yesterday midnight" onwards - Both: If range spans across the boundary, splits the request - CRITICAL: Uses REAL TIME (dt_utils.now()) for API boundary calculation, + CRITICAL: Uses REAL TIME (dt_util.now()) for API boundary calculation, NOT TimeService.now() which may be shifted for internal simulation. This ensures predictable API responses. @@ -173,7 +173,7 @@ def _parse_timestamp(timestamp_str: str) -> datetime: ValueError: If timestamp string cannot be parsed. """ - result = dt_utils.parse_datetime(timestamp_str) + result = dt_util.parse_datetime(timestamp_str) if result is None: msg = f"Failed to parse timestamp: {timestamp_str}" raise ValueError(msg) diff --git a/custom_components/tibber_prices/migrations.py b/custom_components/tibber_prices/migrations.py index a5bc4ca..d28ad7e 100644 --- a/custom_components/tibber_prices/migrations.py +++ b/custom_components/tibber_prices/migrations.py @@ -17,8 +17,7 @@ import logging from typing import TYPE_CHECKING from homeassistant.core import callback -from homeassistant.helpers import entity_registry as er -from homeassistant.helpers import issue_registry as ir +from homeassistant.helpers import entity_registry as er, issue_registry as ir from .const import DOMAIN diff --git a/custom_components/tibber_prices/number/core.py b/custom_components/tibber_prices/number/core.py index 522741d..cfe4084 100644 --- a/custom_components/tibber_prices/number/core.py +++ b/custom_components/tibber_prices/number/core.py @@ -11,19 +11,13 @@ from __future__ import annotations import logging from typing import TYPE_CHECKING, Any -from custom_components.tibber_prices.const import ( - DOMAIN, - get_home_type_translation, - get_translation, -) +from custom_components.tibber_prices.const import DOMAIN, get_home_type_translation, get_translation from homeassistant.components.number import NumberEntity, RestoreNumber from homeassistant.core import callback from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo if TYPE_CHECKING: - from custom_components.tibber_prices.coordinator import ( - TibberPricesDataUpdateCoordinator, - ) + from custom_components.tibber_prices.coordinator import TibberPricesDataUpdateCoordinator from .definitions import TibberPricesNumberEntityDescription diff --git a/custom_components/tibber_prices/number/definitions.py b/custom_components/tibber_prices/number/definitions.py index a7a12c1..b410f4a 100644 --- a/custom_components/tibber_prices/number/definitions.py +++ b/custom_components/tibber_prices/number/definitions.py @@ -13,10 +13,7 @@ from __future__ import annotations from dataclasses import dataclass -from homeassistant.components.number import ( - NumberEntityDescription, - NumberMode, -) +from homeassistant.components.number import NumberEntityDescription, NumberMode from homeassistant.const import PERCENTAGE, EntityCategory diff --git a/custom_components/tibber_prices/sensor/__init__.py b/custom_components/tibber_prices/sensor/__init__.py index b41fccf..c898f6e 100644 --- a/custom_components/tibber_prices/sensor/__init__.py +++ b/custom_components/tibber_prices/sensor/__init__.py @@ -17,10 +17,7 @@ from __future__ import annotations from typing import TYPE_CHECKING -from custom_components.tibber_prices.const import ( - CONF_CURRENCY_DISPLAY_MODE, - DISPLAY_MODE_BASE, -) +from custom_components.tibber_prices.const import CONF_CURRENCY_DISPLAY_MODE, DISPLAY_MODE_BASE from .core import TibberPricesSensor from .definitions import ENTITY_DESCRIPTIONS diff --git a/custom_components/tibber_prices/sensor/attributes/__init__.py b/custom_components/tibber_prices/sensor/attributes/__init__.py index 32bceb7..492fb47 100644 --- a/custom_components/tibber_prices/sensor/attributes/__init__.py +++ b/custom_components/tibber_prices/sensor/attributes/__init__.py @@ -10,10 +10,7 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any -from custom_components.tibber_prices.entity_utils import ( - add_description_attributes, - add_icon_color_attribute, -) +from custom_components.tibber_prices.entity_utils import add_description_attributes, add_icon_color_attribute from custom_components.tibber_prices.sensor.types import ( DailyStatPriceAttributes, DailyStatRatingAttributes, @@ -32,9 +29,7 @@ from custom_components.tibber_prices.sensor.types import ( ) if TYPE_CHECKING: - from custom_components.tibber_prices.coordinator.core import ( - TibberPricesDataUpdateCoordinator, - ) + from custom_components.tibber_prices.coordinator.core import TibberPricesDataUpdateCoordinator from custom_components.tibber_prices.coordinator.time_service import TibberPricesTimeService from custom_components.tibber_prices.data import TibberPricesConfigEntry from homeassistant.core import HomeAssistant @@ -74,7 +69,7 @@ __all__ = [ ] -def build_sensor_attributes( # noqa: PLR0912 +def build_sensor_attributes( key: str, coordinator: TibberPricesDataUpdateCoordinator, native_value: Any, @@ -228,7 +223,7 @@ def build_sensor_attributes( # noqa: PLR0912 return attributes or None -def build_extra_state_attributes( # noqa: PLR0913 +def build_extra_state_attributes( entity_key: str, translation_key: str | None, hass: HomeAssistant, diff --git a/custom_components/tibber_prices/sensor/attributes/helpers.py b/custom_components/tibber_prices/sensor/attributes/helpers.py index f5a041e..3fa41a7 100644 --- a/custom_components/tibber_prices/sensor/attributes/helpers.py +++ b/custom_components/tibber_prices/sensor/attributes/helpers.py @@ -13,7 +13,7 @@ def add_alternate_average_attribute( cached_data: dict, base_key: str, *, - config_entry: TibberPricesConfigEntry, # noqa: ARG001 + config_entry: TibberPricesConfigEntry, ) -> None: """ Add both average values (mean and median) as attributes. diff --git a/custom_components/tibber_prices/sensor/attributes/lifecycle.py b/custom_components/tibber_prices/sensor/attributes/lifecycle.py index fb524a5..0fc4c28 100644 --- a/custom_components/tibber_prices/sensor/attributes/lifecycle.py +++ b/custom_components/tibber_prices/sensor/attributes/lifecycle.py @@ -25,12 +25,8 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any if TYPE_CHECKING: - from custom_components.tibber_prices.coordinator.core import ( - TibberPricesDataUpdateCoordinator, - ) - from custom_components.tibber_prices.sensor.calculators.lifecycle import ( - TibberPricesLifecycleCalculator, - ) + from custom_components.tibber_prices.coordinator.core import TibberPricesDataUpdateCoordinator + from custom_components.tibber_prices.sensor.calculators.lifecycle import TibberPricesLifecycleCalculator def build_lifecycle_attributes( diff --git a/custom_components/tibber_prices/sensor/attributes/metadata.py b/custom_components/tibber_prices/sensor/attributes/metadata.py index bbdc13c..35d93f8 100644 --- a/custom_components/tibber_prices/sensor/attributes/metadata.py +++ b/custom_components/tibber_prices/sensor/attributes/metadata.py @@ -7,9 +7,7 @@ from typing import TYPE_CHECKING, Any from custom_components.tibber_prices.utils.price import find_price_data_for_interval if TYPE_CHECKING: - from custom_components.tibber_prices.coordinator.core import ( - TibberPricesDataUpdateCoordinator, - ) + from custom_components.tibber_prices.coordinator.core import TibberPricesDataUpdateCoordinator from custom_components.tibber_prices.coordinator.time_service import TibberPricesTimeService diff --git a/custom_components/tibber_prices/sensor/attributes/volatility.py b/custom_components/tibber_prices/sensor/attributes/volatility.py index f92d310..8517ee9 100644 --- a/custom_components/tibber_prices/sensor/attributes/volatility.py +++ b/custom_components/tibber_prices/sensor/attributes/volatility.py @@ -16,7 +16,7 @@ def add_volatility_attributes( attributes: dict, cached_data: dict, *, - time: TibberPricesTimeService, # noqa: ARG001 + time: TibberPricesTimeService, ) -> None: """ Add attributes for volatility sensors. @@ -197,9 +197,7 @@ def add_percentile_rank_attributes( coordinator_data = cached_data.get("coordinator_data") if coordinator_data: - from custom_components.tibber_prices.coordinator.helpers import ( # noqa: PLC0415 - get_intervals_for_day_offsets, - ) + from custom_components.tibber_prices.coordinator.helpers import get_intervals_for_day_offsets # noqa: PLC0415 all_intervals = get_intervals_for_day_offsets(coordinator_data, [-1, 0, 1]) now = time.now() diff --git a/custom_components/tibber_prices/sensor/attributes/window_24h.py b/custom_components/tibber_prices/sensor/attributes/window_24h.py index dcb37a2..fca493f 100644 --- a/custom_components/tibber_prices/sensor/attributes/window_24h.py +++ b/custom_components/tibber_prices/sensor/attributes/window_24h.py @@ -7,9 +7,7 @@ from typing import TYPE_CHECKING from custom_components.tibber_prices.coordinator.helpers import get_intervals_for_day_offsets if TYPE_CHECKING: - from custom_components.tibber_prices.coordinator.core import ( - TibberPricesDataUpdateCoordinator, - ) + from custom_components.tibber_prices.coordinator.core import TibberPricesDataUpdateCoordinator from custom_components.tibber_prices.coordinator.time_service import TibberPricesTimeService from custom_components.tibber_prices.data import TibberPricesConfigEntry @@ -43,7 +41,7 @@ def _update_extreme_interval(extreme_interval: dict | None, price_data: dict, ke return price_data if is_new_extreme else extreme_interval -def add_average_price_attributes( # noqa: PLR0913 +def add_average_price_attributes( attributes: dict, key: str, coordinator: TibberPricesDataUpdateCoordinator, diff --git a/custom_components/tibber_prices/sensor/calculators/base.py b/custom_components/tibber_prices/sensor/calculators/base.py index 69a58ca..019a086 100644 --- a/custom_components/tibber_prices/sensor/calculators/base.py +++ b/custom_components/tibber_prices/sensor/calculators/base.py @@ -4,14 +4,10 @@ from __future__ import annotations from typing import TYPE_CHECKING, Any -from custom_components.tibber_prices.coordinator.helpers import ( - get_intervals_for_day_offsets, -) +from custom_components.tibber_prices.coordinator.helpers import get_intervals_for_day_offsets if TYPE_CHECKING: - from custom_components.tibber_prices.coordinator import ( - TibberPricesDataUpdateCoordinator, - ) + from custom_components.tibber_prices.coordinator import TibberPricesDataUpdateCoordinator from custom_components.tibber_prices.data import TibberPricesConfigEntry from homeassistant.core import HomeAssistant diff --git a/custom_components/tibber_prices/sensor/definitions.py b/custom_components/tibber_prices/sensor/definitions.py index 9802689..efa8635 100644 --- a/custom_components/tibber_prices/sensor/definitions.py +++ b/custom_components/tibber_prices/sensor/definitions.py @@ -18,19 +18,8 @@ Organization by calculation pattern: from __future__ import annotations -from homeassistant.components.sensor import ( - SensorDeviceClass, - SensorEntityDescription, - SensorStateClass, -) -from homeassistant.const import ( - PERCENTAGE, - EntityCategory, - UnitOfArea, - UnitOfElectricCurrent, - UnitOfEnergy, - UnitOfTime, -) +from homeassistant.components.sensor import SensorDeviceClass, SensorEntityDescription, SensorStateClass +from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfArea, UnitOfElectricCurrent, UnitOfEnergy, UnitOfTime # ============================================================================ # SENSOR DEFINITIONS - Grouped by calculation method diff --git a/custom_components/tibber_prices/sensor/value_getters.py b/custom_components/tibber_prices/sensor/value_getters.py index 946fcac..5390f02 100644 --- a/custom_components/tibber_prices/sensor/value_getters.py +++ b/custom_components/tibber_prices/sensor/value_getters.py @@ -30,7 +30,7 @@ if TYPE_CHECKING: from custom_components.tibber_prices.sensor.calculators.window_24h import TibberPricesWindow24hCalculator -def get_value_getter_mapping( # noqa: PLR0913 - needs all calculators as parameters +def get_value_getter_mapping( interval_calculator: TibberPricesIntervalCalculator, rolling_hour_calculator: TibberPricesRollingHourCalculator, daily_stat_calculator: TibberPricesDailyStatCalculator, diff --git a/custom_components/tibber_prices/services/__init__.py b/custom_components/tibber_prices/services/__init__.py index 2092600..55b561b 100644 --- a/custom_components/tibber_prices/services/__init__.py +++ b/custom_components/tibber_prices/services/__init__.py @@ -50,11 +50,7 @@ from .find_most_expensive_hours import ( FIND_MOST_EXPENSIVE_HOURS_SERVICE_SCHEMA, handle_find_most_expensive_hours, ) -from .get_apexcharts_yaml import ( - APEXCHARTS_SERVICE_SCHEMA, - APEXCHARTS_YAML_SERVICE_NAME, - handle_apexcharts_yaml, -) +from .get_apexcharts_yaml import APEXCHARTS_SERVICE_SCHEMA, APEXCHARTS_YAML_SERVICE_NAME, handle_apexcharts_yaml from .get_chartdata import CHARTDATA_SERVICE_NAME, CHARTDATA_SERVICE_SCHEMA, handle_chartdata from .get_price import GET_PRICE_SERVICE_NAME, GET_PRICE_SERVICE_SCHEMA, handle_get_price from .refresh_user_data import ( diff --git a/custom_components/tibber_prices/services/debug_clear_tomorrow.py b/custom_components/tibber_prices/services/debug_clear_tomorrow.py index 7ecd91d..2009fd8 100644 --- a/custom_components/tibber_prices/services/debug_clear_tomorrow.py +++ b/custom_components/tibber_prices/services/debug_clear_tomorrow.py @@ -20,8 +20,8 @@ After calling this service: from __future__ import annotations -import logging from datetime import datetime, timedelta +import logging from typing import TYPE_CHECKING, Any import voluptuous as vol diff --git a/custom_components/tibber_prices/services/find_cheapest_block.py b/custom_components/tibber_prices/services/find_cheapest_block.py index 9414cba..61e3857 100644 --- a/custom_components/tibber_prices/services/find_cheapest_block.py +++ b/custom_components/tibber_prices/services/find_cheapest_block.py @@ -8,25 +8,21 @@ machine, dryer). from __future__ import annotations +from datetime import datetime, timedelta import logging import math -from datetime import datetime, timedelta from typing import TYPE_CHECKING import voluptuous as vol -from custom_components.tibber_prices.const import ( - DOMAIN, - get_display_unit_factor, - get_display_unit_string, -) +from custom_components.tibber_prices.const import DOMAIN, get_display_unit_factor, get_display_unit_string from custom_components.tibber_prices.utils.price_window import ( calculate_window_statistics, find_cheapest_contiguous_window, ) from homeassistant.exceptions import ServiceValidationError from homeassistant.helpers import config_validation as cv -from homeassistant.util import dt as dt_utils +from homeassistant.util import dt as dt_util from .helpers import ( INTERVAL_MINUTES, @@ -143,7 +139,7 @@ def _determine_no_window_reason( return "insufficient_contiguous_window" -async def _handle_find_block( # noqa: PLR0915 +async def _handle_find_block( call: ServiceCall, *, reverse: bool = False, @@ -187,7 +183,7 @@ async def _handle_find_block( # noqa: PLR0915 home_tz = ZoneInfo(home_timezone) # Resolve search range (priority: explicit datetime > time+offset > minutes offset > default) - now = dt_utils.now().astimezone(home_tz) + now = dt_util.now().astimezone(home_tz) search_start, search_end = resolve_search_range(call.data, now, home_tz) duration_intervals = duration_minutes // INTERVAL_MINUTES diff --git a/custom_components/tibber_prices/services/find_cheapest_hours.py b/custom_components/tibber_prices/services/find_cheapest_hours.py index ccf4bc8..0093ec0 100644 --- a/custom_components/tibber_prices/services/find_cheapest_hours.py +++ b/custom_components/tibber_prices/services/find_cheapest_hours.py @@ -8,25 +8,18 @@ Intervals need not be contiguous — designed for flexible loads from __future__ import annotations +from datetime import datetime, timedelta import logging import math -from datetime import datetime, timedelta from typing import TYPE_CHECKING import voluptuous as vol -from custom_components.tibber_prices.const import ( - DOMAIN, - get_display_unit_factor, - get_display_unit_string, -) -from custom_components.tibber_prices.utils.price_window import ( - calculate_window_statistics, - find_cheapest_n_intervals, -) +from custom_components.tibber_prices.const import DOMAIN, get_display_unit_factor, get_display_unit_string +from custom_components.tibber_prices.utils.price_window import calculate_window_statistics, find_cheapest_n_intervals from homeassistant.exceptions import ServiceValidationError from homeassistant.helpers import config_validation as cv -from homeassistant.util import dt as dt_utils +from homeassistant.util import dt as dt_util from .helpers import ( INTERVAL_MINUTES, @@ -101,7 +94,7 @@ def _determine_no_intervals_reason( return "insufficient_intervals_for_constraints" -def _build_found_response( # noqa: PLR0913 +def _build_found_response( *, result: dict, comparison_result: dict | None, @@ -202,7 +195,7 @@ def _build_found_response( # noqa: PLR0913 } -async def _handle_find_hours( # noqa: PLR0915 +async def _handle_find_hours( call: ServiceCall, *, reverse: bool = False, @@ -251,7 +244,7 @@ async def _handle_find_hours( # noqa: PLR0915 home_tz = ZoneInfo(home_timezone) # Resolve search range (priority: explicit datetime > time+offset > minutes offset > default) - now = dt_utils.now().astimezone(home_tz) + now = dt_util.now().astimezone(home_tz) search_start, search_end = resolve_search_range(call.data, now, home_tz) total_intervals = total_minutes // INTERVAL_MINUTES diff --git a/custom_components/tibber_prices/services/find_cheapest_schedule.py b/custom_components/tibber_prices/services/find_cheapest_schedule.py index 88ab619..6ac6c81 100644 --- a/custom_components/tibber_prices/services/find_cheapest_schedule.py +++ b/custom_components/tibber_prices/services/find_cheapest_schedule.py @@ -8,25 +8,21 @@ each task claims the cheapest available contiguous window in the remaining pool. from __future__ import annotations +from datetime import datetime, timedelta import logging import math -from datetime import datetime, timedelta from typing import TYPE_CHECKING, Any import voluptuous as vol -from custom_components.tibber_prices.const import ( - DOMAIN, - get_display_unit_factor, - get_display_unit_string, -) +from custom_components.tibber_prices.const import DOMAIN, get_display_unit_factor, get_display_unit_string from custom_components.tibber_prices.utils.price_window import ( calculate_window_statistics, find_cheapest_contiguous_window, ) from homeassistant.exceptions import ServiceValidationError from homeassistant.helpers import config_validation as cv -from homeassistant.util import dt as dt_utils +from homeassistant.util import dt as dt_util from .helpers import ( INTERVAL_MINUTES, @@ -213,7 +209,7 @@ def _find_cheapest_window_in_pool( return (best_start, best_start + duration_intervals) -async def handle_find_cheapest_schedule(call: ServiceCall) -> ServiceResponse: # noqa: PLR0915 +async def handle_find_cheapest_schedule(call: ServiceCall) -> ServiceResponse: """Handle find_cheapest_schedule service call.""" service_label = "find_cheapest_schedule" hass: HomeAssistant = call.hass @@ -255,7 +251,7 @@ async def handle_find_cheapest_schedule(call: ServiceCall) -> ServiceResponse: home_tz = ZoneInfo(home_timezone) - now = dt_utils.now().astimezone(home_tz) + now = dt_util.now().astimezone(home_tz) search_start, search_end = resolve_search_range(call.data, now, home_tz) # Resolve task durations (round up to intervals) diff --git a/custom_components/tibber_prices/services/formatters.py b/custom_components/tibber_prices/services/formatters.py index f4b36d6..9fba586 100644 --- a/custom_components/tibber_prices/services/formatters.py +++ b/custom_components/tibber_prices/services/formatters.py @@ -30,9 +30,7 @@ from custom_components.tibber_prices.const import ( DEFAULT_PRICE_RATING_THRESHOLD_LOW, get_translation, ) -from custom_components.tibber_prices.coordinator.helpers import ( - get_intervals_for_day_offsets, -) +from custom_components.tibber_prices.coordinator.helpers import get_intervals_for_day_offsets from custom_components.tibber_prices.sensor.helpers import aggregate_level_data, aggregate_rating_data from custom_components.tibber_prices.utils.average import calculate_mean, calculate_median @@ -51,7 +49,7 @@ def normalize_rating_level_filter(value: list[str] | None) -> list[str] | None: return [v.upper() for v in value] -def aggregate_to_hourly( # noqa: PLR0912 +def aggregate_to_hourly( intervals: list[dict], coordinator: Any, threshold_low: float = DEFAULT_PRICE_RATING_THRESHOLD_LOW, @@ -166,7 +164,7 @@ def aggregate_to_hourly( # noqa: PLR0912 return hourly_data -def aggregate_hourly_exact( # noqa: PLR0913, PLR0912, PLR0915 +def aggregate_hourly_exact( intervals: list[dict], start_time_field: str, price_field: str, @@ -316,7 +314,7 @@ def aggregate_hourly_exact( # noqa: PLR0913, PLR0912, PLR0915 return hourly_data -def get_period_data( # noqa: PLR0913, PLR0912, PLR0915 +def get_period_data( *, coordinator: Any, period_filter: str, diff --git a/custom_components/tibber_prices/services/get_apexcharts_yaml.py b/custom_components/tibber_prices/services/get_apexcharts_yaml.py index cbdc451..6b0191d 100644 --- a/custom_components/tibber_prices/services/get_apexcharts_yaml.py +++ b/custom_components/tibber_prices/services/get_apexcharts_yaml.py @@ -37,12 +37,7 @@ from custom_components.tibber_prices.const import ( get_translation, ) from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.entity_registry import ( - EntityRegistry, -) -from homeassistant.helpers.entity_registry import ( - async_get as async_get_entity_registry, -) +from homeassistant.helpers.entity_registry import EntityRegistry, async_get as async_get_entity_registry from .formatters import get_level_translation from .helpers import get_entry_and_data @@ -265,7 +260,7 @@ def _get_missing_cards_notification(language: str, missing_cards: list[str]) -> return {"title": title, "message": message} -async def handle_apexcharts_yaml(call: ServiceCall) -> dict[str, Any]: # noqa: PLR0912, PLR0915, C901 +async def handle_apexcharts_yaml(call: ServiceCall) -> dict[str, Any]: # noqa: C901 """ Return YAML snippet for ApexCharts card. diff --git a/custom_components/tibber_prices/services/get_chartdata.py b/custom_components/tibber_prices/services/get_chartdata.py index 6997a79..f88ff90 100644 --- a/custom_components/tibber_prices/services/get_chartdata.py +++ b/custom_components/tibber_prices/services/get_chartdata.py @@ -21,9 +21,9 @@ Response: JSON with chart-ready data from __future__ import annotations +from datetime import datetime, timedelta import math import re -from datetime import datetime, timedelta from typing import TYPE_CHECKING, Any, Final import voluptuous as vol @@ -49,17 +49,10 @@ from custom_components.tibber_prices.const import ( get_currency_info, get_currency_name, ) -from custom_components.tibber_prices.coordinator.helpers import ( - get_intervals_for_day_offsets, -) +from custom_components.tibber_prices.coordinator.helpers import get_intervals_for_day_offsets from homeassistant.exceptions import ServiceValidationError -from .formatters import ( - aggregate_to_hourly, - get_period_data, - normalize_level_filter, - normalize_rating_level_filter, -) +from .formatters import aggregate_to_hourly, get_period_data, normalize_level_filter, normalize_rating_level_filter from .helpers import get_entry_and_data, has_tomorrow_data if TYPE_CHECKING: @@ -92,7 +85,7 @@ def _is_transition_to_more_expensive( return next_rank > current_rank -def _calculate_metadata( # noqa: PLR0912, PLR0913, PLR0915 +def _calculate_metadata( chart_data: list[dict[str, Any]], price_field: str, start_time_field: str, @@ -320,7 +313,7 @@ CHARTDATA_SERVICE_SCHEMA: Final = vol.Schema( ) -async def handle_chartdata(call: ServiceCall) -> dict[str, Any]: # noqa: PLR0912, PLR0915, C901 +async def handle_chartdata(call: ServiceCall) -> dict[str, Any]: # noqa: C901 """ Return price data in chart-friendly format. diff --git a/custom_components/tibber_prices/services/get_price.py b/custom_components/tibber_prices/services/get_price.py index 1da67e3..1190f32 100644 --- a/custom_components/tibber_prices/services/get_price.py +++ b/custom_components/tibber_prices/services/get_price.py @@ -21,7 +21,7 @@ import voluptuous as vol from custom_components.tibber_prices.const import DOMAIN from homeassistant.exceptions import ServiceValidationError from homeassistant.helpers import config_validation as cv -from homeassistant.util import dt as dt_utils +from homeassistant.util import dt as dt_util from .helpers import get_entry_and_data @@ -116,13 +116,13 @@ async def handle_get_price(call: ServiceCall) -> ServiceResponse: if start_time.tzinfo is None: # Step 1: Localize to HA server timezone - start_time = dt_utils.as_local(start_time) + start_time = dt_util.as_local(start_time) # Step 2: Convert to home timezone start_time = start_time.astimezone(home_tz) if end_time.tzinfo is None: # Step 1: Localize to HA server timezone - end_time = dt_utils.as_local(end_time) + end_time = dt_util.as_local(end_time) # Step 2: Convert to home timezone end_time = end_time.astimezone(home_tz) diff --git a/custom_components/tibber_prices/services/helpers.py b/custom_components/tibber_prices/services/helpers.py index 1ff19a9..014fc00 100644 --- a/custom_components/tibber_prices/services/helpers.py +++ b/custom_components/tibber_prices/services/helpers.py @@ -29,14 +29,13 @@ Used by: from __future__ import annotations -from datetime import datetime, timedelta -from datetime import time as dt_time +from datetime import datetime, time as dt_time, timedelta from typing import TYPE_CHECKING, Any from custom_components.tibber_prices.const import DOMAIN from custom_components.tibber_prices.coordinator.helpers import get_intervals_for_day_offsets from homeassistant.exceptions import ServiceValidationError -from homeassistant.util import dt as dt_utils +from homeassistant.util import dt as dt_util if TYPE_CHECKING: from zoneinfo import ZoneInfo @@ -266,13 +265,13 @@ def localize_to_home_tz(dt_value: datetime, home_tz: ZoneInfo) -> datetime: 2. Convert from HA timezone to home timezone """ if dt_value.tzinfo is None: - dt_value = dt_utils.as_local(dt_value) + dt_value = dt_util.as_local(dt_value) return dt_value.astimezone(home_tz) def calculate_end_of_tomorrow(home_tz: ZoneInfo) -> datetime: """Calculate end of tomorrow in home timezone.""" - now_home = dt_utils.now().astimezone(home_tz) + now_home = dt_util.now().astimezone(home_tz) tomorrow = (now_home + timedelta(days=1)).date() # End of tomorrow = midnight at start of day after tomorrow return now_home.replace( @@ -295,9 +294,10 @@ def _resolve_time_with_day_offset( time_value: dt_time, day_offset: int, home_tz: ZoneInfo, + now: datetime, ) -> datetime: """Resolve a time-of-day + day offset to a full datetime in home timezone.""" - now_home = dt_utils.now().astimezone(home_tz) + now_home = now.astimezone(home_tz) target_date = (now_home + timedelta(days=day_offset)).date() return datetime( year=target_date.year, @@ -477,7 +477,7 @@ def resolve_search_range( search_start = localize_to_home_tz(call_data["search_start"], home_tz) elif "search_start_time" in call_data: day_offset = call_data.get("search_start_day_offset", 0) - search_start = _resolve_time_with_day_offset(call_data["search_start_time"], day_offset, home_tz) + search_start = _resolve_time_with_day_offset(call_data["search_start_time"], day_offset, home_tz, now) elif "search_start_offset_minutes" in call_data: search_start = now + timedelta(minutes=call_data["search_start_offset_minutes"]) if include_current: @@ -490,7 +490,7 @@ def resolve_search_range( search_end = localize_to_home_tz(call_data["search_end"], home_tz) elif "search_end_time" in call_data: day_offset = call_data.get("search_end_day_offset", 0) - search_end = _resolve_time_with_day_offset(call_data["search_end_time"], day_offset, home_tz) + search_end = _resolve_time_with_day_offset(call_data["search_end_time"], day_offset, home_tz, now) elif "search_end_offset_minutes" in call_data: search_end = now + timedelta(minutes=call_data["search_end_offset_minutes"]) else: diff --git a/custom_components/tibber_prices/switch/core.py b/custom_components/tibber_prices/switch/core.py index 54487b9..616eac6 100644 --- a/custom_components/tibber_prices/switch/core.py +++ b/custom_components/tibber_prices/switch/core.py @@ -11,20 +11,14 @@ from __future__ import annotations import logging from typing import TYPE_CHECKING, Any -from custom_components.tibber_prices.const import ( - DOMAIN, - get_home_type_translation, - get_translation, -) +from custom_components.tibber_prices.const import DOMAIN, get_home_type_translation, get_translation from homeassistant.components.switch import SwitchEntity from homeassistant.core import callback from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo from homeassistant.helpers.restore_state import RestoreEntity if TYPE_CHECKING: - from custom_components.tibber_prices.coordinator import ( - TibberPricesDataUpdateCoordinator, - ) + from custom_components.tibber_prices.coordinator import TibberPricesDataUpdateCoordinator from .definitions import TibberPricesSwitchEntityDescription diff --git a/custom_components/tibber_prices/utils/price.py b/custom_components/tibber_prices/utils/price.py index 54ac98a..427c519 100644 --- a/custom_components/tibber_prices/utils/price.py +++ b/custom_components/tibber_prices/utils/price.py @@ -3,9 +3,9 @@ from __future__ import annotations import bisect +from datetime import datetime, timedelta import logging import statistics -from datetime import datetime, timedelta from typing import TYPE_CHECKING, Any if TYPE_CHECKING: @@ -353,7 +353,7 @@ def calculate_difference_percentage( return ((current_interval_price - trailing_average) / abs(trailing_average)) * 100 -def calculate_rating_level( # noqa: PLR0911 - Multiple returns justified by clear hysteresis state machine +def calculate_rating_level( difference: float | None, threshold_low: float, threshold_high: float, @@ -435,7 +435,7 @@ def calculate_rating_level( # noqa: PLR0911 - Multiple returns justified by cle return PRICE_RATING_NORMAL -def _process_price_interval( # noqa: PLR0913 - Extra params needed for hysteresis +def _process_price_interval( price_interval: dict[str, Any], all_prices: list[dict[str, Any]], threshold_low: float, @@ -651,7 +651,7 @@ def _apply_rating_gap_tolerance( if interval.get("rating_level") is not None ] - if len(rated_intervals) < 3: # noqa: PLR2004 - Minimum 3 for before/gap/after pattern + if len(rated_intervals) < 3: return # Iteratively merge small blocks until no more changes @@ -720,7 +720,7 @@ def _apply_level_gap_tolerance( if interval.get("level") is not None ] - if len(level_intervals) < 3: # noqa: PLR2004 - Minimum 3 for before/gap/after pattern + if len(level_intervals) < 3: return # Iteratively merge small blocks until no more changes @@ -859,7 +859,7 @@ def _merge_small_level_blocks( return len(merge_decisions) -def enrich_price_info_with_differences( # noqa: PLR0913 - Extra params for rating stabilization +def enrich_price_info_with_differences( all_intervals: list[dict[str, Any]], *, threshold_low: float | None = None, @@ -867,7 +867,7 @@ def enrich_price_info_with_differences( # noqa: PLR0913 - Extra params for rati hysteresis: float | None = None, gap_tolerance: int | None = None, level_gap_tolerance: int | None = None, - time: TibberPricesTimeService | None = None, # noqa: ARG001 # Used in production (via coordinator), kept for compatibility + time: TibberPricesTimeService | None = None, # Used in production (via coordinator), kept for compatibility ) -> list[dict[str, Any]]: """ Enrich price intervals with calculated 'difference' and 'rating_level' values. @@ -1229,7 +1229,7 @@ def _calculate_lookahead_volatility_factor( return factor -def calculate_price_trend( # noqa: PLR0913 - All parameters are necessary for volatility-adaptive calculation +def calculate_price_trend( current_interval_price: float, future_average: float, threshold_rising: float = 3.0, diff --git a/custom_components/tibber_prices/utils/price_window.py b/custom_components/tibber_prices/utils/price_window.py index 09798bf..c6d295c 100644 --- a/custom_components/tibber_prices/utils/price_window.py +++ b/custom_components/tibber_prices/utils/price_window.py @@ -10,8 +10,8 @@ These are stateless pure functions with no Home Assistant dependencies. from __future__ import annotations -import statistics from datetime import datetime, timedelta +import statistics from typing import Any diff --git a/tests/services/test_find_service_responses.py b/tests/services/test_find_service_responses.py index 9c4f143..e2793a2 100644 --- a/tests/services/test_find_service_responses.py +++ b/tests/services/test_find_service_responses.py @@ -11,9 +11,11 @@ if TYPE_CHECKING: import pytest -from custom_components.tibber_prices.services import find_cheapest_block as block_module -from custom_components.tibber_prices.services import find_cheapest_hours as hours_module -from custom_components.tibber_prices.services import find_cheapest_schedule as schedule_module +from custom_components.tibber_prices.services import ( + find_cheapest_block as block_module, + find_cheapest_hours as hours_module, + find_cheapest_schedule as schedule_module, +) from custom_components.tibber_prices.services.find_cheapest_block import ( _determine_no_window_reason, handle_find_cheapest_block, diff --git a/tests/services/test_search_range.py b/tests/services/test_search_range.py index d3527b2..5f0a5d3 100644 --- a/tests/services/test_search_range.py +++ b/tests/services/test_search_range.py @@ -11,22 +11,15 @@ Also validates schema boundaries for all 4 services. from __future__ import annotations -from datetime import datetime, timedelta -from datetime import time as dt_time +from datetime import datetime, time as dt_time, timedelta from zoneinfo import ZoneInfo import pytest import voluptuous as vol -from custom_components.tibber_prices.services.find_cheapest_block import ( - _COMMON_BLOCK_SCHEMA, -) -from custom_components.tibber_prices.services.find_cheapest_hours import ( - _COMMON_HOURS_SCHEMA, -) -from custom_components.tibber_prices.services.helpers import ( - resolve_search_range, -) +from custom_components.tibber_prices.services.find_cheapest_block import _COMMON_BLOCK_SCHEMA +from custom_components.tibber_prices.services.find_cheapest_hours import _COMMON_HOURS_SCHEMA +from custom_components.tibber_prices.services.helpers import resolve_search_range BERLIN = ZoneInfo("Europe/Berlin") diff --git a/tests/test_avg_none_fallback.py b/tests/test_avg_none_fallback.py index 7c96abf..c233248 100644 --- a/tests/test_avg_none_fallback.py +++ b/tests/test_avg_none_fallback.py @@ -4,13 +4,8 @@ from datetime import UTC, datetime, timedelta import pytest -from custom_components.tibber_prices.coordinator.time_service import ( - TibberPricesTimeService, -) -from custom_components.tibber_prices.utils.average import ( - calculate_leading_24h_mean, - calculate_trailing_24h_mean, -) +from custom_components.tibber_prices.coordinator.time_service import TibberPricesTimeService +from custom_components.tibber_prices.utils.average import calculate_leading_24h_mean, calculate_trailing_24h_mean @pytest.fixture diff --git a/tests/test_best_price_e2e.py b/tests/test_best_price_e2e.py index d5cfd70..fda8efd 100644 --- a/tests/test_best_price_e2e.py +++ b/tests/test_best_price_e2e.py @@ -21,9 +21,7 @@ from custom_components.tibber_prices.coordinator.period_handlers import ( TibberPricesPeriodConfig, calculate_periods_with_relaxation, ) -from custom_components.tibber_prices.coordinator.time_service import ( - TibberPricesTimeService, -) +from custom_components.tibber_prices.coordinator.time_service import TibberPricesTimeService from homeassistant.util import dt as dt_util diff --git a/tests/test_coordinator_shutdown.py b/tests/test_coordinator_shutdown.py index 977cabd..9873655 100644 --- a/tests/test_coordinator_shutdown.py +++ b/tests/test_coordinator_shutdown.py @@ -5,9 +5,7 @@ from unittest.mock import AsyncMock, MagicMock import pytest # Import at module level to avoid PLC0415 -from custom_components.tibber_prices.coordinator.core import ( - TibberPricesDataUpdateCoordinator, -) +from custom_components.tibber_prices.coordinator.core import TibberPricesDataUpdateCoordinator @pytest.mark.asyncio diff --git a/tests/test_interval_pool_memory_leak.py b/tests/test_interval_pool_memory_leak.py index ff80209..9cf7d38 100644 --- a/tests/test_interval_pool_memory_leak.py +++ b/tests/test_interval_pool_memory_leak.py @@ -18,24 +18,16 @@ Architecture: Tests access internal components directly for fine-grained verification. """ -import json from datetime import UTC, datetime +import json from unittest.mock import MagicMock import pytest -from custom_components.tibber_prices.interval_pool.cache import ( - TibberPricesIntervalPoolFetchGroupCache, -) -from custom_components.tibber_prices.interval_pool.garbage_collector import ( - TibberPricesIntervalPoolGarbageCollector, -) -from custom_components.tibber_prices.interval_pool.index import ( - TibberPricesIntervalPoolTimestampIndex, -) -from custom_components.tibber_prices.interval_pool.manager import ( - TibberPricesIntervalPool, -) +from custom_components.tibber_prices.interval_pool.cache import TibberPricesIntervalPoolFetchGroupCache +from custom_components.tibber_prices.interval_pool.garbage_collector import TibberPricesIntervalPoolGarbageCollector +from custom_components.tibber_prices.interval_pool.index import TibberPricesIntervalPoolTimestampIndex +from custom_components.tibber_prices.interval_pool.manager import TibberPricesIntervalPool @pytest.fixture diff --git a/tests/test_interval_pool_optimization.py b/tests/test_interval_pool_optimization.py index c33ab27..b89502a 100644 --- a/tests/test_interval_pool_optimization.py +++ b/tests/test_interval_pool_optimization.py @@ -23,7 +23,7 @@ from unittest.mock import AsyncMock, MagicMock import pytest from custom_components.tibber_prices.interval_pool import TibberPricesIntervalPool -from homeassistant.util import dt as dt_utils +from homeassistant.util import dt as dt_util pytest_plugins = ("pytest_homeassistant_custom_component",) @@ -63,7 +63,7 @@ async def test_no_cache_single_api_call() -> None: "_calculate_day_before_yesterday_midnight", ] ) - start = dt_utils.now().replace(hour=10, minute=0, second=0, microsecond=0) + start = dt_util.now().replace(hour=10, minute=0, second=0, microsecond=0) end = start + timedelta(hours=2) # 8 intervals # Create mock response @@ -71,7 +71,7 @@ async def test_no_cache_single_api_call() -> None: api_client.async_get_price_info_for_range = AsyncMock(return_value=mock_intervals) api_client._extract_home_timezones = MagicMock(return_value={"home123": "Europe/Berlin"}) # noqa: SLF001 # Mock boundary calculation (returns day before yesterday midnight) - dby_midnight = (dt_utils.now() - timedelta(days=2)).replace(hour=0, minute=0, second=0, microsecond=0) + dby_midnight = (dt_util.now() - timedelta(days=2)).replace(hour=0, minute=0, second=0, microsecond=0) api_client._calculate_day_before_yesterday_midnight = MagicMock(return_value=dby_midnight) # noqa: SLF001 # Mock the actual price info fetching methods (they call async_get_price_info_for_range internally) api_client.async_get_price_info = AsyncMock(return_value={"priceInfo": mock_intervals}) @@ -103,7 +103,7 @@ async def test_full_cache_zero_api_calls() -> None: "_calculate_day_before_yesterday_midnight", ] ) - start = dt_utils.now().replace(hour=10, minute=0, second=0, microsecond=0) + start = dt_util.now().replace(hour=10, minute=0, second=0, microsecond=0) end = start + timedelta(hours=2) # 8 intervals # Pre-populate cache @@ -111,7 +111,7 @@ async def test_full_cache_zero_api_calls() -> None: api_client.async_get_price_info_for_range = AsyncMock(return_value=mock_intervals) api_client._extract_home_timezones = MagicMock(return_value={"home123": "Europe/Berlin"}) # noqa: SLF001 # Mock boundary calculation (returns day before yesterday midnight) - dby_midnight = (dt_utils.now() - timedelta(days=2)).replace(hour=0, minute=0, second=0, microsecond=0) + dby_midnight = (dt_util.now() - timedelta(days=2)).replace(hour=0, minute=0, second=0, microsecond=0) api_client._calculate_day_before_yesterday_midnight = MagicMock(return_value=dby_midnight) # noqa: SLF001 # Mock the actual price info fetching methods (they call async_get_price_info_for_range internally) api_client.async_get_price_info = AsyncMock(return_value={"priceInfo": mock_intervals}) @@ -146,7 +146,7 @@ async def test_single_gap_single_api_call() -> None: "_calculate_day_before_yesterday_midnight", ] ) - start = dt_utils.now().replace(hour=10, minute=0, second=0, microsecond=0) + start = dt_util.now().replace(hour=10, minute=0, second=0, microsecond=0) end = start + timedelta(hours=3) # 12 intervals total user_data = {"timeZone": "Europe/Berlin"} @@ -198,7 +198,7 @@ async def test_multiple_gaps_multiple_api_calls() -> None: "_calculate_day_before_yesterday_midnight", ] ) - start = dt_utils.now().replace(hour=10, minute=0, second=0, microsecond=0) + start = dt_util.now().replace(hour=10, minute=0, second=0, microsecond=0) end = start + timedelta(hours=4) # 16 intervals total user_data = {"timeZone": "Europe/Berlin"} @@ -270,7 +270,7 @@ async def test_partial_overlap_minimal_fetch() -> None: "_calculate_day_before_yesterday_midnight", ] ) - start = dt_utils.now().replace(hour=10, minute=0, second=0, microsecond=0) + start = dt_util.now().replace(hour=10, minute=0, second=0, microsecond=0) user_data = {"timeZone": "Europe/Berlin"} @@ -314,7 +314,7 @@ async def test_detect_missing_ranges_optimization() -> None: ] ) - start = dt_utils.now().replace(hour=10, minute=0, second=0, microsecond=0) + start = dt_util.now().replace(hour=10, minute=0, second=0, microsecond=0) end = start + timedelta(hours=4) user_data = {"timeZone": "Europe/Berlin"} @@ -337,7 +337,7 @@ async def test_detect_missing_ranges_optimization() -> None: pool._fetch_groups = [ # noqa: SLF001 { "intervals": cached, - "fetch_time": dt_utils.now().isoformat(), + "fetch_time": dt_util.now().isoformat(), } ] pool._timestamp_index = {interval["startsAt"]: idx for idx, interval in enumerate(cached)} # noqa: SLF001 diff --git a/tests/test_level_filtering.py b/tests/test_level_filtering.py index 7936418..d762e99 100644 --- a/tests/test_level_filtering.py +++ b/tests/test_level_filtering.py @@ -16,12 +16,8 @@ from __future__ import annotations import pytest -from custom_components.tibber_prices.coordinator.period_handlers.level_filtering import ( - check_interval_criteria, -) -from custom_components.tibber_prices.coordinator.period_handlers.types import ( - TibberPricesIntervalCriteria, -) +from custom_components.tibber_prices.coordinator.period_handlers.level_filtering import check_interval_criteria +from custom_components.tibber_prices.coordinator.period_handlers.types import TibberPricesIntervalCriteria @pytest.mark.unit diff --git a/tests/test_mean_median_display.py b/tests/test_mean_median_display.py index 7acd975..4d6b7ab 100644 --- a/tests/test_mean_median_display.py +++ b/tests/test_mean_median_display.py @@ -8,19 +8,14 @@ This test verifies that: 4. Calculations that depend on averages use mean internally (not affected by display setting) """ -import statistics from datetime import UTC, datetime, timedelta +import statistics from unittest.mock import Mock import pytest -from custom_components.tibber_prices.const import ( - CONF_AVERAGE_SENSOR_DISPLAY, - DEFAULT_AVERAGE_SENSOR_DISPLAY, -) -from custom_components.tibber_prices.sensor.attributes.helpers import ( - add_alternate_average_attribute, -) +from custom_components.tibber_prices.const import CONF_AVERAGE_SENSOR_DISPLAY, DEFAULT_AVERAGE_SENSOR_DISPLAY +from custom_components.tibber_prices.sensor.attributes.helpers import add_alternate_average_attribute from custom_components.tibber_prices.utils.average import calculate_mean, calculate_median diff --git a/tests/test_midnight_handler.py b/tests/test_midnight_handler.py index 52ae086..f740a23 100644 --- a/tests/test_midnight_handler.py +++ b/tests/test_midnight_handler.py @@ -12,9 +12,7 @@ from zoneinfo import ZoneInfo import pytest -from custom_components.tibber_prices.coordinator.midnight_handler import ( - TibberPricesMidnightHandler, -) +from custom_components.tibber_prices.coordinator.midnight_handler import TibberPricesMidnightHandler @pytest.mark.unit diff --git a/tests/test_midnight_periods.py b/tests/test_midnight_periods.py index 6417db1..7ede405 100644 --- a/tests/test_midnight_periods.py +++ b/tests/test_midnight_periods.py @@ -7,9 +7,7 @@ from zoneinfo import ZoneInfo import pytest -from custom_components.tibber_prices.coordinator.period_handlers.relaxation import ( - group_periods_by_day, -) +from custom_components.tibber_prices.coordinator.period_handlers.relaxation import group_periods_by_day @pytest.fixture diff --git a/tests/test_midnight_turnover.py b/tests/test_midnight_turnover.py index c43b8b3..7e83e04 100644 --- a/tests/test_midnight_turnover.py +++ b/tests/test_midnight_turnover.py @@ -8,15 +8,9 @@ from zoneinfo import ZoneInfo import pytest -from custom_components.tibber_prices.coordinator.period_handlers.core import ( - calculate_periods, -) -from custom_components.tibber_prices.coordinator.period_handlers.types import ( - TibberPricesPeriodConfig, -) -from custom_components.tibber_prices.coordinator.time_service import ( - TibberPricesTimeService, -) +from custom_components.tibber_prices.coordinator.period_handlers.core import calculate_periods +from custom_components.tibber_prices.coordinator.period_handlers.types import TibberPricesPeriodConfig +from custom_components.tibber_prices.coordinator.time_service import TibberPricesTimeService def create_price_interval(dt: datetime, price: float) -> dict: diff --git a/tests/test_minmax_none_fallback.py b/tests/test_minmax_none_fallback.py index 0e5ecb0..e8844cb 100644 --- a/tests/test_minmax_none_fallback.py +++ b/tests/test_minmax_none_fallback.py @@ -4,9 +4,7 @@ from datetime import UTC, datetime, timedelta import pytest -from custom_components.tibber_prices.coordinator.time_service import ( - TibberPricesTimeService, -) +from custom_components.tibber_prices.coordinator.time_service import TibberPricesTimeService from custom_components.tibber_prices.utils.average import ( calculate_leading_24h_max, calculate_leading_24h_min, diff --git a/tests/test_next_api_poll.py b/tests/test_next_api_poll.py index 211f7ca..094398b 100644 --- a/tests/test_next_api_poll.py +++ b/tests/test_next_api_poll.py @@ -14,9 +14,7 @@ from zoneinfo import ZoneInfo import pytest from custom_components.tibber_prices.coordinator.constants import UPDATE_INTERVAL -from custom_components.tibber_prices.sensor.calculators.lifecycle import ( - TibberPricesLifecycleCalculator, -) +from custom_components.tibber_prices.sensor.calculators.lifecycle import TibberPricesLifecycleCalculator @pytest.mark.unit diff --git a/tests/test_peak_price_e2e.py b/tests/test_peak_price_e2e.py index a2978c2..8c56e5b 100644 --- a/tests/test_peak_price_e2e.py +++ b/tests/test_peak_price_e2e.py @@ -21,9 +21,7 @@ from custom_components.tibber_prices.coordinator.period_handlers import ( TibberPricesPeriodConfig, calculate_periods_with_relaxation, ) -from custom_components.tibber_prices.coordinator.time_service import ( - TibberPricesTimeService, -) +from custom_components.tibber_prices.coordinator.time_service import TibberPricesTimeService from homeassistant.util import dt as dt_util diff --git a/tests/test_percentage_calculations.py b/tests/test_percentage_calculations.py index 930db42..57455fb 100644 --- a/tests/test_percentage_calculations.py +++ b/tests/test_percentage_calculations.py @@ -4,9 +4,7 @@ from datetime import UTC, datetime import pytest -from custom_components.tibber_prices.coordinator.period_handlers.period_statistics import ( - calculate_period_price_diff, -) +from custom_components.tibber_prices.coordinator.period_handlers.period_statistics import calculate_period_price_diff from custom_components.tibber_prices.utils.price import calculate_price_trend diff --git a/tests/test_price_calculations.py b/tests/test_price_calculations.py index 21ced4e..97f36d8 100644 --- a/tests/test_price_calculations.py +++ b/tests/test_price_calculations.py @@ -15,9 +15,7 @@ from datetime import UTC, datetime, timedelta import pytest -from custom_components.tibber_prices.coordinator.time_service import ( - TibberPricesTimeService, -) +from custom_components.tibber_prices.coordinator.time_service import TibberPricesTimeService from custom_components.tibber_prices.utils.price import ( aggregate_period_levels, aggregate_period_ratings, @@ -348,7 +346,7 @@ def test_rating_level_none_difference() -> None: (0.1, 0.1, 0.1, 0.0, "NORMAL", "prices near zero: stable"), ], ) -def test_enrich_price_info_scenarios( # noqa: PLR0913 # Many parameters needed for comprehensive test scenarios +def test_enrich_price_info_scenarios( # Many parameters needed for comprehensive test scenarios day_before_yesterday_price: float, yesterday_price: float, today_price: float, diff --git a/tests/test_rating_threshold_validation.py b/tests/test_rating_threshold_validation.py index 0243a22..79ad3bb 100644 --- a/tests/test_rating_threshold_validation.py +++ b/tests/test_rating_threshold_validation.py @@ -5,10 +5,7 @@ from typing import TYPE_CHECKING import pytest -from custom_components.tibber_prices.utils.price import ( - _apply_rating_gap_tolerance, - calculate_rating_level, -) +from custom_components.tibber_prices.utils.price import _apply_rating_gap_tolerance, calculate_rating_level if TYPE_CHECKING: from _pytest.logging import LogCaptureFixture diff --git a/tests/test_resource_cleanup.py b/tests/test_resource_cleanup.py index c09ef4c..7589352 100644 --- a/tests/test_resource_cleanup.py +++ b/tests/test_resource_cleanup.py @@ -4,15 +4,9 @@ from unittest.mock import AsyncMock, MagicMock, Mock import pytest -from custom_components.tibber_prices.binary_sensor.core import ( - TibberPricesBinarySensor, -) -from custom_components.tibber_prices.coordinator.core import ( - TibberPricesDataUpdateCoordinator, -) -from custom_components.tibber_prices.coordinator.listeners import ( - TibberPricesListenerManager, -) +from custom_components.tibber_prices.binary_sensor.core import TibberPricesBinarySensor +from custom_components.tibber_prices.coordinator.core import TibberPricesDataUpdateCoordinator +from custom_components.tibber_prices.coordinator.listeners import TibberPricesListenerManager from custom_components.tibber_prices.sensor.core import TibberPricesSensor @@ -200,15 +194,9 @@ class TestConfigEntryCleanup: from custom_components.tibber_prices.coordinator.data_transformation import ( # noqa: PLC0415 TibberPricesDataTransformer, ) - from custom_components.tibber_prices.coordinator.listeners import ( # noqa: PLC0415 - TibberPricesListenerManager, - ) - from custom_components.tibber_prices.coordinator.periods import ( # noqa: PLC0415 - TibberPricesPeriodCalculator, - ) - from custom_components.tibber_prices.coordinator.time_service import ( # noqa: PLC0415 - TibberPricesTimeService, - ) + from custom_components.tibber_prices.coordinator.listeners import TibberPricesListenerManager # noqa: PLC0415 + from custom_components.tibber_prices.coordinator.periods import TibberPricesPeriodCalculator # noqa: PLC0415 + from custom_components.tibber_prices.coordinator.time_service import TibberPricesTimeService # noqa: PLC0415 coordinator.time = TibberPricesTimeService(hass) coordinator._listener_manager = object.__new__(TibberPricesListenerManager) # noqa: SLF001 @@ -257,9 +245,7 @@ class TestCacheInvalidation: def test_period_cache_invalidated_on_options_change(self) -> None: """Test that period calculation cache is cleared when options change.""" - from custom_components.tibber_prices.coordinator.periods import ( # noqa: PLC0415 - TibberPricesPeriodCalculator, - ) + from custom_components.tibber_prices.coordinator.periods import TibberPricesPeriodCalculator # noqa: PLC0415 # Create calculator with cached data calculator = object.__new__(TibberPricesPeriodCalculator) diff --git a/tests/test_sensor_consistency.py b/tests/test_sensor_consistency.py index 6022e63..c541137 100644 --- a/tests/test_sensor_consistency.py +++ b/tests/test_sensor_consistency.py @@ -8,16 +8,9 @@ from unittest.mock import Mock import pytest -from custom_components.tibber_prices.binary_sensor.core import ( - TibberPricesBinarySensor, -) -from custom_components.tibber_prices.coordinator.core import ( - TibberPricesDataUpdateCoordinator, - get_connection_state, -) -from custom_components.tibber_prices.sensor.calculators.lifecycle import ( - TibberPricesLifecycleCalculator, -) +from custom_components.tibber_prices.binary_sensor.core import TibberPricesBinarySensor +from custom_components.tibber_prices.coordinator.core import TibberPricesDataUpdateCoordinator, get_connection_state +from custom_components.tibber_prices.sensor.calculators.lifecycle import TibberPricesLifecycleCalculator from homeassistant.components.binary_sensor import BinarySensorEntityDescription from homeassistant.exceptions import ConfigEntryAuthFailed from homeassistant.helpers.update_coordinator import UpdateFailed diff --git a/tests/test_sensor_timer_assignment.py b/tests/test_sensor_timer_assignment.py index e607d7a..deace54 100644 --- a/tests/test_sensor_timer_assignment.py +++ b/tests/test_sensor_timer_assignment.py @@ -15,10 +15,7 @@ Ensures: from custom_components.tibber_prices.binary_sensor.definitions import ( ENTITY_DESCRIPTIONS as BINARY_SENSOR_ENTITY_DESCRIPTIONS, ) -from custom_components.tibber_prices.coordinator.constants import ( - MINUTE_UPDATE_ENTITY_KEYS, - TIME_SENSITIVE_ENTITY_KEYS, -) +from custom_components.tibber_prices.coordinator.constants import MINUTE_UPDATE_ENTITY_KEYS, TIME_SENSITIVE_ENTITY_KEYS from custom_components.tibber_prices.sensor.definitions import ENTITY_DESCRIPTIONS diff --git a/tests/test_time_service.py b/tests/test_time_service.py index 936cef9..469822d 100644 --- a/tests/test_time_service.py +++ b/tests/test_time_service.py @@ -6,9 +6,7 @@ from datetime import UTC, datetime import pytest -from custom_components.tibber_prices.coordinator.time_service import ( - TibberPricesTimeService, -) +from custom_components.tibber_prices.coordinator.time_service import TibberPricesTimeService # ============================================================================= # Quarter-Hour Rounding with Boundary Tolerance (CRITICAL) diff --git a/tests/test_timer_scheduling.py b/tests/test_timer_scheduling.py index c14d39d..802183b 100644 --- a/tests/test_timer_scheduling.py +++ b/tests/test_timer_scheduling.py @@ -15,12 +15,8 @@ from unittest.mock import MagicMock, patch import pytest -from custom_components.tibber_prices.coordinator.constants import ( - QUARTER_HOUR_BOUNDARIES, -) -from custom_components.tibber_prices.coordinator.listeners import ( - TibberPricesListenerManager, -) +from custom_components.tibber_prices.coordinator.constants import QUARTER_HOUR_BOUNDARIES +from custom_components.tibber_prices.coordinator.listeners import TibberPricesListenerManager from homeassistant.core import HomeAssistant diff --git a/tests/test_tomorrow_data_refresh.py b/tests/test_tomorrow_data_refresh.py index a426219..ec02db7 100644 --- a/tests/test_tomorrow_data_refresh.py +++ b/tests/test_tomorrow_data_refresh.py @@ -14,12 +14,8 @@ from zoneinfo import ZoneInfo import pytest -from custom_components.tibber_prices.coordinator.data_transformation import ( - TibberPricesDataTransformer, -) -from custom_components.tibber_prices.coordinator.time_service import ( - TibberPricesTimeService, -) +from custom_components.tibber_prices.coordinator.data_transformation import TibberPricesDataTransformer +from custom_components.tibber_prices.coordinator.time_service import TibberPricesTimeService from homeassistant.util import dt as dt_util diff --git a/tests/test_user_data_validation.py b/tests/test_user_data_validation.py index d01ef39..1923e64 100644 --- a/tests/test_user_data_validation.py +++ b/tests/test_user_data_validation.py @@ -26,9 +26,7 @@ import pytest from custom_components.tibber_prices.api.exceptions import TibberPricesApiClientError from custom_components.tibber_prices.api.helpers import flatten_price_info -from custom_components.tibber_prices.coordinator.price_data_manager import ( - TibberPricesPriceDataManager, -) +from custom_components.tibber_prices.coordinator.price_data_manager import TibberPricesPriceDataManager @pytest.fixture @@ -59,7 +57,7 @@ def mock_interval_pool() -> Mock: @pytest.mark.unit -def test_validate_user_data_complete(mock_api_client, mock_time_service, mock_store, mock_interval_pool) -> None: # noqa: ANN001 +def test_validate_user_data_complete(mock_api_client, mock_time_service, mock_store, mock_interval_pool) -> None: """Test that complete user data passes validation.""" price_data_manager = TibberPricesPriceDataManager( api=mock_api_client, @@ -201,7 +199,7 @@ def test_validate_user_data_subscription_without_currency( @pytest.mark.unit -def test_validate_user_data_home_not_found(mock_api_client, mock_time_service, mock_store, mock_interval_pool) -> None: # noqa: ANN001 +def test_validate_user_data_home_not_found(mock_api_client, mock_time_service, mock_store, mock_interval_pool) -> None: """Test that user data without the requested home fails validation.""" price_data_manager = TibberPricesPriceDataManager( api=mock_api_client,