-
-
Notifications
You must be signed in to change notification settings - Fork 19
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
Implement PEP 723 support (script dependencies in a TOML block) #96
Changes from 2 commits
2816515
f239978
d3df4b8
21a641e
36a2ee6
55c7aec
42ae216
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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -11,6 +11,11 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
from jaraco.context import suppress | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from jaraco.functools import compose | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import tomllib # type: ignore | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
except ImportError: # Python <3.11 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import tomli as tomllib # type: ignore | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ValidRequirementString = compose(str, packaging.requirements.Requirement) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -73,7 +78,35 @@ def search(cls, params): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
return cls.try_read(next(files, None)).params() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def read(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return self.read_comments() or self.read_python() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return self.read_toml() or self.read_comments() or self.read_python() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def read_toml(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
r""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
>>> DepsReader('# /// script\n# dependencies = ["foo", "bar"]\n# ///\n').read() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
['foo', 'bar'] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
>>> DepsReader('# /// pyproject\n# dependencies = ["foo", "bar"]\n# ///\n').read_toml() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
[] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
>>> DepsReader('# /// pyproject\n#dependencies = ["foo", "bar"]\n# ///\n').read_toml() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
[] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
>>> DepsReader('# /// script\n# dependencies = ["foo", "bar"]\n').read_toml() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
[] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
TOML_BLOCK_REGEX = r'(?m)^# /// (?P<type>[a-zA-Z0-9-]+)$\s(?P<content>(^#(| .*)$\s)+)^# ///$' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
name = 'script' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
matches = list( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
filter(lambda m: m.group('type') == name, re.finditer(TOML_BLOCK_REGEX, self.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) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
deps = tomllib.loads(content).get("dependencies", []) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
deps = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+98
to
+110
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 performance gain from In the suggested change, because only one match will be needed, we take only one. Next up, we check if there's another one left and raise immediately, not knowing about the rest of the matches as we don't need them.
Suggested change
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. This is taken straight from the PEP, where it's the canonical implementation of parsing. I'd rather not mess with it, simply because I don't see the benefit in risking the possibility that we introduce bugs by doing so. 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. As the author, I am a strong -1 on this change because it is not as easy to translate into other languages as before. edit: I didn't realize which repository this was, feel free to do as you wish if it works but I agree with Paul that there is an inherent risk of introducing a bug. 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.
Thank you for your feedback. I was suggesting the change for pip-run specifically. Is there any other reason you don't like the suggestions? 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. Nope sorry about that, as I mentioned in the edit to my comment feel free to do as you wish! 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. I tested my changes against tests in this PR and observed no regression. In my opinion, a more detailed analysis of the change can disprove the concern regarding the risk of introducing a bug. 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. What is more, the suggested change costlessly handles an edge case (It's also safer to use 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. Hi, sorry for joining an already crowded conversation, but @bswck asked for my thoughts, so here they are:
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. I'll see if I can find the time to review the various suggestions. TBH, this was very much a "drive by" PR, motivated by the fact that I use pip-run and I wanted to ensure that it supported the new standard. Copying the reference implementation was the fastest way to achieve that. I didn't want to spend a lot of time over details that could be hashed out later. In many ways I'd argue that a robust PEP 723 parser should go in a library somewhere, although I can sympathise if people don't want a new dependency just for that one thing. I'm a little bit sad if we end up with lots of different implementations of the parsing code, all with their own quirks and trade-offs. My recommendation would be that unless I get the time to do an update, the code can go in as is, and it can be fixed in a follow-up PR. I'm absolutely not going to get upset about someone updating the code in a follow-up. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return Dependencies.load(deps) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
def read_comments(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
r""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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.
I would also suggest moving this out of the local scope to a global constant somewhere in the upper part of the module.
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.
I'm inclined not to. Partly because I prefer keeping all the code that was directly copied from the PEP together, but also I don't see the benefit. Nothing else uses this value, and referencing it as a local is marginally faster than referencing it as a global. The performance is, of course, utterly irrelevant compared to the cost of reading the file we're parsing, so that's not a compelling argument. But equally, moving it to the top "because that's where global constants go" is also not a compelling argument (to me, anyway).
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.
I see your point. My suggestion was not in any way motivated by performance.
This just looks like a constant and according to PEP 8, constants should be placed after globals, after imports.
When it comes to performance, it might be improved by compiling the pattern in the module scope, which I didn't suggest for the irrelevancy reasons you've mentioned.
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.
If I may put my two cents in, I would make that pattern a global precompiled constant if it were up to me, it just sounds like good and consistent practice to me even though perf is irrelevant.
I personally don't find the "all the code that was directly copied from the PEP together" bit convincing at all 😄
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.
Pre-compiling the pattern in the module scope has one underlying function that was not mentioned: validation.
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.
I don't particularly want to get into an extended debate here. Ultimately, I'd like to see this merged, and I'm not entirely sure if any of the current participants in this discussion are able to merge a PR here. I'm willing to move
TOML_BLOCK_REGEX
to a global compiled regex, or even just a global string, if @jaraco or another project maintainer feels it would be a useful change. But otherwise I'd prefer just to leave it as it stands. I'm not particularly convinced by "because PEP 8 says so" arguments, TBH.(Yes, my comment about performance was silly. Sorry.)
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.
I was assigned to the issue this PR is meant to solve (#95) 1 week after your PR had been submitted. That's why I allowed myself to leave a review. But of course, I agree, everything is up to jaraco.