package: Add functions for removing packages

Functions needed to spot and remove installed conflicting packages
before installation of apps.

- Remove all packages in a single operation as this way apt can search for
solutions to conflicts more easily.

- Use type hints rather than a lot of type checking. Type hints shall later be
enforced using offline checking (with mypy) or at runtime (with enforce, etc.).

Signed-off-by: Fioddor Superconcentrado <fioddor@gmail.com>
[sunil: Run single remove operation on all packages]
[sunil: Use type hints instead of extensive type checking]
[sunil: Trim down the test case as it would only succeed after install]
Reviewed-by: Sunil Mohan Adapa <sunil@medhas.org>

package
This commit is contained in:
Fioddor Superconcentrado 2021-08-31 00:04:09 +02:00 committed by Sunil Mohan Adapa
parent cb539cf1e8
commit 24f7ffe3cf
No known key found for this signature in database
GPG Key ID: 43EA1CFF0AA7C5F2
3 changed files with 77 additions and 1 deletions

View File

@ -48,6 +48,11 @@ def parse_arguments():
'module', help='name of module for which package is being installed')
subparser.add_argument('packages', nargs='+',
help='list of packages to install')
subparser = subparsers.add_parser('remove', help='remove the package(s)')
subparser.add_argument('--packages', required=True,
help='List of packages to remove', nargs='+')
subparsers.add_parser('is-package-manager-busy',
help='Return whether package manager is busy')
subparser = subparsers.add_parser(
@ -100,6 +105,11 @@ def subcommand_install(arguments):
sys.exit(returncode)
def subcommand_remove(arguments):
"""Remove apt package(s)."""
sys.exit(run_apt_command(['remove'] + arguments.packages))
def _assert_managed_packages(module, packages):
"""Check that list of packages are in fact managed by module."""
cfg.read()
@ -116,7 +126,8 @@ def _assert_managed_packages(module, packages):
def subcommand_is_package_manager_busy(_):
"""Check whether package manager is busy.
An exit code of zero indicates that package manager is busy."""
An exit code of zero indicates that package manager is busy.
"""
if not is_package_manager_busy():
sys.exit(-1)

View File

@ -7,11 +7,14 @@ import json
import logging
import subprocess
import threading
from typing import Union
import apt.cache
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy
from plinth import actions
from plinth.errors import ActionError, PlinthError
from plinth.utils import format_lazy
logger = logging.getLogger(__name__)
@ -195,3 +198,30 @@ def filter_conffile_prompt_packages(packages):
'packages',
['filter-conffile-packages', '--packages'] + list(packages))
return json.loads(response)
def packages_installed(candidates: Union[list, tuple]) -> list:
"""Check which candidates are installed on the system.
:param candidates: A list of package names.
:return: A list of installed Debian package names.
"""
cache = apt.cache.Cache()
installed_packages = []
for package_name in candidates:
try:
package = cache[package_name]
if package.is_installed:
installed_packages.append(package_name)
except KeyError:
pass
return installed_packages
def remove(packages: Union[list, tuple]) -> None:
"""Remove packages."""
try:
actions.superuser_run('packages', ['remove', '--packages'] + packages)
except ActionError:
pass

View File

@ -0,0 +1,35 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Test module for package module.
"""
from unittest.mock import call, patch
from plinth.errors import ActionError
from plinth.package import packages_installed, remove
def test_packages_installed():
"""Test packages_installed()."""
# list as input
assert len(packages_installed([])) == 0
assert len(packages_installed(['unknown-package'])) == 0
assert len(packages_installed(['python3'])) == 1
# tuples as input
assert len(packages_installed(())) == 0
assert len(packages_installed(('unknown-package', ))) == 0
assert len(packages_installed(('python3', ))) == 1
@patch('plinth.actions.superuser_run')
def test_remove(run):
"""Test removing packages."""
remove(['package1', 'package2'])
run.assert_has_calls(
[call('packages', ['remove', '--packages', 'package1', 'package2'])])
run.reset_mock()
run.side_effect = ActionError()
remove(['package1'])
run.assert_has_calls(
[call('packages', ['remove', '--packages', 'package1'])])