Skip to content

Conversation

FaserF
Copy link
Owner

@FaserF FaserF commented Sep 24, 2025

Proposed Changes

Full rewrite of the integration to support new sensors, more attributes and custom sensors with search queries.

Related Issues

(Github link to related issues or pull requests)

Summary by CodeRabbit

  • New Features

    • New options UI to add, edit, remove custom search sensors; three default sensors included.
    • Sensors now configured per-instance with unique IDs and improved display names.
    • Per-sensor recipe retrieval delivering richer recipe data (times, calories, servings, category, difficulty, ratings) and per-sensor error reporting.
  • Localization

    • Updated English and German texts to support the new options flow and messages.
  • Chores

    • Integration version bumped to 2.0.0.

@FaserF FaserF self-assigned this Sep 24, 2025
@FaserF FaserF added the enhancement New feature or request label Sep 24, 2025
Copy link

coderabbitai bot commented Sep 24, 2025

Walkthrough

Per-entry, per-sensor concurrent recipe resolution and attribute extraction implemented; sensors now come from entry.options and carry per-config IDs/names; a multi-step Options Flow to add/edit/remove custom sensors added; manifest bumped to 2.0.0 and strings/translations expanded for the options UI.

Changes

Cohort / File(s) Summary
Runtime & data pipeline
custom_components/chefkoch_ha/__init__.py
Replaces single global fetch with async_update_data(hass, entry) that resolves a recipe URL per sensor via _fetch_recipe_url and extracts attributes with extract_recipe_attributes, returning a map keyed by sensor id; simplifies executor/HTTP usage and adjusts logging/error payloads.
Entities / sensor model
custom_components/chefkoch_ha/sensor.py
Builds entities from entry.options["sensors"]; ChefkochSensor now accepts sensor_config (dict), exposes sensor_id, prefixes display name with "Chefkoch " when needed, uses id for unique_id, reads coordinator data keyed by sensor id, and filters attributes for empty/None values.
Config & Options flow
custom_components/chefkoch_ha/config_flow.py
Adds ChefkochConfigFlow (VERSION=1) and ChefkochOptionsFlowHandler with dynamic steps: init (menu), add_sensor, edit_sensor, edit_sensor_form, remove_sensor; introduces get_search_schema() and wires async_get_options_flow to persist sensors (generate UUIDs, update entry.options).
Defaults / constants
custom_components/chefkoch_ha/const.py
Removes SENSOR_TYPES and introduces DEFAULT_SENSORS (three default sensor descriptors: random, daily, vegan) with type, id, and name.
Manifest
custom_components/chefkoch_ha/manifest.json
Bumps package "version" from "1.1.2" to "2.0.0".
Strings & translations
custom_components/chefkoch_ha/strings.json, custom_components/chefkoch_ha/translations/en.json, custom_components/chefkoch_ha/translations/de.json
Reworks config UI strings: removes old flow title, adds config.step.user.title/description, replaces abort.already_configured with abort.single_instance_allowed, and adds a comprehensive config.options section (init menu, add/edit forms, abort.no_custom_sensors) to support the new options flow.
Docs
README.md
Expanded README with integration overview, default sensors, custom search sensor creation, filter descriptions, automation example, and update/reload instructions.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant HA as Home Assistant
  participant Flow as Config/Options Flow
  participant Entry as ConfigEntry
  participant CC as Chefkoch Integration
  participant Coord as DataUpdateCoordinator
  participant Sensor as ChefkochSensor

  User->>HA: Install / Open integration
  HA->>Flow: Start Config / Options Flow
  Flow-->>HA: Create/Update Entry with options.sensors
  HA->>CC: async_setup_entry(entry)
  CC->>Coord: init(update_method=async_update_data(hass, entry))
  Coord->>CC: Call update_method (initial / scheduled)
  CC->>CC: For each sensor in entry.options.sensors -> start fetch task
  CC->>CC: _fetch_recipe_url(sensor_config) / extract_recipe_attributes(url)
  CC-->>Coord: Return aggregated map {sensor_id: data_or_error}
  Coord-->>HA: Coordinator ready
  HA->>Sensor: Create entity per sensor_config
  Sensor-->>User: Entities expose native_value and attributes
Loading
sequenceDiagram
  autonumber
  actor User
  participant HA as Home Assistant
  participant Flow as Options Flow
  participant Entry as ConfigEntry
  participant Coord as DataUpdateCoordinator

  User->>HA: Open Integration Options
  HA->>Flow: async_step_init
  alt Add Defaults
    User->>Flow: Choose add_defaults
    Flow->>Entry: Append missing DEFAULT_SENSORS
  else Add Sensor
    User->>Flow: Choose add_sensor
    Flow->>Flow: Show form (get_search_schema)
    Flow->>Entry: Append new sensor with generated UUID
  else Edit Sensor
    User->>Flow: Choose edit_sensor
    Flow->>Flow: Select sensor -> edit_sensor_form
    Flow->>Entry: Update selected sensor data
  else Remove Sensor
    User->>Flow: Choose remove_sensor
    Flow->>Entry: Remove selected sensors
  end
  Entry-->>Coord: Options updated
  Coord->>Coord: Next refresh uses updated sensors list
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

I nibble code like clover sprigs, so neat,
New sensors sprout—unique IDs, a treat!
Options bloom: add, edit, prune with flair,
Async whiskers twitch—fetching recipes everywhere.
Version hops to two-dot-oh—what cheer! 🥕🐇

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "Integration rewrite: New sensors & attributes and code improvements" accurately summarizes the primary change: a rewrite that introduces new sensors, additional attributes, and general code improvements; this matches the changes in the diff (sensor/config flow updates, attribute extraction, manifest bump). The title is concise, specific, and clear for reviewers scanning history.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch new-featuresAndImprovements

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7e094c1 and 3c2abaa.

📒 Files selected for processing (1)
  • README.md (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • README.md

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (9)
custom_components/chefkoch_ha/config_flow.py (3)

53-67: Default sensors UX: consider auto-seeding on first run.

To reduce friction, pre-populate defaults on initial create (or via a migration) instead of requiring an options step.

If desired, I can draft an async_migrate_entry that seeds defaults when upgrading existing entries.


68-92: Localize difficulty values or store canonical values.

vol.In(['', 'Einfach', 'Normal', 'Schwer']) hardcodes German terms for all locales. Prefer canonical values (e.g., '', 'easy', 'normal', 'hard') and translate labels via strings.json, or make the enum locale-agnostic with selectors.


105-112: Abort reason may mislead on missing ID.

If a selected sensor ID isn’t found, a more specific abort like sensor_not_found would be clearer than no_sensors.

I can add the key to translations and wire it up if you want.

custom_components/chefkoch_ha/translations/de.json (1)

25-31: Difficulty values are German while EN UI exists.

If you adopt canonical values in code, translate the visible labels here to keep both locales consistent.

custom_components/chefkoch_ha/sensor.py (3)

25-39: Entity naming/ID handling looks good; consider future-proof unique_id.

Prefixing non-Chefkoch names is nice. For unique_id, consider including the ConfigEntry id to avoid collisions if multi-instance ever becomes allowed.

Example:

-        self._attr_unique_id = f"chefkoch_{sensor_config['id']}"
+        self._attr_unique_id = f"{coordinator.config_entry.entry_id}_chefkoch_{sensor_config['id']}"

45-50: Return None for unknown to mark state as unavailable.

Returning "unknown" as a string sets a real state. Prefer None to indicate unknown/unavailable to HA.

-        data = self.coordinator.data.get(self.sensor_id, {})
-        return data.get("title", "unknown")
+        data = self.coordinator.data.get(self.sensor_id, {})
+        return data.get("title")

51-64: Attributes filtering is good; add device_info for better UX.

Nice filtering. Adding device_info groups entities under a device in the UI.

 class ChefkochSensor(CoordinatorEntity, SensorEntity):
@@
         self._attr_unique_id = f"chefkoch_{sensor_config['id']}"
 
+    @property
+    def device_info(self):
+        return {
+            "identifiers": {(DOMAIN, "chefkoch")},
+            "name": "Chefkoch",
+            "manufacturer": "Chefkoch",
+        }
custom_components/chefkoch_ha/translations/en.json (1)

25-31: Difficulty values should match code representation.

If you switch to canonical difficulty values, translate the labels here accordingly.

custom_components/chefkoch_ha/__init__.py (1)

79-105: Prefer logging.exception and avoid blind except.

Use _LOGGER.exception to capture tracebacks, and scope exceptions more narrowly if possible.

-    try:
+    try:
         recipe = Recipe(recipe_url)
@@
-    except Exception as e:
-        _LOGGER.error(f"Failed to extract attributes for URL {recipe_url}: {e}")
-        return {"status": "error", "error_message": str(e)}
+    except Exception:
+        _LOGGER.exception("Failed to extract attributes for URL %s", recipe_url)
+        return {"status": "error", "error_message": "exception"}

Also apply similar change above when fetching per sensor:

-        except Exception as e:
-            _LOGGER.error("Error fetching data for sensor %s: %s", sensor_config['name'], e, exc_info=True)
-            data[sensor_id] = {"title": "Error", "status": "error", "error_message": str(e)}
+        except Exception:
+            _LOGGER.exception("Error fetching data for sensor %s", sensor_config['name'])
+            data[sensor_id] = {"title": "Error", "status": "error", "error_message": "exception"}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5919b3b and 297ab6b.

📒 Files selected for processing (7)
  • custom_components/chefkoch_ha/__init__.py (1 hunks)
  • custom_components/chefkoch_ha/config_flow.py (1 hunks)
  • custom_components/chefkoch_ha/manifest.json (1 hunks)
  • custom_components/chefkoch_ha/sensor.py (1 hunks)
  • custom_components/chefkoch_ha/strings.json (1 hunks)
  • custom_components/chefkoch_ha/translations/de.json (1 hunks)
  • custom_components/chefkoch_ha/translations/en.json (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
custom_components/chefkoch_ha/__init__.py (1)
custom_components/chefkoch_ha/sensor.py (2)
  • sensor_id (41-43)
  • async_setup_entry (9-19)
custom_components/chefkoch_ha/sensor.py (1)
custom_components/chefkoch_ha/__init__.py (1)
  • async_setup_entry (106-125)
🪛 Ruff (0.13.1)
custom_components/chefkoch_ha/__init__.py

102-102: Do not catch blind exception: Exception

(BLE001)


103-103: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

custom_components/chefkoch_ha/config_flow.py

14-14: Unused method argument: user_input

(ARG002)


37-37: Unused method argument: user_input

(ARG002)


53-53: Unused method argument: user_input

(ARG002)


79-79: Consider [*self.current_sensors, new_sensor] instead of concatenation

Replace with [*self.current_sensors, new_sensor]

(RUF005)

🪛 GitHub Actions: Validate with hassfest
custom_components/chefkoch_ha/strings.json

[error] 1-1: Invalid strings.json: extra keys not allowed @ data['options']['step']['init']['menu']. Got {'add_defaults': 'Add Default Sensors (Random, Daily, Vegan)', 'add_sensor': 'Add a new Recipe Sensor', 'edit_sensor': 'Edit an existing Sensor', 'remove_sensor': 'Remove a Sensor'}

🔇 Additional comments (9)
custom_components/chefkoch_ha/config_flow.py (3)

12-13: Config flow version noted.

VERSION=1 is fine; no issues.


16-20: Single-instance guard is correct.

Aborting on subsequent setups prevents duplicate entries. Good.


21-26: Options flow hook is correct.

Static async_get_options_flow returning the handler is the right pattern.

custom_components/chefkoch_ha/sensor.py (1)

13-19: Graceful handling when no sensors configured.

Warning and early return are fine.

custom_components/chefkoch_ha/__init__.py (4)

21-44: Concurrent per-sensor processing looks correct.

Using gather with per-sensor try/except and thread offloading for blocking calls is appropriate.


45-77: Retriever selection is fine; minor nit on falsey filtering.

Filtering out empty values for SearchRetriever is good. No functional issues spotted.


110-116: Coordinator wiring is good.

Passing the entry to the update method via lambda and daily scan interval is fine.


127-136: Options reload listener is correct; unload cleans up.

Listener + platform reload and data cleanup look good.

custom_components/chefkoch_ha/manifest.json (1)

17-17: Approve — version bump and dependency compatibility confirmed.
python-chefkoch==2.1.0 is published on PyPI and declares requires_python "<4.0,>=3.8" (compatible with Home Assistant's supported Python); manifest "version": "2.0.0" is appropriate.

Comment on lines 37 to 52
async def async_step_init(self, user_input=None):
"""Manage the options menu."""
menu_options = ["add_sensor"]
if self.current_sensors:
menu_options.extend(["edit_sensor", "remove_sensor"])

default_ids = {"random", "daily", "vegan"}
current_ids = {s.get("id") for s in self.current_sensors}
if not default_ids.issubset(current_ids):
menu_options.insert(0, "add_defaults")

return self.async_show_menu(
step_id="init",
menu_options=menu_options,
)

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix strings schema: use 'menu_options' in strings.json/translations.

async_show_menu expects translations under <domain>.options.step.<step_id>.menu_options.<item>. Hassfest is failing on strings.json because it uses menu instead of menu_options.

Apply this diff (strings.json; similar change needed in en.json and de.json):

-      "init": {
-        "title": "Manage Chefkoch Sensors",
-        "menu": {
+      "init": {
+        "title": "Manage Chefkoch Sensors",
+        "menu_options": {
           "add_defaults": "Add Default Sensors (Random, Daily, Vegan)",
           "add_sensor": "Add a new Recipe Sensor",
           "edit_sensor": "Edit an existing Sensor",
           "remove_sensor": "Remove a Sensor"
         }
       },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async def async_step_init(self, user_input=None):
"""Manage the options menu."""
menu_options = ["add_sensor"]
if self.current_sensors:
menu_options.extend(["edit_sensor", "remove_sensor"])
default_ids = {"random", "daily", "vegan"}
current_ids = {s.get("id") for s in self.current_sensors}
if not default_ids.issubset(current_ids):
menu_options.insert(0, "add_defaults")
return self.async_show_menu(
step_id="init",
menu_options=menu_options,
)
"init": {
"title": "Manage Chefkoch Sensors",
"menu_options": {
"add_defaults": "Add Default Sensors (Random, Daily, Vegan)",
"add_sensor": "Add a new Recipe Sensor",
"edit_sensor": "Edit an existing Sensor",
"remove_sensor": "Remove a Sensor"
}
},
🧰 Tools
🪛 Ruff (0.13.1)

37-37: Unused method argument: user_input

(ARG002)

🤖 Prompt for AI Agents
In custom_components/chefkoch_ha/config_flow.py around lines 37 to 52, the
translations keys used for async_show_menu are incorrect (they use "menu"
instead of the required "menu_options"); update the integration's strings.json
(and corresponding en.json and de.json) to move/rename the keys under
options.step.init.menu_options.<item> (e.g. change "options": { "step": {
"init": { "menu": { ... }}}} to "options": { "step": { "init": { "menu_options":
{ ... }}}}), ensuring each menu entry label key matches the menu_options entries
used by async_show_menu so Hassfest validates.

Comment on lines 16 to 23
"title": "Manage Chefkoch Sensors",
"menu": {
"add_defaults": "Add Default Sensors (Random, Daily, Vegan)",
"add_sensor": "Add a new Recipe Sensor",
"edit_sensor": "Edit an existing Sensor",
"remove_sensor": "Remove a Sensor"
}
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Fix hassfest error: replace 'menu' with 'menu_options'.

Pipeline error indicates invalid key menu. Use menu_options for menu steps.

Apply this diff:

-      "init": {
-        "title": "Manage Chefkoch Sensors",
-        "menu": {
+      "init": {
+        "title": "Manage Chefkoch Sensors",
+        "menu_options": {
           "add_defaults": "Add Default Sensors (Random, Daily, Vegan)",
           "add_sensor": "Add a new Recipe Sensor",
           "edit_sensor": "Edit an existing Sensor",
           "remove_sensor": "Remove a Sensor"
         }
       },

Please re-run hassfest after the change to confirm the pipeline passes.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="custom_components/chefkoch_ha/strings.json"

echo "Checking file: $FILE"
if [ -f "$FILE" ]; then
  echo "---- file content (first 200 lines) ----"
  nl -ba "$FILE" | sed -n '1,200p'
  echo "---- end file content ----"
else
  echo "File not found: $FILE"
fi

echo
echo "---- Searching repo for '\"menu\" :' occurrences ----"
rg -n --hidden -S '"menu"\s*:' || echo "no matches for \"menu\""

echo
echo "---- Searching repo for '\"menu_options\" :' occurrences ----"
rg -n --hidden -S '"menu_options"\s*:' || echo "no matches for \"menu_options\""

Length of output: 139


🏁 Script executed:

#!/bin/bash
FILE="custom_components/chefkoch_ha/strings.json"

echo "Checking file: $FILE"
if [ -f "$FILE" ]; then
  echo "---- file content (first 500 lines) ----"
  if command -v sed >/dev/null 2>&1; then
    sed -n '1,500p' "$FILE"
  elif command -v python3 >/dev/null 2>&1; then
    python3 - <<PY
with open("$FILE", "r", encoding="utf-8") as f:
    for i, line in enumerate(f, 1):
        if i > 500:
            break
        print(f"{i}\t{line.rstrip()}")
PY
  else
    head -n 500 "$FILE" || cat "$FILE"
  fi
else
  echo "File not found: $FILE"
  exit 0
fi

echo
echo "---- Searching for '\"menu\" :' and '\"menu_options\" :' in the file ----"
if command -v rg >/dev/null 2>&1; then
  rg -n --hidden -S '"menu"\s*:' "$FILE" || echo "no matches for \"menu\" in $FILE"
  rg -n --hidden -S '"menu_options"\s*:' "$FILE" || echo "no matches for \"menu_options\" in $FILE"
else
  python3 - <<PY
import re, sys
pat1=re.compile(r'"menu"\s*:')
pat2=re.compile(r'"menu_options"\s*:')
f="$FILE"
try:
    with open(f, 'r', encoding='utf-8') as fh:
        for i, l in enumerate(fh, 1):
            if pat1.search(l):
                print(f"{f}:{i}:{l.rstrip()}")
    with open(f, 'r', encoding='utf-8') as fh:
        for i, l in enumerate(fh, 1):
            if pat2.search(l):
                print(f"{f}:{i}:{l.rstrip()}")
except Exception as e:
    print("error reading file:", e)
PY
fi

Length of output: 1860


Fix hassfest: replace "menu" with "menu_options" in custom_components/chefkoch_ha/strings.json

Hassfest flags an invalid key "menu" under options.step.init — change it to "menu_options" and re-run hassfest.

-      "init": {
-        "title": "Manage Chefkoch Sensors",
-        "menu": {
+      "init": {
+        "title": "Manage Chefkoch Sensors",
+        "menu_options": {
           "add_defaults": "Add Default Sensors (Random, Daily, Vegan)",
           "add_sensor": "Add a new Recipe Sensor",
           "edit_sensor": "Edit an existing Sensor",
           "remove_sensor": "Remove a Sensor"
         }
       },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"title": "Manage Chefkoch Sensors",
"menu": {
"add_defaults": "Add Default Sensors (Random, Daily, Vegan)",
"add_sensor": "Add a new Recipe Sensor",
"edit_sensor": "Edit an existing Sensor",
"remove_sensor": "Remove a Sensor"
}
},
"init": {
"title": "Manage Chefkoch Sensors",
"menu_options": {
"add_defaults": "Add Default Sensors (Random, Daily, Vegan)",
"add_sensor": "Add a new Recipe Sensor",
"edit_sensor": "Edit an existing Sensor",
"remove_sensor": "Remove a Sensor"
}
},
🤖 Prompt for AI Agents
In custom_components/chefkoch_ha/strings.json around lines 16 to 23, the
options.step.init object uses the invalid key "menu" which hassfest rejects;
rename the "menu" key to "menu_options" preserving its child entries
("add_defaults", "add_sensor", "edit_sensor", "remove_sensor") so the structure
matches Home Assistant's expected schema, then re-run hassfest to confirm the
warning is resolved.

Comment on lines 16 to 23
"title": "Chefkoch-Sensoren verwalten",
"menu": {
"add_defaults": "Standard-Sensoren hinzufügen (Zufall, Täglich, Vegan)",
"add_sensor": "Neuen Rezept-Sensor hinzufügen",
"edit_sensor": "Bestehenden Sensor bearbeiten",
"remove_sensor": "Sensor entfernen"
}
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Align with strings schema: use 'menu_options'.

Mirror the strings.json change: rename menu to menu_options under options.step.init.

Apply this diff:

-        "menu": {
+        "menu_options": {
           "add_defaults": "Standard-Sensoren hinzufügen (Zufall, Täglich, Vegan)",
           "add_sensor": "Neuen Rezept-Sensor hinzufügen",
           "edit_sensor": "Bestehenden Sensor bearbeiten",
           "remove_sensor": "Sensor entfernen"
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"title": "Chefkoch-Sensoren verwalten",
"menu": {
"add_defaults": "Standard-Sensoren hinzufügen (Zufall, Täglich, Vegan)",
"add_sensor": "Neuen Rezept-Sensor hinzufügen",
"edit_sensor": "Bestehenden Sensor bearbeiten",
"remove_sensor": "Sensor entfernen"
}
},
"title": "Chefkoch-Sensoren verwalten",
"menu_options": {
"add_defaults": "Standard-Sensoren hinzufügen (Zufall, Täglich, Vegan)",
"add_sensor": "Neuen Rezept-Sensor hinzufügen",
"edit_sensor": "Bestehenden Sensor bearbeiten",
"remove_sensor": "Sensor entfernen"
}
},
🤖 Prompt for AI Agents
In custom_components/chefkoch_ha/translations/de.json around lines 16 to 23, the
JSON key "menu" under options.step.init must be renamed to "menu_options" to
match the strings schema; update the key name only (keep the nested labels and
values unchanged) so the structure matches the updated strings.json.

Comment on lines 16 to 23
"title": "Manage Chefkoch Sensors",
"menu": {
"add_defaults": "Add Default Sensors (Random, Daily, Vegan)",
"add_sensor": "Add a new Recipe Sensor",
"edit_sensor": "Edit an existing Sensor",
"remove_sensor": "Remove a Sensor"
}
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Align with strings schema: use 'menu_options'.

Rename menu to menu_options under options.step.init to match hassfest schema and strings.json.

-        "menu": {
+        "menu_options": {
           "add_defaults": "Add Default Sensors (Random, Daily, Vegan)",
           "add_sensor": "Add a new Recipe Sensor",
           "edit_sensor": "Edit an existing Sensor",
           "remove_sensor": "Remove a Sensor"
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"title": "Manage Chefkoch Sensors",
"menu": {
"add_defaults": "Add Default Sensors (Random, Daily, Vegan)",
"add_sensor": "Add a new Recipe Sensor",
"edit_sensor": "Edit an existing Sensor",
"remove_sensor": "Remove a Sensor"
}
},
"title": "Manage Chefkoch Sensors",
"menu_options": {
"add_defaults": "Add Default Sensors (Random, Daily, Vegan)",
"add_sensor": "Add a new Recipe Sensor",
"edit_sensor": "Edit an existing Sensor",
"remove_sensor": "Remove a Sensor"
}
},
🤖 Prompt for AI Agents
In custom_components/chefkoch_ha/translations/en.json around lines 16 to 23, the
translations object uses the key "menu" under options.step.init but
hassfest/strings.json expects "menu_options"; rename the "menu" property to
"menu_options" and preserve all child keys and values exactly (add_defaults,
add_sensor, edit_sensor, remove_sensor) so the strings schema matches and
localization continues to work.

coderabbitai bot added a commit that referenced this pull request Sep 24, 2025
Docstrings generation was requested by @FaserF.

* #11 (comment)

The following files were modified:

* `custom_components/chefkoch_ha/__init__.py`
* `custom_components/chefkoch_ha/config_flow.py`
* `custom_components/chefkoch_ha/sensor.py`
Copy link

coderabbitai bot commented Sep 24, 2025

Note

Generated docstrings for this pull request at #12

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (7)
custom_components/chefkoch_ha/__init__.py (2)

33-35: Use logging.exception for tracebacks; avoid blind Exception if possible

Replace error logging with logging.exception to capture stack traces. If you need to keep the broad except, annotate BLE001 or narrow exceptions.

Apply this diff:

-        except Exception as e:
-            _LOGGER.error("Error during data fetching for sensor %s: %s", sensor_config['name'], e, exc_info=True)
-            data[sensor_id] = {"title": "Error fetching URL", "status": "error", "error_message": str(e)}
+        except Exception as e:  # noqa: BLE001
+            _LOGGER.exception("Error during data fetching for sensor %s: %s", sensor_config["name"], e)
+            data[sensor_id] = {"title": "Error fetching URL", "status": "error", "error_message": str(e)}

87-119: Prefer logging.exception; narrow catches or annotate

  • Switch to logging.exception for initialization failures.
  • If broad except is necessary, annotate BLE001 or narrow to expected exceptions from chefkoch’s API.

Apply this diff:

 def extract_recipe_attributes(recipe_url):
@@
-    try:
-        recipe = Recipe(recipe_url)
-    except Exception as e:
-        _LOGGER.error("Failed to initialize Recipe object for URL %s: %s", recipe_url, e)
+    try:
+        recipe = Recipe(recipe_url)
+    except Exception as e:  # noqa: BLE001
+        _LOGGER.exception("Failed to initialize Recipe object for URL %s: %s", recipe_url, e)
         return {"title": "Error loading recipe details", "url": recipe_url, "status": "error", "error_message": f"Could not parse recipe page: {e}"}
@@
-    def safe_get_attr(recipe_obj, attr_name, default=None):
-        try:
-            return getattr(recipe_obj, attr_name)
-        except Exception:
+    def safe_get_attr(recipe_obj, attr_name, default=None):
+        try:
+            return getattr(recipe_obj, attr_name)
+        except Exception:  # noqa: BLE001
             _LOGGER.debug("Could not get attribute '%s' for recipe %s", attr_name, recipe_obj.url)
             return default
custom_components/chefkoch_ha/strings.json (1)

23-37: Optional: add titles for edit/remove steps for a cleaner UI

Consider adding options.step.edit_sensor.title and options.step.remove_sensor.title to improve forms.

If you want this, I can draft the additions for all locales.

custom_components/chefkoch_ha/config_flow.py (4)

1-7: Import copy for deep-copying defaults

Needed for safe seeding of DEFAULT_SENSORS (see below).

Apply this diff:

 import voluptuous as vol
 import uuid
+import copy

8-21: Schema: set a non-None default for ‘name’

Avoid showing “None” as default in the form.

Apply this diff:

-        vol.Required("name", default=sensor_data.get("name")): str,
+        vol.Required("name", default=sensor_data.get("name", "")): str,

43-49: Minor: unused arg; make explicit

Rename unused parameter to underscore for clarity.

Apply this diff:

-    async def async_step_init(self, user_input=None):
+    async def async_step_init(self, _user_input=None):

52-55: Nit: avoid list concatenation in favor of unpack for readability

Purely stylistic; optional.

Apply this diff:

-            updated_sensors = self.current_sensors + [new_sensor]
+            updated_sensors = [*self.current_sensors, new_sensor]
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 297ab6b and 7e094c1.

📒 Files selected for processing (6)
  • custom_components/chefkoch_ha/__init__.py (2 hunks)
  • custom_components/chefkoch_ha/config_flow.py (1 hunks)
  • custom_components/chefkoch_ha/const.py (1 hunks)
  • custom_components/chefkoch_ha/strings.json (1 hunks)
  • custom_components/chefkoch_ha/translations/de.json (1 hunks)
  • custom_components/chefkoch_ha/translations/en.json (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
custom_components/chefkoch_ha/__init__.py (1)
custom_components/chefkoch_ha/sensor.py (2)
  • sensor_id (41-43)
  • async_setup_entry (9-19)
🪛 Ruff (0.13.1)
custom_components/chefkoch_ha/config_flow.py

43-43: Unused method argument: user_input

(ARG002)


53-53: Consider [*self.current_sensors, new_sensor] instead of concatenation

Replace with [*self.current_sensors, new_sensor]

(RUF005)

custom_components/chefkoch_ha/__init__.py

91-91: Do not catch blind exception: Exception

(BLE001)


92-92: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


98-98: Do not catch blind exception: Exception

(BLE001)

🪛 GitHub Actions: Validate with hassfest
custom_components/chefkoch_ha/strings.json

[error] 1-1: Invalid strings.json: extra keys not allowed @ data['options']['step']['init']['menu']. Got {'add_sensor': 'Add a new Search Sensor', 'edit_sensor': 'Edit an existing Search Sensor', 'remove_sensor': 'Remove a Search Sensor'} (hassfest). Command: docker run --rm -v /home/runner/work/ha-chefkoch/ha-chefkoch://github/workspace ghcr.io/home-assistant/hassfest

🪛 GitHub Actions: Cron actions
custom_components/chefkoch_ha/strings.json

[error] 1-1: Invalid strings.json: extra keys not allowed @ data['options']['step']['init']['menu']. Got {'add_sensor': 'Add a new Search Sensor', 'edit_sensor': 'Edit an existing Search Sensor', 'remove_sensor': 'Remove a Search Sensor'}

🔇 Additional comments (4)
custom_components/chefkoch_ha/__init__.py (1)

15-39: Per‑sensor concurrent fetch pipeline LGTM

The overall structure (read sensors from options, per-sensor task, run in executor/to_thread, aggregate by sensor_id) is sound.

custom_components/chefkoch_ha/translations/en.json (1)

15-22: Rename ‘menu’ to ‘menu_options’ to match strings schema

Same issue as strings.json; align to menu_options.

Apply this diff:

       "init": {
         "title": "Manage Chefkoch Sensors",
-        "menu": {
+        "menu_options": {
           "add_sensor": "Add a new Search Sensor",
           "edit_sensor": "Edit an existing Search Sensor",
           "remove_sensor": "Remove a Search Sensor"
         }
       },
custom_components/chefkoch_ha/translations/de.json (1)

15-22: ‘menu’ → ‘menu_options’ (schema compliance)

Mirror the base strings change in de.json.

Apply this diff:

       "init": {
         "title": "Chefkoch-Sensoren verwalten",
-        "menu": {
+        "menu_options": {
           "add_sensor": "Neuen Such-Sensor hinzufügen",
           "edit_sensor": "Bestehenden Such-Sensor bearbeiten",
           "remove_sensor": "Such-Sensor entfernen"
         }
       },
custom_components/chefkoch_ha/strings.json (1)

15-22: Fix hassfest failure: rename ‘menu’ to ‘menu_options’

Hassfest rejects options.step.init.menu. Use menu_options.

Apply this diff:

       "init": {
         "title": "Manage Chefkoch Sensors",
-        "menu": {
+        "menu_options": {
           "add_sensor": "Add a new Search Sensor",
           "edit_sensor": "Edit an existing Search Sensor",
           "remove_sensor": "Remove a Search Sensor"
         }
       },

Comment on lines +41 to +85
async def _fetch_recipe_url(sensor_config: dict) -> str | None:
"""Fetch the recipe URL based on sensor config."""
sensor_type = sensor_config["type"]

if sensor_type == "random":
retriever = RandomRetriever()
recipe = await asyncio.to_thread(retriever.get_recipe)
return recipe.url if recipe else None

elif sensor_type == "daily":
retriever = DailyRecipeRetriever()
recipes = await asyncio.to_thread(retriever.get_recipes, type="kochen")
return recipes[0].url if recipes and recipes[0] else None

elif sensor_type == "vegan":
retriever = SearchRetriever(health=["Vegan"])
recipes = await asyncio.to_thread(retriever.get_recipes, search_query="vegan")
return recipes[0].url if recipes and recipes[0] else None

elif sensor_type == "search":
search_query = sensor_config.get("search_query", "")

# Helper to parse comma-separated strings into lists
def parse_list(key):
value = sensor_config.get(key, "")
return [item.strip() for item in value.split(',') if item.strip()] if value else None

init_params = {
"properties": parse_list("properties"),
"health": parse_list("health"),
"categories": parse_list("categories"),
"countries": parse_list("countries"),
"meal_type": parse_list("meal_type"),
"prep_times": sensor_config.get("prep_times"),
"ratings": sensor_config.get("ratings"),
"sort": sensor_config.get("sort")
}

init_params = {k: v for k, v in init_params.items() if v}

retriever = SearchRetriever(**init_params)
recipes = await asyncio.to_thread(retriever.get_recipes, search_query=search_query)
return recipes[0].url if recipes and recipes[0] else None

return None
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Make first-recipe URL extraction robust; don’t drop valid “0” values in filters

  • Accessing recipes[0] is repeated and risks AttributeError if first item is None. Centralize safe extraction.
  • Filtering with “if v” drops valid falsy values (e.g., 0). Use explicit checks.

Apply this diff:

 async def _fetch_recipe_url(sensor_config: dict) -> str | None:
@@
-    if sensor_type == "random":
+    if sensor_type == "random":
         retriever = RandomRetriever()
         recipe = await asyncio.to_thread(retriever.get_recipe)
         return recipe.url if recipe else None
@@
-    elif sensor_type == "daily":
+    elif sensor_type == "daily":
         retriever = DailyRecipeRetriever()
         recipes = await asyncio.to_thread(retriever.get_recipes, type="kochen")
-        return recipes[0].url if recipes and recipes[0] else None
+        first = recipes[0] if recipes else None
+        return getattr(first, "url", None)
@@
-    elif sensor_type == "vegan":
+    elif sensor_type == "vegan":
         retriever = SearchRetriever(health=["Vegan"])
         recipes = await asyncio.to_thread(retriever.get_recipes, search_query="vegan")
-        return recipes[0].url if recipes and recipes[0] else None
+        first = recipes[0] if recipes else None
+        return getattr(first, "url", None)
@@
-    elif sensor_type == "search":
+    elif sensor_type == "search":
         search_query = sensor_config.get("search_query", "")
@@
-        init_params = {k: v for k, v in init_params.items() if v}
+        # Keep zeros; drop only None and empty strings
+        init_params = {
+            k: v
+            for k, v in init_params.items()
+            if v is not None and (not isinstance(v, str) or v.strip() != "")
+        }
@@
-        recipes = await asyncio.to_thread(retriever.get_recipes, search_query=search_query)
-        return recipes[0].url if recipes and recipes[0] else None
+        recipes = await asyncio.to_thread(retriever.get_recipes, search_query=search_query)
+        first = recipes[0] if recipes else None
+        return getattr(first, "url", None)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async def _fetch_recipe_url(sensor_config: dict) -> str | None:
"""Fetch the recipe URL based on sensor config."""
sensor_type = sensor_config["type"]
if sensor_type == "random":
retriever = RandomRetriever()
recipe = await asyncio.to_thread(retriever.get_recipe)
return recipe.url if recipe else None
elif sensor_type == "daily":
retriever = DailyRecipeRetriever()
recipes = await asyncio.to_thread(retriever.get_recipes, type="kochen")
return recipes[0].url if recipes and recipes[0] else None
elif sensor_type == "vegan":
retriever = SearchRetriever(health=["Vegan"])
recipes = await asyncio.to_thread(retriever.get_recipes, search_query="vegan")
return recipes[0].url if recipes and recipes[0] else None
elif sensor_type == "search":
search_query = sensor_config.get("search_query", "")
# Helper to parse comma-separated strings into lists
def parse_list(key):
value = sensor_config.get(key, "")
return [item.strip() for item in value.split(',') if item.strip()] if value else None
init_params = {
"properties": parse_list("properties"),
"health": parse_list("health"),
"categories": parse_list("categories"),
"countries": parse_list("countries"),
"meal_type": parse_list("meal_type"),
"prep_times": sensor_config.get("prep_times"),
"ratings": sensor_config.get("ratings"),
"sort": sensor_config.get("sort")
}
init_params = {k: v for k, v in init_params.items() if v}
retriever = SearchRetriever(**init_params)
recipes = await asyncio.to_thread(retriever.get_recipes, search_query=search_query)
return recipes[0].url if recipes and recipes[0] else None
return None
async def _fetch_recipe_url(sensor_config: dict) -> str | None:
"""Fetch the recipe URL based on sensor config."""
sensor_type = sensor_config["type"]
if sensor_type == "random":
retriever = RandomRetriever()
recipe = await asyncio.to_thread(retriever.get_recipe)
return recipe.url if recipe else None
elif sensor_type == "daily":
retriever = DailyRecipeRetriever()
recipes = await asyncio.to_thread(retriever.get_recipes, type="kochen")
first = recipes[0] if recipes else None
return getattr(first, "url", None)
elif sensor_type == "vegan":
retriever = SearchRetriever(health=["Vegan"])
recipes = await asyncio.to_thread(retriever.get_recipes, search_query="vegan")
first = recipes[0] if recipes else None
return getattr(first, "url", None)
elif sensor_type == "search":
search_query = sensor_config.get("search_query", "")
# Helper to parse comma-separated strings into lists
def parse_list(key):
value = sensor_config.get(key, "")
return [item.strip() for item in value.split(',') if item.strip()] if value else None
init_params = {
"properties": parse_list("properties"),
"health": parse_list("health"),
"categories": parse_list("categories"),
"countries": parse_list("countries"),
"meal_type": parse_list("meal_type"),
"prep_times": sensor_config.get("prep_times"),
"ratings": sensor_config.get("ratings"),
"sort": sensor_config.get("sort")
}
# Keep zeros; drop only None and empty strings
init_params = {
k: v
for k, v in init_params.items()
if v is not None and (not isinstance(v, str) or v.strip() != "")
}
retriever = SearchRetriever(**init_params)
recipes = await asyncio.to_thread(retriever.get_recipes, search_query=search_query)
first = recipes[0] if recipes else None
return getattr(first, "url", None)
return None
🤖 Prompt for AI Agents
In custom_components/chefkoch_ha/__init__.py around lines 41 to 85, centralize
safe extraction of the first recipe URL and avoid filtering out valid falsy
filter values: add a small helper (e.g., _first_recipe_url(recipes)) that
returns recipes[0].url only when recipes is a sequence with at least one element
and recipes[0] is not None, then replace repeated recipes[0].url checks with
calls to that helper; when constructing init_params use a filter that preserves
falsy-but-valid values (e.g., {k:v for k,v in init_params.items() if v is not
None} or explicitly check v is not None) instead of truthiness so 0 or
empty-string-as-valid are kept.

Comment on lines +26 to +30
if self._async_current_entries():
return self.async_abort(reason="single_instance_allowed")
if user_input is not None:
return self.async_create_entry(title="Chefkoch", data={}, options={"sensors": DEFAULT_SENSORS})
return self.async_show_form(step_id="user")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Deep-copy DEFAULT_SENSORS to avoid mutating the constant

Passing the list directly can leak mutations back into the module constant.

Apply this diff:

-            return self.async_create_entry(title="Chefkoch", data={}, options={"sensors": DEFAULT_SENSORS})
+            return self.async_create_entry(
+                title="Chefkoch",
+                data={},
+                options={"sensors": copy.deepcopy(DEFAULT_SENSORS)},
+            )

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In custom_components/chefkoch_ha/config_flow.py around lines 26 to 30, the code
assigns the module constant DEFAULT_SENSORS directly into the created entry
options, which can allow downstream mutations to alter the constant; import the
copy module and use copy.deepcopy(DEFAULT_SENSORS) (or an equivalent deep-copy)
when building options, e.g. options={"sensors": copy.deepcopy(DEFAULT_SENSORS)},
and add the import at the top of the file so the constant isn’t mutated.

Comment on lines +66 to +74
async def async_step_edit_sensor_form(self, user_input=None):
sensor_to_edit = next((s for s in self.current_sensors if s["id"] == self.sensor_to_edit_id), None)
if not sensor_to_edit:
return self.async_abort(reason="no_sensors")
if user_input is not None:
sensor_to_edit.update(user_input)
return self.async_create_entry(title="", data={"sensors": self.current_sensors})
return self.async_show_form(step_id="edit_sensor_form", data_schema=get_search_schema(sensor_to_edit), last_step=True)

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Abort reason key mismatch (‘no_sensors’ not defined in translations)

Use the existing ‘no_custom_sensors’ abort key to avoid untranslated aborts.

Apply this diff:

-        if not sensor_to_edit:
-            return self.async_abort(reason="no_sensors")
+        if not sensor_to_edit:
+            return self.async_abort(reason="no_custom_sensors")

To confirm there’s no ‘no_sensors’ key in translations, run:


🏁 Script executed:

#!/bin/bash
rg -n -C1 -S '"no_sensors"' custom_components/chefkoch_ha/strings.json custom_components/chefkoch_ha/translations/*.json || true

Length of output: 0


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "Repo root: $(pwd)"
echo
echo "List custom_components/chefkoch_ha:"
ls -la custom_components/chefkoch_ha || true
echo
echo "Show config_flow.py (lines 1-200):"
sed -n '1,200p' custom_components/chefkoch_ha/config_flow.py || true
echo
echo "Search repo for abort keys (no_sensors / no_custom_sensors):"
rg -n --hidden -S '\bno_sensors\b' || true
rg -n --hidden -S '\bno_custom_sensors\b' || true
echo
echo "Show strings.json (if exists):"
sed -n '1,200p' custom_components/chefkoch_ha/strings.json || true
echo
echo "Show translation files (if any):"
for f in custom_components/chefkoch_ha/translations/*.json; do
  echo "---- $f ----"
  sed -n '1,200p' "$f" || true
done

Length of output: 12810


Replace undefined abort reason 'no_sensors' with existing 'no_custom_sensors'

strings.json and translations use "no_custom_sensors" — update the abort reason to match.

-        if not sensor_to_edit:
-            return self.async_abort(reason="no_sensors")
+        if not sensor_to_edit:
+            return self.async_abort(reason="no_custom_sensors")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async def async_step_edit_sensor_form(self, user_input=None):
sensor_to_edit = next((s for s in self.current_sensors if s["id"] == self.sensor_to_edit_id), None)
if not sensor_to_edit:
return self.async_abort(reason="no_sensors")
if user_input is not None:
sensor_to_edit.update(user_input)
return self.async_create_entry(title="", data={"sensors": self.current_sensors})
return self.async_show_form(step_id="edit_sensor_form", data_schema=get_search_schema(sensor_to_edit), last_step=True)
async def async_step_edit_sensor_form(self, user_input=None):
sensor_to_edit = next((s for s in self.current_sensors if s["id"] == self.sensor_to_edit_id), None)
if not sensor_to_edit:
return self.async_abort(reason="no_custom_sensors")
if user_input is not None:
sensor_to_edit.update(user_input)
return self.async_create_entry(title="", data={"sensors": self.current_sensors})
return self.async_show_form(step_id="edit_sensor_form", data_schema=get_search_schema(sensor_to_edit), last_step=True)
🤖 Prompt for AI Agents
In custom_components/chefkoch_ha/config_flow.py around lines 66 to 74, the
async_step_edit_sensor_form aborts with an undefined reason "no_sensors"; change
that abort reason to the existing "no_custom_sensors" used in
strings/translations so the abort maps to the correct translation key; update
the return self.async_abort(reason="no_sensors") to use
reason="no_custom_sensors".

Comment on lines +4 to +8
DEFAULT_SENSORS = [
{"type": "random", "id": "random", "name": "Chefkoch Random Recipe"},
{"type": "daily", "id": "daily", "name": "Chefkoch Daily Recipe"},
{"type": "vegan", "id": "vegan", "name": "Chefkoch Vegan Recipe"}
]
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Defaults look good; avoid accidental mutation of DEFAULT_SENSORS at runtime

When seeding entry.options, deep-copy this list to prevent later options edits from mutating the module constant via shared references.

I’ve proposed the concrete fix in config_flow.py to deep-copy when creating the entry.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Searching for DEFAULT_SENSORS occurrences..."
rg -n --hidden -S "DEFAULT_SENSORS" -g '!*__pycache__/*' -C3 || true

echo
echo "Searching for config_flow.py files..."
rg -n --hidden -S "config_flow.py" -g '!*__pycache__/*' || true

echo
echo "Show const.py (if present)..."
sed -n '1,200p' custom_components/chefkoch_ha/const.py || true

echo
echo "Show config_flow.py (if present)..."
sed -n '1,400p' custom_components/chefkoch_ha/config_flow.py || true

Length of output: 5386


Deep-copy DEFAULT_SENSORS before storing it in config entry options

Seeding entry.options with the module list stores it by reference; the options flow mutates that list (sensor_to_edit.update(...)) and will alter the module constant. Fix in custom_components/chefkoch_ha/config_flow.py (async_step_user): use copy.deepcopy(DEFAULT_SENSORS) when creating the entry options instead of passing DEFAULT_SENSORS directly.

🤖 Prompt for AI Agents
In custom_components/chefkoch_ha/const.py around lines 4 to 8 the
DEFAULT_SENSORS list is a module-level constant that is being passed by
reference into a config entry; in custom_components/chefkoch_ha/config_flow.py
(async_step_user) change the code that seeds entry.options to use
copy.deepcopy(DEFAULT_SENSORS) (and add an import for copy at the top if
missing) so the flow mutates a deep copy instead of the module constant,
preventing accidental modification of DEFAULT_SENSORS.

@FaserF FaserF merged commit 11df018 into main Sep 24, 2025
4 of 7 checks passed
@FaserF FaserF deleted the new-featuresAndImprovements branch September 24, 2025 13:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant