Initil after Upgrade
This commit is contained in:
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -11,7 +11,8 @@ class CompositeLoader(Loader):
|
||||
self.loaders = loaders
|
||||
|
||||
async def initialize(self) -> None:
|
||||
[await loader.initialize() for loader in self.loaders] # type: ignore[func-returns-value]
|
||||
for loader in self.loaders:
|
||||
await loader.initialize()
|
||||
|
||||
async def get_manufacturer_listing(
|
||||
self,
|
||||
@@ -20,7 +21,11 @@ class CompositeLoader(Loader):
|
||||
) -> set[tuple[str, str]]:
|
||||
"""Get listing of available manufacturers."""
|
||||
|
||||
return {manufacturer for loader in self.loaders for manufacturer in await loader.get_manufacturer_listing(device_types, discovery_by)}
|
||||
return {
|
||||
manufacturer
|
||||
for loader in self.loaders
|
||||
for manufacturer in await loader.get_manufacturer_listing(device_types, discovery_by)
|
||||
}
|
||||
|
||||
async def find_manufacturers(self, search: str) -> set[str]:
|
||||
"""Check if a manufacturer is available. Also must check aliases."""
|
||||
@@ -42,7 +47,11 @@ class CompositeLoader(Loader):
|
||||
) -> set[tuple[str, str]]:
|
||||
"""Get listing of available models and display names for a given manufacturer."""
|
||||
|
||||
return {model for loader in self.loaders for model in await loader.get_model_listing(manufacturer, device_types, discovery_by)}
|
||||
return {
|
||||
model
|
||||
for loader in self.loaders
|
||||
for model in await loader.get_model_listing(manufacturer, device_types, discovery_by)
|
||||
}
|
||||
|
||||
async def load_model(self, manufacturer: str, model: str) -> tuple[dict, str] | None:
|
||||
for loader in self.loaders:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from functools import partial
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
@@ -101,12 +102,9 @@ class LocalLoader(Loader):
|
||||
_model = model.lower()
|
||||
|
||||
if self._is_custom_directory:
|
||||
model_path = os.path.join(self._data_directory)
|
||||
model_json_path = os.path.join(model_path, "model.json")
|
||||
if not os.path.exists(model_json_path):
|
||||
raise LibraryLoadingError(f"model.json not found for manufacturer {_manufacturer} " + f"and model {_model} in path {model_json_path}")
|
||||
|
||||
model_json = await self._hass.async_add_executor_job(self._load_json, model_json_path)
|
||||
model_path, model_json = await self._hass.async_add_executor_job(
|
||||
partial(self._load_custom_model, _manufacturer, _model),
|
||||
)
|
||||
return model_json, model_path
|
||||
|
||||
lib_models = self._manufacturer_model_listing.get(_manufacturer)
|
||||
@@ -205,6 +203,17 @@ class LocalLoader(Loader):
|
||||
|
||||
self._manufacturer_model_listing[manufacturer].update({search_key: profile})
|
||||
|
||||
def _load_custom_model(self, manufacturer: str, model: str) -> tuple[str, dict[str, Any]]:
|
||||
"""Load model.json from a directly configured custom model directory."""
|
||||
model_path = os.path.join(self._data_directory)
|
||||
model_json_path = os.path.join(model_path, "model.json")
|
||||
if not os.path.exists(model_json_path):
|
||||
raise LibraryLoadingError(
|
||||
f"model.json not found for manufacturer {manufacturer} and model {model} in path {model_json_path}",
|
||||
)
|
||||
|
||||
return model_path, self._load_json(model_json_path)
|
||||
|
||||
def _load_json(self, model_json_path: str) -> dict[str, Any]:
|
||||
"""Load model.json file for a given model."""
|
||||
with open(model_json_path) as file:
|
||||
|
||||
@@ -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"""
|
||||
|
||||
Reference in New Issue
Block a user