periodic push

This commit is contained in:
2023-06-29 16:14:36 +02:00
parent c948b95622
commit 831f676068
267 changed files with 17705 additions and 10864 deletions

View File

@@ -160,9 +160,9 @@ class HacsCommon:
categories: set[str] = field(default_factory=set)
renamed_repositories: dict[str, str] = field(default_factory=dict)
archived_repositories: list[str] = field(default_factory=list)
ignored_repositories: list[str] = field(default_factory=list)
skip: list[str] = field(default_factory=list)
archived_repositories: set[str] = field(default_factory=set)
ignored_repositories: set[str] = field(default_factory=set)
skip: set[str] = field(default_factory=set)
@dataclass
@@ -197,20 +197,20 @@ class HacsRepositories:
"""HACS Repositories."""
_default_repositories: set[str] = field(default_factory=set)
_repositories: list[HacsRepository] = field(default_factory=list)
_repositories: set[HacsRepository] = field(default_factory=set)
_repositories_by_full_name: dict[str, HacsRepository] = field(default_factory=dict)
_repositories_by_id: dict[str, HacsRepository] = field(default_factory=dict)
_removed_repositories: list[RemovedRepository] = field(default_factory=list)
_removed_repositories_by_full_name: dict[str, RemovedRepository] = field(default_factory=dict)
@property
def list_all(self) -> list[HacsRepository]:
"""Return a list of repositories."""
return self._repositories
return list(self._repositories)
@property
def list_removed(self) -> list[RemovedRepository]:
"""Return a list of removed repositories."""
return self._removed_repositories
return list(self._removed_repositories_by_full_name.values())
@property
def list_downloaded(self) -> list[HacsRepository]:
@@ -235,7 +235,7 @@ class HacsRepositories:
repository = registered_repo
if repository not in self._repositories:
self._repositories.append(repository)
self._repositories.add(repository)
self._repositories_by_id[repo_id] = repository
self._repositories_by_full_name[repository.data.full_name_lower] = repository
@@ -333,22 +333,15 @@ class HacsRepositories:
def is_removed(self, repository_full_name: str) -> bool:
"""Check if a repository is removed."""
return repository_full_name in (
repository.repository for repository in self._removed_repositories
)
return repository_full_name in self._removed_repositories_by_full_name
def removed_repository(self, repository_full_name: str) -> RemovedRepository:
"""Get repository by full name."""
if self.is_removed(repository_full_name):
if removed := [
repository
for repository in self._removed_repositories
if repository.repository == repository_full_name
]:
return removed[0]
if removed := self._removed_repositories_by_full_name.get(repository_full_name):
return removed
removed = RemovedRepository(repository=repository_full_name)
self._removed_repositories.append(removed)
self._removed_repositories_by_full_name[repository_full_name] = removed
return removed
@@ -457,7 +450,9 @@ class HacsBase:
try:
await self.hass.async_add_executor_job(_write_file)
except BaseException as error: # lgtm [py/catch-base-exception] pylint: disable=broad-except
except (
BaseException # lgtm [py/catch-base-exception] pylint: disable=broad-except
) as error:
self.log.error("Could not write data to %s - %s", file_path, error)
return False
@@ -476,7 +471,9 @@ class HacsBase:
f"{reset.hour}:{reset.minute}:{reset.second}",
)
self.disable_hacs(HacsDisabledReason.RATE_LIMIT)
except BaseException as exception: # lgtm [py/catch-base-exception] pylint: disable=broad-except
except (
BaseException # lgtm [py/catch-base-exception] pylint: disable=broad-except
) as exception:
self.log.exception(exception)
return 0
@@ -515,7 +512,9 @@ class HacsBase:
raise exception
except GitHubException as exception:
_exception = exception
except BaseException as exception: # lgtm [py/catch-base-exception] pylint: disable=broad-except
except (
BaseException # lgtm [py/catch-base-exception] pylint: disable=broad-except
) as exception:
self.log.exception(exception)
_exception = exception
@@ -547,7 +546,12 @@ class HacsBase:
raise AddonRepositoryException()
if category not in RERPOSITORY_CLASSES:
raise HacsException(f"{category} is not a valid repository category.")
self.log.warning(
"%s is not a valid repository category, %s will not be registered.",
category,
repository_full_name,
)
return
if (renamed := self.common.renamed_repositories.get(repository_full_name)) is not None:
repository_full_name = renamed
@@ -557,7 +561,7 @@ class HacsBase:
try:
await repository.async_registration(ref)
if repository.validate.errors:
self.common.skip.append(repository.data.full_name)
self.common.skip.add(repository.data.full_name)
if not self.status.startup:
self.log.error("Validation for %s failed.", repository_full_name)
if self.system.action:
@@ -576,7 +580,7 @@ class HacsBase:
)
return
except AIOGitHubAPIException as exception:
self.common.skip.append(repository.data.full_name)
self.common.skip.add(repository.data.full_name)
raise HacsException(
f"Validation for {repository_full_name} failed with {exception}."
) from exception
@@ -726,7 +730,9 @@ class HacsBase:
await asyncio.sleep(1)
continue
except BaseException as exception: # lgtm [py/catch-base-exception] pylint: disable=broad-except
except (
BaseException # lgtm [py/catch-base-exception] pylint: disable=broad-except
) as exception:
self.log.exception("Download failed - %s", exception)
return None
@@ -742,7 +748,9 @@ class HacsBase:
entry=self.configuration.config_entry,
platforms=platforms,
)
self.hass.config_entries.async_setup_platforms(self.configuration.config_entry, platforms)
await self.hass.config_entries.async_forward_entry_setups(
self.configuration.config_entry, platforms
)
@callback
def async_dispatch(self, signal: HacsDispatchEvent, data: dict | None = None) -> None:
@@ -755,6 +763,9 @@ class HacsBase:
for category in (HacsCategory.INTEGRATION, HacsCategory.PLUGIN):
self.enable_hacs_category(HacsCategory(category))
if self.configuration.experimental and self.core.ha_version >= "2023.4.0b0":
self.enable_hacs_category(HacsCategory.TEMPLATE)
if HacsCategory.PYTHON_SCRIPT in self.hass.config.components:
self.enable_hacs_category(HacsCategory.PYTHON_SCRIPT)
@@ -764,7 +775,18 @@ class HacsBase:
if self.configuration.appdaemon:
self.enable_hacs_category(HacsCategory.APPDAEMON)
if self.configuration.netdaemon:
self.enable_hacs_category(HacsCategory.NETDAEMON)
downloaded_netdaemon = [
x
for x in self.repositories.list_downloaded
if x.data.category == HacsCategory.NETDAEMON
]
if len(downloaded_netdaemon) != 0:
self.log.warning(
"NetDaemon in HACS is deprectaded. It will stop working in the future. "
"Please remove all your current NetDaemon repositories from HACS "
"and download them manually if you want to continue using them."
)
self.enable_hacs_category(HacsCategory.NETDAEMON)
async def async_load_hacs_from_github(self, _=None) -> None:
"""Load HACS from GitHub."""
@@ -849,6 +871,15 @@ class HacsBase:
repository.repository_manifest.update_data(
{**dict(HACS_MANIFEST_KEYS_TO_EXPORT), **manifest}
)
self.async_dispatch(
HacsDispatchEvent.REPOSITORY,
{
"id": 1337,
"action": "update",
"repository": repository.data.full_name,
"repository_id": repository.data.id,
},
)
if category == "integration":
self.status.inital_fetch_done = True

View File

@@ -31,6 +31,7 @@ class HacsCategory(StrEnum):
PLUGIN = "plugin" # Kept for legacy purposes
NETDAEMON = "netdaemon"
PYTHON_SCRIPT = "python_script"
TEMPLATE = "template"
THEME = "theme"
REMOVED = "removed"

View File

@@ -1,10 +1,9 @@
""""Starting setup task: Frontend"."""
from __future__ import annotations
import os
from typing import TYPE_CHECKING
from aiohttp import web
from homeassistant.components.http import HomeAssistantView
from homeassistant.core import HomeAssistant, callback
from .const import DOMAIN, URL_BASE
@@ -26,11 +25,13 @@ def async_register_frontend(hass: HomeAssistant, hacs: HacsBase) -> None:
hacs.async_setup_frontend_endpoint_themes()
# Register frontend
if hacs.configuration.frontend_repo_url:
if hacs.configuration.dev and (frontend_path := os.getenv("HACS_FRONTEND_DIR")):
hacs.log.warning(
"<HacsFrontend> Frontend development mode enabled. Do not run in production!"
)
hass.http.register_view(HacsFrontendDev())
hass.http.register_static_path(
f"{URL_BASE}/frontend", f"{frontend_path}/hacs_frontend", cache_headers=False
)
elif hacs.configuration.experimental:
hacs.log.info("<HacsFrontend> Using experimental frontend")
hass.http.register_static_path(
@@ -72,23 +73,3 @@ def async_register_frontend(hass: HomeAssistant, hacs: HacsBase) -> None:
# Setup plugin endpoint if needed
hacs.async_setup_frontend_endpoint_plugin()
class HacsFrontendDev(HomeAssistantView):
"""Dev View Class for HACS."""
requires_auth = False
name = "hacs_files:frontend"
url = r"/hacsfiles/frontend/{requested_file:.+}"
async def get(self, request, requested_file): # pylint: disable=unused-argument
"""Handle HACS Web requests."""
hacs: HacsBase = request.app["hass"].data.get(DOMAIN)
requested = requested_file.split("/")[-1]
request = await hacs.session.get(f"{hacs.configuration.frontend_repo_url}/{requested}")
if request.status == 200:
result = await request.read()
response = web.Response(body=result)
response.headers["Content-Type"] = "application/javascript"
return response

View File

@@ -1,9 +1,9 @@
try {
new Function("import('/hacsfiles/frontend/main-aeda8d41.js')")();
new Function("import('/hacsfiles/frontend/main-85e087f9.js')")();
} catch (err) {
var el = document.createElement('script');
el.src = '/hacsfiles/frontend/main-aeda8d41.js';
el.src = '/hacsfiles/frontend/main-85e087f9.js';
el.type = 'module';
document.body.appendChild(el);
}

View File

@@ -1,3 +1,3 @@
{
"./src/main.ts": "main-aeda8d41.js"
"./src/main.ts": "main-85e087f9.js"
}

View File

@@ -1 +1 @@
VERSION="20230127100107"
VERSION="20230406083157"

View File

@@ -1,4 +1,6 @@
{
"domain": "hacs",
"name": "HACS",
"codeowners": [
"@ludeeus"
],
@@ -12,12 +14,10 @@
"repairs"
],
"documentation": "https://hacs.xyz/docs/configuration/start",
"domain": "hacs",
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/hacs/integration/issues",
"name": "HACS",
"requirements": [
"aiogithubapi>=22.10.1"
],
"version": "1.30.1"
"version": "1.32.1"
}

View File

@@ -25,7 +25,7 @@ class RestartRequiredFixFlow(RepairsFlow):
) -> data_entry_flow.FlowResult:
"""Handle the first step of a fix flow."""
return await (self.async_step_confirm_restart())
return await self.async_step_confirm_restart()
async def async_step_confirm_restart(
self, user_input: dict[str, str] | None = None

View File

@@ -8,6 +8,7 @@ from .integration import HacsIntegrationRepository
from .netdaemon import HacsNetdaemonRepository
from .plugin import HacsPluginRepository
from .python_script import HacsPythonScriptRepository
from .template import HacsTemplateRepository
from .theme import HacsThemeRepository
RERPOSITORY_CLASSES: dict[HacsCategory, HacsRepository] = {
@@ -17,4 +18,5 @@ RERPOSITORY_CLASSES: dict[HacsCategory, HacsRepository] = {
HacsCategory.APPDAEMON: HacsAppdaemonRepository,
HacsCategory.NETDAEMON: HacsNetdaemonRepository,
HacsCategory.PLUGIN: HacsPluginRepository,
HacsCategory.TEMPLATE: HacsTemplateRepository,
}

View File

@@ -91,6 +91,8 @@ TOPIC_FILTER = (
"sensor",
"smart-home",
"smarthome",
"template",
"templates",
"theme",
"themes",
)
@@ -102,6 +104,7 @@ REPOSITORY_KEYS_TO_EXPORT = (
("description", ""),
("downloads", 0),
("domain", None),
("etag_releases", None),
("etag_repository", None),
("full_name", ""),
("last_commit", None),
@@ -143,6 +146,7 @@ class RepositoryData:
domain: str = None
downloads: int = 0
etag_repository: str = None
etag_releases: str = None
file_name: str = ""
first_install: bool = False
full_name: str = ""
@@ -505,14 +509,18 @@ class HacsRepository:
self.data.description = self.data.description
@concurrent(concurrenttasks=10, backoff_time=5)
async def common_update(self, ignore_issues=False, force=False) -> bool:
async def common_update(self, ignore_issues=False, force=False, skip_releases=False) -> bool:
"""Common information update steps of the repository."""
self.logger.debug("%s Getting repository information", self.string)
# Attach repository
current_etag = self.data.etag_repository
try:
await self.common_update_data(ignore_issues=ignore_issues, force=force)
await self.common_update_data(
ignore_issues=ignore_issues,
force=force,
skip_releases=skip_releases,
)
except HacsRepositoryExistException:
self.data.full_name = self.hacs.common.renamed_repositories[self.data.full_name]
await self.common_update_data(ignore_issues=ignore_issues, force=force)
@@ -746,9 +754,8 @@ class HacsRepository:
def remove(self) -> None:
"""Run remove tasks."""
self.logger.info("%s Starting removal", self.string)
if self.hacs.repositories.is_registered(repository_id=str(self.data.id)):
self.logger.info("%s Starting removal", self.string)
self.hacs.repositories.unregister(self)
async def uninstall(self) -> None:
@@ -767,6 +774,8 @@ class HacsRepository:
await self.hacs.hass.services.async_call("frontend", "reload_themes", {})
except BaseException: # lgtm [py/catch-base-exception] pylint: disable=broad-except
pass
elif self.data.category == "template":
await self.hacs.hass.services.async_call("homeassistant", "reload_custom_templates", {})
await async_remove_store(self.hacs.hass, f"hacs/{self.data.id}.hacs")
@@ -791,6 +800,8 @@ class HacsRepository:
try:
if self.data.category == "python_script":
local_path = f"{self.content.path.local}/{self.data.name}.py"
elif self.data.category == "template":
local_path = f"{self.content.path.local}/{self.data.file_name}"
elif self.data.category == "theme":
path = (
f"{self.hacs.core.config_path}/"
@@ -818,7 +829,7 @@ class HacsRepository:
return False
self.logger.debug("%s Removing %s", self.string, local_path)
if self.data.category in ["python_script"]:
if self.data.category in ["python_script", "template"]:
os.remove(local_path)
else:
shutil.rmtree(local_path)
@@ -830,7 +841,9 @@ class HacsRepository:
"%s Presumed local content path %s does not exist", self.string, local_path
)
except BaseException as exception: # lgtm [py/catch-base-exception] pylint: disable=broad-except
except (
BaseException # lgtm [py/catch-base-exception] pylint: disable=broad-except
) as exception:
self.logger.debug("%s Removing %s failed with %s", self.string, local_path, exception)
return False
return True
@@ -1048,6 +1061,7 @@ class HacsRepository:
ignore_issues: bool = False,
force: bool = False,
retry=False,
skip_releases=False,
) -> None:
"""Common update data."""
releases = []
@@ -1085,7 +1099,7 @@ class HacsRepository:
if self.data.archived and not ignore_issues:
self.validate.errors.append("Repository is archived.")
if self.data.full_name not in self.hacs.common.archived_repositories:
self.hacs.common.archived_repositories.append(self.data.full_name)
self.hacs.common.archived_repositories.add(self.data.full_name)
raise HacsRepositoryArchivedException(f"{self} Repository is archived.")
# Make sure the repository is not in the blacklist.
@@ -1096,19 +1110,20 @@ class HacsRepository:
raise HacsException(f"{self} Repository has been requested to be removed.")
# Get releases.
try:
releases = await self.get_releases(
prerelease=self.data.show_beta,
returnlimit=self.hacs.configuration.release_limit,
)
if releases:
self.data.releases = True
self.releases.objects = releases
self.data.published_tags = [x.tag_name for x in self.releases.objects]
self.data.last_version = next(iter(self.data.published_tags))
if not skip_releases:
try:
releases = await self.get_releases(
prerelease=self.data.show_beta,
returnlimit=self.hacs.configuration.release_limit,
)
if releases:
self.data.releases = True
self.releases.objects = releases
self.data.published_tags = [x.tag_name for x in self.releases.objects]
self.data.last_version = next(iter(self.data.published_tags))
except HacsException:
self.data.releases = False
except HacsException:
self.data.releases = False
if not self.force_branch:
self.ref = self.version_to_download()
@@ -1118,6 +1133,9 @@ class HacsRepository:
if assets := release.assets:
downloads = next(iter(assets)).download_count
self.data.downloads = downloads
elif self.hacs.system.generator and self.repository_object:
await self.repository_object.set_last_commit()
self.data.last_commit = self.repository_object.last_commit
self.hacs.log.debug(
"%s Running checks against %s", self.string, self.ref.replace("tags/", "")
@@ -1247,7 +1265,9 @@ class HacsRepository:
return
self.validate.errors.append(f"[{content.name}] was not downloaded.")
except BaseException as exception: # lgtm [py/catch-base-exception] pylint: disable=broad-except
except (
BaseException # lgtm [py/catch-base-exception] pylint: disable=broad-except
) as exception:
self.validate.errors.append(f"Download was not completed [{exception}]")
async def async_remove_entity_device(self) -> None:

View File

@@ -47,7 +47,7 @@
"release_limit": "Number of releases to show.",
"debug": "Enable debug.",
"appdaemon": "Enable AppDaemon apps discovery & tracking",
"netdaemon": "Enable NetDaemon apps discovery & tracking",
"netdaemon": "[DEPRECATED] Enable NetDaemon apps discovery & tracking",
"sidepanel_icon": "Side panel icon",
"sidepanel_title": "Side panel title"
}
@@ -68,7 +68,7 @@
},
"removed": {
"title": "Repository removed from HACS",
"description": "{name} has been removed from HACS for {reason} visit the [HACS Panel](/hacs/repository/{repositry_id}) to remove it."
"description": "Because {reason}, '{name}' has been removed from HACS. Please visit the [HACS Panel](/hacs/repository/{repositry_id}) to remove it."
}
}
}
}

View File

@@ -74,7 +74,9 @@ class Backup:
self.local_path,
self.backup_path_full,
)
except BaseException as exception: # lgtm [py/catch-base-exception] pylint: disable=broad-except
except (
BaseException # lgtm [py/catch-base-exception] pylint: disable=broad-except
) as exception:
self.hacs.log.warning("Could not create backup: %s", exception)
def restore(self) -> None:

View File

@@ -207,8 +207,8 @@ class HacsData:
self.logger.info("<HacsData restore> Restore started")
# Hacs
self.hacs.common.archived_repositories = []
self.hacs.common.ignored_repositories = []
self.hacs.common.archived_repositories = set()
self.hacs.common.ignored_repositories = set()
self.hacs.common.renamed_repositories = {}
# Clear out doubble renamed values
@@ -219,14 +219,14 @@ class HacsData:
self.hacs.common.renamed_repositories[entry] = value
# Clear out doubble archived values
for entry in hacs.get("archived_repositories", []):
for entry in hacs.get("archived_repositories", set()):
if entry not in self.hacs.common.archived_repositories:
self.hacs.common.archived_repositories.append(entry)
self.hacs.common.archived_repositories.add(entry)
# Clear out doubble ignored values
for entry in hacs.get("ignored_repositories", []):
for entry in hacs.get("ignored_repositories", set()):
if entry not in self.hacs.common.ignored_repositories:
self.hacs.common.ignored_repositories.append(entry)
self.hacs.common.ignored_repositories.add(entry)
try:
await self.register_unknown_repositories(repositories)
@@ -241,7 +241,9 @@ class HacsData:
self.async_restore_repository(entry, repo_data)
self.logger.info("<HacsData restore> Restore done")
except BaseException as exception: # lgtm [py/catch-base-exception] pylint: disable=broad-except
except (
BaseException # lgtm [py/catch-base-exception] pylint: disable=broad-except
) as exception:
self.logger.critical(
"<HacsData restore> [%s] Restore Failed!", exception, exc_info=exception
)
@@ -282,6 +284,8 @@ class HacsData:
repository.data.description = repository_data.get("description", "")
repository.data.downloads = repository_data.get("downloads", 0)
repository.data.last_updated = repository_data.get("last_updated", 0)
if self.hacs.system.generator:
repository.data.etag_releases = repository_data.get("etag_releases")
repository.data.etag_repository = repository_data.get("etag_repository")
repository.data.topics = [
topic for topic in repository_data.get("topics", []) if topic not in TOPIC_FILTER

View File

@@ -17,4 +17,5 @@ def is_safe(hacs: HacsBase, path: str | Path) -> bool:
Path(f"{hacs.core.config_path}/{hacs.configuration.python_script_path}").as_posix(),
Path(f"{hacs.core.config_path}/{hacs.configuration.theme_path}").as_posix(),
Path(f"{hacs.core.config_path}/custom_components/").as_posix(),
Path(f"{hacs.core.config_path}/custom_templates/").as_posix(),
)

View File

@@ -17,7 +17,9 @@ class HACSStore(Store):
"""Load the data from disk if version matches."""
try:
data = json_util.load_json(self.path)
except BaseException as exception: # lgtm [py/catch-base-exception] pylint: disable=broad-except
except (
BaseException # lgtm [py/catch-base-exception] pylint: disable=broad-except
) as exception:
_LOGGER.critical(
"Could not load '%s', restore it from a backup or delete the file: %s",
self.path,

View File

@@ -31,6 +31,8 @@ def render_template(hacs: HacsBase, content: str, context: HacsRepository) -> st
version_available=context.releases.last_release,
version_installed=context.display_installed_version,
)
except BaseException as exception: # lgtm [py/catch-base-exception] pylint: disable=broad-except
except (
BaseException # lgtm [py/catch-base-exception] pylint: disable=broad-except
) as exception:
context.logger.debug(exception)
return content

View File

@@ -157,14 +157,19 @@ async def hacs_repositories_add(
if renamed := hacs.common.renamed_repositories.get(repository):
repository = renamed
if not hacs.repositories.get_by_full_name(repository):
if category not in hacs.common.categories:
hacs.log.error("%s is not a valid category for %s", category, repository)
elif not hacs.repositories.get_by_full_name(repository):
try:
await hacs.async_register_repository(
repository_full_name=repository,
category=category,
)
except BaseException as exception: # lgtm [py/catch-base-exception] pylint: disable=broad-except
except (
BaseException # lgtm [py/catch-base-exception] pylint: disable=broad-except
) as exception:
hacs.async_dispatch(
HacsDispatchEvent.ERROR,
{
@@ -175,7 +180,6 @@ async def hacs_repositories_add(
)
else:
hacs.async_dispatch(
HacsDispatchEvent.ERROR,
{

View File

@@ -103,7 +103,7 @@ async def hacs_repository_ignore(
"""Ignore a repository."""
hacs: HacsBase = hass.data.get(DOMAIN)
repository = hacs.repositories.get_by_id(msg["repository"])
hacs.common.ignored_repositories.append(repository.data.full_name)
hacs.common.ignored_repositories.add(repository.data.full_name)
await hacs.data.async_write()
connection.send_message(websocket_api.result_message(msg["id"]))