Source code for semantic_release.version.declaration

from __future__ import annotations

# TODO: Remove v10
from abc import ABC, abstractmethod
from logging import getLogger
from pathlib import Path
from typing import TYPE_CHECKING

from deprecated.sphinx import deprecated

from semantic_release.version.declarations.enum import VersionStampType
from semantic_release.version.declarations.i_version_replacer import IVersionReplacer
from semantic_release.version.declarations.pattern import PatternVersionDeclaration
from semantic_release.version.declarations.toml import TomlVersionDeclaration

if TYPE_CHECKING:  # pragma: no cover
    from semantic_release.version.version import Version


# Globals
__all__ = [
    "IVersionReplacer",
    "VersionStampType",
    "PatternVersionDeclaration",
    "TomlVersionDeclaration",
    "VersionDeclarationABC",
]
log = getLogger(__name__)


[docs] @deprecated( version="9.20.0", reason=str.join( " ", [ "Refactored to composition paradigm using the new IVersionReplacer interface.", "This class will be removed in a future release", ], ), ) class VersionDeclarationABC(ABC): """ ABC for classes representing a location in which a version is declared somewhere within the source tree of the repository """ def __init__(self, path: Path | str, search_text: str) -> None: self.path = Path(path) if not self.path.exists(): raise FileNotFoundError(f"path {self.path.resolve()!r} does not exist") self.search_text = search_text self._content: str | None = None @property def content(self) -> str: """ The content of the source file in which the version is stored. This property is cached in the instance variable _content """ if self._content is None: log.debug( "No content stored, reading from source file %s", self.path.resolve() ) self._content = self.path.read_text() return self._content @content.deleter def content(self) -> None: log.debug("resetting instance-stored source file contents") self._content = None
[docs] @abstractmethod def parse(self) -> set[Version]: """ Return a set of the versions which can be parsed from the file. Because a source can match in multiple places, this method returns a set of matches. Generally, there should only be one element in this set (i.e. even if the version is specified in multiple places, it should be the same version in each place), but enforcing that condition is not mandatory or expected. """
[docs] @abstractmethod def replace(self, new_version: Version) -> str: """ Update the versions. This method reads the underlying file, replaces each occurrence of the matched pattern, then writes the updated file. :param new_version: The new version number as a `Version` instance """
[docs] def write(self, content: str) -> None: r""" Write new content back to the source path. Use alongside .replace(): >>> class MyVD(VersionDeclarationABC): ... def parse(self): ... ... def replace(self, new_version: Version): ... ... def write(self, content: str): ... >>> new_version = Version.parse("1.2.3") >>> vd = MyVD("path", r"__version__ = (?P<version>\d+\d+\d+)") >>> vd.write(vd.replace(new_version)) """ log.debug("writing content to %r", self.path.resolve()) self.path.write_text(content) self._content = None