-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Infer target version based on project metadata #3219
Conversation
f6012ef
to
d0cdfaa
Compare
Drive-by comment, for the time being you could document this in the help for |
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.
Thanks for the PR! Some small comments below.
4c332eb
to
6622f6d
Compare
Thanks for the comments so far! I came up with the missing logic for handling some of the special cases. The result isn't super pretty, but I believe it works. It covers all the test cases I could think of. I set the PR to 'ready to review'. Happy to address any concerns you may have! |
6622f6d
to
cb963f2
Compare
cb963f2
to
5b887ce
Compare
@ichard26 Would you be willing to take another look at this? We can also conclude that this kind of complexity is not what we want right now, for the small benefit it provides. Then we can close the PR. But would be nice to have some kind of decision on this! |
Hi sorry, I know it's been a long while since I've last said anything. I promise I haven't forgotten this PR, I'll try my best to review this PR in the coming days! On initial thought, I think this will be an useful QOL feature that the extra complexity is worth it. |
5b887ce
to
2ff413f
Compare
I saw I missed a mypy linting error. I rebased the branch and added a commit to fix the issue. The diff-shades checks are still failing, but I'm not sure how to fix those. |
diff-shades results comparing this PR (1b5b6f4) to main (226cbf0). The full diff is available in the logs under the "Generate HTML diff report" step.
|
2ff413f
to
051c946
Compare
Had to rebase due to #3233 . Should be good to go! |
051c946
to
4f618f7
Compare
I've been a really bad maintainer, but I promise I've started reviewing this. It overall looks great, I just have a few code style remarks and I want to play around with it locally before approving it. Thank you so much for being patient (way beyond what's reasonable!). |
Don't worry about it! Life gets in the way. We'll just pick it back up whenever you're ready 👍 |
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.
Fantastic work overall! Your tests are great and your code was easy to follow. As mentioned earlier, I do have a few stylistic critiques and suggestions to make the logic more robust.
Also two general comments:
-
Should we emit a message telling the user what we inferred the target version to be as part of
--verbose
? -
By inferring a default for
--target-version
, Black will never do per-file auto-detection ifproject.requires-python
exists and is valid. This is certainly better than the status quo for the lower bound, but it can cause some parsing trouble if the file in question is actually using more modern syntax than specified.1Say for example we have
requires-python = ">=3.7"
. If I format a file that's using 3.10's new match statement, Black will fail with aCannot parse: ...
error because Black decides what grammars it'll try from the target version. While this keeps things consistent, I don't think it makes for a good user experience. The main two ways to fix this are either to infer a list of all supported target versions or actually implement this suggestion to make--target-version
a lower-bound by default. The latter is the long-term fix, but I'm not sure when that will be implemented so the former might be necessary as a temporary fix.2
Thanks again for waiting so patiently <3
Footnotes
-
Also Black would never introduce modern syntax that's not permissible under the project's inferred target version even if the file in question is using more modern syntax -- which is probably fine given explicitly providing
--target-version
works the same way. ↩ -
Unless we decide this is an acceptable failure case and the workaround of explicitly setting
--target-version
is okay -- which is fine by me ↩
Thanks for your comments! I just got back from vacation and will take a look at this tomorrow. |
Great suggestion, I will figure out how to do this and add a commit for it.
I hadn't considered this; thanks for explaining this. I think it shouldn't be hard to rewrite my logic to generate the full list of target versions. I'll figure this out and add a commit for this. |
4f618f7
to
b67ca4a
Compare
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.
Almost there! The problem where Black blows up with invalid python-requires is still there.
--verbose
still doesn't say what target version were inferred, but I'm happy to defer this as a future improvement since the per-file autodetection isn't logged anywhere either. A quick note about the changelog otherwise.
Thank you so much for your patience again!
Hey @ichard26 , sorry I have been sidetracked with some other things - I will complete this sometime next week. Indeed my two open points are still:
I've looked at both but there are some tricky things to consider. I just have to sit down and finish it 😸 |
@ichard26 any chance this could make it into the upcoming release ? :-) |
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.
Let's ship this! 🚢 There some tweaks that will probably have to be made, but I'm sick of letting this rot anymore. I'll deal with them in follow up PRs.
It'll be really nice to have this in 23.1.0. Thank you so, so, so much @stinodego for baring with me.
@ ichard26 I addressed your comments, I think this handles all cases properly now. Happy to address any other feedback.
I would also be interested in doing some work as a follow-up to make Black use a single target version, as discussed previously.
That would be wonderful, although as you have noticed, it can get complicated quickly. You've been warned :)
Wait, it seems like the attrs failure on diff-shades is genuine. I thought it was just a fluke, but it turns out attrs doesn't specify Black's target-version in |
Alright, so the reason why this is breaking attrs is that a non-empty Lines 66 to 69 in f16333e
I miscontextualized how Attrs is formatting fine right now because if Lines 50 to 59 in f16333e
So technically this PR doesn't introduce any more regressions, but it just exposes this pitfall... I'm not entirely sure what the best way of fixing this is. My suggestion would be to add the 3.10 grammar if any of the target versions support |
Say you're attrs and you don't configure target-version. Previously Black would default to trying every single grammar it has, including the 3.10 grammar. With this PR, now target-version is configured for you effectively so now get_grammars() is more selective in which grammars it returns. If any target versions don't support the match statement, the 3.10 grammar won't be tried. And while in theory a 3.7+ project shoudn't be using 3.10 features, attrs has a test file which uses match (which fails to parse because the 3.10 grammar isn't selected). To avoid breaking attrs, get_grammars() will now return the 3.10 grammar as long as *any* the target versions support match. The out() call was made redundant by an older PR that prints the configuration if --verbose is passed.
[attrs - https://github.com/python-attrs/attrs.git]
╰─> [revision 9d23ae9536beb3cd30241f05123190ec1ecc20c1](https://github.com/python-attrs/attrs/tree/9d23ae9536beb3cd30241f05123190ec1ecc20c1)
--- a/attrs:src/attr/validators.pyi
+++ b/attrs:src/attr/validators.pyi
@@ -80,7 +80,7 @@
def min_len(length: int) -> _ValidatorType[_T]: ...
def not_(
validator: _ValidatorType[_T],
*,
msg: Optional[str] = None,
- exc_types: Union[Type[Exception], Iterable[Type[Exception]]] = ...
+ exc_types: Union[Type[Exception], Iterable[Type[Exception]]] = ...,
) -> _ValidatorType[_T]: ... I don't have time to investigate, but presumably this is due to better inference of the target version. Not ideal, but as long as we land this in 23.1.0 where we're changing the stable style anyway, it should be fine. |
Thanks again @stinodego. It's been great working with you 🖤 |
Thanks for putting the finishing touches on this, @ichard26 ! Glad to have this part of the latest release. If any issues pop up, do not hesitate to tag me. |
Since Black's 23.1.0 release it infers the target versions via the project metadata (python-requires). Verified that it detects this correctly: $ black -v src [...] target_version: ['py310', 'py311'] [...] See psf/black#3219
Description
Fixes #3124
The logic implemented is as follows:
target_version
is specified in thepyproject.toml
, we ignore the project metadata.TargetVersion
. If we fail for whatever reason, we continue without atarget_version
.requires-python
specifier.3.8.5
becomes["py38"]
>3.6,<3.11
becomes["py37", "py38", "py39", "py310"]
<3.7
becomes["py33", "py34", "py35", "py36"]
(Python 3.3 is the minimal supported version for Black)(for more examples, check the test cases)
Changes:
parse_pyproject_toml
target_version
is specified in Black's config. If not, we try to infer it from the project metadata.project
->requires-python
.packaging
'sVersion
andSpecifierSet
strip_specifier_set
function that I wrote. Unfortunately, working with these kinds of specifiers is nasty when it comes to edge cases. My implementation satisfies all specifier sets I could come up with, but there are probably some corner cases that I did not cover...packaging>=20.0
as a dependency. This is the minimum version that includes the functionality needed for this implementation.Checklist - did you ...