|
17 | 17 | )
|
18 | 18 |
|
19 | 19 | from ._types import (
|
20 |
| - VersionTuple, |
| 20 | + String, |
| 21 | + StringOrInt, |
21 | 22 | VersionDict,
|
22 | 23 | VersionIterator,
|
23 |
| - String, |
| 24 | + # VersionTupleString, |
| 25 | + VersionTuple, |
24 | 26 | VersionPart,
|
25 | 27 | )
|
26 | 28 |
|
@@ -109,12 +111,28 @@ class Version:
|
109 | 111 | """
|
110 | 112 | A semver compatible version class.
|
111 | 113 |
|
| 114 | + :param args: a tuple with version information. It can consist of: |
| 115 | +
|
| 116 | + * a maximum length of 5 items that comprehend the major, |
| 117 | + minor, patch, prerelease, or build. |
| 118 | + * a str or bytes string that contains a valid semver |
| 119 | + version string. |
112 | 120 | :param major: version when you make incompatible API changes.
|
113 | 121 | :param minor: version when you add functionality in
|
114 | 122 | a backwards-compatible manner.
|
115 | 123 | :param patch: version when you make backwards-compatible bug fixes.
|
116 | 124 | :param prerelease: an optional prerelease string
|
117 | 125 | :param build: an optional build string
|
| 126 | +
|
| 127 | + This gives you some options to call the :class:`Version` class. |
| 128 | + Precedence has the keyword arguments over the positional arguments. |
| 129 | +
|
| 130 | + >>> Version(1, 2, 3) |
| 131 | + Version(major=1, minor=2, patch=3, prerelease=None, build=None) |
| 132 | + >>> Version("2.3.4-pre.2") |
| 133 | + Version(major=2, minor=3, patch=4, prerelease="pre.2", build=None) |
| 134 | + >>> Version(major=2, minor=3, patch=4, build="build.2") |
| 135 | + Version(major=2, minor=3, patch=4, prerelease=None, build="build.2") |
118 | 136 | """
|
119 | 137 |
|
120 | 138 | __slots__ = ("_major", "_minor", "_patch", "_prerelease", "_build")
|
@@ -144,27 +162,92 @@ class Version:
|
144 | 162 |
|
145 | 163 | def __init__(
|
146 | 164 | self,
|
147 |
| - major: SupportsInt, |
| 165 | + *args: Tuple[ |
| 166 | + StringOrInt, |
| 167 | + Optional[int], |
| 168 | + Optional[int], |
| 169 | + Optional[str], |
| 170 | + Optional[str], |
| 171 | + ], |
| 172 | + major: SupportsInt = 0, |
148 | 173 | minor: SupportsInt = 0,
|
149 | 174 | patch: SupportsInt = 0,
|
150 |
| - prerelease: Union[String, int] = None, |
151 |
| - build: Union[String, int] = None, |
| 175 | + prerelease: StringOrInt = None, |
| 176 | + build: StringOrInt = None, |
152 | 177 | ):
|
| 178 | + verlist = [None, None, None, None, None] |
| 179 | + |
| 180 | + if args and "." in str(args[0]): |
| 181 | + # we have a version string as first argument |
| 182 | + cls = self.__class__ |
| 183 | + v = cast(dict, cls._parse(args[0])) # type: ignore |
| 184 | + self._major = int(v["major"]) |
| 185 | + self._minor = int(v["minor"]) |
| 186 | + self._patch = int(v["patch"]) |
| 187 | + self._prerelease = v["prerelease"] |
| 188 | + self._build = v["build"] |
| 189 | + return |
| 190 | + if args and len(args) > 5: |
| 191 | + raise ValueError("You cannot pass more than 5 arguments to Version") |
| 192 | + |
| 193 | + for index, item in enumerate(args): |
| 194 | + verlist[index] = args[index] # type: ignore |
| 195 | + |
153 | 196 | # Build a dictionary of the arguments except prerelease and build
|
154 |
| - version_parts = {"major": int(major), "minor": int(minor), "patch": int(patch)} |
| 197 | + try: |
| 198 | + version_parts = { |
| 199 | + # Prefer major, minor, and patch over args |
| 200 | + "major": int(major or verlist[0] or 0), |
| 201 | + "minor": int(minor or verlist[1] or 0), |
| 202 | + "patch": int(patch or verlist[2] or 0), |
| 203 | + } |
| 204 | + except ValueError: |
| 205 | + raise ValueError( |
| 206 | + "Expected integer or integer string for major, " "minor, or patch" |
| 207 | + ) |
155 | 208 |
|
156 | 209 | for name, value in version_parts.items():
|
157 | 210 | if value < 0:
|
158 | 211 | raise ValueError(
|
159 | 212 | "{!r} is negative. A version can only be positive.".format(name)
|
160 | 213 | )
|
161 | 214 |
|
| 215 | + prerelease = prerelease or verlist[3] |
| 216 | + build = build or verlist[4] |
| 217 | + |
162 | 218 | self._major = version_parts["major"]
|
163 | 219 | self._minor = version_parts["minor"]
|
164 | 220 | self._patch = version_parts["patch"]
|
165 | 221 | self._prerelease = None if prerelease is None else str(prerelease)
|
166 | 222 | self._build = None if build is None else str(build)
|
167 | 223 |
|
| 224 | + @classmethod |
| 225 | + def _parse(cls, version: String) -> Dict: |
| 226 | + """ |
| 227 | + Parse version string to a Version instance. |
| 228 | +
|
| 229 | + .. versionchanged:: 2.11.0 |
| 230 | + Changed method from static to classmethod to |
| 231 | + allow subclasses. |
| 232 | +
|
| 233 | + :param version: version string |
| 234 | + :return: a new :class:`Version` instance |
| 235 | + :raises ValueError: if version is invalid |
| 236 | +
|
| 237 | + >>> semver.Version.parse('3.4.5-pre.2+build.4') |
| 238 | + Version(major=3, minor=4, patch=5, \ |
| 239 | +prerelease='pre.2', build='build.4') |
| 240 | + """ |
| 241 | + if isinstance(version, bytes): |
| 242 | + version: str = version.decode("UTF-8") # type: ignore |
| 243 | + elif not isinstance(version, String.__args__): # type: ignore |
| 244 | + raise TypeError(f"not expecting type {type(version)!r}") |
| 245 | + match = cls._REGEX.match(version) |
| 246 | + if match is None: |
| 247 | + raise ValueError(f"{version} is not valid SemVer string") # type: ignore |
| 248 | + |
| 249 | + return cast(dict, match.groupdict()) |
| 250 | + |
168 | 251 | @property
|
169 | 252 | def major(self) -> int:
|
170 | 253 | """The major part of a version (read-only)."""
|
@@ -513,11 +596,11 @@ def __repr__(self) -> str:
|
513 | 596 | return "%s(%s)" % (type(self).__name__, s)
|
514 | 597 |
|
515 | 598 | def __str__(self) -> str:
|
516 |
| - version = "%d.%d.%d" % (self.major, self.minor, self.patch) |
| 599 | + version = f"{self.major:d}.{self.minor:d}.{self.patch:d}" |
517 | 600 | if self.prerelease:
|
518 |
| - version += "-%s" % self.prerelease |
| 601 | + version += f"-{self.prerelease}" |
519 | 602 | if self.build:
|
520 |
| - version += "+%s" % self.build |
| 603 | + version += f"+{self.build}" |
521 | 604 | return version
|
522 | 605 |
|
523 | 606 | def __hash__(self) -> int:
|
@@ -598,13 +681,7 @@ def parse(cls, version: String) -> "Version":
|
598 | 681 | Version(major=3, minor=4, patch=5, \
|
599 | 682 | prerelease='pre.2', build='build.4')
|
600 | 683 | """
|
601 |
| - version_str = ensure_str(version) |
602 |
| - match = cls._REGEX.match(version_str) |
603 |
| - if match is None: |
604 |
| - raise ValueError(f"{version_str} is not valid SemVer string") |
605 |
| - |
606 |
| - matched_version_parts: Dict[str, Any] = match.groupdict() |
607 |
| - |
| 684 | + matched_version_parts: Dict[str, Any] = cls._parse(version) |
608 | 685 | return cls(**matched_version_parts)
|
609 | 686 |
|
610 | 687 | def replace(self, **parts: Union[int, Optional[str]]) -> "Version":
|
|
0 commit comments