Drop ensure_builtin_repositories() (#6012)

* Drop ensure_builtin_repositories

With the new Repository classes we have the is_builtin property, so we
can easily make sure that built-ins are not removed. This allows us to
further cleanup the code by removing the ensure_builtin_repositories
function and the ALL_BUILTIN_REPOSITORIES constant.

* Make sure we add built-ins on load

* Reuse default set and avoid unnecessary copy

Reuse default set and avoid unnecessary copying during validation if
the default is not being used.
This commit is contained in:
Stefan Agner 2025-07-14 22:19:06 +02:00 committed by GitHub
parent eefe2f2e06
commit 99c040520e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 39 additions and 45 deletions

View File

@ -931,5 +931,5 @@ class Backup(JobGroup):
Return a coroutine.
"""
return self.sys_store.update_repositories(
self.repositories, issue_on_error=True, replace=replace
set(self.repositories), issue_on_error=True, replace=replace
)

View File

@ -21,7 +21,7 @@ from .addon import AddonStore
from .const import FILE_HASSIO_STORE, BuiltinRepository
from .data import StoreData
from .repository import Repository
from .validate import SCHEMA_STORE_FILE, ensure_builtin_repositories
from .validate import DEFAULT_REPOSITORIES, SCHEMA_STORE_FILE
_LOGGER: logging.Logger = logging.getLogger(__name__)
@ -63,11 +63,14 @@ class StoreManager(CoreSysAttributes, FileConfiguration):
return self.repositories[slug]
async def load(self) -> None:
"""Start up add-on management."""
# Init custom repositories and load add-ons
await self.update_repositories(
self._data[ATTR_REPOSITORIES], issue_on_error=True
"""Start up add-on store management."""
# Make sure the built-in repositories are all present
# This is especially important when adding new built-in repositories
# to make sure existing installations have them.
all_repositories: set[str] = (
set(self._data.get(ATTR_REPOSITORIES, [])) | DEFAULT_REPOSITORIES
)
await self.update_repositories(all_repositories, issue_on_error=True)
@Job(
name="store_manager_reload",
@ -223,7 +226,7 @@ class StoreManager(CoreSysAttributes, FileConfiguration):
@Job(name="store_manager_update_repositories")
async def update_repositories(
self,
list_repositories: list[str],
list_repositories: set[str],
*,
issue_on_error: bool = False,
replace: bool = True,
@ -231,14 +234,8 @@ class StoreManager(CoreSysAttributes, FileConfiguration):
"""Update repositories by adding new ones and removing stale ones."""
current_repositories = {repository.source for repository in self.all}
# Determine changes needed
if replace:
target_repositories = set(ensure_builtin_repositories(list_repositories))
repositories_to_add = target_repositories - current_repositories
else:
# When not replacing, just add the new repositories
repositories_to_add = set(list_repositories) - current_repositories
target_repositories = current_repositories | repositories_to_add
# Determine repositories to add
repositories_to_add = list_repositories - current_repositories
# Add new repositories
add_errors = await asyncio.gather(
@ -259,7 +256,7 @@ class StoreManager(CoreSysAttributes, FileConfiguration):
repositories_to_remove: list[Repository] = [
repository
for repository in self.all
if repository.source not in target_repositories
if repository.source not in list_repositories
and not repository.is_builtin
]

View File

@ -35,7 +35,3 @@ class BuiltinRepository(StrEnum):
return URL_HASSIO_ADDONS
else:
return self.value # For URL-based repos, value is the URL
# All repositories that are considered "built-in" and protected from removal
ALL_BUILTIN_REPOSITORIES = {repo.value for repo in BuiltinRepository}

View File

@ -4,7 +4,7 @@ import voluptuous as vol
from ..const import ATTR_MAINTAINER, ATTR_NAME, ATTR_REPOSITORIES, ATTR_URL
from ..validate import RE_REPOSITORY
from .const import ALL_BUILTIN_REPOSITORIES, BuiltinRepository
from .const import BuiltinRepository
# pylint: disable=no-value-for-parameter
SCHEMA_REPOSITORY_CONFIG = vol.Schema(
@ -17,15 +17,6 @@ SCHEMA_REPOSITORY_CONFIG = vol.Schema(
)
def ensure_builtin_repositories(addon_repositories: list[str]) -> list[str]:
"""Ensure builtin repositories are in list.
Note: This should not be used in validation as the resulting list is not
stable. This can have side effects when comparing data later on.
"""
return list(set(addon_repositories) | ALL_BUILTIN_REPOSITORIES)
def validate_repository(repository: str) -> str:
"""Validate a valid repository."""
if repository in BuiltinRepository:
@ -44,10 +35,12 @@ def validate_repository(repository: str) -> str:
repositories = vol.All([validate_repository], vol.Unique())
DEFAULT_REPOSITORIES = {repo.value for repo in BuiltinRepository}
SCHEMA_STORE_FILE = vol.Schema(
{
vol.Optional(
ATTR_REPOSITORIES, default=list(ALL_BUILTIN_REPOSITORIES)
ATTR_REPOSITORIES, default=lambda: list(DEFAULT_REPOSITORIES)
): repositories,
},
extra=vol.REMOVE_EXTRA,

View File

@ -17,7 +17,7 @@ from supervisor.exceptions import (
from supervisor.resolution.const import SuggestionType
from supervisor.store import StoreManager
from supervisor.store.addon import AddonStore
from supervisor.store.const import ALL_BUILTIN_REPOSITORIES
from supervisor.store.const import BuiltinRepository
from supervisor.store.repository import Repository
@ -50,7 +50,9 @@ async def test_add_valid_repository(
patch("pathlib.Path.exists", return_value=True),
):
if use_update:
await store_manager.update_repositories(current + ["http://example.com"])
await store_manager.update_repositories(
set(current) | {"http://example.com"}
)
else:
await store_manager.add_repository("http://example.com")
@ -70,7 +72,7 @@ async def test_add_invalid_repository(coresys: CoreSys, store_manager: StoreMana
),
):
await store_manager.update_repositories(
current + ["http://example.com"], issue_on_error=True
set(current) | {"http://example.com"}, issue_on_error=True
)
assert not await get_repository_by_url(
@ -96,7 +98,9 @@ async def test_error_on_invalid_repository(
pytest.raises(StoreError),
):
if use_update:
await store_manager.update_repositories(current + ["http://example.com"])
await store_manager.update_repositories(
set(current) | {"http://example.com"}
)
else:
await store_manager.add_repository("http://example.com")
@ -118,7 +122,7 @@ async def test_add_invalid_repository_file(
patch("pathlib.Path.exists", return_value=False),
):
await store_manager.update_repositories(
current + ["http://example.com"], issue_on_error=True
set(current) | {"http://example.com"}, issue_on_error=True
)
assert not await get_repository_by_url(
@ -146,7 +150,7 @@ async def test_add_repository_with_git_error(
current = coresys.store.repository_urls
with patch("supervisor.store.git.GitRepo.load", side_effect=git_error):
await store_manager.update_repositories(
current + ["http://example.com"], issue_on_error=True
set(current) | {"http://example.com"}, issue_on_error=True
)
assert "http://example.com" in coresys.store.repository_urls
@ -175,7 +179,9 @@ async def test_error_on_repository_with_git_error(
pytest.raises(StoreError),
):
if use_update:
await store_manager.update_repositories(current + ["http://example.com"])
await store_manager.update_repositories(
set(current) | {"http://example.com"}
)
else:
await store_manager.add_repository("http://example.com")
@ -189,7 +195,9 @@ async def test_preinstall_valid_repository(
):
"""Test add core repository valid."""
with patch("supervisor.store.git.GitRepo.load", return_value=None):
await store_manager.update_repositories(list(ALL_BUILTIN_REPOSITORIES))
await store_manager.update_repositories(
{repo.value for repo in BuiltinRepository}
)
def validate():
assert store_manager.get("core").validate()
@ -213,7 +221,7 @@ async def test_remove_repository(
assert test_repository.slug in coresys.store.repositories
if use_update:
await store_manager.update_repositories([])
await store_manager.update_repositories(set())
else:
await store_manager.remove_repository(test_repository)
@ -241,7 +249,7 @@ async def test_remove_used_repository(
match="Can't remove 'https://github.com/awesome-developer/awesome-repo'. It's used by installed add-ons",
):
if use_update:
await store_manager.update_repositories([])
await store_manager.update_repositories(set())
else:
await store_manager.remove_repository(
coresys.store.repositories[store_addon.repository]
@ -252,7 +260,7 @@ async def test_update_partial_error(coresys: CoreSys, store_manager: StoreManage
"""Test partial error on update does partial save and errors."""
with patch("supervisor.store.repository.RepositoryGit.validate", return_value=True):
with patch("supervisor.store.git.GitRepo.load", return_value=None):
await store_manager.update_repositories([])
await store_manager.update_repositories(set())
store_manager.data.update.assert_called_once()
store_manager.data.update.reset_mock()
@ -268,7 +276,7 @@ async def test_update_partial_error(coresys: CoreSys, store_manager: StoreManage
pytest.raises(StoreError),
):
await store_manager.update_repositories(
current + ["http://example.com", "http://example2.com"]
set(current) | {"http://example.com", "http://example2.com"}
)
assert len(coresys.store.repository_urls) == initial + 1
@ -303,7 +311,7 @@ async def test_add_with_update_repositories(
),
patch("pathlib.Path.exists", return_value=True),
):
await store_manager.update_repositories(["http://example.com"], replace=False)
await store_manager.update_repositories({"http://example.com"}, replace=False)
assert test_repository.source in coresys.store.repository_urls
assert "http://example.com" in coresys.store.repository_urls
@ -322,7 +330,7 @@ async def test_add_repository_fails_if_out_of_date(
):
if use_update:
await store_manager.update_repositories(
coresys.store.repository_urls + ["http://example.com"],
set(coresys.store.repository_urls) | {"http://example.com"}
)
else:
await store_manager.add_repository("http://example.com")