From 9846f3f1eac0835e9d1870f41ea0c03f3006a687 Mon Sep 17 00:00:00 2001 From: Harvey Falcic Date: Wed, 16 Apr 2014 11:13:21 -0400 Subject: [PATCH 1/3] Python 3 byte string output: use errors="surrogateescape" ... if available on the version of Python that's in use. This allows obtaining the original byte string (and potentially trying a fallback encoding) if the bytes can't be decoded as UTF-8. Previously, a UnicodeDecodeError would be raised with no way to treat the data as bytes or try another codec. --- Lib/python/pystrings.swg | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Lib/python/pystrings.swg b/Lib/python/pystrings.swg index f6a4eba8a..2b14547ad 100644 --- a/Lib/python/pystrings.swg +++ b/Lib/python/pystrings.swg @@ -89,7 +89,11 @@ SWIG_FromCharPtrAndSize(const char* carray, size_t size) SWIG_InternalNewPointerObj(%const_cast(carray,char *), pchar_descriptor, 0) : SWIG_Py_Void(); } else { %#if PY_VERSION_HEX >= 0x03000000 +%#if PY_VERSION_HEX >= 0x03010000 + return PyUnicode_DecodeUTF8(carray, %numeric_cast(size,int), "surrogateescape"); +%#else return PyUnicode_FromStringAndSize(carray, %numeric_cast(size,int)); +%#endif %#else return PyString_FromStringAndSize(carray, %numeric_cast(size,int)); %#endif From 791f070e66ee4cf96f0df0a4d00e1fe688f6bb80 Mon Sep 17 00:00:00 2001 From: Harvey Falcic Date: Thu, 22 May 2014 22:53:07 -0400 Subject: [PATCH 2/3] Add "unicode_strings" test case for new Python 3 behavior --- Examples/test-suite/common.mk | 1 + .../test-suite/python/unicode_strings_runme.py | 4 ++++ Examples/test-suite/unicode_strings.i | 15 +++++++++++++++ 3 files changed, 20 insertions(+) create mode 100644 Examples/test-suite/python/unicode_strings_runme.py create mode 100644 Examples/test-suite/unicode_strings.i diff --git a/Examples/test-suite/common.mk b/Examples/test-suite/common.mk index bfb960fe5..bc1ca5cb8 100644 --- a/Examples/test-suite/common.mk +++ b/Examples/test-suite/common.mk @@ -473,6 +473,7 @@ CPP_TEST_CASES += \ typemap_various \ typename \ types_directive \ + unicode_strings \ union_scope \ using1 \ using2 \ diff --git a/Examples/test-suite/python/unicode_strings_runme.py b/Examples/test-suite/python/unicode_strings_runme.py new file mode 100644 index 000000000..2d26599aa --- /dev/null +++ b/Examples/test-suite/python/unicode_strings_runme.py @@ -0,0 +1,4 @@ +import unicode_strings + +unicode_strings.test_c_str() +unicode_strings.test_std_string() diff --git a/Examples/test-suite/unicode_strings.i b/Examples/test-suite/unicode_strings.i new file mode 100644 index 000000000..f4a8b8b50 --- /dev/null +++ b/Examples/test-suite/unicode_strings.i @@ -0,0 +1,15 @@ +%module unicode_strings + +%include + +%inline %{ + +const char* test_c_str(void) { + return "h\xe9llo"; +} + +std::string test_std_string(void) { + return std::string("h\xe9llo"); +} + +%} From 5fc851a1e0928d5c179c2b9336e76d5e19324d25 Mon Sep 17 00:00:00 2001 From: Harvey Falcic Date: Fri, 23 May 2014 15:24:35 -0400 Subject: [PATCH 3/3] Add Python 3 'surrogateescape' documentation --- Doc/Manual/Python.html | 87 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/Doc/Manual/Python.html b/Doc/Manual/Python.html index bdb1ada30..45725065d 100644 --- a/Doc/Manual/Python.html +++ b/Doc/Manual/Python.html @@ -116,6 +116,7 @@
  • Function annotation
  • Buffer interface
  • Abstract base classes +
  • Byte string output conversion @@ -5928,6 +5929,92 @@ For details of abstract base class, please see PEP 3119.

    +

    35.12.4 Byte string output conversion

    + + +

    +By default, any byte string (char* or std::string) returned +from C or C++ code is decoded to text as UTF-8. This decoding uses the +surrogateescape error handler under Python 3.1 or higher -- this +error handler decodes invalid byte sequences to high surrogate characters +in the range U+DC80 to U+DCFF. + +As an example, consider the following SWIG interface, which exposes a byte +string that cannot be completely decoded as UTF-8: +

    + +
    +%module example
    +
    +%include <std_string.i>
    +
    +%inline %{
    +
    +const char* non_utf8_c_str(void) {
    +        return "h\xe9llo w\xc3\xb6rld";
    +}
    +
    +%}
    +
    + +

    +When this method is called from Python 3, the return value is the following +text string: +

    + +
    +>>> s = test.non_utf8_c_str()
    +>>> s
    +'h\udce9llo wörld'
    +
    + +

    +Since the C string contains bytes that cannot be decoded as UTF-8, those raw +bytes are represented as high surrogate characters that can be used to obtain +the original byte sequence: +

    + +
    +>>> b = s.encode('utf-8', errors='surrogateescape')
    +>>> b
    +b'h\xe9llo w\xc3\xb6rld'
    +
    + +

    +One can then attempt a different encoding, if desired (or simply leave the +byte string as a raw sequence of bytes for use in binary protocols): +

    + +
    +>>> b.decode('latin-1')
    +'héllo wörld'
    +
    + +

    +Note, however, that text strings containing surrogate characters are rejected +with the default strict codec error handler. For example: +

    + +
    +>>> with open('test', 'w') as f:
    +...     print(s, file=f)
    +...
    +Traceback (most recent call last):
    +  File "<stdin>", line 2, in <module>
    +UnicodeEncodeError: 'utf-8' codec can't encode character '\udce9' in position 1: surrogates not allowed
    +
    + +

    +This requires the user to check most strings returned by SWIG bindings, but +the alternative is for a non-UTF8 byte string to be completely inaccessible +in Python 3 code. +

    + +

    +For more details about the surrogateescape error handler, please see +PEP 383. +

    +