{"record":{"id":"1b4a1ffbd2d00e2a","repo":"tiangolo/fastapi","slug":"invalid-version-version-r-expected-format-x-y","errorCode":null,"errorMessage":"Invalid version: {version!r}. Expected format: X.Y.Z","messagePattern":"Invalid version: (.+?)\\. Expected format: X\\.Y\\.Z","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"scripts/prepare_release.py","lineNumber":29,"sourceCode":"VERSION_HEADING_PATTERN = re.compile(r\"(?m)^## (\\d+\\.\\d+\\.\\d+)(?: \\([^)]+\\))?$\")\nRELEASE_NOTES_HEADER = \"\"\"---\nhide:\n  - navigation\n---\n\n# Release Notes\n\n\"\"\"\nLATEST_CHANGES_HEADER = \"## Latest Changes\"\nBumpType = Literal[\"major\", \"minor\", \"patch\"]\n\napp = typer.Typer()\n\n\ndef parse_version(version: str) -> tuple[int, int, int]:\n    match = re.fullmatch(r\"\\d+\\.\\d+\\.\\d+\", version)\n    if not match:\n        raise ValueError(f\"Invalid version: {version!r}. Expected format: X.Y.Z\")\n    major, minor, patch = version.split(\".\")\n    return int(major), int(minor), int(patch)\n\n\ndef get_current_version(content: str, version_file: Path) -> str:\n    matches = list(VERSION_PATTERN.finditer(content))\n    if len(matches) != 1:\n        raise RuntimeError(\n            f\"Expected exactly one __version__ assignment in {version_file}, \"\n            f\"found {len(matches)}\"\n        )\n    return matches[0].group(1)\n\n\ndef bump_version(version: str, bump: BumpType) -> str:\n    major, minor, patch = parse_version(version)\n    if bump == \"major\":\n        return f\"{major + 1}.0.0\"","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/tiangolo/fastapi/blob/3e8d1526d83a90aaf7d6eb6dc682bf150f180b25/scripts/prepare_release.py#L11-L47","documentation":"Raised by parse_version() in scripts/prepare_release.py:29 when the version string does not fully match the regex \\d+\\.\\d+\\.\\d+ (re.fullmatch at scripts/prepare_release.py:27). Every version handled by the release tool — bump, current, target — must be strict semantic X.Y.Z with no leading 'v', no pre-release suffix, and no extra components.","triggerScenarios":"Calling prepare with a malformed version string passed into parse_version, or a version_file whose __version__ assignment contains a non-conforming value (e.g. '1.2', 'v1.2.3', '1.2.3.0', '1.2.3-rc1'). bump_version() also routes through parse_version.","commonSituations":"Reading a __version__ that includes a pre-release tag. Manual CLI argument with a leading 'v'. A version file that uses a 4-segment calver scheme.","solutions":["Normalize the version to exactly three dot-separated integers, e.g. '1.2.3'.","Strip a leading 'v' and any pre-release suffix before parsing.","Update __version__ in the version file to plain X.Y.Z before running prepare."],"exampleFix":"# before\n__version__ = \"v1.2.3-rc1\"\n# after\n__version__ = \"1.2.3\"","handlingStrategy":"validation","validationCode":"import re\n\ndef is_valid_version(v: str) -> bool:\n    return re.fullmatch(r\"\\d+\\.\\d+\\.\\d+\", v) is not None","typeGuard":"import re\n\ndef is_semver(v: str) -> bool:\n    return isinstance(v, str) and re.fullmatch(r\"\\d+\\.\\d+\\.\\d+\", v) is not None","tryCatchPattern":"try:\n    major, minor, patch = parse_version(version)\nexcept ValueError as e:\n    raise SystemExit(f\"Rejecting malformed version: {e}\") from e","preventionTips":["Keep __version__ strictly X.Y.Z with no prefix or pre-release suffix.","Validate version strings at the CLI boundary before passing to parse_version.","Reject leading 'v' in inputs."],"tags":["release","validation","versioning"],"backgroundTag":null,"analyzedSha":"3e8d1526d83a90aaf7d6eb6dc682bf150f180b25","analyzedAt":"2026-08-11T02:34:52.986Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}