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

Skip to content

Commit 7dfaa27

Browse files
Issue #6815: os.path.expandvars() now supports non-ASCII environment
variables names and values.
2 parents b58f053 + dbb1019 commit 7dfaa27

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
@@ -377,6 +377,7 @@ def expandvars(path):
377377
percent = b'%'
378378
brace = b'{'
379379
dollar = b'$'
380+
environ = getattr(os, 'environb', None)
380381
else:
381382
if '$' not in path and '%' not in path:
382383
return path
@@ -386,6 +387,7 @@ def expandvars(path):
386387
percent = '%'
387388
brace = '{'
388389
dollar = '$'
390+
environ = os.environ
389391
res = path[:0]
390392
index = 0
391393
pathlen = len(path)
@@ -414,14 +416,13 @@ def expandvars(path):
414416
index = pathlen - 1
415417
else:
416418
var = path[:index]
417-
if isinstance(path, bytes):
418-
var = var.decode('ascii')
419-
if var in os.environ:
420-
value = os.environ[var]
421-
else:
422-
value = '%' + var + '%'
423-
if isinstance(path, bytes):
424-
value = value.encode('ascii')
419+
try:
420+
if environ is None:
421+
value = os.fsencode(os.environ[os.fsdecode(var)])
422+
else:
423+
value = environ[var]
424+
except KeyError:
425+
value = percent + var + percent
425426
res += value
426427
elif c == dollar: # variable or '$$'
427428
if path[index + 1:index + 2] == dollar:
@@ -435,39 +436,40 @@ def expandvars(path):
435436
index = path.index(b'}')
436437
else:
437438
index = path.index('}')
438-
var = path[:index]
439-
if isinstance(path, bytes):
440-
var = var.decode('ascii')
441-
if var in os.environ:
442-
value = os.environ[var]
443-
else:
444-
value = '${' + var + '}'
445-
if isinstance(path, bytes):
446-
value = value.encode('ascii')
447-
res += value
448439
except ValueError:
449440
if isinstance(path, bytes):
450441
res += b'${' + path
451442
else:
452443
res += '${' + path
453444
index = pathlen - 1
445+
else:
446+
var = path[:index]
447+
try:
448+
if environ is None:
449+
value = os.fsencode(os.environ[os.fsdecode(var)])
450+
else:
451+
value = environ[var]
452+
except KeyError:
453+
if isinstance(path, bytes):
454+
value = b'${' + var + b'}'
455+
else:
456+
value = '${' + var + '}'
457+
res += value
454458
else:
455-
var = ''
459+
var = path[:0]
456460
index += 1
457461
c = path[index:index + 1]
458462
while c and c in varchars:
459-
if isinstance(path, bytes):
460-
var += c.decode('ascii')
461-
else:
462-
var += c
463+
var += c
463464
index += 1
464465
c = path[index:index + 1]
465-
if var in os.environ:
466-
value = os.environ[var]
467-
else:
468-
value = '$' + var
469-
if isinstance(path, bytes):
470-
value = value.encode('ascii')
466+
try:
467+
if environ is None:
468+
value = os.fsencode(os.environ[os.fsdecode(var)])
469+
else:
470+
value = environ[var]
471+
except KeyError:
472+
value = dollar + var
471473
res += value
472474
if c:
473475
index -= 1

Lib/posixpath.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ def expandvars(path):
279279
search = _varprogb.search
280280
start = b'{'
281281
end = b'}'
282+
environ = getattr(os, 'environb', None)
282283
else:
283284
if '$' not in path:
284285
return path
@@ -288,6 +289,7 @@ def expandvars(path):
288289
search = _varprog.search
289290
start = '{'
290291
end = '}'
292+
environ = os.environ
291293
i = 0
292294
while True:
293295
m = search(path, i)
@@ -297,18 +299,18 @@ def expandvars(path):
297299
name = m.group(1)
298300
if name.startswith(start) and name.endswith(end):
299301
name = name[1:-1]
300-
if isinstance(name, bytes):
301-
name = str(name, 'ASCII')
302-
if name in os.environ:
302+
try:
303+
if environ is None:
304+
value = os.fsencode(os.environ[os.fsdecode(var)])
305+
else:
306+
value = environ[name]
307+
except KeyError:
308+
i = j
309+
else:
303310
tail = path[j:]
304-
value = os.environ[name]
305-
if isinstance(path, bytes):
306-
value = value.encode('ASCII')
307311
path = path[:i] + value
308312
i = len(path)
309313
path += tail
310-
else:
311-
i = j
312314
return path
313315

314316

Lib/test/test_genericpath.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,6 @@ def test_expandvars(self):
329329
self.assertEqual(expandvars("$[foo]bar"), "$[foo]bar")
330330
self.assertEqual(expandvars("$bar bar"), "$bar bar")
331331
self.assertEqual(expandvars("$?bar"), "$?bar")
332-
self.assertEqual(expandvars("${foo}bar"), "barbar")
333332
self.assertEqual(expandvars("$foo}bar"), "bar}bar")
334333
self.assertEqual(expandvars("${foo"), "${foo")
335334
self.assertEqual(expandvars("${{foo}}"), "baz1}")
@@ -342,13 +341,40 @@ def test_expandvars(self):
342341
self.assertEqual(expandvars(b"$[foo]bar"), b"$[foo]bar")
343342
self.assertEqual(expandvars(b"$bar bar"), b"$bar bar")
344343
self.assertEqual(expandvars(b"$?bar"), b"$?bar")
345-
self.assertEqual(expandvars(b"${foo}bar"), b"barbar")
346344
self.assertEqual(expandvars(b"$foo}bar"), b"bar}bar")
347345
self.assertEqual(expandvars(b"${foo"), b"${foo")
348346
self.assertEqual(expandvars(b"${{foo}}"), b"baz1}")
349347
self.assertEqual(expandvars(b"$foo$foo"), b"barbar")
350348
self.assertEqual(expandvars(b"$bar$bar"), b"$bar$bar")
351349

350+
@unittest.skipUnless(support.FS_NONASCII, 'need support.FS_NONASCII')
351+
def test_expandvars_nonascii(self):
352+
if self.pathmodule.__name__ == 'macpath':
353+
self.skipTest('macpath.expandvars is a stub')
354+
expandvars = self.pathmodule.expandvars
355+
def check(value, expected):
356+
self.assertEqual(expandvars(value), expected)
357+
with support.EnvironmentVarGuard() as env:
358+
env.clear()
359+
nonascii = support.FS_NONASCII
360+
env['spam'] = nonascii
361+
env[nonascii] = 'ham' + nonascii
362+
check(nonascii, nonascii)
363+
check('$spam bar', '%s bar' % nonascii)
364+
check('${spam}bar', '%sbar' % nonascii)
365+
check('${%s}bar' % nonascii, 'ham%sbar' % nonascii)
366+
check('$bar%s bar' % nonascii, '$bar%s bar' % nonascii)
367+
check('$spam}bar', '%s}bar' % nonascii)
368+
369+
check(os.fsencode(nonascii), os.fsencode(nonascii))
370+
check(b'$spam bar', os.fsencode('%s bar' % nonascii))
371+
check(b'${spam}bar', os.fsencode('%sbar' % nonascii))
372+
check(os.fsencode('${%s}bar' % nonascii),
373+
os.fsencode('ham%sbar' % nonascii))
374+
check(os.fsencode('$bar%s bar' % nonascii),
375+
os.fsencode('$bar%s bar' % nonascii))
376+
check(b'$spam}bar', os.fsencode('%s}bar' % nonascii))
377+
352378
def test_abspath(self):
353379
self.assertIn("foo", self.pathmodule.abspath("foo"))
354380
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
@@ -15,6 +15,9 @@ Core and Builtins
1515
Library
1616
-------
1717

18+
- Issue #6815: os.path.expandvars() now supports non-ASCII environment
19+
variables names and values.
20+
1821
- Issue #17671: Fixed a crash when use non-initialized io.BufferedRWPair.
1922
Based on patch by Stephen Tu.
2023

0 commit comments

Comments
 (0)