okay fine

This commit is contained in:
pacnpal
2024-11-03 17:47:26 +00:00
parent 01c6004a79
commit 27eb239e97
10020 changed files with 1935769 additions and 2364 deletions

View File

@@ -0,0 +1,565 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Versions for Python packages.
See L{Version}.
"""
from __future__ import division, absolute_import
import os
import sys
import warnings
from typing import TYPE_CHECKING, Any, TypeVar, Union, Optional, Dict, BinaryIO
from dataclasses import dataclass
if TYPE_CHECKING:
import io
from typing_extensions import Literal
from distutils.dist import Distribution as _Distribution
#
# Compat functions
#
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
#
# 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
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: (object) -> 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
def __eq__(self, other): # type: (object) -> bool
c = self.__cmp__(other)
if c is NotImplemented:
return c # type: ignore[return-value]
return c == 0
def __ne__(self, other): # type: (object) -> bool
c = self.__cmp__(other)
if c is NotImplemented:
return c # type: ignore[return-value]
return c != 0
def __lt__(self, other): # type: (object) -> bool
c = self.__cmp__(other)
if c is NotImplemented:
return c # type: ignore[return-value]
return c < 0
def __le__(self, other): # type: (object) -> bool
c = self.__cmp__(other)
if c is NotImplemented:
return c # type: ignore[return-value]
return c <= 0
def __gt__(self, other): # type: (object) -> bool
c = self.__cmp__(other)
if c is NotImplemented:
return c # type: ignore[return-value]
return c > 0
def __ge__(self, other): # type: (object) -> 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 _findPath(path, package): # type: (str, str) -> str
"""
Determine the package root directory.
The result is one of:
- src/{package}
- {package}
Where {package} is downcased.
"""
src_dir = os.path.join(path, "src", package.lower())
current_dir = os.path.join(path, package.lower())
if os.path.isdir(src_dir):
return src_dir
elif os.path.isdir(current_dir):
return current_dir
else:
raise ValueError(
"Can't find the directory of project {}: I looked in {} and {}".format(
package, src_dir, current_dir
)
)
def _existing_version(version_path): # type: (str) -> Version
"""
Load the current version from a ``_version.py`` file.
"""
version_info = {} # type: Dict[str, Version]
with open(version_path, "r") as f:
exec(f.read(), version_info)
return version_info["__version__"]
def _get_setuptools_version(dist): # type: (_Distribution) -> None
"""
Setuptools integration: load the version from the working directory
This function is registered as a setuptools.finalize_distribution_options
entry point [1]. Consequently, it is called in all sorts of weird
contexts. In setuptools, silent failure is the law.
[1]: https://setuptools.pypa.io/en/latest/userguide/extension.html#customizing-distribution-options
@param dist:
A (possibly) empty C{setuptools.Distribution} instance to mutate.
There may be some metadata here if a `setup.py` called `setup()`,
but this hook is always called before setuptools loads anything
from ``pyproject.toml``.
"""
try:
# When operating in a packaging context (i.e. building an sdist or
# wheel) pyproject.toml will always be found in the current working
# directory.
config = _load_pyproject_toml("./pyproject.toml")
except Exception:
return
if not config.opt_in:
return
try:
version = _existing_version(config.version_path)
except FileNotFoundError:
return
dist.metadata.version = version.public()
def _get_distutils_version(dist, keyword, value): # type: (_Distribution, object, object) -> None
"""
Distutils integration: get the version from the package listed in the Distribution.
This function is invoked when a C{setup.py} calls C{setup(use_incremental=True)}.
@see: https://setuptools.pypa.io/en/latest/userguide/extension.html#adding-arguments
"""
if not value: # use_incremental=False
return # pragma: no cover
from setuptools.command import build_py # type: ignore
sp_command = build_py.build_py(dist)
sp_command.finalize_options()
for item in sp_command.find_all_modules():
if item[1] == "_version":
version_path = os.path.join(os.path.dirname(item[2]), "_version.py")
dist.metadata.version = _existing_version(version_path).public()
return
raise Exception("No _version.py found.") # pragma: no cover
def _load_toml(f): # type: (BinaryIO) -> Any
"""
Read the content of a TOML file.
"""
# This import is deferred to avoid a hard dependency on tomli
# when no pyproject.toml is present.
if sys.version_info > (3, 11):
import tomllib
else:
import tomli as tomllib
return tomllib.load(f)
@dataclass(frozen=True)
class _IncrementalConfig:
"""
Configuration loaded from a ``pyproject.toml`` file.
"""
opt_in: bool
"""
Does the pyproject.toml file contain a [tool.incremental]
section? This indicates that the package has explicitly
opted-in to Incremental versioning.
"""
package: str
"""The project name, capitalized as in the project metadata."""
path: str
"""Path to the package root"""
@property
def version_path(self): # type: () -> str
"""Path of the ``_version.py`` file. May not exist."""
return os.path.join(self.path, "_version.py")
def _load_pyproject_toml(toml_path): # type: (str) -> _IncrementalConfig
"""
Load Incremental configuration from a ``pyproject.toml``
If the [tool.incremental] section is empty we take the project name
from the [project] section. Otherwise we require only a C{name} key
specifying the project name. Other keys are forbidden to allow future
extension and catch typos.
@param toml_path:
Path to the ``pyproject.toml`` to load.
"""
with open(toml_path, "rb") as f:
data = _load_toml(f)
tool_incremental = _extract_tool_incremental(data)
# Extract the project name
package = None
if tool_incremental is not None and "name" in tool_incremental:
package = tool_incremental["name"]
if package is None:
# Try to fall back to [project]
try:
package = data["project"]["name"]
except KeyError:
pass
if package is None:
# We can't proceed without a project name.
raise ValueError("""\
Incremental failed to extract the project name from pyproject.toml. Specify it like:
[project]
name = "Foo"
Or:
[tool.incremental]
name = "Foo"
""")
if not isinstance(package, str):
raise TypeError(
"The project name must be a string, but found {}".format(type(package))
)
return _IncrementalConfig(
opt_in=tool_incremental is not None,
package=package,
path=_findPath(os.path.dirname(toml_path), package),
)
def _extract_tool_incremental(data): # type: (Dict[str, object]) -> Optional[Dict[str, object]]
if "tool" not in data:
return None
if not isinstance(data["tool"], dict):
raise ValueError("[tool] must be a table")
if "incremental" not in data["tool"]:
return None
tool_incremental = data["tool"]["incremental"]
if not isinstance(tool_incremental, dict):
raise ValueError("[tool.incremental] must be a table")
if not {"name"}.issuperset(tool_incremental.keys()):
raise ValueError("Unexpected key(s) in [tool.incremental]")
return tool_incremental
from ._version import __version__ # noqa: E402
def _setuptools_version(): # type: () -> str
return __version__.public() # pragma: no cover
__all__ = ["__version__", "Version", "getVersionString"]

View File

@@ -0,0 +1,36 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
import os
import shlex
from typing import Any, Dict, List, Type, TypedDict
from hatchling.version.source.plugin.interface import VersionSourceInterface
from hatchling.plugin import hookimpl
from incremental import _load_pyproject_toml, _existing_version
class _VersionData(TypedDict):
version: str
class IncrementalVersionSource(VersionSourceInterface):
PLUGIN_NAME = "incremental"
def get_version_data(self) -> _VersionData: # type: ignore[override]
path = os.path.join(self.root, "./pyproject.toml")
config = _load_pyproject_toml(path)
return {"version": _existing_version(config.version_path).public()}
def set_version(self, version: str, version_data: Dict[Any, Any]) -> None:
raise NotImplementedError(
f"Run `python -m incremental.version --newversion"
f" {shlex.quote(version)}` to set the version.\n\n"
f" See `python -m incremental.version --help` for more options."
)
@hookimpl
def hatch_register_version_source() -> List[Type[VersionSourceInterface]]:
return [IncrementalVersionSource]

View File

@@ -0,0 +1,11 @@
"""
Provides Incremental version information.
"""
# This file is auto-generated! Do not edit!
# Use `python -m incremental.update Incremental` to change this file.
from incremental import Version
__version__ = Version("Incremental", 24, 7, 2)
__all__ = ["__version__"]

View File

@@ -0,0 +1,169 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""Test handling of ``pyproject.toml`` configuration"""
import os
from typing import cast, Optional, Union
from pathlib import Path
from twisted.trial.unittest import TestCase
from incremental import _load_pyproject_toml, _IncrementalConfig
class VerifyPyprojectDotTomlTests(TestCase):
"""Test the `_load_pyproject_toml` helper function"""
def _loadToml(
self, toml: str, *, path: Union[Path, str, None] = None
) -> Optional[_IncrementalConfig]:
"""
Read a TOML snipped from a temporary file with `_load_pyproject_toml`
@param toml: TOML content of the temporary file
@param path: Path to which the TOML is written
"""
path_: str
if path is None:
path_ = self.mktemp() # type: ignore
else:
path_ = str(path)
with open(path_, "w") as f:
f.write(toml)
try:
return _load_pyproject_toml(path_)
except Exception as e:
if hasattr(e, "add_note"):
e.add_note( # type: ignore[attr-defined]
f"While loading:\n\n{toml}"
) # pragma: no coverage
raise
def test_fileNotFound(self):
"""
An absent ``pyproject.toml`` file produces no result unless
there is opt-in.
"""
path = os.path.join(cast(str, self.mktemp()), "pyproject.toml")
self.assertRaises(FileNotFoundError, _load_pyproject_toml, path)
def test_brokenToml(self):
"""
Syntactially invalid TOML produces an exception. The specific
exception varies by the underlying TOML library.
"""
toml = '[project]\nname = "abc' # Truncated string.
self.assertRaises(Exception, self._loadToml, toml)
def test_nameMissing(self):
"""
`ValueError` is raised when we can't extract the project name.
"""
for toml in [
"\n",
"[tool.notincremental]\n",
"[project]\n",
"[tool.incremental]\n",
"[project]\n[tool.incremental]\n",
]:
self.assertRaises(ValueError, self._loadToml, toml)
def test_nameInvalidOptIn(self):
"""
`TypeError` is raised when the project name isn't a string.
"""
for toml in [
"[project]\nname = false\n",
"[tool.incremental]\nname = -1\n",
"[tool.incremental]\n[project]\nname = 1.0\n",
]:
with self.assertRaisesRegex(TypeError, "The project name must be a string"):
self._loadToml(toml)
def test_toolIncrementalInvalid(self):
"""
`ValueError` is raised when the ``[tool]`` or ``[tool.incremental]``
isn't a table.
"""
for toml in [
"tool = false\n",
"[tool]\nincremental = false\n",
"[tool]\nincremental = 123\n",
"[tool]\nincremental = null\n",
]:
self.assertRaises(ValueError, self._loadToml, toml)
def test_toolIncrementalUnexpecteKeys(self):
"""
Raise `ValueError` when the ``[tool.incremental]`` section contains
keys other than ``"name"``
"""
for toml in [
"[tool.incremental]\nfoo = false\n",
'[tool.incremental]\nname = "OK"\nother = false\n',
]:
self.assertRaises(ValueError, self._loadToml, toml)
def test_setuptoolsOptIn(self):
"""
The package has opted-in to Incremental version management when
the ``[tool.incremental]`` section is a dict. The project name
is taken from ``[tool.incremental] name`` or ``[project] name``.
"""
root = Path(self.mktemp())
pkg = root / "src" / "foo"
pkg.mkdir(parents=True)
for toml in [
'[project]\nname = "Foo"\n[tool.incremental]\n',
'[tool.incremental]\nname = "Foo"\n',
]:
config = self._loadToml(toml, path=root / "pyproject.toml")
self.assertEqual(
config,
_IncrementalConfig(
opt_in=True,
package="Foo",
path=str(pkg),
),
)
def test_packagePathRequired(self):
"""
Raise `ValueError` when the package root can't be resolved.
"""
root = Path(self.mktemp())
root.mkdir() # Contains no package directory.
with self.assertRaisesRegex(ValueError, "Can't find the directory of project "):
self._loadToml(
'[project]\nname = "foo"\n',
path=root / "pyproject.toml",
)
def test_noToolIncrementalSection(self):
"""
The ``[tool.incremental]`` table is not strictly required, but its
``opt_in=False`` indicates its absence.
"""
root = Path(self.mktemp())
pkg = root / "src" / "foo"
pkg.mkdir(parents=True)
config = self._loadToml(
'[project]\nname = "Foo"\n',
path=root / "pyproject.toml",
)
self.assertEqual(
config,
_IncrementalConfig(
opt_in=False,
package="Foo",
path=str(pkg),
),
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,562 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{incremental}.
"""
from __future__ import division, absolute_import
import sys
import unittest
import operator
from incremental import getVersionString, IncomparableVersions
from incremental import Version, _inf
from twisted.trial.unittest import TestCase
class VersionsTests(TestCase):
def test_localIsShort(self):
"""
The local version is the same as the short version.
"""
va = Version("dummy", 1, 0, 0, release_candidate=1, post=2, dev=3)
self.assertEqual(va.local(), va.short())
def test_versionComparison(self):
"""
Versions can be compared for equality and order.
"""
va = Version("dummy", 1, 0, 0)
vb = Version("dummy", 0, 1, 0)
self.assertTrue(va > vb)
self.assertTrue(vb < va)
self.assertTrue(va >= vb)
self.assertTrue(vb <= va)
self.assertTrue(va != vb)
self.assertTrue(vb == Version("dummy", 0, 1, 0))
self.assertTrue(vb == vb)
@unittest.skipIf(sys.version_info < (3,), "Comparisons do not raise on py2")
def test_versionComparisonNonVersion(self):
"""
Versions can be compared with non-versions.
"""
v = Version("dummy", 1, 0, 0)
o = object()
with self.assertRaises(TypeError):
v > o
with self.assertRaises(TypeError):
v < o
with self.assertRaises(TypeError):
v >= o
with self.assertRaises(TypeError):
v <= o
self.assertFalse(v == o)
self.assertTrue(v != o)
def test_versionComparisonCaseInsensitive(self):
"""
Version package names are case insensitive.
"""
va = Version("dummy", 1, 0, 0)
vb = Version("DuMmY", 0, 1, 0)
self.assertTrue(va > vb)
self.assertTrue(vb < va)
self.assertTrue(va >= vb)
self.assertTrue(vb <= va)
self.assertTrue(va != vb)
self.assertTrue(vb == Version("dummy", 0, 1, 0))
self.assertTrue(vb == vb)
def test_comparingNEXTReleases(self):
"""
NEXT releases are always larger than numbered releases.
"""
va = Version("whatever", "NEXT", 0, 0)
vb = Version("whatever", 1, 0, 0)
self.assertTrue(va > vb)
self.assertFalse(va < vb)
self.assertNotEquals(vb, va)
def test_NEXTMustBeAlone(self):
"""
NEXT releases must always have the rest of the numbers set to 0.
"""
with self.assertRaises(ValueError):
Version("whatever", "NEXT", 1, 0, release_candidate=0, post=0, dev=0)
with self.assertRaises(ValueError):
Version("whatever", "NEXT", 0, 1, release_candidate=0, post=0, dev=0)
with self.assertRaises(ValueError):
Version("whatever", "NEXT", 0, 0, release_candidate=1, post=0, dev=0)
with self.assertRaises(ValueError):
Version("whatever", "NEXT", 0, 0, release_candidate=0, post=1, dev=0)
with self.assertRaises(ValueError):
Version("whatever", "NEXT", 0, 0, release_candidate=0, post=0, dev=1)
def test_comparingNEXTReleasesEqual(self):
"""
NEXT releases are equal to each other.
"""
va = Version("whatever", "NEXT", 0, 0)
vb = Version("whatever", "NEXT", 0, 0)
self.assertEquals(vb, va)
def test_comparingPrereleasesWithReleases(self):
"""
Prereleases are always less than versions without prereleases.
"""
va = Version("whatever", 1, 0, 0, prerelease=1)
vb = Version("whatever", 1, 0, 0)
self.assertTrue(va < vb)
self.assertFalse(va > vb)
self.assertNotEquals(vb, va)
def test_prereleaseDeprecated(self):
"""
Passing 'prerelease' to Version is deprecated.
"""
Version("whatever", 1, 0, 0, prerelease=1)
warnings = self.flushWarnings([self.test_prereleaseDeprecated])
self.assertEqual(len(warnings), 1)
self.assertEqual(
warnings[0]["message"],
"Passing prerelease to incremental.Version was deprecated in "
"Incremental 16.9.0. Please pass release_candidate instead.",
)
def test_prereleaseAttributeDeprecated(self):
"""
Accessing 'prerelease' on a Version is deprecated.
"""
va = Version("whatever", 1, 0, 0, release_candidate=1)
va.prerelease
warnings = self.flushWarnings([self.test_prereleaseAttributeDeprecated])
self.assertEqual(len(warnings), 1)
self.assertEqual(
warnings[0]["message"],
"Accessing incremental.Version.prerelease was deprecated in "
"Incremental 16.9.0. Use Version.release_candidate instead.",
)
def test_comparingReleaseCandidatesWithReleases(self):
"""
Release Candidates are always less than versions without release
candidates.
"""
va = Version("whatever", 1, 0, 0, release_candidate=1)
vb = Version("whatever", 1, 0, 0)
self.assertTrue(va < vb)
self.assertFalse(va > vb)
self.assertNotEquals(vb, va)
def test_comparingPostReleasesWithReleases(self):
"""
Post releases are always greater than versions without post
releases.
"""
va = Version("whatever", 1, 0, 0, post=1)
vb = Version("whatever", 1, 0, 0)
self.assertTrue(va > vb)
self.assertFalse(va < vb)
self.assertNotEquals(vb, va)
def test_[AWS-SECRET-REMOVED]ases(self):
"""
Dev releases are always greater than postreleases based on previous
releases.
"""
va = Version("whatever", 1, 0, 1, dev=1)
vb = Version("whatever", 1, 0, 0, post=1)
self.assertTrue(va > vb)
self.assertFalse(va < vb)
self.assertNotEquals(vb, va)
def test_comparingDevReleasesWithReleases(self):
"""
Dev releases are always less than versions without dev releases.
"""
va = Version("whatever", 1, 0, 0, dev=1)
vb = Version("whatever", 1, 0, 0)
self.assertTrue(va < vb)
self.assertFalse(va > vb)
self.assertNotEquals(vb, va)
def test_rcEqualspre(self):
"""
Release Candidates are equal to prereleases.
"""
va = Version("whatever", 1, 0, 0, release_candidate=1)
vb = Version("whatever", 1, 0, 0, prerelease=1)
self.assertTrue(va == vb)
self.assertFalse(va != vb)
def test_rcOrpreButNotBoth(self):
"""
Release Candidate and prerelease can't both be given.
"""
with self.assertRaises(ValueError):
Version("whatever", 1, 0, 0, prerelease=1, release_candidate=1)
def test_comparingReleaseCandidates(self):
"""
The value specified as the release candidate is used in version
comparisons.
"""
va = Version("whatever", 1, 0, 0, release_candidate=1)
vb = Version("whatever", 1, 0, 0, release_candidate=2)
self.assertTrue(va < vb)
self.assertTrue(vb > va)
self.assertTrue(va <= vb)
self.assertTrue(vb >= va)
self.assertTrue(va != vb)
self.assertTrue(vb == Version("whatever", 1, 0, 0, release_candidate=2))
self.assertTrue(va == va)
def test_comparingPost(self):
"""
The value specified as the postrelease is used in version comparisons.
"""
va = Version("whatever", 1, 0, 0, post=1)
vb = Version("whatever", 1, 0, 0, post=2)
self.assertTrue(va < vb)
self.assertTrue(vb > va)
self.assertTrue(va <= vb)
self.assertTrue(vb >= va)
self.assertTrue(va != vb)
self.assertTrue(vb == Version("whatever", 1, 0, 0, post=2))
self.assertTrue(va == va)
def test_comparingDev(self):
"""
The value specified as the dev release is used in version comparisons.
"""
va = Version("whatever", 1, 0, 0, dev=1)
vb = Version("whatever", 1, 0, 0, dev=2)
self.assertTrue(va < vb)
self.assertTrue(vb > va)
self.assertTrue(va <= vb)
self.assertTrue(vb >= va)
self.assertTrue(va != vb)
self.assertTrue(vb == Version("whatever", 1, 0, 0, dev=2))
self.assertTrue(va == va)
def test_comparingDevAndRC(self):
"""
The value specified as the dev release and release candidate is used in
version comparisons.
"""
va = Version("whatever", 1, 0, 0, release_candidate=1, dev=1)
vb = Version("whatever", 1, 0, 0, release_candidate=1, dev=2)
self.assertTrue(va < vb)
self.assertTrue(vb > va)
self.assertTrue(va <= vb)
self.assertTrue(vb >= va)
self.assertTrue(va != vb)
self.assertTrue(vb == Version("whatever", 1, 0, 0, release_candidate=1, dev=2))
self.assertTrue(va == va)
def test_comparingDevAndRCDifferent(self):
"""
The value specified as the dev release and release candidate is used in
version comparisons.
"""
va = Version("whatever", 1, 0, 0, release_candidate=1, dev=1)
vb = Version("whatever", 1, 0, 0, release_candidate=2, dev=1)
self.assertTrue(va < vb)
self.assertTrue(vb > va)
self.assertTrue(va <= vb)
self.assertTrue(vb >= va)
self.assertTrue(va != vb)
self.assertTrue(vb == Version("whatever", 1, 0, 0, release_candidate=2, dev=1))
self.assertTrue(va == va)
def test_infComparisonSelf(self):
"""
L{_inf} is equal to L{_inf}.
This is a regression test.
"""
self.assertEqual(_inf, _inf)
self.assertFalse(_inf < _inf)
self.assertFalse(_inf > _inf)
self.assertTrue(_inf >= _inf)
self.assertTrue(_inf <= _inf)
self.assertFalse(_inf != _inf)
def test_infComparison(self):
"""
L{_inf} is greater than any other object.
"""
o = object()
self.assertTrue(_inf > o)
self.assertFalse(_inf < o)
self.assertTrue(_inf >= o)
self.assertFalse(_inf <= o)
self.assertTrue(_inf != o)
self.assertFalse(_inf == o)
def test_disallowBuggyComparisons(self):
"""
The package names of the Version objects need to be the same.
"""
self.assertRaises(
IncomparableVersions,
operator.eq,
Version("dummy", 1, 0, 0),
Version("dumym", 1, 0, 0),
)
def test_notImplementedComparisons(self):
"""
Comparing a L{Version} to some other object type results in
C{NotImplemented}.
"""
va = Version("dummy", 1, 0, 0)
vb = ("dummy", 1, 0, 0) # a tuple is not a Version object
self.assertEqual(va.__cmp__(vb), NotImplemented)
def test_repr(self):
"""
Calling C{repr} on a version returns a human-readable string
representation of the version.
"""
self.assertEqual(repr(Version("dummy", 1, 2, 3)), "Version('dummy', 1, 2, 3)")
def test_reprWithPrerelease(self):
"""
Calling C{repr} on a version with a prerelease returns a human-readable
string representation of the version including the prerelease as a
release candidate..
"""
self.assertEqual(
repr(Version("dummy", 1, 2, 3, prerelease=4)),
"Version('dummy', 1, 2, 3, release_candidate=4)",
)
def test_reprWithReleaseCandidate(self):
"""
Calling C{repr} on a version with a release candidate returns a
human-readable string representation of the version including the rc.
"""
self.assertEqual(
repr(Version("dummy", 1, 2, 3, release_candidate=4)),
"Version('dummy', 1, 2, 3, release_candidate=4)",
)
def test_reprWithPost(self):
"""
Calling C{repr} on a version with a postrelease returns a
human-readable string representation of the version including the
postrelease.
"""
self.assertEqual(
repr(Version("dummy", 1, 2, 3, post=4)), "Version('dummy', 1, 2, 3, post=4)"
)
def test_reprWithDev(self):
"""
Calling C{repr} on a version with a dev release returns a
human-readable string representation of the version including the dev
release.
"""
self.assertEqual(
repr(Version("dummy", 1, 2, 3, dev=4)), "Version('dummy', 1, 2, 3, dev=4)"
)
def test_str(self):
"""
Calling C{str} on a version returns a human-readable string
representation of the version.
"""
self.assertEqual(str(Version("dummy", 1, 2, 3)), "[dummy, version 1.2.3]")
def test_strWithPrerelease(self):
"""
Calling C{str} on a version with a prerelease includes the prerelease
as a release candidate.
"""
self.assertEqual(
str(Version("dummy", 1, 0, 0, prerelease=1)), "[dummy, version 1.0.0rc1]"
)
def test_strWithReleaseCandidate(self):
"""
Calling C{str} on a version with a release candidate includes the
release candidate.
"""
self.assertEqual(
str(Version("dummy", 1, 0, 0, release_candidate=1)),
"[dummy, version 1.0.0rc1]",
)
def test_strWithPost(self):
"""
Calling C{str} on a version with a postrelease includes the
postrelease.
"""
self.assertEqual(
str(Version("dummy", 1, 0, 0, post=1)), "[dummy, version 1.0.0.post1]"
)
def test_strWithDevAndReleaseCandidate(self):
"""
Calling C{str} on a version with a release candidate and dev release
includes the release candidate and the dev release.
"""
self.assertEqual(
str(Version("dummy", 1, 0, 0, release_candidate=1, dev=2)),
"[dummy, version 1.0.0rc1.dev2]",
)
def test_strWithDev(self):
"""
Calling C{str} on a version with a dev release includes the dev
release.
"""
self.assertEqual(
str(Version("dummy", 1, 0, 0, dev=1)), "[dummy, version 1.0.0.dev1]"
)
def test_strWithDevAndPost(self):
"""
Calling C{str} on a version with a postrelease and dev release
includes the postrelease and the dev release.
"""
self.assertEqual(
str(Version("dummy", 1, 0, 0, post=1, dev=2)),
"[dummy, version 1.0.0.post1.dev2]",
)
def testShort(self):
self.assertEqual(Version("dummy", 1, 2, 3).short(), "1.2.3")
def test_getVersionString(self):
"""
L{getVersionString} returns a string with the package name and the
short version number.
"""
self.assertEqual("Twisted 8.0.0", getVersionString(Version("Twisted", 8, 0, 0)))
def test_getVersionStringWithPrerelease(self):
"""
L{getVersionString} includes the prerelease as a release candidate, if
any.
"""
self.assertEqual(
getVersionString(Version("whatever", 8, 0, 0, prerelease=1)),
"whatever 8.0.0rc1",
)
def test_getVersionStringWithReleaseCandidate(self):
"""
L{getVersionString} includes the release candidate, if any.
"""
self.assertEqual(
getVersionString(Version("whatever", 8, 0, 0, release_candidate=1)),
"whatever 8.0.0rc1",
)
def test_getVersionStringWithPost(self):
"""
L{getVersionString} includes the postrelease, if any.
"""
self.assertEqual(
getVersionString(Version("whatever", 8, 0, 0, post=1)),
"whatever 8.0.0.post1",
)
def test_getVersionStringWithDev(self):
"""
L{getVersionString} includes the dev release, if any.
"""
self.assertEqual(
getVersionString(Version("whatever", 8, 0, 0, dev=1)), "whatever 8.0.0.dev1"
)
def test_getVersionStringWithDevAndRC(self):
"""
L{getVersionString} includes the dev release and release candidate, if
any.
"""
self.assertEqual(
getVersionString(Version("whatever", 8, 0, 0, release_candidate=2, dev=1)),
"whatever 8.0.0rc2.dev1",
)
def test_getVersionStringWithDevAndPost(self):
"""
L{getVersionString} includes the dev release and postrelease, if
any.
"""
self.assertEqual(
getVersionString(Version("whatever", 8, 0, 0, post=2, dev=1)),
"whatever 8.0.0.post2.dev1",
)
def test_baseWithNEXT(self):
"""
The C{base} method returns just "NEXT" when NEXT is the major version.
"""
self.assertEqual(Version("foo", "NEXT", 0, 0).base(), "NEXT")
def test_base(self):
"""
The C{base} method returns a very simple representation of the version.
"""
self.assertEqual(Version("foo", 1, 0, 0).base(), "1.0.0")
def test_baseWithPrerelease(self):
"""
The base version includes 'rcX' for versions with prereleases.
"""
self.assertEqual(Version("foo", 1, 0, 0, prerelease=8).base(), "1.0.0rc8")
def test_baseWithPost(self):
"""
The base version includes 'postX' for versions with postreleases.
"""
self.assertEqual(Version("foo", 1, 0, 0, post=8).base(), "1.0.0.post8")
def test_baseWithDev(self):
"""
The base version includes 'devX' for versions with dev releases.
"""
self.assertEqual(Version("foo", 1, 0, 0, dev=8).base(), "1.0.0.dev8")
def test_baseWithReleaseCandidate(self):
"""
The base version includes 'rcX' for versions with prereleases.
"""
self.assertEqual(
Version("foo", 1, 0, 0, release_candidate=8).base(), "1.0.0rc8"
)
def test_baseWithDevAndRC(self):
"""
The base version includes 'rcXdevX' for versions with dev releases and
a release candidate.
"""
self.assertEqual(
Version("foo", 1, 0, 0, release_candidate=2, dev=8).base(), "1.0.0rc2.dev8"
)
def test_baseWithDevAndPost(self):
"""
The base version includes 'postXdevX' for versions with dev releases
and a postrelease.
"""
self.assertEqual(
Version("foo", 1, 0, 0, post=2, dev=8).base(), "1.0.0.post2.dev8"
)

View File

@@ -0,0 +1,257 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
from __future__ import absolute_import, division, print_function
import click
import os
import datetime
from typing import Dict, Optional, Callable
from incremental import Version, _findPath, _existing_version
_VERSIONPY_TEMPLATE = '''"""
Provides {package} version information.
"""
# This file is auto-generated! Do not edit!
# Use `python -m incremental.update {package}` to change this file.
from incremental import Version
__version__ = {version_repr}
__all__ = ["__version__"]
'''
_YEAR_START = 2000
def _run(
package, # type: str
path, # type: Optional[str]
newversion, # type: Optional[str]
patch, # type: bool
rc, # type: bool
post, # type: bool
dev, # type: bool
create, # type: bool
_date=None, # type: Optional[datetime.date]
_getcwd=None, # type: Optional[Callable[[], str]]
_print=print, # type: Callable[[object], object]
): # type: (...) -> None
if not _getcwd:
_getcwd = os.getcwd
if not _date:
_date = datetime.date.today()
if not path:
path = _findPath(_getcwd(), package)
if (
newversion
and patch
or newversion
and dev
or newversion
and rc
or newversion
and post
):
raise ValueError("Only give --newversion")
if dev and patch or dev and rc or dev and post:
raise ValueError("Only give --dev")
if (
create
and dev
or create
and patch
or create
and rc
or create
and post
or create
and newversion
):
raise ValueError("Only give --create")
versionpath = os.path.join(path, "_version.py")
if newversion:
from pkg_resources import parse_version
existing = _existing_version(versionpath)
st_version = parse_version(newversion)._version # type: ignore[attr-defined]
release = list(st_version.release)
minor = 0
micro = 0
if len(release) == 1:
(major,) = release
elif len(release) == 2:
major, minor = release
else:
major, minor, micro = release
v = Version(
package,
major,
minor,
micro,
release_candidate=st_version.pre[1] if st_version.pre else None,
post=st_version.post[1] if st_version.post else None,
dev=st_version.dev[1] if st_version.dev else None,
)
elif create:
v = Version(package, _date.year - _YEAR_START, _date.month, 0)
existing = v
elif rc and not patch:
existing = _existing_version(versionpath)
if existing.release_candidate:
v = Version(
package,
existing.major,
existing.minor,
existing.micro,
existing.release_candidate + 1,
)
else:
v = Version(package, _date.year - _YEAR_START, _date.month, 0, 1)
elif patch:
existing = _existing_version(versionpath)
v = Version(
package,
existing.major,
existing.minor,
existing.micro + 1,
1 if rc else None,
)
elif post:
existing = _existing_version(versionpath)
if existing.post is None:
_post = 0
else:
_post = existing.post + 1
v = Version(package, existing.major, existing.minor, existing.micro, post=_post)
elif dev:
existing = _existing_version(versionpath)
if existing.dev is None:
_dev = 0
else:
_dev = existing.dev + 1
v = Version(
package,
existing.major,
existing.minor,
existing.micro,
existing.release_candidate,
dev=_dev,
)
else:
existing = _existing_version(versionpath)
if existing.release_candidate:
v = Version(package, existing.major, existing.minor, existing.micro)
else:
raise ValueError("You need to issue a rc before updating the major/minor")
NEXT_repr = repr(Version(package, "NEXT", 0, 0)).split("#")[0].replace("'", '"')
NEXT_repr_bytes = NEXT_repr.encode("utf8")
version_repr = repr(v).split("#")[0].replace("'", '"')
version_repr_bytes = version_repr.encode("utf8")
existing_version_repr = repr(existing).split("#")[0].replace("'", '"')
existing_version_repr_bytes = existing_version_repr.encode("utf8")
_print("Updating codebase to %s" % (v.public()))
for dirpath, dirnames, filenames in os.walk(path):
for filename in filenames:
filepath = os.path.join(dirpath, filename)
with open(filepath, "rb") as f:
original_content = f.read()
content = original_content
# Replace previous release_candidate calls to the new one
if existing.release_candidate:
content = content.replace(
existing_version_repr_bytes, version_repr_bytes
)
content = content.replace(
(package.encode("utf8") + b" " + existing.public().encode("utf8")),
(package.encode("utf8") + b" " + v.public().encode("utf8")),
)
# Replace NEXT Version calls with the new one
content = content.replace(NEXT_repr_bytes, version_repr_bytes)
content = content.replace(
NEXT_repr_bytes.replace(b"'", b'"'), version_repr_bytes
)
# Replace <package> NEXT with <package> <public>
content = content.replace(
package.encode("utf8") + b" NEXT",
(package.encode("utf8") + b" " + v.public().encode("utf8")),
)
if content != original_content:
_print("Updating %s" % (filepath,))
with open(filepath, "wb") as f:
f.write(content)
_print("Updating %s" % (versionpath,))
with open(versionpath, "wb") as f:
f.write(
(
_VERSIONPY_TEMPLATE.format(package=package, version_repr=version_repr)
).encode("utf8")
)
@click.command()
@click.argument("package")
@click.option("--path", default=None)
@click.option("--newversion", default=None)
@click.option("--patch", is_flag=True)
@click.option("--rc", is_flag=True)
@click.option("--post", is_flag=True)
@click.option("--dev", is_flag=True)
@click.option("--create", is_flag=True)
def run(
package, # type: str
path, # type: Optional[str]
newversion, # type: Optional[str]
patch, # type: bool
rc, # type: bool
post, # type: bool
dev, # type: bool
create, # type: bool
): # type: (...) -> None
return _run(
package=package,
path=path,
newversion=newversion,
patch=patch,
rc=rc,
post=post,
dev=dev,
create=create,
)
if __name__ == "__main__": # pragma: no cover
run()