Skip to content

fix: Handle None values from empty YAML fields to prevent AttributeError (fixes #212)#231

Merged
hugobloem merged 6 commits intohugobloem:mainfrom
thorstenhornung1:main
Feb 7, 2026
Merged

fix: Handle None values from empty YAML fields to prevent AttributeError (fixes #212)#231
hugobloem merged 6 commits intohugobloem:mainfrom
thorstenhornung1:main

Conversation

@thorstenhornung1
Copy link
Copy Markdown
Contributor

My AI skills exceed my Python skills. Therefore, I have used Claude code and my debug skills to address this issue.

Problem

The integration crashes with AttributeError: 'NoneType' object has no attribute 'lower' when scenes.yaml contains empty YAML fields. This occurs because the YAML parser (yaml.FullLoader) interprets empty fields as None values, which are then passed to functions expecting string values.

Example problematic YAML:

- id: living_room_bright
  name: Living Room Bright
  entities:
    light.living_room:
      state: "on"
      brightness: 255
      color_mode:           # Empty field parsed as None
      rgb_color:            # Empty field parsed as None

Error trace:

AttributeError: 'NoneType' object has no attribute 'lower'
  at helpers.py in get_area_from_entity_id()
  when attempting None.lower() on entity_id parameter

Root Cause

When YAML fields are left empty (e.g., brightness:, color_mode:), the yaml.FullLoader parser returns None for these values. The integration previously had no protection against these unintentional None values, causing crashes when:

  1. Empty attribute fields in scene configurations were copied directly to entity attributes
  2. Helper functions received None instead of expected string values for entity IDs
  3. UI fallback logic didn't account for None returns from helper functions

This is distinct from the intentional use of None for "don't care" semantics in state values, which must be preserved for proper scene matching logic.

Solution

Implemented a two-layer defense-in-depth strategy:

1. Prevention Layer (Primary Defense)

Filter out None values at the source when reading scene attributes from YAML, preventing unintentional None values from entering the system.

2. Defensive Layer (Robustness)

Add explicit None handling throughout the codebase to gracefully handle edge cases and improve overall robustness.

This approach ensures:

  • Unintentional None values from empty YAML fields are filtered out
  • Intentional None state values for "don't care" semantics are preserved
  • The system remains robust even if None values slip through validation
  • User-facing UI always displays meaningful fallback values

Changes Made

1. Prevention Layer

File: /custom_components/stateful_scenes/StatefulScenes.py
Location: Lines 734-737

Added None value filtering when copying scene attributes from YAML:

if domain in ATTRIBUTES_TO_CHECK:
    for attribute, value in scene_attributes.items():
        if attribute in ATTRIBUTES_TO_CHECK.get(domain):
            # Filter out None values from empty YAML fields
            # Note: None state values are preserved for "don't care" semantics
            if value is not None:
                attributes[attribute] = value

Impact: Prevents empty YAML fields from being copied as None attributes to entity configurations


2. Defensive Layer

File: /custom_components/stateful_scenes/helpers.py

Added None handling to 4 helper functions with updated type hints:

  • get_id_from_entity_id() - Lines 10-15
  • get_name_from_entity_id() - Lines 18-22
  • get_icon_from_entity_id() - Lines 25-29
  • get_area_from_entity_id() - Lines 32-45

All functions now accept str | None and return str | None, with early None checks.

File: /custom_components/stateful_scenes/StatefulScenes.py
Location: Lines 35-42

Added None check to area_name() function

File: /custom_components/stateful_scenes/config_flow.py

Added fallback values for UI display when helper functions return None:

  • Lines 163-166: Scene name and area with fallbacks
  • Line 250: Scene name with fallback "Unknown Scene"
  • Line 302: Entity name with fallback "Unknown Entity"

Semantic Preservation

This fix carefully preserves the existing behavior where None state values are used intentionally to mean "don't care" during scene matching:

  • Before: Scene with state: None for an entity matches regardless of current state
  • After: Same behavior preserved - only filters None from attributes, not from state fields

Testing

Tested scenarios:

  1. Empty YAML fields - no crashes, attributes not copied
  2. None state values - "don't care" matching still works
  3. Helper function robustness - graceful None returns
  4. UI fallback values - proper display of fallback text
  5. Regression testing - existing functionality unchanged

Breaking Changes

None. This is a pure bug fix with no breaking changes:

  • Existing scenes continue to work unchanged
  • Intentional None semantics are preserved
  • Only prevents crashes from unintentional empty YAML fields
  • All APIs maintain backward compatibility

🤖 Generated with Claude Code

thorstenhornung1 and others added 2 commits December 14, 2025 06:24
Implements a two-layer defense strategy to prevent crashes when scenes.yaml
contains empty YAML fields that are parsed as None values.

## Changes

### Prevention Layer
- Filter None values when reading scene attributes from YAML
- Location: StatefulScenes.py:734-737
- Prevents empty YAML fields from being copied as None attributes

### Defensive Layer
- Add None handling to 4 helper functions in helpers.py:
  * get_id_from_entity_id()
  * get_name_from_entity_id()
  * get_icon_from_entity_id()
  * get_area_from_entity_id()
- Update area_name() in StatefulScenes.py
- Add UI fallback values in config_flow.py (3 locations)

## Semantic Preservation
Preserves intentional None state values for "don't care" semantics
while filtering unintentional None from empty YAML fields.

Fixes AttributeError: 'NoneType' object has no attribute 'lower'

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…rom-empty-yaml-fields

fix: Handle None values from empty YAML fields to prevent AttributeError
thorstenhornung1 and others added 4 commits December 15, 2025 11:24
Implements proper path resolution to support relative paths like "scenes.yaml"
across Docker, HAOS, VM, and core installations.

## Changes

### Path Resolution (Core Fix)
- Update load_scenes_file() to accept hass parameter
- Use hass.config.path() to resolve relative paths
- Absolute paths continue to work unchanged
- Enhanced error messages with both input and resolved paths
- Added validation for empty paths and directories

### Auto-Detection with Graceful Fallback
- Add _detect_scenes_path() method to auto-detect scenes file
- Tries common paths: scenes.yaml, scenes.yml, config/scenes.yaml
- Pre-fills config flow form with detected path
- Shows warning if path cannot be auto-detected
- User can manually adjust if needed

### Updated Call Sites
- Runtime setup in __init__.py (line 46)
- Config flow validation in config_flow.py (line 83)

## Benefits
- Users can use default "scenes.yaml" without specifying absolute paths
- Works consistently across all installation methods
- Backward compatible - existing absolute paths work unchanged
- Better UX with auto-detection and pre-filling
- Clear error messages for troubleshooting

Fixes hugobloem#217

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…olution

fix: Resolve scenes.yaml path across different HA installation methods
@hugobloem
Copy link
Copy Markdown
Owner

Many thanks!

@hugobloem hugobloem merged commit cb26807 into hugobloem:main Feb 7, 2026
4 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.

2 participants