##// END OF EJS Templates
Statically type OInfo. (#13973)...
Matthias Bussonnier -
r28166:29b451fc merge
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,1080 +1,1093 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Tools for inspecting Python objects.
3 3
4 4 Uses syntax highlighting for presenting the various information elements.
5 5
6 6 Similar in spirit to the inspect module, but all calls take a name argument to
7 7 reference the name under which an object is being read.
8 8 """
9 9
10 10 # Copyright (c) IPython Development Team.
11 11 # Distributed under the terms of the Modified BSD License.
12 12
13 13 __all__ = ['Inspector','InspectColors']
14 14
15 15 # stdlib modules
16 16 import ast
17 17 import inspect
18 18 from inspect import signature
19 19 import html
20 20 import linecache
21 21 import warnings
22 22 import os
23 23 from textwrap import dedent
24 24 import types
25 25 import io as stdlib_io
26 26
27 27 from typing import Union
28 28
29 29 # IPython's own
30 30 from IPython.core import page
31 31 from IPython.lib.pretty import pretty
32 32 from IPython.testing.skipdoctest import skip_doctest
33 33 from IPython.utils import PyColorize
34 34 from IPython.utils import openpy
35 35 from IPython.utils.dir2 import safe_hasattr
36 36 from IPython.utils.path import compress_user
37 37 from IPython.utils.text import indent
38 38 from IPython.utils.wildcard import list_namespace
39 39 from IPython.utils.wildcard import typestr2type
40 40 from IPython.utils.coloransi import TermColors, ColorScheme, ColorSchemeTable
41 41 from IPython.utils.py3compat import cast_unicode
42 42 from IPython.utils.colorable import Colorable
43 43 from IPython.utils.decorators import undoc
44 44
45 45 from pygments import highlight
46 46 from pygments.lexers import PythonLexer
47 47 from pygments.formatters import HtmlFormatter
48 48
49 from typing import Any
50 from dataclasses import dataclass
51
52
53 @dataclass
54 class OInfo:
55 ismagic: bool
56 isalias: bool
57 found: bool
58 namespace: str
59 parent: Any
60 obj: Any
61
49 62 def pylight(code):
50 63 return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True))
51 64
52 65 # builtin docstrings to ignore
53 66 _func_call_docstring = types.FunctionType.__call__.__doc__
54 67 _object_init_docstring = object.__init__.__doc__
55 68 _builtin_type_docstrings = {
56 69 inspect.getdoc(t) for t in (types.ModuleType, types.MethodType,
57 70 types.FunctionType, property)
58 71 }
59 72
60 73 _builtin_func_type = type(all)
61 74 _builtin_meth_type = type(str.upper) # Bound methods have the same type as builtin functions
62 75 #****************************************************************************
63 76 # Builtin color schemes
64 77
65 78 Colors = TermColors # just a shorthand
66 79
67 80 InspectColors = PyColorize.ANSICodeColors
68 81
69 82 #****************************************************************************
70 83 # Auxiliary functions and objects
71 84
72 85 # See the messaging spec for the definition of all these fields. This list
73 86 # effectively defines the order of display
74 87 info_fields = ['type_name', 'base_class', 'string_form', 'namespace',
75 88 'length', 'file', 'definition', 'docstring', 'source',
76 89 'init_definition', 'class_docstring', 'init_docstring',
77 90 'call_def', 'call_docstring',
78 91 # These won't be printed but will be used to determine how to
79 92 # format the object
80 93 'ismagic', 'isalias', 'isclass', 'found', 'name'
81 94 ]
82 95
83 96
84 97 def object_info(**kw):
85 98 """Make an object info dict with all fields present."""
86 99 infodict = {k:None for k in info_fields}
87 100 infodict.update(kw)
88 101 return infodict
89 102
90 103
91 104 def get_encoding(obj):
92 105 """Get encoding for python source file defining obj
93 106
94 107 Returns None if obj is not defined in a sourcefile.
95 108 """
96 109 ofile = find_file(obj)
97 110 # run contents of file through pager starting at line where the object
98 111 # is defined, as long as the file isn't binary and is actually on the
99 112 # filesystem.
100 113 if ofile is None:
101 114 return None
102 115 elif ofile.endswith(('.so', '.dll', '.pyd')):
103 116 return None
104 117 elif not os.path.isfile(ofile):
105 118 return None
106 119 else:
107 120 # Print only text files, not extension binaries. Note that
108 121 # getsourcelines returns lineno with 1-offset and page() uses
109 122 # 0-offset, so we must adjust.
110 123 with stdlib_io.open(ofile, 'rb') as buffer: # Tweaked to use io.open for Python 2
111 124 encoding, lines = openpy.detect_encoding(buffer.readline)
112 125 return encoding
113 126
114 127 def getdoc(obj) -> Union[str,None]:
115 128 """Stable wrapper around inspect.getdoc.
116 129
117 130 This can't crash because of attribute problems.
118 131
119 132 It also attempts to call a getdoc() method on the given object. This
120 133 allows objects which provide their docstrings via non-standard mechanisms
121 134 (like Pyro proxies) to still be inspected by ipython's ? system.
122 135 """
123 136 # Allow objects to offer customized documentation via a getdoc method:
124 137 try:
125 138 ds = obj.getdoc()
126 139 except Exception:
127 140 pass
128 141 else:
129 142 if isinstance(ds, str):
130 143 return inspect.cleandoc(ds)
131 144 docstr = inspect.getdoc(obj)
132 145 return docstr
133 146
134 147
135 148 def getsource(obj, oname='') -> Union[str,None]:
136 149 """Wrapper around inspect.getsource.
137 150
138 151 This can be modified by other projects to provide customized source
139 152 extraction.
140 153
141 154 Parameters
142 155 ----------
143 156 obj : object
144 157 an object whose source code we will attempt to extract
145 158 oname : str
146 159 (optional) a name under which the object is known
147 160
148 161 Returns
149 162 -------
150 163 src : unicode or None
151 164
152 165 """
153 166
154 167 if isinstance(obj, property):
155 168 sources = []
156 169 for attrname in ['fget', 'fset', 'fdel']:
157 170 fn = getattr(obj, attrname)
158 171 if fn is not None:
159 172 encoding = get_encoding(fn)
160 173 oname_prefix = ('%s.' % oname) if oname else ''
161 174 sources.append(''.join(('# ', oname_prefix, attrname)))
162 175 if inspect.isfunction(fn):
163 176 sources.append(dedent(getsource(fn)))
164 177 else:
165 178 # Default str/repr only prints function name,
166 179 # pretty.pretty prints module name too.
167 180 sources.append(
168 181 '%s%s = %s\n' % (oname_prefix, attrname, pretty(fn))
169 182 )
170 183 if sources:
171 184 return '\n'.join(sources)
172 185 else:
173 186 return None
174 187
175 188 else:
176 189 # Get source for non-property objects.
177 190
178 191 obj = _get_wrapped(obj)
179 192
180 193 try:
181 194 src = inspect.getsource(obj)
182 195 except TypeError:
183 196 # The object itself provided no meaningful source, try looking for
184 197 # its class definition instead.
185 198 try:
186 199 src = inspect.getsource(obj.__class__)
187 200 except (OSError, TypeError):
188 201 return None
189 202 except OSError:
190 203 return None
191 204
192 205 return src
193 206
194 207
195 208 def is_simple_callable(obj):
196 209 """True if obj is a function ()"""
197 210 return (inspect.isfunction(obj) or inspect.ismethod(obj) or \
198 211 isinstance(obj, _builtin_func_type) or isinstance(obj, _builtin_meth_type))
199 212
200 213 @undoc
201 214 def getargspec(obj):
202 215 """Wrapper around :func:`inspect.getfullargspec`
203 216
204 217 In addition to functions and methods, this can also handle objects with a
205 218 ``__call__`` attribute.
206 219
207 220 DEPRECATED: Deprecated since 7.10. Do not use, will be removed.
208 221 """
209 222
210 223 warnings.warn('`getargspec` function is deprecated as of IPython 7.10'
211 224 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
212 225
213 226 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
214 227 obj = obj.__call__
215 228
216 229 return inspect.getfullargspec(obj)
217 230
218 231 @undoc
219 232 def format_argspec(argspec):
220 233 """Format argspect, convenience wrapper around inspect's.
221 234
222 235 This takes a dict instead of ordered arguments and calls
223 236 inspect.format_argspec with the arguments in the necessary order.
224 237
225 238 DEPRECATED (since 7.10): Do not use; will be removed in future versions.
226 239 """
227 240
228 241 warnings.warn('`format_argspec` function is deprecated as of IPython 7.10'
229 242 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
230 243
231 244
232 245 return inspect.formatargspec(argspec['args'], argspec['varargs'],
233 246 argspec['varkw'], argspec['defaults'])
234 247
235 248 @undoc
236 249 def call_tip(oinfo, format_call=True):
237 250 """DEPRECATED since 6.0. Extract call tip data from an oinfo dict."""
238 251 warnings.warn(
239 252 "`call_tip` function is deprecated as of IPython 6.0"
240 253 "and will be removed in future versions.",
241 254 DeprecationWarning,
242 255 stacklevel=2,
243 256 )
244 257 # Get call definition
245 258 argspec = oinfo.get('argspec')
246 259 if argspec is None:
247 260 call_line = None
248 261 else:
249 262 # Callable objects will have 'self' as their first argument, prune
250 263 # it out if it's there for clarity (since users do *not* pass an
251 264 # extra first argument explicitly).
252 265 try:
253 266 has_self = argspec['args'][0] == 'self'
254 267 except (KeyError, IndexError):
255 268 pass
256 269 else:
257 270 if has_self:
258 271 argspec['args'] = argspec['args'][1:]
259 272
260 273 call_line = oinfo['name']+format_argspec(argspec)
261 274
262 275 # Now get docstring.
263 276 # The priority is: call docstring, constructor docstring, main one.
264 277 doc = oinfo.get('call_docstring')
265 278 if doc is None:
266 279 doc = oinfo.get('init_docstring')
267 280 if doc is None:
268 281 doc = oinfo.get('docstring','')
269 282
270 283 return call_line, doc
271 284
272 285
273 286 def _get_wrapped(obj):
274 287 """Get the original object if wrapped in one or more @decorators
275 288
276 289 Some objects automatically construct similar objects on any unrecognised
277 290 attribute access (e.g. unittest.mock.call). To protect against infinite loops,
278 291 this will arbitrarily cut off after 100 levels of obj.__wrapped__
279 292 attribute access. --TK, Jan 2016
280 293 """
281 294 orig_obj = obj
282 295 i = 0
283 296 while safe_hasattr(obj, '__wrapped__'):
284 297 obj = obj.__wrapped__
285 298 i += 1
286 299 if i > 100:
287 300 # __wrapped__ is probably a lie, so return the thing we started with
288 301 return orig_obj
289 302 return obj
290 303
291 304 def find_file(obj) -> str:
292 305 """Find the absolute path to the file where an object was defined.
293 306
294 307 This is essentially a robust wrapper around `inspect.getabsfile`.
295 308
296 309 Returns None if no file can be found.
297 310
298 311 Parameters
299 312 ----------
300 313 obj : any Python object
301 314
302 315 Returns
303 316 -------
304 317 fname : str
305 318 The absolute path to the file where the object was defined.
306 319 """
307 320 obj = _get_wrapped(obj)
308 321
309 322 fname = None
310 323 try:
311 324 fname = inspect.getabsfile(obj)
312 325 except TypeError:
313 326 # For an instance, the file that matters is where its class was
314 327 # declared.
315 328 try:
316 329 fname = inspect.getabsfile(obj.__class__)
317 330 except (OSError, TypeError):
318 331 # Can happen for builtins
319 332 pass
320 333 except OSError:
321 334 pass
322 335
323 336 return cast_unicode(fname)
324 337
325 338
326 339 def find_source_lines(obj):
327 340 """Find the line number in a file where an object was defined.
328 341
329 342 This is essentially a robust wrapper around `inspect.getsourcelines`.
330 343
331 344 Returns None if no file can be found.
332 345
333 346 Parameters
334 347 ----------
335 348 obj : any Python object
336 349
337 350 Returns
338 351 -------
339 352 lineno : int
340 353 The line number where the object definition starts.
341 354 """
342 355 obj = _get_wrapped(obj)
343 356
344 357 try:
345 358 lineno = inspect.getsourcelines(obj)[1]
346 359 except TypeError:
347 360 # For instances, try the class object like getsource() does
348 361 try:
349 362 lineno = inspect.getsourcelines(obj.__class__)[1]
350 363 except (OSError, TypeError):
351 364 return None
352 365 except OSError:
353 366 return None
354 367
355 368 return lineno
356 369
357 370 class Inspector(Colorable):
358 371
359 372 def __init__(self, color_table=InspectColors,
360 373 code_color_table=PyColorize.ANSICodeColors,
361 374 scheme=None,
362 375 str_detail_level=0,
363 376 parent=None, config=None):
364 377 super(Inspector, self).__init__(parent=parent, config=config)
365 378 self.color_table = color_table
366 379 self.parser = PyColorize.Parser(out='str', parent=self, style=scheme)
367 380 self.format = self.parser.format
368 381 self.str_detail_level = str_detail_level
369 382 self.set_active_scheme(scheme)
370 383
371 384 def _getdef(self,obj,oname='') -> Union[str,None]:
372 385 """Return the call signature for any callable object.
373 386
374 387 If any exception is generated, None is returned instead and the
375 388 exception is suppressed."""
376 389 try:
377 390 return _render_signature(signature(obj), oname)
378 391 except:
379 392 return None
380 393
381 394 def __head(self,h) -> str:
382 395 """Return a header string with proper colors."""
383 396 return '%s%s%s' % (self.color_table.active_colors.header,h,
384 397 self.color_table.active_colors.normal)
385 398
386 399 def set_active_scheme(self, scheme):
387 400 if scheme is not None:
388 401 self.color_table.set_active_scheme(scheme)
389 402 self.parser.color_table.set_active_scheme(scheme)
390 403
391 404 def noinfo(self, msg, oname):
392 405 """Generic message when no information is found."""
393 406 print('No %s found' % msg, end=' ')
394 407 if oname:
395 408 print('for %s' % oname)
396 409 else:
397 410 print()
398 411
399 412 def pdef(self, obj, oname=''):
400 413 """Print the call signature for any callable object.
401 414
402 415 If the object is a class, print the constructor information."""
403 416
404 417 if not callable(obj):
405 418 print('Object is not callable.')
406 419 return
407 420
408 421 header = ''
409 422
410 423 if inspect.isclass(obj):
411 424 header = self.__head('Class constructor information:\n')
412 425
413 426
414 427 output = self._getdef(obj,oname)
415 428 if output is None:
416 429 self.noinfo('definition header',oname)
417 430 else:
418 431 print(header,self.format(output), end=' ')
419 432
420 433 # In Python 3, all classes are new-style, so they all have __init__.
421 434 @skip_doctest
422 435 def pdoc(self, obj, oname='', formatter=None):
423 436 """Print the docstring for any object.
424 437
425 438 Optional:
426 439 -formatter: a function to run the docstring through for specially
427 440 formatted docstrings.
428 441
429 442 Examples
430 443 --------
431 444 In [1]: class NoInit:
432 445 ...: pass
433 446
434 447 In [2]: class NoDoc:
435 448 ...: def __init__(self):
436 449 ...: pass
437 450
438 451 In [3]: %pdoc NoDoc
439 452 No documentation found for NoDoc
440 453
441 454 In [4]: %pdoc NoInit
442 455 No documentation found for NoInit
443 456
444 457 In [5]: obj = NoInit()
445 458
446 459 In [6]: %pdoc obj
447 460 No documentation found for obj
448 461
449 462 In [5]: obj2 = NoDoc()
450 463
451 464 In [6]: %pdoc obj2
452 465 No documentation found for obj2
453 466 """
454 467
455 468 head = self.__head # For convenience
456 469 lines = []
457 470 ds = getdoc(obj)
458 471 if formatter:
459 472 ds = formatter(ds).get('plain/text', ds)
460 473 if ds:
461 474 lines.append(head("Class docstring:"))
462 475 lines.append(indent(ds))
463 476 if inspect.isclass(obj) and hasattr(obj, '__init__'):
464 477 init_ds = getdoc(obj.__init__)
465 478 if init_ds is not None:
466 479 lines.append(head("Init docstring:"))
467 480 lines.append(indent(init_ds))
468 481 elif hasattr(obj,'__call__'):
469 482 call_ds = getdoc(obj.__call__)
470 483 if call_ds:
471 484 lines.append(head("Call docstring:"))
472 485 lines.append(indent(call_ds))
473 486
474 487 if not lines:
475 488 self.noinfo('documentation',oname)
476 489 else:
477 490 page.page('\n'.join(lines))
478 491
479 492 def psource(self, obj, oname=''):
480 493 """Print the source code for an object."""
481 494
482 495 # Flush the source cache because inspect can return out-of-date source
483 496 linecache.checkcache()
484 497 try:
485 498 src = getsource(obj, oname=oname)
486 499 except Exception:
487 500 src = None
488 501
489 502 if src is None:
490 503 self.noinfo('source', oname)
491 504 else:
492 505 page.page(self.format(src))
493 506
494 507 def pfile(self, obj, oname=''):
495 508 """Show the whole file where an object was defined."""
496 509
497 510 lineno = find_source_lines(obj)
498 511 if lineno is None:
499 512 self.noinfo('file', oname)
500 513 return
501 514
502 515 ofile = find_file(obj)
503 516 # run contents of file through pager starting at line where the object
504 517 # is defined, as long as the file isn't binary and is actually on the
505 518 # filesystem.
506 519 if ofile.endswith(('.so', '.dll', '.pyd')):
507 520 print('File %r is binary, not printing.' % ofile)
508 521 elif not os.path.isfile(ofile):
509 522 print('File %r does not exist, not printing.' % ofile)
510 523 else:
511 524 # Print only text files, not extension binaries. Note that
512 525 # getsourcelines returns lineno with 1-offset and page() uses
513 526 # 0-offset, so we must adjust.
514 527 page.page(self.format(openpy.read_py_file(ofile, skip_encoding_cookie=False)), lineno - 1)
515 528
516 529
517 530 def _mime_format(self, text:str, formatter=None) -> dict:
518 531 """Return a mime bundle representation of the input text.
519 532
520 533 - if `formatter` is None, the returned mime bundle has
521 534 a ``text/plain`` field, with the input text.
522 535 a ``text/html`` field with a ``<pre>`` tag containing the input text.
523 536
524 537 - if ``formatter`` is not None, it must be a callable transforming the
525 538 input text into a mime bundle. Default values for ``text/plain`` and
526 539 ``text/html`` representations are the ones described above.
527 540
528 541 Note:
529 542
530 543 Formatters returning strings are supported but this behavior is deprecated.
531 544
532 545 """
533 546 defaults = {
534 547 "text/plain": text,
535 548 "text/html": f"<pre>{html.escape(text)}</pre>",
536 549 }
537 550
538 551 if formatter is None:
539 552 return defaults
540 553 else:
541 554 formatted = formatter(text)
542 555
543 556 if not isinstance(formatted, dict):
544 557 # Handle the deprecated behavior of a formatter returning
545 558 # a string instead of a mime bundle.
546 559 return {"text/plain": formatted, "text/html": f"<pre>{formatted}</pre>"}
547 560
548 561 else:
549 562 return dict(defaults, **formatted)
550 563
551 564
552 565 def format_mime(self, bundle):
553 566 """Format a mimebundle being created by _make_info_unformatted into a real mimebundle"""
554 567 # Format text/plain mimetype
555 568 if isinstance(bundle["text/plain"], (list, tuple)):
556 569 # bundle['text/plain'] is a list of (head, formatted body) pairs
557 570 lines = []
558 571 _len = max(len(h) for h, _ in bundle["text/plain"])
559 572
560 573 for head, body in bundle["text/plain"]:
561 574 body = body.strip("\n")
562 575 delim = "\n" if "\n" in body else " "
563 576 lines.append(
564 577 f"{self.__head(head+':')}{(_len - len(head))*' '}{delim}{body}"
565 578 )
566 579
567 580 bundle["text/plain"] = "\n".join(lines)
568 581
569 582 # Format the text/html mimetype
570 583 if isinstance(bundle["text/html"], (list, tuple)):
571 584 # bundle['text/html'] is a list of (head, formatted body) pairs
572 585 bundle["text/html"] = "\n".join(
573 586 (f"<h1>{head}</h1>\n{body}" for (head, body) in bundle["text/html"])
574 587 )
575 588 return bundle
576 589
577 590 def _append_info_field(
578 591 self, bundle, title: str, key: str, info, omit_sections, formatter
579 592 ):
580 593 """Append an info value to the unformatted mimebundle being constructed by _make_info_unformatted"""
581 594 if title in omit_sections or key in omit_sections:
582 595 return
583 596 field = info[key]
584 597 if field is not None:
585 598 formatted_field = self._mime_format(field, formatter)
586 599 bundle["text/plain"].append((title, formatted_field["text/plain"]))
587 600 bundle["text/html"].append((title, formatted_field["text/html"]))
588 601
589 602 def _make_info_unformatted(self, obj, info, formatter, detail_level, omit_sections):
590 603 """Assemble the mimebundle as unformatted lists of information"""
591 604 bundle = {
592 605 "text/plain": [],
593 606 "text/html": [],
594 607 }
595 608
596 609 # A convenience function to simplify calls below
597 610 def append_field(bundle, title: str, key: str, formatter=None):
598 611 self._append_info_field(
599 612 bundle,
600 613 title=title,
601 614 key=key,
602 615 info=info,
603 616 omit_sections=omit_sections,
604 617 formatter=formatter,
605 618 )
606 619
607 620 def code_formatter(text):
608 621 return {
609 622 'text/plain': self.format(text),
610 623 'text/html': pylight(text)
611 624 }
612 625
613 626 if info["isalias"]:
614 627 append_field(bundle, "Repr", "string_form")
615 628
616 629 elif info['ismagic']:
617 630 if detail_level > 0:
618 631 append_field(bundle, "Source", "source", code_formatter)
619 632 else:
620 633 append_field(bundle, "Docstring", "docstring", formatter)
621 634 append_field(bundle, "File", "file")
622 635
623 636 elif info['isclass'] or is_simple_callable(obj):
624 637 # Functions, methods, classes
625 638 append_field(bundle, "Signature", "definition", code_formatter)
626 639 append_field(bundle, "Init signature", "init_definition", code_formatter)
627 640 append_field(bundle, "Docstring", "docstring", formatter)
628 641 if detail_level > 0 and info["source"]:
629 642 append_field(bundle, "Source", "source", code_formatter)
630 643 else:
631 644 append_field(bundle, "Init docstring", "init_docstring", formatter)
632 645
633 646 append_field(bundle, "File", "file")
634 647 append_field(bundle, "Type", "type_name")
635 648 append_field(bundle, "Subclasses", "subclasses")
636 649
637 650 else:
638 651 # General Python objects
639 652 append_field(bundle, "Signature", "definition", code_formatter)
640 653 append_field(bundle, "Call signature", "call_def", code_formatter)
641 654 append_field(bundle, "Type", "type_name")
642 655 append_field(bundle, "String form", "string_form")
643 656
644 657 # Namespace
645 658 if info["namespace"] != "Interactive":
646 659 append_field(bundle, "Namespace", "namespace")
647 660
648 661 append_field(bundle, "Length", "length")
649 662 append_field(bundle, "File", "file")
650 663
651 664 # Source or docstring, depending on detail level and whether
652 665 # source found.
653 666 if detail_level > 0 and info["source"]:
654 667 append_field(bundle, "Source", "source", code_formatter)
655 668 else:
656 669 append_field(bundle, "Docstring", "docstring", formatter)
657 670
658 671 append_field(bundle, "Class docstring", "class_docstring", formatter)
659 672 append_field(bundle, "Init docstring", "init_docstring", formatter)
660 673 append_field(bundle, "Call docstring", "call_docstring", formatter)
661 674 return bundle
662 675
663 676
664 677 def _get_info(
665 678 self, obj, oname="", formatter=None, info=None, detail_level=0, omit_sections=()
666 679 ):
667 680 """Retrieve an info dict and format it.
668 681
669 682 Parameters
670 683 ----------
671 684 obj : any
672 685 Object to inspect and return info from
673 686 oname : str (default: ''):
674 687 Name of the variable pointing to `obj`.
675 688 formatter : callable
676 689 info
677 690 already computed information
678 691 detail_level : integer
679 692 Granularity of detail level, if set to 1, give more information.
680 693 omit_sections : container[str]
681 694 Titles or keys to omit from output (can be set, tuple, etc., anything supporting `in`)
682 695 """
683 696
684 697 info = self.info(obj, oname=oname, info=info, detail_level=detail_level)
685 698 bundle = self._make_info_unformatted(
686 699 obj, info, formatter, detail_level=detail_level, omit_sections=omit_sections
687 700 )
688 701 return self.format_mime(bundle)
689 702
690 703 def pinfo(
691 704 self,
692 705 obj,
693 706 oname="",
694 707 formatter=None,
695 708 info=None,
696 709 detail_level=0,
697 710 enable_html_pager=True,
698 711 omit_sections=(),
699 712 ):
700 713 """Show detailed information about an object.
701 714
702 715 Optional arguments:
703 716
704 717 - oname: name of the variable pointing to the object.
705 718
706 719 - formatter: callable (optional)
707 720 A special formatter for docstrings.
708 721
709 722 The formatter is a callable that takes a string as an input
710 723 and returns either a formatted string or a mime type bundle
711 724 in the form of a dictionary.
712 725
713 726 Although the support of custom formatter returning a string
714 727 instead of a mime type bundle is deprecated.
715 728
716 729 - info: a structure with some information fields which may have been
717 730 precomputed already.
718 731
719 732 - detail_level: if set to 1, more information is given.
720 733
721 734 - omit_sections: set of section keys and titles to omit
722 735 """
723 736 info = self._get_info(
724 737 obj, oname, formatter, info, detail_level, omit_sections=omit_sections
725 738 )
726 739 if not enable_html_pager:
727 740 del info['text/html']
728 741 page.page(info)
729 742
730 743 def _info(self, obj, oname="", info=None, detail_level=0):
731 744 """
732 745 Inspector.info() was likely improperly marked as deprecated
733 746 while only a parameter was deprecated. We "un-deprecate" it.
734 747 """
735 748
736 749 warnings.warn(
737 750 "The `Inspector.info()` method has been un-deprecated as of 8.0 "
738 751 "and the `formatter=` keyword removed. `Inspector._info` is now "
739 752 "an alias, and you can just call `.info()` directly.",
740 753 DeprecationWarning,
741 754 stacklevel=2,
742 755 )
743 756 return self.info(obj, oname=oname, info=info, detail_level=detail_level)
744 757
745 758 def info(self, obj, oname="", info=None, detail_level=0) -> dict:
746 759 """Compute a dict with detailed information about an object.
747 760
748 761 Parameters
749 762 ----------
750 763 obj : any
751 764 An object to find information about
752 765 oname : str (default: '')
753 766 Name of the variable pointing to `obj`.
754 767 info : (default: None)
755 768 A struct (dict like with attr access) with some information fields
756 769 which may have been precomputed already.
757 770 detail_level : int (default:0)
758 771 If set to 1, more information is given.
759 772
760 773 Returns
761 774 -------
762 775 An object info dict with known fields from `info_fields`. Keys are
763 776 strings, values are string or None.
764 777 """
765 778
766 779 if info is None:
767 780 ismagic = False
768 781 isalias = False
769 782 ospace = ''
770 783 else:
771 784 ismagic = info.ismagic
772 785 isalias = info.isalias
773 786 ospace = info.namespace
774 787
775 788 # Get docstring, special-casing aliases:
776 789 if isalias:
777 790 if not callable(obj):
778 791 try:
779 792 ds = "Alias to the system command:\n %s" % obj[1]
780 793 except:
781 794 ds = "Alias: " + str(obj)
782 795 else:
783 796 ds = "Alias to " + str(obj)
784 797 if obj.__doc__:
785 798 ds += "\nDocstring:\n" + obj.__doc__
786 799 else:
787 800 ds = getdoc(obj)
788 801 if ds is None:
789 802 ds = '<no docstring>'
790 803
791 804 # store output in a dict, we initialize it here and fill it as we go
792 805 out = dict(name=oname, found=True, isalias=isalias, ismagic=ismagic, subclasses=None)
793 806
794 807 string_max = 200 # max size of strings to show (snipped if longer)
795 808 shalf = int((string_max - 5) / 2)
796 809
797 810 if ismagic:
798 811 out['type_name'] = 'Magic function'
799 812 elif isalias:
800 813 out['type_name'] = 'System alias'
801 814 else:
802 815 out['type_name'] = type(obj).__name__
803 816
804 817 try:
805 818 bclass = obj.__class__
806 819 out['base_class'] = str(bclass)
807 820 except:
808 821 pass
809 822
810 823 # String form, but snip if too long in ? form (full in ??)
811 824 if detail_level >= self.str_detail_level:
812 825 try:
813 826 ostr = str(obj)
814 827 str_head = 'string_form'
815 828 if not detail_level and len(ostr)>string_max:
816 829 ostr = ostr[:shalf] + ' <...> ' + ostr[-shalf:]
817 830 ostr = ("\n" + " " * len(str_head.expandtabs())).\
818 831 join(q.strip() for q in ostr.split("\n"))
819 832 out[str_head] = ostr
820 833 except:
821 834 pass
822 835
823 836 if ospace:
824 837 out['namespace'] = ospace
825 838
826 839 # Length (for strings and lists)
827 840 try:
828 841 out['length'] = str(len(obj))
829 842 except Exception:
830 843 pass
831 844
832 845 # Filename where object was defined
833 846 binary_file = False
834 847 fname = find_file(obj)
835 848 if fname is None:
836 849 # if anything goes wrong, we don't want to show source, so it's as
837 850 # if the file was binary
838 851 binary_file = True
839 852 else:
840 853 if fname.endswith(('.so', '.dll', '.pyd')):
841 854 binary_file = True
842 855 elif fname.endswith('<string>'):
843 856 fname = 'Dynamically generated function. No source code available.'
844 857 out['file'] = compress_user(fname)
845 858
846 859 # Original source code for a callable, class or property.
847 860 if detail_level:
848 861 # Flush the source cache because inspect can return out-of-date
849 862 # source
850 863 linecache.checkcache()
851 864 try:
852 865 if isinstance(obj, property) or not binary_file:
853 866 src = getsource(obj, oname)
854 867 if src is not None:
855 868 src = src.rstrip()
856 869 out['source'] = src
857 870
858 871 except Exception:
859 872 pass
860 873
861 874 # Add docstring only if no source is to be shown (avoid repetitions).
862 875 if ds and not self._source_contains_docstring(out.get('source'), ds):
863 876 out['docstring'] = ds
864 877
865 878 # Constructor docstring for classes
866 879 if inspect.isclass(obj):
867 880 out['isclass'] = True
868 881
869 882 # get the init signature:
870 883 try:
871 884 init_def = self._getdef(obj, oname)
872 885 except AttributeError:
873 886 init_def = None
874 887
875 888 # get the __init__ docstring
876 889 try:
877 890 obj_init = obj.__init__
878 891 except AttributeError:
879 892 init_ds = None
880 893 else:
881 894 if init_def is None:
882 895 # Get signature from init if top-level sig failed.
883 896 # Can happen for built-in types (list, etc.).
884 897 try:
885 898 init_def = self._getdef(obj_init, oname)
886 899 except AttributeError:
887 900 pass
888 901 init_ds = getdoc(obj_init)
889 902 # Skip Python's auto-generated docstrings
890 903 if init_ds == _object_init_docstring:
891 904 init_ds = None
892 905
893 906 if init_def:
894 907 out['init_definition'] = init_def
895 908
896 909 if init_ds:
897 910 out['init_docstring'] = init_ds
898 911
899 912 names = [sub.__name__ for sub in type.__subclasses__(obj)]
900 913 if len(names) < 10:
901 914 all_names = ', '.join(names)
902 915 else:
903 916 all_names = ', '.join(names[:10]+['...'])
904 917 out['subclasses'] = all_names
905 918 # and class docstring for instances:
906 919 else:
907 920 # reconstruct the function definition and print it:
908 921 defln = self._getdef(obj, oname)
909 922 if defln:
910 923 out['definition'] = defln
911 924
912 925 # First, check whether the instance docstring is identical to the
913 926 # class one, and print it separately if they don't coincide. In
914 927 # most cases they will, but it's nice to print all the info for
915 928 # objects which use instance-customized docstrings.
916 929 if ds:
917 930 try:
918 931 cls = getattr(obj,'__class__')
919 932 except:
920 933 class_ds = None
921 934 else:
922 935 class_ds = getdoc(cls)
923 936 # Skip Python's auto-generated docstrings
924 937 if class_ds in _builtin_type_docstrings:
925 938 class_ds = None
926 939 if class_ds and ds != class_ds:
927 940 out['class_docstring'] = class_ds
928 941
929 942 # Next, try to show constructor docstrings
930 943 try:
931 944 init_ds = getdoc(obj.__init__)
932 945 # Skip Python's auto-generated docstrings
933 946 if init_ds == _object_init_docstring:
934 947 init_ds = None
935 948 except AttributeError:
936 949 init_ds = None
937 950 if init_ds:
938 951 out['init_docstring'] = init_ds
939 952
940 953 # Call form docstring for callable instances
941 954 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
942 955 call_def = self._getdef(obj.__call__, oname)
943 956 if call_def and (call_def != out.get('definition')):
944 957 # it may never be the case that call def and definition differ,
945 958 # but don't include the same signature twice
946 959 out['call_def'] = call_def
947 960 call_ds = getdoc(obj.__call__)
948 961 # Skip Python's auto-generated docstrings
949 962 if call_ds == _func_call_docstring:
950 963 call_ds = None
951 964 if call_ds:
952 965 out['call_docstring'] = call_ds
953 966
954 967 return object_info(**out)
955 968
956 969 @staticmethod
957 970 def _source_contains_docstring(src, doc):
958 971 """
959 972 Check whether the source *src* contains the docstring *doc*.
960 973
961 974 This is is helper function to skip displaying the docstring if the
962 975 source already contains it, avoiding repetition of information.
963 976 """
964 977 try:
965 978 def_node, = ast.parse(dedent(src)).body
966 979 return ast.get_docstring(def_node) == doc
967 980 except Exception:
968 981 # The source can become invalid or even non-existent (because it
969 982 # is re-fetched from the source file) so the above code fail in
970 983 # arbitrary ways.
971 984 return False
972 985
973 986 def psearch(self,pattern,ns_table,ns_search=[],
974 987 ignore_case=False,show_all=False, *, list_types=False):
975 988 """Search namespaces with wildcards for objects.
976 989
977 990 Arguments:
978 991
979 992 - pattern: string containing shell-like wildcards to use in namespace
980 993 searches and optionally a type specification to narrow the search to
981 994 objects of that type.
982 995
983 996 - ns_table: dict of name->namespaces for search.
984 997
985 998 Optional arguments:
986 999
987 1000 - ns_search: list of namespace names to include in search.
988 1001
989 1002 - ignore_case(False): make the search case-insensitive.
990 1003
991 1004 - show_all(False): show all names, including those starting with
992 1005 underscores.
993 1006
994 1007 - list_types(False): list all available object types for object matching.
995 1008 """
996 1009 #print 'ps pattern:<%r>' % pattern # dbg
997 1010
998 1011 # defaults
999 1012 type_pattern = 'all'
1000 1013 filter = ''
1001 1014
1002 1015 # list all object types
1003 1016 if list_types:
1004 1017 page.page('\n'.join(sorted(typestr2type)))
1005 1018 return
1006 1019
1007 1020 cmds = pattern.split()
1008 1021 len_cmds = len(cmds)
1009 1022 if len_cmds == 1:
1010 1023 # Only filter pattern given
1011 1024 filter = cmds[0]
1012 1025 elif len_cmds == 2:
1013 1026 # Both filter and type specified
1014 1027 filter,type_pattern = cmds
1015 1028 else:
1016 1029 raise ValueError('invalid argument string for psearch: <%s>' %
1017 1030 pattern)
1018 1031
1019 1032 # filter search namespaces
1020 1033 for name in ns_search:
1021 1034 if name not in ns_table:
1022 1035 raise ValueError('invalid namespace <%s>. Valid names: %s' %
1023 1036 (name,ns_table.keys()))
1024 1037
1025 1038 #print 'type_pattern:',type_pattern # dbg
1026 1039 search_result, namespaces_seen = set(), set()
1027 1040 for ns_name in ns_search:
1028 1041 ns = ns_table[ns_name]
1029 1042 # Normally, locals and globals are the same, so we just check one.
1030 1043 if id(ns) in namespaces_seen:
1031 1044 continue
1032 1045 namespaces_seen.add(id(ns))
1033 1046 tmp_res = list_namespace(ns, type_pattern, filter,
1034 1047 ignore_case=ignore_case, show_all=show_all)
1035 1048 search_result.update(tmp_res)
1036 1049
1037 1050 page.page('\n'.join(sorted(search_result)))
1038 1051
1039 1052
1040 1053 def _render_signature(obj_signature, obj_name) -> str:
1041 1054 """
1042 1055 This was mostly taken from inspect.Signature.__str__.
1043 1056 Look there for the comments.
1044 1057 The only change is to add linebreaks when this gets too long.
1045 1058 """
1046 1059 result = []
1047 1060 pos_only = False
1048 1061 kw_only = True
1049 1062 for param in obj_signature.parameters.values():
1050 1063 if param.kind == inspect._POSITIONAL_ONLY:
1051 1064 pos_only = True
1052 1065 elif pos_only:
1053 1066 result.append('/')
1054 1067 pos_only = False
1055 1068
1056 1069 if param.kind == inspect._VAR_POSITIONAL:
1057 1070 kw_only = False
1058 1071 elif param.kind == inspect._KEYWORD_ONLY and kw_only:
1059 1072 result.append('*')
1060 1073 kw_only = False
1061 1074
1062 1075 result.append(str(param))
1063 1076
1064 1077 if pos_only:
1065 1078 result.append('/')
1066 1079
1067 1080 # add up name, parameters, braces (2), and commas
1068 1081 if len(obj_name) + sum(len(r) + 2 for r in result) > 75:
1069 1082 # This doesn’t fit behind “Signature: ” in an inspect window.
1070 1083 rendered = '{}(\n{})'.format(obj_name, ''.join(
1071 1084 ' {},\n'.format(r) for r in result)
1072 1085 )
1073 1086 else:
1074 1087 rendered = '{}({})'.format(obj_name, ', '.join(result))
1075 1088
1076 1089 if obj_signature.return_annotation is not inspect._empty:
1077 1090 anno = inspect.formatannotation(obj_signature.return_annotation)
1078 1091 rendered += ' -> {}'.format(anno)
1079 1092
1080 1093 return rendered
@@ -1,698 +1,700 b''
1 1 # encoding: utf-8
2 2 """
3 3 Prefiltering components.
4 4
5 5 Prefilters transform user input before it is exec'd by Python. These
6 6 transforms are used to implement additional syntax such as !ls and %magic.
7 7 """
8 8
9 9 # Copyright (c) IPython Development Team.
10 10 # Distributed under the terms of the Modified BSD License.
11 11
12 12 from keyword import iskeyword
13 13 import re
14 14
15 15 from .autocall import IPyAutocall
16 16 from traitlets.config.configurable import Configurable
17 17 from .inputtransformer2 import (
18 18 ESC_MAGIC,
19 19 ESC_QUOTE,
20 20 ESC_QUOTE2,
21 21 ESC_PAREN,
22 22 )
23 23 from .macro import Macro
24 24 from .splitinput import LineInfo
25 25
26 26 from traitlets import (
27 27 List, Integer, Unicode, Bool, Instance, CRegExp
28 28 )
29 29
30 30 #-----------------------------------------------------------------------------
31 31 # Global utilities, errors and constants
32 32 #-----------------------------------------------------------------------------
33 33
34 34
35 35 class PrefilterError(Exception):
36 36 pass
37 37
38 38
39 39 # RegExp to identify potential function names
40 40 re_fun_name = re.compile(r'[^\W\d]([\w.]*) *$')
41 41
42 42 # RegExp to exclude strings with this start from autocalling. In
43 43 # particular, all binary operators should be excluded, so that if foo is
44 44 # callable, foo OP bar doesn't become foo(OP bar), which is invalid. The
45 45 # characters '!=()' don't need to be checked for, as the checkPythonChars
46 46 # routine explicitly does so, to catch direct calls and rebindings of
47 47 # existing names.
48 48
49 49 # Warning: the '-' HAS TO BE AT THE END of the first group, otherwise
50 50 # it affects the rest of the group in square brackets.
51 51 re_exclude_auto = re.compile(r'^[,&^\|\*/\+-]'
52 52 r'|^is |^not |^in |^and |^or ')
53 53
54 54 # try to catch also methods for stuff in lists/tuples/dicts: off
55 55 # (experimental). For this to work, the line_split regexp would need
56 56 # to be modified so it wouldn't break things at '['. That line is
57 57 # nasty enough that I shouldn't change it until I can test it _well_.
58 58 #self.re_fun_name = re.compile (r'[a-zA-Z_]([a-zA-Z0-9_.\[\]]*) ?$')
59 59
60 60
61 61 # Handler Check Utilities
62 62 def is_shadowed(identifier, ip):
63 63 """Is the given identifier defined in one of the namespaces which shadow
64 64 the alias and magic namespaces? Note that an identifier is different
65 65 than ifun, because it can not contain a '.' character."""
66 66 # This is much safer than calling ofind, which can change state
67 67 return (identifier in ip.user_ns \
68 68 or identifier in ip.user_global_ns \
69 69 or identifier in ip.ns_table['builtin']\
70 70 or iskeyword(identifier))
71 71
72 72
73 73 #-----------------------------------------------------------------------------
74 74 # Main Prefilter manager
75 75 #-----------------------------------------------------------------------------
76 76
77 77
78 78 class PrefilterManager(Configurable):
79 79 """Main prefilter component.
80 80
81 81 The IPython prefilter is run on all user input before it is run. The
82 82 prefilter consumes lines of input and produces transformed lines of
83 83 input.
84 84
85 85 The implementation consists of two phases:
86 86
87 87 1. Transformers
88 88 2. Checkers and handlers
89 89
90 90 Over time, we plan on deprecating the checkers and handlers and doing
91 91 everything in the transformers.
92 92
93 93 The transformers are instances of :class:`PrefilterTransformer` and have
94 94 a single method :meth:`transform` that takes a line and returns a
95 95 transformed line. The transformation can be accomplished using any
96 96 tool, but our current ones use regular expressions for speed.
97 97
98 98 After all the transformers have been run, the line is fed to the checkers,
99 99 which are instances of :class:`PrefilterChecker`. The line is passed to
100 100 the :meth:`check` method, which either returns `None` or a
101 101 :class:`PrefilterHandler` instance. If `None` is returned, the other
102 102 checkers are tried. If an :class:`PrefilterHandler` instance is returned,
103 103 the line is passed to the :meth:`handle` method of the returned
104 104 handler and no further checkers are tried.
105 105
106 106 Both transformers and checkers have a `priority` attribute, that determines
107 107 the order in which they are called. Smaller priorities are tried first.
108 108
109 109 Both transformers and checkers also have `enabled` attribute, which is
110 110 a boolean that determines if the instance is used.
111 111
112 112 Users or developers can change the priority or enabled attribute of
113 113 transformers or checkers, but they must call the :meth:`sort_checkers`
114 114 or :meth:`sort_transformers` method after changing the priority.
115 115 """
116 116
117 117 multi_line_specials = Bool(True).tag(config=True)
118 118 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
119 119
120 120 def __init__(self, shell=None, **kwargs):
121 121 super(PrefilterManager, self).__init__(shell=shell, **kwargs)
122 122 self.shell = shell
123 123 self._transformers = []
124 124 self.init_handlers()
125 125 self.init_checkers()
126 126
127 127 #-------------------------------------------------------------------------
128 128 # API for managing transformers
129 129 #-------------------------------------------------------------------------
130 130
131 131 def sort_transformers(self):
132 132 """Sort the transformers by priority.
133 133
134 134 This must be called after the priority of a transformer is changed.
135 135 The :meth:`register_transformer` method calls this automatically.
136 136 """
137 137 self._transformers.sort(key=lambda x: x.priority)
138 138
139 139 @property
140 140 def transformers(self):
141 141 """Return a list of checkers, sorted by priority."""
142 142 return self._transformers
143 143
144 144 def register_transformer(self, transformer):
145 145 """Register a transformer instance."""
146 146 if transformer not in self._transformers:
147 147 self._transformers.append(transformer)
148 148 self.sort_transformers()
149 149
150 150 def unregister_transformer(self, transformer):
151 151 """Unregister a transformer instance."""
152 152 if transformer in self._transformers:
153 153 self._transformers.remove(transformer)
154 154
155 155 #-------------------------------------------------------------------------
156 156 # API for managing checkers
157 157 #-------------------------------------------------------------------------
158 158
159 159 def init_checkers(self):
160 160 """Create the default checkers."""
161 161 self._checkers = []
162 162 for checker in _default_checkers:
163 163 checker(
164 164 shell=self.shell, prefilter_manager=self, parent=self
165 165 )
166 166
167 167 def sort_checkers(self):
168 168 """Sort the checkers by priority.
169 169
170 170 This must be called after the priority of a checker is changed.
171 171 The :meth:`register_checker` method calls this automatically.
172 172 """
173 173 self._checkers.sort(key=lambda x: x.priority)
174 174
175 175 @property
176 176 def checkers(self):
177 177 """Return a list of checkers, sorted by priority."""
178 178 return self._checkers
179 179
180 180 def register_checker(self, checker):
181 181 """Register a checker instance."""
182 182 if checker not in self._checkers:
183 183 self._checkers.append(checker)
184 184 self.sort_checkers()
185 185
186 186 def unregister_checker(self, checker):
187 187 """Unregister a checker instance."""
188 188 if checker in self._checkers:
189 189 self._checkers.remove(checker)
190 190
191 191 #-------------------------------------------------------------------------
192 192 # API for managing handlers
193 193 #-------------------------------------------------------------------------
194 194
195 195 def init_handlers(self):
196 196 """Create the default handlers."""
197 197 self._handlers = {}
198 198 self._esc_handlers = {}
199 199 for handler in _default_handlers:
200 200 handler(
201 201 shell=self.shell, prefilter_manager=self, parent=self
202 202 )
203 203
204 204 @property
205 205 def handlers(self):
206 206 """Return a dict of all the handlers."""
207 207 return self._handlers
208 208
209 209 def register_handler(self, name, handler, esc_strings):
210 210 """Register a handler instance by name with esc_strings."""
211 211 self._handlers[name] = handler
212 212 for esc_str in esc_strings:
213 213 self._esc_handlers[esc_str] = handler
214 214
215 215 def unregister_handler(self, name, handler, esc_strings):
216 216 """Unregister a handler instance by name with esc_strings."""
217 217 try:
218 218 del self._handlers[name]
219 219 except KeyError:
220 220 pass
221 221 for esc_str in esc_strings:
222 222 h = self._esc_handlers.get(esc_str)
223 223 if h is handler:
224 224 del self._esc_handlers[esc_str]
225 225
226 226 def get_handler_by_name(self, name):
227 227 """Get a handler by its name."""
228 228 return self._handlers.get(name)
229 229
230 230 def get_handler_by_esc(self, esc_str):
231 231 """Get a handler by its escape string."""
232 232 return self._esc_handlers.get(esc_str)
233 233
234 234 #-------------------------------------------------------------------------
235 235 # Main prefiltering API
236 236 #-------------------------------------------------------------------------
237 237
238 238 def prefilter_line_info(self, line_info):
239 239 """Prefilter a line that has been converted to a LineInfo object.
240 240
241 241 This implements the checker/handler part of the prefilter pipe.
242 242 """
243 243 # print "prefilter_line_info: ", line_info
244 244 handler = self.find_handler(line_info)
245 245 return handler.handle(line_info)
246 246
247 247 def find_handler(self, line_info):
248 248 """Find a handler for the line_info by trying checkers."""
249 249 for checker in self.checkers:
250 250 if checker.enabled:
251 251 handler = checker.check(line_info)
252 252 if handler:
253 253 return handler
254 254 return self.get_handler_by_name('normal')
255 255
256 256 def transform_line(self, line, continue_prompt):
257 257 """Calls the enabled transformers in order of increasing priority."""
258 258 for transformer in self.transformers:
259 259 if transformer.enabled:
260 260 line = transformer.transform(line, continue_prompt)
261 261 return line
262 262
263 263 def prefilter_line(self, line, continue_prompt=False):
264 264 """Prefilter a single input line as text.
265 265
266 266 This method prefilters a single line of text by calling the
267 267 transformers and then the checkers/handlers.
268 268 """
269 269
270 270 # print "prefilter_line: ", line, continue_prompt
271 271 # All handlers *must* return a value, even if it's blank ('').
272 272
273 273 # save the line away in case we crash, so the post-mortem handler can
274 274 # record it
275 275 self.shell._last_input_line = line
276 276
277 277 if not line:
278 278 # Return immediately on purely empty lines, so that if the user
279 279 # previously typed some whitespace that started a continuation
280 280 # prompt, he can break out of that loop with just an empty line.
281 281 # This is how the default python prompt works.
282 282 return ''
283 283
284 284 # At this point, we invoke our transformers.
285 285 if not continue_prompt or (continue_prompt and self.multi_line_specials):
286 286 line = self.transform_line(line, continue_prompt)
287 287
288 288 # Now we compute line_info for the checkers and handlers
289 289 line_info = LineInfo(line, continue_prompt)
290 290
291 291 # the input history needs to track even empty lines
292 292 stripped = line.strip()
293 293
294 294 normal_handler = self.get_handler_by_name('normal')
295 295 if not stripped:
296 296 return normal_handler.handle(line_info)
297 297
298 298 # special handlers are only allowed for single line statements
299 299 if continue_prompt and not self.multi_line_specials:
300 300 return normal_handler.handle(line_info)
301 301
302 302 prefiltered = self.prefilter_line_info(line_info)
303 303 # print "prefiltered line: %r" % prefiltered
304 304 return prefiltered
305 305
306 306 def prefilter_lines(self, lines, continue_prompt=False):
307 307 """Prefilter multiple input lines of text.
308 308
309 309 This is the main entry point for prefiltering multiple lines of
310 310 input. This simply calls :meth:`prefilter_line` for each line of
311 311 input.
312 312
313 313 This covers cases where there are multiple lines in the user entry,
314 314 which is the case when the user goes back to a multiline history
315 315 entry and presses enter.
316 316 """
317 317 llines = lines.rstrip('\n').split('\n')
318 318 # We can get multiple lines in one shot, where multiline input 'blends'
319 319 # into one line, in cases like recalling from the readline history
320 320 # buffer. We need to make sure that in such cases, we correctly
321 321 # communicate downstream which line is first and which are continuation
322 322 # ones.
323 323 if len(llines) > 1:
324 324 out = '\n'.join([self.prefilter_line(line, lnum>0)
325 325 for lnum, line in enumerate(llines) ])
326 326 else:
327 327 out = self.prefilter_line(llines[0], continue_prompt)
328 328
329 329 return out
330 330
331 331 #-----------------------------------------------------------------------------
332 332 # Prefilter transformers
333 333 #-----------------------------------------------------------------------------
334 334
335 335
336 336 class PrefilterTransformer(Configurable):
337 337 """Transform a line of user input."""
338 338
339 339 priority = Integer(100).tag(config=True)
340 340 # Transformers don't currently use shell or prefilter_manager, but as we
341 341 # move away from checkers and handlers, they will need them.
342 342 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
343 343 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager', allow_none=True)
344 344 enabled = Bool(True).tag(config=True)
345 345
346 346 def __init__(self, shell=None, prefilter_manager=None, **kwargs):
347 347 super(PrefilterTransformer, self).__init__(
348 348 shell=shell, prefilter_manager=prefilter_manager, **kwargs
349 349 )
350 350 self.prefilter_manager.register_transformer(self)
351 351
352 352 def transform(self, line, continue_prompt):
353 353 """Transform a line, returning the new one."""
354 354 return None
355 355
356 356 def __repr__(self):
357 357 return "<%s(priority=%r, enabled=%r)>" % (
358 358 self.__class__.__name__, self.priority, self.enabled)
359 359
360 360
361 361 #-----------------------------------------------------------------------------
362 362 # Prefilter checkers
363 363 #-----------------------------------------------------------------------------
364 364
365 365
366 366 class PrefilterChecker(Configurable):
367 367 """Inspect an input line and return a handler for that line."""
368 368
369 369 priority = Integer(100).tag(config=True)
370 370 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
371 371 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager', allow_none=True)
372 372 enabled = Bool(True).tag(config=True)
373 373
374 374 def __init__(self, shell=None, prefilter_manager=None, **kwargs):
375 375 super(PrefilterChecker, self).__init__(
376 376 shell=shell, prefilter_manager=prefilter_manager, **kwargs
377 377 )
378 378 self.prefilter_manager.register_checker(self)
379 379
380 380 def check(self, line_info):
381 381 """Inspect line_info and return a handler instance or None."""
382 382 return None
383 383
384 384 def __repr__(self):
385 385 return "<%s(priority=%r, enabled=%r)>" % (
386 386 self.__class__.__name__, self.priority, self.enabled)
387 387
388 388
389 389 class EmacsChecker(PrefilterChecker):
390 390
391 391 priority = Integer(100).tag(config=True)
392 392 enabled = Bool(False).tag(config=True)
393 393
394 394 def check(self, line_info):
395 395 "Emacs ipython-mode tags certain input lines."
396 396 if line_info.line.endswith('# PYTHON-MODE'):
397 397 return self.prefilter_manager.get_handler_by_name('emacs')
398 398 else:
399 399 return None
400 400
401 401
402 402 class MacroChecker(PrefilterChecker):
403 403
404 404 priority = Integer(250).tag(config=True)
405 405
406 406 def check(self, line_info):
407 407 obj = self.shell.user_ns.get(line_info.ifun)
408 408 if isinstance(obj, Macro):
409 409 return self.prefilter_manager.get_handler_by_name('macro')
410 410 else:
411 411 return None
412 412
413 413
414 414 class IPyAutocallChecker(PrefilterChecker):
415 415
416 416 priority = Integer(300).tag(config=True)
417 417
418 418 def check(self, line_info):
419 419 "Instances of IPyAutocall in user_ns get autocalled immediately"
420 420 obj = self.shell.user_ns.get(line_info.ifun, None)
421 421 if isinstance(obj, IPyAutocall):
422 422 obj.set_ip(self.shell)
423 423 return self.prefilter_manager.get_handler_by_name('auto')
424 424 else:
425 425 return None
426 426
427 427
428 428 class AssignmentChecker(PrefilterChecker):
429 429
430 430 priority = Integer(600).tag(config=True)
431 431
432 432 def check(self, line_info):
433 433 """Check to see if user is assigning to a var for the first time, in
434 434 which case we want to avoid any sort of automagic / autocall games.
435 435
436 436 This allows users to assign to either alias or magic names true python
437 437 variables (the magic/alias systems always take second seat to true
438 438 python code). E.g. ls='hi', or ls,that=1,2"""
439 439 if line_info.the_rest:
440 440 if line_info.the_rest[0] in '=,':
441 441 return self.prefilter_manager.get_handler_by_name('normal')
442 442 else:
443 443 return None
444 444
445 445
446 446 class AutoMagicChecker(PrefilterChecker):
447 447
448 448 priority = Integer(700).tag(config=True)
449 449
450 450 def check(self, line_info):
451 451 """If the ifun is magic, and automagic is on, run it. Note: normal,
452 452 non-auto magic would already have been triggered via '%' in
453 453 check_esc_chars. This just checks for automagic. Also, before
454 454 triggering the magic handler, make sure that there is nothing in the
455 455 user namespace which could shadow it."""
456 456 if not self.shell.automagic or not self.shell.find_magic(line_info.ifun):
457 457 return None
458 458
459 459 # We have a likely magic method. Make sure we should actually call it.
460 460 if line_info.continue_prompt and not self.prefilter_manager.multi_line_specials:
461 461 return None
462 462
463 463 head = line_info.ifun.split('.',1)[0]
464 464 if is_shadowed(head, self.shell):
465 465 return None
466 466
467 467 return self.prefilter_manager.get_handler_by_name('magic')
468 468
469 469
470 470 class PythonOpsChecker(PrefilterChecker):
471 471
472 472 priority = Integer(900).tag(config=True)
473 473
474 474 def check(self, line_info):
475 475 """If the 'rest' of the line begins with a function call or pretty much
476 476 any python operator, we should simply execute the line (regardless of
477 477 whether or not there's a possible autocall expansion). This avoids
478 478 spurious (and very confusing) geattr() accesses."""
479 479 if line_info.the_rest and line_info.the_rest[0] in '!=()<>,+*/%^&|':
480 480 return self.prefilter_manager.get_handler_by_name('normal')
481 481 else:
482 482 return None
483 483
484 484
485 485 class AutocallChecker(PrefilterChecker):
486 486
487 487 priority = Integer(1000).tag(config=True)
488 488
489 489 function_name_regexp = CRegExp(re_fun_name,
490 490 help="RegExp to identify potential function names."
491 491 ).tag(config=True)
492 492 exclude_regexp = CRegExp(re_exclude_auto,
493 493 help="RegExp to exclude strings with this start from autocalling."
494 494 ).tag(config=True)
495 495
496 496 def check(self, line_info):
497 497 "Check if the initial word/function is callable and autocall is on."
498 498 if not self.shell.autocall:
499 499 return None
500 500
501 501 oinfo = line_info.ofind(self.shell) # This can mutate state via getattr
502 if not oinfo['found']:
502 if not oinfo.found:
503 503 return None
504 504
505 505 ignored_funs = ['b', 'f', 'r', 'u', 'br', 'rb', 'fr', 'rf']
506 506 ifun = line_info.ifun
507 507 line = line_info.line
508 508 if ifun.lower() in ignored_funs and (line.startswith(ifun + "'") or line.startswith(ifun + '"')):
509 509 return None
510 510
511 if callable(oinfo['obj']) \
512 and (not self.exclude_regexp.match(line_info.the_rest)) \
513 and self.function_name_regexp.match(line_info.ifun):
514 return self.prefilter_manager.get_handler_by_name('auto')
511 if (
512 callable(oinfo.obj)
513 and (not self.exclude_regexp.match(line_info.the_rest))
514 and self.function_name_regexp.match(line_info.ifun)
515 ):
516 return self.prefilter_manager.get_handler_by_name("auto")
515 517 else:
516 518 return None
517 519
518 520
519 521 #-----------------------------------------------------------------------------
520 522 # Prefilter handlers
521 523 #-----------------------------------------------------------------------------
522 524
523 525
524 526 class PrefilterHandler(Configurable):
525 527
526 528 handler_name = Unicode('normal')
527 529 esc_strings = List([])
528 530 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
529 531 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager', allow_none=True)
530 532
531 533 def __init__(self, shell=None, prefilter_manager=None, **kwargs):
532 534 super(PrefilterHandler, self).__init__(
533 535 shell=shell, prefilter_manager=prefilter_manager, **kwargs
534 536 )
535 537 self.prefilter_manager.register_handler(
536 538 self.handler_name,
537 539 self,
538 540 self.esc_strings
539 541 )
540 542
541 543 def handle(self, line_info):
542 544 # print "normal: ", line_info
543 545 """Handle normal input lines. Use as a template for handlers."""
544 546
545 547 # With autoindent on, we need some way to exit the input loop, and I
546 548 # don't want to force the user to have to backspace all the way to
547 549 # clear the line. The rule will be in this case, that either two
548 550 # lines of pure whitespace in a row, or a line of pure whitespace but
549 551 # of a size different to the indent level, will exit the input loop.
550 552 line = line_info.line
551 553 continue_prompt = line_info.continue_prompt
552 554
553 555 if (continue_prompt and
554 556 self.shell.autoindent and
555 557 line.isspace() and
556 558 0 < abs(len(line) - self.shell.indent_current_nsp) <= 2):
557 559 line = ''
558 560
559 561 return line
560 562
561 563 def __str__(self):
562 564 return "<%s(name=%s)>" % (self.__class__.__name__, self.handler_name)
563 565
564 566
565 567 class MacroHandler(PrefilterHandler):
566 568 handler_name = Unicode("macro")
567 569
568 570 def handle(self, line_info):
569 571 obj = self.shell.user_ns.get(line_info.ifun)
570 572 pre_space = line_info.pre_whitespace
571 573 line_sep = "\n" + pre_space
572 574 return pre_space + line_sep.join(obj.value.splitlines())
573 575
574 576
575 577 class MagicHandler(PrefilterHandler):
576 578
577 579 handler_name = Unicode('magic')
578 580 esc_strings = List([ESC_MAGIC])
579 581
580 582 def handle(self, line_info):
581 583 """Execute magic functions."""
582 584 ifun = line_info.ifun
583 585 the_rest = line_info.the_rest
584 586 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
585 587 t_arg_s = ifun + " " + the_rest
586 588 t_magic_name, _, t_magic_arg_s = t_arg_s.partition(' ')
587 589 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
588 590 cmd = '%sget_ipython().run_line_magic(%r, %r)' % (line_info.pre_whitespace, t_magic_name, t_magic_arg_s)
589 591 return cmd
590 592
591 593
592 594 class AutoHandler(PrefilterHandler):
593 595
594 596 handler_name = Unicode('auto')
595 597 esc_strings = List([ESC_PAREN, ESC_QUOTE, ESC_QUOTE2])
596 598
597 599 def handle(self, line_info):
598 600 """Handle lines which can be auto-executed, quoting if requested."""
599 601 line = line_info.line
600 602 ifun = line_info.ifun
601 603 the_rest = line_info.the_rest
602 604 esc = line_info.esc
603 605 continue_prompt = line_info.continue_prompt
604 obj = line_info.ofind(self.shell)['obj']
606 obj = line_info.ofind(self.shell).obj
605 607
606 608 # This should only be active for single-line input!
607 609 if continue_prompt:
608 610 return line
609 611
610 612 force_auto = isinstance(obj, IPyAutocall)
611 613
612 614 # User objects sometimes raise exceptions on attribute access other
613 615 # than AttributeError (we've seen it in the past), so it's safest to be
614 616 # ultra-conservative here and catch all.
615 617 try:
616 618 auto_rewrite = obj.rewrite
617 619 except Exception:
618 620 auto_rewrite = True
619 621
620 622 if esc == ESC_QUOTE:
621 623 # Auto-quote splitting on whitespace
622 624 newcmd = '%s("%s")' % (ifun,'", "'.join(the_rest.split()) )
623 625 elif esc == ESC_QUOTE2:
624 626 # Auto-quote whole string
625 627 newcmd = '%s("%s")' % (ifun,the_rest)
626 628 elif esc == ESC_PAREN:
627 629 newcmd = '%s(%s)' % (ifun,",".join(the_rest.split()))
628 630 else:
629 631 # Auto-paren.
630 632 if force_auto:
631 633 # Don't rewrite if it is already a call.
632 634 do_rewrite = not the_rest.startswith('(')
633 635 else:
634 636 if not the_rest:
635 637 # We only apply it to argument-less calls if the autocall
636 638 # parameter is set to 2.
637 639 do_rewrite = (self.shell.autocall >= 2)
638 640 elif the_rest.startswith('[') and hasattr(obj, '__getitem__'):
639 641 # Don't autocall in this case: item access for an object
640 642 # which is BOTH callable and implements __getitem__.
641 643 do_rewrite = False
642 644 else:
643 645 do_rewrite = True
644 646
645 647 # Figure out the rewritten command
646 648 if do_rewrite:
647 649 if the_rest.endswith(';'):
648 650 newcmd = '%s(%s);' % (ifun.rstrip(),the_rest[:-1])
649 651 else:
650 652 newcmd = '%s(%s)' % (ifun.rstrip(), the_rest)
651 653 else:
652 654 normal_handler = self.prefilter_manager.get_handler_by_name('normal')
653 655 return normal_handler.handle(line_info)
654 656
655 657 # Display the rewritten call
656 658 if auto_rewrite:
657 659 self.shell.auto_rewrite_input(newcmd)
658 660
659 661 return newcmd
660 662
661 663
662 664 class EmacsHandler(PrefilterHandler):
663 665
664 666 handler_name = Unicode('emacs')
665 667 esc_strings = List([])
666 668
667 669 def handle(self, line_info):
668 670 """Handle input lines marked by python-mode."""
669 671
670 672 # Currently, nothing is done. Later more functionality can be added
671 673 # here if needed.
672 674
673 675 # The input cache shouldn't be updated
674 676 return line_info.line
675 677
676 678
677 679 #-----------------------------------------------------------------------------
678 680 # Defaults
679 681 #-----------------------------------------------------------------------------
680 682
681 683
682 684 _default_checkers = [
683 685 EmacsChecker,
684 686 MacroChecker,
685 687 IPyAutocallChecker,
686 688 AssignmentChecker,
687 689 AutoMagicChecker,
688 690 PythonOpsChecker,
689 691 AutocallChecker
690 692 ]
691 693
692 694 _default_handlers = [
693 695 PrefilterHandler,
694 696 MacroHandler,
695 697 MagicHandler,
696 698 AutoHandler,
697 699 EmacsHandler
698 700 ]
@@ -1,137 +1,138 b''
1 1 # encoding: utf-8
2 2 """
3 3 Simple utility for splitting user input. This is used by both inputsplitter and
4 4 prefilter.
5 5
6 6 Authors:
7 7
8 8 * Brian Granger
9 9 * Fernando Perez
10 10 """
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Copyright (C) 2008-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
23 23 import re
24 24 import sys
25 25
26 26 from IPython.utils import py3compat
27 27 from IPython.utils.encoding import get_stream_enc
28 from IPython.core.oinspect import OInfo
28 29
29 30 #-----------------------------------------------------------------------------
30 31 # Main function
31 32 #-----------------------------------------------------------------------------
32 33
33 34 # RegExp for splitting line contents into pre-char//first word-method//rest.
34 35 # For clarity, each group in on one line.
35 36
36 37 # WARNING: update the regexp if the escapes in interactiveshell are changed, as
37 38 # they are hardwired in.
38 39
39 40 # Although it's not solely driven by the regex, note that:
40 41 # ,;/% only trigger if they are the first character on the line
41 42 # ! and !! trigger if they are first char(s) *or* follow an indent
42 43 # ? triggers as first or last char.
43 44
44 45 line_split = re.compile(r"""
45 46 ^(\s*) # any leading space
46 47 ([,;/%]|!!?|\?\??)? # escape character or characters
47 48 \s*(%{0,2}[\w\.\*]*) # function/method, possibly with leading %
48 49 # to correctly treat things like '?%magic'
49 50 (.*?$|$) # rest of line
50 51 """, re.VERBOSE)
51 52
52 53
53 54 def split_user_input(line, pattern=None):
54 55 """Split user input into initial whitespace, escape character, function part
55 56 and the rest.
56 57 """
57 58 # We need to ensure that the rest of this routine deals only with unicode
58 59 encoding = get_stream_enc(sys.stdin, 'utf-8')
59 60 line = py3compat.cast_unicode(line, encoding)
60 61
61 62 if pattern is None:
62 63 pattern = line_split
63 64 match = pattern.match(line)
64 65 if not match:
65 66 # print "match failed for line '%s'" % line
66 67 try:
67 68 ifun, the_rest = line.split(None,1)
68 69 except ValueError:
69 70 # print "split failed for line '%s'" % line
70 71 ifun, the_rest = line, u''
71 72 pre = re.match(r'^(\s*)(.*)',line).groups()[0]
72 73 esc = ""
73 74 else:
74 75 pre, esc, ifun, the_rest = match.groups()
75 76
76 77 #print 'line:<%s>' % line # dbg
77 78 #print 'pre <%s> ifun <%s> rest <%s>' % (pre,ifun.strip(),the_rest) # dbg
78 79 return pre, esc or '', ifun.strip(), the_rest.lstrip()
79 80
80 81
81 82 class LineInfo(object):
82 83 """A single line of input and associated info.
83 84
84 85 Includes the following as properties:
85 86
86 87 line
87 88 The original, raw line
88 89
89 90 continue_prompt
90 91 Is this line a continuation in a sequence of multiline input?
91 92
92 93 pre
93 94 Any leading whitespace.
94 95
95 96 esc
96 97 The escape character(s) in pre or the empty string if there isn't one.
97 98 Note that '!!' and '??' are possible values for esc. Otherwise it will
98 99 always be a single character.
99 100
100 101 ifun
101 102 The 'function part', which is basically the maximal initial sequence
102 103 of valid python identifiers and the '.' character. This is what is
103 104 checked for alias and magic transformations, used for auto-calling,
104 105 etc. In contrast to Python identifiers, it may start with "%" and contain
105 106 "*".
106 107
107 108 the_rest
108 109 Everything else on the line.
109 110 """
110 111 def __init__(self, line, continue_prompt=False):
111 112 self.line = line
112 113 self.continue_prompt = continue_prompt
113 114 self.pre, self.esc, self.ifun, self.the_rest = split_user_input(line)
114 115
115 116 self.pre_char = self.pre.strip()
116 117 if self.pre_char:
117 118 self.pre_whitespace = '' # No whitespace allowed before esc chars
118 119 else:
119 120 self.pre_whitespace = self.pre
120 121
121 def ofind(self, ip):
122 def ofind(self, ip) -> OInfo:
122 123 """Do a full, attribute-walking lookup of the ifun in the various
123 124 namespaces for the given IPython InteractiveShell instance.
124 125
125 126 Return a dict with keys: {found, obj, ospace, ismagic}
126 127
127 128 Note: can cause state changes because of calling getattr, but should
128 129 only be run if autocall is on and if the line hasn't matched any
129 130 other, less dangerous handlers.
130 131
131 132 Does cache the results of the call, so can be called multiple times
132 133 without worrying about *further* damaging state.
133 134 """
134 135 return ip._ofind(self.ifun)
135 136
136 137 def __str__(self):
137 138 return "LineInfo [%s|%s|%s|%s]" %(self.pre, self.esc, self.ifun, self.the_rest)
@@ -1,193 +1,193 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Tests for completerlib.
3 3
4 4 """
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Imports
8 8 #-----------------------------------------------------------------------------
9 9
10 10 import os
11 11 import shutil
12 12 import sys
13 13 import tempfile
14 14 import unittest
15 15 from os.path import join
16 16
17 17 from tempfile import TemporaryDirectory
18 18
19 19 from IPython.core.completerlib import magic_run_completer, module_completion, try_import
20 20 from IPython.testing.decorators import onlyif_unicode_paths
21 21
22 22
23 23 class MockEvent(object):
24 24 def __init__(self, line):
25 25 self.line = line
26 26
27 27 #-----------------------------------------------------------------------------
28 28 # Test functions begin
29 29 #-----------------------------------------------------------------------------
30 30 class Test_magic_run_completer(unittest.TestCase):
31 31 files = [u"aao.py", u"a.py", u"b.py", u"aao.txt"]
32 32 dirs = [u"adir/", "bdir/"]
33 33
34 34 def setUp(self):
35 35 self.BASETESTDIR = tempfile.mkdtemp()
36 36 for fil in self.files:
37 37 with open(join(self.BASETESTDIR, fil), "w", encoding="utf-8") as sfile:
38 38 sfile.write("pass\n")
39 39 for d in self.dirs:
40 40 os.mkdir(join(self.BASETESTDIR, d))
41 41
42 42 self.oldpath = os.getcwd()
43 43 os.chdir(self.BASETESTDIR)
44 44
45 45 def tearDown(self):
46 46 os.chdir(self.oldpath)
47 47 shutil.rmtree(self.BASETESTDIR)
48 48
49 49 def test_1(self):
50 50 """Test magic_run_completer, should match two alternatives
51 51 """
52 52 event = MockEvent(u"%run a")
53 53 mockself = None
54 54 match = set(magic_run_completer(mockself, event))
55 55 self.assertEqual(match, {u"a.py", u"aao.py", u"adir/"})
56 56
57 57 def test_2(self):
58 58 """Test magic_run_completer, should match one alternative
59 59 """
60 60 event = MockEvent(u"%run aa")
61 61 mockself = None
62 62 match = set(magic_run_completer(mockself, event))
63 63 self.assertEqual(match, {u"aao.py"})
64 64
65 65 def test_3(self):
66 66 """Test magic_run_completer with unterminated " """
67 67 event = MockEvent(u'%run "a')
68 68 mockself = None
69 69 match = set(magic_run_completer(mockself, event))
70 70 self.assertEqual(match, {u"a.py", u"aao.py", u"adir/"})
71 71
72 72 def test_completion_more_args(self):
73 73 event = MockEvent(u'%run a.py ')
74 74 match = set(magic_run_completer(None, event))
75 75 self.assertEqual(match, set(self.files + self.dirs))
76 76
77 77 def test_completion_in_dir(self):
78 78 # Github issue #3459
79 79 event = MockEvent(u'%run a.py {}'.format(join(self.BASETESTDIR, 'a')))
80 80 print(repr(event.line))
81 81 match = set(magic_run_completer(None, event))
82 82 # We specifically use replace here rather than normpath, because
83 83 # at one point there were duplicates 'adir' and 'adir/', and normpath
84 84 # would hide the failure for that.
85 85 self.assertEqual(match, {join(self.BASETESTDIR, f).replace('\\','/')
86 86 for f in (u'a.py', u'aao.py', u'aao.txt', u'adir/')})
87 87
88 88 class Test_magic_run_completer_nonascii(unittest.TestCase):
89 89 @onlyif_unicode_paths
90 90 def setUp(self):
91 91 self.BASETESTDIR = tempfile.mkdtemp()
92 92 for fil in [u"aaø.py", u"a.py", u"b.py"]:
93 93 with open(join(self.BASETESTDIR, fil), "w", encoding="utf-8") as sfile:
94 94 sfile.write("pass\n")
95 95 self.oldpath = os.getcwd()
96 96 os.chdir(self.BASETESTDIR)
97 97
98 98 def tearDown(self):
99 99 os.chdir(self.oldpath)
100 100 shutil.rmtree(self.BASETESTDIR)
101 101
102 102 @onlyif_unicode_paths
103 103 def test_1(self):
104 104 """Test magic_run_completer, should match two alternatives
105 105 """
106 106 event = MockEvent(u"%run a")
107 107 mockself = None
108 108 match = set(magic_run_completer(mockself, event))
109 109 self.assertEqual(match, {u"a.py", u"aaø.py"})
110 110
111 111 @onlyif_unicode_paths
112 112 def test_2(self):
113 113 """Test magic_run_completer, should match one alternative
114 114 """
115 115 event = MockEvent(u"%run aa")
116 116 mockself = None
117 117 match = set(magic_run_completer(mockself, event))
118 118 self.assertEqual(match, {u"aaø.py"})
119 119
120 120 @onlyif_unicode_paths
121 121 def test_3(self):
122 122 """Test magic_run_completer with unterminated " """
123 123 event = MockEvent(u'%run "a')
124 124 mockself = None
125 125 match = set(magic_run_completer(mockself, event))
126 126 self.assertEqual(match, {u"a.py", u"aaø.py"})
127 127
128 128 # module_completer:
129 129
130 130 def test_import_invalid_module():
131 131 """Testing of issue https://github.com/ipython/ipython/issues/1107"""
132 132 invalid_module_names = {'foo-bar', 'foo:bar', '10foo'}
133 133 valid_module_names = {'foobar'}
134 134 with TemporaryDirectory() as tmpdir:
135 135 sys.path.insert( 0, tmpdir )
136 136 for name in invalid_module_names | valid_module_names:
137 137 filename = os.path.join(tmpdir, name + ".py")
138 138 open(filename, "w", encoding="utf-8").close()
139 139
140 140 s = set( module_completion('import foo') )
141 141 intersection = s.intersection(invalid_module_names)
142 142 assert intersection == set()
143 143
144 144 assert valid_module_names.issubset(s), valid_module_names.intersection(s)
145 145
146 146
147 147 def test_bad_module_all():
148 148 """Test module with invalid __all__
149 149
150 150 https://github.com/ipython/ipython/issues/9678
151 151 """
152 152 testsdir = os.path.dirname(__file__)
153 153 sys.path.insert(0, testsdir)
154 154 try:
155 155 results = module_completion("from bad_all import ")
156 156 assert "puppies" in results
157 157 for r in results:
158 158 assert isinstance(r, str)
159 159
160 160 # bad_all doesn't contain submodules, but this completion
161 161 # should finish without raising an exception:
162 162 results = module_completion("import bad_all.")
163 163 assert results == []
164 164 finally:
165 165 sys.path.remove(testsdir)
166 166
167 167
168 168 def test_module_without_init():
169 169 """
170 170 Test module without __init__.py.
171 171
172 172 https://github.com/ipython/ipython/issues/11226
173 173 """
174 174 fake_module_name = "foo"
175 175 with TemporaryDirectory() as tmpdir:
176 176 sys.path.insert(0, tmpdir)
177 177 try:
178 178 os.makedirs(os.path.join(tmpdir, fake_module_name))
179 179 s = try_import(mod=fake_module_name)
180 assert s == []
180 assert s == [], f"for module {fake_module_name}"
181 181 finally:
182 182 sys.path.remove(tmpdir)
183 183
184 184
185 185 def test_valid_exported_submodules():
186 186 """
187 187 Test checking exported (__all__) objects are submodules
188 188 """
189 189 results = module_completion("import os.pa")
190 190 # ensure we get a valid submodule:
191 191 assert "os.path" in results
192 192 # ensure we don't get objects that aren't submodules:
193 193 assert "os.pathconf" not in results
@@ -1,1175 +1,1200 b''
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
9 9 # Copyright (c) IPython Development Team.
10 10 # Distributed under the terms of the Modified BSD License.
11 11
12 12 import asyncio
13 13 import ast
14 14 import os
15 15 import signal
16 16 import shutil
17 17 import sys
18 18 import tempfile
19 19 import unittest
20 20 import pytest
21 21 from unittest import mock
22 22
23 23 from os.path import join
24 24
25 25 from IPython.core.error import InputRejected
26 26 from IPython.core.inputtransformer import InputTransformer
27 27 from IPython.core import interactiveshell
28 from IPython.core.oinspect import OInfo
28 29 from IPython.testing.decorators import (
29 30 skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist,
30 31 )
31 32 from IPython.testing import tools as tt
32 33 from IPython.utils.process import find_cmd
33 34
34 35 #-----------------------------------------------------------------------------
35 36 # Globals
36 37 #-----------------------------------------------------------------------------
37 38 # This is used by every single test, no point repeating it ad nauseam
38 39
39 40 #-----------------------------------------------------------------------------
40 41 # Tests
41 42 #-----------------------------------------------------------------------------
42 43
43 44 class DerivedInterrupt(KeyboardInterrupt):
44 45 pass
45 46
46 47 class InteractiveShellTestCase(unittest.TestCase):
47 48 def test_naked_string_cells(self):
48 49 """Test that cells with only naked strings are fully executed"""
49 50 # First, single-line inputs
50 51 ip.run_cell('"a"\n')
51 52 self.assertEqual(ip.user_ns['_'], 'a')
52 53 # And also multi-line cells
53 54 ip.run_cell('"""a\nb"""\n')
54 55 self.assertEqual(ip.user_ns['_'], 'a\nb')
55 56
56 57 def test_run_empty_cell(self):
57 58 """Just make sure we don't get a horrible error with a blank
58 59 cell of input. Yes, I did overlook that."""
59 60 old_xc = ip.execution_count
60 61 res = ip.run_cell('')
61 62 self.assertEqual(ip.execution_count, old_xc)
62 63 self.assertEqual(res.execution_count, None)
63 64
64 65 def test_run_cell_multiline(self):
65 66 """Multi-block, multi-line cells must execute correctly.
66 67 """
67 68 src = '\n'.join(["x=1",
68 69 "y=2",
69 70 "if 1:",
70 71 " x += 1",
71 72 " y += 1",])
72 73 res = ip.run_cell(src)
73 74 self.assertEqual(ip.user_ns['x'], 2)
74 75 self.assertEqual(ip.user_ns['y'], 3)
75 76 self.assertEqual(res.success, True)
76 77 self.assertEqual(res.result, None)
77 78
78 79 def test_multiline_string_cells(self):
79 80 "Code sprinkled with multiline strings should execute (GH-306)"
80 81 ip.run_cell('tmp=0')
81 82 self.assertEqual(ip.user_ns['tmp'], 0)
82 83 res = ip.run_cell('tmp=1;"""a\nb"""\n')
83 84 self.assertEqual(ip.user_ns['tmp'], 1)
84 85 self.assertEqual(res.success, True)
85 86 self.assertEqual(res.result, "a\nb")
86 87
87 88 def test_dont_cache_with_semicolon(self):
88 89 "Ending a line with semicolon should not cache the returned object (GH-307)"
89 90 oldlen = len(ip.user_ns['Out'])
90 91 for cell in ['1;', '1;1;']:
91 92 res = ip.run_cell(cell, store_history=True)
92 93 newlen = len(ip.user_ns['Out'])
93 94 self.assertEqual(oldlen, newlen)
94 95 self.assertIsNone(res.result)
95 96 i = 0
96 97 #also test the default caching behavior
97 98 for cell in ['1', '1;1']:
98 99 ip.run_cell(cell, store_history=True)
99 100 newlen = len(ip.user_ns['Out'])
100 101 i += 1
101 102 self.assertEqual(oldlen+i, newlen)
102 103
103 104 def test_syntax_error(self):
104 105 res = ip.run_cell("raise = 3")
105 106 self.assertIsInstance(res.error_before_exec, SyntaxError)
106 107
107 108 def test_open_standard_input_stream(self):
108 109 res = ip.run_cell("open(0)")
109 110 self.assertIsInstance(res.error_in_exec, ValueError)
110 111
111 112 def test_open_standard_output_stream(self):
112 113 res = ip.run_cell("open(1)")
113 114 self.assertIsInstance(res.error_in_exec, ValueError)
114 115
115 116 def test_open_standard_error_stream(self):
116 117 res = ip.run_cell("open(2)")
117 118 self.assertIsInstance(res.error_in_exec, ValueError)
118 119
119 120 def test_In_variable(self):
120 121 "Verify that In variable grows with user input (GH-284)"
121 122 oldlen = len(ip.user_ns['In'])
122 123 ip.run_cell('1;', store_history=True)
123 124 newlen = len(ip.user_ns['In'])
124 125 self.assertEqual(oldlen+1, newlen)
125 126 self.assertEqual(ip.user_ns['In'][-1],'1;')
126 127
127 128 def test_magic_names_in_string(self):
128 129 ip.run_cell('a = """\n%exit\n"""')
129 130 self.assertEqual(ip.user_ns['a'], '\n%exit\n')
130 131
131 132 def test_trailing_newline(self):
132 133 """test that running !(command) does not raise a SyntaxError"""
133 134 ip.run_cell('!(true)\n', False)
134 135 ip.run_cell('!(true)\n\n\n', False)
135 136
136 137 def test_gh_597(self):
137 138 """Pretty-printing lists of objects with non-ascii reprs may cause
138 139 problems."""
139 140 class Spam(object):
140 141 def __repr__(self):
141 142 return "\xe9"*50
142 143 import IPython.core.formatters
143 144 f = IPython.core.formatters.PlainTextFormatter()
144 145 f([Spam(),Spam()])
145 146
146 147
147 148 def test_future_flags(self):
148 149 """Check that future flags are used for parsing code (gh-777)"""
149 150 ip.run_cell('from __future__ import barry_as_FLUFL')
150 151 try:
151 152 ip.run_cell('prfunc_return_val = 1 <> 2')
152 153 assert 'prfunc_return_val' in ip.user_ns
153 154 finally:
154 155 # Reset compiler flags so we don't mess up other tests.
155 156 ip.compile.reset_compiler_flags()
156 157
157 158 def test_can_pickle(self):
158 159 "Can we pickle objects defined interactively (GH-29)"
159 160 ip = get_ipython()
160 161 ip.reset()
161 162 ip.run_cell(("class Mylist(list):\n"
162 163 " def __init__(self,x=[]):\n"
163 164 " list.__init__(self,x)"))
164 165 ip.run_cell("w=Mylist([1,2,3])")
165 166
166 167 from pickle import dumps
167 168
168 169 # We need to swap in our main module - this is only necessary
169 170 # inside the test framework, because IPython puts the interactive module
170 171 # in place (but the test framework undoes this).
171 172 _main = sys.modules['__main__']
172 173 sys.modules['__main__'] = ip.user_module
173 174 try:
174 175 res = dumps(ip.user_ns["w"])
175 176 finally:
176 177 sys.modules['__main__'] = _main
177 178 self.assertTrue(isinstance(res, bytes))
178 179
179 180 def test_global_ns(self):
180 181 "Code in functions must be able to access variables outside them."
181 182 ip = get_ipython()
182 183 ip.run_cell("a = 10")
183 184 ip.run_cell(("def f(x):\n"
184 185 " return x + a"))
185 186 ip.run_cell("b = f(12)")
186 187 self.assertEqual(ip.user_ns["b"], 22)
187 188
188 189 def test_bad_custom_tb(self):
189 190 """Check that InteractiveShell is protected from bad custom exception handlers"""
190 191 ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
191 192 self.assertEqual(ip.custom_exceptions, (IOError,))
192 193 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
193 194 ip.run_cell(u'raise IOError("foo")')
194 195 self.assertEqual(ip.custom_exceptions, ())
195 196
196 197 def test_bad_custom_tb_return(self):
197 198 """Check that InteractiveShell is protected from bad return types in custom exception handlers"""
198 199 ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
199 200 self.assertEqual(ip.custom_exceptions, (NameError,))
200 201 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
201 202 ip.run_cell(u'a=abracadabra')
202 203 self.assertEqual(ip.custom_exceptions, ())
203 204
204 205 def test_drop_by_id(self):
205 206 myvars = {"a":object(), "b":object(), "c": object()}
206 207 ip.push(myvars, interactive=False)
207 208 for name in myvars:
208 209 assert name in ip.user_ns, name
209 210 assert name in ip.user_ns_hidden, name
210 211 ip.user_ns['b'] = 12
211 212 ip.drop_by_id(myvars)
212 213 for name in ["a", "c"]:
213 214 assert name not in ip.user_ns, name
214 215 assert name not in ip.user_ns_hidden, name
215 216 assert ip.user_ns['b'] == 12
216 217 ip.reset()
217 218
218 219 def test_var_expand(self):
219 220 ip.user_ns['f'] = u'Ca\xf1o'
220 221 self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o')
221 222 self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o')
222 223 self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1')
223 224 self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2')
224 225
225 226 self.assertEqual(ip.var_expand(u"grep x | awk '{print $1}'"), u"grep x | awk '{print $1}'")
226 227
227 228 ip.user_ns['f'] = b'Ca\xc3\xb1o'
228 229 # This should not raise any exception:
229 230 ip.var_expand(u'echo $f')
230 231
231 232 def test_var_expand_local(self):
232 233 """Test local variable expansion in !system and %magic calls"""
233 234 # !system
234 235 ip.run_cell(
235 236 "def test():\n"
236 237 ' lvar = "ttt"\n'
237 238 " ret = !echo {lvar}\n"
238 239 " return ret[0]\n"
239 240 )
240 241 res = ip.user_ns["test"]()
241 242 self.assertIn("ttt", res)
242 243
243 244 # %magic
244 245 ip.run_cell(
245 246 "def makemacro():\n"
246 247 ' macroname = "macro_var_expand_locals"\n'
247 248 " %macro {macroname} codestr\n"
248 249 )
249 250 ip.user_ns["codestr"] = "str(12)"
250 251 ip.run_cell("makemacro()")
251 252 self.assertIn("macro_var_expand_locals", ip.user_ns)
252 253
253 254 def test_var_expand_self(self):
254 255 """Test variable expansion with the name 'self', which was failing.
255 256
256 257 See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218
257 258 """
258 259 ip.run_cell(
259 260 "class cTest:\n"
260 261 ' classvar="see me"\n'
261 262 " def test(self):\n"
262 263 " res = !echo Variable: {self.classvar}\n"
263 264 " return res[0]\n"
264 265 )
265 266 self.assertIn("see me", ip.user_ns["cTest"]().test())
266 267
267 268 def test_bad_var_expand(self):
268 269 """var_expand on invalid formats shouldn't raise"""
269 270 # SyntaxError
270 271 self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}")
271 272 # NameError
272 273 self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}")
273 274 # ZeroDivisionError
274 275 self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}")
275 276
276 277 def test_silent_postexec(self):
277 278 """run_cell(silent=True) doesn't invoke pre/post_run_cell callbacks"""
278 279 pre_explicit = mock.Mock()
279 280 pre_always = mock.Mock()
280 281 post_explicit = mock.Mock()
281 282 post_always = mock.Mock()
282 283 all_mocks = [pre_explicit, pre_always, post_explicit, post_always]
283 284
284 285 ip.events.register('pre_run_cell', pre_explicit)
285 286 ip.events.register('pre_execute', pre_always)
286 287 ip.events.register('post_run_cell', post_explicit)
287 288 ip.events.register('post_execute', post_always)
288 289
289 290 try:
290 291 ip.run_cell("1", silent=True)
291 292 assert pre_always.called
292 293 assert not pre_explicit.called
293 294 assert post_always.called
294 295 assert not post_explicit.called
295 296 # double-check that non-silent exec did what we expected
296 297 # silent to avoid
297 298 ip.run_cell("1")
298 299 assert pre_explicit.called
299 300 assert post_explicit.called
300 301 info, = pre_explicit.call_args[0]
301 302 result, = post_explicit.call_args[0]
302 303 self.assertEqual(info, result.info)
303 304 # check that post hooks are always called
304 305 [m.reset_mock() for m in all_mocks]
305 306 ip.run_cell("syntax error")
306 307 assert pre_always.called
307 308 assert pre_explicit.called
308 309 assert post_always.called
309 310 assert post_explicit.called
310 311 info, = pre_explicit.call_args[0]
311 312 result, = post_explicit.call_args[0]
312 313 self.assertEqual(info, result.info)
313 314 finally:
314 315 # remove post-exec
315 316 ip.events.unregister('pre_run_cell', pre_explicit)
316 317 ip.events.unregister('pre_execute', pre_always)
317 318 ip.events.unregister('post_run_cell', post_explicit)
318 319 ip.events.unregister('post_execute', post_always)
319 320
320 321 def test_silent_noadvance(self):
321 322 """run_cell(silent=True) doesn't advance execution_count"""
322 323 ec = ip.execution_count
323 324 # silent should force store_history=False
324 325 ip.run_cell("1", store_history=True, silent=True)
325 326
326 327 self.assertEqual(ec, ip.execution_count)
327 328 # double-check that non-silent exec did what we expected
328 329 # silent to avoid
329 330 ip.run_cell("1", store_history=True)
330 331 self.assertEqual(ec+1, ip.execution_count)
331 332
332 333 def test_silent_nodisplayhook(self):
333 334 """run_cell(silent=True) doesn't trigger displayhook"""
334 335 d = dict(called=False)
335 336
336 337 trap = ip.display_trap
337 338 save_hook = trap.hook
338 339
339 340 def failing_hook(*args, **kwargs):
340 341 d['called'] = True
341 342
342 343 try:
343 344 trap.hook = failing_hook
344 345 res = ip.run_cell("1", silent=True)
345 346 self.assertFalse(d['called'])
346 347 self.assertIsNone(res.result)
347 348 # double-check that non-silent exec did what we expected
348 349 # silent to avoid
349 350 ip.run_cell("1")
350 351 self.assertTrue(d['called'])
351 352 finally:
352 353 trap.hook = save_hook
353 354
354 355 def test_ofind_line_magic(self):
355 356 from IPython.core.magic import register_line_magic
356 357
357 358 @register_line_magic
358 359 def lmagic(line):
359 360 "A line magic"
360 361
361 362 # Get info on line magic
362 363 lfind = ip._ofind("lmagic")
363 info = dict(
364 info = OInfo(
364 365 found=True,
365 366 isalias=False,
366 367 ismagic=True,
367 368 namespace="IPython internal",
368 369 obj=lmagic,
369 370 parent=None,
370 371 )
371 372 self.assertEqual(lfind, info)
372 373
373 374 def test_ofind_cell_magic(self):
374 375 from IPython.core.magic import register_cell_magic
375 376
376 377 @register_cell_magic
377 378 def cmagic(line, cell):
378 379 "A cell magic"
379 380
380 381 # Get info on cell magic
381 382 find = ip._ofind("cmagic")
382 info = dict(
383 info = OInfo(
383 384 found=True,
384 385 isalias=False,
385 386 ismagic=True,
386 387 namespace="IPython internal",
387 388 obj=cmagic,
388 389 parent=None,
389 390 )
390 391 self.assertEqual(find, info)
391 392
392 393 def test_ofind_property_with_error(self):
393 394 class A(object):
394 395 @property
395 396 def foo(self):
396 397 raise NotImplementedError() # pragma: no cover
397 398
398 399 a = A()
399 400
400 found = ip._ofind('a.foo', [('locals', locals())])
401 info = dict(found=True, isalias=False, ismagic=False,
402 namespace='locals', obj=A.foo, parent=a)
401 found = ip._ofind("a.foo", [("locals", locals())])
402 info = OInfo(
403 found=True,
404 isalias=False,
405 ismagic=False,
406 namespace="locals",
407 obj=A.foo,
408 parent=a,
409 )
403 410 self.assertEqual(found, info)
404 411
405 412 def test_ofind_multiple_attribute_lookups(self):
406 413 class A(object):
407 414 @property
408 415 def foo(self):
409 416 raise NotImplementedError() # pragma: no cover
410 417
411 418 a = A()
412 419 a.a = A()
413 420 a.a.a = A()
414 421
415 found = ip._ofind('a.a.a.foo', [('locals', locals())])
416 info = dict(found=True, isalias=False, ismagic=False,
417 namespace='locals', obj=A.foo, parent=a.a.a)
422 found = ip._ofind("a.a.a.foo", [("locals", locals())])
423 info = OInfo(
424 found=True,
425 isalias=False,
426 ismagic=False,
427 namespace="locals",
428 obj=A.foo,
429 parent=a.a.a,
430 )
418 431 self.assertEqual(found, info)
419 432
420 433 def test_ofind_slotted_attributes(self):
421 434 class A(object):
422 435 __slots__ = ['foo']
423 436 def __init__(self):
424 437 self.foo = 'bar'
425 438
426 439 a = A()
427 found = ip._ofind('a.foo', [('locals', locals())])
428 info = dict(found=True, isalias=False, ismagic=False,
429 namespace='locals', obj=a.foo, parent=a)
440 found = ip._ofind("a.foo", [("locals", locals())])
441 info = OInfo(
442 found=True,
443 isalias=False,
444 ismagic=False,
445 namespace="locals",
446 obj=a.foo,
447 parent=a,
448 )
430 449 self.assertEqual(found, info)
431 450
432 found = ip._ofind('a.bar', [('locals', locals())])
433 info = dict(found=False, isalias=False, ismagic=False,
434 namespace=None, obj=None, parent=a)
451 found = ip._ofind("a.bar", [("locals", locals())])
452 info = OInfo(
453 found=False,
454 isalias=False,
455 ismagic=False,
456 namespace=None,
457 obj=None,
458 parent=a,
459 )
435 460 self.assertEqual(found, info)
436 461
437 462 def test_ofind_prefers_property_to_instance_level_attribute(self):
438 463 class A(object):
439 464 @property
440 465 def foo(self):
441 466 return 'bar'
442 467 a = A()
443 468 a.__dict__["foo"] = "baz"
444 469 self.assertEqual(a.foo, "bar")
445 470 found = ip._ofind("a.foo", [("locals", locals())])
446 self.assertIs(found["obj"], A.foo)
471 self.assertIs(found.obj, A.foo)
447 472
448 473 def test_custom_syntaxerror_exception(self):
449 474 called = []
450 475 def my_handler(shell, etype, value, tb, tb_offset=None):
451 476 called.append(etype)
452 477 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
453 478
454 479 ip.set_custom_exc((SyntaxError,), my_handler)
455 480 try:
456 481 ip.run_cell("1f")
457 482 # Check that this was called, and only once.
458 483 self.assertEqual(called, [SyntaxError])
459 484 finally:
460 485 # Reset the custom exception hook
461 486 ip.set_custom_exc((), None)
462 487
463 488 def test_custom_exception(self):
464 489 called = []
465 490 def my_handler(shell, etype, value, tb, tb_offset=None):
466 491 called.append(etype)
467 492 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
468 493
469 494 ip.set_custom_exc((ValueError,), my_handler)
470 495 try:
471 496 res = ip.run_cell("raise ValueError('test')")
472 497 # Check that this was called, and only once.
473 498 self.assertEqual(called, [ValueError])
474 499 # Check that the error is on the result object
475 500 self.assertIsInstance(res.error_in_exec, ValueError)
476 501 finally:
477 502 # Reset the custom exception hook
478 503 ip.set_custom_exc((), None)
479 504
480 505 @mock.patch("builtins.print")
481 506 def test_showtraceback_with_surrogates(self, mocked_print):
482 507 values = []
483 508
484 509 def mock_print_func(value, sep=" ", end="\n", file=sys.stdout, flush=False):
485 510 values.append(value)
486 511 if value == chr(0xD8FF):
487 512 raise UnicodeEncodeError("utf-8", chr(0xD8FF), 0, 1, "")
488 513
489 514 # mock builtins.print
490 515 mocked_print.side_effect = mock_print_func
491 516
492 517 # ip._showtraceback() is replaced in globalipapp.py.
493 518 # Call original method to test.
494 519 interactiveshell.InteractiveShell._showtraceback(ip, None, None, chr(0xD8FF))
495 520
496 521 self.assertEqual(mocked_print.call_count, 2)
497 522 self.assertEqual(values, [chr(0xD8FF), "\\ud8ff"])
498 523
499 524 def test_mktempfile(self):
500 525 filename = ip.mktempfile()
501 526 # Check that we can open the file again on Windows
502 527 with open(filename, "w", encoding="utf-8") as f:
503 528 f.write("abc")
504 529
505 530 filename = ip.mktempfile(data="blah")
506 531 with open(filename, "r", encoding="utf-8") as f:
507 532 self.assertEqual(f.read(), "blah")
508 533
509 534 def test_new_main_mod(self):
510 535 # Smoketest to check that this accepts a unicode module name
511 536 name = u'jiefmw'
512 537 mod = ip.new_main_mod(u'%s.py' % name, name)
513 538 self.assertEqual(mod.__name__, name)
514 539
515 540 def test_get_exception_only(self):
516 541 try:
517 542 raise KeyboardInterrupt
518 543 except KeyboardInterrupt:
519 544 msg = ip.get_exception_only()
520 545 self.assertEqual(msg, 'KeyboardInterrupt\n')
521 546
522 547 try:
523 548 raise DerivedInterrupt("foo")
524 549 except KeyboardInterrupt:
525 550 msg = ip.get_exception_only()
526 551 self.assertEqual(msg, 'IPython.core.tests.test_interactiveshell.DerivedInterrupt: foo\n')
527 552
528 553 def test_inspect_text(self):
529 554 ip.run_cell('a = 5')
530 555 text = ip.object_inspect_text('a')
531 556 self.assertIsInstance(text, str)
532 557
533 558 def test_last_execution_result(self):
534 559 """ Check that last execution result gets set correctly (GH-10702) """
535 560 result = ip.run_cell('a = 5; a')
536 561 self.assertTrue(ip.last_execution_succeeded)
537 562 self.assertEqual(ip.last_execution_result.result, 5)
538 563
539 564 result = ip.run_cell('a = x_invalid_id_x')
540 565 self.assertFalse(ip.last_execution_succeeded)
541 566 self.assertFalse(ip.last_execution_result.success)
542 567 self.assertIsInstance(ip.last_execution_result.error_in_exec, NameError)
543 568
544 569 def test_reset_aliasing(self):
545 570 """ Check that standard posix aliases work after %reset. """
546 571 if os.name != 'posix':
547 572 return
548 573
549 574 ip.reset()
550 575 for cmd in ('clear', 'more', 'less', 'man'):
551 576 res = ip.run_cell('%' + cmd)
552 577 self.assertEqual(res.success, True)
553 578
554 579
555 580 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
556 581
557 582 @onlyif_unicode_paths
558 583 def setUp(self):
559 584 self.BASETESTDIR = tempfile.mkdtemp()
560 585 self.TESTDIR = join(self.BASETESTDIR, u"åäö")
561 586 os.mkdir(self.TESTDIR)
562 587 with open(
563 588 join(self.TESTDIR, "åäötestscript.py"), "w", encoding="utf-8"
564 589 ) as sfile:
565 590 sfile.write("pass\n")
566 591 self.oldpath = os.getcwd()
567 592 os.chdir(self.TESTDIR)
568 593 self.fname = u"åäötestscript.py"
569 594
570 595 def tearDown(self):
571 596 os.chdir(self.oldpath)
572 597 shutil.rmtree(self.BASETESTDIR)
573 598
574 599 @onlyif_unicode_paths
575 600 def test_1(self):
576 601 """Test safe_execfile with non-ascii path
577 602 """
578 603 ip.safe_execfile(self.fname, {}, raise_exceptions=True)
579 604
580 605 class ExitCodeChecks(tt.TempFileMixin):
581 606
582 607 def setUp(self):
583 608 self.system = ip.system_raw
584 609
585 610 def test_exit_code_ok(self):
586 611 self.system('exit 0')
587 612 self.assertEqual(ip.user_ns['_exit_code'], 0)
588 613
589 614 def test_exit_code_error(self):
590 615 self.system('exit 1')
591 616 self.assertEqual(ip.user_ns['_exit_code'], 1)
592 617
593 618 @skipif(not hasattr(signal, 'SIGALRM'))
594 619 def test_exit_code_signal(self):
595 620 self.mktmp("import signal, time\n"
596 621 "signal.setitimer(signal.ITIMER_REAL, 0.1)\n"
597 622 "time.sleep(1)\n")
598 623 self.system("%s %s" % (sys.executable, self.fname))
599 624 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM)
600 625
601 626 @onlyif_cmds_exist("csh")
602 627 def test_exit_code_signal_csh(self): # pragma: no cover
603 628 SHELL = os.environ.get("SHELL", None)
604 629 os.environ["SHELL"] = find_cmd("csh")
605 630 try:
606 631 self.test_exit_code_signal()
607 632 finally:
608 633 if SHELL is not None:
609 634 os.environ['SHELL'] = SHELL
610 635 else:
611 636 del os.environ['SHELL']
612 637
613 638
614 639 class TestSystemRaw(ExitCodeChecks):
615 640
616 641 def setUp(self):
617 642 super().setUp()
618 643 self.system = ip.system_raw
619 644
620 645 @onlyif_unicode_paths
621 646 def test_1(self):
622 647 """Test system_raw with non-ascii cmd
623 648 """
624 649 cmd = u'''python -c "'åäö'" '''
625 650 ip.system_raw(cmd)
626 651
627 652 @mock.patch('subprocess.call', side_effect=KeyboardInterrupt)
628 653 @mock.patch('os.system', side_effect=KeyboardInterrupt)
629 654 def test_control_c(self, *mocks):
630 655 try:
631 656 self.system("sleep 1 # wont happen")
632 657 except KeyboardInterrupt: # pragma: no cove
633 658 self.fail(
634 659 "system call should intercept "
635 660 "keyboard interrupt from subprocess.call"
636 661 )
637 662 self.assertEqual(ip.user_ns["_exit_code"], -signal.SIGINT)
638 663
639 664
640 665 @pytest.mark.parametrize("magic_cmd", ["pip", "conda", "cd"])
641 666 def test_magic_warnings(magic_cmd):
642 667 if sys.platform == "win32":
643 668 to_mock = "os.system"
644 669 expected_arg, expected_kwargs = magic_cmd, dict()
645 670 else:
646 671 to_mock = "subprocess.call"
647 672 expected_arg, expected_kwargs = magic_cmd, dict(
648 673 shell=True, executable=os.environ.get("SHELL", None)
649 674 )
650 675
651 676 with mock.patch(to_mock, return_value=0) as mock_sub:
652 677 with pytest.warns(Warning, match=r"You executed the system command"):
653 678 ip.system_raw(magic_cmd)
654 679 mock_sub.assert_called_once_with(expected_arg, **expected_kwargs)
655 680
656 681
657 682 # TODO: Exit codes are currently ignored on Windows.
658 683 class TestSystemPipedExitCode(ExitCodeChecks):
659 684
660 685 def setUp(self):
661 686 super().setUp()
662 687 self.system = ip.system_piped
663 688
664 689 @skip_win32
665 690 def test_exit_code_ok(self):
666 691 ExitCodeChecks.test_exit_code_ok(self)
667 692
668 693 @skip_win32
669 694 def test_exit_code_error(self):
670 695 ExitCodeChecks.test_exit_code_error(self)
671 696
672 697 @skip_win32
673 698 def test_exit_code_signal(self):
674 699 ExitCodeChecks.test_exit_code_signal(self)
675 700
676 701 class TestModules(tt.TempFileMixin):
677 702 def test_extraneous_loads(self):
678 703 """Test we're not loading modules on startup that we shouldn't.
679 704 """
680 705 self.mktmp("import sys\n"
681 706 "print('numpy' in sys.modules)\n"
682 707 "print('ipyparallel' in sys.modules)\n"
683 708 "print('ipykernel' in sys.modules)\n"
684 709 )
685 710 out = "False\nFalse\nFalse\n"
686 711 tt.ipexec_validate(self.fname, out)
687 712
688 713 class Negator(ast.NodeTransformer):
689 714 """Negates all number literals in an AST."""
690 715
691 716 # for python 3.7 and earlier
692 717 def visit_Num(self, node):
693 718 node.n = -node.n
694 719 return node
695 720
696 721 # for python 3.8+
697 722 def visit_Constant(self, node):
698 723 if isinstance(node.value, int):
699 724 return self.visit_Num(node)
700 725 return node
701 726
702 727 class TestAstTransform(unittest.TestCase):
703 728 def setUp(self):
704 729 self.negator = Negator()
705 730 ip.ast_transformers.append(self.negator)
706 731
707 732 def tearDown(self):
708 733 ip.ast_transformers.remove(self.negator)
709 734
710 735 def test_non_int_const(self):
711 736 with tt.AssertPrints("hello"):
712 737 ip.run_cell('print("hello")')
713 738
714 739 def test_run_cell(self):
715 740 with tt.AssertPrints("-34"):
716 741 ip.run_cell("print(12 + 22)")
717 742
718 743 # A named reference to a number shouldn't be transformed.
719 744 ip.user_ns["n"] = 55
720 745 with tt.AssertNotPrints("-55"):
721 746 ip.run_cell("print(n)")
722 747
723 748 def test_timeit(self):
724 749 called = set()
725 750 def f(x):
726 751 called.add(x)
727 752 ip.push({'f':f})
728 753
729 754 with tt.AssertPrints("std. dev. of"):
730 755 ip.run_line_magic("timeit", "-n1 f(1)")
731 756 self.assertEqual(called, {-1})
732 757 called.clear()
733 758
734 759 with tt.AssertPrints("std. dev. of"):
735 760 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
736 761 self.assertEqual(called, {-2, -3})
737 762
738 763 def test_time(self):
739 764 called = []
740 765 def f(x):
741 766 called.append(x)
742 767 ip.push({'f':f})
743 768
744 769 # Test with an expression
745 770 with tt.AssertPrints("Wall time: "):
746 771 ip.run_line_magic("time", "f(5+9)")
747 772 self.assertEqual(called, [-14])
748 773 called[:] = []
749 774
750 775 # Test with a statement (different code path)
751 776 with tt.AssertPrints("Wall time: "):
752 777 ip.run_line_magic("time", "a = f(-3 + -2)")
753 778 self.assertEqual(called, [5])
754 779
755 780 def test_macro(self):
756 781 ip.push({'a':10})
757 782 # The AST transformation makes this do a+=-1
758 783 ip.define_macro("amacro", "a+=1\nprint(a)")
759 784
760 785 with tt.AssertPrints("9"):
761 786 ip.run_cell("amacro")
762 787 with tt.AssertPrints("8"):
763 788 ip.run_cell("amacro")
764 789
765 790 class TestMiscTransform(unittest.TestCase):
766 791
767 792
768 793 def test_transform_only_once(self):
769 794 cleanup = 0
770 795 line_t = 0
771 796 def count_cleanup(lines):
772 797 nonlocal cleanup
773 798 cleanup += 1
774 799 return lines
775 800
776 801 def count_line_t(lines):
777 802 nonlocal line_t
778 803 line_t += 1
779 804 return lines
780 805
781 806 ip.input_transformer_manager.cleanup_transforms.append(count_cleanup)
782 807 ip.input_transformer_manager.line_transforms.append(count_line_t)
783 808
784 809 ip.run_cell('1')
785 810
786 811 assert cleanup == 1
787 812 assert line_t == 1
788 813
789 814 class IntegerWrapper(ast.NodeTransformer):
790 815 """Wraps all integers in a call to Integer()"""
791 816
792 817 # for Python 3.7 and earlier
793 818
794 819 # for Python 3.7 and earlier
795 820 def visit_Num(self, node):
796 821 if isinstance(node.n, int):
797 822 return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
798 823 args=[node], keywords=[])
799 824 return node
800 825
801 826 # For Python 3.8+
802 827 def visit_Constant(self, node):
803 828 if isinstance(node.value, int):
804 829 return self.visit_Num(node)
805 830 return node
806 831
807 832
808 833 class TestAstTransform2(unittest.TestCase):
809 834 def setUp(self):
810 835 self.intwrapper = IntegerWrapper()
811 836 ip.ast_transformers.append(self.intwrapper)
812 837
813 838 self.calls = []
814 839 def Integer(*args):
815 840 self.calls.append(args)
816 841 return args
817 842 ip.push({"Integer": Integer})
818 843
819 844 def tearDown(self):
820 845 ip.ast_transformers.remove(self.intwrapper)
821 846 del ip.user_ns['Integer']
822 847
823 848 def test_run_cell(self):
824 849 ip.run_cell("n = 2")
825 850 self.assertEqual(self.calls, [(2,)])
826 851
827 852 # This shouldn't throw an error
828 853 ip.run_cell("o = 2.0")
829 854 self.assertEqual(ip.user_ns['o'], 2.0)
830 855
831 856 def test_run_cell_non_int(self):
832 857 ip.run_cell("n = 'a'")
833 858 assert self.calls == []
834 859
835 860 def test_timeit(self):
836 861 called = set()
837 862 def f(x):
838 863 called.add(x)
839 864 ip.push({'f':f})
840 865
841 866 with tt.AssertPrints("std. dev. of"):
842 867 ip.run_line_magic("timeit", "-n1 f(1)")
843 868 self.assertEqual(called, {(1,)})
844 869 called.clear()
845 870
846 871 with tt.AssertPrints("std. dev. of"):
847 872 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
848 873 self.assertEqual(called, {(2,), (3,)})
849 874
850 875 class ErrorTransformer(ast.NodeTransformer):
851 876 """Throws an error when it sees a number."""
852 877
853 878 def visit_Constant(self, node):
854 879 if isinstance(node.value, int):
855 880 raise ValueError("test")
856 881 return node
857 882
858 883
859 884 class TestAstTransformError(unittest.TestCase):
860 885 def test_unregistering(self):
861 886 err_transformer = ErrorTransformer()
862 887 ip.ast_transformers.append(err_transformer)
863 888
864 889 with self.assertWarnsRegex(UserWarning, "It will be unregistered"):
865 890 ip.run_cell("1 + 2")
866 891
867 892 # This should have been removed.
868 893 self.assertNotIn(err_transformer, ip.ast_transformers)
869 894
870 895
871 896 class StringRejector(ast.NodeTransformer):
872 897 """Throws an InputRejected when it sees a string literal.
873 898
874 899 Used to verify that NodeTransformers can signal that a piece of code should
875 900 not be executed by throwing an InputRejected.
876 901 """
877 902
878 903 # 3.8 only
879 904 def visit_Constant(self, node):
880 905 if isinstance(node.value, str):
881 906 raise InputRejected("test")
882 907 return node
883 908
884 909
885 910 class TestAstTransformInputRejection(unittest.TestCase):
886 911
887 912 def setUp(self):
888 913 self.transformer = StringRejector()
889 914 ip.ast_transformers.append(self.transformer)
890 915
891 916 def tearDown(self):
892 917 ip.ast_transformers.remove(self.transformer)
893 918
894 919 def test_input_rejection(self):
895 920 """Check that NodeTransformers can reject input."""
896 921
897 922 expect_exception_tb = tt.AssertPrints("InputRejected: test")
898 923 expect_no_cell_output = tt.AssertNotPrints("'unsafe'", suppress=False)
899 924
900 925 # Run the same check twice to verify that the transformer is not
901 926 # disabled after raising.
902 927 with expect_exception_tb, expect_no_cell_output:
903 928 ip.run_cell("'unsafe'")
904 929
905 930 with expect_exception_tb, expect_no_cell_output:
906 931 res = ip.run_cell("'unsafe'")
907 932
908 933 self.assertIsInstance(res.error_before_exec, InputRejected)
909 934
910 935 def test__IPYTHON__():
911 936 # This shouldn't raise a NameError, that's all
912 937 __IPYTHON__
913 938
914 939
915 940 class DummyRepr(object):
916 941 def __repr__(self):
917 942 return "DummyRepr"
918 943
919 944 def _repr_html_(self):
920 945 return "<b>dummy</b>"
921 946
922 947 def _repr_javascript_(self):
923 948 return "console.log('hi');", {'key': 'value'}
924 949
925 950
926 951 def test_user_variables():
927 952 # enable all formatters
928 953 ip.display_formatter.active_types = ip.display_formatter.format_types
929 954
930 955 ip.user_ns['dummy'] = d = DummyRepr()
931 956 keys = {'dummy', 'doesnotexist'}
932 957 r = ip.user_expressions({ key:key for key in keys})
933 958
934 959 assert keys == set(r.keys())
935 960 dummy = r["dummy"]
936 961 assert {"status", "data", "metadata"} == set(dummy.keys())
937 962 assert dummy["status"] == "ok"
938 963 data = dummy["data"]
939 964 metadata = dummy["metadata"]
940 965 assert data.get("text/html") == d._repr_html_()
941 966 js, jsmd = d._repr_javascript_()
942 967 assert data.get("application/javascript") == js
943 968 assert metadata.get("application/javascript") == jsmd
944 969
945 970 dne = r["doesnotexist"]
946 971 assert dne["status"] == "error"
947 972 assert dne["ename"] == "NameError"
948 973
949 974 # back to text only
950 975 ip.display_formatter.active_types = ['text/plain']
951 976
952 977 def test_user_expression():
953 978 # enable all formatters
954 979 ip.display_formatter.active_types = ip.display_formatter.format_types
955 980 query = {
956 981 'a' : '1 + 2',
957 982 'b' : '1/0',
958 983 }
959 984 r = ip.user_expressions(query)
960 985 import pprint
961 986 pprint.pprint(r)
962 987 assert set(r.keys()) == set(query.keys())
963 988 a = r["a"]
964 989 assert {"status", "data", "metadata"} == set(a.keys())
965 990 assert a["status"] == "ok"
966 991 data = a["data"]
967 992 metadata = a["metadata"]
968 993 assert data.get("text/plain") == "3"
969 994
970 995 b = r["b"]
971 996 assert b["status"] == "error"
972 997 assert b["ename"] == "ZeroDivisionError"
973 998
974 999 # back to text only
975 1000 ip.display_formatter.active_types = ['text/plain']
976 1001
977 1002
978 1003 class TestSyntaxErrorTransformer(unittest.TestCase):
979 1004 """Check that SyntaxError raised by an input transformer is handled by run_cell()"""
980 1005
981 1006 @staticmethod
982 1007 def transformer(lines):
983 1008 for line in lines:
984 1009 pos = line.find('syntaxerror')
985 1010 if pos >= 0:
986 1011 e = SyntaxError('input contains "syntaxerror"')
987 1012 e.text = line
988 1013 e.offset = pos + 1
989 1014 raise e
990 1015 return lines
991 1016
992 1017 def setUp(self):
993 1018 ip.input_transformers_post.append(self.transformer)
994 1019
995 1020 def tearDown(self):
996 1021 ip.input_transformers_post.remove(self.transformer)
997 1022
998 1023 def test_syntaxerror_input_transformer(self):
999 1024 with tt.AssertPrints('1234'):
1000 1025 ip.run_cell('1234')
1001 1026 with tt.AssertPrints('SyntaxError: invalid syntax'):
1002 1027 ip.run_cell('1 2 3') # plain python syntax error
1003 1028 with tt.AssertPrints('SyntaxError: input contains "syntaxerror"'):
1004 1029 ip.run_cell('2345 # syntaxerror') # input transformer syntax error
1005 1030 with tt.AssertPrints('3456'):
1006 1031 ip.run_cell('3456')
1007 1032
1008 1033
1009 1034 class TestWarningSuppression(unittest.TestCase):
1010 1035 def test_warning_suppression(self):
1011 1036 ip.run_cell("import warnings")
1012 1037 try:
1013 1038 with self.assertWarnsRegex(UserWarning, "asdf"):
1014 1039 ip.run_cell("warnings.warn('asdf')")
1015 1040 # Here's the real test -- if we run that again, we should get the
1016 1041 # warning again. Traditionally, each warning was only issued once per
1017 1042 # IPython session (approximately), even if the user typed in new and
1018 1043 # different code that should have also triggered the warning, leading
1019 1044 # to much confusion.
1020 1045 with self.assertWarnsRegex(UserWarning, "asdf"):
1021 1046 ip.run_cell("warnings.warn('asdf')")
1022 1047 finally:
1023 1048 ip.run_cell("del warnings")
1024 1049
1025 1050
1026 1051 def test_deprecation_warning(self):
1027 1052 ip.run_cell("""
1028 1053 import warnings
1029 1054 def wrn():
1030 1055 warnings.warn(
1031 1056 "I AM A WARNING",
1032 1057 DeprecationWarning
1033 1058 )
1034 1059 """)
1035 1060 try:
1036 1061 with self.assertWarnsRegex(DeprecationWarning, "I AM A WARNING"):
1037 1062 ip.run_cell("wrn()")
1038 1063 finally:
1039 1064 ip.run_cell("del warnings")
1040 1065 ip.run_cell("del wrn")
1041 1066
1042 1067
1043 1068 class TestImportNoDeprecate(tt.TempFileMixin):
1044 1069
1045 1070 def setUp(self):
1046 1071 """Make a valid python temp file."""
1047 1072 self.mktmp("""
1048 1073 import warnings
1049 1074 def wrn():
1050 1075 warnings.warn(
1051 1076 "I AM A WARNING",
1052 1077 DeprecationWarning
1053 1078 )
1054 1079 """)
1055 1080 super().setUp()
1056 1081
1057 1082 def test_no_dep(self):
1058 1083 """
1059 1084 No deprecation warning should be raised from imported functions
1060 1085 """
1061 1086 ip.run_cell("from {} import wrn".format(self.fname))
1062 1087
1063 1088 with tt.AssertNotPrints("I AM A WARNING"):
1064 1089 ip.run_cell("wrn()")
1065 1090 ip.run_cell("del wrn")
1066 1091
1067 1092
1068 1093 def test_custom_exc_count():
1069 1094 hook = mock.Mock(return_value=None)
1070 1095 ip.set_custom_exc((SyntaxError,), hook)
1071 1096 before = ip.execution_count
1072 1097 ip.run_cell("def foo()", store_history=True)
1073 1098 # restore default excepthook
1074 1099 ip.set_custom_exc((), None)
1075 1100 assert hook.call_count == 1
1076 1101 assert ip.execution_count == before + 1
1077 1102
1078 1103
1079 1104 def test_run_cell_async():
1080 1105 ip.run_cell("import asyncio")
1081 1106 coro = ip.run_cell_async("await asyncio.sleep(0.01)\n5")
1082 1107 assert asyncio.iscoroutine(coro)
1083 1108 loop = asyncio.new_event_loop()
1084 1109 result = loop.run_until_complete(coro)
1085 1110 assert isinstance(result, interactiveshell.ExecutionResult)
1086 1111 assert result.result == 5
1087 1112
1088 1113
1089 1114 def test_run_cell_await():
1090 1115 ip.run_cell("import asyncio")
1091 1116 result = ip.run_cell("await asyncio.sleep(0.01); 10")
1092 1117 assert ip.user_ns["_"] == 10
1093 1118
1094 1119
1095 1120 def test_run_cell_asyncio_run():
1096 1121 ip.run_cell("import asyncio")
1097 1122 result = ip.run_cell("await asyncio.sleep(0.01); 1")
1098 1123 assert ip.user_ns["_"] == 1
1099 1124 result = ip.run_cell("asyncio.run(asyncio.sleep(0.01)); 2")
1100 1125 assert ip.user_ns["_"] == 2
1101 1126 result = ip.run_cell("await asyncio.sleep(0.01); 3")
1102 1127 assert ip.user_ns["_"] == 3
1103 1128
1104 1129
1105 1130 def test_should_run_async():
1106 1131 assert not ip.should_run_async("a = 5", transformed_cell="a = 5")
1107 1132 assert ip.should_run_async("await x", transformed_cell="await x")
1108 1133 assert ip.should_run_async(
1109 1134 "import asyncio; await asyncio.sleep(1)",
1110 1135 transformed_cell="import asyncio; await asyncio.sleep(1)",
1111 1136 )
1112 1137
1113 1138
1114 1139 def test_set_custom_completer():
1115 1140 num_completers = len(ip.Completer.matchers)
1116 1141
1117 1142 def foo(*args, **kwargs):
1118 1143 return "I'm a completer!"
1119 1144
1120 1145 ip.set_custom_completer(foo, 0)
1121 1146
1122 1147 # check that we've really added a new completer
1123 1148 assert len(ip.Completer.matchers) == num_completers + 1
1124 1149
1125 1150 # check that the first completer is the function we defined
1126 1151 assert ip.Completer.matchers[0]() == "I'm a completer!"
1127 1152
1128 1153 # clean up
1129 1154 ip.Completer.custom_matchers.pop()
1130 1155
1131 1156
1132 1157 class TestShowTracebackAttack(unittest.TestCase):
1133 1158 """Test that the interactive shell is resilient against the client attack of
1134 1159 manipulating the showtracebacks method. These attacks shouldn't result in an
1135 1160 unhandled exception in the kernel."""
1136 1161
1137 1162 def setUp(self):
1138 1163 self.orig_showtraceback = interactiveshell.InteractiveShell.showtraceback
1139 1164
1140 1165 def tearDown(self):
1141 1166 interactiveshell.InteractiveShell.showtraceback = self.orig_showtraceback
1142 1167
1143 1168 def test_set_show_tracebacks_none(self):
1144 1169 """Test the case of the client setting showtracebacks to None"""
1145 1170
1146 1171 result = ip.run_cell(
1147 1172 """
1148 1173 import IPython.core.interactiveshell
1149 1174 IPython.core.interactiveshell.InteractiveShell.showtraceback = None
1150 1175
1151 1176 assert False, "This should not raise an exception"
1152 1177 """
1153 1178 )
1154 1179 print(result)
1155 1180
1156 1181 assert result.result is None
1157 1182 assert isinstance(result.error_in_exec, TypeError)
1158 1183 assert str(result.error_in_exec) == "'NoneType' object is not callable"
1159 1184
1160 1185 def test_set_show_tracebacks_noop(self):
1161 1186 """Test the case of the client setting showtracebacks to a no op lambda"""
1162 1187
1163 1188 result = ip.run_cell(
1164 1189 """
1165 1190 import IPython.core.interactiveshell
1166 1191 IPython.core.interactiveshell.InteractiveShell.showtraceback = lambda *args, **kwargs: None
1167 1192
1168 1193 assert False, "This should not raise an exception"
1169 1194 """
1170 1195 )
1171 1196 print(result)
1172 1197
1173 1198 assert result.result is None
1174 1199 assert isinstance(result.error_in_exec, AssertionError)
1175 1200 assert str(result.error_in_exec) == "This should not raise an exception"
General Comments 0
You need to be logged in to leave comments. Login now