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