pypa/pip · error · MissingCallableSuffix

Invalid script entry point

Error message

Invalid script entry point: {entry_point} - A callable suffix is required. See https://packaging.python.org/specifications/entry-points/#use-for-scripts for more information.

What it means

Raised as MissingCallableSuffix (subclass of InstallationError) by _raise_for_invalid_entrypoint when a wheel's console_scripts entry point specification lacks the ':callable' suffix. Entry points used for executable scripts must name a callable object ('module:func'); a bare 'module' is invalid and pip refuses to generate the script.

Solutions

  1. Fix the entry point to include the callable: 'name = package.module:func'.
  2. Rebuild the wheel with the corrected entry_points.
  3. Inspect the wheel's '*.dist-info/entry_points.txt' to confirm the ':func' suffix.

Example fix

# before
[console_scripts]
mycli = mypackage.cli

# after
[console_scripts]
mycli = mypackage.cli:main
Defensive patterns

Strategy: validation

Validate before calling

from pip._internal.operations.install.wheel import _raise_for_invalid_entrypoint

def entry_point_ok(spec: str, scripts_dir: str) -> bool:
    try:
        _raise_for_invalid_entrypoint(spec, scripts_dir)
        return True
    except Exception:
        return False

# assert entry_point_ok("pkg.mod:main", "/tmp/scripts")
# assert not entry_point_ok("pkg.mod", "/tmp/scripts")

Prevention

When it happens

Trigger: Installing a wheel whose entry_points.txt (or pyproject/setup.cfg console_scripts) has an entry without ':', e.g. 'mycli = mypackage.cli' instead of 'mycli = mypackage.cli:main'. distlib's get_export_entry returns an entry whose .suffix is None.

Common situations: Hand-edited entry_points; packaging tools/old setuptools emitting malformed entry points; tutorial copy-paste that omitted the ':func' part.

Related errors


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

Appendix: source

Thrown at src/pip/_internal/operations/install/wheel.py:409


class MissingCallableSuffix(InstallationError):
    def __init__(self, entry_point: str) -> None:
        super().__init__(
            f"Invalid script entry point: {entry_point} - A callable "
            "suffix is required. See https://packaging.python.org/"
            "specifications/entry-points/#use-for-scripts for more "
            "information."
        )


def _raise_for_invalid_entrypoint(specification: str, scripts_dir: str) -> None:
    entry = get_export_entry(specification)
    if entry is None:
        return

    if entry.suffix is None:
        raise MissingCallableSuffix(str(entry))

    # distlib joins the entry point name onto the scripts directory, so a name
    # with path separators or ``..`` components can resolve elsewhere. The script
    # must resolve to a path strictly inside the scripts directory.
    dest = os.path.join(scripts_dir, entry.name)
    resolves_to_scripts_dir = os.path.abspath(dest) == os.path.abspath(scripts_dir)
    if resolves_to_scripts_dir or not is_within_directory(scripts_dir, dest):
        raise InstallationError(
            f"Invalid script entry point name {entry.name!r}: the script "
            f"would be installed outside the scripts directory ({scripts_dir})."
        )


class PipScriptMaker(ScriptMaker):
    # Override distlib's default script template with one that
    # doesn't import `re` module, allowing scripts to load faster.
    script_template = textwrap.dedent("""\
        import sys

View on GitHub (pinned to f399c37189)