396 lines
11 KiB
Python
396 lines
11 KiB
Python
# Copyright (c) Twisted Matrix Laboratories.
|
|
# See LICENSE for details.
|
|
|
|
"""
|
|
Versions for Python packages.
|
|
|
|
See L{Version}.
|
|
"""
|
|
|
|
from __future__ import division, absolute_import
|
|
|
|
import sys
|
|
import warnings
|
|
from typing import TYPE_CHECKING, Any, TypeVar, Union, Optional, Dict
|
|
|
|
#
|
|
# Compat functions
|
|
#
|
|
|
|
_T = TypeVar("_T", contravariant=True)
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
from typing_extensions import Literal
|
|
from distutils.dist import Distribution as _Distribution
|
|
|
|
|
|
else:
|
|
_Distribution = object
|
|
|
|
if sys.version_info > (3,):
|
|
|
|
def _cmp(a, b): # type: (Any, Any) -> int
|
|
"""
|
|
Compare two objects.
|
|
|
|
Returns a negative number if C{a < b}, zero if they are equal, and a
|
|
positive number if C{a > b}.
|
|
"""
|
|
if a < b:
|
|
return -1
|
|
elif a == b:
|
|
return 0
|
|
else:
|
|
return 1
|
|
|
|
|
|
else:
|
|
_cmp = cmp # noqa: F821
|
|
|
|
|
|
#
|
|
# Versioning
|
|
#
|
|
|
|
|
|
class _Inf(object):
|
|
"""
|
|
An object that is bigger than all other objects.
|
|
"""
|
|
|
|
def __cmp__(self, other): # type: (object) -> int
|
|
"""
|
|
@param other: Another object.
|
|
@type other: any
|
|
|
|
@return: 0 if other is inf, 1 otherwise.
|
|
@rtype: C{int}
|
|
"""
|
|
if other is _inf:
|
|
return 0
|
|
return 1
|
|
|
|
if sys.version_info >= (3,):
|
|
|
|
def __lt__(self, other): # type: (object) -> bool
|
|
return self.__cmp__(other) < 0
|
|
|
|
def __le__(self, other): # type: (object) -> bool
|
|
return self.__cmp__(other) <= 0
|
|
|
|
def __gt__(self, other): # type: (object) -> bool
|
|
return self.__cmp__(other) > 0
|
|
|
|
def __ge__(self, other): # type: (object) -> bool
|
|
return self.__cmp__(other) >= 0
|
|
|
|
|
|
_inf = _Inf()
|
|
|
|
|
|
class IncomparableVersions(TypeError):
|
|
"""
|
|
Two versions could not be compared.
|
|
"""
|
|
|
|
|
|
class Version(object):
|
|
"""
|
|
An encapsulation of a version for a project, with support for outputting
|
|
PEP-440 compatible version strings.
|
|
|
|
This class supports the standard major.minor.micro[rcN] scheme of
|
|
versioning.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
package, # type: str
|
|
major, # type: Union[Literal["NEXT"], int]
|
|
minor, # type: int
|
|
micro, # type: int
|
|
release_candidate=None, # type: Optional[int]
|
|
prerelease=None, # type: Optional[int]
|
|
post=None, # type: Optional[int]
|
|
dev=None, # type: Optional[int]
|
|
):
|
|
"""
|
|
@param package: Name of the package that this is a version of.
|
|
@type package: C{str}
|
|
@param major: The major version number.
|
|
@type major: C{int} or C{str} (for the "NEXT" symbol)
|
|
@param minor: The minor version number.
|
|
@type minor: C{int}
|
|
@param micro: The micro version number.
|
|
@type micro: C{int}
|
|
@param release_candidate: The release candidate number.
|
|
@type release_candidate: C{int}
|
|
@param prerelease: The prerelease number. (Deprecated)
|
|
@type prerelease: C{int}
|
|
@param post: The postrelease number.
|
|
@type post: C{int}
|
|
@param dev: The development release number.
|
|
@type dev: C{int}
|
|
"""
|
|
if release_candidate and prerelease:
|
|
raise ValueError("Please only return one of these.")
|
|
elif prerelease and not release_candidate:
|
|
release_candidate = prerelease
|
|
warnings.warn(
|
|
"Passing prerelease to incremental.Version was "
|
|
"deprecated in Incremental 16.9.0. Please pass "
|
|
"release_candidate instead.",
|
|
DeprecationWarning,
|
|
stacklevel=2,
|
|
)
|
|
|
|
if major == "NEXT":
|
|
if minor or micro or release_candidate or post or dev:
|
|
raise ValueError(
|
|
"When using NEXT, all other values except Package must be 0."
|
|
)
|
|
|
|
self.package = package
|
|
self.major = major
|
|
self.minor = minor
|
|
self.micro = micro
|
|
self.release_candidate = release_candidate
|
|
self.post = post
|
|
self.dev = dev
|
|
|
|
@property
|
|
def prerelease(self): # type: () -> Optional[int]
|
|
warnings.warn(
|
|
"Accessing incremental.Version.prerelease was "
|
|
"deprecated in Incremental 16.9.0. Use "
|
|
"Version.release_candidate instead.",
|
|
DeprecationWarning,
|
|
stacklevel=2,
|
|
),
|
|
return self.release_candidate
|
|
|
|
def public(self): # type: () -> str
|
|
"""
|
|
Return a PEP440-compatible "public" representation of this L{Version}.
|
|
|
|
Examples:
|
|
|
|
- 14.4.0
|
|
- 1.2.3rc1
|
|
- 14.2.1rc1dev9
|
|
- 16.04.0dev0
|
|
"""
|
|
if self.major == "NEXT":
|
|
return self.major
|
|
|
|
if self.release_candidate is None:
|
|
rc = ""
|
|
else:
|
|
rc = ".rc%s" % (self.release_candidate,)
|
|
|
|
if self.post is None:
|
|
post = ""
|
|
else:
|
|
post = ".post%s" % (self.post,)
|
|
|
|
if self.dev is None:
|
|
dev = ""
|
|
else:
|
|
dev = ".dev%s" % (self.dev,)
|
|
|
|
return "%r.%d.%d%s%s%s" % (self.major, self.minor, self.micro, rc, post, dev)
|
|
|
|
base = public
|
|
short = public
|
|
local = public
|
|
|
|
def __repr__(self): # type: () -> str
|
|
|
|
if self.release_candidate is None:
|
|
release_candidate = ""
|
|
else:
|
|
release_candidate = ", release_candidate=%r" % (self.release_candidate,)
|
|
|
|
if self.post is None:
|
|
post = ""
|
|
else:
|
|
post = ", post=%r" % (self.post,)
|
|
|
|
if self.dev is None:
|
|
dev = ""
|
|
else:
|
|
dev = ", dev=%r" % (self.dev,)
|
|
|
|
return "%s(%r, %r, %d, %d%s%s%s)" % (
|
|
self.__class__.__name__,
|
|
self.package,
|
|
self.major,
|
|
self.minor,
|
|
self.micro,
|
|
release_candidate,
|
|
post,
|
|
dev,
|
|
)
|
|
|
|
def __str__(self): # type: () -> str
|
|
return "[%s, version %s]" % (self.package, self.short())
|
|
|
|
def __cmp__(self, other): # type: (Version) -> int
|
|
"""
|
|
Compare two versions, considering major versions, minor versions, micro
|
|
versions, then release candidates, then postreleases, then dev
|
|
releases. Package names are case insensitive.
|
|
|
|
A version with a release candidate is always less than a version
|
|
without a release candidate. If both versions have release candidates,
|
|
they will be included in the comparison.
|
|
|
|
Likewise, a version with a dev release is always less than a version
|
|
without a dev release. If both versions have dev releases, they will
|
|
be included in the comparison.
|
|
|
|
@param other: Another version.
|
|
@type other: L{Version}
|
|
|
|
@return: NotImplemented when the other object is not a Version, or one
|
|
of -1, 0, or 1.
|
|
|
|
@raise IncomparableVersions: when the package names of the versions
|
|
differ.
|
|
"""
|
|
if not isinstance(other, self.__class__):
|
|
return NotImplemented
|
|
if self.package.lower() != other.package.lower():
|
|
raise IncomparableVersions("%r != %r" % (self.package, other.package))
|
|
|
|
if self.major == "NEXT":
|
|
major = _inf # type: Union[int, _Inf]
|
|
else:
|
|
major = self.major
|
|
|
|
if self.release_candidate is None:
|
|
release_candidate = _inf # type: Union[int, _Inf]
|
|
else:
|
|
release_candidate = self.release_candidate
|
|
|
|
if self.post is None:
|
|
post = -1
|
|
else:
|
|
post = self.post
|
|
|
|
if self.dev is None:
|
|
dev = _inf # type: Union[int, _Inf]
|
|
else:
|
|
dev = self.dev
|
|
|
|
if other.major == "NEXT":
|
|
othermajor = _inf # type: Union[int, _Inf]
|
|
else:
|
|
othermajor = other.major
|
|
|
|
if other.release_candidate is None:
|
|
otherrc = _inf # type: Union[int, _Inf]
|
|
else:
|
|
otherrc = other.release_candidate
|
|
|
|
if other.post is None:
|
|
otherpost = -1
|
|
else:
|
|
otherpost = other.post
|
|
|
|
if other.dev is None:
|
|
otherdev = _inf # type: Union[int, _Inf]
|
|
else:
|
|
otherdev = other.dev
|
|
|
|
x = _cmp(
|
|
(major, self.minor, self.micro, release_candidate, post, dev),
|
|
(othermajor, other.minor, other.micro, otherrc, otherpost, otherdev),
|
|
)
|
|
return x
|
|
|
|
if sys.version_info >= (3,):
|
|
|
|
def __eq__(self, other): # type: (Any) -> bool
|
|
c = self.__cmp__(other)
|
|
if c is NotImplemented:
|
|
return c # type: ignore[return-value]
|
|
return c == 0
|
|
|
|
def __ne__(self, other): # type: (Any) -> bool
|
|
c = self.__cmp__(other)
|
|
if c is NotImplemented:
|
|
return c # type: ignore[return-value]
|
|
return c != 0
|
|
|
|
def __lt__(self, other): # type: (Version) -> bool
|
|
c = self.__cmp__(other)
|
|
if c is NotImplemented:
|
|
return c # type: ignore[return-value]
|
|
return c < 0
|
|
|
|
def __le__(self, other): # type: (Version) -> bool
|
|
c = self.__cmp__(other)
|
|
if c is NotImplemented:
|
|
return c # type: ignore[return-value]
|
|
return c <= 0
|
|
|
|
def __gt__(self, other): # type: (Version) -> bool
|
|
c = self.__cmp__(other)
|
|
if c is NotImplemented:
|
|
return c # type: ignore[return-value]
|
|
return c > 0
|
|
|
|
def __ge__(self, other): # type: (Version) -> bool
|
|
c = self.__cmp__(other)
|
|
if c is NotImplemented:
|
|
return c # type: ignore[return-value]
|
|
return c >= 0
|
|
|
|
|
|
def getVersionString(version): # type: (Version) -> str
|
|
"""
|
|
Get a friendly string for the given version object.
|
|
|
|
@param version: A L{Version} object.
|
|
@return: A string containing the package and short version number.
|
|
"""
|
|
result = "%s %s" % (version.package, version.short())
|
|
return result
|
|
|
|
|
|
def _get_version(dist, keyword, value): # type: (_Distribution, object, object) -> None
|
|
"""
|
|
Get the version from the package listed in the Distribution.
|
|
"""
|
|
if not value:
|
|
return
|
|
|
|
from distutils.command import build_py
|
|
|
|
sp_command = build_py.build_py(dist)
|
|
sp_command.finalize_options()
|
|
|
|
for item in sp_command.find_all_modules(): # type: ignore[attr-defined]
|
|
if item[1] == "_version":
|
|
version_file = {} # type: Dict[str, Version]
|
|
|
|
with open(item[2]) as f:
|
|
exec(f.read(), version_file)
|
|
|
|
dist.metadata.version = version_file["__version__"].public()
|
|
return None
|
|
|
|
raise Exception("No _version.py found.")
|
|
|
|
|
|
from ._version import __version__ # noqa: E402
|
|
|
|
|
|
def _setuptools_version(): # type: () -> str
|
|
return __version__.public()
|
|
|
|
|
|
__all__ = ["__version__", "Version", "getVersionString"]
|