Support scripts with inline script metadata as input files#2107
Support scripts with inline script metadata as input files#2107chrysle wants to merge 1 commit intojazzband:mainfrom
Conversation
2f914a1 to
71d54de
Compare
|
@webknjaz please review; there isn't any other active maintainer left, as it seems.. |
|
What's the use-case for this? |
|
Hmm, suppose you have a Python script that has dependencies, is probably often in use and not part of a package with corresponding metadata; then setting up an environment fastly would be possible through this functionality. |
|
@WhyNotHugo would you like to review this? We're really short on help ATM. |
| dedent( | ||
| """ | ||
| # /// script | ||
| # dependencies = [ |
There was a problem hiding this comment.
Should we also have tests for extras?
There was a problem hiding this comment.
Oh, there's no explicit extras in the spec. But how about requires-python?
There was a problem hiding this comment.
I'm not sure how that would influence the build process?
There was a problem hiding this comment.
It should reject incompatible runtimes, for example.
| DEFAULT_REQUIREMENTS_OUTPUT_FILE = "requirements.txt" | ||
| METADATA_FILENAMES = frozenset({"setup.py", "setup.cfg", "pyproject.toml"}) | ||
|
|
||
| INLINE_SCRIPT_METADATA_REGEX = ( |
There was a problem hiding this comment.
I recommend using (?x) to be able to make this multiline with comments.
| # piping from stdin, we need to briefly save the input from stdin | ||
| # to a temporary file and have pip read that. also used for | ||
| if src_file == "-" or ( | ||
| os.path.basename(src_file).endswith(".py") and not is_setup_file |
There was a problem hiding this comment.
pathlib is usually nicer
| os.path.basename(src_file).endswith(".py") and not is_setup_file | |
| Path(src_file).suffix == ".py" and not is_setup_file |
There was a problem hiding this comment.
The src path should probably be a local variable (not temporary like in the suggestion), to be used for suffix check here and open / read_text on lines below.
| # scripts, we need to briefly save the input or extracted script | ||
| # dependencies to a temporary file and have pip read that. Also used for | ||
| # reading requirements from install_requires in setup.py. | ||
| if os.path.basename(src_file).endswith(".py"): |
There was a problem hiding this comment.
| if os.path.basename(src_file).endswith(".py"): | |
| if Path(src_file).suffix == ".py": |
|
@chrysle sorry, I didn't have time lately. I'll try to come back for another review round during/after EuroPython. |
71d54de to
792e034
Compare
|
@chrysle I clicked “rebase” and it looks like there's some actionable feedback in the earlier comments. |
| matches = list( | ||
| filter( | ||
| lambda m: m.group("type") == name, | ||
| re.finditer(INLINE_SCRIPT_METADATA_REGEX, script), | ||
| ) | ||
| ) | ||
| if len(matches) > 1: | ||
| raise ValueError(f"Multiple {name} blocks found") | ||
| elif len(matches) == 1: | ||
| content = "".join( | ||
| line[2:] if line.startswith("# ") else line[1:] | ||
| for line in matches[0] | ||
| .group("content") | ||
| .splitlines(keepends=True) | ||
| ) | ||
| metadata = tomllib.loads(content) | ||
| reqs_str = metadata.get("dependencies", []) | ||
| tmpfile = tempfile.NamedTemporaryFile(mode="wt", delete=False) | ||
| input_reqs = "\n".join(reqs_str) | ||
| comes_from = ( | ||
| f"{os.path.basename(src_file)} (inline script metadata)" | ||
| ) | ||
| else: | ||
| raise PipToolsError( | ||
| "Input script does not contain valid inline script metadata!" | ||
| ) |
There was a problem hiding this comment.
Let's restructure this as follows
| matches = list( | |
| filter( | |
| lambda m: m.group("type") == name, | |
| re.finditer(INLINE_SCRIPT_METADATA_REGEX, script), | |
| ) | |
| ) | |
| if len(matches) > 1: | |
| raise ValueError(f"Multiple {name} blocks found") | |
| elif len(matches) == 1: | |
| content = "".join( | |
| line[2:] if line.startswith("# ") else line[1:] | |
| for line in matches[0] | |
| .group("content") | |
| .splitlines(keepends=True) | |
| ) | |
| metadata = tomllib.loads(content) | |
| reqs_str = metadata.get("dependencies", []) | |
| tmpfile = tempfile.NamedTemporaryFile(mode="wt", delete=False) | |
| input_reqs = "\n".join(reqs_str) | |
| comes_from = ( | |
| f"{os.path.basename(src_file)} (inline script metadata)" | |
| ) | |
| else: | |
| raise PipToolsError( | |
| "Input script does not contain valid inline script metadata!" | |
| ) | |
| script_metadata_matches = list( | |
| filter( | |
| lambda m: m.group("type") == name, | |
| re.finditer(INLINE_SCRIPT_METADATA_REGEX, script), | |
| ) | |
| ) | |
| if not script_metadata_matches: | |
| raise PipToolsError( | |
| "Input script does not contain valid inline script metadata!" | |
| ) | |
| if len(script_metadata_matches) > 1: | |
| raise ValueError(f"Multiple {name} blocks found") | |
| script_metadata_match = script_metadata_matches[0] | |
| packaging_metadata_toml = "".join( | |
| line[2:] if line.startswith("# ") else line[1:] | |
| for line in script_metadata_match | |
| .group("content") | |
| .splitlines(keepends=True) | |
| ) | |
| metadata = tomllib.loads(packaging_metadata_toml) | |
| reqs_str = metadata.get("dependencies", []) | |
| tmpfile = tempfile.NamedTemporaryFile(mode="wt", delete=False) | |
| input_reqs = "\n".join(reqs_str) | |
| comes_from = ( | |
| f"{os.path.basename(src_file)} (inline script metadata)" | |
| ) |
Closes #2027.
Contributor checklist
Maintainer checklist
backwards incompatible,feature,enhancement,deprecation,bug,dependency,docsorskip-changelogas they determine changelog listing.