pypa/pip · error · InvalidVersion

dev must be non-negative integer, got

Error message

dev must be non-negative integer, got {value}

What it means

Raised by _validate_dev() (version.py:312-318) when the dev-release value passed to Version.from_parts() or Version.__replace__() is not a non-negative integer. Passing None is allowed (means no dev-release). Strings, floats, and negative ints trigger this.

Solutions

  1. Pass dev as a non-negative int (0, 1, 2, ...).
  2. Convert with int(value) and validate >= 0 before calling.
  3. Pass None or omit the argument if there is no dev-release segment.

Example fix

// before
Version.from_parts(release=(1, 0), dev='3')
// after
Version.from_parts(release=(1, 0), dev=3)
Defensive patterns

Strategy: type-guard

Validate before calling

from packaging.version import Version, InvalidVersion

def safe_from_parts(*, release, dev=None, **kwargs):
    if dev is not None and (not isinstance(dev, int) or dev < 0):
        raise TypeError(f"dev must be a non-negative int, got {dev!r}")
    return Version.from_parts(release=release, dev=dev, **kwargs)

Type guard

def is_valid_dev(value) -> bool:
    return value is None or (isinstance(value, int) and value >= 0)

Try / catch

from packaging.version import InvalidVersion

try:
    v = Version.from_parts(release=(1,), dev=dev)
except InvalidVersion as e:
    if "dev must be" in str(e):
        logger.error("Invalid dev value: %s", dev)
    raise

Prevention

When it happens

Trigger: Calling Version.from_parts(release=(1,), dev=-1), Version.from_parts(release=(1,), dev=2.0), or Version.from_parts(release=(1,), dev='3').

Common situations: Passing a dev-release value as a string from a build environment variable, a negative number, or a float.

Related errors


AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08). Data as JSON: /api/errors/4f19f33d3799d328. Report an issue: GitHub.

Appendix: source

Thrown at src/pip/_vendor/packaging/version.py:318

    raise InvalidVersion(msg)


def _validate_post(value: object, /) -> tuple[Literal["post"], int] | None:
    if value is None:
        return value
    if isinstance(value, int) and value >= 0:
        return ("post", value)
    msg = f"post must be non-negative integer, got {value}"
    raise InvalidVersion(msg)


def _validate_dev(value: object, /) -> tuple[Literal["dev"], int] | None:
    if value is None:
        return value
    if isinstance(value, int) and value >= 0:
        return ("dev", value)
    msg = f"dev must be non-negative integer, got {value}"
    raise InvalidVersion(msg)


def _validate_local(value: object, /) -> LocalType | None:
    if value is None:
        return value
    if isinstance(value, str) and _LOCAL_PATTERN.fullmatch(value):
        return _parse_local_version(value)
    msg = f"local must be a valid version string, got {value!r}"
    raise InvalidVersion(msg)


# Backward compatibility for internals before 26.0. Do not use.
class _Version(NamedTuple):
    epoch: int
    release: tuple[int, ...]
    dev: tuple[Literal["dev"], int] | None
    pre: tuple[Literal["a", "b", "rc"], int] | None
    post: tuple[Literal["post"], int] | None

View on GitHub (pinned to f399c37189)