Skip to content

Commit 16f116d

Browse files
committed
chore: refactor to reduce complexity and LOC
1 parent 331cd8d commit 16f116d

8 files changed

Lines changed: 231 additions & 300 deletions

File tree

custom_components/powercalc/__init__.py

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -335,13 +335,7 @@ async def _reload_config(_: ServiceCall) -> None:
335335
if DOMAIN in reload_config:
336336
for sensor_config in reload_config[DOMAIN].get(CONF_SENSORS, []):
337337
sensor_config.update({DISCOVERY_TYPE: PowercalcDiscoveryType.USER_YAML})
338-
await async_load_platform(
339-
hass,
340-
Platform.SENSOR,
341-
DOMAIN,
342-
sensor_config,
343-
reload_config,
344-
)
338+
await _async_load_yaml_sensor(hass, sensor_config, reload_config)
345339

346340
# Reload all config entries
347341
for entry in hass.config_entries.async_entries(DOMAIN):
@@ -426,15 +420,7 @@ async def _load_secondary_sensors(_: None) -> None:
426420
"""Load secondary sensors after primary sensors."""
427421
await asyncio.gather(
428422
*(
429-
hass.async_create_task(
430-
async_load_platform(
431-
hass,
432-
Platform.SENSOR,
433-
DOMAIN,
434-
sensor_config,
435-
config,
436-
),
437-
)
423+
hass.async_create_task(_async_load_yaml_sensor(hass, sensor_config, config))
438424
for sensor_config in secondary_sensors
439425
),
440426
)
@@ -443,20 +429,16 @@ async def _load_secondary_sensors(_: None) -> None:
443429

444430
await asyncio.gather(
445431
*(
446-
hass.async_create_task(
447-
async_load_platform(
448-
hass,
449-
Platform.SENSOR,
450-
DOMAIN,
451-
sensor_config,
452-
config,
453-
),
454-
)
432+
hass.async_create_task(_async_load_yaml_sensor(hass, sensor_config, config))
455433
for sensor_config in primary_sensors
456434
),
457435
)
458436

459437

438+
async def _async_load_yaml_sensor(hass: HomeAssistant, sensor_config: ConfigType, config: ConfigType) -> None:
439+
await async_load_platform(hass, Platform.SENSOR, DOMAIN, sensor_config, config)
440+
441+
460442
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
461443
"""Set up Powercalc integration from a config entry."""
462444

custom_components/powercalc/configuration/config_entry_conversion.py

Lines changed: 36 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22

33
from __future__ import annotations
44

5-
import copy
65
from datetime import timedelta
7-
from typing import Any
86

97
from homeassistant.config_entries import ConfigEntry
108
from homeassistant.const import CONF_ENTITY_ID, CONF_NAME
@@ -35,96 +33,57 @@
3533
)
3634

3735

38-
def convert_config_entry_to_sensor_config(config_entry: ConfigEntry, hass: HomeAssistant) -> ConfigType: # noqa: C901
39-
"""Convert the config entry structure to the sensor config used to create the entities."""
40-
sensor_config = dict(config_entry.data.copy())
41-
sensor_type = sensor_config.get(CONF_SENSOR_TYPE)
36+
def _convert_template(config: ConfigType, source_key: str, target_key: str, hass: HomeAssistant) -> None:
37+
if source_key in config:
38+
config[target_key] = Template(config.pop(source_key), hass)
4239

43-
def handle_sensor_type() -> None:
44-
"""Handle sensor type-specific configuration."""
45-
if sensor_type == SensorType.GROUP:
46-
sensor_config[CONF_CREATE_GROUP] = sensor_config.get(CONF_NAME)
47-
elif sensor_type == SensorType.REAL_POWER:
48-
sensor_config[CONF_POWER_SENSOR_ID] = sensor_config.get(CONF_ENTITY_ID)
49-
sensor_config[CONF_FORCE_ENERGY_SENSOR_CREATION] = True
5040

51-
def process_template(config: dict[str, Any], template_key: str, target_key: str) -> None:
52-
"""Convert a template key in the config to a Template object."""
53-
if template_key in config:
54-
config[target_key] = Template(config[template_key], hass)
55-
del config[template_key]
56-
57-
def process_on_time(config: dict[str, Any]) -> None:
58-
"""Convert on_time dictionary to timedelta."""
59-
on_time = config.get(CONF_ON_TIME)
60-
config[CONF_ON_TIME] = (
41+
def convert_config_entry_to_sensor_config(config_entry: ConfigEntry, hass: HomeAssistant) -> ConfigType:
42+
"""Convert the config entry structure to the sensor config used to create the entities."""
43+
sensor_config = dict(config_entry.data)
44+
sensor_type = sensor_config.get(CONF_SENSOR_TYPE)
45+
if sensor_type == SensorType.GROUP:
46+
sensor_config[CONF_CREATE_GROUP] = sensor_config.get(CONF_NAME)
47+
elif sensor_type == SensorType.REAL_POWER:
48+
sensor_config[CONF_POWER_SENSOR_ID] = sensor_config.get(CONF_ENTITY_ID)
49+
sensor_config[CONF_FORCE_ENERGY_SENSOR_CREATION] = True
50+
51+
if CONF_DAILY_FIXED_ENERGY in sensor_config:
52+
daily_fixed_config = dict(sensor_config[CONF_DAILY_FIXED_ENERGY])
53+
_convert_template(daily_fixed_config, CONF_VALUE_TEMPLATE, CONF_VALUE, hass)
54+
on_time = daily_fixed_config.get(CONF_ON_TIME)
55+
daily_fixed_config[CONF_ON_TIME] = (
6156
timedelta(hours=on_time["hours"], minutes=on_time["minutes"], seconds=on_time["seconds"])
6257
if on_time
6358
else timedelta(days=1)
6459
)
65-
66-
def process_states_power(states_power: dict[str, Any] | list[dict[str, Any]]) -> dict[str, Any]:
67-
"""Convert state power values to Template objects where necessary."""
68-
return {
69-
key: Template(value, hass) if isinstance(value, str) and "{{" in value else value
70-
for key, value in normalize_states_power(states_power).items()
71-
}
72-
73-
def process_daily_fixed_energy() -> None:
74-
"""Process daily fixed energy configuration."""
75-
if CONF_DAILY_FIXED_ENERGY not in sensor_config:
76-
return
77-
78-
daily_fixed_config = copy.copy(sensor_config[CONF_DAILY_FIXED_ENERGY])
79-
process_template(daily_fixed_config, CONF_VALUE_TEMPLATE, CONF_VALUE)
80-
process_on_time(daily_fixed_config)
8160
sensor_config[CONF_DAILY_FIXED_ENERGY] = daily_fixed_config
8261

83-
def process_fixed_config() -> None:
84-
"""Process fixed energy configuration."""
85-
if CONF_FIXED not in sensor_config:
86-
return
87-
88-
fixed_config = copy.copy(sensor_config[CONF_FIXED])
89-
process_template(fixed_config, CONF_POWER_TEMPLATE, CONF_POWER)
62+
if CONF_FIXED in sensor_config:
63+
fixed_config = dict(sensor_config[CONF_FIXED])
64+
_convert_template(fixed_config, CONF_POWER_TEMPLATE, CONF_POWER, hass)
9065
if CONF_STATES_POWER in fixed_config:
91-
fixed_config[CONF_STATES_POWER] = process_states_power(fixed_config[CONF_STATES_POWER])
66+
fixed_config[CONF_STATES_POWER] = {
67+
key: Template(value, hass) if isinstance(value, str) and "{{" in value else value
68+
for key, value in normalize_states_power(fixed_config[CONF_STATES_POWER]).items()
69+
}
9270
sensor_config[CONF_FIXED] = fixed_config
9371

94-
def process_linear_config() -> None:
95-
"""Process linear energy configuration."""
96-
if CONF_LINEAR not in sensor_config:
97-
return
98-
99-
linear_config = copy.copy(sensor_config[CONF_LINEAR])
100-
sensor_config[CONF_LINEAR] = linear_config
72+
if CONF_LINEAR in sensor_config:
73+
sensor_config[CONF_LINEAR] = dict(sensor_config[CONF_LINEAR])
10174

102-
def process_calculation_enabled_condition() -> None:
103-
"""Process calculation enabled condition template."""
104-
if CONF_CALCULATION_ENABLED_CONDITION in sensor_config:
105-
sensor_config[CONF_CALCULATION_ENABLED_CONDITION] = Template(
106-
sensor_config[CONF_CALCULATION_ENABLED_CONDITION],
107-
hass,
108-
)
109-
110-
def process_utility_meter_offset() -> None:
111-
if CONF_UTILITY_METER_OFFSET in sensor_config:
112-
sensor_config[CONF_UTILITY_METER_OFFSET] = timedelta(days=sensor_config[CONF_UTILITY_METER_OFFSET])
113-
114-
def process_playbook_config() -> None:
115-
if CONF_PLAYBOOK not in sensor_config:
116-
return
117-
playbook_config = copy.copy(sensor_config[CONF_PLAYBOOK])
75+
if CONF_PLAYBOOK in sensor_config:
76+
playbook_config = dict(sensor_config[CONF_PLAYBOOK])
11877
playbook_config[CONF_PLAYBOOKS] = normalize_playbooks(playbook_config[CONF_PLAYBOOKS])
11978
sensor_config[CONF_PLAYBOOK] = playbook_config
12079

121-
handle_sensor_type()
80+
if CONF_CALCULATION_ENABLED_CONDITION in sensor_config:
81+
sensor_config[CONF_CALCULATION_ENABLED_CONDITION] = Template(
82+
sensor_config[CONF_CALCULATION_ENABLED_CONDITION],
83+
hass,
84+
)
12285

123-
process_daily_fixed_energy()
124-
process_fixed_config()
125-
process_linear_config()
126-
process_playbook_config()
127-
process_calculation_enabled_condition()
128-
process_utility_meter_offset()
86+
if CONF_UTILITY_METER_OFFSET in sensor_config:
87+
sensor_config[CONF_UTILITY_METER_OFFSET] = timedelta(days=sensor_config[CONF_UTILITY_METER_OFFSET])
12988

13089
return sensor_config

custom_components/powercalc/power_profile/sub_profile_selector.py

Lines changed: 25 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -53,23 +53,30 @@ def get_tracking_entities(self) -> list[str]:
5353
def _create_matcher(self, matcher_config: dict) -> SubProfileMatcher:
5454
"""Create a matcher from json config. Can be extended for more matchers in the future."""
5555
matcher_type: SubProfileMatcherType = matcher_config["type"]
56-
57-
matcher_classes: dict[SubProfileMatcherType, type[SubProfileMatcher]] = {
58-
SubProfileMatcherType.ATTRIBUTE: AttributeMatcher,
59-
SubProfileMatcherType.ENTITY_STATE: EntityStateMatcher,
60-
SubProfileMatcherType.ENTITY_ID: EntityIdMatcher,
61-
SubProfileMatcherType.ENTITY_REGISTRY: EntityRegistryMatcher,
62-
SubProfileMatcherType.INTEGRATION: IntegrationMatcher,
63-
SubProfileMatcherType.MODEL_ID: ModelIdMatcher,
64-
}
65-
if matcher_type not in matcher_classes:
66-
raise PowercalcSetupError(f"Unknown sub profile matcher type: {matcher_type}")
67-
68-
return matcher_classes[matcher_type].from_config(
69-
matcher_config,
70-
hass=self._hass,
71-
source_entity=self._source_entity,
72-
)
56+
match matcher_type:
57+
case SubProfileMatcherType.ATTRIBUTE:
58+
return AttributeMatcher(matcher_config["attribute"], matcher_config["map"])
59+
case SubProfileMatcherType.ENTITY_STATE:
60+
return EntityStateMatcher(
61+
self._hass,
62+
self._source_entity,
63+
matcher_config["entity_id"],
64+
matcher_config["map"],
65+
)
66+
case SubProfileMatcherType.ENTITY_ID:
67+
return EntityIdMatcher(matcher_config["pattern"], matcher_config["profile"])
68+
case SubProfileMatcherType.ENTITY_REGISTRY:
69+
return EntityRegistryMatcher(
70+
matcher_config["property"],
71+
matcher_config["value"],
72+
matcher_config["profile"],
73+
)
74+
case SubProfileMatcherType.INTEGRATION:
75+
return IntegrationMatcher(matcher_config["integration"], matcher_config["profile"])
76+
case SubProfileMatcherType.MODEL_ID:
77+
return ModelIdMatcher(matcher_config["model_id"], matcher_config["profile"])
78+
case _:
79+
raise PowercalcSetupError(f"Unknown sub profile matcher type: {matcher_type}")
7380

7481

7582
class SubProfileSelectConfig(NamedTuple):
@@ -78,21 +85,12 @@ class SubProfileSelectConfig(NamedTuple):
7885

7986

8087
class SubProfileMatcher(Protocol):
81-
@classmethod
82-
def from_config(
83-
cls,
84-
config: dict,
85-
*,
86-
hass: HomeAssistant | None = None,
87-
source_entity: SourceEntity | None = None,
88-
) -> SubProfileMatcher:
89-
"""Create a matcher from a config dict."""
90-
9188
def match(self, entity_state: State, source_entity: SourceEntity) -> str | None:
9289
"""Returns a sub profile."""
9390

9491
def get_tracking_entities(self) -> list[str]:
9592
"""Get extra entities to track for state changes."""
93+
return []
9694

9795

9896
class EntityStateMatcher(SubProfileMatcher):
@@ -119,17 +117,6 @@ def match(self, entity_state: State, source_entity: SourceEntity) -> str | None:
119117

120118
return self._mapping.get(state.state)
121119

122-
@classmethod
123-
def from_config(
124-
cls,
125-
config: dict,
126-
*,
127-
hass: HomeAssistant | None = None,
128-
source_entity: SourceEntity | None = None,
129-
) -> EntityStateMatcher:
130-
assert hass is not None
131-
return cls(hass, source_entity, config["entity_id"], config["map"])
132-
133120
def get_tracking_entities(self) -> list[str]:
134121
return [self._entity_id]
135122

@@ -146,19 +133,6 @@ def match(self, entity_state: State, source_entity: SourceEntity) -> str | None:
146133

147134
return self._mapping.get(val)
148135

149-
@classmethod
150-
def from_config(
151-
cls,
152-
config: dict,
153-
*,
154-
hass: HomeAssistant | None = None,
155-
source_entity: SourceEntity | None = None,
156-
) -> AttributeMatcher:
157-
return cls(config["attribute"], config["map"])
158-
159-
def get_tracking_entities(self) -> list[str]:
160-
return []
161-
162136

163137
class EntityIdMatcher(SubProfileMatcher):
164138
def __init__(self, pattern: str, profile: str) -> None:
@@ -171,19 +145,6 @@ def match(self, entity_state: State, source_entity: SourceEntity) -> str | None:
171145

172146
return None
173147

174-
@classmethod
175-
def from_config(
176-
cls,
177-
config: dict,
178-
*,
179-
hass: HomeAssistant | None = None,
180-
source_entity: SourceEntity | None = None,
181-
) -> EntityIdMatcher:
182-
return cls(config["pattern"], config["profile"])
183-
184-
def get_tracking_entities(self) -> list[str]:
185-
return []
186-
187148

188149
class IntegrationMatcher(SubProfileMatcher):
189150
def __init__(self, integration: str, profile: str) -> None:
@@ -200,19 +161,6 @@ def match(self, entity_state: State, source_entity: SourceEntity) -> str | None:
200161

201162
return None
202163

203-
@classmethod
204-
def from_config(
205-
cls,
206-
config: dict,
207-
*,
208-
hass: HomeAssistant | None = None,
209-
source_entity: SourceEntity | None = None,
210-
) -> IntegrationMatcher:
211-
return cls(config["integration"], config["profile"])
212-
213-
def get_tracking_entities(self) -> list[str]:
214-
return []
215-
216164

217165
class EntityRegistryMatcher(SubProfileMatcher):
218166
def __init__(self, property_name: str, value: object, profile: str) -> None:
@@ -234,19 +182,6 @@ def match(self, entity_state: State, source_entity: SourceEntity) -> str | None:
234182

235183
return None
236184

237-
@classmethod
238-
def from_config(
239-
cls,
240-
config: dict,
241-
*,
242-
hass: HomeAssistant | None = None,
243-
source_entity: SourceEntity | None = None,
244-
) -> EntityRegistryMatcher:
245-
return cls(config["property"], config["value"], config["profile"])
246-
247-
def get_tracking_entities(self) -> list[str]:
248-
return []
249-
250185
def _matches_registry_value(self, registry_value: object) -> bool:
251186
if registry_value == self._value:
252187
return True
@@ -271,16 +206,3 @@ def match(self, entity_state: State, source_entity: SourceEntity) -> str | None:
271206
return self._profile
272207

273208
return None
274-
275-
@classmethod
276-
def from_config(
277-
cls,
278-
config: dict,
279-
*,
280-
hass: HomeAssistant | None = None,
281-
source_entity: SourceEntity | None = None,
282-
) -> ModelIdMatcher:
283-
return cls(config["model_id"], config["profile"])
284-
285-
def get_tracking_entities(self) -> list[str]:
286-
return []

0 commit comments

Comments
 (0)