-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Support installing requirements from inline script metadata (PEP 723) #13052
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+259
−1
Merged
Changes from 35 commits
Commits
Show all changes
36 commits
Select commit
Hold shift + click to select a range
239f181
Add --script to install command
SnoopJ b2a8ff6
Add failing test for --script
SnoopJ 4a7eeb1
Default --script to None
SnoopJ 4aba9b8
Add minimum implementation of parsing requirements from inline metadata
SnoopJ 916e4c0
Issue an error if --script is given multiple times
SnoopJ 0697ec2
Add scripts() to download, wheel subcommands
SnoopJ 517c636
Test that --script can only be given once
SnoopJ 866113b
Remove TODO (I think the answer is 'no')
SnoopJ c88ffba
Add failing test for incompatible requires-python
SnoopJ 26868f6
Correct type annotation of PEP 723 helper
SnoopJ c7a0656
Remove PEP 723 requirements helper in favor of direct access
SnoopJ 2a2efb4
Check requires-python specified in script metadata
SnoopJ 9e52c31
Appease the linters
SnoopJ eb5cc10
Write return annotation correctly
SnoopJ 62702db
Add NEWS fragment
SnoopJ aa6c8a2
Change --script to --requirements-from-script
SnoopJ 5961c4e
Replace --script usage in dedicated PEP 723 tests, fix INITools namin…
SnoopJ 004f144
Add --requirements-from-scripts to lock command
SnoopJ 0958182
Add extra layer of backslash escaping to match test stderr on Windows
SnoopJ a49fcea
Remove obsolete TODO
SnoopJ 839494b
Change typing.Dict[] to dict[] to appease ruff-check
SnoopJ d534187
Adjust test requires-python to 'not this version'
SnoopJ 382f9b2
Simplify test of expected error message
SnoopJ 0bfe3b8
Merge branch 'main' into feature/gh12891-inline-metadata
SnoopJ 3cec1f5
Fix syntax error
SnoopJ 28f02da
Remove comment put in place for sake of review
SnoopJ 7a0d53e
Use 'name' in error
SnoopJ 33e711a
Use CommandError for PEP 723 parsing failures
SnoopJ 003a443
Avoid network use by tests, simplify assertions
SnoopJ 126b6ea
Refine requirements from script metadata
SnoopJ 69011ea
Remove unused import
SnoopJ 9cc1fec
Remove short option for PEP 723 requirements
SnoopJ f42ae07
Move PEP 723 utilities to req
SnoopJ f459c7a
Add tests for failure modes
SnoopJ e3e85d3
Fixup reST in NEWS entry
SnoopJ 8fb769e
Fix backtick syntax in news/12891.feature.rst
ichard26 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| Support installing dependencies declared with inline script metadata | ||
| (:pep:`723`) with `--requirements-from-script`. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
SnoopJ marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| import re | ||
| from typing import Any | ||
|
|
||
| from pip._vendor import tomli as tomllib | ||
|
|
||
| REGEX = r"(?m)^# /// (?P<type>[a-zA-Z0-9-]+)$\s(?P<content>(^#(| .*)$\s)+)^# ///$" | ||
|
|
||
|
|
||
| class PEP723Exception(ValueError): | ||
| """Raised to indicate a problem when parsing PEP 723 metadata from a script""" | ||
|
|
||
| def __init__(self, msg: str) -> None: | ||
| self.msg = msg | ||
|
|
||
|
|
||
| def pep723_metadata(scriptfile: str) -> dict[str, Any]: | ||
| with open(scriptfile) as f: | ||
| script = f.read() | ||
|
|
||
| name = "script" | ||
| matches = list( | ||
| filter(lambda m: m.group("type") == name, re.finditer(REGEX, script)) | ||
| ) | ||
|
|
||
| if len(matches) > 1: | ||
| raise PEP723Exception(f"Multiple {name!r} blocks found in {scriptfile!r}") | ||
| elif len(matches) == 1: | ||
| content = "".join( | ||
| line[2:] if line.startswith("# ") else line[1:] | ||
| for line in matches[0].group("content").splitlines(keepends=True) | ||
| ) | ||
| try: | ||
| metadata = tomllib.loads(content) | ||
| except Exception as exc: | ||
| raise PEP723Exception(f"Failed to parse TOML in {scriptfile!r}") from exc | ||
| else: | ||
| raise PEP723Exception( | ||
| f"File does not contain {name!r} metadata: {scriptfile!r}" | ||
| ) | ||
|
|
||
| return metadata |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,159 @@ | ||
| import sys | ||
| import textwrap | ||
|
|
||
| from tests.lib import PipTestEnvironment | ||
|
|
||
|
|
||
| def test_script_file(script: PipTestEnvironment) -> None: | ||
| """ | ||
| Test installing from a script with inline metadata (PEP 723). | ||
| """ | ||
|
|
||
| script_path = script.scratch_path.joinpath("script.py") | ||
| script_path.write_text( | ||
| textwrap.dedent( | ||
| """\ | ||
| # /// script | ||
| # dependencies = [ | ||
| # "INITools==0.2", | ||
| # "simple==1.0", | ||
| # ] | ||
| # /// | ||
| print("Hello world from a dummy program") | ||
| """ | ||
| ) | ||
| ) | ||
| script.pip_install_local("--requirements-from-script", script_path) | ||
| script.assert_installed(initools="0.2", simple="1.0") | ||
|
|
||
|
|
||
| def test_multiple_scripts(script: PipTestEnvironment) -> None: | ||
| """ | ||
| Test that --requirements-from-script can only be given once in an install command. | ||
| """ | ||
| result = script.pip( | ||
| "install", | ||
| "--requirements-from-script", | ||
| "does_not_exist.py", | ||
| "--requirements-from-script", | ||
| "also_does_not_exist.py", | ||
| allow_stderr_error=True, | ||
| expect_error=True, | ||
| ) | ||
|
|
||
| assert ( | ||
| "ERROR: --requirements-from-script can only be given once" in result.stderr | ||
| ), ("multiple script did not fail as expected -- " + result.stderr) | ||
|
|
||
|
|
||
| def test_script_file_python_version(script: PipTestEnvironment) -> None: | ||
| """ | ||
| Test installing from a script with an incompatible `requires-python` | ||
| """ | ||
|
|
||
| script_path = script.scratch_path.joinpath("script.py") | ||
|
|
||
| script_path.write_text( | ||
| textwrap.dedent( | ||
| f"""\ | ||
| # /// script | ||
| # requires-python = "!={sys.version_info.major}.{sys.version_info.minor}.*" | ||
| # dependencies = [ | ||
| # "INITools==0.2", | ||
| # "simple==1.0", | ||
| # ] | ||
| # /// | ||
| print("Hello world from a dummy program") | ||
| """ | ||
| ) | ||
| ) | ||
|
|
||
| result = script.pip_install_local( | ||
| "--requirements-from-script", | ||
| script_path, | ||
| expect_stderr=True, | ||
| expect_error=True, | ||
| ) | ||
|
|
||
| assert "requires a different Python" in result.stderr, ( | ||
| "Script with incompatible requires-python did not fail as expected -- " | ||
| + result.stderr | ||
| ) | ||
|
|
||
|
|
||
| def test_script_invalid_TOML(script: PipTestEnvironment) -> None: | ||
| """ | ||
| Test installing from a script with invalid TOML in its 'script' metadata | ||
| """ | ||
|
|
||
| script_path = script.scratch_path.joinpath("script.py") | ||
|
|
||
| script_path.write_text( | ||
| textwrap.dedent( | ||
| f"""\ | ||
| # /// script | ||
| # requires-python = "!={sys.version_info.major}.{sys.version_info.minor}.*" | ||
| # dependencies = [ | ||
| # /// | ||
| print("Hello world from a dummy program") | ||
| """ | ||
| ) | ||
| ) | ||
|
|
||
| result = script.pip_install_local( | ||
| "--requirements-from-script", | ||
| script_path, | ||
| expect_stderr=True, | ||
| expect_error=True, | ||
| ) | ||
|
|
||
| assert "Failed to parse TOML" in result.stderr, ( | ||
| "Script with invalid TOML metadata did not fail as expected -- " + result.stderr | ||
| ) | ||
|
|
||
|
|
||
| def test_script_multiple_blocks(script: PipTestEnvironment) -> None: | ||
| """ | ||
| Test installing from a script with multiple metadata blocks | ||
| """ | ||
|
|
||
| script_path = script.scratch_path.joinpath("script.py") | ||
|
|
||
| script_path.write_text( | ||
| textwrap.dedent( | ||
| f"""\ | ||
| # /// script | ||
| # requires-python = "!={sys.version_info.major}.{sys.version_info.minor}.*" | ||
| # dependencies = [ | ||
| # "INITools==0.2", | ||
| # "simple==1.0", | ||
| # ] | ||
| # /// | ||
| # /// script | ||
| # requires-python = "!={sys.version_info.major}.{sys.version_info.minor}.*" | ||
| # dependencies = [ | ||
| # "INITools==0.2", | ||
| # "simple==1.0", | ||
| # ] | ||
| # /// | ||
| print("Hello world from a dummy program") | ||
| """ | ||
| ) | ||
| ) | ||
|
|
||
| result = script.pip_install_local( | ||
| "--requirements-from-script", | ||
| script_path, | ||
| expect_stderr=True, | ||
| expect_error=True, | ||
| ) | ||
|
|
||
| assert "Multiple 'script' blocks" in result.stderr, ( | ||
| "Script with multiple metadata blocks did not fail as expected -- " | ||
| + result.stderr | ||
| ) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.