School/.venv/lib/python3.9/site-packages/setuptools_scm/version.py
Kristofers Solo dba6dee19e Updated .venv
2021-11-22 17:35:39 +02:00

461 lines
14 KiB
Python

import datetime
import os
import re
import time
import warnings
from .config import Configuration
from .config import Version as PkgVersion
from .utils import iter_entry_points
from .utils import trace
SEMVER_MINOR = 2
SEMVER_PATCH = 3
SEMVER_LEN = 3
def _parse_version_tag(tag, config):
tagstring = tag if isinstance(tag, str) else str(tag)
match = config.tag_regex.match(tagstring)
result = None
if match:
if len(match.groups()) == 1:
key = 1
else:
key = "version"
result = {
"version": match.group(key),
"prefix": match.group(0)[: match.start(key)],
"suffix": match.group(0)[match.end(key) :],
}
trace(f"tag '{tag}' parsed to {result}")
return result
def callable_or_entrypoint(group, callable_or_name):
trace("ep", (group, callable_or_name))
if callable(callable_or_name):
return callable_or_name
for ep in iter_entry_points(group, callable_or_name):
trace("ep found:", ep.name)
return ep.load()
def tag_to_version(tag, config: "Configuration | None" = None):
"""
take a tag that might be prefixed with a keyword and return only the version part
:param config: optional configuration object
"""
trace("tag", tag)
if not config:
config = Configuration()
tagdict = _parse_version_tag(tag, config)
if not isinstance(tagdict, dict) or not tagdict.get("version", None):
warnings.warn(f"tag {tag!r} no version found")
return None
version = tagdict["version"]
trace("version pre parse", version)
if tagdict.get("suffix", ""):
warnings.warn(
"tag {!r} will be stripped of its suffix '{}'".format(
tag, tagdict["suffix"]
)
)
version = config.version_cls(version)
trace("version", repr(version))
return version
def tags_to_versions(tags, config=None):
"""
take tags that might be prefixed with a keyword and return only the version part
:param tags: an iterable of tags
:param config: optional configuration object
"""
result = []
for tag in tags:
tag = tag_to_version(tag, config=config)
if tag:
result.append(tag)
return result
class ScmVersion:
def __init__(
self,
tag_version,
distance=None,
node=None,
dirty=False,
preformatted=False,
branch=None,
config=None,
node_date=None,
**kw,
):
if kw:
trace("unknown args", kw)
self.tag = tag_version
if dirty and distance is None:
distance = 0
self.distance = distance
self.node = node
self.node_date = node_date
self.time = datetime.datetime.utcfromtimestamp(
int(os.environ.get("SOURCE_DATE_EPOCH", time.time()))
)
self._extra = kw
self.dirty = dirty
self.preformatted = preformatted
self.branch = branch
self.config = config
@property
def extra(self):
warnings.warn(
"ScmVersion.extra is deprecated and will be removed in future",
category=DeprecationWarning,
stacklevel=2,
)
return self._extra
@property
def exact(self):
return self.distance is None
def __repr__(self):
return self.format_with(
"<ScmVersion {tag} d={distance} n={node} d={dirty} b={branch}>"
)
def format_with(self, fmt, **kw):
return fmt.format(
time=self.time,
tag=self.tag,
distance=self.distance,
node=self.node,
dirty=self.dirty,
branch=self.branch,
node_date=self.node_date,
**kw,
)
def format_choice(self, clean_format, dirty_format, **kw):
return self.format_with(dirty_format if self.dirty else clean_format, **kw)
def format_next_version(self, guess_next, fmt="{guessed}.dev{distance}", **kw):
guessed = guess_next(self.tag, **kw)
return self.format_with(fmt, guessed=guessed)
def _parse_tag(tag, preformatted, config: "Configuration|None"):
if preformatted:
return tag
if config is None or not isinstance(tag, config.version_cls):
tag = tag_to_version(tag, config)
return tag
def meta(
tag,
distance: "int|None" = None,
dirty: bool = False,
node: "str|None" = None,
preformatted: bool = False,
branch: "str|None" = None,
config: "Configuration|None" = None,
**kw,
):
if not config:
warnings.warn(
"meta invoked without explicit configuration,"
" will use defaults where required."
)
parsed_version = _parse_tag(tag, preformatted, config)
trace("version", tag, "->", parsed_version)
assert parsed_version is not None, "Can't parse version %s" % tag
return ScmVersion(
parsed_version, distance, node, dirty, preformatted, branch, config, **kw
)
def guess_next_version(tag_version: ScmVersion):
version = _strip_local(str(tag_version))
return _bump_dev(version) or _bump_regex(version)
def _strip_local(version_string):
public, sep, local = version_string.partition("+")
return public
def _bump_dev(version):
if ".dev" not in version:
return
prefix, tail = version.rsplit(".dev", 1)
if tail != "0":
raise ValueError(
"choosing custom numbers for the `.devX` distance "
"is not supported.\n "
"The {version} can't be bumped\n"
"Please drop the tag or create a new supported one".format(version=version)
)
return prefix
def _bump_regex(version):
match = re.match(r"(.*?)(\d+)$", version)
if match is None:
raise ValueError(
"{version} does not end with a number to bump, "
"please correct or use a custom version scheme".format(version=version)
)
else:
prefix, tail = match.groups()
return "%s%d" % (prefix, int(tail) + 1)
def guess_next_dev_version(version):
if version.exact:
return version.format_with("{tag}")
else:
return version.format_next_version(guess_next_version)
def guess_next_simple_semver(version, retain, increment=True):
try:
parts = [int(i) for i in str(version).split(".")[:retain]]
except ValueError:
raise ValueError(f"{version} can't be parsed as numeric version")
while len(parts) < retain:
parts.append(0)
if increment:
parts[-1] += 1
while len(parts) < SEMVER_LEN:
parts.append(0)
return ".".join(str(i) for i in parts)
def simplified_semver_version(version):
if version.exact:
return guess_next_simple_semver(version.tag, retain=SEMVER_LEN, increment=False)
else:
if version.branch is not None and "feature" in version.branch:
return version.format_next_version(
guess_next_simple_semver, retain=SEMVER_MINOR
)
else:
return version.format_next_version(
guess_next_simple_semver, retain=SEMVER_PATCH
)
def release_branch_semver_version(version):
if version.exact:
return version.format_with("{tag}")
if version.branch is not None:
# Does the branch name (stripped of namespace) parse as a version?
branch_ver = _parse_version_tag(version.branch.split("/")[-1], version.config)
if branch_ver is not None:
branch_ver = branch_ver["version"]
if branch_ver[0] == "v":
# Allow branches that start with 'v', similar to Version.
branch_ver = branch_ver[1:]
# Does the branch version up to the minor part match the tag? If not it
# might be like, an issue number or something and not a version number, so
# we only want to use it if it matches.
tag_ver_up_to_minor = str(version.tag).split(".")[:SEMVER_MINOR]
branch_ver_up_to_minor = branch_ver.split(".")[:SEMVER_MINOR]
if branch_ver_up_to_minor == tag_ver_up_to_minor:
# We're in a release/maintenance branch, next is a patch/rc/beta bump:
return version.format_next_version(guess_next_version)
# We're in a development branch, next is a minor bump:
return version.format_next_version(guess_next_simple_semver, retain=SEMVER_MINOR)
def release_branch_semver(version):
warnings.warn(
"release_branch_semver is deprecated and will be removed in future. "
+ "Use release_branch_semver_version instead",
category=DeprecationWarning,
stacklevel=2,
)
return release_branch_semver_version(version)
def no_guess_dev_version(version):
if version.exact:
return version.format_with("{tag}")
else:
return version.format_with("{tag}.post1.dev{distance}")
def date_ver_match(ver):
match = re.match(
(
r"^(?P<date>(?P<year>\d{2}|\d{4})(?:\.\d{1,2}){2})"
r"(?:\.(?P<patch>\d*)){0,1}?$"
),
str(ver),
)
return match
def guess_next_date_ver(version, node_date=None, date_fmt=None, version_cls=None):
"""
same-day -> patch +1
other-day -> today
distance is always added as .devX
"""
match = date_ver_match(version)
if match is None:
warnings.warn(
f"{version} does not correspond to a valid versioning date, "
"assuming legacy version"
)
if date_fmt is None:
date_fmt = "%y.%m.%d"
# deduct date format if not provided
if date_fmt is None:
date_fmt = "%Y.%m.%d" if len(match.group("year")) == 4 else "%y.%m.%d"
head_date = node_date or datetime.date.today()
# compute patch
if match is None:
tag_date = datetime.date.today()
else:
tag_date = datetime.datetime.strptime(match.group("date"), date_fmt).date()
if tag_date == head_date:
patch = "0" if match is None else (match.group("patch") or "0")
patch = int(patch) + 1
else:
if tag_date > head_date and match is not None:
# warn on future times
warnings.warn(
"your previous tag ({}) is ahead your node date ({})".format(
tag_date, head_date
)
)
patch = 0
next_version = "{node_date:{date_fmt}}.{patch}".format(
node_date=head_date, date_fmt=date_fmt, patch=patch
)
# rely on the Version object to ensure consistency (e.g. remove leading 0s)
if version_cls is None:
version_cls = PkgVersion
next_version = str(version_cls(next_version))
return next_version
def calver_by_date(version):
if version.exact and not version.dirty:
return version.format_with("{tag}")
# TODO: move the release-X check to a new scheme
if version.branch is not None and version.branch.startswith("release-"):
branch_ver = _parse_version_tag(version.branch.split("-")[-1], version.config)
if branch_ver is not None:
ver = branch_ver["version"]
match = date_ver_match(ver)
if match:
return ver
return version.format_next_version(
guess_next_date_ver,
node_date=version.node_date,
version_cls=version.config.version_cls,
)
def _format_local_with_time(version, time_format):
if version.exact or version.node is None:
return version.format_choice(
"", "+d{time:{time_format}}", time_format=time_format
)
else:
return version.format_choice(
"+{node}", "+{node}.d{time:{time_format}}", time_format=time_format
)
def get_local_node_and_date(version):
return _format_local_with_time(version, time_format="%Y%m%d")
def get_local_node_and_timestamp(version, fmt="%Y%m%d%H%M%S"):
return _format_local_with_time(version, time_format=fmt)
def get_local_dirty_tag(version):
return version.format_choice("", "+dirty")
def get_no_local_node(_):
return ""
def postrelease_version(version):
if version.exact:
return version.format_with("{tag}")
else:
return version.format_with("{tag}.post{distance}")
def _get_ep(group, name):
for ep in iter_entry_points(group, name):
trace("ep found:", ep.name)
return ep.load()
def _iter_version_schemes(entrypoint, scheme_value, _memo=None):
if _memo is None:
_memo = set()
if isinstance(scheme_value, str):
scheme_value = _get_ep(entrypoint, scheme_value)
if isinstance(scheme_value, (list, tuple)):
for variant in scheme_value:
if variant not in _memo:
_memo.add(variant)
yield from _iter_version_schemes(entrypoint, variant, _memo=_memo)
elif callable(scheme_value):
yield scheme_value
def _call_version_scheme(version, entypoint, given_value, default):
for scheme in _iter_version_schemes(entypoint, given_value):
result = scheme(version)
if result is not None:
return result
return default
def format_version(version, **config):
trace("scm version", version)
trace("config", config)
if version.preformatted:
return version.tag
main_version = _call_version_scheme(
version, "setuptools_scm.version_scheme", config["version_scheme"], None
)
trace("version", main_version)
assert main_version is not None
local_version = _call_version_scheme(
version, "setuptools_scm.local_scheme", config["local_scheme"], "+unknown"
)
trace("local_version", local_version)
return main_version + local_version