1"""Implementation of basic magic functions."""
2from __future__ import annotations
3
4
5from logging import error
6import io
7import os
8import platform
9from pprint import pformat
10import sys
11from warnings import warn
12
13from traitlets.utils.importstring import import_item
14from IPython.core import magic_arguments, page
15from IPython.core.error import UsageError
16from IPython.core.magic import Magics, magics_class, line_magic, magic_escapes
17from IPython.utils.text import format_screen, dedent, indent
18from IPython.testing.skipdoctest import skip_doctest
19from IPython.utils.ipstruct import Struct
20
21
22class MagicsDisplay:
23 def __init__(self, magics_manager, ignore=None):
24 self.ignore = ignore if ignore else []
25 self.magics_manager = magics_manager
26
27 def _lsmagic(self):
28 """The main implementation of the %lsmagic"""
29 mesc = magic_escapes['line']
30 cesc = magic_escapes['cell']
31 mman = self.magics_manager
32 magics = mman.lsmagic()
33 out = ['Available line magics:',
34 mesc + (' '+mesc).join(sorted([m for m,v in magics['line'].items() if (v not in self.ignore)])),
35 '',
36 'Available cell magics:',
37 cesc + (' '+cesc).join(sorted([m for m,v in magics['cell'].items() if (v not in self.ignore)])),
38 '',
39 mman.auto_status()]
40 return '\n'.join(out)
41
42 def _repr_pretty_(self, p, cycle):
43 p.text(self._lsmagic())
44
45 def __repr__(self):
46 return self.__str__()
47
48 def __str__(self):
49 return self._lsmagic()
50
51 def _jsonable(self):
52 """turn magics dict into jsonable dict of the same structure
53
54 replaces object instances with their class names as strings
55 """
56 magic_dict = {}
57 mman = self.magics_manager
58 magics = mman.lsmagic()
59 for key, subdict in magics.items():
60 d = {}
61 magic_dict[key] = d
62 for name, obj in subdict.items():
63 try:
64 classname = obj.__self__.__class__.__name__
65 except AttributeError:
66 classname = 'Other'
67
68 d[name] = classname
69 return magic_dict
70
71 def _repr_json_(self):
72 return self._jsonable()
73
74
75@magics_class
76class BasicMagics(Magics):
77 """Magics that provide central IPython functionality.
78
79 These are various magics that don't fit into specific categories but that
80 are all part of the base 'IPython experience'."""
81
82 @skip_doctest
83 @magic_arguments.magic_arguments()
84 @magic_arguments.argument(
85 '-l', '--line', action='store_true',
86 help="""Create a line magic alias."""
87 )
88 @magic_arguments.argument(
89 '-c', '--cell', action='store_true',
90 help="""Create a cell magic alias."""
91 )
92 @magic_arguments.argument(
93 'name',
94 help="""Name of the magic to be created."""
95 )
96 @magic_arguments.argument(
97 'target',
98 help="""Name of the existing line or cell magic."""
99 )
100 @magic_arguments.argument(
101 '-p', '--params', default=None,
102 help="""Parameters passed to the magic function."""
103 )
104 @line_magic
105 def alias_magic(self, line=''):
106 """Create an alias for an existing line or cell magic.
107
108 Examples
109 --------
110 ::
111
112 In [1]: %alias_magic t timeit
113 Created `%t` as an alias for `%timeit`.
114 Created `%%t` as an alias for `%%timeit`.
115
116 In [2]: %t -n1 pass
117 107 ns ± 43.6 ns per loop (mean ± std. dev. of 7 runs, 1 loop each)
118
119 In [3]: %%t -n1
120 ...: pass
121 ...:
122 107 ns ± 58.3 ns per loop (mean ± std. dev. of 7 runs, 1 loop each)
123
124 In [4]: %alias_magic --cell whereami pwd
125 UsageError: Cell magic function `%%pwd` not found.
126 In [5]: %alias_magic --line whereami pwd
127 Created `%whereami` as an alias for `%pwd`.
128
129 In [6]: %whereami
130 Out[6]: '/home/testuser'
131
132 In [7]: %alias_magic h history -p "-l 30" --line
133 Created `%h` as an alias for `%history -l 30`.
134 """
135
136 args = magic_arguments.parse_argstring(self.alias_magic, line)
137 shell = self.shell
138 mman = self.shell.magics_manager
139 escs = ''.join(magic_escapes.values())
140
141 target = args.target.lstrip(escs)
142 name = args.name.lstrip(escs)
143
144 params = args.params
145 if (params and
146 ((params.startswith('"') and params.endswith('"'))
147 or (params.startswith("'") and params.endswith("'")))):
148 params = params[1:-1]
149
150 # Find the requested magics.
151 m_line = shell.find_magic(target, 'line')
152 m_cell = shell.find_magic(target, 'cell')
153 if args.line and m_line is None:
154 raise UsageError('Line magic function `%s%s` not found.' %
155 (magic_escapes['line'], target))
156 if args.cell and m_cell is None:
157 raise UsageError('Cell magic function `%s%s` not found.' %
158 (magic_escapes['cell'], target))
159
160 # If --line and --cell are not specified, default to the ones
161 # that are available.
162 if not args.line and not args.cell:
163 if not m_line and not m_cell:
164 raise UsageError(
165 'No line or cell magic with name `%s` found.' % target
166 )
167 args.line = bool(m_line)
168 args.cell = bool(m_cell)
169
170 params_str = "" if params is None else " " + params
171
172 if args.line:
173 mman.register_alias(name, target, 'line', params)
174 print('Created `{}{}` as an alias for `{}{}{}`.'.format(
175 magic_escapes['line'], name,
176 magic_escapes['line'], target, params_str))
177
178 if args.cell:
179 mman.register_alias(name, target, 'cell', params)
180 print('Created `{}{}` as an alias for `{}{}{}`.'.format(
181 magic_escapes['cell'], name,
182 magic_escapes['cell'], target, params_str))
183
184 @magic_arguments.magic_arguments()
185 @magic_arguments.argument(
186 "-j", "--json", action="store_true", help="Return the magic list as JSON."
187 )
188 @line_magic
189 def lsmagic(self, parameter_s=''):
190 """List currently available magic functions.
191
192 Use ``--json`` to return a JSON-compatible dictionary instead of text.
193 """
194 args = magic_arguments.parse_argstring(self.lsmagic, parameter_s)
195 display = MagicsDisplay(self.shell.magics_manager, ignore=[])
196 if args.json:
197 return display._jsonable()
198 return str(display)
199
200 def _magic_docs(self, brief=False, rest=False):
201 """Return docstrings from magic functions."""
202 mman = self.shell.magics_manager
203 docs = mman.lsmagic_docs(brief, missing='No documentation')
204
205 if rest:
206 format_string = '**%s%s**::\n\n%s\n\n'
207 else:
208 format_string = '%s%s:\n%s\n'
209
210 return ''.join(
211 [format_string % (magic_escapes['line'], fname,
212 indent(dedent(fndoc)))
213 for fname, fndoc in sorted(docs['line'].items())]
214 +
215 [format_string % (magic_escapes['cell'], fname,
216 indent(dedent(fndoc)))
217 for fname, fndoc in sorted(docs['cell'].items())]
218 )
219
220 @line_magic
221 def magic(self, parameter_s=''):
222 """Print information about the magic function system.
223
224 Supported formats: -latex, -brief, -rest
225 """
226
227 mode = ''
228 try:
229 mode = parameter_s.split()[0][1:]
230 except IndexError:
231 pass
232
233 brief = (mode == 'brief')
234 rest = (mode == 'rest')
235 magic_docs = self._magic_docs(brief, rest)
236
237 if mode == 'latex':
238 print(self.format_latex(magic_docs))
239 return
240 else:
241 magic_docs = format_screen(magic_docs)
242
243 out = ["""
244IPython's 'magic' functions
245===========================
246
247The magic function system provides a series of functions which allow you to
248control the behavior of IPython itself, plus a lot of system-type
249features. There are two kinds of magics, line-oriented and cell-oriented.
250
251Line magics are prefixed with the % character and work much like OS
252command-line calls: they get as an argument the rest of the line, where
253arguments are passed without parentheses or quotes. For example, this will
254time the given statement::
255
256 %timeit range(1000)
257
258Cell magics are prefixed with a double %%, and they are functions that get as
259an argument not only the rest of the line, but also the lines below it in a
260separate argument. These magics are called with two arguments: the rest of the
261call line and the body of the cell, consisting of the lines below the first.
262For example::
263
264 %%timeit x = numpy.random.randn((100, 100))
265 numpy.linalg.svd(x)
266
267will time the execution of the numpy svd routine, running the assignment of x
268as part of the setup phase, which is not timed.
269
270In a line-oriented client (the terminal or Qt console IPython), starting a new
271input with %% will automatically enter cell mode, and IPython will continue
272reading input until a blank line is given. In the notebook, simply type the
273whole cell as one entity, but keep in mind that the %% escape can only be at
274the very start of the cell.
275
276NOTE: If you have 'automagic' enabled (via the command line option or with the
277%automagic function), you don't need to type in the % explicitly for line
278magics; cell magics always require an explicit '%%' escape. By default,
279IPython ships with automagic on, so you should only rarely need the % escape.
280
281Example: typing '%cd mydir' (without the quotes) changes your working directory
282to 'mydir', if it exists.
283
284For a list of the available magic functions, use %lsmagic. For a description
285of any of them, type %magic_name?, e.g. '%cd?'.
286
287Currently the magic system has the following functions:""",
288 magic_docs,
289 "Summary of magic functions (from %slsmagic):" % magic_escapes['line'],
290 str(self.lsmagic()),
291 ]
292 page.page('\n'.join(out))
293
294
295 @line_magic
296 def page(self, parameter_s=''):
297 """Pretty print the object and display it through a pager.
298
299 %page [options] OBJECT
300
301 If no object is given, use _ (last output).
302
303 Options:
304
305 -r: page str(object), don't pretty-print it."""
306
307 # After a function contributed by Olivier Aubert, slightly modified.
308
309 # Process options/args
310 opts, args = self.parse_options(parameter_s, 'r')
311 raw = 'r' in opts
312
313 oname = args and args or '_'
314 info = self.shell._ofind(oname)
315 if info.found:
316 if raw:
317 txt = str(info.obj)
318 else:
319 txt = pformat(info.obj)
320 page.page(txt)
321 else:
322 print('Object `%s` not found' % oname)
323
324 @line_magic
325 def pprint(self, parameter_s=''):
326 """Toggle pretty printing on/off."""
327 ptformatter = self.shell.display_formatter.formatters['text/plain']
328 ptformatter.pprint = bool(1 - ptformatter.pprint)
329 print('Pretty printing has been turned',
330 ['OFF','ON'][ptformatter.pprint])
331
332 @line_magic
333 def colors(self, parameter_s=''):
334 """Switch color scheme/theme globally for IPython
335
336 Examples
337 --------
338 To get a plain black and white terminal::
339
340 %colors nocolor
341 """
342
343
344 new_theme = parameter_s.strip()
345 if not new_theme:
346 from IPython.utils.PyColorize import theme_table
347
348 raise UsageError(
349 "%colors: you must specify a color theme. See '%colors?'."
350 f" Available themes: {list(theme_table.keys())}"
351 )
352
353 self.shell.colors = new_theme
354
355 @line_magic
356 def xmode(self, parameter_s=''):
357 """Switch modes for the exception handlers.
358
359 Valid modes: Plain, Context, Verbose, Minimal, Docs, and Doctest.
360
361 - ``Plain``: similar to Python's default traceback.
362 - ``Context``: shows several lines of surrounding context for each
363 frame in the traceback.
364 - ``Verbose``: like Context, but also displays local variable values
365 in each frame.
366 - ``Minimal``: shows only the exception type and message, without
367 a traceback.
368 - ``Docs``: a stripped-down version of Verbose, designed for use
369 when running doctests.
370 - ``Doctest``: shows only the traceback header, an ellipsis, and the
371 exception line, for easy copy-paste into Python doctests.
372
373 If called without arguments, cycles through the available modes.
374
375 When in verbose mode the value ``--show`` (and ``--hide``)
376 will respectively show (or hide) frames with ``__tracebackhide__ =
377 True`` value set.
378 """
379
380 def xmode_switch_err(name):
381 warn('Error changing %s exception modes.\n%s' %
382 (name,sys.exc_info()[1]))
383
384 shell = self.shell
385 if parameter_s.strip() == "--show":
386 shell.InteractiveTB.skip_hidden = False
387 return
388 if parameter_s.strip() == "--hide":
389 shell.InteractiveTB.skip_hidden = True
390 return
391
392 new_mode = parameter_s.strip().capitalize()
393 try:
394 shell.InteractiveTB.set_mode(mode=new_mode)
395 print('Exception reporting mode:',shell.InteractiveTB.mode)
396 except Exception:
397 xmode_switch_err('user')
398
399 @line_magic
400 def quickref(self, arg):
401 """ Show a quick reference sheet """
402 from IPython.core.usage import quick_reference
403 qr = quick_reference + self._magic_docs(brief=True)
404 page.page(qr)
405
406 @line_magic
407 def doctest_mode(self, parameter_s=''):
408 """Toggle doctest mode on and off.
409
410 This mode is intended to make IPython behave as much as possible like a
411 plain Python shell, from the perspective of how its prompts, exceptions
412 and output look. This makes it easy to copy and paste parts of a
413 session into doctests. It does so by:
414
415 - Changing the prompts to the classic ``>>>`` ones.
416 - Changing the exception reporting mode to 'Plain'.
417 - Disabling pretty-printing of output.
418
419 Note that IPython also supports the pasting of code snippets that have
420 leading '>>>' and '...' prompts in them. This means that you can paste
421 doctests from files or docstrings (even if they have leading
422 whitespace), and the code will execute correctly. You can then use
423 '%history -t' to see the translated history; this will give you the
424 input after removal of all the leading prompts and whitespace, which
425 can be pasted back into an editor.
426
427 With these features, you can switch into this mode easily whenever you
428 need to do testing and changes to doctests, without having to leave
429 your existing IPython session.
430 """
431
432 # Shorthands
433 shell = self.shell
434 meta = shell.meta
435 disp_formatter = self.shell.display_formatter
436 ptformatter = disp_formatter.formatters['text/plain']
437 # dstore is a data store kept in the instance metadata bag to track any
438 # changes we make, so we can undo them later.
439 dstore = meta.setdefault('doctest_mode',Struct())
440 save_dstore = dstore.setdefault
441
442 # save a few values we'll need to recover later
443 mode = save_dstore('mode',False)
444 save_dstore('rc_pprint',ptformatter.pprint)
445 save_dstore('xmode',shell.InteractiveTB.mode)
446 save_dstore('rc_separate_out',shell.separate_out)
447 save_dstore('rc_separate_out2',shell.separate_out2)
448 save_dstore('rc_separate_in',shell.separate_in)
449 save_dstore('rc_active_types',disp_formatter.active_types)
450
451 if not mode:
452 # turn on
453
454 # Prompt separators like plain python
455 shell.separate_in = ''
456 shell.separate_out = ''
457 shell.separate_out2 = ''
458
459
460 ptformatter.pprint = False
461 disp_formatter.active_types = ['text/plain']
462
463 shell.run_line_magic("xmode", "Plain")
464 else:
465 # turn off
466 shell.separate_in = dstore.rc_separate_in
467
468 shell.separate_out = dstore.rc_separate_out
469 shell.separate_out2 = dstore.rc_separate_out2
470
471 ptformatter.pprint = dstore.rc_pprint
472 disp_formatter.active_types = dstore.rc_active_types
473
474 shell.run_line_magic("xmode", dstore.xmode)
475
476 # mode here is the state before we switch; switch_doctest_mode takes
477 # the mode we're switching to.
478 shell.switch_doctest_mode(not mode)
479
480 # Store new mode and inform
481 dstore.mode = bool(not mode)
482 mode_label = ['OFF','ON'][dstore.mode]
483 print('Doctest mode is:', mode_label)
484
485 @line_magic
486 def gui(self, parameter_s=''):
487 """Enable or disable IPython GUI event loop integration.
488
489 %gui [GUINAME]
490
491 This magic replaces IPython's threaded shells that were activated
492 using the (pylab/wthread/etc.) command line flags. GUI toolkits
493 can now be enabled at runtime and keyboard
494 interrupts should work without any problems. The following toolkits
495 are supported: wxPython, PyQt4, PyGTK, Tk and Cocoa (OSX)::
496
497 %gui wx # enable wxPython event loop integration
498 %gui qt # enable PyQt/PySide event loop integration
499 # with the latest version available.
500 %gui qt6 # enable PyQt6/PySide6 event loop integration
501 %gui qt5 # enable PyQt5/PySide2 event loop integration
502 %gui gtk # enable PyGTK event loop integration
503 %gui gtk3 # enable Gtk3 event loop integration
504 %gui gtk4 # enable Gtk4 event loop integration
505 %gui tk # enable Tk event loop integration
506 %gui osx # enable Cocoa event loop integration
507 # (requires %matplotlib 1.1)
508 %gui # disable all event loop integration
509
510 WARNING: after any of these has been called you can simply create
511 an application object, but DO NOT start the event loop yourself, as
512 we have already handled that.
513 """
514 opts, arg = self.parse_options(parameter_s, '')
515 if arg=='': arg = None
516 try:
517 return self.shell.enable_gui(arg)
518 except Exception as e:
519 # print simple error message, rather than traceback if we can't
520 # hook up the GUI
521 error(str(e))
522
523 @skip_doctest
524 @line_magic
525 def precision(self, s=''):
526 """Set floating point precision for pretty printing.
527
528 Can set either integer precision or a format string.
529
530 If numpy has been imported and precision is an int,
531 numpy display precision will also be set, via ``numpy.set_printoptions``.
532
533 If no argument is given, defaults will be restored.
534
535 Examples
536 --------
537 ::
538
539 In [1]: from math import pi
540
541 In [2]: %precision 3
542 Out[2]: '%.3f'
543
544 In [3]: pi
545 Out[3]: 3.142
546
547 In [4]: %precision %i
548 Out[4]: '%i'
549
550 In [5]: pi
551 Out[5]: 3
552
553 In [6]: %precision %e
554 Out[6]: '%e'
555
556 In [7]: pi**10
557 Out[7]: 9.364805e+04
558
559 In [8]: %precision
560 Out[8]: '%r'
561
562 In [9]: pi**10
563 Out[9]: 93648.047476082982
564 """
565 ptformatter = self.shell.display_formatter.formatters['text/plain']
566 ptformatter.float_precision = s
567 return ptformatter.float_format
568
569 @magic_arguments.magic_arguments()
570 @magic_arguments.argument(
571 'filename', type=str,
572 help='Notebook name or filename'
573 )
574 @line_magic
575 def notebook(self, s):
576 """Export and convert IPython notebooks.
577
578 This function can export the current IPython history to a notebook file.
579 For example, to export the history to "foo.ipynb" do "%notebook foo.ipynb".
580 """
581 args = magic_arguments.parse_argstring(self.notebook, s)
582 outfname = os.path.expanduser(args.filename)
583
584 from nbformat import write, v4
585 from nbformat.sign import NotebookNotary
586
587 cells = []
588 hist = list(self.shell.history_manager.get_range())
589 outputs = self.shell.history_manager.outputs
590 exceptions = self.shell.history_manager.exceptions
591
592 if(len(hist)<=1):
593 raise ValueError('History is empty, cannot export')
594
595 for session, execution_count, source in hist[:-1]:
596 cell = v4.new_code_cell(execution_count=execution_count, source=source)
597
598 for output in outputs[execution_count]:
599 if output.output_type in {"out_stream", "err_stream"}:
600 text_data = []
601 for mime_type, data in output.bundle.items():
602 if isinstance(data, list):
603 text_data.extend(data)
604 else:
605 text_data.append(data)
606 full_text = "".join(text_data)
607 # Replace literal \n with actual newlines
608 full_text = full_text.replace("\\n", "\n")
609 normalized_text = []
610 lines = full_text.split("\n")
611 for i, line in enumerate(lines):
612 if i < len(lines) - 1:
613 normalized_text.append(line + "\n")
614 elif line: # Last line only if it's not empty
615 normalized_text.append(line + "\n")
616 stream_output = v4.new_output("stream", text=normalized_text)
617 if output.output_type == "err_stream":
618 stream_output.name = "stderr"
619 cell.outputs.append(stream_output)
620
621 elif output.output_type == "execute_result":
622 data_dict = {}
623 for mime_type, data in output.bundle.items():
624 data_dict[mime_type] = data
625 cell.outputs.append(
626 v4.new_output(
627 "execute_result",
628 data=data_dict,
629 execution_count=execution_count,
630 )
631 )
632
633 elif output.output_type == "display_data":
634 # Collect all MIME types for this display_data into a single output
635 data_dict = {}
636 for mime_type, data in output.bundle.items():
637 data_dict[mime_type] = data
638 cell.outputs.append(
639 v4.new_output(
640 "display_data",
641 data=data_dict,
642 )
643 )
644 else:
645 raise ValueError(f"Unknown output type: {output.output_type}")
646
647 # Check if this execution_count is in exceptions (current session)
648 if execution_count in exceptions:
649 cell.outputs.append(
650 v4.new_output("error", **exceptions[execution_count])
651 )
652 cells.append(cell)
653
654 kernel_language_info = self._get_kernel_language_info()
655
656 nb = v4.new_notebook(
657 cells=cells,
658 metadata={
659 "kernelspec": {
660 "display_name": "Python 3 (ipykernel)",
661 "language": "python",
662 "name": "python3",
663 },
664 "language_info": kernel_language_info
665 or {
666 "codemirror_mode": {
667 "name": "ipython",
668 "version": sys.version_info[0],
669 },
670 "file_extension": ".py",
671 "mimetype": "text/x-python",
672 "name": "python",
673 "nbconvert_exporter": "python",
674 "pygments_lexer": "ipython3",
675 "version": platform.python_version(),
676 },
677 },
678 )
679 # Sign the notebook to make it trusted
680 notary = NotebookNotary()
681 notary.update_config(self.shell.config)
682 try:
683 notary.sign(nb)
684 finally:
685 # Close the signature store's SQLite connection. NotebookNotary
686 # opens it eagerly but never closes it on its own, so leaving it
687 # open lets the connection be garbage collected unclosed, raising a
688 # spurious "ResourceWarning: unclosed database" in whatever code
689 # happens to be running at collection time (a source of flaky test
690 # failures, notably on the Windows / Python 3.14 CI job).
691 notary.store.close()
692 with open(outfname, "w", encoding="utf-8") as f:
693 write(nb, f, version=4)
694
695 def _get_kernel_language_info(self) -> dict | None:
696 """Get language info from kernel, useful when used in Jupyter Console where kernels exist."""
697 if not hasattr(self.shell, "kernel"):
698 return
699 if not hasattr(self.shell.kernel, "language_info"):
700 return
701 if not isinstance(self.shell.kernel.language_info, dict):
702 return
703 return self.shell.kernel.language_info
704
705@magics_class
706class AsyncMagics(BasicMagics):
707
708 @line_magic
709 def autoawait(self, parameter_s):
710 """
711 Allow to change the status of the autoawait option.
712
713 This allow you to set a specific asynchronous code runner.
714
715 If no value is passed, print the currently used asynchronous integration
716 and whether it is activated.
717
718 It can take a number of value evaluated in the following order:
719
720 - False/false/off deactivate autoawait integration
721 - True/true/on activate autoawait integration using configured default
722 loop
723 - asyncio/curio/trio activate autoawait integration and use integration
724 with said library.
725
726 - `sync` turn on the pseudo-sync integration (mostly used for
727 `IPython.embed()` which does not run IPython with a real eventloop and
728 deactivate running asynchronous code. Turning on Asynchronous code with
729 the pseudo sync loop is undefined behavior and may lead IPython to crash.
730
731 If the passed parameter does not match any of the above and is a python
732 identifier, get said object from user namespace and set it as the
733 runner, and activate autoawait.
734
735 If the object is a fully qualified object name, attempt to import it and
736 set it as the runner, and activate autoawait.
737
738 The exact behavior of autoawait is experimental and subject to change
739 across version of IPython and Python.
740 """
741
742 param = parameter_s.strip()
743 d = {True: "on", False: "off"}
744
745 if not param:
746 print("IPython autoawait is `{}`, and set to use `{}`".format(
747 d[self.shell.autoawait],
748 self.shell.loop_runner
749 ))
750 return None
751
752 if param.lower() in ('false', 'off'):
753 self.shell.autoawait = False
754 return None
755 if param.lower() in ('true', 'on'):
756 self.shell.autoawait = True
757 return None
758
759 if param in self.shell.loop_runner_map:
760 self.shell.loop_runner, self.shell.autoawait = self.shell.loop_runner_map[param]
761 return None
762
763 if param in self.shell.user_ns :
764 self.shell.loop_runner = self.shell.user_ns[param]
765 self.shell.autoawait = True
766 return None
767
768 runner = import_item(param)
769
770 self.shell.loop_runner = runner
771 self.shell.autoawait = True