Skip to content

Commit e34face

Browse files
committed
Comprehensive audit fixes (49 findings) — bump to v2.3.0
Critical bugs fixed: - AttributeError in set_desiccant_reset/set_cleaning_reset/set_filter_reset/set_manual_feed (called .code/.status/.json() on already-parsed dicts) - power_state now consistently returns bool|None (was returning mixed bool/str from int-typed property) - Raise ConfigEntryNotReady on transient setup failures (was returning False, blocking retry) - Fixed duplicate shared pets fetch and isolated owned/shared pet error handling Architecture refactors: - Populate Fountain base class: 34 properties moved, ~1042 lines saved across 4 fountain files - PolarWetFoodFeeder now inherits from Feeder (~159 lines saved, 23 properties deleted) - Consolidated set_desiccant_cycle/set_cleaning_cycle/set_filter_cycle into set_maintenance_frequency - Consolidated set_water_mode_intermittent/constant duplicates (kept currently_off guard variant) - Fix get_device_base_info endpoint (was hitting /device/setting/baseInfo, now /device/device/baseInfo) - feeding_plan_state now reads from realInfo namespace (was reading stale flat key) - Migrated 7 platform setup functions to entry.runtime_data - Removed broken sys.modules lazy-import guards (always evaluated True) Cleanup: - Removed dead PetLibroDataCoordinator class (referenced nonexistent method) - Removed duplicate/unused imports across 5 files - Modernized typing: List/Dict/Type/Optional → list/dict/type/X|None - Added docs/MQTT_RESEARCH.md documenting MQTT discovery and certificate generation block
1 parent 0ab2d03 commit e34face

30 files changed

Lines changed: 726 additions & 1451 deletions

custom_components/petlibro/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from homeassistant.core import HomeAssistant
44
from homeassistant.const import Platform
55
from homeassistant.config_entries import ConfigEntry
6+
from homeassistant.exceptions import ConfigEntryNotReady
67
from homeassistant.helpers.device_registry import DeviceEntry
78
from .devices import Device
89
from .devices.feeders.feeder import Feeder
@@ -95,8 +96,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: PetLibroConfigEntry) ->
9596
return True
9697

9798
except Exception as err:
98-
_LOGGER.error("Failed to set up PetLibro integration: %s", err, exc_info=True)
99-
return False
99+
_LOGGER.warning("PetLibro setup failed, will retry: %s", err)
100+
raise ConfigEntryNotReady(f"PetLibro setup failed: {err}") from err
100101

101102

102103
async def async_unload_entry(hass: HomeAssistant, entry: PetLibroConfigEntry) -> bool:

custom_components/petlibro/api.py

Lines changed: 56 additions & 159 deletions
Large diffs are not rendered by default.

custom_components/petlibro/binary_sensor.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from dataclasses import dataclass
44
from collections.abc import Callable
55
from functools import cached_property
6-
from typing import Optional
76
import logging
87
from .const import Unit, APIKey as API, VALID_UNIT_TYPES
98
from homeassistant.components.binary_sensor import (
@@ -28,7 +27,6 @@
2827
from .devices.fountains.dockstream_2_smart_cordless_fountain import Dockstream2SmartCordlessFountain
2928
from .devices.fountains.dockstream_2_smart_fountain import Dockstream2SmartFountain
3029
from .devices.litterboxes.luma_smart_litter_box import LumaSmartLitterBox
31-
from .entity import PetLibroEntity, _DeviceT, PetLibroEntityDescription
3230

3331

3432
@dataclass(frozen=True)
@@ -37,7 +35,7 @@ class PetLibroBinarySensorEntityDescription(BinarySensorEntityDescription, PetLi
3735

3836
device_class_fn: Callable[[_DeviceT], BinarySensorDeviceClass | None] = lambda _: None
3937
should_report: Callable[[_DeviceT], bool] = lambda _: True
40-
device_class: Optional[BinarySensorDeviceClass] = None
38+
device_class: BinarySensorDeviceClass | None = None
4139
# Optional override for is_on — use when the entity key differs from the device property
4240
value_fn: Callable | None = None
4341

custom_components/petlibro/config_flow.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,10 @@ async def async_step_init(
184184
self.translations = await async_get_translations(
185185
self.hass, self.hass.config.language, "common")
186186
self.entry = self.config_entry
187-
self.hub = self.hass.data[DOMAIN][self.handler]
187+
self.hub = getattr(self.entry, "runtime_data", None)
188+
if self.hub is None:
189+
# Fall back to legacy storage if runtime_data not yet set
190+
self.hub = self.hass.data[DOMAIN][self.handler]
188191
self.api = self.hub.api
189192
self.member = self.hub.member
190193
self._data.clear()

custom_components/petlibro/date.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ async def async_setup_entry(
3838
) -> None:
3939
"""Set up Petlibro dates using config entry."""
4040

41-
hub: PetLibroHub = hass.data[DOMAIN].get(entry.entry_id)
41+
hub: PetLibroHub | None = getattr(entry, "runtime_data", None)
4242

4343
if not hub:
4444
_LOGGER.error("Hub not found for entry: %s", entry.entry_id)

custom_components/petlibro/devices/__init__.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
from typing import Dict, Type
2-
from .device import Device
3-
4-
from . import Device
51
from .device import Device
62
from .feeders.feeder import Feeder
73
from .feeders.air_smart_feeder import AirSmartFeeder
@@ -17,7 +13,7 @@
1713
from .litterboxes.litter_box import LitterBox
1814
from .litterboxes.luma_smart_litter_box import LumaSmartLitterBox
1915

20-
product_name_map : Dict[str, Type[Device]] = {
16+
product_name_map : dict[str, type[Device]] = {
2117
"Air Smart Feeder": AirSmartFeeder,
2218
"Granary Smart Feeder": GranarySmartFeeder,
2319
"Granary Smart Camera Feeder": GranarySmartCameraFeeder,

custom_components/petlibro/devices/device.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# Error Mode - Used for pulling API for new devices. Enable Error Mode and Disable Debug Mode.
22

33
from logging import getLogger
4-
import sys
54
from typing import cast
65

76
from .event import Event, EVENT_UPDATE
@@ -14,8 +13,7 @@
1413
class Device(Event):
1514
def __init__(self, data: dict, hub):
1615
super().__init__()
17-
if "PetLibroHub" not in sys.modules:
18-
from ..hub import PetLibroHub
16+
from ..hub import PetLibroHub
1917
self._data: dict = {}
2018
self.hub: PetLibroHub = hub
2119
self.api = self.hub.api

custom_components/petlibro/devices/feeders/air_smart_feeder.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ async def refresh(self):
2525
get_feeding_plan_today = await self.api.device_feeding_plan_today_new(self.serial)
2626
get_work_record = await self.api.get_device_work_record(self.serial)
2727
feeding_plan_list = (await self.api.device_feeding_plan_list(self.serial)
28-
if self._data.get("enableFeedingPlan") else [])
28+
if self._data.get("realInfo", {}).get("enableFeedingPlan") else [])
2929

3030
self.update_data({
3131
"grainStatus": grain_status or {},

custom_components/petlibro/devices/feeders/feeder.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
"""Generic PETLIBRO feeder"""
22
import ast
33
from zoneinfo import ZoneInfo
4-
import aiohttp
54

65
from typing import cast
76
from logging import getLogger
@@ -205,11 +204,8 @@ def enable_grain_outlet_blocked_notice(self) -> bool:
205204

206205
@property
207206
def feeding_plan_state(self) -> bool:
208-
return bool(self._data.get("enableFeedingPlan", False))
209-
210-
@property
211-
def feeding_plan(self) -> bool:
212-
return self._data.get("enableFeedingPlan", False)
207+
"""Whether the feeding plan is enabled (read from realInfo namespace)."""
208+
return bool(self._data.get("realInfo", {}).get("enableFeedingPlan", False))
213209

214210
async def set_feeding_plan(self, value: bool) -> None:
215211
await self.api.set_feeding_plan(self.serial, value)
@@ -441,7 +437,7 @@ async def set_light_off(self) -> None:
441437
await self.refresh()
442438

443439
async def set_desiccant_cycle(self, value: float) -> None:
444-
await self.api.set_desiccant_cycle(self.serial, value, "changeDesiccantFrequency")
440+
await self.api.set_maintenance_frequency(self.serial, "changeDesiccantFrequency", value)
445441
await self.refresh()
446442

447443
async def set_desiccant_reset(self) -> None:

custom_components/petlibro/devices/feeders/granary_smart_camera_feeder.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ async def refresh(self):
2020
get_feeding_plan_today = await self.api.device_feeding_plan_today_new(self.serial)
2121
get_work_record = await self.api.get_device_work_record(self.serial)
2222
feeding_plan_list = (await self.api.device_feeding_plan_list(self.serial)
23-
if self._data.get("enableFeedingPlan") else [])
23+
if self._data.get("realInfo", {}).get("enableFeedingPlan") else [])
2424

2525
self.update_data({
2626
"grainStatus": grain_status or {},

0 commit comments

Comments
 (0)