pypa/pip · error · InvalidVersion

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

Error message

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

What it means

Raised by `_validate_dev(value)` when the `dev` argument to `Version.from_parts`/`__replace__` is not `None` and not a non-negative `int`. The dev-release number (`1.0.dev1`) is a single non-negative integer; strings, floats, lists, and negatives are rejected. The validator wraps it into `('dev', value)`.

Source

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

    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 d7d0d0a394)

Solutions

  1. Pass a non-negative `int`: `dev=1`.
  2. Coerce with `int(value)` and validate `>= 0` first.
  3. Use `Version('1.0.dev1')` for string input.
  4. Omit `dev` (`None`) if there is no dev-release segment.

Example fix

// before
Version.from_parts(release=(1, 0), dev='1')  # str -> raises

// after
Version.from_parts(release=(1, 0), dev=1)
# or
Version('1.0.dev1')
Defensive patterns

Strategy: type-guard

Validate before calling

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

Type guard

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

Try / catch

from pip._vendor.packaging.version import Version, InvalidVersion

try:
    v = Version.from_parts(release=release, dev=dev)
except InvalidVersion as e:
    if 'dev' in str(e):
        dev = int(dev)
        v = Version.from_parts(release=release, dev=dev)
    raise

Prevention

When it happens

Trigger: `Version.from_parts(release=(1,0), dev='0')`, `dev=-1`, `dev=0.0`, `dev=[1]`. Check is `isinstance(value, int) and value >= 0`.

Common situations: Dev number from a string config value; negative from computation; float from division.

Related errors


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