Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/11440.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Handle pyproject.toml in "install -r".
4 changes: 2 additions & 2 deletions src/pip/_internal/cli/req_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
install_req_from_parsed_requirement,
install_req_from_req_string,
)
from pip._internal.req.dependencies_file import parse_dependencies, parse_requirements
from pip._internal.req.req_dependency_group import parse_dependency_groups
from pip._internal.req.req_file import parse_requirements
from pip._internal.req.req_install import InstallRequirement
from pip._internal.resolution.base import BaseResolver
from pip._internal.utils.temp_dir import (
Expand Down Expand Up @@ -269,7 +269,7 @@ def get_requirements(

# NOTE: options.require_hashes may be set if --require-hashes is True
for filename in options.requirements:
for parsed_req in parse_requirements(
for parsed_req in parse_dependencies(
filename, finder=finder, options=options, session=session
):
req_to_add = install_req_from_parsed_requirement(
Expand Down
31 changes: 31 additions & 0 deletions src/pip/_internal/req/dependencies_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""
Common actions for dealing with dependencies file,
either requirements.txt or pyproject.toml
"""

from __future__ import annotations

import optparse
import pathlib
from collections.abc import Generator

from pip._internal.index.package_finder import PackageFinder
from pip._internal.network.session import PipSession
from pip._internal.req.pyproject_file import parse_pyproject_requirements
from pip._internal.req.req_file import ParsedRequirement, parse_requirements


def parse_dependencies(
filename: str,
session: PipSession,
finder: PackageFinder | None = None,
options: optparse.Values | None = None,
) -> Generator[ParsedRequirement, None, None]:
if pathlib.PurePath(filename).name == "pyproject.toml":
return parse_pyproject_requirements(
filename, finder=finder, options=options, session=session
)
else:
return parse_requirements(
filename, finder=finder, options=options, session=session
)
42 changes: 42 additions & 0 deletions src/pip/_internal/req/pyproject_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from __future__ import annotations

import optparse
from collections.abc import Generator

from pip._internal.exceptions import InstallationError
from pip._internal.index.package_finder import PackageFinder
from pip._internal.network.session import PipSession
from pip._internal.req.req_file import ParsedRequirement
from pip._internal.utils.compat import tomllib


def parse_pyproject_requirements(
filename: str,
session: PipSession,
finder: PackageFinder | None = None,
options: optparse.Values | None = None,
) -> Generator[ParsedRequirement, None, None]:
try:
with open(filename, "rb") as f:
pyproject = tomllib.load(f)
except OSError as exc:
raise InstallationError(f"Could not open requirements file: {exc}")

project = pyproject.get("project", {})
dynamic = project.get("dynamic", [])

if "dependencies" in dynamic:
raise InstallationError(
"Installing dynamic dependencies is not supported "
"(dynamic dependencies in {filename})"
)

for dependency_line in project.get("dependencies", []):
yield ParsedRequirement(
requirement=dependency_line,
is_editable=False,
comes_from=f"-r {filename}",
constraint=False,
options={}, # TODO
line_source=filename,
)