Compare commits

..

8 Commits

12 changed files with 66 additions and 116 deletions

View File

@ -1,29 +0,0 @@
ahriman.core.archive package
============================
Submodules
----------
ahriman.core.archive.archive\_tree module
-----------------------------------------
.. automodule:: ahriman.core.archive.archive_tree
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.archive.archive\_trigger module
--------------------------------------------
.. automodule:: ahriman.core.archive.archive_trigger
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.core.archive
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -8,7 +8,6 @@ Subpackages
:maxdepth: 4
ahriman.core.alpm
ahriman.core.archive
ahriman.core.auth
ahriman.core.build_tools
ahriman.core.configuration

View File

@ -17,6 +17,7 @@
# 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 ahriman.core import context
from ahriman.core.archive.archive_tree import ArchiveTree
from ahriman.core.configuration import Configuration
from ahriman.core.sign.gpg import GPG
@ -44,7 +45,9 @@ class ArchiveTrigger(Trigger):
Trigger.__init__(self, repository_id, configuration)
self.paths = configuration.repository_paths
self.tree = ArchiveTree(self.paths, GPG(configuration).repository_sign_args)
ctx = context.get()
self.tree = ArchiveTree(self.paths, ctx.get(GPG).repository_sign_args)
def on_result(self, result: Result, packages: list[Package]) -> None:
"""

View File

@ -57,7 +57,7 @@ class ConfigurationMultiDict(dict[str, Any]):
OptionError: if the key already exists in the dictionary, but not a single value list or a string
"""
match self.get(key):
case [current_value] | (str() as current_value):
case [current_value] | str(current_value):
value = f"{current_value} {value}"
case None:
pass

View File

@ -36,7 +36,6 @@ class Trigger(LazyLogging):
CONFIGURATION_SCHEMA(ConfigurationSchema): (class attribute) configuration schema template
CONFIGURATION_SCHEMA_FALLBACK(str | None): (class attribute) optional fallback option for defining
configuration schema type used
REQUIRES_REPOSITORY(bool): (class attribute) either trigger requires loaded repository or not
configuration(Configuration): configuration instance
repository_id(RepositoryId): repository unique identifier
@ -60,7 +59,6 @@ class Trigger(LazyLogging):
CONFIGURATION_SCHEMA: ClassVar[ConfigurationSchema] = {}
CONFIGURATION_SCHEMA_FALLBACK: ClassVar[str | None] = None
REQUIRES_REPOSITORY: ClassVar[bool] = True
def __init__(self, repository_id: RepositoryId, configuration: Configuration) -> None:
"""
@ -81,16 +79,6 @@ class Trigger(LazyLogging):
"""
return self.repository_id.architecture
@property
def is_allowed_to_run(self) -> bool:
"""
whether trigger allowed to run or not
Returns:
bool: ``True`` in case if trigger allowed to run and ``False`` otherwise
"""
return not (self.REQUIRES_REPOSITORY and self.repository_id.is_empty)
@classmethod
def configuration_schema(cls, configuration: Configuration | None) -> ConfigurationSchema:
"""

View File

@ -77,9 +77,8 @@ class TriggerLoader(LazyLogging):
"""
instance = cls()
instance.triggers = [
trigger
for trigger_name in instance.selected_triggers(configuration)
if (trigger := instance.load_trigger(trigger_name, repository_id, configuration)).is_allowed_to_run
instance.load_trigger(trigger, repository_id, configuration)
for trigger in instance.selected_triggers(configuration)
]
return instance

View File

@ -57,7 +57,7 @@ def test_repo_init(repo: Repo, mocker: MockerFixture) -> None:
assert check_output_mock.call_args[0][0] == "repo-add"
def test_repo_remove(repo: Repo, package_ahriman: Package, mocker: MockerFixture) -> None:
def test_repo_remove(repo: Repo, package_ahriman: Package,mocker: MockerFixture) -> None:
"""
must call repo-remove on package removal
"""

View File

@ -1,8 +1,11 @@
import pytest
from pytest_mock import MockerFixture
from ahriman.core.archive import ArchiveTrigger
from ahriman.core.archive.archive_tree import ArchiveTree
from ahriman.core.configuration import Configuration
from ahriman.core.sign.gpg import GPG
@pytest.fixture
@ -20,15 +23,18 @@ def archive_tree(configuration: Configuration) -> ArchiveTree:
@pytest.fixture
def archive_trigger(configuration: Configuration) -> ArchiveTrigger:
def archive_trigger(configuration: Configuration, gpg: GPG, mocker: MockerFixture) -> ArchiveTrigger:
"""
archive trigger fixture
Args:
configuration(Configuration): configuration fixture
gpg(GPG): GPG fixture
mocker(MockerFixture): mocker object
Returns:
ArchiveTrigger: archive trigger test instance
"""
mocker.patch("ahriman.core._Context.get", return_value=GPG)
_, repository_id = configuration.check_loaded()
return ArchiveTrigger(repository_id, configuration)
return ArchiveTrigger(repository_id, configuration)

View File

@ -1,3 +1,4 @@
from dataclasses import replace
from pathlib import Path
from pytest_mock import MockerFixture
@ -22,7 +23,6 @@ def test_symlinks_create(archive_tree: ArchiveTree, package_ahriman: Package, pa
must create symlinks
"""
_original_exists = Path.exists
def exists_mock(path: Path) -> bool:
if path.name in (package.filename for package in package_python_schedule.packages.values()):
return True
@ -63,53 +63,6 @@ def test_symlinks_create_empty_filename(archive_tree: ArchiveTree, package_ahrim
symlinks_mock.assert_not_called()
def test_symlinks_fix(archive_tree: ArchiveTree, mocker: MockerFixture) -> None:
"""
must fix broken symlinks
"""
_original_exists = Path.exists
def exists_mock(path: Path) -> bool:
if path.name == "symlink":
return True
return _original_exists(path)
mocker.patch("pathlib.Path.is_symlink", side_effect=[True, True, False])
mocker.patch("pathlib.Path.exists", autospec=True, side_effect=exists_mock)
walk_mock = mocker.patch("ahriman.core.archive.archive_tree.walk", return_value=[
archive_tree.repository_for() / filename
for filename in ("symlink", "broken_symlink", "file")
])
remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove")
archive_tree.symlinks_fix()
walk_mock.assert_called_once_with(archive_tree.paths.archive / "repos")
remove_mock.assert_called_once_with(None, archive_tree.repository_for() / "broken_symlink")
def test_symlinks_fix_foreign_repository(archive_tree: ArchiveTree, mocker: MockerFixture) -> None:
"""
must skip symlinks check if repository name or architecture doesn't match
"""
_original_exists = Path.exists
def exists_mock(path: Path) -> bool:
if path.name == "symlink":
return True
return _original_exists(path)
mocker.patch("pathlib.Path.is_symlink", side_effect=[True, True, False])
mocker.patch("pathlib.Path.exists", autospec=True, side_effect=exists_mock)
mocker.patch("ahriman.core.archive.archive_tree.walk", return_value=[
archive_tree.repository_for().with_name("i686") / filename
for filename in ("symlink", "broken_symlink", "file")
])
remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove")
archive_tree.symlinks_fix()
remove_mock.assert_not_called()
def test_tree_create(archive_tree: ArchiveTree, mocker: MockerFixture) -> None:
"""
must create repository root if not exists

View File

@ -3,7 +3,6 @@ from unittest.mock import MagicMock
from ahriman.core.configuration import Configuration
from ahriman.core.report import ReportTrigger
from ahriman.core.triggers import Trigger
from ahriman.models.repository_id import RepositoryId
from ahriman.models.result import Result
@ -14,19 +13,6 @@ def test_architecture(trigger: Trigger) -> None:
assert trigger.architecture == trigger.repository_id.architecture
def test_is_allowed_to_run(trigger: Trigger) -> None:
"""
must return flag correctly
"""
assert trigger.is_allowed_to_run
trigger.repository_id = RepositoryId("", "")
assert not trigger.is_allowed_to_run
trigger.REQUIRES_REPOSITORY = False
assert trigger.is_allowed_to_run
def test_configuration_schema(configuration: Configuration) -> None:
"""
must return used configuration schema

View File

@ -140,6 +140,8 @@ dynamic_version = "{[project]name}.__version__"
extras = [
{ replace = "ref", of = ["project", "extras"], extend = true },
]
# TODO: steamline shlex usage after https://github.com/iterative/shtab/pull/192 merge
handle_redirect = true
pip_pre = true
set_env.PYTHONPATH = "src"
set_env.SPHINX_APIDOC_OPTIONS = "members,no-undoc-members,show-inheritance"
@ -147,14 +149,18 @@ commands = [
[
"shtab",
{ replace = "ref", of = ["flags", "shtab"], extend = true },
"--shell", "bash",
"--output", "package/share/bash-completion/completions/_ahriman",
"--shell",
"bash",
">",
"package/share/bash-completion/completions/_ahriman",
],
[
"shtab",
{ replace = "ref", of = ["flags", "shtab"], extend = true },
"--shell", "zsh",
"--output", "package/share/zsh/site-functions/_ahriman",
"--shell",
"zsh",
">",
"package/share/zsh/site-functions/_ahriman",
],
[
"argparse-manpage",

View File

@ -18,9 +18,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import importlib
import shlex
import sys
from tox.config.sets import EnvConfigSet
from tox.config.types import Command
from tox.plugin import impl
from tox.session.state import State
from tox.tox_env.api import ToxEnv
@ -54,6 +56,35 @@ def _extract_version(env_conf: EnvConfigSet, python_path: str | None = None) ->
return {"VERSION": version}
def _wrap_commands(env_conf: EnvConfigSet, shell: str = "bash") -> None:
"""
wrap commands into shell if there is redirect
Args:
env_conf(EnvConfigSet): the core configuration object
shell(str, optional): shell command to use (Default value = "bash")
"""
if not env_conf["handle_redirect"]:
return
# append shell just in case
env_conf["allowlist_externals"].append(shell)
for command in env_conf["commands"]:
if len(command.args) < 3: # command itself, redirect and output
continue
redirect, output = command.args[-2:]
if redirect not in (">", "2>", "&>"):
continue
command.args = [
shell,
"-c",
f"{Command(command.args[:-2]).shell} {redirect} {shlex.quote(output)}",
]
@impl
def tox_add_env_config(env_conf: EnvConfigSet, state: State) -> None:
"""
@ -72,6 +103,12 @@ def tox_add_env_config(env_conf: EnvConfigSet, state: State) -> None:
default="",
desc="import path for the version variable",
)
env_conf.add_config(
keys=["handle_redirect"],
of_type=bool,
default=False,
desc="wrap commands to handle redirects if any",
)
@impl
@ -87,3 +124,5 @@ def tox_before_run_commands(tox_env: ToxEnv) -> None:
python_path = set_env.load("PYTHONPATH") if "PYTHONPATH" in set_env else None
set_env.update(_extract_version(env_conf, python_path))
_wrap_commands(env_conf)