refactor: move package archive lockup to package info trait

This commit is contained in:
2026-03-15 20:23:20 +02:00
parent 2e9837b70d
commit a04b6c3b9c
5 changed files with 103 additions and 103 deletions

View File

@@ -27,7 +27,7 @@ from ahriman.core.build_tools.package_archive import PackageArchive
from ahriman.core.build_tools.task import Task from ahriman.core.build_tools.task import Task
from ahriman.core.repository.cleaner import Cleaner from ahriman.core.repository.cleaner import Cleaner
from ahriman.core.repository.package_info import PackageInfo from ahriman.core.repository.package_info import PackageInfo
from ahriman.core.utils import atomic_move, filelock, list_flatmap, package_like, safe_filename, symlink_relative from ahriman.core.utils import atomic_move, filelock, safe_filename, symlink_relative
from ahriman.models.changes import Changes from ahriman.models.changes import Changes
from ahriman.models.event import EventType from ahriman.models.event import EventType
from ahriman.models.package import Package from ahriman.models.package import Package
@@ -41,34 +41,6 @@ class Executor(PackageInfo, Cleaner):
trait for common repository update processes trait for common repository update processes
""" """
def _archive_lookup(self, package: Package) -> list[Path]:
"""
check if there is a rebuilt package already
Args:
package(Package): package to check
Returns:
list[Path]: list of built packages and signatures if available, empty list otherwise
"""
archive = self.paths.archive_for(package.base)
if not archive.is_dir():
return []
for path in filter(package_like, archive.iterdir()):
# check if package version is the same
built = Package.from_archive(path)
if built.version != package.version:
continue
# all packages must be either any or same architecture
if not built.supports_architecture(self.repository_id.architecture):
continue
return list_flatmap(built.packages.values(), lambda single: archive.glob(f"{single.filename}*"))
return []
def _archive_rename(self, description: PackageDescription, package_base: str) -> None: def _archive_rename(self, description: PackageDescription, package_base: str) -> None:
""" """
rename package archive removing special symbols rename package archive removing special symbols
@@ -106,7 +78,7 @@ class Executor(PackageInfo, Cleaner):
commit_sha = task.init(path, patches, local_version) commit_sha = task.init(path, patches, local_version)
loaded_package = Package.from_build(path, self.repository_id.architecture, None) loaded_package = Package.from_build(path, self.repository_id.architecture, None)
if prebuilt := list(self._archive_lookup(loaded_package)): if prebuilt := self.package_archives_lookup(loaded_package):
self.logger.info("using prebuilt packages for %s-%s", loaded_package.base, loaded_package.version) self.logger.info("using prebuilt packages for %s-%s", loaded_package.base, loaded_package.version)
built = [] built = []
for artifact in prebuilt: for artifact in prebuilt:

View File

@@ -30,10 +30,11 @@ from ahriman.core.build_tools.sources import Sources
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.log import LazyLogging from ahriman.core.log import LazyLogging
from ahriman.core.status import Client from ahriman.core.status import Client
from ahriman.core.utils import package_like from ahriman.core.utils import list_flatmap, package_like
from ahriman.models.changes import Changes from ahriman.models.changes import Changes
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.repository_id import RepositoryId from ahriman.models.repository_id import RepositoryId
from ahriman.models.repository_paths import RepositoryPaths
class PackageInfo(LazyLogging): class PackageInfo(LazyLogging):
@@ -43,12 +44,14 @@ class PackageInfo(LazyLogging):
Attributes: Attributes:
configuration(Configuration): configuration instance configuration(Configuration): configuration instance
pacman(Pacman): alpm wrapper instance pacman(Pacman): alpm wrapper instance
paths(RepositoryPaths): repository paths instance
reporter(Client): build status reporter instance reporter(Client): build status reporter instance
repository_id(RepositoryId): repository unique identifier repository_id(RepositoryId): repository unique identifier
""" """
configuration: Configuration configuration: Configuration
pacman: Pacman pacman: Pacman
paths: RepositoryPaths
reporter: Client reporter: Client
repository_id: RepositoryId repository_id: RepositoryId
@@ -130,11 +133,9 @@ class PackageInfo(LazyLogging):
Returns: Returns:
list[Package]: list of packages belonging to this base, sorted by version by ascension list[Package]: list of packages belonging to this base, sorted by version by ascension
""" """
paths = self.configuration.repository_paths
packages: dict[tuple[str, str], Package] = {} packages: dict[tuple[str, str], Package] = {}
# we can't use here load_archives, because it ignores versions # we can't use here load_archives, because it ignores versions
for full_path in filter(package_like, paths.archive_for(package_base).iterdir()): for full_path in filter(package_like, self.paths.archive_for(package_base).iterdir()):
local = Package.from_archive(full_path) local = Package.from_archive(full_path)
if not local.supports_architecture(self.repository_id.architecture): if not local.supports_architecture(self.repository_id.architecture):
continue continue
@@ -143,6 +144,34 @@ class PackageInfo(LazyLogging):
comparator: Callable[[Package, Package], int] = lambda left, right: left.vercmp(right.version) comparator: Callable[[Package, Package], int] = lambda left, right: left.vercmp(right.version)
return sorted(packages.values(), key=cmp_to_key(comparator)) return sorted(packages.values(), key=cmp_to_key(comparator))
def package_archives_lookup(self, package: Package) -> list[Path]:
"""
check if there is a rebuilt package already
Args:
package(Package): package to check
Returns:
list[Path]: list of built packages and signatures if available, empty list otherwise
"""
archive = self.paths.archive_for(package.base)
if not archive.is_dir():
return []
for path in filter(package_like, archive.iterdir()):
# check if package version is the same
built = Package.from_archive(path)
if built.version != package.version:
continue
# all packages must be either any or same architecture
if not built.supports_architecture(self.repository_id.architecture):
continue
return list_flatmap(built.packages.values(), lambda single: archive.glob(f"{single.filename}*"))
return []
def package_changes(self, package: Package, last_commit_sha: str) -> Changes | None: def package_changes(self, package: Package, last_commit_sha: str) -> Changes | None:
""" """
extract package change for the package since last commit if available extract package change for the package since last commit if available
@@ -157,7 +186,7 @@ class PackageInfo(LazyLogging):
with TemporaryDirectory(ignore_cleanup_errors=True) as dir_name: with TemporaryDirectory(ignore_cleanup_errors=True) as dir_name:
dir_path = Path(dir_name) dir_path = Path(dir_name)
patches = self.reporter.package_patches_get(package.base, None) patches = self.reporter.package_patches_get(package.base, None)
current_commit_sha = Sources.load(dir_path, package, patches, self.configuration.repository_paths) current_commit_sha = Sources.load(dir_path, package, patches, self.paths)
if current_commit_sha != last_commit_sha: if current_commit_sha != last_commit_sha:
return Sources.changes(dir_path, last_commit_sha) return Sources.changes(dir_path, last_commit_sha)
@@ -173,7 +202,7 @@ class PackageInfo(LazyLogging):
Returns: Returns:
list[Package]: list of packages properties list[Package]: list of packages properties
""" """
packages = self.load_archives(filter(package_like, self.configuration.repository_paths.repository.iterdir())) packages = self.load_archives(filter(package_like, self.paths.repository.iterdir()))
if filter_packages: if filter_packages:
packages = [package for package in packages if package.base in filter_packages] packages = [package for package in packages if package.base in filter_packages]
@@ -186,7 +215,7 @@ class PackageInfo(LazyLogging):
Returns: Returns:
list[Path]: list of filenames from the directory list[Path]: list of filenames from the directory
""" """
return list(filter(package_like, self.configuration.repository_paths.packages.iterdir())) return list(filter(package_like, self.paths.packages.iterdir()))
def packages_depend_on(self, packages: list[Package], depends_on: Iterable[str] | None) -> list[Package]: def packages_depend_on(self, packages: list[Package], depends_on: Iterable[str] | None) -> list[Package]:
""" """

View File

@@ -103,6 +103,7 @@ def _create_watcher(path: Path, repository_id: RepositoryId) -> Watcher:
# load package info wrapper # load package info wrapper
package_info = PackageInfo() package_info = PackageInfo()
package_info.configuration = configuration package_info.configuration = configuration
package_info.paths = configuration.repository_paths
package_info.repository_id = repository_id package_info.repository_id = repository_id
return Watcher(client, package_info) return Watcher(client, package_info)

View File

@@ -1,6 +1,5 @@
import pytest import pytest
from dataclasses import replace
from pathlib import Path from pathlib import Path
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from typing import Any from typing import Any
@@ -11,72 +10,9 @@ from ahriman.models.changes import Changes
from ahriman.models.dependencies import Dependencies from ahriman.models.dependencies import Dependencies
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.packagers import Packagers from ahriman.models.packagers import Packagers
from ahriman.models.repository_id import RepositoryId
from ahriman.models.user import User from ahriman.models.user import User
def test_archive_lookup(executor: Executor, package_ahriman: Package, package_python_schedule: Package,
mocker: MockerFixture) -> None:
"""
must existing packages which match the version
"""
mocker.patch("pathlib.Path.is_dir", return_value=True)
mocker.patch("pathlib.Path.iterdir", return_value=[
Path("1.pkg.tar.zst"),
Path("2.pkg.tar.zst"),
Path("3.pkg.tar.zst"),
])
mocker.patch("ahriman.models.package.Package.from_archive", side_effect=[
package_ahriman,
package_python_schedule,
replace(package_ahriman, version="1"),
])
glob_mock = mocker.patch("pathlib.Path.glob", return_value=[Path("1.pkg.tar.xz")])
assert list(executor._archive_lookup(package_ahriman)) == [Path("1.pkg.tar.xz")]
glob_mock.assert_called_once_with(f"{package_ahriman.packages[package_ahriman.base].filename}*")
def test_archive_lookup_version_mismatch(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must return nothing if no packages found with the same version
"""
mocker.patch("pathlib.Path.is_dir", return_value=True)
mocker.patch("pathlib.Path.iterdir", return_value=[
Path("1.pkg.tar.zst"),
])
mocker.patch("ahriman.models.package.Package.from_archive", return_value=replace(package_ahriman, version="1"))
assert list(executor._archive_lookup(package_ahriman)) == []
def test_archive_lookup_architecture_mismatch(executor: Executor, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must return nothing if architecture doesn't match
"""
package_ahriman.packages[package_ahriman.base].architecture = "x86_64"
mocker.patch("pathlib.Path.is_dir", return_value=True)
executor.repository_id = RepositoryId("i686", executor.repository_id.name)
mocker.patch("pathlib.Path.iterdir", return_value=[
Path("1.pkg.tar.zst"),
])
mocker.patch("ahriman.models.package.Package.from_archive", return_value=package_ahriman)
assert list(executor._archive_lookup(package_ahriman)) == []
def test_archive_lookup_no_archive_directory(
executor: Executor,
package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must return nothing if no archive directory found
"""
mocker.patch("pathlib.Path.is_dir", return_value=False)
assert list(executor._archive_lookup(package_ahriman)) == []
def test_archive_rename(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None: def test_archive_rename(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
""" """
must correctly remove package archive must correctly remove package archive
@@ -110,7 +46,7 @@ def test_package_build(executor: Executor, package_ahriman: Package, mocker: Moc
status_client_mock = mocker.patch("ahriman.core.status.Client.set_building") status_client_mock = mocker.patch("ahriman.core.status.Client.set_building")
init_mock = mocker.patch("ahriman.core.build_tools.task.Task.init", return_value="sha") init_mock = mocker.patch("ahriman.core.build_tools.task.Task.init", return_value="sha")
package_mock = mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman) package_mock = mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman)
lookup_mock = mocker.patch("ahriman.core.repository.executor.Executor._archive_lookup", return_value=[]) lookup_mock = mocker.patch("ahriman.core.repository.executor.Executor.package_archives_lookup", return_value=[])
with_packages_mock = mocker.patch("ahriman.models.package.Package.with_packages") with_packages_mock = mocker.patch("ahriman.models.package.Package.with_packages")
rename_mock = mocker.patch("ahriman.core.repository.executor.atomic_move") rename_mock = mocker.patch("ahriman.core.repository.executor.atomic_move")
@@ -131,7 +67,7 @@ def test_package_build_copy(executor: Executor, package_ahriman: Package, mocker
mocker.patch("ahriman.core.build_tools.task.Task.build", return_value=[Path(package_ahriman.base)]) mocker.patch("ahriman.core.build_tools.task.Task.build", return_value=[Path(package_ahriman.base)])
mocker.patch("ahriman.core.build_tools.task.Task.init") mocker.patch("ahriman.core.build_tools.task.Task.init")
mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman) mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman)
mocker.patch("ahriman.core.repository.executor.Executor._archive_lookup", return_value=[path]) mocker.patch("ahriman.core.repository.executor.Executor.package_archives_lookup", return_value=[path])
mocker.patch("ahriman.core.repository.executor.atomic_move") mocker.patch("ahriman.core.repository.executor.atomic_move")
mocker.patch("ahriman.models.package.Package.with_packages") mocker.patch("ahriman.models.package.Package.with_packages")
copy_mock = mocker.patch("shutil.copy") copy_mock = mocker.patch("shutil.copy")

View File

@@ -8,6 +8,7 @@ from unittest.mock import MagicMock
from ahriman.core.repository import Repository from ahriman.core.repository import Repository
from ahriman.models.changes import Changes from ahriman.models.changes import Changes
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.repository_id import RepositoryId
def test_full_depends(repository: Repository, package_ahriman: Package, package_python_schedule: Package, def test_full_depends(repository: Repository, package_ahriman: Package, package_python_schedule: Package,
@@ -120,6 +121,67 @@ def test_package_archives_architecture_mismatch(repository: Repository, package_
assert len(result) == 0 assert len(result) == 0
def test_package_archives_lookup(repository: Repository, package_ahriman: Package, package_python_schedule: Package,
mocker: MockerFixture) -> None:
"""
must existing packages which match the version
"""
mocker.patch("pathlib.Path.is_dir", return_value=True)
mocker.patch("pathlib.Path.iterdir", return_value=[
Path("1.pkg.tar.zst"),
Path("2.pkg.tar.zst"),
Path("3.pkg.tar.zst"),
])
mocker.patch("ahriman.models.package.Package.from_archive", side_effect=[
package_ahriman,
package_python_schedule,
replace(package_ahriman, version="1"),
])
glob_mock = mocker.patch("pathlib.Path.glob", return_value=[Path("1.pkg.tar.xz")])
assert repository.package_archives_lookup(package_ahriman) == [Path("1.pkg.tar.xz")]
glob_mock.assert_called_once_with(f"{package_ahriman.packages[package_ahriman.base].filename}*")
def test_package_archives_lookup_version_mismatch(repository: Repository, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must return nothing if no packages found with the same version
"""
mocker.patch("pathlib.Path.is_dir", return_value=True)
mocker.patch("pathlib.Path.iterdir", return_value=[
Path("1.pkg.tar.zst"),
])
mocker.patch("ahriman.models.package.Package.from_archive", return_value=replace(package_ahriman, version="1"))
assert repository.package_archives_lookup(package_ahriman) == []
def test_package_archives_lookup_architecture_mismatch(repository: Repository, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must return nothing if architecture doesn't match
"""
package_ahriman.packages[package_ahriman.base].architecture = "x86_64"
mocker.patch("pathlib.Path.is_dir", return_value=True)
repository.repository_id = RepositoryId("i686", repository.repository_id.name)
mocker.patch("pathlib.Path.iterdir", return_value=[
Path("1.pkg.tar.zst"),
])
mocker.patch("ahriman.models.package.Package.from_archive", return_value=package_ahriman)
assert repository.package_archives_lookup(package_ahriman) == []
def test_package_archives_lookup_no_archive_directory(repository: Repository, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must return nothing if no archive directory found
"""
mocker.patch("pathlib.Path.is_dir", return_value=False)
assert repository.package_archives_lookup(package_ahriman) == []
def test_package_changes(repository: Repository, package_ahriman: Package, mocker: MockerFixture) -> None: def test_package_changes(repository: Repository, package_ahriman: Package, mocker: MockerFixture) -> None:
""" """
must load package changes must load package changes