-
Notifications
You must be signed in to change notification settings - Fork 418
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
feat: support PEP 723 run requirements #1100
Changes from all commits
7e9fab4
99a90ce
569687c
dc9278c
a0eaa76
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
import datetime | ||
import hashlib | ||
import logging | ||
import re | ||
import sys | ||
import time | ||
import urllib.parse | ||
import urllib.request | ||
|
@@ -24,6 +26,11 @@ | |
) | ||
from pipx.venv import Venv | ||
|
||
if sys.version_info < (3, 11): | ||
import tomli as tomllib | ||
else: | ||
import tomllib | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
|
@@ -319,41 +326,46 @@ def _http_get_request(url: str) -> str: | |
raise PipxError(str(e)) from e | ||
|
||
|
||
# This regex comes from PEP 723 | ||
PEP723 = re.compile( | ||
r"(?m)^# /// (?P<type>[a-zA-Z0-9-]+)$\s(?P<content>(^#(| .*)$\s)+)^# ///$" | ||
) | ||
|
||
|
||
def _get_requirements_from_script(content: str) -> Optional[List[str]]: | ||
# An iterator over the lines in the script. We will | ||
# read through this in sections, so it needs to be an | ||
# iterator, not just a list. | ||
lines = iter(content.splitlines()) | ||
|
||
for line in lines: | ||
if not line.startswith("#"): | ||
continue | ||
line_content = line[1:].strip() | ||
if line_content == "Requirements:": | ||
break | ||
else: | ||
# No "Requirements:" line in the file | ||
""" | ||
Supports PEP 723. | ||
""" | ||
|
||
name = "pyproject" | ||
|
||
# Windows is currently getting un-normalized line endings, so normalize | ||
content = content.replace("\r\n", "\n") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this needed? From what I can tell not normalising would simply produce some harmless empty lines that do not match anyway. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If a newline is multiple multiple chars, then it doesn't match the regex. I pre-process this so I can use exactly the regex in the PEP. Another option would be to modify the regex to handle all possible new lines and only that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (The CI was breaking on Windows otherwise ;) ) |
||
|
||
matches = [m for m in PEP723.finditer(content) if m.group("type") == name] | ||
|
||
if not matches: | ||
return None | ||
|
||
# We are now at the first requirement | ||
requirements = [] | ||
for line in lines: | ||
# Stop at the end of the comment block | ||
if not line.startswith("#"): | ||
break | ||
line_content = line[1:].strip() | ||
# Stop at a blank comment line | ||
if not line_content: | ||
break | ||
if len(matches) > 1: | ||
raise ValueError(f"Multiple {name} blocks found") | ||
|
||
content = "".join( | ||
line[2:] if line.startswith("# ") else line[1:] | ||
for line in matches[0].group("content").splitlines(keepends=True) | ||
) | ||
|
||
pyproject = tomllib.loads(content) | ||
|
||
requirements = [] | ||
for requirement in pyproject.get("run", {}).get("requirements", []): | ||
# Validate the requirement | ||
try: | ||
req = Requirement(line_content) | ||
req = Requirement(requirement) | ||
except InvalidRequirement as e: | ||
raise PipxError(f"Invalid requirement {line_content}: {str(e)}") from e | ||
raise PipxError(f"Invalid requirement {requirement}: {e}") from e | ||
|
||
# Use the normalised form of the requirement, | ||
# not the original line. | ||
# Use the normalised form of the requirement | ||
requirements.append(str(req)) | ||
|
||
return requirements |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A comment on the regex pattern (and/or maybe using
re.VERBOSE
) would be a good ideaThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say comment as long as we match the PEP's suggested regex.