-
Notifications
You must be signed in to change notification settings - Fork 50
Expand file tree
/
Copy pathversionmap.py
More file actions
90 lines (73 loc) · 3.18 KB
/
versionmap.py
File metadata and controls
90 lines (73 loc) · 3.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
"""VersionMap interface for managing package settings in plugins."""
import typing
from collections.abc import Iterator, Mapping
from packaging.requirements import Requirement
from packaging.version import Version
class VersionMap(Mapping[Version, typing.Any]):
"""Read-only mapping protocol over versions with helpers for resolution.
Keys must be :class:`packaging.version.Version` instances; callers are
responsible for parsing strings. Mutate the map via :meth:`add`.
"""
_content: dict[Version, typing.Any]
def __init__(
self, initial_content: Mapping[Version, typing.Any] | None = None
) -> None:
"""Initialize the VersionMap.
Stores associations between versions and arbitrary values (for example
download URLs for resolution).
"""
self._content = {}
for k, v in (initial_content or {}).items():
self.add(k, v)
def add(self, key: Version, value: typing.Any) -> None:
"""Associate a value with a version."""
if not isinstance(key, Version):
msg = (
"VersionMap keys must be packaging.version.Version instances, "
f"not {type(key).__name__}"
)
raise TypeError(msg)
self._content[key] = value
def __getitem__(self, key: Version) -> typing.Any:
"""Return the value for a version. Raises KeyError if missing."""
if not isinstance(key, Version):
msg = (
"VersionMap keys must be packaging.version.Version instances, "
f"not {type(key).__name__}"
)
raise TypeError(msg)
return self._content[key]
def __iter__(self) -> Iterator[Version]:
"""Iterate versions in descending order (highest first)."""
return reversed(sorted(self._content.keys()))
def __len__(self) -> int:
return len(self._content)
def versions(self) -> Iterator[Version]:
"""Return known versions, sorted in descending order."""
return iter(self)
def iter_pairs(self) -> Iterator[tuple[Version, typing.Any]]:
"""Yield ``(version, value)`` tuples in descending version order.
Typical use is iteration over versions and URLs for custom providers.
"""
for version in self.versions():
yield version, self._content[version]
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}")