diff --git a/src/ahriman/core/housekeeping/archive_rotation_trigger.py b/src/ahriman/core/housekeeping/archive_rotation_trigger.py index bc1ca230..ae826f59 100644 --- a/src/ahriman/core/housekeeping/archive_rotation_trigger.py +++ b/src/ahriman/core/housekeeping/archive_rotation_trigger.py @@ -17,14 +17,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from collections.abc import Callable -from functools import cmp_to_key - from ahriman.core import context -from ahriman.core.alpm.pacman import Pacman from ahriman.core.configuration import Configuration +from ahriman.core.repository import Repository from ahriman.core.triggers import Trigger -from ahriman.core.utils import package_like from ahriman.models.package import Package from ahriman.models.repository_id import RepositoryId from ahriman.models.result import Result @@ -78,27 +74,20 @@ class ArchiveRotationTrigger(Trigger): """ return list(cls.CONFIGURATION_SCHEMA.keys()) - def archives_remove(self, package: Package, pacman: Pacman) -> None: + def archives_remove(self, package: Package, repository: Repository) -> None: """ remove older versions of the specified package Args: package(Package): package which has been updated to check for older versions - pacman(Pacman): alpm wrapper instance + repository(Repository): repository instance """ # explicit guard to skip process in case if rotation is disabled # this guard is supposed to speedup process if self.keep_built_packages == 0: return - packages: dict[tuple[str, str], Package] = {} - # we can't use here load_archives, because it ignores versions - for full_path in filter(package_like, self.paths.archive_for(package.base).iterdir()): - local = Package.from_archive(full_path, pacman) - packages.setdefault((local.base, local.version), local).packages.update(local.packages) - - comparator: Callable[[Package, Package], int] = lambda left, right: left.vercmp(right.version) - to_remove = sorted(packages.values(), key=cmp_to_key(comparator)) + to_remove = repository.package_archives(package.base) for single in to_remove[:-self.keep_built_packages]: self.logger.info("removing version %s of package %s", single.version, single.base) @@ -115,7 +104,7 @@ class ArchiveRotationTrigger(Trigger): packages(list[Package]): list of all available packages """ ctx = context.get() - pacman = ctx.get(Pacman) + repository = ctx.get(Repository) for package in result.success: - self.archives_remove(package, pacman) + self.archives_remove(package, repository) diff --git a/src/ahriman/core/repository/package_info.py b/src/ahriman/core/repository/package_info.py index 42cde873..cce63285 100644 --- a/src/ahriman/core/repository/package_info.py +++ b/src/ahriman/core/repository/package_info.py @@ -19,7 +19,8 @@ # import copy -from collections.abc import Iterable +from collections.abc import Callable, Iterable +from functools import cmp_to_key from pathlib import Path from tempfile import TemporaryDirectory @@ -102,6 +103,27 @@ class PackageInfo(RepositoryProperties): self.logger.exception("could not load package from %s", full_path) return list(result.values()) + def package_archives(self, package_base: str) -> list[Package]: + """ + load list of packages known for this package base. This method unlike + :func:`ahriman.core.repository.package_info.PackageInfo.load_archives` scans archive directory and loads all + versions available for the ``package_base`` + + Args: + package_base(str): package base + + Returns: + list[Package]: list of packages belonging to this base, sorted by version by ascension + """ + packages: dict[tuple[str, str], Package] = {} + # we can't use here load_archives, because it ignores versions + for full_path in filter(package_like, self.paths.archive_for(package_base).iterdir()): + local = Package.from_archive(full_path, self.pacman) + packages.setdefault((local.base, local.version), local).packages.update(local.packages) + + comparator: Callable[[Package, Package], int] = lambda left, right: left.vercmp(right.version) + return sorted(packages.values(), key=cmp_to_key(comparator)) + def package_changes(self, package: Package, last_commit_sha: str) -> Changes | None: """ extract package change for the package since last commit if available diff --git a/tests/ahriman/core/housekeeping/test_archive_rotation_trigger.py b/tests/ahriman/core/housekeeping/test_archive_rotation_trigger.py index ed2977c4..be212f4e 100644 --- a/tests/ahriman/core/housekeeping/test_archive_rotation_trigger.py +++ b/tests/ahriman/core/housekeeping/test_archive_rotation_trigger.py @@ -3,12 +3,11 @@ import pytest from dataclasses import replace from pathlib import Path from pytest_mock import MockerFixture -from typing import Any from unittest.mock import call as MockCall -from ahriman.core.alpm.pacman import Pacman from ahriman.core.configuration import Configuration from ahriman.core.housekeeping import ArchiveRotationTrigger +from ahriman.core.repository import Repository from ahriman.models.package import Package from ahriman.models.result import Result @@ -21,26 +20,24 @@ def test_configuration_sections(configuration: Configuration) -> None: def test_archives_remove(archive_rotation_trigger: ArchiveRotationTrigger, package_ahriman: Package, - pacman: Pacman, mocker: MockerFixture) -> None: + repository: Repository, mocker: MockerFixture) -> None: """ must remove older packages """ - def package(version: Any, *args: Any, **kwargs: Any) -> Package: - generated = replace(package_ahriman, version=str(version)) + packages = [] + for i in range(5): + generated = replace(package_ahriman, version=str(i)) generated.packages = { - key: replace(value, filename=str(version)) + key: replace(value, filename=str(i)) for key, value in generated.packages.items() } - return generated + packages.append(generated) - mocker.patch("pathlib.Path.is_dir", return_value=True) - mocker.patch("ahriman.core.housekeeping.archive_rotation_trigger.package_like", return_value=True) + mocker.patch("ahriman.core.repository.package_info.PackageInfo.package_archives", return_value=packages) mocker.patch("pathlib.Path.glob", return_value=[Path(str(i)) for i in range(5)]) - mocker.patch("pathlib.Path.iterdir", return_value=[Path(str(i)) for i in range(5)]) - mocker.patch("ahriman.models.package.Package.from_archive", side_effect=package) unlink_mock = mocker.patch("pathlib.Path.unlink", autospec=True) - archive_rotation_trigger.archives_remove(package_ahriman, pacman) + archive_rotation_trigger.archives_remove(package_ahriman, repository) unlink_mock.assert_has_calls([ MockCall(Path("0")), MockCall(Path("1")), @@ -48,28 +45,15 @@ def test_archives_remove(archive_rotation_trigger: ArchiveRotationTrigger, packa def test_archives_remove_keep(archive_rotation_trigger: ArchiveRotationTrigger, package_ahriman: Package, - pacman: Pacman, mocker: MockerFixture) -> None: + repository: Repository, mocker: MockerFixture) -> None: """ must keep all packages if set to """ - def package(version: Any, *args: Any, **kwargs: Any) -> Package: - generated = replace(package_ahriman, version=str(version)) - generated.packages = { - key: replace(value, filename=str(version)) - for key, value in generated.packages.items() - } - return generated - - mocker.patch("pathlib.Path.is_dir", return_value=True) - mocker.patch("ahriman.core.housekeeping.archive_rotation_trigger.package_like", return_value=True) - mocker.patch("pathlib.Path.glob", return_value=[Path(str(i)) for i in range(5)]) - mocker.patch("pathlib.Path.iterdir", return_value=[Path(str(i)) for i in range(5)]) - mocker.patch("ahriman.models.package.Package.from_archive", side_effect=package) - unlink_mock = mocker.patch("pathlib.Path.unlink", autospec=True) + archives_mock = mocker.patch("ahriman.core.repository.package_info.PackageInfo.package_archives") archive_rotation_trigger.keep_built_packages = 0 - archive_rotation_trigger.archives_remove(package_ahriman, pacman) - unlink_mock.assert_not_called() + archive_rotation_trigger.archives_remove(package_ahriman, repository) + archives_mock.assert_not_called() def test_on_result(archive_rotation_trigger: ArchiveRotationTrigger, package_ahriman: Package, diff --git a/tests/ahriman/core/repository/test_package_info.py b/tests/ahriman/core/repository/test_package_info.py index 6dfa5cba..d64637ad 100644 --- a/tests/ahriman/core/repository/test_package_info.py +++ b/tests/ahriman/core/repository/test_package_info.py @@ -91,6 +91,30 @@ def test_load_archives_different_version(package_info: PackageInfo, package_pyth assert packages[0].version == package_python_schedule.version +def test_package_archives(package_info: PackageInfo, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must load package archives sorted by version + """ + from dataclasses import replace + from typing import Any + + def package(version: Any, *args: Any, **kwargs: Any) -> Package: + generated = replace(package_ahriman, version=str(version)) + generated.packages = { + key: replace(value, filename=str(version)) + for key, value in generated.packages.items() + } + return generated + + mocker.patch("ahriman.core.repository.package_info.package_like", return_value=True) + mocker.patch("pathlib.Path.iterdir", return_value=[Path(str(i)) for i in range(5)]) + mocker.patch("ahriman.models.package.Package.from_archive", side_effect=package) + + result = package_info.package_archives(package_ahriman.base) + assert len(result) == 5 + assert [p.version for p in result] == [str(i) for i in range(5)] + + def test_package_changes(package_info: PackageInfo, package_ahriman: Package, mocker: MockerFixture) -> None: """ must load package changes