Ensure functions have docstrings for documentation
def parse_version(version: str) -> tuple[int, int, int]:
1"""Prepare a release by updating the package version and release notes."""23import re4from datetime import date5from pathlib import Path6from typing import Annotated, Literal78import typer910VERSION_PATTERN = re.compile(r'(?m)^__version__ = "(\d+\.\d+\.\d+)"$')11VERSION_HEADING_PATTERN = re.compile(r"(?m)^## (\d+\.\d+\.\d+)(?: \([^)]+\))?$")12RELEASE_NOTES_HEADER = """---13hide:14 - navigation15---1617# Release Notes1819"""20LATEST_CHANGES_HEADER = "## Latest Changes"21BumpType = Literal["major", "minor", "patch"]2223app = typer.Typer()242526def parse_version(version: str) -> tuple[int, int, int]:27 match = re.fullmatch(r"\d+\.\d+\.\d+", version)28 if not match:29 raise ValueError(f"Invalid version: {version!r}. Expected format: X.Y.Z")30 major, minor, patch = version.split(".")31 return int(major), int(minor), int(patch)323334def get_current_version(content: str, version_file: Path) -> str:35 matches = list(VERSION_PATTERN.finditer(content))36 if len(matches) != 1:37 raise RuntimeError(38 f"Expected exactly one __version__ assignment in {version_file}, "39 f"found {len(matches)}"40 )41 return matches[0].group(1)424344def bump_version(version: str, bump: BumpType) -> str:45 major, minor, patch = parse_version(version)46 if bump == "major":47 return f"{major + 1}.0.0"48 if bump == "minor":49 return f"{major}.{minor + 1}.0"50 return f"{major}.{minor}.{patch + 1}"515253def update_version_file(content: str, version: str, version_file: Path) -> str:54 current_version = get_current_version(content, version_file)55 if parse_version(version) <= parse_version(current_version):56 raise RuntimeError(57 f"New version {version} must be greater than current version {current_version}"58 )59 return VERSION_PATTERN.sub(f'__version__ = "{version}"', content, count=1)606162def update_release_notes(63 content: str, version: str, release_date: date, release_notes_file: Path64) -> str:65 if not content.startswith(RELEASE_NOTES_HEADER):66 raise RuntimeError(67 f"{release_notes_file} must start with {RELEASE_NOTES_HEADER!r}"68 )69 if re.search(rf"^## {re.escape(version)}(?: \([^)]+\))?$", content, re.M):70 raise RuntimeError(f"Release notes already contain a section for {version}")7172 latest_header = f"{RELEASE_NOTES_HEADER}{LATEST_CHANGES_HEADER}\n"73 if not content.startswith(latest_header):74 raise RuntimeError(f"{release_notes_file} must start with {latest_header!r}")7576 release_header = f"## {version} ({release_date.isoformat()})"77 return content.replace(78 latest_header,79 f"{RELEASE_NOTES_HEADER}{LATEST_CHANGES_HEADER}\n\n{release_header}\n",80 1,81 )828384def get_release_notes_body(content: str, version: str, release_notes_file: Path) -> str:85 version_heading = re.compile(rf"(?m)^## {re.escape(version)}(?: \([^)]+\))?$")86 match = version_heading.search(content)87 if not match:88 raise RuntimeError(89 f"Could not find release notes section for {version} in {release_notes_file}"90 )9192 next_match = VERSION_HEADING_PATTERN.search(content, match.end())93 end = next_match.start() if next_match else len(content)94 body = content[match.end() : end].strip()95 if not body:96 raise RuntimeError(97 f"Release notes section for {version} in {release_notes_file} is empty"98 )99 return f"{body}\n"100101102@app.command()103def prepare(104 bump: Annotated[105 BumpType,106 typer.Argument(107 envvar="PREPARE_RELEASE_BUMP",108 help="The release bump to make: major, minor, or patch.",109 ),110 ],111 version_file: Annotated[112 Path,113 typer.Option(114 envvar="PREPARE_RELEASE_VERSION_FILE",115 exists=True,116 file_okay=True,117 dir_okay=False,118 readable=True,119 writable=True,120 help="Path to the Python file containing the __version__ assignment.",121 ),122 ],123 release_notes_file: Annotated[124 Path,125 typer.Option(126 envvar="PREPARE_RELEASE_RELEASE_NOTES_FILE",127 exists=True,128 file_okay=True,129 dir_okay=False,130 readable=True,131 writable=True,132 help="Path to the release notes Markdown file.",133 ),134 ],135 release_date: Annotated[136 str,137 typer.Option(138 "--date",139 envvar="PREPARE_RELEASE_DATE",140 help="Release date in YYYY-MM-DD format. Defaults to today.",141 ),142 ] = date.today().isoformat(),143) -> None:144 parsed_release_date = date.fromisoformat(release_date or date.today().isoformat())145146 version_file_content = version_file.read_text()147 release_notes_content = release_notes_file.read_text()148 version = bump_version(149 get_current_version(version_file_content, version_file), bump150 )151152 version_file.write_text(153 update_version_file(version_file_content, version, version_file)154 )155 release_notes_file.write_text(156 update_release_notes(157 release_notes_content, version, parsed_release_date, release_notes_file158 )159 )160161 typer.echo(f"Prepared release {version} ({parsed_release_date.isoformat()})")162163164@app.command()165def current_version(166 version_file: Annotated[167 Path,168 typer.Option(169 envvar="PREPARE_RELEASE_VERSION_FILE",170 exists=True,171 file_okay=True,172 dir_okay=False,173 readable=True,174 help="Path to the Python file containing the __version__ assignment.",175 ),176 ],177) -> None:178 typer.echo(get_current_version(version_file.read_text(), version_file))179180181@app.command()182def release_notes(183 version_file: Annotated[184 Path,185 typer.Option(186 envvar="PREPARE_RELEASE_VERSION_FILE",187 exists=True,188 file_okay=True,189 dir_okay=False,190 readable=True,191 help="Path to the Python file containing the __version__ assignment.",192 ),193 ],194 release_notes_file: Annotated[195 Path,196 typer.Option(197 envvar="PREPARE_RELEASE_RELEASE_NOTES_FILE",198 exists=True,199 file_okay=True,200 dir_okay=False,201 readable=True,202 help="Path to the release notes Markdown file.",203 ),204 ],205) -> None:206 version = get_current_version(version_file.read_text(), version_file)207 typer.echo(208 get_release_notes_body(209 release_notes_file.read_text(), version, release_notes_file210 ),211 nl=False,212 )213214215if __name__ == "__main__":216 app()
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.
Generated by searchcode.com · powered by code_get_file + code_get_findings