Skip to content

Commit b46c00a

Browse files
Berthin TorresBerthin Torres
authored andcommitted
Add docstrings and fix documentation
1 parent d877728 commit b46c00a

2 files changed

Lines changed: 88 additions & 39 deletions

File tree

Doc/library/netrc.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ the Unix :program:`ftp` program and other FTP clients.
1919

2020
A :class:`~netrc.netrc` instance or subclass instance encapsulates data from a netrc
2121
file. The initialization argument, if present, specifies the file to parse. If no
22-
argument is given, it will look for the file path in the :envvar:`NETRC` environment variable.
22+
argument is given, it will look for the file path in the :envvar:`!NETRC` environment variable.
2323
If that is not set, it defaults to reading the file :file:`.netrc` in the user's home
2424
directory -- as determined by :func:`os.path.expanduser`. If the file cannot be found,
2525
a :exc:`FileNotFoundError` exception will be raised.

Lib/test/test_netrc.py

Lines changed: 87 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import netrc, os, unittest, sys, textwrap, tempfile
22

33
from test import support
4-
from unittest import mock
54
from contextlib import ExitStack
65

76
try:
@@ -11,16 +10,43 @@
1110

1211

1312
class NetrcEnvironment:
14-
def __enter__(self):
13+
"""
14+
Context manager for setting up an isolated environment to test `.netrc` file handling.
15+
16+
This class configures a temporary directory for the `.netrc` file and environment variables, providing
17+
a controlled setup to simulate different scenarios.
18+
"""
19+
20+
def __enter__(self) -> 'NetrcEnvironment':
21+
"""
22+
Enters the managed environment.
23+
"""
1524
self.stack = ExitStack()
1625
self.environ = self.stack.enter_context(support.os_helper.EnvironmentVarGuard())
1726
self.tmpdir = self.stack.enter_context(tempfile.TemporaryDirectory())
1827
return self
1928

20-
def __exit__(self, *ignore_exc):
29+
def __exit__(self, *ignore_exc) -> None:
30+
"""
31+
Exits the managed environment and performs cleanup. This method closes the `ExitStack`,
32+
which automatically cleans up the temporary directory and environment.
33+
"""
2134
self.stack.close()
2235

23-
def generate_netrc(self, content, filename=".netrc", mode=0o600, encoding="utf-8"):
36+
def generate_netrc(self, content, filename=".netrc", mode=0o600, encoding="utf-8") -> str:
37+
"""
38+
Creates a `.netrc` file in the temporary directory with the given content and permissions.
39+
40+
Args:
41+
content (str): The content to write into the `.netrc` file.
42+
filename (str, optional): The name of the file to write. Defaults to ".netrc".
43+
mode (int, optional): File permission bits to set after writing. Defaults to `0o600`. Mode
44+
is set only if the platform supports `chmod`.
45+
encoding (str, optional): The encoding used to write the file. Defaults to "utf-8".
46+
47+
Returns:
48+
str: The full path to the generated `.netrc` file.
49+
"""
2450
write_mode = "w"
2551
if sys.platform != "cygwin":
2652
write_mode += "t"
@@ -29,51 +55,74 @@ def generate_netrc(self, content, filename=".netrc", mode=0o600, encoding="utf-8
2955
with open(netrc_file, mode=write_mode, encoding=encoding) as fp:
3056
fp.write(textwrap.dedent(content))
3157

32-
os.chmod(netrc_file, mode=mode)
58+
if support.os_helper.can_chmod():
59+
os.chmod(netrc_file, mode=mode)
3360

3461
return netrc_file
3562

3663

3764
class NetrcBuilder:
65+
"""
66+
Utility class to construct and load `netrc.netrc` instances using different configuration scenarios.
67+
68+
This class provides static methods to simulate different ways the `netrc` module can locate and load
69+
a `.netrc` file.
70+
71+
These methods are useful for testing or mocking `.netrc` behavior in different system environments.
72+
"""
73+
3874
@staticmethod
39-
def use_default_netrc_in_home(*args, **kwargs):
75+
def use_default_netrc_in_home(*args, **kwargs) -> netrc.netrc:
76+
"""
77+
Loads an instance of netrc using the default `.netrc` file from the user's home directory.
78+
"""
4079
with NetrcEnvironment() as helper:
4180
helper.environ.unset("NETRC")
42-
helper.environ.unset("HOME")
81+
helper.environ.set("HOME", helper.tmpdir)
4382

4483
helper.generate_netrc(*args, **kwargs)
45-
46-
with mock.patch("os.path.expanduser"):
47-
os.path.expanduser.return_value = helper.tmpdir
48-
return netrc.netrc()
84+
return netrc.netrc()
4985

5086
@staticmethod
51-
def use_netrc_envvar(*args, **kwargs):
87+
def use_netrc_envvar(*args, **kwargs) -> netrc.netrc:
88+
"""
89+
Loads an instance of the netrc using the `.netrc` file specified by the `NETRC` environment variable.
90+
"""
5291
with NetrcEnvironment() as helper:
5392
netrc_file = helper.generate_netrc(*args, **kwargs)
54-
5593
helper.environ.set("NETRC", netrc_file)
94+
5695
return netrc.netrc()
5796

5897
@staticmethod
59-
def use_file_argument(*args, **kwargs):
98+
def use_file_argument(*args, **kwargs) -> netrc.netrc:
99+
"""
100+
Loads an instance of `.netrc` file using the file as argument.
101+
"""
60102
with NetrcEnvironment() as helper:
103+
# Just to stress a bit more the test scenario, the NETRC envvar will contain
104+
# rubish information which shouldn't be used
61105
helper.environ.set("NETRC", "not-a-file.netrc")
62106

63107
netrc_file = helper.generate_netrc(*args, **kwargs)
64108
return netrc.netrc(netrc_file)
65109

66110
@staticmethod
67-
def use_all_strategies():
111+
def get_all_scenarios():
112+
"""
113+
Returns all `.netrc` loading scenarios as callables.
114+
115+
This method is useful for iterating through all supported ways the `.netrc` file can be located.
116+
"""
68117
return (NetrcBuilder.use_default_netrc_in_home,
69118
NetrcBuilder.use_netrc_envvar,
70119
NetrcBuilder.use_file_argument)
71120

72121

73122
class NetrcTestCase(unittest.TestCase):
74-
ALL_STRATEGIES = NetrcBuilder.use_all_strategies()
123+
ALL_NETRC_FILE_SCENARIOS = NetrcBuilder.get_all_scenarios()
75124

76-
@support.subTests('make_nrc', ALL_STRATEGIES)
125+
@support.subTests('make_nrc', ALL_NETRC_FILE_SCENARIOS)
77126
def test_toplevel_non_ordered_tokens(self, make_nrc):
78127
nrc = make_nrc("""\
79128
machine host.domain.com password pass1 login log1 account acct1
@@ -82,7 +131,7 @@ def test_toplevel_non_ordered_tokens(self, make_nrc):
82131
self.assertEqual(nrc.hosts['host.domain.com'], ('log1', 'acct1', 'pass1'))
83132
self.assertEqual(nrc.hosts['default'], ('log2', 'acct2', 'pass2'))
84133

85-
@support.subTests('make_nrc', ALL_STRATEGIES)
134+
@support.subTests('make_nrc', ALL_NETRC_FILE_SCENARIOS)
86135
def test_toplevel_tokens(self, make_nrc):
87136
nrc = make_nrc("""\
88137
machine host.domain.com login log1 password pass1 account acct1
@@ -91,7 +140,7 @@ def test_toplevel_tokens(self, make_nrc):
91140
self.assertEqual(nrc.hosts['host.domain.com'], ('log1', 'acct1', 'pass1'))
92141
self.assertEqual(nrc.hosts['default'], ('log2', 'acct2', 'pass2'))
93142

94-
@support.subTests('make_nrc', ALL_STRATEGIES)
143+
@support.subTests('make_nrc', ALL_NETRC_FILE_SCENARIOS)
95144
def test_macros(self, make_nrc):
96145
data = """\
97146
macdef macro1
@@ -110,7 +159,7 @@ def test_macros(self, make_nrc):
110159
self.assertRaises(netrc.NetrcParseError, make_nrc,
111160
data.rstrip(' ')[:-1])
112161

113-
@support.subTests('make_nrc', ALL_STRATEGIES)
162+
@support.subTests('make_nrc', ALL_NETRC_FILE_SCENARIOS)
114163
def test_optional_tokens(self, make_nrc):
115164
data = (
116165
"machine host.domain.com",
@@ -137,7 +186,7 @@ def test_optional_tokens(self, make_nrc):
137186
nrc = make_nrc(item)
138187
self.assertEqual(nrc.hosts['default'], ('', '', ''))
139188

140-
@support.subTests('make_nrc', ALL_STRATEGIES)
189+
@support.subTests('make_nrc', ALL_NETRC_FILE_SCENARIOS)
141190
def test_invalid_tokens(self, make_nrc):
142191
data = (
143192
"invalid host.domain.com",
@@ -158,7 +207,7 @@ def _test_token_x(self, make_nrc, content, token, value):
158207
elif token == 'password':
159208
self.assertEqual(nrc.hosts['host.domain.com'], ('log', 'acct', value))
160209

161-
@support.subTests('make_nrc', ALL_STRATEGIES)
210+
@support.subTests('make_nrc', ALL_NETRC_FILE_SCENARIOS)
162211
def test_token_value_quotes(self, make_nrc):
163212
self._test_token_x(make_nrc, """\
164213
machine host.domain.com login "log" password pass account acct
@@ -170,7 +219,7 @@ def test_token_value_quotes(self, make_nrc):
170219
machine host.domain.com login log password "pass" account acct
171220
""", 'password', 'pass')
172221

173-
@support.subTests('make_nrc', ALL_STRATEGIES)
222+
@support.subTests('make_nrc', ALL_NETRC_FILE_SCENARIOS)
174223
def test_token_value_escape(self, make_nrc):
175224
self._test_token_x(make_nrc, """\
176225
machine host.domain.com login \\"log password pass account acct
@@ -191,7 +240,7 @@ def test_token_value_escape(self, make_nrc):
191240
machine host.domain.com login log password "\\"pass" account acct
192241
""", 'password', '"pass')
193242

194-
@support.subTests('make_nrc', ALL_STRATEGIES)
243+
@support.subTests('make_nrc', ALL_NETRC_FILE_SCENARIOS)
195244
def test_token_value_whitespace(self, make_nrc):
196245
self._test_token_x(make_nrc, """\
197246
machine host.domain.com login "lo g" password pass account acct
@@ -203,7 +252,7 @@ def test_token_value_whitespace(self, make_nrc):
203252
machine host.domain.com login log password pass account "acc t"
204253
""", 'account', 'acc t')
205254

206-
@support.subTests('make_nrc', ALL_STRATEGIES)
255+
@support.subTests('make_nrc', ALL_NETRC_FILE_SCENARIOS)
207256
def test_token_value_non_ascii(self, make_nrc):
208257
self._test_token_x(make_nrc, """\
209258
machine host.domain.com login \xa1\xa2 password pass account acct
@@ -215,7 +264,7 @@ def test_token_value_non_ascii(self, make_nrc):
215264
machine host.domain.com login log password \xa1\xa2 account acct
216265
""", 'password', '\xa1\xa2')
217266

218-
@support.subTests('make_nrc', ALL_STRATEGIES)
267+
@support.subTests('make_nrc', ALL_NETRC_FILE_SCENARIOS)
219268
def test_token_value_leading_hash(self, make_nrc):
220269
self._test_token_x(make_nrc, """\
221270
machine host.domain.com login #log password pass account acct
@@ -227,7 +276,7 @@ def test_token_value_leading_hash(self, make_nrc):
227276
machine host.domain.com login log password #pass account acct
228277
""", 'password', '#pass')
229278

230-
@support.subTests('make_nrc', ALL_STRATEGIES)
279+
@support.subTests('make_nrc', ALL_NETRC_FILE_SCENARIOS)
231280
def test_token_value_trailing_hash(self, make_nrc):
232281
self._test_token_x(make_nrc, """\
233282
machine host.domain.com login log# password pass account acct
@@ -239,7 +288,7 @@ def test_token_value_trailing_hash(self, make_nrc):
239288
machine host.domain.com login log password pass# account acct
240289
""", 'password', 'pass#')
241290

242-
@support.subTests('make_nrc', ALL_STRATEGIES)
291+
@support.subTests('make_nrc', ALL_NETRC_FILE_SCENARIOS)
243292
def test_token_value_internal_hash(self, make_nrc):
244293
self._test_token_x(make_nrc, """\
245294
machine host.domain.com login lo#g password pass account acct
@@ -256,31 +305,31 @@ def _test_comment(self, make_nrc, content, passwd='pass'):
256305
self.assertEqual(nrc.hosts['foo.domain.com'], ('bar', '', passwd))
257306
self.assertEqual(nrc.hosts['bar.domain.com'], ('foo', '', 'pass'))
258307

259-
@support.subTests('make_nrc', ALL_STRATEGIES)
308+
@support.subTests('make_nrc', ALL_NETRC_FILE_SCENARIOS)
260309
def test_comment_before_machine_line(self, make_nrc):
261310
self._test_comment(make_nrc, """\
262311
# comment
263312
machine foo.domain.com login bar password pass
264313
machine bar.domain.com login foo password pass
265314
""")
266315

267-
@support.subTests('make_nrc', ALL_STRATEGIES)
316+
@support.subTests('make_nrc', ALL_NETRC_FILE_SCENARIOS)
268317
def test_comment_before_machine_line_no_space(self, make_nrc):
269318
self._test_comment(make_nrc, """\
270319
#comment
271320
machine foo.domain.com login bar password pass
272321
machine bar.domain.com login foo password pass
273322
""")
274323

275-
@support.subTests('make_nrc', ALL_STRATEGIES)
324+
@support.subTests('make_nrc', ALL_NETRC_FILE_SCENARIOS)
276325
def test_comment_before_machine_line_hash_only(self, make_nrc):
277326
self._test_comment(make_nrc, """\
278327
#
279328
machine foo.domain.com login bar password pass
280329
machine bar.domain.com login foo password pass
281330
""")
282331

283-
@support.subTests('make_nrc', ALL_STRATEGIES)
332+
@support.subTests('make_nrc', ALL_NETRC_FILE_SCENARIOS)
284333
def test_comment_after_machine_line(self, make_nrc):
285334
self._test_comment(make_nrc, """\
286335
machine foo.domain.com login bar password pass
@@ -293,7 +342,7 @@ def test_comment_after_machine_line(self, make_nrc):
293342
# comment
294343
""")
295344

296-
@support.subTests('make_nrc', ALL_STRATEGIES)
345+
@support.subTests('make_nrc', ALL_NETRC_FILE_SCENARIOS)
297346
def test_comment_after_machine_line_no_space(self, make_nrc):
298347
self._test_comment(make_nrc, """\
299348
machine foo.domain.com login bar password pass
@@ -306,7 +355,7 @@ def test_comment_after_machine_line_no_space(self, make_nrc):
306355
#comment
307356
""")
308357

309-
@support.subTests('make_nrc', ALL_STRATEGIES)
358+
@support.subTests('make_nrc', ALL_NETRC_FILE_SCENARIOS)
310359
def test_comment_after_machine_line_hash_only(self, make_nrc):
311360
self._test_comment(make_nrc, """\
312361
machine foo.domain.com login bar password pass
@@ -319,21 +368,21 @@ def test_comment_after_machine_line_hash_only(self, make_nrc):
319368
#
320369
""")
321370

322-
@support.subTests('make_nrc', ALL_STRATEGIES)
371+
@support.subTests('make_nrc', ALL_NETRC_FILE_SCENARIOS)
323372
def test_comment_at_end_of_machine_line(self, make_nrc):
324373
self._test_comment(make_nrc, """\
325374
machine foo.domain.com login bar password pass # comment
326375
machine bar.domain.com login foo password pass
327376
""")
328377

329-
@support.subTests('make_nrc', ALL_STRATEGIES)
378+
@support.subTests('make_nrc', ALL_NETRC_FILE_SCENARIOS)
330379
def test_comment_at_end_of_machine_line_no_space(self, make_nrc):
331380
self._test_comment(make_nrc, """\
332381
machine foo.domain.com login bar password pass #comment
333382
machine bar.domain.com login foo password pass
334383
""")
335384

336-
@support.subTests('make_nrc', ALL_STRATEGIES)
385+
@support.subTests('make_nrc', ALL_NETRC_FILE_SCENARIOS)
337386
def test_comment_at_end_of_machine_line_pass_has_hash(self, make_nrc):
338387
self._test_comment(make_nrc, """\
339388
machine foo.domain.com login bar password #pass #comment
@@ -364,7 +413,7 @@ def test_non_anonymous_security(self):
364413
@unittest.skipUnless(os.name == 'posix', 'POSIX only test')
365414
@unittest.skipIf(pwd is None, 'security check requires pwd module')
366415
@support.os_helper.skip_unless_working_chmod
367-
@support.subTests('make_nrc', ALL_STRATEGIES)
416+
@support.subTests('make_nrc', ALL_NETRC_FILE_SCENARIOS)
368417
def test_anonymous_security(self, make_nrc):
369418
# This test is incomplete since we are normally not run as root and
370419
# therefore can't test the file ownership being wrong.

0 commit comments

Comments
 (0)