Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit dbb1019

Browse files
Issue #6815: os.path.expandvars() now supports non-ASCII environment
variables names and values.
1 parent 61e2493 commit dbb1019

5 files changed

Lines changed: 96 additions & 42 deletions

File tree

Lib/ntpath.py

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ def expandvars(path):
362362
percent = b'%'
363363
brace = b'{'
364364
dollar = b'$'
365+
environ = getattr(os, 'environb', None)
365366
else:
366367
if '$' not in path and '%' not in path:
367368
return path
@@ -371,6 +372,7 @@ def expandvars(path):
371372
percent = '%'
372373
brace = '{'
373374
dollar = '$'
375+
environ = os.environ
374376
res = path[:0]
375377
index = 0
376378
pathlen = len(path)
@@ -399,14 +401,13 @@ def expandvars(path):
399401
index = pathlen - 1
400402
else:
401403
var = path[:index]
402-
if isinstance(path, bytes):
403-
var = var.decode('ascii')
404-
if var in os.environ:
405-
value = os.environ[var]
406-
else:
407-
value = '%' + var + '%'
408-
if isinstance(path, bytes):
409-
value = value.encode('ascii')
404+
try:
405+
if environ is None:
406+
value = os.fsencode(os.environ[os.fsdecode(var)])
407+
else:
408+
value = environ[var]
409+
except KeyError:
410+
value = percent + var + percent
410411
res += value
411412
elif c == dollar: # variable or '$$'
412413
if path[index + 1:index + 2] == dollar:
@@ -420,39 +421,40 @@ def expandvars(path):
420421
index = path.index(b'}')
421422
else:
422423
index = path.index('}')
423-
var = path[:index]
424-
if isinstance(path, bytes):
425-
var = var.decode('ascii')
426-
if var in os.environ:
427-
value = os.environ[var]
428-
else:
429-
value = '${' + var + '}'
430-
if isinstance(path, bytes):
431-
value = value.encode('ascii')
432-
res += value
433424
except ValueError:
434425
if isinstance(path, bytes):
435426
res += b'${' + path
436427
else:
437428
res += '${' + path
438429
index = pathlen - 1
430+
else:
431+
var = path[:index]
432+
try:
433+
if environ is None:
434+
value = os.fsencode(os.environ[os.fsdecode(var)])
435+
else:
436+
value = environ[var]
437+
except KeyError:
438+
if isinstance(path, bytes):
439+
value = b'${' + var + b'}'
440+
else:
441+
value = '${' + var + '}'
442+
res += value
439443
else:
440-
var = ''
444+
var = path[:0]
441445
index += 1
442446
c = path[index:index + 1]
443447
while c and c in varchars:
444-
if isinstance(path, bytes):
445-
var += c.decode('ascii')
446-
else:
447-
var += c
448+
var += c
448449
index += 1
449450
c = path[index:index + 1]
450-
if var in os.environ:
451-
value = os.environ[var]
452-
else:
453-
value = '$' + var
454-
if isinstance(path, bytes):
455-
value = value.encode('ascii')
451+
try:
452+
if environ is None:
453+
value = os.fsencode(os.environ[os.fsdecode(var)])
454+
else:
455+
value = environ[var]
456+
except KeyError:
457+
value = dollar + var
456458
res += value
457459
if c:
458460
index -= 1

Lib/posixpath.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ def expandvars(path):
300300
search = _varprogb.search
301301
start = b'{'
302302
end = b'}'
303+
environ = getattr(os, 'environb', None)
303304
else:
304305
if '$' not in path:
305306
return path
@@ -309,6 +310,7 @@ def expandvars(path):
309310
search = _varprog.search
310311
start = '{'
311312
end = '}'
313+
environ = os.environ
312314
i = 0
313315
while True:
314316
m = search(path, i)
@@ -318,18 +320,18 @@ def expandvars(path):
318320
name = m.group(1)
319321
if name.startswith(start) and name.endswith(end):
320322
name = name[1:-1]
321-
if isinstance(name, bytes):
322-
name = str(name, 'ASCII')
323-
if name in os.environ:
323+
try:
324+
if environ is None:
325+
value = os.fsencode(os.environ[os.fsdecode(var)])
326+
else:
327+
value = environ[name]
328+
except KeyError:
329+
i = j
330+
else:
324331
tail = path[j:]
325-
value = os.environ[name]
326-
if isinstance(path, bytes):
327-
value = value.encode('ASCII')
328332
path = path[:i] + value
329333
i = len(path)
330334
path += tail
331-
else:
332-
i = j
333335
return path
334336

335337

Lib/test/test_genericpath.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,6 @@ def test_expandvars(self):
248248
self.assertEqual(expandvars("$[foo]bar"), "$[foo]bar")
249249
self.assertEqual(expandvars("$bar bar"), "$bar bar")
250250
self.assertEqual(expandvars("$?bar"), "$?bar")
251-
self.assertEqual(expandvars("${foo}bar"), "barbar")
252251
self.assertEqual(expandvars("$foo}bar"), "bar}bar")
253252
self.assertEqual(expandvars("${foo"), "${foo")
254253
self.assertEqual(expandvars("${{foo}}"), "baz1}")
@@ -261,13 +260,40 @@ def test_expandvars(self):
261260
self.assertEqual(expandvars(b"$[foo]bar"), b"$[foo]bar")
262261
self.assertEqual(expandvars(b"$bar bar"), b"$bar bar")
263262
self.assertEqual(expandvars(b"$?bar"), b"$?bar")
264-
self.assertEqual(expandvars(b"${foo}bar"), b"barbar")
265263
self.assertEqual(expandvars(b"$foo}bar"), b"bar}bar")
266264
self.assertEqual(expandvars(b"${foo"), b"${foo")
267265
self.assertEqual(expandvars(b"${{foo}}"), b"baz1}")
268266
self.assertEqual(expandvars(b"$foo$foo"), b"barbar")
269267
self.assertEqual(expandvars(b"$bar$bar"), b"$bar$bar")
270268

269+
@unittest.skipUnless(support.FS_NONASCII, 'need support.FS_NONASCII')
270+
def test_expandvars_nonascii(self):
271+
if self.pathmodule.__name__ == 'macpath':
272+
self.skipTest('macpath.expandvars is a stub')
273+
expandvars = self.pathmodule.expandvars
274+
def check(value, expected):
275+
self.assertEqual(expandvars(value), expected)
276+
with support.EnvironmentVarGuard() as env:
277+
env.clear()
278+
nonascii = support.FS_NONASCII
279+
env['spam'] = nonascii
280+
env[nonascii] = 'ham' + nonascii
281+
check(nonascii, nonascii)
282+
check('$spam bar', '%s bar' % nonascii)
283+
check('${spam}bar', '%sbar' % nonascii)
284+
check('${%s}bar' % nonascii, 'ham%sbar' % nonascii)
285+
check('$bar%s bar' % nonascii, '$bar%s bar' % nonascii)
286+
check('$spam}bar', '%s}bar' % nonascii)
287+
288+
check(os.fsencode(nonascii), os.fsencode(nonascii))
289+
check(b'$spam bar', os.fsencode('%s bar' % nonascii))
290+
check(b'${spam}bar', os.fsencode('%sbar' % nonascii))
291+
check(os.fsencode('${%s}bar' % nonascii),
292+
os.fsencode('ham%sbar' % nonascii))
293+
check(os.fsencode('$bar%s bar' % nonascii),
294+
os.fsencode('$bar%s bar' % nonascii))
295+
check(b'$spam}bar', os.fsencode('%s}bar' % nonascii))
296+
271297
def test_abspath(self):
272298
self.assertIn("foo", self.pathmodule.abspath("foo"))
273299
with warnings.catch_warnings():

Lib/test/test_ntpath.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@ def tester(fn, wantResult):
2222
fn = fn.replace('["', '[b"')
2323
fn = fn.replace(", '", ", b'")
2424
fn = fn.replace(', "', ', b"')
25+
fn = os.fsencode(fn).decode('latin1')
26+
fn = fn.encode('ascii', 'backslashreplace').decode('ascii')
2527
with warnings.catch_warnings():
2628
warnings.simplefilter("ignore", DeprecationWarning)
2729
gotResult = eval(fn)
2830
if isinstance(wantResult, str):
29-
wantResult = wantResult.encode('ascii')
31+
wantResult = os.fsencode(wantResult)
3032
elif isinstance(wantResult, tuple):
31-
wantResult = tuple(r.encode('ascii') for r in wantResult)
33+
wantResult = tuple(os.fsencode(r) for r in wantResult)
3234

3335
gotResult = eval(fn)
3436
if wantResult != gotResult:
@@ -223,7 +225,6 @@ def test_expandvars(self):
223225
tester('ntpath.expandvars("$[foo]bar")', "$[foo]bar")
224226
tester('ntpath.expandvars("$bar bar")', "$bar bar")
225227
tester('ntpath.expandvars("$?bar")', "$?bar")
226-
tester('ntpath.expandvars("${foo}bar")', "barbar")
227228
tester('ntpath.expandvars("$foo}bar")', "bar}bar")
228229
tester('ntpath.expandvars("${foo")', "${foo")
229230
tester('ntpath.expandvars("${{foo}}")', "baz1}")
@@ -237,6 +238,26 @@ def test_expandvars(self):
237238
tester('ntpath.expandvars("%foo%%bar")', "bar%bar")
238239
tester('ntpath.expandvars("\'%foo%\'%bar")', "\'%foo%\'%bar")
239240

241+
@unittest.skipUnless(support.FS_NONASCII, 'need support.FS_NONASCII')
242+
def test_expandvars_nonascii(self):
243+
def check(value, expected):
244+
tester('ntpath.expandvars(%r)' % value, expected)
245+
with support.EnvironmentVarGuard() as env:
246+
env.clear()
247+
nonascii = support.FS_NONASCII
248+
env['spam'] = nonascii
249+
env[nonascii] = 'ham' + nonascii
250+
check('$spam bar', '%s bar' % nonascii)
251+
check('$%s bar' % nonascii, '$%s bar' % nonascii)
252+
check('${spam}bar', '%sbar' % nonascii)
253+
check('${%s}bar' % nonascii, 'ham%sbar' % nonascii)
254+
check('$spam}bar', '%s}bar' % nonascii)
255+
check('$%s}bar' % nonascii, '$%s}bar' % nonascii)
256+
check('%spam% bar', '%s bar' % nonascii)
257+
check('%{}% bar'.format(nonascii), 'ham%s bar' % nonascii)
258+
check('%spam%bar', '%sbar' % nonascii)
259+
check('%{}%bar'.format(nonascii), 'ham%sbar' % nonascii)
260+
240261
def test_abspath(self):
241262
# ntpath.abspath() can only be used on a system with the "nt" module
242263
# (reasonably), so we protect this test with "import nt". This allows

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ Core and Builtins
2020
Library
2121
-------
2222

23+
- Issue #6815: os.path.expandvars() now supports non-ASCII environment
24+
variables names and values.
25+
2326
- Issue #17671: Fixed a crash when use non-initialized io.BufferedRWPair.
2427
Based on patch by Stephen Tu.
2528

0 commit comments

Comments
 (0)