pypa/pip · error · InstallationError
Directory is not installable. Neither 'setup.py' nor…
Error message
Directory {name!r} is not installable. Neither 'setup.py' nor 'pyproject.toml' found. What it means
pip was given a local directory path to install, but the directory does not contain either 'setup.py' or 'pyproject.toml'. The function _get_url_from_path() checks is_installable_dir() and, when it fails, reports that the directory is not a valid installable Python project.
Solutions
- Verify the target directory contains setup.py or pyproject.toml: 'ls ./some-dir/setup.py ./some-dir/pyproject.toml'
- Navigate to or point at the correct project root directory that has the build files
- Add a minimal pyproject.toml to the directory if it is meant to be installable
- Check for typos in the path and ensure relative paths are resolved from the correct working directory
Example fix
# before pip install ./src # after pip install ./src/mypackage # the dir that actually has pyproject.toml
Defensive patterns
Strategy: validation
Validate before calling
import os
def is_installable(path: str) -> bool:
if not os.path.isdir(path):
return False
return os.path.exists(os.path.join(path, "setup.py")) or os.path.exists(
os.path.join(path, "pyproject.toml")
)
if not is_installable("./some-dir"):
print("Directory is not installable; add setup.py or pyproject.toml") Type guard
def is_installable_dir(path: str) -> bool:
"""Narrowing guard: True if path is a dir with setup.py or pyproject.toml."""
import os
return (
os.path.isdir(path)
and (
os.path.isfile(os.path.join(path, "setup.py"))
or os.path.isfile(os.path.join(path, "pyproject.toml"))
)
) Try / catch
from pip._internal.exceptions import InstallationError
from pip._internal.utils.misc import is_installable_dir
if not is_installable_dir(path):
raise SystemExit(f"{path} is not an installable directory") Prevention
- Always verify the target directory contains setup.py or pyproject.toml before running pip install
- Use os.path checks in CI scripts before invoking pip install on a directory
- In monorepos, use explicit subdirectory paths pointing at the package root
When it happens
Trigger: Running 'pip install ./some-dir/' where some-dir exists as a directory but has no setup.py or pyproject.toml. Also triggered when a requirements file points at a directory path that is a subfolder of the actual project root.
Common situations: Pointing at the wrong subdirectory in a monorepo (e.g. ./src instead of ./src/mypackage). Checking out only part of a repo. A directory that contains source code but was never set up as a package. CI running from the wrong working directory.
Related errors
- does not appear to be a Python project: neither 'setup.py'…
- Can't verify hashes for these file:// requirements because…
- [dependency-groups] resolution failed for
- [dependency-groups] table was missing from
- Editable requirements are not allowed as constraints
AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08).
Data as JSON: /api/errors/8ea6b09fd2b67472.
Report an issue: GitHub.
Appendix: source
Thrown at src/pip/_internal/req/constructors.py:315
return True
return False
def _get_url_from_path(path: str, name: str) -> str | None:
"""
First, it checks whether a provided path is an installable directory. If it
is, returns the path.
If false, check if the path is an archive file (such as a .whl).
The function checks if the path is a file. If false, if the path has
an @, it will treat it as a PEP 440 URL requirement and return the path.
"""
if _looks_like_path(name) and os.path.isdir(path):
if is_installable_dir(path):
return path_to_url(path)
# TODO: The is_installable_dir test here might not be necessary
# now that it is done in load_pyproject_toml too.
raise InstallationError(
f"Directory {name!r} is not installable. Neither 'setup.py' "
"nor 'pyproject.toml' found."
)
if not is_archive_file(path):
return None
if os.path.isfile(path):
return path_to_url(path)
urlreq_parts = name.split("@", 1)
if len(urlreq_parts) >= 2 and not _looks_like_path(urlreq_parts[0]):
# If the path contains '@' and the part before it does not look
# like a path, try to treat it as a PEP 440 URL req instead.
return None
logger.warning(
"Requirement %r looks like a filename, but the file does not exist",
name,
)
return path_to_url(path)
View on GitHub (pinned to f399c37189)