Correctly detect publicity of modules inside directories #494
Conversation
@samj1912 What's your expectation of these tests? module = parser.parse(code, str(parent_path / "_private_pkg" / "filepath"))
assert not module.is_public
module = parser.parse(code, str(parent_path / "_private_pkg" / "some_pkg" / "filepath"))
assert not module.is_public My expectation is that these should pass ✔️? (the above tests currently fail ❌) Thus, a module containing no leading underscore inside a private package should be considered private. EDIT: Nevermind, according to @Nurdok in this comment and other comments in PR #326, these should be private. This is addressed in commit 123025c. |
39e5c4b
to
123025c
Compare
Publicity is inherently affected by where you run Consider the following example: publicpackage/
├── __init__.py
└── _privatepackage/
└── some_private_module.py # no docstring here $ pydocstyle publicpackage/ ↵
# no errors
Maybe we can simply document that publicity is affected by where This issue was also brought up in #326 by this comment. |
I was on vacation, thanks for taking over @gbroques 👍. I'll close my PR. |
Not a problem! Thank you for your initial work on this. Hopefully we can get it merged :) |
src/pydocstyle/parser.py
Outdated
path = Path(self.name).parent # Ignore the actual module's name | ||
syspath = [Path(p) for p in sys.path] # Convert to pathlib.Path. | ||
|
||
while path != path.parent and path not in syspath: # Bail if we are at the root directory or in `PYTHONPATH`. |
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.
Sorry for the error in my own code, but I guess this exceeds 80 chars? Just move the comment one line up.
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.
No worries, this should be resolved in f807365.
Path("__private_package") / "package" / "module.py", | ||
Path("") / "__private_package" / "package" / "module.py" | ||
)) | ||
def test_module_publicity_with_private_paths(private_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.
Please also add some tests with the same path over different sys.path
cases.
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.
Can you elaborate on how to do this please?
EDIT: It looks like pytest
includes a monkeypatch fixture with syspath_prepend()
method to modify sys.path
.
Is something like this desirable (updated in dde693f)?
@pytest.mark.parametrize("syspath,is_public", (
("/", False),
("_foo/", True),
))
def test_module_publicity_with_different_sys_path(syspath, is_public, monkeypatch):
"""Test module publicity for same path and different sys.path."""
parser = Parser()
code = CodeSnippet("")
monkeypatch.syspath_prepend(syspath)
path = Path("_foo") / "bar" / "baz.py"
module = parser.parse(code, str(path))
assert module.is_public == is_public
fd3ab0d
to
4bb734d
Compare
211088c
to
1bda783
Compare
1bda783
to
dde693f
Compare
@@ -10,7 +10,9 @@ Publicity for all constructs is determined as follows: a construct is | |||
considered *public* if - | |||
|
|||
1. Its immediate parent is public *and* |
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.
Should we also update the docs to include our new behavior?
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.
Which new behavior are you referring to?
Do you have suggestions for wording it?
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.
Ah - I meant the behavior around syspath. That may catch some people off guard.
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 can mention it as a "Note" somewhere in the "How publicity is determined" section.
Note, a construct's parent is recursively checked upward until we reach a directory in
sys.path
to avoid considering the complete filepath.
Thoughts?
EDIT: I suppose this is dependent upon your other comment.
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 keep this comment to what you suggested for now.
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.
Updated in commit 2f6ab9e.
syspath = [Path(p) for p in sys.path] # Convert to pathlib.Path. | ||
|
||
# Bail if we are at the root directory or in `PYTHONPATH`. | ||
while path != path.parent and path not in syspath: |
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 think we should iterate all the way to the root. But only till the current directory? Thoughts?
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.
edit - The current working directory will be in the sys path in certain conditions https://docs.python.org/3.8/library/sys.html#sys.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.
I did some tests with the pydocstyle
CLI locally, and the directory that you execute pydocstyle
from is not included in sys.path
.
I don't think we should iterate all the way to the root. But only till the current directory?
Maybe your right? Please advise.
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 am just wondering about this use case because if we iterate all they way till root, there might be folders with an underscore in their name. On the other hand this means that pydocstyle will have different outputs on the same input based on the CWD
which I am not sure about. So - let's keep it to sys.path
for now simply because it makes the checker consistent.
In the future if we decide to change this behavior we can always put in a PR - @Nurdok what do you think?
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.
+1 for keeping as is and documenting it. A user can always get the current-directory-behavior by calling PYTHONPATH=$PYTHONPATH:$CWD pydocstyle ...
.
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.
No worries! I really appreciate all the excellent feedback you and @Nurdok have provided, and this is just a small way I can say thank you for the work you two do. :) |
b72179d
to
2f6ab9e
Compare
docs/snippets/publicity.rst
Outdated
@@ -25,6 +27,12 @@ a class called ``_Foo`` is considered private. A method ``bar`` in ``_Foo`` is | |||
also considered private since its parent is a private class, even though its | |||
name does not begin with a single underscore. | |||
|
|||
Note, a construct's parent is recursively checked upward until we reach a directory |
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.
construct -> module?
src/pydocstyle/parser.py
Outdated
|
||
def _is_inside_private_package(self): | ||
"""Return True if the module is inside a private package.""" | ||
path = Path(self.name).parent # Ignore the actual module's name |
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.
Add a dot at the end of the comment please (my bad I guess).
@gbroques sorry for the long wait. Thanks for this PR! |
@samj1912 thank you! |
Continuation of PR #470 as @tibdex was taking a little to respond, and their PR relates to issue #493 that I opened.
I branched off @tibdex's work so they get contribution credits for the work they did. Thank you @tibdex!!
@samj1912 @Nurdok Please review. This uses
pathlib
in theparser_test.py
now.