From d49d80caa5b2b62292a980011fe91dd47284aa6b Mon Sep 17 00:00:00 2001 From: Radoslaw Olko Date: Thu, 9 Oct 2025 21:31:11 +0200 Subject: [PATCH 1/3] install requirements from -r --- src/pip/_internal/cli/req_command.py | 4 +-- src/pip/_internal/req/dependencies_file.py | 26 +++++++++++++++ src/pip/_internal/req/pyproject_file.py | 37 ++++++++++++++++++++++ 3 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 src/pip/_internal/req/dependencies_file.py create mode 100644 src/pip/_internal/req/pyproject_file.py diff --git a/src/pip/_internal/cli/req_command.py b/src/pip/_internal/cli/req_command.py index dc1328ff019..ad8be2fca5a 100644 --- a/src/pip/_internal/cli/req_command.py +++ b/src/pip/_internal/cli/req_command.py @@ -32,7 +32,7 @@ install_req_from_req_string, ) from pip._internal.req.req_dependency_group import parse_dependency_groups -from pip._internal.req.req_file import parse_requirements +from pip._internal.req.dependencies_file import parse_dependencies from pip._internal.req.req_install import InstallRequirement from pip._internal.resolution.base import BaseResolver from pip._internal.utils.temp_dir import ( @@ -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( diff --git a/src/pip/_internal/req/dependencies_file.py b/src/pip/_internal/req/dependencies_file.py new file mode 100644 index 00000000000..4e5582d5b19 --- /dev/null +++ b/src/pip/_internal/req/dependencies_file.py @@ -0,0 +1,26 @@ +"""Common actions for dealing with dependencies file, either requirements.txt or pyproject.toml""" + +from collections.abc import Generator +import pathlib +import optparse + +from pip._internal.req.req_file import parse_requirements, ParsedRequirement +from pip._internal.req.pyproject_file import parse_pyproject_requirements +from pip._internal.index.package_finder import PackageFinder +from pip._internal.network.session import PipSession + + +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 + ) diff --git a/src/pip/_internal/req/pyproject_file.py b/src/pip/_internal/req/pyproject_file.py new file mode 100644 index 00000000000..78e2440c4ee --- /dev/null +++ b/src/pip/_internal/req/pyproject_file.py @@ -0,0 +1,37 @@ +from collections.abc import Generator +import optparse + +from pip._internal.exceptions import InstallationError +from pip._internal.req.req_file import ParsedRequirement +from pip._internal.utils.compat import tomllib +from pip._internal.index.package_finder import PackageFinder +from pip._internal.network.session import PipSession + + +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(f"Installing dynamic dependencies is not supported (dynamic dependencies {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, + ) From a2430c18fadbde7bcdcc22bcea5d563633d5f91e Mon Sep 17 00:00:00 2001 From: Radoslaw Olko Date: Thu, 9 Oct 2025 21:43:51 +0200 Subject: [PATCH 2/3] format --- src/pip/_internal/cli/req_command.py | 2 +- src/pip/_internal/req/dependencies_file.py | 15 ++++++++++----- src/pip/_internal/req/pyproject_file.py | 15 ++++++++++----- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/pip/_internal/cli/req_command.py b/src/pip/_internal/cli/req_command.py index ad8be2fca5a..3d69dc67587 100644 --- a/src/pip/_internal/cli/req_command.py +++ b/src/pip/_internal/cli/req_command.py @@ -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.dependencies_file import parse_dependencies from pip._internal.req.req_install import InstallRequirement from pip._internal.resolution.base import BaseResolver from pip._internal.utils.temp_dir import ( diff --git a/src/pip/_internal/req/dependencies_file.py b/src/pip/_internal/req/dependencies_file.py index 4e5582d5b19..7dc38c411d8 100644 --- a/src/pip/_internal/req/dependencies_file.py +++ b/src/pip/_internal/req/dependencies_file.py @@ -1,13 +1,18 @@ -"""Common actions for dealing with dependencies file, either requirements.txt or pyproject.toml""" +""" +Common actions for dealing with dependencies file, +either requirements.txt or pyproject.toml +""" + +from __future__ import annotations -from collections.abc import Generator -import pathlib import optparse +import pathlib +from collections.abc import Generator -from pip._internal.req.req_file import parse_requirements, ParsedRequirement -from pip._internal.req.pyproject_file import parse_pyproject_requirements 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( diff --git a/src/pip/_internal/req/pyproject_file.py b/src/pip/_internal/req/pyproject_file.py index 78e2440c4ee..aa2f65d60c6 100644 --- a/src/pip/_internal/req/pyproject_file.py +++ b/src/pip/_internal/req/pyproject_file.py @@ -1,11 +1,13 @@ -from collections.abc import Generator +from __future__ import annotations + import optparse +from collections.abc import Generator from pip._internal.exceptions import InstallationError -from pip._internal.req.req_file import ParsedRequirement -from pip._internal.utils.compat import tomllib 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( @@ -24,7 +26,10 @@ def parse_pyproject_requirements( dynamic = project.get("dynamic", []) if "dependencies" in dynamic: - raise InstallationError(f"Installing dynamic dependencies is not supported (dynamic dependencies {filename})") + raise InstallationError( + "Installing dynamic dependencies is not supported " + "(dynamic dependencies in {filename})" + ) for dependency_line in project.get("dependencies", []): yield ParsedRequirement( @@ -32,6 +37,6 @@ def parse_pyproject_requirements( is_editable=False, comes_from=f"-r {filename}", constraint=False, - options={}, # TODO + options={}, # TODO line_source=filename, ) From 139bffc13f2c75410cfacc1100f7242a537f03b9 Mon Sep 17 00:00:00 2001 From: Radoslaw Olko Date: Thu, 9 Oct 2025 22:10:43 +0200 Subject: [PATCH 3/3] add news entry --- news/11440.feature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/11440.feature.rst diff --git a/news/11440.feature.rst b/news/11440.feature.rst new file mode 100644 index 00000000000..7ce3bdee3da --- /dev/null +++ b/news/11440.feature.rst @@ -0,0 +1 @@ +Handle pyproject.toml in "install -r".