diff --git a/src/ahriman/application/handlers/setup.py b/src/ahriman/application/handlers/setup.py index f53473f9..f3ddad01 100644 --- a/src/ahriman/application/handlers/setup.py +++ b/src/ahriman/application/handlers/setup.py @@ -71,6 +71,8 @@ class Setup(Handler): Setup.configuration_create_sudo(application.repository.paths, args.build_command, architecture) application.repository.repo.init() + # lazy database sync + application.repository.pacman.handle # pylint: disable=pointless-statement @staticmethod def build_command(root: Path, prefix: str, architecture: str) -> Path: @@ -78,7 +80,7 @@ class Setup(Handler): generate build command name Args: - root(Path): root directory for the build command (must be root of the reporitory) + root(Path): root directory for the build command (must be root of the repository) prefix(str): command prefix in {prefix}-{architecture}-build architecture(str): repository architecture diff --git a/src/ahriman/core/alpm/pacman.py b/src/ahriman/core/alpm/pacman.py index 658c384c..fb3c6311 100644 --- a/src/ahriman/core/alpm/pacman.py +++ b/src/ahriman/core/alpm/pacman.py @@ -21,7 +21,7 @@ import shutil from pathlib import Path from pyalpm import DB, Handle, Package, SIG_PACKAGE, error as PyalpmError # type: ignore -from typing import Generator, Set +from typing import Any, Callable, Generator, Set from ahriman.core.configuration import Configuration from ahriman.core.log import LazyLogging @@ -36,6 +36,8 @@ class Pacman(LazyLogging): handle(Handle): pyalpm root ``Handle`` """ + handle: Handle + def __init__(self, architecture: str, configuration: Configuration, *, refresh_database: int) -> None: """ default constructor @@ -46,6 +48,22 @@ class Pacman(LazyLogging): refresh_database(int): synchronize local cache to remote. If set to ``0``, no syncronization will be enabled, if set to ``1`` - normal syncronization, if set to ``2`` - force syncronization """ + self.__create_handle_fn: Callable[[], Handle] = lambda: self.__create_handle( + architecture, configuration, refresh_database=refresh_database) + + def __create_handle(self, architecture: str, configuration: Configuration, *, refresh_database: int) -> Handle: + """ + create lazy handle function + + Args: + architecture(str): repository architecture + configuration(Configuration): configuration instance + refresh_database(int): synchronize local cache to remote. If set to ``0``, no syncronization will be + enabled, if set to ``1`` - normal syncronization, if set to ``2`` - force syncronization + + Returns: + Handle: fully initialized pacman handle + """ root = configuration.getpath("alpm", "root") pacman_root = configuration.getpath("alpm", "database") use_ahriman_cache = configuration.getboolean("alpm", "use_ahriman_cache") @@ -53,20 +71,42 @@ class Pacman(LazyLogging): paths = configuration.repository_paths database_path = paths.pacman if use_ahriman_cache else pacman_root - self.handle = Handle(str(root), str(database_path)) + handle = Handle(str(root), str(database_path)) for repository in configuration.getlist("alpm", "repositories"): - database = self.database_init(repository, mirror, architecture) - self.database_copy(database, pacman_root, paths, use_ahriman_cache=use_ahriman_cache) + database = self.database_init(handle, repository, mirror, architecture) + self.database_copy(handle, database, pacman_root, paths, use_ahriman_cache=use_ahriman_cache) if use_ahriman_cache and refresh_database: - self.database_sync(refresh_database > 1) + self.database_sync(handle, force=refresh_database > 1) - def database_copy(self, database: DB, pacman_root: Path, paths: RepositoryPaths, *, + return handle + + def __getattr__(self, item: str) -> Any: + """ + pacman handle extractor + + Args: + item(str): property name + + Returns: + Any: attribute by its name + + Raises: + AttributeError: in case if no such attribute found + """ + if item == "handle": + handle = self.__create_handle_fn() + setattr(self, item, handle) + return handle + return super().__getattr__(item) # required for logging attribute + + def database_copy(self, handle: Handle, database: DB, pacman_root: Path, paths: RepositoryPaths, *, use_ahriman_cache: bool) -> None: """ copy database from the operating system root to the ahriman local home Args: + handle(Handle): pacman handle which will be used for database copying database(DB): pacman database instance to be copied pacman_root(Path): operating system pacman root paths(RepositoryPaths): repository paths instance @@ -78,7 +118,7 @@ class Pacman(LazyLogging): if not use_ahriman_cache: return # copy root database if no local copy found - pacman_db_path = Path(self.handle.dbpath) + pacman_db_path = Path(handle.dbpath) if not pacman_db_path.is_dir(): return # root directory does not exist yet dst = repository_database(pacman_db_path) @@ -92,11 +132,12 @@ class Pacman(LazyLogging): shutil.copy(src, dst) paths.chown(dst) - def database_init(self, repository: str, mirror: str, architecture: str) -> DB: + def database_init(self, handle: Handle, repository: str, mirror: str, architecture: str) -> DB: """ create database instance from pacman handler and set its properties Args: + handle(Handle): pacman handle which will be used for database initializing repository(str): pacman repository name (e.g. core) mirror(str): arch linux mirror url architecture(str): repository architecture @@ -104,21 +145,23 @@ class Pacman(LazyLogging): Returns: DB: loaded pacman database instance """ - database: DB = self.handle.register_syncdb(repository, SIG_PACKAGE) + self.logger.info("loading pacman databases") + database: DB = handle.register_syncdb(repository, SIG_PACKAGE) # replace variables in mirror address database.servers = [mirror.replace("$repo", repository).replace("$arch", architecture)] return database - def database_sync(self, force: bool) -> None: + def database_sync(self, handle: Handle, *, force: bool) -> None: """ sync local database Args: + handle(Handle): pacman handle which will be used for database sync force(bool): force database syncronization (same as ``pacman -Syy``) """ self.logger.info("refresh ahriman's home pacman database (force refresh %s)", force) - transaction = self.handle.init_transaction() - for database in self.handle.get_syncdbs(): + transaction = handle.init_transaction() + for database in handle.get_syncdbs(): try: database.update(force) except PyalpmError: diff --git a/tests/ahriman/core/alpm/test_pacman.py b/tests/ahriman/core/alpm/test_pacman.py index 9ed5373a..52501040 100644 --- a/tests/ahriman/core/alpm/test_pacman.py +++ b/tests/ahriman/core/alpm/test_pacman.py @@ -1,3 +1,5 @@ +import pytest + from pathlib import Path from pyalpm import error as PyalpmError from pytest_mock import MockerFixture @@ -21,8 +23,9 @@ def test_init_with_local_cache(configuration: Configuration, mocker: MockerFixtu with TemporaryDirectory(ignore_cleanup_errors=True) as pacman_root: mocker.patch.object(RepositoryPaths, "pacman", Path(pacman_root)) # during the creation pyalpm.Handle will create also version file which we would like to remove later - Pacman("x86_64", configuration, refresh_database=1) - sync_mock.assert_called_once_with(False) + pacman = Pacman("x86_64", configuration, refresh_database=1) + assert pacman.handle + sync_mock.assert_called_once_with(pytest.helpers.anyvar(int), force=False) def test_init_with_local_cache_forced(configuration: Configuration, mocker: MockerFixture) -> None: @@ -37,8 +40,9 @@ def test_init_with_local_cache_forced(configuration: Configuration, mocker: Mock with TemporaryDirectory(ignore_cleanup_errors=True) as pacman_root: mocker.patch.object(RepositoryPaths, "pacman", Path(pacman_root)) # during the creation pyalpm.Handle will create also version file which we would like to remove later - Pacman("x86_64", configuration, refresh_database=2) - sync_mock.assert_called_once_with(True) + pacman = Pacman("x86_64", configuration, refresh_database=2) + assert pacman.handle + sync_mock.assert_called_once_with(pytest.helpers.anyvar(int), force=True) def test_database_copy(pacman: Pacman, repository_paths: RepositoryPaths, mocker: MockerFixture) -> None: @@ -54,7 +58,7 @@ def test_database_copy(pacman: Pacman, repository_paths: RepositoryPaths, mocker copy_mock = mocker.patch("shutil.copy") chown_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.chown") - pacman.database_copy(database, path, repository_paths, use_ahriman_cache=True) + pacman.database_copy(pacman.handle, database, path, repository_paths, use_ahriman_cache=True) copy_mock.assert_called_once_with(path / "sync" / "core.db", dst_path) chown_mock.assert_called_once_with(dst_path) @@ -70,7 +74,7 @@ def test_database_copy_skip(pacman: Pacman, repository_paths: RepositoryPaths, m mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=lambda p: True if p.is_relative_to(path) else False) copy_mock = mocker.patch("shutil.copy") - pacman.database_copy(database, path, repository_paths, use_ahriman_cache=False) + pacman.database_copy(pacman.handle, database, path, repository_paths, use_ahriman_cache=False) copy_mock.assert_not_called() @@ -85,7 +89,7 @@ def test_database_copy_no_directory(pacman: Pacman, repository_paths: Repository mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=lambda p: True if p.is_relative_to(path) else False) copy_mock = mocker.patch("shutil.copy") - pacman.database_copy(database, path, repository_paths, use_ahriman_cache=True) + pacman.database_copy(pacman.handle, database, path, repository_paths, use_ahriman_cache=True) copy_mock.assert_not_called() @@ -100,7 +104,7 @@ def test_database_copy_no_root_file(pacman: Pacman, repository_paths: Repository mocker.patch("pathlib.Path.is_file", return_value=False) copy_mock = mocker.patch("shutil.copy") - pacman.database_copy(database, path, repository_paths, use_ahriman_cache=True) + pacman.database_copy(pacman.handle, database, path, repository_paths, use_ahriman_cache=True) copy_mock.assert_not_called() @@ -114,7 +118,7 @@ def test_database_copy_database_exist(pacman: Pacman, repository_paths: Reposito mocker.patch("pathlib.Path.is_file", return_value=True) copy_mock = mocker.patch("shutil.copy") - pacman.database_copy(database, Path("root"), repository_paths, use_ahriman_cache=True) + pacman.database_copy(pacman.handle, database, Path("root"), repository_paths, use_ahriman_cache=True) copy_mock.assert_not_called() @@ -123,7 +127,7 @@ def test_database_init(pacman: Pacman, configuration: Configuration) -> None: must init database with settings """ mirror = configuration.get("alpm", "mirror") - database = pacman.database_init("test", mirror, "x86_64") + database = pacman.database_init(pacman.handle, "test", mirror, "x86_64") assert len(database.servers) == 1 @@ -139,7 +143,7 @@ def test_database_sync(pacman: Pacman) -> None: handle_mock.init_transaction.return_value = transaction_mock pacman.handle = handle_mock - pacman.database_sync(False) + pacman.database_sync(pacman.handle, force=False) handle_mock.init_transaction.assert_called_once_with() core_mock.update.assert_called_once_with(False) extra_mock.update.assert_called_once_with(False) @@ -157,7 +161,7 @@ def test_database_sync_failed(pacman: Pacman) -> None: handle_mock.get_syncdbs.return_value = [core_mock, extra_mock] pacman.handle = handle_mock - pacman.database_sync(False) + pacman.database_sync(pacman.handle, force=False) extra_mock.update.assert_called_once_with(False) @@ -170,7 +174,7 @@ def test_database_sync_forced(pacman: Pacman) -> None: handle_mock.get_syncdbs.return_value = [core_mock] pacman.handle = handle_mock - pacman.database_sync(True) + pacman.database_sync(pacman.handle, force=True) handle_mock.init_transaction.assert_called_once_with() core_mock.update.assert_called_once_with(True)