/
niceSOFT
/
python3-setuptools_scm
Обзор
Документация
Войти
/
niceSOFT
/
python3-setuptools_scm
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
vcs-versioning/src/vcs_versioning/_get_version_impl.py
356 строк
12 KB
pre-commit-ci[bot]
style: pre-commit fixes
03 авг 2026, 21:57
03 авг 2026, 21:57
8a32949
Код
Авторство
О чём код?
from __future__ import annotations import dataclasses import logging import re import warnings from pathlib import Path from re import Pattern from typing import TYPE_CHECKING, Any, NoReturn from . import _config from . import _types as _t from ._config import Configuration, TagConfiguration from ._environment import resolve_runtime_env if TYPE_CHECKING: from ._config import ParseFunction from ._environment import VcsEnvironment from ._version_cls import Version as _VersionType # Backward-compat re-export used by vcs-versioning/setup.py from ._legacy_parse import ( resolved_fallback_root as resolved_fallback_root, ) from ._overrides import _apply_metadata_overrides, _read_pretended_version_for from ._scm_version import ScmVersion from ._version_cls import _validate_version_cls from ._version_schemes import format_version as _format_version EMPTY_TAG_REGEX_DEPRECATION = DeprecationWarning( "empty regex for tag regex is invalid, using default" ) log = logging.getLogger(__name__) def parse_version(config: Configuration) -> ScmVersion | None: """Backward-compat shim for setuptools-scm <=10.0.x. Those releases import ``parse_version`` from this module. The function was inlined during the 10.1 / vcs-versioning 2.0 refactor, but we keep the name importable so that older setuptools-scm pins still work with newer vcs-versioning releases. """ scm_version = _resolve_version(config) return _apply_metadata_overrides(scm_version, config) def _finalize( scm_version: ScmVersion, config: Configuration, *, force_write: bool, ) -> str: """Apply metadata overrides, format, and optionally write version files.""" applied = _apply_metadata_overrides(scm_version, config) assert applied is not None version_string = _format_version(applied) if force_write: write_version_files(config, version=version_string, scm_version=applied) return version_string def _resolve_version(config: Configuration) -> ScmVersion | None: """Run the version pipeline: pretend -> discovery -> legacy EPs. Discovery handles ``config.parse`` via ``LegacyParseWorkdir`` (deprecated). Returns the raw ``ScmVersion`` (before metadata overrides are applied) or ``None`` when no version could be determined. """ from ._worktree_discovery import discover_workdir pretended = _read_pretended_version_for(config) if pretended is not None: return pretended workdir = discover_workdir(config) if workdir is not None: scm_version = workdir.get_scm_version() if scm_version is not None: return scm_version from ._legacy_parse import ( has_legacy_parse_eps, parse_fallback_version, parse_scm_version, ) if has_legacy_parse_eps(): scm_version = parse_scm_version(config) or parse_fallback_version(config) if scm_version is not None: return scm_version return None def _warn_if_tracked(target: Path, root: Path, config: Configuration) -> bool: """Warn when *target* is tracked in version control (#468). Writing a version file that is tracked makes ``git describe --dirty`` report a dirty tree on tag checkouts, causing wrong version numbers. Returns False if target resolves outside root (caller should skip the write). """ from ._backends._git import GitWorkdir from ._backends._hg import HgWorkdir if not target.is_absolute(): target = root / target resolved_target = target.resolve() resolved_root = root.resolve() try: resolved_target.relative_to(resolved_root) except ValueError: # todo: emit as GitHub Actions warning via ::warning:: syntax warnings.warn( f"version file target {target} resolves outside of the project root {root}", stacklevel=2, ) return False for workdir_cls in (GitWorkdir, HgWorkdir): try: wd = workdir_cls.from_potential_worktree(root, config) except Exception: continue if wd is not None and wd.is_file_tracked(target): warnings.warn( f"version file {target.relative_to(root)} is tracked by" " version control. This will cause dirty-state version bumps" " when the file is rewritten during builds." " Remove it from version control and add it to your VCS ignore file." " See https://github.com/pypa/setuptools-scm/issues/468", stacklevel=3, ) return True return True def write_version_files( config: Configuration, version: str, scm_version: ScmVersion ) -> None: root = Path(config.absolute_root) if config.write_to is not None: from ._dump_version import dump_version write_to = Path(config.write_to) target = root / write_to if not write_to.is_absolute() else write_to if not _warn_if_tracked(target, root, config): return dump_version( root=config.root, version=version, scm_version=scm_version, write_to=config.write_to, template=config.write_to_template, ) if config.version_file: from ._dump_version import write_version_to_path version_file = Path(config.version_file) assert not version_file.is_absolute(), f"{version_file=}" # todo: use a better name than fallback root assert config.relative_to is not None target = Path(config.relative_to).parent.joinpath(version_file) if not _warn_if_tracked(target, root, config): return write_version_to_path( target, template=config.version_file_template, version=version, scm_version=scm_version, ) def _get_version( config: Configuration, force_write_version_files: bool | None = None ) -> str | None: scm_version = _resolve_version(config) if scm_version is None: return None if force_write_version_files is None: force_write_version_files = True warnings.warn( "force_write_version_files ought to be set," " presuming the legacy True value", DeprecationWarning, stacklevel=2, ) return _finalize(scm_version, config, force_write=force_write_version_files) def _find_scm_in_parents(config: Configuration) -> Path | None: """Search parent directories for SCM repositories when relative_to is not set.""" if config.search_parent_directories: return None from ._backends._scm_workdir import ScmWorkdir from ._worktree_discovery import discover_workdir searching_config = dataclasses.replace(config, search_parent_directories=True) result = discover_workdir(searching_config) if result is not None and isinstance(result, ScmWorkdir): return result.path return None def _version_missing( config: Configuration, *, tool: str = "SETUPTOOLS_SCM" ) -> NoReturn: base_error = ( f"setuptools-scm was unable to detect version for {config.absolute_root}.\n\n" ) # If relative_to is not set, check for SCM repositories in parent directories scm_parent = None if config.relative_to is None: scm_parent = _find_scm_in_parents(config) if scm_parent is not None: if tool == "SETUPTOOLS_SCM": api_example = "setuptools_scm.get_version(relative_to=__file__)" tool_section = "[tool.setuptools_scm]" else: api_example = "vcs_versioning.get_version(relative_to=__file__)" tool_section = "[tool.vcs-versioning]" error_msg = ( base_error + f"However, a repository was found in a parent directory: {scm_parent}\n\n" f"To fix this, you have a few options:\n\n" f"1. Use the 'relative_to' parameter to specify the file as reference:\n" f" {api_example}\n\n" f"2. Enable parent directory search in your configuration:\n" f" {tool_section}\n" f" search_parent_directories = true\n\n" f"3. Change your working directory to the repository root: {scm_parent}\n\n" f"4. Set the root explicitly in your configuration:\n" f" {tool_section}\n" f' root = "{scm_parent}"\n\n' "For more information, see: https://setuptools-scm.readthedocs.io/en/latest/config/" ) else: error_msg = ( base_error + "Make sure you're either building from a fully intact git repository " "or PyPI tarballs. Most other sources (such as GitHub's tarballs, a " "git checkout without the .git folder) don't contain the necessary " "metadata and will not work.\n\n" "For example, if you're using pip, instead of " "https://github.com/user/proj/archive/master.zip " "use git+https://github.com/user/proj.git#egg=proj\n\n" "Alternatively, set the version with the environment variable " "SETUPTOOLS_SCM_PRETEND_VERSION_FOR_${NORMALIZED_DIST_NAME} as described " "in https://setuptools-scm.readthedocs.io/en/latest/config/" ) raise LookupError(error_msg) def get_version( root: _t.PathT = ".", version_scheme: _t.VERSION_SCHEME = _config.DEFAULT_VERSION_SCHEME, local_scheme: _t.VERSION_SCHEME = _config.DEFAULT_LOCAL_SCHEME, write_to: _t.PathT | None = None, write_to_template: str | None = None, version_file: _t.PathT | None = None, version_file_template: str | None = None, relative_to: _t.PathT | None = None, tag_regex: str | Pattern[str] = _config.DEFAULT_TAG_REGEX, parentdir_prefix_version: str | None = None, fallback_version: str | None = None, fallback_root: _t.PathT = ".", parse: ParseFunction | None = None, git_describe_command: _t.CMD_TYPE | None = None, dist_name: str | None = None, version_cls: type[_VersionType] | str | None = None, normalize: bool = True, search_parent_directories: bool = False, scm: dict[str, Any] | None = None, _env: VcsEnvironment | None = None, ) -> str: """ If supplied, relative_to should be a file from which root may be resolved. Typically called by a script or module that is not in the root of the repository to direct setuptools-scm to the root of the repository by supplying ``__file__``. """ version_cls = _validate_version_cls(version_cls, normalize) del normalize if tag_regex is _config.DEFAULT_TAG_REGEX: tag_config = TagConfiguration() else: warnings.warn( "get_version() parameter 'tag_regex' is deprecated. " "Use 'tag.regex' in pyproject.toml or pass a TagConfiguration " "via Configuration(tag=...) instead.", DeprecationWarning, stacklevel=2, ) tag_config = TagConfiguration(regex=parse_tag_regex(tag_regex)) scm_config = _config.ScmConfiguration.from_data(data=scm) if _env is None: _env = resolve_runtime_env() config = _config.Configuration( root=root, version_scheme=version_scheme, local_scheme=local_scheme, write_to=write_to, write_to_template=write_to_template, version_file=version_file, version_file_template=version_file_template, relative_to=relative_to, parentdir_prefix_version=parentdir_prefix_version, fallback_version=fallback_version, fallback_root=fallback_root, parse=parse, git_describe_command=git_describe_command, dist_name=dist_name, version_cls=version_cls, search_parent_directories=search_parent_directories, scm=scm_config, tag=tag_config, _env=_env, ) maybe_version = _get_version(config, force_write_version_files=True) if maybe_version is None: _version_missing(config) return maybe_version def parse_tag_regex(tag_regex: str | Pattern[str]) -> Pattern[str]: """Pre-validate and convert tag_regex to Pattern before Configuration. This ensures get_version() emits the deprecation warning for empty strings before Configuration.__post_init__ runs. """ if isinstance(tag_regex, str): if tag_regex == "": warnings.warn(EMPTY_TAG_REGEX_DEPRECATION, stacklevel=3) return _config.DEFAULT_TAG_REGEX else: return re.compile(tag_regex) else: return tag_regex