##// END OF EJS Templates
Fixes for python2.5 compatibility.
Fernando Perez -
Show More
@@ -0,0 +1,110 b''
1 """Code taken from the Python2.6 standard library for backwards compatibility.
2
3 This is just so we can use 2.6 features when running in 2.5, the code below is
4 copied verbatim from the stdlib's collections and doctest modules.
5 """
6
7 from keyword import iskeyword as _iskeyword
8 from operator import itemgetter as _itemgetter
9 import sys as _sys
10
11 def namedtuple(typename, field_names, verbose=False):
12 """Returns a new subclass of tuple with named fields.
13
14 >>> Point = namedtuple('Point', 'x y')
15 >>> Point.__doc__ # docstring for the new class
16 'Point(x, y)'
17 >>> p = Point(11, y=22) # instantiate with positional args or keywords
18 >>> p[0] + p[1] # indexable like a plain tuple
19 33
20 >>> x, y = p # unpack like a regular tuple
21 >>> x, y
22 (11, 22)
23 >>> p.x + p.y # fields also accessable by name
24 33
25 >>> d = p._asdict() # convert to a dictionary
26 >>> d['x']
27 11
28 >>> Point(**d) # convert from a dictionary
29 Point(x=11, y=22)
30 >>> p._replace(x=100) # _replace() is like str.replace() but targets named fields
31 Point(x=100, y=22)
32
33 """
34
35 # Parse and validate the field names. Validation serves two purposes,
36 # generating informative error messages and preventing template injection attacks.
37 if isinstance(field_names, basestring):
38 field_names = field_names.replace(',', ' ').split() # names separated by whitespace and/or commas
39 field_names = tuple(map(str, field_names))
40 for name in (typename,) + field_names:
41 if not all(c.isalnum() or c=='_' for c in name):
42 raise ValueError('Type names and field names can only contain alphanumeric characters and underscores: %r' % name)
43 if _iskeyword(name):
44 raise ValueError('Type names and field names cannot be a keyword: %r' % name)
45 if name[0].isdigit():
46 raise ValueError('Type names and field names cannot start with a number: %r' % name)
47 seen_names = set()
48 for name in field_names:
49 if name.startswith('_'):
50 raise ValueError('Field names cannot start with an underscore: %r' % name)
51 if name in seen_names:
52 raise ValueError('Encountered duplicate field name: %r' % name)
53 seen_names.add(name)
54
55 # Create and fill-in the class template
56 numfields = len(field_names)
57 argtxt = repr(field_names).replace("'", "")[1:-1] # tuple repr without parens or quotes
58 reprtxt = ', '.join('%s=%%r' % name for name in field_names)
59 dicttxt = ', '.join('%r: t[%d]' % (name, pos) for pos, name in enumerate(field_names))
60 template = '''class %(typename)s(tuple):
61 '%(typename)s(%(argtxt)s)' \n
62 __slots__ = () \n
63 _fields = %(field_names)r \n
64 def __new__(_cls, %(argtxt)s):
65 return _tuple.__new__(_cls, (%(argtxt)s)) \n
66 @classmethod
67 def _make(cls, iterable, new=tuple.__new__, len=len):
68 'Make a new %(typename)s object from a sequence or iterable'
69 result = new(cls, iterable)
70 if len(result) != %(numfields)d:
71 raise TypeError('Expected %(numfields)d arguments, got %%d' %% len(result))
72 return result \n
73 def __repr__(self):
74 return '%(typename)s(%(reprtxt)s)' %% self \n
75 def _asdict(t):
76 'Return a new dict which maps field names to their values'
77 return {%(dicttxt)s} \n
78 def _replace(_self, **kwds):
79 'Return a new %(typename)s object replacing specified fields with new values'
80 result = _self._make(map(kwds.pop, %(field_names)r, _self))
81 if kwds:
82 raise ValueError('Got unexpected field names: %%r' %% kwds.keys())
83 return result \n
84 def __getnewargs__(self):
85 return tuple(self) \n\n''' % locals()
86 for i, name in enumerate(field_names):
87 template += ' %s = _property(_itemgetter(%d))\n' % (name, i)
88 if verbose:
89 print template
90
91 # Execute the template string in a temporary namespace and
92 # support tracing utilities by setting a value for frame.f_globals['__name__']
93 namespace = dict(_itemgetter=_itemgetter, __name__='namedtuple_%s' % typename,
94 _property=property, _tuple=tuple)
95 try:
96 exec template in namespace
97 except SyntaxError, e:
98 raise SyntaxError(e.message + ':\n' + template)
99 result = namespace[typename]
100
101 # For pickling to work, the __module__ variable needs to be set to the frame
102 # where the named tuple is created. Bypass this step in enviroments where
103 # sys._getframe is not defined (Jython for example).
104 if hasattr(_sys, '_getframe'):
105 result.__module__ = _sys._getframe(1).f_globals.get('__name__', '__main__')
106
107 return result
108
109
110 TestResults = namedtuple('TestResults', 'failed attempted')
@@ -1,188 +1,192 b''
1 """Experimental code for cleaner support of IPython syntax with unittest.
1 """Experimental code for cleaner support of IPython syntax with unittest.
2
2
3 In IPython up until 0.10, we've used very hacked up nose machinery for running
3 In IPython up until 0.10, we've used very hacked up nose machinery for running
4 tests with IPython special syntax, and this has proved to be extremely slow.
4 tests with IPython special syntax, and this has proved to be extremely slow.
5 This module provides decorators to try a different approach, stemming from a
5 This module provides decorators to try a different approach, stemming from a
6 conversation Brian and I (FP) had about this problem Sept/09.
6 conversation Brian and I (FP) had about this problem Sept/09.
7
7
8 The goal is to be able to easily write simple functions that can be seen by
8 The goal is to be able to easily write simple functions that can be seen by
9 unittest as tests, and ultimately for these to support doctests with full
9 unittest as tests, and ultimately for these to support doctests with full
10 IPython syntax. Nose already offers this based on naming conventions and our
10 IPython syntax. Nose already offers this based on naming conventions and our
11 hackish plugins, but we are seeking to move away from nose dependencies if
11 hackish plugins, but we are seeking to move away from nose dependencies if
12 possible.
12 possible.
13
13
14 This module follows a different approach, based on decorators.
14 This module follows a different approach, based on decorators.
15
15
16 - A decorator called @ipdoctest can mark any function as having a docstring
16 - A decorator called @ipdoctest can mark any function as having a docstring
17 that should be viewed as a doctest, but after syntax conversion.
17 that should be viewed as a doctest, but after syntax conversion.
18
18
19 Authors
19 Authors
20 -------
20 -------
21
21
22 - Fernando Perez <Fernando.Perez@berkeley.edu>
22 - Fernando Perez <Fernando.Perez@berkeley.edu>
23 """
23 """
24
24
25 from __future__ import absolute_import
25 from __future__ import absolute_import
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Copyright (C) 2009 The IPython Development Team
28 # Copyright (C) 2009 The IPython Development Team
29 #
29 #
30 # Distributed under the terms of the BSD License. The full license is in
30 # Distributed under the terms of the BSD License. The full license is in
31 # the file COPYING, distributed as part of this software.
31 # the file COPYING, distributed as part of this software.
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33
33
34
34
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # Imports
36 # Imports
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38
38
39 # Stdlib
39 # Stdlib
40 import re
40 import re
41 import sys
41 import sys
42 import unittest
42 import unittest
43 from doctest import DocTestFinder, DocTestRunner, TestResults
43 from doctest import DocTestFinder, DocTestRunner
44 try:
45 from doctest import TestResults
46 except:
47 from ._doctest26 import TestResults
44
48
45 # Our own, a nose monkeypatch
49 # Our own, a nose monkeypatch
46 from . import nosepatch
50 from . import nosepatch
47
51
48 # We already have python3-compliant code for parametric tests
52 # We already have python3-compliant code for parametric tests
49 if sys.version[0]=='2':
53 if sys.version[0]=='2':
50 from ._paramtestpy2 import ParametricTestCase
54 from ._paramtestpy2 import ParametricTestCase
51 else:
55 else:
52 from ._paramtestpy3 import ParametricTestCase
56 from ._paramtestpy3 import ParametricTestCase
53
57
54 from . import globalipapp
58 from . import globalipapp
55
59
56 #-----------------------------------------------------------------------------
60 #-----------------------------------------------------------------------------
57 # Classes and functions
61 # Classes and functions
58 #-----------------------------------------------------------------------------
62 #-----------------------------------------------------------------------------
59
63
60 def count_failures(runner):
64 def count_failures(runner):
61 """Count number of failures in a doctest runner.
65 """Count number of failures in a doctest runner.
62
66
63 Code modeled after the summarize() method in doctest.
67 Code modeled after the summarize() method in doctest.
64 """
68 """
65 return [TestResults(f, t) for f, t in runner._name2ft.values() if f > 0 ]
69 return [TestResults(f, t) for f, t in runner._name2ft.values() if f > 0 ]
66
70
67
71
68 class IPython2PythonConverter(object):
72 class IPython2PythonConverter(object):
69 """Convert IPython 'syntax' to valid Python.
73 """Convert IPython 'syntax' to valid Python.
70
74
71 Eventually this code may grow to be the full IPython syntax conversion
75 Eventually this code may grow to be the full IPython syntax conversion
72 implementation, but for now it only does prompt convertion."""
76 implementation, but for now it only does prompt convertion."""
73
77
74 def __init__(self):
78 def __init__(self):
75 self.rps1 = re.compile(r'In\ \[\d+\]: ')
79 self.rps1 = re.compile(r'In\ \[\d+\]: ')
76 self.rps2 = re.compile(r'\ \ \ \.\.\.+: ')
80 self.rps2 = re.compile(r'\ \ \ \.\.\.+: ')
77 self.rout = re.compile(r'Out\[\d+\]: \s*?\n?')
81 self.rout = re.compile(r'Out\[\d+\]: \s*?\n?')
78 self.pyps1 = '>>> '
82 self.pyps1 = '>>> '
79 self.pyps2 = '... '
83 self.pyps2 = '... '
80 self.rpyps1 = re.compile ('(\s*%s)(.*)$' % self.pyps1)
84 self.rpyps1 = re.compile ('(\s*%s)(.*)$' % self.pyps1)
81 self.rpyps2 = re.compile ('(\s*%s)(.*)$' % self.pyps2)
85 self.rpyps2 = re.compile ('(\s*%s)(.*)$' % self.pyps2)
82
86
83 def __call__(self, ds):
87 def __call__(self, ds):
84 """Convert IPython prompts to python ones in a string."""
88 """Convert IPython prompts to python ones in a string."""
85 pyps1 = '>>> '
89 pyps1 = '>>> '
86 pyps2 = '... '
90 pyps2 = '... '
87 pyout = ''
91 pyout = ''
88
92
89 dnew = ds
93 dnew = ds
90 dnew = self.rps1.sub(pyps1, dnew)
94 dnew = self.rps1.sub(pyps1, dnew)
91 dnew = self.rps2.sub(pyps2, dnew)
95 dnew = self.rps2.sub(pyps2, dnew)
92 dnew = self.rout.sub(pyout, dnew)
96 dnew = self.rout.sub(pyout, dnew)
93 ip = globalipapp.get_ipython()
97 ip = globalipapp.get_ipython()
94
98
95 # Convert input IPython source into valid Python.
99 # Convert input IPython source into valid Python.
96 out = []
100 out = []
97 newline = out.append
101 newline = out.append
98 for line in dnew.splitlines():
102 for line in dnew.splitlines():
99
103
100 mps1 = self.rpyps1.match(line)
104 mps1 = self.rpyps1.match(line)
101 if mps1 is not None:
105 if mps1 is not None:
102 prompt, text = mps1.groups()
106 prompt, text = mps1.groups()
103 newline(prompt+ip.prefilter(text, False))
107 newline(prompt+ip.prefilter(text, False))
104 continue
108 continue
105
109
106 mps2 = self.rpyps2.match(line)
110 mps2 = self.rpyps2.match(line)
107 if mps2 is not None:
111 if mps2 is not None:
108 prompt, text = mps2.groups()
112 prompt, text = mps2.groups()
109 newline(prompt+ip.prefilter(text, True))
113 newline(prompt+ip.prefilter(text, True))
110 continue
114 continue
111
115
112 newline(line)
116 newline(line)
113 newline('') # ensure a closing newline, needed by doctest
117 newline('') # ensure a closing newline, needed by doctest
114 #print "PYSRC:", '\n'.join(out) # dbg
118 #print "PYSRC:", '\n'.join(out) # dbg
115 return '\n'.join(out)
119 return '\n'.join(out)
116
120
117 #return dnew
121 #return dnew
118
122
119
123
120 class Doc2UnitTester(object):
124 class Doc2UnitTester(object):
121 """Class whose instances act as a decorator for docstring testing.
125 """Class whose instances act as a decorator for docstring testing.
122
126
123 In practice we're only likely to need one instance ever, made below (though
127 In practice we're only likely to need one instance ever, made below (though
124 no attempt is made at turning it into a singleton, there is no need for
128 no attempt is made at turning it into a singleton, there is no need for
125 that).
129 that).
126 """
130 """
127 def __init__(self, verbose=False):
131 def __init__(self, verbose=False):
128 """New decorator.
132 """New decorator.
129
133
130 Parameters
134 Parameters
131 ----------
135 ----------
132
136
133 verbose : boolean, optional (False)
137 verbose : boolean, optional (False)
134 Passed to the doctest finder and runner to control verbosity.
138 Passed to the doctest finder and runner to control verbosity.
135 """
139 """
136 self.verbose = verbose
140 self.verbose = verbose
137 # We can reuse the same finder for all instances
141 # We can reuse the same finder for all instances
138 self.finder = DocTestFinder(verbose=verbose, recurse=False)
142 self.finder = DocTestFinder(verbose=verbose, recurse=False)
139
143
140 def __call__(self, func):
144 def __call__(self, func):
141 """Use as a decorator: doctest a function's docstring as a unittest.
145 """Use as a decorator: doctest a function's docstring as a unittest.
142
146
143 This version runs normal doctests, but the idea is to make it later run
147 This version runs normal doctests, but the idea is to make it later run
144 ipython syntax instead."""
148 ipython syntax instead."""
145
149
146 # Capture the enclosing instance with a different name, so the new
150 # Capture the enclosing instance with a different name, so the new
147 # class below can see it without confusion regarding its own 'self'
151 # class below can see it without confusion regarding its own 'self'
148 # that will point to the test instance at runtime
152 # that will point to the test instance at runtime
149 d2u = self
153 d2u = self
150
154
151 # Rewrite the function's docstring to have python syntax
155 # Rewrite the function's docstring to have python syntax
152 if func.__doc__ is not None:
156 if func.__doc__ is not None:
153 func.__doc__ = ip2py(func.__doc__)
157 func.__doc__ = ip2py(func.__doc__)
154
158
155 # Now, create a tester object that is a real unittest instance, so
159 # Now, create a tester object that is a real unittest instance, so
156 # normal unittest machinery (or Nose, or Trial) can find it.
160 # normal unittest machinery (or Nose, or Trial) can find it.
157 class Tester(unittest.TestCase):
161 class Tester(unittest.TestCase):
158 def test(self):
162 def test(self):
159 # Make a new runner per function to be tested
163 # Make a new runner per function to be tested
160 runner = DocTestRunner(verbose=d2u.verbose)
164 runner = DocTestRunner(verbose=d2u.verbose)
161 map(runner.run, d2u.finder.find(func, func.__name__))
165 map(runner.run, d2u.finder.find(func, func.__name__))
162 failed = count_failures(runner)
166 failed = count_failures(runner)
163 if failed:
167 if failed:
164 # Since we only looked at a single function's docstring,
168 # Since we only looked at a single function's docstring,
165 # failed should contain at most one item. More than that
169 # failed should contain at most one item. More than that
166 # is a case we can't handle and should error out on
170 # is a case we can't handle and should error out on
167 if len(failed) > 1:
171 if len(failed) > 1:
168 err = "Invalid number of test results:" % failed
172 err = "Invalid number of test results:" % failed
169 raise ValueError(err)
173 raise ValueError(err)
170 # Report a normal failure.
174 # Report a normal failure.
171 self.fail('failed doctests: %s' % str(failed[0]))
175 self.fail('failed doctests: %s' % str(failed[0]))
172
176
173 # Rename it so test reports have the original signature.
177 # Rename it so test reports have the original signature.
174 Tester.__name__ = func.__name__
178 Tester.__name__ = func.__name__
175 return Tester
179 return Tester
176
180
177
181
178 def ipdocstring(func):
182 def ipdocstring(func):
179 """Change the function docstring via ip2py.
183 """Change the function docstring via ip2py.
180 """
184 """
181 if func.__doc__ is not None:
185 if func.__doc__ is not None:
182 func.__doc__ = ip2py(func.__doc__)
186 func.__doc__ = ip2py(func.__doc__)
183 return func
187 return func
184
188
185
189
186 # Make an instance of the classes for public use
190 # Make an instance of the classes for public use
187 ipdoctest = Doc2UnitTester()
191 ipdoctest = Doc2UnitTester()
188 ip2py = IPython2PythonConverter()
192 ip2py = IPython2PythonConverter()
General Comments 0
You need to be logged in to leave comments. Login now