2

I'd like to change the way python is handling the language of the returned error strings, for example for WinError/OSError exception. I'm working with ctypes and WinError is defined as

def WinError(code=None, descr=None):
    if code is None:
        code = GetLastError()
    if descr is None:
        descr = FormatError(code).strip()
    return OSError(None, descr, None, code)

The FormatError function is extracted from ..\Python34\DLLs_ctypes.pyd and is the python version of C++ FormatMessage function.

DWORD WINAPI FormatMessage(
  _In_     DWORD   dwFlags,
  _In_opt_ LPCVOID lpSource,
  _In_     DWORD   dwMessageId,
  _In_     DWORD   dwLanguageId,
  _Out_    LPTSTR  lpBuffer,
  _In_     DWORD   nSize,
  _In_opt_ va_list *Arguments
);

Ideally the python equivalent should have the same parameters, but FormatError can only have one parameter and this is FormatError([code]). I found the source code of ctypes written in c++. There is a file called callproc.c and there is the FormatError function defined as

static TCHAR *FormatError(DWORD code)
{
    TCHAR *lpMsgBuf;
    DWORD n;
    n = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
              NULL,
              code,
              MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
              (LPTSTR) &lpMsgBuf,
              0,
              NULL);
    if (n) {
        while (isspace(lpMsgBuf[n-1]))
            --n;
        lpMsgBuf[n] = '\0'; /* rstrip() */
    }
    return lpMsgBuf;
}

LANG_NEUTRAL|SUBLANG_DEFAULT = fallback to user's default language.

Is there a way to control the language of the error strings, perhaps by setting the locale, an environmental variable or something else?

Thanks in advance!

Edit: I think i found something interesting, but i will test it later, cause i'm really sleepy. This should work or not? https://gist.github.com/EBNull/6135237

FalloutBoy
  • 964
  • 1
  • 9
  • 22
  • http://stackoverflow.com/questions/28180159/how-do-i-can-format-exception-stacktraces-in-python-logging gives an example of a custom formatter that subclasses logging.Formatter and overrides its format. –  Sep 13 '15 at 07:02

1 Answers1

0

On Windows Vista and later you can call SetThreadPreferredUILanguages to set a list of up to 5 languages for the current thread, ordered by preference. Windows 7+ also has SetProcessPreferredUILanguages to change this setting for the entire process.

Windows language packs provide the required Multilingual User Interface (MUI) resource libraries. For the system libraries, you'll find the MUI resources in subdirectories of System32 that are named by language. For example, if a Spanish language pack is installed, then System32\es-ES\kernel32.dll.mui should exist. For more info on MUI, read Understanding MUI and MUI Fundamental Concepts Explained.

Here's an example ctypes definition for calling SetThreadPreferredUILanguages:

import ctypes
from ctypes import wintypes

kernel32 = ctypes.WinDLL('kernel32')    

MUI_LANGUAGE_ID    = 0x004
MUI_LANGUAGE_NAME  = 0x008
MUI_RESET_FILTERS  = 0x001
MUI_CONSOLE_FILTER = 0x100
MUI_COMPLEX_SCRIPT_FILTER = 0x200

def errcheck_bool(result, func, args):
    if not result:
        raise ctypes.WinError(ctypes.get_last_error())
    return args

kernel32.SetThreadPreferredUILanguages = ctypes.WINFUNCTYPE(
    wintypes.BOOL, 
    wintypes.DWORD,
    wintypes.LPCWSTR,
    wintypes.PULONG,
    use_last_error=True
)(('SetThreadPreferredUILanguages', kernel32),
  ((1, 'flags', MUI_LANGUAGE_NAME),
   (1, 'languages', None),
   (2, 'pulNumLanguages')))

kernel32.SetThreadPreferredUILanguages.errcheck = errcheck_bool

I defined the pulNumLanguages parameter as an out parameter (i.e. type 2), so ctypes takes care of passing a reference to a temporary c_ulong, and then returns the value as the high-level Python result.

The low-level BOOL result of the C call is handled by errcheck_bool, which potentially raises an exception based on the Windows LastError code. To ensure the accuracy of this value, the native call is configured with use_last_error=True, which captures the LastError value in a thread-local storage variable. This captured error value is retrieved by calling ctypes.get_last_error.

The list of languages is passed as a single wide-character string, with each element terminated by a nul character (i.e. '\0'). Using language names instead of the old language IDs is recommeneded, so I've set MUI_LANGUAGE_NAME as the default flag value.

Example:

>>> ERROR_FILE_NOT_FOUND = 2
>>> kernel32.SetThreadPreferredUILanguages(languages='es-es\0en-us\0')
2
>>> ctypes.FormatError(ERROR_FILE_NOT_FOUND)
'El sistema no puede encontrar el archivo especificado.'

Reset:

>>> kernel32.SetThreadPreferredUILanguages(flags=0)
0
>>> ctypes.FormatError(ERROR_FILE_NOT_FOUND)
'The system cannot find the file specified.'    

Unfortunately this doesn't set the language used by C runtime error messages. Generally CRT error messages are used on Windows whenever Python relies on the CRT's low-level POSIX API, e.g. _open / _wopen:

>>> kernel32.SetThreadPreferredUILanguages(languages='es-es\0en-us\0')
2
>>> open('spam')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'spam'

The above ENOENT message is hard-coded in the CRT's _sys_errlist array. For example here's the first 10 entries of this array in Python 3.5 (using the new universal CRT):

ucrtbase = ctypes.CDLL('ucrtbase')
ucrtbase.__sys_nerr.restype = ctypes.POINTER(ctypes.c_int)
nerr = ucrtbase.__sys_nerr()[0]
ucrtbase.__sys_errlist.restype = ctypes.POINTER(ctypes.c_char_p * nerr)
errlist = ucrtbase.__sys_errlist()[0]

>>> print(*(m.decode() for m in errlist[:10]), sep='\n')
No error
Operation not permitted
No such file or directory
No such process
Interrupted function call
Input/output error
No such device or address
Arg list too long
Exec format error
Bad file descriptor

(Don't actually use ctypes for this. The above was just to show the implementation. In Python use os.strerror to get these CRT error messages.)

In this case you'd have to handle the exception by re-raising it with a custom or machine translated error message.

Eryk Sun
  • 33,190
  • 5
  • 92
  • 111