pypa/pip · error · ValueError

Entry points must be listed in groups

Error message

Entry points must be listed in groups

What it means

Raised by EntryPoint.parse_map when entry-point definition lines appear before any '[group]' section header. parse_map uses split_sections; if it yields a (None, non-empty-lines) tuple it means content exists with no group, which is meaningless for entry points and is rejected with ValueError.

Source

Thrown at src/pip/_vendor/pkg_resources/__init__.py:2848

    @classmethod
    def parse_map(
        cls,
        data: str | Iterable[str] | dict[str, str | Iterable[str]],
        dist: Distribution | None = None,
    ):
        """Parse a map of entry point groups"""
        _data: Iterable[tuple[str | None, str | Iterable[str]]]
        if isinstance(data, dict):
            _data = data.items()
        else:
            _data = split_sections(data)
        maps: dict[str, dict[str, Self]] = {}
        for group, lines in _data:
            if group is None:
                if not lines:
                    continue
                raise ValueError("Entry points must be listed in groups")
            group = group.strip()
            if group in maps:
                raise ValueError("Duplicate group name", group)
            maps[group] = cls.parse_group(group, lines, dist)
        return maps


def _version_from_file(lines):
    """
    Given an iterable of lines from a Metadata file, return
    the value of the Version field, if present, or None otherwise.
    """

    def is_version_line(line):
        return line.lower().startswith('version:')

    version_lines = filter(is_version_line, lines)
    line = next(iter(version_lines), '')

View on GitHub (pinned to d7d0d0a394)

Solutions

  1. Ensure every entry-point line in the data is preceded by a '[group_name]' section header.
  2. If building the map from a dict, pass {group: [lines]} so each key is a group name rather than passing bare lines.
  3. Re-emit the file via your build backend (setuptools/hatch) to get well-formed sections.

Example fix

# before (entry_points.txt)
foo = mod:fn
[console_scripts]
bar = mod:bar

# after
[console_scripts]
foo = mod:fn
bar = mod:bar
Defensive patterns

Strategy: validation

Validate before calling

def has_header_first(text: str) -> bool:
    for line in text.splitlines():
        s = line.strip()
        if not s or s.startswith('#'):
            continue
        return s.startswith('[') and s.endswith(']')
    return True
if has_header_first(data):
    EntryPoint.parse_map(data)

Type guard

def is_well_formed_entry_map(text: str) -> bool:
    # reject if any content line precedes a [section]
    seen_section = False
    for line in text.splitlines():
        s = line.strip()
        if s.startswith('['):
            seen_section = True
        elif s and '=' in s and not seen_section:
            return False
    return True

Try / catch

try:
    EntryPoint.parse_map(data)
except ValueError as e:
    if 'must be listed in groups' in str(e):
        data = '[console_scripts]\n' + data  # prepend header
    raise

Prevention

When it happens

Trigger: Calling EntryPoint.parse_map on a string whose first non-blank lines are entry definitions rather than a section header, e.g. 'foo = mod:fn\n[console_scripts]'.

Common situations: A truncated or hand-written entry_points.txt missing its initial section header, or code that joins entry-point lines without a leading '[group]' line.

Related errors


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