.. function:: putch(char)
- Print the byte string *char* to the console without buffering.
+ Print the byte string *char* to the console without buffering. Raises
+ :exc:`OSError` on failure, for example when the process has no console
+ attached.
+
+ .. versionchanged:: next
+ Failures are now reported by raising :exc:`OSError` instead of being
+ silently ignored.
.. function:: putwch(unicode_char)
Wide char variant of :func:`putch`, accepting a Unicode value.
+ .. versionchanged:: next
+ Failures are now reported by raising :exc:`OSError` instead of being
+ silently ignored.
+
.. function:: ungetch(char)
c_encoded = b'\x57\x5b' # utf-16-le (which windows internally used) encoded char for this CJK char
+def has_console():
+ # A process created without a console (for example by pythonw.exe, or with
+ # the DETACHED_PROCESS creation flag) cannot write to the console.
+ try:
+ with open('CONOUT$', 'w'):
+ return True
+ except OSError:
+ return False
+
+
+requires_console = unittest.skipUnless(has_console(), 'requires a console')
+
+
class TestConsoleIO(unittest.TestCase):
# CREATE_NEW_CONSOLE creates a "popup" window.
@requires_resource('gui')
def test_getwche(self):
self.check_getwch('getwche')
+ @requires_console
def test_putch(self):
msvcrt.putch(b'c')
+ @requires_console
def test_putwch(self):
msvcrt.putwch(c)
+ def test_putch_without_console(self):
+ # gh-69573: putch() and putwch() must report the error instead of
+ # silently ignoring it when the process has no console attached.
+ code = dedent('''
+ import msvcrt
+ import sys
+
+ for name, arg in (('putch', b'c'), ('putwch', 'c')):
+ func = getattr(msvcrt, name)
+ try:
+ func(arg)
+ except OSError:
+ pass
+ else:
+ sys.exit(f'msvcrt.{name}() did not raise OSError')
+ ''')
+ # DETACHED_PROCESS: the child process is created without a console.
+ proc = subprocess.run([sys.executable, '-c', code],
+ creationflags=subprocess.DETACHED_PROCESS,
+ capture_output=True, text=True)
+ self.assertEqual(proc.returncode, 0, proc.stderr)
+
class TestOther(unittest.TestCase):
def test_heap_min(self):
--- /dev/null
+:func:`msvcrt.putch` and :func:`msvcrt.putwch` now check the return value of
+the underlying ``_putch()`` and ``_putwch()`` C functions and raise
+:exc:`OSError` on failure, for example when the process has no console
+attached, instead of silently ignoring the error.
#endif /* MS_WINDOWS_DESKTOP */
+/* Raise an OSError for a failed _putch()/_putwch() call.
+
+ These functions fail, for example, when the process has no console
+ attached, but the CRT reports the failure without setting errno (and
+ without setting the Windows last error either), so fall back to a
+ generic error message in that case. */
+static PyObject *
+set_console_write_error(void)
+{
+ if (errno != 0) {
+ return PyErr_SetFromErrno(PyExc_OSError);
+ }
+ PyErr_SetString(PyExc_OSError, "write to console failed");
+ return NULL;
+}
+
/*[clinic input]
msvcrt.putch
msvcrt_putch_impl(PyObject *module, char char_value)
/*[clinic end generated code: output=92ec9b81012d8f60 input=ec078dd10cb054d6]*/
{
+ int res;
+
_Py_BEGIN_SUPPRESS_IPH
- _putch(char_value);
+ errno = 0;
+ res = _putch(char_value);
_Py_END_SUPPRESS_IPH
+
+ if (res == EOF) {
+ return set_console_write_error();
+ }
Py_RETURN_NONE;
}
msvcrt_putwch_impl(PyObject *module, int unicode_char)
/*[clinic end generated code: output=a3bd1a8951d28eee input=996ccd0bbcbac4c3]*/
{
+ wint_t res;
+
_Py_BEGIN_SUPPRESS_IPH
- _putwch(unicode_char);
+ errno = 0;
+ res = _putwch(unicode_char);
_Py_END_SUPPRESS_IPH
- Py_RETURN_NONE;
+ if (res == WEOF) {
+ return set_console_write_error();
+ }
+ Py_RETURN_NONE;
}
#endif /* MS_WINDOWS_DESKTOP */