##// END OF EJS Templates
use py3compat.which in common locations
Min RK -
Show More
@@ -1,400 +1,369 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Decorators for labeling test objects.
2 """Decorators for labeling test objects.
3
3
4 Decorators that merely return a modified version of the original function
4 Decorators that merely return a modified version of the original function
5 object are straightforward. Decorators that return a new function object need
5 object are straightforward. Decorators that return a new function object need
6 to use nose.tools.make_decorator(original_function)(decorator) in returning the
6 to use nose.tools.make_decorator(original_function)(decorator) in returning the
7 decorator, in order to preserve metadata such as function name, setup and
7 decorator, in order to preserve metadata such as function name, setup and
8 teardown functions and so on - see nose.tools for more information.
8 teardown functions and so on - see nose.tools for more information.
9
9
10 This module provides a set of useful decorators meant to be ready to use in
10 This module provides a set of useful decorators meant to be ready to use in
11 your own tests. See the bottom of the file for the ready-made ones, and if you
11 your own tests. See the bottom of the file for the ready-made ones, and if you
12 find yourself writing a new one that may be of generic use, add it here.
12 find yourself writing a new one that may be of generic use, add it here.
13
13
14 Included decorators:
14 Included decorators:
15
15
16
16
17 Lightweight testing that remains unittest-compatible.
17 Lightweight testing that remains unittest-compatible.
18
18
19 - An @as_unittest decorator can be used to tag any normal parameter-less
19 - An @as_unittest decorator can be used to tag any normal parameter-less
20 function as a unittest TestCase. Then, both nose and normal unittest will
20 function as a unittest TestCase. Then, both nose and normal unittest will
21 recognize it as such. This will make it easier to migrate away from Nose if
21 recognize it as such. This will make it easier to migrate away from Nose if
22 we ever need/want to while maintaining very lightweight tests.
22 we ever need/want to while maintaining very lightweight tests.
23
23
24 NOTE: This file contains IPython-specific decorators. Using the machinery in
24 NOTE: This file contains IPython-specific decorators. Using the machinery in
25 IPython.external.decorators, we import either numpy.testing.decorators if numpy is
25 IPython.external.decorators, we import either numpy.testing.decorators if numpy is
26 available, OR use equivalent code in IPython.external._decorators, which
26 available, OR use equivalent code in IPython.external._decorators, which
27 we've copied verbatim from numpy.
27 we've copied verbatim from numpy.
28
28
29 Authors
30 -------
31
32 - Fernando Perez <Fernando.Perez@berkeley.edu>
33 """
29 """
34
30
35 #-----------------------------------------------------------------------------
31 # Copyright (c) IPython Development Team.
36 # Copyright (C) 2009-2011 The IPython Development Team
32 # Distributed under the terms of the Modified BSD License.
37 #
38 # Distributed under the terms of the BSD License. The full license is in
39 # the file COPYING, distributed as part of this software.
40 #-----------------------------------------------------------------------------
41
33
42 #-----------------------------------------------------------------------------
43 # Imports
44 #-----------------------------------------------------------------------------
45
46 # Stdlib imports
47 import sys
34 import sys
48 import os
35 import os
49 import tempfile
36 import tempfile
50 import unittest
37 import unittest
51
38
52 # Third-party imports
53
54 # This is Michele Simionato's decorator module, kept verbatim.
55 from decorator import decorator
39 from decorator import decorator
56
40
57 # Expose the unittest-driven decorators
41 # Expose the unittest-driven decorators
58 from .ipunittest import ipdoctest, ipdocstring
42 from .ipunittest import ipdoctest, ipdocstring
59
43
60 # Grab the numpy-specific decorators which we keep in a file that we
44 # Grab the numpy-specific decorators which we keep in a file that we
61 # occasionally update from upstream: decorators.py is a copy of
45 # occasionally update from upstream: decorators.py is a copy of
62 # numpy.testing.decorators, we expose all of it here.
46 # numpy.testing.decorators, we expose all of it here.
63 from IPython.external.decorators import *
47 from IPython.external.decorators import *
64
48
65 # For onlyif_cmd_exists decorator
49 # For onlyif_cmd_exists decorator
66 from IPython.utils.process import is_cmd_found
50 from IPython.utils.py3compat import string_types, which
67 from IPython.utils.py3compat import string_types
68
51
69 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
70 # Classes and functions
53 # Classes and functions
71 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
72
55
73 # Simple example of the basic idea
56 # Simple example of the basic idea
74 def as_unittest(func):
57 def as_unittest(func):
75 """Decorator to make a simple function into a normal test via unittest."""
58 """Decorator to make a simple function into a normal test via unittest."""
76 class Tester(unittest.TestCase):
59 class Tester(unittest.TestCase):
77 def test(self):
60 def test(self):
78 func()
61 func()
79
62
80 Tester.__name__ = func.__name__
63 Tester.__name__ = func.__name__
81
64
82 return Tester
65 return Tester
83
66
84 # Utility functions
67 # Utility functions
85
68
86 def apply_wrapper(wrapper,func):
69 def apply_wrapper(wrapper,func):
87 """Apply a wrapper to a function for decoration.
70 """Apply a wrapper to a function for decoration.
88
71
89 This mixes Michele Simionato's decorator tool with nose's make_decorator,
72 This mixes Michele Simionato's decorator tool with nose's make_decorator,
90 to apply a wrapper in a decorator so that all nose attributes, as well as
73 to apply a wrapper in a decorator so that all nose attributes, as well as
91 function signature and other properties, survive the decoration cleanly.
74 function signature and other properties, survive the decoration cleanly.
92 This will ensure that wrapped functions can still be well introspected via
75 This will ensure that wrapped functions can still be well introspected via
93 IPython, for example.
76 IPython, for example.
94 """
77 """
95 import nose.tools
78 import nose.tools
96
79
97 return decorator(wrapper,nose.tools.make_decorator(func)(wrapper))
80 return decorator(wrapper,nose.tools.make_decorator(func)(wrapper))
98
81
99
82
100 def make_label_dec(label,ds=None):
83 def make_label_dec(label,ds=None):
101 """Factory function to create a decorator that applies one or more labels.
84 """Factory function to create a decorator that applies one or more labels.
102
85
103 Parameters
86 Parameters
104 ----------
87 ----------
105 label : string or sequence
88 label : string or sequence
106 One or more labels that will be applied by the decorator to the functions
89 One or more labels that will be applied by the decorator to the functions
107 it decorates. Labels are attributes of the decorated function with their
90 it decorates. Labels are attributes of the decorated function with their
108 value set to True.
91 value set to True.
109
92
110 ds : string
93 ds : string
111 An optional docstring for the resulting decorator. If not given, a
94 An optional docstring for the resulting decorator. If not given, a
112 default docstring is auto-generated.
95 default docstring is auto-generated.
113
96
114 Returns
97 Returns
115 -------
98 -------
116 A decorator.
99 A decorator.
117
100
118 Examples
101 Examples
119 --------
102 --------
120
103
121 A simple labeling decorator:
104 A simple labeling decorator:
122
105
123 >>> slow = make_label_dec('slow')
106 >>> slow = make_label_dec('slow')
124 >>> slow.__doc__
107 >>> slow.__doc__
125 "Labels a test as 'slow'."
108 "Labels a test as 'slow'."
126
109
127 And one that uses multiple labels and a custom docstring:
110 And one that uses multiple labels and a custom docstring:
128
111
129 >>> rare = make_label_dec(['slow','hard'],
112 >>> rare = make_label_dec(['slow','hard'],
130 ... "Mix labels 'slow' and 'hard' for rare tests.")
113 ... "Mix labels 'slow' and 'hard' for rare tests.")
131 >>> rare.__doc__
114 >>> rare.__doc__
132 "Mix labels 'slow' and 'hard' for rare tests."
115 "Mix labels 'slow' and 'hard' for rare tests."
133
116
134 Now, let's test using this one:
117 Now, let's test using this one:
135 >>> @rare
118 >>> @rare
136 ... def f(): pass
119 ... def f(): pass
137 ...
120 ...
138 >>>
121 >>>
139 >>> f.slow
122 >>> f.slow
140 True
123 True
141 >>> f.hard
124 >>> f.hard
142 True
125 True
143 """
126 """
144
127
145 if isinstance(label, string_types):
128 if isinstance(label, string_types):
146 labels = [label]
129 labels = [label]
147 else:
130 else:
148 labels = label
131 labels = label
149
132
150 # Validate that the given label(s) are OK for use in setattr() by doing a
133 # Validate that the given label(s) are OK for use in setattr() by doing a
151 # dry run on a dummy function.
134 # dry run on a dummy function.
152 tmp = lambda : None
135 tmp = lambda : None
153 for label in labels:
136 for label in labels:
154 setattr(tmp,label,True)
137 setattr(tmp,label,True)
155
138
156 # This is the actual decorator we'll return
139 # This is the actual decorator we'll return
157 def decor(f):
140 def decor(f):
158 for label in labels:
141 for label in labels:
159 setattr(f,label,True)
142 setattr(f,label,True)
160 return f
143 return f
161
144
162 # Apply the user's docstring, or autogenerate a basic one
145 # Apply the user's docstring, or autogenerate a basic one
163 if ds is None:
146 if ds is None:
164 ds = "Labels a test as %r." % label
147 ds = "Labels a test as %r." % label
165 decor.__doc__ = ds
148 decor.__doc__ = ds
166
149
167 return decor
150 return decor
168
151
169
152
170 # Inspired by numpy's skipif, but uses the full apply_wrapper utility to
153 # Inspired by numpy's skipif, but uses the full apply_wrapper utility to
171 # preserve function metadata better and allows the skip condition to be a
154 # preserve function metadata better and allows the skip condition to be a
172 # callable.
155 # callable.
173 def skipif(skip_condition, msg=None):
156 def skipif(skip_condition, msg=None):
174 ''' Make function raise SkipTest exception if skip_condition is true
157 ''' Make function raise SkipTest exception if skip_condition is true
175
158
176 Parameters
159 Parameters
177 ----------
160 ----------
178
161
179 skip_condition : bool or callable
162 skip_condition : bool or callable
180 Flag to determine whether to skip test. If the condition is a
163 Flag to determine whether to skip test. If the condition is a
181 callable, it is used at runtime to dynamically make the decision. This
164 callable, it is used at runtime to dynamically make the decision. This
182 is useful for tests that may require costly imports, to delay the cost
165 is useful for tests that may require costly imports, to delay the cost
183 until the test suite is actually executed.
166 until the test suite is actually executed.
184 msg : string
167 msg : string
185 Message to give on raising a SkipTest exception.
168 Message to give on raising a SkipTest exception.
186
169
187 Returns
170 Returns
188 -------
171 -------
189 decorator : function
172 decorator : function
190 Decorator, which, when applied to a function, causes SkipTest
173 Decorator, which, when applied to a function, causes SkipTest
191 to be raised when the skip_condition was True, and the function
174 to be raised when the skip_condition was True, and the function
192 to be called normally otherwise.
175 to be called normally otherwise.
193
176
194 Notes
177 Notes
195 -----
178 -----
196 You will see from the code that we had to further decorate the
179 You will see from the code that we had to further decorate the
197 decorator with the nose.tools.make_decorator function in order to
180 decorator with the nose.tools.make_decorator function in order to
198 transmit function name, and various other metadata.
181 transmit function name, and various other metadata.
199 '''
182 '''
200
183
201 def skip_decorator(f):
184 def skip_decorator(f):
202 # Local import to avoid a hard nose dependency and only incur the
185 # Local import to avoid a hard nose dependency and only incur the
203 # import time overhead at actual test-time.
186 # import time overhead at actual test-time.
204 import nose
187 import nose
205
188
206 # Allow for both boolean or callable skip conditions.
189 # Allow for both boolean or callable skip conditions.
207 if callable(skip_condition):
190 if callable(skip_condition):
208 skip_val = skip_condition
191 skip_val = skip_condition
209 else:
192 else:
210 skip_val = lambda : skip_condition
193 skip_val = lambda : skip_condition
211
194
212 def get_msg(func,msg=None):
195 def get_msg(func,msg=None):
213 """Skip message with information about function being skipped."""
196 """Skip message with information about function being skipped."""
214 if msg is None: out = 'Test skipped due to test condition.'
197 if msg is None: out = 'Test skipped due to test condition.'
215 else: out = msg
198 else: out = msg
216 return "Skipping test: %s. %s" % (func.__name__,out)
199 return "Skipping test: %s. %s" % (func.__name__,out)
217
200
218 # We need to define *two* skippers because Python doesn't allow both
201 # We need to define *two* skippers because Python doesn't allow both
219 # return with value and yield inside the same function.
202 # return with value and yield inside the same function.
220 def skipper_func(*args, **kwargs):
203 def skipper_func(*args, **kwargs):
221 """Skipper for normal test functions."""
204 """Skipper for normal test functions."""
222 if skip_val():
205 if skip_val():
223 raise nose.SkipTest(get_msg(f,msg))
206 raise nose.SkipTest(get_msg(f,msg))
224 else:
207 else:
225 return f(*args, **kwargs)
208 return f(*args, **kwargs)
226
209
227 def skipper_gen(*args, **kwargs):
210 def skipper_gen(*args, **kwargs):
228 """Skipper for test generators."""
211 """Skipper for test generators."""
229 if skip_val():
212 if skip_val():
230 raise nose.SkipTest(get_msg(f,msg))
213 raise nose.SkipTest(get_msg(f,msg))
231 else:
214 else:
232 for x in f(*args, **kwargs):
215 for x in f(*args, **kwargs):
233 yield x
216 yield x
234
217
235 # Choose the right skipper to use when building the actual generator.
218 # Choose the right skipper to use when building the actual generator.
236 if nose.util.isgenerator(f):
219 if nose.util.isgenerator(f):
237 skipper = skipper_gen
220 skipper = skipper_gen
238 else:
221 else:
239 skipper = skipper_func
222 skipper = skipper_func
240
223
241 return nose.tools.make_decorator(f)(skipper)
224 return nose.tools.make_decorator(f)(skipper)
242
225
243 return skip_decorator
226 return skip_decorator
244
227
245 # A version with the condition set to true, common case just to attach a message
228 # A version with the condition set to true, common case just to attach a message
246 # to a skip decorator
229 # to a skip decorator
247 def skip(msg=None):
230 def skip(msg=None):
248 """Decorator factory - mark a test function for skipping from test suite.
231 """Decorator factory - mark a test function for skipping from test suite.
249
232
250 Parameters
233 Parameters
251 ----------
234 ----------
252 msg : string
235 msg : string
253 Optional message to be added.
236 Optional message to be added.
254
237
255 Returns
238 Returns
256 -------
239 -------
257 decorator : function
240 decorator : function
258 Decorator, which, when applied to a function, causes SkipTest
241 Decorator, which, when applied to a function, causes SkipTest
259 to be raised, with the optional message added.
242 to be raised, with the optional message added.
260 """
243 """
261
244
262 return skipif(True,msg)
245 return skipif(True,msg)
263
246
264
247
265 def onlyif(condition, msg):
248 def onlyif(condition, msg):
266 """The reverse from skipif, see skipif for details."""
249 """The reverse from skipif, see skipif for details."""
267
250
268 if callable(condition):
251 if callable(condition):
269 skip_condition = lambda : not condition()
252 skip_condition = lambda : not condition()
270 else:
253 else:
271 skip_condition = lambda : not condition
254 skip_condition = lambda : not condition
272
255
273 return skipif(skip_condition, msg)
256 return skipif(skip_condition, msg)
274
257
275 #-----------------------------------------------------------------------------
258 #-----------------------------------------------------------------------------
276 # Utility functions for decorators
259 # Utility functions for decorators
277 def module_not_available(module):
260 def module_not_available(module):
278 """Can module be imported? Returns true if module does NOT import.
261 """Can module be imported? Returns true if module does NOT import.
279
262
280 This is used to make a decorator to skip tests that require module to be
263 This is used to make a decorator to skip tests that require module to be
281 available, but delay the 'import numpy' to test execution time.
264 available, but delay the 'import numpy' to test execution time.
282 """
265 """
283 try:
266 try:
284 mod = __import__(module)
267 mod = __import__(module)
285 mod_not_avail = False
268 mod_not_avail = False
286 except ImportError:
269 except ImportError:
287 mod_not_avail = True
270 mod_not_avail = True
288
271
289 return mod_not_avail
272 return mod_not_avail
290
273
291
274
292 def decorated_dummy(dec, name):
275 def decorated_dummy(dec, name):
293 """Return a dummy function decorated with dec, with the given name.
276 """Return a dummy function decorated with dec, with the given name.
294
277
295 Examples
278 Examples
296 --------
279 --------
297 import IPython.testing.decorators as dec
280 import IPython.testing.decorators as dec
298 setup = dec.decorated_dummy(dec.skip_if_no_x11, __name__)
281 setup = dec.decorated_dummy(dec.skip_if_no_x11, __name__)
299 """
282 """
300 dummy = lambda: None
283 dummy = lambda: None
301 dummy.__name__ = name
284 dummy.__name__ = name
302 return dec(dummy)
285 return dec(dummy)
303
286
304 #-----------------------------------------------------------------------------
287 #-----------------------------------------------------------------------------
305 # Decorators for public use
288 # Decorators for public use
306
289
307 # Decorators to skip certain tests on specific platforms.
290 # Decorators to skip certain tests on specific platforms.
308 skip_win32 = skipif(sys.platform == 'win32',
291 skip_win32 = skipif(sys.platform == 'win32',
309 "This test does not run under Windows")
292 "This test does not run under Windows")
310 skip_linux = skipif(sys.platform.startswith('linux'),
293 skip_linux = skipif(sys.platform.startswith('linux'),
311 "This test does not run under Linux")
294 "This test does not run under Linux")
312 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
295 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
313
296
314
297
315 # Decorators to skip tests if not on specific platforms.
298 # Decorators to skip tests if not on specific platforms.
316 skip_if_not_win32 = skipif(sys.platform != 'win32',
299 skip_if_not_win32 = skipif(sys.platform != 'win32',
317 "This test only runs under Windows")
300 "This test only runs under Windows")
318 skip_if_not_linux = skipif(not sys.platform.startswith('linux'),
301 skip_if_not_linux = skipif(not sys.platform.startswith('linux'),
319 "This test only runs under Linux")
302 "This test only runs under Linux")
320 skip_if_not_osx = skipif(sys.platform != 'darwin',
303 skip_if_not_osx = skipif(sys.platform != 'darwin',
321 "This test only runs under OSX")
304 "This test only runs under OSX")
322
305
323
306
324 _x11_skip_cond = (sys.platform not in ('darwin', 'win32') and
307 _x11_skip_cond = (sys.platform not in ('darwin', 'win32') and
325 os.environ.get('DISPLAY', '') == '')
308 os.environ.get('DISPLAY', '') == '')
326 _x11_skip_msg = "Skipped under *nix when X11/XOrg not available"
309 _x11_skip_msg = "Skipped under *nix when X11/XOrg not available"
327
310
328 skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg)
311 skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg)
329
312
330 # not a decorator itself, returns a dummy function to be used as setup
313 # not a decorator itself, returns a dummy function to be used as setup
331 def skip_file_no_x11(name):
314 def skip_file_no_x11(name):
332 return decorated_dummy(skip_if_no_x11, name) if _x11_skip_cond else None
315 return decorated_dummy(skip_if_no_x11, name) if _x11_skip_cond else None
333
316
334 # Other skip decorators
317 # Other skip decorators
335
318
336 # generic skip without module
319 # generic skip without module
337 skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod)
320 skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod)
338
321
339 skipif_not_numpy = skip_without('numpy')
322 skipif_not_numpy = skip_without('numpy')
340
323
341 skipif_not_matplotlib = skip_without('matplotlib')
324 skipif_not_matplotlib = skip_without('matplotlib')
342
325
343 skipif_not_sympy = skip_without('sympy')
326 skipif_not_sympy = skip_without('sympy')
344
327
345 skip_known_failure = knownfailureif(True,'This test is known to fail')
328 skip_known_failure = knownfailureif(True,'This test is known to fail')
346
329
347 known_failure_py3 = knownfailureif(sys.version_info[0] >= 3,
330 known_failure_py3 = knownfailureif(sys.version_info[0] >= 3,
348 'This test is known to fail on Python 3.')
331 'This test is known to fail on Python 3.')
349
332
350 # A null 'decorator', useful to make more readable code that needs to pick
333 # A null 'decorator', useful to make more readable code that needs to pick
351 # between different decorators based on OS or other conditions
334 # between different decorators based on OS or other conditions
352 null_deco = lambda f: f
335 null_deco = lambda f: f
353
336
354 # Some tests only run where we can use unicode paths. Note that we can't just
337 # Some tests only run where we can use unicode paths. Note that we can't just
355 # check os.path.supports_unicode_filenames, which is always False on Linux.
338 # check os.path.supports_unicode_filenames, which is always False on Linux.
356 try:
339 try:
357 f = tempfile.NamedTemporaryFile(prefix=u"tmp€")
340 f = tempfile.NamedTemporaryFile(prefix=u"tmp€")
358 except UnicodeEncodeError:
341 except UnicodeEncodeError:
359 unicode_paths = False
342 unicode_paths = False
360 else:
343 else:
361 unicode_paths = True
344 unicode_paths = True
362 f.close()
345 f.close()
363
346
364 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
347 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
365 "where we can use unicode in filenames."))
348 "where we can use unicode in filenames."))
366
349
367
350
368 def onlyif_cmds_exist(*commands):
351 def onlyif_cmds_exist(*commands):
369 """
352 """
370 Decorator to skip test when at least one of `commands` is not found.
353 Decorator to skip test when at least one of `commands` is not found.
371 """
354 """
372 for cmd in commands:
355 for cmd in commands:
373 try:
356 if not which(cmd):
374 if not is_cmd_found(cmd):
357 return skip("This test runs only if command '{0}' "
375 return skip("This test runs only if command '{0}' "
358 "is installed".format(cmd))
376 "is installed".format(cmd))
377 except ImportError as e:
378 # is_cmd_found uses pywin32 on windows, which might not be available
379 if sys.platform == 'win32' and 'pywin32' in str(e):
380 return skip("This test runs only if pywin32 and command '{0}' "
381 "is installed".format(cmd))
382 raise e
383 return null_deco
359 return null_deco
384
360
385 def onlyif_any_cmd_exists(*commands):
361 def onlyif_any_cmd_exists(*commands):
386 """
362 """
387 Decorator to skip test unless at least one of `commands` is found.
363 Decorator to skip test unless at least one of `commands` is found.
388 """
364 """
389 for cmd in commands:
365 for cmd in commands:
390 try:
366 if which(cmd):
391 if is_cmd_found(cmd):
367 return null_deco
392 return null_deco
393 except ImportError as e:
394 # is_cmd_found uses pywin32 on windows, which might not be available
395 if sys.platform == 'win32' and 'pywin32' in str(e):
396 return skip("This test runs only if pywin32 and commands '{0}' "
397 "are installed".format(commands))
398 raise e
399 return skip("This test runs only if one of the commands {0} "
368 return skip("This test runs only if one of the commands {0} "
400 "is installed".format(commands))
369 "is installed".format(commands))
@@ -1,123 +1,106 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for working with external processes.
3 Utilities for working with external processes.
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 # Copyright (c) IPython Development Team.
7 # Copyright (C) 2008-2011 The IPython Development Team
7 # Distributed under the terms of the Modified BSD License.
8 #
8
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
12
13 #-----------------------------------------------------------------------------
14 # Imports
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
9 from __future__ import print_function
17
10
18 # Stdlib
19 import os
11 import os
20 import sys
12 import sys
21
13
22 # Our own
23 if sys.platform == 'win32':
14 if sys.platform == 'win32':
24 from ._process_win32 import _find_cmd, system, getoutput, arg_split, check_pid
15 from ._process_win32 import system, getoutput, arg_split, check_pid
25 elif sys.platform == 'cli':
16 elif sys.platform == 'cli':
26 from ._process_cli import _find_cmd, system, getoutput, arg_split, check_pid
17 from ._process_cli import system, getoutput, arg_split, check_pid
27 else:
18 else:
28 from ._process_posix import _find_cmd, system, getoutput, arg_split, check_pid
19 from ._process_posix import system, getoutput, arg_split, check_pid
29
20
30 from ._process_common import getoutputerror, get_output_error_code, process_handler
21 from ._process_common import getoutputerror, get_output_error_code, process_handler
31 from . import py3compat
22 from . import py3compat
32
23
33 #-----------------------------------------------------------------------------
34 # Code
35 #-----------------------------------------------------------------------------
36
37
24
38 class FindCmdError(Exception):
25 class FindCmdError(Exception):
39 pass
26 pass
40
27
41
28
42 def find_cmd(cmd):
29 def find_cmd(cmd):
43 """Find absolute path to executable cmd in a cross platform manner.
30 """Find absolute path to executable cmd in a cross platform manner.
44
31
45 This function tries to determine the full path to a command line program
32 This function tries to determine the full path to a command line program
46 using `which` on Unix/Linux/OS X and `win32api` on Windows. Most of the
33 using `which` on Unix/Linux/OS X and `win32api` on Windows. Most of the
47 time it will use the version that is first on the users `PATH`.
34 time it will use the version that is first on the users `PATH`.
48
35
49 Warning, don't use this to find IPython command line programs as there
36 Warning, don't use this to find IPython command line programs as there
50 is a risk you will find the wrong one. Instead find those using the
37 is a risk you will find the wrong one. Instead find those using the
51 following code and looking for the application itself::
38 following code and looking for the application itself::
52
39
53 from IPython.utils.path import get_ipython_module_path
40 from IPython.utils.path import get_ipython_module_path
54 from IPython.utils.process import pycmd2argv
41 from IPython.utils.process import pycmd2argv
55 argv = pycmd2argv(get_ipython_module_path('IPython.terminal.ipapp'))
42 argv = pycmd2argv(get_ipython_module_path('IPython.terminal.ipapp'))
56
43
57 Parameters
44 Parameters
58 ----------
45 ----------
59 cmd : str
46 cmd : str
60 The command line program to look for.
47 The command line program to look for.
61 """
48 """
62 try:
49 path = py3compat.which(cmd)
63 path = _find_cmd(cmd).rstrip()
50 if path is None:
64 except OSError:
65 raise FindCmdError('command could not be found: %s' % cmd)
66 # which returns empty if not found
67 if path == '':
68 raise FindCmdError('command could not be found: %s' % cmd)
51 raise FindCmdError('command could not be found: %s' % cmd)
69 return os.path.abspath(path)
52 return path
70
53
71
54
72 def is_cmd_found(cmd):
55 def is_cmd_found(cmd):
73 """Check whether executable `cmd` exists or not and return a bool."""
56 """Check whether executable `cmd` exists or not and return a bool."""
74 try:
57 try:
75 find_cmd(cmd)
58 find_cmd(cmd)
76 return True
59 return True
77 except FindCmdError:
60 except FindCmdError:
78 return False
61 return False
79
62
80
63
81 def pycmd2argv(cmd):
64 def pycmd2argv(cmd):
82 r"""Take the path of a python command and return a list (argv-style).
65 r"""Take the path of a python command and return a list (argv-style).
83
66
84 This only works on Python based command line programs and will find the
67 This only works on Python based command line programs and will find the
85 location of the ``python`` executable using ``sys.executable`` to make
68 location of the ``python`` executable using ``sys.executable`` to make
86 sure the right version is used.
69 sure the right version is used.
87
70
88 For a given path ``cmd``, this returns [cmd] if cmd's extension is .exe,
71 For a given path ``cmd``, this returns [cmd] if cmd's extension is .exe,
89 .com or .bat, and [, cmd] otherwise.
72 .com or .bat, and [, cmd] otherwise.
90
73
91 Parameters
74 Parameters
92 ----------
75 ----------
93 cmd : string
76 cmd : string
94 The path of the command.
77 The path of the command.
95
78
96 Returns
79 Returns
97 -------
80 -------
98 argv-style list.
81 argv-style list.
99 """
82 """
100 ext = os.path.splitext(cmd)[1]
83 ext = os.path.splitext(cmd)[1]
101 if ext in ['.exe', '.com', '.bat']:
84 if ext in ['.exe', '.com', '.bat']:
102 return [cmd]
85 return [cmd]
103 else:
86 else:
104 return [sys.executable, cmd]
87 return [sys.executable, cmd]
105
88
106
89
107 def abbrev_cwd():
90 def abbrev_cwd():
108 """ Return abbreviated version of cwd, e.g. d:mydir """
91 """ Return abbreviated version of cwd, e.g. d:mydir """
109 cwd = py3compat.getcwd().replace('\\','/')
92 cwd = py3compat.getcwd().replace('\\','/')
110 drivepart = ''
93 drivepart = ''
111 tail = cwd
94 tail = cwd
112 if sys.platform == 'win32':
95 if sys.platform == 'win32':
113 if len(cwd) < 4:
96 if len(cwd) < 4:
114 return cwd
97 return cwd
115 drivepart,tail = os.path.splitdrive(cwd)
98 drivepart,tail = os.path.splitdrive(cwd)
116
99
117
100
118 parts = tail.split('/')
101 parts = tail.split('/')
119 if len(parts) > 2:
102 if len(parts) > 2:
120 tail = '/'.join(parts[-2:])
103 tail = '/'.join(parts[-2:])
121
104
122 return (drivepart + (
105 return (drivepart + (
123 cwd == '/' and '/' or tail))
106 cwd == '/' and '/' or tail))
@@ -1,147 +1,149 b''
1 """Export to PDF via latex"""
1 """Export to PDF via latex"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import subprocess
6 import subprocess
7 import os
7 import os
8 import sys
8 import sys
9
9
10 from IPython.utils.process import find_cmd
10 from IPython.utils.py3compat import which
11 from IPython.utils.traitlets import Integer, List, Bool, Instance
11 from IPython.utils.traitlets import Integer, List, Bool, Instance
12 from IPython.utils.tempdir import TemporaryWorkingDirectory
12 from IPython.utils.tempdir import TemporaryWorkingDirectory
13 from .latex import LatexExporter
13 from .latex import LatexExporter
14
14
15
15
16 class PDFExporter(LatexExporter):
16 class PDFExporter(LatexExporter):
17 """Writer designed to write to PDF files"""
17 """Writer designed to write to PDF files"""
18
18
19 latex_count = Integer(3, config=True,
19 latex_count = Integer(3, config=True,
20 help="How many times latex will be called."
20 help="How many times latex will be called."
21 )
21 )
22
22
23 latex_command = List([u"pdflatex", u"{filename}"], config=True,
23 latex_command = List([u"pdflatex", u"{filename}"], config=True,
24 help="Shell command used to compile latex."
24 help="Shell command used to compile latex."
25 )
25 )
26
26
27 bib_command = List([u"bibtex", u"{filename}"], config=True,
27 bib_command = List([u"bibtex", u"{filename}"], config=True,
28 help="Shell command used to run bibtex."
28 help="Shell command used to run bibtex."
29 )
29 )
30
30
31 verbose = Bool(False, config=True,
31 verbose = Bool(False, config=True,
32 help="Whether to display the output of latex commands."
32 help="Whether to display the output of latex commands."
33 )
33 )
34
34
35 temp_file_exts = List(['.aux', '.bbl', '.blg', '.idx', '.log', '.out'], config=True,
35 temp_file_exts = List(['.aux', '.bbl', '.blg', '.idx', '.log', '.out'], config=True,
36 help="File extensions of temp files to remove after running."
36 help="File extensions of temp files to remove after running."
37 )
37 )
38
38
39 writer = Instance("jupyter_nbconvert.writers.FilesWriter", args=())
39 writer = Instance("jupyter_nbconvert.writers.FilesWriter", args=())
40
40
41 def run_command(self, command_list, filename, count, log_function):
41 def run_command(self, command_list, filename, count, log_function):
42 """Run command_list count times.
42 """Run command_list count times.
43
43
44 Parameters
44 Parameters
45 ----------
45 ----------
46 command_list : list
46 command_list : list
47 A list of args to provide to Popen. Each element of this
47 A list of args to provide to Popen. Each element of this
48 list will be interpolated with the filename to convert.
48 list will be interpolated with the filename to convert.
49 filename : unicode
49 filename : unicode
50 The name of the file to convert.
50 The name of the file to convert.
51 count : int
51 count : int
52 How many times to run the command.
52 How many times to run the command.
53
53
54 Returns
54 Returns
55 -------
55 -------
56 success : bool
56 success : bool
57 A boolean indicating if the command was successful (True)
57 A boolean indicating if the command was successful (True)
58 or failed (False).
58 or failed (False).
59 """
59 """
60 command = [c.format(filename=filename) for c in command_list]
60 command = [c.format(filename=filename) for c in command_list]
61
61
62 # On windows with python 2.x there is a bug in subprocess.Popen and
62 # On windows with python 2.x there is a bug in subprocess.Popen and
63 # unicode commands are not supported
63 # unicode commands are not supported
64 if sys.platform == 'win32' and sys.version_info < (3,0):
64 if sys.platform == 'win32' and sys.version_info < (3,0):
65 #We must use cp1252 encoding for calling subprocess.Popen
65 #We must use cp1252 encoding for calling subprocess.Popen
66 #Note that sys.stdin.encoding and encoding.DEFAULT_ENCODING
66 #Note that sys.stdin.encoding and encoding.DEFAULT_ENCODING
67 # could be different (cp437 in case of dos console)
67 # could be different (cp437 in case of dos console)
68 command = [c.encode('cp1252') for c in command]
68 command = [c.encode('cp1252') for c in command]
69
69
70 # This will throw a clearer error if the command is not found
70 # This will throw a clearer error if the command is not found
71 find_cmd(command_list[0])
71 cmd = which(command_list[0])
72 if cmd is None:
73 raise OSError("%s not found on PATH" % command_list[0])
72
74
73 times = 'time' if count == 1 else 'times'
75 times = 'time' if count == 1 else 'times'
74 self.log.info("Running %s %i %s: %s", command_list[0], count, times, command)
76 self.log.info("Running %s %i %s: %s", command_list[0], count, times, command)
75 with open(os.devnull, 'rb') as null:
77 with open(os.devnull, 'rb') as null:
76 stdout = subprocess.PIPE if not self.verbose else None
78 stdout = subprocess.PIPE if not self.verbose else None
77 for index in range(count):
79 for index in range(count):
78 p = subprocess.Popen(command, stdout=stdout, stdin=null)
80 p = subprocess.Popen(command, stdout=stdout, stdin=null)
79 out, err = p.communicate()
81 out, err = p.communicate()
80 if p.returncode:
82 if p.returncode:
81 if self.verbose:
83 if self.verbose:
82 # verbose means I didn't capture stdout with PIPE,
84 # verbose means I didn't capture stdout with PIPE,
83 # so it's already been displayed and `out` is None.
85 # so it's already been displayed and `out` is None.
84 out = u''
86 out = u''
85 else:
87 else:
86 out = out.decode('utf-8', 'replace')
88 out = out.decode('utf-8', 'replace')
87 log_function(command, out)
89 log_function(command, out)
88 return False # failure
90 return False # failure
89 return True # success
91 return True # success
90
92
91 def run_latex(self, filename):
93 def run_latex(self, filename):
92 """Run pdflatex self.latex_count times."""
94 """Run pdflatex self.latex_count times."""
93
95
94 def log_error(command, out):
96 def log_error(command, out):
95 self.log.critical(u"%s failed: %s\n%s", command[0], command, out)
97 self.log.critical(u"%s failed: %s\n%s", command[0], command, out)
96
98
97 return self.run_command(self.latex_command, filename,
99 return self.run_command(self.latex_command, filename,
98 self.latex_count, log_error)
100 self.latex_count, log_error)
99
101
100 def run_bib(self, filename):
102 def run_bib(self, filename):
101 """Run bibtex self.latex_count times."""
103 """Run bibtex self.latex_count times."""
102 filename = os.path.splitext(filename)[0]
104 filename = os.path.splitext(filename)[0]
103
105
104 def log_error(command, out):
106 def log_error(command, out):
105 self.log.warn('%s had problems, most likely because there were no citations',
107 self.log.warn('%s had problems, most likely because there were no citations',
106 command[0])
108 command[0])
107 self.log.debug(u"%s output: %s\n%s", command[0], command, out)
109 self.log.debug(u"%s output: %s\n%s", command[0], command, out)
108
110
109 return self.run_command(self.bib_command, filename, 1, log_error)
111 return self.run_command(self.bib_command, filename, 1, log_error)
110
112
111 def clean_temp_files(self, filename):
113 def clean_temp_files(self, filename):
112 """Remove temporary files created by pdflatex/bibtex."""
114 """Remove temporary files created by pdflatex/bibtex."""
113 self.log.info("Removing temporary LaTeX files")
115 self.log.info("Removing temporary LaTeX files")
114 filename = os.path.splitext(filename)[0]
116 filename = os.path.splitext(filename)[0]
115 for ext in self.temp_file_exts:
117 for ext in self.temp_file_exts:
116 try:
118 try:
117 os.remove(filename+ext)
119 os.remove(filename+ext)
118 except OSError:
120 except OSError:
119 pass
121 pass
120
122
121 def from_notebook_node(self, nb, resources=None, **kw):
123 def from_notebook_node(self, nb, resources=None, **kw):
122 latex, resources = super(PDFExporter, self).from_notebook_node(
124 latex, resources = super(PDFExporter, self).from_notebook_node(
123 nb, resources=resources, **kw
125 nb, resources=resources, **kw
124 )
126 )
125 with TemporaryWorkingDirectory() as td:
127 with TemporaryWorkingDirectory() as td:
126 notebook_name = "notebook"
128 notebook_name = "notebook"
127 tex_file = self.writer.write(latex, resources, notebook_name=notebook_name)
129 tex_file = self.writer.write(latex, resources, notebook_name=notebook_name)
128 self.log.info("Building PDF")
130 self.log.info("Building PDF")
129 rc = self.run_latex(tex_file)
131 rc = self.run_latex(tex_file)
130 if not rc:
132 if not rc:
131 rc = self.run_bib(tex_file)
133 rc = self.run_bib(tex_file)
132 if not rc:
134 if not rc:
133 rc = self.run_latex(tex_file)
135 rc = self.run_latex(tex_file)
134
136
135 pdf_file = notebook_name + '.pdf'
137 pdf_file = notebook_name + '.pdf'
136 if not os.path.isfile(pdf_file):
138 if not os.path.isfile(pdf_file):
137 raise RuntimeError("PDF creating failed")
139 raise RuntimeError("PDF creating failed")
138 self.log.info('PDF successfully created')
140 self.log.info('PDF successfully created')
139 with open(pdf_file, 'rb') as f:
141 with open(pdf_file, 'rb') as f:
140 pdf_data = f.read()
142 pdf_data = f.read()
141
143
142 # convert output extension to pdf
144 # convert output extension to pdf
143 # the writer above required it to be tex
145 # the writer above required it to be tex
144 resources['output_extension'] = '.pdf'
146 resources['output_extension'] = '.pdf'
145
147
146 return pdf_data, resources
148 return pdf_data, resources
147
149
General Comments 0
You need to be logged in to leave comments. Login now