Show More
@@ -1,654 +1,657 | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | """Display formatters. |
|
3 | 3 | |
|
4 | 4 | Inheritance diagram: |
|
5 | 5 | |
|
6 | 6 | .. inheritance-diagram:: IPython.core.formatters |
|
7 | 7 | :parts: 3 |
|
8 | 8 | |
|
9 | 9 | Authors: |
|
10 | 10 | |
|
11 | 11 | * Robert Kern |
|
12 | 12 | * Brian Granger |
|
13 | 13 | """ |
|
14 | 14 | #----------------------------------------------------------------------------- |
|
15 | 15 | # Copyright (C) 2010-2011, IPython Development Team. |
|
16 | 16 | # |
|
17 | 17 | # Distributed under the terms of the Modified BSD License. |
|
18 | 18 | # |
|
19 | 19 | # The full license is in the file COPYING.txt, distributed with this software. |
|
20 | 20 | #----------------------------------------------------------------------------- |
|
21 | 21 | |
|
22 | 22 | #----------------------------------------------------------------------------- |
|
23 | 23 | # Imports |
|
24 | 24 | #----------------------------------------------------------------------------- |
|
25 | 25 | |
|
26 | 26 | # Stdlib imports |
|
27 | 27 | import abc |
|
28 | 28 | import sys |
|
29 | 29 | import warnings |
|
30 | # We must use StringIO, as cStringIO doesn't handle unicode properly. | |
|
31 | from io import StringIO | |
|
32 | 30 | |
|
33 | 31 | # Our own imports |
|
34 | 32 | from IPython.config.configurable import Configurable |
|
35 | 33 | from IPython.lib import pretty |
|
36 | 34 | from IPython.utils.traitlets import ( |
|
37 | 35 | Bool, Dict, Integer, Unicode, CUnicode, ObjectName, List, |
|
38 | 36 | ) |
|
39 | from IPython.utils.py3compat import unicode_to_str, with_metaclass | |
|
37 | from IPython.utils.py3compat import unicode_to_str, with_metaclass, PY3 | |
|
38 | ||
|
39 | if PY3: | |
|
40 | from io import StringIO | |
|
41 | else: | |
|
42 | from StringIO import StringIO | |
|
40 | 43 | |
|
41 | 44 | |
|
42 | 45 | #----------------------------------------------------------------------------- |
|
43 | 46 | # The main DisplayFormatter class |
|
44 | 47 | #----------------------------------------------------------------------------- |
|
45 | 48 | |
|
46 | 49 | |
|
47 | 50 | class DisplayFormatter(Configurable): |
|
48 | 51 | |
|
49 | 52 | # When set to true only the default plain text formatter will be used. |
|
50 | 53 | plain_text_only = Bool(False, config=True) |
|
51 | 54 | def _plain_text_only_changed(self, name, old, new): |
|
52 | 55 | warnings.warn("""DisplayFormatter.plain_text_only is deprecated. |
|
53 | 56 | |
|
54 | 57 | Use DisplayFormatter.active_types = ['text/plain'] |
|
55 | 58 | for the same effect. |
|
56 | 59 | """, DeprecationWarning) |
|
57 | 60 | if new: |
|
58 | 61 | self.active_types = ['text/plain'] |
|
59 | 62 | else: |
|
60 | 63 | self.active_types = self.format_types |
|
61 | 64 | |
|
62 | 65 | active_types = List(Unicode, config=True, |
|
63 | 66 | help="""List of currently active mime-types to display. |
|
64 | 67 | You can use this to set a white-list for formats to display. |
|
65 | 68 | |
|
66 | 69 | Most users will not need to change this value. |
|
67 | 70 | """) |
|
68 | 71 | def _active_types_default(self): |
|
69 | 72 | return self.format_types |
|
70 | 73 | |
|
71 | 74 | def _active_types_changed(self, name, old, new): |
|
72 | 75 | for key, formatter in self.formatters.items(): |
|
73 | 76 | if key in new: |
|
74 | 77 | formatter.enabled = True |
|
75 | 78 | else: |
|
76 | 79 | formatter.enabled = False |
|
77 | 80 | |
|
78 | 81 | # A dict of formatter whose keys are format types (MIME types) and whose |
|
79 | 82 | # values are subclasses of BaseFormatter. |
|
80 | 83 | formatters = Dict() |
|
81 | 84 | def _formatters_default(self): |
|
82 | 85 | """Activate the default formatters.""" |
|
83 | 86 | formatter_classes = [ |
|
84 | 87 | PlainTextFormatter, |
|
85 | 88 | HTMLFormatter, |
|
86 | 89 | SVGFormatter, |
|
87 | 90 | PNGFormatter, |
|
88 | 91 | JPEGFormatter, |
|
89 | 92 | LatexFormatter, |
|
90 | 93 | JSONFormatter, |
|
91 | 94 | JavascriptFormatter |
|
92 | 95 | ] |
|
93 | 96 | d = {} |
|
94 | 97 | for cls in formatter_classes: |
|
95 | 98 | f = cls(parent=self) |
|
96 | 99 | d[f.format_type] = f |
|
97 | 100 | return d |
|
98 | 101 | |
|
99 | 102 | def format(self, obj, include=None, exclude=None): |
|
100 | 103 | """Return a format data dict for an object. |
|
101 | 104 | |
|
102 | 105 | By default all format types will be computed. |
|
103 | 106 | |
|
104 | 107 | The following MIME types are currently implemented: |
|
105 | 108 | |
|
106 | 109 | * text/plain |
|
107 | 110 | * text/html |
|
108 | 111 | * text/latex |
|
109 | 112 | * application/json |
|
110 | 113 | * application/javascript |
|
111 | 114 | * image/png |
|
112 | 115 | * image/jpeg |
|
113 | 116 | * image/svg+xml |
|
114 | 117 | |
|
115 | 118 | Parameters |
|
116 | 119 | ---------- |
|
117 | 120 | obj : object |
|
118 | 121 | The Python object whose format data will be computed. |
|
119 | 122 | include : list or tuple, optional |
|
120 | 123 | A list of format type strings (MIME types) to include in the |
|
121 | 124 | format data dict. If this is set *only* the format types included |
|
122 | 125 | in this list will be computed. |
|
123 | 126 | exclude : list or tuple, optional |
|
124 | 127 | A list of format type string (MIME types) to exclude in the format |
|
125 | 128 | data dict. If this is set all format types will be computed, |
|
126 | 129 | except for those included in this argument. |
|
127 | 130 | |
|
128 | 131 | Returns |
|
129 | 132 | ------- |
|
130 | 133 | (format_dict, metadata_dict) : tuple of two dicts |
|
131 | 134 | |
|
132 | 135 | format_dict is a dictionary of key/value pairs, one of each format that was |
|
133 | 136 | generated for the object. The keys are the format types, which |
|
134 | 137 | will usually be MIME type strings and the values and JSON'able |
|
135 | 138 | data structure containing the raw data for the representation in |
|
136 | 139 | that format. |
|
137 | 140 | |
|
138 | 141 | metadata_dict is a dictionary of metadata about each mime-type output. |
|
139 | 142 | Its keys will be a strict subset of the keys in format_dict. |
|
140 | 143 | """ |
|
141 | 144 | format_dict = {} |
|
142 | 145 | md_dict = {} |
|
143 | 146 | |
|
144 | 147 | for format_type, formatter in self.formatters.items(): |
|
145 | 148 | if include and format_type not in include: |
|
146 | 149 | continue |
|
147 | 150 | if exclude and format_type in exclude: |
|
148 | 151 | continue |
|
149 | 152 | |
|
150 | 153 | md = None |
|
151 | 154 | try: |
|
152 | 155 | data = formatter(obj) |
|
153 | 156 | except: |
|
154 | 157 | # FIXME: log the exception |
|
155 | 158 | raise |
|
156 | 159 | |
|
157 | 160 | # formatters can return raw data or (data, metadata) |
|
158 | 161 | if isinstance(data, tuple) and len(data) == 2: |
|
159 | 162 | data, md = data |
|
160 | 163 | |
|
161 | 164 | if data is not None: |
|
162 | 165 | format_dict[format_type] = data |
|
163 | 166 | if md is not None: |
|
164 | 167 | md_dict[format_type] = md |
|
165 | 168 | |
|
166 | 169 | return format_dict, md_dict |
|
167 | 170 | |
|
168 | 171 | @property |
|
169 | 172 | def format_types(self): |
|
170 | 173 | """Return the format types (MIME types) of the active formatters.""" |
|
171 | 174 | return list(self.formatters.keys()) |
|
172 | 175 | |
|
173 | 176 | |
|
174 | 177 | #----------------------------------------------------------------------------- |
|
175 | 178 | # Formatters for specific format types (text, html, svg, etc.) |
|
176 | 179 | #----------------------------------------------------------------------------- |
|
177 | 180 | |
|
178 | 181 | |
|
179 | 182 | class FormatterABC(with_metaclass(abc.ABCMeta, object)): |
|
180 | 183 | """ Abstract base class for Formatters. |
|
181 | 184 | |
|
182 | 185 | A formatter is a callable class that is responsible for computing the |
|
183 | 186 | raw format data for a particular format type (MIME type). For example, |
|
184 | 187 | an HTML formatter would have a format type of `text/html` and would return |
|
185 | 188 | the HTML representation of the object when called. |
|
186 | 189 | """ |
|
187 | 190 | |
|
188 | 191 | # The format type of the data returned, usually a MIME type. |
|
189 | 192 | format_type = 'text/plain' |
|
190 | 193 | |
|
191 | 194 | # Is the formatter enabled... |
|
192 | 195 | enabled = True |
|
193 | 196 | |
|
194 | 197 | @abc.abstractmethod |
|
195 | 198 | def __call__(self, obj): |
|
196 | 199 | """Return a JSON'able representation of the object. |
|
197 | 200 | |
|
198 | 201 | If the object cannot be formatted by this formatter, then return None |
|
199 | 202 | """ |
|
200 | 203 | try: |
|
201 | 204 | return repr(obj) |
|
202 | 205 | except TypeError: |
|
203 | 206 | return None |
|
204 | 207 | |
|
205 | 208 | |
|
206 | 209 | class BaseFormatter(Configurable): |
|
207 | 210 | """A base formatter class that is configurable. |
|
208 | 211 | |
|
209 | 212 | This formatter should usually be used as the base class of all formatters. |
|
210 | 213 | It is a traited :class:`Configurable` class and includes an extensible |
|
211 | 214 | API for users to determine how their objects are formatted. The following |
|
212 | 215 | logic is used to find a function to format an given object. |
|
213 | 216 | |
|
214 | 217 | 1. The object is introspected to see if it has a method with the name |
|
215 | 218 | :attr:`print_method`. If is does, that object is passed to that method |
|
216 | 219 | for formatting. |
|
217 | 220 | 2. If no print method is found, three internal dictionaries are consulted |
|
218 | 221 | to find print method: :attr:`singleton_printers`, :attr:`type_printers` |
|
219 | 222 | and :attr:`deferred_printers`. |
|
220 | 223 | |
|
221 | 224 | Users should use these dictionaries to register functions that will be |
|
222 | 225 | used to compute the format data for their objects (if those objects don't |
|
223 | 226 | have the special print methods). The easiest way of using these |
|
224 | 227 | dictionaries is through the :meth:`for_type` and :meth:`for_type_by_name` |
|
225 | 228 | methods. |
|
226 | 229 | |
|
227 | 230 | If no function/callable is found to compute the format data, ``None`` is |
|
228 | 231 | returned and this format type is not used. |
|
229 | 232 | """ |
|
230 | 233 | |
|
231 | 234 | format_type = Unicode('text/plain') |
|
232 | 235 | |
|
233 | 236 | enabled = Bool(True, config=True) |
|
234 | 237 | |
|
235 | 238 | print_method = ObjectName('__repr__') |
|
236 | 239 | |
|
237 | 240 | # The singleton printers. |
|
238 | 241 | # Maps the IDs of the builtin singleton objects to the format functions. |
|
239 | 242 | singleton_printers = Dict(config=True) |
|
240 | 243 | def _singleton_printers_default(self): |
|
241 | 244 | return {} |
|
242 | 245 | |
|
243 | 246 | # The type-specific printers. |
|
244 | 247 | # Map type objects to the format functions. |
|
245 | 248 | type_printers = Dict(config=True) |
|
246 | 249 | def _type_printers_default(self): |
|
247 | 250 | return {} |
|
248 | 251 | |
|
249 | 252 | # The deferred-import type-specific printers. |
|
250 | 253 | # Map (modulename, classname) pairs to the format functions. |
|
251 | 254 | deferred_printers = Dict(config=True) |
|
252 | 255 | def _deferred_printers_default(self): |
|
253 | 256 | return {} |
|
254 | 257 | |
|
255 | 258 | def __call__(self, obj): |
|
256 | 259 | """Compute the format for an object.""" |
|
257 | 260 | if self.enabled: |
|
258 | 261 | obj_id = id(obj) |
|
259 | 262 | try: |
|
260 | 263 | obj_class = getattr(obj, '__class__', None) or type(obj) |
|
261 | 264 | # First try to find registered singleton printers for the type. |
|
262 | 265 | try: |
|
263 | 266 | printer = self.singleton_printers[obj_id] |
|
264 | 267 | except (TypeError, KeyError): |
|
265 | 268 | pass |
|
266 | 269 | else: |
|
267 | 270 | return printer(obj) |
|
268 | 271 | # Next look for type_printers. |
|
269 | 272 | for cls in pretty._get_mro(obj_class): |
|
270 | 273 | if cls in self.type_printers: |
|
271 | 274 | return self.type_printers[cls](obj) |
|
272 | 275 | else: |
|
273 | 276 | printer = self._in_deferred_types(cls) |
|
274 | 277 | if printer is not None: |
|
275 | 278 | return printer(obj) |
|
276 | 279 | # Finally look for special method names. |
|
277 | 280 | if hasattr(obj_class, self.print_method): |
|
278 | 281 | printer = getattr(obj_class, self.print_method) |
|
279 | 282 | return printer(obj) |
|
280 | 283 | return None |
|
281 | 284 | except Exception: |
|
282 | 285 | pass |
|
283 | 286 | else: |
|
284 | 287 | return None |
|
285 | 288 | |
|
286 | 289 | def for_type(self, typ, func): |
|
287 | 290 | """Add a format function for a given type. |
|
288 | 291 | |
|
289 | 292 | Parameters |
|
290 | 293 | ----------- |
|
291 | 294 | typ : class |
|
292 | 295 | The class of the object that will be formatted using `func`. |
|
293 | 296 | func : callable |
|
294 | 297 | The callable that will be called to compute the format data. The |
|
295 | 298 | call signature of this function is simple, it must take the |
|
296 | 299 | object to be formatted and return the raw data for the given |
|
297 | 300 | format. Subclasses may use a different call signature for the |
|
298 | 301 | `func` argument. |
|
299 | 302 | """ |
|
300 | 303 | oldfunc = self.type_printers.get(typ, None) |
|
301 | 304 | if func is not None: |
|
302 | 305 | # To support easy restoration of old printers, we need to ignore |
|
303 | 306 | # Nones. |
|
304 | 307 | self.type_printers[typ] = func |
|
305 | 308 | return oldfunc |
|
306 | 309 | |
|
307 | 310 | def for_type_by_name(self, type_module, type_name, func): |
|
308 | 311 | """Add a format function for a type specified by the full dotted |
|
309 | 312 | module and name of the type, rather than the type of the object. |
|
310 | 313 | |
|
311 | 314 | Parameters |
|
312 | 315 | ---------- |
|
313 | 316 | type_module : str |
|
314 | 317 | The full dotted name of the module the type is defined in, like |
|
315 | 318 | ``numpy``. |
|
316 | 319 | type_name : str |
|
317 | 320 | The name of the type (the class name), like ``dtype`` |
|
318 | 321 | func : callable |
|
319 | 322 | The callable that will be called to compute the format data. The |
|
320 | 323 | call signature of this function is simple, it must take the |
|
321 | 324 | object to be formatted and return the raw data for the given |
|
322 | 325 | format. Subclasses may use a different call signature for the |
|
323 | 326 | `func` argument. |
|
324 | 327 | """ |
|
325 | 328 | key = (type_module, type_name) |
|
326 | 329 | oldfunc = self.deferred_printers.get(key, None) |
|
327 | 330 | if func is not None: |
|
328 | 331 | # To support easy restoration of old printers, we need to ignore |
|
329 | 332 | # Nones. |
|
330 | 333 | self.deferred_printers[key] = func |
|
331 | 334 | return oldfunc |
|
332 | 335 | |
|
333 | 336 | def _in_deferred_types(self, cls): |
|
334 | 337 | """ |
|
335 | 338 | Check if the given class is specified in the deferred type registry. |
|
336 | 339 | |
|
337 | 340 | Returns the printer from the registry if it exists, and None if the |
|
338 | 341 | class is not in the registry. Successful matches will be moved to the |
|
339 | 342 | regular type registry for future use. |
|
340 | 343 | """ |
|
341 | 344 | mod = getattr(cls, '__module__', None) |
|
342 | 345 | name = getattr(cls, '__name__', None) |
|
343 | 346 | key = (mod, name) |
|
344 | 347 | printer = None |
|
345 | 348 | if key in self.deferred_printers: |
|
346 | 349 | # Move the printer over to the regular registry. |
|
347 | 350 | printer = self.deferred_printers.pop(key) |
|
348 | 351 | self.type_printers[cls] = printer |
|
349 | 352 | return printer |
|
350 | 353 | |
|
351 | 354 | |
|
352 | 355 | class PlainTextFormatter(BaseFormatter): |
|
353 | 356 | """The default pretty-printer. |
|
354 | 357 | |
|
355 | 358 | This uses :mod:`IPython.lib.pretty` to compute the format data of |
|
356 | 359 | the object. If the object cannot be pretty printed, :func:`repr` is used. |
|
357 | 360 | See the documentation of :mod:`IPython.lib.pretty` for details on |
|
358 | 361 | how to write pretty printers. Here is a simple example:: |
|
359 | 362 | |
|
360 | 363 | def dtype_pprinter(obj, p, cycle): |
|
361 | 364 | if cycle: |
|
362 | 365 | return p.text('dtype(...)') |
|
363 | 366 | if hasattr(obj, 'fields'): |
|
364 | 367 | if obj.fields is None: |
|
365 | 368 | p.text(repr(obj)) |
|
366 | 369 | else: |
|
367 | 370 | p.begin_group(7, 'dtype([') |
|
368 | 371 | for i, field in enumerate(obj.descr): |
|
369 | 372 | if i > 0: |
|
370 | 373 | p.text(',') |
|
371 | 374 | p.breakable() |
|
372 | 375 | p.pretty(field) |
|
373 | 376 | p.end_group(7, '])') |
|
374 | 377 | """ |
|
375 | 378 | |
|
376 | 379 | # The format type of data returned. |
|
377 | 380 | format_type = Unicode('text/plain') |
|
378 | 381 | |
|
379 | 382 | # This subclass ignores this attribute as it always need to return |
|
380 | 383 | # something. |
|
381 | 384 | enabled = Bool(True, config=False) |
|
382 | 385 | |
|
383 | 386 | # Look for a _repr_pretty_ methods to use for pretty printing. |
|
384 | 387 | print_method = ObjectName('_repr_pretty_') |
|
385 | 388 | |
|
386 | 389 | # Whether to pretty-print or not. |
|
387 | 390 | pprint = Bool(True, config=True) |
|
388 | 391 | |
|
389 | 392 | # Whether to be verbose or not. |
|
390 | 393 | verbose = Bool(False, config=True) |
|
391 | 394 | |
|
392 | 395 | # The maximum width. |
|
393 | 396 | max_width = Integer(79, config=True) |
|
394 | 397 | |
|
395 | 398 | # The newline character. |
|
396 | 399 | newline = Unicode('\n', config=True) |
|
397 | 400 | |
|
398 | 401 | # format-string for pprinting floats |
|
399 | 402 | float_format = Unicode('%r') |
|
400 | 403 | # setter for float precision, either int or direct format-string |
|
401 | 404 | float_precision = CUnicode('', config=True) |
|
402 | 405 | |
|
403 | 406 | def _float_precision_changed(self, name, old, new): |
|
404 | 407 | """float_precision changed, set float_format accordingly. |
|
405 | 408 | |
|
406 | 409 | float_precision can be set by int or str. |
|
407 | 410 | This will set float_format, after interpreting input. |
|
408 | 411 | If numpy has been imported, numpy print precision will also be set. |
|
409 | 412 | |
|
410 | 413 | integer `n` sets format to '%.nf', otherwise, format set directly. |
|
411 | 414 | |
|
412 | 415 | An empty string returns to defaults (repr for float, 8 for numpy). |
|
413 | 416 | |
|
414 | 417 | This parameter can be set via the '%precision' magic. |
|
415 | 418 | """ |
|
416 | 419 | |
|
417 | 420 | if '%' in new: |
|
418 | 421 | # got explicit format string |
|
419 | 422 | fmt = new |
|
420 | 423 | try: |
|
421 | 424 | fmt%3.14159 |
|
422 | 425 | except Exception: |
|
423 | 426 | raise ValueError("Precision must be int or format string, not %r"%new) |
|
424 | 427 | elif new: |
|
425 | 428 | # otherwise, should be an int |
|
426 | 429 | try: |
|
427 | 430 | i = int(new) |
|
428 | 431 | assert i >= 0 |
|
429 | 432 | except ValueError: |
|
430 | 433 | raise ValueError("Precision must be int or format string, not %r"%new) |
|
431 | 434 | except AssertionError: |
|
432 | 435 | raise ValueError("int precision must be non-negative, not %r"%i) |
|
433 | 436 | |
|
434 | 437 | fmt = '%%.%if'%i |
|
435 | 438 | if 'numpy' in sys.modules: |
|
436 | 439 | # set numpy precision if it has been imported |
|
437 | 440 | import numpy |
|
438 | 441 | numpy.set_printoptions(precision=i) |
|
439 | 442 | else: |
|
440 | 443 | # default back to repr |
|
441 | 444 | fmt = '%r' |
|
442 | 445 | if 'numpy' in sys.modules: |
|
443 | 446 | import numpy |
|
444 | 447 | # numpy default is 8 |
|
445 | 448 | numpy.set_printoptions(precision=8) |
|
446 | 449 | self.float_format = fmt |
|
447 | 450 | |
|
448 | 451 | # Use the default pretty printers from IPython.lib.pretty. |
|
449 | 452 | def _singleton_printers_default(self): |
|
450 | 453 | return pretty._singleton_pprinters.copy() |
|
451 | 454 | |
|
452 | 455 | def _type_printers_default(self): |
|
453 | 456 | d = pretty._type_pprinters.copy() |
|
454 | 457 | d[float] = lambda obj,p,cycle: p.text(self.float_format%obj) |
|
455 | 458 | return d |
|
456 | 459 | |
|
457 | 460 | def _deferred_printers_default(self): |
|
458 | 461 | return pretty._deferred_type_pprinters.copy() |
|
459 | 462 | |
|
460 | 463 | #### FormatterABC interface #### |
|
461 | 464 | |
|
462 | 465 | def __call__(self, obj): |
|
463 | 466 | """Compute the pretty representation of the object.""" |
|
464 | 467 | if not self.pprint: |
|
465 | 468 | try: |
|
466 | 469 | return repr(obj) |
|
467 | 470 | except TypeError: |
|
468 | 471 | return '' |
|
469 | 472 | else: |
|
470 | 473 | # This uses use StringIO, as cStringIO doesn't handle unicode. |
|
471 | 474 | stream = StringIO() |
|
472 | 475 | # self.newline.encode() is a quick fix for issue gh-597. We need to |
|
473 | 476 | # ensure that stream does not get a mix of unicode and bytestrings, |
|
474 | 477 | # or it will cause trouble. |
|
475 | 478 | printer = pretty.RepresentationPrinter(stream, self.verbose, |
|
476 | 479 | self.max_width, unicode_to_str(self.newline), |
|
477 | 480 | singleton_pprinters=self.singleton_printers, |
|
478 | 481 | type_pprinters=self.type_printers, |
|
479 | 482 | deferred_pprinters=self.deferred_printers) |
|
480 | 483 | printer.pretty(obj) |
|
481 | 484 | printer.flush() |
|
482 | 485 | return stream.getvalue() |
|
483 | 486 | |
|
484 | 487 | |
|
485 | 488 | class HTMLFormatter(BaseFormatter): |
|
486 | 489 | """An HTML formatter. |
|
487 | 490 | |
|
488 | 491 | To define the callables that compute the HTML representation of your |
|
489 | 492 | objects, define a :meth:`_repr_html_` method or use the :meth:`for_type` |
|
490 | 493 | or :meth:`for_type_by_name` methods to register functions that handle |
|
491 | 494 | this. |
|
492 | 495 | |
|
493 | 496 | The return value of this formatter should be a valid HTML snippet that |
|
494 | 497 | could be injected into an existing DOM. It should *not* include the |
|
495 | 498 | ```<html>`` or ```<body>`` tags. |
|
496 | 499 | """ |
|
497 | 500 | format_type = Unicode('text/html') |
|
498 | 501 | |
|
499 | 502 | print_method = ObjectName('_repr_html_') |
|
500 | 503 | |
|
501 | 504 | |
|
502 | 505 | class SVGFormatter(BaseFormatter): |
|
503 | 506 | """An SVG formatter. |
|
504 | 507 | |
|
505 | 508 | To define the callables that compute the SVG representation of your |
|
506 | 509 | objects, define a :meth:`_repr_svg_` method or use the :meth:`for_type` |
|
507 | 510 | or :meth:`for_type_by_name` methods to register functions that handle |
|
508 | 511 | this. |
|
509 | 512 | |
|
510 | 513 | The return value of this formatter should be valid SVG enclosed in |
|
511 | 514 | ```<svg>``` tags, that could be injected into an existing DOM. It should |
|
512 | 515 | *not* include the ```<html>`` or ```<body>`` tags. |
|
513 | 516 | """ |
|
514 | 517 | format_type = Unicode('image/svg+xml') |
|
515 | 518 | |
|
516 | 519 | print_method = ObjectName('_repr_svg_') |
|
517 | 520 | |
|
518 | 521 | |
|
519 | 522 | class PNGFormatter(BaseFormatter): |
|
520 | 523 | """A PNG formatter. |
|
521 | 524 | |
|
522 | 525 | To define the callables that compute the PNG representation of your |
|
523 | 526 | objects, define a :meth:`_repr_png_` method or use the :meth:`for_type` |
|
524 | 527 | or :meth:`for_type_by_name` methods to register functions that handle |
|
525 | 528 | this. |
|
526 | 529 | |
|
527 | 530 | The return value of this formatter should be raw PNG data, *not* |
|
528 | 531 | base64 encoded. |
|
529 | 532 | """ |
|
530 | 533 | format_type = Unicode('image/png') |
|
531 | 534 | |
|
532 | 535 | print_method = ObjectName('_repr_png_') |
|
533 | 536 | |
|
534 | 537 | |
|
535 | 538 | class JPEGFormatter(BaseFormatter): |
|
536 | 539 | """A JPEG formatter. |
|
537 | 540 | |
|
538 | 541 | To define the callables that compute the JPEG representation of your |
|
539 | 542 | objects, define a :meth:`_repr_jpeg_` method or use the :meth:`for_type` |
|
540 | 543 | or :meth:`for_type_by_name` methods to register functions that handle |
|
541 | 544 | this. |
|
542 | 545 | |
|
543 | 546 | The return value of this formatter should be raw JPEG data, *not* |
|
544 | 547 | base64 encoded. |
|
545 | 548 | """ |
|
546 | 549 | format_type = Unicode('image/jpeg') |
|
547 | 550 | |
|
548 | 551 | print_method = ObjectName('_repr_jpeg_') |
|
549 | 552 | |
|
550 | 553 | |
|
551 | 554 | class LatexFormatter(BaseFormatter): |
|
552 | 555 | """A LaTeX formatter. |
|
553 | 556 | |
|
554 | 557 | To define the callables that compute the LaTeX representation of your |
|
555 | 558 | objects, define a :meth:`_repr_latex_` method or use the :meth:`for_type` |
|
556 | 559 | or :meth:`for_type_by_name` methods to register functions that handle |
|
557 | 560 | this. |
|
558 | 561 | |
|
559 | 562 | The return value of this formatter should be a valid LaTeX equation, |
|
560 | 563 | enclosed in either ```$```, ```$$``` or another LaTeX equation |
|
561 | 564 | environment. |
|
562 | 565 | """ |
|
563 | 566 | format_type = Unicode('text/latex') |
|
564 | 567 | |
|
565 | 568 | print_method = ObjectName('_repr_latex_') |
|
566 | 569 | |
|
567 | 570 | |
|
568 | 571 | class JSONFormatter(BaseFormatter): |
|
569 | 572 | """A JSON string formatter. |
|
570 | 573 | |
|
571 | 574 | To define the callables that compute the JSON string representation of |
|
572 | 575 | your objects, define a :meth:`_repr_json_` method or use the :meth:`for_type` |
|
573 | 576 | or :meth:`for_type_by_name` methods to register functions that handle |
|
574 | 577 | this. |
|
575 | 578 | |
|
576 | 579 | The return value of this formatter should be a valid JSON string. |
|
577 | 580 | """ |
|
578 | 581 | format_type = Unicode('application/json') |
|
579 | 582 | |
|
580 | 583 | print_method = ObjectName('_repr_json_') |
|
581 | 584 | |
|
582 | 585 | |
|
583 | 586 | class JavascriptFormatter(BaseFormatter): |
|
584 | 587 | """A Javascript formatter. |
|
585 | 588 | |
|
586 | 589 | To define the callables that compute the Javascript representation of |
|
587 | 590 | your objects, define a :meth:`_repr_javascript_` method or use the |
|
588 | 591 | :meth:`for_type` or :meth:`for_type_by_name` methods to register functions |
|
589 | 592 | that handle this. |
|
590 | 593 | |
|
591 | 594 | The return value of this formatter should be valid Javascript code and |
|
592 | 595 | should *not* be enclosed in ```<script>``` tags. |
|
593 | 596 | """ |
|
594 | 597 | format_type = Unicode('application/javascript') |
|
595 | 598 | |
|
596 | 599 | print_method = ObjectName('_repr_javascript_') |
|
597 | 600 | |
|
598 | 601 | FormatterABC.register(BaseFormatter) |
|
599 | 602 | FormatterABC.register(PlainTextFormatter) |
|
600 | 603 | FormatterABC.register(HTMLFormatter) |
|
601 | 604 | FormatterABC.register(SVGFormatter) |
|
602 | 605 | FormatterABC.register(PNGFormatter) |
|
603 | 606 | FormatterABC.register(JPEGFormatter) |
|
604 | 607 | FormatterABC.register(LatexFormatter) |
|
605 | 608 | FormatterABC.register(JSONFormatter) |
|
606 | 609 | FormatterABC.register(JavascriptFormatter) |
|
607 | 610 | |
|
608 | 611 | |
|
609 | 612 | def format_display_data(obj, include=None, exclude=None): |
|
610 | 613 | """Return a format data dict for an object. |
|
611 | 614 | |
|
612 | 615 | By default all format types will be computed. |
|
613 | 616 | |
|
614 | 617 | The following MIME types are currently implemented: |
|
615 | 618 | |
|
616 | 619 | * text/plain |
|
617 | 620 | * text/html |
|
618 | 621 | * text/latex |
|
619 | 622 | * application/json |
|
620 | 623 | * application/javascript |
|
621 | 624 | * image/png |
|
622 | 625 | * image/jpeg |
|
623 | 626 | * image/svg+xml |
|
624 | 627 | |
|
625 | 628 | Parameters |
|
626 | 629 | ---------- |
|
627 | 630 | obj : object |
|
628 | 631 | The Python object whose format data will be computed. |
|
629 | 632 | |
|
630 | 633 | Returns |
|
631 | 634 | ------- |
|
632 | 635 | format_dict : dict |
|
633 | 636 | A dictionary of key/value pairs, one or each format that was |
|
634 | 637 | generated for the object. The keys are the format types, which |
|
635 | 638 | will usually be MIME type strings and the values and JSON'able |
|
636 | 639 | data structure containing the raw data for the representation in |
|
637 | 640 | that format. |
|
638 | 641 | include : list or tuple, optional |
|
639 | 642 | A list of format type strings (MIME types) to include in the |
|
640 | 643 | format data dict. If this is set *only* the format types included |
|
641 | 644 | in this list will be computed. |
|
642 | 645 | exclude : list or tuple, optional |
|
643 | 646 | A list of format type string (MIME types) to exclue in the format |
|
644 | 647 | data dict. If this is set all format types will be computed, |
|
645 | 648 | except for those included in this argument. |
|
646 | 649 | """ |
|
647 | 650 | from IPython.core.interactiveshell import InteractiveShell |
|
648 | 651 | |
|
649 | 652 | InteractiveShell.instance().display_formatter.format( |
|
650 | 653 | obj, |
|
651 | 654 | include, |
|
652 | 655 | exclude |
|
653 | 656 | ) |
|
654 | 657 |
@@ -1,531 +1,535 | |||
|
1 | 1 | import abc |
|
2 | 2 | import functools |
|
3 | 3 | import re |
|
4 | from io import StringIO | |
|
5 | 4 | |
|
6 | 5 | from IPython.core.splitinput import LineInfo |
|
7 | 6 | from IPython.utils import tokenize2 |
|
8 | 7 | from IPython.utils.openpy import cookie_comment_re |
|
9 | from IPython.utils.py3compat import with_metaclass | |
|
8 | from IPython.utils.py3compat import with_metaclass, PY3 | |
|
10 | 9 | from IPython.utils.tokenize2 import generate_tokens, untokenize, TokenError |
|
11 | 10 | |
|
11 | if PY3: | |
|
12 | from io import StringIO | |
|
13 | else: | |
|
14 | from StringIO import StringIO | |
|
15 | ||
|
12 | 16 | #----------------------------------------------------------------------------- |
|
13 | 17 | # Globals |
|
14 | 18 | #----------------------------------------------------------------------------- |
|
15 | 19 | |
|
16 | 20 | # The escape sequences that define the syntax transformations IPython will |
|
17 | 21 | # apply to user input. These can NOT be just changed here: many regular |
|
18 | 22 | # expressions and other parts of the code may use their hardcoded values, and |
|
19 | 23 | # for all intents and purposes they constitute the 'IPython syntax', so they |
|
20 | 24 | # should be considered fixed. |
|
21 | 25 | |
|
22 | 26 | ESC_SHELL = '!' # Send line to underlying system shell |
|
23 | 27 | ESC_SH_CAP = '!!' # Send line to system shell and capture output |
|
24 | 28 | ESC_HELP = '?' # Find information about object |
|
25 | 29 | ESC_HELP2 = '??' # Find extra-detailed information about object |
|
26 | 30 | ESC_MAGIC = '%' # Call magic function |
|
27 | 31 | ESC_MAGIC2 = '%%' # Call cell-magic function |
|
28 | 32 | ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call |
|
29 | 33 | ESC_QUOTE2 = ';' # Quote all args as a single string, call |
|
30 | 34 | ESC_PAREN = '/' # Call first argument with rest of line as arguments |
|
31 | 35 | |
|
32 | 36 | ESC_SEQUENCES = [ESC_SHELL, ESC_SH_CAP, ESC_HELP ,\ |
|
33 | 37 | ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,\ |
|
34 | 38 | ESC_QUOTE, ESC_QUOTE2, ESC_PAREN ] |
|
35 | 39 | |
|
36 | 40 | |
|
37 | 41 | class InputTransformer(with_metaclass(abc.ABCMeta, object)): |
|
38 | 42 | """Abstract base class for line-based input transformers.""" |
|
39 | 43 | |
|
40 | 44 | @abc.abstractmethod |
|
41 | 45 | def push(self, line): |
|
42 | 46 | """Send a line of input to the transformer, returning the transformed |
|
43 | 47 | input or None if the transformer is waiting for more input. |
|
44 | 48 | |
|
45 | 49 | Must be overridden by subclasses. |
|
46 | 50 | """ |
|
47 | 51 | pass |
|
48 | 52 | |
|
49 | 53 | @abc.abstractmethod |
|
50 | 54 | def reset(self): |
|
51 | 55 | """Return, transformed any lines that the transformer has accumulated, |
|
52 | 56 | and reset its internal state. |
|
53 | 57 | |
|
54 | 58 | Must be overridden by subclasses. |
|
55 | 59 | """ |
|
56 | 60 | pass |
|
57 | 61 | |
|
58 | 62 | @classmethod |
|
59 | 63 | def wrap(cls, func): |
|
60 | 64 | """Can be used by subclasses as a decorator, to return a factory that |
|
61 | 65 | will allow instantiation with the decorated object. |
|
62 | 66 | """ |
|
63 | 67 | @functools.wraps(func) |
|
64 | 68 | def transformer_factory(**kwargs): |
|
65 | 69 | return cls(func, **kwargs) |
|
66 | 70 | |
|
67 | 71 | return transformer_factory |
|
68 | 72 | |
|
69 | 73 | class StatelessInputTransformer(InputTransformer): |
|
70 | 74 | """Wrapper for a stateless input transformer implemented as a function.""" |
|
71 | 75 | def __init__(self, func): |
|
72 | 76 | self.func = func |
|
73 | 77 | |
|
74 | 78 | def __repr__(self): |
|
75 | 79 | return "StatelessInputTransformer(func={0!r})".format(self.func) |
|
76 | 80 | |
|
77 | 81 | def push(self, line): |
|
78 | 82 | """Send a line of input to the transformer, returning the |
|
79 | 83 | transformed input.""" |
|
80 | 84 | return self.func(line) |
|
81 | 85 | |
|
82 | 86 | def reset(self): |
|
83 | 87 | """No-op - exists for compatibility.""" |
|
84 | 88 | pass |
|
85 | 89 | |
|
86 | 90 | class CoroutineInputTransformer(InputTransformer): |
|
87 | 91 | """Wrapper for an input transformer implemented as a coroutine.""" |
|
88 | 92 | def __init__(self, coro, **kwargs): |
|
89 | 93 | # Prime it |
|
90 | 94 | self.coro = coro(**kwargs) |
|
91 | 95 | next(self.coro) |
|
92 | 96 | |
|
93 | 97 | def __repr__(self): |
|
94 | 98 | return "CoroutineInputTransformer(coro={0!r})".format(self.coro) |
|
95 | 99 | |
|
96 | 100 | def push(self, line): |
|
97 | 101 | """Send a line of input to the transformer, returning the |
|
98 | 102 | transformed input or None if the transformer is waiting for more |
|
99 | 103 | input. |
|
100 | 104 | """ |
|
101 | 105 | return self.coro.send(line) |
|
102 | 106 | |
|
103 | 107 | def reset(self): |
|
104 | 108 | """Return, transformed any lines that the transformer has |
|
105 | 109 | accumulated, and reset its internal state. |
|
106 | 110 | """ |
|
107 | 111 | return self.coro.send(None) |
|
108 | 112 | |
|
109 | 113 | class TokenInputTransformer(InputTransformer): |
|
110 | 114 | """Wrapper for a token-based input transformer. |
|
111 | 115 | |
|
112 | 116 | func should accept a list of tokens (5-tuples, see tokenize docs), and |
|
113 | 117 | return an iterable which can be passed to tokenize.untokenize(). |
|
114 | 118 | """ |
|
115 | 119 | def __init__(self, func): |
|
116 | 120 | self.func = func |
|
117 | 121 | self.current_line = "" |
|
118 | 122 | self.line_used = False |
|
119 | 123 | self.reset_tokenizer() |
|
120 | 124 | |
|
121 | 125 | def reset_tokenizer(self): |
|
122 | 126 | self.tokenizer = generate_tokens(self.get_line) |
|
123 | 127 | |
|
124 | 128 | def get_line(self): |
|
125 | 129 | if self.line_used: |
|
126 | 130 | raise TokenError |
|
127 | 131 | self.line_used = True |
|
128 | 132 | return self.current_line |
|
129 | 133 | |
|
130 | 134 | def push(self, line): |
|
131 | 135 | self.current_line += line + "\n" |
|
132 | 136 | if self.current_line.isspace(): |
|
133 | 137 | return self.reset() |
|
134 | 138 | |
|
135 | 139 | self.line_used = False |
|
136 | 140 | tokens = [] |
|
137 | 141 | stop_at_NL = False |
|
138 | 142 | try: |
|
139 | 143 | for intok in self.tokenizer: |
|
140 | 144 | tokens.append(intok) |
|
141 | 145 | t = intok[0] |
|
142 | 146 | if t == tokenize2.NEWLINE or (stop_at_NL and t == tokenize2.NL): |
|
143 | 147 | # Stop before we try to pull a line we don't have yet |
|
144 | 148 | break |
|
145 | 149 | elif t == tokenize2.ERRORTOKEN: |
|
146 | 150 | stop_at_NL = True |
|
147 | 151 | except TokenError: |
|
148 | 152 | # Multi-line statement - stop and try again with the next line |
|
149 | 153 | self.reset_tokenizer() |
|
150 | 154 | return None |
|
151 | 155 | |
|
152 | 156 | return self.output(tokens) |
|
153 | 157 | |
|
154 | 158 | def output(self, tokens): |
|
155 | 159 | self.current_line = "" |
|
156 | 160 | self.reset_tokenizer() |
|
157 | 161 | return untokenize(self.func(tokens)).rstrip('\n') |
|
158 | 162 | |
|
159 | 163 | def reset(self): |
|
160 | 164 | l = self.current_line |
|
161 | 165 | self.current_line = "" |
|
162 | 166 | self.reset_tokenizer() |
|
163 | 167 | if l: |
|
164 | 168 | return l.rstrip('\n') |
|
165 | 169 | |
|
166 | 170 | class assemble_python_lines(TokenInputTransformer): |
|
167 | 171 | def __init__(self): |
|
168 | 172 | super(assemble_python_lines, self).__init__(None) |
|
169 | 173 | |
|
170 | 174 | def output(self, tokens): |
|
171 | 175 | return self.reset() |
|
172 | 176 | |
|
173 | 177 | @CoroutineInputTransformer.wrap |
|
174 | 178 | def assemble_logical_lines(): |
|
175 | 179 | """Join lines following explicit line continuations (\)""" |
|
176 | 180 | line = '' |
|
177 | 181 | while True: |
|
178 | 182 | line = (yield line) |
|
179 | 183 | if not line or line.isspace(): |
|
180 | 184 | continue |
|
181 | 185 | |
|
182 | 186 | parts = [] |
|
183 | 187 | while line is not None: |
|
184 | 188 | if line.endswith('\\') and (not has_comment(line)): |
|
185 | 189 | parts.append(line[:-1]) |
|
186 | 190 | line = (yield None) # Get another line |
|
187 | 191 | else: |
|
188 | 192 | parts.append(line) |
|
189 | 193 | break |
|
190 | 194 | |
|
191 | 195 | # Output |
|
192 | 196 | line = ''.join(parts) |
|
193 | 197 | |
|
194 | 198 | # Utilities |
|
195 | 199 | def _make_help_call(target, esc, lspace, next_input=None): |
|
196 | 200 | """Prepares a pinfo(2)/psearch call from a target name and the escape |
|
197 | 201 | (i.e. ? or ??)""" |
|
198 | 202 | method = 'pinfo2' if esc == '??' \ |
|
199 | 203 | else 'psearch' if '*' in target \ |
|
200 | 204 | else 'pinfo' |
|
201 | 205 | arg = " ".join([method, target]) |
|
202 | 206 | if next_input is None: |
|
203 | 207 | return '%sget_ipython().magic(%r)' % (lspace, arg) |
|
204 | 208 | else: |
|
205 | 209 | return '%sget_ipython().set_next_input(%r);get_ipython().magic(%r)' % \ |
|
206 | 210 | (lspace, next_input, arg) |
|
207 | 211 | |
|
208 | 212 | # These define the transformations for the different escape characters. |
|
209 | 213 | def _tr_system(line_info): |
|
210 | 214 | "Translate lines escaped with: !" |
|
211 | 215 | cmd = line_info.line.lstrip().lstrip(ESC_SHELL) |
|
212 | 216 | return '%sget_ipython().system(%r)' % (line_info.pre, cmd) |
|
213 | 217 | |
|
214 | 218 | def _tr_system2(line_info): |
|
215 | 219 | "Translate lines escaped with: !!" |
|
216 | 220 | cmd = line_info.line.lstrip()[2:] |
|
217 | 221 | return '%sget_ipython().getoutput(%r)' % (line_info.pre, cmd) |
|
218 | 222 | |
|
219 | 223 | def _tr_help(line_info): |
|
220 | 224 | "Translate lines escaped with: ?/??" |
|
221 | 225 | # A naked help line should just fire the intro help screen |
|
222 | 226 | if not line_info.line[1:]: |
|
223 | 227 | return 'get_ipython().show_usage()' |
|
224 | 228 | |
|
225 | 229 | return _make_help_call(line_info.ifun, line_info.esc, line_info.pre) |
|
226 | 230 | |
|
227 | 231 | def _tr_magic(line_info): |
|
228 | 232 | "Translate lines escaped with: %" |
|
229 | 233 | tpl = '%sget_ipython().magic(%r)' |
|
230 | 234 | if line_info.line.startswith(ESC_MAGIC2): |
|
231 | 235 | return line_info.line |
|
232 | 236 | cmd = ' '.join([line_info.ifun, line_info.the_rest]).strip() |
|
233 | 237 | return tpl % (line_info.pre, cmd) |
|
234 | 238 | |
|
235 | 239 | def _tr_quote(line_info): |
|
236 | 240 | "Translate lines escaped with: ," |
|
237 | 241 | return '%s%s("%s")' % (line_info.pre, line_info.ifun, |
|
238 | 242 | '", "'.join(line_info.the_rest.split()) ) |
|
239 | 243 | |
|
240 | 244 | def _tr_quote2(line_info): |
|
241 | 245 | "Translate lines escaped with: ;" |
|
242 | 246 | return '%s%s("%s")' % (line_info.pre, line_info.ifun, |
|
243 | 247 | line_info.the_rest) |
|
244 | 248 | |
|
245 | 249 | def _tr_paren(line_info): |
|
246 | 250 | "Translate lines escaped with: /" |
|
247 | 251 | return '%s%s(%s)' % (line_info.pre, line_info.ifun, |
|
248 | 252 | ", ".join(line_info.the_rest.split())) |
|
249 | 253 | |
|
250 | 254 | tr = { ESC_SHELL : _tr_system, |
|
251 | 255 | ESC_SH_CAP : _tr_system2, |
|
252 | 256 | ESC_HELP : _tr_help, |
|
253 | 257 | ESC_HELP2 : _tr_help, |
|
254 | 258 | ESC_MAGIC : _tr_magic, |
|
255 | 259 | ESC_QUOTE : _tr_quote, |
|
256 | 260 | ESC_QUOTE2 : _tr_quote2, |
|
257 | 261 | ESC_PAREN : _tr_paren } |
|
258 | 262 | |
|
259 | 263 | @StatelessInputTransformer.wrap |
|
260 | 264 | def escaped_commands(line): |
|
261 | 265 | """Transform escaped commands - %magic, !system, ?help + various autocalls. |
|
262 | 266 | """ |
|
263 | 267 | if not line or line.isspace(): |
|
264 | 268 | return line |
|
265 | 269 | lineinf = LineInfo(line) |
|
266 | 270 | if lineinf.esc not in tr: |
|
267 | 271 | return line |
|
268 | 272 | |
|
269 | 273 | return tr[lineinf.esc](lineinf) |
|
270 | 274 | |
|
271 | 275 | _initial_space_re = re.compile(r'\s*') |
|
272 | 276 | |
|
273 | 277 | _help_end_re = re.compile(r"""(%{0,2} |
|
274 | 278 | [a-zA-Z_*][\w*]* # Variable name |
|
275 | 279 | (\.[a-zA-Z_*][\w*]*)* # .etc.etc |
|
276 | 280 | ) |
|
277 | 281 | (\?\??)$ # ? or ?? |
|
278 | 282 | """, |
|
279 | 283 | re.VERBOSE) |
|
280 | 284 | |
|
281 | 285 | # Extra pseudotokens for multiline strings and data structures |
|
282 | 286 | _MULTILINE_STRING = object() |
|
283 | 287 | _MULTILINE_STRUCTURE = object() |
|
284 | 288 | |
|
285 | 289 | def _line_tokens(line): |
|
286 | 290 | """Helper for has_comment and ends_in_comment_or_string.""" |
|
287 | 291 | readline = StringIO(line).readline |
|
288 | 292 | toktypes = set() |
|
289 | 293 | try: |
|
290 | 294 | for t in generate_tokens(readline): |
|
291 | 295 | toktypes.add(t[0]) |
|
292 | 296 | except TokenError as e: |
|
293 | 297 | # There are only two cases where a TokenError is raised. |
|
294 | 298 | if 'multi-line string' in e.args[0]: |
|
295 | 299 | toktypes.add(_MULTILINE_STRING) |
|
296 | 300 | else: |
|
297 | 301 | toktypes.add(_MULTILINE_STRUCTURE) |
|
298 | 302 | return toktypes |
|
299 | 303 | |
|
300 | 304 | def has_comment(src): |
|
301 | 305 | """Indicate whether an input line has (i.e. ends in, or is) a comment. |
|
302 | 306 | |
|
303 | 307 | This uses tokenize, so it can distinguish comments from # inside strings. |
|
304 | 308 | |
|
305 | 309 | Parameters |
|
306 | 310 | ---------- |
|
307 | 311 | src : string |
|
308 | 312 | A single line input string. |
|
309 | 313 | |
|
310 | 314 | Returns |
|
311 | 315 | ------- |
|
312 | 316 | comment : bool |
|
313 | 317 | True if source has a comment. |
|
314 | 318 | """ |
|
315 | 319 | return (tokenize2.COMMENT in _line_tokens(src)) |
|
316 | 320 | |
|
317 | 321 | def ends_in_comment_or_string(src): |
|
318 | 322 | """Indicates whether or not an input line ends in a comment or within |
|
319 | 323 | a multiline string. |
|
320 | 324 | |
|
321 | 325 | Parameters |
|
322 | 326 | ---------- |
|
323 | 327 | src : string |
|
324 | 328 | A single line input string. |
|
325 | 329 | |
|
326 | 330 | Returns |
|
327 | 331 | ------- |
|
328 | 332 | comment : bool |
|
329 | 333 | True if source ends in a comment or multiline string. |
|
330 | 334 | """ |
|
331 | 335 | toktypes = _line_tokens(src) |
|
332 | 336 | return (tokenize2.COMMENT in toktypes) or (_MULTILINE_STRING in toktypes) |
|
333 | 337 | |
|
334 | 338 | |
|
335 | 339 | @StatelessInputTransformer.wrap |
|
336 | 340 | def help_end(line): |
|
337 | 341 | """Translate lines with ?/?? at the end""" |
|
338 | 342 | m = _help_end_re.search(line) |
|
339 | 343 | if m is None or ends_in_comment_or_string(line): |
|
340 | 344 | return line |
|
341 | 345 | target = m.group(1) |
|
342 | 346 | esc = m.group(3) |
|
343 | 347 | lspace = _initial_space_re.match(line).group(0) |
|
344 | 348 | |
|
345 | 349 | # If we're mid-command, put it back on the next prompt for the user. |
|
346 | 350 | next_input = line.rstrip('?') if line.strip() != m.group(0) else None |
|
347 | 351 | |
|
348 | 352 | return _make_help_call(target, esc, lspace, next_input) |
|
349 | 353 | |
|
350 | 354 | |
|
351 | 355 | @CoroutineInputTransformer.wrap |
|
352 | 356 | def cellmagic(end_on_blank_line=False): |
|
353 | 357 | """Captures & transforms cell magics. |
|
354 | 358 | |
|
355 | 359 | After a cell magic is started, this stores up any lines it gets until it is |
|
356 | 360 | reset (sent None). |
|
357 | 361 | """ |
|
358 | 362 | tpl = 'get_ipython().run_cell_magic(%r, %r, %r)' |
|
359 | 363 | cellmagic_help_re = re.compile('%%\w+\?') |
|
360 | 364 | line = '' |
|
361 | 365 | while True: |
|
362 | 366 | line = (yield line) |
|
363 | 367 | # consume leading empty lines |
|
364 | 368 | while not line: |
|
365 | 369 | line = (yield line) |
|
366 | 370 | |
|
367 | 371 | if not line.startswith(ESC_MAGIC2): |
|
368 | 372 | # This isn't a cell magic, idle waiting for reset then start over |
|
369 | 373 | while line is not None: |
|
370 | 374 | line = (yield line) |
|
371 | 375 | continue |
|
372 | 376 | |
|
373 | 377 | if cellmagic_help_re.match(line): |
|
374 | 378 | # This case will be handled by help_end |
|
375 | 379 | continue |
|
376 | 380 | |
|
377 | 381 | first = line |
|
378 | 382 | body = [] |
|
379 | 383 | line = (yield None) |
|
380 | 384 | while (line is not None) and \ |
|
381 | 385 | ((line.strip() != '') or not end_on_blank_line): |
|
382 | 386 | body.append(line) |
|
383 | 387 | line = (yield None) |
|
384 | 388 | |
|
385 | 389 | # Output |
|
386 | 390 | magic_name, _, first = first.partition(' ') |
|
387 | 391 | magic_name = magic_name.lstrip(ESC_MAGIC2) |
|
388 | 392 | line = tpl % (magic_name, first, u'\n'.join(body)) |
|
389 | 393 | |
|
390 | 394 | |
|
391 | 395 | def _strip_prompts(prompt_re, initial_re=None): |
|
392 | 396 | """Remove matching input prompts from a block of input. |
|
393 | 397 | |
|
394 | 398 | Parameters |
|
395 | 399 | ---------- |
|
396 | 400 | prompt_re : regular expression |
|
397 | 401 | A regular expression matching any input prompt (including continuation) |
|
398 | 402 | initial_re : regular expression, optional |
|
399 | 403 | A regular expression matching only the initial prompt, but not continuation. |
|
400 | 404 | If no initial expression is given, prompt_re will be used everywhere. |
|
401 | 405 | Used mainly for plain Python prompts, where the continuation prompt |
|
402 | 406 | ``...`` is a valid Python expression in Python 3, so shouldn't be stripped. |
|
403 | 407 | |
|
404 | 408 | If initial_re and prompt_re differ, |
|
405 | 409 | only initial_re will be tested against the first line. |
|
406 | 410 | If any prompt is found on the first two lines, |
|
407 | 411 | prompts will be stripped from the rest of the block. |
|
408 | 412 | """ |
|
409 | 413 | if initial_re is None: |
|
410 | 414 | initial_re = prompt_re |
|
411 | 415 | line = '' |
|
412 | 416 | while True: |
|
413 | 417 | line = (yield line) |
|
414 | 418 | |
|
415 | 419 | # First line of cell |
|
416 | 420 | if line is None: |
|
417 | 421 | continue |
|
418 | 422 | out, n1 = initial_re.subn('', line, count=1) |
|
419 | 423 | line = (yield out) |
|
420 | 424 | |
|
421 | 425 | if line is None: |
|
422 | 426 | continue |
|
423 | 427 | # check for any prompt on the second line of the cell, |
|
424 | 428 | # because people often copy from just after the first prompt, |
|
425 | 429 | # so we might not see it in the first line. |
|
426 | 430 | out, n2 = prompt_re.subn('', line, count=1) |
|
427 | 431 | line = (yield out) |
|
428 | 432 | |
|
429 | 433 | if n1 or n2: |
|
430 | 434 | # Found a prompt in the first two lines - check for it in |
|
431 | 435 | # the rest of the cell as well. |
|
432 | 436 | while line is not None: |
|
433 | 437 | line = (yield prompt_re.sub('', line, count=1)) |
|
434 | 438 | |
|
435 | 439 | else: |
|
436 | 440 | # Prompts not in input - wait for reset |
|
437 | 441 | while line is not None: |
|
438 | 442 | line = (yield line) |
|
439 | 443 | |
|
440 | 444 | @CoroutineInputTransformer.wrap |
|
441 | 445 | def classic_prompt(): |
|
442 | 446 | """Strip the >>>/... prompts of the Python interactive shell.""" |
|
443 | 447 | # FIXME: non-capturing version (?:...) usable? |
|
444 | 448 | prompt_re = re.compile(r'^(>>> ?|\.\.\. ?)') |
|
445 | 449 | initial_re = re.compile(r'^(>>> ?)') |
|
446 | 450 | return _strip_prompts(prompt_re, initial_re) |
|
447 | 451 | |
|
448 | 452 | @CoroutineInputTransformer.wrap |
|
449 | 453 | def ipy_prompt(): |
|
450 | 454 | """Strip IPython's In [1]:/...: prompts.""" |
|
451 | 455 | # FIXME: non-capturing version (?:...) usable? |
|
452 | 456 | # FIXME: r'^(In \[\d+\]: | {3}\.{3,}: )' clearer? |
|
453 | 457 | prompt_re = re.compile(r'^(In \[\d+\]: |\ \ \ \.\.\.+: )') |
|
454 | 458 | return _strip_prompts(prompt_re) |
|
455 | 459 | |
|
456 | 460 | |
|
457 | 461 | @CoroutineInputTransformer.wrap |
|
458 | 462 | def leading_indent(): |
|
459 | 463 | """Remove leading indentation. |
|
460 | 464 | |
|
461 | 465 | If the first line starts with a spaces or tabs, the same whitespace will be |
|
462 | 466 | removed from each following line until it is reset. |
|
463 | 467 | """ |
|
464 | 468 | space_re = re.compile(r'^[ \t]+') |
|
465 | 469 | line = '' |
|
466 | 470 | while True: |
|
467 | 471 | line = (yield line) |
|
468 | 472 | |
|
469 | 473 | if line is None: |
|
470 | 474 | continue |
|
471 | 475 | |
|
472 | 476 | m = space_re.match(line) |
|
473 | 477 | if m: |
|
474 | 478 | space = m.group(0) |
|
475 | 479 | while line is not None: |
|
476 | 480 | if line.startswith(space): |
|
477 | 481 | line = line[len(space):] |
|
478 | 482 | line = (yield line) |
|
479 | 483 | else: |
|
480 | 484 | # No leading spaces - wait for reset |
|
481 | 485 | while line is not None: |
|
482 | 486 | line = (yield line) |
|
483 | 487 | |
|
484 | 488 | |
|
485 | 489 | @CoroutineInputTransformer.wrap |
|
486 | 490 | def strip_encoding_cookie(): |
|
487 | 491 | """Remove encoding comment if found in first two lines |
|
488 | 492 | |
|
489 | 493 | If the first or second line has the `# coding: utf-8` comment, |
|
490 | 494 | it will be removed. |
|
491 | 495 | """ |
|
492 | 496 | line = '' |
|
493 | 497 | while True: |
|
494 | 498 | line = (yield line) |
|
495 | 499 | # check comment on first two lines |
|
496 | 500 | for i in range(2): |
|
497 | 501 | if line is None: |
|
498 | 502 | break |
|
499 | 503 | if cookie_comment_re.match(line): |
|
500 | 504 | line = (yield "") |
|
501 | 505 | else: |
|
502 | 506 | line = (yield line) |
|
503 | 507 | |
|
504 | 508 | # no-op on the rest of the cell |
|
505 | 509 | while line is not None: |
|
506 | 510 | line = (yield line) |
|
507 | 511 | |
|
508 | 512 | |
|
509 | 513 | assign_system_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))' |
|
510 | 514 | r'\s*=\s*!\s*(?P<cmd>.*)') |
|
511 | 515 | assign_system_template = '%s = get_ipython().getoutput(%r)' |
|
512 | 516 | @StatelessInputTransformer.wrap |
|
513 | 517 | def assign_from_system(line): |
|
514 | 518 | """Transform assignment from system commands (e.g. files = !ls)""" |
|
515 | 519 | m = assign_system_re.match(line) |
|
516 | 520 | if m is None: |
|
517 | 521 | return line |
|
518 | 522 | |
|
519 | 523 | return assign_system_template % m.group('lhs', 'cmd') |
|
520 | 524 | |
|
521 | 525 | assign_magic_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))' |
|
522 | 526 | r'\s*=\s*%\s*(?P<cmd>.*)') |
|
523 | 527 | assign_magic_template = '%s = get_ipython().magic(%r)' |
|
524 | 528 | @StatelessInputTransformer.wrap |
|
525 | 529 | def assign_from_magic(line): |
|
526 | 530 | """Transform assignment from magic commands (e.g. a = %who_ls)""" |
|
527 | 531 | m = assign_magic_re.match(line) |
|
528 | 532 | if m is None: |
|
529 | 533 | return line |
|
530 | 534 | |
|
531 | 535 | return assign_magic_template % m.group('lhs', 'cmd') |
@@ -1,1291 +1,1294 | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | """Implementation of execution-related magic functions. |
|
3 | 3 | """ |
|
4 | 4 | from __future__ import print_function |
|
5 | 5 | #----------------------------------------------------------------------------- |
|
6 | 6 | # Copyright (c) 2012 The IPython Development Team. |
|
7 | 7 | # |
|
8 | 8 | # Distributed under the terms of the Modified BSD License. |
|
9 | 9 | # |
|
10 | 10 | # The full license is in the file COPYING.txt, distributed with this software. |
|
11 | 11 | #----------------------------------------------------------------------------- |
|
12 | 12 | |
|
13 | 13 | #----------------------------------------------------------------------------- |
|
14 | 14 | # Imports |
|
15 | 15 | #----------------------------------------------------------------------------- |
|
16 | 16 | |
|
17 | 17 | # Stdlib |
|
18 | 18 | import ast |
|
19 | 19 | import bdb |
|
20 | 20 | import os |
|
21 | 21 | import sys |
|
22 | 22 | import time |
|
23 | from io import StringIO | |
|
24 | 23 | |
|
25 | 24 | # cProfile was added in Python2.5 |
|
26 | 25 | try: |
|
27 | 26 | import cProfile as profile |
|
28 | 27 | import pstats |
|
29 | 28 | except ImportError: |
|
30 | 29 | # profile isn't bundled by default in Debian for license reasons |
|
31 | 30 | try: |
|
32 | 31 | import profile, pstats |
|
33 | 32 | except ImportError: |
|
34 | 33 | profile = pstats = None |
|
35 | 34 | |
|
36 | 35 | # Our own packages |
|
37 | 36 | from IPython.core import debugger, oinspect |
|
38 | 37 | from IPython.core import magic_arguments |
|
39 | 38 | from IPython.core import page |
|
40 | 39 | from IPython.core.error import UsageError |
|
41 | 40 | from IPython.core.macro import Macro |
|
42 | 41 | from IPython.core.magic import (Magics, magics_class, line_magic, cell_magic, |
|
43 | 42 | line_cell_magic, on_off, needs_local_scope) |
|
44 | 43 | from IPython.testing.skipdoctest import skip_doctest |
|
45 | 44 | from IPython.utils import py3compat |
|
46 | from IPython.utils.py3compat import builtin_mod, iteritems | |
|
45 | from IPython.utils.py3compat import builtin_mod, iteritems, PY3 | |
|
47 | 46 | from IPython.utils.contexts import preserve_keys |
|
48 | 47 | from IPython.utils.io import capture_output |
|
49 | 48 | from IPython.utils.ipstruct import Struct |
|
50 | 49 | from IPython.utils.module_paths import find_mod |
|
51 | 50 | from IPython.utils.path import get_py_filename, unquote_filename, shellglob |
|
52 | 51 | from IPython.utils.timing import clock, clock2 |
|
53 | 52 | from IPython.utils.warn import warn, error |
|
54 | 53 | |
|
54 | if PY3: | |
|
55 | from io import StringIO | |
|
56 | else: | |
|
57 | from StringIO import StringIO | |
|
55 | 58 | |
|
56 | 59 | #----------------------------------------------------------------------------- |
|
57 | 60 | # Magic implementation classes |
|
58 | 61 | #----------------------------------------------------------------------------- |
|
59 | 62 | |
|
60 | 63 | |
|
61 | 64 | class TimeitResult(object): |
|
62 | 65 | """ |
|
63 | 66 | Object returned by the timeit magic with info about the run. |
|
64 | 67 | |
|
65 | 68 | Contain the following attributes : |
|
66 | 69 | |
|
67 | 70 | loops: (int) number of loop done per measurement |
|
68 | 71 | repeat: (int) number of time the mesurement has been repeated |
|
69 | 72 | best: (float) best execusion time / number |
|
70 | 73 | all_runs: (list of float) execusion time of each run (in s) |
|
71 | 74 | compile_time: (float) time of statement compilation (s) |
|
72 | 75 | |
|
73 | 76 | """ |
|
74 | 77 | |
|
75 | 78 | def __init__(self, loops, repeat, best, all_runs, compile_time, precision): |
|
76 | 79 | self.loops = loops |
|
77 | 80 | self.repeat = repeat |
|
78 | 81 | self.best = best |
|
79 | 82 | self.all_runs = all_runs |
|
80 | 83 | self.compile_time = compile_time |
|
81 | 84 | self._precision = precision |
|
82 | 85 | |
|
83 | 86 | def _repr_pretty_(self, p , cycle): |
|
84 | 87 | unic = u"%d loops, best of %d: %s per loop" % (self.loops, self.repeat, |
|
85 | 88 | _format_time(self.best, self._precision)) |
|
86 | 89 | p.text(u'<TimeitResult : '+unic+u'>') |
|
87 | 90 | |
|
88 | 91 | |
|
89 | 92 | class TimeitTemplateFiller(ast.NodeTransformer): |
|
90 | 93 | "This is quite tightly tied to the template definition above." |
|
91 | 94 | def __init__(self, ast_setup, ast_stmt): |
|
92 | 95 | self.ast_setup = ast_setup |
|
93 | 96 | self.ast_stmt = ast_stmt |
|
94 | 97 | |
|
95 | 98 | def visit_FunctionDef(self, node): |
|
96 | 99 | "Fill in the setup statement" |
|
97 | 100 | self.generic_visit(node) |
|
98 | 101 | if node.name == "inner": |
|
99 | 102 | node.body[:1] = self.ast_setup.body |
|
100 | 103 | |
|
101 | 104 | return node |
|
102 | 105 | |
|
103 | 106 | def visit_For(self, node): |
|
104 | 107 | "Fill in the statement to be timed" |
|
105 | 108 | if getattr(getattr(node.body[0], 'value', None), 'id', None) == 'stmt': |
|
106 | 109 | node.body = self.ast_stmt.body |
|
107 | 110 | return node |
|
108 | 111 | |
|
109 | 112 | |
|
110 | 113 | @magics_class |
|
111 | 114 | class ExecutionMagics(Magics): |
|
112 | 115 | """Magics related to code execution, debugging, profiling, etc. |
|
113 | 116 | |
|
114 | 117 | """ |
|
115 | 118 | |
|
116 | 119 | def __init__(self, shell): |
|
117 | 120 | super(ExecutionMagics, self).__init__(shell) |
|
118 | 121 | if profile is None: |
|
119 | 122 | self.prun = self.profile_missing_notice |
|
120 | 123 | # Default execution function used to actually run user code. |
|
121 | 124 | self.default_runner = None |
|
122 | 125 | |
|
123 | 126 | def profile_missing_notice(self, *args, **kwargs): |
|
124 | 127 | error("""\ |
|
125 | 128 | The profile module could not be found. It has been removed from the standard |
|
126 | 129 | python packages because of its non-free license. To use profiling, install the |
|
127 | 130 | python-profiler package from non-free.""") |
|
128 | 131 | |
|
129 | 132 | @skip_doctest |
|
130 | 133 | @line_cell_magic |
|
131 | 134 | def prun(self, parameter_s='', cell=None): |
|
132 | 135 | |
|
133 | 136 | """Run a statement through the python code profiler. |
|
134 | 137 | |
|
135 | 138 | Usage, in line mode: |
|
136 | 139 | %prun [options] statement |
|
137 | 140 | |
|
138 | 141 | Usage, in cell mode: |
|
139 | 142 | %%prun [options] [statement] |
|
140 | 143 | code... |
|
141 | 144 | code... |
|
142 | 145 | |
|
143 | 146 | In cell mode, the additional code lines are appended to the (possibly |
|
144 | 147 | empty) statement in the first line. Cell mode allows you to easily |
|
145 | 148 | profile multiline blocks without having to put them in a separate |
|
146 | 149 | function. |
|
147 | 150 | |
|
148 | 151 | The given statement (which doesn't require quote marks) is run via the |
|
149 | 152 | python profiler in a manner similar to the profile.run() function. |
|
150 | 153 | Namespaces are internally managed to work correctly; profile.run |
|
151 | 154 | cannot be used in IPython because it makes certain assumptions about |
|
152 | 155 | namespaces which do not hold under IPython. |
|
153 | 156 | |
|
154 | 157 | Options: |
|
155 | 158 | |
|
156 | 159 | -l <limit> |
|
157 | 160 | you can place restrictions on what or how much of the |
|
158 | 161 | profile gets printed. The limit value can be: |
|
159 | 162 | |
|
160 | 163 | * A string: only information for function names containing this string |
|
161 | 164 | is printed. |
|
162 | 165 | |
|
163 | 166 | * An integer: only these many lines are printed. |
|
164 | 167 | |
|
165 | 168 | * A float (between 0 and 1): this fraction of the report is printed |
|
166 | 169 | (for example, use a limit of 0.4 to see the topmost 40% only). |
|
167 | 170 | |
|
168 | 171 | You can combine several limits with repeated use of the option. For |
|
169 | 172 | example, ``-l __init__ -l 5`` will print only the topmost 5 lines of |
|
170 | 173 | information about class constructors. |
|
171 | 174 | |
|
172 | 175 | -r |
|
173 | 176 | return the pstats.Stats object generated by the profiling. This |
|
174 | 177 | object has all the information about the profile in it, and you can |
|
175 | 178 | later use it for further analysis or in other functions. |
|
176 | 179 | |
|
177 | 180 | -s <key> |
|
178 | 181 | sort profile by given key. You can provide more than one key |
|
179 | 182 | by using the option several times: '-s key1 -s key2 -s key3...'. The |
|
180 | 183 | default sorting key is 'time'. |
|
181 | 184 | |
|
182 | 185 | The following is copied verbatim from the profile documentation |
|
183 | 186 | referenced below: |
|
184 | 187 | |
|
185 | 188 | When more than one key is provided, additional keys are used as |
|
186 | 189 | secondary criteria when the there is equality in all keys selected |
|
187 | 190 | before them. |
|
188 | 191 | |
|
189 | 192 | Abbreviations can be used for any key names, as long as the |
|
190 | 193 | abbreviation is unambiguous. The following are the keys currently |
|
191 | 194 | defined: |
|
192 | 195 | |
|
193 | 196 | ============ ===================== |
|
194 | 197 | Valid Arg Meaning |
|
195 | 198 | ============ ===================== |
|
196 | 199 | "calls" call count |
|
197 | 200 | "cumulative" cumulative time |
|
198 | 201 | "file" file name |
|
199 | 202 | "module" file name |
|
200 | 203 | "pcalls" primitive call count |
|
201 | 204 | "line" line number |
|
202 | 205 | "name" function name |
|
203 | 206 | "nfl" name/file/line |
|
204 | 207 | "stdname" standard name |
|
205 | 208 | "time" internal time |
|
206 | 209 | ============ ===================== |
|
207 | 210 | |
|
208 | 211 | Note that all sorts on statistics are in descending order (placing |
|
209 | 212 | most time consuming items first), where as name, file, and line number |
|
210 | 213 | searches are in ascending order (i.e., alphabetical). The subtle |
|
211 | 214 | distinction between "nfl" and "stdname" is that the standard name is a |
|
212 | 215 | sort of the name as printed, which means that the embedded line |
|
213 | 216 | numbers get compared in an odd way. For example, lines 3, 20, and 40 |
|
214 | 217 | would (if the file names were the same) appear in the string order |
|
215 | 218 | "20" "3" and "40". In contrast, "nfl" does a numeric compare of the |
|
216 | 219 | line numbers. In fact, sort_stats("nfl") is the same as |
|
217 | 220 | sort_stats("name", "file", "line"). |
|
218 | 221 | |
|
219 | 222 | -T <filename> |
|
220 | 223 | save profile results as shown on screen to a text |
|
221 | 224 | file. The profile is still shown on screen. |
|
222 | 225 | |
|
223 | 226 | -D <filename> |
|
224 | 227 | save (via dump_stats) profile statistics to given |
|
225 | 228 | filename. This data is in a format understood by the pstats module, and |
|
226 | 229 | is generated by a call to the dump_stats() method of profile |
|
227 | 230 | objects. The profile is still shown on screen. |
|
228 | 231 | |
|
229 | 232 | -q |
|
230 | 233 | suppress output to the pager. Best used with -T and/or -D above. |
|
231 | 234 | |
|
232 | 235 | If you want to run complete programs under the profiler's control, use |
|
233 | 236 | ``%run -p [prof_opts] filename.py [args to program]`` where prof_opts |
|
234 | 237 | contains profiler specific options as described here. |
|
235 | 238 | |
|
236 | 239 | You can read the complete documentation for the profile module with:: |
|
237 | 240 | |
|
238 | 241 | In [1]: import profile; profile.help() |
|
239 | 242 | """ |
|
240 | 243 | opts, arg_str = self.parse_options(parameter_s, 'D:l:rs:T:q', |
|
241 | 244 | list_all=True, posix=False) |
|
242 | 245 | if cell is not None: |
|
243 | 246 | arg_str += '\n' + cell |
|
244 | 247 | arg_str = self.shell.input_splitter.transform_cell(arg_str) |
|
245 | 248 | return self._run_with_profiler(arg_str, opts, self.shell.user_ns) |
|
246 | 249 | |
|
247 | 250 | def _run_with_profiler(self, code, opts, namespace): |
|
248 | 251 | """ |
|
249 | 252 | Run `code` with profiler. Used by ``%prun`` and ``%run -p``. |
|
250 | 253 | |
|
251 | 254 | Parameters |
|
252 | 255 | ---------- |
|
253 | 256 | code : str |
|
254 | 257 | Code to be executed. |
|
255 | 258 | opts : Struct |
|
256 | 259 | Options parsed by `self.parse_options`. |
|
257 | 260 | namespace : dict |
|
258 | 261 | A dictionary for Python namespace (e.g., `self.shell.user_ns`). |
|
259 | 262 | |
|
260 | 263 | """ |
|
261 | 264 | |
|
262 | 265 | # Fill default values for unspecified options: |
|
263 | 266 | opts.merge(Struct(D=[''], l=[], s=['time'], T=[''])) |
|
264 | 267 | |
|
265 | 268 | prof = profile.Profile() |
|
266 | 269 | try: |
|
267 | 270 | prof = prof.runctx(code, namespace, namespace) |
|
268 | 271 | sys_exit = '' |
|
269 | 272 | except SystemExit: |
|
270 | 273 | sys_exit = """*** SystemExit exception caught in code being profiled.""" |
|
271 | 274 | |
|
272 | 275 | stats = pstats.Stats(prof).strip_dirs().sort_stats(*opts.s) |
|
273 | 276 | |
|
274 | 277 | lims = opts.l |
|
275 | 278 | if lims: |
|
276 | 279 | lims = [] # rebuild lims with ints/floats/strings |
|
277 | 280 | for lim in opts.l: |
|
278 | 281 | try: |
|
279 | 282 | lims.append(int(lim)) |
|
280 | 283 | except ValueError: |
|
281 | 284 | try: |
|
282 | 285 | lims.append(float(lim)) |
|
283 | 286 | except ValueError: |
|
284 | 287 | lims.append(lim) |
|
285 | 288 | |
|
286 | 289 | # Trap output. |
|
287 | 290 | stdout_trap = StringIO() |
|
288 | 291 | stats_stream = stats.stream |
|
289 | 292 | try: |
|
290 | 293 | stats.stream = stdout_trap |
|
291 | 294 | stats.print_stats(*lims) |
|
292 | 295 | finally: |
|
293 | 296 | stats.stream = stats_stream |
|
294 | 297 | |
|
295 | 298 | output = stdout_trap.getvalue() |
|
296 | 299 | output = output.rstrip() |
|
297 | 300 | |
|
298 | 301 | if 'q' not in opts: |
|
299 | 302 | page.page(output) |
|
300 | 303 | print(sys_exit, end=' ') |
|
301 | 304 | |
|
302 | 305 | dump_file = opts.D[0] |
|
303 | 306 | text_file = opts.T[0] |
|
304 | 307 | if dump_file: |
|
305 | 308 | dump_file = unquote_filename(dump_file) |
|
306 | 309 | prof.dump_stats(dump_file) |
|
307 | 310 | print('\n*** Profile stats marshalled to file',\ |
|
308 | 311 | repr(dump_file)+'.',sys_exit) |
|
309 | 312 | if text_file: |
|
310 | 313 | text_file = unquote_filename(text_file) |
|
311 | 314 | pfile = open(text_file,'w') |
|
312 | 315 | pfile.write(output) |
|
313 | 316 | pfile.close() |
|
314 | 317 | print('\n*** Profile printout saved to text file',\ |
|
315 | 318 | repr(text_file)+'.',sys_exit) |
|
316 | 319 | |
|
317 | 320 | if 'r' in opts: |
|
318 | 321 | return stats |
|
319 | 322 | else: |
|
320 | 323 | return None |
|
321 | 324 | |
|
322 | 325 | @line_magic |
|
323 | 326 | def pdb(self, parameter_s=''): |
|
324 | 327 | """Control the automatic calling of the pdb interactive debugger. |
|
325 | 328 | |
|
326 | 329 | Call as '%pdb on', '%pdb 1', '%pdb off' or '%pdb 0'. If called without |
|
327 | 330 | argument it works as a toggle. |
|
328 | 331 | |
|
329 | 332 | When an exception is triggered, IPython can optionally call the |
|
330 | 333 | interactive pdb debugger after the traceback printout. %pdb toggles |
|
331 | 334 | this feature on and off. |
|
332 | 335 | |
|
333 | 336 | The initial state of this feature is set in your configuration |
|
334 | 337 | file (the option is ``InteractiveShell.pdb``). |
|
335 | 338 | |
|
336 | 339 | If you want to just activate the debugger AFTER an exception has fired, |
|
337 | 340 | without having to type '%pdb on' and rerunning your code, you can use |
|
338 | 341 | the %debug magic.""" |
|
339 | 342 | |
|
340 | 343 | par = parameter_s.strip().lower() |
|
341 | 344 | |
|
342 | 345 | if par: |
|
343 | 346 | try: |
|
344 | 347 | new_pdb = {'off':0,'0':0,'on':1,'1':1}[par] |
|
345 | 348 | except KeyError: |
|
346 | 349 | print ('Incorrect argument. Use on/1, off/0, ' |
|
347 | 350 | 'or nothing for a toggle.') |
|
348 | 351 | return |
|
349 | 352 | else: |
|
350 | 353 | # toggle |
|
351 | 354 | new_pdb = not self.shell.call_pdb |
|
352 | 355 | |
|
353 | 356 | # set on the shell |
|
354 | 357 | self.shell.call_pdb = new_pdb |
|
355 | 358 | print('Automatic pdb calling has been turned',on_off(new_pdb)) |
|
356 | 359 | |
|
357 | 360 | @skip_doctest |
|
358 | 361 | @magic_arguments.magic_arguments() |
|
359 | 362 | @magic_arguments.argument('--breakpoint', '-b', metavar='FILE:LINE', |
|
360 | 363 | help=""" |
|
361 | 364 | Set break point at LINE in FILE. |
|
362 | 365 | """ |
|
363 | 366 | ) |
|
364 | 367 | @magic_arguments.argument('statement', nargs='*', |
|
365 | 368 | help=""" |
|
366 | 369 | Code to run in debugger. |
|
367 | 370 | You can omit this in cell magic mode. |
|
368 | 371 | """ |
|
369 | 372 | ) |
|
370 | 373 | @line_cell_magic |
|
371 | 374 | def debug(self, line='', cell=None): |
|
372 | 375 | """Activate the interactive debugger. |
|
373 | 376 | |
|
374 | 377 | This magic command support two ways of activating debugger. |
|
375 | 378 | One is to activate debugger before executing code. This way, you |
|
376 | 379 | can set a break point, to step through the code from the point. |
|
377 | 380 | You can use this mode by giving statements to execute and optionally |
|
378 | 381 | a breakpoint. |
|
379 | 382 | |
|
380 | 383 | The other one is to activate debugger in post-mortem mode. You can |
|
381 | 384 | activate this mode simply running %debug without any argument. |
|
382 | 385 | If an exception has just occurred, this lets you inspect its stack |
|
383 | 386 | frames interactively. Note that this will always work only on the last |
|
384 | 387 | traceback that occurred, so you must call this quickly after an |
|
385 | 388 | exception that you wish to inspect has fired, because if another one |
|
386 | 389 | occurs, it clobbers the previous one. |
|
387 | 390 | |
|
388 | 391 | If you want IPython to automatically do this on every exception, see |
|
389 | 392 | the %pdb magic for more details. |
|
390 | 393 | """ |
|
391 | 394 | args = magic_arguments.parse_argstring(self.debug, line) |
|
392 | 395 | |
|
393 | 396 | if not (args.breakpoint or args.statement or cell): |
|
394 | 397 | self._debug_post_mortem() |
|
395 | 398 | else: |
|
396 | 399 | code = "\n".join(args.statement) |
|
397 | 400 | if cell: |
|
398 | 401 | code += "\n" + cell |
|
399 | 402 | self._debug_exec(code, args.breakpoint) |
|
400 | 403 | |
|
401 | 404 | def _debug_post_mortem(self): |
|
402 | 405 | self.shell.debugger(force=True) |
|
403 | 406 | |
|
404 | 407 | def _debug_exec(self, code, breakpoint): |
|
405 | 408 | if breakpoint: |
|
406 | 409 | (filename, bp_line) = breakpoint.split(':', 1) |
|
407 | 410 | bp_line = int(bp_line) |
|
408 | 411 | else: |
|
409 | 412 | (filename, bp_line) = (None, None) |
|
410 | 413 | self._run_with_debugger(code, self.shell.user_ns, filename, bp_line) |
|
411 | 414 | |
|
412 | 415 | @line_magic |
|
413 | 416 | def tb(self, s): |
|
414 | 417 | """Print the last traceback with the currently active exception mode. |
|
415 | 418 | |
|
416 | 419 | See %xmode for changing exception reporting modes.""" |
|
417 | 420 | self.shell.showtraceback() |
|
418 | 421 | |
|
419 | 422 | @skip_doctest |
|
420 | 423 | @line_magic |
|
421 | 424 | def run(self, parameter_s='', runner=None, |
|
422 | 425 | file_finder=get_py_filename): |
|
423 | 426 | """Run the named file inside IPython as a program. |
|
424 | 427 | |
|
425 | 428 | Usage:: |
|
426 | 429 | |
|
427 | 430 | %run [-n -i -e -G] |
|
428 | 431 | [( -t [-N<N>] | -d [-b<N>] | -p [profile options] )] |
|
429 | 432 | ( -m mod | file ) [args] |
|
430 | 433 | |
|
431 | 434 | Parameters after the filename are passed as command-line arguments to |
|
432 | 435 | the program (put in sys.argv). Then, control returns to IPython's |
|
433 | 436 | prompt. |
|
434 | 437 | |
|
435 | 438 | This is similar to running at a system prompt ``python file args``, |
|
436 | 439 | but with the advantage of giving you IPython's tracebacks, and of |
|
437 | 440 | loading all variables into your interactive namespace for further use |
|
438 | 441 | (unless -p is used, see below). |
|
439 | 442 | |
|
440 | 443 | The file is executed in a namespace initially consisting only of |
|
441 | 444 | ``__name__=='__main__'`` and sys.argv constructed as indicated. It thus |
|
442 | 445 | sees its environment as if it were being run as a stand-alone program |
|
443 | 446 | (except for sharing global objects such as previously imported |
|
444 | 447 | modules). But after execution, the IPython interactive namespace gets |
|
445 | 448 | updated with all variables defined in the program (except for __name__ |
|
446 | 449 | and sys.argv). This allows for very convenient loading of code for |
|
447 | 450 | interactive work, while giving each program a 'clean sheet' to run in. |
|
448 | 451 | |
|
449 | 452 | Arguments are expanded using shell-like glob match. Patterns |
|
450 | 453 | '*', '?', '[seq]' and '[!seq]' can be used. Additionally, |
|
451 | 454 | tilde '~' will be expanded into user's home directory. Unlike |
|
452 | 455 | real shells, quotation does not suppress expansions. Use |
|
453 | 456 | *two* back slashes (e.g. ``\\\\*``) to suppress expansions. |
|
454 | 457 | To completely disable these expansions, you can use -G flag. |
|
455 | 458 | |
|
456 | 459 | Options: |
|
457 | 460 | |
|
458 | 461 | -n |
|
459 | 462 | __name__ is NOT set to '__main__', but to the running file's name |
|
460 | 463 | without extension (as python does under import). This allows running |
|
461 | 464 | scripts and reloading the definitions in them without calling code |
|
462 | 465 | protected by an ``if __name__ == "__main__"`` clause. |
|
463 | 466 | |
|
464 | 467 | -i |
|
465 | 468 | run the file in IPython's namespace instead of an empty one. This |
|
466 | 469 | is useful if you are experimenting with code written in a text editor |
|
467 | 470 | which depends on variables defined interactively. |
|
468 | 471 | |
|
469 | 472 | -e |
|
470 | 473 | ignore sys.exit() calls or SystemExit exceptions in the script |
|
471 | 474 | being run. This is particularly useful if IPython is being used to |
|
472 | 475 | run unittests, which always exit with a sys.exit() call. In such |
|
473 | 476 | cases you are interested in the output of the test results, not in |
|
474 | 477 | seeing a traceback of the unittest module. |
|
475 | 478 | |
|
476 | 479 | -t |
|
477 | 480 | print timing information at the end of the run. IPython will give |
|
478 | 481 | you an estimated CPU time consumption for your script, which under |
|
479 | 482 | Unix uses the resource module to avoid the wraparound problems of |
|
480 | 483 | time.clock(). Under Unix, an estimate of time spent on system tasks |
|
481 | 484 | is also given (for Windows platforms this is reported as 0.0). |
|
482 | 485 | |
|
483 | 486 | If -t is given, an additional ``-N<N>`` option can be given, where <N> |
|
484 | 487 | must be an integer indicating how many times you want the script to |
|
485 | 488 | run. The final timing report will include total and per run results. |
|
486 | 489 | |
|
487 | 490 | For example (testing the script uniq_stable.py):: |
|
488 | 491 | |
|
489 | 492 | In [1]: run -t uniq_stable |
|
490 | 493 | |
|
491 | 494 | IPython CPU timings (estimated): |
|
492 | 495 | User : 0.19597 s. |
|
493 | 496 | System: 0.0 s. |
|
494 | 497 | |
|
495 | 498 | In [2]: run -t -N5 uniq_stable |
|
496 | 499 | |
|
497 | 500 | IPython CPU timings (estimated): |
|
498 | 501 | Total runs performed: 5 |
|
499 | 502 | Times : Total Per run |
|
500 | 503 | User : 0.910862 s, 0.1821724 s. |
|
501 | 504 | System: 0.0 s, 0.0 s. |
|
502 | 505 | |
|
503 | 506 | -d |
|
504 | 507 | run your program under the control of pdb, the Python debugger. |
|
505 | 508 | This allows you to execute your program step by step, watch variables, |
|
506 | 509 | etc. Internally, what IPython does is similar to calling:: |
|
507 | 510 | |
|
508 | 511 | pdb.run('execfile("YOURFILENAME")') |
|
509 | 512 | |
|
510 | 513 | with a breakpoint set on line 1 of your file. You can change the line |
|
511 | 514 | number for this automatic breakpoint to be <N> by using the -bN option |
|
512 | 515 | (where N must be an integer). For example:: |
|
513 | 516 | |
|
514 | 517 | %run -d -b40 myscript |
|
515 | 518 | |
|
516 | 519 | will set the first breakpoint at line 40 in myscript.py. Note that |
|
517 | 520 | the first breakpoint must be set on a line which actually does |
|
518 | 521 | something (not a comment or docstring) for it to stop execution. |
|
519 | 522 | |
|
520 | 523 | Or you can specify a breakpoint in a different file:: |
|
521 | 524 | |
|
522 | 525 | %run -d -b myotherfile.py:20 myscript |
|
523 | 526 | |
|
524 | 527 | When the pdb debugger starts, you will see a (Pdb) prompt. You must |
|
525 | 528 | first enter 'c' (without quotes) to start execution up to the first |
|
526 | 529 | breakpoint. |
|
527 | 530 | |
|
528 | 531 | Entering 'help' gives information about the use of the debugger. You |
|
529 | 532 | can easily see pdb's full documentation with "import pdb;pdb.help()" |
|
530 | 533 | at a prompt. |
|
531 | 534 | |
|
532 | 535 | -p |
|
533 | 536 | run program under the control of the Python profiler module (which |
|
534 | 537 | prints a detailed report of execution times, function calls, etc). |
|
535 | 538 | |
|
536 | 539 | You can pass other options after -p which affect the behavior of the |
|
537 | 540 | profiler itself. See the docs for %prun for details. |
|
538 | 541 | |
|
539 | 542 | In this mode, the program's variables do NOT propagate back to the |
|
540 | 543 | IPython interactive namespace (because they remain in the namespace |
|
541 | 544 | where the profiler executes them). |
|
542 | 545 | |
|
543 | 546 | Internally this triggers a call to %prun, see its documentation for |
|
544 | 547 | details on the options available specifically for profiling. |
|
545 | 548 | |
|
546 | 549 | There is one special usage for which the text above doesn't apply: |
|
547 | 550 | if the filename ends with .ipy, the file is run as ipython script, |
|
548 | 551 | just as if the commands were written on IPython prompt. |
|
549 | 552 | |
|
550 | 553 | -m |
|
551 | 554 | specify module name to load instead of script path. Similar to |
|
552 | 555 | the -m option for the python interpreter. Use this option last if you |
|
553 | 556 | want to combine with other %run options. Unlike the python interpreter |
|
554 | 557 | only source modules are allowed no .pyc or .pyo files. |
|
555 | 558 | For example:: |
|
556 | 559 | |
|
557 | 560 | %run -m example |
|
558 | 561 | |
|
559 | 562 | will run the example module. |
|
560 | 563 | |
|
561 | 564 | -G |
|
562 | 565 | disable shell-like glob expansion of arguments. |
|
563 | 566 | |
|
564 | 567 | """ |
|
565 | 568 | |
|
566 | 569 | # get arguments and set sys.argv for program to be run. |
|
567 | 570 | opts, arg_lst = self.parse_options(parameter_s, |
|
568 | 571 | 'nidtN:b:pD:l:rs:T:em:G', |
|
569 | 572 | mode='list', list_all=1) |
|
570 | 573 | if "m" in opts: |
|
571 | 574 | modulename = opts["m"][0] |
|
572 | 575 | modpath = find_mod(modulename) |
|
573 | 576 | if modpath is None: |
|
574 | 577 | warn('%r is not a valid modulename on sys.path'%modulename) |
|
575 | 578 | return |
|
576 | 579 | arg_lst = [modpath] + arg_lst |
|
577 | 580 | try: |
|
578 | 581 | filename = file_finder(arg_lst[0]) |
|
579 | 582 | except IndexError: |
|
580 | 583 | warn('you must provide at least a filename.') |
|
581 | 584 | print('\n%run:\n', oinspect.getdoc(self.run)) |
|
582 | 585 | return |
|
583 | 586 | except IOError as e: |
|
584 | 587 | try: |
|
585 | 588 | msg = str(e) |
|
586 | 589 | except UnicodeError: |
|
587 | 590 | msg = e.message |
|
588 | 591 | error(msg) |
|
589 | 592 | return |
|
590 | 593 | |
|
591 | 594 | if filename.lower().endswith('.ipy'): |
|
592 | 595 | with preserve_keys(self.shell.user_ns, '__file__'): |
|
593 | 596 | self.shell.user_ns['__file__'] = filename |
|
594 | 597 | self.shell.safe_execfile_ipy(filename) |
|
595 | 598 | return |
|
596 | 599 | |
|
597 | 600 | # Control the response to exit() calls made by the script being run |
|
598 | 601 | exit_ignore = 'e' in opts |
|
599 | 602 | |
|
600 | 603 | # Make sure that the running script gets a proper sys.argv as if it |
|
601 | 604 | # were run from a system shell. |
|
602 | 605 | save_argv = sys.argv # save it for later restoring |
|
603 | 606 | |
|
604 | 607 | if 'G' in opts: |
|
605 | 608 | args = arg_lst[1:] |
|
606 | 609 | else: |
|
607 | 610 | # tilde and glob expansion |
|
608 | 611 | args = shellglob(map(os.path.expanduser, arg_lst[1:])) |
|
609 | 612 | |
|
610 | 613 | sys.argv = [filename] + args # put in the proper filename |
|
611 | 614 | # protect sys.argv from potential unicode strings on Python 2: |
|
612 | 615 | if not py3compat.PY3: |
|
613 | 616 | sys.argv = [ py3compat.cast_bytes(a) for a in sys.argv ] |
|
614 | 617 | |
|
615 | 618 | if 'i' in opts: |
|
616 | 619 | # Run in user's interactive namespace |
|
617 | 620 | prog_ns = self.shell.user_ns |
|
618 | 621 | __name__save = self.shell.user_ns['__name__'] |
|
619 | 622 | prog_ns['__name__'] = '__main__' |
|
620 | 623 | main_mod = self.shell.user_module |
|
621 | 624 | |
|
622 | 625 | # Since '%run foo' emulates 'python foo.py' at the cmd line, we must |
|
623 | 626 | # set the __file__ global in the script's namespace |
|
624 | 627 | # TK: Is this necessary in interactive mode? |
|
625 | 628 | prog_ns['__file__'] = filename |
|
626 | 629 | else: |
|
627 | 630 | # Run in a fresh, empty namespace |
|
628 | 631 | if 'n' in opts: |
|
629 | 632 | name = os.path.splitext(os.path.basename(filename))[0] |
|
630 | 633 | else: |
|
631 | 634 | name = '__main__' |
|
632 | 635 | |
|
633 | 636 | # The shell MUST hold a reference to prog_ns so after %run |
|
634 | 637 | # exits, the python deletion mechanism doesn't zero it out |
|
635 | 638 | # (leaving dangling references). See interactiveshell for details |
|
636 | 639 | main_mod = self.shell.new_main_mod(filename, name) |
|
637 | 640 | prog_ns = main_mod.__dict__ |
|
638 | 641 | |
|
639 | 642 | # pickle fix. See interactiveshell for an explanation. But we need to |
|
640 | 643 | # make sure that, if we overwrite __main__, we replace it at the end |
|
641 | 644 | main_mod_name = prog_ns['__name__'] |
|
642 | 645 | |
|
643 | 646 | if main_mod_name == '__main__': |
|
644 | 647 | restore_main = sys.modules['__main__'] |
|
645 | 648 | else: |
|
646 | 649 | restore_main = False |
|
647 | 650 | |
|
648 | 651 | # This needs to be undone at the end to prevent holding references to |
|
649 | 652 | # every single object ever created. |
|
650 | 653 | sys.modules[main_mod_name] = main_mod |
|
651 | 654 | |
|
652 | 655 | if 'p' in opts or 'd' in opts: |
|
653 | 656 | if 'm' in opts: |
|
654 | 657 | code = 'run_module(modulename, prog_ns)' |
|
655 | 658 | code_ns = { |
|
656 | 659 | 'run_module': self.shell.safe_run_module, |
|
657 | 660 | 'prog_ns': prog_ns, |
|
658 | 661 | 'modulename': modulename, |
|
659 | 662 | } |
|
660 | 663 | else: |
|
661 | 664 | code = 'execfile(filename, prog_ns)' |
|
662 | 665 | code_ns = { |
|
663 | 666 | 'execfile': self.shell.safe_execfile, |
|
664 | 667 | 'prog_ns': prog_ns, |
|
665 | 668 | 'filename': get_py_filename(filename), |
|
666 | 669 | } |
|
667 | 670 | |
|
668 | 671 | try: |
|
669 | 672 | stats = None |
|
670 | 673 | with self.shell.readline_no_record: |
|
671 | 674 | if 'p' in opts: |
|
672 | 675 | stats = self._run_with_profiler(code, opts, code_ns) |
|
673 | 676 | else: |
|
674 | 677 | if 'd' in opts: |
|
675 | 678 | bp_file, bp_line = parse_breakpoint( |
|
676 | 679 | opts.get('b', ['1'])[0], filename) |
|
677 | 680 | self._run_with_debugger( |
|
678 | 681 | code, code_ns, filename, bp_line, bp_file) |
|
679 | 682 | else: |
|
680 | 683 | if 'm' in opts: |
|
681 | 684 | def run(): |
|
682 | 685 | self.shell.safe_run_module(modulename, prog_ns) |
|
683 | 686 | else: |
|
684 | 687 | if runner is None: |
|
685 | 688 | runner = self.default_runner |
|
686 | 689 | if runner is None: |
|
687 | 690 | runner = self.shell.safe_execfile |
|
688 | 691 | |
|
689 | 692 | def run(): |
|
690 | 693 | runner(filename, prog_ns, prog_ns, |
|
691 | 694 | exit_ignore=exit_ignore) |
|
692 | 695 | |
|
693 | 696 | if 't' in opts: |
|
694 | 697 | # timed execution |
|
695 | 698 | try: |
|
696 | 699 | nruns = int(opts['N'][0]) |
|
697 | 700 | if nruns < 1: |
|
698 | 701 | error('Number of runs must be >=1') |
|
699 | 702 | return |
|
700 | 703 | except (KeyError): |
|
701 | 704 | nruns = 1 |
|
702 | 705 | self._run_with_timing(run, nruns) |
|
703 | 706 | else: |
|
704 | 707 | # regular execution |
|
705 | 708 | run() |
|
706 | 709 | |
|
707 | 710 | if 'i' in opts: |
|
708 | 711 | self.shell.user_ns['__name__'] = __name__save |
|
709 | 712 | else: |
|
710 | 713 | # update IPython interactive namespace |
|
711 | 714 | |
|
712 | 715 | # Some forms of read errors on the file may mean the |
|
713 | 716 | # __name__ key was never set; using pop we don't have to |
|
714 | 717 | # worry about a possible KeyError. |
|
715 | 718 | prog_ns.pop('__name__', None) |
|
716 | 719 | |
|
717 | 720 | with preserve_keys(self.shell.user_ns, '__file__'): |
|
718 | 721 | self.shell.user_ns.update(prog_ns) |
|
719 | 722 | finally: |
|
720 | 723 | # It's a bit of a mystery why, but __builtins__ can change from |
|
721 | 724 | # being a module to becoming a dict missing some key data after |
|
722 | 725 | # %run. As best I can see, this is NOT something IPython is doing |
|
723 | 726 | # at all, and similar problems have been reported before: |
|
724 | 727 | # http://coding.derkeiler.com/Archive/Python/comp.lang.python/2004-10/0188.html |
|
725 | 728 | # Since this seems to be done by the interpreter itself, the best |
|
726 | 729 | # we can do is to at least restore __builtins__ for the user on |
|
727 | 730 | # exit. |
|
728 | 731 | self.shell.user_ns['__builtins__'] = builtin_mod |
|
729 | 732 | |
|
730 | 733 | # Ensure key global structures are restored |
|
731 | 734 | sys.argv = save_argv |
|
732 | 735 | if restore_main: |
|
733 | 736 | sys.modules['__main__'] = restore_main |
|
734 | 737 | else: |
|
735 | 738 | # Remove from sys.modules the reference to main_mod we'd |
|
736 | 739 | # added. Otherwise it will trap references to objects |
|
737 | 740 | # contained therein. |
|
738 | 741 | del sys.modules[main_mod_name] |
|
739 | 742 | |
|
740 | 743 | return stats |
|
741 | 744 | |
|
742 | 745 | def _run_with_debugger(self, code, code_ns, filename=None, |
|
743 | 746 | bp_line=None, bp_file=None): |
|
744 | 747 | """ |
|
745 | 748 | Run `code` in debugger with a break point. |
|
746 | 749 | |
|
747 | 750 | Parameters |
|
748 | 751 | ---------- |
|
749 | 752 | code : str |
|
750 | 753 | Code to execute. |
|
751 | 754 | code_ns : dict |
|
752 | 755 | A namespace in which `code` is executed. |
|
753 | 756 | filename : str |
|
754 | 757 | `code` is ran as if it is in `filename`. |
|
755 | 758 | bp_line : int, optional |
|
756 | 759 | Line number of the break point. |
|
757 | 760 | bp_file : str, optional |
|
758 | 761 | Path to the file in which break point is specified. |
|
759 | 762 | `filename` is used if not given. |
|
760 | 763 | |
|
761 | 764 | Raises |
|
762 | 765 | ------ |
|
763 | 766 | UsageError |
|
764 | 767 | If the break point given by `bp_line` is not valid. |
|
765 | 768 | |
|
766 | 769 | """ |
|
767 | 770 | deb = debugger.Pdb(self.shell.colors) |
|
768 | 771 | # reset Breakpoint state, which is moronically kept |
|
769 | 772 | # in a class |
|
770 | 773 | bdb.Breakpoint.next = 1 |
|
771 | 774 | bdb.Breakpoint.bplist = {} |
|
772 | 775 | bdb.Breakpoint.bpbynumber = [None] |
|
773 | 776 | if bp_line is not None: |
|
774 | 777 | # Set an initial breakpoint to stop execution |
|
775 | 778 | maxtries = 10 |
|
776 | 779 | bp_file = bp_file or filename |
|
777 | 780 | checkline = deb.checkline(bp_file, bp_line) |
|
778 | 781 | if not checkline: |
|
779 | 782 | for bp in range(bp_line + 1, bp_line + maxtries + 1): |
|
780 | 783 | if deb.checkline(bp_file, bp): |
|
781 | 784 | break |
|
782 | 785 | else: |
|
783 | 786 | msg = ("\nI failed to find a valid line to set " |
|
784 | 787 | "a breakpoint\n" |
|
785 | 788 | "after trying up to line: %s.\n" |
|
786 | 789 | "Please set a valid breakpoint manually " |
|
787 | 790 | "with the -b option." % bp) |
|
788 | 791 | raise UsageError(msg) |
|
789 | 792 | # if we find a good linenumber, set the breakpoint |
|
790 | 793 | deb.do_break('%s:%s' % (bp_file, bp_line)) |
|
791 | 794 | |
|
792 | 795 | if filename: |
|
793 | 796 | # Mimic Pdb._runscript(...) |
|
794 | 797 | deb._wait_for_mainpyfile = True |
|
795 | 798 | deb.mainpyfile = deb.canonic(filename) |
|
796 | 799 | |
|
797 | 800 | # Start file run |
|
798 | 801 | print("NOTE: Enter 'c' at the %s prompt to continue execution." % deb.prompt) |
|
799 | 802 | try: |
|
800 | 803 | if filename: |
|
801 | 804 | # save filename so it can be used by methods on the deb object |
|
802 | 805 | deb._exec_filename = filename |
|
803 | 806 | deb.run(code, code_ns) |
|
804 | 807 | |
|
805 | 808 | except: |
|
806 | 809 | etype, value, tb = sys.exc_info() |
|
807 | 810 | # Skip three frames in the traceback: the %run one, |
|
808 | 811 | # one inside bdb.py, and the command-line typed by the |
|
809 | 812 | # user (run by exec in pdb itself). |
|
810 | 813 | self.shell.InteractiveTB(etype, value, tb, tb_offset=3) |
|
811 | 814 | |
|
812 | 815 | @staticmethod |
|
813 | 816 | def _run_with_timing(run, nruns): |
|
814 | 817 | """ |
|
815 | 818 | Run function `run` and print timing information. |
|
816 | 819 | |
|
817 | 820 | Parameters |
|
818 | 821 | ---------- |
|
819 | 822 | run : callable |
|
820 | 823 | Any callable object which takes no argument. |
|
821 | 824 | nruns : int |
|
822 | 825 | Number of times to execute `run`. |
|
823 | 826 | |
|
824 | 827 | """ |
|
825 | 828 | twall0 = time.time() |
|
826 | 829 | if nruns == 1: |
|
827 | 830 | t0 = clock2() |
|
828 | 831 | run() |
|
829 | 832 | t1 = clock2() |
|
830 | 833 | t_usr = t1[0] - t0[0] |
|
831 | 834 | t_sys = t1[1] - t0[1] |
|
832 | 835 | print("\nIPython CPU timings (estimated):") |
|
833 | 836 | print(" User : %10.2f s." % t_usr) |
|
834 | 837 | print(" System : %10.2f s." % t_sys) |
|
835 | 838 | else: |
|
836 | 839 | runs = range(nruns) |
|
837 | 840 | t0 = clock2() |
|
838 | 841 | for nr in runs: |
|
839 | 842 | run() |
|
840 | 843 | t1 = clock2() |
|
841 | 844 | t_usr = t1[0] - t0[0] |
|
842 | 845 | t_sys = t1[1] - t0[1] |
|
843 | 846 | print("\nIPython CPU timings (estimated):") |
|
844 | 847 | print("Total runs performed:", nruns) |
|
845 | 848 | print(" Times : %10s %10s" % ('Total', 'Per run')) |
|
846 | 849 | print(" User : %10.2f s, %10.2f s." % (t_usr, t_usr / nruns)) |
|
847 | 850 | print(" System : %10.2f s, %10.2f s." % (t_sys, t_sys / nruns)) |
|
848 | 851 | twall1 = time.time() |
|
849 | 852 | print("Wall time: %10.2f s." % (twall1 - twall0)) |
|
850 | 853 | |
|
851 | 854 | @skip_doctest |
|
852 | 855 | @line_cell_magic |
|
853 | 856 | def timeit(self, line='', cell=None): |
|
854 | 857 | """Time execution of a Python statement or expression |
|
855 | 858 | |
|
856 | 859 | Usage, in line mode: |
|
857 | 860 | %timeit [-n<N> -r<R> [-t|-c] -q -p<P> -o] statement |
|
858 | 861 | or in cell mode: |
|
859 | 862 | %%timeit [-n<N> -r<R> [-t|-c] -q -p<P> -o] setup_code |
|
860 | 863 | code |
|
861 | 864 | code... |
|
862 | 865 | |
|
863 | 866 | Time execution of a Python statement or expression using the timeit |
|
864 | 867 | module. This function can be used both as a line and cell magic: |
|
865 | 868 | |
|
866 | 869 | - In line mode you can time a single-line statement (though multiple |
|
867 | 870 | ones can be chained with using semicolons). |
|
868 | 871 | |
|
869 | 872 | - In cell mode, the statement in the first line is used as setup code |
|
870 | 873 | (executed but not timed) and the body of the cell is timed. The cell |
|
871 | 874 | body has access to any variables created in the setup code. |
|
872 | 875 | |
|
873 | 876 | Options: |
|
874 | 877 | -n<N>: execute the given statement <N> times in a loop. If this value |
|
875 | 878 | is not given, a fitting value is chosen. |
|
876 | 879 | |
|
877 | 880 | -r<R>: repeat the loop iteration <R> times and take the best result. |
|
878 | 881 | Default: 3 |
|
879 | 882 | |
|
880 | 883 | -t: use time.time to measure the time, which is the default on Unix. |
|
881 | 884 | This function measures wall time. |
|
882 | 885 | |
|
883 | 886 | -c: use time.clock to measure the time, which is the default on |
|
884 | 887 | Windows and measures wall time. On Unix, resource.getrusage is used |
|
885 | 888 | instead and returns the CPU user time. |
|
886 | 889 | |
|
887 | 890 | -p<P>: use a precision of <P> digits to display the timing result. |
|
888 | 891 | Default: 3 |
|
889 | 892 | |
|
890 | 893 | -q: Quiet, do not print result. |
|
891 | 894 | |
|
892 | 895 | -o: return a TimeitResult that can be stored in a variable to inspect |
|
893 | 896 | the result in more details. |
|
894 | 897 | |
|
895 | 898 | |
|
896 | 899 | Examples |
|
897 | 900 | -------- |
|
898 | 901 | :: |
|
899 | 902 | |
|
900 | 903 | In [1]: %timeit pass |
|
901 | 904 | 10000000 loops, best of 3: 53.3 ns per loop |
|
902 | 905 | |
|
903 | 906 | In [2]: u = None |
|
904 | 907 | |
|
905 | 908 | In [3]: %timeit u is None |
|
906 | 909 | 10000000 loops, best of 3: 184 ns per loop |
|
907 | 910 | |
|
908 | 911 | In [4]: %timeit -r 4 u == None |
|
909 | 912 | 1000000 loops, best of 4: 242 ns per loop |
|
910 | 913 | |
|
911 | 914 | In [5]: import time |
|
912 | 915 | |
|
913 | 916 | In [6]: %timeit -n1 time.sleep(2) |
|
914 | 917 | 1 loops, best of 3: 2 s per loop |
|
915 | 918 | |
|
916 | 919 | |
|
917 | 920 | The times reported by %timeit will be slightly higher than those |
|
918 | 921 | reported by the timeit.py script when variables are accessed. This is |
|
919 | 922 | due to the fact that %timeit executes the statement in the namespace |
|
920 | 923 | of the shell, compared with timeit.py, which uses a single setup |
|
921 | 924 | statement to import function or create variables. Generally, the bias |
|
922 | 925 | does not matter as long as results from timeit.py are not mixed with |
|
923 | 926 | those from %timeit.""" |
|
924 | 927 | |
|
925 | 928 | import timeit |
|
926 | 929 | |
|
927 | 930 | opts, stmt = self.parse_options(line,'n:r:tcp:qo', |
|
928 | 931 | posix=False, strict=False) |
|
929 | 932 | if stmt == "" and cell is None: |
|
930 | 933 | return |
|
931 | 934 | |
|
932 | 935 | timefunc = timeit.default_timer |
|
933 | 936 | number = int(getattr(opts, "n", 0)) |
|
934 | 937 | repeat = int(getattr(opts, "r", timeit.default_repeat)) |
|
935 | 938 | precision = int(getattr(opts, "p", 3)) |
|
936 | 939 | quiet = 'q' in opts |
|
937 | 940 | return_result = 'o' in opts |
|
938 | 941 | if hasattr(opts, "t"): |
|
939 | 942 | timefunc = time.time |
|
940 | 943 | if hasattr(opts, "c"): |
|
941 | 944 | timefunc = clock |
|
942 | 945 | |
|
943 | 946 | timer = timeit.Timer(timer=timefunc) |
|
944 | 947 | # this code has tight coupling to the inner workings of timeit.Timer, |
|
945 | 948 | # but is there a better way to achieve that the code stmt has access |
|
946 | 949 | # to the shell namespace? |
|
947 | 950 | transform = self.shell.input_splitter.transform_cell |
|
948 | 951 | |
|
949 | 952 | if cell is None: |
|
950 | 953 | # called as line magic |
|
951 | 954 | ast_setup = ast.parse("pass") |
|
952 | 955 | ast_stmt = ast.parse(transform(stmt)) |
|
953 | 956 | else: |
|
954 | 957 | ast_setup = ast.parse(transform(stmt)) |
|
955 | 958 | ast_stmt = ast.parse(transform(cell)) |
|
956 | 959 | |
|
957 | 960 | ast_setup = self.shell.transform_ast(ast_setup) |
|
958 | 961 | ast_stmt = self.shell.transform_ast(ast_stmt) |
|
959 | 962 | |
|
960 | 963 | # This codestring is taken from timeit.template - we fill it in as an |
|
961 | 964 | # AST, so that we can apply our AST transformations to the user code |
|
962 | 965 | # without affecting the timing code. |
|
963 | 966 | timeit_ast_template = ast.parse('def inner(_it, _timer):\n' |
|
964 | 967 | ' setup\n' |
|
965 | 968 | ' _t0 = _timer()\n' |
|
966 | 969 | ' for _i in _it:\n' |
|
967 | 970 | ' stmt\n' |
|
968 | 971 | ' _t1 = _timer()\n' |
|
969 | 972 | ' return _t1 - _t0\n') |
|
970 | 973 | |
|
971 | 974 | timeit_ast = TimeitTemplateFiller(ast_setup, ast_stmt).visit(timeit_ast_template) |
|
972 | 975 | timeit_ast = ast.fix_missing_locations(timeit_ast) |
|
973 | 976 | |
|
974 | 977 | # Track compilation time so it can be reported if too long |
|
975 | 978 | # Minimum time above which compilation time will be reported |
|
976 | 979 | tc_min = 0.1 |
|
977 | 980 | |
|
978 | 981 | t0 = clock() |
|
979 | 982 | code = compile(timeit_ast, "<magic-timeit>", "exec") |
|
980 | 983 | tc = clock()-t0 |
|
981 | 984 | |
|
982 | 985 | ns = {} |
|
983 | 986 | exec(code, self.shell.user_ns, ns) |
|
984 | 987 | timer.inner = ns["inner"] |
|
985 | 988 | |
|
986 | 989 | if number == 0: |
|
987 | 990 | # determine number so that 0.2 <= total time < 2.0 |
|
988 | 991 | number = 1 |
|
989 | 992 | for _ in range(1, 10): |
|
990 | 993 | if timer.timeit(number) >= 0.2: |
|
991 | 994 | break |
|
992 | 995 | number *= 10 |
|
993 | 996 | all_runs = timer.repeat(repeat, number) |
|
994 | 997 | best = min(all_runs) / number |
|
995 | 998 | if not quiet : |
|
996 | 999 | print(u"%d loops, best of %d: %s per loop" % (number, repeat, |
|
997 | 1000 | _format_time(best, precision))) |
|
998 | 1001 | if tc > tc_min: |
|
999 | 1002 | print("Compiler time: %.2f s" % tc) |
|
1000 | 1003 | if return_result: |
|
1001 | 1004 | return TimeitResult(number, repeat, best, all_runs, tc, precision) |
|
1002 | 1005 | |
|
1003 | 1006 | @skip_doctest |
|
1004 | 1007 | @needs_local_scope |
|
1005 | 1008 | @line_cell_magic |
|
1006 | 1009 | def time(self,line='', cell=None, local_ns=None): |
|
1007 | 1010 | """Time execution of a Python statement or expression. |
|
1008 | 1011 | |
|
1009 | 1012 | The CPU and wall clock times are printed, and the value of the |
|
1010 | 1013 | expression (if any) is returned. Note that under Win32, system time |
|
1011 | 1014 | is always reported as 0, since it can not be measured. |
|
1012 | 1015 | |
|
1013 | 1016 | This function can be used both as a line and cell magic: |
|
1014 | 1017 | |
|
1015 | 1018 | - In line mode you can time a single-line statement (though multiple |
|
1016 | 1019 | ones can be chained with using semicolons). |
|
1017 | 1020 | |
|
1018 | 1021 | - In cell mode, you can time the cell body (a directly |
|
1019 | 1022 | following statement raises an error). |
|
1020 | 1023 | |
|
1021 | 1024 | This function provides very basic timing functionality. Use the timeit |
|
1022 | 1025 | magic for more controll over the measurement. |
|
1023 | 1026 | |
|
1024 | 1027 | Examples |
|
1025 | 1028 | -------- |
|
1026 | 1029 | :: |
|
1027 | 1030 | |
|
1028 | 1031 | In [1]: %time 2**128 |
|
1029 | 1032 | CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s |
|
1030 | 1033 | Wall time: 0.00 |
|
1031 | 1034 | Out[1]: 340282366920938463463374607431768211456L |
|
1032 | 1035 | |
|
1033 | 1036 | In [2]: n = 1000000 |
|
1034 | 1037 | |
|
1035 | 1038 | In [3]: %time sum(range(n)) |
|
1036 | 1039 | CPU times: user 1.20 s, sys: 0.05 s, total: 1.25 s |
|
1037 | 1040 | Wall time: 1.37 |
|
1038 | 1041 | Out[3]: 499999500000L |
|
1039 | 1042 | |
|
1040 | 1043 | In [4]: %time print 'hello world' |
|
1041 | 1044 | hello world |
|
1042 | 1045 | CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s |
|
1043 | 1046 | Wall time: 0.00 |
|
1044 | 1047 | |
|
1045 | 1048 | Note that the time needed by Python to compile the given expression |
|
1046 | 1049 | will be reported if it is more than 0.1s. In this example, the |
|
1047 | 1050 | actual exponentiation is done by Python at compilation time, so while |
|
1048 | 1051 | the expression can take a noticeable amount of time to compute, that |
|
1049 | 1052 | time is purely due to the compilation: |
|
1050 | 1053 | |
|
1051 | 1054 | In [5]: %time 3**9999; |
|
1052 | 1055 | CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s |
|
1053 | 1056 | Wall time: 0.00 s |
|
1054 | 1057 | |
|
1055 | 1058 | In [6]: %time 3**999999; |
|
1056 | 1059 | CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s |
|
1057 | 1060 | Wall time: 0.00 s |
|
1058 | 1061 | Compiler : 0.78 s |
|
1059 | 1062 | """ |
|
1060 | 1063 | |
|
1061 | 1064 | # fail immediately if the given expression can't be compiled |
|
1062 | 1065 | |
|
1063 | 1066 | if line and cell: |
|
1064 | 1067 | raise UsageError("Can't use statement directly after '%%time'!") |
|
1065 | 1068 | |
|
1066 | 1069 | if cell: |
|
1067 | 1070 | expr = self.shell.input_transformer_manager.transform_cell(cell) |
|
1068 | 1071 | else: |
|
1069 | 1072 | expr = self.shell.input_transformer_manager.transform_cell(line) |
|
1070 | 1073 | |
|
1071 | 1074 | # Minimum time above which parse time will be reported |
|
1072 | 1075 | tp_min = 0.1 |
|
1073 | 1076 | |
|
1074 | 1077 | t0 = clock() |
|
1075 | 1078 | expr_ast = ast.parse(expr) |
|
1076 | 1079 | tp = clock()-t0 |
|
1077 | 1080 | |
|
1078 | 1081 | # Apply AST transformations |
|
1079 | 1082 | expr_ast = self.shell.transform_ast(expr_ast) |
|
1080 | 1083 | |
|
1081 | 1084 | # Minimum time above which compilation time will be reported |
|
1082 | 1085 | tc_min = 0.1 |
|
1083 | 1086 | |
|
1084 | 1087 | if len(expr_ast.body)==1 and isinstance(expr_ast.body[0], ast.Expr): |
|
1085 | 1088 | mode = 'eval' |
|
1086 | 1089 | source = '<timed eval>' |
|
1087 | 1090 | expr_ast = ast.Expression(expr_ast.body[0].value) |
|
1088 | 1091 | else: |
|
1089 | 1092 | mode = 'exec' |
|
1090 | 1093 | source = '<timed exec>' |
|
1091 | 1094 | t0 = clock() |
|
1092 | 1095 | code = compile(expr_ast, source, mode) |
|
1093 | 1096 | tc = clock()-t0 |
|
1094 | 1097 | |
|
1095 | 1098 | # skew measurement as little as possible |
|
1096 | 1099 | glob = self.shell.user_ns |
|
1097 | 1100 | wtime = time.time |
|
1098 | 1101 | # time execution |
|
1099 | 1102 | wall_st = wtime() |
|
1100 | 1103 | if mode=='eval': |
|
1101 | 1104 | st = clock2() |
|
1102 | 1105 | out = eval(code, glob, local_ns) |
|
1103 | 1106 | end = clock2() |
|
1104 | 1107 | else: |
|
1105 | 1108 | st = clock2() |
|
1106 | 1109 | exec(code, glob, local_ns) |
|
1107 | 1110 | end = clock2() |
|
1108 | 1111 | out = None |
|
1109 | 1112 | wall_end = wtime() |
|
1110 | 1113 | # Compute actual times and report |
|
1111 | 1114 | wall_time = wall_end-wall_st |
|
1112 | 1115 | cpu_user = end[0]-st[0] |
|
1113 | 1116 | cpu_sys = end[1]-st[1] |
|
1114 | 1117 | cpu_tot = cpu_user+cpu_sys |
|
1115 | 1118 | # On windows cpu_sys is always zero, so no new information to the next print |
|
1116 | 1119 | if sys.platform != 'win32': |
|
1117 | 1120 | print("CPU times: user %s, sys: %s, total: %s" % \ |
|
1118 | 1121 | (_format_time(cpu_user),_format_time(cpu_sys),_format_time(cpu_tot))) |
|
1119 | 1122 | print("Wall time: %s" % _format_time(wall_time)) |
|
1120 | 1123 | if tc > tc_min: |
|
1121 | 1124 | print("Compiler : %s" % _format_time(tc)) |
|
1122 | 1125 | if tp > tp_min: |
|
1123 | 1126 | print("Parser : %s" % _format_time(tp)) |
|
1124 | 1127 | return out |
|
1125 | 1128 | |
|
1126 | 1129 | @skip_doctest |
|
1127 | 1130 | @line_magic |
|
1128 | 1131 | def macro(self, parameter_s=''): |
|
1129 | 1132 | """Define a macro for future re-execution. It accepts ranges of history, |
|
1130 | 1133 | filenames or string objects. |
|
1131 | 1134 | |
|
1132 | 1135 | Usage:\\ |
|
1133 | 1136 | %macro [options] name n1-n2 n3-n4 ... n5 .. n6 ... |
|
1134 | 1137 | |
|
1135 | 1138 | Options: |
|
1136 | 1139 | |
|
1137 | 1140 | -r: use 'raw' input. By default, the 'processed' history is used, |
|
1138 | 1141 | so that magics are loaded in their transformed version to valid |
|
1139 | 1142 | Python. If this option is given, the raw input as typed at the |
|
1140 | 1143 | command line is used instead. |
|
1141 | 1144 | |
|
1142 | 1145 | -q: quiet macro definition. By default, a tag line is printed |
|
1143 | 1146 | to indicate the macro has been created, and then the contents of |
|
1144 | 1147 | the macro are printed. If this option is given, then no printout |
|
1145 | 1148 | is produced once the macro is created. |
|
1146 | 1149 | |
|
1147 | 1150 | This will define a global variable called `name` which is a string |
|
1148 | 1151 | made of joining the slices and lines you specify (n1,n2,... numbers |
|
1149 | 1152 | above) from your input history into a single string. This variable |
|
1150 | 1153 | acts like an automatic function which re-executes those lines as if |
|
1151 | 1154 | you had typed them. You just type 'name' at the prompt and the code |
|
1152 | 1155 | executes. |
|
1153 | 1156 | |
|
1154 | 1157 | The syntax for indicating input ranges is described in %history. |
|
1155 | 1158 | |
|
1156 | 1159 | Note: as a 'hidden' feature, you can also use traditional python slice |
|
1157 | 1160 | notation, where N:M means numbers N through M-1. |
|
1158 | 1161 | |
|
1159 | 1162 | For example, if your history contains (print using %hist -n ):: |
|
1160 | 1163 | |
|
1161 | 1164 | 44: x=1 |
|
1162 | 1165 | 45: y=3 |
|
1163 | 1166 | 46: z=x+y |
|
1164 | 1167 | 47: print x |
|
1165 | 1168 | 48: a=5 |
|
1166 | 1169 | 49: print 'x',x,'y',y |
|
1167 | 1170 | |
|
1168 | 1171 | you can create a macro with lines 44 through 47 (included) and line 49 |
|
1169 | 1172 | called my_macro with:: |
|
1170 | 1173 | |
|
1171 | 1174 | In [55]: %macro my_macro 44-47 49 |
|
1172 | 1175 | |
|
1173 | 1176 | Now, typing `my_macro` (without quotes) will re-execute all this code |
|
1174 | 1177 | in one pass. |
|
1175 | 1178 | |
|
1176 | 1179 | You don't need to give the line-numbers in order, and any given line |
|
1177 | 1180 | number can appear multiple times. You can assemble macros with any |
|
1178 | 1181 | lines from your input history in any order. |
|
1179 | 1182 | |
|
1180 | 1183 | The macro is a simple object which holds its value in an attribute, |
|
1181 | 1184 | but IPython's display system checks for macros and executes them as |
|
1182 | 1185 | code instead of printing them when you type their name. |
|
1183 | 1186 | |
|
1184 | 1187 | You can view a macro's contents by explicitly printing it with:: |
|
1185 | 1188 | |
|
1186 | 1189 | print macro_name |
|
1187 | 1190 | |
|
1188 | 1191 | """ |
|
1189 | 1192 | opts,args = self.parse_options(parameter_s,'rq',mode='list') |
|
1190 | 1193 | if not args: # List existing macros |
|
1191 | 1194 | return sorted(k for k,v in iteritems(self.shell.user_ns) if\ |
|
1192 | 1195 | isinstance(v, Macro)) |
|
1193 | 1196 | if len(args) == 1: |
|
1194 | 1197 | raise UsageError( |
|
1195 | 1198 | "%macro insufficient args; usage '%macro name n1-n2 n3-4...") |
|
1196 | 1199 | name, codefrom = args[0], " ".join(args[1:]) |
|
1197 | 1200 | |
|
1198 | 1201 | #print 'rng',ranges # dbg |
|
1199 | 1202 | try: |
|
1200 | 1203 | lines = self.shell.find_user_code(codefrom, 'r' in opts) |
|
1201 | 1204 | except (ValueError, TypeError) as e: |
|
1202 | 1205 | print(e.args[0]) |
|
1203 | 1206 | return |
|
1204 | 1207 | macro = Macro(lines) |
|
1205 | 1208 | self.shell.define_macro(name, macro) |
|
1206 | 1209 | if not ( 'q' in opts) : |
|
1207 | 1210 | print('Macro `%s` created. To execute, type its name (without quotes).' % name) |
|
1208 | 1211 | print('=== Macro contents: ===') |
|
1209 | 1212 | print(macro, end=' ') |
|
1210 | 1213 | |
|
1211 | 1214 | @magic_arguments.magic_arguments() |
|
1212 | 1215 | @magic_arguments.argument('output', type=str, default='', nargs='?', |
|
1213 | 1216 | help="""The name of the variable in which to store output. |
|
1214 | 1217 | This is a utils.io.CapturedIO object with stdout/err attributes |
|
1215 | 1218 | for the text of the captured output. |
|
1216 | 1219 | |
|
1217 | 1220 | CapturedOutput also has a show() method for displaying the output, |
|
1218 | 1221 | and __call__ as well, so you can use that to quickly display the |
|
1219 | 1222 | output. |
|
1220 | 1223 | |
|
1221 | 1224 | If unspecified, captured output is discarded. |
|
1222 | 1225 | """ |
|
1223 | 1226 | ) |
|
1224 | 1227 | @magic_arguments.argument('--no-stderr', action="store_true", |
|
1225 | 1228 | help="""Don't capture stderr.""" |
|
1226 | 1229 | ) |
|
1227 | 1230 | @magic_arguments.argument('--no-stdout', action="store_true", |
|
1228 | 1231 | help="""Don't capture stdout.""" |
|
1229 | 1232 | ) |
|
1230 | 1233 | @magic_arguments.argument('--no-display', action="store_true", |
|
1231 | 1234 | help="""Don't capture IPython's rich display.""" |
|
1232 | 1235 | ) |
|
1233 | 1236 | @cell_magic |
|
1234 | 1237 | def capture(self, line, cell): |
|
1235 | 1238 | """run the cell, capturing stdout, stderr, and IPython's rich display() calls.""" |
|
1236 | 1239 | args = magic_arguments.parse_argstring(self.capture, line) |
|
1237 | 1240 | out = not args.no_stdout |
|
1238 | 1241 | err = not args.no_stderr |
|
1239 | 1242 | disp = not args.no_display |
|
1240 | 1243 | with capture_output(out, err, disp) as io: |
|
1241 | 1244 | self.shell.run_cell(cell) |
|
1242 | 1245 | if args.output: |
|
1243 | 1246 | self.shell.user_ns[args.output] = io |
|
1244 | 1247 | |
|
1245 | 1248 | def parse_breakpoint(text, current_file): |
|
1246 | 1249 | '''Returns (file, line) for file:line and (current_file, line) for line''' |
|
1247 | 1250 | colon = text.find(':') |
|
1248 | 1251 | if colon == -1: |
|
1249 | 1252 | return current_file, int(text) |
|
1250 | 1253 | else: |
|
1251 | 1254 | return text[:colon], int(text[colon+1:]) |
|
1252 | 1255 | |
|
1253 | 1256 | def _format_time(timespan, precision=3): |
|
1254 | 1257 | """Formats the timespan in a human readable form""" |
|
1255 | 1258 | import math |
|
1256 | 1259 | |
|
1257 | 1260 | if timespan >= 60.0: |
|
1258 | 1261 | # we have more than a minute, format that in a human readable form |
|
1259 | 1262 | # Idea from http://snipplr.com/view/5713/ |
|
1260 | 1263 | parts = [("d", 60*60*24),("h", 60*60),("min", 60), ("s", 1)] |
|
1261 | 1264 | time = [] |
|
1262 | 1265 | leftover = timespan |
|
1263 | 1266 | for suffix, length in parts: |
|
1264 | 1267 | value = int(leftover / length) |
|
1265 | 1268 | if value > 0: |
|
1266 | 1269 | leftover = leftover % length |
|
1267 | 1270 | time.append(u'%s%s' % (str(value), suffix)) |
|
1268 | 1271 | if leftover < 1: |
|
1269 | 1272 | break |
|
1270 | 1273 | return " ".join(time) |
|
1271 | 1274 | |
|
1272 | 1275 | |
|
1273 | 1276 | # Unfortunately the unicode 'micro' symbol can cause problems in |
|
1274 | 1277 | # certain terminals. |
|
1275 | 1278 | # See bug: https://bugs.launchpad.net/ipython/+bug/348466 |
|
1276 | 1279 | # Try to prevent crashes by being more secure than it needs to |
|
1277 | 1280 | # E.g. eclipse is able to print a µ, but has no sys.stdout.encoding set. |
|
1278 | 1281 | units = [u"s", u"ms",u'us',"ns"] # the save value |
|
1279 | 1282 | if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding: |
|
1280 | 1283 | try: |
|
1281 | 1284 | u'\xb5'.encode(sys.stdout.encoding) |
|
1282 | 1285 | units = [u"s", u"ms",u'\xb5s',"ns"] |
|
1283 | 1286 | except: |
|
1284 | 1287 | pass |
|
1285 | 1288 | scaling = [1, 1e3, 1e6, 1e9] |
|
1286 | 1289 | |
|
1287 | 1290 | if timespan > 0.0: |
|
1288 | 1291 | order = min(-int(math.floor(math.log10(timespan)) // 3), 3) |
|
1289 | 1292 | else: |
|
1290 | 1293 | order = 3 |
|
1291 | 1294 | return u"%.*g %s" % (precision, timespan * scaling[order], units[order]) |
@@ -1,672 +1,676 | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | """Tests for the key interactiveshell module. |
|
3 | 3 | |
|
4 | 4 | Historically the main classes in interactiveshell have been under-tested. This |
|
5 | 5 | module should grow as many single-method tests as possible to trap many of the |
|
6 | 6 | recurring bugs we seem to encounter with high-level interaction. |
|
7 | 7 | |
|
8 | 8 | Authors |
|
9 | 9 | ------- |
|
10 | 10 | * Fernando Perez |
|
11 | 11 | """ |
|
12 | 12 | #----------------------------------------------------------------------------- |
|
13 | 13 | # Copyright (C) 2011 The IPython Development Team |
|
14 | 14 | # |
|
15 | 15 | # Distributed under the terms of the BSD License. The full license is in |
|
16 | 16 | # the file COPYING, distributed as part of this software. |
|
17 | 17 | #----------------------------------------------------------------------------- |
|
18 | 18 | |
|
19 | 19 | #----------------------------------------------------------------------------- |
|
20 | 20 | # Imports |
|
21 | 21 | #----------------------------------------------------------------------------- |
|
22 | 22 | # stdlib |
|
23 | 23 | import ast |
|
24 | 24 | import os |
|
25 | 25 | import signal |
|
26 | 26 | import shutil |
|
27 | 27 | import sys |
|
28 | 28 | import tempfile |
|
29 | 29 | import unittest |
|
30 | 30 | from os.path import join |
|
31 | from io import StringIO | |
|
32 | 31 | |
|
33 | 32 | # third-party |
|
34 | 33 | import nose.tools as nt |
|
35 | 34 | |
|
36 | 35 | # Our own |
|
37 | 36 | from IPython.testing.decorators import skipif, skip_win32, onlyif_unicode_paths |
|
38 | 37 | from IPython.testing import tools as tt |
|
39 | 38 | from IPython.utils import io |
|
40 | from IPython.utils.py3compat import unicode_type | |
|
39 | from IPython.utils.py3compat import unicode_type, PY3 | |
|
40 | ||
|
41 | if PY3: | |
|
42 | from io import StringIO | |
|
43 | else: | |
|
44 | from StringIO import StringIO | |
|
41 | 45 | |
|
42 | 46 | #----------------------------------------------------------------------------- |
|
43 | 47 | # Globals |
|
44 | 48 | #----------------------------------------------------------------------------- |
|
45 | 49 | # This is used by every single test, no point repeating it ad nauseam |
|
46 | 50 | ip = get_ipython() |
|
47 | 51 | |
|
48 | 52 | #----------------------------------------------------------------------------- |
|
49 | 53 | # Tests |
|
50 | 54 | #----------------------------------------------------------------------------- |
|
51 | 55 | |
|
52 | 56 | class InteractiveShellTestCase(unittest.TestCase): |
|
53 | 57 | def test_naked_string_cells(self): |
|
54 | 58 | """Test that cells with only naked strings are fully executed""" |
|
55 | 59 | # First, single-line inputs |
|
56 | 60 | ip.run_cell('"a"\n') |
|
57 | 61 | self.assertEqual(ip.user_ns['_'], 'a') |
|
58 | 62 | # And also multi-line cells |
|
59 | 63 | ip.run_cell('"""a\nb"""\n') |
|
60 | 64 | self.assertEqual(ip.user_ns['_'], 'a\nb') |
|
61 | 65 | |
|
62 | 66 | def test_run_empty_cell(self): |
|
63 | 67 | """Just make sure we don't get a horrible error with a blank |
|
64 | 68 | cell of input. Yes, I did overlook that.""" |
|
65 | 69 | old_xc = ip.execution_count |
|
66 | 70 | ip.run_cell('') |
|
67 | 71 | self.assertEqual(ip.execution_count, old_xc) |
|
68 | 72 | |
|
69 | 73 | def test_run_cell_multiline(self): |
|
70 | 74 | """Multi-block, multi-line cells must execute correctly. |
|
71 | 75 | """ |
|
72 | 76 | src = '\n'.join(["x=1", |
|
73 | 77 | "y=2", |
|
74 | 78 | "if 1:", |
|
75 | 79 | " x += 1", |
|
76 | 80 | " y += 1",]) |
|
77 | 81 | ip.run_cell(src) |
|
78 | 82 | self.assertEqual(ip.user_ns['x'], 2) |
|
79 | 83 | self.assertEqual(ip.user_ns['y'], 3) |
|
80 | 84 | |
|
81 | 85 | def test_multiline_string_cells(self): |
|
82 | 86 | "Code sprinkled with multiline strings should execute (GH-306)" |
|
83 | 87 | ip.run_cell('tmp=0') |
|
84 | 88 | self.assertEqual(ip.user_ns['tmp'], 0) |
|
85 | 89 | ip.run_cell('tmp=1;"""a\nb"""\n') |
|
86 | 90 | self.assertEqual(ip.user_ns['tmp'], 1) |
|
87 | 91 | |
|
88 | 92 | def test_dont_cache_with_semicolon(self): |
|
89 | 93 | "Ending a line with semicolon should not cache the returned object (GH-307)" |
|
90 | 94 | oldlen = len(ip.user_ns['Out']) |
|
91 | 95 | a = ip.run_cell('1;', store_history=True) |
|
92 | 96 | newlen = len(ip.user_ns['Out']) |
|
93 | 97 | self.assertEqual(oldlen, newlen) |
|
94 | 98 | #also test the default caching behavior |
|
95 | 99 | ip.run_cell('1', store_history=True) |
|
96 | 100 | newlen = len(ip.user_ns['Out']) |
|
97 | 101 | self.assertEqual(oldlen+1, newlen) |
|
98 | 102 | |
|
99 | 103 | def test_In_variable(self): |
|
100 | 104 | "Verify that In variable grows with user input (GH-284)" |
|
101 | 105 | oldlen = len(ip.user_ns['In']) |
|
102 | 106 | ip.run_cell('1;', store_history=True) |
|
103 | 107 | newlen = len(ip.user_ns['In']) |
|
104 | 108 | self.assertEqual(oldlen+1, newlen) |
|
105 | 109 | self.assertEqual(ip.user_ns['In'][-1],'1;') |
|
106 | 110 | |
|
107 | 111 | def test_magic_names_in_string(self): |
|
108 | 112 | ip.run_cell('a = """\n%exit\n"""') |
|
109 | 113 | self.assertEqual(ip.user_ns['a'], '\n%exit\n') |
|
110 | 114 | |
|
111 | 115 | def test_trailing_newline(self): |
|
112 | 116 | """test that running !(command) does not raise a SyntaxError""" |
|
113 | 117 | ip.run_cell('!(true)\n', False) |
|
114 | 118 | ip.run_cell('!(true)\n\n\n', False) |
|
115 | 119 | |
|
116 | 120 | def test_gh_597(self): |
|
117 | 121 | """Pretty-printing lists of objects with non-ascii reprs may cause |
|
118 | 122 | problems.""" |
|
119 | 123 | class Spam(object): |
|
120 | 124 | def __repr__(self): |
|
121 | 125 | return "\xe9"*50 |
|
122 | 126 | import IPython.core.formatters |
|
123 | 127 | f = IPython.core.formatters.PlainTextFormatter() |
|
124 | 128 | f([Spam(),Spam()]) |
|
125 | 129 | |
|
126 | 130 | |
|
127 | 131 | def test_future_flags(self): |
|
128 | 132 | """Check that future flags are used for parsing code (gh-777)""" |
|
129 | 133 | ip.run_cell('from __future__ import print_function') |
|
130 | 134 | try: |
|
131 | 135 | ip.run_cell('prfunc_return_val = print(1,2, sep=" ")') |
|
132 | 136 | assert 'prfunc_return_val' in ip.user_ns |
|
133 | 137 | finally: |
|
134 | 138 | # Reset compiler flags so we don't mess up other tests. |
|
135 | 139 | ip.compile.reset_compiler_flags() |
|
136 | 140 | |
|
137 | 141 | def test_future_unicode(self): |
|
138 | 142 | """Check that unicode_literals is imported from __future__ (gh #786)""" |
|
139 | 143 | try: |
|
140 | 144 | ip.run_cell(u'byte_str = "a"') |
|
141 | 145 | assert isinstance(ip.user_ns['byte_str'], str) # string literals are byte strings by default |
|
142 | 146 | ip.run_cell('from __future__ import unicode_literals') |
|
143 | 147 | ip.run_cell(u'unicode_str = "a"') |
|
144 | 148 | assert isinstance(ip.user_ns['unicode_str'], unicode_type) # strings literals are now unicode |
|
145 | 149 | finally: |
|
146 | 150 | # Reset compiler flags so we don't mess up other tests. |
|
147 | 151 | ip.compile.reset_compiler_flags() |
|
148 | 152 | |
|
149 | 153 | def test_can_pickle(self): |
|
150 | 154 | "Can we pickle objects defined interactively (GH-29)" |
|
151 | 155 | ip = get_ipython() |
|
152 | 156 | ip.reset() |
|
153 | 157 | ip.run_cell(("class Mylist(list):\n" |
|
154 | 158 | " def __init__(self,x=[]):\n" |
|
155 | 159 | " list.__init__(self,x)")) |
|
156 | 160 | ip.run_cell("w=Mylist([1,2,3])") |
|
157 | 161 | |
|
158 | 162 | from pickle import dumps |
|
159 | 163 | |
|
160 | 164 | # We need to swap in our main module - this is only necessary |
|
161 | 165 | # inside the test framework, because IPython puts the interactive module |
|
162 | 166 | # in place (but the test framework undoes this). |
|
163 | 167 | _main = sys.modules['__main__'] |
|
164 | 168 | sys.modules['__main__'] = ip.user_module |
|
165 | 169 | try: |
|
166 | 170 | res = dumps(ip.user_ns["w"]) |
|
167 | 171 | finally: |
|
168 | 172 | sys.modules['__main__'] = _main |
|
169 | 173 | self.assertTrue(isinstance(res, bytes)) |
|
170 | 174 | |
|
171 | 175 | def test_global_ns(self): |
|
172 | 176 | "Code in functions must be able to access variables outside them." |
|
173 | 177 | ip = get_ipython() |
|
174 | 178 | ip.run_cell("a = 10") |
|
175 | 179 | ip.run_cell(("def f(x):\n" |
|
176 | 180 | " return x + a")) |
|
177 | 181 | ip.run_cell("b = f(12)") |
|
178 | 182 | self.assertEqual(ip.user_ns["b"], 22) |
|
179 | 183 | |
|
180 | 184 | def test_bad_custom_tb(self): |
|
181 | 185 | """Check that InteractiveShell is protected from bad custom exception handlers""" |
|
182 | 186 | from IPython.utils import io |
|
183 | 187 | save_stderr = io.stderr |
|
184 | 188 | try: |
|
185 | 189 | # capture stderr |
|
186 | 190 | io.stderr = StringIO() |
|
187 | 191 | ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0) |
|
188 | 192 | self.assertEqual(ip.custom_exceptions, (IOError,)) |
|
189 | 193 | ip.run_cell(u'raise IOError("foo")') |
|
190 | 194 | self.assertEqual(ip.custom_exceptions, ()) |
|
191 | 195 | self.assertTrue("Custom TB Handler failed" in io.stderr.getvalue()) |
|
192 | 196 | finally: |
|
193 | 197 | io.stderr = save_stderr |
|
194 | 198 | |
|
195 | 199 | def test_bad_custom_tb_return(self): |
|
196 | 200 | """Check that InteractiveShell is protected from bad return types in custom exception handlers""" |
|
197 | 201 | from IPython.utils import io |
|
198 | 202 | save_stderr = io.stderr |
|
199 | 203 | try: |
|
200 | 204 | # capture stderr |
|
201 | 205 | io.stderr = StringIO() |
|
202 | 206 | ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1) |
|
203 | 207 | self.assertEqual(ip.custom_exceptions, (NameError,)) |
|
204 | 208 | ip.run_cell(u'a=abracadabra') |
|
205 | 209 | self.assertEqual(ip.custom_exceptions, ()) |
|
206 | 210 | self.assertTrue("Custom TB Handler failed" in io.stderr.getvalue()) |
|
207 | 211 | finally: |
|
208 | 212 | io.stderr = save_stderr |
|
209 | 213 | |
|
210 | 214 | def test_drop_by_id(self): |
|
211 | 215 | myvars = {"a":object(), "b":object(), "c": object()} |
|
212 | 216 | ip.push(myvars, interactive=False) |
|
213 | 217 | for name in myvars: |
|
214 | 218 | assert name in ip.user_ns, name |
|
215 | 219 | assert name in ip.user_ns_hidden, name |
|
216 | 220 | ip.user_ns['b'] = 12 |
|
217 | 221 | ip.drop_by_id(myvars) |
|
218 | 222 | for name in ["a", "c"]: |
|
219 | 223 | assert name not in ip.user_ns, name |
|
220 | 224 | assert name not in ip.user_ns_hidden, name |
|
221 | 225 | assert ip.user_ns['b'] == 12 |
|
222 | 226 | ip.reset() |
|
223 | 227 | |
|
224 | 228 | def test_var_expand(self): |
|
225 | 229 | ip.user_ns['f'] = u'Ca\xf1o' |
|
226 | 230 | self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o') |
|
227 | 231 | self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o') |
|
228 | 232 | self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1') |
|
229 | 233 | self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2') |
|
230 | 234 | |
|
231 | 235 | ip.user_ns['f'] = b'Ca\xc3\xb1o' |
|
232 | 236 | # This should not raise any exception: |
|
233 | 237 | ip.var_expand(u'echo $f') |
|
234 | 238 | |
|
235 | 239 | def test_var_expand_local(self): |
|
236 | 240 | """Test local variable expansion in !system and %magic calls""" |
|
237 | 241 | # !system |
|
238 | 242 | ip.run_cell('def test():\n' |
|
239 | 243 | ' lvar = "ttt"\n' |
|
240 | 244 | ' ret = !echo {lvar}\n' |
|
241 | 245 | ' return ret[0]\n') |
|
242 | 246 | res = ip.user_ns['test']() |
|
243 | 247 | nt.assert_in('ttt', res) |
|
244 | 248 | |
|
245 | 249 | # %magic |
|
246 | 250 | ip.run_cell('def makemacro():\n' |
|
247 | 251 | ' macroname = "macro_var_expand_locals"\n' |
|
248 | 252 | ' %macro {macroname} codestr\n') |
|
249 | 253 | ip.user_ns['codestr'] = "str(12)" |
|
250 | 254 | ip.run_cell('makemacro()') |
|
251 | 255 | nt.assert_in('macro_var_expand_locals', ip.user_ns) |
|
252 | 256 | |
|
253 | 257 | def test_var_expand_self(self): |
|
254 | 258 | """Test variable expansion with the name 'self', which was failing. |
|
255 | 259 | |
|
256 | 260 | See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218 |
|
257 | 261 | """ |
|
258 | 262 | ip.run_cell('class cTest:\n' |
|
259 | 263 | ' classvar="see me"\n' |
|
260 | 264 | ' def test(self):\n' |
|
261 | 265 | ' res = !echo Variable: {self.classvar}\n' |
|
262 | 266 | ' return res[0]\n') |
|
263 | 267 | nt.assert_in('see me', ip.user_ns['cTest']().test()) |
|
264 | 268 | |
|
265 | 269 | def test_bad_var_expand(self): |
|
266 | 270 | """var_expand on invalid formats shouldn't raise""" |
|
267 | 271 | # SyntaxError |
|
268 | 272 | self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}") |
|
269 | 273 | # NameError |
|
270 | 274 | self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}") |
|
271 | 275 | # ZeroDivisionError |
|
272 | 276 | self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}") |
|
273 | 277 | |
|
274 | 278 | def test_silent_nopostexec(self): |
|
275 | 279 | """run_cell(silent=True) doesn't invoke post-exec funcs""" |
|
276 | 280 | d = dict(called=False) |
|
277 | 281 | def set_called(): |
|
278 | 282 | d['called'] = True |
|
279 | 283 | |
|
280 | 284 | ip.register_post_execute(set_called) |
|
281 | 285 | ip.run_cell("1", silent=True) |
|
282 | 286 | self.assertFalse(d['called']) |
|
283 | 287 | # double-check that non-silent exec did what we expected |
|
284 | 288 | # silent to avoid |
|
285 | 289 | ip.run_cell("1") |
|
286 | 290 | self.assertTrue(d['called']) |
|
287 | 291 | # remove post-exec |
|
288 | 292 | ip._post_execute.pop(set_called) |
|
289 | 293 | |
|
290 | 294 | def test_silent_noadvance(self): |
|
291 | 295 | """run_cell(silent=True) doesn't advance execution_count""" |
|
292 | 296 | ec = ip.execution_count |
|
293 | 297 | # silent should force store_history=False |
|
294 | 298 | ip.run_cell("1", store_history=True, silent=True) |
|
295 | 299 | |
|
296 | 300 | self.assertEqual(ec, ip.execution_count) |
|
297 | 301 | # double-check that non-silent exec did what we expected |
|
298 | 302 | # silent to avoid |
|
299 | 303 | ip.run_cell("1", store_history=True) |
|
300 | 304 | self.assertEqual(ec+1, ip.execution_count) |
|
301 | 305 | |
|
302 | 306 | def test_silent_nodisplayhook(self): |
|
303 | 307 | """run_cell(silent=True) doesn't trigger displayhook""" |
|
304 | 308 | d = dict(called=False) |
|
305 | 309 | |
|
306 | 310 | trap = ip.display_trap |
|
307 | 311 | save_hook = trap.hook |
|
308 | 312 | |
|
309 | 313 | def failing_hook(*args, **kwargs): |
|
310 | 314 | d['called'] = True |
|
311 | 315 | |
|
312 | 316 | try: |
|
313 | 317 | trap.hook = failing_hook |
|
314 | 318 | ip.run_cell("1", silent=True) |
|
315 | 319 | self.assertFalse(d['called']) |
|
316 | 320 | # double-check that non-silent exec did what we expected |
|
317 | 321 | # silent to avoid |
|
318 | 322 | ip.run_cell("1") |
|
319 | 323 | self.assertTrue(d['called']) |
|
320 | 324 | finally: |
|
321 | 325 | trap.hook = save_hook |
|
322 | 326 | |
|
323 | 327 | @skipif(sys.version_info[0] >= 3, "softspace removed in py3") |
|
324 | 328 | def test_print_softspace(self): |
|
325 | 329 | """Verify that softspace is handled correctly when executing multiple |
|
326 | 330 | statements. |
|
327 | 331 | |
|
328 | 332 | In [1]: print 1; print 2 |
|
329 | 333 | 1 |
|
330 | 334 | 2 |
|
331 | 335 | |
|
332 | 336 | In [2]: print 1,; print 2 |
|
333 | 337 | 1 2 |
|
334 | 338 | """ |
|
335 | 339 | |
|
336 | 340 | def test_ofind_line_magic(self): |
|
337 | 341 | from IPython.core.magic import register_line_magic |
|
338 | 342 | |
|
339 | 343 | @register_line_magic |
|
340 | 344 | def lmagic(line): |
|
341 | 345 | "A line magic" |
|
342 | 346 | |
|
343 | 347 | # Get info on line magic |
|
344 | 348 | lfind = ip._ofind('lmagic') |
|
345 | 349 | info = dict(found=True, isalias=False, ismagic=True, |
|
346 | 350 | namespace = 'IPython internal', obj= lmagic.__wrapped__, |
|
347 | 351 | parent = None) |
|
348 | 352 | nt.assert_equal(lfind, info) |
|
349 | 353 | |
|
350 | 354 | def test_ofind_cell_magic(self): |
|
351 | 355 | from IPython.core.magic import register_cell_magic |
|
352 | 356 | |
|
353 | 357 | @register_cell_magic |
|
354 | 358 | def cmagic(line, cell): |
|
355 | 359 | "A cell magic" |
|
356 | 360 | |
|
357 | 361 | # Get info on cell magic |
|
358 | 362 | find = ip._ofind('cmagic') |
|
359 | 363 | info = dict(found=True, isalias=False, ismagic=True, |
|
360 | 364 | namespace = 'IPython internal', obj= cmagic.__wrapped__, |
|
361 | 365 | parent = None) |
|
362 | 366 | nt.assert_equal(find, info) |
|
363 | 367 | |
|
364 | 368 | def test_custom_exception(self): |
|
365 | 369 | called = [] |
|
366 | 370 | def my_handler(shell, etype, value, tb, tb_offset=None): |
|
367 | 371 | called.append(etype) |
|
368 | 372 | shell.showtraceback((etype, value, tb), tb_offset=tb_offset) |
|
369 | 373 | |
|
370 | 374 | ip.set_custom_exc((ValueError,), my_handler) |
|
371 | 375 | try: |
|
372 | 376 | ip.run_cell("raise ValueError('test')") |
|
373 | 377 | # Check that this was called, and only once. |
|
374 | 378 | self.assertEqual(called, [ValueError]) |
|
375 | 379 | finally: |
|
376 | 380 | # Reset the custom exception hook |
|
377 | 381 | ip.set_custom_exc((), None) |
|
378 | 382 | |
|
379 | 383 | @skipif(sys.version_info[0] >= 3, "no differences with __future__ in py3") |
|
380 | 384 | def test_future_environment(self): |
|
381 | 385 | "Can we run code with & without the shell's __future__ imports?" |
|
382 | 386 | ip.run_cell("from __future__ import division") |
|
383 | 387 | ip.run_cell("a = 1/2", shell_futures=True) |
|
384 | 388 | self.assertEqual(ip.user_ns['a'], 0.5) |
|
385 | 389 | ip.run_cell("b = 1/2", shell_futures=False) |
|
386 | 390 | self.assertEqual(ip.user_ns['b'], 0) |
|
387 | 391 | |
|
388 | 392 | ip.compile.reset_compiler_flags() |
|
389 | 393 | # This shouldn't leak to the shell's compiler |
|
390 | 394 | ip.run_cell("from __future__ import division \nc=1/2", shell_futures=False) |
|
391 | 395 | self.assertEqual(ip.user_ns['c'], 0.5) |
|
392 | 396 | ip.run_cell("d = 1/2", shell_futures=True) |
|
393 | 397 | self.assertEqual(ip.user_ns['d'], 0) |
|
394 | 398 | |
|
395 | 399 | |
|
396 | 400 | class TestSafeExecfileNonAsciiPath(unittest.TestCase): |
|
397 | 401 | |
|
398 | 402 | @onlyif_unicode_paths |
|
399 | 403 | def setUp(self): |
|
400 | 404 | self.BASETESTDIR = tempfile.mkdtemp() |
|
401 | 405 | self.TESTDIR = join(self.BASETESTDIR, u"åäö") |
|
402 | 406 | os.mkdir(self.TESTDIR) |
|
403 | 407 | with open(join(self.TESTDIR, u"åäötestscript.py"), "w") as sfile: |
|
404 | 408 | sfile.write("pass\n") |
|
405 | 409 | self.oldpath = os.getcwdu() |
|
406 | 410 | os.chdir(self.TESTDIR) |
|
407 | 411 | self.fname = u"åäötestscript.py" |
|
408 | 412 | |
|
409 | 413 | def tearDown(self): |
|
410 | 414 | os.chdir(self.oldpath) |
|
411 | 415 | shutil.rmtree(self.BASETESTDIR) |
|
412 | 416 | |
|
413 | 417 | @onlyif_unicode_paths |
|
414 | 418 | def test_1(self): |
|
415 | 419 | """Test safe_execfile with non-ascii path |
|
416 | 420 | """ |
|
417 | 421 | ip.safe_execfile(self.fname, {}, raise_exceptions=True) |
|
418 | 422 | |
|
419 | 423 | class ExitCodeChecks(tt.TempFileMixin): |
|
420 | 424 | def test_exit_code_ok(self): |
|
421 | 425 | self.system('exit 0') |
|
422 | 426 | self.assertEqual(ip.user_ns['_exit_code'], 0) |
|
423 | 427 | |
|
424 | 428 | def test_exit_code_error(self): |
|
425 | 429 | self.system('exit 1') |
|
426 | 430 | self.assertEqual(ip.user_ns['_exit_code'], 1) |
|
427 | 431 | |
|
428 | 432 | @skipif(not hasattr(signal, 'SIGALRM')) |
|
429 | 433 | def test_exit_code_signal(self): |
|
430 | 434 | self.mktmp("import signal, time\n" |
|
431 | 435 | "signal.setitimer(signal.ITIMER_REAL, 0.1)\n" |
|
432 | 436 | "time.sleep(1)\n") |
|
433 | 437 | self.system("%s %s" % (sys.executable, self.fname)) |
|
434 | 438 | self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM) |
|
435 | 439 | |
|
436 | 440 | class TestSystemRaw(unittest.TestCase, ExitCodeChecks): |
|
437 | 441 | system = ip.system_raw |
|
438 | 442 | |
|
439 | 443 | @onlyif_unicode_paths |
|
440 | 444 | def test_1(self): |
|
441 | 445 | """Test system_raw with non-ascii cmd |
|
442 | 446 | """ |
|
443 | 447 | cmd = u'''python -c "'åäö'" ''' |
|
444 | 448 | ip.system_raw(cmd) |
|
445 | 449 | |
|
446 | 450 | # TODO: Exit codes are currently ignored on Windows. |
|
447 | 451 | class TestSystemPipedExitCode(unittest.TestCase, ExitCodeChecks): |
|
448 | 452 | system = ip.system_piped |
|
449 | 453 | |
|
450 | 454 | @skip_win32 |
|
451 | 455 | def test_exit_code_ok(self): |
|
452 | 456 | ExitCodeChecks.test_exit_code_ok(self) |
|
453 | 457 | |
|
454 | 458 | @skip_win32 |
|
455 | 459 | def test_exit_code_error(self): |
|
456 | 460 | ExitCodeChecks.test_exit_code_error(self) |
|
457 | 461 | |
|
458 | 462 | @skip_win32 |
|
459 | 463 | def test_exit_code_signal(self): |
|
460 | 464 | ExitCodeChecks.test_exit_code_signal(self) |
|
461 | 465 | |
|
462 | 466 | class TestModules(unittest.TestCase, tt.TempFileMixin): |
|
463 | 467 | def test_extraneous_loads(self): |
|
464 | 468 | """Test we're not loading modules on startup that we shouldn't. |
|
465 | 469 | """ |
|
466 | 470 | self.mktmp("import sys\n" |
|
467 | 471 | "print('numpy' in sys.modules)\n" |
|
468 | 472 | "print('IPython.parallel' in sys.modules)\n" |
|
469 | 473 | "print('IPython.kernel.zmq' in sys.modules)\n" |
|
470 | 474 | ) |
|
471 | 475 | out = "False\nFalse\nFalse\n" |
|
472 | 476 | tt.ipexec_validate(self.fname, out) |
|
473 | 477 | |
|
474 | 478 | class Negator(ast.NodeTransformer): |
|
475 | 479 | """Negates all number literals in an AST.""" |
|
476 | 480 | def visit_Num(self, node): |
|
477 | 481 | node.n = -node.n |
|
478 | 482 | return node |
|
479 | 483 | |
|
480 | 484 | class TestAstTransform(unittest.TestCase): |
|
481 | 485 | def setUp(self): |
|
482 | 486 | self.negator = Negator() |
|
483 | 487 | ip.ast_transformers.append(self.negator) |
|
484 | 488 | |
|
485 | 489 | def tearDown(self): |
|
486 | 490 | ip.ast_transformers.remove(self.negator) |
|
487 | 491 | |
|
488 | 492 | def test_run_cell(self): |
|
489 | 493 | with tt.AssertPrints('-34'): |
|
490 | 494 | ip.run_cell('print (12 + 22)') |
|
491 | 495 | |
|
492 | 496 | # A named reference to a number shouldn't be transformed. |
|
493 | 497 | ip.user_ns['n'] = 55 |
|
494 | 498 | with tt.AssertNotPrints('-55'): |
|
495 | 499 | ip.run_cell('print (n)') |
|
496 | 500 | |
|
497 | 501 | def test_timeit(self): |
|
498 | 502 | called = set() |
|
499 | 503 | def f(x): |
|
500 | 504 | called.add(x) |
|
501 | 505 | ip.push({'f':f}) |
|
502 | 506 | |
|
503 | 507 | with tt.AssertPrints("best of "): |
|
504 | 508 | ip.run_line_magic("timeit", "-n1 f(1)") |
|
505 | 509 | self.assertEqual(called, set([-1])) |
|
506 | 510 | called.clear() |
|
507 | 511 | |
|
508 | 512 | with tt.AssertPrints("best of "): |
|
509 | 513 | ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)") |
|
510 | 514 | self.assertEqual(called, set([-2, -3])) |
|
511 | 515 | |
|
512 | 516 | def test_time(self): |
|
513 | 517 | called = [] |
|
514 | 518 | def f(x): |
|
515 | 519 | called.append(x) |
|
516 | 520 | ip.push({'f':f}) |
|
517 | 521 | |
|
518 | 522 | # Test with an expression |
|
519 | 523 | with tt.AssertPrints("Wall time: "): |
|
520 | 524 | ip.run_line_magic("time", "f(5+9)") |
|
521 | 525 | self.assertEqual(called, [-14]) |
|
522 | 526 | called[:] = [] |
|
523 | 527 | |
|
524 | 528 | # Test with a statement (different code path) |
|
525 | 529 | with tt.AssertPrints("Wall time: "): |
|
526 | 530 | ip.run_line_magic("time", "a = f(-3 + -2)") |
|
527 | 531 | self.assertEqual(called, [5]) |
|
528 | 532 | |
|
529 | 533 | def test_macro(self): |
|
530 | 534 | ip.push({'a':10}) |
|
531 | 535 | # The AST transformation makes this do a+=-1 |
|
532 | 536 | ip.define_macro("amacro", "a+=1\nprint(a)") |
|
533 | 537 | |
|
534 | 538 | with tt.AssertPrints("9"): |
|
535 | 539 | ip.run_cell("amacro") |
|
536 | 540 | with tt.AssertPrints("8"): |
|
537 | 541 | ip.run_cell("amacro") |
|
538 | 542 | |
|
539 | 543 | class IntegerWrapper(ast.NodeTransformer): |
|
540 | 544 | """Wraps all integers in a call to Integer()""" |
|
541 | 545 | def visit_Num(self, node): |
|
542 | 546 | if isinstance(node.n, int): |
|
543 | 547 | return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()), |
|
544 | 548 | args=[node], keywords=[]) |
|
545 | 549 | return node |
|
546 | 550 | |
|
547 | 551 | class TestAstTransform2(unittest.TestCase): |
|
548 | 552 | def setUp(self): |
|
549 | 553 | self.intwrapper = IntegerWrapper() |
|
550 | 554 | ip.ast_transformers.append(self.intwrapper) |
|
551 | 555 | |
|
552 | 556 | self.calls = [] |
|
553 | 557 | def Integer(*args): |
|
554 | 558 | self.calls.append(args) |
|
555 | 559 | return args |
|
556 | 560 | ip.push({"Integer": Integer}) |
|
557 | 561 | |
|
558 | 562 | def tearDown(self): |
|
559 | 563 | ip.ast_transformers.remove(self.intwrapper) |
|
560 | 564 | del ip.user_ns['Integer'] |
|
561 | 565 | |
|
562 | 566 | def test_run_cell(self): |
|
563 | 567 | ip.run_cell("n = 2") |
|
564 | 568 | self.assertEqual(self.calls, [(2,)]) |
|
565 | 569 | |
|
566 | 570 | # This shouldn't throw an error |
|
567 | 571 | ip.run_cell("o = 2.0") |
|
568 | 572 | self.assertEqual(ip.user_ns['o'], 2.0) |
|
569 | 573 | |
|
570 | 574 | def test_timeit(self): |
|
571 | 575 | called = set() |
|
572 | 576 | def f(x): |
|
573 | 577 | called.add(x) |
|
574 | 578 | ip.push({'f':f}) |
|
575 | 579 | |
|
576 | 580 | with tt.AssertPrints("best of "): |
|
577 | 581 | ip.run_line_magic("timeit", "-n1 f(1)") |
|
578 | 582 | self.assertEqual(called, set([(1,)])) |
|
579 | 583 | called.clear() |
|
580 | 584 | |
|
581 | 585 | with tt.AssertPrints("best of "): |
|
582 | 586 | ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)") |
|
583 | 587 | self.assertEqual(called, set([(2,), (3,)])) |
|
584 | 588 | |
|
585 | 589 | class ErrorTransformer(ast.NodeTransformer): |
|
586 | 590 | """Throws an error when it sees a number.""" |
|
587 | 591 | def visit_Num(self): |
|
588 | 592 | raise ValueError("test") |
|
589 | 593 | |
|
590 | 594 | class TestAstTransformError(unittest.TestCase): |
|
591 | 595 | def test_unregistering(self): |
|
592 | 596 | err_transformer = ErrorTransformer() |
|
593 | 597 | ip.ast_transformers.append(err_transformer) |
|
594 | 598 | |
|
595 | 599 | with tt.AssertPrints("unregister", channel='stderr'): |
|
596 | 600 | ip.run_cell("1 + 2") |
|
597 | 601 | |
|
598 | 602 | # This should have been removed. |
|
599 | 603 | nt.assert_not_in(err_transformer, ip.ast_transformers) |
|
600 | 604 | |
|
601 | 605 | def test__IPYTHON__(): |
|
602 | 606 | # This shouldn't raise a NameError, that's all |
|
603 | 607 | __IPYTHON__ |
|
604 | 608 | |
|
605 | 609 | |
|
606 | 610 | class DummyRepr(object): |
|
607 | 611 | def __repr__(self): |
|
608 | 612 | return "DummyRepr" |
|
609 | 613 | |
|
610 | 614 | def _repr_html_(self): |
|
611 | 615 | return "<b>dummy</b>" |
|
612 | 616 | |
|
613 | 617 | def _repr_javascript_(self): |
|
614 | 618 | return "console.log('hi');", {'key': 'value'} |
|
615 | 619 | |
|
616 | 620 | |
|
617 | 621 | def test_user_variables(): |
|
618 | 622 | # enable all formatters |
|
619 | 623 | ip.display_formatter.active_types = ip.display_formatter.format_types |
|
620 | 624 | |
|
621 | 625 | ip.user_ns['dummy'] = d = DummyRepr() |
|
622 | 626 | keys = set(['dummy', 'doesnotexist']) |
|
623 | 627 | r = ip.user_variables(keys) |
|
624 | 628 | |
|
625 | 629 | nt.assert_equal(keys, set(r.keys())) |
|
626 | 630 | dummy = r['dummy'] |
|
627 | 631 | nt.assert_equal(set(['status', 'data', 'metadata']), set(dummy.keys())) |
|
628 | 632 | nt.assert_equal(dummy['status'], 'ok') |
|
629 | 633 | data = dummy['data'] |
|
630 | 634 | metadata = dummy['metadata'] |
|
631 | 635 | nt.assert_equal(data.get('text/html'), d._repr_html_()) |
|
632 | 636 | js, jsmd = d._repr_javascript_() |
|
633 | 637 | nt.assert_equal(data.get('application/javascript'), js) |
|
634 | 638 | nt.assert_equal(metadata.get('application/javascript'), jsmd) |
|
635 | 639 | |
|
636 | 640 | dne = r['doesnotexist'] |
|
637 | 641 | nt.assert_equal(dne['status'], 'error') |
|
638 | 642 | nt.assert_equal(dne['ename'], 'KeyError') |
|
639 | 643 | |
|
640 | 644 | # back to text only |
|
641 | 645 | ip.display_formatter.active_types = ['text/plain'] |
|
642 | 646 | |
|
643 | 647 | def test_user_expression(): |
|
644 | 648 | # enable all formatters |
|
645 | 649 | ip.display_formatter.active_types = ip.display_formatter.format_types |
|
646 | 650 | query = { |
|
647 | 651 | 'a' : '1 + 2', |
|
648 | 652 | 'b' : '1/0', |
|
649 | 653 | } |
|
650 | 654 | r = ip.user_expressions(query) |
|
651 | 655 | import pprint |
|
652 | 656 | pprint.pprint(r) |
|
653 | 657 | nt.assert_equal(r.keys(), query.keys()) |
|
654 | 658 | a = r['a'] |
|
655 | 659 | nt.assert_equal(set(['status', 'data', 'metadata']), set(a.keys())) |
|
656 | 660 | nt.assert_equal(a['status'], 'ok') |
|
657 | 661 | data = a['data'] |
|
658 | 662 | metadata = a['metadata'] |
|
659 | 663 | nt.assert_equal(data.get('text/plain'), '3') |
|
660 | 664 | |
|
661 | 665 | b = r['b'] |
|
662 | 666 | nt.assert_equal(b['status'], 'error') |
|
663 | 667 | nt.assert_equal(b['ename'], 'ZeroDivisionError') |
|
664 | 668 | |
|
665 | 669 | # back to text only |
|
666 | 670 | ip.display_formatter.active_types = ['text/plain'] |
|
667 | 671 | |
|
668 | 672 | |
|
669 | 673 | |
|
670 | 674 | |
|
671 | 675 | |
|
672 | 676 |
@@ -1,940 +1,944 | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | """Tests for various magic functions. |
|
3 | 3 | |
|
4 | 4 | Needs to be run by nose (to make ipython session available). |
|
5 | 5 | """ |
|
6 | 6 | from __future__ import absolute_import |
|
7 | 7 | |
|
8 | 8 | #----------------------------------------------------------------------------- |
|
9 | 9 | # Imports |
|
10 | 10 | #----------------------------------------------------------------------------- |
|
11 | 11 | |
|
12 | 12 | import io |
|
13 | 13 | import os |
|
14 | 14 | import sys |
|
15 | from io import StringIO | |
|
16 | 15 | from unittest import TestCase |
|
17 | 16 | |
|
18 | 17 | try: |
|
19 | 18 | from importlib import invalidate_caches # Required from Python 3.3 |
|
20 | 19 | except ImportError: |
|
21 | 20 | def invalidate_caches(): |
|
22 | 21 | pass |
|
23 | 22 | |
|
24 | 23 | import nose.tools as nt |
|
25 | 24 | |
|
26 | 25 | from IPython.core import magic |
|
27 | 26 | from IPython.core.magic import (Magics, magics_class, line_magic, |
|
28 | 27 | cell_magic, line_cell_magic, |
|
29 | 28 | register_line_magic, register_cell_magic, |
|
30 | 29 | register_line_cell_magic) |
|
31 | 30 | from IPython.core.magics import execution, script, code |
|
32 | 31 | from IPython.nbformat.v3.tests.nbexamples import nb0 |
|
33 | 32 | from IPython.nbformat import current |
|
34 | 33 | from IPython.testing import decorators as dec |
|
35 | 34 | from IPython.testing import tools as tt |
|
36 | 35 | from IPython.utils import py3compat |
|
37 | 36 | from IPython.utils.io import capture_output |
|
38 | 37 | from IPython.utils.tempdir import TemporaryDirectory |
|
39 | 38 | from IPython.utils.process import find_cmd |
|
40 | 39 | |
|
40 | if py3compat.PY3: | |
|
41 | from io import StringIO | |
|
42 | else: | |
|
43 | from StringIO import StringIO | |
|
44 | ||
|
41 | 45 | #----------------------------------------------------------------------------- |
|
42 | 46 | # Test functions begin |
|
43 | 47 | #----------------------------------------------------------------------------- |
|
44 | 48 | |
|
45 | 49 | @magic.magics_class |
|
46 | 50 | class DummyMagics(magic.Magics): pass |
|
47 | 51 | |
|
48 | 52 | def test_extract_code_ranges(): |
|
49 | 53 | instr = "1 3 5-6 7-9 10:15 17: :10 10- -13 :" |
|
50 | 54 | expected = [(0, 1), |
|
51 | 55 | (2, 3), |
|
52 | 56 | (4, 6), |
|
53 | 57 | (6, 9), |
|
54 | 58 | (9, 14), |
|
55 | 59 | (16, None), |
|
56 | 60 | (None, 9), |
|
57 | 61 | (9, None), |
|
58 | 62 | (None, 13), |
|
59 | 63 | (None, None)] |
|
60 | 64 | actual = list(code.extract_code_ranges(instr)) |
|
61 | 65 | nt.assert_equal(actual, expected) |
|
62 | 66 | |
|
63 | 67 | def test_extract_symbols(): |
|
64 | 68 | source = """import foo\na = 10\ndef b():\n return 42\n\n\nclass A: pass\n\n\n""" |
|
65 | 69 | symbols_args = ["a", "b", "A", "A,b", "A,a", "z"] |
|
66 | 70 | expected = [([], ['a']), |
|
67 | 71 | (["def b():\n return 42\n"], []), |
|
68 | 72 | (["class A: pass\n"], []), |
|
69 | 73 | (["class A: pass\n", "def b():\n return 42\n"], []), |
|
70 | 74 | (["class A: pass\n"], ['a']), |
|
71 | 75 | ([], ['z'])] |
|
72 | 76 | for symbols, exp in zip(symbols_args, expected): |
|
73 | 77 | nt.assert_equal(code.extract_symbols(source, symbols), exp) |
|
74 | 78 | |
|
75 | 79 | |
|
76 | 80 | def test_extract_symbols_raises_exception_with_non_python_code(): |
|
77 | 81 | source = ("=begin A Ruby program :)=end\n" |
|
78 | 82 | "def hello\n" |
|
79 | 83 | "puts 'Hello world'\n" |
|
80 | 84 | "end") |
|
81 | 85 | with nt.assert_raises(SyntaxError): |
|
82 | 86 | code.extract_symbols(source, "hello") |
|
83 | 87 | |
|
84 | 88 | def test_config(): |
|
85 | 89 | """ test that config magic does not raise |
|
86 | 90 | can happen if Configurable init is moved too early into |
|
87 | 91 | Magics.__init__ as then a Config object will be registerd as a |
|
88 | 92 | magic. |
|
89 | 93 | """ |
|
90 | 94 | ## should not raise. |
|
91 | 95 | _ip.magic('config') |
|
92 | 96 | |
|
93 | 97 | def test_rehashx(): |
|
94 | 98 | # clear up everything |
|
95 | 99 | _ip = get_ipython() |
|
96 | 100 | _ip.alias_manager.clear_aliases() |
|
97 | 101 | del _ip.db['syscmdlist'] |
|
98 | 102 | |
|
99 | 103 | _ip.magic('rehashx') |
|
100 | 104 | # Practically ALL ipython development systems will have more than 10 aliases |
|
101 | 105 | |
|
102 | 106 | nt.assert_true(len(_ip.alias_manager.aliases) > 10) |
|
103 | 107 | for name, cmd in _ip.alias_manager.aliases: |
|
104 | 108 | # we must strip dots from alias names |
|
105 | 109 | nt.assert_not_in('.', name) |
|
106 | 110 | |
|
107 | 111 | # rehashx must fill up syscmdlist |
|
108 | 112 | scoms = _ip.db['syscmdlist'] |
|
109 | 113 | nt.assert_true(len(scoms) > 10) |
|
110 | 114 | |
|
111 | 115 | |
|
112 | 116 | def test_magic_parse_options(): |
|
113 | 117 | """Test that we don't mangle paths when parsing magic options.""" |
|
114 | 118 | ip = get_ipython() |
|
115 | 119 | path = 'c:\\x' |
|
116 | 120 | m = DummyMagics(ip) |
|
117 | 121 | opts = m.parse_options('-f %s' % path,'f:')[0] |
|
118 | 122 | # argv splitting is os-dependent |
|
119 | 123 | if os.name == 'posix': |
|
120 | 124 | expected = 'c:x' |
|
121 | 125 | else: |
|
122 | 126 | expected = path |
|
123 | 127 | nt.assert_equal(opts['f'], expected) |
|
124 | 128 | |
|
125 | 129 | def test_magic_parse_long_options(): |
|
126 | 130 | """Magic.parse_options can handle --foo=bar long options""" |
|
127 | 131 | ip = get_ipython() |
|
128 | 132 | m = DummyMagics(ip) |
|
129 | 133 | opts, _ = m.parse_options('--foo --bar=bubble', 'a', 'foo', 'bar=') |
|
130 | 134 | nt.assert_in('foo', opts) |
|
131 | 135 | nt.assert_in('bar', opts) |
|
132 | 136 | nt.assert_equal(opts['bar'], "bubble") |
|
133 | 137 | |
|
134 | 138 | |
|
135 | 139 | @dec.skip_without('sqlite3') |
|
136 | 140 | def doctest_hist_f(): |
|
137 | 141 | """Test %hist -f with temporary filename. |
|
138 | 142 | |
|
139 | 143 | In [9]: import tempfile |
|
140 | 144 | |
|
141 | 145 | In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-') |
|
142 | 146 | |
|
143 | 147 | In [11]: %hist -nl -f $tfile 3 |
|
144 | 148 | |
|
145 | 149 | In [13]: import os; os.unlink(tfile) |
|
146 | 150 | """ |
|
147 | 151 | |
|
148 | 152 | |
|
149 | 153 | @dec.skip_without('sqlite3') |
|
150 | 154 | def doctest_hist_r(): |
|
151 | 155 | """Test %hist -r |
|
152 | 156 | |
|
153 | 157 | XXX - This test is not recording the output correctly. For some reason, in |
|
154 | 158 | testing mode the raw history isn't getting populated. No idea why. |
|
155 | 159 | Disabling the output checking for now, though at least we do run it. |
|
156 | 160 | |
|
157 | 161 | In [1]: 'hist' in _ip.lsmagic() |
|
158 | 162 | Out[1]: True |
|
159 | 163 | |
|
160 | 164 | In [2]: x=1 |
|
161 | 165 | |
|
162 | 166 | In [3]: %hist -rl 2 |
|
163 | 167 | x=1 # random |
|
164 | 168 | %hist -r 2 |
|
165 | 169 | """ |
|
166 | 170 | |
|
167 | 171 | |
|
168 | 172 | @dec.skip_without('sqlite3') |
|
169 | 173 | def doctest_hist_op(): |
|
170 | 174 | """Test %hist -op |
|
171 | 175 | |
|
172 | 176 | In [1]: class b(float): |
|
173 | 177 | ...: pass |
|
174 | 178 | ...: |
|
175 | 179 | |
|
176 | 180 | In [2]: class s(object): |
|
177 | 181 | ...: def __str__(self): |
|
178 | 182 | ...: return 's' |
|
179 | 183 | ...: |
|
180 | 184 | |
|
181 | 185 | In [3]: |
|
182 | 186 | |
|
183 | 187 | In [4]: class r(b): |
|
184 | 188 | ...: def __repr__(self): |
|
185 | 189 | ...: return 'r' |
|
186 | 190 | ...: |
|
187 | 191 | |
|
188 | 192 | In [5]: class sr(s,r): pass |
|
189 | 193 | ...: |
|
190 | 194 | |
|
191 | 195 | In [6]: |
|
192 | 196 | |
|
193 | 197 | In [7]: bb=b() |
|
194 | 198 | |
|
195 | 199 | In [8]: ss=s() |
|
196 | 200 | |
|
197 | 201 | In [9]: rr=r() |
|
198 | 202 | |
|
199 | 203 | In [10]: ssrr=sr() |
|
200 | 204 | |
|
201 | 205 | In [11]: 4.5 |
|
202 | 206 | Out[11]: 4.5 |
|
203 | 207 | |
|
204 | 208 | In [12]: str(ss) |
|
205 | 209 | Out[12]: 's' |
|
206 | 210 | |
|
207 | 211 | In [13]: |
|
208 | 212 | |
|
209 | 213 | In [14]: %hist -op |
|
210 | 214 | >>> class b: |
|
211 | 215 | ... pass |
|
212 | 216 | ... |
|
213 | 217 | >>> class s(b): |
|
214 | 218 | ... def __str__(self): |
|
215 | 219 | ... return 's' |
|
216 | 220 | ... |
|
217 | 221 | >>> |
|
218 | 222 | >>> class r(b): |
|
219 | 223 | ... def __repr__(self): |
|
220 | 224 | ... return 'r' |
|
221 | 225 | ... |
|
222 | 226 | >>> class sr(s,r): pass |
|
223 | 227 | >>> |
|
224 | 228 | >>> bb=b() |
|
225 | 229 | >>> ss=s() |
|
226 | 230 | >>> rr=r() |
|
227 | 231 | >>> ssrr=sr() |
|
228 | 232 | >>> 4.5 |
|
229 | 233 | 4.5 |
|
230 | 234 | >>> str(ss) |
|
231 | 235 | 's' |
|
232 | 236 | >>> |
|
233 | 237 | """ |
|
234 | 238 | |
|
235 | 239 | |
|
236 | 240 | @dec.skip_without('sqlite3') |
|
237 | 241 | def test_macro(): |
|
238 | 242 | ip = get_ipython() |
|
239 | 243 | ip.history_manager.reset() # Clear any existing history. |
|
240 | 244 | cmds = ["a=1", "def b():\n return a**2", "print(a,b())"] |
|
241 | 245 | for i, cmd in enumerate(cmds, start=1): |
|
242 | 246 | ip.history_manager.store_inputs(i, cmd) |
|
243 | 247 | ip.magic("macro test 1-3") |
|
244 | 248 | nt.assert_equal(ip.user_ns["test"].value, "\n".join(cmds)+"\n") |
|
245 | 249 | |
|
246 | 250 | # List macros |
|
247 | 251 | nt.assert_in("test", ip.magic("macro")) |
|
248 | 252 | |
|
249 | 253 | |
|
250 | 254 | @dec.skip_without('sqlite3') |
|
251 | 255 | def test_macro_run(): |
|
252 | 256 | """Test that we can run a multi-line macro successfully.""" |
|
253 | 257 | ip = get_ipython() |
|
254 | 258 | ip.history_manager.reset() |
|
255 | 259 | cmds = ["a=10", "a+=1", py3compat.doctest_refactor_print("print a"), |
|
256 | 260 | "%macro test 2-3"] |
|
257 | 261 | for cmd in cmds: |
|
258 | 262 | ip.run_cell(cmd, store_history=True) |
|
259 | 263 | nt.assert_equal(ip.user_ns["test"].value, |
|
260 | 264 | py3compat.doctest_refactor_print("a+=1\nprint a\n")) |
|
261 | 265 | with tt.AssertPrints("12"): |
|
262 | 266 | ip.run_cell("test") |
|
263 | 267 | with tt.AssertPrints("13"): |
|
264 | 268 | ip.run_cell("test") |
|
265 | 269 | |
|
266 | 270 | |
|
267 | 271 | def test_magic_magic(): |
|
268 | 272 | """Test %magic""" |
|
269 | 273 | ip = get_ipython() |
|
270 | 274 | with capture_output() as captured: |
|
271 | 275 | ip.magic("magic") |
|
272 | 276 | |
|
273 | 277 | stdout = captured.stdout |
|
274 | 278 | nt.assert_in('%magic', stdout) |
|
275 | 279 | nt.assert_in('IPython', stdout) |
|
276 | 280 | nt.assert_in('Available', stdout) |
|
277 | 281 | |
|
278 | 282 | |
|
279 | 283 | @dec.skipif_not_numpy |
|
280 | 284 | def test_numpy_reset_array_undec(): |
|
281 | 285 | "Test '%reset array' functionality" |
|
282 | 286 | _ip.ex('import numpy as np') |
|
283 | 287 | _ip.ex('a = np.empty(2)') |
|
284 | 288 | nt.assert_in('a', _ip.user_ns) |
|
285 | 289 | _ip.magic('reset -f array') |
|
286 | 290 | nt.assert_not_in('a', _ip.user_ns) |
|
287 | 291 | |
|
288 | 292 | def test_reset_out(): |
|
289 | 293 | "Test '%reset out' magic" |
|
290 | 294 | _ip.run_cell("parrot = 'dead'", store_history=True) |
|
291 | 295 | # test '%reset -f out', make an Out prompt |
|
292 | 296 | _ip.run_cell("parrot", store_history=True) |
|
293 | 297 | nt.assert_true('dead' in [_ip.user_ns[x] for x in '_','__','___']) |
|
294 | 298 | _ip.magic('reset -f out') |
|
295 | 299 | nt.assert_false('dead' in [_ip.user_ns[x] for x in '_','__','___']) |
|
296 | 300 | nt.assert_equal(len(_ip.user_ns['Out']), 0) |
|
297 | 301 | |
|
298 | 302 | def test_reset_in(): |
|
299 | 303 | "Test '%reset in' magic" |
|
300 | 304 | # test '%reset -f in' |
|
301 | 305 | _ip.run_cell("parrot", store_history=True) |
|
302 | 306 | nt.assert_true('parrot' in [_ip.user_ns[x] for x in '_i','_ii','_iii']) |
|
303 | 307 | _ip.magic('%reset -f in') |
|
304 | 308 | nt.assert_false('parrot' in [_ip.user_ns[x] for x in '_i','_ii','_iii']) |
|
305 | 309 | nt.assert_equal(len(set(_ip.user_ns['In'])), 1) |
|
306 | 310 | |
|
307 | 311 | def test_reset_dhist(): |
|
308 | 312 | "Test '%reset dhist' magic" |
|
309 | 313 | _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing |
|
310 | 314 | _ip.magic('cd ' + os.path.dirname(nt.__file__)) |
|
311 | 315 | _ip.magic('cd -') |
|
312 | 316 | nt.assert_true(len(_ip.user_ns['_dh']) > 0) |
|
313 | 317 | _ip.magic('reset -f dhist') |
|
314 | 318 | nt.assert_equal(len(_ip.user_ns['_dh']), 0) |
|
315 | 319 | _ip.run_cell("_dh = [d for d in tmp]") #restore |
|
316 | 320 | |
|
317 | 321 | def test_reset_in_length(): |
|
318 | 322 | "Test that '%reset in' preserves In[] length" |
|
319 | 323 | _ip.run_cell("print 'foo'") |
|
320 | 324 | _ip.run_cell("reset -f in") |
|
321 | 325 | nt.assert_equal(len(_ip.user_ns['In']), _ip.displayhook.prompt_count+1) |
|
322 | 326 | |
|
323 | 327 | def test_tb_syntaxerror(): |
|
324 | 328 | """test %tb after a SyntaxError""" |
|
325 | 329 | ip = get_ipython() |
|
326 | 330 | ip.run_cell("for") |
|
327 | 331 | |
|
328 | 332 | # trap and validate stdout |
|
329 | 333 | save_stdout = sys.stdout |
|
330 | 334 | try: |
|
331 | 335 | sys.stdout = StringIO() |
|
332 | 336 | ip.run_cell("%tb") |
|
333 | 337 | out = sys.stdout.getvalue() |
|
334 | 338 | finally: |
|
335 | 339 | sys.stdout = save_stdout |
|
336 | 340 | # trim output, and only check the last line |
|
337 | 341 | last_line = out.rstrip().splitlines()[-1].strip() |
|
338 | 342 | nt.assert_equal(last_line, "SyntaxError: invalid syntax") |
|
339 | 343 | |
|
340 | 344 | |
|
341 | 345 | def test_time(): |
|
342 | 346 | ip = get_ipython() |
|
343 | 347 | |
|
344 | 348 | with tt.AssertPrints("Wall time: "): |
|
345 | 349 | ip.run_cell("%time None") |
|
346 | 350 | |
|
347 | 351 | ip.run_cell("def f(kmjy):\n" |
|
348 | 352 | " %time print (2*kmjy)") |
|
349 | 353 | |
|
350 | 354 | with tt.AssertPrints("Wall time: "): |
|
351 | 355 | with tt.AssertPrints("hihi", suppress=False): |
|
352 | 356 | ip.run_cell("f('hi')") |
|
353 | 357 | |
|
354 | 358 | |
|
355 | 359 | @dec.skip_win32 |
|
356 | 360 | def test_time2(): |
|
357 | 361 | ip = get_ipython() |
|
358 | 362 | |
|
359 | 363 | with tt.AssertPrints("CPU times: user "): |
|
360 | 364 | ip.run_cell("%time None") |
|
361 | 365 | |
|
362 | 366 | def test_time3(): |
|
363 | 367 | """Erroneous magic function calls, issue gh-3334""" |
|
364 | 368 | ip = get_ipython() |
|
365 | 369 | ip.user_ns.pop('run', None) |
|
366 | 370 | |
|
367 | 371 | with tt.AssertNotPrints("not found", channel='stderr'): |
|
368 | 372 | ip.run_cell("%%time\n" |
|
369 | 373 | "run = 0\n" |
|
370 | 374 | "run += 1") |
|
371 | 375 | |
|
372 | 376 | def test_doctest_mode(): |
|
373 | 377 | "Toggle doctest_mode twice, it should be a no-op and run without error" |
|
374 | 378 | _ip.magic('doctest_mode') |
|
375 | 379 | _ip.magic('doctest_mode') |
|
376 | 380 | |
|
377 | 381 | |
|
378 | 382 | def test_parse_options(): |
|
379 | 383 | """Tests for basic options parsing in magics.""" |
|
380 | 384 | # These are only the most minimal of tests, more should be added later. At |
|
381 | 385 | # the very least we check that basic text/unicode calls work OK. |
|
382 | 386 | m = DummyMagics(_ip) |
|
383 | 387 | nt.assert_equal(m.parse_options('foo', '')[1], 'foo') |
|
384 | 388 | nt.assert_equal(m.parse_options(u'foo', '')[1], u'foo') |
|
385 | 389 | |
|
386 | 390 | |
|
387 | 391 | def test_dirops(): |
|
388 | 392 | """Test various directory handling operations.""" |
|
389 | 393 | # curpath = lambda :os.path.splitdrive(os.getcwdu())[1].replace('\\','/') |
|
390 | 394 | curpath = os.getcwdu |
|
391 | 395 | startdir = os.getcwdu() |
|
392 | 396 | ipdir = os.path.realpath(_ip.ipython_dir) |
|
393 | 397 | try: |
|
394 | 398 | _ip.magic('cd "%s"' % ipdir) |
|
395 | 399 | nt.assert_equal(curpath(), ipdir) |
|
396 | 400 | _ip.magic('cd -') |
|
397 | 401 | nt.assert_equal(curpath(), startdir) |
|
398 | 402 | _ip.magic('pushd "%s"' % ipdir) |
|
399 | 403 | nt.assert_equal(curpath(), ipdir) |
|
400 | 404 | _ip.magic('popd') |
|
401 | 405 | nt.assert_equal(curpath(), startdir) |
|
402 | 406 | finally: |
|
403 | 407 | os.chdir(startdir) |
|
404 | 408 | |
|
405 | 409 | |
|
406 | 410 | def test_xmode(): |
|
407 | 411 | # Calling xmode three times should be a no-op |
|
408 | 412 | xmode = _ip.InteractiveTB.mode |
|
409 | 413 | for i in range(3): |
|
410 | 414 | _ip.magic("xmode") |
|
411 | 415 | nt.assert_equal(_ip.InteractiveTB.mode, xmode) |
|
412 | 416 | |
|
413 | 417 | def test_reset_hard(): |
|
414 | 418 | monitor = [] |
|
415 | 419 | class A(object): |
|
416 | 420 | def __del__(self): |
|
417 | 421 | monitor.append(1) |
|
418 | 422 | def __repr__(self): |
|
419 | 423 | return "<A instance>" |
|
420 | 424 | |
|
421 | 425 | _ip.user_ns["a"] = A() |
|
422 | 426 | _ip.run_cell("a") |
|
423 | 427 | |
|
424 | 428 | nt.assert_equal(monitor, []) |
|
425 | 429 | _ip.magic("reset -f") |
|
426 | 430 | nt.assert_equal(monitor, [1]) |
|
427 | 431 | |
|
428 | 432 | class TestXdel(tt.TempFileMixin): |
|
429 | 433 | def test_xdel(self): |
|
430 | 434 | """Test that references from %run are cleared by xdel.""" |
|
431 | 435 | src = ("class A(object):\n" |
|
432 | 436 | " monitor = []\n" |
|
433 | 437 | " def __del__(self):\n" |
|
434 | 438 | " self.monitor.append(1)\n" |
|
435 | 439 | "a = A()\n") |
|
436 | 440 | self.mktmp(src) |
|
437 | 441 | # %run creates some hidden references... |
|
438 | 442 | _ip.magic("run %s" % self.fname) |
|
439 | 443 | # ... as does the displayhook. |
|
440 | 444 | _ip.run_cell("a") |
|
441 | 445 | |
|
442 | 446 | monitor = _ip.user_ns["A"].monitor |
|
443 | 447 | nt.assert_equal(monitor, []) |
|
444 | 448 | |
|
445 | 449 | _ip.magic("xdel a") |
|
446 | 450 | |
|
447 | 451 | # Check that a's __del__ method has been called. |
|
448 | 452 | nt.assert_equal(monitor, [1]) |
|
449 | 453 | |
|
450 | 454 | def doctest_who(): |
|
451 | 455 | """doctest for %who |
|
452 | 456 | |
|
453 | 457 | In [1]: %reset -f |
|
454 | 458 | |
|
455 | 459 | In [2]: alpha = 123 |
|
456 | 460 | |
|
457 | 461 | In [3]: beta = 'beta' |
|
458 | 462 | |
|
459 | 463 | In [4]: %who int |
|
460 | 464 | alpha |
|
461 | 465 | |
|
462 | 466 | In [5]: %who str |
|
463 | 467 | beta |
|
464 | 468 | |
|
465 | 469 | In [6]: %whos |
|
466 | 470 | Variable Type Data/Info |
|
467 | 471 | ---------------------------- |
|
468 | 472 | alpha int 123 |
|
469 | 473 | beta str beta |
|
470 | 474 | |
|
471 | 475 | In [7]: %who_ls |
|
472 | 476 | Out[7]: ['alpha', 'beta'] |
|
473 | 477 | """ |
|
474 | 478 | |
|
475 | 479 | def test_whos(): |
|
476 | 480 | """Check that whos is protected against objects where repr() fails.""" |
|
477 | 481 | class A(object): |
|
478 | 482 | def __repr__(self): |
|
479 | 483 | raise Exception() |
|
480 | 484 | _ip.user_ns['a'] = A() |
|
481 | 485 | _ip.magic("whos") |
|
482 | 486 | |
|
483 | 487 | @py3compat.u_format |
|
484 | 488 | def doctest_precision(): |
|
485 | 489 | """doctest for %precision |
|
486 | 490 | |
|
487 | 491 | In [1]: f = get_ipython().display_formatter.formatters['text/plain'] |
|
488 | 492 | |
|
489 | 493 | In [2]: %precision 5 |
|
490 | 494 | Out[2]: {u}'%.5f' |
|
491 | 495 | |
|
492 | 496 | In [3]: f.float_format |
|
493 | 497 | Out[3]: {u}'%.5f' |
|
494 | 498 | |
|
495 | 499 | In [4]: %precision %e |
|
496 | 500 | Out[4]: {u}'%e' |
|
497 | 501 | |
|
498 | 502 | In [5]: f(3.1415927) |
|
499 | 503 | Out[5]: {u}'3.141593e+00' |
|
500 | 504 | """ |
|
501 | 505 | |
|
502 | 506 | def test_psearch(): |
|
503 | 507 | with tt.AssertPrints("dict.fromkeys"): |
|
504 | 508 | _ip.run_cell("dict.fr*?") |
|
505 | 509 | |
|
506 | 510 | def test_timeit_shlex(): |
|
507 | 511 | """test shlex issues with timeit (#1109)""" |
|
508 | 512 | _ip.ex("def f(*a,**kw): pass") |
|
509 | 513 | _ip.magic('timeit -n1 "this is a bug".count(" ")') |
|
510 | 514 | _ip.magic('timeit -r1 -n1 f(" ", 1)') |
|
511 | 515 | _ip.magic('timeit -r1 -n1 f(" ", 1, " ", 2, " ")') |
|
512 | 516 | _ip.magic('timeit -r1 -n1 ("a " + "b")') |
|
513 | 517 | _ip.magic('timeit -r1 -n1 f("a " + "b")') |
|
514 | 518 | _ip.magic('timeit -r1 -n1 f("a " + "b ")') |
|
515 | 519 | |
|
516 | 520 | |
|
517 | 521 | def test_timeit_arguments(): |
|
518 | 522 | "Test valid timeit arguments, should not cause SyntaxError (GH #1269)" |
|
519 | 523 | _ip.magic("timeit ('#')") |
|
520 | 524 | |
|
521 | 525 | |
|
522 | 526 | def test_timeit_special_syntax(): |
|
523 | 527 | "Test %%timeit with IPython special syntax" |
|
524 | 528 | @register_line_magic |
|
525 | 529 | def lmagic(line): |
|
526 | 530 | ip = get_ipython() |
|
527 | 531 | ip.user_ns['lmagic_out'] = line |
|
528 | 532 | |
|
529 | 533 | # line mode test |
|
530 | 534 | _ip.run_line_magic('timeit', '-n1 -r1 %lmagic my line') |
|
531 | 535 | nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line') |
|
532 | 536 | # cell mode test |
|
533 | 537 | _ip.run_cell_magic('timeit', '-n1 -r1', '%lmagic my line2') |
|
534 | 538 | nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line2') |
|
535 | 539 | |
|
536 | 540 | def test_timeit_return(): |
|
537 | 541 | """ |
|
538 | 542 | test wether timeit -o return object |
|
539 | 543 | """ |
|
540 | 544 | |
|
541 | 545 | res = _ip.run_line_magic('timeit','-n10 -r10 -o 1') |
|
542 | 546 | assert(res is not None) |
|
543 | 547 | |
|
544 | 548 | def test_timeit_quiet(): |
|
545 | 549 | """ |
|
546 | 550 | test quiet option of timeit magic |
|
547 | 551 | """ |
|
548 | 552 | with tt.AssertNotPrints("loops"): |
|
549 | 553 | _ip.run_cell("%timeit -n1 -r1 -q 1") |
|
550 | 554 | |
|
551 | 555 | @dec.skipif(execution.profile is None) |
|
552 | 556 | def test_prun_special_syntax(): |
|
553 | 557 | "Test %%prun with IPython special syntax" |
|
554 | 558 | @register_line_magic |
|
555 | 559 | def lmagic(line): |
|
556 | 560 | ip = get_ipython() |
|
557 | 561 | ip.user_ns['lmagic_out'] = line |
|
558 | 562 | |
|
559 | 563 | # line mode test |
|
560 | 564 | _ip.run_line_magic('prun', '-q %lmagic my line') |
|
561 | 565 | nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line') |
|
562 | 566 | # cell mode test |
|
563 | 567 | _ip.run_cell_magic('prun', '-q', '%lmagic my line2') |
|
564 | 568 | nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line2') |
|
565 | 569 | |
|
566 | 570 | @dec.skipif(execution.profile is None) |
|
567 | 571 | def test_prun_quotes(): |
|
568 | 572 | "Test that prun does not clobber string escapes (GH #1302)" |
|
569 | 573 | _ip.magic(r"prun -q x = '\t'") |
|
570 | 574 | nt.assert_equal(_ip.user_ns['x'], '\t') |
|
571 | 575 | |
|
572 | 576 | def test_extension(): |
|
573 | 577 | tmpdir = TemporaryDirectory() |
|
574 | 578 | orig_ipython_dir = _ip.ipython_dir |
|
575 | 579 | try: |
|
576 | 580 | _ip.ipython_dir = tmpdir.name |
|
577 | 581 | nt.assert_raises(ImportError, _ip.magic, "load_ext daft_extension") |
|
578 | 582 | url = os.path.join(os.path.dirname(__file__), "daft_extension.py") |
|
579 | 583 | _ip.magic("install_ext %s" % url) |
|
580 | 584 | _ip.user_ns.pop('arq', None) |
|
581 | 585 | invalidate_caches() # Clear import caches |
|
582 | 586 | _ip.magic("load_ext daft_extension") |
|
583 | 587 | nt.assert_equal(_ip.user_ns['arq'], 185) |
|
584 | 588 | _ip.magic("unload_ext daft_extension") |
|
585 | 589 | assert 'arq' not in _ip.user_ns |
|
586 | 590 | finally: |
|
587 | 591 | _ip.ipython_dir = orig_ipython_dir |
|
588 | 592 | tmpdir.cleanup() |
|
589 | 593 | |
|
590 | 594 | def test_notebook_export_json(): |
|
591 | 595 | with TemporaryDirectory() as td: |
|
592 | 596 | outfile = os.path.join(td, "nb.ipynb") |
|
593 | 597 | _ip.ex(py3compat.u_format(u"u = {u}'héllo'")) |
|
594 | 598 | _ip.magic("notebook -e %s" % outfile) |
|
595 | 599 | |
|
596 | 600 | def test_notebook_export_py(): |
|
597 | 601 | with TemporaryDirectory() as td: |
|
598 | 602 | outfile = os.path.join(td, "nb.py") |
|
599 | 603 | _ip.ex(py3compat.u_format(u"u = {u}'héllo'")) |
|
600 | 604 | _ip.magic("notebook -e %s" % outfile) |
|
601 | 605 | |
|
602 | 606 | def test_notebook_reformat_py(): |
|
603 | 607 | with TemporaryDirectory() as td: |
|
604 | 608 | infile = os.path.join(td, "nb.ipynb") |
|
605 | 609 | with io.open(infile, 'w', encoding='utf-8') as f: |
|
606 | 610 | current.write(nb0, f, 'json') |
|
607 | 611 | |
|
608 | 612 | _ip.ex(py3compat.u_format(u"u = {u}'héllo'")) |
|
609 | 613 | _ip.magic("notebook -f py %s" % infile) |
|
610 | 614 | |
|
611 | 615 | def test_notebook_reformat_json(): |
|
612 | 616 | with TemporaryDirectory() as td: |
|
613 | 617 | infile = os.path.join(td, "nb.py") |
|
614 | 618 | with io.open(infile, 'w', encoding='utf-8') as f: |
|
615 | 619 | current.write(nb0, f, 'py') |
|
616 | 620 | |
|
617 | 621 | _ip.ex(py3compat.u_format(u"u = {u}'héllo'")) |
|
618 | 622 | _ip.magic("notebook -f ipynb %s" % infile) |
|
619 | 623 | _ip.magic("notebook -f json %s" % infile) |
|
620 | 624 | |
|
621 | 625 | def test_env(): |
|
622 | 626 | env = _ip.magic("env") |
|
623 | 627 | assert isinstance(env, dict), type(env) |
|
624 | 628 | |
|
625 | 629 | |
|
626 | 630 | class CellMagicTestCase(TestCase): |
|
627 | 631 | |
|
628 | 632 | def check_ident(self, magic): |
|
629 | 633 | # Manually called, we get the result |
|
630 | 634 | out = _ip.run_cell_magic(magic, 'a', 'b') |
|
631 | 635 | nt.assert_equal(out, ('a','b')) |
|
632 | 636 | # Via run_cell, it goes into the user's namespace via displayhook |
|
633 | 637 | _ip.run_cell('%%' + magic +' c\nd') |
|
634 | 638 | nt.assert_equal(_ip.user_ns['_'], ('c','d')) |
|
635 | 639 | |
|
636 | 640 | def test_cell_magic_func_deco(self): |
|
637 | 641 | "Cell magic using simple decorator" |
|
638 | 642 | @register_cell_magic |
|
639 | 643 | def cellm(line, cell): |
|
640 | 644 | return line, cell |
|
641 | 645 | |
|
642 | 646 | self.check_ident('cellm') |
|
643 | 647 | |
|
644 | 648 | def test_cell_magic_reg(self): |
|
645 | 649 | "Cell magic manually registered" |
|
646 | 650 | def cellm(line, cell): |
|
647 | 651 | return line, cell |
|
648 | 652 | |
|
649 | 653 | _ip.register_magic_function(cellm, 'cell', 'cellm2') |
|
650 | 654 | self.check_ident('cellm2') |
|
651 | 655 | |
|
652 | 656 | def test_cell_magic_class(self): |
|
653 | 657 | "Cell magics declared via a class" |
|
654 | 658 | @magics_class |
|
655 | 659 | class MyMagics(Magics): |
|
656 | 660 | |
|
657 | 661 | @cell_magic |
|
658 | 662 | def cellm3(self, line, cell): |
|
659 | 663 | return line, cell |
|
660 | 664 | |
|
661 | 665 | _ip.register_magics(MyMagics) |
|
662 | 666 | self.check_ident('cellm3') |
|
663 | 667 | |
|
664 | 668 | def test_cell_magic_class2(self): |
|
665 | 669 | "Cell magics declared via a class, #2" |
|
666 | 670 | @magics_class |
|
667 | 671 | class MyMagics2(Magics): |
|
668 | 672 | |
|
669 | 673 | @cell_magic('cellm4') |
|
670 | 674 | def cellm33(self, line, cell): |
|
671 | 675 | return line, cell |
|
672 | 676 | |
|
673 | 677 | _ip.register_magics(MyMagics2) |
|
674 | 678 | self.check_ident('cellm4') |
|
675 | 679 | # Check that nothing is registered as 'cellm33' |
|
676 | 680 | c33 = _ip.find_cell_magic('cellm33') |
|
677 | 681 | nt.assert_equal(c33, None) |
|
678 | 682 | |
|
679 | 683 | def test_file(): |
|
680 | 684 | """Basic %%file""" |
|
681 | 685 | ip = get_ipython() |
|
682 | 686 | with TemporaryDirectory() as td: |
|
683 | 687 | fname = os.path.join(td, 'file1') |
|
684 | 688 | ip.run_cell_magic("file", fname, u'\n'.join([ |
|
685 | 689 | 'line1', |
|
686 | 690 | 'line2', |
|
687 | 691 | ])) |
|
688 | 692 | with open(fname) as f: |
|
689 | 693 | s = f.read() |
|
690 | 694 | nt.assert_in('line1\n', s) |
|
691 | 695 | nt.assert_in('line2', s) |
|
692 | 696 | |
|
693 | 697 | def test_file_var_expand(): |
|
694 | 698 | """%%file $filename""" |
|
695 | 699 | ip = get_ipython() |
|
696 | 700 | with TemporaryDirectory() as td: |
|
697 | 701 | fname = os.path.join(td, 'file1') |
|
698 | 702 | ip.user_ns['filename'] = fname |
|
699 | 703 | ip.run_cell_magic("file", '$filename', u'\n'.join([ |
|
700 | 704 | 'line1', |
|
701 | 705 | 'line2', |
|
702 | 706 | ])) |
|
703 | 707 | with open(fname) as f: |
|
704 | 708 | s = f.read() |
|
705 | 709 | nt.assert_in('line1\n', s) |
|
706 | 710 | nt.assert_in('line2', s) |
|
707 | 711 | |
|
708 | 712 | def test_file_unicode(): |
|
709 | 713 | """%%file with unicode cell""" |
|
710 | 714 | ip = get_ipython() |
|
711 | 715 | with TemporaryDirectory() as td: |
|
712 | 716 | fname = os.path.join(td, 'file1') |
|
713 | 717 | ip.run_cell_magic("file", fname, u'\n'.join([ |
|
714 | 718 | u'liné1', |
|
715 | 719 | u'liné2', |
|
716 | 720 | ])) |
|
717 | 721 | with io.open(fname, encoding='utf-8') as f: |
|
718 | 722 | s = f.read() |
|
719 | 723 | nt.assert_in(u'liné1\n', s) |
|
720 | 724 | nt.assert_in(u'liné2', s) |
|
721 | 725 | |
|
722 | 726 | def test_file_amend(): |
|
723 | 727 | """%%file -a amends files""" |
|
724 | 728 | ip = get_ipython() |
|
725 | 729 | with TemporaryDirectory() as td: |
|
726 | 730 | fname = os.path.join(td, 'file2') |
|
727 | 731 | ip.run_cell_magic("file", fname, u'\n'.join([ |
|
728 | 732 | 'line1', |
|
729 | 733 | 'line2', |
|
730 | 734 | ])) |
|
731 | 735 | ip.run_cell_magic("file", "-a %s" % fname, u'\n'.join([ |
|
732 | 736 | 'line3', |
|
733 | 737 | 'line4', |
|
734 | 738 | ])) |
|
735 | 739 | with open(fname) as f: |
|
736 | 740 | s = f.read() |
|
737 | 741 | nt.assert_in('line1\n', s) |
|
738 | 742 | nt.assert_in('line3\n', s) |
|
739 | 743 | |
|
740 | 744 | |
|
741 | 745 | def test_script_config(): |
|
742 | 746 | ip = get_ipython() |
|
743 | 747 | ip.config.ScriptMagics.script_magics = ['whoda'] |
|
744 | 748 | sm = script.ScriptMagics(shell=ip) |
|
745 | 749 | nt.assert_in('whoda', sm.magics['cell']) |
|
746 | 750 | |
|
747 | 751 | @dec.skip_win32 |
|
748 | 752 | def test_script_out(): |
|
749 | 753 | ip = get_ipython() |
|
750 | 754 | ip.run_cell_magic("script", "--out output sh", "echo 'hi'") |
|
751 | 755 | nt.assert_equal(ip.user_ns['output'], 'hi\n') |
|
752 | 756 | |
|
753 | 757 | @dec.skip_win32 |
|
754 | 758 | def test_script_err(): |
|
755 | 759 | ip = get_ipython() |
|
756 | 760 | ip.run_cell_magic("script", "--err error sh", "echo 'hello' >&2") |
|
757 | 761 | nt.assert_equal(ip.user_ns['error'], 'hello\n') |
|
758 | 762 | |
|
759 | 763 | @dec.skip_win32 |
|
760 | 764 | def test_script_out_err(): |
|
761 | 765 | ip = get_ipython() |
|
762 | 766 | ip.run_cell_magic("script", "--out output --err error sh", "echo 'hi'\necho 'hello' >&2") |
|
763 | 767 | nt.assert_equal(ip.user_ns['output'], 'hi\n') |
|
764 | 768 | nt.assert_equal(ip.user_ns['error'], 'hello\n') |
|
765 | 769 | |
|
766 | 770 | @dec.skip_win32 |
|
767 | 771 | def test_script_bg_out(): |
|
768 | 772 | ip = get_ipython() |
|
769 | 773 | ip.run_cell_magic("script", "--bg --out output sh", "echo 'hi'") |
|
770 | 774 | nt.assert_equal(ip.user_ns['output'].read(), b'hi\n') |
|
771 | 775 | |
|
772 | 776 | @dec.skip_win32 |
|
773 | 777 | def test_script_bg_err(): |
|
774 | 778 | ip = get_ipython() |
|
775 | 779 | ip.run_cell_magic("script", "--bg --err error sh", "echo 'hello' >&2") |
|
776 | 780 | nt.assert_equal(ip.user_ns['error'].read(), b'hello\n') |
|
777 | 781 | |
|
778 | 782 | @dec.skip_win32 |
|
779 | 783 | def test_script_bg_out_err(): |
|
780 | 784 | ip = get_ipython() |
|
781 | 785 | ip.run_cell_magic("script", "--bg --out output --err error sh", "echo 'hi'\necho 'hello' >&2") |
|
782 | 786 | nt.assert_equal(ip.user_ns['output'].read(), b'hi\n') |
|
783 | 787 | nt.assert_equal(ip.user_ns['error'].read(), b'hello\n') |
|
784 | 788 | |
|
785 | 789 | def test_script_defaults(): |
|
786 | 790 | ip = get_ipython() |
|
787 | 791 | for cmd in ['sh', 'bash', 'perl', 'ruby']: |
|
788 | 792 | try: |
|
789 | 793 | find_cmd(cmd) |
|
790 | 794 | except Exception: |
|
791 | 795 | pass |
|
792 | 796 | else: |
|
793 | 797 | nt.assert_in(cmd, ip.magics_manager.magics['cell']) |
|
794 | 798 | |
|
795 | 799 | |
|
796 | 800 | @magics_class |
|
797 | 801 | class FooFoo(Magics): |
|
798 | 802 | """class with both %foo and %%foo magics""" |
|
799 | 803 | @line_magic('foo') |
|
800 | 804 | def line_foo(self, line): |
|
801 | 805 | "I am line foo" |
|
802 | 806 | pass |
|
803 | 807 | |
|
804 | 808 | @cell_magic("foo") |
|
805 | 809 | def cell_foo(self, line, cell): |
|
806 | 810 | "I am cell foo, not line foo" |
|
807 | 811 | pass |
|
808 | 812 | |
|
809 | 813 | def test_line_cell_info(): |
|
810 | 814 | """%%foo and %foo magics are distinguishable to inspect""" |
|
811 | 815 | ip = get_ipython() |
|
812 | 816 | ip.magics_manager.register(FooFoo) |
|
813 | 817 | oinfo = ip.object_inspect('foo') |
|
814 | 818 | nt.assert_true(oinfo['found']) |
|
815 | 819 | nt.assert_true(oinfo['ismagic']) |
|
816 | 820 | |
|
817 | 821 | oinfo = ip.object_inspect('%%foo') |
|
818 | 822 | nt.assert_true(oinfo['found']) |
|
819 | 823 | nt.assert_true(oinfo['ismagic']) |
|
820 | 824 | nt.assert_equal(oinfo['docstring'], FooFoo.cell_foo.__doc__) |
|
821 | 825 | |
|
822 | 826 | oinfo = ip.object_inspect('%foo') |
|
823 | 827 | nt.assert_true(oinfo['found']) |
|
824 | 828 | nt.assert_true(oinfo['ismagic']) |
|
825 | 829 | nt.assert_equal(oinfo['docstring'], FooFoo.line_foo.__doc__) |
|
826 | 830 | |
|
827 | 831 | def test_multiple_magics(): |
|
828 | 832 | ip = get_ipython() |
|
829 | 833 | foo1 = FooFoo(ip) |
|
830 | 834 | foo2 = FooFoo(ip) |
|
831 | 835 | mm = ip.magics_manager |
|
832 | 836 | mm.register(foo1) |
|
833 | 837 | nt.assert_true(mm.magics['line']['foo'].im_self is foo1) |
|
834 | 838 | mm.register(foo2) |
|
835 | 839 | nt.assert_true(mm.magics['line']['foo'].im_self is foo2) |
|
836 | 840 | |
|
837 | 841 | def test_alias_magic(): |
|
838 | 842 | """Test %alias_magic.""" |
|
839 | 843 | ip = get_ipython() |
|
840 | 844 | mm = ip.magics_manager |
|
841 | 845 | |
|
842 | 846 | # Basic operation: both cell and line magics are created, if possible. |
|
843 | 847 | ip.run_line_magic('alias_magic', 'timeit_alias timeit') |
|
844 | 848 | nt.assert_in('timeit_alias', mm.magics['line']) |
|
845 | 849 | nt.assert_in('timeit_alias', mm.magics['cell']) |
|
846 | 850 | |
|
847 | 851 | # --cell is specified, line magic not created. |
|
848 | 852 | ip.run_line_magic('alias_magic', '--cell timeit_cell_alias timeit') |
|
849 | 853 | nt.assert_not_in('timeit_cell_alias', mm.magics['line']) |
|
850 | 854 | nt.assert_in('timeit_cell_alias', mm.magics['cell']) |
|
851 | 855 | |
|
852 | 856 | # Test that line alias is created successfully. |
|
853 | 857 | ip.run_line_magic('alias_magic', '--line env_alias env') |
|
854 | 858 | nt.assert_equal(ip.run_line_magic('env', ''), |
|
855 | 859 | ip.run_line_magic('env_alias', '')) |
|
856 | 860 | |
|
857 | 861 | def test_save(): |
|
858 | 862 | """Test %save.""" |
|
859 | 863 | ip = get_ipython() |
|
860 | 864 | ip.history_manager.reset() # Clear any existing history. |
|
861 | 865 | cmds = [u"a=1", u"def b():\n return a**2", u"print(a, b())"] |
|
862 | 866 | for i, cmd in enumerate(cmds, start=1): |
|
863 | 867 | ip.history_manager.store_inputs(i, cmd) |
|
864 | 868 | with TemporaryDirectory() as tmpdir: |
|
865 | 869 | file = os.path.join(tmpdir, "testsave.py") |
|
866 | 870 | ip.run_line_magic("save", "%s 1-10" % file) |
|
867 | 871 | with open(file) as f: |
|
868 | 872 | content = f.read() |
|
869 | 873 | nt.assert_equal(content.count(cmds[0]), 1) |
|
870 | 874 | nt.assert_in('coding: utf-8', content) |
|
871 | 875 | ip.run_line_magic("save", "-a %s 1-10" % file) |
|
872 | 876 | with open(file) as f: |
|
873 | 877 | content = f.read() |
|
874 | 878 | nt.assert_equal(content.count(cmds[0]), 2) |
|
875 | 879 | nt.assert_in('coding: utf-8', content) |
|
876 | 880 | |
|
877 | 881 | |
|
878 | 882 | def test_store(): |
|
879 | 883 | """Test %store.""" |
|
880 | 884 | ip = get_ipython() |
|
881 | 885 | ip.run_line_magic('load_ext', 'storemagic') |
|
882 | 886 | |
|
883 | 887 | # make sure the storage is empty |
|
884 | 888 | ip.run_line_magic('store', '-z') |
|
885 | 889 | ip.user_ns['var'] = 42 |
|
886 | 890 | ip.run_line_magic('store', 'var') |
|
887 | 891 | ip.user_ns['var'] = 39 |
|
888 | 892 | ip.run_line_magic('store', '-r') |
|
889 | 893 | nt.assert_equal(ip.user_ns['var'], 42) |
|
890 | 894 | |
|
891 | 895 | ip.run_line_magic('store', '-d var') |
|
892 | 896 | ip.user_ns['var'] = 39 |
|
893 | 897 | ip.run_line_magic('store' , '-r') |
|
894 | 898 | nt.assert_equal(ip.user_ns['var'], 39) |
|
895 | 899 | |
|
896 | 900 | |
|
897 | 901 | def _run_edit_test(arg_s, exp_filename=None, |
|
898 | 902 | exp_lineno=-1, |
|
899 | 903 | exp_contents=None, |
|
900 | 904 | exp_is_temp=None): |
|
901 | 905 | ip = get_ipython() |
|
902 | 906 | M = code.CodeMagics(ip) |
|
903 | 907 | last_call = ['',''] |
|
904 | 908 | opts,args = M.parse_options(arg_s,'prxn:') |
|
905 | 909 | filename, lineno, is_temp = M._find_edit_target(ip, args, opts, last_call) |
|
906 | 910 | |
|
907 | 911 | if exp_filename is not None: |
|
908 | 912 | nt.assert_equal(exp_filename, filename) |
|
909 | 913 | if exp_contents is not None: |
|
910 | 914 | with io.open(filename, 'r') as f: |
|
911 | 915 | contents = f.read() |
|
912 | 916 | nt.assert_equal(exp_contents, contents) |
|
913 | 917 | if exp_lineno != -1: |
|
914 | 918 | nt.assert_equal(exp_lineno, lineno) |
|
915 | 919 | if exp_is_temp is not None: |
|
916 | 920 | nt.assert_equal(exp_is_temp, is_temp) |
|
917 | 921 | |
|
918 | 922 | |
|
919 | 923 | def test_edit_interactive(): |
|
920 | 924 | """%edit on interactively defined objects""" |
|
921 | 925 | ip = get_ipython() |
|
922 | 926 | n = ip.execution_count |
|
923 | 927 | ip.run_cell(u"def foo(): return 1", store_history=True) |
|
924 | 928 | |
|
925 | 929 | try: |
|
926 | 930 | _run_edit_test("foo") |
|
927 | 931 | except code.InteractivelyDefined as e: |
|
928 | 932 | nt.assert_equal(e.index, n) |
|
929 | 933 | else: |
|
930 | 934 | raise AssertionError("Should have raised InteractivelyDefined") |
|
931 | 935 | |
|
932 | 936 | |
|
933 | 937 | def test_edit_cell(): |
|
934 | 938 | """%edit [cell id]""" |
|
935 | 939 | ip = get_ipython() |
|
936 | 940 | |
|
937 | 941 | ip.run_cell(u"def foo(): return 1", store_history=True) |
|
938 | 942 | |
|
939 | 943 | # test |
|
940 | 944 | _run_edit_test("1", exp_contents=ip.user_ns['In'][1], exp_is_temp=True) |
@@ -1,317 +1,322 | |||
|
1 | 1 | """Tests for autoreload extension. |
|
2 | 2 | """ |
|
3 | 3 | #----------------------------------------------------------------------------- |
|
4 | 4 | # Copyright (c) 2012 IPython Development Team. |
|
5 | 5 | # |
|
6 | 6 | # Distributed under the terms of the Modified BSD License. |
|
7 | 7 | # |
|
8 | 8 | # The full license is in the file COPYING.txt, distributed with this software. |
|
9 | 9 | #----------------------------------------------------------------------------- |
|
10 | 10 | |
|
11 | 11 | #----------------------------------------------------------------------------- |
|
12 | 12 | # Imports |
|
13 | 13 | #----------------------------------------------------------------------------- |
|
14 | 14 | |
|
15 | 15 | import os |
|
16 | 16 | import sys |
|
17 | 17 | import tempfile |
|
18 | 18 | import shutil |
|
19 | 19 | import random |
|
20 | 20 | import time |
|
21 | from io import StringIO | |
|
22 | 21 | |
|
23 | 22 | import nose.tools as nt |
|
24 | 23 | import IPython.testing.tools as tt |
|
25 | 24 | |
|
26 | 25 | from IPython.extensions.autoreload import AutoreloadMagics |
|
27 | 26 | from IPython.core.hooks import TryNext |
|
27 | from IPython.utils.py3compat import PY3 | |
|
28 | ||
|
29 | if PY3: | |
|
30 | from io import StringIO | |
|
31 | else: | |
|
32 | from StringIO import StringIO | |
|
28 | 33 | |
|
29 | 34 | #----------------------------------------------------------------------------- |
|
30 | 35 | # Test fixture |
|
31 | 36 | #----------------------------------------------------------------------------- |
|
32 | 37 | |
|
33 | 38 | noop = lambda *a, **kw: None |
|
34 | 39 | |
|
35 | 40 | class FakeShell(object): |
|
36 | 41 | |
|
37 | 42 | def __init__(self): |
|
38 | 43 | self.ns = {} |
|
39 | 44 | self.auto_magics = AutoreloadMagics(shell=self) |
|
40 | 45 | |
|
41 | 46 | register_magics = set_hook = noop |
|
42 | 47 | |
|
43 | 48 | def run_code(self, code): |
|
44 | 49 | try: |
|
45 | 50 | self.auto_magics.pre_run_code_hook(self) |
|
46 | 51 | except TryNext: |
|
47 | 52 | pass |
|
48 | 53 | exec(code, self.ns) |
|
49 | 54 | |
|
50 | 55 | def push(self, items): |
|
51 | 56 | self.ns.update(items) |
|
52 | 57 | |
|
53 | 58 | def magic_autoreload(self, parameter): |
|
54 | 59 | self.auto_magics.autoreload(parameter) |
|
55 | 60 | |
|
56 | 61 | def magic_aimport(self, parameter, stream=None): |
|
57 | 62 | self.auto_magics.aimport(parameter, stream=stream) |
|
58 | 63 | |
|
59 | 64 | |
|
60 | 65 | class Fixture(object): |
|
61 | 66 | """Fixture for creating test module files""" |
|
62 | 67 | |
|
63 | 68 | test_dir = None |
|
64 | 69 | old_sys_path = None |
|
65 | 70 | filename_chars = "abcdefghijklmopqrstuvwxyz0123456789" |
|
66 | 71 | |
|
67 | 72 | def setUp(self): |
|
68 | 73 | self.test_dir = tempfile.mkdtemp() |
|
69 | 74 | self.old_sys_path = list(sys.path) |
|
70 | 75 | sys.path.insert(0, self.test_dir) |
|
71 | 76 | self.shell = FakeShell() |
|
72 | 77 | |
|
73 | 78 | def tearDown(self): |
|
74 | 79 | shutil.rmtree(self.test_dir) |
|
75 | 80 | sys.path = self.old_sys_path |
|
76 | 81 | |
|
77 | 82 | self.test_dir = None |
|
78 | 83 | self.old_sys_path = None |
|
79 | 84 | self.shell = None |
|
80 | 85 | |
|
81 | 86 | def get_module(self): |
|
82 | 87 | module_name = "tmpmod_" + "".join(random.sample(self.filename_chars,20)) |
|
83 | 88 | if module_name in sys.modules: |
|
84 | 89 | del sys.modules[module_name] |
|
85 | 90 | file_name = os.path.join(self.test_dir, module_name + ".py") |
|
86 | 91 | return module_name, file_name |
|
87 | 92 | |
|
88 | 93 | def write_file(self, filename, content): |
|
89 | 94 | """ |
|
90 | 95 | Write a file, and force a timestamp difference of at least one second |
|
91 | 96 | |
|
92 | 97 | Notes |
|
93 | 98 | ----- |
|
94 | 99 | Python's .pyc files record the timestamp of their compilation |
|
95 | 100 | with a time resolution of one second. |
|
96 | 101 | |
|
97 | 102 | Therefore, we need to force a timestamp difference between .py |
|
98 | 103 | and .pyc, without having the .py file be timestamped in the |
|
99 | 104 | future, and without changing the timestamp of the .pyc file |
|
100 | 105 | (because that is stored in the file). The only reliable way |
|
101 | 106 | to achieve this seems to be to sleep. |
|
102 | 107 | """ |
|
103 | 108 | |
|
104 | 109 | # Sleep one second + eps |
|
105 | 110 | time.sleep(1.05) |
|
106 | 111 | |
|
107 | 112 | # Write |
|
108 | 113 | f = open(filename, 'w') |
|
109 | 114 | try: |
|
110 | 115 | f.write(content) |
|
111 | 116 | finally: |
|
112 | 117 | f.close() |
|
113 | 118 | |
|
114 | 119 | def new_module(self, code): |
|
115 | 120 | mod_name, mod_fn = self.get_module() |
|
116 | 121 | f = open(mod_fn, 'w') |
|
117 | 122 | try: |
|
118 | 123 | f.write(code) |
|
119 | 124 | finally: |
|
120 | 125 | f.close() |
|
121 | 126 | return mod_name, mod_fn |
|
122 | 127 | |
|
123 | 128 | #----------------------------------------------------------------------------- |
|
124 | 129 | # Test automatic reloading |
|
125 | 130 | #----------------------------------------------------------------------------- |
|
126 | 131 | |
|
127 | 132 | class TestAutoreload(Fixture): |
|
128 | 133 | def _check_smoketest(self, use_aimport=True): |
|
129 | 134 | """ |
|
130 | 135 | Functional test for the automatic reloader using either |
|
131 | 136 | '%autoreload 1' or '%autoreload 2' |
|
132 | 137 | """ |
|
133 | 138 | |
|
134 | 139 | mod_name, mod_fn = self.new_module(""" |
|
135 | 140 | x = 9 |
|
136 | 141 | |
|
137 | 142 | z = 123 # this item will be deleted |
|
138 | 143 | |
|
139 | 144 | def foo(y): |
|
140 | 145 | return y + 3 |
|
141 | 146 | |
|
142 | 147 | class Baz(object): |
|
143 | 148 | def __init__(self, x): |
|
144 | 149 | self.x = x |
|
145 | 150 | def bar(self, y): |
|
146 | 151 | return self.x + y |
|
147 | 152 | @property |
|
148 | 153 | def quux(self): |
|
149 | 154 | return 42 |
|
150 | 155 | def zzz(self): |
|
151 | 156 | '''This method will be deleted below''' |
|
152 | 157 | return 99 |
|
153 | 158 | |
|
154 | 159 | class Bar: # old-style class: weakref doesn't work for it on Python < 2.7 |
|
155 | 160 | def foo(self): |
|
156 | 161 | return 1 |
|
157 | 162 | """) |
|
158 | 163 | |
|
159 | 164 | # |
|
160 | 165 | # Import module, and mark for reloading |
|
161 | 166 | # |
|
162 | 167 | if use_aimport: |
|
163 | 168 | self.shell.magic_autoreload("1") |
|
164 | 169 | self.shell.magic_aimport(mod_name) |
|
165 | 170 | stream = StringIO() |
|
166 | 171 | self.shell.magic_aimport("", stream=stream) |
|
167 | 172 | nt.assert_true(("Modules to reload:\n%s" % mod_name) in |
|
168 | 173 | stream.getvalue()) |
|
169 | 174 | |
|
170 | 175 | nt.assert_raises( |
|
171 | 176 | ImportError, |
|
172 | 177 | self.shell.magic_aimport, "tmpmod_as318989e89ds") |
|
173 | 178 | else: |
|
174 | 179 | self.shell.magic_autoreload("2") |
|
175 | 180 | self.shell.run_code("import %s" % mod_name) |
|
176 | 181 | stream = StringIO() |
|
177 | 182 | self.shell.magic_aimport("", stream=stream) |
|
178 | 183 | nt.assert_true("Modules to reload:\nall-except-skipped" in |
|
179 | 184 | stream.getvalue()) |
|
180 | 185 | nt.assert_in(mod_name, self.shell.ns) |
|
181 | 186 | |
|
182 | 187 | mod = sys.modules[mod_name] |
|
183 | 188 | |
|
184 | 189 | # |
|
185 | 190 | # Test module contents |
|
186 | 191 | # |
|
187 | 192 | old_foo = mod.foo |
|
188 | 193 | old_obj = mod.Baz(9) |
|
189 | 194 | old_obj2 = mod.Bar() |
|
190 | 195 | |
|
191 | 196 | def check_module_contents(): |
|
192 | 197 | nt.assert_equal(mod.x, 9) |
|
193 | 198 | nt.assert_equal(mod.z, 123) |
|
194 | 199 | |
|
195 | 200 | nt.assert_equal(old_foo(0), 3) |
|
196 | 201 | nt.assert_equal(mod.foo(0), 3) |
|
197 | 202 | |
|
198 | 203 | obj = mod.Baz(9) |
|
199 | 204 | nt.assert_equal(old_obj.bar(1), 10) |
|
200 | 205 | nt.assert_equal(obj.bar(1), 10) |
|
201 | 206 | nt.assert_equal(obj.quux, 42) |
|
202 | 207 | nt.assert_equal(obj.zzz(), 99) |
|
203 | 208 | |
|
204 | 209 | obj2 = mod.Bar() |
|
205 | 210 | nt.assert_equal(old_obj2.foo(), 1) |
|
206 | 211 | nt.assert_equal(obj2.foo(), 1) |
|
207 | 212 | |
|
208 | 213 | check_module_contents() |
|
209 | 214 | |
|
210 | 215 | # |
|
211 | 216 | # Simulate a failed reload: no reload should occur and exactly |
|
212 | 217 | # one error message should be printed |
|
213 | 218 | # |
|
214 | 219 | self.write_file(mod_fn, """ |
|
215 | 220 | a syntax error |
|
216 | 221 | """) |
|
217 | 222 | |
|
218 | 223 | with tt.AssertPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'): |
|
219 | 224 | self.shell.run_code("pass") # trigger reload |
|
220 | 225 | with tt.AssertNotPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'): |
|
221 | 226 | self.shell.run_code("pass") # trigger another reload |
|
222 | 227 | check_module_contents() |
|
223 | 228 | |
|
224 | 229 | # |
|
225 | 230 | # Rewrite module (this time reload should succeed) |
|
226 | 231 | # |
|
227 | 232 | self.write_file(mod_fn, """ |
|
228 | 233 | x = 10 |
|
229 | 234 | |
|
230 | 235 | def foo(y): |
|
231 | 236 | return y + 4 |
|
232 | 237 | |
|
233 | 238 | class Baz(object): |
|
234 | 239 | def __init__(self, x): |
|
235 | 240 | self.x = x |
|
236 | 241 | def bar(self, y): |
|
237 | 242 | return self.x + y + 1 |
|
238 | 243 | @property |
|
239 | 244 | def quux(self): |
|
240 | 245 | return 43 |
|
241 | 246 | |
|
242 | 247 | class Bar: # old-style class |
|
243 | 248 | def foo(self): |
|
244 | 249 | return 2 |
|
245 | 250 | """) |
|
246 | 251 | |
|
247 | 252 | def check_module_contents(): |
|
248 | 253 | nt.assert_equal(mod.x, 10) |
|
249 | 254 | nt.assert_false(hasattr(mod, 'z')) |
|
250 | 255 | |
|
251 | 256 | nt.assert_equal(old_foo(0), 4) # superreload magic! |
|
252 | 257 | nt.assert_equal(mod.foo(0), 4) |
|
253 | 258 | |
|
254 | 259 | obj = mod.Baz(9) |
|
255 | 260 | nt.assert_equal(old_obj.bar(1), 11) # superreload magic! |
|
256 | 261 | nt.assert_equal(obj.bar(1), 11) |
|
257 | 262 | |
|
258 | 263 | nt.assert_equal(old_obj.quux, 43) |
|
259 | 264 | nt.assert_equal(obj.quux, 43) |
|
260 | 265 | |
|
261 | 266 | nt.assert_false(hasattr(old_obj, 'zzz')) |
|
262 | 267 | nt.assert_false(hasattr(obj, 'zzz')) |
|
263 | 268 | |
|
264 | 269 | obj2 = mod.Bar() |
|
265 | 270 | nt.assert_equal(old_obj2.foo(), 2) |
|
266 | 271 | nt.assert_equal(obj2.foo(), 2) |
|
267 | 272 | |
|
268 | 273 | self.shell.run_code("pass") # trigger reload |
|
269 | 274 | check_module_contents() |
|
270 | 275 | |
|
271 | 276 | # |
|
272 | 277 | # Another failure case: deleted file (shouldn't reload) |
|
273 | 278 | # |
|
274 | 279 | os.unlink(mod_fn) |
|
275 | 280 | |
|
276 | 281 | self.shell.run_code("pass") # trigger reload |
|
277 | 282 | check_module_contents() |
|
278 | 283 | |
|
279 | 284 | # |
|
280 | 285 | # Disable autoreload and rewrite module: no reload should occur |
|
281 | 286 | # |
|
282 | 287 | if use_aimport: |
|
283 | 288 | self.shell.magic_aimport("-" + mod_name) |
|
284 | 289 | stream = StringIO() |
|
285 | 290 | self.shell.magic_aimport("", stream=stream) |
|
286 | 291 | nt.assert_true(("Modules to skip:\n%s" % mod_name) in |
|
287 | 292 | stream.getvalue()) |
|
288 | 293 | |
|
289 | 294 | # This should succeed, although no such module exists |
|
290 | 295 | self.shell.magic_aimport("-tmpmod_as318989e89ds") |
|
291 | 296 | else: |
|
292 | 297 | self.shell.magic_autoreload("0") |
|
293 | 298 | |
|
294 | 299 | self.write_file(mod_fn, """ |
|
295 | 300 | x = -99 |
|
296 | 301 | """) |
|
297 | 302 | |
|
298 | 303 | self.shell.run_code("pass") # trigger reload |
|
299 | 304 | self.shell.run_code("pass") |
|
300 | 305 | check_module_contents() |
|
301 | 306 | |
|
302 | 307 | # |
|
303 | 308 | # Re-enable autoreload: reload should now occur |
|
304 | 309 | # |
|
305 | 310 | if use_aimport: |
|
306 | 311 | self.shell.magic_aimport(mod_name) |
|
307 | 312 | else: |
|
308 | 313 | self.shell.magic_autoreload("") |
|
309 | 314 | |
|
310 | 315 | self.shell.run_code("pass") # trigger reload |
|
311 | 316 | nt.assert_equal(mod.x, -99) |
|
312 | 317 | |
|
313 | 318 | def test_smoketest_aimport(self): |
|
314 | 319 | self._check_smoketest(use_aimport=True) |
|
315 | 320 | |
|
316 | 321 | def test_smoketest_autoreload(self): |
|
317 | 322 | self._check_smoketest(use_aimport=False) |
@@ -1,122 +1,126 | |||
|
1 | from io import StringIO | |
|
2 | ||
|
3 | 1 |
|
|
4 | 2 | from IPython.testing.decorators import skip_without |
|
5 | 3 | from IPython.extensions import rmagic |
|
4 | from IPython.utils.py3compat import PY3 | |
|
6 | 5 | from rpy2 import rinterface |
|
7 | 6 | import nose.tools as nt |
|
8 | 7 | |
|
8 | if PY3: | |
|
9 | from io import StringIO | |
|
10 | else: | |
|
11 | from StringIO import StringIO | |
|
12 | ||
|
9 | 13 | ip = get_ipython() |
|
10 | 14 | ip.magic('load_ext rmagic') |
|
11 | 15 | |
|
12 | 16 | |
|
13 | 17 | def test_push(): |
|
14 | 18 | rm = rmagic.RMagics(ip) |
|
15 | 19 | ip.push({'X':np.arange(5), 'Y':np.array([3,5,4,6,7])}) |
|
16 | 20 | ip.run_line_magic('Rpush', 'X Y') |
|
17 | 21 | np.testing.assert_almost_equal(np.asarray(rm.r('X')), ip.user_ns['X']) |
|
18 | 22 | np.testing.assert_almost_equal(np.asarray(rm.r('Y')), ip.user_ns['Y']) |
|
19 | 23 | |
|
20 | 24 | def test_push_localscope(): |
|
21 | 25 | """Test that Rpush looks for variables in the local scope first.""" |
|
22 | 26 | ip.run_cell(''' |
|
23 | 27 | def rmagic_addone(u): |
|
24 | 28 | %Rpush u |
|
25 | 29 | %R result = u+1 |
|
26 | 30 | %Rpull result |
|
27 | 31 | return result[0] |
|
28 | 32 | u = 0 |
|
29 | 33 | result = rmagic_addone(12344) |
|
30 | 34 | ''') |
|
31 | 35 | result = ip.user_ns['result'] |
|
32 | 36 | np.testing.assert_equal(result, 12345) |
|
33 | 37 | |
|
34 | 38 | @skip_without('pandas') |
|
35 | 39 | def test_push_dataframe(): |
|
36 | 40 | from pandas import DataFrame |
|
37 | 41 | rm = rmagic.RMagics(ip) |
|
38 | 42 | df = DataFrame([{'a': 1, 'b': 'bar'}, {'a': 5, 'b': 'foo', 'c': 20}]) |
|
39 | 43 | ip.push({'df':df}) |
|
40 | 44 | ip.run_line_magic('Rpush', 'df') |
|
41 | 45 | |
|
42 | 46 | # This is converted to factors, which are currently converted back to Python |
|
43 | 47 | # as integers, so for now we test its representation in R. |
|
44 | 48 | sio = StringIO() |
|
45 | 49 | rinterface.set_writeconsole(sio.write) |
|
46 | 50 | try: |
|
47 | 51 | rm.r('print(df$b[1])') |
|
48 | 52 | nt.assert_in('[1] bar', sio.getvalue()) |
|
49 | 53 | finally: |
|
50 | 54 | rinterface.set_writeconsole(None) |
|
51 | 55 | |
|
52 | 56 | # Values come packaged in arrays, so we unbox them to test. |
|
53 | 57 | nt.assert_equal(rm.r('df$a[2]')[0], 5) |
|
54 | 58 | missing = rm.r('df$c[1]')[0] |
|
55 | 59 | assert np.isnan(missing), missing |
|
56 | 60 | |
|
57 | 61 | def test_pull(): |
|
58 | 62 | rm = rmagic.RMagics(ip) |
|
59 | 63 | rm.r('Z=c(11:20)') |
|
60 | 64 | ip.run_line_magic('Rpull', 'Z') |
|
61 | 65 | np.testing.assert_almost_equal(np.asarray(rm.r('Z')), ip.user_ns['Z']) |
|
62 | 66 | np.testing.assert_almost_equal(ip.user_ns['Z'], np.arange(11,21)) |
|
63 | 67 | |
|
64 | 68 | def test_Rconverter(): |
|
65 | 69 | datapy= np.array([(1, 2.9, 'a'), (2, 3.5, 'b'), (3, 2.1, 'c')], |
|
66 | 70 | dtype=[('x', '<i4'), ('y', '<f8'), ('z', '|S1')]) |
|
67 | 71 | ip.user_ns['datapy'] = datapy |
|
68 | 72 | ip.run_line_magic('Rpush', 'datapy') |
|
69 | 73 | |
|
70 | 74 | # test to see if a copy is being made |
|
71 | 75 | v = ip.run_line_magic('Rget', '-d datapy') |
|
72 | 76 | w = ip.run_line_magic('Rget', '-d datapy') |
|
73 | 77 | np.testing.assert_almost_equal(w['x'], v['x']) |
|
74 | 78 | np.testing.assert_almost_equal(w['y'], v['y']) |
|
75 | 79 | nt.assert_true(np.all(w['z'] == v['z'])) |
|
76 | 80 | np.testing.assert_equal(id(w.data), id(v.data)) |
|
77 | 81 | nt.assert_equal(w.dtype, v.dtype) |
|
78 | 82 | |
|
79 | 83 | ip.run_cell_magic('R', ' -d datar', 'datar=datapy') |
|
80 | 84 | |
|
81 | 85 | u = ip.run_line_magic('Rget', ' -d datar') |
|
82 | 86 | np.testing.assert_almost_equal(u['x'], v['x']) |
|
83 | 87 | np.testing.assert_almost_equal(u['y'], v['y']) |
|
84 | 88 | nt.assert_true(np.all(u['z'] == v['z'])) |
|
85 | 89 | np.testing.assert_equal(id(u.data), id(v.data)) |
|
86 | 90 | nt.assert_equal(u.dtype, v.dtype) |
|
87 | 91 | |
|
88 | 92 | |
|
89 | 93 | def test_cell_magic(): |
|
90 | 94 | |
|
91 | 95 | ip.push({'x':np.arange(5), 'y':np.array([3,5,4,6,7])}) |
|
92 | 96 | snippet = ''' |
|
93 | 97 | print(summary(a)) |
|
94 | 98 | plot(x, y, pch=23, bg='orange', cex=2) |
|
95 | 99 | plot(x, x) |
|
96 | 100 | print(summary(x)) |
|
97 | 101 | r = resid(a) |
|
98 | 102 | xc = coef(a) |
|
99 | 103 | ''' |
|
100 | 104 | ip.run_cell_magic('R', '-i x,y -o r,xc -w 150 -u mm a=lm(y~x)', snippet) |
|
101 | 105 | np.testing.assert_almost_equal(ip.user_ns['xc'], [3.2, 0.9]) |
|
102 | 106 | np.testing.assert_almost_equal(ip.user_ns['r'], np.array([-0.2, 0.9, -1. , 0.1, 0.2])) |
|
103 | 107 | |
|
104 | 108 | |
|
105 | 109 | def test_rmagic_localscope(): |
|
106 | 110 | ip.push({'x':0}) |
|
107 | 111 | ip.run_line_magic('R', '-i x -o result result <-x+1') |
|
108 | 112 | result = ip.user_ns['result'] |
|
109 | 113 | nt.assert_equal(result[0], 1) |
|
110 | 114 | |
|
111 | 115 | ip.run_cell('''def rmagic_addone(u): |
|
112 | 116 | %R -i u -o result result <- u+1 |
|
113 | 117 | return result[0]''') |
|
114 | 118 | ip.run_cell('result = rmagic_addone(1)') |
|
115 | 119 | result = ip.user_ns['result'] |
|
116 | 120 | nt.assert_equal(result, 2) |
|
117 | 121 | |
|
118 | 122 | nt.assert_raises( |
|
119 | 123 | NameError, |
|
120 | 124 | ip.run_line_magic, |
|
121 | 125 | "R", |
|
122 | 126 | "-i var_not_defined 1+1") |
@@ -1,91 +1,95 | |||
|
1 | 1 | #------------------------------------------------------------------------------- |
|
2 | 2 | # Copyright (C) 2012 The IPython Development Team |
|
3 | 3 | # |
|
4 | 4 | # Distributed under the terms of the BSD License. The full license is in |
|
5 | 5 | # the file COPYING, distributed as part of this software. |
|
6 | 6 | #------------------------------------------------------------------------------- |
|
7 | 7 | |
|
8 | 8 | #----------------------------------------------------------------------------- |
|
9 | 9 | # Imports |
|
10 | 10 | #----------------------------------------------------------------------------- |
|
11 | 11 | from __future__ import print_function |
|
12 | 12 | |
|
13 | 13 | # Standard library imports |
|
14 | from io import StringIO | |
|
15 | 14 | import sys |
|
16 | 15 | import unittest |
|
17 | 16 | |
|
18 | 17 | # Local imports |
|
19 | 18 | from IPython.kernel.inprocess.blocking import BlockingInProcessKernelClient |
|
20 | 19 | from IPython.kernel.inprocess.manager import InProcessKernelManager |
|
21 | 20 | from IPython.kernel.inprocess.ipkernel import InProcessKernel |
|
22 | 21 | from IPython.testing.decorators import skipif_not_matplotlib |
|
23 | 22 | from IPython.utils.io import capture_output |
|
24 | 23 | from IPython.utils import py3compat |
|
25 | 24 | |
|
25 | if py3compat.PY3: | |
|
26 | from io import StringIO | |
|
27 | else: | |
|
28 | from StringIO import StringIO | |
|
29 | ||
|
26 | 30 | #----------------------------------------------------------------------------- |
|
27 | 31 | # Test case |
|
28 | 32 | #----------------------------------------------------------------------------- |
|
29 | 33 | |
|
30 | 34 | class InProcessKernelTestCase(unittest.TestCase): |
|
31 | 35 | |
|
32 | 36 | def setUp(self): |
|
33 | 37 | self.km = InProcessKernelManager() |
|
34 | 38 | self.km.start_kernel() |
|
35 | 39 | self.kc = BlockingInProcessKernelClient(kernel=self.km.kernel) |
|
36 | 40 | self.kc.start_channels() |
|
37 | 41 | |
|
38 | 42 | @skipif_not_matplotlib |
|
39 | 43 | def test_pylab(self): |
|
40 | 44 | """ Does pylab work in the in-process kernel? |
|
41 | 45 | """ |
|
42 | 46 | kc = self.kc |
|
43 | 47 | kc.execute('%pylab') |
|
44 | 48 | msg = get_stream_message(kc) |
|
45 | 49 | self.assert_('matplotlib' in msg['content']['data']) |
|
46 | 50 | |
|
47 | 51 | def test_raw_input(self): |
|
48 | 52 | """ Does the in-process kernel handle raw_input correctly? |
|
49 | 53 | """ |
|
50 | 54 | io = StringIO('foobar\n') |
|
51 | 55 | sys_stdin = sys.stdin |
|
52 | 56 | sys.stdin = io |
|
53 | 57 | try: |
|
54 | 58 | if py3compat.PY3: |
|
55 | 59 | self.kc.execute('x = input()') |
|
56 | 60 | else: |
|
57 | 61 | self.kc.execute('x = raw_input()') |
|
58 | 62 | finally: |
|
59 | 63 | sys.stdin = sys_stdin |
|
60 | 64 | self.assertEqual(self.km.kernel.shell.user_ns.get('x'), 'foobar') |
|
61 | 65 | |
|
62 | 66 | def test_stdout(self): |
|
63 | 67 | """ Does the in-process kernel correctly capture IO? |
|
64 | 68 | """ |
|
65 | 69 | kernel = InProcessKernel() |
|
66 | 70 | |
|
67 | 71 | with capture_output() as io: |
|
68 | 72 | kernel.shell.run_cell('print("foo")') |
|
69 | 73 | self.assertEqual(io.stdout, 'foo\n') |
|
70 | 74 | |
|
71 | 75 | kc = BlockingInProcessKernelClient(kernel=kernel) |
|
72 | 76 | kernel.frontends.append(kc) |
|
73 | 77 | kc.shell_channel.execute('print("bar")') |
|
74 | 78 | msg = get_stream_message(kc) |
|
75 | 79 | self.assertEqual(msg['content']['data'], 'bar\n') |
|
76 | 80 | |
|
77 | 81 | #----------------------------------------------------------------------------- |
|
78 | 82 | # Utility functions |
|
79 | 83 | #----------------------------------------------------------------------------- |
|
80 | 84 | |
|
81 | 85 | def get_stream_message(kernel_client, timeout=5): |
|
82 | 86 | """ Gets a single stream message synchronously from the sub channel. |
|
83 | 87 | """ |
|
84 | 88 | while True: |
|
85 | 89 | msg = kernel_client.get_iopub_msg(timeout=timeout) |
|
86 | 90 | if msg['header']['msg_type'] == 'stream': |
|
87 | 91 | return msg |
|
88 | 92 | |
|
89 | 93 | |
|
90 | 94 | if __name__ == '__main__': |
|
91 | 95 | unittest.main() |
@@ -1,791 +1,797 | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | """ |
|
3 | 3 | Python advanced pretty printer. This pretty printer is intended to |
|
4 | 4 | replace the old `pprint` python module which does not allow developers |
|
5 | 5 | to provide their own pretty print callbacks. |
|
6 | 6 | |
|
7 | 7 | This module is based on ruby's `prettyprint.rb` library by `Tanaka Akira`. |
|
8 | 8 | |
|
9 | 9 | |
|
10 | 10 | Example Usage |
|
11 | 11 | ------------- |
|
12 | 12 | |
|
13 | 13 | To directly print the representation of an object use `pprint`:: |
|
14 | 14 | |
|
15 | 15 | from pretty import pprint |
|
16 | 16 | pprint(complex_object) |
|
17 | 17 | |
|
18 | 18 | To get a string of the output use `pretty`:: |
|
19 | 19 | |
|
20 | 20 | from pretty import pretty |
|
21 | 21 | string = pretty(complex_object) |
|
22 | 22 | |
|
23 | 23 | |
|
24 | 24 | Extending |
|
25 | 25 | --------- |
|
26 | 26 | |
|
27 | 27 | The pretty library allows developers to add pretty printing rules for their |
|
28 | 28 | own objects. This process is straightforward. All you have to do is to |
|
29 | 29 | add a `_repr_pretty_` method to your object and call the methods on the |
|
30 | 30 | pretty printer passed:: |
|
31 | 31 | |
|
32 | 32 | class MyObject(object): |
|
33 | 33 | |
|
34 | 34 | def _repr_pretty_(self, p, cycle): |
|
35 | 35 | ... |
|
36 | 36 | |
|
37 | 37 | Depending on the python version you want to support you have two |
|
38 | 38 | possibilities. The following list shows the python 2.5 version and the |
|
39 | 39 | compatibility one. |
|
40 | 40 | |
|
41 | 41 | |
|
42 | 42 | Here the example implementation of a `_repr_pretty_` method for a list |
|
43 | 43 | subclass for python 2.5 and higher (python 2.5 requires the with statement |
|
44 | 44 | __future__ import):: |
|
45 | 45 | |
|
46 | 46 | class MyList(list): |
|
47 | 47 | |
|
48 | 48 | def _repr_pretty_(self, p, cycle): |
|
49 | 49 | if cycle: |
|
50 | 50 | p.text('MyList(...)') |
|
51 | 51 | else: |
|
52 | 52 | with p.group(8, 'MyList([', '])'): |
|
53 | 53 | for idx, item in enumerate(self): |
|
54 | 54 | if idx: |
|
55 | 55 | p.text(',') |
|
56 | 56 | p.breakable() |
|
57 | 57 | p.pretty(item) |
|
58 | 58 | |
|
59 | 59 | The `cycle` parameter is `True` if pretty detected a cycle. You *have* to |
|
60 | 60 | react to that or the result is an infinite loop. `p.text()` just adds |
|
61 | 61 | non breaking text to the output, `p.breakable()` either adds a whitespace |
|
62 | 62 | or breaks here. If you pass it an argument it's used instead of the |
|
63 | 63 | default space. `p.pretty` prettyprints another object using the pretty print |
|
64 | 64 | method. |
|
65 | 65 | |
|
66 | 66 | The first parameter to the `group` function specifies the extra indentation |
|
67 | 67 | of the next line. In this example the next item will either be not |
|
68 | 68 | breaked (if the items are short enough) or aligned with the right edge of |
|
69 | 69 | the opening bracked of `MyList`. |
|
70 | 70 | |
|
71 | 71 | If you want to support python 2.4 and lower you can use this code:: |
|
72 | 72 | |
|
73 | 73 | class MyList(list): |
|
74 | 74 | |
|
75 | 75 | def _repr_pretty_(self, p, cycle): |
|
76 | 76 | if cycle: |
|
77 | 77 | p.text('MyList(...)') |
|
78 | 78 | else: |
|
79 | 79 | p.begin_group(8, 'MyList([') |
|
80 | 80 | for idx, item in enumerate(self): |
|
81 | 81 | if idx: |
|
82 | 82 | p.text(',') |
|
83 | 83 | p.breakable() |
|
84 | 84 | p.pretty(item) |
|
85 | 85 | p.end_group(8, '])') |
|
86 | 86 | |
|
87 | 87 | If you just want to indent something you can use the group function |
|
88 | 88 | without open / close parameters. Under python 2.5 you can also use this |
|
89 | 89 | code:: |
|
90 | 90 | |
|
91 | 91 | with p.indent(2): |
|
92 | 92 | ... |
|
93 | 93 | |
|
94 | 94 | Or under python2.4 you might want to modify ``p.indentation`` by hand but |
|
95 | 95 | this is rather ugly. |
|
96 | 96 | |
|
97 | 97 | Inheritance diagram: |
|
98 | 98 | |
|
99 | 99 | .. inheritance-diagram:: IPython.lib.pretty |
|
100 | 100 | :parts: 3 |
|
101 | 101 | |
|
102 | 102 | :copyright: 2007 by Armin Ronacher. |
|
103 | 103 | Portions (c) 2009 by Robert Kern. |
|
104 | 104 | :license: BSD License. |
|
105 | 105 | """ |
|
106 | 106 | from __future__ import print_function |
|
107 | 107 | from contextlib import contextmanager |
|
108 | 108 | import sys |
|
109 | 109 | import types |
|
110 | 110 | import re |
|
111 | 111 | import datetime |
|
112 | from io import StringIO | |
|
113 | 112 | from collections import deque |
|
114 | 113 | |
|
114 | from IPython.utils.py3compat import PY3 | |
|
115 | ||
|
116 | if PY3: | |
|
117 | from io import StringIO | |
|
118 | else: | |
|
119 | from StringIO import StringIO | |
|
120 | ||
|
115 | 121 | |
|
116 | 122 | __all__ = ['pretty', 'pprint', 'PrettyPrinter', 'RepresentationPrinter', |
|
117 | 123 | 'for_type', 'for_type_by_name'] |
|
118 | 124 | |
|
119 | 125 | |
|
120 | 126 | _re_pattern_type = type(re.compile('')) |
|
121 | 127 | |
|
122 | 128 | |
|
123 | 129 | def pretty(obj, verbose=False, max_width=79, newline='\n'): |
|
124 | 130 | """ |
|
125 | 131 | Pretty print the object's representation. |
|
126 | 132 | """ |
|
127 | 133 | stream = StringIO() |
|
128 | 134 | printer = RepresentationPrinter(stream, verbose, max_width, newline) |
|
129 | 135 | printer.pretty(obj) |
|
130 | 136 | printer.flush() |
|
131 | 137 | return stream.getvalue() |
|
132 | 138 | |
|
133 | 139 | |
|
134 | 140 | def pprint(obj, verbose=False, max_width=79, newline='\n'): |
|
135 | 141 | """ |
|
136 | 142 | Like `pretty` but print to stdout. |
|
137 | 143 | """ |
|
138 | 144 | printer = RepresentationPrinter(sys.stdout, verbose, max_width, newline) |
|
139 | 145 | printer.pretty(obj) |
|
140 | 146 | printer.flush() |
|
141 | 147 | sys.stdout.write(newline) |
|
142 | 148 | sys.stdout.flush() |
|
143 | 149 | |
|
144 | 150 | class _PrettyPrinterBase(object): |
|
145 | 151 | |
|
146 | 152 | @contextmanager |
|
147 | 153 | def indent(self, indent): |
|
148 | 154 | """with statement support for indenting/dedenting.""" |
|
149 | 155 | self.indentation += indent |
|
150 | 156 | try: |
|
151 | 157 | yield |
|
152 | 158 | finally: |
|
153 | 159 | self.indentation -= indent |
|
154 | 160 | |
|
155 | 161 | @contextmanager |
|
156 | 162 | def group(self, indent=0, open='', close=''): |
|
157 | 163 | """like begin_group / end_group but for the with statement.""" |
|
158 | 164 | self.begin_group(indent, open) |
|
159 | 165 | try: |
|
160 | 166 | yield |
|
161 | 167 | finally: |
|
162 | 168 | self.end_group(indent, close) |
|
163 | 169 | |
|
164 | 170 | class PrettyPrinter(_PrettyPrinterBase): |
|
165 | 171 | """ |
|
166 | 172 | Baseclass for the `RepresentationPrinter` prettyprinter that is used to |
|
167 | 173 | generate pretty reprs of objects. Contrary to the `RepresentationPrinter` |
|
168 | 174 | this printer knows nothing about the default pprinters or the `_repr_pretty_` |
|
169 | 175 | callback method. |
|
170 | 176 | """ |
|
171 | 177 | |
|
172 | 178 | def __init__(self, output, max_width=79, newline='\n'): |
|
173 | 179 | self.output = output |
|
174 | 180 | self.max_width = max_width |
|
175 | 181 | self.newline = newline |
|
176 | 182 | self.output_width = 0 |
|
177 | 183 | self.buffer_width = 0 |
|
178 | 184 | self.buffer = deque() |
|
179 | 185 | |
|
180 | 186 | root_group = Group(0) |
|
181 | 187 | self.group_stack = [root_group] |
|
182 | 188 | self.group_queue = GroupQueue(root_group) |
|
183 | 189 | self.indentation = 0 |
|
184 | 190 | |
|
185 | 191 | def _break_outer_groups(self): |
|
186 | 192 | while self.max_width < self.output_width + self.buffer_width: |
|
187 | 193 | group = self.group_queue.deq() |
|
188 | 194 | if not group: |
|
189 | 195 | return |
|
190 | 196 | while group.breakables: |
|
191 | 197 | x = self.buffer.popleft() |
|
192 | 198 | self.output_width = x.output(self.output, self.output_width) |
|
193 | 199 | self.buffer_width -= x.width |
|
194 | 200 | while self.buffer and isinstance(self.buffer[0], Text): |
|
195 | 201 | x = self.buffer.popleft() |
|
196 | 202 | self.output_width = x.output(self.output, self.output_width) |
|
197 | 203 | self.buffer_width -= x.width |
|
198 | 204 | |
|
199 | 205 | def text(self, obj): |
|
200 | 206 | """Add literal text to the output.""" |
|
201 | 207 | width = len(obj) |
|
202 | 208 | if self.buffer: |
|
203 | 209 | text = self.buffer[-1] |
|
204 | 210 | if not isinstance(text, Text): |
|
205 | 211 | text = Text() |
|
206 | 212 | self.buffer.append(text) |
|
207 | 213 | text.add(obj, width) |
|
208 | 214 | self.buffer_width += width |
|
209 | 215 | self._break_outer_groups() |
|
210 | 216 | else: |
|
211 | 217 | self.output.write(obj) |
|
212 | 218 | self.output_width += width |
|
213 | 219 | |
|
214 | 220 | def breakable(self, sep=' '): |
|
215 | 221 | """ |
|
216 | 222 | Add a breakable separator to the output. This does not mean that it |
|
217 | 223 | will automatically break here. If no breaking on this position takes |
|
218 | 224 | place the `sep` is inserted which default to one space. |
|
219 | 225 | """ |
|
220 | 226 | width = len(sep) |
|
221 | 227 | group = self.group_stack[-1] |
|
222 | 228 | if group.want_break: |
|
223 | 229 | self.flush() |
|
224 | 230 | self.output.write(self.newline) |
|
225 | 231 | self.output.write(' ' * self.indentation) |
|
226 | 232 | self.output_width = self.indentation |
|
227 | 233 | self.buffer_width = 0 |
|
228 | 234 | else: |
|
229 | 235 | self.buffer.append(Breakable(sep, width, self)) |
|
230 | 236 | self.buffer_width += width |
|
231 | 237 | self._break_outer_groups() |
|
232 | 238 | |
|
233 | 239 | def break_(self): |
|
234 | 240 | """ |
|
235 | 241 | Explicitly insert a newline into the output, maintaining correct indentation. |
|
236 | 242 | """ |
|
237 | 243 | self.flush() |
|
238 | 244 | self.output.write(self.newline) |
|
239 | 245 | self.output.write(' ' * self.indentation) |
|
240 | 246 | self.output_width = self.indentation |
|
241 | 247 | self.buffer_width = 0 |
|
242 | 248 | |
|
243 | 249 | |
|
244 | 250 | def begin_group(self, indent=0, open=''): |
|
245 | 251 | """ |
|
246 | 252 | Begin a group. If you want support for python < 2.5 which doesn't has |
|
247 | 253 | the with statement this is the preferred way: |
|
248 | 254 | |
|
249 | 255 | p.begin_group(1, '{') |
|
250 | 256 | ... |
|
251 | 257 | p.end_group(1, '}') |
|
252 | 258 | |
|
253 | 259 | The python 2.5 expression would be this: |
|
254 | 260 | |
|
255 | 261 | with p.group(1, '{', '}'): |
|
256 | 262 | ... |
|
257 | 263 | |
|
258 | 264 | The first parameter specifies the indentation for the next line (usually |
|
259 | 265 | the width of the opening text), the second the opening text. All |
|
260 | 266 | parameters are optional. |
|
261 | 267 | """ |
|
262 | 268 | if open: |
|
263 | 269 | self.text(open) |
|
264 | 270 | group = Group(self.group_stack[-1].depth + 1) |
|
265 | 271 | self.group_stack.append(group) |
|
266 | 272 | self.group_queue.enq(group) |
|
267 | 273 | self.indentation += indent |
|
268 | 274 | |
|
269 | 275 | def end_group(self, dedent=0, close=''): |
|
270 | 276 | """End a group. See `begin_group` for more details.""" |
|
271 | 277 | self.indentation -= dedent |
|
272 | 278 | group = self.group_stack.pop() |
|
273 | 279 | if not group.breakables: |
|
274 | 280 | self.group_queue.remove(group) |
|
275 | 281 | if close: |
|
276 | 282 | self.text(close) |
|
277 | 283 | |
|
278 | 284 | def flush(self): |
|
279 | 285 | """Flush data that is left in the buffer.""" |
|
280 | 286 | for data in self.buffer: |
|
281 | 287 | self.output_width += data.output(self.output, self.output_width) |
|
282 | 288 | self.buffer.clear() |
|
283 | 289 | self.buffer_width = 0 |
|
284 | 290 | |
|
285 | 291 | |
|
286 | 292 | def _get_mro(obj_class): |
|
287 | 293 | """ Get a reasonable method resolution order of a class and its superclasses |
|
288 | 294 | for both old-style and new-style classes. |
|
289 | 295 | """ |
|
290 | 296 | if not hasattr(obj_class, '__mro__'): |
|
291 | 297 | # Old-style class. Mix in object to make a fake new-style class. |
|
292 | 298 | try: |
|
293 | 299 | obj_class = type(obj_class.__name__, (obj_class, object), {}) |
|
294 | 300 | except TypeError: |
|
295 | 301 | # Old-style extension type that does not descend from object. |
|
296 | 302 | # FIXME: try to construct a more thorough MRO. |
|
297 | 303 | mro = [obj_class] |
|
298 | 304 | else: |
|
299 | 305 | mro = obj_class.__mro__[1:-1] |
|
300 | 306 | else: |
|
301 | 307 | mro = obj_class.__mro__ |
|
302 | 308 | return mro |
|
303 | 309 | |
|
304 | 310 | |
|
305 | 311 | class RepresentationPrinter(PrettyPrinter): |
|
306 | 312 | """ |
|
307 | 313 | Special pretty printer that has a `pretty` method that calls the pretty |
|
308 | 314 | printer for a python object. |
|
309 | 315 | |
|
310 | 316 | This class stores processing data on `self` so you must *never* use |
|
311 | 317 | this class in a threaded environment. Always lock it or reinstanciate |
|
312 | 318 | it. |
|
313 | 319 | |
|
314 | 320 | Instances also have a verbose flag callbacks can access to control their |
|
315 | 321 | output. For example the default instance repr prints all attributes and |
|
316 | 322 | methods that are not prefixed by an underscore if the printer is in |
|
317 | 323 | verbose mode. |
|
318 | 324 | """ |
|
319 | 325 | |
|
320 | 326 | def __init__(self, output, verbose=False, max_width=79, newline='\n', |
|
321 | 327 | singleton_pprinters=None, type_pprinters=None, deferred_pprinters=None): |
|
322 | 328 | |
|
323 | 329 | PrettyPrinter.__init__(self, output, max_width, newline) |
|
324 | 330 | self.verbose = verbose |
|
325 | 331 | self.stack = [] |
|
326 | 332 | if singleton_pprinters is None: |
|
327 | 333 | singleton_pprinters = _singleton_pprinters.copy() |
|
328 | 334 | self.singleton_pprinters = singleton_pprinters |
|
329 | 335 | if type_pprinters is None: |
|
330 | 336 | type_pprinters = _type_pprinters.copy() |
|
331 | 337 | self.type_pprinters = type_pprinters |
|
332 | 338 | if deferred_pprinters is None: |
|
333 | 339 | deferred_pprinters = _deferred_type_pprinters.copy() |
|
334 | 340 | self.deferred_pprinters = deferred_pprinters |
|
335 | 341 | |
|
336 | 342 | def pretty(self, obj): |
|
337 | 343 | """Pretty print the given object.""" |
|
338 | 344 | obj_id = id(obj) |
|
339 | 345 | cycle = obj_id in self.stack |
|
340 | 346 | self.stack.append(obj_id) |
|
341 | 347 | self.begin_group() |
|
342 | 348 | try: |
|
343 | 349 | obj_class = getattr(obj, '__class__', None) or type(obj) |
|
344 | 350 | # First try to find registered singleton printers for the type. |
|
345 | 351 | try: |
|
346 | 352 | printer = self.singleton_pprinters[obj_id] |
|
347 | 353 | except (TypeError, KeyError): |
|
348 | 354 | pass |
|
349 | 355 | else: |
|
350 | 356 | return printer(obj, self, cycle) |
|
351 | 357 | # Next walk the mro and check for either: |
|
352 | 358 | # 1) a registered printer |
|
353 | 359 | # 2) a _repr_pretty_ method |
|
354 | 360 | for cls in _get_mro(obj_class): |
|
355 | 361 | if cls in self.type_pprinters: |
|
356 | 362 | # printer registered in self.type_pprinters |
|
357 | 363 | return self.type_pprinters[cls](obj, self, cycle) |
|
358 | 364 | else: |
|
359 | 365 | # deferred printer |
|
360 | 366 | printer = self._in_deferred_types(cls) |
|
361 | 367 | if printer is not None: |
|
362 | 368 | return printer(obj, self, cycle) |
|
363 | 369 | else: |
|
364 | 370 | # Finally look for special method names. |
|
365 | 371 | # Some objects automatically create any requested |
|
366 | 372 | # attribute. Try to ignore most of them by checking for |
|
367 | 373 | # callability. |
|
368 | 374 | if '_repr_pretty_' in cls.__dict__: |
|
369 | 375 | meth = cls._repr_pretty_ |
|
370 | 376 | if callable(meth): |
|
371 | 377 | return meth(obj, self, cycle) |
|
372 | 378 | return _default_pprint(obj, self, cycle) |
|
373 | 379 | finally: |
|
374 | 380 | self.end_group() |
|
375 | 381 | self.stack.pop() |
|
376 | 382 | |
|
377 | 383 | def _in_deferred_types(self, cls): |
|
378 | 384 | """ |
|
379 | 385 | Check if the given class is specified in the deferred type registry. |
|
380 | 386 | |
|
381 | 387 | Returns the printer from the registry if it exists, and None if the |
|
382 | 388 | class is not in the registry. Successful matches will be moved to the |
|
383 | 389 | regular type registry for future use. |
|
384 | 390 | """ |
|
385 | 391 | mod = getattr(cls, '__module__', None) |
|
386 | 392 | name = getattr(cls, '__name__', None) |
|
387 | 393 | key = (mod, name) |
|
388 | 394 | printer = None |
|
389 | 395 | if key in self.deferred_pprinters: |
|
390 | 396 | # Move the printer over to the regular registry. |
|
391 | 397 | printer = self.deferred_pprinters.pop(key) |
|
392 | 398 | self.type_pprinters[cls] = printer |
|
393 | 399 | return printer |
|
394 | 400 | |
|
395 | 401 | |
|
396 | 402 | class Printable(object): |
|
397 | 403 | |
|
398 | 404 | def output(self, stream, output_width): |
|
399 | 405 | return output_width |
|
400 | 406 | |
|
401 | 407 | |
|
402 | 408 | class Text(Printable): |
|
403 | 409 | |
|
404 | 410 | def __init__(self): |
|
405 | 411 | self.objs = [] |
|
406 | 412 | self.width = 0 |
|
407 | 413 | |
|
408 | 414 | def output(self, stream, output_width): |
|
409 | 415 | for obj in self.objs: |
|
410 | 416 | stream.write(obj) |
|
411 | 417 | return output_width + self.width |
|
412 | 418 | |
|
413 | 419 | def add(self, obj, width): |
|
414 | 420 | self.objs.append(obj) |
|
415 | 421 | self.width += width |
|
416 | 422 | |
|
417 | 423 | |
|
418 | 424 | class Breakable(Printable): |
|
419 | 425 | |
|
420 | 426 | def __init__(self, seq, width, pretty): |
|
421 | 427 | self.obj = seq |
|
422 | 428 | self.width = width |
|
423 | 429 | self.pretty = pretty |
|
424 | 430 | self.indentation = pretty.indentation |
|
425 | 431 | self.group = pretty.group_stack[-1] |
|
426 | 432 | self.group.breakables.append(self) |
|
427 | 433 | |
|
428 | 434 | def output(self, stream, output_width): |
|
429 | 435 | self.group.breakables.popleft() |
|
430 | 436 | if self.group.want_break: |
|
431 | 437 | stream.write(self.pretty.newline) |
|
432 | 438 | stream.write(' ' * self.indentation) |
|
433 | 439 | return self.indentation |
|
434 | 440 | if not self.group.breakables: |
|
435 | 441 | self.pretty.group_queue.remove(self.group) |
|
436 | 442 | stream.write(self.obj) |
|
437 | 443 | return output_width + self.width |
|
438 | 444 | |
|
439 | 445 | |
|
440 | 446 | class Group(Printable): |
|
441 | 447 | |
|
442 | 448 | def __init__(self, depth): |
|
443 | 449 | self.depth = depth |
|
444 | 450 | self.breakables = deque() |
|
445 | 451 | self.want_break = False |
|
446 | 452 | |
|
447 | 453 | |
|
448 | 454 | class GroupQueue(object): |
|
449 | 455 | |
|
450 | 456 | def __init__(self, *groups): |
|
451 | 457 | self.queue = [] |
|
452 | 458 | for group in groups: |
|
453 | 459 | self.enq(group) |
|
454 | 460 | |
|
455 | 461 | def enq(self, group): |
|
456 | 462 | depth = group.depth |
|
457 | 463 | while depth > len(self.queue) - 1: |
|
458 | 464 | self.queue.append([]) |
|
459 | 465 | self.queue[depth].append(group) |
|
460 | 466 | |
|
461 | 467 | def deq(self): |
|
462 | 468 | for stack in self.queue: |
|
463 | 469 | for idx, group in enumerate(reversed(stack)): |
|
464 | 470 | if group.breakables: |
|
465 | 471 | del stack[idx] |
|
466 | 472 | group.want_break = True |
|
467 | 473 | return group |
|
468 | 474 | for group in stack: |
|
469 | 475 | group.want_break = True |
|
470 | 476 | del stack[:] |
|
471 | 477 | |
|
472 | 478 | def remove(self, group): |
|
473 | 479 | try: |
|
474 | 480 | self.queue[group.depth].remove(group) |
|
475 | 481 | except ValueError: |
|
476 | 482 | pass |
|
477 | 483 | |
|
478 | 484 | try: |
|
479 | 485 | _baseclass_reprs = (object.__repr__, types.InstanceType.__repr__) |
|
480 | 486 | except AttributeError: # Python 3 |
|
481 | 487 | _baseclass_reprs = (object.__repr__,) |
|
482 | 488 | |
|
483 | 489 | |
|
484 | 490 | def _default_pprint(obj, p, cycle): |
|
485 | 491 | """ |
|
486 | 492 | The default print function. Used if an object does not provide one and |
|
487 | 493 | it's none of the builtin objects. |
|
488 | 494 | """ |
|
489 | 495 | klass = getattr(obj, '__class__', None) or type(obj) |
|
490 | 496 | if getattr(klass, '__repr__', None) not in _baseclass_reprs: |
|
491 | 497 | # A user-provided repr. Find newlines and replace them with p.break_() |
|
492 | 498 | output = repr(obj) |
|
493 | 499 | for idx,output_line in enumerate(output.splitlines()): |
|
494 | 500 | if idx: |
|
495 | 501 | p.break_() |
|
496 | 502 | p.text(output_line) |
|
497 | 503 | return |
|
498 | 504 | p.begin_group(1, '<') |
|
499 | 505 | p.pretty(klass) |
|
500 | 506 | p.text(' at 0x%x' % id(obj)) |
|
501 | 507 | if cycle: |
|
502 | 508 | p.text(' ...') |
|
503 | 509 | elif p.verbose: |
|
504 | 510 | first = True |
|
505 | 511 | for key in dir(obj): |
|
506 | 512 | if not key.startswith('_'): |
|
507 | 513 | try: |
|
508 | 514 | value = getattr(obj, key) |
|
509 | 515 | except AttributeError: |
|
510 | 516 | continue |
|
511 | 517 | if isinstance(value, types.MethodType): |
|
512 | 518 | continue |
|
513 | 519 | if not first: |
|
514 | 520 | p.text(',') |
|
515 | 521 | p.breakable() |
|
516 | 522 | p.text(key) |
|
517 | 523 | p.text('=') |
|
518 | 524 | step = len(key) + 1 |
|
519 | 525 | p.indentation += step |
|
520 | 526 | p.pretty(value) |
|
521 | 527 | p.indentation -= step |
|
522 | 528 | first = False |
|
523 | 529 | p.end_group(1, '>') |
|
524 | 530 | |
|
525 | 531 | |
|
526 | 532 | def _seq_pprinter_factory(start, end, basetype): |
|
527 | 533 | """ |
|
528 | 534 | Factory that returns a pprint function useful for sequences. Used by |
|
529 | 535 | the default pprint for tuples, dicts, and lists. |
|
530 | 536 | """ |
|
531 | 537 | def inner(obj, p, cycle): |
|
532 | 538 | typ = type(obj) |
|
533 | 539 | if basetype is not None and typ is not basetype and typ.__repr__ != basetype.__repr__: |
|
534 | 540 | # If the subclass provides its own repr, use it instead. |
|
535 | 541 | return p.text(typ.__repr__(obj)) |
|
536 | 542 | |
|
537 | 543 | if cycle: |
|
538 | 544 | return p.text(start + '...' + end) |
|
539 | 545 | step = len(start) |
|
540 | 546 | p.begin_group(step, start) |
|
541 | 547 | for idx, x in enumerate(obj): |
|
542 | 548 | if idx: |
|
543 | 549 | p.text(',') |
|
544 | 550 | p.breakable() |
|
545 | 551 | p.pretty(x) |
|
546 | 552 | if len(obj) == 1 and type(obj) is tuple: |
|
547 | 553 | # Special case for 1-item tuples. |
|
548 | 554 | p.text(',') |
|
549 | 555 | p.end_group(step, end) |
|
550 | 556 | return inner |
|
551 | 557 | |
|
552 | 558 | |
|
553 | 559 | def _set_pprinter_factory(start, end, basetype): |
|
554 | 560 | """ |
|
555 | 561 | Factory that returns a pprint function useful for sets and frozensets. |
|
556 | 562 | """ |
|
557 | 563 | def inner(obj, p, cycle): |
|
558 | 564 | typ = type(obj) |
|
559 | 565 | if basetype is not None and typ is not basetype and typ.__repr__ != basetype.__repr__: |
|
560 | 566 | # If the subclass provides its own repr, use it instead. |
|
561 | 567 | return p.text(typ.__repr__(obj)) |
|
562 | 568 | |
|
563 | 569 | if cycle: |
|
564 | 570 | return p.text(start + '...' + end) |
|
565 | 571 | if len(obj) == 0: |
|
566 | 572 | # Special case. |
|
567 | 573 | p.text(basetype.__name__ + '()') |
|
568 | 574 | else: |
|
569 | 575 | step = len(start) |
|
570 | 576 | p.begin_group(step, start) |
|
571 | 577 | # Like dictionary keys, we will try to sort the items. |
|
572 | 578 | items = list(obj) |
|
573 | 579 | try: |
|
574 | 580 | items.sort() |
|
575 | 581 | except Exception: |
|
576 | 582 | # Sometimes the items don't sort. |
|
577 | 583 | pass |
|
578 | 584 | for idx, x in enumerate(items): |
|
579 | 585 | if idx: |
|
580 | 586 | p.text(',') |
|
581 | 587 | p.breakable() |
|
582 | 588 | p.pretty(x) |
|
583 | 589 | p.end_group(step, end) |
|
584 | 590 | return inner |
|
585 | 591 | |
|
586 | 592 | |
|
587 | 593 | def _dict_pprinter_factory(start, end, basetype=None): |
|
588 | 594 | """ |
|
589 | 595 | Factory that returns a pprint function used by the default pprint of |
|
590 | 596 | dicts and dict proxies. |
|
591 | 597 | """ |
|
592 | 598 | def inner(obj, p, cycle): |
|
593 | 599 | typ = type(obj) |
|
594 | 600 | if basetype is not None and typ is not basetype and typ.__repr__ != basetype.__repr__: |
|
595 | 601 | # If the subclass provides its own repr, use it instead. |
|
596 | 602 | return p.text(typ.__repr__(obj)) |
|
597 | 603 | |
|
598 | 604 | if cycle: |
|
599 | 605 | return p.text('{...}') |
|
600 | 606 | p.begin_group(1, start) |
|
601 | 607 | keys = obj.keys() |
|
602 | 608 | try: |
|
603 | 609 | keys.sort() |
|
604 | 610 | except Exception as e: |
|
605 | 611 | # Sometimes the keys don't sort. |
|
606 | 612 | pass |
|
607 | 613 | for idx, key in enumerate(keys): |
|
608 | 614 | if idx: |
|
609 | 615 | p.text(',') |
|
610 | 616 | p.breakable() |
|
611 | 617 | p.pretty(key) |
|
612 | 618 | p.text(': ') |
|
613 | 619 | p.pretty(obj[key]) |
|
614 | 620 | p.end_group(1, end) |
|
615 | 621 | return inner |
|
616 | 622 | |
|
617 | 623 | |
|
618 | 624 | def _super_pprint(obj, p, cycle): |
|
619 | 625 | """The pprint for the super type.""" |
|
620 | 626 | p.begin_group(8, '<super: ') |
|
621 | 627 | p.pretty(obj.__self_class__) |
|
622 | 628 | p.text(',') |
|
623 | 629 | p.breakable() |
|
624 | 630 | p.pretty(obj.__self__) |
|
625 | 631 | p.end_group(8, '>') |
|
626 | 632 | |
|
627 | 633 | |
|
628 | 634 | def _re_pattern_pprint(obj, p, cycle): |
|
629 | 635 | """The pprint function for regular expression patterns.""" |
|
630 | 636 | p.text('re.compile(') |
|
631 | 637 | pattern = repr(obj.pattern) |
|
632 | 638 | if pattern[:1] in 'uU': |
|
633 | 639 | pattern = pattern[1:] |
|
634 | 640 | prefix = 'ur' |
|
635 | 641 | else: |
|
636 | 642 | prefix = 'r' |
|
637 | 643 | pattern = prefix + pattern.replace('\\\\', '\\') |
|
638 | 644 | p.text(pattern) |
|
639 | 645 | if obj.flags: |
|
640 | 646 | p.text(',') |
|
641 | 647 | p.breakable() |
|
642 | 648 | done_one = False |
|
643 | 649 | for flag in ('TEMPLATE', 'IGNORECASE', 'LOCALE', 'MULTILINE', 'DOTALL', |
|
644 | 650 | 'UNICODE', 'VERBOSE', 'DEBUG'): |
|
645 | 651 | if obj.flags & getattr(re, flag): |
|
646 | 652 | if done_one: |
|
647 | 653 | p.text('|') |
|
648 | 654 | p.text('re.' + flag) |
|
649 | 655 | done_one = True |
|
650 | 656 | p.text(')') |
|
651 | 657 | |
|
652 | 658 | |
|
653 | 659 | def _type_pprint(obj, p, cycle): |
|
654 | 660 | """The pprint for classes and types.""" |
|
655 | 661 | mod = getattr(obj, '__module__', None) |
|
656 | 662 | if mod is None: |
|
657 | 663 | # Heap allocated types might not have the module attribute, |
|
658 | 664 | # and others may set it to None. |
|
659 | 665 | return p.text(obj.__name__) |
|
660 | 666 | |
|
661 | 667 | if mod in ('__builtin__', 'builtins', 'exceptions'): |
|
662 | 668 | name = obj.__name__ |
|
663 | 669 | else: |
|
664 | 670 | name = mod + '.' + obj.__name__ |
|
665 | 671 | p.text(name) |
|
666 | 672 | |
|
667 | 673 | |
|
668 | 674 | def _repr_pprint(obj, p, cycle): |
|
669 | 675 | """A pprint that just redirects to the normal repr function.""" |
|
670 | 676 | p.text(repr(obj)) |
|
671 | 677 | |
|
672 | 678 | |
|
673 | 679 | def _function_pprint(obj, p, cycle): |
|
674 | 680 | """Base pprint for all functions and builtin functions.""" |
|
675 | 681 | if obj.__module__ in ('__builtin__', 'builtins', 'exceptions') or not obj.__module__: |
|
676 | 682 | name = obj.__name__ |
|
677 | 683 | else: |
|
678 | 684 | name = obj.__module__ + '.' + obj.__name__ |
|
679 | 685 | p.text('<function %s>' % name) |
|
680 | 686 | |
|
681 | 687 | |
|
682 | 688 | def _exception_pprint(obj, p, cycle): |
|
683 | 689 | """Base pprint for all exceptions.""" |
|
684 | 690 | if obj.__class__.__module__ in ('exceptions', 'builtins'): |
|
685 | 691 | name = obj.__class__.__name__ |
|
686 | 692 | else: |
|
687 | 693 | name = '%s.%s' % ( |
|
688 | 694 | obj.__class__.__module__, |
|
689 | 695 | obj.__class__.__name__ |
|
690 | 696 | ) |
|
691 | 697 | step = len(name) + 1 |
|
692 | 698 | p.begin_group(step, name + '(') |
|
693 | 699 | for idx, arg in enumerate(getattr(obj, 'args', ())): |
|
694 | 700 | if idx: |
|
695 | 701 | p.text(',') |
|
696 | 702 | p.breakable() |
|
697 | 703 | p.pretty(arg) |
|
698 | 704 | p.end_group(step, ')') |
|
699 | 705 | |
|
700 | 706 | |
|
701 | 707 | #: the exception base |
|
702 | 708 | try: |
|
703 | 709 | _exception_base = BaseException |
|
704 | 710 | except NameError: |
|
705 | 711 | _exception_base = Exception |
|
706 | 712 | |
|
707 | 713 | |
|
708 | 714 | #: printers for builtin types |
|
709 | 715 | _type_pprinters = { |
|
710 | 716 | int: _repr_pprint, |
|
711 | 717 | float: _repr_pprint, |
|
712 | 718 | str: _repr_pprint, |
|
713 | 719 | tuple: _seq_pprinter_factory('(', ')', tuple), |
|
714 | 720 | list: _seq_pprinter_factory('[', ']', list), |
|
715 | 721 | dict: _dict_pprinter_factory('{', '}', dict), |
|
716 | 722 | |
|
717 | 723 | set: _set_pprinter_factory('{', '}', set), |
|
718 | 724 | frozenset: _set_pprinter_factory('frozenset({', '})', frozenset), |
|
719 | 725 | super: _super_pprint, |
|
720 | 726 | _re_pattern_type: _re_pattern_pprint, |
|
721 | 727 | type: _type_pprint, |
|
722 | 728 | types.FunctionType: _function_pprint, |
|
723 | 729 | types.BuiltinFunctionType: _function_pprint, |
|
724 | 730 | types.MethodType: _repr_pprint, |
|
725 | 731 | |
|
726 | 732 | datetime.datetime: _repr_pprint, |
|
727 | 733 | datetime.timedelta: _repr_pprint, |
|
728 | 734 | _exception_base: _exception_pprint |
|
729 | 735 | } |
|
730 | 736 | |
|
731 | 737 | try: |
|
732 | 738 | _type_pprinters[types.DictProxyType] = _dict_pprinter_factory('<dictproxy {', '}>') |
|
733 | 739 | _type_pprinters[types.ClassType] = _type_pprint |
|
734 | 740 | _type_pprinters[types.SliceType] = _repr_pprint |
|
735 | 741 | except AttributeError: # Python 3 |
|
736 | 742 | _type_pprinters[slice] = _repr_pprint |
|
737 | 743 | |
|
738 | 744 | try: |
|
739 | 745 | _type_pprinters[xrange] = _repr_pprint |
|
740 | 746 | _type_pprinters[long] = _repr_pprint |
|
741 | 747 | _type_pprinters[unicode] = _repr_pprint |
|
742 | 748 | except NameError: |
|
743 | 749 | _type_pprinters[range] = _repr_pprint |
|
744 | 750 | _type_pprinters[bytes] = _repr_pprint |
|
745 | 751 | |
|
746 | 752 | #: printers for types specified by name |
|
747 | 753 | _deferred_type_pprinters = { |
|
748 | 754 | } |
|
749 | 755 | |
|
750 | 756 | def for_type(typ, func): |
|
751 | 757 | """ |
|
752 | 758 | Add a pretty printer for a given type. |
|
753 | 759 | """ |
|
754 | 760 | oldfunc = _type_pprinters.get(typ, None) |
|
755 | 761 | if func is not None: |
|
756 | 762 | # To support easy restoration of old pprinters, we need to ignore Nones. |
|
757 | 763 | _type_pprinters[typ] = func |
|
758 | 764 | return oldfunc |
|
759 | 765 | |
|
760 | 766 | def for_type_by_name(type_module, type_name, func): |
|
761 | 767 | """ |
|
762 | 768 | Add a pretty printer for a type specified by the module and name of a type |
|
763 | 769 | rather than the type object itself. |
|
764 | 770 | """ |
|
765 | 771 | key = (type_module, type_name) |
|
766 | 772 | oldfunc = _deferred_type_pprinters.get(key, None) |
|
767 | 773 | if func is not None: |
|
768 | 774 | # To support easy restoration of old pprinters, we need to ignore Nones. |
|
769 | 775 | _deferred_type_pprinters[key] = func |
|
770 | 776 | return oldfunc |
|
771 | 777 | |
|
772 | 778 | |
|
773 | 779 | #: printers for the default singletons |
|
774 | 780 | _singleton_pprinters = dict.fromkeys(map(id, [None, True, False, Ellipsis, |
|
775 | 781 | NotImplemented]), _repr_pprint) |
|
776 | 782 | |
|
777 | 783 | |
|
778 | 784 | if __name__ == '__main__': |
|
779 | 785 | from random import randrange |
|
780 | 786 | class Foo(object): |
|
781 | 787 | def __init__(self): |
|
782 | 788 | self.foo = 1 |
|
783 | 789 | self.bar = re.compile(r'\s+') |
|
784 | 790 | self.blub = dict.fromkeys(range(30), randrange(1, 40)) |
|
785 | 791 | self.hehe = 23424.234234 |
|
786 | 792 | self.list = ["blub", "blah", self] |
|
787 | 793 | |
|
788 | 794 | def get_foo(self): |
|
789 | 795 | print("foo") |
|
790 | 796 | |
|
791 | 797 | pprint(Foo(), verbose=True) |
@@ -1,180 +1,184 | |||
|
1 | 1 | """Test suite for the irunner module. |
|
2 | 2 | |
|
3 | 3 | Not the most elegant or fine-grained, but it does cover at least the bulk |
|
4 | 4 | functionality.""" |
|
5 | 5 | from __future__ import print_function |
|
6 | 6 | |
|
7 | 7 | # Global to make tests extra verbose and help debugging |
|
8 | 8 | VERBOSE = True |
|
9 | 9 | |
|
10 | 10 | # stdlib imports |
|
11 | import io | |
|
12 | 11 | import sys |
|
13 | 12 | import unittest |
|
14 | 13 | |
|
15 | 14 | # IPython imports |
|
16 | 15 | from IPython.lib import irunner |
|
17 | from IPython.utils.py3compat import doctest_refactor_print | |
|
16 | from IPython.utils.py3compat import doctest_refactor_print, PY3 | |
|
17 | ||
|
18 | if PY3: | |
|
19 | from io import StringIO | |
|
20 | else: | |
|
21 | from StringIO import StringIO | |
|
18 | 22 | |
|
19 | 23 | # Testing code begins |
|
20 | 24 | class RunnerTestCase(unittest.TestCase): |
|
21 | 25 | |
|
22 | 26 | def setUp(self): |
|
23 |
self.out = |
|
|
27 | self.out = StringIO() | |
|
24 | 28 | #self.out = sys.stdout |
|
25 | 29 | |
|
26 | 30 | def _test_runner(self,runner,source,output): |
|
27 | 31 | """Test that a given runner's input/output match.""" |
|
28 | 32 | |
|
29 | 33 | runner.run_source(source) |
|
30 | 34 | out = self.out.getvalue() |
|
31 | 35 | #out = '' |
|
32 | 36 | # this output contains nasty \r\n lineends, and the initial ipython |
|
33 | 37 | # banner. clean it up for comparison, removing lines of whitespace |
|
34 | 38 | output_l = [l for l in output.splitlines() if l and not l.isspace()] |
|
35 | 39 | out_l = [l for l in out.splitlines() if l and not l.isspace()] |
|
36 | 40 | mismatch = 0 |
|
37 | 41 | if len(output_l) != len(out_l): |
|
38 | 42 | message = ("Mismatch in number of lines\n\n" |
|
39 | 43 | "Expected:\n" |
|
40 | 44 | "~~~~~~~~~\n" |
|
41 | 45 | "%s\n\n" |
|
42 | 46 | "Got:\n" |
|
43 | 47 | "~~~~~~~~~\n" |
|
44 | 48 | "%s" |
|
45 | 49 | ) % ("\n".join(output_l), "\n".join(out_l)) |
|
46 | 50 | self.fail(message) |
|
47 | 51 | for n in range(len(output_l)): |
|
48 | 52 | # Do a line-by-line comparison |
|
49 | 53 | ol1 = output_l[n].strip() |
|
50 | 54 | ol2 = out_l[n].strip() |
|
51 | 55 | if ol1 != ol2: |
|
52 | 56 | mismatch += 1 |
|
53 | 57 | if VERBOSE: |
|
54 | 58 | print('<<< line %s does not match:' % n) |
|
55 | 59 | print(repr(ol1)) |
|
56 | 60 | print(repr(ol2)) |
|
57 | 61 | print('>>>') |
|
58 | 62 | self.assertTrue(mismatch==0,'Number of mismatched lines: %s' % |
|
59 | 63 | mismatch) |
|
60 | 64 | |
|
61 | 65 | def testIPython(self): |
|
62 | 66 | """Test the IPython runner.""" |
|
63 | 67 | source = doctest_refactor_print(""" |
|
64 | 68 | print 'hello, this is python' |
|
65 | 69 | # some more code |
|
66 | 70 | x=1;y=2 |
|
67 | 71 | x+y**2 |
|
68 | 72 | |
|
69 | 73 | # An example of autocall functionality |
|
70 | 74 | from math import * |
|
71 | 75 | autocall 1 |
|
72 | 76 | cos pi |
|
73 | 77 | autocall 0 |
|
74 | 78 | cos pi |
|
75 | 79 | cos(pi) |
|
76 | 80 | |
|
77 | 81 | for i in range(5): |
|
78 | 82 | print i |
|
79 | 83 | |
|
80 | 84 | print "that's all folks!" |
|
81 | 85 | |
|
82 | 86 | exit |
|
83 | 87 | """) |
|
84 | 88 | output = doctest_refactor_print("""\ |
|
85 | 89 | In [1]: print 'hello, this is python' |
|
86 | 90 | hello, this is python |
|
87 | 91 | |
|
88 | 92 | |
|
89 | 93 | # some more code |
|
90 | 94 | In [2]: x=1;y=2 |
|
91 | 95 | |
|
92 | 96 | In [3]: x+y**2 |
|
93 | 97 | Out[3]: 5 |
|
94 | 98 | |
|
95 | 99 | |
|
96 | 100 | # An example of autocall functionality |
|
97 | 101 | In [4]: from math import * |
|
98 | 102 | |
|
99 | 103 | In [5]: autocall 1 |
|
100 | 104 | Automatic calling is: Smart |
|
101 | 105 | |
|
102 | 106 | In [6]: cos pi |
|
103 | 107 | ------> cos(pi) |
|
104 | 108 | Out[6]: -1.0 |
|
105 | 109 | |
|
106 | 110 | In [7]: autocall 0 |
|
107 | 111 | Automatic calling is: OFF |
|
108 | 112 | |
|
109 | 113 | In [8]: cos pi |
|
110 | 114 | File "<ipython-input-8-6bd7313dd9a9>", line 1 |
|
111 | 115 | cos pi |
|
112 | 116 | ^ |
|
113 | 117 | SyntaxError: invalid syntax |
|
114 | 118 | |
|
115 | 119 | |
|
116 | 120 | In [9]: cos(pi) |
|
117 | 121 | Out[9]: -1.0 |
|
118 | 122 | |
|
119 | 123 | |
|
120 | 124 | In [10]: for i in range(5): |
|
121 | 125 | ....: print i |
|
122 | 126 | ....: |
|
123 | 127 | 0 |
|
124 | 128 | 1 |
|
125 | 129 | 2 |
|
126 | 130 | 3 |
|
127 | 131 | 4 |
|
128 | 132 | |
|
129 | 133 | In [11]: print "that's all folks!" |
|
130 | 134 | that's all folks! |
|
131 | 135 | |
|
132 | 136 | |
|
133 | 137 | In [12]: exit |
|
134 | 138 | """) |
|
135 | 139 | runner = irunner.IPythonRunner(out=self.out) |
|
136 | 140 | self._test_runner(runner,source,output) |
|
137 | 141 | |
|
138 | 142 | def testPython(self): |
|
139 | 143 | """Test the Python runner.""" |
|
140 | 144 | runner = irunner.PythonRunner(out=self.out, args=['-E']) |
|
141 | 145 | source = doctest_refactor_print(""" |
|
142 | 146 | print 'hello, this is python' |
|
143 | 147 | |
|
144 | 148 | # some more code |
|
145 | 149 | x=1;y=2 |
|
146 | 150 | x+y**2 |
|
147 | 151 | |
|
148 | 152 | from math import * |
|
149 | 153 | cos(pi) |
|
150 | 154 | |
|
151 | 155 | for i in range(5): |
|
152 | 156 | print i |
|
153 | 157 | |
|
154 | 158 | print "that's all folks!" |
|
155 | 159 | """) |
|
156 | 160 | output = doctest_refactor_print("""\ |
|
157 | 161 | >>> print 'hello, this is python' |
|
158 | 162 | hello, this is python |
|
159 | 163 | |
|
160 | 164 | # some more code |
|
161 | 165 | >>> x=1;y=2 |
|
162 | 166 | >>> x+y**2 |
|
163 | 167 | 5 |
|
164 | 168 | |
|
165 | 169 | >>> from math import * |
|
166 | 170 | >>> cos(pi) |
|
167 | 171 | -1.0 |
|
168 | 172 | |
|
169 | 173 | >>> for i in range(5): |
|
170 | 174 | ... print i |
|
171 | 175 | ... |
|
172 | 176 | 0 |
|
173 | 177 | 1 |
|
174 | 178 | 2 |
|
175 | 179 | 3 |
|
176 | 180 | 4 |
|
177 | 181 | >>> print "that's all folks!" |
|
178 | 182 | that's all folks! |
|
179 | 183 | """) |
|
180 | 184 | self._test_runner(runner,source,output) |
@@ -1,121 +1,126 | |||
|
1 | 1 | """Test suite for pylab_import_all magic |
|
2 | 2 | Modified from the irunner module but using regex. |
|
3 | 3 | """ |
|
4 | 4 | from __future__ import print_function |
|
5 | 5 | |
|
6 | 6 | # Global to make tests extra verbose and help debugging |
|
7 | 7 | VERBOSE = True |
|
8 | 8 | |
|
9 | 9 | # stdlib imports |
|
10 | import io | |
|
11 | 10 | import sys |
|
12 | 11 | import unittest |
|
13 | 12 | import re |
|
14 | 13 | |
|
15 | 14 | # IPython imports |
|
16 | 15 | from IPython.lib import irunner |
|
17 | 16 | from IPython.testing import decorators |
|
17 | from IPython.utils.py3compat import PY3 | |
|
18 | ||
|
19 | if PY3: | |
|
20 | from io import StringIO | |
|
21 | else: | |
|
22 | from StringIO import StringIO | |
|
18 | 23 | |
|
19 | 24 | def pylab_not_importable(): |
|
20 | 25 | """Test if importing pylab fails. (For example, when having no display)""" |
|
21 | 26 | try: |
|
22 | 27 | import pylab |
|
23 | 28 | return False |
|
24 | 29 | except: |
|
25 | 30 | return True |
|
26 | 31 | |
|
27 | 32 | # Testing code begins |
|
28 | 33 | class RunnerTestCase(unittest.TestCase): |
|
29 | 34 | |
|
30 | 35 | def setUp(self): |
|
31 |
self.out = |
|
|
36 | self.out = StringIO() | |
|
32 | 37 | #self.out = sys.stdout |
|
33 | 38 | |
|
34 | 39 | def _test_runner(self,runner,source,output): |
|
35 | 40 | """Test that a given runner's input/output match.""" |
|
36 | 41 | |
|
37 | 42 | runner.run_source(source) |
|
38 | 43 | out = self.out.getvalue() |
|
39 | 44 | #out = '' |
|
40 | 45 | # this output contains nasty \r\n lineends, and the initial ipython |
|
41 | 46 | # banner. clean it up for comparison, removing lines of whitespace |
|
42 | 47 | output_l = [l for l in output.splitlines() if l and not l.isspace()] |
|
43 | 48 | out_l = [l for l in out.splitlines() if l and not l.isspace()] |
|
44 | 49 | mismatch = 0 |
|
45 | 50 | if len(output_l) != len(out_l): |
|
46 | 51 | message = ("Mismatch in number of lines\n\n" |
|
47 | 52 | "Expected:\n" |
|
48 | 53 | "~~~~~~~~~\n" |
|
49 | 54 | "%s\n\n" |
|
50 | 55 | "Got:\n" |
|
51 | 56 | "~~~~~~~~~\n" |
|
52 | 57 | "%s" |
|
53 | 58 | ) % ("\n".join(output_l), "\n".join(out_l)) |
|
54 | 59 | self.fail(message) |
|
55 | 60 | for n in range(len(output_l)): |
|
56 | 61 | # Do a line-by-line comparison |
|
57 | 62 | ol1 = output_l[n].strip() |
|
58 | 63 | ol2 = out_l[n].strip() |
|
59 | 64 | if not re.match(ol1,ol2): |
|
60 | 65 | mismatch += 1 |
|
61 | 66 | if VERBOSE: |
|
62 | 67 | print('<<< line %s does not match:' % n) |
|
63 | 68 | print(repr(ol1)) |
|
64 | 69 | print(repr(ol2)) |
|
65 | 70 | print('>>>') |
|
66 | 71 | self.assertTrue(mismatch==0,'Number of mismatched lines: %s' % |
|
67 | 72 | mismatch) |
|
68 | 73 | |
|
69 | 74 | @decorators.skip_if_no_x11 |
|
70 | 75 | @decorators.skipif_not_matplotlib |
|
71 | 76 | @decorators.skipif(pylab_not_importable, "Likely a run without X.") |
|
72 | 77 | def test_pylab_import_all_enabled(self): |
|
73 | 78 | "Verify that plot is available when pylab_import_all = True" |
|
74 | 79 | source = """ |
|
75 | 80 | from IPython.config.application import Application |
|
76 | 81 | app = Application.instance() |
|
77 | 82 | app.pylab_import_all = True |
|
78 | 83 | pylab |
|
79 | 84 | ip=get_ipython() |
|
80 | 85 | 'plot' in ip.user_ns |
|
81 | 86 | """ |
|
82 | 87 | output = """ |
|
83 | 88 | In \[1\]: from IPython\.config\.application import Application |
|
84 | 89 | In \[2\]: app = Application\.instance\(\) |
|
85 | 90 | In \[3\]: app\.pylab_import_all = True |
|
86 | 91 | In \[4\]: pylab |
|
87 | 92 | ^Using matplotlib backend: |
|
88 | 93 | Populating the interactive namespace from numpy and matplotlib |
|
89 | 94 | In \[5\]: ip=get_ipython\(\) |
|
90 | 95 | In \[6\]: \'plot\' in ip\.user_ns |
|
91 | 96 | Out\[6\]: True |
|
92 | 97 | """ |
|
93 | 98 | runner = irunner.IPythonRunner(out=self.out) |
|
94 | 99 | self._test_runner(runner,source,output) |
|
95 | 100 | |
|
96 | 101 | @decorators.skip_if_no_x11 |
|
97 | 102 | @decorators.skipif_not_matplotlib |
|
98 | 103 | @decorators.skipif(pylab_not_importable, "Likely a run without X.") |
|
99 | 104 | def test_pylab_import_all_disabled(self): |
|
100 | 105 | "Verify that plot is not available when pylab_import_all = False" |
|
101 | 106 | source = """ |
|
102 | 107 | from IPython.config.application import Application |
|
103 | 108 | app = Application.instance() |
|
104 | 109 | app.pylab_import_all = False |
|
105 | 110 | pylab |
|
106 | 111 | ip=get_ipython() |
|
107 | 112 | 'plot' in ip.user_ns |
|
108 | 113 | """ |
|
109 | 114 | output = """ |
|
110 | 115 | In \[1\]: from IPython\.config\.application import Application |
|
111 | 116 | In \[2\]: app = Application\.instance\(\) |
|
112 | 117 | In \[3\]: app\.pylab_import_all = False |
|
113 | 118 | In \[4\]: pylab |
|
114 | 119 | ^Using matplotlib backend: |
|
115 | 120 | Populating the interactive namespace from numpy and matplotlib |
|
116 | 121 | In \[5\]: ip=get_ipython\(\) |
|
117 | 122 | In \[6\]: \'plot\' in ip\.user_ns |
|
118 | 123 | Out\[6\]: False |
|
119 | 124 | """ |
|
120 | 125 | runner = irunner.IPythonRunner(out=self.out) |
|
121 | 126 | self._test_runner(runner,source,output) |
@@ -1,51 +1,56 | |||
|
1 | 1 | """ |
|
2 | 2 | Module with tests for debug |
|
3 | 3 | """ |
|
4 | 4 | |
|
5 | 5 | #----------------------------------------------------------------------------- |
|
6 | 6 | # Copyright (c) 2013, the IPython Development Team. |
|
7 | 7 | # |
|
8 | 8 | # Distributed under the terms of the Modified BSD License. |
|
9 | 9 | # |
|
10 | 10 | # The full license is in the file COPYING.txt, distributed with this software. |
|
11 | 11 | #----------------------------------------------------------------------------- |
|
12 | 12 | |
|
13 | 13 | #----------------------------------------------------------------------------- |
|
14 | 14 | # Imports |
|
15 | 15 | #----------------------------------------------------------------------------- |
|
16 | 16 | |
|
17 | 17 | import sys |
|
18 | from io import StringIO | |
|
19 | 18 | |
|
20 | 19 | from ...tests.base import TestsBase |
|
21 | 20 | from ..debug import DebugWriter |
|
21 | from IPython.utils.py3compat import PY3 | |
|
22 | ||
|
23 | if PY3: | |
|
24 | from io import StringIO | |
|
25 | else: | |
|
26 | from StringIO import StringIO | |
|
22 | 27 | |
|
23 | 28 | |
|
24 | 29 | #----------------------------------------------------------------------------- |
|
25 | 30 | # Class |
|
26 | 31 | #----------------------------------------------------------------------------- |
|
27 | 32 | |
|
28 | 33 | class TestDebug(TestsBase): |
|
29 | 34 | """Contains test functions for debug.py""" |
|
30 | 35 | |
|
31 | 36 | def test_output(self): |
|
32 | 37 | """Test debug writer output.""" |
|
33 | 38 | |
|
34 | 39 | # Capture the stdout. Remember original. |
|
35 | 40 | stdout = sys.stdout |
|
36 | 41 | stream = StringIO() |
|
37 | 42 | sys.stdout = stream |
|
38 | 43 | |
|
39 | 44 | # Create stdout writer, get output |
|
40 | 45 | writer = DebugWriter() |
|
41 | 46 | writer.write('aaa', {'outputs': {'bbb': 'ccc'}}) |
|
42 | 47 | output = stream.getvalue() |
|
43 | 48 | |
|
44 | 49 | # Check output. Make sure resources dictionary is dumped, but nothing |
|
45 | 50 | # else. |
|
46 | 51 | assert 'aaa' not in output |
|
47 | 52 | assert 'bbb' in output |
|
48 | 53 | assert 'ccc' in output |
|
49 | 54 | |
|
50 | 55 | # Revert stdout |
|
51 | 56 | sys.stdout = stdout No newline at end of file |
@@ -1,159 +1,164 | |||
|
1 | 1 | """ |
|
2 | 2 | Module with tests for files |
|
3 | 3 | """ |
|
4 | 4 | |
|
5 | 5 | #----------------------------------------------------------------------------- |
|
6 | 6 | # Copyright (c) 2013, the IPython Development Team. |
|
7 | 7 | # |
|
8 | 8 | # Distributed under the terms of the Modified BSD License. |
|
9 | 9 | # |
|
10 | 10 | # The full license is in the file COPYING.txt, distributed with this software. |
|
11 | 11 | #----------------------------------------------------------------------------- |
|
12 | 12 | |
|
13 | 13 | #----------------------------------------------------------------------------- |
|
14 | 14 | # Imports |
|
15 | 15 | #----------------------------------------------------------------------------- |
|
16 | 16 | |
|
17 | 17 | import sys |
|
18 | 18 | import os |
|
19 | from io import StringIO | |
|
20 | 19 | |
|
21 | 20 | from ...tests.base import TestsBase |
|
22 | 21 | from ..files import FilesWriter |
|
22 | from IPython.utils.py3compat import PY3 | |
|
23 | ||
|
24 | if PY3: | |
|
25 | from io import StringIO | |
|
26 | else: | |
|
27 | from StringIO import StringIO | |
|
23 | 28 | |
|
24 | 29 | |
|
25 | 30 | #----------------------------------------------------------------------------- |
|
26 | 31 | # Class |
|
27 | 32 | #----------------------------------------------------------------------------- |
|
28 | 33 | |
|
29 | 34 | class Testfiles(TestsBase): |
|
30 | 35 | """Contains test functions for files.py""" |
|
31 | 36 | |
|
32 | 37 | def test_basic_output(self): |
|
33 | 38 | """Is FilesWriter basic output correct?""" |
|
34 | 39 | |
|
35 | 40 | # Work in a temporary directory. |
|
36 | 41 | with self.create_temp_cwd(): |
|
37 | 42 | |
|
38 | 43 | # Create the resoruces dictionary |
|
39 | 44 | res = {} |
|
40 | 45 | |
|
41 | 46 | # Create files writer, test output |
|
42 | 47 | writer = FilesWriter() |
|
43 | 48 | writer.write(u'y', res, notebook_name="z") |
|
44 | 49 | |
|
45 | 50 | # Check the output of the file |
|
46 | 51 | with open('z', 'r') as f: |
|
47 | 52 | output = f.read() |
|
48 | 53 | self.assertEqual(output, u'y') |
|
49 | 54 | |
|
50 | 55 | def test_ext(self): |
|
51 | 56 | """Does the FilesWriter add the correct extension to the output?""" |
|
52 | 57 | |
|
53 | 58 | # Work in a temporary directory. |
|
54 | 59 | with self.create_temp_cwd(): |
|
55 | 60 | |
|
56 | 61 | # Create the resoruces dictionary |
|
57 | 62 | res = {'output_extension': 'txt'} |
|
58 | 63 | |
|
59 | 64 | # Create files writer, test output |
|
60 | 65 | writer = FilesWriter() |
|
61 | 66 | writer.write(u'y', res, notebook_name="z") |
|
62 | 67 | |
|
63 | 68 | # Check the output of the file |
|
64 | 69 | assert os.path.isfile('z.txt') |
|
65 | 70 | with open('z.txt', 'r') as f: |
|
66 | 71 | output = f.read() |
|
67 | 72 | self.assertEqual(output, u'y') |
|
68 | 73 | |
|
69 | 74 | |
|
70 | 75 | def test_extract(self): |
|
71 | 76 | """Can FilesWriter write extracted figures correctly?""" |
|
72 | 77 | |
|
73 | 78 | # Work in a temporary directory. |
|
74 | 79 | with self.create_temp_cwd(): |
|
75 | 80 | |
|
76 | 81 | # Create the resoruces dictionary |
|
77 | 82 | res = {'outputs': {os.path.join('z_files', 'a'): b'b'}} |
|
78 | 83 | |
|
79 | 84 | # Create files writer, test output |
|
80 | 85 | writer = FilesWriter() |
|
81 | 86 | writer.write(u'y', res, notebook_name="z") |
|
82 | 87 | |
|
83 | 88 | # Check the output of the file |
|
84 | 89 | with open('z', 'r') as f: |
|
85 | 90 | output = f.read() |
|
86 | 91 | self.assertEqual(output, u'y') |
|
87 | 92 | |
|
88 | 93 | # Check the output of the extracted file |
|
89 | 94 | extracted_file_dest = os.path.join('z_files', 'a') |
|
90 | 95 | assert os.path.isfile(extracted_file_dest) |
|
91 | 96 | with open(extracted_file_dest, 'r') as f: |
|
92 | 97 | output = f.read() |
|
93 | 98 | self.assertEqual(output, 'b') |
|
94 | 99 | |
|
95 | 100 | |
|
96 | 101 | def test_builddir(self): |
|
97 | 102 | """Can FilesWriter write to a build dir correctly?""" |
|
98 | 103 | |
|
99 | 104 | # Work in a temporary directory. |
|
100 | 105 | with self.create_temp_cwd(): |
|
101 | 106 | |
|
102 | 107 | # Create the resoruces dictionary |
|
103 | 108 | res = {'outputs': {os.path.join('z_files', 'a'): b'b'}} |
|
104 | 109 | |
|
105 | 110 | # Create files writer, test output |
|
106 | 111 | writer = FilesWriter() |
|
107 | 112 | writer.build_directory = u'build' |
|
108 | 113 | writer.write(u'y', res, notebook_name="z") |
|
109 | 114 | |
|
110 | 115 | # Check the output of the file |
|
111 | 116 | assert os.path.isdir(writer.build_directory) |
|
112 | 117 | dest = os.path.join(writer.build_directory, 'z') |
|
113 | 118 | with open(dest, 'r') as f: |
|
114 | 119 | output = f.read() |
|
115 | 120 | self.assertEqual(output, u'y') |
|
116 | 121 | |
|
117 | 122 | # Check the output of the extracted file |
|
118 | 123 | extracted_file_dest = os.path.join(writer.build_directory, 'z_files', 'a') |
|
119 | 124 | assert os.path.isfile(extracted_file_dest) |
|
120 | 125 | with open(extracted_file_dest, 'r') as f: |
|
121 | 126 | output = f.read() |
|
122 | 127 | self.assertEqual(output, 'b') |
|
123 | 128 | |
|
124 | 129 | |
|
125 | 130 | def test_links(self): |
|
126 | 131 | """Can the FilesWriter handle linked files correctly?""" |
|
127 | 132 | |
|
128 | 133 | # Work in a temporary directory. |
|
129 | 134 | with self.create_temp_cwd(): |
|
130 | 135 | |
|
131 | 136 | # Create test file |
|
132 | 137 | os.mkdir('sub') |
|
133 | 138 | with open(os.path.join('sub', 'c'), 'w') as f: |
|
134 | 139 | f.write('d') |
|
135 | 140 | |
|
136 | 141 | # Create the resoruces dictionary |
|
137 | 142 | res = {} |
|
138 | 143 | |
|
139 | 144 | # Create files writer, test output |
|
140 | 145 | writer = FilesWriter() |
|
141 | 146 | writer.files = [os.path.join('sub', 'c')] |
|
142 | 147 | writer.build_directory = u'build' |
|
143 | 148 | writer.write(u'y', res, notebook_name="z") |
|
144 | 149 | |
|
145 | 150 | # Check the output of the file |
|
146 | 151 | assert os.path.isdir(writer.build_directory) |
|
147 | 152 | dest = os.path.join(writer.build_directory, 'z') |
|
148 | 153 | with open(dest, 'r') as f: |
|
149 | 154 | output = f.read() |
|
150 | 155 | self.assertEqual(output, u'y') |
|
151 | 156 | |
|
152 | 157 | # Check to make sure the linked file was copied |
|
153 | 158 | path = os.path.join(writer.build_directory, 'sub') |
|
154 | 159 | assert os.path.isdir(path) |
|
155 | 160 | dest = os.path.join(path, 'c') |
|
156 | 161 | assert os.path.isfile(dest) |
|
157 | 162 | with open(dest, 'r') as f: |
|
158 | 163 | output = f.read() |
|
159 | 164 | self.assertEqual(output, 'd') |
@@ -1,46 +1,51 | |||
|
1 | 1 | """ |
|
2 | 2 | Module with tests for stdout |
|
3 | 3 | """ |
|
4 | 4 | |
|
5 | 5 | #----------------------------------------------------------------------------- |
|
6 | 6 | # Copyright (c) 2013, the IPython Development Team. |
|
7 | 7 | # |
|
8 | 8 | # Distributed under the terms of the Modified BSD License. |
|
9 | 9 | # |
|
10 | 10 | # The full license is in the file COPYING.txt, distributed with this software. |
|
11 | 11 | #----------------------------------------------------------------------------- |
|
12 | 12 | |
|
13 | 13 | #----------------------------------------------------------------------------- |
|
14 | 14 | # Imports |
|
15 | 15 | #----------------------------------------------------------------------------- |
|
16 | 16 | |
|
17 | 17 | import sys |
|
18 | from io import StringIO | |
|
19 | 18 | |
|
20 | 19 | from ...tests.base import TestsBase |
|
21 | 20 | from ..stdout import StdoutWriter |
|
21 | from IPython.utils.py3compat import PY3 | |
|
22 | ||
|
23 | if PY3: | |
|
24 | from io import StringIO | |
|
25 | else: | |
|
26 | from StringIO import StringIO | |
|
22 | 27 | |
|
23 | 28 | |
|
24 | 29 | #----------------------------------------------------------------------------- |
|
25 | 30 | # Class |
|
26 | 31 | #----------------------------------------------------------------------------- |
|
27 | 32 | |
|
28 | 33 | class TestStdout(TestsBase): |
|
29 | 34 | """Contains test functions for stdout.py""" |
|
30 | 35 | |
|
31 | 36 | def test_output(self): |
|
32 | 37 | """Test stdout writer output.""" |
|
33 | 38 | |
|
34 | 39 | # Capture the stdout. Remember original. |
|
35 | 40 | stdout = sys.stdout |
|
36 | 41 | stream = StringIO() |
|
37 | 42 | sys.stdout = stream |
|
38 | 43 | |
|
39 | 44 | # Create stdout writer, test output |
|
40 | 45 | writer = StdoutWriter() |
|
41 | 46 | writer.write('a', {'b': 'c'}) |
|
42 | 47 | output = stream.getvalue() |
|
43 | 48 | self.fuzzy_compare(output, 'a') |
|
44 | 49 | |
|
45 | 50 | # Revert stdout |
|
46 | 51 | sys.stdout = stdout No newline at end of file |
@@ -1,193 +1,192 | |||
|
1 | 1 | """base class for parallel client tests |
|
2 | 2 | |
|
3 | 3 | Authors: |
|
4 | 4 | |
|
5 | 5 | * Min RK |
|
6 | 6 | """ |
|
7 | 7 | |
|
8 | 8 | #------------------------------------------------------------------------------- |
|
9 | 9 | # Copyright (C) 2011 The IPython Development Team |
|
10 | 10 | # |
|
11 | 11 | # Distributed under the terms of the BSD License. The full license is in |
|
12 | 12 | # the file COPYING, distributed as part of this software. |
|
13 | 13 | #------------------------------------------------------------------------------- |
|
14 | 14 | from __future__ import print_function |
|
15 | 15 | |
|
16 | 16 | import sys |
|
17 | 17 | import tempfile |
|
18 | 18 | import time |
|
19 | from io import StringIO | |
|
20 | 19 | |
|
21 | 20 | from nose import SkipTest |
|
22 | 21 | |
|
23 | 22 | import zmq |
|
24 | 23 | from zmq.tests import BaseZMQTestCase |
|
25 | 24 | |
|
26 | 25 | from IPython.external.decorator import decorator |
|
27 | 26 | |
|
28 | 27 | from IPython.parallel import error |
|
29 | 28 | from IPython.parallel import Client |
|
30 | 29 | |
|
31 | 30 | from IPython.parallel.tests import launchers, add_engines |
|
32 | 31 | |
|
33 | 32 | # simple tasks for use in apply tests |
|
34 | 33 | |
|
35 | 34 | def segfault(): |
|
36 | 35 | """this will segfault""" |
|
37 | 36 | import ctypes |
|
38 | 37 | ctypes.memset(-1,0,1) |
|
39 | 38 | |
|
40 | 39 | def crash(): |
|
41 | 40 | """from stdlib crashers in the test suite""" |
|
42 | 41 | import types |
|
43 | 42 | if sys.platform.startswith('win'): |
|
44 | 43 | import ctypes |
|
45 | 44 | ctypes.windll.kernel32.SetErrorMode(0x0002); |
|
46 | 45 | args = [ 0, 0, 0, 0, b'\x04\x71\x00\x00', (), (), (), '', '', 1, b''] |
|
47 | 46 | if sys.version_info[0] >= 3: |
|
48 | 47 | # Python3 adds 'kwonlyargcount' as the second argument to Code |
|
49 | 48 | args.insert(1, 0) |
|
50 | 49 | |
|
51 | 50 | co = types.CodeType(*args) |
|
52 | 51 | exec(co) |
|
53 | 52 | |
|
54 | 53 | def wait(n): |
|
55 | 54 | """sleep for a time""" |
|
56 | 55 | import time |
|
57 | 56 | time.sleep(n) |
|
58 | 57 | return n |
|
59 | 58 | |
|
60 | 59 | def raiser(eclass): |
|
61 | 60 | """raise an exception""" |
|
62 | 61 | raise eclass() |
|
63 | 62 | |
|
64 | 63 | def generate_output(): |
|
65 | 64 | """function for testing output |
|
66 | 65 | |
|
67 | 66 | publishes two outputs of each type, and returns |
|
68 | 67 | a rich displayable object. |
|
69 | 68 | """ |
|
70 | 69 | |
|
71 | 70 | import sys |
|
72 | 71 | from IPython.core.display import display, HTML, Math |
|
73 | 72 | |
|
74 | 73 | print("stdout") |
|
75 | 74 | print("stderr", file=sys.stderr) |
|
76 | 75 | |
|
77 | 76 | display(HTML("<b>HTML</b>")) |
|
78 | 77 | |
|
79 | 78 | print("stdout2") |
|
80 | 79 | print("stderr2", file=sys.stderr) |
|
81 | 80 | |
|
82 | 81 | display(Math(r"\alpha=\beta")) |
|
83 | 82 | |
|
84 | 83 | return Math("42") |
|
85 | 84 | |
|
86 | 85 | # test decorator for skipping tests when libraries are unavailable |
|
87 | 86 | def skip_without(*names): |
|
88 | 87 | """skip a test if some names are not importable""" |
|
89 | 88 | @decorator |
|
90 | 89 | def skip_without_names(f, *args, **kwargs): |
|
91 | 90 | """decorator to skip tests in the absence of numpy.""" |
|
92 | 91 | for name in names: |
|
93 | 92 | try: |
|
94 | 93 | __import__(name) |
|
95 | 94 | except ImportError: |
|
96 | 95 | raise SkipTest |
|
97 | 96 | return f(*args, **kwargs) |
|
98 | 97 | return skip_without_names |
|
99 | 98 | |
|
100 | 99 | #------------------------------------------------------------------------------- |
|
101 | 100 | # Classes |
|
102 | 101 | #------------------------------------------------------------------------------- |
|
103 | 102 | |
|
104 | 103 | |
|
105 | 104 | class ClusterTestCase(BaseZMQTestCase): |
|
106 | 105 | timeout = 10 |
|
107 | 106 | |
|
108 | 107 | def add_engines(self, n=1, block=True): |
|
109 | 108 | """add multiple engines to our cluster""" |
|
110 | 109 | self.engines.extend(add_engines(n)) |
|
111 | 110 | if block: |
|
112 | 111 | self.wait_on_engines() |
|
113 | 112 | |
|
114 | 113 | def minimum_engines(self, n=1, block=True): |
|
115 | 114 | """add engines until there are at least n connected""" |
|
116 | 115 | self.engines.extend(add_engines(n, total=True)) |
|
117 | 116 | if block: |
|
118 | 117 | self.wait_on_engines() |
|
119 | 118 | |
|
120 | 119 | |
|
121 | 120 | def wait_on_engines(self, timeout=5): |
|
122 | 121 | """wait for our engines to connect.""" |
|
123 | 122 | n = len(self.engines)+self.base_engine_count |
|
124 | 123 | tic = time.time() |
|
125 | 124 | while time.time()-tic < timeout and len(self.client.ids) < n: |
|
126 | 125 | time.sleep(0.1) |
|
127 | 126 | |
|
128 | 127 | assert not len(self.client.ids) < n, "waiting for engines timed out" |
|
129 | 128 | |
|
130 | 129 | def client_wait(self, client, jobs=None, timeout=-1): |
|
131 | 130 | """my wait wrapper, sets a default finite timeout to avoid hangs""" |
|
132 | 131 | if timeout < 0: |
|
133 | 132 | timeout = self.timeout |
|
134 | 133 | return Client.wait(client, jobs, timeout) |
|
135 | 134 | |
|
136 | 135 | def connect_client(self): |
|
137 | 136 | """connect a client with my Context, and track its sockets for cleanup""" |
|
138 | 137 | c = Client(profile='iptest', context=self.context) |
|
139 | 138 | c.wait = lambda *a, **kw: self.client_wait(c, *a, **kw) |
|
140 | 139 | |
|
141 | 140 | for name in filter(lambda n:n.endswith('socket'), dir(c)): |
|
142 | 141 | s = getattr(c, name) |
|
143 | 142 | s.setsockopt(zmq.LINGER, 0) |
|
144 | 143 | self.sockets.append(s) |
|
145 | 144 | return c |
|
146 | 145 | |
|
147 | 146 | def assertRaisesRemote(self, etype, f, *args, **kwargs): |
|
148 | 147 | try: |
|
149 | 148 | try: |
|
150 | 149 | f(*args, **kwargs) |
|
151 | 150 | except error.CompositeError as e: |
|
152 | 151 | e.raise_exception() |
|
153 | 152 | except error.RemoteError as e: |
|
154 | 153 | self.assertEqual(etype.__name__, e.ename, "Should have raised %r, but raised %r"%(etype.__name__, e.ename)) |
|
155 | 154 | else: |
|
156 | 155 | self.fail("should have raised a RemoteError") |
|
157 | 156 | |
|
158 | 157 | def _wait_for(self, f, timeout=10): |
|
159 | 158 | """wait for a condition""" |
|
160 | 159 | tic = time.time() |
|
161 | 160 | while time.time() <= tic + timeout: |
|
162 | 161 | if f(): |
|
163 | 162 | return |
|
164 | 163 | time.sleep(0.1) |
|
165 | 164 | self.client.spin() |
|
166 | 165 | if not f(): |
|
167 | 166 | print("Warning: Awaited condition never arrived") |
|
168 | 167 | |
|
169 | 168 | def setUp(self): |
|
170 | 169 | BaseZMQTestCase.setUp(self) |
|
171 | 170 | self.client = self.connect_client() |
|
172 | 171 | # start every test with clean engine namespaces: |
|
173 | 172 | self.client.clear(block=True) |
|
174 | 173 | self.base_engine_count=len(self.client.ids) |
|
175 | 174 | self.engines=[] |
|
176 | 175 | |
|
177 | 176 | def tearDown(self): |
|
178 | 177 | # self.client.clear(block=True) |
|
179 | 178 | # close fds: |
|
180 | 179 | for e in filter(lambda e: e.poll() is not None, launchers): |
|
181 | 180 | launchers.remove(e) |
|
182 | 181 | |
|
183 | 182 | # allow flushing of incoming messages to prevent crash on socket close |
|
184 | 183 | self.client.wait(timeout=2) |
|
185 | 184 | # time.sleep(2) |
|
186 | 185 | self.client.spin() |
|
187 | 186 | self.client.close() |
|
188 | 187 | BaseZMQTestCase.tearDown(self) |
|
189 | 188 | # this will be redundant when pyzmq merges PR #88 |
|
190 | 189 | # self.context.term() |
|
191 | 190 | # print tempfile.TemporaryFile().fileno(), |
|
192 | 191 | # sys.stdout.flush() |
|
193 | 192 |
@@ -1,830 +1,835 | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | """Sphinx directive to support embedded IPython code. |
|
3 | 3 | |
|
4 | 4 | This directive allows pasting of entire interactive IPython sessions, prompts |
|
5 | 5 | and all, and their code will actually get re-executed at doc build time, with |
|
6 | 6 | all prompts renumbered sequentially. It also allows you to input code as a pure |
|
7 | 7 | python input by giving the argument python to the directive. The output looks |
|
8 | 8 | like an interactive ipython section. |
|
9 | 9 | |
|
10 | 10 | To enable this directive, simply list it in your Sphinx ``conf.py`` file |
|
11 | 11 | (making sure the directory where you placed it is visible to sphinx, as is |
|
12 | 12 | needed for all Sphinx directives). |
|
13 | 13 | |
|
14 | 14 | By default this directive assumes that your prompts are unchanged IPython ones, |
|
15 | 15 | but this can be customized. The configurable options that can be placed in |
|
16 | 16 | conf.py are |
|
17 | 17 | |
|
18 | 18 | ipython_savefig_dir: |
|
19 | 19 | The directory in which to save the figures. This is relative to the |
|
20 | 20 | Sphinx source directory. The default is `html_static_path`. |
|
21 | 21 | ipython_rgxin: |
|
22 | 22 | The compiled regular expression to denote the start of IPython input |
|
23 | 23 | lines. The default is re.compile('In \[(\d+)\]:\s?(.*)\s*'). You |
|
24 | 24 | shouldn't need to change this. |
|
25 | 25 | ipython_rgxout: |
|
26 | 26 | The compiled regular expression to denote the start of IPython output |
|
27 | 27 | lines. The default is re.compile('Out\[(\d+)\]:\s?(.*)\s*'). You |
|
28 | 28 | shouldn't need to change this. |
|
29 | 29 | ipython_promptin: |
|
30 | 30 | The string to represent the IPython input prompt in the generated ReST. |
|
31 | 31 | The default is 'In [%d]:'. This expects that the line numbers are used |
|
32 | 32 | in the prompt. |
|
33 | 33 | ipython_promptout: |
|
34 | 34 | |
|
35 | 35 | The string to represent the IPython prompt in the generated ReST. The |
|
36 | 36 | default is 'Out [%d]:'. This expects that the line numbers are used |
|
37 | 37 | in the prompt. |
|
38 | 38 | |
|
39 | 39 | ToDo |
|
40 | 40 | ---- |
|
41 | 41 | |
|
42 | 42 | - Turn the ad-hoc test() function into a real test suite. |
|
43 | 43 | - Break up ipython-specific functionality from matplotlib stuff into better |
|
44 | 44 | separated code. |
|
45 | 45 | |
|
46 | 46 | Authors |
|
47 | 47 | ------- |
|
48 | 48 | |
|
49 | 49 | - John D Hunter: orignal author. |
|
50 | 50 | - Fernando Perez: refactoring, documentation, cleanups, port to 0.11. |
|
51 | 51 | - VáclavŠmilauer <eudoxos-AT-arcig.cz>: Prompt generalizations. |
|
52 | 52 | - Skipper Seabold, refactoring, cleanups, pure python addition |
|
53 | 53 | """ |
|
54 | 54 | from __future__ import print_function |
|
55 | 55 | |
|
56 | 56 | #----------------------------------------------------------------------------- |
|
57 | 57 | # Imports |
|
58 | 58 | #----------------------------------------------------------------------------- |
|
59 | 59 | |
|
60 | 60 | # Stdlib |
|
61 | import io | |
|
62 | 61 | import os |
|
63 | 62 | import re |
|
64 | 63 | import sys |
|
65 | 64 | import tempfile |
|
66 | 65 | import ast |
|
67 | 66 | |
|
68 | 67 | # To keep compatibility with various python versions |
|
69 | 68 | try: |
|
70 | 69 | from hashlib import md5 |
|
71 | 70 | except ImportError: |
|
72 | 71 | from md5 import md5 |
|
73 | 72 | |
|
74 | 73 | # Third-party |
|
75 | 74 | import matplotlib |
|
76 | 75 | import sphinx |
|
77 | 76 | from docutils.parsers.rst import directives |
|
78 | 77 | from docutils import nodes |
|
79 | 78 | from sphinx.util.compat import Directive |
|
80 | 79 | |
|
81 | 80 | matplotlib.use('Agg') |
|
82 | 81 | |
|
83 | 82 | # Our own |
|
84 | 83 | from IPython import Config, InteractiveShell |
|
85 | 84 | from IPython.core.profiledir import ProfileDir |
|
86 | 85 | from IPython.utils import io |
|
86 | from IPython.utils.py3compat import PY3 | |
|
87 | ||
|
88 | if PY3: | |
|
89 | from io import StringIO | |
|
90 | else: | |
|
91 | from StringIO import StringIO | |
|
87 | 92 | |
|
88 | 93 | #----------------------------------------------------------------------------- |
|
89 | 94 | # Globals |
|
90 | 95 | #----------------------------------------------------------------------------- |
|
91 | 96 | # for tokenizing blocks |
|
92 | 97 | COMMENT, INPUT, OUTPUT = range(3) |
|
93 | 98 | |
|
94 | 99 | #----------------------------------------------------------------------------- |
|
95 | 100 | # Functions and class declarations |
|
96 | 101 | #----------------------------------------------------------------------------- |
|
97 | 102 | def block_parser(part, rgxin, rgxout, fmtin, fmtout): |
|
98 | 103 | """ |
|
99 | 104 | part is a string of ipython text, comprised of at most one |
|
100 | 105 | input, one ouput, comments, and blank lines. The block parser |
|
101 | 106 | parses the text into a list of:: |
|
102 | 107 | |
|
103 | 108 | blocks = [ (TOKEN0, data0), (TOKEN1, data1), ...] |
|
104 | 109 | |
|
105 | 110 | where TOKEN is one of [COMMENT | INPUT | OUTPUT ] and |
|
106 | 111 | data is, depending on the type of token:: |
|
107 | 112 | |
|
108 | 113 | COMMENT : the comment string |
|
109 | 114 | |
|
110 | 115 | INPUT: the (DECORATOR, INPUT_LINE, REST) where |
|
111 | 116 | DECORATOR: the input decorator (or None) |
|
112 | 117 | INPUT_LINE: the input as string (possibly multi-line) |
|
113 | 118 | REST : any stdout generated by the input line (not OUTPUT) |
|
114 | 119 | |
|
115 | 120 | |
|
116 | 121 | OUTPUT: the output string, possibly multi-line |
|
117 | 122 | """ |
|
118 | 123 | |
|
119 | 124 | block = [] |
|
120 | 125 | lines = part.split('\n') |
|
121 | 126 | N = len(lines) |
|
122 | 127 | i = 0 |
|
123 | 128 | decorator = None |
|
124 | 129 | while 1: |
|
125 | 130 | |
|
126 | 131 | if i==N: |
|
127 | 132 | # nothing left to parse -- the last line |
|
128 | 133 | break |
|
129 | 134 | |
|
130 | 135 | line = lines[i] |
|
131 | 136 | i += 1 |
|
132 | 137 | line_stripped = line.strip() |
|
133 | 138 | if line_stripped.startswith('#'): |
|
134 | 139 | block.append((COMMENT, line)) |
|
135 | 140 | continue |
|
136 | 141 | |
|
137 | 142 | if line_stripped.startswith('@'): |
|
138 | 143 | # we're assuming at most one decorator -- may need to |
|
139 | 144 | # rethink |
|
140 | 145 | decorator = line_stripped |
|
141 | 146 | continue |
|
142 | 147 | |
|
143 | 148 | # does this look like an input line? |
|
144 | 149 | matchin = rgxin.match(line) |
|
145 | 150 | if matchin: |
|
146 | 151 | lineno, inputline = int(matchin.group(1)), matchin.group(2) |
|
147 | 152 | |
|
148 | 153 | # the ....: continuation string |
|
149 | 154 | continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2)) |
|
150 | 155 | Nc = len(continuation) |
|
151 | 156 | # input lines can continue on for more than one line, if |
|
152 | 157 | # we have a '\' line continuation char or a function call |
|
153 | 158 | # echo line 'print'. The input line can only be |
|
154 | 159 | # terminated by the end of the block or an output line, so |
|
155 | 160 | # we parse out the rest of the input line if it is |
|
156 | 161 | # multiline as well as any echo text |
|
157 | 162 | |
|
158 | 163 | rest = [] |
|
159 | 164 | while i<N: |
|
160 | 165 | |
|
161 | 166 | # look ahead; if the next line is blank, or a comment, or |
|
162 | 167 | # an output line, we're done |
|
163 | 168 | |
|
164 | 169 | nextline = lines[i] |
|
165 | 170 | matchout = rgxout.match(nextline) |
|
166 | 171 | #print "nextline=%s, continuation=%s, starts=%s"%(nextline, continuation, nextline.startswith(continuation)) |
|
167 | 172 | if matchout or nextline.startswith('#'): |
|
168 | 173 | break |
|
169 | 174 | elif nextline.startswith(continuation): |
|
170 | 175 | inputline += '\n' + nextline[Nc:] |
|
171 | 176 | else: |
|
172 | 177 | rest.append(nextline) |
|
173 | 178 | i+= 1 |
|
174 | 179 | |
|
175 | 180 | block.append((INPUT, (decorator, inputline, '\n'.join(rest)))) |
|
176 | 181 | continue |
|
177 | 182 | |
|
178 | 183 | # if it looks like an output line grab all the text to the end |
|
179 | 184 | # of the block |
|
180 | 185 | matchout = rgxout.match(line) |
|
181 | 186 | if matchout: |
|
182 | 187 | lineno, output = int(matchout.group(1)), matchout.group(2) |
|
183 | 188 | if i<N-1: |
|
184 | 189 | output = '\n'.join([output] + lines[i:]) |
|
185 | 190 | |
|
186 | 191 | block.append((OUTPUT, output)) |
|
187 | 192 | break |
|
188 | 193 | |
|
189 | 194 | return block |
|
190 | 195 | |
|
191 | 196 | class EmbeddedSphinxShell(object): |
|
192 | 197 | """An embedded IPython instance to run inside Sphinx""" |
|
193 | 198 | |
|
194 | 199 | def __init__(self): |
|
195 | 200 | |
|
196 |
self.cout = |
|
|
201 | self.cout = StringIO() | |
|
197 | 202 | |
|
198 | 203 | |
|
199 | 204 | # Create config object for IPython |
|
200 | 205 | config = Config() |
|
201 | 206 | config.Global.display_banner = False |
|
202 | 207 | config.Global.exec_lines = ['import numpy as np', |
|
203 | 208 | 'from pylab import *' |
|
204 | 209 | ] |
|
205 | 210 | config.InteractiveShell.autocall = False |
|
206 | 211 | config.InteractiveShell.autoindent = False |
|
207 | 212 | config.InteractiveShell.colors = 'NoColor' |
|
208 | 213 | |
|
209 | 214 | # create a profile so instance history isn't saved |
|
210 | 215 | tmp_profile_dir = tempfile.mkdtemp(prefix='profile_') |
|
211 | 216 | profname = 'auto_profile_sphinx_build' |
|
212 | 217 | pdir = os.path.join(tmp_profile_dir,profname) |
|
213 | 218 | profile = ProfileDir.create_profile_dir(pdir) |
|
214 | 219 | |
|
215 | 220 | # Create and initialize ipython, but don't start its mainloop |
|
216 | 221 | IP = InteractiveShell.instance(config=config, profile_dir=profile) |
|
217 | 222 | # io.stdout redirect must be done *after* instantiating InteractiveShell |
|
218 | 223 | io.stdout = self.cout |
|
219 | 224 | io.stderr = self.cout |
|
220 | 225 | |
|
221 | 226 | # For debugging, so we can see normal output, use this: |
|
222 | 227 | #from IPython.utils.io import Tee |
|
223 | 228 | #io.stdout = Tee(self.cout, channel='stdout') # dbg |
|
224 | 229 | #io.stderr = Tee(self.cout, channel='stderr') # dbg |
|
225 | 230 | |
|
226 | 231 | # Store a few parts of IPython we'll need. |
|
227 | 232 | self.IP = IP |
|
228 | 233 | self.user_ns = self.IP.user_ns |
|
229 | 234 | self.user_global_ns = self.IP.user_global_ns |
|
230 | 235 | |
|
231 | 236 | self.input = '' |
|
232 | 237 | self.output = '' |
|
233 | 238 | |
|
234 | 239 | self.is_verbatim = False |
|
235 | 240 | self.is_doctest = False |
|
236 | 241 | self.is_suppress = False |
|
237 | 242 | |
|
238 | 243 | # on the first call to the savefig decorator, we'll import |
|
239 | 244 | # pyplot as plt so we can make a call to the plt.gcf().savefig |
|
240 | 245 | self._pyplot_imported = False |
|
241 | 246 | |
|
242 | 247 | def clear_cout(self): |
|
243 | 248 | self.cout.seek(0) |
|
244 | 249 | self.cout.truncate(0) |
|
245 | 250 | |
|
246 | 251 | def process_input_line(self, line, store_history=True): |
|
247 | 252 | """process the input, capturing stdout""" |
|
248 | 253 | #print "input='%s'"%self.input |
|
249 | 254 | stdout = sys.stdout |
|
250 | 255 | splitter = self.IP.input_splitter |
|
251 | 256 | try: |
|
252 | 257 | sys.stdout = self.cout |
|
253 | 258 | splitter.push(line) |
|
254 | 259 | more = splitter.push_accepts_more() |
|
255 | 260 | if not more: |
|
256 | 261 | source_raw = splitter.source_raw_reset()[1] |
|
257 | 262 | self.IP.run_cell(source_raw, store_history=store_history) |
|
258 | 263 | finally: |
|
259 | 264 | sys.stdout = stdout |
|
260 | 265 | |
|
261 | 266 | def process_image(self, decorator): |
|
262 | 267 | """ |
|
263 | 268 | # build out an image directive like |
|
264 | 269 | # .. image:: somefile.png |
|
265 | 270 | # :width 4in |
|
266 | 271 | # |
|
267 | 272 | # from an input like |
|
268 | 273 | # savefig somefile.png width=4in |
|
269 | 274 | """ |
|
270 | 275 | savefig_dir = self.savefig_dir |
|
271 | 276 | source_dir = self.source_dir |
|
272 | 277 | saveargs = decorator.split(' ') |
|
273 | 278 | filename = saveargs[1] |
|
274 | 279 | # insert relative path to image file in source |
|
275 | 280 | outfile = os.path.relpath(os.path.join(savefig_dir,filename), |
|
276 | 281 | source_dir) |
|
277 | 282 | |
|
278 | 283 | imagerows = ['.. image:: %s'%outfile] |
|
279 | 284 | |
|
280 | 285 | for kwarg in saveargs[2:]: |
|
281 | 286 | arg, val = kwarg.split('=') |
|
282 | 287 | arg = arg.strip() |
|
283 | 288 | val = val.strip() |
|
284 | 289 | imagerows.append(' :%s: %s'%(arg, val)) |
|
285 | 290 | |
|
286 | 291 | image_file = os.path.basename(outfile) # only return file name |
|
287 | 292 | image_directive = '\n'.join(imagerows) |
|
288 | 293 | return image_file, image_directive |
|
289 | 294 | |
|
290 | 295 | |
|
291 | 296 | # Callbacks for each type of token |
|
292 | 297 | def process_input(self, data, input_prompt, lineno): |
|
293 | 298 | """Process data block for INPUT token.""" |
|
294 | 299 | decorator, input, rest = data |
|
295 | 300 | image_file = None |
|
296 | 301 | image_directive = None |
|
297 | 302 | #print 'INPUT:', data # dbg |
|
298 | 303 | is_verbatim = decorator=='@verbatim' or self.is_verbatim |
|
299 | 304 | is_doctest = decorator=='@doctest' or self.is_doctest |
|
300 | 305 | is_suppress = decorator=='@suppress' or self.is_suppress |
|
301 | 306 | is_savefig = decorator is not None and \ |
|
302 | 307 | decorator.startswith('@savefig') |
|
303 | 308 | |
|
304 | 309 | input_lines = input.split('\n') |
|
305 | 310 | if len(input_lines) > 1: |
|
306 | 311 | if input_lines[-1] != "": |
|
307 | 312 | input_lines.append('') # make sure there's a blank line |
|
308 | 313 | # so splitter buffer gets reset |
|
309 | 314 | |
|
310 | 315 | continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2)) |
|
311 | 316 | Nc = len(continuation) |
|
312 | 317 | |
|
313 | 318 | if is_savefig: |
|
314 | 319 | image_file, image_directive = self.process_image(decorator) |
|
315 | 320 | |
|
316 | 321 | ret = [] |
|
317 | 322 | is_semicolon = False |
|
318 | 323 | |
|
319 | 324 | for i, line in enumerate(input_lines): |
|
320 | 325 | if line.endswith(';'): |
|
321 | 326 | is_semicolon = True |
|
322 | 327 | |
|
323 | 328 | if i==0: |
|
324 | 329 | # process the first input line |
|
325 | 330 | if is_verbatim: |
|
326 | 331 | self.process_input_line('') |
|
327 | 332 | self.IP.execution_count += 1 # increment it anyway |
|
328 | 333 | else: |
|
329 | 334 | # only submit the line in non-verbatim mode |
|
330 | 335 | self.process_input_line(line, store_history=True) |
|
331 | 336 | formatted_line = '%s %s'%(input_prompt, line) |
|
332 | 337 | else: |
|
333 | 338 | # process a continuation line |
|
334 | 339 | if not is_verbatim: |
|
335 | 340 | self.process_input_line(line, store_history=True) |
|
336 | 341 | |
|
337 | 342 | formatted_line = '%s %s'%(continuation, line) |
|
338 | 343 | |
|
339 | 344 | if not is_suppress: |
|
340 | 345 | ret.append(formatted_line) |
|
341 | 346 | |
|
342 | 347 | if not is_suppress and len(rest.strip()) and is_verbatim: |
|
343 | 348 | # the "rest" is the standard output of the |
|
344 | 349 | # input, which needs to be added in |
|
345 | 350 | # verbatim mode |
|
346 | 351 | ret.append(rest) |
|
347 | 352 | |
|
348 | 353 | self.cout.seek(0) |
|
349 | 354 | output = self.cout.read() |
|
350 | 355 | if not is_suppress and not is_semicolon: |
|
351 | 356 | ret.append(output) |
|
352 | 357 | elif is_semicolon: # get spacing right |
|
353 | 358 | ret.append('') |
|
354 | 359 | |
|
355 | 360 | self.cout.truncate(0) |
|
356 | 361 | return (ret, input_lines, output, is_doctest, image_file, |
|
357 | 362 | image_directive) |
|
358 | 363 | #print 'OUTPUT', output # dbg |
|
359 | 364 | |
|
360 | 365 | def process_output(self, data, output_prompt, |
|
361 | 366 | input_lines, output, is_doctest, image_file): |
|
362 | 367 | """Process data block for OUTPUT token.""" |
|
363 | 368 | if is_doctest: |
|
364 | 369 | submitted = data.strip() |
|
365 | 370 | found = output |
|
366 | 371 | if found is not None: |
|
367 | 372 | found = found.strip() |
|
368 | 373 | |
|
369 | 374 | # XXX - fperez: in 0.11, 'output' never comes with the prompt |
|
370 | 375 | # in it, just the actual output text. So I think all this code |
|
371 | 376 | # can be nuked... |
|
372 | 377 | |
|
373 | 378 | # the above comment does not appear to be accurate... (minrk) |
|
374 | 379 | |
|
375 | 380 | ind = found.find(output_prompt) |
|
376 | 381 | if ind<0: |
|
377 | 382 | e='output prompt="%s" does not match out line=%s' % \ |
|
378 | 383 | (output_prompt, found) |
|
379 | 384 | raise RuntimeError(e) |
|
380 | 385 | found = found[len(output_prompt):].strip() |
|
381 | 386 | |
|
382 | 387 | if found!=submitted: |
|
383 | 388 | e = ('doctest failure for input_lines="%s" with ' |
|
384 | 389 | 'found_output="%s" and submitted output="%s"' % |
|
385 | 390 | (input_lines, found, submitted) ) |
|
386 | 391 | raise RuntimeError(e) |
|
387 | 392 | #print 'doctest PASSED for input_lines="%s" with found_output="%s" and submitted output="%s"'%(input_lines, found, submitted) |
|
388 | 393 | |
|
389 | 394 | def process_comment(self, data): |
|
390 | 395 | """Process data fPblock for COMMENT token.""" |
|
391 | 396 | if not self.is_suppress: |
|
392 | 397 | return [data] |
|
393 | 398 | |
|
394 | 399 | def save_image(self, image_file): |
|
395 | 400 | """ |
|
396 | 401 | Saves the image file to disk. |
|
397 | 402 | """ |
|
398 | 403 | self.ensure_pyplot() |
|
399 | 404 | command = 'plt.gcf().savefig("%s")'%image_file |
|
400 | 405 | #print 'SAVEFIG', command # dbg |
|
401 | 406 | self.process_input_line('bookmark ipy_thisdir', store_history=False) |
|
402 | 407 | self.process_input_line('cd -b ipy_savedir', store_history=False) |
|
403 | 408 | self.process_input_line(command, store_history=False) |
|
404 | 409 | self.process_input_line('cd -b ipy_thisdir', store_history=False) |
|
405 | 410 | self.process_input_line('bookmark -d ipy_thisdir', store_history=False) |
|
406 | 411 | self.clear_cout() |
|
407 | 412 | |
|
408 | 413 | |
|
409 | 414 | def process_block(self, block): |
|
410 | 415 | """ |
|
411 | 416 | process block from the block_parser and return a list of processed lines |
|
412 | 417 | """ |
|
413 | 418 | ret = [] |
|
414 | 419 | output = None |
|
415 | 420 | input_lines = None |
|
416 | 421 | lineno = self.IP.execution_count |
|
417 | 422 | |
|
418 | 423 | input_prompt = self.promptin%lineno |
|
419 | 424 | output_prompt = self.promptout%lineno |
|
420 | 425 | image_file = None |
|
421 | 426 | image_directive = None |
|
422 | 427 | |
|
423 | 428 | for token, data in block: |
|
424 | 429 | if token==COMMENT: |
|
425 | 430 | out_data = self.process_comment(data) |
|
426 | 431 | elif token==INPUT: |
|
427 | 432 | (out_data, input_lines, output, is_doctest, image_file, |
|
428 | 433 | image_directive) = \ |
|
429 | 434 | self.process_input(data, input_prompt, lineno) |
|
430 | 435 | elif token==OUTPUT: |
|
431 | 436 | out_data = \ |
|
432 | 437 | self.process_output(data, output_prompt, |
|
433 | 438 | input_lines, output, is_doctest, |
|
434 | 439 | image_file) |
|
435 | 440 | if out_data: |
|
436 | 441 | ret.extend(out_data) |
|
437 | 442 | |
|
438 | 443 | # save the image files |
|
439 | 444 | if image_file is not None: |
|
440 | 445 | self.save_image(image_file) |
|
441 | 446 | |
|
442 | 447 | return ret, image_directive |
|
443 | 448 | |
|
444 | 449 | def ensure_pyplot(self): |
|
445 | 450 | if self._pyplot_imported: |
|
446 | 451 | return |
|
447 | 452 | self.process_input_line('import matplotlib.pyplot as plt', |
|
448 | 453 | store_history=False) |
|
449 | 454 | |
|
450 | 455 | def process_pure_python(self, content): |
|
451 | 456 | """ |
|
452 | 457 | content is a list of strings. it is unedited directive conent |
|
453 | 458 | |
|
454 | 459 | This runs it line by line in the InteractiveShell, prepends |
|
455 | 460 | prompts as needed capturing stderr and stdout, then returns |
|
456 | 461 | the content as a list as if it were ipython code |
|
457 | 462 | """ |
|
458 | 463 | output = [] |
|
459 | 464 | savefig = False # keep up with this to clear figure |
|
460 | 465 | multiline = False # to handle line continuation |
|
461 | 466 | multiline_start = None |
|
462 | 467 | fmtin = self.promptin |
|
463 | 468 | |
|
464 | 469 | ct = 0 |
|
465 | 470 | |
|
466 | 471 | for lineno, line in enumerate(content): |
|
467 | 472 | |
|
468 | 473 | line_stripped = line.strip() |
|
469 | 474 | if not len(line): |
|
470 | 475 | output.append(line) |
|
471 | 476 | continue |
|
472 | 477 | |
|
473 | 478 | # handle decorators |
|
474 | 479 | if line_stripped.startswith('@'): |
|
475 | 480 | output.extend([line]) |
|
476 | 481 | if 'savefig' in line: |
|
477 | 482 | savefig = True # and need to clear figure |
|
478 | 483 | continue |
|
479 | 484 | |
|
480 | 485 | # handle comments |
|
481 | 486 | if line_stripped.startswith('#'): |
|
482 | 487 | output.extend([line]) |
|
483 | 488 | continue |
|
484 | 489 | |
|
485 | 490 | # deal with lines checking for multiline |
|
486 | 491 | continuation = u' %s:'% ''.join(['.']*(len(str(ct))+2)) |
|
487 | 492 | if not multiline: |
|
488 | 493 | modified = u"%s %s" % (fmtin % ct, line_stripped) |
|
489 | 494 | output.append(modified) |
|
490 | 495 | ct += 1 |
|
491 | 496 | try: |
|
492 | 497 | ast.parse(line_stripped) |
|
493 | 498 | output.append(u'') |
|
494 | 499 | except Exception: # on a multiline |
|
495 | 500 | multiline = True |
|
496 | 501 | multiline_start = lineno |
|
497 | 502 | else: # still on a multiline |
|
498 | 503 | modified = u'%s %s' % (continuation, line) |
|
499 | 504 | output.append(modified) |
|
500 | 505 | |
|
501 | 506 | # if the next line is indented, it should be part of multiline |
|
502 | 507 | if len(content) > lineno + 1: |
|
503 | 508 | nextline = content[lineno + 1] |
|
504 | 509 | if len(nextline) - len(nextline.lstrip()) > 3: |
|
505 | 510 | continue |
|
506 | 511 | try: |
|
507 | 512 | mod = ast.parse( |
|
508 | 513 | '\n'.join(content[multiline_start:lineno+1])) |
|
509 | 514 | if isinstance(mod.body[0], ast.FunctionDef): |
|
510 | 515 | # check to see if we have the whole function |
|
511 | 516 | for element in mod.body[0].body: |
|
512 | 517 | if isinstance(element, ast.Return): |
|
513 | 518 | multiline = False |
|
514 | 519 | else: |
|
515 | 520 | output.append(u'') |
|
516 | 521 | multiline = False |
|
517 | 522 | except Exception: |
|
518 | 523 | pass |
|
519 | 524 | |
|
520 | 525 | if savefig: # clear figure if plotted |
|
521 | 526 | self.ensure_pyplot() |
|
522 | 527 | self.process_input_line('plt.clf()', store_history=False) |
|
523 | 528 | self.clear_cout() |
|
524 | 529 | savefig = False |
|
525 | 530 | |
|
526 | 531 | return output |
|
527 | 532 | |
|
528 | 533 | class IPythonDirective(Directive): |
|
529 | 534 | |
|
530 | 535 | has_content = True |
|
531 | 536 | required_arguments = 0 |
|
532 | 537 | optional_arguments = 4 # python, suppress, verbatim, doctest |
|
533 | 538 | final_argumuent_whitespace = True |
|
534 | 539 | option_spec = { 'python': directives.unchanged, |
|
535 | 540 | 'suppress' : directives.flag, |
|
536 | 541 | 'verbatim' : directives.flag, |
|
537 | 542 | 'doctest' : directives.flag, |
|
538 | 543 | } |
|
539 | 544 | |
|
540 | 545 | shell = None |
|
541 | 546 | |
|
542 | 547 | seen_docs = set() |
|
543 | 548 | |
|
544 | 549 | def get_config_options(self): |
|
545 | 550 | # contains sphinx configuration variables |
|
546 | 551 | config = self.state.document.settings.env.config |
|
547 | 552 | |
|
548 | 553 | # get config variables to set figure output directory |
|
549 | 554 | confdir = self.state.document.settings.env.app.confdir |
|
550 | 555 | savefig_dir = config.ipython_savefig_dir |
|
551 | 556 | source_dir = os.path.dirname(self.state.document.current_source) |
|
552 | 557 | if savefig_dir is None: |
|
553 | 558 | savefig_dir = config.html_static_path |
|
554 | 559 | if isinstance(savefig_dir, list): |
|
555 | 560 | savefig_dir = savefig_dir[0] # safe to assume only one path? |
|
556 | 561 | savefig_dir = os.path.join(confdir, savefig_dir) |
|
557 | 562 | |
|
558 | 563 | # get regex and prompt stuff |
|
559 | 564 | rgxin = config.ipython_rgxin |
|
560 | 565 | rgxout = config.ipython_rgxout |
|
561 | 566 | promptin = config.ipython_promptin |
|
562 | 567 | promptout = config.ipython_promptout |
|
563 | 568 | |
|
564 | 569 | return savefig_dir, source_dir, rgxin, rgxout, promptin, promptout |
|
565 | 570 | |
|
566 | 571 | def setup(self): |
|
567 | 572 | if self.shell is None: |
|
568 | 573 | self.shell = EmbeddedSphinxShell() |
|
569 | 574 | # reset the execution count if we haven't processed this doc |
|
570 | 575 | #NOTE: this may be borked if there are multiple seen_doc tmp files |
|
571 | 576 | #check time stamp? |
|
572 | 577 | |
|
573 | 578 | if not self.state.document.current_source in self.seen_docs: |
|
574 | 579 | self.shell.IP.history_manager.reset() |
|
575 | 580 | self.shell.IP.execution_count = 1 |
|
576 | 581 | self.seen_docs.add(self.state.document.current_source) |
|
577 | 582 | |
|
578 | 583 | |
|
579 | 584 | |
|
580 | 585 | # get config values |
|
581 | 586 | (savefig_dir, source_dir, rgxin, |
|
582 | 587 | rgxout, promptin, promptout) = self.get_config_options() |
|
583 | 588 | |
|
584 | 589 | # and attach to shell so we don't have to pass them around |
|
585 | 590 | self.shell.rgxin = rgxin |
|
586 | 591 | self.shell.rgxout = rgxout |
|
587 | 592 | self.shell.promptin = promptin |
|
588 | 593 | self.shell.promptout = promptout |
|
589 | 594 | self.shell.savefig_dir = savefig_dir |
|
590 | 595 | self.shell.source_dir = source_dir |
|
591 | 596 | |
|
592 | 597 | # setup bookmark for saving figures directory |
|
593 | 598 | |
|
594 | 599 | self.shell.process_input_line('bookmark ipy_savedir %s'%savefig_dir, |
|
595 | 600 | store_history=False) |
|
596 | 601 | self.shell.clear_cout() |
|
597 | 602 | |
|
598 | 603 | return rgxin, rgxout, promptin, promptout |
|
599 | 604 | |
|
600 | 605 | |
|
601 | 606 | def teardown(self): |
|
602 | 607 | # delete last bookmark |
|
603 | 608 | self.shell.process_input_line('bookmark -d ipy_savedir', |
|
604 | 609 | store_history=False) |
|
605 | 610 | self.shell.clear_cout() |
|
606 | 611 | |
|
607 | 612 | def run(self): |
|
608 | 613 | debug = False |
|
609 | 614 | |
|
610 | 615 | #TODO, any reason block_parser can't be a method of embeddable shell |
|
611 | 616 | # then we wouldn't have to carry these around |
|
612 | 617 | rgxin, rgxout, promptin, promptout = self.setup() |
|
613 | 618 | |
|
614 | 619 | options = self.options |
|
615 | 620 | self.shell.is_suppress = 'suppress' in options |
|
616 | 621 | self.shell.is_doctest = 'doctest' in options |
|
617 | 622 | self.shell.is_verbatim = 'verbatim' in options |
|
618 | 623 | |
|
619 | 624 | |
|
620 | 625 | # handle pure python code |
|
621 | 626 | if 'python' in self.arguments: |
|
622 | 627 | content = self.content |
|
623 | 628 | self.content = self.shell.process_pure_python(content) |
|
624 | 629 | |
|
625 | 630 | parts = '\n'.join(self.content).split('\n\n') |
|
626 | 631 | |
|
627 | 632 | lines = ['.. code-block:: ipython',''] |
|
628 | 633 | figures = [] |
|
629 | 634 | |
|
630 | 635 | for part in parts: |
|
631 | 636 | |
|
632 | 637 | block = block_parser(part, rgxin, rgxout, promptin, promptout) |
|
633 | 638 | |
|
634 | 639 | if len(block): |
|
635 | 640 | rows, figure = self.shell.process_block(block) |
|
636 | 641 | for row in rows: |
|
637 | 642 | lines.extend([' %s'%line for line in row.split('\n')]) |
|
638 | 643 | |
|
639 | 644 | if figure is not None: |
|
640 | 645 | figures.append(figure) |
|
641 | 646 | |
|
642 | 647 | #text = '\n'.join(lines) |
|
643 | 648 | #figs = '\n'.join(figures) |
|
644 | 649 | |
|
645 | 650 | for figure in figures: |
|
646 | 651 | lines.append('') |
|
647 | 652 | lines.extend(figure.split('\n')) |
|
648 | 653 | lines.append('') |
|
649 | 654 | |
|
650 | 655 | #print lines |
|
651 | 656 | if len(lines)>2: |
|
652 | 657 | if debug: |
|
653 | 658 | print('\n'.join(lines)) |
|
654 | 659 | else: #NOTE: this raises some errors, what's it for? |
|
655 | 660 | #print 'INSERTING %d lines'%len(lines) |
|
656 | 661 | self.state_machine.insert_input( |
|
657 | 662 | lines, self.state_machine.input_lines.source(0)) |
|
658 | 663 | |
|
659 | 664 | text = '\n'.join(lines) |
|
660 | 665 | txtnode = nodes.literal_block(text, text) |
|
661 | 666 | txtnode['language'] = 'ipython' |
|
662 | 667 | #imgnode = nodes.image(figs) |
|
663 | 668 | |
|
664 | 669 | # cleanup |
|
665 | 670 | self.teardown() |
|
666 | 671 | |
|
667 | 672 | return []#, imgnode] |
|
668 | 673 | |
|
669 | 674 | # Enable as a proper Sphinx directive |
|
670 | 675 | def setup(app): |
|
671 | 676 | setup.app = app |
|
672 | 677 | |
|
673 | 678 | app.add_directive('ipython', IPythonDirective) |
|
674 | 679 | app.add_config_value('ipython_savefig_dir', None, True) |
|
675 | 680 | app.add_config_value('ipython_rgxin', |
|
676 | 681 | re.compile('In \[(\d+)\]:\s?(.*)\s*'), True) |
|
677 | 682 | app.add_config_value('ipython_rgxout', |
|
678 | 683 | re.compile('Out\[(\d+)\]:\s?(.*)\s*'), True) |
|
679 | 684 | app.add_config_value('ipython_promptin', 'In [%d]:', True) |
|
680 | 685 | app.add_config_value('ipython_promptout', 'Out[%d]:', True) |
|
681 | 686 | |
|
682 | 687 | |
|
683 | 688 | # Simple smoke test, needs to be converted to a proper automatic test. |
|
684 | 689 | def test(): |
|
685 | 690 | |
|
686 | 691 | examples = [ |
|
687 | 692 | r""" |
|
688 | 693 | In [9]: pwd |
|
689 | 694 | Out[9]: '/home/jdhunter/py4science/book' |
|
690 | 695 | |
|
691 | 696 | In [10]: cd bookdata/ |
|
692 | 697 | /home/jdhunter/py4science/book/bookdata |
|
693 | 698 | |
|
694 | 699 | In [2]: from pylab import * |
|
695 | 700 | |
|
696 | 701 | In [2]: ion() |
|
697 | 702 | |
|
698 | 703 | In [3]: im = imread('stinkbug.png') |
|
699 | 704 | |
|
700 | 705 | @savefig mystinkbug.png width=4in |
|
701 | 706 | In [4]: imshow(im) |
|
702 | 707 | Out[4]: <matplotlib.image.AxesImage object at 0x39ea850> |
|
703 | 708 | |
|
704 | 709 | """, |
|
705 | 710 | r""" |
|
706 | 711 | |
|
707 | 712 | In [1]: x = 'hello world' |
|
708 | 713 | |
|
709 | 714 | # string methods can be |
|
710 | 715 | # used to alter the string |
|
711 | 716 | @doctest |
|
712 | 717 | In [2]: x.upper() |
|
713 | 718 | Out[2]: 'HELLO WORLD' |
|
714 | 719 | |
|
715 | 720 | @verbatim |
|
716 | 721 | In [3]: x.st<TAB> |
|
717 | 722 | x.startswith x.strip |
|
718 | 723 | """, |
|
719 | 724 | r""" |
|
720 | 725 | |
|
721 | 726 | In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\ |
|
722 | 727 | .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv' |
|
723 | 728 | |
|
724 | 729 | In [131]: print url.split('&') |
|
725 | 730 | ['http://ichart.finance.yahoo.com/table.csv?s=CROX', 'd=9', 'e=22', 'f=2009', 'g=d', 'a=1', 'b=8', 'c=2006', 'ignore=.csv'] |
|
726 | 731 | |
|
727 | 732 | In [60]: import urllib |
|
728 | 733 | |
|
729 | 734 | """, |
|
730 | 735 | r"""\ |
|
731 | 736 | |
|
732 | 737 | In [133]: import numpy.random |
|
733 | 738 | |
|
734 | 739 | @suppress |
|
735 | 740 | In [134]: numpy.random.seed(2358) |
|
736 | 741 | |
|
737 | 742 | @doctest |
|
738 | 743 | In [135]: numpy.random.rand(10,2) |
|
739 | 744 | Out[135]: |
|
740 | 745 | array([[ 0.64524308, 0.59943846], |
|
741 | 746 | [ 0.47102322, 0.8715456 ], |
|
742 | 747 | [ 0.29370834, 0.74776844], |
|
743 | 748 | [ 0.99539577, 0.1313423 ], |
|
744 | 749 | [ 0.16250302, 0.21103583], |
|
745 | 750 | [ 0.81626524, 0.1312433 ], |
|
746 | 751 | [ 0.67338089, 0.72302393], |
|
747 | 752 | [ 0.7566368 , 0.07033696], |
|
748 | 753 | [ 0.22591016, 0.77731835], |
|
749 | 754 | [ 0.0072729 , 0.34273127]]) |
|
750 | 755 | |
|
751 | 756 | """, |
|
752 | 757 | |
|
753 | 758 | r""" |
|
754 | 759 | In [106]: print x |
|
755 | 760 | jdh |
|
756 | 761 | |
|
757 | 762 | In [109]: for i in range(10): |
|
758 | 763 | .....: print i |
|
759 | 764 | .....: |
|
760 | 765 | .....: |
|
761 | 766 | 0 |
|
762 | 767 | 1 |
|
763 | 768 | 2 |
|
764 | 769 | 3 |
|
765 | 770 | 4 |
|
766 | 771 | 5 |
|
767 | 772 | 6 |
|
768 | 773 | 7 |
|
769 | 774 | 8 |
|
770 | 775 | 9 |
|
771 | 776 | """, |
|
772 | 777 | |
|
773 | 778 | r""" |
|
774 | 779 | |
|
775 | 780 | In [144]: from pylab import * |
|
776 | 781 | |
|
777 | 782 | In [145]: ion() |
|
778 | 783 | |
|
779 | 784 | # use a semicolon to suppress the output |
|
780 | 785 | @savefig test_hist.png width=4in |
|
781 | 786 | In [151]: hist(np.random.randn(10000), 100); |
|
782 | 787 | |
|
783 | 788 | |
|
784 | 789 | @savefig test_plot.png width=4in |
|
785 | 790 | In [151]: plot(np.random.randn(10000), 'o'); |
|
786 | 791 | """, |
|
787 | 792 | |
|
788 | 793 | r""" |
|
789 | 794 | # use a semicolon to suppress the output |
|
790 | 795 | In [151]: plt.clf() |
|
791 | 796 | |
|
792 | 797 | @savefig plot_simple.png width=4in |
|
793 | 798 | In [151]: plot([1,2,3]) |
|
794 | 799 | |
|
795 | 800 | @savefig hist_simple.png width=4in |
|
796 | 801 | In [151]: hist(np.random.randn(10000), 100); |
|
797 | 802 | |
|
798 | 803 | """, |
|
799 | 804 | r""" |
|
800 | 805 | # update the current fig |
|
801 | 806 | In [151]: ylabel('number') |
|
802 | 807 | |
|
803 | 808 | In [152]: title('normal distribution') |
|
804 | 809 | |
|
805 | 810 | |
|
806 | 811 | @savefig hist_with_text.png |
|
807 | 812 | In [153]: grid(True) |
|
808 | 813 | |
|
809 | 814 | """, |
|
810 | 815 | ] |
|
811 | 816 | # skip local-file depending first example: |
|
812 | 817 | examples = examples[1:] |
|
813 | 818 | |
|
814 | 819 | #ipython_directive.DEBUG = True # dbg |
|
815 | 820 | #options = dict(suppress=True) # dbg |
|
816 | 821 | options = dict() |
|
817 | 822 | for example in examples: |
|
818 | 823 | content = example.split('\n') |
|
819 | 824 | IPythonDirective('debug', arguments=None, options=options, |
|
820 | 825 | content=content, lineno=0, |
|
821 | 826 | content_offset=None, block_text=None, |
|
822 | 827 | state=None, state_machine=None, |
|
823 | 828 | ) |
|
824 | 829 | |
|
825 | 830 | # Run test suite as a script |
|
826 | 831 | if __name__=='__main__': |
|
827 | 832 | if not os.path.isdir('_static'): |
|
828 | 833 | os.mkdir('_static') |
|
829 | 834 | test() |
|
830 | 835 | print('All OK? Check figures in _static/') |
@@ -1,760 +1,764 | |||
|
1 | 1 | """Nose Plugin that supports IPython doctests. |
|
2 | 2 | |
|
3 | 3 | Limitations: |
|
4 | 4 | |
|
5 | 5 | - When generating examples for use as doctests, make sure that you have |
|
6 | 6 | pretty-printing OFF. This can be done either by setting the |
|
7 | 7 | ``PlainTextFormatter.pprint`` option in your configuration file to False, or |
|
8 | 8 | by interactively disabling it with %Pprint. This is required so that IPython |
|
9 | 9 | output matches that of normal Python, which is used by doctest for internal |
|
10 | 10 | execution. |
|
11 | 11 | |
|
12 | 12 | - Do not rely on specific prompt numbers for results (such as using |
|
13 | 13 | '_34==True', for example). For IPython tests run via an external process the |
|
14 | 14 | prompt numbers may be different, and IPython tests run as normal python code |
|
15 | 15 | won't even have these special _NN variables set at all. |
|
16 | 16 | """ |
|
17 | 17 | |
|
18 | 18 | #----------------------------------------------------------------------------- |
|
19 | 19 | # Module imports |
|
20 | 20 | |
|
21 | 21 | # From the standard library |
|
22 | 22 | import doctest |
|
23 | 23 | import inspect |
|
24 | 24 | import logging |
|
25 | 25 | import os |
|
26 | 26 | import re |
|
27 | 27 | import sys |
|
28 | 28 | import traceback |
|
29 | 29 | import unittest |
|
30 | 30 | |
|
31 | 31 | from inspect import getmodule |
|
32 | from io import StringIO | |
|
33 | 32 | |
|
34 | 33 | # We are overriding the default doctest runner, so we need to import a few |
|
35 | 34 | # things from doctest directly |
|
36 | 35 | from doctest import (REPORTING_FLAGS, REPORT_ONLY_FIRST_FAILURE, |
|
37 | 36 | _unittest_reportflags, DocTestRunner, |
|
38 | 37 | _extract_future_flags, pdb, _OutputRedirectingPdb, |
|
39 | 38 | _exception_traceback, |
|
40 | 39 | linecache) |
|
41 | 40 | |
|
42 | 41 | # Third-party modules |
|
43 | 42 | import nose.core |
|
44 | 43 | |
|
45 | 44 | from nose.plugins import doctests, Plugin |
|
46 | 45 | from nose.util import anyp, getpackage, test_address, resolve_name, tolist |
|
47 | 46 | |
|
48 | 47 | # Our own imports |
|
49 | from IPython.utils.py3compat import builtin_mod | |
|
48 | from IPython.utils.py3compat import builtin_mod, PY3 | |
|
49 | ||
|
50 | if PY3: | |
|
51 | from io import StringIO | |
|
52 | else: | |
|
53 | from StringIO import StringIO | |
|
50 | 54 | |
|
51 | 55 | #----------------------------------------------------------------------------- |
|
52 | 56 | # Module globals and other constants |
|
53 | 57 | #----------------------------------------------------------------------------- |
|
54 | 58 | |
|
55 | 59 | log = logging.getLogger(__name__) |
|
56 | 60 | |
|
57 | 61 | |
|
58 | 62 | #----------------------------------------------------------------------------- |
|
59 | 63 | # Classes and functions |
|
60 | 64 | #----------------------------------------------------------------------------- |
|
61 | 65 | |
|
62 | 66 | def is_extension_module(filename): |
|
63 | 67 | """Return whether the given filename is an extension module. |
|
64 | 68 | |
|
65 | 69 | This simply checks that the extension is either .so or .pyd. |
|
66 | 70 | """ |
|
67 | 71 | return os.path.splitext(filename)[1].lower() in ('.so','.pyd') |
|
68 | 72 | |
|
69 | 73 | |
|
70 | 74 | class DocTestSkip(object): |
|
71 | 75 | """Object wrapper for doctests to be skipped.""" |
|
72 | 76 | |
|
73 | 77 | ds_skip = """Doctest to skip. |
|
74 | 78 | >>> 1 #doctest: +SKIP |
|
75 | 79 | """ |
|
76 | 80 | |
|
77 | 81 | def __init__(self,obj): |
|
78 | 82 | self.obj = obj |
|
79 | 83 | |
|
80 | 84 | def __getattribute__(self,key): |
|
81 | 85 | if key == '__doc__': |
|
82 | 86 | return DocTestSkip.ds_skip |
|
83 | 87 | else: |
|
84 | 88 | return getattr(object.__getattribute__(self,'obj'),key) |
|
85 | 89 | |
|
86 | 90 | # Modified version of the one in the stdlib, that fixes a python bug (doctests |
|
87 | 91 | # not found in extension modules, http://bugs.python.org/issue3158) |
|
88 | 92 | class DocTestFinder(doctest.DocTestFinder): |
|
89 | 93 | |
|
90 | 94 | def _from_module(self, module, object): |
|
91 | 95 | """ |
|
92 | 96 | Return true if the given object is defined in the given |
|
93 | 97 | module. |
|
94 | 98 | """ |
|
95 | 99 | if module is None: |
|
96 | 100 | return True |
|
97 | 101 | elif inspect.isfunction(object): |
|
98 | 102 | return module.__dict__ is object.__globals__ |
|
99 | 103 | elif inspect.isbuiltin(object): |
|
100 | 104 | return module.__name__ == object.__module__ |
|
101 | 105 | elif inspect.isclass(object): |
|
102 | 106 | return module.__name__ == object.__module__ |
|
103 | 107 | elif inspect.ismethod(object): |
|
104 | 108 | # This one may be a bug in cython that fails to correctly set the |
|
105 | 109 | # __module__ attribute of methods, but since the same error is easy |
|
106 | 110 | # to make by extension code writers, having this safety in place |
|
107 | 111 | # isn't such a bad idea |
|
108 | 112 | return module.__name__ == object.im_class.__module__ |
|
109 | 113 | elif inspect.getmodule(object) is not None: |
|
110 | 114 | return module is inspect.getmodule(object) |
|
111 | 115 | elif hasattr(object, '__module__'): |
|
112 | 116 | return module.__name__ == object.__module__ |
|
113 | 117 | elif isinstance(object, property): |
|
114 | 118 | return True # [XX] no way not be sure. |
|
115 | 119 | else: |
|
116 | 120 | raise ValueError("object must be a class or function") |
|
117 | 121 | |
|
118 | 122 | def _find(self, tests, obj, name, module, source_lines, globs, seen): |
|
119 | 123 | """ |
|
120 | 124 | Find tests for the given object and any contained objects, and |
|
121 | 125 | add them to `tests`. |
|
122 | 126 | """ |
|
123 | 127 | #print '_find for:', obj, name, module # dbg |
|
124 | 128 | if hasattr(obj,"skip_doctest"): |
|
125 | 129 | #print 'SKIPPING DOCTEST FOR:',obj # dbg |
|
126 | 130 | obj = DocTestSkip(obj) |
|
127 | 131 | |
|
128 | 132 | doctest.DocTestFinder._find(self,tests, obj, name, module, |
|
129 | 133 | source_lines, globs, seen) |
|
130 | 134 | |
|
131 | 135 | # Below we re-run pieces of the above method with manual modifications, |
|
132 | 136 | # because the original code is buggy and fails to correctly identify |
|
133 | 137 | # doctests in extension modules. |
|
134 | 138 | |
|
135 | 139 | # Local shorthands |
|
136 | 140 | from inspect import isroutine, isclass, ismodule |
|
137 | 141 | |
|
138 | 142 | # Look for tests in a module's contained objects. |
|
139 | 143 | if inspect.ismodule(obj) and self._recurse: |
|
140 | 144 | for valname, val in obj.__dict__.items(): |
|
141 | 145 | valname1 = '%s.%s' % (name, valname) |
|
142 | 146 | if ( (isroutine(val) or isclass(val)) |
|
143 | 147 | and self._from_module(module, val) ): |
|
144 | 148 | |
|
145 | 149 | self._find(tests, val, valname1, module, source_lines, |
|
146 | 150 | globs, seen) |
|
147 | 151 | |
|
148 | 152 | # Look for tests in a class's contained objects. |
|
149 | 153 | if inspect.isclass(obj) and self._recurse: |
|
150 | 154 | #print 'RECURSE into class:',obj # dbg |
|
151 | 155 | for valname, val in obj.__dict__.items(): |
|
152 | 156 | # Special handling for staticmethod/classmethod. |
|
153 | 157 | if isinstance(val, staticmethod): |
|
154 | 158 | val = getattr(obj, valname) |
|
155 | 159 | if isinstance(val, classmethod): |
|
156 | 160 | val = getattr(obj, valname).im_func |
|
157 | 161 | |
|
158 | 162 | # Recurse to methods, properties, and nested classes. |
|
159 | 163 | if ((inspect.isfunction(val) or inspect.isclass(val) or |
|
160 | 164 | inspect.ismethod(val) or |
|
161 | 165 | isinstance(val, property)) and |
|
162 | 166 | self._from_module(module, val)): |
|
163 | 167 | valname = '%s.%s' % (name, valname) |
|
164 | 168 | self._find(tests, val, valname, module, source_lines, |
|
165 | 169 | globs, seen) |
|
166 | 170 | |
|
167 | 171 | |
|
168 | 172 | class IPDoctestOutputChecker(doctest.OutputChecker): |
|
169 | 173 | """Second-chance checker with support for random tests. |
|
170 | 174 | |
|
171 | 175 | If the default comparison doesn't pass, this checker looks in the expected |
|
172 | 176 | output string for flags that tell us to ignore the output. |
|
173 | 177 | """ |
|
174 | 178 | |
|
175 | 179 | random_re = re.compile(r'#\s*random\s+') |
|
176 | 180 | |
|
177 | 181 | def check_output(self, want, got, optionflags): |
|
178 | 182 | """Check output, accepting special markers embedded in the output. |
|
179 | 183 | |
|
180 | 184 | If the output didn't pass the default validation but the special string |
|
181 | 185 | '#random' is included, we accept it.""" |
|
182 | 186 | |
|
183 | 187 | # Let the original tester verify first, in case people have valid tests |
|
184 | 188 | # that happen to have a comment saying '#random' embedded in. |
|
185 | 189 | ret = doctest.OutputChecker.check_output(self, want, got, |
|
186 | 190 | optionflags) |
|
187 | 191 | if not ret and self.random_re.search(want): |
|
188 | 192 | #print >> sys.stderr, 'RANDOM OK:',want # dbg |
|
189 | 193 | return True |
|
190 | 194 | |
|
191 | 195 | return ret |
|
192 | 196 | |
|
193 | 197 | |
|
194 | 198 | class DocTestCase(doctests.DocTestCase): |
|
195 | 199 | """Proxy for DocTestCase: provides an address() method that |
|
196 | 200 | returns the correct address for the doctest case. Otherwise |
|
197 | 201 | acts as a proxy to the test case. To provide hints for address(), |
|
198 | 202 | an obj may also be passed -- this will be used as the test object |
|
199 | 203 | for purposes of determining the test address, if it is provided. |
|
200 | 204 | """ |
|
201 | 205 | |
|
202 | 206 | # Note: this method was taken from numpy's nosetester module. |
|
203 | 207 | |
|
204 | 208 | # Subclass nose.plugins.doctests.DocTestCase to work around a bug in |
|
205 | 209 | # its constructor that blocks non-default arguments from being passed |
|
206 | 210 | # down into doctest.DocTestCase |
|
207 | 211 | |
|
208 | 212 | def __init__(self, test, optionflags=0, setUp=None, tearDown=None, |
|
209 | 213 | checker=None, obj=None, result_var='_'): |
|
210 | 214 | self._result_var = result_var |
|
211 | 215 | doctests.DocTestCase.__init__(self, test, |
|
212 | 216 | optionflags=optionflags, |
|
213 | 217 | setUp=setUp, tearDown=tearDown, |
|
214 | 218 | checker=checker) |
|
215 | 219 | # Now we must actually copy the original constructor from the stdlib |
|
216 | 220 | # doctest class, because we can't call it directly and a bug in nose |
|
217 | 221 | # means it never gets passed the right arguments. |
|
218 | 222 | |
|
219 | 223 | self._dt_optionflags = optionflags |
|
220 | 224 | self._dt_checker = checker |
|
221 | 225 | self._dt_test = test |
|
222 | 226 | self._dt_test_globs_ori = test.globs |
|
223 | 227 | self._dt_setUp = setUp |
|
224 | 228 | self._dt_tearDown = tearDown |
|
225 | 229 | |
|
226 | 230 | # XXX - store this runner once in the object! |
|
227 | 231 | runner = IPDocTestRunner(optionflags=optionflags, |
|
228 | 232 | checker=checker, verbose=False) |
|
229 | 233 | self._dt_runner = runner |
|
230 | 234 | |
|
231 | 235 | |
|
232 | 236 | # Each doctest should remember the directory it was loaded from, so |
|
233 | 237 | # things like %run work without too many contortions |
|
234 | 238 | self._ori_dir = os.path.dirname(test.filename) |
|
235 | 239 | |
|
236 | 240 | # Modified runTest from the default stdlib |
|
237 | 241 | def runTest(self): |
|
238 | 242 | test = self._dt_test |
|
239 | 243 | runner = self._dt_runner |
|
240 | 244 | |
|
241 | 245 | old = sys.stdout |
|
242 | 246 | new = StringIO() |
|
243 | 247 | optionflags = self._dt_optionflags |
|
244 | 248 | |
|
245 | 249 | if not (optionflags & REPORTING_FLAGS): |
|
246 | 250 | # The option flags don't include any reporting flags, |
|
247 | 251 | # so add the default reporting flags |
|
248 | 252 | optionflags |= _unittest_reportflags |
|
249 | 253 | |
|
250 | 254 | try: |
|
251 | 255 | # Save our current directory and switch out to the one where the |
|
252 | 256 | # test was originally created, in case another doctest did a |
|
253 | 257 | # directory change. We'll restore this in the finally clause. |
|
254 | 258 | curdir = os.getcwdu() |
|
255 | 259 | #print 'runTest in dir:', self._ori_dir # dbg |
|
256 | 260 | os.chdir(self._ori_dir) |
|
257 | 261 | |
|
258 | 262 | runner.DIVIDER = "-"*70 |
|
259 | 263 | failures, tries = runner.run(test,out=new.write, |
|
260 | 264 | clear_globs=False) |
|
261 | 265 | finally: |
|
262 | 266 | sys.stdout = old |
|
263 | 267 | os.chdir(curdir) |
|
264 | 268 | |
|
265 | 269 | if failures: |
|
266 | 270 | raise self.failureException(self.format_failure(new.getvalue())) |
|
267 | 271 | |
|
268 | 272 | def setUp(self): |
|
269 | 273 | """Modified test setup that syncs with ipython namespace""" |
|
270 | 274 | #print "setUp test", self._dt_test.examples # dbg |
|
271 | 275 | if isinstance(self._dt_test.examples[0], IPExample): |
|
272 | 276 | # for IPython examples *only*, we swap the globals with the ipython |
|
273 | 277 | # namespace, after updating it with the globals (which doctest |
|
274 | 278 | # fills with the necessary info from the module being tested). |
|
275 | 279 | self.user_ns_orig = {} |
|
276 | 280 | self.user_ns_orig.update(_ip.user_ns) |
|
277 | 281 | _ip.user_ns.update(self._dt_test.globs) |
|
278 | 282 | # We must remove the _ key in the namespace, so that Python's |
|
279 | 283 | # doctest code sets it naturally |
|
280 | 284 | _ip.user_ns.pop('_', None) |
|
281 | 285 | _ip.user_ns['__builtins__'] = builtin_mod |
|
282 | 286 | self._dt_test.globs = _ip.user_ns |
|
283 | 287 | |
|
284 | 288 | super(DocTestCase, self).setUp() |
|
285 | 289 | |
|
286 | 290 | def tearDown(self): |
|
287 | 291 | |
|
288 | 292 | # Undo the test.globs reassignment we made, so that the parent class |
|
289 | 293 | # teardown doesn't destroy the ipython namespace |
|
290 | 294 | if isinstance(self._dt_test.examples[0], IPExample): |
|
291 | 295 | self._dt_test.globs = self._dt_test_globs_ori |
|
292 | 296 | _ip.user_ns.clear() |
|
293 | 297 | _ip.user_ns.update(self.user_ns_orig) |
|
294 | 298 | |
|
295 | 299 | # XXX - fperez: I am not sure if this is truly a bug in nose 0.11, but |
|
296 | 300 | # it does look like one to me: its tearDown method tries to run |
|
297 | 301 | # |
|
298 | 302 | # delattr(builtin_mod, self._result_var) |
|
299 | 303 | # |
|
300 | 304 | # without checking that the attribute really is there; it implicitly |
|
301 | 305 | # assumes it should have been set via displayhook. But if the |
|
302 | 306 | # displayhook was never called, this doesn't necessarily happen. I |
|
303 | 307 | # haven't been able to find a little self-contained example outside of |
|
304 | 308 | # ipython that would show the problem so I can report it to the nose |
|
305 | 309 | # team, but it does happen a lot in our code. |
|
306 | 310 | # |
|
307 | 311 | # So here, we just protect as narrowly as possible by trapping an |
|
308 | 312 | # attribute error whose message would be the name of self._result_var, |
|
309 | 313 | # and letting any other error propagate. |
|
310 | 314 | try: |
|
311 | 315 | super(DocTestCase, self).tearDown() |
|
312 | 316 | except AttributeError as exc: |
|
313 | 317 | if exc.args[0] != self._result_var: |
|
314 | 318 | raise |
|
315 | 319 | |
|
316 | 320 | |
|
317 | 321 | # A simple subclassing of the original with a different class name, so we can |
|
318 | 322 | # distinguish and treat differently IPython examples from pure python ones. |
|
319 | 323 | class IPExample(doctest.Example): pass |
|
320 | 324 | |
|
321 | 325 | |
|
322 | 326 | class IPExternalExample(doctest.Example): |
|
323 | 327 | """Doctest examples to be run in an external process.""" |
|
324 | 328 | |
|
325 | 329 | def __init__(self, source, want, exc_msg=None, lineno=0, indent=0, |
|
326 | 330 | options=None): |
|
327 | 331 | # Parent constructor |
|
328 | 332 | doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options) |
|
329 | 333 | |
|
330 | 334 | # An EXTRA newline is needed to prevent pexpect hangs |
|
331 | 335 | self.source += '\n' |
|
332 | 336 | |
|
333 | 337 | |
|
334 | 338 | class IPDocTestParser(doctest.DocTestParser): |
|
335 | 339 | """ |
|
336 | 340 | A class used to parse strings containing doctest examples. |
|
337 | 341 | |
|
338 | 342 | Note: This is a version modified to properly recognize IPython input and |
|
339 | 343 | convert any IPython examples into valid Python ones. |
|
340 | 344 | """ |
|
341 | 345 | # This regular expression is used to find doctest examples in a |
|
342 | 346 | # string. It defines three groups: `source` is the source code |
|
343 | 347 | # (including leading indentation and prompts); `indent` is the |
|
344 | 348 | # indentation of the first (PS1) line of the source code; and |
|
345 | 349 | # `want` is the expected output (including leading indentation). |
|
346 | 350 | |
|
347 | 351 | # Classic Python prompts or default IPython ones |
|
348 | 352 | _PS1_PY = r'>>>' |
|
349 | 353 | _PS2_PY = r'\.\.\.' |
|
350 | 354 | |
|
351 | 355 | _PS1_IP = r'In\ \[\d+\]:' |
|
352 | 356 | _PS2_IP = r'\ \ \ \.\.\.+:' |
|
353 | 357 | |
|
354 | 358 | _RE_TPL = r''' |
|
355 | 359 | # Source consists of a PS1 line followed by zero or more PS2 lines. |
|
356 | 360 | (?P<source> |
|
357 | 361 | (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line |
|
358 | 362 | (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines |
|
359 | 363 | \n? # a newline |
|
360 | 364 | # Want consists of any non-blank lines that do not start with PS1. |
|
361 | 365 | (?P<want> (?:(?![ ]*$) # Not a blank line |
|
362 | 366 | (?![ ]*%s) # Not a line starting with PS1 |
|
363 | 367 | (?![ ]*%s) # Not a line starting with PS2 |
|
364 | 368 | .*$\n? # But any other line |
|
365 | 369 | )*) |
|
366 | 370 | ''' |
|
367 | 371 | |
|
368 | 372 | _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY), |
|
369 | 373 | re.MULTILINE | re.VERBOSE) |
|
370 | 374 | |
|
371 | 375 | _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP), |
|
372 | 376 | re.MULTILINE | re.VERBOSE) |
|
373 | 377 | |
|
374 | 378 | # Mark a test as being fully random. In this case, we simply append the |
|
375 | 379 | # random marker ('#random') to each individual example's output. This way |
|
376 | 380 | # we don't need to modify any other code. |
|
377 | 381 | _RANDOM_TEST = re.compile(r'#\s*all-random\s+') |
|
378 | 382 | |
|
379 | 383 | # Mark tests to be executed in an external process - currently unsupported. |
|
380 | 384 | _EXTERNAL_IP = re.compile(r'#\s*ipdoctest:\s*EXTERNAL') |
|
381 | 385 | |
|
382 | 386 | def ip2py(self,source): |
|
383 | 387 | """Convert input IPython source into valid Python.""" |
|
384 | 388 | block = _ip.input_transformer_manager.transform_cell(source) |
|
385 | 389 | if len(block.splitlines()) == 1: |
|
386 | 390 | return _ip.prefilter(block) |
|
387 | 391 | else: |
|
388 | 392 | return block |
|
389 | 393 | |
|
390 | 394 | def parse(self, string, name='<string>'): |
|
391 | 395 | """ |
|
392 | 396 | Divide the given string into examples and intervening text, |
|
393 | 397 | and return them as a list of alternating Examples and strings. |
|
394 | 398 | Line numbers for the Examples are 0-based. The optional |
|
395 | 399 | argument `name` is a name identifying this string, and is only |
|
396 | 400 | used for error messages. |
|
397 | 401 | """ |
|
398 | 402 | |
|
399 | 403 | #print 'Parse string:\n',string # dbg |
|
400 | 404 | |
|
401 | 405 | string = string.expandtabs() |
|
402 | 406 | # If all lines begin with the same indentation, then strip it. |
|
403 | 407 | min_indent = self._min_indent(string) |
|
404 | 408 | if min_indent > 0: |
|
405 | 409 | string = '\n'.join([l[min_indent:] for l in string.split('\n')]) |
|
406 | 410 | |
|
407 | 411 | output = [] |
|
408 | 412 | charno, lineno = 0, 0 |
|
409 | 413 | |
|
410 | 414 | # We make 'all random' tests by adding the '# random' mark to every |
|
411 | 415 | # block of output in the test. |
|
412 | 416 | if self._RANDOM_TEST.search(string): |
|
413 | 417 | random_marker = '\n# random' |
|
414 | 418 | else: |
|
415 | 419 | random_marker = '' |
|
416 | 420 | |
|
417 | 421 | # Whether to convert the input from ipython to python syntax |
|
418 | 422 | ip2py = False |
|
419 | 423 | # Find all doctest examples in the string. First, try them as Python |
|
420 | 424 | # examples, then as IPython ones |
|
421 | 425 | terms = list(self._EXAMPLE_RE_PY.finditer(string)) |
|
422 | 426 | if terms: |
|
423 | 427 | # Normal Python example |
|
424 | 428 | #print '-'*70 # dbg |
|
425 | 429 | #print 'PyExample, Source:\n',string # dbg |
|
426 | 430 | #print '-'*70 # dbg |
|
427 | 431 | Example = doctest.Example |
|
428 | 432 | else: |
|
429 | 433 | # It's an ipython example. Note that IPExamples are run |
|
430 | 434 | # in-process, so their syntax must be turned into valid python. |
|
431 | 435 | # IPExternalExamples are run out-of-process (via pexpect) so they |
|
432 | 436 | # don't need any filtering (a real ipython will be executing them). |
|
433 | 437 | terms = list(self._EXAMPLE_RE_IP.finditer(string)) |
|
434 | 438 | if self._EXTERNAL_IP.search(string): |
|
435 | 439 | #print '-'*70 # dbg |
|
436 | 440 | #print 'IPExternalExample, Source:\n',string # dbg |
|
437 | 441 | #print '-'*70 # dbg |
|
438 | 442 | Example = IPExternalExample |
|
439 | 443 | else: |
|
440 | 444 | #print '-'*70 # dbg |
|
441 | 445 | #print 'IPExample, Source:\n',string # dbg |
|
442 | 446 | #print '-'*70 # dbg |
|
443 | 447 | Example = IPExample |
|
444 | 448 | ip2py = True |
|
445 | 449 | |
|
446 | 450 | for m in terms: |
|
447 | 451 | # Add the pre-example text to `output`. |
|
448 | 452 | output.append(string[charno:m.start()]) |
|
449 | 453 | # Update lineno (lines before this example) |
|
450 | 454 | lineno += string.count('\n', charno, m.start()) |
|
451 | 455 | # Extract info from the regexp match. |
|
452 | 456 | (source, options, want, exc_msg) = \ |
|
453 | 457 | self._parse_example(m, name, lineno,ip2py) |
|
454 | 458 | |
|
455 | 459 | # Append the random-output marker (it defaults to empty in most |
|
456 | 460 | # cases, it's only non-empty for 'all-random' tests): |
|
457 | 461 | want += random_marker |
|
458 | 462 | |
|
459 | 463 | if Example is IPExternalExample: |
|
460 | 464 | options[doctest.NORMALIZE_WHITESPACE] = True |
|
461 | 465 | want += '\n' |
|
462 | 466 | |
|
463 | 467 | # Create an Example, and add it to the list. |
|
464 | 468 | if not self._IS_BLANK_OR_COMMENT(source): |
|
465 | 469 | output.append(Example(source, want, exc_msg, |
|
466 | 470 | lineno=lineno, |
|
467 | 471 | indent=min_indent+len(m.group('indent')), |
|
468 | 472 | options=options)) |
|
469 | 473 | # Update lineno (lines inside this example) |
|
470 | 474 | lineno += string.count('\n', m.start(), m.end()) |
|
471 | 475 | # Update charno. |
|
472 | 476 | charno = m.end() |
|
473 | 477 | # Add any remaining post-example text to `output`. |
|
474 | 478 | output.append(string[charno:]) |
|
475 | 479 | return output |
|
476 | 480 | |
|
477 | 481 | def _parse_example(self, m, name, lineno,ip2py=False): |
|
478 | 482 | """ |
|
479 | 483 | Given a regular expression match from `_EXAMPLE_RE` (`m`), |
|
480 | 484 | return a pair `(source, want)`, where `source` is the matched |
|
481 | 485 | example's source code (with prompts and indentation stripped); |
|
482 | 486 | and `want` is the example's expected output (with indentation |
|
483 | 487 | stripped). |
|
484 | 488 | |
|
485 | 489 | `name` is the string's name, and `lineno` is the line number |
|
486 | 490 | where the example starts; both are used for error messages. |
|
487 | 491 | |
|
488 | 492 | Optional: |
|
489 | 493 | `ip2py`: if true, filter the input via IPython to convert the syntax |
|
490 | 494 | into valid python. |
|
491 | 495 | """ |
|
492 | 496 | |
|
493 | 497 | # Get the example's indentation level. |
|
494 | 498 | indent = len(m.group('indent')) |
|
495 | 499 | |
|
496 | 500 | # Divide source into lines; check that they're properly |
|
497 | 501 | # indented; and then strip their indentation & prompts. |
|
498 | 502 | source_lines = m.group('source').split('\n') |
|
499 | 503 | |
|
500 | 504 | # We're using variable-length input prompts |
|
501 | 505 | ps1 = m.group('ps1') |
|
502 | 506 | ps2 = m.group('ps2') |
|
503 | 507 | ps1_len = len(ps1) |
|
504 | 508 | |
|
505 | 509 | self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len) |
|
506 | 510 | if ps2: |
|
507 | 511 | self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno) |
|
508 | 512 | |
|
509 | 513 | source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines]) |
|
510 | 514 | |
|
511 | 515 | if ip2py: |
|
512 | 516 | # Convert source input from IPython into valid Python syntax |
|
513 | 517 | source = self.ip2py(source) |
|
514 | 518 | |
|
515 | 519 | # Divide want into lines; check that it's properly indented; and |
|
516 | 520 | # then strip the indentation. Spaces before the last newline should |
|
517 | 521 | # be preserved, so plain rstrip() isn't good enough. |
|
518 | 522 | want = m.group('want') |
|
519 | 523 | want_lines = want.split('\n') |
|
520 | 524 | if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]): |
|
521 | 525 | del want_lines[-1] # forget final newline & spaces after it |
|
522 | 526 | self._check_prefix(want_lines, ' '*indent, name, |
|
523 | 527 | lineno + len(source_lines)) |
|
524 | 528 | |
|
525 | 529 | # Remove ipython output prompt that might be present in the first line |
|
526 | 530 | want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0]) |
|
527 | 531 | |
|
528 | 532 | want = '\n'.join([wl[indent:] for wl in want_lines]) |
|
529 | 533 | |
|
530 | 534 | # If `want` contains a traceback message, then extract it. |
|
531 | 535 | m = self._EXCEPTION_RE.match(want) |
|
532 | 536 | if m: |
|
533 | 537 | exc_msg = m.group('msg') |
|
534 | 538 | else: |
|
535 | 539 | exc_msg = None |
|
536 | 540 | |
|
537 | 541 | # Extract options from the source. |
|
538 | 542 | options = self._find_options(source, name, lineno) |
|
539 | 543 | |
|
540 | 544 | return source, options, want, exc_msg |
|
541 | 545 | |
|
542 | 546 | def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len): |
|
543 | 547 | """ |
|
544 | 548 | Given the lines of a source string (including prompts and |
|
545 | 549 | leading indentation), check to make sure that every prompt is |
|
546 | 550 | followed by a space character. If any line is not followed by |
|
547 | 551 | a space character, then raise ValueError. |
|
548 | 552 | |
|
549 | 553 | Note: IPython-modified version which takes the input prompt length as a |
|
550 | 554 | parameter, so that prompts of variable length can be dealt with. |
|
551 | 555 | """ |
|
552 | 556 | space_idx = indent+ps1_len |
|
553 | 557 | min_len = space_idx+1 |
|
554 | 558 | for i, line in enumerate(lines): |
|
555 | 559 | if len(line) >= min_len and line[space_idx] != ' ': |
|
556 | 560 | raise ValueError('line %r of the docstring for %s ' |
|
557 | 561 | 'lacks blank after %s: %r' % |
|
558 | 562 | (lineno+i+1, name, |
|
559 | 563 | line[indent:space_idx], line)) |
|
560 | 564 | |
|
561 | 565 | |
|
562 | 566 | SKIP = doctest.register_optionflag('SKIP') |
|
563 | 567 | |
|
564 | 568 | |
|
565 | 569 | class IPDocTestRunner(doctest.DocTestRunner,object): |
|
566 | 570 | """Test runner that synchronizes the IPython namespace with test globals. |
|
567 | 571 | """ |
|
568 | 572 | |
|
569 | 573 | def run(self, test, compileflags=None, out=None, clear_globs=True): |
|
570 | 574 | |
|
571 | 575 | # Hack: ipython needs access to the execution context of the example, |
|
572 | 576 | # so that it can propagate user variables loaded by %run into |
|
573 | 577 | # test.globs. We put them here into our modified %run as a function |
|
574 | 578 | # attribute. Our new %run will then only make the namespace update |
|
575 | 579 | # when called (rather than unconconditionally updating test.globs here |
|
576 | 580 | # for all examples, most of which won't be calling %run anyway). |
|
577 | 581 | #_ip._ipdoctest_test_globs = test.globs |
|
578 | 582 | #_ip._ipdoctest_test_filename = test.filename |
|
579 | 583 | |
|
580 | 584 | test.globs.update(_ip.user_ns) |
|
581 | 585 | |
|
582 | 586 | return super(IPDocTestRunner,self).run(test, |
|
583 | 587 | compileflags,out,clear_globs) |
|
584 | 588 | |
|
585 | 589 | |
|
586 | 590 | class DocFileCase(doctest.DocFileCase): |
|
587 | 591 | """Overrides to provide filename |
|
588 | 592 | """ |
|
589 | 593 | def address(self): |
|
590 | 594 | return (self._dt_test.filename, None, None) |
|
591 | 595 | |
|
592 | 596 | |
|
593 | 597 | class ExtensionDoctest(doctests.Doctest): |
|
594 | 598 | """Nose Plugin that supports doctests in extension modules. |
|
595 | 599 | """ |
|
596 | 600 | name = 'extdoctest' # call nosetests with --with-extdoctest |
|
597 | 601 | enabled = True |
|
598 | 602 | |
|
599 | 603 | def options(self, parser, env=os.environ): |
|
600 | 604 | Plugin.options(self, parser, env) |
|
601 | 605 | parser.add_option('--doctest-tests', action='store_true', |
|
602 | 606 | dest='doctest_tests', |
|
603 | 607 | default=env.get('NOSE_DOCTEST_TESTS',True), |
|
604 | 608 | help="Also look for doctests in test modules. " |
|
605 | 609 | "Note that classes, methods and functions should " |
|
606 | 610 | "have either doctests or non-doctest tests, " |
|
607 | 611 | "not both. [NOSE_DOCTEST_TESTS]") |
|
608 | 612 | parser.add_option('--doctest-extension', action="append", |
|
609 | 613 | dest="doctestExtension", |
|
610 | 614 | help="Also look for doctests in files with " |
|
611 | 615 | "this extension [NOSE_DOCTEST_EXTENSION]") |
|
612 | 616 | # Set the default as a list, if given in env; otherwise |
|
613 | 617 | # an additional value set on the command line will cause |
|
614 | 618 | # an error. |
|
615 | 619 | env_setting = env.get('NOSE_DOCTEST_EXTENSION') |
|
616 | 620 | if env_setting is not None: |
|
617 | 621 | parser.set_defaults(doctestExtension=tolist(env_setting)) |
|
618 | 622 | |
|
619 | 623 | |
|
620 | 624 | def configure(self, options, config): |
|
621 | 625 | Plugin.configure(self, options, config) |
|
622 | 626 | # Pull standard doctest plugin out of config; we will do doctesting |
|
623 | 627 | config.plugins.plugins = [p for p in config.plugins.plugins |
|
624 | 628 | if p.name != 'doctest'] |
|
625 | 629 | self.doctest_tests = options.doctest_tests |
|
626 | 630 | self.extension = tolist(options.doctestExtension) |
|
627 | 631 | |
|
628 | 632 | self.parser = doctest.DocTestParser() |
|
629 | 633 | self.finder = DocTestFinder() |
|
630 | 634 | self.checker = IPDoctestOutputChecker() |
|
631 | 635 | self.globs = None |
|
632 | 636 | self.extraglobs = None |
|
633 | 637 | |
|
634 | 638 | |
|
635 | 639 | def loadTestsFromExtensionModule(self,filename): |
|
636 | 640 | bpath,mod = os.path.split(filename) |
|
637 | 641 | modname = os.path.splitext(mod)[0] |
|
638 | 642 | try: |
|
639 | 643 | sys.path.append(bpath) |
|
640 | 644 | module = __import__(modname) |
|
641 | 645 | tests = list(self.loadTestsFromModule(module)) |
|
642 | 646 | finally: |
|
643 | 647 | sys.path.pop() |
|
644 | 648 | return tests |
|
645 | 649 | |
|
646 | 650 | # NOTE: the method below is almost a copy of the original one in nose, with |
|
647 | 651 | # a few modifications to control output checking. |
|
648 | 652 | |
|
649 | 653 | def loadTestsFromModule(self, module): |
|
650 | 654 | #print '*** ipdoctest - lTM',module # dbg |
|
651 | 655 | |
|
652 | 656 | if not self.matches(module.__name__): |
|
653 | 657 | log.debug("Doctest doesn't want module %s", module) |
|
654 | 658 | return |
|
655 | 659 | |
|
656 | 660 | tests = self.finder.find(module,globs=self.globs, |
|
657 | 661 | extraglobs=self.extraglobs) |
|
658 | 662 | if not tests: |
|
659 | 663 | return |
|
660 | 664 | |
|
661 | 665 | # always use whitespace and ellipsis options |
|
662 | 666 | optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS |
|
663 | 667 | |
|
664 | 668 | tests.sort() |
|
665 | 669 | module_file = module.__file__ |
|
666 | 670 | if module_file[-4:] in ('.pyc', '.pyo'): |
|
667 | 671 | module_file = module_file[:-1] |
|
668 | 672 | for test in tests: |
|
669 | 673 | if not test.examples: |
|
670 | 674 | continue |
|
671 | 675 | if not test.filename: |
|
672 | 676 | test.filename = module_file |
|
673 | 677 | |
|
674 | 678 | yield DocTestCase(test, |
|
675 | 679 | optionflags=optionflags, |
|
676 | 680 | checker=self.checker) |
|
677 | 681 | |
|
678 | 682 | |
|
679 | 683 | def loadTestsFromFile(self, filename): |
|
680 | 684 | #print "ipdoctest - from file", filename # dbg |
|
681 | 685 | if is_extension_module(filename): |
|
682 | 686 | for t in self.loadTestsFromExtensionModule(filename): |
|
683 | 687 | yield t |
|
684 | 688 | else: |
|
685 | 689 | if self.extension and anyp(filename.endswith, self.extension): |
|
686 | 690 | name = os.path.basename(filename) |
|
687 | 691 | dh = open(filename) |
|
688 | 692 | try: |
|
689 | 693 | doc = dh.read() |
|
690 | 694 | finally: |
|
691 | 695 | dh.close() |
|
692 | 696 | test = self.parser.get_doctest( |
|
693 | 697 | doc, globs={'__file__': filename}, name=name, |
|
694 | 698 | filename=filename, lineno=0) |
|
695 | 699 | if test.examples: |
|
696 | 700 | #print 'FileCase:',test.examples # dbg |
|
697 | 701 | yield DocFileCase(test) |
|
698 | 702 | else: |
|
699 | 703 | yield False # no tests to load |
|
700 | 704 | |
|
701 | 705 | |
|
702 | 706 | class IPythonDoctest(ExtensionDoctest): |
|
703 | 707 | """Nose Plugin that supports doctests in extension modules. |
|
704 | 708 | """ |
|
705 | 709 | name = 'ipdoctest' # call nosetests with --with-ipdoctest |
|
706 | 710 | enabled = True |
|
707 | 711 | |
|
708 | 712 | def makeTest(self, obj, parent): |
|
709 | 713 | """Look for doctests in the given object, which will be a |
|
710 | 714 | function, method or class. |
|
711 | 715 | """ |
|
712 | 716 | #print 'Plugin analyzing:', obj, parent # dbg |
|
713 | 717 | # always use whitespace and ellipsis options |
|
714 | 718 | optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS |
|
715 | 719 | |
|
716 | 720 | doctests = self.finder.find(obj, module=getmodule(parent)) |
|
717 | 721 | if doctests: |
|
718 | 722 | for test in doctests: |
|
719 | 723 | if len(test.examples) == 0: |
|
720 | 724 | continue |
|
721 | 725 | |
|
722 | 726 | yield DocTestCase(test, obj=obj, |
|
723 | 727 | optionflags=optionflags, |
|
724 | 728 | checker=self.checker) |
|
725 | 729 | |
|
726 | 730 | def options(self, parser, env=os.environ): |
|
727 | 731 | #print "Options for nose plugin:", self.name # dbg |
|
728 | 732 | Plugin.options(self, parser, env) |
|
729 | 733 | parser.add_option('--ipdoctest-tests', action='store_true', |
|
730 | 734 | dest='ipdoctest_tests', |
|
731 | 735 | default=env.get('NOSE_IPDOCTEST_TESTS',True), |
|
732 | 736 | help="Also look for doctests in test modules. " |
|
733 | 737 | "Note that classes, methods and functions should " |
|
734 | 738 | "have either doctests or non-doctest tests, " |
|
735 | 739 | "not both. [NOSE_IPDOCTEST_TESTS]") |
|
736 | 740 | parser.add_option('--ipdoctest-extension', action="append", |
|
737 | 741 | dest="ipdoctest_extension", |
|
738 | 742 | help="Also look for doctests in files with " |
|
739 | 743 | "this extension [NOSE_IPDOCTEST_EXTENSION]") |
|
740 | 744 | # Set the default as a list, if given in env; otherwise |
|
741 | 745 | # an additional value set on the command line will cause |
|
742 | 746 | # an error. |
|
743 | 747 | env_setting = env.get('NOSE_IPDOCTEST_EXTENSION') |
|
744 | 748 | if env_setting is not None: |
|
745 | 749 | parser.set_defaults(ipdoctest_extension=tolist(env_setting)) |
|
746 | 750 | |
|
747 | 751 | def configure(self, options, config): |
|
748 | 752 | #print "Configuring nose plugin:", self.name # dbg |
|
749 | 753 | Plugin.configure(self, options, config) |
|
750 | 754 | # Pull standard doctest plugin out of config; we will do doctesting |
|
751 | 755 | config.plugins.plugins = [p for p in config.plugins.plugins |
|
752 | 756 | if p.name != 'doctest'] |
|
753 | 757 | self.doctest_tests = options.ipdoctest_tests |
|
754 | 758 | self.extension = tolist(options.ipdoctest_extension) |
|
755 | 759 | |
|
756 | 760 | self.parser = IPDocTestParser() |
|
757 | 761 | self.finder = DocTestFinder(parser=self.parser) |
|
758 | 762 | self.checker = IPDoctestOutputChecker() |
|
759 | 763 | self.globs = None |
|
760 | 764 | self.extraglobs = None |
@@ -1,310 +1,315 | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | """ |
|
3 | 3 | Class and program to colorize python source code for ANSI terminals. |
|
4 | 4 | |
|
5 | 5 | Based on an HTML code highlighter by Jurgen Hermann found at: |
|
6 | 6 | http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52298 |
|
7 | 7 | |
|
8 | 8 | Modifications by Fernando Perez (fperez@colorado.edu). |
|
9 | 9 | |
|
10 | 10 | Information on the original HTML highlighter follows: |
|
11 | 11 | |
|
12 | 12 | MoinMoin - Python Source Parser |
|
13 | 13 | |
|
14 | 14 | Title: Colorize Python source using the built-in tokenizer |
|
15 | 15 | |
|
16 | 16 | Submitter: Jurgen Hermann |
|
17 | 17 | Last Updated:2001/04/06 |
|
18 | 18 | |
|
19 | 19 | Version no:1.2 |
|
20 | 20 | |
|
21 | 21 | Description: |
|
22 | 22 | |
|
23 | 23 | This code is part of MoinMoin (http://moin.sourceforge.net/) and converts |
|
24 | 24 | Python source code to HTML markup, rendering comments, keywords, |
|
25 | 25 | operators, numeric and string literals in different colors. |
|
26 | 26 | |
|
27 | 27 | It shows how to use the built-in keyword, token and tokenize modules to |
|
28 | 28 | scan Python source code and re-emit it with no changes to its original |
|
29 | 29 | formatting (which is the hard part). |
|
30 | 30 | """ |
|
31 | 31 | from __future__ import print_function |
|
32 | 32 | from __future__ import absolute_import |
|
33 | 33 | from __future__ import unicode_literals |
|
34 | 34 | |
|
35 | 35 | __all__ = ['ANSICodeColors','Parser'] |
|
36 | 36 | |
|
37 | 37 | _scheme_default = 'Linux' |
|
38 | 38 | |
|
39 | 39 | |
|
40 | 40 | # Imports |
|
41 | import io | |
|
42 | 41 | import keyword |
|
43 | 42 | import os |
|
44 | 43 | import sys |
|
45 | 44 | import token |
|
46 | 45 | import tokenize |
|
47 | 46 | |
|
48 | 47 | try: |
|
49 | 48 | generate_tokens = tokenize.generate_tokens |
|
50 | 49 | except AttributeError: |
|
51 | 50 | # Python 3. Note that we use the undocumented _tokenize because it expects |
|
52 | 51 | # strings, not bytes. See also Python issue #9969. |
|
53 | 52 | generate_tokens = tokenize._tokenize |
|
54 | 53 | |
|
55 | 54 | from IPython.utils.coloransi import * |
|
55 | from IPython.utils.py3compat import PY3 | |
|
56 | ||
|
57 | if PY3: | |
|
58 | from io import StringIO | |
|
59 | else: | |
|
60 | from StringIO import StringIO | |
|
56 | 61 | |
|
57 | 62 | ############################################################################# |
|
58 | 63 | ### Python Source Parser (does Hilighting) |
|
59 | 64 | ############################################################################# |
|
60 | 65 | |
|
61 | 66 | _KEYWORD = token.NT_OFFSET + 1 |
|
62 | 67 | _TEXT = token.NT_OFFSET + 2 |
|
63 | 68 | |
|
64 | 69 | #**************************************************************************** |
|
65 | 70 | # Builtin color schemes |
|
66 | 71 | |
|
67 | 72 | Colors = TermColors # just a shorthand |
|
68 | 73 | |
|
69 | 74 | # Build a few color schemes |
|
70 | 75 | NoColor = ColorScheme( |
|
71 | 76 | 'NoColor',{ |
|
72 | 77 | token.NUMBER : Colors.NoColor, |
|
73 | 78 | token.OP : Colors.NoColor, |
|
74 | 79 | token.STRING : Colors.NoColor, |
|
75 | 80 | tokenize.COMMENT : Colors.NoColor, |
|
76 | 81 | token.NAME : Colors.NoColor, |
|
77 | 82 | token.ERRORTOKEN : Colors.NoColor, |
|
78 | 83 | |
|
79 | 84 | _KEYWORD : Colors.NoColor, |
|
80 | 85 | _TEXT : Colors.NoColor, |
|
81 | 86 | |
|
82 | 87 | 'normal' : Colors.NoColor # color off (usu. Colors.Normal) |
|
83 | 88 | } ) |
|
84 | 89 | |
|
85 | 90 | LinuxColors = ColorScheme( |
|
86 | 91 | 'Linux',{ |
|
87 | 92 | token.NUMBER : Colors.LightCyan, |
|
88 | 93 | token.OP : Colors.Yellow, |
|
89 | 94 | token.STRING : Colors.LightBlue, |
|
90 | 95 | tokenize.COMMENT : Colors.LightRed, |
|
91 | 96 | token.NAME : Colors.Normal, |
|
92 | 97 | token.ERRORTOKEN : Colors.Red, |
|
93 | 98 | |
|
94 | 99 | _KEYWORD : Colors.LightGreen, |
|
95 | 100 | _TEXT : Colors.Yellow, |
|
96 | 101 | |
|
97 | 102 | 'normal' : Colors.Normal # color off (usu. Colors.Normal) |
|
98 | 103 | } ) |
|
99 | 104 | |
|
100 | 105 | LightBGColors = ColorScheme( |
|
101 | 106 | 'LightBG',{ |
|
102 | 107 | token.NUMBER : Colors.Cyan, |
|
103 | 108 | token.OP : Colors.Blue, |
|
104 | 109 | token.STRING : Colors.Blue, |
|
105 | 110 | tokenize.COMMENT : Colors.Red, |
|
106 | 111 | token.NAME : Colors.Normal, |
|
107 | 112 | token.ERRORTOKEN : Colors.Red, |
|
108 | 113 | |
|
109 | 114 | _KEYWORD : Colors.Green, |
|
110 | 115 | _TEXT : Colors.Blue, |
|
111 | 116 | |
|
112 | 117 | 'normal' : Colors.Normal # color off (usu. Colors.Normal) |
|
113 | 118 | } ) |
|
114 | 119 | |
|
115 | 120 | # Build table of color schemes (needed by the parser) |
|
116 | 121 | ANSICodeColors = ColorSchemeTable([NoColor,LinuxColors,LightBGColors], |
|
117 | 122 | _scheme_default) |
|
118 | 123 | |
|
119 | 124 | class Parser: |
|
120 | 125 | """ Format colored Python source. |
|
121 | 126 | """ |
|
122 | 127 | |
|
123 | 128 | def __init__(self, color_table=None,out = sys.stdout): |
|
124 | 129 | """ Create a parser with a specified color table and output channel. |
|
125 | 130 | |
|
126 | 131 | Call format() to process code. |
|
127 | 132 | """ |
|
128 | 133 | self.color_table = color_table and color_table or ANSICodeColors |
|
129 | 134 | self.out = out |
|
130 | 135 | |
|
131 | 136 | def format(self, raw, out = None, scheme = ''): |
|
132 | 137 | return self.format2(raw, out, scheme)[0] |
|
133 | 138 | |
|
134 | 139 | def format2(self, raw, out = None, scheme = ''): |
|
135 | 140 | """ Parse and send the colored source. |
|
136 | 141 | |
|
137 | 142 | If out and scheme are not specified, the defaults (given to |
|
138 | 143 | constructor) are used. |
|
139 | 144 | |
|
140 | 145 | out should be a file-type object. Optionally, out can be given as the |
|
141 | 146 | string 'str' and the parser will automatically return the output in a |
|
142 | 147 | string.""" |
|
143 | 148 | |
|
144 | 149 | string_output = 0 |
|
145 | 150 | if out == 'str' or self.out == 'str' or \ |
|
146 |
isinstance(self.out, |
|
|
151 | isinstance(self.out,StringIO): | |
|
147 | 152 | # XXX - I don't really like this state handling logic, but at this |
|
148 | 153 | # point I don't want to make major changes, so adding the |
|
149 | 154 | # isinstance() check is the simplest I can do to ensure correct |
|
150 | 155 | # behavior. |
|
151 | 156 | out_old = self.out |
|
152 |
self.out = |
|
|
157 | self.out = StringIO() | |
|
153 | 158 | string_output = 1 |
|
154 | 159 | elif out is not None: |
|
155 | 160 | self.out = out |
|
156 | 161 | |
|
157 | 162 | # Fast return of the unmodified input for NoColor scheme |
|
158 | 163 | if scheme == 'NoColor': |
|
159 | 164 | error = False |
|
160 | 165 | self.out.write(raw) |
|
161 | 166 | if string_output: |
|
162 | 167 | return raw,error |
|
163 | 168 | else: |
|
164 | 169 | return None,error |
|
165 | 170 | |
|
166 | 171 | # local shorthands |
|
167 | 172 | colors = self.color_table[scheme].colors |
|
168 | 173 | self.colors = colors # put in object so __call__ sees it |
|
169 | 174 | |
|
170 | 175 | # Remove trailing whitespace and normalize tabs |
|
171 | 176 | self.raw = raw.expandtabs().rstrip() |
|
172 | 177 | |
|
173 | 178 | # store line offsets in self.lines |
|
174 | 179 | self.lines = [0, 0] |
|
175 | 180 | pos = 0 |
|
176 | 181 | raw_find = self.raw.find |
|
177 | 182 | lines_append = self.lines.append |
|
178 | 183 | while 1: |
|
179 | 184 | pos = raw_find('\n', pos) + 1 |
|
180 | 185 | if not pos: break |
|
181 | 186 | lines_append(pos) |
|
182 | 187 | lines_append(len(self.raw)) |
|
183 | 188 | |
|
184 | 189 | # parse the source and write it |
|
185 | 190 | self.pos = 0 |
|
186 |
text = |
|
|
191 | text = StringIO(self.raw) | |
|
187 | 192 | |
|
188 | 193 | error = False |
|
189 | 194 | try: |
|
190 | 195 | for atoken in generate_tokens(text.readline): |
|
191 | 196 | self(*atoken) |
|
192 | 197 | except tokenize.TokenError as ex: |
|
193 | 198 | msg = ex.args[0] |
|
194 | 199 | line = ex.args[1][0] |
|
195 | 200 | self.out.write("%s\n\n*** ERROR: %s%s%s\n" % |
|
196 | 201 | (colors[token.ERRORTOKEN], |
|
197 | 202 | msg, self.raw[self.lines[line]:], |
|
198 | 203 | colors.normal) |
|
199 | 204 | ) |
|
200 | 205 | error = True |
|
201 | 206 | self.out.write(colors.normal+'\n') |
|
202 | 207 | if string_output: |
|
203 | 208 | output = self.out.getvalue() |
|
204 | 209 | self.out = out_old |
|
205 | 210 | return (output, error) |
|
206 | 211 | return (None, error) |
|
207 | 212 | |
|
208 | 213 | def __call__(self, toktype, toktext, start_pos, end_pos, line): |
|
209 | 214 | """ Token handler, with syntax highlighting.""" |
|
210 | 215 | (srow,scol) = start_pos |
|
211 | 216 | (erow,ecol) = end_pos |
|
212 | 217 | colors = self.colors |
|
213 | 218 | owrite = self.out.write |
|
214 | 219 | |
|
215 | 220 | # line separator, so this works across platforms |
|
216 | 221 | linesep = os.linesep |
|
217 | 222 | |
|
218 | 223 | # calculate new positions |
|
219 | 224 | oldpos = self.pos |
|
220 | 225 | newpos = self.lines[srow] + scol |
|
221 | 226 | self.pos = newpos + len(toktext) |
|
222 | 227 | |
|
223 | 228 | # send the original whitespace, if needed |
|
224 | 229 | if newpos > oldpos: |
|
225 | 230 | owrite(self.raw[oldpos:newpos]) |
|
226 | 231 | |
|
227 | 232 | # skip indenting tokens |
|
228 | 233 | if toktype in [token.INDENT, token.DEDENT]: |
|
229 | 234 | self.pos = newpos |
|
230 | 235 | return |
|
231 | 236 | |
|
232 | 237 | # map token type to a color group |
|
233 | 238 | if token.LPAR <= toktype and toktype <= token.OP: |
|
234 | 239 | toktype = token.OP |
|
235 | 240 | elif toktype == token.NAME and keyword.iskeyword(toktext): |
|
236 | 241 | toktype = _KEYWORD |
|
237 | 242 | color = colors.get(toktype, colors[_TEXT]) |
|
238 | 243 | |
|
239 | 244 | #print '<%s>' % toktext, # dbg |
|
240 | 245 | |
|
241 | 246 | # Triple quoted strings must be handled carefully so that backtracking |
|
242 | 247 | # in pagers works correctly. We need color terminators on _each_ line. |
|
243 | 248 | if linesep in toktext: |
|
244 | 249 | toktext = toktext.replace(linesep, '%s%s%s' % |
|
245 | 250 | (colors.normal,linesep,color)) |
|
246 | 251 | |
|
247 | 252 | # send text |
|
248 | 253 | owrite('%s%s%s' % (color,toktext,colors.normal)) |
|
249 | 254 | |
|
250 | 255 | def main(argv=None): |
|
251 | 256 | """Run as a command-line script: colorize a python file or stdin using ANSI |
|
252 | 257 | color escapes and print to stdout. |
|
253 | 258 | |
|
254 | 259 | Inputs: |
|
255 | 260 | |
|
256 | 261 | - argv(None): a list of strings like sys.argv[1:] giving the command-line |
|
257 | 262 | arguments. If None, use sys.argv[1:]. |
|
258 | 263 | """ |
|
259 | 264 | |
|
260 | 265 | usage_msg = """%prog [options] [filename] |
|
261 | 266 | |
|
262 | 267 | Colorize a python file or stdin using ANSI color escapes and print to stdout. |
|
263 | 268 | If no filename is given, or if filename is -, read standard input.""" |
|
264 | 269 | |
|
265 | 270 | import optparse |
|
266 | 271 | parser = optparse.OptionParser(usage=usage_msg) |
|
267 | 272 | newopt = parser.add_option |
|
268 | 273 | newopt('-s','--scheme',metavar='NAME',dest='scheme_name',action='store', |
|
269 | 274 | choices=['Linux','LightBG','NoColor'],default=_scheme_default, |
|
270 | 275 | help="give the color scheme to use. Currently only 'Linux'\ |
|
271 | 276 | (default) and 'LightBG' and 'NoColor' are implemented (give without\ |
|
272 | 277 | quotes)") |
|
273 | 278 | |
|
274 | 279 | opts,args = parser.parse_args(argv) |
|
275 | 280 | |
|
276 | 281 | if len(args) > 1: |
|
277 | 282 | parser.error("you must give at most one filename.") |
|
278 | 283 | |
|
279 | 284 | if len(args) == 0: |
|
280 | 285 | fname = '-' # no filename given; setup to read from stdin |
|
281 | 286 | else: |
|
282 | 287 | fname = args[0] |
|
283 | 288 | |
|
284 | 289 | if fname == '-': |
|
285 | 290 | stream = sys.stdin |
|
286 | 291 | else: |
|
287 | 292 | try: |
|
288 | 293 | stream = open(fname) |
|
289 | 294 | except IOError as msg: |
|
290 | 295 | print(msg, file=sys.stderr) |
|
291 | 296 | sys.exit(1) |
|
292 | 297 | |
|
293 | 298 | parser = Parser() |
|
294 | 299 | |
|
295 | 300 | # we need nested try blocks because pre-2.5 python doesn't support unified |
|
296 | 301 | # try-except-finally |
|
297 | 302 | try: |
|
298 | 303 | try: |
|
299 | 304 | # write colorized version to stdout |
|
300 | 305 | parser.format(stream.read(),scheme=opts.scheme_name) |
|
301 | 306 | except IOError as msg: |
|
302 | 307 | # if user reads through a pager and quits, don't print traceback |
|
303 | 308 | if msg.args != (32,'Broken pipe'): |
|
304 | 309 | raise |
|
305 | 310 | finally: |
|
306 | 311 | if stream is not sys.stdin: |
|
307 | 312 | stream.close() # in case a non-handled exception happened above |
|
308 | 313 | |
|
309 | 314 | if __name__ == "__main__": |
|
310 | 315 | main() |
@@ -1,173 +1,179 | |||
|
1 | 1 | # encoding: utf-8 |
|
2 | 2 | """ |
|
3 | 3 | IO capturing utilities. |
|
4 | 4 | """ |
|
5 | 5 | |
|
6 | 6 | #----------------------------------------------------------------------------- |
|
7 | 7 | # Copyright (C) 2013 The IPython Development Team |
|
8 | 8 | # |
|
9 | 9 | # Distributed under the terms of the BSD License. The full license is in |
|
10 | 10 | # the file COPYING, distributed as part of this software. |
|
11 | 11 | #----------------------------------------------------------------------------- |
|
12 | 12 | from __future__ import print_function, absolute_import |
|
13 | 13 | |
|
14 | 14 | #----------------------------------------------------------------------------- |
|
15 | 15 | # Imports |
|
16 | 16 | #----------------------------------------------------------------------------- |
|
17 | 17 | |
|
18 | 18 | import sys |
|
19 | from io import StringIO | |
|
19 | ||
|
20 | from IPython.utils.py3compat import PY3 | |
|
21 | ||
|
22 | if PY3: | |
|
23 | from io import StringIO | |
|
24 | else: | |
|
25 | from StringIO import StringIO | |
|
20 | 26 | |
|
21 | 27 | #----------------------------------------------------------------------------- |
|
22 | 28 | # Classes and functions |
|
23 | 29 | #----------------------------------------------------------------------------- |
|
24 | 30 | |
|
25 | 31 | |
|
26 | 32 | class RichOutput(object): |
|
27 | 33 | def __init__(self, source="", data=None, metadata=None): |
|
28 | 34 | self.source = source |
|
29 | 35 | self.data = data or {} |
|
30 | 36 | self.metadata = metadata or {} |
|
31 | 37 | |
|
32 | 38 | def display(self): |
|
33 | 39 | from IPython.display import publish_display_data |
|
34 | 40 | publish_display_data(self.source, self.data, self.metadata) |
|
35 | 41 | |
|
36 | 42 | def _repr_mime_(self, mime): |
|
37 | 43 | if mime not in self.data: |
|
38 | 44 | return |
|
39 | 45 | data = self.data[mime] |
|
40 | 46 | if mime in self.metadata: |
|
41 | 47 | return data, self.metadata[mime] |
|
42 | 48 | else: |
|
43 | 49 | return data |
|
44 | 50 | |
|
45 | 51 | def _repr_html_(self): |
|
46 | 52 | return self._repr_mime_("text/html") |
|
47 | 53 | |
|
48 | 54 | def _repr_latex_(self): |
|
49 | 55 | return self._repr_mime_("text/latex") |
|
50 | 56 | |
|
51 | 57 | def _repr_json_(self): |
|
52 | 58 | return self._repr_mime_("application/json") |
|
53 | 59 | |
|
54 | 60 | def _repr_javascript_(self): |
|
55 | 61 | return self._repr_mime_("application/javascript") |
|
56 | 62 | |
|
57 | 63 | def _repr_png_(self): |
|
58 | 64 | return self._repr_mime_("image/png") |
|
59 | 65 | |
|
60 | 66 | def _repr_jpeg_(self): |
|
61 | 67 | return self._repr_mime_("image/jpeg") |
|
62 | 68 | |
|
63 | 69 | def _repr_svg_(self): |
|
64 | 70 | return self._repr_mime_("image/svg+xml") |
|
65 | 71 | |
|
66 | 72 | |
|
67 | 73 | class CapturedIO(object): |
|
68 | 74 | """Simple object for containing captured stdout/err and rich display StringIO objects |
|
69 | 75 | |
|
70 | 76 | Each instance `c` has three attributes: |
|
71 | 77 | |
|
72 | 78 | - ``c.stdout`` : standard output as a string |
|
73 | 79 | - ``c.stderr`` : standard error as a string |
|
74 | 80 | - ``c.outputs``: a list of rich display outputs |
|
75 | 81 | |
|
76 | 82 | Additionally, there's a ``c.show()`` method which will print all of the |
|
77 | 83 | above in the same order, and can be invoked simply via ``c()``. |
|
78 | 84 | """ |
|
79 | 85 | |
|
80 | 86 | def __init__(self, stdout, stderr, outputs=None): |
|
81 | 87 | self._stdout = stdout |
|
82 | 88 | self._stderr = stderr |
|
83 | 89 | if outputs is None: |
|
84 | 90 | outputs = [] |
|
85 | 91 | self._outputs = outputs |
|
86 | 92 | |
|
87 | 93 | def __str__(self): |
|
88 | 94 | return self.stdout |
|
89 | 95 | |
|
90 | 96 | @property |
|
91 | 97 | def stdout(self): |
|
92 | 98 | "Captured standard output" |
|
93 | 99 | if not self._stdout: |
|
94 | 100 | return '' |
|
95 | 101 | return self._stdout.getvalue() |
|
96 | 102 | |
|
97 | 103 | @property |
|
98 | 104 | def stderr(self): |
|
99 | 105 | "Captured standard error" |
|
100 | 106 | if not self._stderr: |
|
101 | 107 | return '' |
|
102 | 108 | return self._stderr.getvalue() |
|
103 | 109 | |
|
104 | 110 | @property |
|
105 | 111 | def outputs(self): |
|
106 | 112 | """A list of the captured rich display outputs, if any. |
|
107 | 113 | |
|
108 | 114 | If you have a CapturedIO object ``c``, these can be displayed in IPython |
|
109 | 115 | using:: |
|
110 | 116 | |
|
111 | 117 | from IPython.display import display |
|
112 | 118 | for o in c.outputs: |
|
113 | 119 | display(o) |
|
114 | 120 | """ |
|
115 | 121 | return [ RichOutput(s, d, md) for s, d, md in self._outputs ] |
|
116 | 122 | |
|
117 | 123 | def show(self): |
|
118 | 124 | """write my output to sys.stdout/err as appropriate""" |
|
119 | 125 | sys.stdout.write(self.stdout) |
|
120 | 126 | sys.stderr.write(self.stderr) |
|
121 | 127 | sys.stdout.flush() |
|
122 | 128 | sys.stderr.flush() |
|
123 | 129 | for source, data, metadata in self._outputs: |
|
124 | 130 | RichOutput(source, data, metadata).display() |
|
125 | 131 | |
|
126 | 132 | __call__ = show |
|
127 | 133 | |
|
128 | 134 | |
|
129 | 135 | class capture_output(object): |
|
130 | 136 | """context manager for capturing stdout/err""" |
|
131 | 137 | stdout = True |
|
132 | 138 | stderr = True |
|
133 | 139 | display = True |
|
134 | 140 | |
|
135 | 141 | def __init__(self, stdout=True, stderr=True, display=True): |
|
136 | 142 | self.stdout = stdout |
|
137 | 143 | self.stderr = stderr |
|
138 | 144 | self.display = display |
|
139 | 145 | self.shell = None |
|
140 | 146 | |
|
141 | 147 | def __enter__(self): |
|
142 | 148 | from IPython.core.getipython import get_ipython |
|
143 | 149 | from IPython.core.displaypub import CapturingDisplayPublisher |
|
144 | 150 | |
|
145 | 151 | self.sys_stdout = sys.stdout |
|
146 | 152 | self.sys_stderr = sys.stderr |
|
147 | 153 | |
|
148 | 154 | if self.display: |
|
149 | 155 | self.shell = get_ipython() |
|
150 | 156 | if self.shell is None: |
|
151 | 157 | self.save_display_pub = None |
|
152 | 158 | self.display = False |
|
153 | 159 | |
|
154 | 160 | stdout = stderr = outputs = None |
|
155 | 161 | if self.stdout: |
|
156 | 162 | stdout = sys.stdout = StringIO() |
|
157 | 163 | if self.stderr: |
|
158 | 164 | stderr = sys.stderr = StringIO() |
|
159 | 165 | if self.display: |
|
160 | 166 | self.save_display_pub = self.shell.display_pub |
|
161 | 167 | self.shell.display_pub = CapturingDisplayPublisher() |
|
162 | 168 | outputs = self.shell.display_pub.outputs |
|
163 | 169 | |
|
164 | 170 | |
|
165 | 171 | return CapturedIO(stdout, stderr, outputs) |
|
166 | 172 | |
|
167 | 173 | def __exit__(self, exc_type, exc_value, traceback): |
|
168 | 174 | sys.stdout = self.sys_stdout |
|
169 | 175 | sys.stderr = self.sys_stderr |
|
170 | 176 | if self.display and self.shell: |
|
171 | 177 | self.shell.display_pub = self.save_display_pub |
|
172 | 178 | |
|
173 | 179 |
@@ -1,86 +1,90 | |||
|
1 | 1 | # encoding: utf-8 |
|
2 | 2 | """Tests for io.py""" |
|
3 | 3 | |
|
4 | 4 | #----------------------------------------------------------------------------- |
|
5 | 5 | # Copyright (C) 2008-2011 The IPython Development Team |
|
6 | 6 | # |
|
7 | 7 | # Distributed under the terms of the BSD License. The full license is in |
|
8 | 8 | # the file COPYING, distributed as part of this software. |
|
9 | 9 | #----------------------------------------------------------------------------- |
|
10 | 10 | |
|
11 | 11 | #----------------------------------------------------------------------------- |
|
12 | 12 | # Imports |
|
13 | 13 | #----------------------------------------------------------------------------- |
|
14 | 14 | from __future__ import print_function |
|
15 | 15 | |
|
16 | 16 | import sys |
|
17 | 17 | |
|
18 | from io import StringIO | |
|
19 | 18 | from subprocess import Popen, PIPE |
|
20 | 19 | import unittest |
|
21 | 20 | |
|
22 | 21 | import nose.tools as nt |
|
23 | 22 | |
|
24 | 23 | from IPython.utils.io import Tee, capture_output |
|
25 | from IPython.utils.py3compat import doctest_refactor_print | |
|
24 | from IPython.utils.py3compat import doctest_refactor_print, PY3 | |
|
25 | ||
|
26 | if PY3: | |
|
27 | from io import StringIO | |
|
28 | else: | |
|
29 | from StringIO import StringIO | |
|
26 | 30 | |
|
27 | 31 | #----------------------------------------------------------------------------- |
|
28 | 32 | # Tests |
|
29 | 33 | #----------------------------------------------------------------------------- |
|
30 | 34 | |
|
31 | 35 | |
|
32 | 36 | def test_tee_simple(): |
|
33 | 37 | "Very simple check with stdout only" |
|
34 | 38 | chan = StringIO() |
|
35 | 39 | text = 'Hello' |
|
36 | 40 | tee = Tee(chan, channel='stdout') |
|
37 | 41 | print(text, file=chan) |
|
38 | 42 | nt.assert_equal(chan.getvalue(), text+"\n") |
|
39 | 43 | |
|
40 | 44 | |
|
41 | 45 | class TeeTestCase(unittest.TestCase): |
|
42 | 46 | |
|
43 | 47 | def tchan(self, channel, check='close'): |
|
44 | 48 | trap = StringIO() |
|
45 | 49 | chan = StringIO() |
|
46 | 50 | text = 'Hello' |
|
47 | 51 | |
|
48 | 52 | std_ori = getattr(sys, channel) |
|
49 | 53 | setattr(sys, channel, trap) |
|
50 | 54 | |
|
51 | 55 | tee = Tee(chan, channel=channel) |
|
52 | 56 | print(text, end='', file=chan) |
|
53 | 57 | setattr(sys, channel, std_ori) |
|
54 | 58 | trap_val = trap.getvalue() |
|
55 | 59 | nt.assert_equal(chan.getvalue(), text) |
|
56 | 60 | if check=='close': |
|
57 | 61 | tee.close() |
|
58 | 62 | else: |
|
59 | 63 | del tee |
|
60 | 64 | |
|
61 | 65 | def test(self): |
|
62 | 66 | for chan in ['stdout', 'stderr']: |
|
63 | 67 | for check in ['close', 'del']: |
|
64 | 68 | self.tchan(chan, check) |
|
65 | 69 | |
|
66 | 70 | def test_io_init(): |
|
67 | 71 | """Test that io.stdin/out/err exist at startup""" |
|
68 | 72 | for name in ('stdin', 'stdout', 'stderr'): |
|
69 | 73 | cmd = doctest_refactor_print("from IPython.utils import io;print io.%s.__class__"%name) |
|
70 | 74 | p = Popen([sys.executable, '-c', cmd], |
|
71 | 75 | stdout=PIPE) |
|
72 | 76 | p.wait() |
|
73 | 77 | classname = p.stdout.read().strip().decode('ascii') |
|
74 | 78 | # __class__ is a reference to the class object in Python 3, so we can't |
|
75 | 79 | # just test for string equality. |
|
76 | 80 | assert 'IPython.utils.io.IOStream' in classname, classname |
|
77 | 81 | |
|
78 | 82 | def test_capture_output(): |
|
79 | 83 | """capture_output() context works""" |
|
80 | 84 | |
|
81 | 85 | with capture_output() as io: |
|
82 | 86 | print('hi, stdout') |
|
83 | 87 | print('hi, stderr', file=sys.stderr) |
|
84 | 88 | |
|
85 | 89 | nt.assert_equal(io.stdout, 'hi, stdout\n') |
|
86 | 90 | nt.assert_equal(io.stderr, 'hi, stderr\n') |
General Comments 0
You need to be logged in to leave comments.
Login now