Source code for config.version

import dataclasses
import typing
from enum import Enum
from pathlib import Path
from typing import Self

[docs] class VersionType(Enum): """SemVer part types and their index in the version tuple. Attributes: MAJOR (VersionType): Major version index 0. MINOR (VersionType): Minor version index 1. PATCH (VersionType): Patch version index 2. """ MAJOR = 0 MINOR = 1 PATCH = 2
[docs] def __init__(self, part_index: int): """Store the index corresponding to this version part.""" self.part_index: int = part_index
[docs] class VersionTag(Enum): """Optional tags appended to version strings. Attributes: TEST (VersionTag): "test" tag. PROD (VersionTag): "prod" tag. """ TEST = "test" PROD = "prod"
SemverParts = tuple[int, int, int]
[docs] @dataclasses.dataclass(frozen=True) class Version: """Semantic version representation with optional tag. Attributes: version_parts (tuple[int, int, int]): (major, minor, patch). version_tag (VersionTag | None): Optional tag to append. """ VERSION_SPLIT = "." VERSION_FILE_SPLIT = "_" VERSION_FILE_PREFIX = "v" VERSION_LIMIT = 100 VERSION_PARTS = 3 version_parts: SemverParts version_tag: VersionTag | None = None
[docs] @staticmethod def from_string(version_string: str) -> "Version": """Parse a dotted version string into a Version, padding missing parts with zero.""" split = version_string.split(Version.VERSION_SPLIT) split += [0] * (Version.VERSION_PARTS - len(split)) return Version(typing.cast(SemverParts, tuple(map(int, split))))
[docs] def with_tag(self, version_tag: VersionTag) -> "Version": """Return a new Version with the given tag.""" return dataclasses.replace(self, version_tag=version_tag)
[docs] def as_file_version(self) -> str: """Generate the file-friendly version string, e.g. '1_2_3-test'.""" version_string = self.VERSION_FILE_SPLIT.join(map(str, self.version_parts)) if self.version_tag is None: return version_string return f"{version_string}-{self.version_tag.value}"
[docs] def is_to_high(self) -> bool: """Check if any version part exceeds the limit.""" return any(part >= self.VERSION_LIMIT for part in self.version_parts)
[docs] def next_version(self, version_type: VersionType) -> "Version": """Increment the specified part and return a new Version.""" parts = list(self.version_parts) parts[version_type.part_index] += 1 return Version(typing.cast(SemverParts, tuple(parts)))
def __str__(self) -> str: """Return the dotted version string, e.g. '1.2.3'.""" return self.VERSION_SPLIT.join(map(str, self.version_parts))
[docs] @dataclasses.dataclass(frozen=True) class ReleaseInformation: """Holds metadata for a release and computes output file paths. Attributes: version (Version): Semantic version. output_dir (Path): Directory to write the file. file_prefix (str): Prefix for the output filename. file_extension (str): Extension for the output filename. release_type (VersionType): Which part to bump next. """ version: Version output_dir: Path file_prefix: str = "ticket-dataset-" file_extension: str = ".json" release_type: VersionType = VersionType.PATCH
[docs] def get_output_file(self) -> Path: """Compose the full output file Path for this release.""" name = f"{self.file_prefix}{self.version.as_file_version()}{self.file_extension}" return self.output_dir / name
[docs] def exists(self) -> bool: """Check if the output file already exists.""" return self.get_output_file().exists()
[docs] def get_next_existing(self) -> Self: """Recursively find the next non-existing release, raising if limit exceeded.""" if self.version.is_to_high(): raise ValueError("Could not find a new version") if not self.exists(): return self return self.next_version.get_next_existing()
@property def next_version(self) -> Self: """Return a new ReleaseInformation with its version bumped.""" return dataclasses.replace( self, version=self.version.next_version(self.release_type) )