8000 Dev baseline by cabberley · Pull Request #5 · cabberley/utility_meter_evolved · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Dev baseline #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 19, 2025
Merged

Dev baseline #5

merged 2 commits into from
Jun 19, 2025

Conversation

cabberley
Copy link
Owner
@cabberley cabberley commented Jun 19, 2025

Summary by Sourcery

Add flexible cycle configuration with cron and predefined modes, refactor config and options flows, enhance validation, and expose additional attributes in sensors.

New Features:

  • Support cron-based cycle definitions with expression validation
  • Introduce predefined cycle configuration alongside cron mode selectable in UI
  • Expose cron pattern, cycle type, and source ID in sensor state attributes

Bug Fixes:

  • Handle corrupted restore data by catching combined KeyError and InvalidOperation
  • Fix tariff change logic to properly account for the 'total' tariff

Enhancements:

  • Refactor config flow to use Home Assistant ConfigFlow with multi-step user, cron, and predefined flows
  • Generate dynamic option schemas for cron and predefined modes
  • Centralize source and calculation sensor state parsing with Decimal and improved error handling
  • Filter out 'total' from tariff select options with a warning log

	modified:   .gitignore
	modified:   custom_components/utility_meter_evolved/__init__.py
	modified:   custom_components/utility_meter_evolved/config_flow.py
	modified:   custom_components/utility_meter_evolved/const.py
	modified:   custom_components/utility_meter_evolved/select.py
	modified:   custom_components/utility_meter_evolved/sensor.py
	new file:   custom_components/utility_meter_evolved/strings copy.json
	modified:   custom_components/utility_meter_evolved/strings.json
	modified:   custom_components/utility_meter_evolved/translations/en.json
Copy link
sourcery-ai bot commented Jun 19, 2025

Reviewer's Guide

Refactors config and options flows to leverage Home Assistant’s ConfigFlow API for both cron-based and predefined utility meter cycles with dynamic schema builders; improves validation (catching CronSimError and merging state restore errors); extends sensor state persistence for cron patterns and source IDs; filters out the “total” tariff in select entities with a warning; and adds a cron test script for pattern validation.

Sequence diagram for the new ConfigFlow and OptionsFlow interaction

sequenceDiagram
    actor User
    participant ConfigFlow as UtilityMeterEvolvedCustomConfigFlow
    participant OptionsFlow as OptionsFlowHandler
    participant HomeAssistant as Home Assistant

    User->>ConfigFlow: Start configuration (async_step_user)
    ConfigFlow->>HomeAssistant: Validate source sensor state
    HomeAssistant-->>ConfigFlow: State info
    ConfigFlow->>ConfigFlow: Validate input, choose flow
    alt Cron config
        ConfigFlow->>ConfigFlow: async_step_cron
        ConfigFlow->>HomeAssistant: Validate cron pattern
        HomeAssistant-->>ConfigFlow: Validation result
        ConfigFlow->>HomeAssistant: async_create_entry (options)
    else Predefined config
        ConfigFlow->>ConfigFlow: async_step_predefined
        ConfigFlow->>HomeAssistant: Validate predefined config
        HomeAssistant-->>ConfigFlow: Validation result
        ConfigFlow->>HomeAssistant: async_create_entry (options)
    end
    User->>OptionsFlow: Modify options (async_step_init)
    OptionsFlow->>HomeAssistant: Validate calc sensor state
    HomeAssistant-->>OptionsFlow: State info
    OptionsFlow->>HomeAssistant: async_create_entry (updated options)
Loading

Class diagram for updated ConfigFlow and OptionsFlow structure

classDiagram
    class UtilityMeterEvolvedCustomConfigFlow {
        +data: Optional[Dict[str, Any]]
        +async_step_user(user_input)
        +async_step_cron(user_input)
        +async_step_predefined(user_input)
        +_validate_state(state)
        +async_get_options_flow(config_entry)
    }
    class OptionsFlowHandler {
        +config_entry: ConfigEntry
        +options_schema
        +async_step_init(user_input)
        +_validate_state(state)
    }
    UtilityMeterEvolvedCustomConfigFlow --|> ConfigFlow
    OptionsFlowHandler --|> OptionsFlow
    OptionsFlowHandler o-- ConfigEntry
    UtilityMeterEvolvedCustomConfigFlow ..> OptionsFlowHandler : creates
Loading

Class diagram for UtilityMeterSensor and state persistence changes

classDiagram
    class UtilityMeterSensor {
        -_unrecorded_attributes: frozenset
        +extra_state_attributes()
        +_change_status(tariff)
    }
    UtilityMeterSensor : +_unrecorded_attributes = {ATTR_NEXT_RESET, CONF_CRON_PATTERN, CONF_METER_TYPE, ATTR_SOURCE_ID}
    UtilityMeterSensor : +extra_state_attributes() adds cron_pattern, meter_type, source_id
Loading

Class diagram for Select entity tariff filtering

classDiagram
    class UtilityMeterTariffSelect {
        +options: list[str]
        +current_option: str | None
    }
    UtilityMeterTariffSelect : +options filters out 'total' with warning
Loading

Flow diagram for dynamic option schema creation

flowchart TD
    A[User selects config type] -->|cron| B[create_option_schema_cron]
    A -->|predefined| C[create_option_schema_predefined]
    B --> D[Options schema for cron cycles]
    C --> E[Options schema for predefined cycles]
    D --> F[OptionsFlowHandler uses schema]
    E --> F
    F --> G[Show options form to user]
Loading

File-Level Changes

Change Details Files
Refactor configuration flows to HA ConfigFlow API supporting cron and predefined cycles
  • Replace SchemaFlowFormStep with config_entries.ConfigFlow class and async_step_* methods
  • Define BASE_CONFIG_SCHEMA, CRON_CYCLES_SCHEMA, PREDEFINED_CYCLES_SCHEMA
  • Route user input to async_step_cron or async_step_predefined based on config_type
custom_components/utility_meter_evolved/config_flow.py
Implement dynamic OptionsFlowHandler with schema builders for cron and predefined options
  • Introduce OptionsFlowHandler class using create_option_schema_cron/predefined
  • Dynamically set options_schema based on existing config_entry.options
  • Handle removal of calculated sensor option
custom_components/utility_meter_evolved/config_flow.py
Improve validation by catching CronSimError and merging sensor rest 8000 ore errors
  • Catch CronSimError instead of voluptuous.Invalid in config and options validation
  • Validate cron patterns using CronSim in async_step_cron
  • Catch KeyError and InvalidOperation together when restoring sensor state
custom_components/utility_meter_evolved/config_flow.py
custom_components/utility_meter_evolved/sensor.py
Extend sensor state persistence and extra attributes to include cron pattern and source id
  • Add CONF_CRON_PATTERN, CONF_METER_TYPE, ATTR_SOURCE_ID to _unrecorded_attributes
  • Persist cron_pattern, meter_type, and source sensor ID in extra_state_attributes
custom_components/utility_meter_evolved/sensor.py
Filter out “total” tariff option in select entity with warning
  • Log a warning and remove “total” from tariff options list in select.options
custom_components/utility_meter_evolved/select.py
Add temp/crontest.py script for cron pattern validation
  • Provide a standalone script validating cron patterns via CronSim and voluptuous
  • Include example usage and error reporting for invalid patterns
temp/crontest.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey @cabberley - I've reviewed your changes - here's some feedback:

  • Remove the temp/crontest.py scratch file from the commit, as it doesn’t belong in the production codebase.
  • The create_option_schema_cron and create_option_schema_predefined functions duplicate a lot of schema logic and manually merge .schema dictionaries—consider extracting common fields and building schemas more declaratively to DRY up the code.
  • There’s significant duplication between the user, cron, and predefined flow steps—extract the shared validation logic into helper methods to simplify the config and options flow implementations.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Remove the `temp/crontest.py` scratch file from the commit, as it doesn’t belong in the production codebase.
- The `create_option_schema_cron` and `create_option_schema_predefined` functions duplicate a lot of schema logic and manually merge `.schema` dictionaries—consider extracting common fields and building schemas more declaratively to DRY up the code.
- There’s significant duplication between the user, cron, and predefined flow steps—extract the shared validation logic into helper methods to simplify the config and options flow implementations.

## Individual Comments

### Comment 1
<location> `custom_components/utility_meter_evolved/config_flow.py:140` </location>
<code_context>
+def create_option_schema_cron(data):
+    """Create the options schema for cron cycles."""
+
+    if data[CONF_SOURCE_CALC_SENSOR] is None:
+        cron_option_schema_1 = vol.Schema(
+            {
+                vol.Required(CONF_SOURCE_SENSOR,
+                    default=data[CONF_SOURCE_SENSOR]): selector.EntitySelector(
+                    selector.EntitySelectorConfig(domain=SENSOR_DOMAIN),
+                ),
+                vol.Optional(CONF_SOURCE_CALC_SENSOR ): selector.EntitySelector(
+                    selector.EntitySelectorConfig(domain=SENSOR_DOMAIN),
+                ),
+            }
+        )
+    else:
+        cron_option_schema_1 = vol.Schema(
+            {
</code_context>

<issue_to_address>
Repeated schema construction logic could be refactored.

Extract the shared schema-building logic from both functions into a helper to avoid duplication and simplify future maintenance.

Suggested implementation:

```python
def _build_sensor_entity_schema(data):
    """Helper to build the shared entity selector schema."""
    schema = {
        vol.Required(CONF_SOURCE_SENSOR, default=data.get(CONF_SOURCE_SENSOR)): selector.EntitySelector(
            selector.EntitySelectorConfig(domain=SENSOR_DOMAIN),
        ),
        vol.Optional(CONF_SOURCE_CALC_SENSOR): selector.EntitySelector(
            selector.EntitySelectorConfig(domain=SENSOR_DOMAIN),
        ),
    }
    return schema

def create_option_schema_cron(data):
    """Create the options schema for cron cycles."""

```

```python

    schema = _build_sensor_entity_schema(data)
    schema[vol.Required(CONF_METER_PERIODICALLY_RESETTING)] = selector.BooleanSelector()
    return vol.Schema(schema)

```
</issue_to_address>

### Comment 2
<location> `custom_components/utility_meter_evolved/config_flow.py:268` </location>
<code_context>
+        vol.Optional(CONF_SOURCE_CALC_SENSOR): selector.EntitySelector(
+            selector.EntitySelectorConfig(domain=SENSOR_DOMAIN),
+        ),
+        vol.Required(CONF_CONFIG_TYPE, default=[CONF_CONFIG_PREDEFINED]): selector.SelectSelector(
+            selector.SelectSelectorConfig(
+                options=CONFIG_TYPES,
+                translation_key=CONF_CONFIG_TYPE,
+                mode=selector.SelectSelectorMode.LIST,
+                custom_value =False,
+                multiple=False
+            ),
+        ),
</code_context>

<issue_to_address>
Default value for CONF_CONFIG_TYPE is a list, but mode is not multiple.

Since multiple=False, the default should be a string (e.g., CONF_CONFIG_PREDEFINED) rather than a list to avoid validation issues.
</issue_to_address>

### Comment 3
<location> `custom_components/utility_meter_evolved/sensor.py:143` </location>
<code_context>
         source_entity_id,
     )

-    cron_pattern = None
+    cron_pattern = config_entry.options[CONF_CRON_PATTERN]
     delta_values = config_entry.options[CONF_METER_DELTA_VALUES]
     meter_offset = timedelta(days=config_entry.options[CONF_METER_OFFSET])
</code_context>

<issue_to_address>
Direct access to config_entry.options[CONF_CRON_PATTERN] may raise KeyError.

This key may be missing for predefined cycles, causing a KeyError. Use .get(CONF_CRON_PATTERN) with a default or add a guard to handle missing keys.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +140 to +149
if data[CONF_SOURCE_CALC_SENSOR] is None:
cron_option_schema_1 = vol.Schema(
{
vol.Required(CONF_SOURCE_SENSOR,
default=data[CONF_SOURCE_SENSOR]): selector.EntitySelector(
selector.EntitySelectorConfig(domain=SENSOR_DOMAIN),
),
vol.Optional(CONF_SOURCE_CALC_SENSOR ): selector.EntitySelector(
selector.EntitySelectorConfig(domain=SENSOR_DOMAIN),
),
Copy link

Choose a reason for hiding this comment

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

suggestion: Repeated schema construction logic could be refactored.

Extract the shared schema-building logic from both functions into a helper to avoid duplication and simplify future maintenance.

Suggested implementation:

def _build_sensor_entity_schema(data):
    """Helper to build the shared entity selector schema."""
    schema = {
        vol.Required(CONF_SOURCE_SENSOR, default=data.get(CONF_SOURCE_SENSOR)): selector.EntitySelector(
            selector.EntitySelectorConfig(domain=SENSOR_DOMAIN),
        ),
        vol.Optional(CONF_SOURCE_CALC_SENSOR): selector.EntitySelector(
            selector.EntitySelectorConfig(domain=SENSOR_DOMAIN),
        ),
    }
    return schema

def create_option_schema_cron(data):
    """Create the options schema for cron cycles."""
    schema = _build_sensor_entity_schema(data)
    schema[vol.Required(CONF_METER_PERIODICALLY_RESETTING)] = selector.BooleanSelector()
    return vol.Schema(schema)

Comment on lines +268 to +274
vol.Required(CONF_CONFIG_TYPE, default=[CONF_CONFIG_PREDEFINED]): selector.SelectSelector(
selector.SelectSelectorConfig(
options=CONFIG_TYPES,
translation_key=CONF_CONFIG_TYPE,
mode=selector.SelectSelectorMode.LIST,
custom_value =False,
multiple=False
Copy link

Choose a reason for hiding this comment

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

issue (bug_risk): Default value for CONF_CONFIG_TYPE is a list, but mode is not multiple.

Since multiple=False, the default should be a string (e.g., CONF_CONFIG_PREDEFINED) rather than a list to avoid validation issues.

@@ -140,7 +140,7 @@ async def async_setup_entry(
source_entity_id,
)

cron_pattern = None
Copy link

Choose a reason for hiding this comment

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

issue (bug_risk): Direct access to config_entry.options[CONF_CRON_PATTERN] may raise KeyError.

This key may be missing for predefined cycles, causing a KeyError. Use .get(CONF_CRON_PATTERN) with a default or add a guard to handle missing keys.

Comment on lines +492 to +495
if CONF_REMOVE_CALC_SENSOR in user_input:
if user_input[CONF_REMOVE_CALC_SENSOR]:
# Remove the calc sensor from the options.
user_input[CONF_SOURCE_CALC_SENSOR] = None
Copy link

Choose a reason for hiding this comment

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

suggestion (code-quality): Merge nested if conditions (merge-nested-ifs)

Suggested change
if CONF_REMOVE_CALC_SENSOR in user_input:
if user_input[CONF_REMOVE_CALC_SENSOR]:
# Remove the calc sensor from the options.
user_input[CONF_SOURCE_CALC_SENSOR] = None
if CONF_REMOVE_CALC_SENSOR in user_input and user_input[CONF_REMOVE_CALC_SENSOR]:
user_input[CONF_SOURCE_CALC_SENSOR] = None


ExplanationToo much nesting can make code difficult to understand, and this is especially
true in Python, where there are no brackets to help out with the delineation of
different nesting levels.

Reading deeply nested code is confusing, since you have to keep track of which
conditions relate to which levels. We therefore strive to reduce nesting where
possible, and the situation where two if conditions can be combined using
and is an easy win.

@cabberley cabberley merged commit b151202 into main Jun 19, 2025
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant
0