Skip to content

Commit 354ace8

Browse files
authored
gh-91954: Emit EncodingWarning from locale and subprocess (GH-91977)
locale.getpreferredencoding() and subprocess.Popen() emit EncodingWarning
1 parent c7b7f12 commit 354ace8

File tree

5 files changed

+65
-14
lines changed

5 files changed

+65
-14
lines changed

Doc/library/subprocess.rst

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,7 @@ functions.
619619

620620
If *encoding* or *errors* are specified, or *text* is true, the file objects
621621
*stdin*, *stdout* and *stderr* are opened in text mode with the specified
622-
encoding and *errors*, as described above in :ref:`frequently-used-arguments`.
622+
*encoding* and *errors*, as described above in :ref:`frequently-used-arguments`.
623623
The *universal_newlines* argument is equivalent to *text* and is provided
624624
for backwards compatibility. By default, file objects are opened in binary mode.
625625

@@ -1445,12 +1445,13 @@ This module also provides the following legacy functions from the 2.x
14451445
none of the guarantees described above regarding security and exception
14461446
handling consistency are valid for these functions.
14471447

1448-
.. function:: getstatusoutput(cmd)
1448+
.. function:: getstatusoutput(cmd, *, encoding=None, errors=None)
14491449

14501450
Return ``(exitcode, output)`` of executing *cmd* in a shell.
14511451

14521452
Execute the string *cmd* in a shell with :meth:`Popen.check_output` and
1453-
return a 2-tuple ``(exitcode, output)``. The locale encoding is used;
1453+
return a 2-tuple ``(exitcode, output)``.
1454+
*encoding* and *errors* are used to decode output;
14541455
see the notes on :ref:`frequently-used-arguments` for more details.
14551456

14561457
A trailing newline is stripped from the output.
@@ -1475,8 +1476,10 @@ handling consistency are valid for these functions.
14751476
as it did in Python 3.3.3 and earlier. exitcode has the same value as
14761477
:attr:`~Popen.returncode`.
14771478

1479+
.. versionadded:: 3.11
1480+
Added *encoding* and *errors* arguments.
14781481

1479-
.. function:: getoutput(cmd)
1482+
.. function:: getoutput(cmd, *, encoding=None, errors=None)
14801483

14811484
Return output (stdout and stderr) of executing *cmd* in a shell.
14821485

@@ -1491,6 +1494,9 @@ handling consistency are valid for these functions.
14911494
.. versionchanged:: 3.3.4
14921495
Windows support added
14931496

1497+
.. versionadded:: 3.11
1498+
Added *encoding* and *errors* arguments.
1499+
14941500

14951501
Notes
14961502
-----

Lib/locale.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,11 @@ def getencoding():
655655
except NameError:
656656
def getpreferredencoding(do_setlocale=True):
657657
"""Return the charset that the user is likely using."""
658+
if sys.flags.warn_default_encoding:
659+
import warnings
660+
warnings.warn(
661+
"UTF-8 Mode affects locale.getpreferredencoding(). Consider locale.getencoding() instead.",
662+
EncodingWarning, 2)
658663
if sys.flags.utf8_mode:
659664
return 'utf-8'
660665
return getencoding()
@@ -663,6 +668,12 @@ def getpreferredencoding(do_setlocale=True):
663668
def getpreferredencoding(do_setlocale=True):
664669
"""Return the charset that the user is likely using,
665670
according to the system configuration."""
671+
672+
if sys.flags.warn_default_encoding:
673+
import warnings
674+
warnings.warn(
675+
"UTF-8 Mode affects locale.getpreferredencoding(). Consider locale.getencoding() instead.",
676+
EncodingWarning, 2)
666677
if sys.flags.utf8_mode:
667678
return 'utf-8'
668679

Lib/subprocess.py

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import builtins
4444
import errno
4545
import io
46+
import locale
4647
import os
4748
import time
4849
import signal
@@ -344,6 +345,26 @@ def _args_from_interpreter_flags():
344345
return args
345346

346347

348+
def _text_encoding():
349+
# Return default text encoding and emit EncodingWarning if
350+
# sys.flags.warn_default_encoding is true.
351+
if sys.flags.warn_default_encoding:
352+
f = sys._getframe()
353+
filename = f.f_code.co_filename
354+
stacklevel = 2
355+
while f := f.f_back:
356+
if f.f_code.co_filename != filename:
357+
break
358+
stacklevel += 1
359+
warnings.warn("'encoding' argument not specified.",
360+
EncodingWarning, stacklevel)
361+
362+
if sys.flags.utf8_mode:
363+
return "utf-8"
364+
else:
365+
return locale.getencoding()
366+
367+
347368
def call(*popenargs, timeout=None, **kwargs):
348369
"""Run command with arguments. Wait for command to complete or
349370
timeout, then return the returncode attribute.
@@ -610,7 +631,7 @@ def list2cmdline(seq):
610631
# Various tools for executing commands and looking at their output and status.
611632
#
612633

613-
def getstatusoutput(cmd):
634+
def getstatusoutput(cmd, *, encoding=None, errors=None):
614635
"""Return (exitcode, output) of executing cmd in a shell.
615636
616637
Execute the string 'cmd' in a shell with 'check_output' and
@@ -632,7 +653,8 @@ def getstatusoutput(cmd):
632653
(-15, '')
633654
"""
634655
try:
635-
data = check_output(cmd, shell=True, text=True, stderr=STDOUT)
656+
data = check_output(cmd, shell=True, text=True, stderr=STDOUT,
657+
encoding=encoding, errors=errors)
636658
exitcode = 0
637659
except CalledProcessError as ex:
638660
data = ex.output
@@ -641,7 +663,7 @@ def getstatusoutput(cmd):
641663
data = data[:-1]
642664
return exitcode, data
643665

644-
def getoutput(cmd):
666+
def getoutput(cmd, *, encoding=None, errors=None):
645667
"""Return output (stdout or stderr) of executing cmd in a shell.
646668
647669
Like getstatusoutput(), except the exit status is ignored and the return
@@ -651,7 +673,8 @@ def getoutput(cmd):
651673
>>> subprocess.getoutput('ls /bin/ls')
652674
'/bin/ls'
653675
"""
654-
return getstatusoutput(cmd)[1]
676+
return getstatusoutput(cmd, encoding=encoding, errors=errors)[1]
677+
655678

656679

657680
def _use_posix_spawn():
@@ -858,13 +881,8 @@ def __init__(self, args, bufsize=-1, executable=None,
858881
errread = msvcrt.open_osfhandle(errread.Detach(), 0)
859882

860883
self.text_mode = encoding or errors or text or universal_newlines
861-
862-
# PEP 597: We suppress the EncodingWarning in subprocess module
863-
# for now (at Python 3.10), because we focus on files for now.
864-
# This will be changed to encoding = io.text_encoding(encoding)
865-
# in the future.
866884
if self.text_mode and encoding is None:
867-
self.encoding = encoding = "locale"
885+
self.encoding = encoding = _text_encoding()
868886

869887
# How long to resume waiting on a child after the first ^C.
870888
# There is no right value for this. The purpose is to be polite

Lib/test/test_subprocess.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1733,6 +1733,20 @@ def test_run_with_shell_timeout_and_capture_output(self):
17331733
msg="TimeoutExpired was delayed! Bad traceback:\n```\n"
17341734
f"{stacks}```")
17351735

1736+
def test_encoding_warning(self):
1737+
code = textwrap.dedent("""\
1738+
from subprocess import *
1739+
args = ["echo", "hello"]
1740+
run(args, text=True)
1741+
check_output(args, text=True)
1742+
""")
1743+
cp = subprocess.run([sys.executable, "-Xwarn_default_encoding", "-c", code],
1744+
capture_output=True)
1745+
lines = cp.stderr.splitlines()
1746+
self.assertEqual(len(lines), 2)
1747+
self.assertTrue(lines[0].startswith(b"<string>:3: EncodingWarning: "))
1748+
self.assertTrue(lines[1].startswith(b"<string>:4: EncodingWarning: "))
1749+
17361750

17371751
def _get_test_grp_name():
17381752
for name_group in ('staff', 'nogroup', 'grp', 'nobody', 'nfsnobody'):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add *encoding* and *errors* arguments to :func:`subprocess.getoutput` and
2+
:func:`subprocess.getstatusoutput`.

0 commit comments

Comments
 (0)