refactor: separate package_archives method from trigger

This commit is contained in:
2026-03-11 13:36:59 +02:00
parent 998ed48dde
commit 2cd4ef5e86
4 changed files with 66 additions and 47 deletions

View File

@@ -17,14 +17,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
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)

View File

@@ -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

View File

@@ -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,

View File

@@ -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