]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-91954: Emit EncodingWarning from locale and subprocess (GH-91977)
authorInada Naoki <songofacandy@gmail.com>
Sat, 30 Apr 2022 06:53:29 +0000 (15:53 +0900)
committerGitHub <noreply@github.com>
Sat, 30 Apr 2022 06:53:29 +0000 (15:53 +0900)
locale.getpreferredencoding() and subprocess.Popen() emit EncodingWarning

Doc/library/subprocess.rst
Lib/locale.py
Lib/subprocess.py
Lib/test/test_subprocess.py
Misc/NEWS.d/next/Library/2022-04-27-13-30-26.gh-issue-91954.cC7ga_.rst [new file with mode: 0644]

index fca55496492685d07db71cbb9f3dcf4f04e64359..6a334acc5a17cc5dcf417a905b49855c118fa229 100644 (file)
@@ -619,7 +619,7 @@ functions.
 
    If *encoding* or *errors* are specified, or *text* is true, the file objects
    *stdin*, *stdout* and *stderr* are opened in text mode with the specified
-   encoding and *errors*, as described above in :ref:`frequently-used-arguments`.
+   *encoding* and *errors*, as described above in :ref:`frequently-used-arguments`.
    The *universal_newlines* argument is equivalent  to *text* and is provided
    for backwards compatibility. By default, file objects are opened in binary mode.
 
@@ -1445,12 +1445,13 @@ This module also provides the following legacy functions from the 2.x
 none of the guarantees described above regarding security and exception
 handling consistency are valid for these functions.
 
-.. function:: getstatusoutput(cmd)
+.. function:: getstatusoutput(cmd, *, encoding=None, errors=None)
 
    Return ``(exitcode, output)`` of executing *cmd* in a shell.
 
    Execute the string *cmd* in a shell with :meth:`Popen.check_output` and
-   return a 2-tuple ``(exitcode, output)``. The locale encoding is used;
+   return a 2-tuple ``(exitcode, output)``.
+   *encoding* and *errors* are used to decode output;
    see the notes on :ref:`frequently-used-arguments` for more details.
 
    A trailing newline is stripped from the output.
@@ -1475,8 +1476,10 @@ handling consistency are valid for these functions.
       as it did in Python 3.3.3 and earlier.  exitcode has the same value as
       :attr:`~Popen.returncode`.
 
+   .. versionadded:: 3.11
+      Added *encoding* and *errors* arguments.
 
-.. function:: getoutput(cmd)
+.. function:: getoutput(cmd, *, encoding=None, errors=None)
 
    Return output (stdout and stderr) of executing *cmd* in a shell.
 
@@ -1491,6 +1494,9 @@ handling consistency are valid for these functions.
    .. versionchanged:: 3.3.4
       Windows support added
 
+   .. versionadded:: 3.11
+      Added *encoding* and *errors* arguments.
+
 
 Notes
 -----
index 170e5eea45b8ca5a5983601d3545f72da2747f2c..25eb75ac65a32ba59ef8cf07d874435b69262544 100644 (file)
@@ -655,6 +655,11 @@ try:
 except NameError:
     def getpreferredencoding(do_setlocale=True):
         """Return the charset that the user is likely using."""
+        if sys.flags.warn_default_encoding:
+            import warnings
+            warnings.warn(
+                "UTF-8 Mode affects locale.getpreferredencoding(). Consider locale.getencoding() instead.",
+                EncodingWarning, 2)
         if sys.flags.utf8_mode:
             return 'utf-8'
         return getencoding()
@@ -663,6 +668,12 @@ else:
     def getpreferredencoding(do_setlocale=True):
         """Return the charset that the user is likely using,
         according to the system configuration."""
+
+        if sys.flags.warn_default_encoding:
+            import warnings
+            warnings.warn(
+                "UTF-8 Mode affects locale.getpreferredencoding(). Consider locale.getencoding() instead.",
+                EncodingWarning, 2)
         if sys.flags.utf8_mode:
             return 'utf-8'
 
index a5fa152715c14568d0752dfefb9bd59c26f7355d..968cfc14ddf910c39a10a4170a49a9c67ad2fd66 100644 (file)
@@ -43,6 +43,7 @@ getstatusoutput(...): Runs a command in the shell, waits for it to complete,
 import builtins
 import errno
 import io
+import locale
 import os
 import time
 import signal
@@ -344,6 +345,26 @@ def _args_from_interpreter_flags():
     return args
 
 
+def _text_encoding():
+    # Return default text encoding and emit EncodingWarning if
+    # sys.flags.warn_default_encoding is true.
+    if sys.flags.warn_default_encoding:
+        f = sys._getframe()
+        filename = f.f_code.co_filename
+        stacklevel = 2
+        while f := f.f_back:
+            if f.f_code.co_filename != filename:
+                break
+            stacklevel += 1
+        warnings.warn("'encoding' argument not specified.",
+                      EncodingWarning, stacklevel)
+
+    if sys.flags.utf8_mode:
+        return "utf-8"
+    else:
+        return locale.getencoding()
+
+
 def call(*popenargs, timeout=None, **kwargs):
     """Run command with arguments.  Wait for command to complete or
     timeout, then return the returncode attribute.
@@ -610,7 +631,7 @@ def list2cmdline(seq):
 # Various tools for executing commands and looking at their output and status.
 #
 
-def getstatusoutput(cmd):
+def getstatusoutput(cmd, *, encoding=None, errors=None):
     """Return (exitcode, output) of executing cmd in a shell.
 
     Execute the string 'cmd' in a shell with 'check_output' and
@@ -632,7 +653,8 @@ def getstatusoutput(cmd):
     (-15, '')
     """
     try:
-        data = check_output(cmd, shell=True, text=True, stderr=STDOUT)
+        data = check_output(cmd, shell=True, text=True, stderr=STDOUT,
+                            encoding=encoding, errors=errors)
         exitcode = 0
     except CalledProcessError as ex:
         data = ex.output
@@ -641,7 +663,7 @@ def getstatusoutput(cmd):
         data = data[:-1]
     return exitcode, data
 
-def getoutput(cmd):
+def getoutput(cmd, *, encoding=None, errors=None):
     """Return output (stdout or stderr) of executing cmd in a shell.
 
     Like getstatusoutput(), except the exit status is ignored and the return
@@ -651,7 +673,8 @@ def getoutput(cmd):
     >>> subprocess.getoutput('ls /bin/ls')
     '/bin/ls'
     """
-    return getstatusoutput(cmd)[1]
+    return getstatusoutput(cmd, encoding=encoding, errors=errors)[1]
+
 
 
 def _use_posix_spawn():
@@ -858,13 +881,8 @@ class Popen:
                 errread = msvcrt.open_osfhandle(errread.Detach(), 0)
 
         self.text_mode = encoding or errors or text or universal_newlines
-
-        # PEP 597: We suppress the EncodingWarning in subprocess module
-        # for now (at Python 3.10), because we focus on files for now.
-        # This will be changed to encoding = io.text_encoding(encoding)
-        # in the future.
         if self.text_mode and encoding is None:
-            self.encoding = encoding = "locale"
+            self.encoding = encoding = _text_encoding()
 
         # How long to resume waiting on a child after the first ^C.
         # There is no right value for this.  The purpose is to be polite
index 99b5947e54be685c8abbae0ac59d708ac4461f7e..5814a6d924e128407ddfc513b478e77ae8d7bd8c 100644 (file)
@@ -1733,6 +1733,20 @@ class RunFuncTestCase(BaseTestCase):
                         msg="TimeoutExpired was delayed! Bad traceback:\n```\n"
                         f"{stacks}```")
 
+    def test_encoding_warning(self):
+        code = textwrap.dedent("""\
+            from subprocess import *
+            args = ["echo", "hello"]
+            run(args, text=True)
+            check_output(args, text=True)
+            """)
+        cp = subprocess.run([sys.executable, "-Xwarn_default_encoding", "-c", code],
+                            capture_output=True)
+        lines = cp.stderr.splitlines()
+        self.assertEqual(len(lines), 2)
+        self.assertTrue(lines[0].startswith(b"<string>:3: EncodingWarning: "))
+        self.assertTrue(lines[1].startswith(b"<string>:4: EncodingWarning: "))
+
 
 def _get_test_grp_name():
     for name_group in ('staff', 'nogroup', 'grp', 'nobody', 'nfsnobody'):
diff --git a/Misc/NEWS.d/next/Library/2022-04-27-13-30-26.gh-issue-91954.cC7ga_.rst b/Misc/NEWS.d/next/Library/2022-04-27-13-30-26.gh-issue-91954.cC7ga_.rst
new file mode 100644 (file)
index 0000000..b63db25
--- /dev/null
@@ -0,0 +1,2 @@
+Add *encoding* and *errors* arguments to :func:`subprocess.getoutput` and
+:func:`subprocess.getstatusoutput`.