comparison compat.py @ 50:4ea7133ac25c

Converted 2.00 into the default branch
author Oleg Oshmyan <chortos@inbox.lv>
date Sun, 19 Dec 2010 23:25:13 +0200
parents 2.00/compat.py@23aa8da5be5f
children e0f8b28e15b5
comparison
equal deleted inserted replaced
47:06f1683c8db9 50:4ea7133ac25c
1 #! /usr/bin/env python
2 # Copyright (c) 2010 Chortos-2 <chortos@inbox.lv>
3
4 # A compatibility layer for Python 2.5+. This is what lets test.py
5 # run on all versions of Python starting with 2.5, including Python 3.
6
7 # A few notes regarding some compatibility-driven peculiarities
8 # in the use of the language that can be seen in all modules:
9 #
10 # * Except statements never specify target; instead, when needed,
11 # the exception is taken from sys.exc_info(). Blame the incompatible
12 # syntaxes of the except clause in Python 2.5 and Python 3 and the lack
13 # of preprocessor macros in Python of any version ;P.
14 #
15 # * Keyword-only parameters are never used, even for parameters
16 # that should never be given in as arguments. The reason is
17 # the laziness of some Python developers who have failed to finish
18 # implementing them in Python 2 even though they had several years
19 # of time and multiple version releases to sneak them in.
20 #
21 # * Abstract classes are only implemented for Python 2.6 and 2.7.
22 # ABC's require the abc module and the specification of metaclasses,
23 # but in Python 2.5, the abc module does not exist, while in Python 3,
24 # metaclasses are specified using a syntax totally incompatible
25 # with Python 2 and not usable conditionally via exec() and such
26 # because it is a detail of the syntax of the class statement itself.
27
28 try:
29 import builtins
30 except ImportError:
31 import __builtin__ as builtins
32
33 __all__ = ('say', 'basestring', 'range', 'map', 'zip', 'filter', 'items',
34 'keys', 'values', 'zip_longest', 'callable',
35 'ABCMeta', 'abstractmethod', 'CompatBuiltins')
36
37 try:
38 # Python 3
39 exec('say = print')
40 except SyntaxError:
41 try:
42 # Python 2.6/2.7
43 # An alternative is exec('from __future__ import print_function; say = print');
44 # if problems arise with the current line, one should try replacing it
45 # with this one with the future import before abandoning the idea altogether
46 say = getattr(builtins, 'print')
47 except Exception:
48 # Python 2.5
49 import sys
50 # This should fully emulate the print function of Python 2.6 in Python 2.3+
51 # The error messages are taken from Python 2.6
52 # The name bindings at the bottom of this file are in effect
53 def saytypeerror(value, name):
54 return TypeError(' '.join((name, 'must be None, str or unicode, not', type(value).__name__)))
55 def say(*values, **kwargs):
56 sep = kwargs.pop('sep' , None)
57 end = kwargs.pop('end' , None)
58 file = kwargs.pop('file', None)
59 if kwargs: raise TypeError("'%s' is an invalid keyword argument for this function" % kwargs.popitem()[0])
60 if sep is None: sep = ' '
61 if end is None: end = '\n'
62 if file is None: file = sys.stdout
63 if not isinstance(sep, basestring): raise saytypeerror(sep, 'sep')
64 if not isinstance(end, basestring): raise saytypeerror(end, 'end')
65 file.write(sep.join(map(str, values)) + end)
66
67 try:
68 from os.path import relpath
69 except ImportError:
70 # Python 2.5
71 import os.path as _path
72
73 # Adapted from Python 2.7.1
74
75 if hasattr(_path, 'splitunc'):
76 def _abspath_split(path):
77 abs = _path.abspath(_path.normpath(path))
78 prefix, rest = _path.splitunc(abs)
79 is_unc = bool(prefix)
80 if not is_unc:
81 prefix, rest = _path.splitdrive(abs)
82 return is_unc, prefix, [x for x in rest.split(_path.sep) if x]
83 else:
84 def _abspath_split(path):
85 prefix, rest = _path.splitdrive(_path.abspath(_path.normpath(path)))
86 return False, prefix, [x for x in rest.split(_path.sep) if x]
87
88 def relpath(path, start=_path.curdir):
89 """Return a relative version of a path"""
90
91 if not path:
92 raise ValueError("no path specified")
93
94 start_is_unc, start_prefix, start_list = _abspath_split(start)
95 path_is_unc, path_prefix, path_list = _abspath_split(path)
96
97 if path_is_unc ^ start_is_unc:
98 raise ValueError("Cannot mix UNC and non-UNC paths (%s and %s)"
99 % (path, start))
100 if path_prefix.lower() != start_prefix.lower():
101 if path_is_unc:
102 raise ValueError("path is on UNC root %s, start on UNC root %s"
103 % (path_prefix, start_prefix))
104 else:
105 raise ValueError("path is on drive %s, start on drive %s"
106 % (path_prefix, start_prefix))
107 # Work out how much of the filepath is shared by start and path.
108 i = 0
109 for e1, e2 in zip(start_list, path_list):
110 if e1.lower() != e2.lower():
111 break
112 i += 1
113
114 rel_list = [_path.pardir] * (len(start_list)-i) + path_list[i:]
115 if not rel_list:
116 return _path.curdir
117 return _path.join(*rel_list)
118
119 _path.relpath = relpath
120
121 def import_urllib():
122 try:
123 # Python 3
124 import urllib.request
125 return urllib.request, lambda url: urllib.request.urlopen(url).read().decode()
126 except ImportError:
127 # Python 2
128 import urllib
129 return urllib, lambda url: urllib.urlopen(url).read()
130
131 try:
132 from abc import ABCMeta, abstractmethod
133 except ImportError:
134 ABCMeta, abstractmethod = None, lambda x: x
135
136 # In all of the following, the try clause is for Python 2 and the except
137 # clause is for Python 3. More checks are performed than needed
138 # for standard builds of Python to ensure as much as possible works
139 # on custom builds.
140 try:
141 basestring = basestring
142 except NameError:
143 basestring = str
144
145 try:
146 range = xrange
147 except NameError:
148 range = range
149
150 try:
151 callable = callable
152 except NameError:
153 callable = lambda obj: hasattr(obj, '__call__')
154
155 try:
156 next = next
157 except NameError:
158 next = lambda obj: obj.next()
159
160 try:
161 from itertools import imap as map
162 except ImportError:
163 map = map
164
165 try:
166 from itertools import izip as zip
167 except ImportError:
168 zip = zip
169
170 try:
171 from itertools import ifilter as filter
172 except ImportError:
173 filter = filter
174
175 items = dict.iteritems if hasattr(dict, 'iteritems') else dict.items
176 keys = dict.iterkeys if hasattr(dict, 'iterkeys') else dict.keys
177 values = dict.itervalues if hasattr(dict, 'itervalues') else dict.values
178
179 try:
180 # Python 3
181 from itertools import zip_longest
182 except ImportError:
183 try:
184 # Python 2.6/2.7
185 from itertools import izip_longest as zip_longest
186 except ImportError:
187 # Python 2.5
188 from itertools import chain, repeat
189 # Adapted from the documentation of itertools.izip_longest
190 def zip_longest(*args, **kwargs):
191 fillvalue = kwargs.get('fillvalue')
192 def sentinel(counter=([fillvalue]*(len(args)-1)).pop):
193 yield counter()
194 fillers = repeat(fillvalue)
195 iters = [chain(it, sentinel(), fillers) for it in args]
196 try:
197 for tup in zip(*iters):
198 yield tup
199 except IndexError:
200 pass
201
202 # Automatically import * from this module into testconf.py's
203 class CompatBuiltins(object):
204 __slots__ = 'originals'
205 def __init__(self):
206 self.originals = {}
207 def __enter__(self):
208 g = globals()
209 for name in __all__:
210 if hasattr(builtins, name):
211 self.originals[name] = getattr(builtins, name)
212 setattr(builtins, name, g[name])
213 def __exit__(self, exc_type, exc_val, exc_tb):
214 for name in self.originals:
215 setattr(builtins, name, self.originals[name])
216
217 # Support simple testconf.py's written for test.py 1.x
218 builtins.xrange = range