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, Minimal, and Docs.
348
349 - ``Plain``: similar to Python's default traceback.
350 - ``Context``: shows several lines of surrounding context for each
351 frame in the traceback.
352 - ``Verbose``: like Context, but also displays local variable values
353 in each frame.
354 - ``Minimal``: shows only the exception type and message, without
355 a traceback.
356 - ``Docs``: a stripped-down version of Verbose, designed for use
357 when running doctests.
358
359 If called without arguments, cycles through the available modes.
360
361 When in verbose mode the value ``--show`` (and ``--hide``)
362 will respectively show (or hide) frames with ``__tracebackhide__ =
363 True`` value set.
364 """
365
366 def xmode_switch_err(name):
367 warn('Error changing %s exception modes.\n%s' %
368 (name,sys.exc_info()[1]))
369
370 shell = self.shell
371 if parameter_s.strip() == "--show":
372 shell.InteractiveTB.skip_hidden = False
373 return
374 if parameter_s.strip() == "--hide":
375 shell.InteractiveTB.skip_hidden = True
376 return
377
378 new_mode = parameter_s.strip().capitalize()
379 try:
380 shell.InteractiveTB.set_mode(mode=new_mode)
381 print('Exception reporting mode:',shell.InteractiveTB.mode)
382 except:
383 raise
384 xmode_switch_err('user')
385
386 @line_magic
387 def quickref(self, arg):
388 """ Show a quick reference sheet """
389 from IPython.core.usage import quick_reference
390 qr = quick_reference + self._magic_docs(brief=True)
391 page.page(qr)
392
393 @line_magic
394 def doctest_mode(self, parameter_s=''):
395 """Toggle doctest mode on and off.
396
397 This mode is intended to make IPython behave as much as possible like a
398 plain Python shell, from the perspective of how its prompts, exceptions
399 and output look. This makes it easy to copy and paste parts of a
400 session into doctests. It does so by:
401
402 - Changing the prompts to the classic ``>>>`` ones.
403 - Changing the exception reporting mode to 'Plain'.
404 - Disabling pretty-printing of output.
405
406 Note that IPython also supports the pasting of code snippets that have
407 leading '>>>' and '...' prompts in them. This means that you can paste
408 doctests from files or docstrings (even if they have leading
409 whitespace), and the code will execute correctly. You can then use
410 '%history -t' to see the translated history; this will give you the
411 input after removal of all the leading prompts and whitespace, which
412 can be pasted back into an editor.
413
414 With these features, you can switch into this mode easily whenever you
415 need to do testing and changes to doctests, without having to leave
416 your existing IPython session.
417 """
418
419 # Shorthands
420 shell = self.shell
421 meta = shell.meta
422 disp_formatter = self.shell.display_formatter
423 ptformatter = disp_formatter.formatters['text/plain']
424 # dstore is a data store kept in the instance metadata bag to track any
425 # changes we make, so we can undo them later.
426 dstore = meta.setdefault('doctest_mode',Struct())
427 save_dstore = dstore.setdefault
428
429 # save a few values we'll need to recover later
430 mode = save_dstore('mode',False)
431 save_dstore('rc_pprint',ptformatter.pprint)
432 save_dstore('xmode',shell.InteractiveTB.mode)
433 save_dstore('rc_separate_out',shell.separate_out)
434 save_dstore('rc_separate_out2',shell.separate_out2)
435 save_dstore('rc_separate_in',shell.separate_in)
436 save_dstore('rc_active_types',disp_formatter.active_types)
437
438 if not mode:
439 # turn on
440
441 # Prompt separators like plain python
442 shell.separate_in = ''
443 shell.separate_out = ''
444 shell.separate_out2 = ''
445
446
447 ptformatter.pprint = False
448 disp_formatter.active_types = ['text/plain']
449
450 shell.run_line_magic("xmode", "Plain")
451 else:
452 # turn off
453 shell.separate_in = dstore.rc_separate_in
454
455 shell.separate_out = dstore.rc_separate_out
456 shell.separate_out2 = dstore.rc_separate_out2
457
458 ptformatter.pprint = dstore.rc_pprint
459 disp_formatter.active_types = dstore.rc_active_types
460
461 shell.run_line_magic("xmode", dstore.xmode)
462
463 # mode here is the state before we switch; switch_doctest_mode takes
464 # the mode we're switching to.
465 shell.switch_doctest_mode(not mode)
466
467 # Store new mode and inform
468 dstore.mode = bool(not mode)
469 mode_label = ['OFF','ON'][dstore.mode]
470 print('Doctest mode is:', mode_label)
471
472 @line_magic
473 def gui(self, parameter_s=''):
474 """Enable or disable IPython GUI event loop integration.
475
476 %gui [GUINAME]
477
478 This magic replaces IPython's threaded shells that were activated
479 using the (pylab/wthread/etc.) command line flags. GUI toolkits
480 can now be enabled at runtime and keyboard
481 interrupts should work without any problems. The following toolkits
482 are supported: wxPython, PyQt4, PyGTK, Tk and Cocoa (OSX)::
483
484 %gui wx # enable wxPython event loop integration
485 %gui qt # enable PyQt/PySide event loop integration
486 # with the latest version available.
487 %gui qt6 # enable PyQt6/PySide6 event loop integration
488 %gui qt5 # enable PyQt5/PySide2 event loop integration
489 %gui gtk # enable PyGTK event loop integration
490 %gui gtk3 # enable Gtk3 event loop integration
491 %gui gtk4 # enable Gtk4 event loop integration
492 %gui tk # enable Tk event loop integration
493 %gui osx # enable Cocoa event loop integration
494 # (requires %matplotlib 1.1)
495 %gui # disable all event loop integration
496
497 WARNING: after any of these has been called you can simply create
498 an application object, but DO NOT start the event loop yourself, as
499 we have already handled that.
500 """
501 opts, arg = self.parse_options(parameter_s, '')
502 if arg=='': arg = None
503 try:
504 return self.shell.enable_gui(arg)
505 except Exception as e:
506 # print simple error message, rather than traceback if we can't
507 # hook up the GUI
508 error(str(e))
509
510 @skip_doctest
511 @line_magic
512 def precision(self, s=''):
513 """Set floating point precision for pretty printing.
514
515 Can set either integer precision or a format string.
516
517 If numpy has been imported and precision is an int,
518 numpy display precision will also be set, via ``numpy.set_printoptions``.
519
520 If no argument is given, defaults will be restored.
521
522 Examples
523 --------
524 ::
525
526 In [1]: from math import pi
527
528 In [2]: %precision 3
529 Out[2]: '%.3f'
530
531 In [3]: pi
532 Out[3]: 3.142
533
534 In [4]: %precision %i
535 Out[4]: '%i'
536
537 In [5]: pi
538 Out[5]: 3
539
540 In [6]: %precision %e
541 Out[6]: '%e'
542
543 In [7]: pi**10
544 Out[7]: 9.364805e+04
545
546 In [8]: %precision
547 Out[8]: '%r'
548
549 In [9]: pi**10
550 Out[9]: 93648.047476082982
551 """
552 ptformatter = self.shell.display_formatter.formatters['text/plain']
553 ptformatter.float_precision = s
554 return ptformatter.float_format
555
556 @magic_arguments.magic_arguments()
557 @magic_arguments.argument(
558 'filename', type=str,
559 help='Notebook name or filename'
560 )
561 @line_magic
562 def notebook(self, s):
563 """Export and convert IPython notebooks.
564
565 This function can export the current IPython history to a notebook file.
566 For example, to export the history to "foo.ipynb" do "%notebook foo.ipynb".
567 """
568 args = magic_arguments.parse_argstring(self.notebook, s)
569 outfname = os.path.expanduser(args.filename)
570
571 from nbformat import write, v4
572 from nbformat.sign import NotebookNotary
573
574 cells = []
575 hist = list(self.shell.history_manager.get_range())
576 outputs = self.shell.history_manager.outputs
577 exceptions = self.shell.history_manager.exceptions
578
579 if(len(hist)<=1):
580 raise ValueError('History is empty, cannot export')
581
582 for session, execution_count, source in hist[:-1]:
583 cell = v4.new_code_cell(execution_count=execution_count, source=source)
584
585 for output in outputs[execution_count]:
586 if output.output_type in {"out_stream", "err_stream"}:
587 text_data = []
588 for mime_type, data in output.bundle.items():
589 if isinstance(data, list):
590 text_data.extend(data)
591 else:
592 text_data.append(data)
593 full_text = "".join(text_data)
594 # Replace literal \n with actual newlines
595 full_text = full_text.replace("\\n", "\n")
596 normalized_text = []
597 lines = full_text.split("\n")
598 for i, line in enumerate(lines):
599 if i < len(lines) - 1:
600 normalized_text.append(line + "\n")
601 elif line: # Last line only if it's not empty
602 normalized_text.append(line + "\n")
603 stream_output = v4.new_output("stream", text=normalized_text)
604 if output.output_type == "err_stream":
605 stream_output.name = "stderr"
606 cell.outputs.append(stream_output)
607
608 elif output.output_type == "execute_result":
609 data_dict = {}
610 for mime_type, data in output.bundle.items():
611 data_dict[mime_type] = data
612 cell.outputs.append(
613 v4.new_output(
614 "execute_result",
615 data=data_dict,
616 execution_count=execution_count,
617 )
618 )
619
620 elif output.output_type == "display_data":
621 # Collect all MIME types for this display_data into a single output
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 "display_data",
628 data=data_dict,
629 )
630 )
631 else:
632 raise ValueError(f"Unknown output type: {output.output_type}")
633
634 # Check if this execution_count is in exceptions (current session)
635 if execution_count in exceptions:
636 cell.outputs.append(
637 v4.new_output("error", **exceptions[execution_count])
638 )
639 cells.append(cell)
640
641 kernel_language_info = self._get_kernel_language_info()
642
643 nb = v4.new_notebook(
644 cells=cells,
645 metadata={
646 "kernelspec": {
647 "display_name": "Python 3 (ipykernel)",
648 "language": "python",
649 "name": "python3",
650 },
651 "language_info": kernel_language_info
652 or {
653 "codemirror_mode": {
654 "name": "ipython",
655 "version": sys.version_info[0],
656 },
657 "file_extension": ".py",
658 "mimetype": "text/x-python",
659 "name": "python",
660 "nbconvert_exporter": "python",
661 "pygments_lexer": "ipython3",
662 "version": platform.python_version(),
663 },
664 },
665 )
666 # Sign the notebook to make it trusted
667 notary = NotebookNotary()
668 notary.update_config(self.shell.config)
669 notary.sign(nb)
670 with io.open(outfname, "w", encoding="utf-8") as f:
671 write(nb, f, version=4)
672
673 def _get_kernel_language_info(self) -> dict | None:
674 """Get language info from kernel, useful when used in Jupyter Console where kernels exist."""
675 if not hasattr(self.shell, "kernel"):
676 return
677 if not hasattr(self.shell.kernel, "language_info"):
678 return
679 if not isinstance(self.shell.kernel.language_info, dict):
680 return
681 return self.shell.kernel.language_info
682
683@magics_class
684class AsyncMagics(BasicMagics):
685
686 @line_magic
687 def autoawait(self, parameter_s):
688 """
689 Allow to change the status of the autoawait option.
690
691 This allow you to set a specific asynchronous code runner.
692
693 If no value is passed, print the currently used asynchronous integration
694 and whether it is activated.
695
696 It can take a number of value evaluated in the following order:
697
698 - False/false/off deactivate autoawait integration
699 - True/true/on activate autoawait integration using configured default
700 loop
701 - asyncio/curio/trio activate autoawait integration and use integration
702 with said library.
703
704 - `sync` turn on the pseudo-sync integration (mostly used for
705 `IPython.embed()` which does not run IPython with a real eventloop and
706 deactivate running asynchronous code. Turning on Asynchronous code with
707 the pseudo sync loop is undefined behavior and may lead IPython to crash.
708
709 If the passed parameter does not match any of the above and is a python
710 identifier, get said object from user namespace and set it as the
711 runner, and activate autoawait.
712
713 If the object is a fully qualified object name, attempt to import it and
714 set it as the runner, and activate autoawait.
715
716 The exact behavior of autoawait is experimental and subject to change
717 across version of IPython and Python.
718 """
719
720 param = parameter_s.strip()
721 d = {True: "on", False: "off"}
722
723 if not param:
724 print("IPython autoawait is `{}`, and set to use `{}`".format(
725 d[self.shell.autoawait],
726 self.shell.loop_runner
727 ))
728 return None
729
730 if param.lower() in ('false', 'off'):
731 self.shell.autoawait = False
732 return None
733 if param.lower() in ('true', 'on'):
734 self.shell.autoawait = True
735 return None
736
737 if param in self.shell.loop_runner_map:
738 self.shell.loop_runner, self.shell.autoawait = self.shell.loop_runner_map[param]
739 return None
740
741 if param in self.shell.user_ns :
742 self.shell.loop_runner = self.shell.user_ns[param]
743 self.shell.autoawait = True
744 return None
745
746 runner = import_item(param)
747
748 self.shell.loop_runner = runner
749 self.shell.autoawait = True