[Git][NTPsec/ntpsec][master] Improve Python 3 compatibility.

Eric S. Raymond gitlab at mg.gitlab.com
Thu Oct 20 21:46:02 UTC 2016


Eric S. Raymond pushed to branch master at NTPsec / ntpsec


Commits:
f1333310 by Eric S. Raymond at 2016-10-20T17:45:27-04:00
Improve Python 3 compatibility.

- - - - -


1 changed file:

- pylib/packet.py


Changes:

=====================================
pylib/packet.py
=====================================
--- a/pylib/packet.py
+++ b/pylib/packet.py
@@ -7,6 +7,88 @@
 from __future__ import print_function, division
 import sys, socket, select, struct, curses.ascii, collections
 
+# General notes on Python 2/3 compatibility:
+#
+# This code uses the following strategy to allow it to run on both Python 2
+# and Python 3:
+#
+# - Use binary I/O to read/write data from/to files and subprocesses;
+#   where the exact bytes are important (such as in checking for
+#   modified files), use the binary data directly
+#
+# - Use latin-1 encoding to transform binary data to/from Unicode when
+#   necessary for operations where Python 3 expects Unicode; the
+#   polystr and polybytes functions are used to do this so that
+#   when running on Python 2, the byte string data is used unchanged;
+#   also, the make_wrapper function constructs a text stream that can
+#   wrap a file opened in binary mode for cases where a file object
+#   that can be passed around from function to function is needed
+#
+# - Construct custom stdin, stdout, and stderr streams when running
+#   on Python 3 that force latin-1 encoding, and wrap them around the
+#   underlying binary buffers (in Python 2, the streams are binary
+#   and are used unchanged); this ensures that the same transformation
+#   is done on data from/to the standard streams, as is done on binary
+#   data from/to files and subprocesses; the make_std_wrapper function
+#   does this
+
+master_encoding = 'latin-1'
+
+if str is bytes:  # Python 2
+    polystr = str
+    polybytes = bytes
+
+    def string_escape(s):
+        return s.decode('string_escape')
+
+    def make_wrapper(fp):
+        return fp
+
+else:  # Python 3
+    import io
+
+    def polystr(o):
+        "Polymorphic string factory function"
+        if isinstance(o, str):
+            return o
+        if not isinstance(o, bytes):
+            return str(o)
+        return str(o, encoding=master_encoding)
+
+    def polybytes(s):
+        "Polymorphic string encoding function"
+        if isinstance(s, bytes):
+            return s
+        if not isinstance(s, str):
+            return bytes(s)
+        return bytes(s, encoding=master_encoding)
+
+    def string_escape(s):
+        "Polymorphic string_escape/unicode_escape"
+        # This hack is necessary because Unicode strings in Python 3 don't
+        # have a decode method, so there's no simple way to ask it for the
+        # equivalent of decode('string_escape') in Python 2. This function
+        # assumes that it will be called with a Python 3 'str' instance
+        return s.encode(master_encoding).decode('unicode_escape')
+
+    def make_wrapper(fp):
+        "Wrapper factory function to enforce master encoding"
+        # This can be used to wrap normally binary streams for API
+        # compatibility with functions that need a text stream in
+        # Python 3; it ensures that the binary bytes are decoded using
+        # the master encoding we use to turn bytes to Unicode in
+        # polystr above
+        # newline="\n" ensures that Python 3 won't mangle line breaks
+        return io.TextIOWrapper(fp, encoding=master_encoding, newline="\n")
+
+    def make_std_wrapper(stream):
+        "Standard input/output wrapper factory function"
+        # This ensures that the encoding of standard output and standard
+        # error on Python 3 matches the master encoding we use to turn
+        # bytes to Unicode in polystr above
+        # line_buffering=True ensures that interactive command sessions work as expected
+        return io.TextIOWrapper(stream.buffer, encoding=master_encoding, newline="\n", line_buffering=True)
+
 from ntp_magic import *
 from ntp_control import *
 
@@ -295,7 +377,7 @@ class Mode6Session:
         if self.debug >= 3:
                 print("Sending %d octets\n" % len(xdata))
         try:
-            self.sock.sendall(xdata)
+            self.sock.sendall(polybytes(xdata))
         except socket.error:
             # On failure, we don't know how much data was actually received
             sys.stderr.write("Write to %s failed\n" % self.name)
@@ -376,7 +458,7 @@ class Mode6Session:
                                     ("not " "", )[seenlastfrag])
                     raise Mode6Exception(SERR_INCOMPLETE)
 
-            rawdata = self.sock.recv(4096)
+            rawdata = polystr(self.sock.recv(4096))
             if self.debug:
                 warn("Received %d octets\n" % len(rawdata))
             rpkt = Mode6Packet(self)



View it on GitLab: https://gitlab.com/NTPsec/ntpsec/commit/f13333104f06610a1e6eeb2e332e5a565e7d3c18
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.ntpsec.org/pipermail/vc/attachments/20161020/9b880d8e/attachment.html>


More information about the vc mailing list