-
-
Notifications
You must be signed in to change notification settings - Fork 266
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
Add pathlib
brain to handle .parents
inference
#1442
Conversation
pathlib
brain to handle parents
in Python 3.10
for more information, see https://pre-commit.ci
for more information, see https://pre-commit.ci
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 @deepyaman, left some initial comments!
I also updated your OP not to directly close the pylint
issue. We tend to fix issues in astroid
and then add a regression test to pylint
to fully close the issue. If you want you can make a PR with a regression test for pylint
as well!
tests/unittest_brain_pathlib.py
Outdated
@@ -0,0 +1,42 @@ | |||
import pytest |
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.
Just for "totality" could you also add a test for the inference of parents
itself?
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 feel like I'm almost there, but it's not currently working for:
from pathlib import Path
def _main() -> None:
current_path = Path().resolve()
path_parents = current_path.parents
parent_path = path_parents[2].resolve()
print(f"{current_path=}")
print(f"{parent_path=}")
if __name__ == "__main__":
_main()
It fails with the same test.py:7:18: E1101: Instance of 'tuple' has no 'resolve' member (no-member)
.
Seems to be why my attempt at adding a nicer-looking test failed:
def test_inference_parents_subscript_index() -> None:
"""Test inference of ``pathlib.Path.parents``, accessed by index."""
parents, path = astroid.extract_node(
"""
from pathlib import Path
current_path = Path().resolve()
path_parents = current_path.parents
path_parents #@
path_parents[2] #@
"""
)
inferred = parents.inferred()
assert len(inferred) == 1
assert isinstance(inferred[0], bases.Instance)
assert inferred[0].qname() == "pathlib._PathParents"
inferred = path.inferred()
assert len(inferred) == 1
if PY310_PLUS:
assert isinstance(inferred[0], bases.Instance)
assert inferred[0].qname() == "pathlib.Path"
else:
assert inferred[0] is Uninferable
@DanielNoord Thank you for the detailed feedback! I will try and implement the requested changes/feedback over the next few days and get back to you. :) |
for more information, see https://pre-commit.ci
This reverts commit e5b2e18.
@deepyaman Sorry for letting this rest so long. I have been quite busy and haven't had the time to look into this enough. I hope to find some time for it this weekend. Again, sorry for the long delay! |
No problem, and thanks again for taking the time to guide me through this! |
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.
@deepyaman Sorry this took so long to review. When I finally got to it I was in so deep that it seemed easier to just push the fixes I found.
Sorry for taking over your PR with prior notice like that.
In this new iteration we now also correctly infer an index lookup. I do this by creating an inference of the normal ClassDef
for Path
and then instantiating an instance from it.
I am not completely sure Uninferable
for a Slice
is correct on <3.9. I'll let another maintainer comment on that: do we infer Uninferable
for code that would raise warnings?
I'll re-ask @cdce8p for a review since he had some valuable input in the first round.
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.
@DanielNoord No problem! Thanks for resolving it!
@@ -72,7 +77,7 @@ class A: | |||
parents = 42 | |||
|
|||
c = A() | |||
error = c.parents[2] | |||
error = c.parents[:2] |
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.
Did this have to be changed?
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.
Originally I hadn't fixed the normal index look up, so this was not really testing anything:
The brain returned Uninferable
and the non-brain would return Uninferable
. So we weren't actually testing if this doesn't actually enter the brain. By using the slice we can see that this doesn't trigger the brain as it would give a different result for PY3.10+
.
Now that I fixed the brain to work with index as well the change is perhaps not necessary anymore.
Co-authored-by: Deepyaman Datta <deepyaman.datta@utexas.edu>
astroid/brain/brain_pathlib.py
Outdated
PATH = extract_node( | ||
"""from pathlib import Path | ||
Path | ||
""" | ||
) | ||
PATH_CLASSDEF: nodes.ClassDef = next(PATH.infer()) |
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.
Inference calls are non-trivial. I'm don't think it's a good idea to do them at import. Better to only define the code template as global const and call extract_node
(or even _extract_single_node
) in infer_parents_subscript
.
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.
But it isn't it better to use the actual ClassDef
? You're more experienced here, but this seems to pass tests and doesn't this give us more information than a constructed mimic of Path
?
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.
It's probably fine to use extract_node
and infer
to get the ClassDef
, just don't do it at Module level / import time.
As example, this is how the typing
brain does something similar
https://github.com/PyCQA/astroid/blob/e57ac42bca80c105bbdad4bae61637b39288a47b/astroid/brain/brain_typing.py#L99-L103
https://github.com/PyCQA/astroid/blob/e57ac42bca80c105bbdad4bae61637b39288a47b/astroid/brain/brain_typing.py#L167-L168
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'll refactor!
Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com>
astroid/brain/brain_pathlib.py
Outdated
PATH = extract_node( | ||
"""from pathlib import Path | ||
Path | ||
""" | ||
) | ||
PATH_CLASSDEF: nodes.ClassDef = next(PATH.infer()) |
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.
It's probably fine to use extract_node
and infer
to get the ClassDef
, just don't do it at Module level / import time.
As example, this is how the typing
brain does something similar
https://github.com/PyCQA/astroid/blob/e57ac42bca80c105bbdad4bae61637b39288a47b/astroid/brain/brain_typing.py#L99-L103
https://github.com/PyCQA/astroid/blob/e57ac42bca80c105bbdad4bae61637b39288a47b/astroid/brain/brain_typing.py#L167-L168
Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com>
@DanielNoord I moved |
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.
Good to merge once the CI checks pass.
Thanks! I'm still finding my way through the |
Off-topic: There is an open proposal for lazy imports in Python: PEP 690. Thankfully opt-in, but nevertheless will be quite challenging as it is to make pylint and astroid compatible. |
Thanks @deepyaman for the initial PR and thanks to @cdce8p for the reviews! Happy to finally see this merged! |
Steps
Description
The
PurePath.parents
sequence supports slices since Python 3.10. This brain allows pylint to understand that passing an index (as opposed to a slice) toPurePath.parents
will still yield a path, not a tuple.Given
test_index.py
:Running pylint yields:
Given
test_slice.py
:Running pylint yields:
Type of Changes
Related Issue
Closes with a test case pylint-dev/pylint#5783
Partially addresses pylint-dev/pylint#5832