tiangolo/fastapi · error · ValueError

Invalid version: {version!r}. Expected format: X.Y.Z

Error message

Invalid version: {version!r}. Expected format: X.Y.Z

What it means

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.

Source

Thrown at scripts/prepare_release.py:29

VERSION_HEADING_PATTERN = re.compile(r"(?m)^## (\d+\.\d+\.\d+)(?: \([^)]+\))?$")
RELEASE_NOTES_HEADER = """---
hide:
  - navigation
---

# Release Notes

"""
LATEST_CHANGES_HEADER = "## Latest Changes"
BumpType = Literal["major", "minor", "patch"]

app = typer.Typer()


def parse_version(version: str) -> tuple[int, int, int]:
    match = re.fullmatch(r"\d+\.\d+\.\d+", version)
    if not match:
        raise ValueError(f"Invalid version: {version!r}. Expected format: X.Y.Z")
    major, minor, patch = version.split(".")
    return int(major), int(minor), int(patch)


def get_current_version(content: str, version_file: Path) -> str:
    matches = list(VERSION_PATTERN.finditer(content))
    if len(matches) != 1:
        raise RuntimeError(
            f"Expected exactly one __version__ assignment in {version_file}, "
            f"found {len(matches)}"
        )
    return matches[0].group(1)


def bump_version(version: str, bump: BumpType) -> str:
    major, minor, patch = parse_version(version)
    if bump == "major":
        return f"{major + 1}.0.0"

View on GitHub (pinned to 3e8d1526d8)

Solutions

  1. Normalize the version to exactly three dot-separated integers, e.g. '1.2.3'.
  2. Strip a leading 'v' and any pre-release suffix before parsing.
  3. Update __version__ in the version file to plain X.Y.Z before running prepare.

Example fix

# before
__version__ = "v1.2.3-rc1"
# after
__version__ = "1.2.3"
Defensive patterns

Strategy: validation

Validate before calling

import re

def is_valid_version(v: str) -> bool:
    return re.fullmatch(r"\d+\.\d+\.\d+", v) is not None

Type guard

import re

def is_semver(v: str) -> bool:
    return isinstance(v, str) and re.fullmatch(r"\d+\.\d+\.\d+", v) is not None

Try / catch

try:
    major, minor, patch = parse_version(version)
except ValueError as e:
    raise SystemExit(f"Rejecting malformed version: {e}") from e

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of tiangolo/fastapi@3e8d1526d8 (2026-08-11). Data as JSON: /api/errors/1b4a1ffbd2d00e2a. Report an issue: GitHub.