lib.version.Dependency: Beautify exceptions

Add a dependent_package parameter to Dependency's constructor, and use it
in exceptions the class raises.
This commit is contained in:
Jan Lindemann 2026-09-10 07:41:32 +02:00
commit 6b13ae1212
Signed by: Jan Lindemann
GPG key ID: 3750640C9E25DD61
2 changed files with 43 additions and 31 deletions

View file

@ -5,13 +5,13 @@ import re
from functools import cached_property
from typing import TYPE_CHECKING, override
from ..log import WARNING, log
from ..log import get_caller_pos
from .base import Boundary, Lookup, Syntax
from .Version import Version
if TYPE_CHECKING:
from collections.abc import Sequence
from typing import ClassVar
from typing import ClassVar, Never
class Dependency: # export
"""A single package dependency: a package name and an optional
@ -24,30 +24,38 @@ class Dependency: # export
__SPLIT_RE: ClassVar[re.Pattern[str]] = re.compile('([=><]+)')
@property
def __target_prefix(self) -> str:
if self.__dependent_package is None:
return ''
return f' Package "{self.__dependent_package}"'
def __raise(self, msg: str, cls: type[Exception] = Error) -> Never:
mod, file, line = get_caller_pos()
raise cls(f'[{file}:{line}]{self.__target_prefix}: {msg}')
@cached_property
def __parsed_spec(self) -> tuple[str, Boundary | None]:
parts = [part.strip() for part in self.__SPLIT_RE.split(self.__spec)]
match len(parts):
case 1:
if not parts[0]:
raise Dependency.Error(f'Invalid dependency spec "{self.__spec}"')
self.__raise(f'Invalid dependency spec "{self.__spec}"')
return parts[0], None
case 3:
if not parts[0] or not parts[2]:
raise Dependency.Error(f'Invalid dependency spec "{self.__spec}"')
self.__raise(f'Invalid dependency spec "{self.__spec}"')
if parts[2] == 'REVISION':
log(
WARNING,
f'Spec "{self.__spec}": a bare REVISION renders as a bare '
'number, which RPM reads as a version constraint, not a '
'release constraint'
self.__raise(
f'Spec "{self.__spec}": a bare REVISION renders as a '
'bare number, which is likely not what the user intended'
)
return parts[0], Boundary(
op = parts[1],
version = Version(parts[0], parts[2], self.__lookup_version),
op=parts[1],
version=Version(parts[0], parts[2], self.__lookup_version),
)
case _:
raise Dependency.Error(f'Invalid dependency spec "{self.__spec}"')
self.__raise(f'Invalid dependency spec "{self.__spec}"')
def __version_boundaries(
self,
@ -69,12 +77,13 @@ class Dependency: # export
case '<' | '<=':
ret.append(specified)
case _:
raise NotImplementedError(
self.__raise(
(
'Expanding version boundary '
f'"{self.full_name} {specified.op} {specified.version}" '
'is not yet implemented'
)
),
cls = NotImplementedError,
)
return ret
@ -84,18 +93,24 @@ class Dependency: # export
self,
spec: str,
lookup_version: Lookup | None = None,
dependent_package: str | None = None,
) -> None:
self.__spec = spec
self.__lookup_version = lookup_version
self.__dependent_package = dependent_package
@override
def __str__(self) -> str:
return self.__spec
@property
def dependent_package(self) -> str | None:
return self.__dependent_package
@cached_property
def current_version(self) -> str:
if not self.__lookup_version:
raise Dependency.Error(
self.__raise(
f'Tried to look up "{self.__spec}" for package '
f'"{self.base_name}" without lookup function'
)
@ -155,9 +170,13 @@ class Dependency: # export
cls,
spec: str,
lookup_version: Lookup | None = None,
dependent_package: str | None = None,
) -> Sequence[Dependency]:
"""Split a comma-separated specification into Dependency objects"""
return [
Dependency(spec = spec.strip(), lookup_version = lookup_version)
for spec in spec.split(',')
Dependency(
spec = spec.strip(),
lookup_version = lookup_version,
dependent_package = dependent_package,
) for spec in spec.split(',')
]

View file

@ -1,6 +1,3 @@
import io
from jw.pkg.lib.log import add_capture_stream, rm_capture_stream
from jw.pkg.lib.version import Component, Dependency, Syntax, Version
class FakeApp:
@ -82,17 +79,13 @@ assert d.constraint_str(untemplated=False, include_revision=False) == \
# untemplated=False keeps the specifiers as written
d = Dependency('foo = VERSION', app.get_version)
assert d.constraint_str(untemplated = False) == 'foo = VERSION'
# REVISION resolves to the revision part
d = Dependency('foo = REVISION', app.get_version)
assert d.constraint_str() == 'foo = 45'
assert d.constraint_str(untemplated = False) == 'foo = REVISION'
# A bare REVISION warns: RPM reads a bare number as a version
buf = io.StringIO()
sd = add_capture_stream(buf)
d = Dependency('foo = REVISION', app.get_version)
assert d.constraint_str() == 'foo = 45'
assert 'not a release constraint' in buf.getvalue()
rm_capture_stream(sd)
# A bare REVISION is rejected on the grounds that is assumed that
# the user wanted VERSION instead
try:
Dependency('foo = REVISION', app.get_version).constraint_str()
assert False, 'Should have raised'
except Dependency.Error:
pass
# The lookup uses the base name, not the subpackage name
d = Dependency('foo-devel = VERSION', app.get_version)