mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-04-15 09:51:21 +00:00
package: Add package expressions
- Package represents a package to potentially be installed. - PackageOr allows an alternate package, in case the first one is not available. Signed-off-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
parent
8d8a7c9837
commit
31a457029c
@ -25,3 +25,11 @@ class DomainNotRegisteredError(PlinthError):
|
||||
FreedomBox doesn't have a registered domain
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class MissingPackageError(PlinthError):
|
||||
"""Package is not available to be installed at this time."""
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
super().__init__(self.name)
|
||||
|
||||
@ -17,12 +17,78 @@ from django.utils.translation import gettext as _
|
||||
from django.utils.translation import gettext_lazy
|
||||
|
||||
from plinth import actions, app
|
||||
from plinth.errors import ActionError
|
||||
from plinth.errors import ActionError, MissingPackageError
|
||||
from plinth.utils import format_lazy
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PackageExpression:
|
||||
|
||||
def possible(self) -> list[str]:
|
||||
"""Return the list of possible packages before resolving."""
|
||||
raise NotImplementedError
|
||||
|
||||
def actual(self) -> str:
|
||||
"""Return the resolved list of packages to install.
|
||||
|
||||
TODO: Also return version and suite to install from.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Package(PackageExpression):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
optional: bool = False,
|
||||
version: Optional[str] = None, # ">=1.0,<2.0"
|
||||
distribution: Optional[str] = None, # Debian, Ubuntu
|
||||
suite: Optional[str] = None, # stable, testing
|
||||
codename: Optional[str] = None, # bullseye-backports
|
||||
architecture: Optional[str] = None): # arm64
|
||||
self.name = name
|
||||
self.optional = optional
|
||||
self.version = version
|
||||
self.distribution = distribution
|
||||
self.suite = suite
|
||||
self.codename = codename
|
||||
self.architecture = architecture
|
||||
|
||||
def __or__(self, other):
|
||||
return PackageOr(self, other)
|
||||
|
||||
def possible(self) -> list[str]:
|
||||
return [self.name]
|
||||
|
||||
def actual(self) -> str:
|
||||
cache = apt.Cache()
|
||||
if self.name in cache:
|
||||
# TODO: Also return version and suite to install from
|
||||
return self.name
|
||||
|
||||
raise MissingPackageError(self.name)
|
||||
|
||||
|
||||
class PackageOr(PackageExpression):
|
||||
"""Specify that one of the two packages will be installed."""
|
||||
|
||||
def __init__(self, package1: PackageExpression,
|
||||
package2: PackageExpression):
|
||||
self.package1 = package1
|
||||
self.package2 = package2
|
||||
|
||||
def possible(self) -> list[str]:
|
||||
return self.package1.possible() + self.package2.possible()
|
||||
|
||||
def actual(self) -> str:
|
||||
try:
|
||||
return self.package1.actual()
|
||||
except MissingPackageError:
|
||||
return self.package2.actual()
|
||||
|
||||
|
||||
class Packages(app.FollowerComponent):
|
||||
"""Component to manage the packages of an app.
|
||||
|
||||
|
||||
@ -3,17 +3,53 @@
|
||||
Test module for package module.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from unittest.mock import Mock, call, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from plinth.app import App
|
||||
from plinth.errors import ActionError
|
||||
from plinth.package import Packages, packages_installed, remove
|
||||
from plinth.errors import ActionError, MissingPackageError
|
||||
from plinth.package import Package, Packages, packages_installed, remove
|
||||
|
||||
setup_helper = Mock()
|
||||
|
||||
|
||||
class TestPackageExpressions(unittest.TestCase):
|
||||
|
||||
def test_package(self):
|
||||
"""Test resolving package names."""
|
||||
package = Package('python3')
|
||||
assert package.possible() == ['python3']
|
||||
assert package.actual() == 'python3'
|
||||
|
||||
package = Package('unknown-package')
|
||||
assert package.possible() == ['unknown-package']
|
||||
self.assertRaises(MissingPackageError, package.actual)
|
||||
|
||||
def test_package_or_expression(self):
|
||||
"""Test resolving package OR expressions."""
|
||||
expression = Package('python3') | Package('unknown-package')
|
||||
assert expression.possible() == ['python3', 'unknown-package']
|
||||
assert expression.actual() == 'python3'
|
||||
|
||||
expression = Package('unknown-package') | Package('python3')
|
||||
assert expression.possible() == ['unknown-package', 'python3']
|
||||
assert expression.actual() == 'python3'
|
||||
|
||||
# When both packages are available, prefer the first.
|
||||
expression = Package('bash') | Package('dash')
|
||||
assert expression.possible() == ['bash', 'dash']
|
||||
assert expression.actual() == 'bash'
|
||||
|
||||
expression = Package('unknown-package') | Package(
|
||||
'another-unknown-package')
|
||||
assert expression.possible() == [
|
||||
'unknown-package', 'another-unknown-package'
|
||||
]
|
||||
self.assertRaises(MissingPackageError, expression.actual)
|
||||
|
||||
|
||||
def test_packages_init():
|
||||
"""Test initialization of packages component."""
|
||||
component = Packages('test-component', ['foo', 'bar'])
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user