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