Skip to content

Commit

Permalink
Merge pull request #542 from dhellmann/versionmap-type
Browse files Browse the repository at this point in the history
add VersionMap helper
  • Loading branch information
mergify[bot] authored Jan 30, 2025
2 parents e6f94c9 + ebf2345 commit 431f479
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 0 deletions.
57 changes: 57 additions & 0 deletions src/fromager/versionmap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""VersionMap interface for managing package settings in plugins."""

import typing

from packaging.requirements import Requirement
from packaging.version import Version


class VersionMap:
def __init__(
self, initial_content: dict[Version | str, typing.Any] | None = None
) -> None:
"""Initialize the VersionMap
Stores the inputs associating versions and arbitrary data. If the
versions are strings, they are converted to Version instances
internally. Any exceptions from the conversion are propagated.
"""
self._content: dict[Version, typing.Any] = {}
for k, v in (initial_content or {}).items():
self.add(k, v)

def add(self, key: Version | str, value: typing.Any) -> None:
"""Add a single value associated with a version
String keys are converted to Version instances. Any exceptions from the
conversion are propagated.
"""
if not isinstance(key, Version):
key = Version(key)
self._content[key] = value

def versions(self) -> typing.Iterable[Version]:
"""Return the known versions, sorted in descending order."""
return reversed(sorted(self._content.keys()))

def lookup(
self,
req: Requirement,
constraint: Requirement | None = None,
allow_prerelease: bool = False,
) -> tuple[Version, typing.Any]:
"""Return the matching version and associated value.
Finds the known version that best matches the requirement and optional
constraint and returns a tuple containing that version and the
associated value.
"""
for version in self.versions():
if not req.specifier.contains(version, prereleases=allow_prerelease):
continue
if constraint and not constraint.specifier.contains(
version, prereleases=allow_prerelease
):
continue
return (version, self._content[version])
raise ValueError(f"No version matched {req} with constraint {constraint}")
102 changes: 102 additions & 0 deletions tests/test_versionmap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import pytest
from packaging.requirements import Requirement
from packaging.version import Version

from fromager.versionmap import VersionMap


def test_initialize():
m = VersionMap(
{
"1.2": "value for 1.2",
Version("1.3"): "value for 1.3",
"1.0": "value for 1.0",
}
)
assert list(m.versions()) == [Version("1.3"), Version("1.2"), Version("1.0")]


def test_lookup():
m = VersionMap(
{
"1.2": "value for 1.2",
Version("1.3"): "value for 1.3",
"1.0": "value for 1.0",
}
)
assert m.lookup(Requirement("pkg")) == (Version("1.3"), "value for 1.3")
assert m.lookup(Requirement("pkg>1.0")) == (Version("1.3"), "value for 1.3")
assert m.lookup(Requirement("pkg<1.3")) == (Version("1.2"), "value for 1.2")


def test_prerelease():
m = VersionMap(
{
Version("0.4.1b0"): "value for 0.4.1b0",
"1.2": "value for 1.2",
Version("1.3"): "value for 1.3",
"1.0": "value for 1.0",
"1.5.0a0": "value for 1.5.0a0",
}
)
assert m.lookup(Requirement("pkg")) == (Version("1.3"), "value for 1.3")
assert m.lookup(Requirement("pkg>1.0")) == (Version("1.3"), "value for 1.3")
assert m.lookup(Requirement("pkg<1.3")) == (Version("1.2"), "value for 1.2")
assert m.lookup(Requirement("pkg"), allow_prerelease=True) == (
Version("1.5.0a0"),
"value for 1.5.0a0",
)
with pytest.raises(ValueError):
assert (
m.lookup(Requirement("pkg"), Requirement("pkg<1.0")) == "value for 0.4.1b"
)
assert m.lookup(
Requirement("pkg"), Requirement("pkg<1.0"), allow_prerelease=True
) == (Version("0.4.1b0"), "value for 0.4.1b0")


def test_only_prerelease():
m = VersionMap(
{
Version("0.4.1b0"): "value for 0.4.1b0",
Version("0.6b0"): "value for 0.6b0",
}
)
assert m.lookup(
Requirement("pkg"), constraint=Requirement("pkg<0.6b"), allow_prerelease=True
) == (
Version("0.4.1b0"),
"value for 0.4.1b0",
)


def test_with_constraint():
m = VersionMap(
{
"1.2": "value for 1.2",
Version("1.3"): "value for 1.3",
"1.0": "value for 1.0",
}
)
assert m.lookup(Requirement("pkg"), Requirement("pkg<1.3")) == (
Version("1.2"),
"value for 1.2",
)
assert m.lookup(Requirement("pkg>1.0"), Requirement("pkg==1.2")) == (
Version("1.2"),
"value for 1.2",
)


def test_no_match():
m = VersionMap(
{
"1.2": "value for 1.2",
Version("1.3"): "value for 1.3",
"1.0": "value for 1.0",
}
)
with pytest.raises(ValueError):
m.lookup(Requirement("pkg"), Requirement("pkg<1.0"))
with pytest.raises(ValueError):
m.lookup(Requirement("pkg>1.0"), Requirement("pkg<1.0"))

0 comments on commit 431f479

Please sign in to comment.