Initil after Upgrade

This commit is contained in:
2026-06-15 10:53:52 -04:00
parent 2fe9bf0dd6
commit 887feaa50a
143 changed files with 2288 additions and 881 deletions
@@ -64,7 +64,7 @@ class RemoteLoader(Loader):
"""Initialize the loader."""
integration = await async_get_integration(self.hass, DOMAIN)
powercalc_version = AwesomeVersion(integration.version)
powercalc_version = AwesomeVersion(str(integration.version))
self.library_contents = await self.load_library_json()
self.profile_hashes = await self.hass.async_add_executor_job(self._load_profile_hashes)
@@ -138,7 +138,7 @@ class RemoteLoader(Loader):
async def _download_remote_library_json() -> dict[str, Any] | None:
"""
Download library.json from Github.
If download is successful, save it to local storage to use as fallback in case of internet connection issues.
On success, save it to local storage as a fallback for internet connection issues.
"""
_LOGGER.debug("Loading library.json from github")
@@ -182,7 +182,10 @@ class RemoteLoader(Loader):
return {
(manufacturer["dir_name"], manufacturer["full_name"])
for manufacturer in self.library_contents.get("manufacturers", [])
if any(self._model_matches_filters(model, device_types, discovery_by) for model in manufacturer.get("models", []))
if any(
self._model_matches_filters(model, device_types, discovery_by)
for model in manufacturer.get("models", [])
)
}
@async_cache
@@ -225,7 +228,12 @@ class RemoteLoader(Loader):
async def find_model(self, manufacturer: str, search: set[str]) -> list[str]:
"""Find matching model IDs in the library."""
models = self.model_lookup.get(manufacturer, {})
return [model["id"] for phrase in search if (phrase_lower := phrase.lower()) in models for model in models[phrase_lower]]
return [
model["id"]
for phrase in search
if (phrase_lower := phrase.lower()) in models
for model in models[phrase_lower]
]
@async_cache
async def find_model_migration(self, manufacturer: str, model: str) -> str | None:
@@ -274,12 +282,19 @@ class RemoteLoader(Loader):
raise LibraryLoadingError("Model not found in library: %s/%s", manufacturer, model)
return model_info
async def _needs_update(self, model_info: LibraryModel, manufacturer: str, model: str, model_path: str, force_update: bool) -> bool:
async def _needs_update(
self,
model_info: LibraryModel,
manufacturer: str,
model: str,
model_path: str,
force_update: bool,
) -> bool:
"""Check if the model needs to be updated."""
if force_update:
return True
path_exists = os.path.exists(model_path)
path_exists = await self.hass.async_add_executor_job(os.path.exists, model_path)
if not path_exists:
return True
@@ -287,7 +302,13 @@ class RemoteLoader(Loader):
new_hash = model_info.get("hash")
return existing_hash != new_hash
async def _download_profile_with_retry(self, manufacturer: str, model: str, storage_path: str, model_path: str) -> None:
async def _download_profile_with_retry(
self,
manufacturer: str,
model: str,
storage_path: str,
model_path: str,
) -> None:
"""Attempt to download the profile, with retry logic and error handling."""
try:
model_info = self._get_library_model(manufacturer, model)
@@ -297,12 +318,22 @@ class RemoteLoader(Loader):
self.profile_hashes[f"{manufacturer}/{model}"] = model_hash
await self.hass.async_add_executor_job(self._write_profile_hashes, self.profile_hashes)
except ProfileDownloadError as e:
if not os.path.exists(model_path):
if os.path.exists(storage_path):
path_exists, storage_path_exists = await self.hass.async_add_executor_job(
self._profile_paths_exist,
model_path,
storage_path,
)
if not path_exists:
if storage_path_exists:
await self.hass.async_add_executor_job(shutil.rmtree, storage_path) # pragma: no cover
raise e
_LOGGER.debug("Failed to download profile, falling back to local profile")
@staticmethod
def _profile_paths_exist(model_path: str, storage_path: str) -> tuple[bool, bool]:
"""Check profile paths from the executor."""
return os.path.exists(model_path), os.path.exists(storage_path)
async def _load_model_json(self, model_path: str) -> dict:
"""Load the JSON data from the model file."""
@@ -330,7 +361,10 @@ class RemoteLoader(Loader):
"""Retrieve the storage path for a given manufacturer and model."""
return str(self.hass.config.path(STORAGE_DIR, BUILT_IN_LIBRARY_DIR, manufacturer, model))
async def download_with_retry(self, callback: Callable[[], Coroutine[Any, Any, None | dict[str, Any]]]) -> None | dict[str, Any]:
async def download_with_retry(
self,
callback: Callable[[], Coroutine[Any, Any, None | dict[str, Any]]],
) -> None | dict[str, Any]:
"""Download a file from a remote endpoint with retries"""
max_retries = 3
retry_count = 0
@@ -342,7 +376,9 @@ class RemoteLoader(Loader):
_LOGGER.debug(e)
retry_count += 1
if retry_count == max_retries:
raise ProfileDownloadError(f"Failed to download even after {max_retries} retries, falling back to local copy") from e
raise ProfileDownloadError(
f"Failed to download even after {max_retries} retries, falling back to local copy",
) from e
await asyncio.sleep(self.retry_timeout)
_LOGGER.warning("Failed to download, retrying... (Attempt %d of %d)", retry_count + 1, max_retries)
@@ -396,7 +432,7 @@ class RemoteLoader(Loader):
return {}
with open(path) as f:
return json.load(f) # type: ignore
return json.load(f) # type: ignore[no-any-return]
def _write_profile_hashes(self, hashes: dict[str, str]) -> None:
"""Write profile hashes to local storage"""