bpo-28334: fix netrc not working when $HOME is not set #123
Conversation
|
Hello, and thanks for your contribution! I'm a bot set up to make sure that the project can legally accept your contribution by verifying you have signed the PSF contributor agreement (CLA). Unfortunately we couldn't find an account corresponding to your GitHub username on bugs.python.org (b.p.o) to verify you have signed the CLA. This is necessary for legal reasons before we can look at your contribution. Please follow these steps to help rectify the issue:
Thanks again to your contribution and we look forward to looking at it! |
|
As discussed during patch review, no tests were added, but doc was updated. |
|
@the-knights-who-say-ni : |
|
Thanks for the PR, but like I said in review we can't accept this without a test case. It should be easy to test at least the |
| @@ -32,6 +32,10 @@ the Unix :program:`ftp` program and other FTP clients. | |||
|
|
|||
| .. versionchanged:: 3.4 Added the POSIX permission check. | |||
|
|
|||
| .. versionchanged:: 3.7 Now uses :func:`os.path.expanduser`, so if | |||
berkerpeksag
Feb 19, 2017
Member
Style nit:
.. versionchanged:: 3.7
Now uses :func:`os.path.expanduser`, so if [...]
Style nit:
.. versionchanged:: 3.7
Now uses :func:`os.path.expanduser`, so if [...]|
Rebased, added a test, and fix doc style as requested. |
| @@ -123,6 +123,15 @@ def test_security(self): | |||
| os.chmod(fn, 0o622) | |||
| self.assertRaises(netrc.NetrcParseError, netrc.netrc) | |||
|
|
|||
| def test_when_not_found(self): | |||
zhangyangyu
Mar 8, 2017
Member
I would like the name just test_file_not_found.
I would like the name just test_file_not_found.
dmerejkowsky
Mar 11, 2017
Author
agreed
agreed
| with support.EnvironmentVarGuard() as environ: | ||
| environ.set('HOME', d) | ||
| self.assertRaises(FileNotFoundError, netrc.netrc) | ||
|
|
zhangyangyu
Mar 8, 2017
Member
When you explicitly pass a not exist file, it could also raise the error.
When you explicitly pass a not exist file, it could also raise the error.
dmerejkowsky
Mar 11, 2017
Author
OK. I wrote an other test for this. (I like to follow the 'one assert per test' rule)
OK. I wrote an other test for this. (I like to follow the 'one assert per test' rule)
|
LGTM. But I'd wait for others' reviews for some time. And I want to treat this as an enhancement though it looks like a bug on Windows. |
|
Thanks! Looks good to me, just left some minor comments. Also, since this a behavior change we need to add a note to |
| @@ -32,6 +32,11 @@ the Unix :program:`ftp` program and other FTP clients. | |||
|
|
|||
| .. versionchanged:: 3.4 Added the POSIX permission check. | |||
|
|
|||
| .. versionchanged:: 3.7 | |||
| Now uses :func:`os.path.expanduser`, so if :envvar:`HOME` is not | |||
berkerpeksag
Mar 15, 2017
Member
I think the "if :envvar:HOME is not set [...]" part should be moved to the main documentation block and the versionchanged annotation should only mention that it now uses expanduser() to determine the location of the .netrc file if *file* is not passed.
I think the "if :envvar:HOME is not set [...]" part should be moved to the main documentation block and the versionchanged annotation should only mention that it now uses expanduser() to determine the location of the .netrc file if *file* is not passed.
dmerejkowsky
Mar 15, 2017
Author
Makes sense.
Makes sense.
| @@ -32,6 +32,11 @@ the Unix :program:`ftp` program and other FTP clients. | |||
|
|
|||
| .. versionchanged:: 3.4 Added the POSIX permission check. | |||
|
|
|||
| .. versionchanged:: 3.7 | |||
| Now uses :func:`os.path.expanduser`, so if :envvar:`HOME` is not | |||
| set *and* ``~/.netrc`` not found, will raise | |||
berkerpeksag
Mar 15, 2017
Member
Double spaces before "will".
Double spaces before "will".
dmerejkowsky
Mar 15, 2017
Author
oups, sorry :)
oups, sorry :)
| self.assertRaises(FileNotFoundError, netrc.netrc) | ||
|
|
||
| def test_file_not_found_explicit(self): | ||
| unlikely_file = "unlikely_netrc" |
berkerpeksag
Mar 15, 2017
Member
No need to use unlikely_file here. Just pass "unlikely_netrc" to the constructor.
No need to use unlikely_file here. Just pass "unlikely_netrc" to the constructor.
dmerejkowsky
Mar 15, 2017
Author
agreed
agreed
|
@berkerpeksag rebased and conformed with your latest comments |
| no argument is given, the file :file:`.netrc` in the user's home directory will | ||
| be read. Parse errors will raise :exc:`NetrcParseError` with diagnostic | ||
| no argument is given, the file :file:`.netrc` in the user's home directory -- | ||
| as returned by :func:`os.path.expanduser` -- will be read. If the file is not found, will raise |
zhangyangyu
Apr 10, 2017
Member
Two spaces before "If the file ...".
Two spaces before "If the file ...".
dmerejkowsky
Apr 10, 2017
Author
done
done
| @@ -1046,6 +1046,8 @@ Core and Builtins | |||
| Library | |||
| ------- | |||
|
|
|||
| - bpo-28334: fix netrc not working when $HOME is not set | |||
zhangyangyu
Apr 10, 2017
Member
Uppercase please and colon at end. And instead of fixing, I'd rather something like "Use os.path.expanduser rather than barely checking $HOME is set or not when no argument is given to netrc.netrc().". Ignore my wording, I am not a native speaker. :-(
Uppercase please and colon at end. And instead of fixing, I'd rather something like "Use os.path.expanduser rather than barely checking $HOME is set or not when no argument is given to netrc.netrc().". Ignore my wording, I am not a native speaker. :-(
dmerejkowsky
Apr 10, 2017
Author
Also done. It's a bit long, but I hope it documents precisely the new behavior
Also done. It's a bit long, but I hope it documents precisely the new behavior
df1f1bc
to
09abcaf
|
@zhangyangyu Thanks for the suggestions! It's worth making sure the documentation is as best as possible :) |
| @@ -1125,6 +1125,11 @@ Core and Builtins | |||
| Library | |||
| ------- | |||
|
|
|||
| - bpo-28334: Use os.path.expanduser() to find the ~/.netrc file in netrc.netrc(). | |||
zhangyangyu
Apr 10, 2017
Member
Too wordy. Just
bpo-28334: Use os.path.expanduser() to find the ~/.netrc file in netrc.netrc().
If the file does not exist, a FileNotFoundError is raised.
Too wordy. Just
bpo-28334: Use os.path.expanduser() to find the ~/.netrc file in netrc.netrc().
If the file does not exist, a FileNotFoundError is raised.
| be read. Parse errors will raise :exc:`NetrcParseError` with diagnostic | ||
| no argument is given, the file :file:`.netrc` in the user's home directory -- | ||
| as returned by :func:`os.path.expanduser` -- will be read. If the file is | ||
| not found, will raise :exc:`FileNotFoundError`. |
serhiy-storchaka
Apr 10, 2017
Member
There are many causes why the file can't be read. It can be a directory or broken symlink, or user can not have permissions to read it. Different OSError subclasses are raised in these cases. I think no need to specify this is explicitly. Exceptions that can be raised by the function are rarely documented in Python docs.
There are many causes why the file can't be read. It can be a directory or broken symlink, or user can not have permissions to read it. Different OSError subclasses are raised in these cases. I think no need to specify this is explicitly. Exceptions that can be raised by the function are rarely documented in Python docs.
dmerejkowsky
Apr 11, 2017
Author
Fair enough. I removed the sentence.
Fair enough. I removed the sentence.
| os.mkdir(d) | ||
| self.addCleanup(support.rmtree, d) | ||
| with support.EnvironmentVarGuard() as environ: | ||
| environ.set('HOME', d) |
serhiy-storchaka
Apr 10, 2017
Member
This doesn't test a new behavior. Needed a test for the case when HOME is not set.
This doesn't test a new behavior. Needed a test for the case when HOME is not set.
dmerejkowsky
Apr 11, 2017
Author
This doesn't test a new behavior
I think it does. It checks the type of the exception raised, which is the whole point of this patch,
getting rid of the weird 'OSError' about HOME not set ...
Needed a test for the case when HOME is not set.
Yeah I was not sure about this one. I've added a test where I monkeypatch os.path.expanduser, but I'm not sure it's useful. My guts tell me such a test is too close to the implementation.
This doesn't test a new behavior
I think it does. It checks the type of the exception raised, which is the whole point of this patch,
getting rid of the weird 'OSError' about HOME not set ...
Needed a test for the case when HOME is not set.
Yeah I was not sure about this one. I've added a test where I monkeypatch os.path.expanduser, but I'm not sure it's useful. My guts tell me such a test is too close to the implementation.
berkerpeksag
Apr 26, 2017
•
Member
Serhiy is right that we need a test for this case and unless I'm missing something you can do it without doing any mocking:
with support.EnvironmentVarGuard() as environ:
environ.unset('HOME')
self.assertRaises(FileNotFoundError, netrc.netrc)
Serhiy is right that we need a test for this case and unless I'm missing something you can do it without doing any mocking:
with support.EnvironmentVarGuard() as environ:
environ.unset('HOME')
self.assertRaises(FileNotFoundError, netrc.netrc)|
Wrote a test monkeypatching |
|
@serhiy-storchaka @berkerpeksag @terryjreedy gentle reminder :) |
|
@dmerejkowsky For reminding, you don't need to squash your commit every change makes, all commit will be squash when the PR is merged. See devguide last paragraph. |
| os.chmod(fake_netrc_path, 0o600) | ||
| with support.EnvironmentVarGuard() as environ: | ||
| environ.unset('HOME') | ||
| with mock.patch('os.path.expanduser') as mock_expanduser: |
berkerpeksag
Apr 26, 2017
Member
Mocking os.path.expanduser is not needed here in my opinion. If you can change the test to
def test_home_not_set(self):
with support.EnvironmentVarGuard() as environ:
environ.unset('HOME')
self.assertRaises(FileNotFoundError, netrc.netrc)
then we are good to go! :)
Mocking os.path.expanduser is not needed here in my opinion. If you can change the test to
def test_home_not_set(self):
with support.EnvironmentVarGuard() as environ:
environ.unset('HOME')
self.assertRaises(FileNotFoundError, netrc.netrc)then we are good to go! :)
dmerejkowsky
Apr 27, 2017
Author
I'm afraid not. If we unset HOME and ~/.netrc does exist on the developer box, os.path.expanduser will read /etc/passwd using pwd (see [1]) and no exception will be raised. I'd prefer the tests to not depend on what's on the home directory of the user running the tests.
[1]: https://github.com/python/cpython/blob/master/Lib/posixpath.py#L245-L247
I'm afraid not. If we unset HOME and ~/.netrc does exist on the developer box, os.path.expanduser will read /etc/passwd using pwd (see [1]) and no exception will be raised. I'd prefer the tests to not depend on what's on the home directory of the user running the tests.
[1]: https://github.com/python/cpython/blob/master/Lib/posixpath.py#L245-L247
| @@ -1125,6 +1125,9 @@ Core and Builtins | |||
| Library | |||
| ------- | |||
|
|
|||
| - bpo-28334: Use os.path.expanduser() to find the ~/.netrc file in netrc.netrc(). | |||
berkerpeksag
Apr 26, 2017
Member
~/.netrc -> ``~/.netrc``
(otherwise our doc build will complain)
~/.netrc -> ``~/.netrc``
(otherwise our doc build will complain)
dmerejkowsky
Apr 27, 2017
Author
done
done
|
@dmerejkowsky and just one more comment: Please add your name to |
| no argument is given, the file :file:`.netrc` in the user's home directory will | ||
| be read. Parse errors will raise :exc:`NetrcParseError` with diagnostic | ||
| no argument is given, the file :file:`.netrc` in the user's home directory -- | ||
| as returned by :func:`os.path.expanduser` -- will be read. |
serhiy-storchaka
Apr 27, 2017
Member
Maybe "as determined"? expanduser() itself doesn't return the user's home directory.
Or "as returned by os.path.expanduser('~') ", but this exposes more implementation details and is harder to format as a reference.
Maybe "as determined"? expanduser() itself doesn't return the user's home directory.
Or "as returned by os.path.expanduser('~') ", but this exposes more implementation details and is harder to format as a reference.
dmerejkowsky
Apr 28, 2017
Author
I like 'determined', thanks :)
I like 'determined', thanks :)
| file = os.path.join(os.environ['HOME'], ".netrc") | ||
| except KeyError: | ||
| raise OSError("Could not find .netrc: $HOME is not set") from None | ||
| file = os.path.expanduser("~/.netrc") |
serhiy-storchaka
Apr 27, 2017
Member
'/' is Posix specific pathname separator. Windows accepts it too, but it is better to use the native pathname separator, os.sep. Or use os.path.join():
os.path.expanduser(os.path.join("~", ".netrc"))
or
os.path.join(os.path.expanduser("~"), ".netrc")
'/' is Posix specific pathname separator. Windows accepts it too, but it is better to use the native pathname separator, os.sep. Or use os.path.join():
os.path.expanduser(os.path.join("~", ".netrc"))or
os.path.join(os.path.expanduser("~"), ".netrc")
dmerejkowsky
Apr 28, 2017
•
Author
ok.
ok.
| @@ -32,6 +33,10 @@ the Unix :program:`ftp` program and other FTP clients. | |||
|
|
|||
| .. versionchanged:: 3.4 Added the POSIX permission check. | |||
|
|
|||
| .. versionchanged:: 3.7 | |||
| Now uses :func:`os.path.expanduser` to determine the location | |||
| of ``~/.netrc`` if *file* is not passed as argument. | |||
serhiy-storchaka
Apr 27, 2017
Member
~/.netrc is Posix-specific syntax. Windows users of netrc can even not know what ~/ means. Maybe "of the file :file:'.netrc'" of "of the default netrc file"?
~/.netrc is Posix-specific syntax. Windows users of netrc can even not know what ~/ means. Maybe "of the file :file:'.netrc'" of "of the default netrc file"?
dmerejkowsky
Apr 28, 2017
Author
agreed
agreed
| environ.unset('HOME') | ||
| with mock.patch('os.path.expanduser') as mock_expanduser: | ||
| mock_expanduser.return_value = fake_netrc_path | ||
| nrc = netrc.netrc() |
serhiy-storchaka
Apr 27, 2017
Member
Check the content of nrc for testing that the configuration is read from the right file. Add a random data in the fake .netrc.
Check the content of nrc for testing that the configuration is read from the right file. Add a random data in the fake .netrc.
| @@ -1125,6 +1125,9 @@ Core and Builtins | |||
| Library | |||
| ------- | |||
|
|
|||
| - bpo-28334: Use os.path.expanduser() to find the ``~/.netrc`` file in netrc.netrc(). | |||
serhiy-storchaka
Apr 27, 2017
Member
Too long line.
Add "Patch by Dimitri Merejkowsky."
Too long line.
Add "Patch by Dimitri Merejkowsky."
dmerejkowsky
Apr 28, 2017
Author
done
done
| @@ -1125,6 +1125,9 @@ Core and Builtins | |||
| Library | |||
| ------- | |||
|
|
|||
| - bpo-28334: Use os.path.expanduser() to find the ``~/.netrc`` file in netrc.netrc(). | |||
| If the file does not exist, a FileNotFoundError is raised. | |||
serhiy-storchaka
Apr 27, 2017
Member
Is this a new behavior?
Is this a new behavior?
dmerejkowsky
Apr 28, 2017
Author
Nope, fixed
Nope, fixed
| os.chmod(fake_netrc_path, 0o600) | ||
| with support.EnvironmentVarGuard() as environ: | ||
| environ.unset('HOME') | ||
| with mock.patch('os.path.expanduser') as mock_expanduser: |
serhiy-storchaka
Apr 27, 2017
Member
This is too fragile. If rather than os.path.expanduser("~/.netrc") use one of variants suggested by me, the test will be broken.
I would monkey-patch expanduser with a code like the following:
orig_expanduser = os.path.expanduser
called = []
def fake_expanduser(s):
called.append(s)
environ.set('HOME', d)
result = orig_expanduser(s)
environ.unset('HOME')
return result
with test.support.swap_attr(os.path, 'expanduser', fake_expanduser):
nrc = netrc.netrc()
self.assertTrue(called)
This is too fragile. If rather than os.path.expanduser("~/.netrc") use one of variants suggested by me, the test will be broken.
I would monkey-patch expanduser with a code like the following:
orig_expanduser = os.path.expanduser
called = []
def fake_expanduser(s):
called.append(s)
environ.set('HOME', d)
result = orig_expanduser(s)
environ.unset('HOME')
return result
with test.support.swap_attr(os.path, 'expanduser', fake_expanduser):
nrc = netrc.netrc()
self.assertTrue(called)
dmerejkowsky
Apr 28, 2017
Author
You're right, test is less likely to break this way.
You're right, test is less likely to break this way.
That's a Python bug: http://bugs.python.org/issue28334 Most likely it will be fixed in Python 3.7: python/cpython#123
During interpreter shutdown Stackless kills tasklets on daemon threads, if they have a non-trivial C-stack. This caused an assertion violation. https://bitbucket.org/stackless-dev/stackless/issues/123 (grafted from 9fd4f6a9d266250bef5baa692b89516e73b00e62)
|
I've fixed merge conflicts, created NEWS entry with blurb and made some cosmetic changes. Is there anything left to do here? |
| self.assertRaises(FileNotFoundError, netrc.netrc, | ||
| file='unlikely_netrc') | ||
|
|
||
| def test_home_not_set(self): |
serhiy-storchaka
Nov 24, 2017
Member
Sorry, I don't understand how this test is related to the case when HOME is not set. Why not use the simple test suggested by @berkerpeksag above?
Sorry, I don't understand how this test is related to the case when HOME is not set. Why not use the simple test suggested by @berkerpeksag above?
berkerpeksag
Nov 24, 2017
•
Member
My suggested test won't work, because of the following code in expanduser:
if 'HOME' not in os.environ:
import pwd
userhome = pwd.getpwuid(os.getuid()).pw_dir
I have a .netrc file in my HOME directory so the test won't pass:
def test_foo(self):
with support.EnvironmentVarGuard() as environ:
environ.unset('HOME')
self.assertRaises(FileNotFoundError, netrc.netrc)
======================================================================
FAIL: test_foo (test.test_netrc.NetrcTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/berker/projects/cpython/master/Lib/test/test_netrc.py", line 34, in test_foo
self.assertRaises(FileNotFoundError, netrc.netrc)
AssertionError: FileNotFoundError not raised by netrc
My suggested test won't work, because of the following code in expanduser:
if 'HOME' not in os.environ:
import pwd
userhome = pwd.getpwuid(os.getuid()).pw_dirI have a .netrc file in my HOME directory so the test won't pass:
def test_foo(self):
with support.EnvironmentVarGuard() as environ:
environ.unset('HOME')
self.assertRaises(FileNotFoundError, netrc.netrc)======================================================================
FAIL: test_foo (test.test_netrc.NetrcTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/berker/projects/cpython/master/Lib/test/test_netrc.py", line 34, in test_foo
self.assertRaises(FileNotFoundError, netrc.netrc)
AssertionError: FileNotFoundError not raised by netrc
serhiy-storchaka
Nov 24, 2017
Member
Thanks, now I see that this is the simplest platform-independent test.
Thanks, now I see that this is the simplest platform-independent test.
Also add more tests to check that proper exceptions are raised
|
For some reason, Travis CI didn't pick up latest changes:
Serhiy fixed it a while ago and I don't get any warnings in my development environment. |
|
Ok, this looks like a Travis bug to me. I've opened PR #4537 and documentation build passed there. @dmerejkowsky could you please create a new PR? If I merge PR #4537, GitHub overwrite author information and set me as author. |
It's not a problem for me :) It was very nice of you to finish up my pull request, so by all means just merge your own and let GitHub set you as author of the patch, you deserve it. What matters to me is to be listed in the ACKS file |
|
I've just merged PR #4537. Thank you for your work again, @dmerejkowsky. It took more than a year but we eventually fixed the bug :) |
https://bugs.python.org/issue28334