pypa/pip · error · InvalidPyProjectBuildRequires

invalid-pyproject-build-system-requires

invalid-pyproject-build-system-requires

Error message

Can not process {package}

What it means

Raised as InvalidPyProjectBuildRequires when [build-system].requires exists in pyproject.toml but is not a list of strings (validated by _is_list_of_str at pyproject.py:18). PEP 518 requires `requires` to be an array of PEP 508 requirement strings. A non-list value (a single string, a number, a dict) or a list containing non-string elements is rejected.

Source

Thrown at src/pip/_internal/pyproject.py:90

        # traditional direct setup.py execution, and require wheel and
        # a version of setuptools that supports that backend.

        build_system = {
            "requires": ["setuptools>=40.8.0"],
            "build-backend": "setuptools.build_meta:__legacy__",
        }

    # Ensure that the build-system section in pyproject.toml conforms
    # to PEP 518.

    # Specifying the build-system table but not the requires key is invalid
    if "requires" not in build_system:
        raise MissingPyProjectBuildRequires(package=req_name)

    # Error out if requires is not a list of strings
    requires = build_system["requires"]
    if not _is_list_of_str(requires):
        raise InvalidPyProjectBuildRequires(
            package=req_name,
            reason="It is not a list of strings.",
        )

    # Each requirement must be valid as per PEP 508
    for requirement in requires:
        try:
            get_requirement(requirement)
        except InvalidRequirement as error:
            raise InvalidPyProjectBuildRequires(
                package=req_name,
                reason=f"It contains an invalid requirement: {requirement!r}",
            ) from error

    backend = build_system.get("build-backend")
    backend_path = build_system.get("backend-path", [])
    check: list[str] = []
    if backend is None:

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Change `requires` to be a TOML array of strings: wrap the value in `[ ... ]` and quote each entry.
  2. Ensure every element is a quoted PEP 508 specifier string, e.g. `requires = ["setuptools>=61.0", "wheel"]`.
  3. Re-run pip install; the build-system table now passes _is_list_of_str.

Example fix

# before
[build-system]
requires = "setuptools"
# after
[build-system]
requires = ["setuptools>=61.0"]
Defensive patterns

Strategy: validation

Validate before calling

import tomllib

def validate_requires_is_str_list(path: str = "pyproject.toml") -> None:
    with open(path, "rb") as f:
        data = tomllib.load(f)
    requires = data.get("build-system", {}).get("requires")
    if not (isinstance(requires, list) and all(isinstance(r, str) for r in requires)):
        raise TypeError("[build-system].requires must be a list of strings")

validate_requires_is_str_list()

Type guard

def is_list_of_str(obj) -> bool:
    return isinstance(obj, list) and all(isinstance(i, str) for i in obj)

Try / catch

null

Prevention

When it happens

Trigger: Setting `requires = "setuptools"` (a bare string instead of a list). Setting `requires = [42]` or `requires = [{name="setuptools"}]`. The check at pyproject.py:89 fails because either isinstance(obj, list) is False or any element is not a str.

Common situations: Authors unfamiliar with TOML/PEP 518 writing a scalar instead of an array. Misconfigured tooling that emits a malformed requires. Editing pyproject.toml by hand and dropping the square brackets.

Related errors


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