package: Introduce component API for package conflicts

This is help in eliminating the module level package_conflicts declarations.

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Sunil Mohan Adapa 2021-11-17 19:08:56 -08:00 committed by James Valleroy
parent 1681aeb2f9
commit 82876a6e23
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
2 changed files with 54 additions and 3 deletions

View File

@ -3,12 +3,13 @@
Framework for installing and updating distribution packages
"""
import enum
import json
import logging
import subprocess
import sys
import threading
from typing import Union
from typing import Optional, Union
import apt.cache
from django.utils.translation import gettext as _
@ -28,8 +29,15 @@ class Packages(app.FollowerComponent):
of packages required by an app.
"""
class ConflictsAction(enum.Enum):
"""Action to take when a conflicting package is installed."""
IGNORE = 'ignore' # Proceed as if there are no conflicts
REMOVE = 'remove' # Remove the packages before installing the app
def __init__(self, component_id: str, packages: list[str],
skip_recommends=False):
skip_recommends: bool = False,
conflicts: Optional[list[str]] = None,
conflicts_action: Optional[ConflictsAction] = None):
"""Initialize a new packages component.
'component_id' should be a unique ID across all components of an app
@ -39,12 +47,22 @@ class Packages(app.FollowerComponent):
'skip_recommends' is a boolean specifying whether recommended packages
should be installed along with the listed packages.
'conflicts' is the list of Debian packages that can't simultaneously be
installed with packages listed here. None if there are no known
conflicting packages.
'conflicts_action' is a string representing the action to take when it
is found that conflicting Debian packages are installed on the system.
None if there are no known conflicting packages.
"""
super().__init__(component_id)
self.component_id = component_id
self._packages = packages
self.skip_recommends = skip_recommends
self.conflicts = conflicts
self.conflicts_action = conflicts_action
@property
def packages(self) -> list[str]:
@ -59,6 +77,13 @@ class Packages(app.FollowerComponent):
helper = module.setup_helper
helper.install(self.packages, skip_recommends=self.skip_recommends)
def find_conflicts(self) -> Optional[list[str]]:
"""Return list of conflicting packages installed on the system."""
if not self.conflicts:
return None
return packages_installed(self.conflicts)
class PackageException(Exception):
"""A package operation has failed."""

View File

@ -20,13 +20,19 @@ def test_packages_init():
assert component.component_id == 'test-component'
assert component.packages == ['foo', 'bar']
assert not component.skip_recommends
assert component.conflicts is None
assert component.conflicts_action is None
with pytest.raises(ValueError):
Packages(None, [])
component = Packages('test-component', [], skip_recommends=True)
component = Packages('test-component', [], skip_recommends=True,
conflicts=['conflict1', 'conflict2'],
conflicts_action=Packages.ConflictsAction.IGNORE)
assert component.packages == []
assert component.skip_recommends
assert component.conflicts == ['conflict1', 'conflict2']
assert component.conflicts_action == Packages.ConflictsAction.IGNORE
def test_packages_setup():
@ -54,6 +60,26 @@ def test_packages_setup():
[call(['foo2', 'bar2'], skip_recommends=True)])
@patch('plinth.package.packages_installed')
def test_packages_find_conflicts(packages_installed_):
"""Test finding conflicts."""
packages_installed_.return_value = []
component = Packages('test-component', ['package3', 'package4'])
assert component.find_conflicts() is None
packages_installed_.return_value = []
component = Packages('test-component', ['package3', 'package4'],
conflicts=['package5', 'package6'],
conflicts_action=Packages.ConflictsAction.IGNORE)
assert component.find_conflicts() == []
packages_installed_.return_value = ['package1', 'package2']
component = Packages('test-component', ['package3', 'package4'],
conflicts=['package1', 'package2'],
conflicts_action=Packages.ConflictsAction.IGNORE)
assert component.find_conflicts() == ['package1', 'package2']
def test_packages_installed():
"""Test packages_installed()."""
# list as input