1 """Add things to old Pythons so I can pretend they are newer.""" 2 3 # This file does lots of tricky stuff, so disable a bunch of lintisms. 4 # pylint: disable=F0401,W0611,W0622 5 # F0401: Unable to import blah 6 # W0611: Unused import blah 7 # W0622: Redefining built-in blah 8 9 import os, sys 10 11 # Python 2.3 doesn't have `set` 12 try: 13 set = set # new in 2.4 14 except NameError: 15 from sets import Set as set 16 17 # Python 2.3 doesn't have `sorted`. 18 try: 19 sorted = sorted 20 except NameError: 21 def sorted(iterable): 22 """A 2.3-compatible implementation of `sorted`.""" 23 lst = list(iterable) 24 lst.sort() 25 return lst 26 27 # Pythons 2 and 3 differ on where to get StringIO 28 try: 29 from cStringIO import StringIO 30 BytesIO = StringIO 31 except ImportError: 32 from io import StringIO, BytesIO 33 34 # What's a string called? 35 try: 36 string_class = basestring 37 except NameError: 38 string_class = str 39 40 # Where do pickles come from? 41 try: 42 import cPickle as pickle 43 except ImportError: 44 import pickle 45 46 # range or xrange? 47 try: 48 range = xrange 49 except NameError: 50 range = range 51 52 # Exec is a statement in Py2, a function in Py3 53 if sys.version_info >= (3, 0): 54 def exec_code_object(code, global_map): 55 """A wrapper around exec().""" 56 exec(code, global_map) 57 else: 58 # OK, this is pretty gross. In Py2, exec was a statement, but that will 59 # be a syntax error if we try to put it in a Py3 file, even if it is never 60 # executed. So hide it inside an evaluated string literal instead. 61 eval( 62 compile( 63 "def exec_code_object(code, global_map):\n" 64 " exec code in global_map\n", 65 "<exec_function>", "exec" 66 ) 67 ) 68 69 # ConfigParser was renamed to the more-standard configparser 70 try: 71 import configparser 72 except ImportError: 73 import ConfigParser as configparser 74 75 # Python 3.2 provides `tokenize.open`, the best way to open source files. 76 import tokenize 77 try: 78 open_source = tokenize.open # pylint: disable=E1101 79 except AttributeError: 80 try: 81 detect_encoding = tokenize.detect_encoding # pylint: disable=E1101 82 except AttributeError: 83 def open_source(fname): 84 """Open a source file the best way.""" 85 return open(fname, "rU") 86 else: 87 from io import TextIOWrapper 88 # Copied from the 3.2 stdlib: 89 def open_source(fname): 90 """Open a file in read only mode using the encoding detected by 91 detect_encoding(). 92 """ 93 buffer = open(fname, 'rb') 94 encoding, _ = detect_encoding(buffer.readline) 95 buffer.seek(0) 96 text = TextIOWrapper(buffer, encoding, line_buffering=True) 97 text.mode = 'r' 98 return text 99 100 # Python 3.x is picky about bytes and strings, so provide methods to 101 # get them right, and make them no-ops in 2.x 102 if sys.version_info >= (3, 0): 103 def to_bytes(s): 104 """Convert string `s` to bytes.""" 105 return s.encode('utf8') 106 107 def to_string(b): 108 """Convert bytes `b` to a string.""" 109 return b.decode('utf8') 110 111 else: 112 def to_bytes(s): 113 """Convert string `s` to bytes (no-op in 2.x).""" 114 return s 115 116 def to_string(b): 117 """Convert bytes `b` to a string (no-op in 2.x).""" 118 return b 119 120 # A few details about writing encoded text are different in 2.x and 3.x. 121 if sys.version_info >= (3, 0): 122 def write_encoded(fname, text, encoding='utf8', errors='strict'): 123 '''Write string `text` to file names `fname`, with encoding.''' 124 # Don't use "with", so that this file is still good for old 2.x. 125 f = open(fname, 'w', encoding=encoding, errors=errors) 126 try: 127 f.write(text) 128 finally: 129 f.close() 130 else: 131 # It's not clear that using utf8 strings in 2.x is the right thing to do. 132 def write_encoded(fname, text, encoding='utf8', errors='strict'): 133 '''Write utf8 string `text` to file names `fname`, with encoding.''' 134 import codecs 135 f = codecs.open(fname, 'w', encoding=encoding, errors=errors) 136 try: 137 f.write(text.decode('utf8')) 138 finally: 139 f.close() 140 141 # Md5 is available in different places. 142 try: 143 import hashlib 144 md5 = hashlib.md5 145 except ImportError: 146 import md5 147 md5 = md5.new 148