11import netrc , os , unittest , sys , textwrap , tempfile
22
33from test import support
4- from unittest import mock
54from contextlib import ExitStack
65
76try :
1110
1211
1312class 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
3764class 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
73122class 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