Show More
The requested changes are too big and content was truncated. Show full diff
This diff has been collapsed as it changes many lines, (670 lines changed) Show them Hide them | |||
@@ -0,0 +1,670 b'' | |||
|
1 | """ | |
|
2 | Module to define and register Terminal IPython shortcuts with | |
|
3 | :mod:`prompt_toolkit` | |
|
4 | """ | |
|
5 | ||
|
6 | # Copyright (c) IPython Development Team. | |
|
7 | # Distributed under the terms of the Modified BSD License. | |
|
8 | ||
|
9 | import os | |
|
10 | import re | |
|
11 | import signal | |
|
12 | import sys | |
|
13 | import warnings | |
|
14 | from typing import Callable, Dict, Union | |
|
15 | ||
|
16 | from prompt_toolkit.application.current import get_app | |
|
17 | from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER | |
|
18 | from prompt_toolkit.filters import Condition, emacs_insert_mode, has_completions | |
|
19 | from prompt_toolkit.filters import has_focus as has_focus_impl | |
|
20 | from prompt_toolkit.filters import ( | |
|
21 | has_selection, | |
|
22 | has_suggestion, | |
|
23 | vi_insert_mode, | |
|
24 | vi_mode, | |
|
25 | ) | |
|
26 | from prompt_toolkit.key_binding import KeyBindings | |
|
27 | from prompt_toolkit.key_binding.bindings import named_commands as nc | |
|
28 | from prompt_toolkit.key_binding.bindings.completion import ( | |
|
29 | display_completions_like_readline, | |
|
30 | ) | |
|
31 | from prompt_toolkit.key_binding.vi_state import InputMode, ViState | |
|
32 | from prompt_toolkit.layout.layout import FocusableElement | |
|
33 | ||
|
34 | from IPython.terminal.shortcuts import auto_match as match | |
|
35 | from IPython.terminal.shortcuts import auto_suggest | |
|
36 | from IPython.utils.decorators import undoc | |
|
37 | ||
|
38 | __all__ = ["create_ipython_shortcuts"] | |
|
39 | ||
|
40 | ||
|
41 | @undoc | |
|
42 | @Condition | |
|
43 | def cursor_in_leading_ws(): | |
|
44 | before = get_app().current_buffer.document.current_line_before_cursor | |
|
45 | return (not before) or before.isspace() | |
|
46 | ||
|
47 | ||
|
48 | def has_focus(value: FocusableElement): | |
|
49 | """Wrapper around has_focus adding a nice `__name__` to tester function""" | |
|
50 | tester = has_focus_impl(value).func | |
|
51 | tester.__name__ = f"is_focused({value})" | |
|
52 | return Condition(tester) | |
|
53 | ||
|
54 | ||
|
55 | @undoc | |
|
56 | @Condition | |
|
57 | def has_line_below() -> bool: | |
|
58 | document = get_app().current_buffer.document | |
|
59 | return document.cursor_position_row < len(document.lines) - 1 | |
|
60 | ||
|
61 | ||
|
62 | @undoc | |
|
63 | @Condition | |
|
64 | def has_line_above() -> bool: | |
|
65 | document = get_app().current_buffer.document | |
|
66 | return document.cursor_position_row != 0 | |
|
67 | ||
|
68 | ||
|
69 | def create_ipython_shortcuts(shell, for_all_platforms: bool = False) -> KeyBindings: | |
|
70 | """Set up the prompt_toolkit keyboard shortcuts for IPython. | |
|
71 | ||
|
72 | Parameters | |
|
73 | ---------- | |
|
74 | shell: InteractiveShell | |
|
75 | The current IPython shell Instance | |
|
76 | for_all_platforms: bool (default false) | |
|
77 | This parameter is mostly used in generating the documentation | |
|
78 | to create the shortcut binding for all the platforms, and export | |
|
79 | them. | |
|
80 | ||
|
81 | Returns | |
|
82 | ------- | |
|
83 | KeyBindings | |
|
84 | the keybinding instance for prompt toolkit. | |
|
85 | ||
|
86 | """ | |
|
87 | # Warning: if possible, do NOT define handler functions in the locals | |
|
88 | # scope of this function, instead define functions in the global | |
|
89 | # scope, or a separate module, and include a user-friendly docstring | |
|
90 | # describing the action. | |
|
91 | ||
|
92 | kb = KeyBindings() | |
|
93 | insert_mode = vi_insert_mode | emacs_insert_mode | |
|
94 | ||
|
95 | if getattr(shell, "handle_return", None): | |
|
96 | return_handler = shell.handle_return(shell) | |
|
97 | else: | |
|
98 | return_handler = newline_or_execute_outer(shell) | |
|
99 | ||
|
100 | kb.add("enter", filter=(has_focus(DEFAULT_BUFFER) & ~has_selection & insert_mode))( | |
|
101 | return_handler | |
|
102 | ) | |
|
103 | ||
|
104 | @Condition | |
|
105 | def ebivim(): | |
|
106 | return shell.emacs_bindings_in_vi_insert_mode | |
|
107 | ||
|
108 | @kb.add( | |
|
109 | "escape", | |
|
110 | "enter", | |
|
111 | filter=(has_focus(DEFAULT_BUFFER) & ~has_selection & insert_mode & ebivim), | |
|
112 | ) | |
|
113 | def reformat_and_execute(event): | |
|
114 | """Reformat code and execute it""" | |
|
115 | reformat_text_before_cursor( | |
|
116 | event.current_buffer, event.current_buffer.document, shell | |
|
117 | ) | |
|
118 | event.current_buffer.validate_and_handle() | |
|
119 | ||
|
120 | kb.add("c-\\")(quit) | |
|
121 | ||
|
122 | kb.add("c-p", filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER)))( | |
|
123 | previous_history_or_previous_completion | |
|
124 | ) | |
|
125 | ||
|
126 | kb.add("c-n", filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER)))( | |
|
127 | next_history_or_next_completion | |
|
128 | ) | |
|
129 | ||
|
130 | kb.add("c-g", filter=(has_focus(DEFAULT_BUFFER) & has_completions))( | |
|
131 | dismiss_completion | |
|
132 | ) | |
|
133 | ||
|
134 | kb.add("c-c", filter=has_focus(DEFAULT_BUFFER))(reset_buffer) | |
|
135 | ||
|
136 | kb.add("c-c", filter=has_focus(SEARCH_BUFFER))(reset_search_buffer) | |
|
137 | ||
|
138 | supports_suspend = Condition(lambda: hasattr(signal, "SIGTSTP")) | |
|
139 | kb.add("c-z", filter=supports_suspend)(suspend_to_bg) | |
|
140 | ||
|
141 | # Ctrl+I == Tab | |
|
142 | kb.add( | |
|
143 | "tab", | |
|
144 | filter=( | |
|
145 | has_focus(DEFAULT_BUFFER) | |
|
146 | & ~has_selection | |
|
147 | & insert_mode | |
|
148 | & cursor_in_leading_ws | |
|
149 | ), | |
|
150 | )(indent_buffer) | |
|
151 | kb.add("c-o", filter=(has_focus(DEFAULT_BUFFER) & emacs_insert_mode))( | |
|
152 | newline_autoindent_outer(shell.input_transformer_manager) | |
|
153 | ) | |
|
154 | ||
|
155 | kb.add("f2", filter=has_focus(DEFAULT_BUFFER))(open_input_in_editor) | |
|
156 | ||
|
157 | @Condition | |
|
158 | def auto_match(): | |
|
159 | return shell.auto_match | |
|
160 | ||
|
161 | def all_quotes_paired(quote, buf): | |
|
162 | paired = True | |
|
163 | i = 0 | |
|
164 | while i < len(buf): | |
|
165 | c = buf[i] | |
|
166 | if c == quote: | |
|
167 | paired = not paired | |
|
168 | elif c == "\\": | |
|
169 | i += 1 | |
|
170 | i += 1 | |
|
171 | return paired | |
|
172 | ||
|
173 | focused_insert = (vi_insert_mode | emacs_insert_mode) & has_focus(DEFAULT_BUFFER) | |
|
174 | _preceding_text_cache: Dict[Union[str, Callable], Condition] = {} | |
|
175 | _following_text_cache: Dict[Union[str, Callable], Condition] = {} | |
|
176 | ||
|
177 | def preceding_text(pattern: Union[str, Callable]): | |
|
178 | if pattern in _preceding_text_cache: | |
|
179 | return _preceding_text_cache[pattern] | |
|
180 | ||
|
181 | if callable(pattern): | |
|
182 | ||
|
183 | def _preceding_text(): | |
|
184 | app = get_app() | |
|
185 | before_cursor = app.current_buffer.document.current_line_before_cursor | |
|
186 | # mypy can't infer if(callable): https://github.com/python/mypy/issues/3603 | |
|
187 | return bool(pattern(before_cursor)) # type: ignore[operator] | |
|
188 | ||
|
189 | else: | |
|
190 | m = re.compile(pattern) | |
|
191 | ||
|
192 | def _preceding_text(): | |
|
193 | app = get_app() | |
|
194 | before_cursor = app.current_buffer.document.current_line_before_cursor | |
|
195 | return bool(m.match(before_cursor)) | |
|
196 | ||
|
197 | _preceding_text.__name__ = f"preceding_text({pattern!r})" | |
|
198 | ||
|
199 | condition = Condition(_preceding_text) | |
|
200 | _preceding_text_cache[pattern] = condition | |
|
201 | return condition | |
|
202 | ||
|
203 | def following_text(pattern): | |
|
204 | try: | |
|
205 | return _following_text_cache[pattern] | |
|
206 | except KeyError: | |
|
207 | pass | |
|
208 | m = re.compile(pattern) | |
|
209 | ||
|
210 | def _following_text(): | |
|
211 | app = get_app() | |
|
212 | return bool(m.match(app.current_buffer.document.current_line_after_cursor)) | |
|
213 | ||
|
214 | _following_text.__name__ = f"following_text({pattern!r})" | |
|
215 | ||
|
216 | condition = Condition(_following_text) | |
|
217 | _following_text_cache[pattern] = condition | |
|
218 | return condition | |
|
219 | ||
|
220 | @Condition | |
|
221 | def not_inside_unclosed_string(): | |
|
222 | app = get_app() | |
|
223 | s = app.current_buffer.document.text_before_cursor | |
|
224 | # remove escaped quotes | |
|
225 | s = s.replace('\\"', "").replace("\\'", "") | |
|
226 | # remove triple-quoted string literals | |
|
227 | s = re.sub(r"(?:\"\"\"[\s\S]*\"\"\"|'''[\s\S]*''')", "", s) | |
|
228 | # remove single-quoted string literals | |
|
229 | s = re.sub(r"""(?:"[^"]*["\n]|'[^']*['\n])""", "", s) | |
|
230 | return not ('"' in s or "'" in s) | |
|
231 | ||
|
232 | # auto match | |
|
233 | for key, cmd in match.auto_match_parens.items(): | |
|
234 | kb.add(key, filter=focused_insert & auto_match & following_text(r"[,)}\]]|$"))( | |
|
235 | cmd | |
|
236 | ) | |
|
237 | ||
|
238 | # raw string | |
|
239 | for key, cmd in match.auto_match_parens_raw_string.items(): | |
|
240 | kb.add( | |
|
241 | key, | |
|
242 | filter=focused_insert & auto_match & preceding_text(r".*(r|R)[\"'](-*)$"), | |
|
243 | )(cmd) | |
|
244 | ||
|
245 | kb.add( | |
|
246 | '"', | |
|
247 | filter=focused_insert | |
|
248 | & auto_match | |
|
249 | & not_inside_unclosed_string | |
|
250 | & preceding_text(lambda line: all_quotes_paired('"', line)) | |
|
251 | & following_text(r"[,)}\]]|$"), | |
|
252 | )(match.double_quote) | |
|
253 | ||
|
254 | kb.add( | |
|
255 | "'", | |
|
256 | filter=focused_insert | |
|
257 | & auto_match | |
|
258 | & not_inside_unclosed_string | |
|
259 | & preceding_text(lambda line: all_quotes_paired("'", line)) | |
|
260 | & following_text(r"[,)}\]]|$"), | |
|
261 | )(match.single_quote) | |
|
262 | ||
|
263 | kb.add( | |
|
264 | '"', | |
|
265 | filter=focused_insert | |
|
266 | & auto_match | |
|
267 | & not_inside_unclosed_string | |
|
268 | & preceding_text(r'^.*""$'), | |
|
269 | )(match.docstring_double_quotes) | |
|
270 | ||
|
271 | kb.add( | |
|
272 | "'", | |
|
273 | filter=focused_insert | |
|
274 | & auto_match | |
|
275 | & not_inside_unclosed_string | |
|
276 | & preceding_text(r"^.*''$"), | |
|
277 | )(match.docstring_single_quotes) | |
|
278 | ||
|
279 | # just move cursor | |
|
280 | kb.add(")", filter=focused_insert & auto_match & following_text(r"^\)"))( | |
|
281 | match.skip_over | |
|
282 | ) | |
|
283 | kb.add("]", filter=focused_insert & auto_match & following_text(r"^\]"))( | |
|
284 | match.skip_over | |
|
285 | ) | |
|
286 | kb.add("}", filter=focused_insert & auto_match & following_text(r"^\}"))( | |
|
287 | match.skip_over | |
|
288 | ) | |
|
289 | kb.add('"', filter=focused_insert & auto_match & following_text('^"'))( | |
|
290 | match.skip_over | |
|
291 | ) | |
|
292 | kb.add("'", filter=focused_insert & auto_match & following_text("^'"))( | |
|
293 | match.skip_over | |
|
294 | ) | |
|
295 | ||
|
296 | kb.add( | |
|
297 | "backspace", | |
|
298 | filter=focused_insert | |
|
299 | & preceding_text(r".*\($") | |
|
300 | & auto_match | |
|
301 | & following_text(r"^\)"), | |
|
302 | )(match.delete_pair) | |
|
303 | kb.add( | |
|
304 | "backspace", | |
|
305 | filter=focused_insert | |
|
306 | & preceding_text(r".*\[$") | |
|
307 | & auto_match | |
|
308 | & following_text(r"^\]"), | |
|
309 | )(match.delete_pair) | |
|
310 | kb.add( | |
|
311 | "backspace", | |
|
312 | filter=focused_insert | |
|
313 | & preceding_text(r".*\{$") | |
|
314 | & auto_match | |
|
315 | & following_text(r"^\}"), | |
|
316 | )(match.delete_pair) | |
|
317 | kb.add( | |
|
318 | "backspace", | |
|
319 | filter=focused_insert | |
|
320 | & preceding_text('.*"$') | |
|
321 | & auto_match | |
|
322 | & following_text('^"'), | |
|
323 | )(match.delete_pair) | |
|
324 | kb.add( | |
|
325 | "backspace", | |
|
326 | filter=focused_insert | |
|
327 | & preceding_text(r".*'$") | |
|
328 | & auto_match | |
|
329 | & following_text(r"^'"), | |
|
330 | )(match.delete_pair) | |
|
331 | ||
|
332 | if shell.display_completions == "readlinelike": | |
|
333 | kb.add( | |
|
334 | "c-i", | |
|
335 | filter=( | |
|
336 | has_focus(DEFAULT_BUFFER) | |
|
337 | & ~has_selection | |
|
338 | & insert_mode | |
|
339 | & ~cursor_in_leading_ws | |
|
340 | ), | |
|
341 | )(display_completions_like_readline) | |
|
342 | ||
|
343 | if sys.platform == "win32" or for_all_platforms: | |
|
344 | kb.add("c-v", filter=(has_focus(DEFAULT_BUFFER) & ~vi_mode))(win_paste) | |
|
345 | ||
|
346 | focused_insert_vi = has_focus(DEFAULT_BUFFER) & vi_insert_mode | |
|
347 | ||
|
348 | # autosuggestions | |
|
349 | @Condition | |
|
350 | def navigable_suggestions(): | |
|
351 | return isinstance( | |
|
352 | shell.auto_suggest, auto_suggest.NavigableAutoSuggestFromHistory | |
|
353 | ) | |
|
354 | ||
|
355 | kb.add("end", filter=has_focus(DEFAULT_BUFFER) & (ebivim | ~vi_insert_mode))( | |
|
356 | auto_suggest.accept_in_vi_insert_mode | |
|
357 | ) | |
|
358 | kb.add("c-e", filter=focused_insert_vi & ebivim)( | |
|
359 | auto_suggest.accept_in_vi_insert_mode | |
|
360 | ) | |
|
361 | kb.add("c-f", filter=focused_insert_vi)(auto_suggest.accept) | |
|
362 | kb.add("escape", "f", filter=focused_insert_vi & ebivim)(auto_suggest.accept_word) | |
|
363 | kb.add("c-right", filter=has_suggestion & has_focus(DEFAULT_BUFFER))( | |
|
364 | auto_suggest.accept_token | |
|
365 | ) | |
|
366 | kb.add( | |
|
367 | "escape", filter=has_suggestion & has_focus(DEFAULT_BUFFER) & emacs_insert_mode | |
|
368 | )(auto_suggest.discard) | |
|
369 | kb.add( | |
|
370 | "up", | |
|
371 | filter=navigable_suggestions | |
|
372 | & ~has_line_above | |
|
373 | & has_suggestion | |
|
374 | & has_focus(DEFAULT_BUFFER), | |
|
375 | )(auto_suggest.swap_autosuggestion_up(shell.auto_suggest)) | |
|
376 | kb.add( | |
|
377 | "down", | |
|
378 | filter=navigable_suggestions | |
|
379 | & ~has_line_below | |
|
380 | & has_suggestion | |
|
381 | & has_focus(DEFAULT_BUFFER), | |
|
382 | )(auto_suggest.swap_autosuggestion_down(shell.auto_suggest)) | |
|
383 | kb.add( | |
|
384 | "up", filter=has_line_above & navigable_suggestions & has_focus(DEFAULT_BUFFER) | |
|
385 | )(auto_suggest.up_and_update_hint) | |
|
386 | kb.add( | |
|
387 | "down", | |
|
388 | filter=has_line_below & navigable_suggestions & has_focus(DEFAULT_BUFFER), | |
|
389 | )(auto_suggest.down_and_update_hint) | |
|
390 | kb.add("right", filter=has_suggestion & has_focus(DEFAULT_BUFFER))( | |
|
391 | auto_suggest.accept_character | |
|
392 | ) | |
|
393 | kb.add("c-left", filter=has_suggestion & has_focus(DEFAULT_BUFFER))( | |
|
394 | auto_suggest.accept_and_move_cursor_left | |
|
395 | ) | |
|
396 | kb.add("c-down", filter=has_suggestion & has_focus(DEFAULT_BUFFER))( | |
|
397 | auto_suggest.accept_and_keep_cursor | |
|
398 | ) | |
|
399 | kb.add("backspace", filter=has_suggestion & has_focus(DEFAULT_BUFFER))( | |
|
400 | auto_suggest.backspace_and_resume_hint | |
|
401 | ) | |
|
402 | ||
|
403 | # Simple Control keybindings | |
|
404 | key_cmd_dict = { | |
|
405 | "c-a": nc.beginning_of_line, | |
|
406 | "c-b": nc.backward_char, | |
|
407 | "c-k": nc.kill_line, | |
|
408 | "c-w": nc.backward_kill_word, | |
|
409 | "c-y": nc.yank, | |
|
410 | "c-_": nc.undo, | |
|
411 | } | |
|
412 | ||
|
413 | for key, cmd in key_cmd_dict.items(): | |
|
414 | kb.add(key, filter=focused_insert_vi & ebivim)(cmd) | |
|
415 | ||
|
416 | # Alt and Combo Control keybindings | |
|
417 | keys_cmd_dict = { | |
|
418 | # Control Combos | |
|
419 | ("c-x", "c-e"): nc.edit_and_execute, | |
|
420 | ("c-x", "e"): nc.edit_and_execute, | |
|
421 | # Alt | |
|
422 | ("escape", "b"): nc.backward_word, | |
|
423 | ("escape", "c"): nc.capitalize_word, | |
|
424 | ("escape", "d"): nc.kill_word, | |
|
425 | ("escape", "h"): nc.backward_kill_word, | |
|
426 | ("escape", "l"): nc.downcase_word, | |
|
427 | ("escape", "u"): nc.uppercase_word, | |
|
428 | ("escape", "y"): nc.yank_pop, | |
|
429 | ("escape", "."): nc.yank_last_arg, | |
|
430 | } | |
|
431 | ||
|
432 | for keys, cmd in keys_cmd_dict.items(): | |
|
433 | kb.add(*keys, filter=focused_insert_vi & ebivim)(cmd) | |
|
434 | ||
|
435 | def get_input_mode(self): | |
|
436 | app = get_app() | |
|
437 | app.ttimeoutlen = shell.ttimeoutlen | |
|
438 | app.timeoutlen = shell.timeoutlen | |
|
439 | ||
|
440 | return self._input_mode | |
|
441 | ||
|
442 | def set_input_mode(self, mode): | |
|
443 | shape = {InputMode.NAVIGATION: 2, InputMode.REPLACE: 4}.get(mode, 6) | |
|
444 | cursor = "\x1b[{} q".format(shape) | |
|
445 | ||
|
446 | sys.stdout.write(cursor) | |
|
447 | sys.stdout.flush() | |
|
448 | ||
|
449 | self._input_mode = mode | |
|
450 | ||
|
451 | if shell.editing_mode == "vi" and shell.modal_cursor: | |
|
452 | ViState._input_mode = InputMode.INSERT # type: ignore | |
|
453 | ViState.input_mode = property(get_input_mode, set_input_mode) # type: ignore | |
|
454 | return kb | |
|
455 | ||
|
456 | ||
|
457 | def reformat_text_before_cursor(buffer, document, shell): | |
|
458 | text = buffer.delete_before_cursor(len(document.text[: document.cursor_position])) | |
|
459 | try: | |
|
460 | formatted_text = shell.reformat_handler(text) | |
|
461 | buffer.insert_text(formatted_text) | |
|
462 | except Exception as e: | |
|
463 | buffer.insert_text(text) | |
|
464 | ||
|
465 | ||
|
466 | def newline_or_execute_outer(shell): | |
|
467 | def newline_or_execute(event): | |
|
468 | """When the user presses return, insert a newline or execute the code.""" | |
|
469 | b = event.current_buffer | |
|
470 | d = b.document | |
|
471 | ||
|
472 | if b.complete_state: | |
|
473 | cc = b.complete_state.current_completion | |
|
474 | if cc: | |
|
475 | b.apply_completion(cc) | |
|
476 | else: | |
|
477 | b.cancel_completion() | |
|
478 | return | |
|
479 | ||
|
480 | # If there's only one line, treat it as if the cursor is at the end. | |
|
481 | # See https://github.com/ipython/ipython/issues/10425 | |
|
482 | if d.line_count == 1: | |
|
483 | check_text = d.text | |
|
484 | else: | |
|
485 | check_text = d.text[: d.cursor_position] | |
|
486 | status, indent = shell.check_complete(check_text) | |
|
487 | ||
|
488 | # if all we have after the cursor is whitespace: reformat current text | |
|
489 | # before cursor | |
|
490 | after_cursor = d.text[d.cursor_position :] | |
|
491 | reformatted = False | |
|
492 | if not after_cursor.strip(): | |
|
493 | reformat_text_before_cursor(b, d, shell) | |
|
494 | reformatted = True | |
|
495 | if not ( | |
|
496 | d.on_last_line | |
|
497 | or d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end() | |
|
498 | ): | |
|
499 | if shell.autoindent: | |
|
500 | b.insert_text("\n" + indent) | |
|
501 | else: | |
|
502 | b.insert_text("\n") | |
|
503 | return | |
|
504 | ||
|
505 | if (status != "incomplete") and b.accept_handler: | |
|
506 | if not reformatted: | |
|
507 | reformat_text_before_cursor(b, d, shell) | |
|
508 | b.validate_and_handle() | |
|
509 | else: | |
|
510 | if shell.autoindent: | |
|
511 | b.insert_text("\n" + indent) | |
|
512 | else: | |
|
513 | b.insert_text("\n") | |
|
514 | ||
|
515 | newline_or_execute.__qualname__ = "newline_or_execute" | |
|
516 | ||
|
517 | return newline_or_execute | |
|
518 | ||
|
519 | ||
|
520 | def previous_history_or_previous_completion(event): | |
|
521 | """ | |
|
522 | Control-P in vi edit mode on readline is history next, unlike default prompt toolkit. | |
|
523 | ||
|
524 | If completer is open this still select previous completion. | |
|
525 | """ | |
|
526 | event.current_buffer.auto_up() | |
|
527 | ||
|
528 | ||
|
529 | def next_history_or_next_completion(event): | |
|
530 | """ | |
|
531 | Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit. | |
|
532 | ||
|
533 | If completer is open this still select next completion. | |
|
534 | """ | |
|
535 | event.current_buffer.auto_down() | |
|
536 | ||
|
537 | ||
|
538 | def dismiss_completion(event): | |
|
539 | """Dismiss completion""" | |
|
540 | b = event.current_buffer | |
|
541 | if b.complete_state: | |
|
542 | b.cancel_completion() | |
|
543 | ||
|
544 | ||
|
545 | def reset_buffer(event): | |
|
546 | """Reset buffer""" | |
|
547 | b = event.current_buffer | |
|
548 | if b.complete_state: | |
|
549 | b.cancel_completion() | |
|
550 | else: | |
|
551 | b.reset() | |
|
552 | ||
|
553 | ||
|
554 | def reset_search_buffer(event): | |
|
555 | """Reset search buffer""" | |
|
556 | if event.current_buffer.document.text: | |
|
557 | event.current_buffer.reset() | |
|
558 | else: | |
|
559 | event.app.layout.focus(DEFAULT_BUFFER) | |
|
560 | ||
|
561 | ||
|
562 | def suspend_to_bg(event): | |
|
563 | """Suspend to background""" | |
|
564 | event.app.suspend_to_background() | |
|
565 | ||
|
566 | ||
|
567 | def quit(event): | |
|
568 | """ | |
|
569 | Quit application with ``SIGQUIT`` if supported or ``sys.exit`` otherwise. | |
|
570 | ||
|
571 | On platforms that support SIGQUIT, send SIGQUIT to the current process. | |
|
572 | On other platforms, just exit the process with a message. | |
|
573 | """ | |
|
574 | sigquit = getattr(signal, "SIGQUIT", None) | |
|
575 | if sigquit is not None: | |
|
576 | os.kill(0, signal.SIGQUIT) | |
|
577 | else: | |
|
578 | sys.exit("Quit") | |
|
579 | ||
|
580 | ||
|
581 | def indent_buffer(event): | |
|
582 | """Indent buffer""" | |
|
583 | event.current_buffer.insert_text(" " * 4) | |
|
584 | ||
|
585 | ||
|
586 | @undoc | |
|
587 | def newline_with_copy_margin(event): | |
|
588 | """ | |
|
589 | DEPRECATED since IPython 6.0 | |
|
590 | ||
|
591 | See :any:`newline_autoindent_outer` for a replacement. | |
|
592 | ||
|
593 | Preserve margin and cursor position when using | |
|
594 | Control-O to insert a newline in EMACS mode | |
|
595 | """ | |
|
596 | warnings.warn( | |
|
597 | "`newline_with_copy_margin(event)` is deprecated since IPython 6.0. " | |
|
598 | "see `newline_autoindent_outer(shell)(event)` for a replacement.", | |
|
599 | DeprecationWarning, | |
|
600 | stacklevel=2, | |
|
601 | ) | |
|
602 | ||
|
603 | b = event.current_buffer | |
|
604 | cursor_start_pos = b.document.cursor_position_col | |
|
605 | b.newline(copy_margin=True) | |
|
606 | b.cursor_up(count=1) | |
|
607 | cursor_end_pos = b.document.cursor_position_col | |
|
608 | if cursor_start_pos != cursor_end_pos: | |
|
609 | pos_diff = cursor_start_pos - cursor_end_pos | |
|
610 | b.cursor_right(count=pos_diff) | |
|
611 | ||
|
612 | ||
|
613 | def newline_autoindent_outer(inputsplitter) -> Callable[..., None]: | |
|
614 | """ | |
|
615 | Return a function suitable for inserting a indented newline after the cursor. | |
|
616 | ||
|
617 | Fancier version of deprecated ``newline_with_copy_margin`` which should | |
|
618 | compute the correct indentation of the inserted line. That is to say, indent | |
|
619 | by 4 extra space after a function definition, class definition, context | |
|
620 | manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``. | |
|
621 | """ | |
|
622 | ||
|
623 | def newline_autoindent(event): | |
|
624 | """Insert a newline after the cursor indented appropriately.""" | |
|
625 | b = event.current_buffer | |
|
626 | d = b.document | |
|
627 | ||
|
628 | if b.complete_state: | |
|
629 | b.cancel_completion() | |
|
630 | text = d.text[: d.cursor_position] + "\n" | |
|
631 | _, indent = inputsplitter.check_complete(text) | |
|
632 | b.insert_text("\n" + (" " * (indent or 0)), move_cursor=False) | |
|
633 | ||
|
634 | newline_autoindent.__qualname__ = "newline_autoindent" | |
|
635 | ||
|
636 | return newline_autoindent | |
|
637 | ||
|
638 | ||
|
639 | def open_input_in_editor(event): | |
|
640 | """Open code from input in external editor""" | |
|
641 | event.app.current_buffer.open_in_editor() | |
|
642 | ||
|
643 | ||
|
644 | if sys.platform == "win32": | |
|
645 | from IPython.core.error import TryNext | |
|
646 | from IPython.lib.clipboard import ( | |
|
647 | ClipboardEmpty, | |
|
648 | tkinter_clipboard_get, | |
|
649 | win32_clipboard_get, | |
|
650 | ) | |
|
651 | ||
|
652 | @undoc | |
|
653 | def win_paste(event): | |
|
654 | try: | |
|
655 | text = win32_clipboard_get() | |
|
656 | except TryNext: | |
|
657 | try: | |
|
658 | text = tkinter_clipboard_get() | |
|
659 | except (TryNext, ClipboardEmpty): | |
|
660 | return | |
|
661 | except ClipboardEmpty: | |
|
662 | return | |
|
663 | event.current_buffer.insert_text(text.replace("\t", " " * 4)) | |
|
664 | ||
|
665 | else: | |
|
666 | ||
|
667 | @undoc | |
|
668 | def win_paste(event): | |
|
669 | """Stub used when auto-generating shortcuts for documentation""" | |
|
670 | pass |
@@ -0,0 +1,104 b'' | |||
|
1 | """ | |
|
2 | Utilities function for keybinding with prompt toolkit. | |
|
3 | ||
|
4 | This will be bound to specific key press and filter modes, | |
|
5 | like whether we are in edit mode, and whether the completer is open. | |
|
6 | """ | |
|
7 | import re | |
|
8 | from prompt_toolkit.key_binding import KeyPressEvent | |
|
9 | ||
|
10 | ||
|
11 | def parenthesis(event: KeyPressEvent): | |
|
12 | """Auto-close parenthesis""" | |
|
13 | event.current_buffer.insert_text("()") | |
|
14 | event.current_buffer.cursor_left() | |
|
15 | ||
|
16 | ||
|
17 | def brackets(event: KeyPressEvent): | |
|
18 | """Auto-close brackets""" | |
|
19 | event.current_buffer.insert_text("[]") | |
|
20 | event.current_buffer.cursor_left() | |
|
21 | ||
|
22 | ||
|
23 | def braces(event: KeyPressEvent): | |
|
24 | """Auto-close braces""" | |
|
25 | event.current_buffer.insert_text("{}") | |
|
26 | event.current_buffer.cursor_left() | |
|
27 | ||
|
28 | ||
|
29 | def double_quote(event: KeyPressEvent): | |
|
30 | """Auto-close double quotes""" | |
|
31 | event.current_buffer.insert_text('""') | |
|
32 | event.current_buffer.cursor_left() | |
|
33 | ||
|
34 | ||
|
35 | def single_quote(event: KeyPressEvent): | |
|
36 | """Auto-close single quotes""" | |
|
37 | event.current_buffer.insert_text("''") | |
|
38 | event.current_buffer.cursor_left() | |
|
39 | ||
|
40 | ||
|
41 | def docstring_double_quotes(event: KeyPressEvent): | |
|
42 | """Auto-close docstring (double quotes)""" | |
|
43 | event.current_buffer.insert_text('""""') | |
|
44 | event.current_buffer.cursor_left(3) | |
|
45 | ||
|
46 | ||
|
47 | def docstring_single_quotes(event: KeyPressEvent): | |
|
48 | """Auto-close docstring (single quotes)""" | |
|
49 | event.current_buffer.insert_text("''''") | |
|
50 | event.current_buffer.cursor_left(3) | |
|
51 | ||
|
52 | ||
|
53 | def raw_string_parenthesis(event: KeyPressEvent): | |
|
54 | """Auto-close parenthesis in raw strings""" | |
|
55 | matches = re.match( | |
|
56 | r".*(r|R)[\"'](-*)", | |
|
57 | event.current_buffer.document.current_line_before_cursor, | |
|
58 | ) | |
|
59 | dashes = matches.group(2) if matches else "" | |
|
60 | event.current_buffer.insert_text("()" + dashes) | |
|
61 | event.current_buffer.cursor_left(len(dashes) + 1) | |
|
62 | ||
|
63 | ||
|
64 | def raw_string_bracket(event: KeyPressEvent): | |
|
65 | """Auto-close bracker in raw strings""" | |
|
66 | matches = re.match( | |
|
67 | r".*(r|R)[\"'](-*)", | |
|
68 | event.current_buffer.document.current_line_before_cursor, | |
|
69 | ) | |
|
70 | dashes = matches.group(2) if matches else "" | |
|
71 | event.current_buffer.insert_text("[]" + dashes) | |
|
72 | event.current_buffer.cursor_left(len(dashes) + 1) | |
|
73 | ||
|
74 | ||
|
75 | def raw_string_braces(event: KeyPressEvent): | |
|
76 | """Auto-close braces in raw strings""" | |
|
77 | matches = re.match( | |
|
78 | r".*(r|R)[\"'](-*)", | |
|
79 | event.current_buffer.document.current_line_before_cursor, | |
|
80 | ) | |
|
81 | dashes = matches.group(2) if matches else "" | |
|
82 | event.current_buffer.insert_text("{}" + dashes) | |
|
83 | event.current_buffer.cursor_left(len(dashes) + 1) | |
|
84 | ||
|
85 | ||
|
86 | def skip_over(event: KeyPressEvent): | |
|
87 | """Skip over automatically added parenthesis. | |
|
88 | ||
|
89 | (rather than adding another parenthesis)""" | |
|
90 | event.current_buffer.cursor_right() | |
|
91 | ||
|
92 | ||
|
93 | def delete_pair(event: KeyPressEvent): | |
|
94 | """Delete auto-closed parenthesis""" | |
|
95 | event.current_buffer.delete() | |
|
96 | event.current_buffer.delete_before_cursor() | |
|
97 | ||
|
98 | ||
|
99 | auto_match_parens = {"(": parenthesis, "[": brackets, "{": braces} | |
|
100 | auto_match_parens_raw_string = { | |
|
101 | "(": raw_string_parenthesis, | |
|
102 | "[": raw_string_bracket, | |
|
103 | "{": raw_string_braces, | |
|
104 | } |
@@ -0,0 +1,378 b'' | |||
|
1 | import re | |
|
2 | import tokenize | |
|
3 | from io import StringIO | |
|
4 | from typing import Callable, List, Optional, Union, Generator, Tuple, Sequence | |
|
5 | ||
|
6 | from prompt_toolkit.buffer import Buffer | |
|
7 | from prompt_toolkit.key_binding import KeyPressEvent | |
|
8 | from prompt_toolkit.key_binding.bindings import named_commands as nc | |
|
9 | from prompt_toolkit.auto_suggest import AutoSuggestFromHistory, Suggestion | |
|
10 | from prompt_toolkit.document import Document | |
|
11 | from prompt_toolkit.history import History | |
|
12 | from prompt_toolkit.shortcuts import PromptSession | |
|
13 | from prompt_toolkit.layout.processors import ( | |
|
14 | Processor, | |
|
15 | Transformation, | |
|
16 | TransformationInput, | |
|
17 | ) | |
|
18 | ||
|
19 | from IPython.utils.tokenutil import generate_tokens | |
|
20 | ||
|
21 | ||
|
22 | def _get_query(document: Document): | |
|
23 | return document.lines[document.cursor_position_row] | |
|
24 | ||
|
25 | ||
|
26 | class AppendAutoSuggestionInAnyLine(Processor): | |
|
27 | """ | |
|
28 | Append the auto suggestion to lines other than the last (appending to the | |
|
29 | last line is natively supported by the prompt toolkit). | |
|
30 | """ | |
|
31 | ||
|
32 | def __init__(self, style: str = "class:auto-suggestion") -> None: | |
|
33 | self.style = style | |
|
34 | ||
|
35 | def apply_transformation(self, ti: TransformationInput) -> Transformation: | |
|
36 | is_last_line = ti.lineno == ti.document.line_count - 1 | |
|
37 | is_active_line = ti.lineno == ti.document.cursor_position_row | |
|
38 | ||
|
39 | if not is_last_line and is_active_line: | |
|
40 | buffer = ti.buffer_control.buffer | |
|
41 | ||
|
42 | if buffer.suggestion and ti.document.is_cursor_at_the_end_of_line: | |
|
43 | suggestion = buffer.suggestion.text | |
|
44 | else: | |
|
45 | suggestion = "" | |
|
46 | ||
|
47 | return Transformation(fragments=ti.fragments + [(self.style, suggestion)]) | |
|
48 | else: | |
|
49 | return Transformation(fragments=ti.fragments) | |
|
50 | ||
|
51 | ||
|
52 | class NavigableAutoSuggestFromHistory(AutoSuggestFromHistory): | |
|
53 | """ | |
|
54 | A subclass of AutoSuggestFromHistory that allow navigation to next/previous | |
|
55 | suggestion from history. To do so it remembers the current position, but it | |
|
56 | state need to carefully be cleared on the right events. | |
|
57 | """ | |
|
58 | ||
|
59 | def __init__( | |
|
60 | self, | |
|
61 | ): | |
|
62 | self.skip_lines = 0 | |
|
63 | self._connected_apps = [] | |
|
64 | ||
|
65 | def reset_history_position(self, _: Buffer): | |
|
66 | self.skip_lines = 0 | |
|
67 | ||
|
68 | def disconnect(self): | |
|
69 | for pt_app in self._connected_apps: | |
|
70 | text_insert_event = pt_app.default_buffer.on_text_insert | |
|
71 | text_insert_event.remove_handler(self.reset_history_position) | |
|
72 | ||
|
73 | def connect(self, pt_app: PromptSession): | |
|
74 | self._connected_apps.append(pt_app) | |
|
75 | # note: `on_text_changed` could be used for a bit different behaviour | |
|
76 | # on character deletion (i.e. reseting history position on backspace) | |
|
77 | pt_app.default_buffer.on_text_insert.add_handler(self.reset_history_position) | |
|
78 | pt_app.default_buffer.on_cursor_position_changed.add_handler(self._dismiss) | |
|
79 | ||
|
80 | def get_suggestion( | |
|
81 | self, buffer: Buffer, document: Document | |
|
82 | ) -> Optional[Suggestion]: | |
|
83 | text = _get_query(document) | |
|
84 | ||
|
85 | if text.strip(): | |
|
86 | for suggestion, _ in self._find_next_match( | |
|
87 | text, self.skip_lines, buffer.history | |
|
88 | ): | |
|
89 | return Suggestion(suggestion) | |
|
90 | ||
|
91 | return None | |
|
92 | ||
|
93 | def _dismiss(self, buffer, *args, **kwargs): | |
|
94 | buffer.suggestion = None | |
|
95 | ||
|
96 | def _find_match( | |
|
97 | self, text: str, skip_lines: float, history: History, previous: bool | |
|
98 | ) -> Generator[Tuple[str, float], None, None]: | |
|
99 | """ | |
|
100 | text : str | |
|
101 | Text content to find a match for, the user cursor is most of the | |
|
102 | time at the end of this text. | |
|
103 | skip_lines : float | |
|
104 | number of items to skip in the search, this is used to indicate how | |
|
105 | far in the list the user has navigated by pressing up or down. | |
|
106 | The float type is used as the base value is +inf | |
|
107 | history : History | |
|
108 | prompt_toolkit History instance to fetch previous entries from. | |
|
109 | previous : bool | |
|
110 | Direction of the search, whether we are looking previous match | |
|
111 | (True), or next match (False). | |
|
112 | ||
|
113 | Yields | |
|
114 | ------ | |
|
115 | Tuple with: | |
|
116 | str: | |
|
117 | current suggestion. | |
|
118 | float: | |
|
119 | will actually yield only ints, which is passed back via skip_lines, | |
|
120 | which may be a +inf (float) | |
|
121 | ||
|
122 | ||
|
123 | """ | |
|
124 | line_number = -1 | |
|
125 | for string in reversed(list(history.get_strings())): | |
|
126 | for line in reversed(string.splitlines()): | |
|
127 | line_number += 1 | |
|
128 | if not previous and line_number < skip_lines: | |
|
129 | continue | |
|
130 | # do not return empty suggestions as these | |
|
131 | # close the auto-suggestion overlay (and are useless) | |
|
132 | if line.startswith(text) and len(line) > len(text): | |
|
133 | yield line[len(text) :], line_number | |
|
134 | if previous and line_number >= skip_lines: | |
|
135 | return | |
|
136 | ||
|
137 | def _find_next_match( | |
|
138 | self, text: str, skip_lines: float, history: History | |
|
139 | ) -> Generator[Tuple[str, float], None, None]: | |
|
140 | return self._find_match(text, skip_lines, history, previous=False) | |
|
141 | ||
|
142 | def _find_previous_match(self, text: str, skip_lines: float, history: History): | |
|
143 | return reversed( | |
|
144 | list(self._find_match(text, skip_lines, history, previous=True)) | |
|
145 | ) | |
|
146 | ||
|
147 | def up(self, query: str, other_than: str, history: History) -> None: | |
|
148 | for suggestion, line_number in self._find_next_match( | |
|
149 | query, self.skip_lines, history | |
|
150 | ): | |
|
151 | # if user has history ['very.a', 'very', 'very.b'] and typed 'very' | |
|
152 | # we want to switch from 'very.b' to 'very.a' because a) if the | |
|
153 | # suggestion equals current text, prompt-toolkit aborts suggesting | |
|
154 | # b) user likely would not be interested in 'very' anyways (they | |
|
155 | # already typed it). | |
|
156 | if query + suggestion != other_than: | |
|
157 | self.skip_lines = line_number | |
|
158 | break | |
|
159 | else: | |
|
160 | # no matches found, cycle back to beginning | |
|
161 | self.skip_lines = 0 | |
|
162 | ||
|
163 | def down(self, query: str, other_than: str, history: History) -> None: | |
|
164 | for suggestion, line_number in self._find_previous_match( | |
|
165 | query, self.skip_lines, history | |
|
166 | ): | |
|
167 | if query + suggestion != other_than: | |
|
168 | self.skip_lines = line_number | |
|
169 | break | |
|
170 | else: | |
|
171 | # no matches found, cycle to end | |
|
172 | for suggestion, line_number in self._find_previous_match( | |
|
173 | query, float("Inf"), history | |
|
174 | ): | |
|
175 | if query + suggestion != other_than: | |
|
176 | self.skip_lines = line_number | |
|
177 | break | |
|
178 | ||
|
179 | ||
|
180 | # Needed for to accept autosuggestions in vi insert mode | |
|
181 | def accept_in_vi_insert_mode(event: KeyPressEvent): | |
|
182 | """Apply autosuggestion if at end of line.""" | |
|
183 | buffer = event.current_buffer | |
|
184 | d = buffer.document | |
|
185 | after_cursor = d.text[d.cursor_position :] | |
|
186 | lines = after_cursor.split("\n") | |
|
187 | end_of_current_line = lines[0].strip() | |
|
188 | suggestion = buffer.suggestion | |
|
189 | if (suggestion is not None) and (suggestion.text) and (end_of_current_line == ""): | |
|
190 | buffer.insert_text(suggestion.text) | |
|
191 | else: | |
|
192 | nc.end_of_line(event) | |
|
193 | ||
|
194 | ||
|
195 | def accept(event: KeyPressEvent): | |
|
196 | """Accept autosuggestion""" | |
|
197 | buffer = event.current_buffer | |
|
198 | suggestion = buffer.suggestion | |
|
199 | if suggestion: | |
|
200 | buffer.insert_text(suggestion.text) | |
|
201 | else: | |
|
202 | nc.forward_char(event) | |
|
203 | ||
|
204 | ||
|
205 | def discard(event: KeyPressEvent): | |
|
206 | """Discard autosuggestion""" | |
|
207 | buffer = event.current_buffer | |
|
208 | buffer.suggestion = None | |
|
209 | ||
|
210 | ||
|
211 | def accept_word(event: KeyPressEvent): | |
|
212 | """Fill partial autosuggestion by word""" | |
|
213 | buffer = event.current_buffer | |
|
214 | suggestion = buffer.suggestion | |
|
215 | if suggestion: | |
|
216 | t = re.split(r"(\S+\s+)", suggestion.text) | |
|
217 | buffer.insert_text(next((x for x in t if x), "")) | |
|
218 | else: | |
|
219 | nc.forward_word(event) | |
|
220 | ||
|
221 | ||
|
222 | def accept_character(event: KeyPressEvent): | |
|
223 | """Fill partial autosuggestion by character""" | |
|
224 | b = event.current_buffer | |
|
225 | suggestion = b.suggestion | |
|
226 | if suggestion and suggestion.text: | |
|
227 | b.insert_text(suggestion.text[0]) | |
|
228 | ||
|
229 | ||
|
230 | def accept_and_keep_cursor(event: KeyPressEvent): | |
|
231 | """Accept autosuggestion and keep cursor in place""" | |
|
232 | buffer = event.current_buffer | |
|
233 | old_position = buffer.cursor_position | |
|
234 | suggestion = buffer.suggestion | |
|
235 | if suggestion: | |
|
236 | buffer.insert_text(suggestion.text) | |
|
237 | buffer.cursor_position = old_position | |
|
238 | ||
|
239 | ||
|
240 | def accept_and_move_cursor_left(event: KeyPressEvent): | |
|
241 | """Accept autosuggestion and move cursor left in place""" | |
|
242 | accept_and_keep_cursor(event) | |
|
243 | nc.backward_char(event) | |
|
244 | ||
|
245 | ||
|
246 | def _update_hint(buffer: Buffer): | |
|
247 | if buffer.auto_suggest: | |
|
248 | suggestion = buffer.auto_suggest.get_suggestion(buffer, buffer.document) | |
|
249 | buffer.suggestion = suggestion | |
|
250 | ||
|
251 | ||
|
252 | def backspace_and_resume_hint(event: KeyPressEvent): | |
|
253 | """Resume autosuggestions after deleting last character""" | |
|
254 | current_buffer = event.current_buffer | |
|
255 | ||
|
256 | def resume_hinting(buffer: Buffer): | |
|
257 | _update_hint(buffer) | |
|
258 | current_buffer.on_text_changed.remove_handler(resume_hinting) | |
|
259 | ||
|
260 | current_buffer.on_text_changed.add_handler(resume_hinting) | |
|
261 | nc.backward_delete_char(event) | |
|
262 | ||
|
263 | ||
|
264 | def up_and_update_hint(event: KeyPressEvent): | |
|
265 | """Go up and update hint""" | |
|
266 | current_buffer = event.current_buffer | |
|
267 | ||
|
268 | current_buffer.auto_up(count=event.arg) | |
|
269 | _update_hint(current_buffer) | |
|
270 | ||
|
271 | ||
|
272 | def down_and_update_hint(event: KeyPressEvent): | |
|
273 | """Go down and update hint""" | |
|
274 | current_buffer = event.current_buffer | |
|
275 | ||
|
276 | current_buffer.auto_down(count=event.arg) | |
|
277 | _update_hint(current_buffer) | |
|
278 | ||
|
279 | ||
|
280 | def accept_token(event: KeyPressEvent): | |
|
281 | """Fill partial autosuggestion by token""" | |
|
282 | b = event.current_buffer | |
|
283 | suggestion = b.suggestion | |
|
284 | ||
|
285 | if suggestion: | |
|
286 | prefix = _get_query(b.document) | |
|
287 | text = prefix + suggestion.text | |
|
288 | ||
|
289 | tokens: List[Optional[str]] = [None, None, None] | |
|
290 | substrings = [""] | |
|
291 | i = 0 | |
|
292 | ||
|
293 | for token in generate_tokens(StringIO(text).readline): | |
|
294 | if token.type == tokenize.NEWLINE: | |
|
295 | index = len(text) | |
|
296 | else: | |
|
297 | index = text.index(token[1], len(substrings[-1])) | |
|
298 | substrings.append(text[:index]) | |
|
299 | tokenized_so_far = substrings[-1] | |
|
300 | if tokenized_so_far.startswith(prefix): | |
|
301 | if i == 0 and len(tokenized_so_far) > len(prefix): | |
|
302 | tokens[0] = tokenized_so_far[len(prefix) :] | |
|
303 | substrings.append(tokenized_so_far) | |
|
304 | i += 1 | |
|
305 | tokens[i] = token[1] | |
|
306 | if i == 2: | |
|
307 | break | |
|
308 | i += 1 | |
|
309 | ||
|
310 | if tokens[0]: | |
|
311 | to_insert: str | |
|
312 | insert_text = substrings[-2] | |
|
313 | if tokens[1] and len(tokens[1]) == 1: | |
|
314 | insert_text = substrings[-1] | |
|
315 | to_insert = insert_text[len(prefix) :] | |
|
316 | b.insert_text(to_insert) | |
|
317 | return | |
|
318 | ||
|
319 | nc.forward_word(event) | |
|
320 | ||
|
321 | ||
|
322 | Provider = Union[AutoSuggestFromHistory, NavigableAutoSuggestFromHistory, None] | |
|
323 | ||
|
324 | ||
|
325 | def _swap_autosuggestion( | |
|
326 | buffer: Buffer, | |
|
327 | provider: NavigableAutoSuggestFromHistory, | |
|
328 | direction_method: Callable, | |
|
329 | ): | |
|
330 | """ | |
|
331 | We skip most recent history entry (in either direction) if it equals the | |
|
332 | current autosuggestion because if user cycles when auto-suggestion is shown | |
|
333 | they most likely want something else than what was suggested (otherwise | |
|
334 | they would have accepted the suggestion). | |
|
335 | """ | |
|
336 | suggestion = buffer.suggestion | |
|
337 | if not suggestion: | |
|
338 | return | |
|
339 | ||
|
340 | query = _get_query(buffer.document) | |
|
341 | current = query + suggestion.text | |
|
342 | ||
|
343 | direction_method(query=query, other_than=current, history=buffer.history) | |
|
344 | ||
|
345 | new_suggestion = provider.get_suggestion(buffer, buffer.document) | |
|
346 | buffer.suggestion = new_suggestion | |
|
347 | ||
|
348 | ||
|
349 | def swap_autosuggestion_up(provider: Provider): | |
|
350 | def swap_autosuggestion_up(event: KeyPressEvent): | |
|
351 | """Get next autosuggestion from history.""" | |
|
352 | if not isinstance(provider, NavigableAutoSuggestFromHistory): | |
|
353 | return | |
|
354 | ||
|
355 | return _swap_autosuggestion( | |
|
356 | buffer=event.current_buffer, provider=provider, direction_method=provider.up | |
|
357 | ) | |
|
358 | ||
|
359 | swap_autosuggestion_up.__name__ = "swap_autosuggestion_up" | |
|
360 | return swap_autosuggestion_up | |
|
361 | ||
|
362 | ||
|
363 | def swap_autosuggestion_down( | |
|
364 | provider: Union[AutoSuggestFromHistory, NavigableAutoSuggestFromHistory, None] | |
|
365 | ): | |
|
366 | def swap_autosuggestion_down(event: KeyPressEvent): | |
|
367 | """Get previous autosuggestion from history.""" | |
|
368 | if not isinstance(provider, NavigableAutoSuggestFromHistory): | |
|
369 | return | |
|
370 | ||
|
371 | return _swap_autosuggestion( | |
|
372 | buffer=event.current_buffer, | |
|
373 | provider=provider, | |
|
374 | direction_method=provider.down, | |
|
375 | ) | |
|
376 | ||
|
377 | swap_autosuggestion_down.__name__ = "swap_autosuggestion_down" | |
|
378 | return swap_autosuggestion_down |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: new file 100644, binary diff hidden |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
@@ -1,39 +1,41 b'' | |||
|
1 | 1 | name: Run MyPy |
|
2 | 2 | |
|
3 | 3 | on: |
|
4 | 4 | push: |
|
5 | 5 | branches: [ main, 7.x] |
|
6 | 6 | pull_request: |
|
7 | 7 | branches: [ main, 7.x] |
|
8 | 8 | |
|
9 | 9 | permissions: |
|
10 | 10 | contents: read |
|
11 | 11 | |
|
12 | 12 | jobs: |
|
13 | 13 | build: |
|
14 | 14 | |
|
15 | 15 | runs-on: ubuntu-latest |
|
16 | 16 | strategy: |
|
17 | 17 | matrix: |
|
18 | 18 | python-version: ["3.x"] |
|
19 | 19 | |
|
20 | 20 | steps: |
|
21 | 21 | - uses: actions/checkout@v3 |
|
22 | 22 | - name: Set up Python ${{ matrix.python-version }} |
|
23 | 23 | uses: actions/setup-python@v4 |
|
24 | 24 | with: |
|
25 | 25 | python-version: ${{ matrix.python-version }} |
|
26 | 26 | - name: Install dependencies |
|
27 | 27 | run: | |
|
28 | 28 | python -m pip install --upgrade pip |
|
29 | 29 | pip install mypy pyflakes flake8 |
|
30 | 30 | - name: Lint with mypy |
|
31 | 31 | run: | |
|
32 | set -e | |
|
32 | 33 | mypy -p IPython.terminal |
|
33 | 34 | mypy -p IPython.core.magics |
|
34 | 35 | mypy -p IPython.core.guarded_eval |
|
35 | 36 | mypy -p IPython.core.completer |
|
36 | 37 | - name: Lint with pyflakes |
|
37 | 38 | run: | |
|
39 | set -e | |
|
38 | 40 | flake8 IPython/core/magics/script.py |
|
39 | 41 | flake8 IPython/core/magics/packaging.py |
@@ -1,489 +1,488 b'' | |||
|
1 | 1 | # encoding: utf-8 |
|
2 | 2 | """ |
|
3 | 3 | An application for IPython. |
|
4 | 4 | |
|
5 | 5 | All top-level applications should use the classes in this module for |
|
6 | 6 | handling configuration and creating configurables. |
|
7 | 7 | |
|
8 | 8 | The job of an :class:`Application` is to create the master configuration |
|
9 | 9 | object and then create the configurable objects, passing the config to them. |
|
10 | 10 | """ |
|
11 | 11 | |
|
12 | 12 | # Copyright (c) IPython Development Team. |
|
13 | 13 | # Distributed under the terms of the Modified BSD License. |
|
14 | 14 | |
|
15 | 15 | import atexit |
|
16 | 16 | from copy import deepcopy |
|
17 | 17 | import logging |
|
18 | 18 | import os |
|
19 | 19 | import shutil |
|
20 | 20 | import sys |
|
21 | 21 | |
|
22 | 22 | from pathlib import Path |
|
23 | 23 | |
|
24 | 24 | from traitlets.config.application import Application, catch_config_error |
|
25 | 25 | from traitlets.config.loader import ConfigFileNotFound, PyFileConfigLoader |
|
26 | 26 | from IPython.core import release, crashhandler |
|
27 | 27 | from IPython.core.profiledir import ProfileDir, ProfileDirError |
|
28 | 28 | from IPython.paths import get_ipython_dir, get_ipython_package_dir |
|
29 | 29 | from IPython.utils.path import ensure_dir_exists |
|
30 | 30 | from traitlets import ( |
|
31 | 31 | List, Unicode, Type, Bool, Set, Instance, Undefined, |
|
32 | 32 | default, observe, |
|
33 | 33 | ) |
|
34 | 34 | |
|
35 | 35 | if os.name == "nt": |
|
36 | 36 | programdata = os.environ.get("PROGRAMDATA", None) |
|
37 | 37 | if programdata is not None: |
|
38 | 38 | SYSTEM_CONFIG_DIRS = [str(Path(programdata) / "ipython")] |
|
39 | 39 | else: # PROGRAMDATA is not defined by default on XP. |
|
40 | 40 | SYSTEM_CONFIG_DIRS = [] |
|
41 | 41 | else: |
|
42 | 42 | SYSTEM_CONFIG_DIRS = [ |
|
43 | 43 | "/usr/local/etc/ipython", |
|
44 | 44 | "/etc/ipython", |
|
45 | 45 | ] |
|
46 | 46 | |
|
47 | 47 | |
|
48 | 48 | ENV_CONFIG_DIRS = [] |
|
49 | 49 | _env_config_dir = os.path.join(sys.prefix, 'etc', 'ipython') |
|
50 | 50 | if _env_config_dir not in SYSTEM_CONFIG_DIRS: |
|
51 | 51 | # only add ENV_CONFIG if sys.prefix is not already included |
|
52 | 52 | ENV_CONFIG_DIRS.append(_env_config_dir) |
|
53 | 53 | |
|
54 | 54 | |
|
55 | 55 | _envvar = os.environ.get('IPYTHON_SUPPRESS_CONFIG_ERRORS') |
|
56 | 56 | if _envvar in {None, ''}: |
|
57 | 57 | IPYTHON_SUPPRESS_CONFIG_ERRORS = None |
|
58 | 58 | else: |
|
59 | 59 | if _envvar.lower() in {'1','true'}: |
|
60 | 60 | IPYTHON_SUPPRESS_CONFIG_ERRORS = True |
|
61 | 61 | elif _envvar.lower() in {'0','false'} : |
|
62 | 62 | IPYTHON_SUPPRESS_CONFIG_ERRORS = False |
|
63 | 63 | else: |
|
64 | 64 | sys.exit("Unsupported value for environment variable: 'IPYTHON_SUPPRESS_CONFIG_ERRORS' is set to '%s' which is none of {'0', '1', 'false', 'true', ''}."% _envvar ) |
|
65 | 65 | |
|
66 | 66 | # aliases and flags |
|
67 | 67 | |
|
68 | 68 | base_aliases = {} |
|
69 | 69 | if isinstance(Application.aliases, dict): |
|
70 | 70 | # traitlets 5 |
|
71 | 71 | base_aliases.update(Application.aliases) |
|
72 | 72 | base_aliases.update( |
|
73 | 73 | { |
|
74 | 74 | "profile-dir": "ProfileDir.location", |
|
75 | 75 | "profile": "BaseIPythonApplication.profile", |
|
76 | 76 | "ipython-dir": "BaseIPythonApplication.ipython_dir", |
|
77 | 77 | "log-level": "Application.log_level", |
|
78 | 78 | "config": "BaseIPythonApplication.extra_config_file", |
|
79 | 79 | } |
|
80 | 80 | ) |
|
81 | 81 | |
|
82 | 82 | base_flags = dict() |
|
83 | 83 | if isinstance(Application.flags, dict): |
|
84 | 84 | # traitlets 5 |
|
85 | 85 | base_flags.update(Application.flags) |
|
86 | 86 | base_flags.update( |
|
87 | 87 | dict( |
|
88 | 88 | debug=( |
|
89 | 89 | {"Application": {"log_level": logging.DEBUG}}, |
|
90 | 90 | "set log level to logging.DEBUG (maximize logging output)", |
|
91 | 91 | ), |
|
92 | 92 | quiet=( |
|
93 | 93 | {"Application": {"log_level": logging.CRITICAL}}, |
|
94 | 94 | "set log level to logging.CRITICAL (minimize logging output)", |
|
95 | 95 | ), |
|
96 | 96 | init=( |
|
97 | 97 | { |
|
98 | 98 | "BaseIPythonApplication": { |
|
99 | 99 | "copy_config_files": True, |
|
100 | 100 | "auto_create": True, |
|
101 | 101 | } |
|
102 | 102 | }, |
|
103 | 103 | """Initialize profile with default config files. This is equivalent |
|
104 | 104 | to running `ipython profile create <profile>` prior to startup. |
|
105 | 105 | """, |
|
106 | 106 | ), |
|
107 | 107 | ) |
|
108 | 108 | ) |
|
109 | 109 | |
|
110 | 110 | |
|
111 | 111 | class ProfileAwareConfigLoader(PyFileConfigLoader): |
|
112 | 112 | """A Python file config loader that is aware of IPython profiles.""" |
|
113 | 113 | def load_subconfig(self, fname, path=None, profile=None): |
|
114 | 114 | if profile is not None: |
|
115 | 115 | try: |
|
116 | 116 | profile_dir = ProfileDir.find_profile_dir_by_name( |
|
117 | 117 | get_ipython_dir(), |
|
118 | 118 | profile, |
|
119 | 119 | ) |
|
120 | 120 | except ProfileDirError: |
|
121 | 121 | return |
|
122 | 122 | path = profile_dir.location |
|
123 | 123 | return super(ProfileAwareConfigLoader, self).load_subconfig(fname, path=path) |
|
124 | 124 | |
|
125 | 125 | class BaseIPythonApplication(Application): |
|
126 | ||
|
127 | name = u'ipython' | |
|
128 | description = Unicode(u'IPython: an enhanced interactive Python shell.') | |
|
126 | name = "ipython" | |
|
127 | description = "IPython: an enhanced interactive Python shell." | |
|
129 | 128 | version = Unicode(release.version) |
|
130 | 129 | |
|
131 | 130 | aliases = base_aliases |
|
132 | 131 | flags = base_flags |
|
133 | 132 | classes = List([ProfileDir]) |
|
134 | 133 | |
|
135 | 134 | # enable `load_subconfig('cfg.py', profile='name')` |
|
136 | 135 | python_config_loader_class = ProfileAwareConfigLoader |
|
137 | 136 | |
|
138 | 137 | # Track whether the config_file has changed, |
|
139 | 138 | # because some logic happens only if we aren't using the default. |
|
140 | 139 | config_file_specified = Set() |
|
141 | 140 | |
|
142 | 141 | config_file_name = Unicode() |
|
143 | 142 | @default('config_file_name') |
|
144 | 143 | def _config_file_name_default(self): |
|
145 | 144 | return self.name.replace('-','_') + u'_config.py' |
|
146 | 145 | @observe('config_file_name') |
|
147 | 146 | def _config_file_name_changed(self, change): |
|
148 | 147 | if change['new'] != change['old']: |
|
149 | 148 | self.config_file_specified.add(change['new']) |
|
150 | 149 | |
|
151 | 150 | # The directory that contains IPython's builtin profiles. |
|
152 | 151 | builtin_profile_dir = Unicode( |
|
153 | 152 | os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default') |
|
154 | 153 | ) |
|
155 | 154 | |
|
156 | 155 | config_file_paths = List(Unicode()) |
|
157 | 156 | @default('config_file_paths') |
|
158 | 157 | def _config_file_paths_default(self): |
|
159 | 158 | return [] |
|
160 | 159 | |
|
161 | 160 | extra_config_file = Unicode( |
|
162 | 161 | help="""Path to an extra config file to load. |
|
163 | 162 | |
|
164 | 163 | If specified, load this config file in addition to any other IPython config. |
|
165 | 164 | """).tag(config=True) |
|
166 | 165 | @observe('extra_config_file') |
|
167 | 166 | def _extra_config_file_changed(self, change): |
|
168 | 167 | old = change['old'] |
|
169 | 168 | new = change['new'] |
|
170 | 169 | try: |
|
171 | 170 | self.config_files.remove(old) |
|
172 | 171 | except ValueError: |
|
173 | 172 | pass |
|
174 | 173 | self.config_file_specified.add(new) |
|
175 | 174 | self.config_files.append(new) |
|
176 | 175 | |
|
177 | 176 | profile = Unicode(u'default', |
|
178 | 177 | help="""The IPython profile to use.""" |
|
179 | 178 | ).tag(config=True) |
|
180 | 179 | |
|
181 | 180 | @observe('profile') |
|
182 | 181 | def _profile_changed(self, change): |
|
183 | 182 | self.builtin_profile_dir = os.path.join( |
|
184 | 183 | get_ipython_package_dir(), u'config', u'profile', change['new'] |
|
185 | 184 | ) |
|
186 | 185 | |
|
187 | 186 | add_ipython_dir_to_sys_path = Bool( |
|
188 | 187 | False, |
|
189 | 188 | """Should the IPython profile directory be added to sys path ? |
|
190 | 189 | |
|
191 | 190 | This option was non-existing before IPython 8.0, and ipython_dir was added to |
|
192 | 191 | sys path to allow import of extensions present there. This was historical |
|
193 | 192 | baggage from when pip did not exist. This now default to false, |
|
194 | 193 | but can be set to true for legacy reasons. |
|
195 | 194 | """, |
|
196 | 195 | ).tag(config=True) |
|
197 | 196 | |
|
198 | 197 | ipython_dir = Unicode( |
|
199 | 198 | help=""" |
|
200 | 199 | The name of the IPython directory. This directory is used for logging |
|
201 | 200 | configuration (through profiles), history storage, etc. The default |
|
202 | 201 | is usually $HOME/.ipython. This option can also be specified through |
|
203 | 202 | the environment variable IPYTHONDIR. |
|
204 | 203 | """ |
|
205 | 204 | ).tag(config=True) |
|
206 | 205 | @default('ipython_dir') |
|
207 | 206 | def _ipython_dir_default(self): |
|
208 | 207 | d = get_ipython_dir() |
|
209 | 208 | self._ipython_dir_changed({ |
|
210 | 209 | 'name': 'ipython_dir', |
|
211 | 210 | 'old': d, |
|
212 | 211 | 'new': d, |
|
213 | 212 | }) |
|
214 | 213 | return d |
|
215 | 214 | |
|
216 | 215 | _in_init_profile_dir = False |
|
217 | 216 | profile_dir = Instance(ProfileDir, allow_none=True) |
|
218 | 217 | @default('profile_dir') |
|
219 | 218 | def _profile_dir_default(self): |
|
220 | 219 | # avoid recursion |
|
221 | 220 | if self._in_init_profile_dir: |
|
222 | 221 | return |
|
223 | 222 | # profile_dir requested early, force initialization |
|
224 | 223 | self.init_profile_dir() |
|
225 | 224 | return self.profile_dir |
|
226 | 225 | |
|
227 | 226 | overwrite = Bool(False, |
|
228 | 227 | help="""Whether to overwrite existing config files when copying""" |
|
229 | 228 | ).tag(config=True) |
|
230 | 229 | auto_create = Bool(False, |
|
231 | 230 | help="""Whether to create profile dir if it doesn't exist""" |
|
232 | 231 | ).tag(config=True) |
|
233 | 232 | |
|
234 | 233 | config_files = List(Unicode()) |
|
235 | 234 | @default('config_files') |
|
236 | 235 | def _config_files_default(self): |
|
237 | 236 | return [self.config_file_name] |
|
238 | 237 | |
|
239 | 238 | copy_config_files = Bool(False, |
|
240 | 239 | help="""Whether to install the default config files into the profile dir. |
|
241 | 240 | If a new profile is being created, and IPython contains config files for that |
|
242 | 241 | profile, then they will be staged into the new directory. Otherwise, |
|
243 | 242 | default config files will be automatically generated. |
|
244 | 243 | """).tag(config=True) |
|
245 | 244 | |
|
246 | 245 | verbose_crash = Bool(False, |
|
247 | 246 | help="""Create a massive crash report when IPython encounters what may be an |
|
248 | 247 | internal error. The default is to append a short message to the |
|
249 | 248 | usual traceback""").tag(config=True) |
|
250 | 249 | |
|
251 | 250 | # The class to use as the crash handler. |
|
252 | 251 | crash_handler_class = Type(crashhandler.CrashHandler) |
|
253 | 252 | |
|
254 | 253 | @catch_config_error |
|
255 | 254 | def __init__(self, **kwargs): |
|
256 | 255 | super(BaseIPythonApplication, self).__init__(**kwargs) |
|
257 | 256 | # ensure current working directory exists |
|
258 | 257 | try: |
|
259 | 258 | os.getcwd() |
|
260 | 259 | except: |
|
261 | 260 | # exit if cwd doesn't exist |
|
262 | 261 | self.log.error("Current working directory doesn't exist.") |
|
263 | 262 | self.exit(1) |
|
264 | 263 | |
|
265 | 264 | #------------------------------------------------------------------------- |
|
266 | 265 | # Various stages of Application creation |
|
267 | 266 | #------------------------------------------------------------------------- |
|
268 | 267 | |
|
269 | 268 | def init_crash_handler(self): |
|
270 | 269 | """Create a crash handler, typically setting sys.excepthook to it.""" |
|
271 | 270 | self.crash_handler = self.crash_handler_class(self) |
|
272 | 271 | sys.excepthook = self.excepthook |
|
273 | 272 | def unset_crashhandler(): |
|
274 | 273 | sys.excepthook = sys.__excepthook__ |
|
275 | 274 | atexit.register(unset_crashhandler) |
|
276 | 275 | |
|
277 | 276 | def excepthook(self, etype, evalue, tb): |
|
278 | 277 | """this is sys.excepthook after init_crashhandler |
|
279 | 278 | |
|
280 | 279 | set self.verbose_crash=True to use our full crashhandler, instead of |
|
281 | 280 | a regular traceback with a short message (crash_handler_lite) |
|
282 | 281 | """ |
|
283 | 282 | |
|
284 | 283 | if self.verbose_crash: |
|
285 | 284 | return self.crash_handler(etype, evalue, tb) |
|
286 | 285 | else: |
|
287 | 286 | return crashhandler.crash_handler_lite(etype, evalue, tb) |
|
288 | 287 | |
|
289 | 288 | @observe('ipython_dir') |
|
290 | 289 | def _ipython_dir_changed(self, change): |
|
291 | 290 | old = change['old'] |
|
292 | 291 | new = change['new'] |
|
293 | 292 | if old is not Undefined: |
|
294 | 293 | str_old = os.path.abspath(old) |
|
295 | 294 | if str_old in sys.path: |
|
296 | 295 | sys.path.remove(str_old) |
|
297 | 296 | if self.add_ipython_dir_to_sys_path: |
|
298 | 297 | str_path = os.path.abspath(new) |
|
299 | 298 | sys.path.append(str_path) |
|
300 | 299 | ensure_dir_exists(new) |
|
301 | 300 | readme = os.path.join(new, "README") |
|
302 | 301 | readme_src = os.path.join( |
|
303 | 302 | get_ipython_package_dir(), "config", "profile", "README" |
|
304 | 303 | ) |
|
305 | 304 | if not os.path.exists(readme) and os.path.exists(readme_src): |
|
306 | 305 | shutil.copy(readme_src, readme) |
|
307 | 306 | for d in ("extensions", "nbextensions"): |
|
308 | 307 | path = os.path.join(new, d) |
|
309 | 308 | try: |
|
310 | 309 | ensure_dir_exists(path) |
|
311 | 310 | except OSError as e: |
|
312 | 311 | # this will not be EEXIST |
|
313 | 312 | self.log.error("couldn't create path %s: %s", path, e) |
|
314 |
self.log.debug("IPYTHONDIR set to: %s" |
|
|
313 | self.log.debug("IPYTHONDIR set to: %s", new) | |
|
315 | 314 | |
|
316 | 315 | def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS): |
|
317 | 316 | """Load the config file. |
|
318 | 317 | |
|
319 | 318 | By default, errors in loading config are handled, and a warning |
|
320 | 319 | printed on screen. For testing, the suppress_errors option is set |
|
321 | 320 | to False, so errors will make tests fail. |
|
322 | 321 | |
|
323 | 322 | `suppress_errors` default value is to be `None` in which case the |
|
324 | 323 | behavior default to the one of `traitlets.Application`. |
|
325 | 324 | |
|
326 | 325 | The default value can be set : |
|
327 | 326 | - to `False` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '0', or 'false' (case insensitive). |
|
328 | 327 | - to `True` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '1' or 'true' (case insensitive). |
|
329 | 328 | - to `None` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '' (empty string) or leaving it unset. |
|
330 | 329 | |
|
331 | 330 | Any other value are invalid, and will make IPython exit with a non-zero return code. |
|
332 | 331 | """ |
|
333 | 332 | |
|
334 | 333 | |
|
335 | 334 | self.log.debug("Searching path %s for config files", self.config_file_paths) |
|
336 | 335 | base_config = 'ipython_config.py' |
|
337 | 336 | self.log.debug("Attempting to load config file: %s" % |
|
338 | 337 | base_config) |
|
339 | 338 | try: |
|
340 | 339 | if suppress_errors is not None: |
|
341 | 340 | old_value = Application.raise_config_file_errors |
|
342 | 341 | Application.raise_config_file_errors = not suppress_errors; |
|
343 | 342 | Application.load_config_file( |
|
344 | 343 | self, |
|
345 | 344 | base_config, |
|
346 | 345 | path=self.config_file_paths |
|
347 | 346 | ) |
|
348 | 347 | except ConfigFileNotFound: |
|
349 | 348 | # ignore errors loading parent |
|
350 | 349 | self.log.debug("Config file %s not found", base_config) |
|
351 | 350 | pass |
|
352 | 351 | if suppress_errors is not None: |
|
353 | 352 | Application.raise_config_file_errors = old_value |
|
354 | 353 | |
|
355 | 354 | for config_file_name in self.config_files: |
|
356 | 355 | if not config_file_name or config_file_name == base_config: |
|
357 | 356 | continue |
|
358 | 357 | self.log.debug("Attempting to load config file: %s" % |
|
359 | 358 | self.config_file_name) |
|
360 | 359 | try: |
|
361 | 360 | Application.load_config_file( |
|
362 | 361 | self, |
|
363 | 362 | config_file_name, |
|
364 | 363 | path=self.config_file_paths |
|
365 | 364 | ) |
|
366 | 365 | except ConfigFileNotFound: |
|
367 | 366 | # Only warn if the default config file was NOT being used. |
|
368 | 367 | if config_file_name in self.config_file_specified: |
|
369 | 368 | msg = self.log.warning |
|
370 | 369 | else: |
|
371 | 370 | msg = self.log.debug |
|
372 | 371 | msg("Config file not found, skipping: %s", config_file_name) |
|
373 | 372 | except Exception: |
|
374 | 373 | # For testing purposes. |
|
375 | 374 | if not suppress_errors: |
|
376 | 375 | raise |
|
377 | 376 | self.log.warning("Error loading config file: %s" % |
|
378 | 377 | self.config_file_name, exc_info=True) |
|
379 | 378 | |
|
380 | 379 | def init_profile_dir(self): |
|
381 | 380 | """initialize the profile dir""" |
|
382 | 381 | self._in_init_profile_dir = True |
|
383 | 382 | if self.profile_dir is not None: |
|
384 | 383 | # already ran |
|
385 | 384 | return |
|
386 | 385 | if 'ProfileDir.location' not in self.config: |
|
387 | 386 | # location not specified, find by profile name |
|
388 | 387 | try: |
|
389 | 388 | p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config) |
|
390 | 389 | except ProfileDirError: |
|
391 | 390 | # not found, maybe create it (always create default profile) |
|
392 | 391 | if self.auto_create or self.profile == 'default': |
|
393 | 392 | try: |
|
394 | 393 | p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config) |
|
395 | 394 | except ProfileDirError: |
|
396 | 395 | self.log.fatal("Could not create profile: %r"%self.profile) |
|
397 | 396 | self.exit(1) |
|
398 | 397 | else: |
|
399 | 398 | self.log.info("Created profile dir: %r"%p.location) |
|
400 | 399 | else: |
|
401 | 400 | self.log.fatal("Profile %r not found."%self.profile) |
|
402 | 401 | self.exit(1) |
|
403 | 402 | else: |
|
404 |
self.log.debug( |
|
|
403 | self.log.debug("Using existing profile dir: %r", p.location) | |
|
405 | 404 | else: |
|
406 | 405 | location = self.config.ProfileDir.location |
|
407 | 406 | # location is fully specified |
|
408 | 407 | try: |
|
409 | 408 | p = ProfileDir.find_profile_dir(location, self.config) |
|
410 | 409 | except ProfileDirError: |
|
411 | 410 | # not found, maybe create it |
|
412 | 411 | if self.auto_create: |
|
413 | 412 | try: |
|
414 | 413 | p = ProfileDir.create_profile_dir(location, self.config) |
|
415 | 414 | except ProfileDirError: |
|
416 | 415 | self.log.fatal("Could not create profile directory: %r"%location) |
|
417 | 416 | self.exit(1) |
|
418 | 417 | else: |
|
419 | 418 | self.log.debug("Creating new profile dir: %r"%location) |
|
420 | 419 | else: |
|
421 | 420 | self.log.fatal("Profile directory %r not found."%location) |
|
422 | 421 | self.exit(1) |
|
423 | 422 | else: |
|
424 |
self.log.debug( |
|
|
423 | self.log.debug("Using existing profile dir: %r", p.location) | |
|
425 | 424 | # if profile_dir is specified explicitly, set profile name |
|
426 | 425 | dir_name = os.path.basename(p.location) |
|
427 | 426 | if dir_name.startswith('profile_'): |
|
428 | 427 | self.profile = dir_name[8:] |
|
429 | 428 | |
|
430 | 429 | self.profile_dir = p |
|
431 | 430 | self.config_file_paths.append(p.location) |
|
432 | 431 | self._in_init_profile_dir = False |
|
433 | 432 | |
|
434 | 433 | def init_config_files(self): |
|
435 | 434 | """[optionally] copy default config files into profile dir.""" |
|
436 | 435 | self.config_file_paths.extend(ENV_CONFIG_DIRS) |
|
437 | 436 | self.config_file_paths.extend(SYSTEM_CONFIG_DIRS) |
|
438 | 437 | # copy config files |
|
439 | 438 | path = Path(self.builtin_profile_dir) |
|
440 | 439 | if self.copy_config_files: |
|
441 | 440 | src = self.profile |
|
442 | 441 | |
|
443 | 442 | cfg = self.config_file_name |
|
444 | 443 | if path and (path / cfg).exists(): |
|
445 | 444 | self.log.warning( |
|
446 | 445 | "Staging %r from %s into %r [overwrite=%s]" |
|
447 | 446 | % (cfg, src, self.profile_dir.location, self.overwrite) |
|
448 | 447 | ) |
|
449 | 448 | self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite) |
|
450 | 449 | else: |
|
451 | 450 | self.stage_default_config_file() |
|
452 | 451 | else: |
|
453 | 452 | # Still stage *bundled* config files, but not generated ones |
|
454 | 453 | # This is necessary for `ipython profile=sympy` to load the profile |
|
455 | 454 | # on the first go |
|
456 | 455 | files = path.glob("*.py") |
|
457 | 456 | for fullpath in files: |
|
458 | 457 | cfg = fullpath.name |
|
459 | 458 | if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False): |
|
460 | 459 | # file was copied |
|
461 | 460 | self.log.warning("Staging bundled %s from %s into %r"%( |
|
462 | 461 | cfg, self.profile, self.profile_dir.location) |
|
463 | 462 | ) |
|
464 | 463 | |
|
465 | 464 | |
|
466 | 465 | def stage_default_config_file(self): |
|
467 | 466 | """auto generate default config file, and stage it into the profile.""" |
|
468 | 467 | s = self.generate_config_file() |
|
469 | 468 | config_file = Path(self.profile_dir.location) / self.config_file_name |
|
470 | 469 | if self.overwrite or not config_file.exists(): |
|
471 |
self.log.warning("Generating default config file: %r" |
|
|
470 | self.log.warning("Generating default config file: %r", (config_file)) | |
|
472 | 471 | config_file.write_text(s, encoding="utf-8") |
|
473 | 472 | |
|
474 | 473 | @catch_config_error |
|
475 | 474 | def initialize(self, argv=None): |
|
476 | 475 | # don't hook up crash handler before parsing command-line |
|
477 | 476 | self.parse_command_line(argv) |
|
478 | 477 | self.init_crash_handler() |
|
479 | 478 | if self.subapp is not None: |
|
480 | 479 | # stop here if subapp is taking over |
|
481 | 480 | return |
|
482 | 481 | # save a copy of CLI config to re-load after config files |
|
483 | 482 | # so that it has highest priority |
|
484 | 483 | cl_config = deepcopy(self.config) |
|
485 | 484 | self.init_profile_dir() |
|
486 | 485 | self.init_config_files() |
|
487 | 486 | self.load_config_file() |
|
488 | 487 | # enforce cl-opts override configfile opts: |
|
489 | 488 | self.update_config(cl_config) |
@@ -1,325 +1,331 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | """Displayhook for IPython. |
|
3 | 3 | |
|
4 | 4 | This defines a callable class that IPython uses for `sys.displayhook`. |
|
5 | 5 | """ |
|
6 | 6 | |
|
7 | 7 | # Copyright (c) IPython Development Team. |
|
8 | 8 | # Distributed under the terms of the Modified BSD License. |
|
9 | 9 | |
|
10 | 10 | import builtins as builtin_mod |
|
11 | 11 | import sys |
|
12 | 12 | import io as _io |
|
13 | 13 | import tokenize |
|
14 | 14 | |
|
15 | 15 | from traitlets.config.configurable import Configurable |
|
16 | 16 | from traitlets import Instance, Float |
|
17 | 17 | from warnings import warn |
|
18 | 18 | |
|
19 | 19 | # TODO: Move the various attributes (cache_size, [others now moved]). Some |
|
20 | 20 | # of these are also attributes of InteractiveShell. They should be on ONE object |
|
21 | 21 | # only and the other objects should ask that one object for their values. |
|
22 | 22 | |
|
23 | 23 | class DisplayHook(Configurable): |
|
24 | 24 | """The custom IPython displayhook to replace sys.displayhook. |
|
25 | 25 | |
|
26 | 26 | This class does many things, but the basic idea is that it is a callable |
|
27 | 27 | that gets called anytime user code returns a value. |
|
28 | 28 | """ |
|
29 | 29 | |
|
30 | 30 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', |
|
31 | 31 | allow_none=True) |
|
32 | 32 | exec_result = Instance('IPython.core.interactiveshell.ExecutionResult', |
|
33 | 33 | allow_none=True) |
|
34 | 34 | cull_fraction = Float(0.2) |
|
35 | 35 | |
|
36 | 36 | def __init__(self, shell=None, cache_size=1000, **kwargs): |
|
37 | 37 | super(DisplayHook, self).__init__(shell=shell, **kwargs) |
|
38 | 38 | cache_size_min = 3 |
|
39 | 39 | if cache_size <= 0: |
|
40 | 40 | self.do_full_cache = 0 |
|
41 | 41 | cache_size = 0 |
|
42 | 42 | elif cache_size < cache_size_min: |
|
43 | 43 | self.do_full_cache = 0 |
|
44 | 44 | cache_size = 0 |
|
45 | 45 | warn('caching was disabled (min value for cache size is %s).' % |
|
46 | 46 | cache_size_min,stacklevel=3) |
|
47 | 47 | else: |
|
48 | 48 | self.do_full_cache = 1 |
|
49 | 49 | |
|
50 | 50 | self.cache_size = cache_size |
|
51 | 51 | |
|
52 | 52 | # we need a reference to the user-level namespace |
|
53 | 53 | self.shell = shell |
|
54 | 54 | |
|
55 | 55 | self._,self.__,self.___ = '','','' |
|
56 | 56 | |
|
57 | 57 | # these are deliberately global: |
|
58 | 58 | to_user_ns = {'_':self._,'__':self.__,'___':self.___} |
|
59 | 59 | self.shell.user_ns.update(to_user_ns) |
|
60 | 60 | |
|
61 | 61 | @property |
|
62 | 62 | def prompt_count(self): |
|
63 | 63 | return self.shell.execution_count |
|
64 | 64 | |
|
65 | 65 | #------------------------------------------------------------------------- |
|
66 | 66 | # Methods used in __call__. Override these methods to modify the behavior |
|
67 | 67 | # of the displayhook. |
|
68 | 68 | #------------------------------------------------------------------------- |
|
69 | 69 | |
|
70 | 70 | def check_for_underscore(self): |
|
71 | 71 | """Check if the user has set the '_' variable by hand.""" |
|
72 | 72 | # If something injected a '_' variable in __builtin__, delete |
|
73 | 73 | # ipython's automatic one so we don't clobber that. gettext() in |
|
74 | 74 | # particular uses _, so we need to stay away from it. |
|
75 | 75 | if '_' in builtin_mod.__dict__: |
|
76 | 76 | try: |
|
77 | 77 | user_value = self.shell.user_ns['_'] |
|
78 | 78 | if user_value is not self._: |
|
79 | 79 | return |
|
80 | 80 | del self.shell.user_ns['_'] |
|
81 | 81 | except KeyError: |
|
82 | 82 | pass |
|
83 | 83 | |
|
84 | 84 | def quiet(self): |
|
85 | 85 | """Should we silence the display hook because of ';'?""" |
|
86 | 86 | # do not print output if input ends in ';' |
|
87 | 87 | |
|
88 | 88 | try: |
|
89 | 89 | cell = self.shell.history_manager.input_hist_parsed[-1] |
|
90 | 90 | except IndexError: |
|
91 | 91 | # some uses of ipshellembed may fail here |
|
92 | 92 | return False |
|
93 | 93 | |
|
94 | sio = _io.StringIO(cell) | |
|
94 | return self.semicolon_at_end_of_expression(cell) | |
|
95 | ||
|
96 | @staticmethod | |
|
97 | def semicolon_at_end_of_expression(expression): | |
|
98 | """Parse Python expression and detects whether last token is ';'""" | |
|
99 | ||
|
100 | sio = _io.StringIO(expression) | |
|
95 | 101 | tokens = list(tokenize.generate_tokens(sio.readline)) |
|
96 | 102 | |
|
97 | 103 | for token in reversed(tokens): |
|
98 | 104 | if token[0] in (tokenize.ENDMARKER, tokenize.NL, tokenize.NEWLINE, tokenize.COMMENT): |
|
99 | 105 | continue |
|
100 | 106 | if (token[0] == tokenize.OP) and (token[1] == ';'): |
|
101 | 107 | return True |
|
102 | 108 | else: |
|
103 | 109 | return False |
|
104 | 110 | |
|
105 | 111 | def start_displayhook(self): |
|
106 | 112 | """Start the displayhook, initializing resources.""" |
|
107 | 113 | pass |
|
108 | 114 | |
|
109 | 115 | def write_output_prompt(self): |
|
110 | 116 | """Write the output prompt. |
|
111 | 117 | |
|
112 | 118 | The default implementation simply writes the prompt to |
|
113 | 119 | ``sys.stdout``. |
|
114 | 120 | """ |
|
115 | 121 | # Use write, not print which adds an extra space. |
|
116 | 122 | sys.stdout.write(self.shell.separate_out) |
|
117 | 123 | outprompt = 'Out[{}]: '.format(self.shell.execution_count) |
|
118 | 124 | if self.do_full_cache: |
|
119 | 125 | sys.stdout.write(outprompt) |
|
120 | 126 | |
|
121 | 127 | def compute_format_data(self, result): |
|
122 | 128 | """Compute format data of the object to be displayed. |
|
123 | 129 | |
|
124 | 130 | The format data is a generalization of the :func:`repr` of an object. |
|
125 | 131 | In the default implementation the format data is a :class:`dict` of |
|
126 | 132 | key value pair where the keys are valid MIME types and the values |
|
127 | 133 | are JSON'able data structure containing the raw data for that MIME |
|
128 | 134 | type. It is up to frontends to determine pick a MIME to to use and |
|
129 | 135 | display that data in an appropriate manner. |
|
130 | 136 | |
|
131 | 137 | This method only computes the format data for the object and should |
|
132 | 138 | NOT actually print or write that to a stream. |
|
133 | 139 | |
|
134 | 140 | Parameters |
|
135 | 141 | ---------- |
|
136 | 142 | result : object |
|
137 | 143 | The Python object passed to the display hook, whose format will be |
|
138 | 144 | computed. |
|
139 | 145 | |
|
140 | 146 | Returns |
|
141 | 147 | ------- |
|
142 | 148 | (format_dict, md_dict) : dict |
|
143 | 149 | format_dict is a :class:`dict` whose keys are valid MIME types and values are |
|
144 | 150 | JSON'able raw data for that MIME type. It is recommended that |
|
145 | 151 | all return values of this should always include the "text/plain" |
|
146 | 152 | MIME type representation of the object. |
|
147 | 153 | md_dict is a :class:`dict` with the same MIME type keys |
|
148 | 154 | of metadata associated with each output. |
|
149 | 155 | |
|
150 | 156 | """ |
|
151 | 157 | return self.shell.display_formatter.format(result) |
|
152 | 158 | |
|
153 | 159 | # This can be set to True by the write_output_prompt method in a subclass |
|
154 | 160 | prompt_end_newline = False |
|
155 | 161 | |
|
156 | 162 | def write_format_data(self, format_dict, md_dict=None) -> None: |
|
157 | 163 | """Write the format data dict to the frontend. |
|
158 | 164 | |
|
159 | 165 | This default version of this method simply writes the plain text |
|
160 | 166 | representation of the object to ``sys.stdout``. Subclasses should |
|
161 | 167 | override this method to send the entire `format_dict` to the |
|
162 | 168 | frontends. |
|
163 | 169 | |
|
164 | 170 | Parameters |
|
165 | 171 | ---------- |
|
166 | 172 | format_dict : dict |
|
167 | 173 | The format dict for the object passed to `sys.displayhook`. |
|
168 | 174 | md_dict : dict (optional) |
|
169 | 175 | The metadata dict to be associated with the display data. |
|
170 | 176 | """ |
|
171 | 177 | if 'text/plain' not in format_dict: |
|
172 | 178 | # nothing to do |
|
173 | 179 | return |
|
174 | 180 | # We want to print because we want to always make sure we have a |
|
175 | 181 | # newline, even if all the prompt separators are ''. This is the |
|
176 | 182 | # standard IPython behavior. |
|
177 | 183 | result_repr = format_dict['text/plain'] |
|
178 | 184 | if '\n' in result_repr: |
|
179 | 185 | # So that multi-line strings line up with the left column of |
|
180 | 186 | # the screen, instead of having the output prompt mess up |
|
181 | 187 | # their first line. |
|
182 | 188 | # We use the prompt template instead of the expanded prompt |
|
183 | 189 | # because the expansion may add ANSI escapes that will interfere |
|
184 | 190 | # with our ability to determine whether or not we should add |
|
185 | 191 | # a newline. |
|
186 | 192 | if not self.prompt_end_newline: |
|
187 | 193 | # But avoid extraneous empty lines. |
|
188 | 194 | result_repr = '\n' + result_repr |
|
189 | 195 | |
|
190 | 196 | try: |
|
191 | 197 | print(result_repr) |
|
192 | 198 | except UnicodeEncodeError: |
|
193 | 199 | # If a character is not supported by the terminal encoding replace |
|
194 | 200 | # it with its \u or \x representation |
|
195 | 201 | print(result_repr.encode(sys.stdout.encoding,'backslashreplace').decode(sys.stdout.encoding)) |
|
196 | 202 | |
|
197 | 203 | def update_user_ns(self, result): |
|
198 | 204 | """Update user_ns with various things like _, __, _1, etc.""" |
|
199 | 205 | |
|
200 | 206 | # Avoid recursive reference when displaying _oh/Out |
|
201 | 207 | if self.cache_size and result is not self.shell.user_ns['_oh']: |
|
202 | 208 | if len(self.shell.user_ns['_oh']) >= self.cache_size and self.do_full_cache: |
|
203 | 209 | self.cull_cache() |
|
204 | 210 | |
|
205 | 211 | # Don't overwrite '_' and friends if '_' is in __builtin__ |
|
206 | 212 | # (otherwise we cause buggy behavior for things like gettext). and |
|
207 | 213 | # do not overwrite _, __ or ___ if one of these has been assigned |
|
208 | 214 | # by the user. |
|
209 | 215 | update_unders = True |
|
210 | 216 | for unders in ['_'*i for i in range(1,4)]: |
|
211 | 217 | if not unders in self.shell.user_ns: |
|
212 | 218 | continue |
|
213 | 219 | if getattr(self, unders) is not self.shell.user_ns.get(unders): |
|
214 | 220 | update_unders = False |
|
215 | 221 | |
|
216 | 222 | self.___ = self.__ |
|
217 | 223 | self.__ = self._ |
|
218 | 224 | self._ = result |
|
219 | 225 | |
|
220 | 226 | if ('_' not in builtin_mod.__dict__) and (update_unders): |
|
221 | 227 | self.shell.push({'_':self._, |
|
222 | 228 | '__':self.__, |
|
223 | 229 | '___':self.___}, interactive=False) |
|
224 | 230 | |
|
225 | 231 | # hackish access to top-level namespace to create _1,_2... dynamically |
|
226 | 232 | to_main = {} |
|
227 | 233 | if self.do_full_cache: |
|
228 | 234 | new_result = '_%s' % self.prompt_count |
|
229 | 235 | to_main[new_result] = result |
|
230 | 236 | self.shell.push(to_main, interactive=False) |
|
231 | 237 | self.shell.user_ns['_oh'][self.prompt_count] = result |
|
232 | 238 | |
|
233 | 239 | def fill_exec_result(self, result): |
|
234 | 240 | if self.exec_result is not None: |
|
235 | 241 | self.exec_result.result = result |
|
236 | 242 | |
|
237 | 243 | def log_output(self, format_dict): |
|
238 | 244 | """Log the output.""" |
|
239 | 245 | if 'text/plain' not in format_dict: |
|
240 | 246 | # nothing to do |
|
241 | 247 | return |
|
242 | 248 | if self.shell.logger.log_output: |
|
243 | 249 | self.shell.logger.log_write(format_dict['text/plain'], 'output') |
|
244 | 250 | self.shell.history_manager.output_hist_reprs[self.prompt_count] = \ |
|
245 | 251 | format_dict['text/plain'] |
|
246 | 252 | |
|
247 | 253 | def finish_displayhook(self): |
|
248 | 254 | """Finish up all displayhook activities.""" |
|
249 | 255 | sys.stdout.write(self.shell.separate_out2) |
|
250 | 256 | sys.stdout.flush() |
|
251 | 257 | |
|
252 | 258 | def __call__(self, result=None): |
|
253 | 259 | """Printing with history cache management. |
|
254 | 260 | |
|
255 | 261 | This is invoked every time the interpreter needs to print, and is |
|
256 | 262 | activated by setting the variable sys.displayhook to it. |
|
257 | 263 | """ |
|
258 | 264 | self.check_for_underscore() |
|
259 | 265 | if result is not None and not self.quiet(): |
|
260 | 266 | self.start_displayhook() |
|
261 | 267 | self.write_output_prompt() |
|
262 | 268 | format_dict, md_dict = self.compute_format_data(result) |
|
263 | 269 | self.update_user_ns(result) |
|
264 | 270 | self.fill_exec_result(result) |
|
265 | 271 | if format_dict: |
|
266 | 272 | self.write_format_data(format_dict, md_dict) |
|
267 | 273 | self.log_output(format_dict) |
|
268 | 274 | self.finish_displayhook() |
|
269 | 275 | |
|
270 | 276 | def cull_cache(self): |
|
271 | 277 | """Output cache is full, cull the oldest entries""" |
|
272 | 278 | oh = self.shell.user_ns.get('_oh', {}) |
|
273 | 279 | sz = len(oh) |
|
274 | 280 | cull_count = max(int(sz * self.cull_fraction), 2) |
|
275 | 281 | warn('Output cache limit (currently {sz} entries) hit.\n' |
|
276 | 282 | 'Flushing oldest {cull_count} entries.'.format(sz=sz, cull_count=cull_count)) |
|
277 | 283 | |
|
278 | 284 | for i, n in enumerate(sorted(oh)): |
|
279 | 285 | if i >= cull_count: |
|
280 | 286 | break |
|
281 | 287 | self.shell.user_ns.pop('_%i' % n, None) |
|
282 | 288 | oh.pop(n, None) |
|
283 | 289 | |
|
284 | 290 | |
|
285 | 291 | def flush(self): |
|
286 | 292 | if not self.do_full_cache: |
|
287 | 293 | raise ValueError("You shouldn't have reached the cache flush " |
|
288 | 294 | "if full caching is not enabled!") |
|
289 | 295 | # delete auto-generated vars from global namespace |
|
290 | 296 | |
|
291 | 297 | for n in range(1,self.prompt_count + 1): |
|
292 | 298 | key = '_'+repr(n) |
|
293 | 299 | try: |
|
294 | 300 | del self.shell.user_ns[key] |
|
295 | 301 | except: pass |
|
296 | 302 | # In some embedded circumstances, the user_ns doesn't have the |
|
297 | 303 | # '_oh' key set up. |
|
298 | 304 | oh = self.shell.user_ns.get('_oh', None) |
|
299 | 305 | if oh is not None: |
|
300 | 306 | oh.clear() |
|
301 | 307 | |
|
302 | 308 | # Release our own references to objects: |
|
303 | 309 | self._, self.__, self.___ = '', '', '' |
|
304 | 310 | |
|
305 | 311 | if '_' not in builtin_mod.__dict__: |
|
306 | 312 | self.shell.user_ns.update({'_':self._,'__':self.__,'___':self.___}) |
|
307 | 313 | import gc |
|
308 | 314 | # TODO: Is this really needed? |
|
309 | 315 | # IronPython blocks here forever |
|
310 | 316 | if sys.platform != "cli": |
|
311 | 317 | gc.collect() |
|
312 | 318 | |
|
313 | 319 | |
|
314 | 320 | class CapturingDisplayHook(object): |
|
315 | 321 | def __init__(self, shell, outputs=None): |
|
316 | 322 | self.shell = shell |
|
317 | 323 | if outputs is None: |
|
318 | 324 | outputs = [] |
|
319 | 325 | self.outputs = outputs |
|
320 | 326 | |
|
321 | 327 | def __call__(self, result=None): |
|
322 | 328 | if result is None: |
|
323 | 329 | return |
|
324 | 330 | format_dict, md_dict = self.shell.display_formatter.format(result) |
|
325 | 331 | self.outputs.append({ 'data': format_dict, 'metadata': md_dict }) |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
@@ -1,218 +1,227 b'' | |||
|
1 | 1 | """Logger class for IPython's logging facilities. |
|
2 | 2 | """ |
|
3 | 3 | |
|
4 | 4 | #***************************************************************************** |
|
5 | 5 | # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de> and |
|
6 | 6 | # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu> |
|
7 | 7 | # |
|
8 | 8 | # Distributed under the terms of the BSD License. The full license is in |
|
9 | 9 | # the file COPYING, distributed as part of this software. |
|
10 | 10 | #***************************************************************************** |
|
11 | 11 | |
|
12 | 12 | #**************************************************************************** |
|
13 | 13 | # Modules and globals |
|
14 | 14 | |
|
15 | 15 | # Python standard modules |
|
16 | 16 | import glob |
|
17 | 17 | import io |
|
18 | 18 | import os |
|
19 | 19 | import time |
|
20 | 20 | |
|
21 | 21 | |
|
22 | 22 | #**************************************************************************** |
|
23 | 23 | # FIXME: This class isn't a mixin anymore, but it still needs attributes from |
|
24 | 24 | # ipython and does input cache management. Finish cleanup later... |
|
25 | 25 | |
|
26 | 26 | class Logger(object): |
|
27 | 27 | """A Logfile class with different policies for file creation""" |
|
28 | 28 | |
|
29 | 29 | def __init__(self, home_dir, logfname='Logger.log', loghead=u'', |
|
30 | 30 | logmode='over'): |
|
31 | 31 | |
|
32 | 32 | # this is the full ipython instance, we need some attributes from it |
|
33 | 33 | # which won't exist until later. What a mess, clean up later... |
|
34 | 34 | self.home_dir = home_dir |
|
35 | 35 | |
|
36 | 36 | self.logfname = logfname |
|
37 | 37 | self.loghead = loghead |
|
38 | 38 | self.logmode = logmode |
|
39 | 39 | self.logfile = None |
|
40 | 40 | |
|
41 | 41 | # Whether to log raw or processed input |
|
42 | 42 | self.log_raw_input = False |
|
43 | 43 | |
|
44 | 44 | # whether to also log output |
|
45 | 45 | self.log_output = False |
|
46 | 46 | |
|
47 | 47 | # whether to put timestamps before each log entry |
|
48 | 48 | self.timestamp = False |
|
49 | 49 | |
|
50 | 50 | # activity control flags |
|
51 | 51 | self.log_active = False |
|
52 | 52 | |
|
53 | 53 | # logmode is a validated property |
|
54 | 54 | def _set_mode(self,mode): |
|
55 | 55 | if mode not in ['append','backup','global','over','rotate']: |
|
56 | 56 | raise ValueError('invalid log mode %s given' % mode) |
|
57 | 57 | self._logmode = mode |
|
58 | 58 | |
|
59 | 59 | def _get_mode(self): |
|
60 | 60 | return self._logmode |
|
61 | 61 | |
|
62 | 62 | logmode = property(_get_mode,_set_mode) |
|
63 | 63 | |
|
64 | 64 | def logstart(self, logfname=None, loghead=None, logmode=None, |
|
65 | 65 | log_output=False, timestamp=False, log_raw_input=False): |
|
66 | 66 | """Generate a new log-file with a default header. |
|
67 | 67 | |
|
68 | 68 | Raises RuntimeError if the log has already been started""" |
|
69 | 69 | |
|
70 | 70 | if self.logfile is not None: |
|
71 | 71 | raise RuntimeError('Log file is already active: %s' % |
|
72 | 72 | self.logfname) |
|
73 | 73 | |
|
74 | 74 | # The parameters can override constructor defaults |
|
75 | 75 | if logfname is not None: self.logfname = logfname |
|
76 | 76 | if loghead is not None: self.loghead = loghead |
|
77 | 77 | if logmode is not None: self.logmode = logmode |
|
78 | 78 | |
|
79 | 79 | # Parameters not part of the constructor |
|
80 | 80 | self.timestamp = timestamp |
|
81 | 81 | self.log_output = log_output |
|
82 | 82 | self.log_raw_input = log_raw_input |
|
83 | 83 | |
|
84 | 84 | # init depending on the log mode requested |
|
85 | 85 | isfile = os.path.isfile |
|
86 | 86 | logmode = self.logmode |
|
87 | 87 | |
|
88 | 88 | if logmode == 'append': |
|
89 | 89 | self.logfile = io.open(self.logfname, 'a', encoding='utf-8') |
|
90 | 90 | |
|
91 | 91 | elif logmode == 'backup': |
|
92 | 92 | if isfile(self.logfname): |
|
93 | 93 | backup_logname = self.logfname+'~' |
|
94 | 94 | # Manually remove any old backup, since os.rename may fail |
|
95 | 95 | # under Windows. |
|
96 | 96 | if isfile(backup_logname): |
|
97 | 97 | os.remove(backup_logname) |
|
98 | 98 | os.rename(self.logfname,backup_logname) |
|
99 | 99 | self.logfile = io.open(self.logfname, 'w', encoding='utf-8') |
|
100 | 100 | |
|
101 | 101 | elif logmode == 'global': |
|
102 | 102 | self.logfname = os.path.join(self.home_dir,self.logfname) |
|
103 | 103 | self.logfile = io.open(self.logfname, 'a', encoding='utf-8') |
|
104 | 104 | |
|
105 | 105 | elif logmode == 'over': |
|
106 | 106 | if isfile(self.logfname): |
|
107 | 107 | os.remove(self.logfname) |
|
108 | 108 | self.logfile = io.open(self.logfname,'w', encoding='utf-8') |
|
109 | 109 | |
|
110 | 110 | elif logmode == 'rotate': |
|
111 | 111 | if isfile(self.logfname): |
|
112 | 112 | if isfile(self.logfname+'.001~'): |
|
113 | 113 | old = glob.glob(self.logfname+'.*~') |
|
114 | 114 | old.sort() |
|
115 | 115 | old.reverse() |
|
116 | 116 | for f in old: |
|
117 | 117 | root, ext = os.path.splitext(f) |
|
118 | 118 | num = int(ext[1:-1])+1 |
|
119 | 119 | os.rename(f, root+'.'+repr(num).zfill(3)+'~') |
|
120 | 120 | os.rename(self.logfname, self.logfname+'.001~') |
|
121 | 121 | self.logfile = io.open(self.logfname, 'w', encoding='utf-8') |
|
122 | 122 | |
|
123 | 123 | if logmode != 'append': |
|
124 | 124 | self.logfile.write(self.loghead) |
|
125 | 125 | |
|
126 | 126 | self.logfile.flush() |
|
127 | 127 | self.log_active = True |
|
128 | 128 | |
|
129 | 129 | def switch_log(self,val): |
|
130 | 130 | """Switch logging on/off. val should be ONLY a boolean.""" |
|
131 | 131 | |
|
132 | 132 | if val not in [False,True,0,1]: |
|
133 | 133 | raise ValueError('Call switch_log ONLY with a boolean argument, ' |
|
134 | 134 | 'not with: %s' % val) |
|
135 | 135 | |
|
136 | 136 | label = {0:'OFF',1:'ON',False:'OFF',True:'ON'} |
|
137 | 137 | |
|
138 | 138 | if self.logfile is None: |
|
139 | 139 | print(""" |
|
140 | 140 | Logging hasn't been started yet (use logstart for that). |
|
141 | 141 | |
|
142 | 142 | %logon/%logoff are for temporarily starting and stopping logging for a logfile |
|
143 | 143 | which already exists. But you must first start the logging process with |
|
144 | 144 | %logstart (optionally giving a logfile name).""") |
|
145 | 145 | |
|
146 | 146 | else: |
|
147 | 147 | if self.log_active == val: |
|
148 | 148 | print('Logging is already',label[val]) |
|
149 | 149 | else: |
|
150 | 150 | print('Switching logging',label[val]) |
|
151 | 151 | self.log_active = not self.log_active |
|
152 | 152 | self.log_active_out = self.log_active |
|
153 | 153 | |
|
154 | 154 | def logstate(self): |
|
155 | 155 | """Print a status message about the logger.""" |
|
156 | 156 | if self.logfile is None: |
|
157 | 157 | print('Logging has not been activated.') |
|
158 | 158 | else: |
|
159 | 159 | state = self.log_active and 'active' or 'temporarily suspended' |
|
160 | 160 | print('Filename :', self.logfname) |
|
161 | 161 | print('Mode :', self.logmode) |
|
162 | 162 | print('Output logging :', self.log_output) |
|
163 | 163 | print('Raw input log :', self.log_raw_input) |
|
164 | 164 | print('Timestamping :', self.timestamp) |
|
165 | 165 | print('State :', state) |
|
166 | 166 | |
|
167 | 167 | def log(self, line_mod, line_ori): |
|
168 | 168 | """Write the sources to a log. |
|
169 | 169 | |
|
170 | 170 | Inputs: |
|
171 | 171 | |
|
172 | 172 | - line_mod: possibly modified input, such as the transformations made |
|
173 | 173 | by input prefilters or input handlers of various kinds. This should |
|
174 | 174 | always be valid Python. |
|
175 | 175 | |
|
176 | 176 | - line_ori: unmodified input line from the user. This is not |
|
177 | 177 | necessarily valid Python. |
|
178 | 178 | """ |
|
179 | 179 | |
|
180 | 180 | # Write the log line, but decide which one according to the |
|
181 | 181 | # log_raw_input flag, set when the log is started. |
|
182 | 182 | if self.log_raw_input: |
|
183 | 183 | self.log_write(line_ori) |
|
184 | 184 | else: |
|
185 | 185 | self.log_write(line_mod) |
|
186 | 186 | |
|
187 | 187 | def log_write(self, data, kind='input'): |
|
188 | 188 | """Write data to the log file, if active""" |
|
189 | 189 | |
|
190 | 190 | #print 'data: %r' % data # dbg |
|
191 | 191 | if self.log_active and data: |
|
192 | 192 | write = self.logfile.write |
|
193 | 193 | if kind=='input': |
|
194 | 194 | if self.timestamp: |
|
195 | 195 | write(time.strftime('# %a, %d %b %Y %H:%M:%S\n', time.localtime())) |
|
196 | 196 | write(data) |
|
197 | 197 | elif kind=='output' and self.log_output: |
|
198 | 198 | odata = u'\n'.join([u'#[Out]# %s' % s |
|
199 | 199 | for s in data.splitlines()]) |
|
200 | 200 | write(u'%s\n' % odata) |
|
201 | self.logfile.flush() | |
|
201 | try: | |
|
202 | self.logfile.flush() | |
|
203 | except OSError: | |
|
204 | print("Failed to flush the log file.") | |
|
205 | print( | |
|
206 | f"Please check that {self.logfname} exists and have the right permissions." | |
|
207 | ) | |
|
208 | print( | |
|
209 | "Also consider turning off the log with `%logstop` to avoid this warning." | |
|
210 | ) | |
|
202 | 211 | |
|
203 | 212 | def logstop(self): |
|
204 | 213 | """Fully stop logging and close log file. |
|
205 | 214 | |
|
206 | 215 | In order to start logging again, a new logstart() call needs to be |
|
207 | 216 | made, possibly (though not necessarily) with a new filename, mode and |
|
208 | 217 | other options.""" |
|
209 | 218 | |
|
210 | 219 | if self.logfile is not None: |
|
211 | 220 | self.logfile.close() |
|
212 | 221 | self.logfile = None |
|
213 | 222 | else: |
|
214 | 223 | print("Logging hadn't been started.") |
|
215 | 224 | self.log_active = False |
|
216 | 225 | |
|
217 | 226 | # For backwards compatibility, in case anyone was using this. |
|
218 | 227 | close_log = logstop |
@@ -1,746 +1,757 b'' | |||
|
1 | 1 | # encoding: utf-8 |
|
2 | 2 | """Magic functions for InteractiveShell. |
|
3 | 3 | """ |
|
4 | 4 | |
|
5 | 5 | #----------------------------------------------------------------------------- |
|
6 | 6 | # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de> and |
|
7 | 7 | # Copyright (C) 2001 Fernando Perez <fperez@colorado.edu> |
|
8 | 8 | # Copyright (C) 2008 The IPython Development Team |
|
9 | 9 | |
|
10 | 10 | # Distributed under the terms of the BSD License. The full license is in |
|
11 | 11 | # the file COPYING, distributed as part of this software. |
|
12 | 12 | #----------------------------------------------------------------------------- |
|
13 | 13 | |
|
14 | 14 | import os |
|
15 | 15 | import re |
|
16 | 16 | import sys |
|
17 | 17 | from getopt import getopt, GetoptError |
|
18 | 18 | |
|
19 | 19 | from traitlets.config.configurable import Configurable |
|
20 | 20 | from . import oinspect |
|
21 | 21 | from .error import UsageError |
|
22 | 22 | from .inputtransformer2 import ESC_MAGIC, ESC_MAGIC2 |
|
23 | 23 | from ..utils.ipstruct import Struct |
|
24 | 24 | from ..utils.process import arg_split |
|
25 | 25 | from ..utils.text import dedent |
|
26 | 26 | from traitlets import Bool, Dict, Instance, observe |
|
27 | 27 | from logging import error |
|
28 | 28 | |
|
29 | 29 | #----------------------------------------------------------------------------- |
|
30 | 30 | # Globals |
|
31 | 31 | #----------------------------------------------------------------------------- |
|
32 | 32 | |
|
33 | 33 | # A dict we'll use for each class that has magics, used as temporary storage to |
|
34 | 34 | # pass information between the @line/cell_magic method decorators and the |
|
35 | 35 | # @magics_class class decorator, because the method decorators have no |
|
36 | 36 | # access to the class when they run. See for more details: |
|
37 | 37 | # http://stackoverflow.com/questions/2366713/can-a-python-decorator-of-an-instance-method-access-the-class |
|
38 | 38 | |
|
39 | 39 | magics = dict(line={}, cell={}) |
|
40 | 40 | |
|
41 | 41 | magic_kinds = ('line', 'cell') |
|
42 | 42 | magic_spec = ('line', 'cell', 'line_cell') |
|
43 | 43 | magic_escapes = dict(line=ESC_MAGIC, cell=ESC_MAGIC2) |
|
44 | 44 | |
|
45 | 45 | #----------------------------------------------------------------------------- |
|
46 | 46 | # Utility classes and functions |
|
47 | 47 | #----------------------------------------------------------------------------- |
|
48 | 48 | |
|
49 | 49 | class Bunch: pass |
|
50 | 50 | |
|
51 | 51 | |
|
52 | 52 | def on_off(tag): |
|
53 | 53 | """Return an ON/OFF string for a 1/0 input. Simple utility function.""" |
|
54 | 54 | return ['OFF','ON'][tag] |
|
55 | 55 | |
|
56 | 56 | |
|
57 | 57 | def compress_dhist(dh): |
|
58 | 58 | """Compress a directory history into a new one with at most 20 entries. |
|
59 | 59 | |
|
60 | 60 | Return a new list made from the first and last 10 elements of dhist after |
|
61 | 61 | removal of duplicates. |
|
62 | 62 | """ |
|
63 | 63 | head, tail = dh[:-10], dh[-10:] |
|
64 | 64 | |
|
65 | 65 | newhead = [] |
|
66 | 66 | done = set() |
|
67 | 67 | for h in head: |
|
68 | 68 | if h in done: |
|
69 | 69 | continue |
|
70 | 70 | newhead.append(h) |
|
71 | 71 | done.add(h) |
|
72 | 72 | |
|
73 | 73 | return newhead + tail |
|
74 | 74 | |
|
75 | 75 | |
|
76 | 76 | def needs_local_scope(func): |
|
77 | 77 | """Decorator to mark magic functions which need to local scope to run.""" |
|
78 | 78 | func.needs_local_scope = True |
|
79 | 79 | return func |
|
80 | 80 | |
|
81 | 81 | #----------------------------------------------------------------------------- |
|
82 | 82 | # Class and method decorators for registering magics |
|
83 | 83 | #----------------------------------------------------------------------------- |
|
84 | 84 | |
|
85 | 85 | def magics_class(cls): |
|
86 | 86 | """Class decorator for all subclasses of the main Magics class. |
|
87 | 87 | |
|
88 | 88 | Any class that subclasses Magics *must* also apply this decorator, to |
|
89 | 89 | ensure that all the methods that have been decorated as line/cell magics |
|
90 | 90 | get correctly registered in the class instance. This is necessary because |
|
91 | 91 | when method decorators run, the class does not exist yet, so they |
|
92 | 92 | temporarily store their information into a module global. Application of |
|
93 | 93 | this class decorator copies that global data to the class instance and |
|
94 | 94 | clears the global. |
|
95 | 95 | |
|
96 | 96 | Obviously, this mechanism is not thread-safe, which means that the |
|
97 | 97 | *creation* of subclasses of Magic should only be done in a single-thread |
|
98 | 98 | context. Instantiation of the classes has no restrictions. Given that |
|
99 | 99 | these classes are typically created at IPython startup time and before user |
|
100 | 100 | application code becomes active, in practice this should not pose any |
|
101 | 101 | problems. |
|
102 | 102 | """ |
|
103 | 103 | cls.registered = True |
|
104 | 104 | cls.magics = dict(line = magics['line'], |
|
105 | 105 | cell = magics['cell']) |
|
106 | 106 | magics['line'] = {} |
|
107 | 107 | magics['cell'] = {} |
|
108 | 108 | return cls |
|
109 | 109 | |
|
110 | 110 | |
|
111 | 111 | def record_magic(dct, magic_kind, magic_name, func): |
|
112 | 112 | """Utility function to store a function as a magic of a specific kind. |
|
113 | 113 | |
|
114 | 114 | Parameters |
|
115 | 115 | ---------- |
|
116 | 116 | dct : dict |
|
117 | 117 | A dictionary with 'line' and 'cell' subdicts. |
|
118 | 118 | magic_kind : str |
|
119 | 119 | Kind of magic to be stored. |
|
120 | 120 | magic_name : str |
|
121 | 121 | Key to store the magic as. |
|
122 | 122 | func : function |
|
123 | 123 | Callable object to store. |
|
124 | 124 | """ |
|
125 | 125 | if magic_kind == 'line_cell': |
|
126 | 126 | dct['line'][magic_name] = dct['cell'][magic_name] = func |
|
127 | 127 | else: |
|
128 | 128 | dct[magic_kind][magic_name] = func |
|
129 | 129 | |
|
130 | 130 | |
|
131 | 131 | def validate_type(magic_kind): |
|
132 | 132 | """Ensure that the given magic_kind is valid. |
|
133 | 133 | |
|
134 | 134 | Check that the given magic_kind is one of the accepted spec types (stored |
|
135 | 135 | in the global `magic_spec`), raise ValueError otherwise. |
|
136 | 136 | """ |
|
137 | 137 | if magic_kind not in magic_spec: |
|
138 | 138 | raise ValueError('magic_kind must be one of %s, %s given' % |
|
139 | 139 | magic_kinds, magic_kind) |
|
140 | 140 | |
|
141 | 141 | |
|
142 | 142 | # The docstrings for the decorator below will be fairly similar for the two |
|
143 | 143 | # types (method and function), so we generate them here once and reuse the |
|
144 | 144 | # templates below. |
|
145 | 145 | _docstring_template = \ |
|
146 | 146 | """Decorate the given {0} as {1} magic. |
|
147 | 147 | |
|
148 | 148 | The decorator can be used with or without arguments, as follows. |
|
149 | 149 | |
|
150 | 150 | i) without arguments: it will create a {1} magic named as the {0} being |
|
151 | 151 | decorated:: |
|
152 | 152 | |
|
153 | 153 | @deco |
|
154 | 154 | def foo(...) |
|
155 | 155 | |
|
156 | 156 | will create a {1} magic named `foo`. |
|
157 | 157 | |
|
158 | 158 | ii) with one string argument: which will be used as the actual name of the |
|
159 | 159 | resulting magic:: |
|
160 | 160 | |
|
161 | 161 | @deco('bar') |
|
162 | 162 | def foo(...) |
|
163 | 163 | |
|
164 | 164 | will create a {1} magic named `bar`. |
|
165 | 165 | |
|
166 | 166 | To register a class magic use ``Interactiveshell.register_magic(class or instance)``. |
|
167 | 167 | """ |
|
168 | 168 | |
|
169 | 169 | # These two are decorator factories. While they are conceptually very similar, |
|
170 | 170 | # there are enough differences in the details that it's simpler to have them |
|
171 | 171 | # written as completely standalone functions rather than trying to share code |
|
172 | 172 | # and make a single one with convoluted logic. |
|
173 | 173 | |
|
174 | 174 | def _method_magic_marker(magic_kind): |
|
175 | 175 | """Decorator factory for methods in Magics subclasses. |
|
176 | 176 | """ |
|
177 | 177 | |
|
178 | 178 | validate_type(magic_kind) |
|
179 | 179 | |
|
180 | 180 | # This is a closure to capture the magic_kind. We could also use a class, |
|
181 | 181 | # but it's overkill for just that one bit of state. |
|
182 | 182 | def magic_deco(arg): |
|
183 | 183 | if callable(arg): |
|
184 | 184 | # "Naked" decorator call (just @foo, no args) |
|
185 | 185 | func = arg |
|
186 | 186 | name = func.__name__ |
|
187 | 187 | retval = arg |
|
188 | 188 | record_magic(magics, magic_kind, name, name) |
|
189 | 189 | elif isinstance(arg, str): |
|
190 | 190 | # Decorator called with arguments (@foo('bar')) |
|
191 | 191 | name = arg |
|
192 | 192 | def mark(func, *a, **kw): |
|
193 | 193 | record_magic(magics, magic_kind, name, func.__name__) |
|
194 | 194 | return func |
|
195 | 195 | retval = mark |
|
196 | 196 | else: |
|
197 | 197 | raise TypeError("Decorator can only be called with " |
|
198 | 198 | "string or function") |
|
199 | 199 | return retval |
|
200 | 200 | |
|
201 | 201 | # Ensure the resulting decorator has a usable docstring |
|
202 | 202 | magic_deco.__doc__ = _docstring_template.format('method', magic_kind) |
|
203 | 203 | return magic_deco |
|
204 | 204 | |
|
205 | 205 | |
|
206 | 206 | def _function_magic_marker(magic_kind): |
|
207 | 207 | """Decorator factory for standalone functions. |
|
208 | 208 | """ |
|
209 | 209 | validate_type(magic_kind) |
|
210 | 210 | |
|
211 | 211 | # This is a closure to capture the magic_kind. We could also use a class, |
|
212 | 212 | # but it's overkill for just that one bit of state. |
|
213 | 213 | def magic_deco(arg): |
|
214 | 214 | # Find get_ipython() in the caller's namespace |
|
215 | 215 | caller = sys._getframe(1) |
|
216 | 216 | for ns in ['f_locals', 'f_globals', 'f_builtins']: |
|
217 | 217 | get_ipython = getattr(caller, ns).get('get_ipython') |
|
218 | 218 | if get_ipython is not None: |
|
219 | 219 | break |
|
220 | 220 | else: |
|
221 | 221 | raise NameError('Decorator can only run in context where ' |
|
222 | 222 | '`get_ipython` exists') |
|
223 | 223 | |
|
224 | 224 | ip = get_ipython() |
|
225 | 225 | |
|
226 | 226 | if callable(arg): |
|
227 | 227 | # "Naked" decorator call (just @foo, no args) |
|
228 | 228 | func = arg |
|
229 | 229 | name = func.__name__ |
|
230 | 230 | ip.register_magic_function(func, magic_kind, name) |
|
231 | 231 | retval = arg |
|
232 | 232 | elif isinstance(arg, str): |
|
233 | 233 | # Decorator called with arguments (@foo('bar')) |
|
234 | 234 | name = arg |
|
235 | 235 | def mark(func, *a, **kw): |
|
236 | 236 | ip.register_magic_function(func, magic_kind, name) |
|
237 | 237 | return func |
|
238 | 238 | retval = mark |
|
239 | 239 | else: |
|
240 | 240 | raise TypeError("Decorator can only be called with " |
|
241 | 241 | "string or function") |
|
242 | 242 | return retval |
|
243 | 243 | |
|
244 | 244 | # Ensure the resulting decorator has a usable docstring |
|
245 | 245 | ds = _docstring_template.format('function', magic_kind) |
|
246 | 246 | |
|
247 | 247 | ds += dedent(""" |
|
248 | 248 | Note: this decorator can only be used in a context where IPython is already |
|
249 | 249 | active, so that the `get_ipython()` call succeeds. You can therefore use |
|
250 | 250 | it in your startup files loaded after IPython initializes, but *not* in the |
|
251 | 251 | IPython configuration file itself, which is executed before IPython is |
|
252 | 252 | fully up and running. Any file located in the `startup` subdirectory of |
|
253 | 253 | your configuration profile will be OK in this sense. |
|
254 | 254 | """) |
|
255 | 255 | |
|
256 | 256 | magic_deco.__doc__ = ds |
|
257 | 257 | return magic_deco |
|
258 | 258 | |
|
259 | 259 | |
|
260 |
MAGIC_NO_VAR_EXPAND_ATTR = |
|
|
260 | MAGIC_NO_VAR_EXPAND_ATTR = "_ipython_magic_no_var_expand" | |
|
261 | MAGIC_OUTPUT_CAN_BE_SILENCED = "_ipython_magic_output_can_be_silenced" | |
|
261 | 262 | |
|
262 | 263 | |
|
263 | 264 | def no_var_expand(magic_func): |
|
264 | 265 | """Mark a magic function as not needing variable expansion |
|
265 | 266 | |
|
266 | 267 | By default, IPython interprets `{a}` or `$a` in the line passed to magics |
|
267 | 268 | as variables that should be interpolated from the interactive namespace |
|
268 | 269 | before passing the line to the magic function. |
|
269 | 270 | This is not always desirable, e.g. when the magic executes Python code |
|
270 | 271 | (%timeit, %time, etc.). |
|
271 | 272 | Decorate magics with `@no_var_expand` to opt-out of variable expansion. |
|
272 | 273 | |
|
273 | 274 | .. versionadded:: 7.3 |
|
274 | 275 | """ |
|
275 | 276 | setattr(magic_func, MAGIC_NO_VAR_EXPAND_ATTR, True) |
|
276 | 277 | return magic_func |
|
277 | 278 | |
|
278 | 279 | |
|
280 | def output_can_be_silenced(magic_func): | |
|
281 | """Mark a magic function so its output may be silenced. | |
|
282 | ||
|
283 | The output is silenced if the Python code used as a parameter of | |
|
284 | the magic ends in a semicolon, not counting a Python comment that can | |
|
285 | follow it. | |
|
286 | """ | |
|
287 | setattr(magic_func, MAGIC_OUTPUT_CAN_BE_SILENCED, True) | |
|
288 | return magic_func | |
|
289 | ||
|
279 | 290 | # Create the actual decorators for public use |
|
280 | 291 | |
|
281 | 292 | # These three are used to decorate methods in class definitions |
|
282 | 293 | line_magic = _method_magic_marker('line') |
|
283 | 294 | cell_magic = _method_magic_marker('cell') |
|
284 | 295 | line_cell_magic = _method_magic_marker('line_cell') |
|
285 | 296 | |
|
286 | 297 | # These three decorate standalone functions and perform the decoration |
|
287 | 298 | # immediately. They can only run where get_ipython() works |
|
288 | 299 | register_line_magic = _function_magic_marker('line') |
|
289 | 300 | register_cell_magic = _function_magic_marker('cell') |
|
290 | 301 | register_line_cell_magic = _function_magic_marker('line_cell') |
|
291 | 302 | |
|
292 | 303 | #----------------------------------------------------------------------------- |
|
293 | 304 | # Core Magic classes |
|
294 | 305 | #----------------------------------------------------------------------------- |
|
295 | 306 | |
|
296 | 307 | class MagicsManager(Configurable): |
|
297 | 308 | """Object that handles all magic-related functionality for IPython. |
|
298 | 309 | """ |
|
299 | 310 | # Non-configurable class attributes |
|
300 | 311 | |
|
301 | 312 | # A two-level dict, first keyed by magic type, then by magic function, and |
|
302 | 313 | # holding the actual callable object as value. This is the dict used for |
|
303 | 314 | # magic function dispatch |
|
304 | 315 | magics = Dict() |
|
305 | 316 | lazy_magics = Dict( |
|
306 | 317 | help=""" |
|
307 | 318 | Mapping from magic names to modules to load. |
|
308 | 319 | |
|
309 | 320 | This can be used in IPython/IPykernel configuration to declare lazy magics |
|
310 | 321 | that will only be imported/registered on first use. |
|
311 | 322 | |
|
312 | 323 | For example:: |
|
313 | 324 | |
|
314 | 325 | c.MagicsManager.lazy_magics = { |
|
315 | 326 | "my_magic": "slow.to.import", |
|
316 | 327 | "my_other_magic": "also.slow", |
|
317 | 328 | } |
|
318 | 329 | |
|
319 | 330 | On first invocation of `%my_magic`, `%%my_magic`, `%%my_other_magic` or |
|
320 | 331 | `%%my_other_magic`, the corresponding module will be loaded as an ipython |
|
321 | 332 | extensions as if you had previously done `%load_ext ipython`. |
|
322 | 333 | |
|
323 | 334 | Magics names should be without percent(s) as magics can be both cell |
|
324 | 335 | and line magics. |
|
325 | 336 | |
|
326 | 337 | Lazy loading happen relatively late in execution process, and |
|
327 | 338 | complex extensions that manipulate Python/IPython internal state or global state |
|
328 | 339 | might not support lazy loading. |
|
329 | 340 | """ |
|
330 | 341 | ).tag( |
|
331 | 342 | config=True, |
|
332 | 343 | ) |
|
333 | 344 | |
|
334 | 345 | # A registry of the original objects that we've been given holding magics. |
|
335 | 346 | registry = Dict() |
|
336 | 347 | |
|
337 | 348 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True) |
|
338 | 349 | |
|
339 | 350 | auto_magic = Bool(True, help= |
|
340 | 351 | "Automatically call line magics without requiring explicit % prefix" |
|
341 | 352 | ).tag(config=True) |
|
342 | 353 | @observe('auto_magic') |
|
343 | 354 | def _auto_magic_changed(self, change): |
|
344 | 355 | self.shell.automagic = change['new'] |
|
345 | 356 | |
|
346 | 357 | _auto_status = [ |
|
347 | 358 | 'Automagic is OFF, % prefix IS needed for line magics.', |
|
348 | 359 | 'Automagic is ON, % prefix IS NOT needed for line magics.'] |
|
349 | 360 | |
|
350 | 361 | user_magics = Instance('IPython.core.magics.UserMagics', allow_none=True) |
|
351 | 362 | |
|
352 | 363 | def __init__(self, shell=None, config=None, user_magics=None, **traits): |
|
353 | 364 | |
|
354 | 365 | super(MagicsManager, self).__init__(shell=shell, config=config, |
|
355 | 366 | user_magics=user_magics, **traits) |
|
356 | 367 | self.magics = dict(line={}, cell={}) |
|
357 | 368 | # Let's add the user_magics to the registry for uniformity, so *all* |
|
358 | 369 | # registered magic containers can be found there. |
|
359 | 370 | self.registry[user_magics.__class__.__name__] = user_magics |
|
360 | 371 | |
|
361 | 372 | def auto_status(self): |
|
362 | 373 | """Return descriptive string with automagic status.""" |
|
363 | 374 | return self._auto_status[self.auto_magic] |
|
364 | 375 | |
|
365 | 376 | def lsmagic(self): |
|
366 | 377 | """Return a dict of currently available magic functions. |
|
367 | 378 | |
|
368 | 379 | The return dict has the keys 'line' and 'cell', corresponding to the |
|
369 | 380 | two types of magics we support. Each value is a list of names. |
|
370 | 381 | """ |
|
371 | 382 | return self.magics |
|
372 | 383 | |
|
373 | 384 | def lsmagic_docs(self, brief=False, missing=''): |
|
374 | 385 | """Return dict of documentation of magic functions. |
|
375 | 386 | |
|
376 | 387 | The return dict has the keys 'line' and 'cell', corresponding to the |
|
377 | 388 | two types of magics we support. Each value is a dict keyed by magic |
|
378 | 389 | name whose value is the function docstring. If a docstring is |
|
379 | 390 | unavailable, the value of `missing` is used instead. |
|
380 | 391 | |
|
381 | 392 | If brief is True, only the first line of each docstring will be returned. |
|
382 | 393 | """ |
|
383 | 394 | docs = {} |
|
384 | 395 | for m_type in self.magics: |
|
385 | 396 | m_docs = {} |
|
386 | 397 | for m_name, m_func in self.magics[m_type].items(): |
|
387 | 398 | if m_func.__doc__: |
|
388 | 399 | if brief: |
|
389 | 400 | m_docs[m_name] = m_func.__doc__.split('\n', 1)[0] |
|
390 | 401 | else: |
|
391 | 402 | m_docs[m_name] = m_func.__doc__.rstrip() |
|
392 | 403 | else: |
|
393 | 404 | m_docs[m_name] = missing |
|
394 | 405 | docs[m_type] = m_docs |
|
395 | 406 | return docs |
|
396 | 407 | |
|
397 | 408 | def register_lazy(self, name: str, fully_qualified_name: str): |
|
398 | 409 | """ |
|
399 | 410 | Lazily register a magic via an extension. |
|
400 | 411 | |
|
401 | 412 | |
|
402 | 413 | Parameters |
|
403 | 414 | ---------- |
|
404 | 415 | name : str |
|
405 | 416 | Name of the magic you wish to register. |
|
406 | 417 | fully_qualified_name : |
|
407 | 418 | Fully qualified name of the module/submodule that should be loaded |
|
408 | 419 | as an extensions when the magic is first called. |
|
409 | 420 | It is assumed that loading this extensions will register the given |
|
410 | 421 | magic. |
|
411 | 422 | """ |
|
412 | 423 | |
|
413 | 424 | self.lazy_magics[name] = fully_qualified_name |
|
414 | 425 | |
|
415 | 426 | def register(self, *magic_objects): |
|
416 | 427 | """Register one or more instances of Magics. |
|
417 | 428 | |
|
418 | 429 | Take one or more classes or instances of classes that subclass the main |
|
419 | 430 | `core.Magic` class, and register them with IPython to use the magic |
|
420 | 431 | functions they provide. The registration process will then ensure that |
|
421 | 432 | any methods that have decorated to provide line and/or cell magics will |
|
422 | 433 | be recognized with the `%x`/`%%x` syntax as a line/cell magic |
|
423 | 434 | respectively. |
|
424 | 435 | |
|
425 | 436 | If classes are given, they will be instantiated with the default |
|
426 | 437 | constructor. If your classes need a custom constructor, you should |
|
427 | 438 | instanitate them first and pass the instance. |
|
428 | 439 | |
|
429 | 440 | The provided arguments can be an arbitrary mix of classes and instances. |
|
430 | 441 | |
|
431 | 442 | Parameters |
|
432 | 443 | ---------- |
|
433 | 444 | *magic_objects : one or more classes or instances |
|
434 | 445 | """ |
|
435 | 446 | # Start by validating them to ensure they have all had their magic |
|
436 | 447 | # methods registered at the instance level |
|
437 | 448 | for m in magic_objects: |
|
438 | 449 | if not m.registered: |
|
439 | 450 | raise ValueError("Class of magics %r was constructed without " |
|
440 | 451 | "the @register_magics class decorator") |
|
441 | 452 | if isinstance(m, type): |
|
442 | 453 | # If we're given an uninstantiated class |
|
443 | 454 | m = m(shell=self.shell) |
|
444 | 455 | |
|
445 | 456 | # Now that we have an instance, we can register it and update the |
|
446 | 457 | # table of callables |
|
447 | 458 | self.registry[m.__class__.__name__] = m |
|
448 | 459 | for mtype in magic_kinds: |
|
449 | 460 | self.magics[mtype].update(m.magics[mtype]) |
|
450 | 461 | |
|
451 | 462 | def register_function(self, func, magic_kind='line', magic_name=None): |
|
452 | 463 | """Expose a standalone function as magic function for IPython. |
|
453 | 464 | |
|
454 | 465 | This will create an IPython magic (line, cell or both) from a |
|
455 | 466 | standalone function. The functions should have the following |
|
456 | 467 | signatures: |
|
457 | 468 | |
|
458 | 469 | * For line magics: `def f(line)` |
|
459 | 470 | * For cell magics: `def f(line, cell)` |
|
460 | 471 | * For a function that does both: `def f(line, cell=None)` |
|
461 | 472 | |
|
462 | 473 | In the latter case, the function will be called with `cell==None` when |
|
463 | 474 | invoked as `%f`, and with cell as a string when invoked as `%%f`. |
|
464 | 475 | |
|
465 | 476 | Parameters |
|
466 | 477 | ---------- |
|
467 | 478 | func : callable |
|
468 | 479 | Function to be registered as a magic. |
|
469 | 480 | magic_kind : str |
|
470 | 481 | Kind of magic, one of 'line', 'cell' or 'line_cell' |
|
471 | 482 | magic_name : optional str |
|
472 | 483 | If given, the name the magic will have in the IPython namespace. By |
|
473 | 484 | default, the name of the function itself is used. |
|
474 | 485 | """ |
|
475 | 486 | |
|
476 | 487 | # Create the new method in the user_magics and register it in the |
|
477 | 488 | # global table |
|
478 | 489 | validate_type(magic_kind) |
|
479 | 490 | magic_name = func.__name__ if magic_name is None else magic_name |
|
480 | 491 | setattr(self.user_magics, magic_name, func) |
|
481 | 492 | record_magic(self.magics, magic_kind, magic_name, func) |
|
482 | 493 | |
|
483 | 494 | def register_alias(self, alias_name, magic_name, magic_kind='line', magic_params=None): |
|
484 | 495 | """Register an alias to a magic function. |
|
485 | 496 | |
|
486 | 497 | The alias is an instance of :class:`MagicAlias`, which holds the |
|
487 | 498 | name and kind of the magic it should call. Binding is done at |
|
488 | 499 | call time, so if the underlying magic function is changed the alias |
|
489 | 500 | will call the new function. |
|
490 | 501 | |
|
491 | 502 | Parameters |
|
492 | 503 | ---------- |
|
493 | 504 | alias_name : str |
|
494 | 505 | The name of the magic to be registered. |
|
495 | 506 | magic_name : str |
|
496 | 507 | The name of an existing magic. |
|
497 | 508 | magic_kind : str |
|
498 | 509 | Kind of magic, one of 'line' or 'cell' |
|
499 | 510 | """ |
|
500 | 511 | |
|
501 | 512 | # `validate_type` is too permissive, as it allows 'line_cell' |
|
502 | 513 | # which we do not handle. |
|
503 | 514 | if magic_kind not in magic_kinds: |
|
504 | 515 | raise ValueError('magic_kind must be one of %s, %s given' % |
|
505 | 516 | magic_kinds, magic_kind) |
|
506 | 517 | |
|
507 | 518 | alias = MagicAlias(self.shell, magic_name, magic_kind, magic_params) |
|
508 | 519 | setattr(self.user_magics, alias_name, alias) |
|
509 | 520 | record_magic(self.magics, magic_kind, alias_name, alias) |
|
510 | 521 | |
|
511 | 522 | # Key base class that provides the central functionality for magics. |
|
512 | 523 | |
|
513 | 524 | |
|
514 | 525 | class Magics(Configurable): |
|
515 | 526 | """Base class for implementing magic functions. |
|
516 | 527 | |
|
517 | 528 | Shell functions which can be reached as %function_name. All magic |
|
518 | 529 | functions should accept a string, which they can parse for their own |
|
519 | 530 | needs. This can make some functions easier to type, eg `%cd ../` |
|
520 | 531 | vs. `%cd("../")` |
|
521 | 532 | |
|
522 | 533 | Classes providing magic functions need to subclass this class, and they |
|
523 | 534 | MUST: |
|
524 | 535 | |
|
525 | 536 | - Use the method decorators `@line_magic` and `@cell_magic` to decorate |
|
526 | 537 | individual methods as magic functions, AND |
|
527 | 538 | |
|
528 | 539 | - Use the class decorator `@magics_class` to ensure that the magic |
|
529 | 540 | methods are properly registered at the instance level upon instance |
|
530 | 541 | initialization. |
|
531 | 542 | |
|
532 | 543 | See :mod:`magic_functions` for examples of actual implementation classes. |
|
533 | 544 | """ |
|
534 | 545 | # Dict holding all command-line options for each magic. |
|
535 | 546 | options_table = None |
|
536 | 547 | # Dict for the mapping of magic names to methods, set by class decorator |
|
537 | 548 | magics = None |
|
538 | 549 | # Flag to check that the class decorator was properly applied |
|
539 | 550 | registered = False |
|
540 | 551 | # Instance of IPython shell |
|
541 | 552 | shell = None |
|
542 | 553 | |
|
543 | 554 | def __init__(self, shell=None, **kwargs): |
|
544 | 555 | if not(self.__class__.registered): |
|
545 | 556 | raise ValueError('Magics subclass without registration - ' |
|
546 | 557 | 'did you forget to apply @magics_class?') |
|
547 | 558 | if shell is not None: |
|
548 | 559 | if hasattr(shell, 'configurables'): |
|
549 | 560 | shell.configurables.append(self) |
|
550 | 561 | if hasattr(shell, 'config'): |
|
551 | 562 | kwargs.setdefault('parent', shell) |
|
552 | 563 | |
|
553 | 564 | self.shell = shell |
|
554 | 565 | self.options_table = {} |
|
555 | 566 | # The method decorators are run when the instance doesn't exist yet, so |
|
556 | 567 | # they can only record the names of the methods they are supposed to |
|
557 | 568 | # grab. Only now, that the instance exists, can we create the proper |
|
558 | 569 | # mapping to bound methods. So we read the info off the original names |
|
559 | 570 | # table and replace each method name by the actual bound method. |
|
560 | 571 | # But we mustn't clobber the *class* mapping, in case of multiple instances. |
|
561 | 572 | class_magics = self.magics |
|
562 | 573 | self.magics = {} |
|
563 | 574 | for mtype in magic_kinds: |
|
564 | 575 | tab = self.magics[mtype] = {} |
|
565 | 576 | cls_tab = class_magics[mtype] |
|
566 | 577 | for magic_name, meth_name in cls_tab.items(): |
|
567 | 578 | if isinstance(meth_name, str): |
|
568 | 579 | # it's a method name, grab it |
|
569 | 580 | tab[magic_name] = getattr(self, meth_name) |
|
570 | 581 | else: |
|
571 | 582 | # it's the real thing |
|
572 | 583 | tab[magic_name] = meth_name |
|
573 | 584 | # Configurable **needs** to be initiated at the end or the config |
|
574 | 585 | # magics get screwed up. |
|
575 | 586 | super(Magics, self).__init__(**kwargs) |
|
576 | 587 | |
|
577 | 588 | def arg_err(self,func): |
|
578 | 589 | """Print docstring if incorrect arguments were passed""" |
|
579 | 590 | print('Error in arguments:') |
|
580 | 591 | print(oinspect.getdoc(func)) |
|
581 | 592 | |
|
582 | 593 | def format_latex(self, strng): |
|
583 | 594 | """Format a string for latex inclusion.""" |
|
584 | 595 | |
|
585 | 596 | # Characters that need to be escaped for latex: |
|
586 | 597 | escape_re = re.compile(r'(%|_|\$|#|&)',re.MULTILINE) |
|
587 | 598 | # Magic command names as headers: |
|
588 | 599 | cmd_name_re = re.compile(r'^(%s.*?):' % ESC_MAGIC, |
|
589 | 600 | re.MULTILINE) |
|
590 | 601 | # Magic commands |
|
591 | 602 | cmd_re = re.compile(r'(?P<cmd>%s.+?\b)(?!\}\}:)' % ESC_MAGIC, |
|
592 | 603 | re.MULTILINE) |
|
593 | 604 | # Paragraph continue |
|
594 | 605 | par_re = re.compile(r'\\$',re.MULTILINE) |
|
595 | 606 | |
|
596 | 607 | # The "\n" symbol |
|
597 | 608 | newline_re = re.compile(r'\\n') |
|
598 | 609 | |
|
599 | 610 | # Now build the string for output: |
|
600 | 611 | #strng = cmd_name_re.sub(r'\n\\texttt{\\textsl{\\large \1}}:',strng) |
|
601 | 612 | strng = cmd_name_re.sub(r'\n\\bigskip\n\\texttt{\\textbf{ \1}}:', |
|
602 | 613 | strng) |
|
603 | 614 | strng = cmd_re.sub(r'\\texttt{\g<cmd>}',strng) |
|
604 | 615 | strng = par_re.sub(r'\\\\',strng) |
|
605 | 616 | strng = escape_re.sub(r'\\\1',strng) |
|
606 | 617 | strng = newline_re.sub(r'\\textbackslash{}n',strng) |
|
607 | 618 | return strng |
|
608 | 619 | |
|
609 | 620 | def parse_options(self, arg_str, opt_str, *long_opts, **kw): |
|
610 | 621 | """Parse options passed to an argument string. |
|
611 | 622 | |
|
612 | 623 | The interface is similar to that of :func:`getopt.getopt`, but it |
|
613 | 624 | returns a :class:`~IPython.utils.struct.Struct` with the options as keys |
|
614 | 625 | and the stripped argument string still as a string. |
|
615 | 626 | |
|
616 | 627 | arg_str is quoted as a true sys.argv vector by using shlex.split. |
|
617 | 628 | This allows us to easily expand variables, glob files, quote |
|
618 | 629 | arguments, etc. |
|
619 | 630 | |
|
620 | 631 | Parameters |
|
621 | 632 | ---------- |
|
622 | 633 | arg_str : str |
|
623 | 634 | The arguments to parse. |
|
624 | 635 | opt_str : str |
|
625 | 636 | The options specification. |
|
626 | 637 | mode : str, default 'string' |
|
627 | 638 | If given as 'list', the argument string is returned as a list (split |
|
628 | 639 | on whitespace) instead of a string. |
|
629 | 640 | list_all : bool, default False |
|
630 | 641 | Put all option values in lists. Normally only options |
|
631 | 642 | appearing more than once are put in a list. |
|
632 | 643 | posix : bool, default True |
|
633 | 644 | Whether to split the input line in POSIX mode or not, as per the |
|
634 | 645 | conventions outlined in the :mod:`shlex` module from the standard |
|
635 | 646 | library. |
|
636 | 647 | """ |
|
637 | 648 | |
|
638 | 649 | # inject default options at the beginning of the input line |
|
639 | 650 | caller = sys._getframe(1).f_code.co_name |
|
640 | 651 | arg_str = '%s %s' % (self.options_table.get(caller,''),arg_str) |
|
641 | 652 | |
|
642 | 653 | mode = kw.get('mode','string') |
|
643 | 654 | if mode not in ['string','list']: |
|
644 | 655 | raise ValueError('incorrect mode given: %s' % mode) |
|
645 | 656 | # Get options |
|
646 | 657 | list_all = kw.get('list_all',0) |
|
647 | 658 | posix = kw.get('posix', os.name == 'posix') |
|
648 | 659 | strict = kw.get('strict', True) |
|
649 | 660 | |
|
650 | 661 | preserve_non_opts = kw.get("preserve_non_opts", False) |
|
651 | 662 | remainder_arg_str = arg_str |
|
652 | 663 | |
|
653 | 664 | # Check if we have more than one argument to warrant extra processing: |
|
654 | 665 | odict = {} # Dictionary with options |
|
655 | 666 | args = arg_str.split() |
|
656 | 667 | if len(args) >= 1: |
|
657 | 668 | # If the list of inputs only has 0 or 1 thing in it, there's no |
|
658 | 669 | # need to look for options |
|
659 | 670 | argv = arg_split(arg_str, posix, strict) |
|
660 | 671 | # Do regular option processing |
|
661 | 672 | try: |
|
662 | 673 | opts,args = getopt(argv, opt_str, long_opts) |
|
663 | 674 | except GetoptError as e: |
|
664 | 675 | raise UsageError( |
|
665 | 676 | '%s ( allowed: "%s" %s)' % (e.msg, opt_str, " ".join(long_opts)) |
|
666 | 677 | ) from e |
|
667 | 678 | for o, a in opts: |
|
668 | 679 | if mode == "string" and preserve_non_opts: |
|
669 | 680 | # remove option-parts from the original args-string and preserve remaining-part. |
|
670 | 681 | # This relies on the arg_split(...) and getopt(...)'s impl spec, that the parsed options are |
|
671 | 682 | # returned in the original order. |
|
672 | 683 | remainder_arg_str = remainder_arg_str.replace(o, "", 1).replace( |
|
673 | 684 | a, "", 1 |
|
674 | 685 | ) |
|
675 | 686 | if o.startswith("--"): |
|
676 | 687 | o = o[2:] |
|
677 | 688 | else: |
|
678 | 689 | o = o[1:] |
|
679 | 690 | try: |
|
680 | 691 | odict[o].append(a) |
|
681 | 692 | except AttributeError: |
|
682 | 693 | odict[o] = [odict[o],a] |
|
683 | 694 | except KeyError: |
|
684 | 695 | if list_all: |
|
685 | 696 | odict[o] = [a] |
|
686 | 697 | else: |
|
687 | 698 | odict[o] = a |
|
688 | 699 | |
|
689 | 700 | # Prepare opts,args for return |
|
690 | 701 | opts = Struct(odict) |
|
691 | 702 | if mode == 'string': |
|
692 | 703 | if preserve_non_opts: |
|
693 | 704 | args = remainder_arg_str.lstrip() |
|
694 | 705 | else: |
|
695 | 706 | args = " ".join(args) |
|
696 | 707 | |
|
697 | 708 | return opts,args |
|
698 | 709 | |
|
699 | 710 | def default_option(self, fn, optstr): |
|
700 | 711 | """Make an entry in the options_table for fn, with value optstr""" |
|
701 | 712 | |
|
702 | 713 | if fn not in self.lsmagic(): |
|
703 | 714 | error("%s is not a magic function" % fn) |
|
704 | 715 | self.options_table[fn] = optstr |
|
705 | 716 | |
|
706 | 717 | |
|
707 | 718 | class MagicAlias(object): |
|
708 | 719 | """An alias to another magic function. |
|
709 | 720 | |
|
710 | 721 | An alias is determined by its magic name and magic kind. Lookup |
|
711 | 722 | is done at call time, so if the underlying magic changes the alias |
|
712 | 723 | will call the new function. |
|
713 | 724 | |
|
714 | 725 | Use the :meth:`MagicsManager.register_alias` method or the |
|
715 | 726 | `%alias_magic` magic function to create and register a new alias. |
|
716 | 727 | """ |
|
717 | 728 | def __init__(self, shell, magic_name, magic_kind, magic_params=None): |
|
718 | 729 | self.shell = shell |
|
719 | 730 | self.magic_name = magic_name |
|
720 | 731 | self.magic_params = magic_params |
|
721 | 732 | self.magic_kind = magic_kind |
|
722 | 733 | |
|
723 | 734 | self.pretty_target = '%s%s' % (magic_escapes[self.magic_kind], self.magic_name) |
|
724 | 735 | self.__doc__ = "Alias for `%s`." % self.pretty_target |
|
725 | 736 | |
|
726 | 737 | self._in_call = False |
|
727 | 738 | |
|
728 | 739 | def __call__(self, *args, **kwargs): |
|
729 | 740 | """Call the magic alias.""" |
|
730 | 741 | fn = self.shell.find_magic(self.magic_name, self.magic_kind) |
|
731 | 742 | if fn is None: |
|
732 | 743 | raise UsageError("Magic `%s` not found." % self.pretty_target) |
|
733 | 744 | |
|
734 | 745 | # Protect against infinite recursion. |
|
735 | 746 | if self._in_call: |
|
736 | 747 | raise UsageError("Infinite recursion detected; " |
|
737 | 748 | "magic aliases cannot call themselves.") |
|
738 | 749 | self._in_call = True |
|
739 | 750 | try: |
|
740 | 751 | if self.magic_params: |
|
741 | 752 | args_list = list(args) |
|
742 | 753 | args_list[0] = self.magic_params + " " + args[0] |
|
743 | 754 | args = tuple(args_list) |
|
744 | 755 | return fn(*args, **kwargs) |
|
745 | 756 | finally: |
|
746 | 757 | self._in_call = False |
@@ -1,1510 +1,1512 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | """Implementation of execution-related magic functions.""" |
|
3 | 3 | |
|
4 | 4 | # Copyright (c) IPython Development Team. |
|
5 | 5 | # Distributed under the terms of the Modified BSD License. |
|
6 | 6 | |
|
7 | 7 | |
|
8 | 8 | import ast |
|
9 | 9 | import bdb |
|
10 | 10 | import builtins as builtin_mod |
|
11 | 11 | import cProfile as profile |
|
12 | 12 | import gc |
|
13 | 13 | import itertools |
|
14 | 14 | import math |
|
15 | 15 | import os |
|
16 | 16 | import pstats |
|
17 | 17 | import re |
|
18 | 18 | import shlex |
|
19 | 19 | import sys |
|
20 | 20 | import time |
|
21 | 21 | import timeit |
|
22 | 22 | from ast import Module |
|
23 | 23 | from io import StringIO |
|
24 | 24 | from logging import error |
|
25 | 25 | from pathlib import Path |
|
26 | 26 | from pdb import Restart |
|
27 | 27 | from warnings import warn |
|
28 | 28 | |
|
29 | 29 | from IPython.core import magic_arguments, oinspect, page |
|
30 | 30 | from IPython.core.error import UsageError |
|
31 | 31 | from IPython.core.macro import Macro |
|
32 | 32 | from IPython.core.magic import ( |
|
33 | 33 | Magics, |
|
34 | 34 | cell_magic, |
|
35 | 35 | line_cell_magic, |
|
36 | 36 | line_magic, |
|
37 | 37 | magics_class, |
|
38 | 38 | needs_local_scope, |
|
39 | 39 | no_var_expand, |
|
40 | output_can_be_silenced, | |
|
40 | 41 | on_off, |
|
41 | 42 | ) |
|
42 | 43 | from IPython.testing.skipdoctest import skip_doctest |
|
43 | 44 | from IPython.utils.capture import capture_output |
|
44 | 45 | from IPython.utils.contexts import preserve_keys |
|
45 | 46 | from IPython.utils.ipstruct import Struct |
|
46 | 47 | from IPython.utils.module_paths import find_mod |
|
47 | 48 | from IPython.utils.path import get_py_filename, shellglob |
|
48 | 49 | from IPython.utils.timing import clock, clock2 |
|
49 | 50 | |
|
50 | 51 | #----------------------------------------------------------------------------- |
|
51 | 52 | # Magic implementation classes |
|
52 | 53 | #----------------------------------------------------------------------------- |
|
53 | 54 | |
|
54 | 55 | |
|
55 | 56 | class TimeitResult(object): |
|
56 | 57 | """ |
|
57 | 58 | Object returned by the timeit magic with info about the run. |
|
58 | 59 | |
|
59 | 60 | Contains the following attributes : |
|
60 | 61 | |
|
61 | 62 | loops: (int) number of loops done per measurement |
|
62 | 63 | repeat: (int) number of times the measurement has been repeated |
|
63 | 64 | best: (float) best execution time / number |
|
64 | 65 | all_runs: (list of float) execution time of each run (in s) |
|
65 | 66 | compile_time: (float) time of statement compilation (s) |
|
66 | 67 | |
|
67 | 68 | """ |
|
68 | 69 | def __init__(self, loops, repeat, best, worst, all_runs, compile_time, precision): |
|
69 | 70 | self.loops = loops |
|
70 | 71 | self.repeat = repeat |
|
71 | 72 | self.best = best |
|
72 | 73 | self.worst = worst |
|
73 | 74 | self.all_runs = all_runs |
|
74 | 75 | self.compile_time = compile_time |
|
75 | 76 | self._precision = precision |
|
76 | 77 | self.timings = [ dt / self.loops for dt in all_runs] |
|
77 | 78 | |
|
78 | 79 | @property |
|
79 | 80 | def average(self): |
|
80 | 81 | return math.fsum(self.timings) / len(self.timings) |
|
81 | 82 | |
|
82 | 83 | @property |
|
83 | 84 | def stdev(self): |
|
84 | 85 | mean = self.average |
|
85 | 86 | return (math.fsum([(x - mean) ** 2 for x in self.timings]) / len(self.timings)) ** 0.5 |
|
86 | 87 | |
|
87 | 88 | def __str__(self): |
|
88 | 89 | pm = '+-' |
|
89 | 90 | if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding: |
|
90 | 91 | try: |
|
91 | 92 | u'\xb1'.encode(sys.stdout.encoding) |
|
92 | 93 | pm = u'\xb1' |
|
93 | 94 | except: |
|
94 | 95 | pass |
|
95 | 96 | return "{mean} {pm} {std} per loop (mean {pm} std. dev. of {runs} run{run_plural}, {loops:,} loop{loop_plural} each)".format( |
|
96 | 97 | pm=pm, |
|
97 | 98 | runs=self.repeat, |
|
98 | 99 | loops=self.loops, |
|
99 | 100 | loop_plural="" if self.loops == 1 else "s", |
|
100 | 101 | run_plural="" if self.repeat == 1 else "s", |
|
101 | 102 | mean=_format_time(self.average, self._precision), |
|
102 | 103 | std=_format_time(self.stdev, self._precision), |
|
103 | 104 | ) |
|
104 | 105 | |
|
105 | 106 | def _repr_pretty_(self, p , cycle): |
|
106 | 107 | unic = self.__str__() |
|
107 | 108 | p.text(u'<TimeitResult : '+unic+u'>') |
|
108 | 109 | |
|
109 | 110 | |
|
110 | 111 | class TimeitTemplateFiller(ast.NodeTransformer): |
|
111 | 112 | """Fill in the AST template for timing execution. |
|
112 | 113 | |
|
113 | 114 | This is quite closely tied to the template definition, which is in |
|
114 | 115 | :meth:`ExecutionMagics.timeit`. |
|
115 | 116 | """ |
|
116 | 117 | def __init__(self, ast_setup, ast_stmt): |
|
117 | 118 | self.ast_setup = ast_setup |
|
118 | 119 | self.ast_stmt = ast_stmt |
|
119 | 120 | |
|
120 | 121 | def visit_FunctionDef(self, node): |
|
121 | 122 | "Fill in the setup statement" |
|
122 | 123 | self.generic_visit(node) |
|
123 | 124 | if node.name == "inner": |
|
124 | 125 | node.body[:1] = self.ast_setup.body |
|
125 | 126 | |
|
126 | 127 | return node |
|
127 | 128 | |
|
128 | 129 | def visit_For(self, node): |
|
129 | 130 | "Fill in the statement to be timed" |
|
130 | 131 | if getattr(getattr(node.body[0], 'value', None), 'id', None) == 'stmt': |
|
131 | 132 | node.body = self.ast_stmt.body |
|
132 | 133 | return node |
|
133 | 134 | |
|
134 | 135 | |
|
135 | 136 | class Timer(timeit.Timer): |
|
136 | 137 | """Timer class that explicitly uses self.inner |
|
137 | 138 | |
|
138 | 139 | which is an undocumented implementation detail of CPython, |
|
139 | 140 | not shared by PyPy. |
|
140 | 141 | """ |
|
141 | 142 | # Timer.timeit copied from CPython 3.4.2 |
|
142 | 143 | def timeit(self, number=timeit.default_number): |
|
143 | 144 | """Time 'number' executions of the main statement. |
|
144 | 145 | |
|
145 | 146 | To be precise, this executes the setup statement once, and |
|
146 | 147 | then returns the time it takes to execute the main statement |
|
147 | 148 | a number of times, as a float measured in seconds. The |
|
148 | 149 | argument is the number of times through the loop, defaulting |
|
149 | 150 | to one million. The main statement, the setup statement and |
|
150 | 151 | the timer function to be used are passed to the constructor. |
|
151 | 152 | """ |
|
152 | 153 | it = itertools.repeat(None, number) |
|
153 | 154 | gcold = gc.isenabled() |
|
154 | 155 | gc.disable() |
|
155 | 156 | try: |
|
156 | 157 | timing = self.inner(it, self.timer) |
|
157 | 158 | finally: |
|
158 | 159 | if gcold: |
|
159 | 160 | gc.enable() |
|
160 | 161 | return timing |
|
161 | 162 | |
|
162 | 163 | |
|
163 | 164 | @magics_class |
|
164 | 165 | class ExecutionMagics(Magics): |
|
165 | 166 | """Magics related to code execution, debugging, profiling, etc. |
|
166 | 167 | |
|
167 | 168 | """ |
|
168 | 169 | |
|
169 | 170 | def __init__(self, shell): |
|
170 | 171 | super(ExecutionMagics, self).__init__(shell) |
|
171 | 172 | # Default execution function used to actually run user code. |
|
172 | 173 | self.default_runner = None |
|
173 | 174 | |
|
174 | 175 | @skip_doctest |
|
175 | 176 | @no_var_expand |
|
176 | 177 | @line_cell_magic |
|
177 | 178 | def prun(self, parameter_s='', cell=None): |
|
178 | 179 | |
|
179 | 180 | """Run a statement through the python code profiler. |
|
180 | 181 | |
|
181 | 182 | Usage, in line mode: |
|
182 | 183 | %prun [options] statement |
|
183 | 184 | |
|
184 | 185 | Usage, in cell mode: |
|
185 | 186 | %%prun [options] [statement] |
|
186 | 187 | code... |
|
187 | 188 | code... |
|
188 | 189 | |
|
189 | 190 | In cell mode, the additional code lines are appended to the (possibly |
|
190 | 191 | empty) statement in the first line. Cell mode allows you to easily |
|
191 | 192 | profile multiline blocks without having to put them in a separate |
|
192 | 193 | function. |
|
193 | 194 | |
|
194 | 195 | The given statement (which doesn't require quote marks) is run via the |
|
195 | 196 | python profiler in a manner similar to the profile.run() function. |
|
196 | 197 | Namespaces are internally managed to work correctly; profile.run |
|
197 | 198 | cannot be used in IPython because it makes certain assumptions about |
|
198 | 199 | namespaces which do not hold under IPython. |
|
199 | 200 | |
|
200 | 201 | Options: |
|
201 | 202 | |
|
202 | 203 | -l <limit> |
|
203 | 204 | you can place restrictions on what or how much of the |
|
204 | 205 | profile gets printed. The limit value can be: |
|
205 | 206 | |
|
206 | 207 | * A string: only information for function names containing this string |
|
207 | 208 | is printed. |
|
208 | 209 | |
|
209 | 210 | * An integer: only these many lines are printed. |
|
210 | 211 | |
|
211 | 212 | * A float (between 0 and 1): this fraction of the report is printed |
|
212 | 213 | (for example, use a limit of 0.4 to see the topmost 40% only). |
|
213 | 214 | |
|
214 | 215 | You can combine several limits with repeated use of the option. For |
|
215 | 216 | example, ``-l __init__ -l 5`` will print only the topmost 5 lines of |
|
216 | 217 | information about class constructors. |
|
217 | 218 | |
|
218 | 219 | -r |
|
219 | 220 | return the pstats.Stats object generated by the profiling. This |
|
220 | 221 | object has all the information about the profile in it, and you can |
|
221 | 222 | later use it for further analysis or in other functions. |
|
222 | 223 | |
|
223 | 224 | -s <key> |
|
224 | 225 | sort profile by given key. You can provide more than one key |
|
225 | 226 | by using the option several times: '-s key1 -s key2 -s key3...'. The |
|
226 | 227 | default sorting key is 'time'. |
|
227 | 228 | |
|
228 | 229 | The following is copied verbatim from the profile documentation |
|
229 | 230 | referenced below: |
|
230 | 231 | |
|
231 | 232 | When more than one key is provided, additional keys are used as |
|
232 | 233 | secondary criteria when the there is equality in all keys selected |
|
233 | 234 | before them. |
|
234 | 235 | |
|
235 | 236 | Abbreviations can be used for any key names, as long as the |
|
236 | 237 | abbreviation is unambiguous. The following are the keys currently |
|
237 | 238 | defined: |
|
238 | 239 | |
|
239 | 240 | ============ ===================== |
|
240 | 241 | Valid Arg Meaning |
|
241 | 242 | ============ ===================== |
|
242 | 243 | "calls" call count |
|
243 | 244 | "cumulative" cumulative time |
|
244 | 245 | "file" file name |
|
245 | 246 | "module" file name |
|
246 | 247 | "pcalls" primitive call count |
|
247 | 248 | "line" line number |
|
248 | 249 | "name" function name |
|
249 | 250 | "nfl" name/file/line |
|
250 | 251 | "stdname" standard name |
|
251 | 252 | "time" internal time |
|
252 | 253 | ============ ===================== |
|
253 | 254 | |
|
254 | 255 | Note that all sorts on statistics are in descending order (placing |
|
255 | 256 | most time consuming items first), where as name, file, and line number |
|
256 | 257 | searches are in ascending order (i.e., alphabetical). The subtle |
|
257 | 258 | distinction between "nfl" and "stdname" is that the standard name is a |
|
258 | 259 | sort of the name as printed, which means that the embedded line |
|
259 | 260 | numbers get compared in an odd way. For example, lines 3, 20, and 40 |
|
260 | 261 | would (if the file names were the same) appear in the string order |
|
261 | 262 | "20" "3" and "40". In contrast, "nfl" does a numeric compare of the |
|
262 | 263 | line numbers. In fact, sort_stats("nfl") is the same as |
|
263 | 264 | sort_stats("name", "file", "line"). |
|
264 | 265 | |
|
265 | 266 | -T <filename> |
|
266 | 267 | save profile results as shown on screen to a text |
|
267 | 268 | file. The profile is still shown on screen. |
|
268 | 269 | |
|
269 | 270 | -D <filename> |
|
270 | 271 | save (via dump_stats) profile statistics to given |
|
271 | 272 | filename. This data is in a format understood by the pstats module, and |
|
272 | 273 | is generated by a call to the dump_stats() method of profile |
|
273 | 274 | objects. The profile is still shown on screen. |
|
274 | 275 | |
|
275 | 276 | -q |
|
276 | 277 | suppress output to the pager. Best used with -T and/or -D above. |
|
277 | 278 | |
|
278 | 279 | If you want to run complete programs under the profiler's control, use |
|
279 | 280 | ``%run -p [prof_opts] filename.py [args to program]`` where prof_opts |
|
280 | 281 | contains profiler specific options as described here. |
|
281 | 282 | |
|
282 | 283 | You can read the complete documentation for the profile module with:: |
|
283 | 284 | |
|
284 | 285 | In [1]: import profile; profile.help() |
|
285 | 286 | |
|
286 | 287 | .. versionchanged:: 7.3 |
|
287 | 288 | User variables are no longer expanded, |
|
288 | 289 | the magic line is always left unmodified. |
|
289 | 290 | |
|
290 | 291 | """ |
|
291 | 292 | opts, arg_str = self.parse_options(parameter_s, 'D:l:rs:T:q', |
|
292 | 293 | list_all=True, posix=False) |
|
293 | 294 | if cell is not None: |
|
294 | 295 | arg_str += '\n' + cell |
|
295 | 296 | arg_str = self.shell.transform_cell(arg_str) |
|
296 | 297 | return self._run_with_profiler(arg_str, opts, self.shell.user_ns) |
|
297 | 298 | |
|
298 | 299 | def _run_with_profiler(self, code, opts, namespace): |
|
299 | 300 | """ |
|
300 | 301 | Run `code` with profiler. Used by ``%prun`` and ``%run -p``. |
|
301 | 302 | |
|
302 | 303 | Parameters |
|
303 | 304 | ---------- |
|
304 | 305 | code : str |
|
305 | 306 | Code to be executed. |
|
306 | 307 | opts : Struct |
|
307 | 308 | Options parsed by `self.parse_options`. |
|
308 | 309 | namespace : dict |
|
309 | 310 | A dictionary for Python namespace (e.g., `self.shell.user_ns`). |
|
310 | 311 | |
|
311 | 312 | """ |
|
312 | 313 | |
|
313 | 314 | # Fill default values for unspecified options: |
|
314 | 315 | opts.merge(Struct(D=[''], l=[], s=['time'], T=[''])) |
|
315 | 316 | |
|
316 | 317 | prof = profile.Profile() |
|
317 | 318 | try: |
|
318 | 319 | prof = prof.runctx(code, namespace, namespace) |
|
319 | 320 | sys_exit = '' |
|
320 | 321 | except SystemExit: |
|
321 | 322 | sys_exit = """*** SystemExit exception caught in code being profiled.""" |
|
322 | 323 | |
|
323 | 324 | stats = pstats.Stats(prof).strip_dirs().sort_stats(*opts.s) |
|
324 | 325 | |
|
325 | 326 | lims = opts.l |
|
326 | 327 | if lims: |
|
327 | 328 | lims = [] # rebuild lims with ints/floats/strings |
|
328 | 329 | for lim in opts.l: |
|
329 | 330 | try: |
|
330 | 331 | lims.append(int(lim)) |
|
331 | 332 | except ValueError: |
|
332 | 333 | try: |
|
333 | 334 | lims.append(float(lim)) |
|
334 | 335 | except ValueError: |
|
335 | 336 | lims.append(lim) |
|
336 | 337 | |
|
337 | 338 | # Trap output. |
|
338 | 339 | stdout_trap = StringIO() |
|
339 | 340 | stats_stream = stats.stream |
|
340 | 341 | try: |
|
341 | 342 | stats.stream = stdout_trap |
|
342 | 343 | stats.print_stats(*lims) |
|
343 | 344 | finally: |
|
344 | 345 | stats.stream = stats_stream |
|
345 | 346 | |
|
346 | 347 | output = stdout_trap.getvalue() |
|
347 | 348 | output = output.rstrip() |
|
348 | 349 | |
|
349 | 350 | if 'q' not in opts: |
|
350 | 351 | page.page(output) |
|
351 | 352 | print(sys_exit, end=' ') |
|
352 | 353 | |
|
353 | 354 | dump_file = opts.D[0] |
|
354 | 355 | text_file = opts.T[0] |
|
355 | 356 | if dump_file: |
|
356 | 357 | prof.dump_stats(dump_file) |
|
357 | 358 | print( |
|
358 | 359 | f"\n*** Profile stats marshalled to file {repr(dump_file)}.{sys_exit}" |
|
359 | 360 | ) |
|
360 | 361 | if text_file: |
|
361 | 362 | pfile = Path(text_file) |
|
362 | 363 | pfile.touch(exist_ok=True) |
|
363 | 364 | pfile.write_text(output, encoding="utf-8") |
|
364 | 365 | |
|
365 | 366 | print( |
|
366 | 367 | f"\n*** Profile printout saved to text file {repr(text_file)}.{sys_exit}" |
|
367 | 368 | ) |
|
368 | 369 | |
|
369 | 370 | if 'r' in opts: |
|
370 | 371 | return stats |
|
371 | 372 | |
|
372 | 373 | return None |
|
373 | 374 | |
|
374 | 375 | @line_magic |
|
375 | 376 | def pdb(self, parameter_s=''): |
|
376 | 377 | """Control the automatic calling of the pdb interactive debugger. |
|
377 | 378 | |
|
378 | 379 | Call as '%pdb on', '%pdb 1', '%pdb off' or '%pdb 0'. If called without |
|
379 | 380 | argument it works as a toggle. |
|
380 | 381 | |
|
381 | 382 | When an exception is triggered, IPython can optionally call the |
|
382 | 383 | interactive pdb debugger after the traceback printout. %pdb toggles |
|
383 | 384 | this feature on and off. |
|
384 | 385 | |
|
385 | 386 | The initial state of this feature is set in your configuration |
|
386 | 387 | file (the option is ``InteractiveShell.pdb``). |
|
387 | 388 | |
|
388 | 389 | If you want to just activate the debugger AFTER an exception has fired, |
|
389 | 390 | without having to type '%pdb on' and rerunning your code, you can use |
|
390 | 391 | the %debug magic.""" |
|
391 | 392 | |
|
392 | 393 | par = parameter_s.strip().lower() |
|
393 | 394 | |
|
394 | 395 | if par: |
|
395 | 396 | try: |
|
396 | 397 | new_pdb = {'off':0,'0':0,'on':1,'1':1}[par] |
|
397 | 398 | except KeyError: |
|
398 | 399 | print ('Incorrect argument. Use on/1, off/0, ' |
|
399 | 400 | 'or nothing for a toggle.') |
|
400 | 401 | return |
|
401 | 402 | else: |
|
402 | 403 | # toggle |
|
403 | 404 | new_pdb = not self.shell.call_pdb |
|
404 | 405 | |
|
405 | 406 | # set on the shell |
|
406 | 407 | self.shell.call_pdb = new_pdb |
|
407 | 408 | print('Automatic pdb calling has been turned',on_off(new_pdb)) |
|
408 | 409 | |
|
409 | 410 | @magic_arguments.magic_arguments() |
|
410 | 411 | @magic_arguments.argument('--breakpoint', '-b', metavar='FILE:LINE', |
|
411 | 412 | help=""" |
|
412 | 413 | Set break point at LINE in FILE. |
|
413 | 414 | """ |
|
414 | 415 | ) |
|
415 | 416 | @magic_arguments.argument('statement', nargs='*', |
|
416 | 417 | help=""" |
|
417 | 418 | Code to run in debugger. |
|
418 | 419 | You can omit this in cell magic mode. |
|
419 | 420 | """ |
|
420 | 421 | ) |
|
421 | 422 | @no_var_expand |
|
422 | 423 | @line_cell_magic |
|
423 | 424 | def debug(self, line='', cell=None): |
|
424 | 425 | """Activate the interactive debugger. |
|
425 | 426 | |
|
426 | 427 | This magic command support two ways of activating debugger. |
|
427 | 428 | One is to activate debugger before executing code. This way, you |
|
428 | 429 | can set a break point, to step through the code from the point. |
|
429 | 430 | You can use this mode by giving statements to execute and optionally |
|
430 | 431 | a breakpoint. |
|
431 | 432 | |
|
432 | 433 | The other one is to activate debugger in post-mortem mode. You can |
|
433 | 434 | activate this mode simply running %debug without any argument. |
|
434 | 435 | If an exception has just occurred, this lets you inspect its stack |
|
435 | 436 | frames interactively. Note that this will always work only on the last |
|
436 | 437 | traceback that occurred, so you must call this quickly after an |
|
437 | 438 | exception that you wish to inspect has fired, because if another one |
|
438 | 439 | occurs, it clobbers the previous one. |
|
439 | 440 | |
|
440 | 441 | If you want IPython to automatically do this on every exception, see |
|
441 | 442 | the %pdb magic for more details. |
|
442 | 443 | |
|
443 | 444 | .. versionchanged:: 7.3 |
|
444 | 445 | When running code, user variables are no longer expanded, |
|
445 | 446 | the magic line is always left unmodified. |
|
446 | 447 | |
|
447 | 448 | """ |
|
448 | 449 | args = magic_arguments.parse_argstring(self.debug, line) |
|
449 | 450 | |
|
450 | 451 | if not (args.breakpoint or args.statement or cell): |
|
451 | 452 | self._debug_post_mortem() |
|
452 | 453 | elif not (args.breakpoint or cell): |
|
453 | 454 | # If there is no breakpoints, the line is just code to execute |
|
454 | 455 | self._debug_exec(line, None) |
|
455 | 456 | else: |
|
456 | 457 | # Here we try to reconstruct the code from the output of |
|
457 | 458 | # parse_argstring. This might not work if the code has spaces |
|
458 | 459 | # For example this fails for `print("a b")` |
|
459 | 460 | code = "\n".join(args.statement) |
|
460 | 461 | if cell: |
|
461 | 462 | code += "\n" + cell |
|
462 | 463 | self._debug_exec(code, args.breakpoint) |
|
463 | 464 | |
|
464 | 465 | def _debug_post_mortem(self): |
|
465 | 466 | self.shell.debugger(force=True) |
|
466 | 467 | |
|
467 | 468 | def _debug_exec(self, code, breakpoint): |
|
468 | 469 | if breakpoint: |
|
469 | 470 | (filename, bp_line) = breakpoint.rsplit(':', 1) |
|
470 | 471 | bp_line = int(bp_line) |
|
471 | 472 | else: |
|
472 | 473 | (filename, bp_line) = (None, None) |
|
473 | 474 | self._run_with_debugger(code, self.shell.user_ns, filename, bp_line) |
|
474 | 475 | |
|
475 | 476 | @line_magic |
|
476 | 477 | def tb(self, s): |
|
477 | 478 | """Print the last traceback. |
|
478 | 479 | |
|
479 | 480 | Optionally, specify an exception reporting mode, tuning the |
|
480 | 481 | verbosity of the traceback. By default the currently-active exception |
|
481 | 482 | mode is used. See %xmode for changing exception reporting modes. |
|
482 | 483 | |
|
483 | 484 | Valid modes: Plain, Context, Verbose, and Minimal. |
|
484 | 485 | """ |
|
485 | 486 | interactive_tb = self.shell.InteractiveTB |
|
486 | 487 | if s: |
|
487 | 488 | # Switch exception reporting mode for this one call. |
|
488 | 489 | # Ensure it is switched back. |
|
489 | 490 | def xmode_switch_err(name): |
|
490 | 491 | warn('Error changing %s exception modes.\n%s' % |
|
491 | 492 | (name,sys.exc_info()[1])) |
|
492 | 493 | |
|
493 | 494 | new_mode = s.strip().capitalize() |
|
494 | 495 | original_mode = interactive_tb.mode |
|
495 | 496 | try: |
|
496 | 497 | try: |
|
497 | 498 | interactive_tb.set_mode(mode=new_mode) |
|
498 | 499 | except Exception: |
|
499 | 500 | xmode_switch_err('user') |
|
500 | 501 | else: |
|
501 | 502 | self.shell.showtraceback() |
|
502 | 503 | finally: |
|
503 | 504 | interactive_tb.set_mode(mode=original_mode) |
|
504 | 505 | else: |
|
505 | 506 | self.shell.showtraceback() |
|
506 | 507 | |
|
507 | 508 | @skip_doctest |
|
508 | 509 | @line_magic |
|
509 | 510 | def run(self, parameter_s='', runner=None, |
|
510 | 511 | file_finder=get_py_filename): |
|
511 | 512 | """Run the named file inside IPython as a program. |
|
512 | 513 | |
|
513 | 514 | Usage:: |
|
514 | 515 | |
|
515 | 516 | %run [-n -i -e -G] |
|
516 | 517 | [( -t [-N<N>] | -d [-b<N>] | -p [profile options] )] |
|
517 | 518 | ( -m mod | filename ) [args] |
|
518 | 519 | |
|
519 | 520 | The filename argument should be either a pure Python script (with |
|
520 | 521 | extension ``.py``), or a file with custom IPython syntax (such as |
|
521 | 522 | magics). If the latter, the file can be either a script with ``.ipy`` |
|
522 | 523 | extension, or a Jupyter notebook with ``.ipynb`` extension. When running |
|
523 | 524 | a Jupyter notebook, the output from print statements and other |
|
524 | 525 | displayed objects will appear in the terminal (even matplotlib figures |
|
525 | 526 | will open, if a terminal-compliant backend is being used). Note that, |
|
526 | 527 | at the system command line, the ``jupyter run`` command offers similar |
|
527 | 528 | functionality for executing notebooks (albeit currently with some |
|
528 | 529 | differences in supported options). |
|
529 | 530 | |
|
530 | 531 | Parameters after the filename are passed as command-line arguments to |
|
531 | 532 | the program (put in sys.argv). Then, control returns to IPython's |
|
532 | 533 | prompt. |
|
533 | 534 | |
|
534 | 535 | This is similar to running at a system prompt ``python file args``, |
|
535 | 536 | but with the advantage of giving you IPython's tracebacks, and of |
|
536 | 537 | loading all variables into your interactive namespace for further use |
|
537 | 538 | (unless -p is used, see below). |
|
538 | 539 | |
|
539 | 540 | The file is executed in a namespace initially consisting only of |
|
540 | 541 | ``__name__=='__main__'`` and sys.argv constructed as indicated. It thus |
|
541 | 542 | sees its environment as if it were being run as a stand-alone program |
|
542 | 543 | (except for sharing global objects such as previously imported |
|
543 | 544 | modules). But after execution, the IPython interactive namespace gets |
|
544 | 545 | updated with all variables defined in the program (except for __name__ |
|
545 | 546 | and sys.argv). This allows for very convenient loading of code for |
|
546 | 547 | interactive work, while giving each program a 'clean sheet' to run in. |
|
547 | 548 | |
|
548 | 549 | Arguments are expanded using shell-like glob match. Patterns |
|
549 | 550 | '*', '?', '[seq]' and '[!seq]' can be used. Additionally, |
|
550 | 551 | tilde '~' will be expanded into user's home directory. Unlike |
|
551 | 552 | real shells, quotation does not suppress expansions. Use |
|
552 | 553 | *two* back slashes (e.g. ``\\\\*``) to suppress expansions. |
|
553 | 554 | To completely disable these expansions, you can use -G flag. |
|
554 | 555 | |
|
555 | 556 | On Windows systems, the use of single quotes `'` when specifying |
|
556 | 557 | a file is not supported. Use double quotes `"`. |
|
557 | 558 | |
|
558 | 559 | Options: |
|
559 | 560 | |
|
560 | 561 | -n |
|
561 | 562 | __name__ is NOT set to '__main__', but to the running file's name |
|
562 | 563 | without extension (as python does under import). This allows running |
|
563 | 564 | scripts and reloading the definitions in them without calling code |
|
564 | 565 | protected by an ``if __name__ == "__main__"`` clause. |
|
565 | 566 | |
|
566 | 567 | -i |
|
567 | 568 | run the file in IPython's namespace instead of an empty one. This |
|
568 | 569 | is useful if you are experimenting with code written in a text editor |
|
569 | 570 | which depends on variables defined interactively. |
|
570 | 571 | |
|
571 | 572 | -e |
|
572 | 573 | ignore sys.exit() calls or SystemExit exceptions in the script |
|
573 | 574 | being run. This is particularly useful if IPython is being used to |
|
574 | 575 | run unittests, which always exit with a sys.exit() call. In such |
|
575 | 576 | cases you are interested in the output of the test results, not in |
|
576 | 577 | seeing a traceback of the unittest module. |
|
577 | 578 | |
|
578 | 579 | -t |
|
579 | 580 | print timing information at the end of the run. IPython will give |
|
580 | 581 | you an estimated CPU time consumption for your script, which under |
|
581 | 582 | Unix uses the resource module to avoid the wraparound problems of |
|
582 | 583 | time.clock(). Under Unix, an estimate of time spent on system tasks |
|
583 | 584 | is also given (for Windows platforms this is reported as 0.0). |
|
584 | 585 | |
|
585 | 586 | If -t is given, an additional ``-N<N>`` option can be given, where <N> |
|
586 | 587 | must be an integer indicating how many times you want the script to |
|
587 | 588 | run. The final timing report will include total and per run results. |
|
588 | 589 | |
|
589 | 590 | For example (testing the script uniq_stable.py):: |
|
590 | 591 | |
|
591 | 592 | In [1]: run -t uniq_stable |
|
592 | 593 | |
|
593 | 594 | IPython CPU timings (estimated): |
|
594 | 595 | User : 0.19597 s. |
|
595 | 596 | System: 0.0 s. |
|
596 | 597 | |
|
597 | 598 | In [2]: run -t -N5 uniq_stable |
|
598 | 599 | |
|
599 | 600 | IPython CPU timings (estimated): |
|
600 | 601 | Total runs performed: 5 |
|
601 | 602 | Times : Total Per run |
|
602 | 603 | User : 0.910862 s, 0.1821724 s. |
|
603 | 604 | System: 0.0 s, 0.0 s. |
|
604 | 605 | |
|
605 | 606 | -d |
|
606 | 607 | run your program under the control of pdb, the Python debugger. |
|
607 | 608 | This allows you to execute your program step by step, watch variables, |
|
608 | 609 | etc. Internally, what IPython does is similar to calling:: |
|
609 | 610 | |
|
610 | 611 | pdb.run('execfile("YOURFILENAME")') |
|
611 | 612 | |
|
612 | 613 | with a breakpoint set on line 1 of your file. You can change the line |
|
613 | 614 | number for this automatic breakpoint to be <N> by using the -bN option |
|
614 | 615 | (where N must be an integer). For example:: |
|
615 | 616 | |
|
616 | 617 | %run -d -b40 myscript |
|
617 | 618 | |
|
618 | 619 | will set the first breakpoint at line 40 in myscript.py. Note that |
|
619 | 620 | the first breakpoint must be set on a line which actually does |
|
620 | 621 | something (not a comment or docstring) for it to stop execution. |
|
621 | 622 | |
|
622 | 623 | Or you can specify a breakpoint in a different file:: |
|
623 | 624 | |
|
624 | 625 | %run -d -b myotherfile.py:20 myscript |
|
625 | 626 | |
|
626 | 627 | When the pdb debugger starts, you will see a (Pdb) prompt. You must |
|
627 | 628 | first enter 'c' (without quotes) to start execution up to the first |
|
628 | 629 | breakpoint. |
|
629 | 630 | |
|
630 | 631 | Entering 'help' gives information about the use of the debugger. You |
|
631 | 632 | can easily see pdb's full documentation with "import pdb;pdb.help()" |
|
632 | 633 | at a prompt. |
|
633 | 634 | |
|
634 | 635 | -p |
|
635 | 636 | run program under the control of the Python profiler module (which |
|
636 | 637 | prints a detailed report of execution times, function calls, etc). |
|
637 | 638 | |
|
638 | 639 | You can pass other options after -p which affect the behavior of the |
|
639 | 640 | profiler itself. See the docs for %prun for details. |
|
640 | 641 | |
|
641 | 642 | In this mode, the program's variables do NOT propagate back to the |
|
642 | 643 | IPython interactive namespace (because they remain in the namespace |
|
643 | 644 | where the profiler executes them). |
|
644 | 645 | |
|
645 | 646 | Internally this triggers a call to %prun, see its documentation for |
|
646 | 647 | details on the options available specifically for profiling. |
|
647 | 648 | |
|
648 | 649 | There is one special usage for which the text above doesn't apply: |
|
649 | 650 | if the filename ends with .ipy[nb], the file is run as ipython script, |
|
650 | 651 | just as if the commands were written on IPython prompt. |
|
651 | 652 | |
|
652 | 653 | -m |
|
653 | 654 | specify module name to load instead of script path. Similar to |
|
654 | 655 | the -m option for the python interpreter. Use this option last if you |
|
655 | 656 | want to combine with other %run options. Unlike the python interpreter |
|
656 | 657 | only source modules are allowed no .pyc or .pyo files. |
|
657 | 658 | For example:: |
|
658 | 659 | |
|
659 | 660 | %run -m example |
|
660 | 661 | |
|
661 | 662 | will run the example module. |
|
662 | 663 | |
|
663 | 664 | -G |
|
664 | 665 | disable shell-like glob expansion of arguments. |
|
665 | 666 | |
|
666 | 667 | """ |
|
667 | 668 | |
|
668 | 669 | # Logic to handle issue #3664 |
|
669 | 670 | # Add '--' after '-m <module_name>' to ignore additional args passed to a module. |
|
670 | 671 | if '-m' in parameter_s and '--' not in parameter_s: |
|
671 | 672 | argv = shlex.split(parameter_s, posix=(os.name == 'posix')) |
|
672 | 673 | for idx, arg in enumerate(argv): |
|
673 | 674 | if arg and arg.startswith('-') and arg != '-': |
|
674 | 675 | if arg == '-m': |
|
675 | 676 | argv.insert(idx + 2, '--') |
|
676 | 677 | break |
|
677 | 678 | else: |
|
678 | 679 | # Positional arg, break |
|
679 | 680 | break |
|
680 | 681 | parameter_s = ' '.join(shlex.quote(arg) for arg in argv) |
|
681 | 682 | |
|
682 | 683 | # get arguments and set sys.argv for program to be run. |
|
683 | 684 | opts, arg_lst = self.parse_options(parameter_s, |
|
684 | 685 | 'nidtN:b:pD:l:rs:T:em:G', |
|
685 | 686 | mode='list', list_all=1) |
|
686 | 687 | if "m" in opts: |
|
687 | 688 | modulename = opts["m"][0] |
|
688 | 689 | modpath = find_mod(modulename) |
|
689 | 690 | if modpath is None: |
|
690 | 691 | msg = '%r is not a valid modulename on sys.path'%modulename |
|
691 | 692 | raise Exception(msg) |
|
692 | 693 | arg_lst = [modpath] + arg_lst |
|
693 | 694 | try: |
|
694 | 695 | fpath = None # initialize to make sure fpath is in scope later |
|
695 | 696 | fpath = arg_lst[0] |
|
696 | 697 | filename = file_finder(fpath) |
|
697 | 698 | except IndexError as e: |
|
698 | 699 | msg = 'you must provide at least a filename.' |
|
699 | 700 | raise Exception(msg) from e |
|
700 | 701 | except IOError as e: |
|
701 | 702 | try: |
|
702 | 703 | msg = str(e) |
|
703 | 704 | except UnicodeError: |
|
704 | 705 | msg = e.message |
|
705 | 706 | if os.name == 'nt' and re.match(r"^'.*'$",fpath): |
|
706 | 707 | warn('For Windows, use double quotes to wrap a filename: %run "mypath\\myfile.py"') |
|
707 | 708 | raise Exception(msg) from e |
|
708 | 709 | except TypeError: |
|
709 | 710 | if fpath in sys.meta_path: |
|
710 | 711 | filename = "" |
|
711 | 712 | else: |
|
712 | 713 | raise |
|
713 | 714 | |
|
714 | 715 | if filename.lower().endswith(('.ipy', '.ipynb')): |
|
715 | 716 | with preserve_keys(self.shell.user_ns, '__file__'): |
|
716 | 717 | self.shell.user_ns['__file__'] = filename |
|
717 | 718 | self.shell.safe_execfile_ipy(filename, raise_exceptions=True) |
|
718 | 719 | return |
|
719 | 720 | |
|
720 | 721 | # Control the response to exit() calls made by the script being run |
|
721 | 722 | exit_ignore = 'e' in opts |
|
722 | 723 | |
|
723 | 724 | # Make sure that the running script gets a proper sys.argv as if it |
|
724 | 725 | # were run from a system shell. |
|
725 | 726 | save_argv = sys.argv # save it for later restoring |
|
726 | 727 | |
|
727 | 728 | if 'G' in opts: |
|
728 | 729 | args = arg_lst[1:] |
|
729 | 730 | else: |
|
730 | 731 | # tilde and glob expansion |
|
731 | 732 | args = shellglob(map(os.path.expanduser, arg_lst[1:])) |
|
732 | 733 | |
|
733 | 734 | sys.argv = [filename] + args # put in the proper filename |
|
734 | 735 | |
|
735 | 736 | if 'n' in opts: |
|
736 | 737 | name = Path(filename).stem |
|
737 | 738 | else: |
|
738 | 739 | name = '__main__' |
|
739 | 740 | |
|
740 | 741 | if 'i' in opts: |
|
741 | 742 | # Run in user's interactive namespace |
|
742 | 743 | prog_ns = self.shell.user_ns |
|
743 | 744 | __name__save = self.shell.user_ns['__name__'] |
|
744 | 745 | prog_ns['__name__'] = name |
|
745 | 746 | main_mod = self.shell.user_module |
|
746 | 747 | |
|
747 | 748 | # Since '%run foo' emulates 'python foo.py' at the cmd line, we must |
|
748 | 749 | # set the __file__ global in the script's namespace |
|
749 | 750 | # TK: Is this necessary in interactive mode? |
|
750 | 751 | prog_ns['__file__'] = filename |
|
751 | 752 | else: |
|
752 | 753 | # Run in a fresh, empty namespace |
|
753 | 754 | |
|
754 | 755 | # The shell MUST hold a reference to prog_ns so after %run |
|
755 | 756 | # exits, the python deletion mechanism doesn't zero it out |
|
756 | 757 | # (leaving dangling references). See interactiveshell for details |
|
757 | 758 | main_mod = self.shell.new_main_mod(filename, name) |
|
758 | 759 | prog_ns = main_mod.__dict__ |
|
759 | 760 | |
|
760 | 761 | # pickle fix. See interactiveshell for an explanation. But we need to |
|
761 | 762 | # make sure that, if we overwrite __main__, we replace it at the end |
|
762 | 763 | main_mod_name = prog_ns['__name__'] |
|
763 | 764 | |
|
764 | 765 | if main_mod_name == '__main__': |
|
765 | 766 | restore_main = sys.modules['__main__'] |
|
766 | 767 | else: |
|
767 | 768 | restore_main = False |
|
768 | 769 | |
|
769 | 770 | # This needs to be undone at the end to prevent holding references to |
|
770 | 771 | # every single object ever created. |
|
771 | 772 | sys.modules[main_mod_name] = main_mod |
|
772 | 773 | |
|
773 | 774 | if 'p' in opts or 'd' in opts: |
|
774 | 775 | if 'm' in opts: |
|
775 | 776 | code = 'run_module(modulename, prog_ns)' |
|
776 | 777 | code_ns = { |
|
777 | 778 | 'run_module': self.shell.safe_run_module, |
|
778 | 779 | 'prog_ns': prog_ns, |
|
779 | 780 | 'modulename': modulename, |
|
780 | 781 | } |
|
781 | 782 | else: |
|
782 | 783 | if 'd' in opts: |
|
783 | 784 | # allow exceptions to raise in debug mode |
|
784 | 785 | code = 'execfile(filename, prog_ns, raise_exceptions=True)' |
|
785 | 786 | else: |
|
786 | 787 | code = 'execfile(filename, prog_ns)' |
|
787 | 788 | code_ns = { |
|
788 | 789 | 'execfile': self.shell.safe_execfile, |
|
789 | 790 | 'prog_ns': prog_ns, |
|
790 | 791 | 'filename': get_py_filename(filename), |
|
791 | 792 | } |
|
792 | 793 | |
|
793 | 794 | try: |
|
794 | 795 | stats = None |
|
795 | 796 | if 'p' in opts: |
|
796 | 797 | stats = self._run_with_profiler(code, opts, code_ns) |
|
797 | 798 | else: |
|
798 | 799 | if 'd' in opts: |
|
799 | 800 | bp_file, bp_line = parse_breakpoint( |
|
800 | 801 | opts.get('b', ['1'])[0], filename) |
|
801 | 802 | self._run_with_debugger( |
|
802 | 803 | code, code_ns, filename, bp_line, bp_file) |
|
803 | 804 | else: |
|
804 | 805 | if 'm' in opts: |
|
805 | 806 | def run(): |
|
806 | 807 | self.shell.safe_run_module(modulename, prog_ns) |
|
807 | 808 | else: |
|
808 | 809 | if runner is None: |
|
809 | 810 | runner = self.default_runner |
|
810 | 811 | if runner is None: |
|
811 | 812 | runner = self.shell.safe_execfile |
|
812 | 813 | |
|
813 | 814 | def run(): |
|
814 | 815 | runner(filename, prog_ns, prog_ns, |
|
815 | 816 | exit_ignore=exit_ignore) |
|
816 | 817 | |
|
817 | 818 | if 't' in opts: |
|
818 | 819 | # timed execution |
|
819 | 820 | try: |
|
820 | 821 | nruns = int(opts['N'][0]) |
|
821 | 822 | if nruns < 1: |
|
822 | 823 | error('Number of runs must be >=1') |
|
823 | 824 | return |
|
824 | 825 | except (KeyError): |
|
825 | 826 | nruns = 1 |
|
826 | 827 | self._run_with_timing(run, nruns) |
|
827 | 828 | else: |
|
828 | 829 | # regular execution |
|
829 | 830 | run() |
|
830 | 831 | |
|
831 | 832 | if 'i' in opts: |
|
832 | 833 | self.shell.user_ns['__name__'] = __name__save |
|
833 | 834 | else: |
|
834 | 835 | # update IPython interactive namespace |
|
835 | 836 | |
|
836 | 837 | # Some forms of read errors on the file may mean the |
|
837 | 838 | # __name__ key was never set; using pop we don't have to |
|
838 | 839 | # worry about a possible KeyError. |
|
839 | 840 | prog_ns.pop('__name__', None) |
|
840 | 841 | |
|
841 | 842 | with preserve_keys(self.shell.user_ns, '__file__'): |
|
842 | 843 | self.shell.user_ns.update(prog_ns) |
|
843 | 844 | finally: |
|
844 | 845 | # It's a bit of a mystery why, but __builtins__ can change from |
|
845 | 846 | # being a module to becoming a dict missing some key data after |
|
846 | 847 | # %run. As best I can see, this is NOT something IPython is doing |
|
847 | 848 | # at all, and similar problems have been reported before: |
|
848 | 849 | # http://coding.derkeiler.com/Archive/Python/comp.lang.python/2004-10/0188.html |
|
849 | 850 | # Since this seems to be done by the interpreter itself, the best |
|
850 | 851 | # we can do is to at least restore __builtins__ for the user on |
|
851 | 852 | # exit. |
|
852 | 853 | self.shell.user_ns['__builtins__'] = builtin_mod |
|
853 | 854 | |
|
854 | 855 | # Ensure key global structures are restored |
|
855 | 856 | sys.argv = save_argv |
|
856 | 857 | if restore_main: |
|
857 | 858 | sys.modules['__main__'] = restore_main |
|
858 | 859 | if '__mp_main__' in sys.modules: |
|
859 | 860 | sys.modules['__mp_main__'] = restore_main |
|
860 | 861 | else: |
|
861 | 862 | # Remove from sys.modules the reference to main_mod we'd |
|
862 | 863 | # added. Otherwise it will trap references to objects |
|
863 | 864 | # contained therein. |
|
864 | 865 | del sys.modules[main_mod_name] |
|
865 | 866 | |
|
866 | 867 | return stats |
|
867 | 868 | |
|
868 | 869 | def _run_with_debugger(self, code, code_ns, filename=None, |
|
869 | 870 | bp_line=None, bp_file=None): |
|
870 | 871 | """ |
|
871 | 872 | Run `code` in debugger with a break point. |
|
872 | 873 | |
|
873 | 874 | Parameters |
|
874 | 875 | ---------- |
|
875 | 876 | code : str |
|
876 | 877 | Code to execute. |
|
877 | 878 | code_ns : dict |
|
878 | 879 | A namespace in which `code` is executed. |
|
879 | 880 | filename : str |
|
880 | 881 | `code` is ran as if it is in `filename`. |
|
881 | 882 | bp_line : int, optional |
|
882 | 883 | Line number of the break point. |
|
883 | 884 | bp_file : str, optional |
|
884 | 885 | Path to the file in which break point is specified. |
|
885 | 886 | `filename` is used if not given. |
|
886 | 887 | |
|
887 | 888 | Raises |
|
888 | 889 | ------ |
|
889 | 890 | UsageError |
|
890 | 891 | If the break point given by `bp_line` is not valid. |
|
891 | 892 | |
|
892 | 893 | """ |
|
893 | 894 | deb = self.shell.InteractiveTB.pdb |
|
894 | 895 | if not deb: |
|
895 | 896 | self.shell.InteractiveTB.pdb = self.shell.InteractiveTB.debugger_cls() |
|
896 | 897 | deb = self.shell.InteractiveTB.pdb |
|
897 | 898 | |
|
898 | 899 | # deb.checkline() fails if deb.curframe exists but is None; it can |
|
899 | 900 | # handle it not existing. https://github.com/ipython/ipython/issues/10028 |
|
900 | 901 | if hasattr(deb, 'curframe'): |
|
901 | 902 | del deb.curframe |
|
902 | 903 | |
|
903 | 904 | # reset Breakpoint state, which is moronically kept |
|
904 | 905 | # in a class |
|
905 | 906 | bdb.Breakpoint.next = 1 |
|
906 | 907 | bdb.Breakpoint.bplist = {} |
|
907 | 908 | bdb.Breakpoint.bpbynumber = [None] |
|
908 | 909 | deb.clear_all_breaks() |
|
909 | 910 | if bp_line is not None: |
|
910 | 911 | # Set an initial breakpoint to stop execution |
|
911 | 912 | maxtries = 10 |
|
912 | 913 | bp_file = bp_file or filename |
|
913 | 914 | checkline = deb.checkline(bp_file, bp_line) |
|
914 | 915 | if not checkline: |
|
915 | 916 | for bp in range(bp_line + 1, bp_line + maxtries + 1): |
|
916 | 917 | if deb.checkline(bp_file, bp): |
|
917 | 918 | break |
|
918 | 919 | else: |
|
919 | 920 | msg = ("\nI failed to find a valid line to set " |
|
920 | 921 | "a breakpoint\n" |
|
921 | 922 | "after trying up to line: %s.\n" |
|
922 | 923 | "Please set a valid breakpoint manually " |
|
923 | 924 | "with the -b option." % bp) |
|
924 | 925 | raise UsageError(msg) |
|
925 | 926 | # if we find a good linenumber, set the breakpoint |
|
926 | 927 | deb.do_break('%s:%s' % (bp_file, bp_line)) |
|
927 | 928 | |
|
928 | 929 | if filename: |
|
929 | 930 | # Mimic Pdb._runscript(...) |
|
930 | 931 | deb._wait_for_mainpyfile = True |
|
931 | 932 | deb.mainpyfile = deb.canonic(filename) |
|
932 | 933 | |
|
933 | 934 | # Start file run |
|
934 | 935 | print("NOTE: Enter 'c' at the %s prompt to continue execution." % deb.prompt) |
|
935 | 936 | try: |
|
936 | 937 | if filename: |
|
937 | 938 | # save filename so it can be used by methods on the deb object |
|
938 | 939 | deb._exec_filename = filename |
|
939 | 940 | while True: |
|
940 | 941 | try: |
|
941 | 942 | trace = sys.gettrace() |
|
942 | 943 | deb.run(code, code_ns) |
|
943 | 944 | except Restart: |
|
944 | 945 | print("Restarting") |
|
945 | 946 | if filename: |
|
946 | 947 | deb._wait_for_mainpyfile = True |
|
947 | 948 | deb.mainpyfile = deb.canonic(filename) |
|
948 | 949 | continue |
|
949 | 950 | else: |
|
950 | 951 | break |
|
951 | 952 | finally: |
|
952 | 953 | sys.settrace(trace) |
|
953 | 954 | |
|
954 | 955 | |
|
955 | 956 | except: |
|
956 | 957 | etype, value, tb = sys.exc_info() |
|
957 | 958 | # Skip three frames in the traceback: the %run one, |
|
958 | 959 | # one inside bdb.py, and the command-line typed by the |
|
959 | 960 | # user (run by exec in pdb itself). |
|
960 | 961 | self.shell.InteractiveTB(etype, value, tb, tb_offset=3) |
|
961 | 962 | |
|
962 | 963 | @staticmethod |
|
963 | 964 | def _run_with_timing(run, nruns): |
|
964 | 965 | """ |
|
965 | 966 | Run function `run` and print timing information. |
|
966 | 967 | |
|
967 | 968 | Parameters |
|
968 | 969 | ---------- |
|
969 | 970 | run : callable |
|
970 | 971 | Any callable object which takes no argument. |
|
971 | 972 | nruns : int |
|
972 | 973 | Number of times to execute `run`. |
|
973 | 974 | |
|
974 | 975 | """ |
|
975 | 976 | twall0 = time.perf_counter() |
|
976 | 977 | if nruns == 1: |
|
977 | 978 | t0 = clock2() |
|
978 | 979 | run() |
|
979 | 980 | t1 = clock2() |
|
980 | 981 | t_usr = t1[0] - t0[0] |
|
981 | 982 | t_sys = t1[1] - t0[1] |
|
982 | 983 | print("\nIPython CPU timings (estimated):") |
|
983 | 984 | print(" User : %10.2f s." % t_usr) |
|
984 | 985 | print(" System : %10.2f s." % t_sys) |
|
985 | 986 | else: |
|
986 | 987 | runs = range(nruns) |
|
987 | 988 | t0 = clock2() |
|
988 | 989 | for nr in runs: |
|
989 | 990 | run() |
|
990 | 991 | t1 = clock2() |
|
991 | 992 | t_usr = t1[0] - t0[0] |
|
992 | 993 | t_sys = t1[1] - t0[1] |
|
993 | 994 | print("\nIPython CPU timings (estimated):") |
|
994 | 995 | print("Total runs performed:", nruns) |
|
995 | 996 | print(" Times : %10s %10s" % ('Total', 'Per run')) |
|
996 | 997 | print(" User : %10.2f s, %10.2f s." % (t_usr, t_usr / nruns)) |
|
997 | 998 | print(" System : %10.2f s, %10.2f s." % (t_sys, t_sys / nruns)) |
|
998 | 999 | twall1 = time.perf_counter() |
|
999 | 1000 | print("Wall time: %10.2f s." % (twall1 - twall0)) |
|
1000 | 1001 | |
|
1001 | 1002 | @skip_doctest |
|
1002 | 1003 | @no_var_expand |
|
1003 | 1004 | @line_cell_magic |
|
1004 | 1005 | @needs_local_scope |
|
1005 | 1006 | def timeit(self, line='', cell=None, local_ns=None): |
|
1006 | 1007 | """Time execution of a Python statement or expression |
|
1007 | 1008 | |
|
1008 | 1009 | Usage, in line mode: |
|
1009 | 1010 | %timeit [-n<N> -r<R> [-t|-c] -q -p<P> -o] statement |
|
1010 | 1011 | or in cell mode: |
|
1011 | 1012 | %%timeit [-n<N> -r<R> [-t|-c] -q -p<P> -o] setup_code |
|
1012 | 1013 | code |
|
1013 | 1014 | code... |
|
1014 | 1015 | |
|
1015 | 1016 | Time execution of a Python statement or expression using the timeit |
|
1016 | 1017 | module. This function can be used both as a line and cell magic: |
|
1017 | 1018 | |
|
1018 | 1019 | - In line mode you can time a single-line statement (though multiple |
|
1019 | 1020 | ones can be chained with using semicolons). |
|
1020 | 1021 | |
|
1021 | 1022 | - In cell mode, the statement in the first line is used as setup code |
|
1022 | 1023 | (executed but not timed) and the body of the cell is timed. The cell |
|
1023 | 1024 | body has access to any variables created in the setup code. |
|
1024 | 1025 | |
|
1025 | 1026 | Options: |
|
1026 | 1027 | -n<N>: execute the given statement <N> times in a loop. If <N> is not |
|
1027 | 1028 | provided, <N> is determined so as to get sufficient accuracy. |
|
1028 | 1029 | |
|
1029 | 1030 | -r<R>: number of repeats <R>, each consisting of <N> loops, and take the |
|
1030 | 1031 | best result. |
|
1031 | 1032 | Default: 7 |
|
1032 | 1033 | |
|
1033 | 1034 | -t: use time.time to measure the time, which is the default on Unix. |
|
1034 | 1035 | This function measures wall time. |
|
1035 | 1036 | |
|
1036 | 1037 | -c: use time.clock to measure the time, which is the default on |
|
1037 | 1038 | Windows and measures wall time. On Unix, resource.getrusage is used |
|
1038 | 1039 | instead and returns the CPU user time. |
|
1039 | 1040 | |
|
1040 | 1041 | -p<P>: use a precision of <P> digits to display the timing result. |
|
1041 | 1042 | Default: 3 |
|
1042 | 1043 | |
|
1043 | 1044 | -q: Quiet, do not print result. |
|
1044 | 1045 | |
|
1045 | 1046 | -o: return a TimeitResult that can be stored in a variable to inspect |
|
1046 | 1047 | the result in more details. |
|
1047 | 1048 | |
|
1048 | 1049 | .. versionchanged:: 7.3 |
|
1049 | 1050 | User variables are no longer expanded, |
|
1050 | 1051 | the magic line is always left unmodified. |
|
1051 | 1052 | |
|
1052 | 1053 | Examples |
|
1053 | 1054 | -------- |
|
1054 | 1055 | :: |
|
1055 | 1056 | |
|
1056 | 1057 | In [1]: %timeit pass |
|
1057 | 1058 | 8.26 ns ± 0.12 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each) |
|
1058 | 1059 | |
|
1059 | 1060 | In [2]: u = None |
|
1060 | 1061 | |
|
1061 | 1062 | In [3]: %timeit u is None |
|
1062 | 1063 | 29.9 ns ± 0.643 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each) |
|
1063 | 1064 | |
|
1064 | 1065 | In [4]: %timeit -r 4 u == None |
|
1065 | 1066 | |
|
1066 | 1067 | In [5]: import time |
|
1067 | 1068 | |
|
1068 | 1069 | In [6]: %timeit -n1 time.sleep(2) |
|
1069 | 1070 | |
|
1070 | 1071 | The times reported by %timeit will be slightly higher than those |
|
1071 | 1072 | reported by the timeit.py script when variables are accessed. This is |
|
1072 | 1073 | due to the fact that %timeit executes the statement in the namespace |
|
1073 | 1074 | of the shell, compared with timeit.py, which uses a single setup |
|
1074 | 1075 | statement to import function or create variables. Generally, the bias |
|
1075 | 1076 | does not matter as long as results from timeit.py are not mixed with |
|
1076 | 1077 | those from %timeit.""" |
|
1077 | 1078 | |
|
1078 | 1079 | opts, stmt = self.parse_options( |
|
1079 | 1080 | line, "n:r:tcp:qo", posix=False, strict=False, preserve_non_opts=True |
|
1080 | 1081 | ) |
|
1081 | 1082 | if stmt == "" and cell is None: |
|
1082 | 1083 | return |
|
1083 | 1084 | |
|
1084 | 1085 | timefunc = timeit.default_timer |
|
1085 | 1086 | number = int(getattr(opts, "n", 0)) |
|
1086 | 1087 | default_repeat = 7 if timeit.default_repeat < 7 else timeit.default_repeat |
|
1087 | 1088 | repeat = int(getattr(opts, "r", default_repeat)) |
|
1088 | 1089 | precision = int(getattr(opts, "p", 3)) |
|
1089 | 1090 | quiet = 'q' in opts |
|
1090 | 1091 | return_result = 'o' in opts |
|
1091 | 1092 | if hasattr(opts, "t"): |
|
1092 | 1093 | timefunc = time.time |
|
1093 | 1094 | if hasattr(opts, "c"): |
|
1094 | 1095 | timefunc = clock |
|
1095 | 1096 | |
|
1096 | 1097 | timer = Timer(timer=timefunc) |
|
1097 | 1098 | # this code has tight coupling to the inner workings of timeit.Timer, |
|
1098 | 1099 | # but is there a better way to achieve that the code stmt has access |
|
1099 | 1100 | # to the shell namespace? |
|
1100 | 1101 | transform = self.shell.transform_cell |
|
1101 | 1102 | |
|
1102 | 1103 | if cell is None: |
|
1103 | 1104 | # called as line magic |
|
1104 | 1105 | ast_setup = self.shell.compile.ast_parse("pass") |
|
1105 | 1106 | ast_stmt = self.shell.compile.ast_parse(transform(stmt)) |
|
1106 | 1107 | else: |
|
1107 | 1108 | ast_setup = self.shell.compile.ast_parse(transform(stmt)) |
|
1108 | 1109 | ast_stmt = self.shell.compile.ast_parse(transform(cell)) |
|
1109 | 1110 | |
|
1110 | 1111 | ast_setup = self.shell.transform_ast(ast_setup) |
|
1111 | 1112 | ast_stmt = self.shell.transform_ast(ast_stmt) |
|
1112 | 1113 | |
|
1113 | 1114 | # Check that these compile to valid Python code *outside* the timer func |
|
1114 | 1115 | # Invalid code may become valid when put inside the function & loop, |
|
1115 | 1116 | # which messes up error messages. |
|
1116 | 1117 | # https://github.com/ipython/ipython/issues/10636 |
|
1117 | 1118 | self.shell.compile(ast_setup, "<magic-timeit-setup>", "exec") |
|
1118 | 1119 | self.shell.compile(ast_stmt, "<magic-timeit-stmt>", "exec") |
|
1119 | 1120 | |
|
1120 | 1121 | # This codestring is taken from timeit.template - we fill it in as an |
|
1121 | 1122 | # AST, so that we can apply our AST transformations to the user code |
|
1122 | 1123 | # without affecting the timing code. |
|
1123 | 1124 | timeit_ast_template = ast.parse('def inner(_it, _timer):\n' |
|
1124 | 1125 | ' setup\n' |
|
1125 | 1126 | ' _t0 = _timer()\n' |
|
1126 | 1127 | ' for _i in _it:\n' |
|
1127 | 1128 | ' stmt\n' |
|
1128 | 1129 | ' _t1 = _timer()\n' |
|
1129 | 1130 | ' return _t1 - _t0\n') |
|
1130 | 1131 | |
|
1131 | 1132 | timeit_ast = TimeitTemplateFiller(ast_setup, ast_stmt).visit(timeit_ast_template) |
|
1132 | 1133 | timeit_ast = ast.fix_missing_locations(timeit_ast) |
|
1133 | 1134 | |
|
1134 | 1135 | # Track compilation time so it can be reported if too long |
|
1135 | 1136 | # Minimum time above which compilation time will be reported |
|
1136 | 1137 | tc_min = 0.1 |
|
1137 | 1138 | |
|
1138 | 1139 | t0 = clock() |
|
1139 | 1140 | code = self.shell.compile(timeit_ast, "<magic-timeit>", "exec") |
|
1140 | 1141 | tc = clock()-t0 |
|
1141 | 1142 | |
|
1142 | 1143 | ns = {} |
|
1143 | 1144 | glob = self.shell.user_ns |
|
1144 | 1145 | # handles global vars with same name as local vars. We store them in conflict_globs. |
|
1145 | 1146 | conflict_globs = {} |
|
1146 | 1147 | if local_ns and cell is None: |
|
1147 | 1148 | for var_name, var_val in glob.items(): |
|
1148 | 1149 | if var_name in local_ns: |
|
1149 | 1150 | conflict_globs[var_name] = var_val |
|
1150 | 1151 | glob.update(local_ns) |
|
1151 | 1152 | |
|
1152 | 1153 | exec(code, glob, ns) |
|
1153 | 1154 | timer.inner = ns["inner"] |
|
1154 | 1155 | |
|
1155 | 1156 | # This is used to check if there is a huge difference between the |
|
1156 | 1157 | # best and worst timings. |
|
1157 | 1158 | # Issue: https://github.com/ipython/ipython/issues/6471 |
|
1158 | 1159 | if number == 0: |
|
1159 | 1160 | # determine number so that 0.2 <= total time < 2.0 |
|
1160 | 1161 | for index in range(0, 10): |
|
1161 | 1162 | number = 10 ** index |
|
1162 | 1163 | time_number = timer.timeit(number) |
|
1163 | 1164 | if time_number >= 0.2: |
|
1164 | 1165 | break |
|
1165 | 1166 | |
|
1166 | 1167 | all_runs = timer.repeat(repeat, number) |
|
1167 | 1168 | best = min(all_runs) / number |
|
1168 | 1169 | worst = max(all_runs) / number |
|
1169 | 1170 | timeit_result = TimeitResult(number, repeat, best, worst, all_runs, tc, precision) |
|
1170 | 1171 | |
|
1171 | 1172 | # Restore global vars from conflict_globs |
|
1172 | 1173 | if conflict_globs: |
|
1173 | 1174 | glob.update(conflict_globs) |
|
1174 | 1175 | |
|
1175 | 1176 | if not quiet : |
|
1176 | 1177 | # Check best timing is greater than zero to avoid a |
|
1177 | 1178 | # ZeroDivisionError. |
|
1178 | 1179 | # In cases where the slowest timing is lesser than a microsecond |
|
1179 | 1180 | # we assume that it does not really matter if the fastest |
|
1180 | 1181 | # timing is 4 times faster than the slowest timing or not. |
|
1181 | 1182 | if worst > 4 * best and best > 0 and worst > 1e-6: |
|
1182 | 1183 | print("The slowest run took %0.2f times longer than the " |
|
1183 | 1184 | "fastest. This could mean that an intermediate result " |
|
1184 | 1185 | "is being cached." % (worst / best)) |
|
1185 | 1186 | |
|
1186 | 1187 | print( timeit_result ) |
|
1187 | 1188 | |
|
1188 | 1189 | if tc > tc_min: |
|
1189 | 1190 | print("Compiler time: %.2f s" % tc) |
|
1190 | 1191 | if return_result: |
|
1191 | 1192 | return timeit_result |
|
1192 | 1193 | |
|
1193 | 1194 | @skip_doctest |
|
1194 | 1195 | @no_var_expand |
|
1195 | 1196 | @needs_local_scope |
|
1196 | 1197 | @line_cell_magic |
|
1198 | @output_can_be_silenced | |
|
1197 | 1199 | def time(self,line='', cell=None, local_ns=None): |
|
1198 | 1200 | """Time execution of a Python statement or expression. |
|
1199 | 1201 | |
|
1200 | 1202 | The CPU and wall clock times are printed, and the value of the |
|
1201 | 1203 | expression (if any) is returned. Note that under Win32, system time |
|
1202 | 1204 | is always reported as 0, since it can not be measured. |
|
1203 | 1205 | |
|
1204 | 1206 | This function can be used both as a line and cell magic: |
|
1205 | 1207 | |
|
1206 | 1208 | - In line mode you can time a single-line statement (though multiple |
|
1207 | 1209 | ones can be chained with using semicolons). |
|
1208 | 1210 | |
|
1209 | 1211 | - In cell mode, you can time the cell body (a directly |
|
1210 | 1212 | following statement raises an error). |
|
1211 | 1213 | |
|
1212 | 1214 | This function provides very basic timing functionality. Use the timeit |
|
1213 | 1215 | magic for more control over the measurement. |
|
1214 | 1216 | |
|
1215 | 1217 | .. versionchanged:: 7.3 |
|
1216 | 1218 | User variables are no longer expanded, |
|
1217 | 1219 | the magic line is always left unmodified. |
|
1218 | 1220 | |
|
1219 | 1221 | Examples |
|
1220 | 1222 | -------- |
|
1221 | 1223 | :: |
|
1222 | 1224 | |
|
1223 | 1225 | In [1]: %time 2**128 |
|
1224 | 1226 | CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s |
|
1225 | 1227 | Wall time: 0.00 |
|
1226 | 1228 | Out[1]: 340282366920938463463374607431768211456L |
|
1227 | 1229 | |
|
1228 | 1230 | In [2]: n = 1000000 |
|
1229 | 1231 | |
|
1230 | 1232 | In [3]: %time sum(range(n)) |
|
1231 | 1233 | CPU times: user 1.20 s, sys: 0.05 s, total: 1.25 s |
|
1232 | 1234 | Wall time: 1.37 |
|
1233 | 1235 | Out[3]: 499999500000L |
|
1234 | 1236 | |
|
1235 | 1237 | In [4]: %time print 'hello world' |
|
1236 | 1238 | hello world |
|
1237 | 1239 | CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s |
|
1238 | 1240 | Wall time: 0.00 |
|
1239 | 1241 | |
|
1240 | 1242 | .. note:: |
|
1241 | 1243 | The time needed by Python to compile the given expression will be |
|
1242 | 1244 | reported if it is more than 0.1s. |
|
1243 | 1245 | |
|
1244 | 1246 | In the example below, the actual exponentiation is done by Python |
|
1245 | 1247 | at compilation time, so while the expression can take a noticeable |
|
1246 | 1248 | amount of time to compute, that time is purely due to the |
|
1247 | 1249 | compilation:: |
|
1248 | 1250 | |
|
1249 | 1251 | In [5]: %time 3**9999; |
|
1250 | 1252 | CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s |
|
1251 | 1253 | Wall time: 0.00 s |
|
1252 | 1254 | |
|
1253 | 1255 | In [6]: %time 3**999999; |
|
1254 | 1256 | CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s |
|
1255 | 1257 | Wall time: 0.00 s |
|
1256 | 1258 | Compiler : 0.78 s |
|
1257 | 1259 | """ |
|
1258 | 1260 | # fail immediately if the given expression can't be compiled |
|
1259 | 1261 | |
|
1260 | 1262 | if line and cell: |
|
1261 | 1263 | raise UsageError("Can't use statement directly after '%%time'!") |
|
1262 | 1264 | |
|
1263 | 1265 | if cell: |
|
1264 | 1266 | expr = self.shell.transform_cell(cell) |
|
1265 | 1267 | else: |
|
1266 | 1268 | expr = self.shell.transform_cell(line) |
|
1267 | 1269 | |
|
1268 | 1270 | # Minimum time above which parse time will be reported |
|
1269 | 1271 | tp_min = 0.1 |
|
1270 | 1272 | |
|
1271 | 1273 | t0 = clock() |
|
1272 | 1274 | expr_ast = self.shell.compile.ast_parse(expr) |
|
1273 | 1275 | tp = clock()-t0 |
|
1274 | 1276 | |
|
1275 | 1277 | # Apply AST transformations |
|
1276 | 1278 | expr_ast = self.shell.transform_ast(expr_ast) |
|
1277 | 1279 | |
|
1278 | 1280 | # Minimum time above which compilation time will be reported |
|
1279 | 1281 | tc_min = 0.1 |
|
1280 | 1282 | |
|
1281 | 1283 | expr_val=None |
|
1282 | 1284 | if len(expr_ast.body)==1 and isinstance(expr_ast.body[0], ast.Expr): |
|
1283 | 1285 | mode = 'eval' |
|
1284 | 1286 | source = '<timed eval>' |
|
1285 | 1287 | expr_ast = ast.Expression(expr_ast.body[0].value) |
|
1286 | 1288 | else: |
|
1287 | 1289 | mode = 'exec' |
|
1288 | 1290 | source = '<timed exec>' |
|
1289 | 1291 | # multi-line %%time case |
|
1290 | 1292 | if len(expr_ast.body) > 1 and isinstance(expr_ast.body[-1], ast.Expr): |
|
1291 | 1293 | expr_val= expr_ast.body[-1] |
|
1292 | 1294 | expr_ast = expr_ast.body[:-1] |
|
1293 | 1295 | expr_ast = Module(expr_ast, []) |
|
1294 | 1296 | expr_val = ast.Expression(expr_val.value) |
|
1295 | 1297 | |
|
1296 | 1298 | t0 = clock() |
|
1297 | 1299 | code = self.shell.compile(expr_ast, source, mode) |
|
1298 | 1300 | tc = clock()-t0 |
|
1299 | 1301 | |
|
1300 | 1302 | # skew measurement as little as possible |
|
1301 | 1303 | glob = self.shell.user_ns |
|
1302 | 1304 | wtime = time.time |
|
1303 | 1305 | # time execution |
|
1304 | 1306 | wall_st = wtime() |
|
1305 | 1307 | if mode=='eval': |
|
1306 | 1308 | st = clock2() |
|
1307 | 1309 | try: |
|
1308 | 1310 | out = eval(code, glob, local_ns) |
|
1309 | 1311 | except: |
|
1310 | 1312 | self.shell.showtraceback() |
|
1311 | 1313 | return |
|
1312 | 1314 | end = clock2() |
|
1313 | 1315 | else: |
|
1314 | 1316 | st = clock2() |
|
1315 | 1317 | try: |
|
1316 | 1318 | exec(code, glob, local_ns) |
|
1317 | 1319 | out=None |
|
1318 | 1320 | # multi-line %%time case |
|
1319 | 1321 | if expr_val is not None: |
|
1320 | 1322 | code_2 = self.shell.compile(expr_val, source, 'eval') |
|
1321 | 1323 | out = eval(code_2, glob, local_ns) |
|
1322 | 1324 | except: |
|
1323 | 1325 | self.shell.showtraceback() |
|
1324 | 1326 | return |
|
1325 | 1327 | end = clock2() |
|
1326 | 1328 | |
|
1327 | 1329 | wall_end = wtime() |
|
1328 | 1330 | # Compute actual times and report |
|
1329 | 1331 | wall_time = wall_end - wall_st |
|
1330 | 1332 | cpu_user = end[0] - st[0] |
|
1331 | 1333 | cpu_sys = end[1] - st[1] |
|
1332 | 1334 | cpu_tot = cpu_user + cpu_sys |
|
1333 | 1335 | # On windows cpu_sys is always zero, so only total is displayed |
|
1334 | 1336 | if sys.platform != "win32": |
|
1335 | 1337 | print( |
|
1336 | 1338 | f"CPU times: user {_format_time(cpu_user)}, sys: {_format_time(cpu_sys)}, total: {_format_time(cpu_tot)}" |
|
1337 | 1339 | ) |
|
1338 | 1340 | else: |
|
1339 | 1341 | print(f"CPU times: total: {_format_time(cpu_tot)}") |
|
1340 | 1342 | print(f"Wall time: {_format_time(wall_time)}") |
|
1341 | 1343 | if tc > tc_min: |
|
1342 | 1344 | print(f"Compiler : {_format_time(tc)}") |
|
1343 | 1345 | if tp > tp_min: |
|
1344 | 1346 | print(f"Parser : {_format_time(tp)}") |
|
1345 | 1347 | return out |
|
1346 | 1348 | |
|
1347 | 1349 | @skip_doctest |
|
1348 | 1350 | @line_magic |
|
1349 | 1351 | def macro(self, parameter_s=''): |
|
1350 | 1352 | """Define a macro for future re-execution. It accepts ranges of history, |
|
1351 | 1353 | filenames or string objects. |
|
1352 | 1354 | |
|
1353 | 1355 | Usage:\\ |
|
1354 | 1356 | %macro [options] name n1-n2 n3-n4 ... n5 .. n6 ... |
|
1355 | 1357 | |
|
1356 | 1358 | Options: |
|
1357 | 1359 | |
|
1358 | 1360 | -r: use 'raw' input. By default, the 'processed' history is used, |
|
1359 | 1361 | so that magics are loaded in their transformed version to valid |
|
1360 | 1362 | Python. If this option is given, the raw input as typed at the |
|
1361 | 1363 | command line is used instead. |
|
1362 | 1364 | |
|
1363 | 1365 | -q: quiet macro definition. By default, a tag line is printed |
|
1364 | 1366 | to indicate the macro has been created, and then the contents of |
|
1365 | 1367 | the macro are printed. If this option is given, then no printout |
|
1366 | 1368 | is produced once the macro is created. |
|
1367 | 1369 | |
|
1368 | 1370 | This will define a global variable called `name` which is a string |
|
1369 | 1371 | made of joining the slices and lines you specify (n1,n2,... numbers |
|
1370 | 1372 | above) from your input history into a single string. This variable |
|
1371 | 1373 | acts like an automatic function which re-executes those lines as if |
|
1372 | 1374 | you had typed them. You just type 'name' at the prompt and the code |
|
1373 | 1375 | executes. |
|
1374 | 1376 | |
|
1375 | 1377 | The syntax for indicating input ranges is described in %history. |
|
1376 | 1378 | |
|
1377 | 1379 | Note: as a 'hidden' feature, you can also use traditional python slice |
|
1378 | 1380 | notation, where N:M means numbers N through M-1. |
|
1379 | 1381 | |
|
1380 | 1382 | For example, if your history contains (print using %hist -n ):: |
|
1381 | 1383 | |
|
1382 | 1384 | 44: x=1 |
|
1383 | 1385 | 45: y=3 |
|
1384 | 1386 | 46: z=x+y |
|
1385 | 1387 | 47: print x |
|
1386 | 1388 | 48: a=5 |
|
1387 | 1389 | 49: print 'x',x,'y',y |
|
1388 | 1390 | |
|
1389 | 1391 | you can create a macro with lines 44 through 47 (included) and line 49 |
|
1390 | 1392 | called my_macro with:: |
|
1391 | 1393 | |
|
1392 | 1394 | In [55]: %macro my_macro 44-47 49 |
|
1393 | 1395 | |
|
1394 | 1396 | Now, typing `my_macro` (without quotes) will re-execute all this code |
|
1395 | 1397 | in one pass. |
|
1396 | 1398 | |
|
1397 | 1399 | You don't need to give the line-numbers in order, and any given line |
|
1398 | 1400 | number can appear multiple times. You can assemble macros with any |
|
1399 | 1401 | lines from your input history in any order. |
|
1400 | 1402 | |
|
1401 | 1403 | The macro is a simple object which holds its value in an attribute, |
|
1402 | 1404 | but IPython's display system checks for macros and executes them as |
|
1403 | 1405 | code instead of printing them when you type their name. |
|
1404 | 1406 | |
|
1405 | 1407 | You can view a macro's contents by explicitly printing it with:: |
|
1406 | 1408 | |
|
1407 | 1409 | print macro_name |
|
1408 | 1410 | |
|
1409 | 1411 | """ |
|
1410 | 1412 | opts,args = self.parse_options(parameter_s,'rq',mode='list') |
|
1411 | 1413 | if not args: # List existing macros |
|
1412 | 1414 | return sorted(k for k,v in self.shell.user_ns.items() if isinstance(v, Macro)) |
|
1413 | 1415 | if len(args) == 1: |
|
1414 | 1416 | raise UsageError( |
|
1415 | 1417 | "%macro insufficient args; usage '%macro name n1-n2 n3-4...") |
|
1416 | 1418 | name, codefrom = args[0], " ".join(args[1:]) |
|
1417 | 1419 | |
|
1418 | 1420 | #print 'rng',ranges # dbg |
|
1419 | 1421 | try: |
|
1420 | 1422 | lines = self.shell.find_user_code(codefrom, 'r' in opts) |
|
1421 | 1423 | except (ValueError, TypeError) as e: |
|
1422 | 1424 | print(e.args[0]) |
|
1423 | 1425 | return |
|
1424 | 1426 | macro = Macro(lines) |
|
1425 | 1427 | self.shell.define_macro(name, macro) |
|
1426 | 1428 | if not ( 'q' in opts) : |
|
1427 | 1429 | print('Macro `%s` created. To execute, type its name (without quotes).' % name) |
|
1428 | 1430 | print('=== Macro contents: ===') |
|
1429 | 1431 | print(macro, end=' ') |
|
1430 | 1432 | |
|
1431 | 1433 | @magic_arguments.magic_arguments() |
|
1432 | 1434 | @magic_arguments.argument('output', type=str, default='', nargs='?', |
|
1433 | 1435 | help="""The name of the variable in which to store output. |
|
1434 | 1436 | This is a utils.io.CapturedIO object with stdout/err attributes |
|
1435 | 1437 | for the text of the captured output. |
|
1436 | 1438 | |
|
1437 | 1439 | CapturedOutput also has a show() method for displaying the output, |
|
1438 | 1440 | and __call__ as well, so you can use that to quickly display the |
|
1439 | 1441 | output. |
|
1440 | 1442 | |
|
1441 | 1443 | If unspecified, captured output is discarded. |
|
1442 | 1444 | """ |
|
1443 | 1445 | ) |
|
1444 | 1446 | @magic_arguments.argument('--no-stderr', action="store_true", |
|
1445 | 1447 | help="""Don't capture stderr.""" |
|
1446 | 1448 | ) |
|
1447 | 1449 | @magic_arguments.argument('--no-stdout', action="store_true", |
|
1448 | 1450 | help="""Don't capture stdout.""" |
|
1449 | 1451 | ) |
|
1450 | 1452 | @magic_arguments.argument('--no-display', action="store_true", |
|
1451 | 1453 | help="""Don't capture IPython's rich display.""" |
|
1452 | 1454 | ) |
|
1453 | 1455 | @cell_magic |
|
1454 | 1456 | def capture(self, line, cell): |
|
1455 | 1457 | """run the cell, capturing stdout, stderr, and IPython's rich display() calls.""" |
|
1456 | 1458 | args = magic_arguments.parse_argstring(self.capture, line) |
|
1457 | 1459 | out = not args.no_stdout |
|
1458 | 1460 | err = not args.no_stderr |
|
1459 | 1461 | disp = not args.no_display |
|
1460 | 1462 | with capture_output(out, err, disp) as io: |
|
1461 | 1463 | self.shell.run_cell(cell) |
|
1462 | 1464 | if args.output: |
|
1463 | 1465 | self.shell.user_ns[args.output] = io |
|
1464 | 1466 | |
|
1465 | 1467 | def parse_breakpoint(text, current_file): |
|
1466 | 1468 | '''Returns (file, line) for file:line and (current_file, line) for line''' |
|
1467 | 1469 | colon = text.find(':') |
|
1468 | 1470 | if colon == -1: |
|
1469 | 1471 | return current_file, int(text) |
|
1470 | 1472 | else: |
|
1471 | 1473 | return text[:colon], int(text[colon+1:]) |
|
1472 | 1474 | |
|
1473 | 1475 | def _format_time(timespan, precision=3): |
|
1474 | 1476 | """Formats the timespan in a human readable form""" |
|
1475 | 1477 | |
|
1476 | 1478 | if timespan >= 60.0: |
|
1477 | 1479 | # we have more than a minute, format that in a human readable form |
|
1478 | 1480 | # Idea from http://snipplr.com/view/5713/ |
|
1479 | 1481 | parts = [("d", 60*60*24),("h", 60*60),("min", 60), ("s", 1)] |
|
1480 | 1482 | time = [] |
|
1481 | 1483 | leftover = timespan |
|
1482 | 1484 | for suffix, length in parts: |
|
1483 | 1485 | value = int(leftover / length) |
|
1484 | 1486 | if value > 0: |
|
1485 | 1487 | leftover = leftover % length |
|
1486 | 1488 | time.append(u'%s%s' % (str(value), suffix)) |
|
1487 | 1489 | if leftover < 1: |
|
1488 | 1490 | break |
|
1489 | 1491 | return " ".join(time) |
|
1490 | 1492 | |
|
1491 | 1493 | |
|
1492 | 1494 | # Unfortunately the unicode 'micro' symbol can cause problems in |
|
1493 | 1495 | # certain terminals. |
|
1494 | 1496 | # See bug: https://bugs.launchpad.net/ipython/+bug/348466 |
|
1495 | 1497 | # Try to prevent crashes by being more secure than it needs to |
|
1496 | 1498 | # E.g. eclipse is able to print a µ, but has no sys.stdout.encoding set. |
|
1497 | 1499 | units = [u"s", u"ms",u'us',"ns"] # the save value |
|
1498 | 1500 | if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding: |
|
1499 | 1501 | try: |
|
1500 | 1502 | u'\xb5'.encode(sys.stdout.encoding) |
|
1501 | 1503 | units = [u"s", u"ms",u'\xb5s',"ns"] |
|
1502 | 1504 | except: |
|
1503 | 1505 | pass |
|
1504 | 1506 | scaling = [1, 1e3, 1e6, 1e9] |
|
1505 | 1507 | |
|
1506 | 1508 | if timespan > 0.0: |
|
1507 | 1509 | order = min(-int(math.floor(math.log10(timespan)) // 3), 3) |
|
1508 | 1510 | else: |
|
1509 | 1511 | order = 3 |
|
1510 | 1512 | return u"%.*g %s" % (precision, timespan * scaling[order], units[order]) |
@@ -1,855 +1,855 b'' | |||
|
1 | 1 | """Implementation of magic functions for interaction with the OS. |
|
2 | 2 | |
|
3 | 3 | Note: this module is named 'osm' instead of 'os' to avoid a collision with the |
|
4 | 4 | builtin. |
|
5 | 5 | """ |
|
6 | 6 | # Copyright (c) IPython Development Team. |
|
7 | 7 | # Distributed under the terms of the Modified BSD License. |
|
8 | 8 | |
|
9 | 9 | import io |
|
10 | 10 | import os |
|
11 | 11 | import pathlib |
|
12 | 12 | import re |
|
13 | 13 | import sys |
|
14 | 14 | from pprint import pformat |
|
15 | 15 | |
|
16 | 16 | from IPython.core import magic_arguments |
|
17 | 17 | from IPython.core import oinspect |
|
18 | 18 | from IPython.core import page |
|
19 | 19 | from IPython.core.alias import AliasError, Alias |
|
20 | 20 | from IPython.core.error import UsageError |
|
21 | 21 | from IPython.core.magic import ( |
|
22 | 22 | Magics, compress_dhist, magics_class, line_magic, cell_magic, line_cell_magic |
|
23 | 23 | ) |
|
24 | 24 | from IPython.testing.skipdoctest import skip_doctest |
|
25 | 25 | from IPython.utils.openpy import source_to_unicode |
|
26 | 26 | from IPython.utils.process import abbrev_cwd |
|
27 | 27 | from IPython.utils.terminal import set_term_title |
|
28 | 28 | from traitlets import Bool |
|
29 | 29 | from warnings import warn |
|
30 | 30 | |
|
31 | 31 | |
|
32 | 32 | @magics_class |
|
33 | 33 | class OSMagics(Magics): |
|
34 | 34 | """Magics to interact with the underlying OS (shell-type functionality). |
|
35 | 35 | """ |
|
36 | 36 | |
|
37 | 37 | cd_force_quiet = Bool(False, |
|
38 | 38 | help="Force %cd magic to be quiet even if -q is not passed." |
|
39 | 39 | ).tag(config=True) |
|
40 | 40 | |
|
41 | 41 | def __init__(self, shell=None, **kwargs): |
|
42 | 42 | |
|
43 | 43 | # Now define isexec in a cross platform manner. |
|
44 | 44 | self.is_posix = False |
|
45 | 45 | self.execre = None |
|
46 | 46 | if os.name == 'posix': |
|
47 | 47 | self.is_posix = True |
|
48 | 48 | else: |
|
49 | 49 | try: |
|
50 | 50 | winext = os.environ['pathext'].replace(';','|').replace('.','') |
|
51 | 51 | except KeyError: |
|
52 | 52 | winext = 'exe|com|bat|py' |
|
53 | 53 | try: |
|
54 | 54 | self.execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE) |
|
55 | 55 | except re.error: |
|
56 | 56 | warn("Seems like your pathext environmental " |
|
57 | 57 | "variable is malformed. Please check it to " |
|
58 | 58 | "enable a proper handle of file extensions " |
|
59 | 59 | "managed for your system") |
|
60 | 60 | winext = 'exe|com|bat|py' |
|
61 | 61 | self.execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE) |
|
62 | 62 | |
|
63 | 63 | # call up the chain |
|
64 | 64 | super().__init__(shell=shell, **kwargs) |
|
65 | 65 | |
|
66 | 66 | |
|
67 | 67 | def _isexec_POSIX(self, file): |
|
68 | 68 | """ |
|
69 | 69 | Test for executable on a POSIX system |
|
70 | 70 | """ |
|
71 | 71 | if os.access(file.path, os.X_OK): |
|
72 | 72 | # will fail on maxOS if access is not X_OK |
|
73 | 73 | return file.is_file() |
|
74 | 74 | return False |
|
75 | 75 | |
|
76 | 76 | |
|
77 | 77 | |
|
78 | 78 | def _isexec_WIN(self, file): |
|
79 | 79 | """ |
|
80 | 80 | Test for executable file on non POSIX system |
|
81 | 81 | """ |
|
82 | 82 | return file.is_file() and self.execre.match(file.name) is not None |
|
83 | 83 | |
|
84 | 84 | def isexec(self, file): |
|
85 | 85 | """ |
|
86 | 86 | Test for executable file on non POSIX system |
|
87 | 87 | """ |
|
88 | 88 | if self.is_posix: |
|
89 | 89 | return self._isexec_POSIX(file) |
|
90 | 90 | else: |
|
91 | 91 | return self._isexec_WIN(file) |
|
92 | 92 | |
|
93 | 93 | |
|
94 | 94 | @skip_doctest |
|
95 | 95 | @line_magic |
|
96 | 96 | def alias(self, parameter_s=''): |
|
97 | 97 | """Define an alias for a system command. |
|
98 | 98 | |
|
99 | 99 | '%alias alias_name cmd' defines 'alias_name' as an alias for 'cmd' |
|
100 | 100 | |
|
101 | 101 | Then, typing 'alias_name params' will execute the system command 'cmd |
|
102 | 102 | params' (from your underlying operating system). |
|
103 | 103 | |
|
104 | 104 | Aliases have lower precedence than magic functions and Python normal |
|
105 | 105 | variables, so if 'foo' is both a Python variable and an alias, the |
|
106 | 106 | alias can not be executed until 'del foo' removes the Python variable. |
|
107 | 107 | |
|
108 | 108 | You can use the %l specifier in an alias definition to represent the |
|
109 | 109 | whole line when the alias is called. For example:: |
|
110 | 110 | |
|
111 | 111 | In [2]: alias bracket echo "Input in brackets: <%l>" |
|
112 | 112 | In [3]: bracket hello world |
|
113 | 113 | Input in brackets: <hello world> |
|
114 | 114 | |
|
115 | 115 | You can also define aliases with parameters using %s specifiers (one |
|
116 | 116 | per parameter):: |
|
117 | 117 | |
|
118 | 118 | In [1]: alias parts echo first %s second %s |
|
119 | 119 | In [2]: %parts A B |
|
120 | 120 | first A second B |
|
121 | 121 | In [3]: %parts A |
|
122 | 122 | Incorrect number of arguments: 2 expected. |
|
123 | 123 | parts is an alias to: 'echo first %s second %s' |
|
124 | 124 | |
|
125 | 125 | Note that %l and %s are mutually exclusive. You can only use one or |
|
126 | 126 | the other in your aliases. |
|
127 | 127 | |
|
128 | 128 | Aliases expand Python variables just like system calls using ! or !! |
|
129 | 129 | do: all expressions prefixed with '$' get expanded. For details of |
|
130 | 130 | the semantic rules, see PEP-215: |
|
131 | 131 | https://peps.python.org/pep-0215/. This is the library used by |
|
132 | 132 | IPython for variable expansion. If you want to access a true shell |
|
133 | 133 | variable, an extra $ is necessary to prevent its expansion by |
|
134 | 134 | IPython:: |
|
135 | 135 | |
|
136 | 136 | In [6]: alias show echo |
|
137 | 137 | In [7]: PATH='A Python string' |
|
138 | 138 | In [8]: show $PATH |
|
139 | 139 | A Python string |
|
140 | 140 | In [9]: show $$PATH |
|
141 | 141 | /usr/local/lf9560/bin:/usr/local/intel/compiler70/ia32/bin:... |
|
142 | 142 | |
|
143 | 143 | You can use the alias facility to access all of $PATH. See the %rehashx |
|
144 | 144 | function, which automatically creates aliases for the contents of your |
|
145 | 145 | $PATH. |
|
146 | 146 | |
|
147 | 147 | If called with no parameters, %alias prints the current alias table |
|
148 | 148 | for your system. For posix systems, the default aliases are 'cat', |
|
149 | 149 | 'cp', 'mv', 'rm', 'rmdir', and 'mkdir', and other platform-specific |
|
150 | 150 | aliases are added. For windows-based systems, the default aliases are |
|
151 | 151 | 'copy', 'ddir', 'echo', 'ls', 'ldir', 'mkdir', 'ren', and 'rmdir'. |
|
152 | 152 | |
|
153 | 153 | You can see the definition of alias by adding a question mark in the |
|
154 | 154 | end:: |
|
155 | 155 | |
|
156 | 156 | In [1]: cat? |
|
157 | 157 | Repr: <alias cat for 'cat'>""" |
|
158 | 158 | |
|
159 | 159 | par = parameter_s.strip() |
|
160 | 160 | if not par: |
|
161 | 161 | aliases = sorted(self.shell.alias_manager.aliases) |
|
162 | 162 | # stored = self.shell.db.get('stored_aliases', {} ) |
|
163 | 163 | # for k, v in stored: |
|
164 | 164 | # atab.append(k, v[0]) |
|
165 | 165 | |
|
166 | 166 | print("Total number of aliases:", len(aliases)) |
|
167 | 167 | sys.stdout.flush() |
|
168 | 168 | return aliases |
|
169 | 169 | |
|
170 | 170 | # Now try to define a new one |
|
171 | 171 | try: |
|
172 | 172 | alias,cmd = par.split(None, 1) |
|
173 | 173 | except TypeError: |
|
174 | 174 | print(oinspect.getdoc(self.alias)) |
|
175 | 175 | return |
|
176 | 176 | |
|
177 | 177 | try: |
|
178 | 178 | self.shell.alias_manager.define_alias(alias, cmd) |
|
179 | 179 | except AliasError as e: |
|
180 | 180 | print(e) |
|
181 | 181 | # end magic_alias |
|
182 | 182 | |
|
183 | 183 | @line_magic |
|
184 | 184 | def unalias(self, parameter_s=''): |
|
185 | 185 | """Remove an alias""" |
|
186 | 186 | |
|
187 | 187 | aname = parameter_s.strip() |
|
188 | 188 | try: |
|
189 | 189 | self.shell.alias_manager.undefine_alias(aname) |
|
190 | 190 | except ValueError as e: |
|
191 | 191 | print(e) |
|
192 | 192 | return |
|
193 | 193 | |
|
194 | 194 | stored = self.shell.db.get('stored_aliases', {} ) |
|
195 | 195 | if aname in stored: |
|
196 | 196 | print("Removing %stored alias",aname) |
|
197 | 197 | del stored[aname] |
|
198 | 198 | self.shell.db['stored_aliases'] = stored |
|
199 | 199 | |
|
200 | 200 | @line_magic |
|
201 | 201 | def rehashx(self, parameter_s=''): |
|
202 | 202 | """Update the alias table with all executable files in $PATH. |
|
203 | 203 | |
|
204 | 204 | rehashx explicitly checks that every entry in $PATH is a file |
|
205 | 205 | with execute access (os.X_OK). |
|
206 | 206 | |
|
207 | 207 | Under Windows, it checks executability as a match against a |
|
208 | 208 | '|'-separated string of extensions, stored in the IPython config |
|
209 | 209 | variable win_exec_ext. This defaults to 'exe|com|bat'. |
|
210 | 210 | |
|
211 | 211 | This function also resets the root module cache of module completer, |
|
212 | 212 | used on slow filesystems. |
|
213 | 213 | """ |
|
214 | 214 | from IPython.core.alias import InvalidAliasError |
|
215 | 215 | |
|
216 | 216 | # for the benefit of module completer in ipy_completers.py |
|
217 | 217 | del self.shell.db['rootmodules_cache'] |
|
218 | 218 | |
|
219 | 219 | path = [os.path.abspath(os.path.expanduser(p)) for p in |
|
220 | 220 | os.environ.get('PATH','').split(os.pathsep)] |
|
221 | 221 | |
|
222 | 222 | syscmdlist = [] |
|
223 | 223 | savedir = os.getcwd() |
|
224 | 224 | |
|
225 | 225 | # Now walk the paths looking for executables to alias. |
|
226 | 226 | try: |
|
227 | 227 | # write the whole loop for posix/Windows so we don't have an if in |
|
228 | 228 | # the innermost part |
|
229 | 229 | if self.is_posix: |
|
230 | 230 | for pdir in path: |
|
231 | 231 | try: |
|
232 | 232 | os.chdir(pdir) |
|
233 | 233 | except OSError: |
|
234 | 234 | continue |
|
235 | 235 | |
|
236 | 236 | # for python 3.6+ rewrite to: with os.scandir(pdir) as dirlist: |
|
237 | 237 | dirlist = os.scandir(path=pdir) |
|
238 | 238 | for ff in dirlist: |
|
239 | 239 | if self.isexec(ff): |
|
240 | 240 | fname = ff.name |
|
241 | 241 | try: |
|
242 | 242 | # Removes dots from the name since ipython |
|
243 | 243 | # will assume names with dots to be python. |
|
244 | 244 | if not self.shell.alias_manager.is_alias(fname): |
|
245 | 245 | self.shell.alias_manager.define_alias( |
|
246 | 246 | fname.replace('.',''), fname) |
|
247 | 247 | except InvalidAliasError: |
|
248 | 248 | pass |
|
249 | 249 | else: |
|
250 | 250 | syscmdlist.append(fname) |
|
251 | 251 | else: |
|
252 | 252 | no_alias = Alias.blacklist |
|
253 | 253 | for pdir in path: |
|
254 | 254 | try: |
|
255 | 255 | os.chdir(pdir) |
|
256 | 256 | except OSError: |
|
257 | 257 | continue |
|
258 | 258 | |
|
259 | 259 | # for python 3.6+ rewrite to: with os.scandir(pdir) as dirlist: |
|
260 | 260 | dirlist = os.scandir(pdir) |
|
261 | 261 | for ff in dirlist: |
|
262 | 262 | fname = ff.name |
|
263 | 263 | base, ext = os.path.splitext(fname) |
|
264 | 264 | if self.isexec(ff) and base.lower() not in no_alias: |
|
265 | 265 | if ext.lower() == '.exe': |
|
266 | 266 | fname = base |
|
267 | 267 | try: |
|
268 | 268 | # Removes dots from the name since ipython |
|
269 | 269 | # will assume names with dots to be python. |
|
270 | 270 | self.shell.alias_manager.define_alias( |
|
271 | 271 | base.lower().replace('.',''), fname) |
|
272 | 272 | except InvalidAliasError: |
|
273 | 273 | pass |
|
274 | 274 | syscmdlist.append(fname) |
|
275 | 275 | |
|
276 | 276 | self.shell.db['syscmdlist'] = syscmdlist |
|
277 | 277 | finally: |
|
278 | 278 | os.chdir(savedir) |
|
279 | 279 | |
|
280 | 280 | @skip_doctest |
|
281 | 281 | @line_magic |
|
282 | 282 | def pwd(self, parameter_s=''): |
|
283 | 283 | """Return the current working directory path. |
|
284 | 284 | |
|
285 | 285 | Examples |
|
286 | 286 | -------- |
|
287 | 287 | :: |
|
288 | 288 | |
|
289 | 289 | In [9]: pwd |
|
290 | 290 | Out[9]: '/home/tsuser/sprint/ipython' |
|
291 | 291 | """ |
|
292 | 292 | try: |
|
293 | 293 | return os.getcwd() |
|
294 | 294 | except FileNotFoundError as e: |
|
295 | 295 | raise UsageError("CWD no longer exists - please use %cd to change directory.") from e |
|
296 | 296 | |
|
297 | 297 | @skip_doctest |
|
298 | 298 | @line_magic |
|
299 | 299 | def cd(self, parameter_s=''): |
|
300 | 300 | """Change the current working directory. |
|
301 | 301 | |
|
302 | 302 | This command automatically maintains an internal list of directories |
|
303 | 303 | you visit during your IPython session, in the variable ``_dh``. The |
|
304 | 304 | command :magic:`%dhist` shows this history nicely formatted. You can |
|
305 | 305 | also do ``cd -<tab>`` to see directory history conveniently. |
|
306 | 306 | Usage: |
|
307 | 307 | |
|
308 | 308 | - ``cd 'dir'``: changes to directory 'dir'. |
|
309 | 309 | - ``cd -``: changes to the last visited directory. |
|
310 | 310 | - ``cd -<n>``: changes to the n-th directory in the directory history. |
|
311 | 311 | - ``cd --foo``: change to directory that matches 'foo' in history |
|
312 | 312 | - ``cd -b <bookmark_name>``: jump to a bookmark set by %bookmark |
|
313 | 313 | - Hitting a tab key after ``cd -b`` allows you to tab-complete |
|
314 | 314 | bookmark names. |
|
315 | 315 | |
|
316 | 316 | .. note:: |
|
317 | 317 | ``cd <bookmark_name>`` is enough if there is no directory |
|
318 | 318 | ``<bookmark_name>``, but a bookmark with the name exists. |
|
319 | 319 | |
|
320 | 320 | Options: |
|
321 | 321 | |
|
322 | 322 | -q Be quiet. Do not print the working directory after the |
|
323 | 323 | cd command is executed. By default IPython's cd |
|
324 | 324 | command does print this directory, since the default |
|
325 | 325 | prompts do not display path information. |
|
326 | 326 | |
|
327 | 327 | .. note:: |
|
328 | 328 | Note that ``!cd`` doesn't work for this purpose because the shell |
|
329 | 329 | where ``!command`` runs is immediately discarded after executing |
|
330 | 330 | 'command'. |
|
331 | 331 | |
|
332 | 332 | Examples |
|
333 | 333 | -------- |
|
334 | 334 | :: |
|
335 | 335 | |
|
336 | 336 | In [10]: cd parent/child |
|
337 | 337 | /home/tsuser/parent/child |
|
338 | 338 | """ |
|
339 | 339 | |
|
340 | 340 | try: |
|
341 | 341 | oldcwd = os.getcwd() |
|
342 | 342 | except FileNotFoundError: |
|
343 | 343 | # Happens if the CWD has been deleted. |
|
344 | 344 | oldcwd = None |
|
345 | 345 | |
|
346 | 346 | numcd = re.match(r'(-)(\d+)$',parameter_s) |
|
347 | 347 | # jump in directory history by number |
|
348 | 348 | if numcd: |
|
349 | 349 | nn = int(numcd.group(2)) |
|
350 | 350 | try: |
|
351 | 351 | ps = self.shell.user_ns['_dh'][nn] |
|
352 | 352 | except IndexError: |
|
353 | 353 | print('The requested directory does not exist in history.') |
|
354 | 354 | return |
|
355 | 355 | else: |
|
356 | 356 | opts = {} |
|
357 | 357 | elif parameter_s.startswith('--'): |
|
358 | 358 | ps = None |
|
359 | 359 | fallback = None |
|
360 | 360 | pat = parameter_s[2:] |
|
361 | 361 | dh = self.shell.user_ns['_dh'] |
|
362 | 362 | # first search only by basename (last component) |
|
363 | 363 | for ent in reversed(dh): |
|
364 | 364 | if pat in os.path.basename(ent) and os.path.isdir(ent): |
|
365 | 365 | ps = ent |
|
366 | 366 | break |
|
367 | 367 | |
|
368 | 368 | if fallback is None and pat in ent and os.path.isdir(ent): |
|
369 | 369 | fallback = ent |
|
370 | 370 | |
|
371 | 371 | # if we have no last part match, pick the first full path match |
|
372 | 372 | if ps is None: |
|
373 | 373 | ps = fallback |
|
374 | 374 | |
|
375 | 375 | if ps is None: |
|
376 | 376 | print("No matching entry in directory history") |
|
377 | 377 | return |
|
378 | 378 | else: |
|
379 | 379 | opts = {} |
|
380 | 380 | |
|
381 | 381 | |
|
382 | 382 | else: |
|
383 | 383 | opts, ps = self.parse_options(parameter_s, 'qb', mode='string') |
|
384 | 384 | # jump to previous |
|
385 | 385 | if ps == '-': |
|
386 | 386 | try: |
|
387 | 387 | ps = self.shell.user_ns['_dh'][-2] |
|
388 | 388 | except IndexError as e: |
|
389 | 389 | raise UsageError('%cd -: No previous directory to change to.') from e |
|
390 | 390 | # jump to bookmark if needed |
|
391 | 391 | else: |
|
392 | 392 | if not os.path.isdir(ps) or 'b' in opts: |
|
393 | 393 | bkms = self.shell.db.get('bookmarks', {}) |
|
394 | 394 | |
|
395 | 395 | if ps in bkms: |
|
396 | 396 | target = bkms[ps] |
|
397 | 397 | print('(bookmark:%s) -> %s' % (ps, target)) |
|
398 | 398 | ps = target |
|
399 | 399 | else: |
|
400 | 400 | if 'b' in opts: |
|
401 | 401 | raise UsageError("Bookmark '%s' not found. " |
|
402 | 402 | "Use '%%bookmark -l' to see your bookmarks." % ps) |
|
403 | 403 | |
|
404 | 404 | # at this point ps should point to the target dir |
|
405 | 405 | if ps: |
|
406 | 406 | try: |
|
407 | 407 | os.chdir(os.path.expanduser(ps)) |
|
408 | 408 | if hasattr(self.shell, 'term_title') and self.shell.term_title: |
|
409 | 409 | set_term_title(self.shell.term_title_format.format(cwd=abbrev_cwd())) |
|
410 | 410 | except OSError: |
|
411 | 411 | print(sys.exc_info()[1]) |
|
412 | 412 | else: |
|
413 | 413 | cwd = pathlib.Path.cwd() |
|
414 | 414 | dhist = self.shell.user_ns['_dh'] |
|
415 | 415 | if oldcwd != cwd: |
|
416 | 416 | dhist.append(cwd) |
|
417 | 417 | self.shell.db['dhist'] = compress_dhist(dhist)[-100:] |
|
418 | 418 | |
|
419 | 419 | else: |
|
420 | 420 | os.chdir(self.shell.home_dir) |
|
421 | 421 | if hasattr(self.shell, 'term_title') and self.shell.term_title: |
|
422 | 422 | set_term_title(self.shell.term_title_format.format(cwd="~")) |
|
423 | 423 | cwd = pathlib.Path.cwd() |
|
424 | 424 | dhist = self.shell.user_ns['_dh'] |
|
425 | 425 | |
|
426 | 426 | if oldcwd != cwd: |
|
427 | 427 | dhist.append(cwd) |
|
428 | 428 | self.shell.db['dhist'] = compress_dhist(dhist)[-100:] |
|
429 | 429 | if not 'q' in opts and not self.cd_force_quiet and self.shell.user_ns['_dh']: |
|
430 | 430 | print(self.shell.user_ns['_dh'][-1]) |
|
431 | 431 | |
|
432 | 432 | @line_magic |
|
433 | 433 | def env(self, parameter_s=''): |
|
434 | 434 | """Get, set, or list environment variables. |
|
435 | 435 | |
|
436 | 436 | Usage:\\ |
|
437 | 437 | |
|
438 | 438 | :``%env``: lists all environment variables/values |
|
439 | 439 | :``%env var``: get value for var |
|
440 | 440 | :``%env var val``: set value for var |
|
441 | 441 | :``%env var=val``: set value for var |
|
442 | 442 | :``%env var=$val``: set value for var, using python expansion if possible |
|
443 | 443 | """ |
|
444 | 444 | if parameter_s.strip(): |
|
445 | 445 | split = '=' if '=' in parameter_s else ' ' |
|
446 | 446 | bits = parameter_s.split(split) |
|
447 | 447 | if len(bits) == 1: |
|
448 | 448 | key = parameter_s.strip() |
|
449 | 449 | if key in os.environ: |
|
450 | 450 | return os.environ[key] |
|
451 | 451 | else: |
|
452 | 452 | err = "Environment does not have key: {0}".format(key) |
|
453 | 453 | raise UsageError(err) |
|
454 | 454 | if len(bits) > 1: |
|
455 | 455 | return self.set_env(parameter_s) |
|
456 | 456 | env = dict(os.environ) |
|
457 | 457 | # hide likely secrets when printing the whole environment |
|
458 | 458 | for key in list(env): |
|
459 | 459 | if any(s in key.lower() for s in ('key', 'token', 'secret')): |
|
460 | 460 | env[key] = '<hidden>' |
|
461 | 461 | |
|
462 | 462 | return env |
|
463 | 463 | |
|
464 | 464 | @line_magic |
|
465 | 465 | def set_env(self, parameter_s): |
|
466 | 466 | """Set environment variables. Assumptions are that either "val" is a |
|
467 | 467 | name in the user namespace, or val is something that evaluates to a |
|
468 | 468 | string. |
|
469 | 469 | |
|
470 | 470 | Usage:\\ |
|
471 | %set_env var val: set value for var | |
|
472 | %set_env var=val: set value for var | |
|
473 | %set_env var=$val: set value for var, using python expansion if possible | |
|
471 | :``%set_env var val``: set value for var | |
|
472 | :``%set_env var=val``: set value for var | |
|
473 | :``%set_env var=$val``: set value for var, using python expansion if possible | |
|
474 | 474 | """ |
|
475 | 475 | split = '=' if '=' in parameter_s else ' ' |
|
476 | 476 | bits = parameter_s.split(split, 1) |
|
477 | 477 | if not parameter_s.strip() or len(bits)<2: |
|
478 | 478 | raise UsageError("usage is 'set_env var=val'") |
|
479 | 479 | var = bits[0].strip() |
|
480 | 480 | val = bits[1].strip() |
|
481 | 481 | if re.match(r'.*\s.*', var): |
|
482 | 482 | # an environment variable with whitespace is almost certainly |
|
483 | 483 | # not what the user intended. what's more likely is the wrong |
|
484 | 484 | # split was chosen, ie for "set_env cmd_args A=B", we chose |
|
485 | 485 | # '=' for the split and should have chosen ' '. to get around |
|
486 | 486 | # this, users should just assign directly to os.environ or use |
|
487 | 487 | # standard magic {var} expansion. |
|
488 | 488 | err = "refusing to set env var with whitespace: '{0}'" |
|
489 | 489 | err = err.format(val) |
|
490 | 490 | raise UsageError(err) |
|
491 | 491 | os.environ[var] = val |
|
492 | 492 | print('env: {0}={1}'.format(var,val)) |
|
493 | 493 | |
|
494 | 494 | @line_magic |
|
495 | 495 | def pushd(self, parameter_s=''): |
|
496 | 496 | """Place the current dir on stack and change directory. |
|
497 | 497 | |
|
498 | 498 | Usage:\\ |
|
499 | 499 | %pushd ['dirname'] |
|
500 | 500 | """ |
|
501 | 501 | |
|
502 | 502 | dir_s = self.shell.dir_stack |
|
503 | 503 | tgt = os.path.expanduser(parameter_s) |
|
504 | 504 | cwd = os.getcwd().replace(self.shell.home_dir,'~') |
|
505 | 505 | if tgt: |
|
506 | 506 | self.cd(parameter_s) |
|
507 | 507 | dir_s.insert(0,cwd) |
|
508 | 508 | return self.shell.run_line_magic('dirs', '') |
|
509 | 509 | |
|
510 | 510 | @line_magic |
|
511 | 511 | def popd(self, parameter_s=''): |
|
512 | 512 | """Change to directory popped off the top of the stack. |
|
513 | 513 | """ |
|
514 | 514 | if not self.shell.dir_stack: |
|
515 | 515 | raise UsageError("%popd on empty stack") |
|
516 | 516 | top = self.shell.dir_stack.pop(0) |
|
517 | 517 | self.cd(top) |
|
518 | 518 | print("popd ->",top) |
|
519 | 519 | |
|
520 | 520 | @line_magic |
|
521 | 521 | def dirs(self, parameter_s=''): |
|
522 | 522 | """Return the current directory stack.""" |
|
523 | 523 | |
|
524 | 524 | return self.shell.dir_stack |
|
525 | 525 | |
|
526 | 526 | @line_magic |
|
527 | 527 | def dhist(self, parameter_s=''): |
|
528 | 528 | """Print your history of visited directories. |
|
529 | 529 | |
|
530 | 530 | %dhist -> print full history\\ |
|
531 | 531 | %dhist n -> print last n entries only\\ |
|
532 | 532 | %dhist n1 n2 -> print entries between n1 and n2 (n2 not included)\\ |
|
533 | 533 | |
|
534 | 534 | This history is automatically maintained by the %cd command, and |
|
535 | 535 | always available as the global list variable _dh. You can use %cd -<n> |
|
536 | 536 | to go to directory number <n>. |
|
537 | 537 | |
|
538 | 538 | Note that most of time, you should view directory history by entering |
|
539 | 539 | cd -<TAB>. |
|
540 | 540 | |
|
541 | 541 | """ |
|
542 | 542 | |
|
543 | 543 | dh = self.shell.user_ns['_dh'] |
|
544 | 544 | if parameter_s: |
|
545 | 545 | try: |
|
546 | 546 | args = map(int,parameter_s.split()) |
|
547 | 547 | except: |
|
548 | 548 | self.arg_err(self.dhist) |
|
549 | 549 | return |
|
550 | 550 | if len(args) == 1: |
|
551 | 551 | ini,fin = max(len(dh)-(args[0]),0),len(dh) |
|
552 | 552 | elif len(args) == 2: |
|
553 | 553 | ini,fin = args |
|
554 | 554 | fin = min(fin, len(dh)) |
|
555 | 555 | else: |
|
556 | 556 | self.arg_err(self.dhist) |
|
557 | 557 | return |
|
558 | 558 | else: |
|
559 | 559 | ini,fin = 0,len(dh) |
|
560 | 560 | print('Directory history (kept in _dh)') |
|
561 | 561 | for i in range(ini, fin): |
|
562 | 562 | print("%d: %s" % (i, dh[i])) |
|
563 | 563 | |
|
564 | 564 | @skip_doctest |
|
565 | 565 | @line_magic |
|
566 | 566 | def sc(self, parameter_s=''): |
|
567 | 567 | """Shell capture - run shell command and capture output (DEPRECATED use !). |
|
568 | 568 | |
|
569 | 569 | DEPRECATED. Suboptimal, retained for backwards compatibility. |
|
570 | 570 | |
|
571 | 571 | You should use the form 'var = !command' instead. Example: |
|
572 | 572 | |
|
573 | 573 | "%sc -l myfiles = ls ~" should now be written as |
|
574 | 574 | |
|
575 | 575 | "myfiles = !ls ~" |
|
576 | 576 | |
|
577 | 577 | myfiles.s, myfiles.l and myfiles.n still apply as documented |
|
578 | 578 | below. |
|
579 | 579 | |
|
580 | 580 | -- |
|
581 | 581 | %sc [options] varname=command |
|
582 | 582 | |
|
583 | 583 | IPython will run the given command using commands.getoutput(), and |
|
584 | 584 | will then update the user's interactive namespace with a variable |
|
585 | 585 | called varname, containing the value of the call. Your command can |
|
586 | 586 | contain shell wildcards, pipes, etc. |
|
587 | 587 | |
|
588 | 588 | The '=' sign in the syntax is mandatory, and the variable name you |
|
589 | 589 | supply must follow Python's standard conventions for valid names. |
|
590 | 590 | |
|
591 | 591 | (A special format without variable name exists for internal use) |
|
592 | 592 | |
|
593 | 593 | Options: |
|
594 | 594 | |
|
595 | 595 | -l: list output. Split the output on newlines into a list before |
|
596 | 596 | assigning it to the given variable. By default the output is stored |
|
597 | 597 | as a single string. |
|
598 | 598 | |
|
599 | 599 | -v: verbose. Print the contents of the variable. |
|
600 | 600 | |
|
601 | 601 | In most cases you should not need to split as a list, because the |
|
602 | 602 | returned value is a special type of string which can automatically |
|
603 | 603 | provide its contents either as a list (split on newlines) or as a |
|
604 | 604 | space-separated string. These are convenient, respectively, either |
|
605 | 605 | for sequential processing or to be passed to a shell command. |
|
606 | 606 | |
|
607 | 607 | For example:: |
|
608 | 608 | |
|
609 | 609 | # Capture into variable a |
|
610 | 610 | In [1]: sc a=ls *py |
|
611 | 611 | |
|
612 | 612 | # a is a string with embedded newlines |
|
613 | 613 | In [2]: a |
|
614 | 614 | Out[2]: 'setup.py\\nwin32_manual_post_install.py' |
|
615 | 615 | |
|
616 | 616 | # which can be seen as a list: |
|
617 | 617 | In [3]: a.l |
|
618 | 618 | Out[3]: ['setup.py', 'win32_manual_post_install.py'] |
|
619 | 619 | |
|
620 | 620 | # or as a whitespace-separated string: |
|
621 | 621 | In [4]: a.s |
|
622 | 622 | Out[4]: 'setup.py win32_manual_post_install.py' |
|
623 | 623 | |
|
624 | 624 | # a.s is useful to pass as a single command line: |
|
625 | 625 | In [5]: !wc -l $a.s |
|
626 | 626 | 146 setup.py |
|
627 | 627 | 130 win32_manual_post_install.py |
|
628 | 628 | 276 total |
|
629 | 629 | |
|
630 | 630 | # while the list form is useful to loop over: |
|
631 | 631 | In [6]: for f in a.l: |
|
632 | 632 | ...: !wc -l $f |
|
633 | 633 | ...: |
|
634 | 634 | 146 setup.py |
|
635 | 635 | 130 win32_manual_post_install.py |
|
636 | 636 | |
|
637 | 637 | Similarly, the lists returned by the -l option are also special, in |
|
638 | 638 | the sense that you can equally invoke the .s attribute on them to |
|
639 | 639 | automatically get a whitespace-separated string from their contents:: |
|
640 | 640 | |
|
641 | 641 | In [7]: sc -l b=ls *py |
|
642 | 642 | |
|
643 | 643 | In [8]: b |
|
644 | 644 | Out[8]: ['setup.py', 'win32_manual_post_install.py'] |
|
645 | 645 | |
|
646 | 646 | In [9]: b.s |
|
647 | 647 | Out[9]: 'setup.py win32_manual_post_install.py' |
|
648 | 648 | |
|
649 | 649 | In summary, both the lists and strings used for output capture have |
|
650 | 650 | the following special attributes:: |
|
651 | 651 | |
|
652 | 652 | .l (or .list) : value as list. |
|
653 | 653 | .n (or .nlstr): value as newline-separated string. |
|
654 | 654 | .s (or .spstr): value as space-separated string. |
|
655 | 655 | """ |
|
656 | 656 | |
|
657 | 657 | opts,args = self.parse_options(parameter_s, 'lv') |
|
658 | 658 | # Try to get a variable name and command to run |
|
659 | 659 | try: |
|
660 | 660 | # the variable name must be obtained from the parse_options |
|
661 | 661 | # output, which uses shlex.split to strip options out. |
|
662 | 662 | var,_ = args.split('=', 1) |
|
663 | 663 | var = var.strip() |
|
664 | 664 | # But the command has to be extracted from the original input |
|
665 | 665 | # parameter_s, not on what parse_options returns, to avoid the |
|
666 | 666 | # quote stripping which shlex.split performs on it. |
|
667 | 667 | _,cmd = parameter_s.split('=', 1) |
|
668 | 668 | except ValueError: |
|
669 | 669 | var,cmd = '','' |
|
670 | 670 | # If all looks ok, proceed |
|
671 | 671 | split = 'l' in opts |
|
672 | 672 | out = self.shell.getoutput(cmd, split=split) |
|
673 | 673 | if 'v' in opts: |
|
674 | 674 | print('%s ==\n%s' % (var, pformat(out))) |
|
675 | 675 | if var: |
|
676 | 676 | self.shell.user_ns.update({var:out}) |
|
677 | 677 | else: |
|
678 | 678 | return out |
|
679 | 679 | |
|
680 | 680 | @line_cell_magic |
|
681 | 681 | def sx(self, line='', cell=None): |
|
682 | 682 | """Shell execute - run shell command and capture output (!! is short-hand). |
|
683 | 683 | |
|
684 | 684 | %sx command |
|
685 | 685 | |
|
686 | 686 | IPython will run the given command using commands.getoutput(), and |
|
687 | 687 | return the result formatted as a list (split on '\\n'). Since the |
|
688 | 688 | output is _returned_, it will be stored in ipython's regular output |
|
689 | 689 | cache Out[N] and in the '_N' automatic variables. |
|
690 | 690 | |
|
691 | 691 | Notes: |
|
692 | 692 | |
|
693 | 693 | 1) If an input line begins with '!!', then %sx is automatically |
|
694 | 694 | invoked. That is, while:: |
|
695 | 695 | |
|
696 | 696 | !ls |
|
697 | 697 | |
|
698 | 698 | causes ipython to simply issue system('ls'), typing:: |
|
699 | 699 | |
|
700 | 700 | !!ls |
|
701 | 701 | |
|
702 | 702 | is a shorthand equivalent to:: |
|
703 | 703 | |
|
704 | 704 | %sx ls |
|
705 | 705 | |
|
706 | 706 | 2) %sx differs from %sc in that %sx automatically splits into a list, |
|
707 | 707 | like '%sc -l'. The reason for this is to make it as easy as possible |
|
708 | 708 | to process line-oriented shell output via further python commands. |
|
709 | 709 | %sc is meant to provide much finer control, but requires more |
|
710 | 710 | typing. |
|
711 | 711 | |
|
712 | 712 | 3) Just like %sc -l, this is a list with special attributes: |
|
713 | 713 | :: |
|
714 | 714 | |
|
715 | 715 | .l (or .list) : value as list. |
|
716 | 716 | .n (or .nlstr): value as newline-separated string. |
|
717 | 717 | .s (or .spstr): value as whitespace-separated string. |
|
718 | 718 | |
|
719 | 719 | This is very useful when trying to use such lists as arguments to |
|
720 | 720 | system commands.""" |
|
721 | 721 | |
|
722 | 722 | if cell is None: |
|
723 | 723 | # line magic |
|
724 | 724 | return self.shell.getoutput(line) |
|
725 | 725 | else: |
|
726 | 726 | opts,args = self.parse_options(line, '', 'out=') |
|
727 | 727 | output = self.shell.getoutput(cell) |
|
728 | 728 | out_name = opts.get('out', opts.get('o')) |
|
729 | 729 | if out_name: |
|
730 | 730 | self.shell.user_ns[out_name] = output |
|
731 | 731 | else: |
|
732 | 732 | return output |
|
733 | 733 | |
|
734 | 734 | system = line_cell_magic('system')(sx) |
|
735 | 735 | bang = cell_magic('!')(sx) |
|
736 | 736 | |
|
737 | 737 | @line_magic |
|
738 | 738 | def bookmark(self, parameter_s=''): |
|
739 | 739 | """Manage IPython's bookmark system. |
|
740 | 740 | |
|
741 | 741 | %bookmark <name> - set bookmark to current dir |
|
742 | 742 | %bookmark <name> <dir> - set bookmark to <dir> |
|
743 | 743 | %bookmark -l - list all bookmarks |
|
744 | 744 | %bookmark -d <name> - remove bookmark |
|
745 | 745 | %bookmark -r - remove all bookmarks |
|
746 | 746 | |
|
747 | 747 | You can later on access a bookmarked folder with:: |
|
748 | 748 | |
|
749 | 749 | %cd -b <name> |
|
750 | 750 | |
|
751 | 751 | or simply '%cd <name>' if there is no directory called <name> AND |
|
752 | 752 | there is such a bookmark defined. |
|
753 | 753 | |
|
754 | 754 | Your bookmarks persist through IPython sessions, but they are |
|
755 | 755 | associated with each profile.""" |
|
756 | 756 | |
|
757 | 757 | opts,args = self.parse_options(parameter_s,'drl',mode='list') |
|
758 | 758 | if len(args) > 2: |
|
759 | 759 | raise UsageError("%bookmark: too many arguments") |
|
760 | 760 | |
|
761 | 761 | bkms = self.shell.db.get('bookmarks',{}) |
|
762 | 762 | |
|
763 | 763 | if 'd' in opts: |
|
764 | 764 | try: |
|
765 | 765 | todel = args[0] |
|
766 | 766 | except IndexError as e: |
|
767 | 767 | raise UsageError( |
|
768 | 768 | "%bookmark -d: must provide a bookmark to delete") from e |
|
769 | 769 | else: |
|
770 | 770 | try: |
|
771 | 771 | del bkms[todel] |
|
772 | 772 | except KeyError as e: |
|
773 | 773 | raise UsageError( |
|
774 | 774 | "%%bookmark -d: Can't delete bookmark '%s'" % todel) from e |
|
775 | 775 | |
|
776 | 776 | elif 'r' in opts: |
|
777 | 777 | bkms = {} |
|
778 | 778 | elif 'l' in opts: |
|
779 | 779 | bks = sorted(bkms) |
|
780 | 780 | if bks: |
|
781 | 781 | size = max(map(len, bks)) |
|
782 | 782 | else: |
|
783 | 783 | size = 0 |
|
784 | 784 | fmt = '%-'+str(size)+'s -> %s' |
|
785 | 785 | print('Current bookmarks:') |
|
786 | 786 | for bk in bks: |
|
787 | 787 | print(fmt % (bk, bkms[bk])) |
|
788 | 788 | else: |
|
789 | 789 | if not args: |
|
790 | 790 | raise UsageError("%bookmark: You must specify the bookmark name") |
|
791 | 791 | elif len(args)==1: |
|
792 | 792 | bkms[args[0]] = os.getcwd() |
|
793 | 793 | elif len(args)==2: |
|
794 | 794 | bkms[args[0]] = args[1] |
|
795 | 795 | self.shell.db['bookmarks'] = bkms |
|
796 | 796 | |
|
797 | 797 | @line_magic |
|
798 | 798 | def pycat(self, parameter_s=''): |
|
799 | 799 | """Show a syntax-highlighted file through a pager. |
|
800 | 800 | |
|
801 | 801 | This magic is similar to the cat utility, but it will assume the file |
|
802 | 802 | to be Python source and will show it with syntax highlighting. |
|
803 | 803 | |
|
804 | 804 | This magic command can either take a local filename, an url, |
|
805 | 805 | an history range (see %history) or a macro as argument. |
|
806 | 806 | |
|
807 | 807 | If no parameter is given, prints out history of current session up to |
|
808 | 808 | this point. :: |
|
809 | 809 | |
|
810 | 810 | %pycat myscript.py |
|
811 | 811 | %pycat 7-27 |
|
812 | 812 | %pycat myMacro |
|
813 | 813 | %pycat http://www.example.com/myscript.py |
|
814 | 814 | """ |
|
815 | 815 | try: |
|
816 | 816 | cont = self.shell.find_user_code(parameter_s, skip_encoding_cookie=False) |
|
817 | 817 | except (ValueError, IOError): |
|
818 | 818 | print("Error: no such file, variable, URL, history range or macro") |
|
819 | 819 | return |
|
820 | 820 | |
|
821 | 821 | page.page(self.shell.pycolorize(source_to_unicode(cont))) |
|
822 | 822 | |
|
823 | 823 | @magic_arguments.magic_arguments() |
|
824 | 824 | @magic_arguments.argument( |
|
825 | 825 | '-a', '--append', action='store_true', default=False, |
|
826 | 826 | help='Append contents of the cell to an existing file. ' |
|
827 | 827 | 'The file will be created if it does not exist.' |
|
828 | 828 | ) |
|
829 | 829 | @magic_arguments.argument( |
|
830 | 830 | 'filename', type=str, |
|
831 | 831 | help='file to write' |
|
832 | 832 | ) |
|
833 | 833 | @cell_magic |
|
834 | 834 | def writefile(self, line, cell): |
|
835 | 835 | """Write the contents of the cell to a file. |
|
836 | 836 | |
|
837 | 837 | The file will be overwritten unless the -a (--append) flag is specified. |
|
838 | 838 | """ |
|
839 | 839 | args = magic_arguments.parse_argstring(self.writefile, line) |
|
840 | 840 | if re.match(r'^(\'.*\')|(".*")$', args.filename): |
|
841 | 841 | filename = os.path.expanduser(args.filename[1:-1]) |
|
842 | 842 | else: |
|
843 | 843 | filename = os.path.expanduser(args.filename) |
|
844 | 844 | |
|
845 | 845 | if os.path.exists(filename): |
|
846 | 846 | if args.append: |
|
847 | 847 | print("Appending to %s" % filename) |
|
848 | 848 | else: |
|
849 | 849 | print("Overwriting %s" % filename) |
|
850 | 850 | else: |
|
851 | 851 | print("Writing %s" % filename) |
|
852 | 852 | |
|
853 | 853 | mode = 'a' if args.append else 'w' |
|
854 | 854 | with io.open(filename, mode, encoding='utf-8') as f: |
|
855 | 855 | f.write(cell) |
@@ -1,169 +1,169 b'' | |||
|
1 | 1 | """Implementation of magic functions for matplotlib/pylab support. |
|
2 | 2 | """ |
|
3 | 3 | #----------------------------------------------------------------------------- |
|
4 | 4 | # Copyright (c) 2012 The IPython Development Team. |
|
5 | 5 | # |
|
6 | 6 | # Distributed under the terms of the Modified BSD License. |
|
7 | 7 | # |
|
8 | 8 | # The full license is in the file COPYING.txt, distributed with this software. |
|
9 | 9 | #----------------------------------------------------------------------------- |
|
10 | 10 | |
|
11 | 11 | #----------------------------------------------------------------------------- |
|
12 | 12 | # Imports |
|
13 | 13 | #----------------------------------------------------------------------------- |
|
14 | 14 | |
|
15 | 15 | # Our own packages |
|
16 | 16 | from traitlets.config.application import Application |
|
17 | 17 | from IPython.core import magic_arguments |
|
18 | 18 | from IPython.core.magic import Magics, magics_class, line_magic |
|
19 | 19 | from IPython.testing.skipdoctest import skip_doctest |
|
20 | 20 | from warnings import warn |
|
21 | 21 | from IPython.core.pylabtools import backends |
|
22 | 22 | |
|
23 | 23 | #----------------------------------------------------------------------------- |
|
24 | 24 | # Magic implementation classes |
|
25 | 25 | #----------------------------------------------------------------------------- |
|
26 | 26 | |
|
27 | 27 | magic_gui_arg = magic_arguments.argument( |
|
28 | 28 | 'gui', nargs='?', |
|
29 | 29 | help="""Name of the matplotlib backend to use %s. |
|
30 | 30 | If given, the corresponding matplotlib backend is used, |
|
31 | 31 | otherwise it will be matplotlib's default |
|
32 | 32 | (which you can set in your matplotlib config file). |
|
33 | 33 | """ % str(tuple(sorted(backends.keys()))) |
|
34 | 34 | ) |
|
35 | 35 | |
|
36 | 36 | |
|
37 | 37 | @magics_class |
|
38 | 38 | class PylabMagics(Magics): |
|
39 | 39 | """Magics related to matplotlib's pylab support""" |
|
40 | 40 | |
|
41 | 41 | @skip_doctest |
|
42 | 42 | @line_magic |
|
43 | 43 | @magic_arguments.magic_arguments() |
|
44 | 44 | @magic_arguments.argument('-l', '--list', action='store_true', |
|
45 | 45 | help='Show available matplotlib backends') |
|
46 | 46 | @magic_gui_arg |
|
47 | 47 | def matplotlib(self, line=''): |
|
48 | 48 | """Set up matplotlib to work interactively. |
|
49 | 49 | |
|
50 | 50 | This function lets you activate matplotlib interactive support |
|
51 | 51 | at any point during an IPython session. It does not import anything |
|
52 | 52 | into the interactive namespace. |
|
53 | 53 | |
|
54 | 54 | If you are using the inline matplotlib backend in the IPython Notebook |
|
55 | 55 | you can set which figure formats are enabled using the following:: |
|
56 | 56 | |
|
57 |
In [1]: from |
|
|
57 | In [1]: from matplotlib_inline.backend_inline import set_matplotlib_formats | |
|
58 | 58 | |
|
59 | 59 | In [2]: set_matplotlib_formats('pdf', 'svg') |
|
60 | 60 | |
|
61 | 61 | The default for inline figures sets `bbox_inches` to 'tight'. This can |
|
62 | 62 | cause discrepancies between the displayed image and the identical |
|
63 | 63 | image created using `savefig`. This behavior can be disabled using the |
|
64 | 64 | `%config` magic:: |
|
65 | 65 | |
|
66 | 66 | In [3]: %config InlineBackend.print_figure_kwargs = {'bbox_inches':None} |
|
67 | 67 | |
|
68 | In addition, see the docstring of | |
|
69 |
` |
|
|
70 |
` |
|
|
68 | In addition, see the docstrings of | |
|
69 | `matplotlib_inline.backend_inline.set_matplotlib_formats` and | |
|
70 | `matplotlib_inline.backend_inline.set_matplotlib_close` for more information on | |
|
71 | 71 | changing additional behaviors of the inline backend. |
|
72 | 72 | |
|
73 | 73 | Examples |
|
74 | 74 | -------- |
|
75 | 75 | To enable the inline backend for usage with the IPython Notebook:: |
|
76 | 76 | |
|
77 | 77 | In [1]: %matplotlib inline |
|
78 | 78 | |
|
79 | 79 | In this case, where the matplotlib default is TkAgg:: |
|
80 | 80 | |
|
81 | 81 | In [2]: %matplotlib |
|
82 | 82 | Using matplotlib backend: TkAgg |
|
83 | 83 | |
|
84 | 84 | But you can explicitly request a different GUI backend:: |
|
85 | 85 | |
|
86 | 86 | In [3]: %matplotlib qt |
|
87 | 87 | |
|
88 | 88 | You can list the available backends using the -l/--list option:: |
|
89 | 89 | |
|
90 | 90 | In [4]: %matplotlib --list |
|
91 | 91 | Available matplotlib backends: ['osx', 'qt4', 'qt5', 'gtk3', 'gtk4', 'notebook', 'wx', 'qt', 'nbagg', |
|
92 | 92 | 'gtk', 'tk', 'inline'] |
|
93 | 93 | """ |
|
94 | 94 | args = magic_arguments.parse_argstring(self.matplotlib, line) |
|
95 | 95 | if args.list: |
|
96 | 96 | backends_list = list(backends.keys()) |
|
97 | 97 | print("Available matplotlib backends: %s" % backends_list) |
|
98 | 98 | else: |
|
99 | 99 | gui, backend = self.shell.enable_matplotlib(args.gui.lower() if isinstance(args.gui, str) else args.gui) |
|
100 | 100 | self._show_matplotlib_backend(args.gui, backend) |
|
101 | 101 | |
|
102 | 102 | @skip_doctest |
|
103 | 103 | @line_magic |
|
104 | 104 | @magic_arguments.magic_arguments() |
|
105 | 105 | @magic_arguments.argument( |
|
106 | 106 | '--no-import-all', action='store_true', default=None, |
|
107 | 107 | help="""Prevent IPython from performing ``import *`` into the interactive namespace. |
|
108 | 108 | |
|
109 | 109 | You can govern the default behavior of this flag with the |
|
110 | 110 | InteractiveShellApp.pylab_import_all configurable. |
|
111 | 111 | """ |
|
112 | 112 | ) |
|
113 | 113 | @magic_gui_arg |
|
114 | 114 | def pylab(self, line=''): |
|
115 | 115 | """Load numpy and matplotlib to work interactively. |
|
116 | 116 | |
|
117 | 117 | This function lets you activate pylab (matplotlib, numpy and |
|
118 | 118 | interactive support) at any point during an IPython session. |
|
119 | 119 | |
|
120 | 120 | %pylab makes the following imports:: |
|
121 | 121 | |
|
122 | 122 | import numpy |
|
123 | 123 | import matplotlib |
|
124 | 124 | from matplotlib import pylab, mlab, pyplot |
|
125 | 125 | np = numpy |
|
126 | 126 | plt = pyplot |
|
127 | 127 | |
|
128 | 128 | from IPython.display import display |
|
129 | 129 | from IPython.core.pylabtools import figsize, getfigs |
|
130 | 130 | |
|
131 | 131 | from pylab import * |
|
132 | 132 | from numpy import * |
|
133 | 133 | |
|
134 | 134 | If you pass `--no-import-all`, the last two `*` imports will be excluded. |
|
135 | 135 | |
|
136 | 136 | See the %matplotlib magic for more details about activating matplotlib |
|
137 | 137 | without affecting the interactive namespace. |
|
138 | 138 | """ |
|
139 | 139 | args = magic_arguments.parse_argstring(self.pylab, line) |
|
140 | 140 | if args.no_import_all is None: |
|
141 | 141 | # get default from Application |
|
142 | 142 | if Application.initialized(): |
|
143 | 143 | app = Application.instance() |
|
144 | 144 | try: |
|
145 | 145 | import_all = app.pylab_import_all |
|
146 | 146 | except AttributeError: |
|
147 | 147 | import_all = True |
|
148 | 148 | else: |
|
149 | 149 | # nothing specified, no app - default True |
|
150 | 150 | import_all = True |
|
151 | 151 | else: |
|
152 | 152 | # invert no-import flag |
|
153 | 153 | import_all = not args.no_import_all |
|
154 | 154 | |
|
155 | 155 | gui, backend, clobbered = self.shell.enable_pylab(args.gui, import_all=import_all) |
|
156 | 156 | self._show_matplotlib_backend(args.gui, backend) |
|
157 | 157 | print( |
|
158 | 158 | "%pylab is deprecated, use %matplotlib inline and import the required libraries." |
|
159 | 159 | ) |
|
160 | 160 | print("Populating the interactive namespace from numpy and matplotlib") |
|
161 | 161 | if clobbered: |
|
162 | 162 | warn("pylab import has clobbered these variables: %s" % clobbered + |
|
163 | 163 | "\n`%matplotlib` prevents importing * from pylab and numpy" |
|
164 | 164 | ) |
|
165 | 165 | |
|
166 | 166 | def _show_matplotlib_backend(self, gui, backend): |
|
167 | 167 | """show matplotlib message backend message""" |
|
168 | 168 | if not gui or gui == 'auto': |
|
169 | 169 | print("Using matplotlib backend: %s" % backend) |
@@ -1,362 +1,362 b'' | |||
|
1 | 1 | """Magic functions for running cells in various scripts.""" |
|
2 | 2 | |
|
3 | 3 | # Copyright (c) IPython Development Team. |
|
4 | 4 | # Distributed under the terms of the Modified BSD License. |
|
5 | 5 | |
|
6 | 6 | import asyncio |
|
7 | 7 | import atexit |
|
8 | 8 | import errno |
|
9 | 9 | import os |
|
10 | 10 | import signal |
|
11 | 11 | import sys |
|
12 | 12 | import time |
|
13 | 13 | from subprocess import CalledProcessError |
|
14 | 14 | from threading import Thread |
|
15 | 15 | |
|
16 | 16 | from traitlets import Any, Dict, List, default |
|
17 | 17 | |
|
18 | 18 | from IPython.core import magic_arguments |
|
19 | 19 | from IPython.core.async_helpers import _AsyncIOProxy |
|
20 | 20 | from IPython.core.magic import Magics, cell_magic, line_magic, magics_class |
|
21 | 21 | from IPython.utils.process import arg_split |
|
22 | 22 | |
|
23 | 23 | #----------------------------------------------------------------------------- |
|
24 | 24 | # Magic implementation classes |
|
25 | 25 | #----------------------------------------------------------------------------- |
|
26 | 26 | |
|
27 | 27 | def script_args(f): |
|
28 | 28 | """single decorator for adding script args""" |
|
29 | 29 | args = [ |
|
30 | 30 | magic_arguments.argument( |
|
31 | 31 | '--out', type=str, |
|
32 | 32 | help="""The variable in which to store stdout from the script. |
|
33 | 33 | If the script is backgrounded, this will be the stdout *pipe*, |
|
34 | 34 | instead of the stderr text itself and will not be auto closed. |
|
35 | 35 | """ |
|
36 | 36 | ), |
|
37 | 37 | magic_arguments.argument( |
|
38 | 38 | '--err', type=str, |
|
39 | 39 | help="""The variable in which to store stderr from the script. |
|
40 | 40 | If the script is backgrounded, this will be the stderr *pipe*, |
|
41 | 41 | instead of the stderr text itself and will not be autoclosed. |
|
42 | 42 | """ |
|
43 | 43 | ), |
|
44 | 44 | magic_arguments.argument( |
|
45 | 45 | '--bg', action="store_true", |
|
46 | 46 | help="""Whether to run the script in the background. |
|
47 | 47 | If given, the only way to see the output of the command is |
|
48 | 48 | with --out/err. |
|
49 | 49 | """ |
|
50 | 50 | ), |
|
51 | 51 | magic_arguments.argument( |
|
52 | 52 | '--proc', type=str, |
|
53 | 53 | help="""The variable in which to store Popen instance. |
|
54 | 54 | This is used only when --bg option is given. |
|
55 | 55 | """ |
|
56 | 56 | ), |
|
57 | 57 | magic_arguments.argument( |
|
58 | 58 | '--no-raise-error', action="store_false", dest='raise_error', |
|
59 | 59 | help="""Whether you should raise an error message in addition to |
|
60 | 60 | a stream on stderr if you get a nonzero exit code. |
|
61 | 61 | """, |
|
62 | 62 | ), |
|
63 | 63 | ] |
|
64 | 64 | for arg in args: |
|
65 | 65 | f = arg(f) |
|
66 | 66 | return f |
|
67 | 67 | |
|
68 | 68 | |
|
69 | 69 | @magics_class |
|
70 | 70 | class ScriptMagics(Magics): |
|
71 | 71 | """Magics for talking to scripts |
|
72 | 72 | |
|
73 | 73 | This defines a base `%%script` cell magic for running a cell |
|
74 | 74 | with a program in a subprocess, and registers a few top-level |
|
75 | 75 | magics that call %%script with common interpreters. |
|
76 | 76 | """ |
|
77 | 77 | |
|
78 | 78 | event_loop = Any( |
|
79 | 79 | help=""" |
|
80 | 80 | The event loop on which to run subprocesses |
|
81 | 81 | |
|
82 | 82 | Not the main event loop, |
|
83 | 83 | because we want to be able to make blocking calls |
|
84 | 84 | and have certain requirements we don't want to impose on the main loop. |
|
85 | 85 | """ |
|
86 | 86 | ) |
|
87 | 87 | |
|
88 | 88 | script_magics = List( |
|
89 | 89 | help="""Extra script cell magics to define |
|
90 | 90 | |
|
91 | 91 | This generates simple wrappers of `%%script foo` as `%%foo`. |
|
92 | 92 | |
|
93 | 93 | If you want to add script magics that aren't on your path, |
|
94 | 94 | specify them in script_paths |
|
95 | 95 | """, |
|
96 | 96 | ).tag(config=True) |
|
97 | 97 | @default('script_magics') |
|
98 | 98 | def _script_magics_default(self): |
|
99 | 99 | """default to a common list of programs""" |
|
100 | 100 | |
|
101 | 101 | defaults = [ |
|
102 | 102 | 'sh', |
|
103 | 103 | 'bash', |
|
104 | 104 | 'perl', |
|
105 | 105 | 'ruby', |
|
106 | 106 | 'python', |
|
107 | 107 | 'python2', |
|
108 | 108 | 'python3', |
|
109 | 109 | 'pypy', |
|
110 | 110 | ] |
|
111 | 111 | if os.name == 'nt': |
|
112 | 112 | defaults.extend([ |
|
113 | 113 | 'cmd', |
|
114 | 114 | ]) |
|
115 | 115 | |
|
116 | 116 | return defaults |
|
117 | 117 | |
|
118 | 118 | script_paths = Dict( |
|
119 | 119 | help="""Dict mapping short 'ruby' names to full paths, such as '/opt/secret/bin/ruby' |
|
120 | 120 | |
|
121 | 121 | Only necessary for items in script_magics where the default path will not |
|
122 | 122 | find the right interpreter. |
|
123 | 123 | """ |
|
124 | 124 | ).tag(config=True) |
|
125 | 125 | |
|
126 | 126 | def __init__(self, shell=None): |
|
127 | 127 | super(ScriptMagics, self).__init__(shell=shell) |
|
128 | 128 | self._generate_script_magics() |
|
129 | 129 | self.bg_processes = [] |
|
130 | 130 | atexit.register(self.kill_bg_processes) |
|
131 | 131 | |
|
132 | 132 | def __del__(self): |
|
133 | 133 | self.kill_bg_processes() |
|
134 | 134 | |
|
135 | 135 | def _generate_script_magics(self): |
|
136 | 136 | cell_magics = self.magics['cell'] |
|
137 | 137 | for name in self.script_magics: |
|
138 | 138 | cell_magics[name] = self._make_script_magic(name) |
|
139 | 139 | |
|
140 | 140 | def _make_script_magic(self, name): |
|
141 | 141 | """make a named magic, that calls %%script with a particular program""" |
|
142 | 142 | # expand to explicit path if necessary: |
|
143 | 143 | script = self.script_paths.get(name, name) |
|
144 | 144 | |
|
145 | 145 | @magic_arguments.magic_arguments() |
|
146 | 146 | @script_args |
|
147 | 147 | def named_script_magic(line, cell): |
|
148 | 148 | # if line, add it as cl-flags |
|
149 | 149 | if line: |
|
150 | 150 | line = "%s %s" % (script, line) |
|
151 | 151 | else: |
|
152 | 152 | line = script |
|
153 | 153 | return self.shebang(line, cell) |
|
154 | 154 | |
|
155 | 155 | # write a basic docstring: |
|
156 | 156 | named_script_magic.__doc__ = \ |
|
157 | 157 | """%%{name} script magic |
|
158 | 158 | |
|
159 | 159 | Run cells with {script} in a subprocess. |
|
160 | 160 | |
|
161 | 161 | This is a shortcut for `%%script {script}` |
|
162 | 162 | """.format(**locals()) |
|
163 | 163 | |
|
164 | 164 | return named_script_magic |
|
165 | 165 | |
|
166 | 166 | @magic_arguments.magic_arguments() |
|
167 | 167 | @script_args |
|
168 | 168 | @cell_magic("script") |
|
169 | 169 | def shebang(self, line, cell): |
|
170 | 170 | """Run a cell via a shell command |
|
171 | 171 | |
|
172 | 172 | The `%%script` line is like the #! line of script, |
|
173 | 173 | specifying a program (bash, perl, ruby, etc.) with which to run. |
|
174 | 174 | |
|
175 | 175 | The rest of the cell is run by that program. |
|
176 | 176 | |
|
177 | 177 | Examples |
|
178 | 178 | -------- |
|
179 | 179 | :: |
|
180 | 180 | |
|
181 | 181 | In [1]: %%script bash |
|
182 | 182 | ...: for i in 1 2 3; do |
|
183 | 183 | ...: echo $i |
|
184 | 184 | ...: done |
|
185 | 185 | 1 |
|
186 | 186 | 2 |
|
187 | 187 | 3 |
|
188 | 188 | """ |
|
189 | 189 | |
|
190 | 190 | # Create the event loop in which to run script magics |
|
191 | 191 | # this operates on a background thread |
|
192 | 192 | if self.event_loop is None: |
|
193 | 193 | if sys.platform == "win32": |
|
194 | 194 | # don't override the current policy, |
|
195 | 195 | # just create an event loop |
|
196 | 196 | event_loop = asyncio.WindowsProactorEventLoopPolicy().new_event_loop() |
|
197 | 197 | else: |
|
198 | 198 | event_loop = asyncio.new_event_loop() |
|
199 | 199 | self.event_loop = event_loop |
|
200 | 200 | |
|
201 | 201 | # start the loop in a background thread |
|
202 | 202 | asyncio_thread = Thread(target=event_loop.run_forever, daemon=True) |
|
203 | 203 | asyncio_thread.start() |
|
204 | 204 | else: |
|
205 | 205 | event_loop = self.event_loop |
|
206 | 206 | |
|
207 | 207 | def in_thread(coro): |
|
208 | 208 | """Call a coroutine on the asyncio thread""" |
|
209 | 209 | return asyncio.run_coroutine_threadsafe(coro, event_loop).result() |
|
210 | 210 | |
|
211 | 211 | async def _handle_stream(stream, stream_arg, file_object): |
|
212 | 212 | while True: |
|
213 | line = (await stream.readline()).decode("utf8") | |
|
213 | line = (await stream.readline()).decode("utf8", errors="replace") | |
|
214 | 214 | if not line: |
|
215 | 215 | break |
|
216 | 216 | if stream_arg: |
|
217 | 217 | self.shell.user_ns[stream_arg] = line |
|
218 | 218 | else: |
|
219 | 219 | file_object.write(line) |
|
220 | 220 | file_object.flush() |
|
221 | 221 | |
|
222 | 222 | async def _stream_communicate(process, cell): |
|
223 | 223 | process.stdin.write(cell) |
|
224 | 224 | process.stdin.close() |
|
225 | 225 | stdout_task = asyncio.create_task( |
|
226 | 226 | _handle_stream(process.stdout, args.out, sys.stdout) |
|
227 | 227 | ) |
|
228 | 228 | stderr_task = asyncio.create_task( |
|
229 | 229 | _handle_stream(process.stderr, args.err, sys.stderr) |
|
230 | 230 | ) |
|
231 | 231 | await asyncio.wait([stdout_task, stderr_task]) |
|
232 | 232 | await process.wait() |
|
233 | 233 | |
|
234 | 234 | argv = arg_split(line, posix=not sys.platform.startswith("win")) |
|
235 | 235 | args, cmd = self.shebang.parser.parse_known_args(argv) |
|
236 | 236 | |
|
237 | 237 | try: |
|
238 | 238 | p = in_thread( |
|
239 | 239 | asyncio.create_subprocess_exec( |
|
240 | 240 | *cmd, |
|
241 | 241 | stdout=asyncio.subprocess.PIPE, |
|
242 | 242 | stderr=asyncio.subprocess.PIPE, |
|
243 | 243 | stdin=asyncio.subprocess.PIPE, |
|
244 | 244 | ) |
|
245 | 245 | ) |
|
246 | 246 | except OSError as e: |
|
247 | 247 | if e.errno == errno.ENOENT: |
|
248 | 248 | print("Couldn't find program: %r" % cmd[0]) |
|
249 | 249 | return |
|
250 | 250 | else: |
|
251 | 251 | raise |
|
252 | 252 | |
|
253 | 253 | if not cell.endswith('\n'): |
|
254 | 254 | cell += '\n' |
|
255 | 255 | cell = cell.encode('utf8', 'replace') |
|
256 | 256 | if args.bg: |
|
257 | 257 | self.bg_processes.append(p) |
|
258 | 258 | self._gc_bg_processes() |
|
259 | 259 | to_close = [] |
|
260 | 260 | if args.out: |
|
261 | 261 | self.shell.user_ns[args.out] = _AsyncIOProxy(p.stdout, event_loop) |
|
262 | 262 | else: |
|
263 | 263 | to_close.append(p.stdout) |
|
264 | 264 | if args.err: |
|
265 | 265 | self.shell.user_ns[args.err] = _AsyncIOProxy(p.stderr, event_loop) |
|
266 | 266 | else: |
|
267 | 267 | to_close.append(p.stderr) |
|
268 | 268 | event_loop.call_soon_threadsafe( |
|
269 | 269 | lambda: asyncio.Task(self._run_script(p, cell, to_close)) |
|
270 | 270 | ) |
|
271 | 271 | if args.proc: |
|
272 | 272 | proc_proxy = _AsyncIOProxy(p, event_loop) |
|
273 | 273 | proc_proxy.stdout = _AsyncIOProxy(p.stdout, event_loop) |
|
274 | 274 | proc_proxy.stderr = _AsyncIOProxy(p.stderr, event_loop) |
|
275 | 275 | self.shell.user_ns[args.proc] = proc_proxy |
|
276 | 276 | return |
|
277 | 277 | |
|
278 | 278 | try: |
|
279 | 279 | in_thread(_stream_communicate(p, cell)) |
|
280 | 280 | except KeyboardInterrupt: |
|
281 | 281 | try: |
|
282 | 282 | p.send_signal(signal.SIGINT) |
|
283 | 283 | in_thread(asyncio.wait_for(p.wait(), timeout=0.1)) |
|
284 | 284 | if p.returncode is not None: |
|
285 | 285 | print("Process is interrupted.") |
|
286 | 286 | return |
|
287 | 287 | p.terminate() |
|
288 | 288 | in_thread(asyncio.wait_for(p.wait(), timeout=0.1)) |
|
289 | 289 | if p.returncode is not None: |
|
290 | 290 | print("Process is terminated.") |
|
291 | 291 | return |
|
292 | 292 | p.kill() |
|
293 | 293 | print("Process is killed.") |
|
294 | 294 | except OSError: |
|
295 | 295 | pass |
|
296 | 296 | except Exception as e: |
|
297 | 297 | print("Error while terminating subprocess (pid=%i): %s" % (p.pid, e)) |
|
298 | 298 | return |
|
299 | 299 | |
|
300 | 300 | if args.raise_error and p.returncode != 0: |
|
301 | 301 | # If we get here and p.returncode is still None, we must have |
|
302 | 302 | # killed it but not yet seen its return code. We don't wait for it, |
|
303 | 303 | # in case it's stuck in uninterruptible sleep. -9 = SIGKILL |
|
304 | 304 | rc = p.returncode or -9 |
|
305 | 305 | raise CalledProcessError(rc, cell) |
|
306 | 306 | |
|
307 | 307 | shebang.__skip_doctest__ = os.name != "posix" |
|
308 | 308 | |
|
309 | 309 | async def _run_script(self, p, cell, to_close): |
|
310 | 310 | """callback for running the script in the background""" |
|
311 | 311 | |
|
312 | 312 | p.stdin.write(cell) |
|
313 | 313 | await p.stdin.drain() |
|
314 | 314 | p.stdin.close() |
|
315 | 315 | await p.stdin.wait_closed() |
|
316 | 316 | await p.wait() |
|
317 | 317 | # asyncio read pipes have no close |
|
318 | 318 | # but we should drain the data anyway |
|
319 | 319 | for s in to_close: |
|
320 | 320 | await s.read() |
|
321 | 321 | self._gc_bg_processes() |
|
322 | 322 | |
|
323 | 323 | @line_magic("killbgscripts") |
|
324 | 324 | def killbgscripts(self, _nouse_=''): |
|
325 | 325 | """Kill all BG processes started by %%script and its family.""" |
|
326 | 326 | self.kill_bg_processes() |
|
327 | 327 | print("All background processes were killed.") |
|
328 | 328 | |
|
329 | 329 | def kill_bg_processes(self): |
|
330 | 330 | """Kill all BG processes which are still running.""" |
|
331 | 331 | if not self.bg_processes: |
|
332 | 332 | return |
|
333 | 333 | for p in self.bg_processes: |
|
334 | 334 | if p.returncode is None: |
|
335 | 335 | try: |
|
336 | 336 | p.send_signal(signal.SIGINT) |
|
337 | 337 | except: |
|
338 | 338 | pass |
|
339 | 339 | time.sleep(0.1) |
|
340 | 340 | self._gc_bg_processes() |
|
341 | 341 | if not self.bg_processes: |
|
342 | 342 | return |
|
343 | 343 | for p in self.bg_processes: |
|
344 | 344 | if p.returncode is None: |
|
345 | 345 | try: |
|
346 | 346 | p.terminate() |
|
347 | 347 | except: |
|
348 | 348 | pass |
|
349 | 349 | time.sleep(0.1) |
|
350 | 350 | self._gc_bg_processes() |
|
351 | 351 | if not self.bg_processes: |
|
352 | 352 | return |
|
353 | 353 | for p in self.bg_processes: |
|
354 | 354 | if p.returncode is None: |
|
355 | 355 | try: |
|
356 | 356 | p.kill() |
|
357 | 357 | except: |
|
358 | 358 | pass |
|
359 | 359 | self._gc_bg_processes() |
|
360 | 360 | |
|
361 | 361 | def _gc_bg_processes(self): |
|
362 | 362 | self.bg_processes = [p for p in self.bg_processes if p.returncode is None] |
@@ -1,54 +1,54 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | """Release data for the IPython project.""" |
|
3 | 3 | |
|
4 | 4 | #----------------------------------------------------------------------------- |
|
5 | 5 | # Copyright (c) 2008, IPython Development Team. |
|
6 | 6 | # Copyright (c) 2001, Fernando Perez <fernando.perez@colorado.edu> |
|
7 | 7 | # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de> |
|
8 | 8 | # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu> |
|
9 | 9 | # |
|
10 | 10 | # Distributed under the terms of the Modified BSD License. |
|
11 | 11 | # |
|
12 | 12 | # The full license is in the file COPYING.txt, distributed with this software. |
|
13 | 13 | #----------------------------------------------------------------------------- |
|
14 | 14 | |
|
15 | 15 | # IPython version information. An empty _version_extra corresponds to a full |
|
16 | 16 | # release. 'dev' as a _version_extra string means this is a development |
|
17 | 17 | # version |
|
18 | 18 | _version_major = 8 |
|
19 |
_version_minor = |
|
|
19 | _version_minor = 10 | |
|
20 | 20 | _version_patch = 0 |
|
21 | 21 | _version_extra = ".dev" |
|
22 | 22 | # _version_extra = "rc1" |
|
23 | 23 | # _version_extra = "" # Uncomment this for full releases |
|
24 | 24 | |
|
25 | 25 | # Construct full version string from these. |
|
26 | 26 | _ver = [_version_major, _version_minor, _version_patch] |
|
27 | 27 | |
|
28 | 28 | __version__ = '.'.join(map(str, _ver)) |
|
29 | 29 | if _version_extra: |
|
30 | 30 | __version__ = __version__ + _version_extra |
|
31 | 31 | |
|
32 | 32 | version = __version__ # backwards compatibility name |
|
33 | 33 | version_info = (_version_major, _version_minor, _version_patch, _version_extra) |
|
34 | 34 | |
|
35 | 35 | # Change this when incrementing the kernel protocol version |
|
36 | 36 | kernel_protocol_version_info = (5, 0) |
|
37 | 37 | kernel_protocol_version = "%i.%i" % kernel_protocol_version_info |
|
38 | 38 | |
|
39 | 39 | license = "BSD-3-Clause" |
|
40 | 40 | |
|
41 | 41 | authors = {'Fernando' : ('Fernando Perez','fperez.net@gmail.com'), |
|
42 | 42 | 'Janko' : ('Janko Hauser','jhauser@zscout.de'), |
|
43 | 43 | 'Nathan' : ('Nathaniel Gray','n8gray@caltech.edu'), |
|
44 | 44 | 'Ville' : ('Ville Vainio','vivainio@gmail.com'), |
|
45 | 45 | 'Brian' : ('Brian E Granger', 'ellisonbg@gmail.com'), |
|
46 | 46 | 'Min' : ('Min Ragan-Kelley', 'benjaminrk@gmail.com'), |
|
47 | 47 | 'Thomas' : ('Thomas A. Kluyver', 'takowl@gmail.com'), |
|
48 | 48 | 'Jorgen' : ('Jorgen Stenarson', 'jorgen.stenarson@bostream.nu'), |
|
49 | 49 | 'Matthias' : ('Matthias Bussonnier', 'bussonniermatthias@gmail.com'), |
|
50 | 50 | } |
|
51 | 51 | |
|
52 | 52 | author = 'The IPython Development Team' |
|
53 | 53 | |
|
54 | 54 | author_email = 'ipython-dev@python.org' |
@@ -1,451 +1,451 b'' | |||
|
1 | 1 | # encoding: utf-8 |
|
2 | 2 | """ |
|
3 | 3 | A mixin for :class:`~IPython.core.application.Application` classes that |
|
4 | 4 | launch InteractiveShell instances, load extensions, etc. |
|
5 | 5 | """ |
|
6 | 6 | |
|
7 | 7 | # Copyright (c) IPython Development Team. |
|
8 | 8 | # Distributed under the terms of the Modified BSD License. |
|
9 | 9 | |
|
10 | 10 | import glob |
|
11 | 11 | from itertools import chain |
|
12 | 12 | import os |
|
13 | 13 | import sys |
|
14 | 14 | |
|
15 | 15 | from traitlets.config.application import boolean_flag |
|
16 | 16 | from traitlets.config.configurable import Configurable |
|
17 | 17 | from traitlets.config.loader import Config |
|
18 | 18 | from IPython.core.application import SYSTEM_CONFIG_DIRS, ENV_CONFIG_DIRS |
|
19 | 19 | from IPython.core import pylabtools |
|
20 | 20 | from IPython.utils.contexts import preserve_keys |
|
21 | 21 | from IPython.utils.path import filefind |
|
22 | 22 | from traitlets import ( |
|
23 | 23 | Unicode, Instance, List, Bool, CaselessStrEnum, observe, |
|
24 | 24 | DottedObjectName, |
|
25 | 25 | ) |
|
26 | 26 | from IPython.terminal import pt_inputhooks |
|
27 | 27 | |
|
28 | 28 | #----------------------------------------------------------------------------- |
|
29 | 29 | # Aliases and Flags |
|
30 | 30 | #----------------------------------------------------------------------------- |
|
31 | 31 | |
|
32 | 32 | gui_keys = tuple(sorted(pt_inputhooks.backends) + sorted(pt_inputhooks.aliases)) |
|
33 | 33 | |
|
34 | 34 | backend_keys = sorted(pylabtools.backends.keys()) |
|
35 | 35 | backend_keys.insert(0, 'auto') |
|
36 | 36 | |
|
37 | 37 | shell_flags = {} |
|
38 | 38 | |
|
39 | 39 | addflag = lambda *args: shell_flags.update(boolean_flag(*args)) |
|
40 | 40 | addflag('autoindent', 'InteractiveShell.autoindent', |
|
41 | 41 | 'Turn on autoindenting.', 'Turn off autoindenting.' |
|
42 | 42 | ) |
|
43 | 43 | addflag('automagic', 'InteractiveShell.automagic', |
|
44 | 44 | """Turn on the auto calling of magic commands. Type %%magic at the |
|
45 | 45 | IPython prompt for more information.""", |
|
46 | 46 | 'Turn off the auto calling of magic commands.' |
|
47 | 47 | ) |
|
48 | 48 | addflag('pdb', 'InteractiveShell.pdb', |
|
49 | 49 | "Enable auto calling the pdb debugger after every exception.", |
|
50 | 50 | "Disable auto calling the pdb debugger after every exception." |
|
51 | 51 | ) |
|
52 | 52 | addflag('pprint', 'PlainTextFormatter.pprint', |
|
53 | 53 | "Enable auto pretty printing of results.", |
|
54 | 54 | "Disable auto pretty printing of results." |
|
55 | 55 | ) |
|
56 | 56 | addflag('color-info', 'InteractiveShell.color_info', |
|
57 | 57 | """IPython can display information about objects via a set of functions, |
|
58 | 58 | and optionally can use colors for this, syntax highlighting |
|
59 | 59 | source code and various other elements. This is on by default, but can cause |
|
60 | 60 | problems with some pagers. If you see such problems, you can disable the |
|
61 | 61 | colours.""", |
|
62 | 62 | "Disable using colors for info related things." |
|
63 | 63 | ) |
|
64 | 64 | addflag('ignore-cwd', 'InteractiveShellApp.ignore_cwd', |
|
65 | 65 | "Exclude the current working directory from sys.path", |
|
66 | 66 | "Include the current working directory in sys.path", |
|
67 | 67 | ) |
|
68 | 68 | nosep_config = Config() |
|
69 | 69 | nosep_config.InteractiveShell.separate_in = '' |
|
70 | 70 | nosep_config.InteractiveShell.separate_out = '' |
|
71 | 71 | nosep_config.InteractiveShell.separate_out2 = '' |
|
72 | 72 | |
|
73 | 73 | shell_flags['nosep']=(nosep_config, "Eliminate all spacing between prompts.") |
|
74 | 74 | shell_flags['pylab'] = ( |
|
75 | 75 | {'InteractiveShellApp' : {'pylab' : 'auto'}}, |
|
76 | 76 | """Pre-load matplotlib and numpy for interactive use with |
|
77 | 77 | the default matplotlib backend.""" |
|
78 | 78 | ) |
|
79 | 79 | shell_flags['matplotlib'] = ( |
|
80 | 80 | {'InteractiveShellApp' : {'matplotlib' : 'auto'}}, |
|
81 | 81 | """Configure matplotlib for interactive use with |
|
82 | 82 | the default matplotlib backend.""" |
|
83 | 83 | ) |
|
84 | 84 | |
|
85 | 85 | # it's possible we don't want short aliases for *all* of these: |
|
86 | 86 | shell_aliases = dict( |
|
87 | 87 | autocall='InteractiveShell.autocall', |
|
88 | 88 | colors='InteractiveShell.colors', |
|
89 | 89 | logfile='InteractiveShell.logfile', |
|
90 | 90 | logappend='InteractiveShell.logappend', |
|
91 | 91 | c='InteractiveShellApp.code_to_run', |
|
92 | 92 | m='InteractiveShellApp.module_to_run', |
|
93 | 93 | ext="InteractiveShellApp.extra_extensions", |
|
94 | 94 | gui='InteractiveShellApp.gui', |
|
95 | 95 | pylab='InteractiveShellApp.pylab', |
|
96 | 96 | matplotlib='InteractiveShellApp.matplotlib', |
|
97 | 97 | ) |
|
98 | 98 | shell_aliases['cache-size'] = 'InteractiveShell.cache_size' |
|
99 | 99 | |
|
100 | 100 | #----------------------------------------------------------------------------- |
|
101 | 101 | # Main classes and functions |
|
102 | 102 | #----------------------------------------------------------------------------- |
|
103 | 103 | |
|
104 | 104 | class InteractiveShellApp(Configurable): |
|
105 | 105 | """A Mixin for applications that start InteractiveShell instances. |
|
106 | 106 | |
|
107 | 107 | Provides configurables for loading extensions and executing files |
|
108 | 108 | as part of configuring a Shell environment. |
|
109 | 109 | |
|
110 | 110 | The following methods should be called by the :meth:`initialize` method |
|
111 | 111 | of the subclass: |
|
112 | 112 | |
|
113 | 113 | - :meth:`init_path` |
|
114 | 114 | - :meth:`init_shell` (to be implemented by the subclass) |
|
115 | 115 | - :meth:`init_gui_pylab` |
|
116 | 116 | - :meth:`init_extensions` |
|
117 | 117 | - :meth:`init_code` |
|
118 | 118 | """ |
|
119 | 119 | extensions = List(Unicode(), |
|
120 | 120 | help="A list of dotted module names of IPython extensions to load." |
|
121 | 121 | ).tag(config=True) |
|
122 | 122 | |
|
123 | 123 | extra_extensions = List( |
|
124 | 124 | DottedObjectName(), |
|
125 | 125 | help=""" |
|
126 | 126 | Dotted module name(s) of one or more IPython extensions to load. |
|
127 | 127 | |
|
128 | 128 | For specifying extra extensions to load on the command-line. |
|
129 | 129 | |
|
130 | 130 | .. versionadded:: 7.10 |
|
131 | 131 | """, |
|
132 | 132 | ).tag(config=True) |
|
133 | 133 | |
|
134 | 134 | reraise_ipython_extension_failures = Bool(False, |
|
135 | 135 | help="Reraise exceptions encountered loading IPython extensions?", |
|
136 | 136 | ).tag(config=True) |
|
137 | 137 | |
|
138 | 138 | # Extensions that are always loaded (not configurable) |
|
139 | 139 | default_extensions = List(Unicode(), [u'storemagic']).tag(config=False) |
|
140 | 140 | |
|
141 | 141 | hide_initial_ns = Bool(True, |
|
142 | 142 | help="""Should variables loaded at startup (by startup files, exec_lines, etc.) |
|
143 | 143 | be hidden from tools like %who?""" |
|
144 | 144 | ).tag(config=True) |
|
145 | 145 | |
|
146 | 146 | exec_files = List(Unicode(), |
|
147 | 147 | help="""List of files to run at IPython startup.""" |
|
148 | 148 | ).tag(config=True) |
|
149 | 149 | exec_PYTHONSTARTUP = Bool(True, |
|
150 | 150 | help="""Run the file referenced by the PYTHONSTARTUP environment |
|
151 | 151 | variable at IPython startup.""" |
|
152 | 152 | ).tag(config=True) |
|
153 | 153 | file_to_run = Unicode('', |
|
154 | 154 | help="""A file to be run""").tag(config=True) |
|
155 | 155 | |
|
156 | 156 | exec_lines = List(Unicode(), |
|
157 | 157 | help="""lines of code to run at IPython startup.""" |
|
158 | 158 | ).tag(config=True) |
|
159 | 159 | code_to_run = Unicode('', |
|
160 | 160 | help="Execute the given command string." |
|
161 | 161 | ).tag(config=True) |
|
162 | 162 | module_to_run = Unicode('', |
|
163 | 163 | help="Run the module as a script." |
|
164 | 164 | ).tag(config=True) |
|
165 | 165 | gui = CaselessStrEnum(gui_keys, allow_none=True, |
|
166 | 166 | help="Enable GUI event loop integration with any of {0}.".format(gui_keys) |
|
167 | 167 | ).tag(config=True) |
|
168 | 168 | matplotlib = CaselessStrEnum(backend_keys, allow_none=True, |
|
169 | 169 | help="""Configure matplotlib for interactive use with |
|
170 | 170 | the default matplotlib backend.""" |
|
171 | 171 | ).tag(config=True) |
|
172 | 172 | pylab = CaselessStrEnum(backend_keys, allow_none=True, |
|
173 | 173 | help="""Pre-load matplotlib and numpy for interactive use, |
|
174 | 174 | selecting a particular matplotlib backend and loop integration. |
|
175 | 175 | """ |
|
176 | 176 | ).tag(config=True) |
|
177 | 177 | pylab_import_all = Bool(True, |
|
178 | 178 | help="""If true, IPython will populate the user namespace with numpy, pylab, etc. |
|
179 | 179 | and an ``import *`` is done from numpy and pylab, when using pylab mode. |
|
180 | 180 | |
|
181 | 181 | When False, pylab mode should not import any names into the user namespace. |
|
182 | 182 | """ |
|
183 | 183 | ).tag(config=True) |
|
184 | 184 | ignore_cwd = Bool( |
|
185 | 185 | False, |
|
186 | 186 | help="""If True, IPython will not add the current working directory to sys.path. |
|
187 | 187 | When False, the current working directory is added to sys.path, allowing imports |
|
188 | 188 | of modules defined in the current directory.""" |
|
189 | 189 | ).tag(config=True) |
|
190 | 190 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', |
|
191 | 191 | allow_none=True) |
|
192 | 192 | # whether interact-loop should start |
|
193 | 193 | interact = Bool(True) |
|
194 | 194 | |
|
195 | 195 | user_ns = Instance(dict, args=None, allow_none=True) |
|
196 | 196 | @observe('user_ns') |
|
197 | 197 | def _user_ns_changed(self, change): |
|
198 | 198 | if self.shell is not None: |
|
199 | 199 | self.shell.user_ns = change['new'] |
|
200 | 200 | self.shell.init_user_ns() |
|
201 | 201 | |
|
202 | 202 | def init_path(self): |
|
203 | 203 | """Add current working directory, '', to sys.path |
|
204 | 204 | |
|
205 | 205 | Unlike Python's default, we insert before the first `site-packages` |
|
206 | 206 | or `dist-packages` directory, |
|
207 | 207 | so that it is after the standard library. |
|
208 | 208 | |
|
209 | 209 | .. versionchanged:: 7.2 |
|
210 | 210 | Try to insert after the standard library, instead of first. |
|
211 | 211 | .. versionchanged:: 8.0 |
|
212 | 212 | Allow optionally not including the current directory in sys.path |
|
213 | 213 | """ |
|
214 | 214 | if '' in sys.path or self.ignore_cwd: |
|
215 | 215 | return |
|
216 | 216 | for idx, path in enumerate(sys.path): |
|
217 | 217 | parent, last_part = os.path.split(path) |
|
218 | 218 | if last_part in {'site-packages', 'dist-packages'}: |
|
219 | 219 | break |
|
220 | 220 | else: |
|
221 | 221 | # no site-packages or dist-packages found (?!) |
|
222 | 222 | # back to original behavior of inserting at the front |
|
223 | 223 | idx = 0 |
|
224 | 224 | sys.path.insert(idx, '') |
|
225 | 225 | |
|
226 | 226 | def init_shell(self): |
|
227 | 227 | raise NotImplementedError("Override in subclasses") |
|
228 | 228 | |
|
229 | 229 | def init_gui_pylab(self): |
|
230 | 230 | """Enable GUI event loop integration, taking pylab into account.""" |
|
231 | 231 | enable = False |
|
232 | 232 | shell = self.shell |
|
233 | 233 | if self.pylab: |
|
234 | 234 | enable = lambda key: shell.enable_pylab(key, import_all=self.pylab_import_all) |
|
235 | 235 | key = self.pylab |
|
236 | 236 | elif self.matplotlib: |
|
237 | 237 | enable = shell.enable_matplotlib |
|
238 | 238 | key = self.matplotlib |
|
239 | 239 | elif self.gui: |
|
240 | 240 | enable = shell.enable_gui |
|
241 | 241 | key = self.gui |
|
242 | 242 | |
|
243 | 243 | if not enable: |
|
244 | 244 | return |
|
245 | 245 | |
|
246 | 246 | try: |
|
247 | 247 | r = enable(key) |
|
248 | 248 | except ImportError: |
|
249 | 249 | self.log.warning("Eventloop or matplotlib integration failed. Is matplotlib installed?") |
|
250 | 250 | self.shell.showtraceback() |
|
251 | 251 | return |
|
252 | 252 | except Exception: |
|
253 | 253 | self.log.warning("GUI event loop or pylab initialization failed") |
|
254 | 254 | self.shell.showtraceback() |
|
255 | 255 | return |
|
256 | 256 | |
|
257 | 257 | if isinstance(r, tuple): |
|
258 | 258 | gui, backend = r[:2] |
|
259 | 259 | self.log.info("Enabling GUI event loop integration, " |
|
260 | 260 | "eventloop=%s, matplotlib=%s", gui, backend) |
|
261 | 261 | if key == "auto": |
|
262 | 262 | print("Using matplotlib backend: %s" % backend) |
|
263 | 263 | else: |
|
264 | 264 | gui = r |
|
265 | 265 | self.log.info("Enabling GUI event loop integration, " |
|
266 | 266 | "eventloop=%s", gui) |
|
267 | 267 | |
|
268 | 268 | def init_extensions(self): |
|
269 | 269 | """Load all IPython extensions in IPythonApp.extensions. |
|
270 | 270 | |
|
271 | 271 | This uses the :meth:`ExtensionManager.load_extensions` to load all |
|
272 | 272 | the extensions listed in ``self.extensions``. |
|
273 | 273 | """ |
|
274 | 274 | try: |
|
275 | 275 | self.log.debug("Loading IPython extensions...") |
|
276 | 276 | extensions = ( |
|
277 | 277 | self.default_extensions + self.extensions + self.extra_extensions |
|
278 | 278 | ) |
|
279 | 279 | for ext in extensions: |
|
280 | 280 | try: |
|
281 |
self.log.info("Loading IPython extension: %s" |
|
|
281 | self.log.info("Loading IPython extension: %s", ext) | |
|
282 | 282 | self.shell.extension_manager.load_extension(ext) |
|
283 | 283 | except: |
|
284 | 284 | if self.reraise_ipython_extension_failures: |
|
285 | 285 | raise |
|
286 | 286 | msg = ("Error in loading extension: {ext}\n" |
|
287 | 287 | "Check your config files in {location}".format( |
|
288 | 288 | ext=ext, |
|
289 | 289 | location=self.profile_dir.location |
|
290 | 290 | )) |
|
291 | 291 | self.log.warning(msg, exc_info=True) |
|
292 | 292 | except: |
|
293 | 293 | if self.reraise_ipython_extension_failures: |
|
294 | 294 | raise |
|
295 | 295 | self.log.warning("Unknown error in loading extensions:", exc_info=True) |
|
296 | 296 | |
|
297 | 297 | def init_code(self): |
|
298 | 298 | """run the pre-flight code, specified via exec_lines""" |
|
299 | 299 | self._run_startup_files() |
|
300 | 300 | self._run_exec_lines() |
|
301 | 301 | self._run_exec_files() |
|
302 | 302 | |
|
303 | 303 | # Hide variables defined here from %who etc. |
|
304 | 304 | if self.hide_initial_ns: |
|
305 | 305 | self.shell.user_ns_hidden.update(self.shell.user_ns) |
|
306 | 306 | |
|
307 | 307 | # command-line execution (ipython -i script.py, ipython -m module) |
|
308 | 308 | # should *not* be excluded from %whos |
|
309 | 309 | self._run_cmd_line_code() |
|
310 | 310 | self._run_module() |
|
311 | 311 | |
|
312 | 312 | # flush output, so itwon't be attached to the first cell |
|
313 | 313 | sys.stdout.flush() |
|
314 | 314 | sys.stderr.flush() |
|
315 | 315 | self.shell._sys_modules_keys = set(sys.modules.keys()) |
|
316 | 316 | |
|
317 | 317 | def _run_exec_lines(self): |
|
318 | 318 | """Run lines of code in IPythonApp.exec_lines in the user's namespace.""" |
|
319 | 319 | if not self.exec_lines: |
|
320 | 320 | return |
|
321 | 321 | try: |
|
322 | 322 | self.log.debug("Running code from IPythonApp.exec_lines...") |
|
323 | 323 | for line in self.exec_lines: |
|
324 | 324 | try: |
|
325 | 325 | self.log.info("Running code in user namespace: %s" % |
|
326 | 326 | line) |
|
327 | 327 | self.shell.run_cell(line, store_history=False) |
|
328 | 328 | except: |
|
329 | 329 | self.log.warning("Error in executing line in user " |
|
330 | 330 | "namespace: %s" % line) |
|
331 | 331 | self.shell.showtraceback() |
|
332 | 332 | except: |
|
333 | 333 | self.log.warning("Unknown error in handling IPythonApp.exec_lines:") |
|
334 | 334 | self.shell.showtraceback() |
|
335 | 335 | |
|
336 | 336 | def _exec_file(self, fname, shell_futures=False): |
|
337 | 337 | try: |
|
338 | 338 | full_filename = filefind(fname, [u'.', self.ipython_dir]) |
|
339 | 339 | except IOError: |
|
340 | 340 | self.log.warning("File not found: %r"%fname) |
|
341 | 341 | return |
|
342 | 342 | # Make sure that the running script gets a proper sys.argv as if it |
|
343 | 343 | # were run from a system shell. |
|
344 | 344 | save_argv = sys.argv |
|
345 | 345 | sys.argv = [full_filename] + self.extra_args[1:] |
|
346 | 346 | try: |
|
347 | 347 | if os.path.isfile(full_filename): |
|
348 | 348 | self.log.info("Running file in user namespace: %s" % |
|
349 | 349 | full_filename) |
|
350 | 350 | # Ensure that __file__ is always defined to match Python |
|
351 | 351 | # behavior. |
|
352 | 352 | with preserve_keys(self.shell.user_ns, '__file__'): |
|
353 | 353 | self.shell.user_ns['__file__'] = fname |
|
354 | 354 | if full_filename.endswith('.ipy') or full_filename.endswith('.ipynb'): |
|
355 | 355 | self.shell.safe_execfile_ipy(full_filename, |
|
356 | 356 | shell_futures=shell_futures) |
|
357 | 357 | else: |
|
358 | 358 | # default to python, even without extension |
|
359 | 359 | self.shell.safe_execfile(full_filename, |
|
360 | 360 | self.shell.user_ns, |
|
361 | 361 | shell_futures=shell_futures, |
|
362 | 362 | raise_exceptions=True) |
|
363 | 363 | finally: |
|
364 | 364 | sys.argv = save_argv |
|
365 | 365 | |
|
366 | 366 | def _run_startup_files(self): |
|
367 | 367 | """Run files from profile startup directory""" |
|
368 | 368 | startup_dirs = [self.profile_dir.startup_dir] + [ |
|
369 | 369 | os.path.join(p, 'startup') for p in chain(ENV_CONFIG_DIRS, SYSTEM_CONFIG_DIRS) |
|
370 | 370 | ] |
|
371 | 371 | startup_files = [] |
|
372 | 372 | |
|
373 | 373 | if self.exec_PYTHONSTARTUP and os.environ.get('PYTHONSTARTUP', False) and \ |
|
374 | 374 | not (self.file_to_run or self.code_to_run or self.module_to_run): |
|
375 | 375 | python_startup = os.environ['PYTHONSTARTUP'] |
|
376 | 376 | self.log.debug("Running PYTHONSTARTUP file %s...", python_startup) |
|
377 | 377 | try: |
|
378 | 378 | self._exec_file(python_startup) |
|
379 | 379 | except: |
|
380 | 380 | self.log.warning("Unknown error in handling PYTHONSTARTUP file %s:", python_startup) |
|
381 | 381 | self.shell.showtraceback() |
|
382 | 382 | for startup_dir in startup_dirs[::-1]: |
|
383 | 383 | startup_files += glob.glob(os.path.join(startup_dir, '*.py')) |
|
384 | 384 | startup_files += glob.glob(os.path.join(startup_dir, '*.ipy')) |
|
385 | 385 | if not startup_files: |
|
386 | 386 | return |
|
387 | 387 | |
|
388 | 388 | self.log.debug("Running startup files from %s...", startup_dir) |
|
389 | 389 | try: |
|
390 | 390 | for fname in sorted(startup_files): |
|
391 | 391 | self._exec_file(fname) |
|
392 | 392 | except: |
|
393 | 393 | self.log.warning("Unknown error in handling startup files:") |
|
394 | 394 | self.shell.showtraceback() |
|
395 | 395 | |
|
396 | 396 | def _run_exec_files(self): |
|
397 | 397 | """Run files from IPythonApp.exec_files""" |
|
398 | 398 | if not self.exec_files: |
|
399 | 399 | return |
|
400 | 400 | |
|
401 | 401 | self.log.debug("Running files in IPythonApp.exec_files...") |
|
402 | 402 | try: |
|
403 | 403 | for fname in self.exec_files: |
|
404 | 404 | self._exec_file(fname) |
|
405 | 405 | except: |
|
406 | 406 | self.log.warning("Unknown error in handling IPythonApp.exec_files:") |
|
407 | 407 | self.shell.showtraceback() |
|
408 | 408 | |
|
409 | 409 | def _run_cmd_line_code(self): |
|
410 | 410 | """Run code or file specified at the command-line""" |
|
411 | 411 | if self.code_to_run: |
|
412 | 412 | line = self.code_to_run |
|
413 | 413 | try: |
|
414 | 414 | self.log.info("Running code given at command line (c=): %s" % |
|
415 | 415 | line) |
|
416 | 416 | self.shell.run_cell(line, store_history=False) |
|
417 | 417 | except: |
|
418 | 418 | self.log.warning("Error in executing line in user namespace: %s" % |
|
419 | 419 | line) |
|
420 | 420 | self.shell.showtraceback() |
|
421 | 421 | if not self.interact: |
|
422 | 422 | self.exit(1) |
|
423 | 423 | |
|
424 | 424 | # Like Python itself, ignore the second if the first of these is present |
|
425 | 425 | elif self.file_to_run: |
|
426 | 426 | fname = self.file_to_run |
|
427 | 427 | if os.path.isdir(fname): |
|
428 | 428 | fname = os.path.join(fname, "__main__.py") |
|
429 | 429 | if not os.path.exists(fname): |
|
430 | 430 | self.log.warning("File '%s' doesn't exist", fname) |
|
431 | 431 | if not self.interact: |
|
432 | 432 | self.exit(2) |
|
433 | 433 | try: |
|
434 | 434 | self._exec_file(fname, shell_futures=True) |
|
435 | 435 | except: |
|
436 | 436 | self.shell.showtraceback(tb_offset=4) |
|
437 | 437 | if not self.interact: |
|
438 | 438 | self.exit(1) |
|
439 | 439 | |
|
440 | 440 | def _run_module(self): |
|
441 | 441 | """Run module specified at the command-line.""" |
|
442 | 442 | if self.module_to_run: |
|
443 | 443 | # Make sure that the module gets a proper sys.argv as if it were |
|
444 | 444 | # run using `python -m`. |
|
445 | 445 | save_argv = sys.argv |
|
446 | 446 | sys.argv = [sys.executable] + self.extra_args |
|
447 | 447 | try: |
|
448 | 448 | self.shell.safe_run_module(self.module_to_run, |
|
449 | 449 | self.shell.user_ns) |
|
450 | 450 | finally: |
|
451 | 451 | sys.argv = save_argv |
@@ -1,1454 +1,1513 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | """Tests for various magic functions.""" |
|
3 | 3 | |
|
4 | 4 | import gc |
|
5 | 5 | import io |
|
6 | 6 | import os |
|
7 | 7 | import re |
|
8 | 8 | import shlex |
|
9 | 9 | import sys |
|
10 | 10 | import warnings |
|
11 | 11 | from importlib import invalidate_caches |
|
12 | 12 | from io import StringIO |
|
13 | 13 | from pathlib import Path |
|
14 | 14 | from textwrap import dedent |
|
15 | 15 | from unittest import TestCase, mock |
|
16 | 16 | |
|
17 | 17 | import pytest |
|
18 | 18 | |
|
19 | 19 | from IPython import get_ipython |
|
20 | 20 | from IPython.core import magic |
|
21 | 21 | from IPython.core.error import UsageError |
|
22 | 22 | from IPython.core.magic import ( |
|
23 | 23 | Magics, |
|
24 | 24 | cell_magic, |
|
25 | 25 | line_magic, |
|
26 | 26 | magics_class, |
|
27 | 27 | register_cell_magic, |
|
28 | 28 | register_line_magic, |
|
29 | 29 | ) |
|
30 | 30 | from IPython.core.magics import code, execution, logging, osm, script |
|
31 | 31 | from IPython.testing import decorators as dec |
|
32 | 32 | from IPython.testing import tools as tt |
|
33 | 33 | from IPython.utils.io import capture_output |
|
34 | 34 | from IPython.utils.process import find_cmd |
|
35 | 35 | from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory |
|
36 | 36 | from IPython.utils.syspathcontext import prepended_to_syspath |
|
37 | 37 | |
|
38 | 38 | from .test_debugger import PdbTestInput |
|
39 | 39 | |
|
40 | 40 | from tempfile import NamedTemporaryFile |
|
41 | 41 | |
|
42 | 42 | @magic.magics_class |
|
43 | 43 | class DummyMagics(magic.Magics): pass |
|
44 | 44 | |
|
45 | 45 | def test_extract_code_ranges(): |
|
46 | 46 | instr = "1 3 5-6 7-9 10:15 17: :10 10- -13 :" |
|
47 | 47 | expected = [ |
|
48 | 48 | (0, 1), |
|
49 | 49 | (2, 3), |
|
50 | 50 | (4, 6), |
|
51 | 51 | (6, 9), |
|
52 | 52 | (9, 14), |
|
53 | 53 | (16, None), |
|
54 | 54 | (None, 9), |
|
55 | 55 | (9, None), |
|
56 | 56 | (None, 13), |
|
57 | 57 | (None, None), |
|
58 | 58 | ] |
|
59 | 59 | actual = list(code.extract_code_ranges(instr)) |
|
60 | 60 | assert actual == expected |
|
61 | 61 | |
|
62 | 62 | def test_extract_symbols(): |
|
63 | 63 | source = """import foo\na = 10\ndef b():\n return 42\n\n\nclass A: pass\n\n\n""" |
|
64 | 64 | symbols_args = ["a", "b", "A", "A,b", "A,a", "z"] |
|
65 | 65 | expected = [([], ['a']), |
|
66 | 66 | (["def b():\n return 42\n"], []), |
|
67 | 67 | (["class A: pass\n"], []), |
|
68 | 68 | (["class A: pass\n", "def b():\n return 42\n"], []), |
|
69 | 69 | (["class A: pass\n"], ['a']), |
|
70 | 70 | ([], ['z'])] |
|
71 | 71 | for symbols, exp in zip(symbols_args, expected): |
|
72 | 72 | assert code.extract_symbols(source, symbols) == exp |
|
73 | 73 | |
|
74 | 74 | |
|
75 | 75 | def test_extract_symbols_raises_exception_with_non_python_code(): |
|
76 | 76 | source = ("=begin A Ruby program :)=end\n" |
|
77 | 77 | "def hello\n" |
|
78 | 78 | "puts 'Hello world'\n" |
|
79 | 79 | "end") |
|
80 | 80 | with pytest.raises(SyntaxError): |
|
81 | 81 | code.extract_symbols(source, "hello") |
|
82 | 82 | |
|
83 | 83 | |
|
84 | 84 | def test_magic_not_found(): |
|
85 | 85 | # magic not found raises UsageError |
|
86 | 86 | with pytest.raises(UsageError): |
|
87 | 87 | _ip.run_line_magic("doesntexist", "") |
|
88 | 88 | |
|
89 | 89 | # ensure result isn't success when a magic isn't found |
|
90 | 90 | result = _ip.run_cell('%doesntexist') |
|
91 | 91 | assert isinstance(result.error_in_exec, UsageError) |
|
92 | 92 | |
|
93 | 93 | |
|
94 | 94 | def test_cell_magic_not_found(): |
|
95 | 95 | # magic not found raises UsageError |
|
96 | 96 | with pytest.raises(UsageError): |
|
97 | 97 | _ip.run_cell_magic('doesntexist', 'line', 'cell') |
|
98 | 98 | |
|
99 | 99 | # ensure result isn't success when a magic isn't found |
|
100 | 100 | result = _ip.run_cell('%%doesntexist') |
|
101 | 101 | assert isinstance(result.error_in_exec, UsageError) |
|
102 | 102 | |
|
103 | 103 | |
|
104 | 104 | def test_magic_error_status(): |
|
105 | 105 | def fail(shell): |
|
106 | 106 | 1/0 |
|
107 | 107 | _ip.register_magic_function(fail) |
|
108 | 108 | result = _ip.run_cell('%fail') |
|
109 | 109 | assert isinstance(result.error_in_exec, ZeroDivisionError) |
|
110 | 110 | |
|
111 | 111 | |
|
112 | 112 | def test_config(): |
|
113 | 113 | """ test that config magic does not raise |
|
114 | 114 | can happen if Configurable init is moved too early into |
|
115 | 115 | Magics.__init__ as then a Config object will be registered as a |
|
116 | 116 | magic. |
|
117 | 117 | """ |
|
118 | 118 | ## should not raise. |
|
119 | 119 | _ip.run_line_magic("config", "") |
|
120 | 120 | |
|
121 | 121 | |
|
122 | 122 | def test_config_available_configs(): |
|
123 | 123 | """ test that config magic prints available configs in unique and |
|
124 | 124 | sorted order. """ |
|
125 | 125 | with capture_output() as captured: |
|
126 | 126 | _ip.run_line_magic("config", "") |
|
127 | 127 | |
|
128 | 128 | stdout = captured.stdout |
|
129 | 129 | config_classes = stdout.strip().split('\n')[1:] |
|
130 | 130 | assert config_classes == sorted(set(config_classes)) |
|
131 | 131 | |
|
132 | 132 | def test_config_print_class(): |
|
133 | 133 | """ test that config with a classname prints the class's options. """ |
|
134 | 134 | with capture_output() as captured: |
|
135 | 135 | _ip.run_line_magic("config", "TerminalInteractiveShell") |
|
136 | 136 | |
|
137 | 137 | stdout = captured.stdout |
|
138 | 138 | assert re.match( |
|
139 | 139 | "TerminalInteractiveShell.* options", stdout.splitlines()[0] |
|
140 | 140 | ), f"{stdout}\n\n1st line of stdout not like 'TerminalInteractiveShell.* options'" |
|
141 | 141 | |
|
142 | 142 | |
|
143 | 143 | def test_rehashx(): |
|
144 | 144 | # clear up everything |
|
145 | 145 | _ip.alias_manager.clear_aliases() |
|
146 | 146 | del _ip.db['syscmdlist'] |
|
147 | 147 | |
|
148 | 148 | _ip.run_line_magic("rehashx", "") |
|
149 | 149 | # Practically ALL ipython development systems will have more than 10 aliases |
|
150 | 150 | |
|
151 | 151 | assert len(_ip.alias_manager.aliases) > 10 |
|
152 | 152 | for name, cmd in _ip.alias_manager.aliases: |
|
153 | 153 | # we must strip dots from alias names |
|
154 | 154 | assert "." not in name |
|
155 | 155 | |
|
156 | 156 | # rehashx must fill up syscmdlist |
|
157 | 157 | scoms = _ip.db['syscmdlist'] |
|
158 | 158 | assert len(scoms) > 10 |
|
159 | 159 | |
|
160 | 160 | |
|
161 | 161 | def test_magic_parse_options(): |
|
162 | 162 | """Test that we don't mangle paths when parsing magic options.""" |
|
163 | 163 | ip = get_ipython() |
|
164 | 164 | path = 'c:\\x' |
|
165 | 165 | m = DummyMagics(ip) |
|
166 | 166 | opts = m.parse_options('-f %s' % path,'f:')[0] |
|
167 | 167 | # argv splitting is os-dependent |
|
168 | 168 | if os.name == 'posix': |
|
169 | 169 | expected = 'c:x' |
|
170 | 170 | else: |
|
171 | 171 | expected = path |
|
172 | 172 | assert opts["f"] == expected |
|
173 | 173 | |
|
174 | 174 | |
|
175 | 175 | def test_magic_parse_long_options(): |
|
176 | 176 | """Magic.parse_options can handle --foo=bar long options""" |
|
177 | 177 | ip = get_ipython() |
|
178 | 178 | m = DummyMagics(ip) |
|
179 | 179 | opts, _ = m.parse_options("--foo --bar=bubble", "a", "foo", "bar=") |
|
180 | 180 | assert "foo" in opts |
|
181 | 181 | assert "bar" in opts |
|
182 | 182 | assert opts["bar"] == "bubble" |
|
183 | 183 | |
|
184 | 184 | |
|
185 | 185 | def doctest_hist_f(): |
|
186 | 186 | """Test %hist -f with temporary filename. |
|
187 | 187 | |
|
188 | 188 | In [9]: import tempfile |
|
189 | 189 | |
|
190 | 190 | In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-') |
|
191 | 191 | |
|
192 | 192 | In [11]: %hist -nl -f $tfile 3 |
|
193 | 193 | |
|
194 | 194 | In [13]: import os; os.unlink(tfile) |
|
195 | 195 | """ |
|
196 | 196 | |
|
197 | 197 | |
|
198 | 198 | def doctest_hist_op(): |
|
199 | 199 | """Test %hist -op |
|
200 | 200 | |
|
201 | 201 | In [1]: class b(float): |
|
202 | 202 | ...: pass |
|
203 | 203 | ...: |
|
204 | 204 | |
|
205 | 205 | In [2]: class s(object): |
|
206 | 206 | ...: def __str__(self): |
|
207 | 207 | ...: return 's' |
|
208 | 208 | ...: |
|
209 | 209 | |
|
210 | 210 | In [3]: |
|
211 | 211 | |
|
212 | 212 | In [4]: class r(b): |
|
213 | 213 | ...: def __repr__(self): |
|
214 | 214 | ...: return 'r' |
|
215 | 215 | ...: |
|
216 | 216 | |
|
217 | 217 | In [5]: class sr(s,r): pass |
|
218 | 218 | ...: |
|
219 | 219 | |
|
220 | 220 | In [6]: |
|
221 | 221 | |
|
222 | 222 | In [7]: bb=b() |
|
223 | 223 | |
|
224 | 224 | In [8]: ss=s() |
|
225 | 225 | |
|
226 | 226 | In [9]: rr=r() |
|
227 | 227 | |
|
228 | 228 | In [10]: ssrr=sr() |
|
229 | 229 | |
|
230 | 230 | In [11]: 4.5 |
|
231 | 231 | Out[11]: 4.5 |
|
232 | 232 | |
|
233 | 233 | In [12]: str(ss) |
|
234 | 234 | Out[12]: 's' |
|
235 | 235 | |
|
236 | 236 | In [13]: |
|
237 | 237 | |
|
238 | 238 | In [14]: %hist -op |
|
239 | 239 | >>> class b: |
|
240 | 240 | ... pass |
|
241 | 241 | ... |
|
242 | 242 | >>> class s(b): |
|
243 | 243 | ... def __str__(self): |
|
244 | 244 | ... return 's' |
|
245 | 245 | ... |
|
246 | 246 | >>> |
|
247 | 247 | >>> class r(b): |
|
248 | 248 | ... def __repr__(self): |
|
249 | 249 | ... return 'r' |
|
250 | 250 | ... |
|
251 | 251 | >>> class sr(s,r): pass |
|
252 | 252 | >>> |
|
253 | 253 | >>> bb=b() |
|
254 | 254 | >>> ss=s() |
|
255 | 255 | >>> rr=r() |
|
256 | 256 | >>> ssrr=sr() |
|
257 | 257 | >>> 4.5 |
|
258 | 258 | 4.5 |
|
259 | 259 | >>> str(ss) |
|
260 | 260 | 's' |
|
261 | 261 | >>> |
|
262 | 262 | """ |
|
263 | 263 | |
|
264 | 264 | def test_hist_pof(): |
|
265 | 265 | ip = get_ipython() |
|
266 | 266 | ip.run_cell("1+2", store_history=True) |
|
267 | 267 | #raise Exception(ip.history_manager.session_number) |
|
268 | 268 | #raise Exception(list(ip.history_manager._get_range_session())) |
|
269 | 269 | with TemporaryDirectory() as td: |
|
270 | 270 | tf = os.path.join(td, 'hist.py') |
|
271 | 271 | ip.run_line_magic('history', '-pof %s' % tf) |
|
272 | 272 | assert os.path.isfile(tf) |
|
273 | 273 | |
|
274 | 274 | |
|
275 | 275 | def test_macro(): |
|
276 | 276 | ip = get_ipython() |
|
277 | 277 | ip.history_manager.reset() # Clear any existing history. |
|
278 | 278 | cmds = ["a=1", "def b():\n return a**2", "print(a,b())"] |
|
279 | 279 | for i, cmd in enumerate(cmds, start=1): |
|
280 | 280 | ip.history_manager.store_inputs(i, cmd) |
|
281 | 281 | ip.run_line_magic("macro", "test 1-3") |
|
282 | 282 | assert ip.user_ns["test"].value == "\n".join(cmds) + "\n" |
|
283 | 283 | |
|
284 | 284 | # List macros |
|
285 | 285 | assert "test" in ip.run_line_magic("macro", "") |
|
286 | 286 | |
|
287 | 287 | |
|
288 | 288 | def test_macro_run(): |
|
289 | 289 | """Test that we can run a multi-line macro successfully.""" |
|
290 | 290 | ip = get_ipython() |
|
291 | 291 | ip.history_manager.reset() |
|
292 | 292 | cmds = ["a=10", "a+=1", "print(a)", "%macro test 2-3"] |
|
293 | 293 | for cmd in cmds: |
|
294 | 294 | ip.run_cell(cmd, store_history=True) |
|
295 | 295 | assert ip.user_ns["test"].value == "a+=1\nprint(a)\n" |
|
296 | 296 | with tt.AssertPrints("12"): |
|
297 | 297 | ip.run_cell("test") |
|
298 | 298 | with tt.AssertPrints("13"): |
|
299 | 299 | ip.run_cell("test") |
|
300 | 300 | |
|
301 | 301 | |
|
302 | 302 | def test_magic_magic(): |
|
303 | 303 | """Test %magic""" |
|
304 | 304 | ip = get_ipython() |
|
305 | 305 | with capture_output() as captured: |
|
306 | 306 | ip.run_line_magic("magic", "") |
|
307 | 307 | |
|
308 | 308 | stdout = captured.stdout |
|
309 | 309 | assert "%magic" in stdout |
|
310 | 310 | assert "IPython" in stdout |
|
311 | 311 | assert "Available" in stdout |
|
312 | 312 | |
|
313 | 313 | |
|
314 | 314 | @dec.skipif_not_numpy |
|
315 | 315 | def test_numpy_reset_array_undec(): |
|
316 | 316 | "Test '%reset array' functionality" |
|
317 | 317 | _ip.ex("import numpy as np") |
|
318 | 318 | _ip.ex("a = np.empty(2)") |
|
319 | 319 | assert "a" in _ip.user_ns |
|
320 | 320 | _ip.run_line_magic("reset", "-f array") |
|
321 | 321 | assert "a" not in _ip.user_ns |
|
322 | 322 | |
|
323 | 323 | |
|
324 | 324 | def test_reset_out(): |
|
325 | 325 | "Test '%reset out' magic" |
|
326 | 326 | _ip.run_cell("parrot = 'dead'", store_history=True) |
|
327 | 327 | # test '%reset -f out', make an Out prompt |
|
328 | 328 | _ip.run_cell("parrot", store_history=True) |
|
329 | 329 | assert "dead" in [_ip.user_ns[x] for x in ("_", "__", "___")] |
|
330 | 330 | _ip.run_line_magic("reset", "-f out") |
|
331 | 331 | assert "dead" not in [_ip.user_ns[x] for x in ("_", "__", "___")] |
|
332 | 332 | assert len(_ip.user_ns["Out"]) == 0 |
|
333 | 333 | |
|
334 | 334 | |
|
335 | 335 | def test_reset_in(): |
|
336 | 336 | "Test '%reset in' magic" |
|
337 | 337 | # test '%reset -f in' |
|
338 | 338 | _ip.run_cell("parrot", store_history=True) |
|
339 | 339 | assert "parrot" in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")] |
|
340 | 340 | _ip.run_line_magic("reset", "-f in") |
|
341 | 341 | assert "parrot" not in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")] |
|
342 | 342 | assert len(set(_ip.user_ns["In"])) == 1 |
|
343 | 343 | |
|
344 | 344 | |
|
345 | 345 | def test_reset_dhist(): |
|
346 | 346 | "Test '%reset dhist' magic" |
|
347 | 347 | _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing |
|
348 | 348 | _ip.run_line_magic("cd", os.path.dirname(pytest.__file__)) |
|
349 | 349 | _ip.run_line_magic("cd", "-") |
|
350 | 350 | assert len(_ip.user_ns["_dh"]) > 0 |
|
351 | 351 | _ip.run_line_magic("reset", "-f dhist") |
|
352 | 352 | assert len(_ip.user_ns["_dh"]) == 0 |
|
353 | 353 | _ip.run_cell("_dh = [d for d in tmp]") # restore |
|
354 | 354 | |
|
355 | 355 | |
|
356 | 356 | def test_reset_in_length(): |
|
357 | 357 | "Test that '%reset in' preserves In[] length" |
|
358 | 358 | _ip.run_cell("print 'foo'") |
|
359 | 359 | _ip.run_cell("reset -f in") |
|
360 | 360 | assert len(_ip.user_ns["In"]) == _ip.displayhook.prompt_count + 1 |
|
361 | 361 | |
|
362 | 362 | |
|
363 | 363 | class TestResetErrors(TestCase): |
|
364 | 364 | |
|
365 | 365 | def test_reset_redefine(self): |
|
366 | 366 | |
|
367 | 367 | @magics_class |
|
368 | 368 | class KernelMagics(Magics): |
|
369 | 369 | @line_magic |
|
370 | 370 | def less(self, shell): pass |
|
371 | 371 | |
|
372 | 372 | _ip.register_magics(KernelMagics) |
|
373 | 373 | |
|
374 | 374 | with self.assertLogs() as cm: |
|
375 | 375 | # hack, we want to just capture logs, but assertLogs fails if not |
|
376 | 376 | # logs get produce. |
|
377 | 377 | # so log one things we ignore. |
|
378 | 378 | import logging as log_mod |
|
379 | 379 | log = log_mod.getLogger() |
|
380 | 380 | log.info('Nothing') |
|
381 | 381 | # end hack. |
|
382 | 382 | _ip.run_cell("reset -f") |
|
383 | 383 | |
|
384 | 384 | assert len(cm.output) == 1 |
|
385 | 385 | for out in cm.output: |
|
386 | 386 | assert "Invalid alias" not in out |
|
387 | 387 | |
|
388 | 388 | def test_tb_syntaxerror(): |
|
389 | 389 | """test %tb after a SyntaxError""" |
|
390 | 390 | ip = get_ipython() |
|
391 | 391 | ip.run_cell("for") |
|
392 | 392 | |
|
393 | 393 | # trap and validate stdout |
|
394 | 394 | save_stdout = sys.stdout |
|
395 | 395 | try: |
|
396 | 396 | sys.stdout = StringIO() |
|
397 | 397 | ip.run_cell("%tb") |
|
398 | 398 | out = sys.stdout.getvalue() |
|
399 | 399 | finally: |
|
400 | 400 | sys.stdout = save_stdout |
|
401 | 401 | # trim output, and only check the last line |
|
402 | 402 | last_line = out.rstrip().splitlines()[-1].strip() |
|
403 | 403 | assert last_line == "SyntaxError: invalid syntax" |
|
404 | 404 | |
|
405 | 405 | |
|
406 | 406 | def test_time(): |
|
407 | 407 | ip = get_ipython() |
|
408 | 408 | |
|
409 | 409 | with tt.AssertPrints("Wall time: "): |
|
410 | 410 | ip.run_cell("%time None") |
|
411 | 411 | |
|
412 | 412 | ip.run_cell("def f(kmjy):\n" |
|
413 | 413 | " %time print (2*kmjy)") |
|
414 | 414 | |
|
415 | 415 | with tt.AssertPrints("Wall time: "): |
|
416 | 416 | with tt.AssertPrints("hihi", suppress=False): |
|
417 | 417 | ip.run_cell("f('hi')") |
|
418 | 418 | |
|
419 | ||
|
420 | # ';' at the end of %time prevents instruction value to be printed. | |
|
421 | # This tests fix for #13837. | |
|
422 | def test_time_no_output_with_semicolon(): | |
|
423 | ip = get_ipython() | |
|
424 | ||
|
425 | # Test %time cases | |
|
426 | with tt.AssertPrints(" 123456"): | |
|
427 | with tt.AssertPrints("Wall time: ", suppress=False): | |
|
428 | with tt.AssertPrints("CPU times: ", suppress=False): | |
|
429 | ip.run_cell("%time 123000+456") | |
|
430 | ||
|
431 | with tt.AssertNotPrints(" 123456"): | |
|
432 | with tt.AssertPrints("Wall time: ", suppress=False): | |
|
433 | with tt.AssertPrints("CPU times: ", suppress=False): | |
|
434 | ip.run_cell("%time 123000+456;") | |
|
435 | ||
|
436 | with tt.AssertPrints(" 123456"): | |
|
437 | with tt.AssertPrints("Wall time: ", suppress=False): | |
|
438 | with tt.AssertPrints("CPU times: ", suppress=False): | |
|
439 | ip.run_cell("%time 123000+456 # Comment") | |
|
440 | ||
|
441 | with tt.AssertNotPrints(" 123456"): | |
|
442 | with tt.AssertPrints("Wall time: ", suppress=False): | |
|
443 | with tt.AssertPrints("CPU times: ", suppress=False): | |
|
444 | ip.run_cell("%time 123000+456; # Comment") | |
|
445 | ||
|
446 | with tt.AssertPrints(" 123456"): | |
|
447 | with tt.AssertPrints("Wall time: ", suppress=False): | |
|
448 | with tt.AssertPrints("CPU times: ", suppress=False): | |
|
449 | ip.run_cell("%time 123000+456 # ;Comment") | |
|
450 | ||
|
451 | # Test %%time cases | |
|
452 | with tt.AssertPrints("123456"): | |
|
453 | with tt.AssertPrints("Wall time: ", suppress=False): | |
|
454 | with tt.AssertPrints("CPU times: ", suppress=False): | |
|
455 | ip.run_cell("%%time\n123000+456\n\n\n") | |
|
456 | ||
|
457 | with tt.AssertNotPrints("123456"): | |
|
458 | with tt.AssertPrints("Wall time: ", suppress=False): | |
|
459 | with tt.AssertPrints("CPU times: ", suppress=False): | |
|
460 | ip.run_cell("%%time\n123000+456;\n\n\n") | |
|
461 | ||
|
462 | with tt.AssertPrints("123456"): | |
|
463 | with tt.AssertPrints("Wall time: ", suppress=False): | |
|
464 | with tt.AssertPrints("CPU times: ", suppress=False): | |
|
465 | ip.run_cell("%%time\n123000+456 # Comment\n\n\n") | |
|
466 | ||
|
467 | with tt.AssertNotPrints("123456"): | |
|
468 | with tt.AssertPrints("Wall time: ", suppress=False): | |
|
469 | with tt.AssertPrints("CPU times: ", suppress=False): | |
|
470 | ip.run_cell("%%time\n123000+456; # Comment\n\n\n") | |
|
471 | ||
|
472 | with tt.AssertPrints("123456"): | |
|
473 | with tt.AssertPrints("Wall time: ", suppress=False): | |
|
474 | with tt.AssertPrints("CPU times: ", suppress=False): | |
|
475 | ip.run_cell("%%time\n123000+456 # ;Comment\n\n\n") | |
|
476 | ||
|
477 | ||
|
419 | 478 | def test_time_last_not_expression(): |
|
420 | 479 | ip.run_cell("%%time\n" |
|
421 | 480 | "var_1 = 1\n" |
|
422 | 481 | "var_2 = 2\n") |
|
423 | 482 | assert ip.user_ns['var_1'] == 1 |
|
424 | 483 | del ip.user_ns['var_1'] |
|
425 | 484 | assert ip.user_ns['var_2'] == 2 |
|
426 | 485 | del ip.user_ns['var_2'] |
|
427 | 486 | |
|
428 | 487 | |
|
429 | 488 | @dec.skip_win32 |
|
430 | 489 | def test_time2(): |
|
431 | 490 | ip = get_ipython() |
|
432 | 491 | |
|
433 | 492 | with tt.AssertPrints("CPU times: user "): |
|
434 | 493 | ip.run_cell("%time None") |
|
435 | 494 | |
|
436 | 495 | def test_time3(): |
|
437 | 496 | """Erroneous magic function calls, issue gh-3334""" |
|
438 | 497 | ip = get_ipython() |
|
439 | 498 | ip.user_ns.pop('run', None) |
|
440 | 499 | |
|
441 | 500 | with tt.AssertNotPrints("not found", channel='stderr'): |
|
442 | 501 | ip.run_cell("%%time\n" |
|
443 | 502 | "run = 0\n" |
|
444 | 503 | "run += 1") |
|
445 | 504 | |
|
446 | 505 | def test_multiline_time(): |
|
447 | 506 | """Make sure last statement from time return a value.""" |
|
448 | 507 | ip = get_ipython() |
|
449 | 508 | ip.user_ns.pop('run', None) |
|
450 | 509 | |
|
451 | 510 | ip.run_cell( |
|
452 | 511 | dedent( |
|
453 | 512 | """\ |
|
454 | 513 | %%time |
|
455 | 514 | a = "ho" |
|
456 | 515 | b = "hey" |
|
457 | 516 | a+b |
|
458 | 517 | """ |
|
459 | 518 | ) |
|
460 | 519 | ) |
|
461 | 520 | assert ip.user_ns_hidden["_"] == "hohey" |
|
462 | 521 | |
|
463 | 522 | |
|
464 | 523 | def test_time_local_ns(): |
|
465 | 524 | """ |
|
466 | 525 | Test that local_ns is actually global_ns when running a cell magic |
|
467 | 526 | """ |
|
468 | 527 | ip = get_ipython() |
|
469 | 528 | ip.run_cell("%%time\n" "myvar = 1") |
|
470 | 529 | assert ip.user_ns["myvar"] == 1 |
|
471 | 530 | del ip.user_ns["myvar"] |
|
472 | 531 | |
|
473 | 532 | |
|
474 | 533 | def test_doctest_mode(): |
|
475 | 534 | "Toggle doctest_mode twice, it should be a no-op and run without error" |
|
476 | 535 | _ip.run_line_magic("doctest_mode", "") |
|
477 | 536 | _ip.run_line_magic("doctest_mode", "") |
|
478 | 537 | |
|
479 | 538 | |
|
480 | 539 | def test_parse_options(): |
|
481 | 540 | """Tests for basic options parsing in magics.""" |
|
482 | 541 | # These are only the most minimal of tests, more should be added later. At |
|
483 | 542 | # the very least we check that basic text/unicode calls work OK. |
|
484 | 543 | m = DummyMagics(_ip) |
|
485 | 544 | assert m.parse_options("foo", "")[1] == "foo" |
|
486 | 545 | assert m.parse_options("foo", "")[1] == "foo" |
|
487 | 546 | |
|
488 | 547 | |
|
489 | 548 | def test_parse_options_preserve_non_option_string(): |
|
490 | 549 | """Test to assert preservation of non-option part of magic-block, while parsing magic options.""" |
|
491 | 550 | m = DummyMagics(_ip) |
|
492 | 551 | opts, stmt = m.parse_options( |
|
493 | 552 | " -n1 -r 13 _ = 314 + foo", "n:r:", preserve_non_opts=True |
|
494 | 553 | ) |
|
495 | 554 | assert opts == {"n": "1", "r": "13"} |
|
496 | 555 | assert stmt == "_ = 314 + foo" |
|
497 | 556 | |
|
498 | 557 | |
|
499 | 558 | def test_run_magic_preserve_code_block(): |
|
500 | 559 | """Test to assert preservation of non-option part of magic-block, while running magic.""" |
|
501 | 560 | _ip.user_ns["spaces"] = [] |
|
502 | 561 | _ip.run_line_magic( |
|
503 | 562 | "timeit", "-n1 -r1 spaces.append([s.count(' ') for s in ['document']])" |
|
504 | 563 | ) |
|
505 | 564 | assert _ip.user_ns["spaces"] == [[0]] |
|
506 | 565 | |
|
507 | 566 | |
|
508 | 567 | def test_dirops(): |
|
509 | 568 | """Test various directory handling operations.""" |
|
510 | 569 | # curpath = lambda :os.path.splitdrive(os.getcwd())[1].replace('\\','/') |
|
511 | 570 | curpath = os.getcwd |
|
512 | 571 | startdir = os.getcwd() |
|
513 | 572 | ipdir = os.path.realpath(_ip.ipython_dir) |
|
514 | 573 | try: |
|
515 | 574 | _ip.run_line_magic("cd", '"%s"' % ipdir) |
|
516 | 575 | assert curpath() == ipdir |
|
517 | 576 | _ip.run_line_magic("cd", "-") |
|
518 | 577 | assert curpath() == startdir |
|
519 | 578 | _ip.run_line_magic("pushd", '"%s"' % ipdir) |
|
520 | 579 | assert curpath() == ipdir |
|
521 | 580 | _ip.run_line_magic("popd", "") |
|
522 | 581 | assert curpath() == startdir |
|
523 | 582 | finally: |
|
524 | 583 | os.chdir(startdir) |
|
525 | 584 | |
|
526 | 585 | |
|
527 | 586 | def test_cd_force_quiet(): |
|
528 | 587 | """Test OSMagics.cd_force_quiet option""" |
|
529 | 588 | _ip.config.OSMagics.cd_force_quiet = True |
|
530 | 589 | osmagics = osm.OSMagics(shell=_ip) |
|
531 | 590 | |
|
532 | 591 | startdir = os.getcwd() |
|
533 | 592 | ipdir = os.path.realpath(_ip.ipython_dir) |
|
534 | 593 | |
|
535 | 594 | try: |
|
536 | 595 | with tt.AssertNotPrints(ipdir): |
|
537 | 596 | osmagics.cd('"%s"' % ipdir) |
|
538 | 597 | with tt.AssertNotPrints(startdir): |
|
539 | 598 | osmagics.cd('-') |
|
540 | 599 | finally: |
|
541 | 600 | os.chdir(startdir) |
|
542 | 601 | |
|
543 | 602 | |
|
544 | 603 | def test_xmode(): |
|
545 | 604 | # Calling xmode three times should be a no-op |
|
546 | 605 | xmode = _ip.InteractiveTB.mode |
|
547 | 606 | for i in range(4): |
|
548 | 607 | _ip.run_line_magic("xmode", "") |
|
549 | 608 | assert _ip.InteractiveTB.mode == xmode |
|
550 | 609 | |
|
551 | 610 | def test_reset_hard(): |
|
552 | 611 | monitor = [] |
|
553 | 612 | class A(object): |
|
554 | 613 | def __del__(self): |
|
555 | 614 | monitor.append(1) |
|
556 | 615 | def __repr__(self): |
|
557 | 616 | return "<A instance>" |
|
558 | 617 | |
|
559 | 618 | _ip.user_ns["a"] = A() |
|
560 | 619 | _ip.run_cell("a") |
|
561 | 620 | |
|
562 | 621 | assert monitor == [] |
|
563 | 622 | _ip.run_line_magic("reset", "-f") |
|
564 | 623 | assert monitor == [1] |
|
565 | 624 | |
|
566 | 625 | class TestXdel(tt.TempFileMixin): |
|
567 | 626 | def test_xdel(self): |
|
568 | 627 | """Test that references from %run are cleared by xdel.""" |
|
569 | 628 | src = ("class A(object):\n" |
|
570 | 629 | " monitor = []\n" |
|
571 | 630 | " def __del__(self):\n" |
|
572 | 631 | " self.monitor.append(1)\n" |
|
573 | 632 | "a = A()\n") |
|
574 | 633 | self.mktmp(src) |
|
575 | 634 | # %run creates some hidden references... |
|
576 | 635 | _ip.run_line_magic("run", "%s" % self.fname) |
|
577 | 636 | # ... as does the displayhook. |
|
578 | 637 | _ip.run_cell("a") |
|
579 | 638 | |
|
580 | 639 | monitor = _ip.user_ns["A"].monitor |
|
581 | 640 | assert monitor == [] |
|
582 | 641 | |
|
583 | 642 | _ip.run_line_magic("xdel", "a") |
|
584 | 643 | |
|
585 | 644 | # Check that a's __del__ method has been called. |
|
586 | 645 | gc.collect(0) |
|
587 | 646 | assert monitor == [1] |
|
588 | 647 | |
|
589 | 648 | def doctest_who(): |
|
590 | 649 | """doctest for %who |
|
591 | 650 | |
|
592 | 651 | In [1]: %reset -sf |
|
593 | 652 | |
|
594 | 653 | In [2]: alpha = 123 |
|
595 | 654 | |
|
596 | 655 | In [3]: beta = 'beta' |
|
597 | 656 | |
|
598 | 657 | In [4]: %who int |
|
599 | 658 | alpha |
|
600 | 659 | |
|
601 | 660 | In [5]: %who str |
|
602 | 661 | beta |
|
603 | 662 | |
|
604 | 663 | In [6]: %whos |
|
605 | 664 | Variable Type Data/Info |
|
606 | 665 | ---------------------------- |
|
607 | 666 | alpha int 123 |
|
608 | 667 | beta str beta |
|
609 | 668 | |
|
610 | 669 | In [7]: %who_ls |
|
611 | 670 | Out[7]: ['alpha', 'beta'] |
|
612 | 671 | """ |
|
613 | 672 | |
|
614 | 673 | def test_whos(): |
|
615 | 674 | """Check that whos is protected against objects where repr() fails.""" |
|
616 | 675 | class A(object): |
|
617 | 676 | def __repr__(self): |
|
618 | 677 | raise Exception() |
|
619 | 678 | _ip.user_ns['a'] = A() |
|
620 | 679 | _ip.run_line_magic("whos", "") |
|
621 | 680 | |
|
622 | 681 | def doctest_precision(): |
|
623 | 682 | """doctest for %precision |
|
624 | 683 | |
|
625 | 684 | In [1]: f = get_ipython().display_formatter.formatters['text/plain'] |
|
626 | 685 | |
|
627 | 686 | In [2]: %precision 5 |
|
628 | 687 | Out[2]: '%.5f' |
|
629 | 688 | |
|
630 | 689 | In [3]: f.float_format |
|
631 | 690 | Out[3]: '%.5f' |
|
632 | 691 | |
|
633 | 692 | In [4]: %precision %e |
|
634 | 693 | Out[4]: '%e' |
|
635 | 694 | |
|
636 | 695 | In [5]: f(3.1415927) |
|
637 | 696 | Out[5]: '3.141593e+00' |
|
638 | 697 | """ |
|
639 | 698 | |
|
640 | 699 | def test_debug_magic(): |
|
641 | 700 | """Test debugging a small code with %debug |
|
642 | 701 | |
|
643 | 702 | In [1]: with PdbTestInput(['c']): |
|
644 | 703 | ...: %debug print("a b") #doctest: +ELLIPSIS |
|
645 | 704 | ...: |
|
646 | 705 | ... |
|
647 | 706 | ipdb> c |
|
648 | 707 | a b |
|
649 | 708 | In [2]: |
|
650 | 709 | """ |
|
651 | 710 | |
|
652 | 711 | def test_psearch(): |
|
653 | 712 | with tt.AssertPrints("dict.fromkeys"): |
|
654 | 713 | _ip.run_cell("dict.fr*?") |
|
655 | 714 | with tt.AssertPrints("π.is_integer"): |
|
656 | 715 | _ip.run_cell("π = 3.14;\nπ.is_integ*?") |
|
657 | 716 | |
|
658 | 717 | def test_timeit_shlex(): |
|
659 | 718 | """test shlex issues with timeit (#1109)""" |
|
660 | 719 | _ip.ex("def f(*a,**kw): pass") |
|
661 | 720 | _ip.run_line_magic("timeit", '-n1 "this is a bug".count(" ")') |
|
662 | 721 | _ip.run_line_magic("timeit", '-r1 -n1 f(" ", 1)') |
|
663 | 722 | _ip.run_line_magic("timeit", '-r1 -n1 f(" ", 1, " ", 2, " ")') |
|
664 | 723 | _ip.run_line_magic("timeit", '-r1 -n1 ("a " + "b")') |
|
665 | 724 | _ip.run_line_magic("timeit", '-r1 -n1 f("a " + "b")') |
|
666 | 725 | _ip.run_line_magic("timeit", '-r1 -n1 f("a " + "b ")') |
|
667 | 726 | |
|
668 | 727 | |
|
669 | 728 | def test_timeit_special_syntax(): |
|
670 | 729 | "Test %%timeit with IPython special syntax" |
|
671 | 730 | @register_line_magic |
|
672 | 731 | def lmagic(line): |
|
673 | 732 | ip = get_ipython() |
|
674 | 733 | ip.user_ns['lmagic_out'] = line |
|
675 | 734 | |
|
676 | 735 | # line mode test |
|
677 | 736 | _ip.run_line_magic("timeit", "-n1 -r1 %lmagic my line") |
|
678 | 737 | assert _ip.user_ns["lmagic_out"] == "my line" |
|
679 | 738 | # cell mode test |
|
680 | 739 | _ip.run_cell_magic("timeit", "-n1 -r1", "%lmagic my line2") |
|
681 | 740 | assert _ip.user_ns["lmagic_out"] == "my line2" |
|
682 | 741 | |
|
683 | 742 | |
|
684 | 743 | def test_timeit_return(): |
|
685 | 744 | """ |
|
686 | 745 | test whether timeit -o return object |
|
687 | 746 | """ |
|
688 | 747 | |
|
689 | 748 | res = _ip.run_line_magic('timeit','-n10 -r10 -o 1') |
|
690 | 749 | assert(res is not None) |
|
691 | 750 | |
|
692 | 751 | def test_timeit_quiet(): |
|
693 | 752 | """ |
|
694 | 753 | test quiet option of timeit magic |
|
695 | 754 | """ |
|
696 | 755 | with tt.AssertNotPrints("loops"): |
|
697 | 756 | _ip.run_cell("%timeit -n1 -r1 -q 1") |
|
698 | 757 | |
|
699 | 758 | def test_timeit_return_quiet(): |
|
700 | 759 | with tt.AssertNotPrints("loops"): |
|
701 | 760 | res = _ip.run_line_magic('timeit', '-n1 -r1 -q -o 1') |
|
702 | 761 | assert (res is not None) |
|
703 | 762 | |
|
704 | 763 | def test_timeit_invalid_return(): |
|
705 | 764 | with pytest.raises(SyntaxError): |
|
706 | 765 | _ip.run_line_magic('timeit', 'return') |
|
707 | 766 | |
|
708 | 767 | @dec.skipif(execution.profile is None) |
|
709 | 768 | def test_prun_special_syntax(): |
|
710 | 769 | "Test %%prun with IPython special syntax" |
|
711 | 770 | @register_line_magic |
|
712 | 771 | def lmagic(line): |
|
713 | 772 | ip = get_ipython() |
|
714 | 773 | ip.user_ns['lmagic_out'] = line |
|
715 | 774 | |
|
716 | 775 | # line mode test |
|
717 | 776 | _ip.run_line_magic("prun", "-q %lmagic my line") |
|
718 | 777 | assert _ip.user_ns["lmagic_out"] == "my line" |
|
719 | 778 | # cell mode test |
|
720 | 779 | _ip.run_cell_magic("prun", "-q", "%lmagic my line2") |
|
721 | 780 | assert _ip.user_ns["lmagic_out"] == "my line2" |
|
722 | 781 | |
|
723 | 782 | |
|
724 | 783 | @dec.skipif(execution.profile is None) |
|
725 | 784 | def test_prun_quotes(): |
|
726 | 785 | "Test that prun does not clobber string escapes (GH #1302)" |
|
727 | 786 | _ip.magic(r"prun -q x = '\t'") |
|
728 | 787 | assert _ip.user_ns["x"] == "\t" |
|
729 | 788 | |
|
730 | 789 | |
|
731 | 790 | def test_extension(): |
|
732 | 791 | # Debugging information for failures of this test |
|
733 | 792 | print('sys.path:') |
|
734 | 793 | for p in sys.path: |
|
735 | 794 | print(' ', p) |
|
736 | 795 | print('CWD', os.getcwd()) |
|
737 | 796 | |
|
738 | 797 | pytest.raises(ImportError, _ip.magic, "load_ext daft_extension") |
|
739 | 798 | daft_path = os.path.join(os.path.dirname(__file__), "daft_extension") |
|
740 | 799 | sys.path.insert(0, daft_path) |
|
741 | 800 | try: |
|
742 | 801 | _ip.user_ns.pop('arq', None) |
|
743 | 802 | invalidate_caches() # Clear import caches |
|
744 | 803 | _ip.run_line_magic("load_ext", "daft_extension") |
|
745 | 804 | assert _ip.user_ns["arq"] == 185 |
|
746 | 805 | _ip.run_line_magic("unload_ext", "daft_extension") |
|
747 | 806 | assert 'arq' not in _ip.user_ns |
|
748 | 807 | finally: |
|
749 | 808 | sys.path.remove(daft_path) |
|
750 | 809 | |
|
751 | 810 | |
|
752 | 811 | def test_notebook_export_json(): |
|
753 | 812 | pytest.importorskip("nbformat") |
|
754 | 813 | _ip = get_ipython() |
|
755 | 814 | _ip.history_manager.reset() # Clear any existing history. |
|
756 | 815 | cmds = ["a=1", "def b():\n return a**2", "print('noël, été', b())"] |
|
757 | 816 | for i, cmd in enumerate(cmds, start=1): |
|
758 | 817 | _ip.history_manager.store_inputs(i, cmd) |
|
759 | 818 | with TemporaryDirectory() as td: |
|
760 | 819 | outfile = os.path.join(td, "nb.ipynb") |
|
761 | 820 | _ip.run_line_magic("notebook", "%s" % outfile) |
|
762 | 821 | |
|
763 | 822 | |
|
764 | 823 | class TestEnv(TestCase): |
|
765 | 824 | |
|
766 | 825 | def test_env(self): |
|
767 | 826 | env = _ip.run_line_magic("env", "") |
|
768 | 827 | self.assertTrue(isinstance(env, dict)) |
|
769 | 828 | |
|
770 | 829 | def test_env_secret(self): |
|
771 | 830 | env = _ip.run_line_magic("env", "") |
|
772 | 831 | hidden = "<hidden>" |
|
773 | 832 | with mock.patch.dict( |
|
774 | 833 | os.environ, |
|
775 | 834 | { |
|
776 | 835 | "API_KEY": "abc123", |
|
777 | 836 | "SECRET_THING": "ssshhh", |
|
778 | 837 | "JUPYTER_TOKEN": "", |
|
779 | 838 | "VAR": "abc" |
|
780 | 839 | } |
|
781 | 840 | ): |
|
782 | 841 | env = _ip.run_line_magic("env", "") |
|
783 | 842 | assert env["API_KEY"] == hidden |
|
784 | 843 | assert env["SECRET_THING"] == hidden |
|
785 | 844 | assert env["JUPYTER_TOKEN"] == hidden |
|
786 | 845 | assert env["VAR"] == "abc" |
|
787 | 846 | |
|
788 | 847 | def test_env_get_set_simple(self): |
|
789 | 848 | env = _ip.run_line_magic("env", "var val1") |
|
790 | 849 | self.assertEqual(env, None) |
|
791 | 850 | self.assertEqual(os.environ["var"], "val1") |
|
792 | 851 | self.assertEqual(_ip.run_line_magic("env", "var"), "val1") |
|
793 | 852 | env = _ip.run_line_magic("env", "var=val2") |
|
794 | 853 | self.assertEqual(env, None) |
|
795 | 854 | self.assertEqual(os.environ['var'], 'val2') |
|
796 | 855 | |
|
797 | 856 | def test_env_get_set_complex(self): |
|
798 | 857 | env = _ip.run_line_magic("env", "var 'val1 '' 'val2") |
|
799 | 858 | self.assertEqual(env, None) |
|
800 | 859 | self.assertEqual(os.environ['var'], "'val1 '' 'val2") |
|
801 | 860 | self.assertEqual(_ip.run_line_magic("env", "var"), "'val1 '' 'val2") |
|
802 | 861 | env = _ip.run_line_magic("env", 'var=val2 val3="val4') |
|
803 | 862 | self.assertEqual(env, None) |
|
804 | 863 | self.assertEqual(os.environ['var'], 'val2 val3="val4') |
|
805 | 864 | |
|
806 | 865 | def test_env_set_bad_input(self): |
|
807 | 866 | self.assertRaises(UsageError, lambda: _ip.run_line_magic("set_env", "var")) |
|
808 | 867 | |
|
809 | 868 | def test_env_set_whitespace(self): |
|
810 | 869 | self.assertRaises(UsageError, lambda: _ip.run_line_magic("env", "var A=B")) |
|
811 | 870 | |
|
812 | 871 | |
|
813 | 872 | class CellMagicTestCase(TestCase): |
|
814 | 873 | |
|
815 | 874 | def check_ident(self, magic): |
|
816 | 875 | # Manually called, we get the result |
|
817 | 876 | out = _ip.run_cell_magic(magic, "a", "b") |
|
818 | 877 | assert out == ("a", "b") |
|
819 | 878 | # Via run_cell, it goes into the user's namespace via displayhook |
|
820 | 879 | _ip.run_cell("%%" + magic + " c\nd\n") |
|
821 | 880 | assert _ip.user_ns["_"] == ("c", "d\n") |
|
822 | 881 | |
|
823 | 882 | def test_cell_magic_func_deco(self): |
|
824 | 883 | "Cell magic using simple decorator" |
|
825 | 884 | @register_cell_magic |
|
826 | 885 | def cellm(line, cell): |
|
827 | 886 | return line, cell |
|
828 | 887 | |
|
829 | 888 | self.check_ident('cellm') |
|
830 | 889 | |
|
831 | 890 | def test_cell_magic_reg(self): |
|
832 | 891 | "Cell magic manually registered" |
|
833 | 892 | def cellm(line, cell): |
|
834 | 893 | return line, cell |
|
835 | 894 | |
|
836 | 895 | _ip.register_magic_function(cellm, 'cell', 'cellm2') |
|
837 | 896 | self.check_ident('cellm2') |
|
838 | 897 | |
|
839 | 898 | def test_cell_magic_class(self): |
|
840 | 899 | "Cell magics declared via a class" |
|
841 | 900 | @magics_class |
|
842 | 901 | class MyMagics(Magics): |
|
843 | 902 | |
|
844 | 903 | @cell_magic |
|
845 | 904 | def cellm3(self, line, cell): |
|
846 | 905 | return line, cell |
|
847 | 906 | |
|
848 | 907 | _ip.register_magics(MyMagics) |
|
849 | 908 | self.check_ident('cellm3') |
|
850 | 909 | |
|
851 | 910 | def test_cell_magic_class2(self): |
|
852 | 911 | "Cell magics declared via a class, #2" |
|
853 | 912 | @magics_class |
|
854 | 913 | class MyMagics2(Magics): |
|
855 | 914 | |
|
856 | 915 | @cell_magic('cellm4') |
|
857 | 916 | def cellm33(self, line, cell): |
|
858 | 917 | return line, cell |
|
859 | 918 | |
|
860 | 919 | _ip.register_magics(MyMagics2) |
|
861 | 920 | self.check_ident('cellm4') |
|
862 | 921 | # Check that nothing is registered as 'cellm33' |
|
863 | 922 | c33 = _ip.find_cell_magic('cellm33') |
|
864 | 923 | assert c33 == None |
|
865 | 924 | |
|
866 | 925 | def test_file(): |
|
867 | 926 | """Basic %%writefile""" |
|
868 | 927 | ip = get_ipython() |
|
869 | 928 | with TemporaryDirectory() as td: |
|
870 | 929 | fname = os.path.join(td, "file1") |
|
871 | 930 | ip.run_cell_magic( |
|
872 | 931 | "writefile", |
|
873 | 932 | fname, |
|
874 | 933 | "\n".join( |
|
875 | 934 | [ |
|
876 | 935 | "line1", |
|
877 | 936 | "line2", |
|
878 | 937 | ] |
|
879 | 938 | ), |
|
880 | 939 | ) |
|
881 | 940 | s = Path(fname).read_text(encoding="utf-8") |
|
882 | 941 | assert "line1\n" in s |
|
883 | 942 | assert "line2" in s |
|
884 | 943 | |
|
885 | 944 | |
|
886 | 945 | @dec.skip_win32 |
|
887 | 946 | def test_file_single_quote(): |
|
888 | 947 | """Basic %%writefile with embedded single quotes""" |
|
889 | 948 | ip = get_ipython() |
|
890 | 949 | with TemporaryDirectory() as td: |
|
891 | 950 | fname = os.path.join(td, "'file1'") |
|
892 | 951 | ip.run_cell_magic( |
|
893 | 952 | "writefile", |
|
894 | 953 | fname, |
|
895 | 954 | "\n".join( |
|
896 | 955 | [ |
|
897 | 956 | "line1", |
|
898 | 957 | "line2", |
|
899 | 958 | ] |
|
900 | 959 | ), |
|
901 | 960 | ) |
|
902 | 961 | s = Path(fname).read_text(encoding="utf-8") |
|
903 | 962 | assert "line1\n" in s |
|
904 | 963 | assert "line2" in s |
|
905 | 964 | |
|
906 | 965 | |
|
907 | 966 | @dec.skip_win32 |
|
908 | 967 | def test_file_double_quote(): |
|
909 | 968 | """Basic %%writefile with embedded double quotes""" |
|
910 | 969 | ip = get_ipython() |
|
911 | 970 | with TemporaryDirectory() as td: |
|
912 | 971 | fname = os.path.join(td, '"file1"') |
|
913 | 972 | ip.run_cell_magic( |
|
914 | 973 | "writefile", |
|
915 | 974 | fname, |
|
916 | 975 | "\n".join( |
|
917 | 976 | [ |
|
918 | 977 | "line1", |
|
919 | 978 | "line2", |
|
920 | 979 | ] |
|
921 | 980 | ), |
|
922 | 981 | ) |
|
923 | 982 | s = Path(fname).read_text(encoding="utf-8") |
|
924 | 983 | assert "line1\n" in s |
|
925 | 984 | assert "line2" in s |
|
926 | 985 | |
|
927 | 986 | |
|
928 | 987 | def test_file_var_expand(): |
|
929 | 988 | """%%writefile $filename""" |
|
930 | 989 | ip = get_ipython() |
|
931 | 990 | with TemporaryDirectory() as td: |
|
932 | 991 | fname = os.path.join(td, "file1") |
|
933 | 992 | ip.user_ns["filename"] = fname |
|
934 | 993 | ip.run_cell_magic( |
|
935 | 994 | "writefile", |
|
936 | 995 | "$filename", |
|
937 | 996 | "\n".join( |
|
938 | 997 | [ |
|
939 | 998 | "line1", |
|
940 | 999 | "line2", |
|
941 | 1000 | ] |
|
942 | 1001 | ), |
|
943 | 1002 | ) |
|
944 | 1003 | s = Path(fname).read_text(encoding="utf-8") |
|
945 | 1004 | assert "line1\n" in s |
|
946 | 1005 | assert "line2" in s |
|
947 | 1006 | |
|
948 | 1007 | |
|
949 | 1008 | def test_file_unicode(): |
|
950 | 1009 | """%%writefile with unicode cell""" |
|
951 | 1010 | ip = get_ipython() |
|
952 | 1011 | with TemporaryDirectory() as td: |
|
953 | 1012 | fname = os.path.join(td, 'file1') |
|
954 | 1013 | ip.run_cell_magic("writefile", fname, u'\n'.join([ |
|
955 | 1014 | u'liné1', |
|
956 | 1015 | u'liné2', |
|
957 | 1016 | ])) |
|
958 | 1017 | with io.open(fname, encoding='utf-8') as f: |
|
959 | 1018 | s = f.read() |
|
960 | 1019 | assert "liné1\n" in s |
|
961 | 1020 | assert "liné2" in s |
|
962 | 1021 | |
|
963 | 1022 | |
|
964 | 1023 | def test_file_amend(): |
|
965 | 1024 | """%%writefile -a amends files""" |
|
966 | 1025 | ip = get_ipython() |
|
967 | 1026 | with TemporaryDirectory() as td: |
|
968 | 1027 | fname = os.path.join(td, "file2") |
|
969 | 1028 | ip.run_cell_magic( |
|
970 | 1029 | "writefile", |
|
971 | 1030 | fname, |
|
972 | 1031 | "\n".join( |
|
973 | 1032 | [ |
|
974 | 1033 | "line1", |
|
975 | 1034 | "line2", |
|
976 | 1035 | ] |
|
977 | 1036 | ), |
|
978 | 1037 | ) |
|
979 | 1038 | ip.run_cell_magic( |
|
980 | 1039 | "writefile", |
|
981 | 1040 | "-a %s" % fname, |
|
982 | 1041 | "\n".join( |
|
983 | 1042 | [ |
|
984 | 1043 | "line3", |
|
985 | 1044 | "line4", |
|
986 | 1045 | ] |
|
987 | 1046 | ), |
|
988 | 1047 | ) |
|
989 | 1048 | s = Path(fname).read_text(encoding="utf-8") |
|
990 | 1049 | assert "line1\n" in s |
|
991 | 1050 | assert "line3\n" in s |
|
992 | 1051 | |
|
993 | 1052 | |
|
994 | 1053 | def test_file_spaces(): |
|
995 | 1054 | """%%file with spaces in filename""" |
|
996 | 1055 | ip = get_ipython() |
|
997 | 1056 | with TemporaryWorkingDirectory() as td: |
|
998 | 1057 | fname = "file name" |
|
999 | 1058 | ip.run_cell_magic( |
|
1000 | 1059 | "file", |
|
1001 | 1060 | '"%s"' % fname, |
|
1002 | 1061 | "\n".join( |
|
1003 | 1062 | [ |
|
1004 | 1063 | "line1", |
|
1005 | 1064 | "line2", |
|
1006 | 1065 | ] |
|
1007 | 1066 | ), |
|
1008 | 1067 | ) |
|
1009 | 1068 | s = Path(fname).read_text(encoding="utf-8") |
|
1010 | 1069 | assert "line1\n" in s |
|
1011 | 1070 | assert "line2" in s |
|
1012 | 1071 | |
|
1013 | 1072 | |
|
1014 | 1073 | def test_script_config(): |
|
1015 | 1074 | ip = get_ipython() |
|
1016 | 1075 | ip.config.ScriptMagics.script_magics = ['whoda'] |
|
1017 | 1076 | sm = script.ScriptMagics(shell=ip) |
|
1018 | 1077 | assert "whoda" in sm.magics["cell"] |
|
1019 | 1078 | |
|
1020 | 1079 | |
|
1021 | 1080 | def test_script_out(): |
|
1022 | 1081 | ip = get_ipython() |
|
1023 | 1082 | ip.run_cell_magic("script", f"--out output {sys.executable}", "print('hi')") |
|
1024 | 1083 | assert ip.user_ns["output"].strip() == "hi" |
|
1025 | 1084 | |
|
1026 | 1085 | |
|
1027 | 1086 | def test_script_err(): |
|
1028 | 1087 | ip = get_ipython() |
|
1029 | 1088 | ip.run_cell_magic( |
|
1030 | 1089 | "script", |
|
1031 | 1090 | f"--err error {sys.executable}", |
|
1032 | 1091 | "import sys; print('hello', file=sys.stderr)", |
|
1033 | 1092 | ) |
|
1034 | 1093 | assert ip.user_ns["error"].strip() == "hello" |
|
1035 | 1094 | |
|
1036 | 1095 | |
|
1037 | 1096 | def test_script_out_err(): |
|
1038 | 1097 | |
|
1039 | 1098 | ip = get_ipython() |
|
1040 | 1099 | ip.run_cell_magic( |
|
1041 | 1100 | "script", |
|
1042 | 1101 | f"--out output --err error {sys.executable}", |
|
1043 | 1102 | "\n".join( |
|
1044 | 1103 | [ |
|
1045 | 1104 | "import sys", |
|
1046 | 1105 | "print('hi')", |
|
1047 | 1106 | "print('hello', file=sys.stderr)", |
|
1048 | 1107 | ] |
|
1049 | 1108 | ), |
|
1050 | 1109 | ) |
|
1051 | 1110 | assert ip.user_ns["output"].strip() == "hi" |
|
1052 | 1111 | assert ip.user_ns["error"].strip() == "hello" |
|
1053 | 1112 | |
|
1054 | 1113 | |
|
1055 | 1114 | async def test_script_bg_out(): |
|
1056 | 1115 | ip = get_ipython() |
|
1057 | 1116 | ip.run_cell_magic("script", f"--bg --out output {sys.executable}", "print('hi')") |
|
1058 | 1117 | assert (await ip.user_ns["output"].read()).strip() == b"hi" |
|
1059 | 1118 | assert ip.user_ns["output"].at_eof() |
|
1060 | 1119 | |
|
1061 | 1120 | |
|
1062 | 1121 | async def test_script_bg_err(): |
|
1063 | 1122 | ip = get_ipython() |
|
1064 | 1123 | ip.run_cell_magic( |
|
1065 | 1124 | "script", |
|
1066 | 1125 | f"--bg --err error {sys.executable}", |
|
1067 | 1126 | "import sys; print('hello', file=sys.stderr)", |
|
1068 | 1127 | ) |
|
1069 | 1128 | assert (await ip.user_ns["error"].read()).strip() == b"hello" |
|
1070 | 1129 | assert ip.user_ns["error"].at_eof() |
|
1071 | 1130 | |
|
1072 | 1131 | |
|
1073 | 1132 | async def test_script_bg_out_err(): |
|
1074 | 1133 | ip = get_ipython() |
|
1075 | 1134 | ip.run_cell_magic( |
|
1076 | 1135 | "script", |
|
1077 | 1136 | f"--bg --out output --err error {sys.executable}", |
|
1078 | 1137 | "\n".join( |
|
1079 | 1138 | [ |
|
1080 | 1139 | "import sys", |
|
1081 | 1140 | "print('hi')", |
|
1082 | 1141 | "print('hello', file=sys.stderr)", |
|
1083 | 1142 | ] |
|
1084 | 1143 | ), |
|
1085 | 1144 | ) |
|
1086 | 1145 | assert (await ip.user_ns["output"].read()).strip() == b"hi" |
|
1087 | 1146 | assert (await ip.user_ns["error"].read()).strip() == b"hello" |
|
1088 | 1147 | assert ip.user_ns["output"].at_eof() |
|
1089 | 1148 | assert ip.user_ns["error"].at_eof() |
|
1090 | 1149 | |
|
1091 | 1150 | |
|
1092 | 1151 | async def test_script_bg_proc(): |
|
1093 | 1152 | ip = get_ipython() |
|
1094 | 1153 | ip.run_cell_magic( |
|
1095 | 1154 | "script", |
|
1096 | 1155 | f"--bg --out output --proc p {sys.executable}", |
|
1097 | 1156 | "\n".join( |
|
1098 | 1157 | [ |
|
1099 | 1158 | "import sys", |
|
1100 | 1159 | "print('hi')", |
|
1101 | 1160 | "print('hello', file=sys.stderr)", |
|
1102 | 1161 | ] |
|
1103 | 1162 | ), |
|
1104 | 1163 | ) |
|
1105 | 1164 | p = ip.user_ns["p"] |
|
1106 | 1165 | await p.wait() |
|
1107 | 1166 | assert p.returncode == 0 |
|
1108 | 1167 | assert (await p.stdout.read()).strip() == b"hi" |
|
1109 | 1168 | # not captured, so empty |
|
1110 | 1169 | assert (await p.stderr.read()) == b"" |
|
1111 | 1170 | assert p.stdout.at_eof() |
|
1112 | 1171 | assert p.stderr.at_eof() |
|
1113 | 1172 | |
|
1114 | 1173 | |
|
1115 | 1174 | def test_script_defaults(): |
|
1116 | 1175 | ip = get_ipython() |
|
1117 | 1176 | for cmd in ['sh', 'bash', 'perl', 'ruby']: |
|
1118 | 1177 | try: |
|
1119 | 1178 | find_cmd(cmd) |
|
1120 | 1179 | except Exception: |
|
1121 | 1180 | pass |
|
1122 | 1181 | else: |
|
1123 | 1182 | assert cmd in ip.magics_manager.magics["cell"] |
|
1124 | 1183 | |
|
1125 | 1184 | |
|
1126 | 1185 | @magics_class |
|
1127 | 1186 | class FooFoo(Magics): |
|
1128 | 1187 | """class with both %foo and %%foo magics""" |
|
1129 | 1188 | @line_magic('foo') |
|
1130 | 1189 | def line_foo(self, line): |
|
1131 | 1190 | "I am line foo" |
|
1132 | 1191 | pass |
|
1133 | 1192 | |
|
1134 | 1193 | @cell_magic("foo") |
|
1135 | 1194 | def cell_foo(self, line, cell): |
|
1136 | 1195 | "I am cell foo, not line foo" |
|
1137 | 1196 | pass |
|
1138 | 1197 | |
|
1139 | 1198 | def test_line_cell_info(): |
|
1140 | 1199 | """%%foo and %foo magics are distinguishable to inspect""" |
|
1141 | 1200 | ip = get_ipython() |
|
1142 | 1201 | ip.magics_manager.register(FooFoo) |
|
1143 | 1202 | oinfo = ip.object_inspect("foo") |
|
1144 | 1203 | assert oinfo["found"] is True |
|
1145 | 1204 | assert oinfo["ismagic"] is True |
|
1146 | 1205 | |
|
1147 | 1206 | oinfo = ip.object_inspect("%%foo") |
|
1148 | 1207 | assert oinfo["found"] is True |
|
1149 | 1208 | assert oinfo["ismagic"] is True |
|
1150 | 1209 | assert oinfo["docstring"] == FooFoo.cell_foo.__doc__ |
|
1151 | 1210 | |
|
1152 | 1211 | oinfo = ip.object_inspect("%foo") |
|
1153 | 1212 | assert oinfo["found"] is True |
|
1154 | 1213 | assert oinfo["ismagic"] is True |
|
1155 | 1214 | assert oinfo["docstring"] == FooFoo.line_foo.__doc__ |
|
1156 | 1215 | |
|
1157 | 1216 | |
|
1158 | 1217 | def test_multiple_magics(): |
|
1159 | 1218 | ip = get_ipython() |
|
1160 | 1219 | foo1 = FooFoo(ip) |
|
1161 | 1220 | foo2 = FooFoo(ip) |
|
1162 | 1221 | mm = ip.magics_manager |
|
1163 | 1222 | mm.register(foo1) |
|
1164 | 1223 | assert mm.magics["line"]["foo"].__self__ is foo1 |
|
1165 | 1224 | mm.register(foo2) |
|
1166 | 1225 | assert mm.magics["line"]["foo"].__self__ is foo2 |
|
1167 | 1226 | |
|
1168 | 1227 | |
|
1169 | 1228 | def test_alias_magic(): |
|
1170 | 1229 | """Test %alias_magic.""" |
|
1171 | 1230 | ip = get_ipython() |
|
1172 | 1231 | mm = ip.magics_manager |
|
1173 | 1232 | |
|
1174 | 1233 | # Basic operation: both cell and line magics are created, if possible. |
|
1175 | 1234 | ip.run_line_magic("alias_magic", "timeit_alias timeit") |
|
1176 | 1235 | assert "timeit_alias" in mm.magics["line"] |
|
1177 | 1236 | assert "timeit_alias" in mm.magics["cell"] |
|
1178 | 1237 | |
|
1179 | 1238 | # --cell is specified, line magic not created. |
|
1180 | 1239 | ip.run_line_magic("alias_magic", "--cell timeit_cell_alias timeit") |
|
1181 | 1240 | assert "timeit_cell_alias" not in mm.magics["line"] |
|
1182 | 1241 | assert "timeit_cell_alias" in mm.magics["cell"] |
|
1183 | 1242 | |
|
1184 | 1243 | # Test that line alias is created successfully. |
|
1185 | 1244 | ip.run_line_magic("alias_magic", "--line env_alias env") |
|
1186 | 1245 | assert ip.run_line_magic("env", "") == ip.run_line_magic("env_alias", "") |
|
1187 | 1246 | |
|
1188 | 1247 | # Test that line alias with parameters passed in is created successfully. |
|
1189 | 1248 | ip.run_line_magic( |
|
1190 | 1249 | "alias_magic", "--line history_alias history --params " + shlex.quote("3") |
|
1191 | 1250 | ) |
|
1192 | 1251 | assert "history_alias" in mm.magics["line"] |
|
1193 | 1252 | |
|
1194 | 1253 | |
|
1195 | 1254 | def test_save(): |
|
1196 | 1255 | """Test %save.""" |
|
1197 | 1256 | ip = get_ipython() |
|
1198 | 1257 | ip.history_manager.reset() # Clear any existing history. |
|
1199 | 1258 | cmds = ["a=1", "def b():\n return a**2", "print(a, b())"] |
|
1200 | 1259 | for i, cmd in enumerate(cmds, start=1): |
|
1201 | 1260 | ip.history_manager.store_inputs(i, cmd) |
|
1202 | 1261 | with TemporaryDirectory() as tmpdir: |
|
1203 | 1262 | file = os.path.join(tmpdir, "testsave.py") |
|
1204 | 1263 | ip.run_line_magic("save", "%s 1-10" % file) |
|
1205 | 1264 | content = Path(file).read_text(encoding="utf-8") |
|
1206 | 1265 | assert content.count(cmds[0]) == 1 |
|
1207 | 1266 | assert "coding: utf-8" in content |
|
1208 | 1267 | ip.run_line_magic("save", "-a %s 1-10" % file) |
|
1209 | 1268 | content = Path(file).read_text(encoding="utf-8") |
|
1210 | 1269 | assert content.count(cmds[0]) == 2 |
|
1211 | 1270 | assert "coding: utf-8" in content |
|
1212 | 1271 | |
|
1213 | 1272 | |
|
1214 | 1273 | def test_save_with_no_args(): |
|
1215 | 1274 | ip = get_ipython() |
|
1216 | 1275 | ip.history_manager.reset() # Clear any existing history. |
|
1217 | 1276 | cmds = ["a=1", "def b():\n return a**2", "print(a, b())", "%save"] |
|
1218 | 1277 | for i, cmd in enumerate(cmds, start=1): |
|
1219 | 1278 | ip.history_manager.store_inputs(i, cmd) |
|
1220 | 1279 | |
|
1221 | 1280 | with TemporaryDirectory() as tmpdir: |
|
1222 | 1281 | path = os.path.join(tmpdir, "testsave.py") |
|
1223 | 1282 | ip.run_line_magic("save", path) |
|
1224 | 1283 | content = Path(path).read_text(encoding="utf-8") |
|
1225 | 1284 | expected_content = dedent( |
|
1226 | 1285 | """\ |
|
1227 | 1286 | # coding: utf-8 |
|
1228 | 1287 | a=1 |
|
1229 | 1288 | def b(): |
|
1230 | 1289 | return a**2 |
|
1231 | 1290 | print(a, b()) |
|
1232 | 1291 | """ |
|
1233 | 1292 | ) |
|
1234 | 1293 | assert content == expected_content |
|
1235 | 1294 | |
|
1236 | 1295 | |
|
1237 | 1296 | def test_store(): |
|
1238 | 1297 | """Test %store.""" |
|
1239 | 1298 | ip = get_ipython() |
|
1240 | 1299 | ip.run_line_magic('load_ext', 'storemagic') |
|
1241 | 1300 | |
|
1242 | 1301 | # make sure the storage is empty |
|
1243 | 1302 | ip.run_line_magic("store", "-z") |
|
1244 | 1303 | ip.user_ns["var"] = 42 |
|
1245 | 1304 | ip.run_line_magic("store", "var") |
|
1246 | 1305 | ip.user_ns["var"] = 39 |
|
1247 | 1306 | ip.run_line_magic("store", "-r") |
|
1248 | 1307 | assert ip.user_ns["var"] == 42 |
|
1249 | 1308 | |
|
1250 | 1309 | ip.run_line_magic("store", "-d var") |
|
1251 | 1310 | ip.user_ns["var"] = 39 |
|
1252 | 1311 | ip.run_line_magic("store", "-r") |
|
1253 | 1312 | assert ip.user_ns["var"] == 39 |
|
1254 | 1313 | |
|
1255 | 1314 | |
|
1256 | 1315 | def _run_edit_test(arg_s, exp_filename=None, |
|
1257 | 1316 | exp_lineno=-1, |
|
1258 | 1317 | exp_contents=None, |
|
1259 | 1318 | exp_is_temp=None): |
|
1260 | 1319 | ip = get_ipython() |
|
1261 | 1320 | M = code.CodeMagics(ip) |
|
1262 | 1321 | last_call = ['',''] |
|
1263 | 1322 | opts,args = M.parse_options(arg_s,'prxn:') |
|
1264 | 1323 | filename, lineno, is_temp = M._find_edit_target(ip, args, opts, last_call) |
|
1265 | 1324 | |
|
1266 | 1325 | if exp_filename is not None: |
|
1267 | 1326 | assert exp_filename == filename |
|
1268 | 1327 | if exp_contents is not None: |
|
1269 | 1328 | with io.open(filename, 'r', encoding='utf-8') as f: |
|
1270 | 1329 | contents = f.read() |
|
1271 | 1330 | assert exp_contents == contents |
|
1272 | 1331 | if exp_lineno != -1: |
|
1273 | 1332 | assert exp_lineno == lineno |
|
1274 | 1333 | if exp_is_temp is not None: |
|
1275 | 1334 | assert exp_is_temp == is_temp |
|
1276 | 1335 | |
|
1277 | 1336 | |
|
1278 | 1337 | def test_edit_interactive(): |
|
1279 | 1338 | """%edit on interactively defined objects""" |
|
1280 | 1339 | ip = get_ipython() |
|
1281 | 1340 | n = ip.execution_count |
|
1282 | 1341 | ip.run_cell("def foo(): return 1", store_history=True) |
|
1283 | 1342 | |
|
1284 | 1343 | with pytest.raises(code.InteractivelyDefined) as e: |
|
1285 | 1344 | _run_edit_test("foo") |
|
1286 | 1345 | assert e.value.index == n |
|
1287 | 1346 | |
|
1288 | 1347 | |
|
1289 | 1348 | def test_edit_cell(): |
|
1290 | 1349 | """%edit [cell id]""" |
|
1291 | 1350 | ip = get_ipython() |
|
1292 | 1351 | |
|
1293 | 1352 | ip.run_cell("def foo(): return 1", store_history=True) |
|
1294 | 1353 | |
|
1295 | 1354 | # test |
|
1296 | 1355 | _run_edit_test("1", exp_contents=ip.user_ns['In'][1], exp_is_temp=True) |
|
1297 | 1356 | |
|
1298 | 1357 | def test_edit_fname(): |
|
1299 | 1358 | """%edit file""" |
|
1300 | 1359 | # test |
|
1301 | 1360 | _run_edit_test("test file.py", exp_filename="test file.py") |
|
1302 | 1361 | |
|
1303 | 1362 | def test_bookmark(): |
|
1304 | 1363 | ip = get_ipython() |
|
1305 | 1364 | ip.run_line_magic('bookmark', 'bmname') |
|
1306 | 1365 | with tt.AssertPrints('bmname'): |
|
1307 | 1366 | ip.run_line_magic('bookmark', '-l') |
|
1308 | 1367 | ip.run_line_magic('bookmark', '-d bmname') |
|
1309 | 1368 | |
|
1310 | 1369 | def test_ls_magic(): |
|
1311 | 1370 | ip = get_ipython() |
|
1312 | 1371 | json_formatter = ip.display_formatter.formatters['application/json'] |
|
1313 | 1372 | json_formatter.enabled = True |
|
1314 | 1373 | lsmagic = ip.run_line_magic("lsmagic", "") |
|
1315 | 1374 | with warnings.catch_warnings(record=True) as w: |
|
1316 | 1375 | j = json_formatter(lsmagic) |
|
1317 | 1376 | assert sorted(j) == ["cell", "line"] |
|
1318 | 1377 | assert w == [] # no warnings |
|
1319 | 1378 | |
|
1320 | 1379 | |
|
1321 | 1380 | def test_strip_initial_indent(): |
|
1322 | 1381 | def sii(s): |
|
1323 | 1382 | lines = s.splitlines() |
|
1324 | 1383 | return '\n'.join(code.strip_initial_indent(lines)) |
|
1325 | 1384 | |
|
1326 | 1385 | assert sii(" a = 1\nb = 2") == "a = 1\nb = 2" |
|
1327 | 1386 | assert sii(" a\n b\nc") == "a\n b\nc" |
|
1328 | 1387 | assert sii("a\n b") == "a\n b" |
|
1329 | 1388 | |
|
1330 | 1389 | def test_logging_magic_quiet_from_arg(): |
|
1331 | 1390 | _ip.config.LoggingMagics.quiet = False |
|
1332 | 1391 | lm = logging.LoggingMagics(shell=_ip) |
|
1333 | 1392 | with TemporaryDirectory() as td: |
|
1334 | 1393 | try: |
|
1335 | 1394 | with tt.AssertNotPrints(re.compile("Activating.*")): |
|
1336 | 1395 | lm.logstart('-q {}'.format( |
|
1337 | 1396 | os.path.join(td, "quiet_from_arg.log"))) |
|
1338 | 1397 | finally: |
|
1339 | 1398 | _ip.logger.logstop() |
|
1340 | 1399 | |
|
1341 | 1400 | def test_logging_magic_quiet_from_config(): |
|
1342 | 1401 | _ip.config.LoggingMagics.quiet = True |
|
1343 | 1402 | lm = logging.LoggingMagics(shell=_ip) |
|
1344 | 1403 | with TemporaryDirectory() as td: |
|
1345 | 1404 | try: |
|
1346 | 1405 | with tt.AssertNotPrints(re.compile("Activating.*")): |
|
1347 | 1406 | lm.logstart(os.path.join(td, "quiet_from_config.log")) |
|
1348 | 1407 | finally: |
|
1349 | 1408 | _ip.logger.logstop() |
|
1350 | 1409 | |
|
1351 | 1410 | |
|
1352 | 1411 | def test_logging_magic_not_quiet(): |
|
1353 | 1412 | _ip.config.LoggingMagics.quiet = False |
|
1354 | 1413 | lm = logging.LoggingMagics(shell=_ip) |
|
1355 | 1414 | with TemporaryDirectory() as td: |
|
1356 | 1415 | try: |
|
1357 | 1416 | with tt.AssertPrints(re.compile("Activating.*")): |
|
1358 | 1417 | lm.logstart(os.path.join(td, "not_quiet.log")) |
|
1359 | 1418 | finally: |
|
1360 | 1419 | _ip.logger.logstop() |
|
1361 | 1420 | |
|
1362 | 1421 | |
|
1363 | 1422 | def test_time_no_var_expand(): |
|
1364 | 1423 | _ip.user_ns["a"] = 5 |
|
1365 | 1424 | _ip.user_ns["b"] = [] |
|
1366 | 1425 | _ip.run_line_magic("time", 'b.append("{a}")') |
|
1367 | 1426 | assert _ip.user_ns["b"] == ["{a}"] |
|
1368 | 1427 | |
|
1369 | 1428 | |
|
1370 | 1429 | # this is slow, put at the end for local testing. |
|
1371 | 1430 | def test_timeit_arguments(): |
|
1372 | 1431 | "Test valid timeit arguments, should not cause SyntaxError (GH #1269)" |
|
1373 | 1432 | _ip.run_line_magic("timeit", "-n1 -r1 a=('#')") |
|
1374 | 1433 | |
|
1375 | 1434 | |
|
1376 | 1435 | MINIMAL_LAZY_MAGIC = """ |
|
1377 | 1436 | from IPython.core.magic import ( |
|
1378 | 1437 | Magics, |
|
1379 | 1438 | magics_class, |
|
1380 | 1439 | line_magic, |
|
1381 | 1440 | cell_magic, |
|
1382 | 1441 | ) |
|
1383 | 1442 | |
|
1384 | 1443 | |
|
1385 | 1444 | @magics_class |
|
1386 | 1445 | class LazyMagics(Magics): |
|
1387 | 1446 | @line_magic |
|
1388 | 1447 | def lazy_line(self, line): |
|
1389 | 1448 | print("Lazy Line") |
|
1390 | 1449 | |
|
1391 | 1450 | @cell_magic |
|
1392 | 1451 | def lazy_cell(self, line, cell): |
|
1393 | 1452 | print("Lazy Cell") |
|
1394 | 1453 | |
|
1395 | 1454 | |
|
1396 | 1455 | def load_ipython_extension(ipython): |
|
1397 | 1456 | ipython.register_magics(LazyMagics) |
|
1398 | 1457 | """ |
|
1399 | 1458 | |
|
1400 | 1459 | |
|
1401 | 1460 | def test_lazy_magics(): |
|
1402 | 1461 | with pytest.raises(UsageError): |
|
1403 | 1462 | ip.run_line_magic("lazy_line", "") |
|
1404 | 1463 | |
|
1405 | 1464 | startdir = os.getcwd() |
|
1406 | 1465 | |
|
1407 | 1466 | with TemporaryDirectory() as tmpdir: |
|
1408 | 1467 | with prepended_to_syspath(tmpdir): |
|
1409 | 1468 | ptempdir = Path(tmpdir) |
|
1410 | 1469 | tf = ptempdir / "lazy_magic_module.py" |
|
1411 | 1470 | tf.write_text(MINIMAL_LAZY_MAGIC) |
|
1412 | 1471 | ip.magics_manager.register_lazy("lazy_line", Path(tf.name).name[:-3]) |
|
1413 | 1472 | with tt.AssertPrints("Lazy Line"): |
|
1414 | 1473 | ip.run_line_magic("lazy_line", "") |
|
1415 | 1474 | |
|
1416 | 1475 | |
|
1417 | 1476 | TEST_MODULE = """ |
|
1418 | 1477 | print('Loaded my_tmp') |
|
1419 | 1478 | if __name__ == "__main__": |
|
1420 | 1479 | print('I just ran a script') |
|
1421 | 1480 | """ |
|
1422 | 1481 | |
|
1423 | 1482 | def test_run_module_from_import_hook(): |
|
1424 | 1483 | "Test that a module can be loaded via an import hook" |
|
1425 | 1484 | with TemporaryDirectory() as tmpdir: |
|
1426 | 1485 | fullpath = os.path.join(tmpdir, "my_tmp.py") |
|
1427 | 1486 | Path(fullpath).write_text(TEST_MODULE, encoding="utf-8") |
|
1428 | 1487 | |
|
1429 | 1488 | import importlib.abc |
|
1430 | 1489 | import importlib.util |
|
1431 | 1490 | |
|
1432 | 1491 | class MyTempImporter(importlib.abc.MetaPathFinder, importlib.abc.SourceLoader): |
|
1433 | 1492 | def find_spec(self, fullname, path, target=None): |
|
1434 | 1493 | if fullname == "my_tmp": |
|
1435 | 1494 | return importlib.util.spec_from_loader(fullname, self) |
|
1436 | 1495 | |
|
1437 | 1496 | def get_filename(self, fullname): |
|
1438 | 1497 | assert fullname == "my_tmp" |
|
1439 | 1498 | return fullpath |
|
1440 | 1499 | |
|
1441 | 1500 | def get_data(self, path): |
|
1442 | 1501 | assert Path(path).samefile(fullpath) |
|
1443 | 1502 | return Path(fullpath).read_text(encoding="utf-8") |
|
1444 | 1503 | |
|
1445 | 1504 | sys.meta_path.insert(0, MyTempImporter()) |
|
1446 | 1505 | |
|
1447 | 1506 | with capture_output() as captured: |
|
1448 | 1507 | _ip.run_line_magic("run", "-m my_tmp") |
|
1449 | 1508 | _ip.run_cell("import my_tmp") |
|
1450 | 1509 | |
|
1451 | 1510 | output = "Loaded my_tmp\nI just ran a script\nLoaded my_tmp\n" |
|
1452 | 1511 | assert output == captured.stdout |
|
1453 | 1512 | |
|
1454 | 1513 | sys.meta_path.pop(0) |
@@ -1,773 +1,808 b'' | |||
|
1 | 1 | """IPython terminal interface using prompt_toolkit""" |
|
2 | 2 | |
|
3 | 3 | import asyncio |
|
4 | 4 | import os |
|
5 | 5 | import sys |
|
6 | 6 | from warnings import warn |
|
7 | from typing import Union as UnionType | |
|
7 | 8 | |
|
8 | 9 | from IPython.core.async_helpers import get_asyncio_loop |
|
9 | 10 | from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC |
|
10 | 11 | from IPython.utils.py3compat import input |
|
11 | 12 | from IPython.utils.terminal import toggle_set_term_title, set_term_title, restore_term_title |
|
12 | 13 | from IPython.utils.process import abbrev_cwd |
|
13 | 14 | from traitlets import ( |
|
14 | 15 | Bool, |
|
15 | 16 | Unicode, |
|
16 | 17 | Dict, |
|
17 | 18 | Integer, |
|
18 | 19 | observe, |
|
19 | 20 | Instance, |
|
20 | 21 | Type, |
|
21 | 22 | default, |
|
22 | 23 | Enum, |
|
23 | 24 | Union, |
|
24 | 25 | Any, |
|
25 | 26 | validate, |
|
26 | 27 | Float, |
|
27 | 28 | ) |
|
28 | 29 | |
|
29 | 30 | from prompt_toolkit.auto_suggest import AutoSuggestFromHistory |
|
30 | 31 | from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode |
|
31 | 32 | from prompt_toolkit.filters import (HasFocus, Condition, IsDone) |
|
32 | 33 | from prompt_toolkit.formatted_text import PygmentsTokens |
|
33 | 34 | from prompt_toolkit.history import History |
|
34 | 35 | from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor |
|
35 | 36 | from prompt_toolkit.output import ColorDepth |
|
36 | 37 | from prompt_toolkit.patch_stdout import patch_stdout |
|
37 | 38 | from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text |
|
38 | 39 | from prompt_toolkit.styles import DynamicStyle, merge_styles |
|
39 | 40 | from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict |
|
40 | 41 | from prompt_toolkit import __version__ as ptk_version |
|
41 | 42 | |
|
42 | 43 | from pygments.styles import get_style_by_name |
|
43 | 44 | from pygments.style import Style |
|
44 | 45 | from pygments.token import Token |
|
45 | 46 | |
|
46 | 47 | from .debugger import TerminalPdb, Pdb |
|
47 | 48 | from .magics import TerminalMagics |
|
48 | 49 | from .pt_inputhooks import get_inputhook_name_and_func |
|
49 | 50 | from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook |
|
50 | 51 | from .ptutils import IPythonPTCompleter, IPythonPTLexer |
|
51 | 52 | from .shortcuts import create_ipython_shortcuts |
|
53 | from .shortcuts.auto_suggest import ( | |
|
54 | NavigableAutoSuggestFromHistory, | |
|
55 | AppendAutoSuggestionInAnyLine, | |
|
56 | ) | |
|
52 | 57 | |
|
53 | 58 | PTK3 = ptk_version.startswith('3.') |
|
54 | 59 | |
|
55 | 60 | |
|
56 | 61 | class _NoStyle(Style): pass |
|
57 | 62 | |
|
58 | 63 | |
|
59 | 64 | |
|
60 | 65 | _style_overrides_light_bg = { |
|
61 | 66 | Token.Prompt: '#ansibrightblue', |
|
62 | 67 | Token.PromptNum: '#ansiblue bold', |
|
63 | 68 | Token.OutPrompt: '#ansibrightred', |
|
64 | 69 | Token.OutPromptNum: '#ansired bold', |
|
65 | 70 | } |
|
66 | 71 | |
|
67 | 72 | _style_overrides_linux = { |
|
68 | 73 | Token.Prompt: '#ansibrightgreen', |
|
69 | 74 | Token.PromptNum: '#ansigreen bold', |
|
70 | 75 | Token.OutPrompt: '#ansibrightred', |
|
71 | 76 | Token.OutPromptNum: '#ansired bold', |
|
72 | 77 | } |
|
73 | 78 | |
|
74 | 79 | def get_default_editor(): |
|
75 | 80 | try: |
|
76 | 81 | return os.environ['EDITOR'] |
|
77 | 82 | except KeyError: |
|
78 | 83 | pass |
|
79 | 84 | except UnicodeError: |
|
80 | 85 | warn("$EDITOR environment variable is not pure ASCII. Using platform " |
|
81 | 86 | "default editor.") |
|
82 | 87 | |
|
83 | 88 | if os.name == 'posix': |
|
84 | 89 | return 'vi' # the only one guaranteed to be there! |
|
85 | 90 | else: |
|
86 | 91 | return 'notepad' # same in Windows! |
|
87 | 92 | |
|
88 | 93 | # conservatively check for tty |
|
89 | 94 | # overridden streams can result in things like: |
|
90 | 95 | # - sys.stdin = None |
|
91 | 96 | # - no isatty method |
|
92 | 97 | for _name in ('stdin', 'stdout', 'stderr'): |
|
93 | 98 | _stream = getattr(sys, _name) |
|
94 | 99 | try: |
|
95 | 100 | if not _stream or not hasattr(_stream, "isatty") or not _stream.isatty(): |
|
96 | 101 | _is_tty = False |
|
97 | 102 | break |
|
98 | 103 | except ValueError: |
|
99 | 104 | # stream is closed |
|
100 | 105 | _is_tty = False |
|
101 | 106 | break |
|
102 | 107 | else: |
|
103 | 108 | _is_tty = True |
|
104 | 109 | |
|
105 | 110 | |
|
106 | 111 | _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty) |
|
107 | 112 | |
|
108 | 113 | def black_reformat_handler(text_before_cursor): |
|
109 | 114 | """ |
|
110 | 115 | We do not need to protect against error, |
|
111 | 116 | this is taken care at a higher level where any reformat error is ignored. |
|
112 | 117 | Indeed we may call reformatting on incomplete code. |
|
113 | 118 | """ |
|
114 | 119 | import black |
|
115 | 120 | |
|
116 | 121 | formatted_text = black.format_str(text_before_cursor, mode=black.FileMode()) |
|
117 | 122 | if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"): |
|
118 | 123 | formatted_text = formatted_text[:-1] |
|
119 | 124 | return formatted_text |
|
120 | 125 | |
|
121 | 126 | |
|
122 | 127 | def yapf_reformat_handler(text_before_cursor): |
|
123 | 128 | from yapf.yapflib import file_resources |
|
124 | 129 | from yapf.yapflib import yapf_api |
|
125 | 130 | |
|
126 | 131 | style_config = file_resources.GetDefaultStyleForDir(os.getcwd()) |
|
127 | 132 | formatted_text, was_formatted = yapf_api.FormatCode( |
|
128 | 133 | text_before_cursor, style_config=style_config |
|
129 | 134 | ) |
|
130 | 135 | if was_formatted: |
|
131 | 136 | if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"): |
|
132 | 137 | formatted_text = formatted_text[:-1] |
|
133 | 138 | return formatted_text |
|
134 | 139 | else: |
|
135 | 140 | return text_before_cursor |
|
136 | 141 | |
|
137 | 142 | |
|
138 | 143 | class PtkHistoryAdapter(History): |
|
139 | 144 | """ |
|
140 | 145 | Prompt toolkit has it's own way of handling history, Where it assumes it can |
|
141 | 146 | Push/pull from history. |
|
142 | 147 | |
|
143 | 148 | """ |
|
144 | 149 | |
|
145 | 150 | def __init__(self, shell): |
|
146 | 151 | super().__init__() |
|
147 | 152 | self.shell = shell |
|
148 | 153 | self._refresh() |
|
149 | 154 | |
|
150 | 155 | def append_string(self, string): |
|
151 | 156 | # we rely on sql for that. |
|
152 | 157 | self._loaded = False |
|
153 | 158 | self._refresh() |
|
154 | 159 | |
|
155 | 160 | def _refresh(self): |
|
156 | 161 | if not self._loaded: |
|
157 | 162 | self._loaded_strings = list(self.load_history_strings()) |
|
158 | 163 | |
|
159 | 164 | def load_history_strings(self): |
|
160 | 165 | last_cell = "" |
|
161 | 166 | res = [] |
|
162 | 167 | for __, ___, cell in self.shell.history_manager.get_tail( |
|
163 | 168 | self.shell.history_load_length, include_latest=True |
|
164 | 169 | ): |
|
165 | 170 | # Ignore blank lines and consecutive duplicates |
|
166 | 171 | cell = cell.rstrip() |
|
167 | 172 | if cell and (cell != last_cell): |
|
168 | 173 | res.append(cell) |
|
169 | 174 | last_cell = cell |
|
170 | 175 | yield from res[::-1] |
|
171 | 176 | |
|
172 | 177 | def store_string(self, string: str) -> None: |
|
173 | 178 | pass |
|
174 | 179 | |
|
175 | 180 | class TerminalInteractiveShell(InteractiveShell): |
|
176 | 181 | mime_renderers = Dict().tag(config=True) |
|
177 | 182 | |
|
178 | 183 | space_for_menu = Integer(6, help='Number of line at the bottom of the screen ' |
|
179 | 184 | 'to reserve for the tab completion menu, ' |
|
180 | 185 | 'search history, ...etc, the height of ' |
|
181 | 186 | 'these menus will at most this value. ' |
|
182 | 187 | 'Increase it is you prefer long and skinny ' |
|
183 | 188 | 'menus, decrease for short and wide.' |
|
184 | 189 | ).tag(config=True) |
|
185 | 190 | |
|
186 | pt_app = None | |
|
191 | pt_app: UnionType[PromptSession, None] = None | |
|
192 | auto_suggest: UnionType[ | |
|
193 | AutoSuggestFromHistory, NavigableAutoSuggestFromHistory, None | |
|
194 | ] = None | |
|
187 | 195 | debugger_history = None |
|
188 | 196 | |
|
189 | 197 | debugger_history_file = Unicode( |
|
190 | 198 | "~/.pdbhistory", help="File in which to store and read history" |
|
191 | 199 | ).tag(config=True) |
|
192 | 200 | |
|
193 | 201 | simple_prompt = Bool(_use_simple_prompt, |
|
194 | 202 | help="""Use `raw_input` for the REPL, without completion and prompt colors. |
|
195 | 203 | |
|
196 | 204 | Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are: |
|
197 | 205 | IPython own testing machinery, and emacs inferior-shell integration through elpy. |
|
198 | 206 | |
|
199 | 207 | This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT` |
|
200 | 208 | environment variable is set, or the current terminal is not a tty.""" |
|
201 | 209 | ).tag(config=True) |
|
202 | 210 | |
|
203 | 211 | @property |
|
204 | 212 | def debugger_cls(self): |
|
205 | 213 | return Pdb if self.simple_prompt else TerminalPdb |
|
206 | 214 | |
|
207 | 215 | confirm_exit = Bool(True, |
|
208 | 216 | help=""" |
|
209 | 217 | Set to confirm when you try to exit IPython with an EOF (Control-D |
|
210 | 218 | in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit', |
|
211 | 219 | you can force a direct exit without any confirmation.""", |
|
212 | 220 | ).tag(config=True) |
|
213 | 221 | |
|
214 | 222 | editing_mode = Unicode('emacs', |
|
215 | 223 | help="Shortcut style to use at the prompt. 'vi' or 'emacs'.", |
|
216 | 224 | ).tag(config=True) |
|
217 | 225 | |
|
218 | 226 | emacs_bindings_in_vi_insert_mode = Bool( |
|
219 | 227 | True, |
|
220 | 228 | help="Add shortcuts from 'emacs' insert mode to 'vi' insert mode.", |
|
221 | 229 | ).tag(config=True) |
|
222 | 230 | |
|
223 | 231 | modal_cursor = Bool( |
|
224 | 232 | True, |
|
225 | 233 | help=""" |
|
226 | 234 | Cursor shape changes depending on vi mode: beam in vi insert mode, |
|
227 | 235 | block in nav mode, underscore in replace mode.""", |
|
228 | 236 | ).tag(config=True) |
|
229 | 237 | |
|
230 | 238 | ttimeoutlen = Float( |
|
231 | 239 | 0.01, |
|
232 | 240 | help="""The time in milliseconds that is waited for a key code |
|
233 | 241 | to complete.""", |
|
234 | 242 | ).tag(config=True) |
|
235 | 243 | |
|
236 | 244 | timeoutlen = Float( |
|
237 | 245 | 0.5, |
|
238 | 246 | help="""The time in milliseconds that is waited for a mapped key |
|
239 | 247 | sequence to complete.""", |
|
240 | 248 | ).tag(config=True) |
|
241 | 249 | |
|
242 | 250 | autoformatter = Unicode( |
|
243 | 251 | None, |
|
244 | 252 | help="Autoformatter to reformat Terminal code. Can be `'black'`, `'yapf'` or `None`", |
|
245 | 253 | allow_none=True |
|
246 | 254 | ).tag(config=True) |
|
247 | 255 | |
|
248 | 256 | auto_match = Bool( |
|
249 | 257 | False, |
|
250 | 258 | help=""" |
|
251 | 259 | Automatically add/delete closing bracket or quote when opening bracket or quote is entered/deleted. |
|
252 | 260 | Brackets: (), [], {} |
|
253 | 261 | Quotes: '', \"\" |
|
254 | 262 | """, |
|
255 | 263 | ).tag(config=True) |
|
256 | 264 | |
|
257 | 265 | mouse_support = Bool(False, |
|
258 | 266 | help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)" |
|
259 | 267 | ).tag(config=True) |
|
260 | 268 | |
|
261 | 269 | # We don't load the list of styles for the help string, because loading |
|
262 | 270 | # Pygments plugins takes time and can cause unexpected errors. |
|
263 | 271 | highlighting_style = Union([Unicode('legacy'), Type(klass=Style)], |
|
264 | 272 | help="""The name or class of a Pygments style to use for syntax |
|
265 | 273 | highlighting. To see available styles, run `pygmentize -L styles`.""" |
|
266 | 274 | ).tag(config=True) |
|
267 | 275 | |
|
268 | 276 | @validate('editing_mode') |
|
269 | 277 | def _validate_editing_mode(self, proposal): |
|
270 | 278 | if proposal['value'].lower() == 'vim': |
|
271 | 279 | proposal['value']= 'vi' |
|
272 | 280 | elif proposal['value'].lower() == 'default': |
|
273 | 281 | proposal['value']= 'emacs' |
|
274 | 282 | |
|
275 | 283 | if hasattr(EditingMode, proposal['value'].upper()): |
|
276 | 284 | return proposal['value'].lower() |
|
277 | 285 | |
|
278 | 286 | return self.editing_mode |
|
279 | 287 | |
|
280 | 288 | |
|
281 | 289 | @observe('editing_mode') |
|
282 | 290 | def _editing_mode(self, change): |
|
283 | 291 | if self.pt_app: |
|
284 | 292 | self.pt_app.editing_mode = getattr(EditingMode, change.new.upper()) |
|
285 | 293 | |
|
286 | 294 | def _set_formatter(self, formatter): |
|
287 | 295 | if formatter is None: |
|
288 | 296 | self.reformat_handler = lambda x:x |
|
289 | 297 | elif formatter == 'black': |
|
290 | 298 | self.reformat_handler = black_reformat_handler |
|
291 | 299 | elif formatter == "yapf": |
|
292 | 300 | self.reformat_handler = yapf_reformat_handler |
|
293 | 301 | else: |
|
294 | 302 | raise ValueError |
|
295 | 303 | |
|
296 | 304 | @observe("autoformatter") |
|
297 | 305 | def _autoformatter_changed(self, change): |
|
298 | 306 | formatter = change.new |
|
299 | 307 | self._set_formatter(formatter) |
|
300 | 308 | |
|
301 | 309 | @observe('highlighting_style') |
|
302 | 310 | @observe('colors') |
|
303 | 311 | def _highlighting_style_changed(self, change): |
|
304 | 312 | self.refresh_style() |
|
305 | 313 | |
|
306 | 314 | def refresh_style(self): |
|
307 | 315 | self._style = self._make_style_from_name_or_cls(self.highlighting_style) |
|
308 | 316 | |
|
309 | 317 | |
|
310 | 318 | highlighting_style_overrides = Dict( |
|
311 | 319 | help="Override highlighting format for specific tokens" |
|
312 | 320 | ).tag(config=True) |
|
313 | 321 | |
|
314 | 322 | true_color = Bool(False, |
|
315 | 323 | help="""Use 24bit colors instead of 256 colors in prompt highlighting. |
|
316 | 324 | If your terminal supports true color, the following command should |
|
317 | 325 | print ``TRUECOLOR`` in orange:: |
|
318 | 326 | |
|
319 | 327 | printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\" |
|
320 | 328 | """, |
|
321 | 329 | ).tag(config=True) |
|
322 | 330 | |
|
323 | 331 | editor = Unicode(get_default_editor(), |
|
324 | 332 | help="Set the editor used by IPython (default to $EDITOR/vi/notepad)." |
|
325 | 333 | ).tag(config=True) |
|
326 | 334 | |
|
327 | 335 | prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True) |
|
328 | 336 | |
|
329 | 337 | prompts = Instance(Prompts) |
|
330 | 338 | |
|
331 | 339 | @default('prompts') |
|
332 | 340 | def _prompts_default(self): |
|
333 | 341 | return self.prompts_class(self) |
|
334 | 342 | |
|
335 | 343 | # @observe('prompts') |
|
336 | 344 | # def _(self, change): |
|
337 | 345 | # self._update_layout() |
|
338 | 346 | |
|
339 | 347 | @default('displayhook_class') |
|
340 | 348 | def _displayhook_class_default(self): |
|
341 | 349 | return RichPromptDisplayHook |
|
342 | 350 | |
|
343 | 351 | term_title = Bool(True, |
|
344 | 352 | help="Automatically set the terminal title" |
|
345 | 353 | ).tag(config=True) |
|
346 | 354 | |
|
347 | 355 | term_title_format = Unicode("IPython: {cwd}", |
|
348 | 356 | help="Customize the terminal title format. This is a python format string. " + |
|
349 | 357 | "Available substitutions are: {cwd}." |
|
350 | 358 | ).tag(config=True) |
|
351 | 359 | |
|
352 | 360 | display_completions = Enum(('column', 'multicolumn','readlinelike'), |
|
353 | 361 | help= ( "Options for displaying tab completions, 'column', 'multicolumn', and " |
|
354 | 362 | "'readlinelike'. These options are for `prompt_toolkit`, see " |
|
355 | 363 | "`prompt_toolkit` documentation for more information." |
|
356 | 364 | ), |
|
357 | 365 | default_value='multicolumn').tag(config=True) |
|
358 | 366 | |
|
359 | 367 | highlight_matching_brackets = Bool(True, |
|
360 | 368 | help="Highlight matching brackets.", |
|
361 | 369 | ).tag(config=True) |
|
362 | 370 | |
|
363 | 371 | extra_open_editor_shortcuts = Bool(False, |
|
364 | 372 | help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. " |
|
365 | 373 | "This is in addition to the F2 binding, which is always enabled." |
|
366 | 374 | ).tag(config=True) |
|
367 | 375 | |
|
368 | 376 | handle_return = Any(None, |
|
369 | 377 | help="Provide an alternative handler to be called when the user presses " |
|
370 | 378 | "Return. This is an advanced option intended for debugging, which " |
|
371 | 379 | "may be changed or removed in later releases." |
|
372 | 380 | ).tag(config=True) |
|
373 | 381 | |
|
374 | 382 | enable_history_search = Bool(True, |
|
375 | 383 | help="Allows to enable/disable the prompt toolkit history search" |
|
376 | 384 | ).tag(config=True) |
|
377 | 385 | |
|
378 | 386 | autosuggestions_provider = Unicode( |
|
379 | "AutoSuggestFromHistory", | |
|
387 | "NavigableAutoSuggestFromHistory", | |
|
380 | 388 | help="Specifies from which source automatic suggestions are provided. " |
|
381 |
"Can be set to `'AutoSuggestFromHistory |
|
|
382 |
" |
|
|
389 | "Can be set to ``'NavigableAutoSuggestFromHistory'`` (:kbd:`up` and " | |
|
390 | ":kbd:`down` swap suggestions), ``'AutoSuggestFromHistory'``, " | |
|
391 | " or ``None`` to disable automatic suggestions. " | |
|
392 | "Default is `'NavigableAutoSuggestFromHistory`'.", | |
|
383 | 393 | allow_none=True, |
|
384 | 394 | ).tag(config=True) |
|
385 | 395 | |
|
386 | 396 | def _set_autosuggestions(self, provider): |
|
397 | # disconnect old handler | |
|
398 | if self.auto_suggest and isinstance( | |
|
399 | self.auto_suggest, NavigableAutoSuggestFromHistory | |
|
400 | ): | |
|
401 | self.auto_suggest.disconnect() | |
|
387 | 402 | if provider is None: |
|
388 | 403 | self.auto_suggest = None |
|
389 | 404 | elif provider == "AutoSuggestFromHistory": |
|
390 | 405 | self.auto_suggest = AutoSuggestFromHistory() |
|
406 | elif provider == "NavigableAutoSuggestFromHistory": | |
|
407 | self.auto_suggest = NavigableAutoSuggestFromHistory() | |
|
391 | 408 | else: |
|
392 | 409 | raise ValueError("No valid provider.") |
|
393 | 410 | if self.pt_app: |
|
394 | 411 | self.pt_app.auto_suggest = self.auto_suggest |
|
395 | 412 | |
|
396 | 413 | @observe("autosuggestions_provider") |
|
397 | 414 | def _autosuggestions_provider_changed(self, change): |
|
398 | 415 | provider = change.new |
|
399 | 416 | self._set_autosuggestions(provider) |
|
400 | 417 | |
|
401 | 418 | prompt_includes_vi_mode = Bool(True, |
|
402 | 419 | help="Display the current vi mode (when using vi editing mode)." |
|
403 | 420 | ).tag(config=True) |
|
404 | 421 | |
|
405 | 422 | @observe('term_title') |
|
406 | 423 | def init_term_title(self, change=None): |
|
407 | 424 | # Enable or disable the terminal title. |
|
408 | 425 | if self.term_title and _is_tty: |
|
409 | 426 | toggle_set_term_title(True) |
|
410 | 427 | set_term_title(self.term_title_format.format(cwd=abbrev_cwd())) |
|
411 | 428 | else: |
|
412 | 429 | toggle_set_term_title(False) |
|
413 | 430 | |
|
414 | 431 | def restore_term_title(self): |
|
415 | 432 | if self.term_title and _is_tty: |
|
416 | 433 | restore_term_title() |
|
417 | 434 | |
|
418 | 435 | def init_display_formatter(self): |
|
419 | 436 | super(TerminalInteractiveShell, self).init_display_formatter() |
|
420 | 437 | # terminal only supports plain text |
|
421 | 438 | self.display_formatter.active_types = ["text/plain"] |
|
422 | 439 | |
|
423 | 440 | def init_prompt_toolkit_cli(self): |
|
424 | 441 | if self.simple_prompt: |
|
425 | 442 | # Fall back to plain non-interactive output for tests. |
|
426 | 443 | # This is very limited. |
|
427 | 444 | def prompt(): |
|
428 | 445 | prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens()) |
|
429 | 446 | lines = [input(prompt_text)] |
|
430 | 447 | prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens()) |
|
431 | 448 | while self.check_complete('\n'.join(lines))[0] == 'incomplete': |
|
432 | 449 | lines.append( input(prompt_continuation) ) |
|
433 | 450 | return '\n'.join(lines) |
|
434 | 451 | self.prompt_for_code = prompt |
|
435 | 452 | return |
|
436 | 453 | |
|
437 | 454 | # Set up keyboard shortcuts |
|
438 | 455 | key_bindings = create_ipython_shortcuts(self) |
|
439 | 456 | |
|
440 | 457 | |
|
441 | 458 | # Pre-populate history from IPython's history database |
|
442 | 459 | history = PtkHistoryAdapter(self) |
|
443 | 460 | |
|
444 | 461 | self._style = self._make_style_from_name_or_cls(self.highlighting_style) |
|
445 | 462 | self.style = DynamicStyle(lambda: self._style) |
|
446 | 463 | |
|
447 | 464 | editing_mode = getattr(EditingMode, self.editing_mode.upper()) |
|
448 | 465 | |
|
449 | 466 | self.pt_loop = asyncio.new_event_loop() |
|
450 | 467 | self.pt_app = PromptSession( |
|
451 | 468 | auto_suggest=self.auto_suggest, |
|
452 | 469 | editing_mode=editing_mode, |
|
453 | 470 | key_bindings=key_bindings, |
|
454 | 471 | history=history, |
|
455 | 472 | completer=IPythonPTCompleter(shell=self), |
|
456 | 473 | enable_history_search=self.enable_history_search, |
|
457 | 474 | style=self.style, |
|
458 | 475 | include_default_pygments_style=False, |
|
459 | 476 | mouse_support=self.mouse_support, |
|
460 | 477 | enable_open_in_editor=self.extra_open_editor_shortcuts, |
|
461 | 478 | color_depth=self.color_depth, |
|
462 | 479 | tempfile_suffix=".py", |
|
463 | 480 | **self._extra_prompt_options() |
|
464 | 481 | ) |
|
482 | if isinstance(self.auto_suggest, NavigableAutoSuggestFromHistory): | |
|
483 | self.auto_suggest.connect(self.pt_app) | |
|
465 | 484 | |
|
466 | 485 | def _make_style_from_name_or_cls(self, name_or_cls): |
|
467 | 486 | """ |
|
468 | 487 | Small wrapper that make an IPython compatible style from a style name |
|
469 | 488 | |
|
470 | 489 | We need that to add style for prompt ... etc. |
|
471 | 490 | """ |
|
472 | 491 | style_overrides = {} |
|
473 | 492 | if name_or_cls == 'legacy': |
|
474 | 493 | legacy = self.colors.lower() |
|
475 | 494 | if legacy == 'linux': |
|
476 | 495 | style_cls = get_style_by_name('monokai') |
|
477 | 496 | style_overrides = _style_overrides_linux |
|
478 | 497 | elif legacy == 'lightbg': |
|
479 | 498 | style_overrides = _style_overrides_light_bg |
|
480 | 499 | style_cls = get_style_by_name('pastie') |
|
481 | 500 | elif legacy == 'neutral': |
|
482 | 501 | # The default theme needs to be visible on both a dark background |
|
483 | 502 | # and a light background, because we can't tell what the terminal |
|
484 | 503 | # looks like. These tweaks to the default theme help with that. |
|
485 | 504 | style_cls = get_style_by_name('default') |
|
486 | 505 | style_overrides.update({ |
|
487 | 506 | Token.Number: '#ansigreen', |
|
488 | 507 | Token.Operator: 'noinherit', |
|
489 | 508 | Token.String: '#ansiyellow', |
|
490 | 509 | Token.Name.Function: '#ansiblue', |
|
491 | 510 | Token.Name.Class: 'bold #ansiblue', |
|
492 | 511 | Token.Name.Namespace: 'bold #ansiblue', |
|
493 | 512 | Token.Name.Variable.Magic: '#ansiblue', |
|
494 | 513 | Token.Prompt: '#ansigreen', |
|
495 | 514 | Token.PromptNum: '#ansibrightgreen bold', |
|
496 | 515 | Token.OutPrompt: '#ansired', |
|
497 | 516 | Token.OutPromptNum: '#ansibrightred bold', |
|
498 | 517 | }) |
|
499 | 518 | |
|
500 | 519 | # Hack: Due to limited color support on the Windows console |
|
501 | 520 | # the prompt colors will be wrong without this |
|
502 | 521 | if os.name == 'nt': |
|
503 | 522 | style_overrides.update({ |
|
504 | 523 | Token.Prompt: '#ansidarkgreen', |
|
505 | 524 | Token.PromptNum: '#ansigreen bold', |
|
506 | 525 | Token.OutPrompt: '#ansidarkred', |
|
507 | 526 | Token.OutPromptNum: '#ansired bold', |
|
508 | 527 | }) |
|
509 | 528 | elif legacy =='nocolor': |
|
510 | 529 | style_cls=_NoStyle |
|
511 | 530 | style_overrides = {} |
|
512 | 531 | else : |
|
513 | 532 | raise ValueError('Got unknown colors: ', legacy) |
|
514 | 533 | else : |
|
515 | 534 | if isinstance(name_or_cls, str): |
|
516 | 535 | style_cls = get_style_by_name(name_or_cls) |
|
517 | 536 | else: |
|
518 | 537 | style_cls = name_or_cls |
|
519 | 538 | style_overrides = { |
|
520 | 539 | Token.Prompt: '#ansigreen', |
|
521 | 540 | Token.PromptNum: '#ansibrightgreen bold', |
|
522 | 541 | Token.OutPrompt: '#ansired', |
|
523 | 542 | Token.OutPromptNum: '#ansibrightred bold', |
|
524 | 543 | } |
|
525 | 544 | style_overrides.update(self.highlighting_style_overrides) |
|
526 | 545 | style = merge_styles([ |
|
527 | 546 | style_from_pygments_cls(style_cls), |
|
528 | 547 | style_from_pygments_dict(style_overrides), |
|
529 | 548 | ]) |
|
530 | 549 | |
|
531 | 550 | return style |
|
532 | 551 | |
|
533 | 552 | @property |
|
534 | 553 | def pt_complete_style(self): |
|
535 | 554 | return { |
|
536 | 555 | 'multicolumn': CompleteStyle.MULTI_COLUMN, |
|
537 | 556 | 'column': CompleteStyle.COLUMN, |
|
538 | 557 | 'readlinelike': CompleteStyle.READLINE_LIKE, |
|
539 | 558 | }[self.display_completions] |
|
540 | 559 | |
|
541 | 560 | @property |
|
542 | 561 | def color_depth(self): |
|
543 | 562 | return (ColorDepth.TRUE_COLOR if self.true_color else None) |
|
544 | 563 | |
|
545 | 564 | def _extra_prompt_options(self): |
|
546 | 565 | """ |
|
547 | 566 | Return the current layout option for the current Terminal InteractiveShell |
|
548 | 567 | """ |
|
549 | 568 | def get_message(): |
|
550 | 569 | return PygmentsTokens(self.prompts.in_prompt_tokens()) |
|
551 | 570 | |
|
552 | 571 | if self.editing_mode == 'emacs': |
|
553 | 572 | # with emacs mode the prompt is (usually) static, so we call only |
|
554 | 573 | # the function once. With VI mode it can toggle between [ins] and |
|
555 | 574 | # [nor] so we can't precompute. |
|
556 | 575 | # here I'm going to favor the default keybinding which almost |
|
557 | 576 | # everybody uses to decrease CPU usage. |
|
558 | 577 | # if we have issues with users with custom Prompts we can see how to |
|
559 | 578 | # work around this. |
|
560 | 579 | get_message = get_message() |
|
561 | 580 | |
|
562 | 581 | options = { |
|
563 |
|
|
|
564 |
|
|
|
565 |
|
|
|
566 |
|
|
|
567 |
|
|
|
568 |
|
|
|
569 |
|
|
|
570 |
|
|
|
571 | 'complete_style': self.pt_complete_style, | |
|
572 | ||
|
582 | "complete_in_thread": False, | |
|
583 | "lexer": IPythonPTLexer(), | |
|
584 | "reserve_space_for_menu": self.space_for_menu, | |
|
585 | "message": get_message, | |
|
586 | "prompt_continuation": ( | |
|
587 | lambda width, lineno, is_soft_wrap: PygmentsTokens( | |
|
588 | self.prompts.continuation_prompt_tokens(width) | |
|
589 | ) | |
|
590 | ), | |
|
591 | "multiline": True, | |
|
592 | "complete_style": self.pt_complete_style, | |
|
593 | "input_processors": [ | |
|
573 | 594 | # Highlight matching brackets, but only when this setting is |
|
574 | 595 | # enabled, and only when the DEFAULT_BUFFER has the focus. |
|
575 |
|
|
|
576 |
|
|
|
577 |
|
|
|
578 | Condition(lambda: self.highlight_matching_brackets))], | |
|
579 | } | |
|
596 | ConditionalProcessor( | |
|
597 | processor=HighlightMatchingBracketProcessor(chars="[](){}"), | |
|
598 | filter=HasFocus(DEFAULT_BUFFER) | |
|
599 | & ~IsDone() | |
|
600 | & Condition(lambda: self.highlight_matching_brackets), | |
|
601 | ), | |
|
602 | # Show auto-suggestion in lines other than the last line. | |
|
603 | ConditionalProcessor( | |
|
604 | processor=AppendAutoSuggestionInAnyLine(), | |
|
605 | filter=HasFocus(DEFAULT_BUFFER) | |
|
606 | & ~IsDone() | |
|
607 | & Condition( | |
|
608 | lambda: isinstance( | |
|
609 | self.auto_suggest, NavigableAutoSuggestFromHistory | |
|
610 | ) | |
|
611 | ), | |
|
612 | ), | |
|
613 | ], | |
|
614 | } | |
|
580 | 615 | if not PTK3: |
|
581 | 616 | options['inputhook'] = self.inputhook |
|
582 | 617 | |
|
583 | 618 | return options |
|
584 | 619 | |
|
585 | 620 | def prompt_for_code(self): |
|
586 | 621 | if self.rl_next_input: |
|
587 | 622 | default = self.rl_next_input |
|
588 | 623 | self.rl_next_input = None |
|
589 | 624 | else: |
|
590 | 625 | default = '' |
|
591 | 626 | |
|
592 | 627 | # In order to make sure that asyncio code written in the |
|
593 | 628 | # interactive shell doesn't interfere with the prompt, we run the |
|
594 | 629 | # prompt in a different event loop. |
|
595 | 630 | # If we don't do this, people could spawn coroutine with a |
|
596 | 631 | # while/true inside which will freeze the prompt. |
|
597 | 632 | |
|
598 | 633 | policy = asyncio.get_event_loop_policy() |
|
599 | 634 | old_loop = get_asyncio_loop() |
|
600 | 635 | |
|
601 | 636 | # FIXME: prompt_toolkit is using the deprecated `asyncio.get_event_loop` |
|
602 | 637 | # to get the current event loop. |
|
603 | 638 | # This will probably be replaced by an attribute or input argument, |
|
604 | 639 | # at which point we can stop calling the soon-to-be-deprecated `set_event_loop` here. |
|
605 | 640 | if old_loop is not self.pt_loop: |
|
606 | 641 | policy.set_event_loop(self.pt_loop) |
|
607 | 642 | try: |
|
608 | 643 | with patch_stdout(raw=True): |
|
609 | 644 | text = self.pt_app.prompt( |
|
610 | 645 | default=default, |
|
611 | 646 | **self._extra_prompt_options()) |
|
612 | 647 | finally: |
|
613 | 648 | # Restore the original event loop. |
|
614 | 649 | if old_loop is not None and old_loop is not self.pt_loop: |
|
615 | 650 | policy.set_event_loop(old_loop) |
|
616 | 651 | |
|
617 | 652 | return text |
|
618 | 653 | |
|
619 | 654 | def enable_win_unicode_console(self): |
|
620 | 655 | # Since IPython 7.10 doesn't support python < 3.6 and PEP 528, Python uses the unicode APIs for the Windows |
|
621 | 656 | # console by default, so WUC shouldn't be needed. |
|
622 | 657 | warn("`enable_win_unicode_console` is deprecated since IPython 7.10, does not do anything and will be removed in the future", |
|
623 | 658 | DeprecationWarning, |
|
624 | 659 | stacklevel=2) |
|
625 | 660 | |
|
626 | 661 | def init_io(self): |
|
627 | 662 | if sys.platform not in {'win32', 'cli'}: |
|
628 | 663 | return |
|
629 | 664 | |
|
630 | 665 | import colorama |
|
631 | 666 | colorama.init() |
|
632 | 667 | |
|
633 | 668 | def init_magics(self): |
|
634 | 669 | super(TerminalInteractiveShell, self).init_magics() |
|
635 | 670 | self.register_magics(TerminalMagics) |
|
636 | 671 | |
|
637 | 672 | def init_alias(self): |
|
638 | 673 | # The parent class defines aliases that can be safely used with any |
|
639 | 674 | # frontend. |
|
640 | 675 | super(TerminalInteractiveShell, self).init_alias() |
|
641 | 676 | |
|
642 | 677 | # Now define aliases that only make sense on the terminal, because they |
|
643 | 678 | # need direct access to the console in a way that we can't emulate in |
|
644 | 679 | # GUI or web frontend |
|
645 | 680 | if os.name == 'posix': |
|
646 | 681 | for cmd in ('clear', 'more', 'less', 'man'): |
|
647 | 682 | self.alias_manager.soft_define_alias(cmd, cmd) |
|
648 | 683 | |
|
649 | 684 | |
|
650 | def __init__(self, *args, **kwargs): | |
|
685 | def __init__(self, *args, **kwargs) -> None: | |
|
651 | 686 | super(TerminalInteractiveShell, self).__init__(*args, **kwargs) |
|
652 | 687 | self._set_autosuggestions(self.autosuggestions_provider) |
|
653 | 688 | self.init_prompt_toolkit_cli() |
|
654 | 689 | self.init_term_title() |
|
655 | 690 | self.keep_running = True |
|
656 | 691 | self._set_formatter(self.autoformatter) |
|
657 | 692 | |
|
658 | 693 | |
|
659 | 694 | def ask_exit(self): |
|
660 | 695 | self.keep_running = False |
|
661 | 696 | |
|
662 | 697 | rl_next_input = None |
|
663 | 698 | |
|
664 | 699 | def interact(self): |
|
665 | 700 | self.keep_running = True |
|
666 | 701 | while self.keep_running: |
|
667 | 702 | print(self.separate_in, end='') |
|
668 | 703 | |
|
669 | 704 | try: |
|
670 | 705 | code = self.prompt_for_code() |
|
671 | 706 | except EOFError: |
|
672 | 707 | if (not self.confirm_exit) \ |
|
673 | 708 | or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'): |
|
674 | 709 | self.ask_exit() |
|
675 | 710 | |
|
676 | 711 | else: |
|
677 | 712 | if code: |
|
678 | 713 | self.run_cell(code, store_history=True) |
|
679 | 714 | |
|
680 | 715 | def mainloop(self): |
|
681 | 716 | # An extra layer of protection in case someone mashing Ctrl-C breaks |
|
682 | 717 | # out of our internal code. |
|
683 | 718 | while True: |
|
684 | 719 | try: |
|
685 | 720 | self.interact() |
|
686 | 721 | break |
|
687 | 722 | except KeyboardInterrupt as e: |
|
688 | 723 | print("\n%s escaped interact()\n" % type(e).__name__) |
|
689 | 724 | finally: |
|
690 | 725 | # An interrupt during the eventloop will mess up the |
|
691 | 726 | # internal state of the prompt_toolkit library. |
|
692 | 727 | # Stopping the eventloop fixes this, see |
|
693 | 728 | # https://github.com/ipython/ipython/pull/9867 |
|
694 | 729 | if hasattr(self, '_eventloop'): |
|
695 | 730 | self._eventloop.stop() |
|
696 | 731 | |
|
697 | 732 | self.restore_term_title() |
|
698 | 733 | |
|
699 | 734 | # try to call some at-exit operation optimistically as some things can't |
|
700 | 735 | # be done during interpreter shutdown. this is technically inaccurate as |
|
701 | 736 | # this make mainlool not re-callable, but that should be a rare if not |
|
702 | 737 | # in existent use case. |
|
703 | 738 | |
|
704 | 739 | self._atexit_once() |
|
705 | 740 | |
|
706 | 741 | |
|
707 | 742 | _inputhook = None |
|
708 | 743 | def inputhook(self, context): |
|
709 | 744 | if self._inputhook is not None: |
|
710 | 745 | self._inputhook(context) |
|
711 | 746 | |
|
712 | 747 | active_eventloop = None |
|
713 | 748 | def enable_gui(self, gui=None): |
|
714 | 749 | if gui and (gui not in {"inline", "webagg"}): |
|
715 | 750 | self.active_eventloop, self._inputhook = get_inputhook_name_and_func(gui) |
|
716 | 751 | else: |
|
717 | 752 | self.active_eventloop = self._inputhook = None |
|
718 | 753 | |
|
719 | 754 | # For prompt_toolkit 3.0. We have to create an asyncio event loop with |
|
720 | 755 | # this inputhook. |
|
721 | 756 | if PTK3: |
|
722 | 757 | import asyncio |
|
723 | 758 | from prompt_toolkit.eventloop import new_eventloop_with_inputhook |
|
724 | 759 | |
|
725 | 760 | if gui == 'asyncio': |
|
726 | 761 | # When we integrate the asyncio event loop, run the UI in the |
|
727 | 762 | # same event loop as the rest of the code. don't use an actual |
|
728 | 763 | # input hook. (Asyncio is not made for nesting event loops.) |
|
729 | 764 | self.pt_loop = get_asyncio_loop() |
|
730 | 765 | |
|
731 | 766 | elif self._inputhook: |
|
732 | 767 | # If an inputhook was set, create a new asyncio event loop with |
|
733 | 768 | # this inputhook for the prompt. |
|
734 | 769 | self.pt_loop = new_eventloop_with_inputhook(self._inputhook) |
|
735 | 770 | else: |
|
736 | 771 | # When there's no inputhook, run the prompt in a separate |
|
737 | 772 | # asyncio event loop. |
|
738 | 773 | self.pt_loop = asyncio.new_event_loop() |
|
739 | 774 | |
|
740 | 775 | # Run !system commands directly, not through pipes, so terminal programs |
|
741 | 776 | # work correctly. |
|
742 | 777 | system = InteractiveShell.system_raw |
|
743 | 778 | |
|
744 | 779 | def auto_rewrite_input(self, cmd): |
|
745 | 780 | """Overridden from the parent class to use fancy rewriting prompt""" |
|
746 | 781 | if not self.show_rewritten_input: |
|
747 | 782 | return |
|
748 | 783 | |
|
749 | 784 | tokens = self.prompts.rewrite_prompt_tokens() |
|
750 | 785 | if self.pt_app: |
|
751 | 786 | print_formatted_text(PygmentsTokens(tokens), end='', |
|
752 | 787 | style=self.pt_app.app.style) |
|
753 | 788 | print(cmd) |
|
754 | 789 | else: |
|
755 | 790 | prompt = ''.join(s for t, s in tokens) |
|
756 | 791 | print(prompt, cmd, sep='') |
|
757 | 792 | |
|
758 | 793 | _prompts_before = None |
|
759 | 794 | def switch_doctest_mode(self, mode): |
|
760 | 795 | """Switch prompts to classic for %doctest_mode""" |
|
761 | 796 | if mode: |
|
762 | 797 | self._prompts_before = self.prompts |
|
763 | 798 | self.prompts = ClassicPrompts(self) |
|
764 | 799 | elif self._prompts_before: |
|
765 | 800 | self.prompts = self._prompts_before |
|
766 | 801 | self._prompts_before = None |
|
767 | 802 | # self._update_layout() |
|
768 | 803 | |
|
769 | 804 | |
|
770 | 805 | InteractiveShellABC.register(TerminalInteractiveShell) |
|
771 | 806 | |
|
772 | 807 | if __name__ == '__main__': |
|
773 | 808 | TerminalInteractiveShell.instance().interact() |
@@ -1,343 +1,343 b'' | |||
|
1 | 1 | #!/usr/bin/env python |
|
2 | 2 | # encoding: utf-8 |
|
3 | 3 | """ |
|
4 | 4 | The :class:`~traitlets.config.application.Application` object for the command |
|
5 | 5 | line :command:`ipython` program. |
|
6 | 6 | """ |
|
7 | 7 | |
|
8 | 8 | # Copyright (c) IPython Development Team. |
|
9 | 9 | # Distributed under the terms of the Modified BSD License. |
|
10 | 10 | |
|
11 | 11 | |
|
12 | 12 | import logging |
|
13 | 13 | import os |
|
14 | 14 | import sys |
|
15 | 15 | import warnings |
|
16 | 16 | |
|
17 | 17 | from traitlets.config.loader import Config |
|
18 | 18 | from traitlets.config.application import boolean_flag, catch_config_error |
|
19 | 19 | from IPython.core import release |
|
20 | 20 | from IPython.core import usage |
|
21 | 21 | from IPython.core.completer import IPCompleter |
|
22 | 22 | from IPython.core.crashhandler import CrashHandler |
|
23 | 23 | from IPython.core.formatters import PlainTextFormatter |
|
24 | 24 | from IPython.core.history import HistoryManager |
|
25 | 25 | from IPython.core.application import ( |
|
26 | 26 | ProfileDir, BaseIPythonApplication, base_flags, base_aliases |
|
27 | 27 | ) |
|
28 | 28 | from IPython.core.magic import MagicsManager |
|
29 | 29 | from IPython.core.magics import ( |
|
30 | 30 | ScriptMagics, LoggingMagics |
|
31 | 31 | ) |
|
32 | 32 | from IPython.core.shellapp import ( |
|
33 | 33 | InteractiveShellApp, shell_flags, shell_aliases |
|
34 | 34 | ) |
|
35 | 35 | from IPython.extensions.storemagic import StoreMagics |
|
36 | 36 | from .interactiveshell import TerminalInteractiveShell |
|
37 | 37 | from IPython.paths import get_ipython_dir |
|
38 | 38 | from traitlets import ( |
|
39 | 39 | Bool, List, default, observe, Type |
|
40 | 40 | ) |
|
41 | 41 | |
|
42 | 42 | #----------------------------------------------------------------------------- |
|
43 | 43 | # Globals, utilities and helpers |
|
44 | 44 | #----------------------------------------------------------------------------- |
|
45 | 45 | |
|
46 | 46 | _examples = """ |
|
47 | 47 | ipython --matplotlib # enable matplotlib integration |
|
48 | 48 | ipython --matplotlib=qt # enable matplotlib integration with qt4 backend |
|
49 | 49 | |
|
50 | 50 | ipython --log-level=DEBUG # set logging to DEBUG |
|
51 | 51 | ipython --profile=foo # start with profile foo |
|
52 | 52 | |
|
53 | 53 | ipython profile create foo # create profile foo w/ default config files |
|
54 | 54 | ipython help profile # show the help for the profile subcmd |
|
55 | 55 | |
|
56 | 56 | ipython locate # print the path to the IPython directory |
|
57 | 57 | ipython locate profile foo # print the path to the directory for profile `foo` |
|
58 | 58 | """ |
|
59 | 59 | |
|
60 | 60 | #----------------------------------------------------------------------------- |
|
61 | 61 | # Crash handler for this application |
|
62 | 62 | #----------------------------------------------------------------------------- |
|
63 | 63 | |
|
64 | 64 | class IPAppCrashHandler(CrashHandler): |
|
65 | 65 | """sys.excepthook for IPython itself, leaves a detailed report on disk.""" |
|
66 | 66 | |
|
67 | 67 | def __init__(self, app): |
|
68 | 68 | contact_name = release.author |
|
69 | 69 | contact_email = release.author_email |
|
70 | 70 | bug_tracker = 'https://github.com/ipython/ipython/issues' |
|
71 | 71 | super(IPAppCrashHandler,self).__init__( |
|
72 | 72 | app, contact_name, contact_email, bug_tracker |
|
73 | 73 | ) |
|
74 | 74 | |
|
75 | 75 | def make_report(self,traceback): |
|
76 | 76 | """Return a string containing a crash report.""" |
|
77 | 77 | |
|
78 | 78 | sec_sep = self.section_sep |
|
79 | 79 | # Start with parent report |
|
80 | 80 | report = [super(IPAppCrashHandler, self).make_report(traceback)] |
|
81 | 81 | # Add interactive-specific info we may have |
|
82 | 82 | rpt_add = report.append |
|
83 | 83 | try: |
|
84 | 84 | rpt_add(sec_sep+"History of session input:") |
|
85 | 85 | for line in self.app.shell.user_ns['_ih']: |
|
86 | 86 | rpt_add(line) |
|
87 | 87 | rpt_add('\n*** Last line of input (may not be in above history):\n') |
|
88 | 88 | rpt_add(self.app.shell._last_input_line+'\n') |
|
89 | 89 | except: |
|
90 | 90 | pass |
|
91 | 91 | |
|
92 | 92 | return ''.join(report) |
|
93 | 93 | |
|
94 | 94 | #----------------------------------------------------------------------------- |
|
95 | 95 | # Aliases and Flags |
|
96 | 96 | #----------------------------------------------------------------------------- |
|
97 | 97 | flags = dict(base_flags) |
|
98 | 98 | flags.update(shell_flags) |
|
99 | 99 | frontend_flags = {} |
|
100 | 100 | addflag = lambda *args: frontend_flags.update(boolean_flag(*args)) |
|
101 | 101 | addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax', |
|
102 | 102 | 'Turn on auto editing of files with syntax errors.', |
|
103 | 103 | 'Turn off auto editing of files with syntax errors.' |
|
104 | 104 | ) |
|
105 | 105 | addflag('simple-prompt', 'TerminalInteractiveShell.simple_prompt', |
|
106 | 106 | "Force simple minimal prompt using `raw_input`", |
|
107 | 107 | "Use a rich interactive prompt with prompt_toolkit", |
|
108 | 108 | ) |
|
109 | 109 | |
|
110 | 110 | addflag('banner', 'TerminalIPythonApp.display_banner', |
|
111 | 111 | "Display a banner upon starting IPython.", |
|
112 | 112 | "Don't display a banner upon starting IPython." |
|
113 | 113 | ) |
|
114 | 114 | addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit', |
|
115 | 115 | """Set to confirm when you try to exit IPython with an EOF (Control-D |
|
116 | 116 | in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit', |
|
117 | 117 | you can force a direct exit without any confirmation.""", |
|
118 | 118 | "Don't prompt the user when exiting." |
|
119 | 119 | ) |
|
120 | 120 | addflag('term-title', 'TerminalInteractiveShell.term_title', |
|
121 | 121 | "Enable auto setting the terminal title.", |
|
122 | 122 | "Disable auto setting the terminal title." |
|
123 | 123 | ) |
|
124 | 124 | classic_config = Config() |
|
125 | 125 | classic_config.InteractiveShell.cache_size = 0 |
|
126 | 126 | classic_config.PlainTextFormatter.pprint = False |
|
127 | 127 | classic_config.TerminalInteractiveShell.prompts_class='IPython.terminal.prompts.ClassicPrompts' |
|
128 | 128 | classic_config.InteractiveShell.separate_in = '' |
|
129 | 129 | classic_config.InteractiveShell.separate_out = '' |
|
130 | 130 | classic_config.InteractiveShell.separate_out2 = '' |
|
131 | 131 | classic_config.InteractiveShell.colors = 'NoColor' |
|
132 | 132 | classic_config.InteractiveShell.xmode = 'Plain' |
|
133 | 133 | |
|
134 | 134 | frontend_flags['classic']=( |
|
135 | 135 | classic_config, |
|
136 | 136 | "Gives IPython a similar feel to the classic Python prompt." |
|
137 | 137 | ) |
|
138 | 138 | # # log doesn't make so much sense this way anymore |
|
139 | 139 | # paa('--log','-l', |
|
140 | 140 | # action='store_true', dest='InteractiveShell.logstart', |
|
141 | 141 | # help="Start logging to the default log file (./ipython_log.py).") |
|
142 | 142 | # |
|
143 | 143 | # # quick is harder to implement |
|
144 | 144 | frontend_flags['quick']=( |
|
145 | 145 | {'TerminalIPythonApp' : {'quick' : True}}, |
|
146 | 146 | "Enable quick startup with no config files." |
|
147 | 147 | ) |
|
148 | 148 | |
|
149 | 149 | frontend_flags['i'] = ( |
|
150 | 150 | {'TerminalIPythonApp' : {'force_interact' : True}}, |
|
151 | 151 | """If running code from the command line, become interactive afterwards. |
|
152 | 152 | It is often useful to follow this with `--` to treat remaining flags as |
|
153 | 153 | script arguments. |
|
154 | 154 | """ |
|
155 | 155 | ) |
|
156 | 156 | flags.update(frontend_flags) |
|
157 | 157 | |
|
158 | 158 | aliases = dict(base_aliases) |
|
159 | aliases.update(shell_aliases) | |
|
159 | aliases.update(shell_aliases) # type: ignore[arg-type] | |
|
160 | 160 | |
|
161 | 161 | #----------------------------------------------------------------------------- |
|
162 | 162 | # Main classes and functions |
|
163 | 163 | #----------------------------------------------------------------------------- |
|
164 | 164 | |
|
165 | 165 | |
|
166 | 166 | class LocateIPythonApp(BaseIPythonApplication): |
|
167 | 167 | description = """print the path to the IPython dir""" |
|
168 | 168 | subcommands = dict( |
|
169 | 169 | profile=('IPython.core.profileapp.ProfileLocate', |
|
170 | 170 | "print the path to an IPython profile directory", |
|
171 | 171 | ), |
|
172 | 172 | ) |
|
173 | 173 | def start(self): |
|
174 | 174 | if self.subapp is not None: |
|
175 | 175 | return self.subapp.start() |
|
176 | 176 | else: |
|
177 | 177 | print(self.ipython_dir) |
|
178 | 178 | |
|
179 | 179 | |
|
180 | 180 | class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp): |
|
181 | 181 | name = u'ipython' |
|
182 | 182 | description = usage.cl_usage |
|
183 | crash_handler_class = IPAppCrashHandler | |
|
183 | crash_handler_class = IPAppCrashHandler # typing: ignore[assignment] | |
|
184 | 184 | examples = _examples |
|
185 | 185 | |
|
186 | 186 | flags = flags |
|
187 | 187 | aliases = aliases |
|
188 | 188 | classes = List() |
|
189 | 189 | |
|
190 | 190 | interactive_shell_class = Type( |
|
191 | 191 | klass=object, # use default_value otherwise which only allow subclasses. |
|
192 | 192 | default_value=TerminalInteractiveShell, |
|
193 | 193 | help="Class to use to instantiate the TerminalInteractiveShell object. Useful for custom Frontends" |
|
194 | 194 | ).tag(config=True) |
|
195 | 195 | |
|
196 | 196 | @default('classes') |
|
197 | 197 | def _classes_default(self): |
|
198 | 198 | """This has to be in a method, for TerminalIPythonApp to be available.""" |
|
199 | 199 | return [ |
|
200 | 200 | InteractiveShellApp, # ShellApp comes before TerminalApp, because |
|
201 | 201 | self.__class__, # it will also affect subclasses (e.g. QtConsole) |
|
202 | 202 | TerminalInteractiveShell, |
|
203 | 203 | HistoryManager, |
|
204 | 204 | MagicsManager, |
|
205 | 205 | ProfileDir, |
|
206 | 206 | PlainTextFormatter, |
|
207 | 207 | IPCompleter, |
|
208 | 208 | ScriptMagics, |
|
209 | 209 | LoggingMagics, |
|
210 | 210 | StoreMagics, |
|
211 | 211 | ] |
|
212 | 212 | |
|
213 | 213 | subcommands = dict( |
|
214 | 214 | profile = ("IPython.core.profileapp.ProfileApp", |
|
215 | 215 | "Create and manage IPython profiles." |
|
216 | 216 | ), |
|
217 | 217 | kernel = ("ipykernel.kernelapp.IPKernelApp", |
|
218 | 218 | "Start a kernel without an attached frontend." |
|
219 | 219 | ), |
|
220 | 220 | locate=('IPython.terminal.ipapp.LocateIPythonApp', |
|
221 | 221 | LocateIPythonApp.description |
|
222 | 222 | ), |
|
223 | 223 | history=('IPython.core.historyapp.HistoryApp', |
|
224 | 224 | "Manage the IPython history database." |
|
225 | 225 | ), |
|
226 | 226 | ) |
|
227 | 227 | |
|
228 | 228 | |
|
229 | 229 | # *do* autocreate requested profile, but don't create the config file. |
|
230 | 230 | auto_create=Bool(True) |
|
231 | 231 | # configurables |
|
232 | 232 | quick = Bool(False, |
|
233 | 233 | help="""Start IPython quickly by skipping the loading of config files.""" |
|
234 | 234 | ).tag(config=True) |
|
235 | 235 | @observe('quick') |
|
236 | 236 | def _quick_changed(self, change): |
|
237 | 237 | if change['new']: |
|
238 | 238 | self.load_config_file = lambda *a, **kw: None |
|
239 | 239 | |
|
240 | 240 | display_banner = Bool(True, |
|
241 | 241 | help="Whether to display a banner upon starting IPython." |
|
242 | 242 | ).tag(config=True) |
|
243 | 243 | |
|
244 | 244 | # if there is code of files to run from the cmd line, don't interact |
|
245 | 245 | # unless the --i flag (App.force_interact) is true. |
|
246 | 246 | force_interact = Bool(False, |
|
247 | 247 | help="""If a command or file is given via the command-line, |
|
248 | 248 | e.g. 'ipython foo.py', start an interactive shell after executing the |
|
249 | 249 | file or command.""" |
|
250 | 250 | ).tag(config=True) |
|
251 | 251 | @observe('force_interact') |
|
252 | 252 | def _force_interact_changed(self, change): |
|
253 | 253 | if change['new']: |
|
254 | 254 | self.interact = True |
|
255 | 255 | |
|
256 | 256 | @observe('file_to_run', 'code_to_run', 'module_to_run') |
|
257 | 257 | def _file_to_run_changed(self, change): |
|
258 | 258 | new = change['new'] |
|
259 | 259 | if new: |
|
260 | 260 | self.something_to_run = True |
|
261 | 261 | if new and not self.force_interact: |
|
262 | 262 | self.interact = False |
|
263 | 263 | |
|
264 | 264 | # internal, not-configurable |
|
265 | 265 | something_to_run=Bool(False) |
|
266 | 266 | |
|
267 | 267 | @catch_config_error |
|
268 | 268 | def initialize(self, argv=None): |
|
269 | 269 | """Do actions after construct, but before starting the app.""" |
|
270 | 270 | super(TerminalIPythonApp, self).initialize(argv) |
|
271 | 271 | if self.subapp is not None: |
|
272 | 272 | # don't bother initializing further, starting subapp |
|
273 | 273 | return |
|
274 | 274 | # print self.extra_args |
|
275 | 275 | if self.extra_args and not self.something_to_run: |
|
276 | 276 | self.file_to_run = self.extra_args[0] |
|
277 | 277 | self.init_path() |
|
278 | 278 | # create the shell |
|
279 | 279 | self.init_shell() |
|
280 | 280 | # and draw the banner |
|
281 | 281 | self.init_banner() |
|
282 | 282 | # Now a variety of things that happen after the banner is printed. |
|
283 | 283 | self.init_gui_pylab() |
|
284 | 284 | self.init_extensions() |
|
285 | 285 | self.init_code() |
|
286 | 286 | |
|
287 | 287 | def init_shell(self): |
|
288 | 288 | """initialize the InteractiveShell instance""" |
|
289 | 289 | # Create an InteractiveShell instance. |
|
290 | 290 | # shell.display_banner should always be False for the terminal |
|
291 | 291 | # based app, because we call shell.show_banner() by hand below |
|
292 | 292 | # so the banner shows *before* all extension loading stuff. |
|
293 | 293 | self.shell = self.interactive_shell_class.instance(parent=self, |
|
294 | 294 | profile_dir=self.profile_dir, |
|
295 | 295 | ipython_dir=self.ipython_dir, user_ns=self.user_ns) |
|
296 | 296 | self.shell.configurables.append(self) |
|
297 | 297 | |
|
298 | 298 | def init_banner(self): |
|
299 | 299 | """optionally display the banner""" |
|
300 | 300 | if self.display_banner and self.interact: |
|
301 | 301 | self.shell.show_banner() |
|
302 | 302 | # Make sure there is a space below the banner. |
|
303 | 303 | if self.log_level <= logging.INFO: print() |
|
304 | 304 | |
|
305 | 305 | def _pylab_changed(self, name, old, new): |
|
306 | 306 | """Replace --pylab='inline' with --pylab='auto'""" |
|
307 | 307 | if new == 'inline': |
|
308 | 308 | warnings.warn("'inline' not available as pylab backend, " |
|
309 | 309 | "using 'auto' instead.") |
|
310 | 310 | self.pylab = 'auto' |
|
311 | 311 | |
|
312 | 312 | def start(self): |
|
313 | 313 | if self.subapp is not None: |
|
314 | 314 | return self.subapp.start() |
|
315 | 315 | # perform any prexec steps: |
|
316 | 316 | if self.interact: |
|
317 | 317 | self.log.debug("Starting IPython's mainloop...") |
|
318 | 318 | self.shell.mainloop() |
|
319 | 319 | else: |
|
320 | 320 | self.log.debug("IPython not interactive...") |
|
321 | 321 | self.shell.restore_term_title() |
|
322 | 322 | if not self.shell.last_execution_succeeded: |
|
323 | 323 | sys.exit(1) |
|
324 | 324 | |
|
325 | 325 | def load_default_config(ipython_dir=None): |
|
326 | 326 | """Load the default config file from the default ipython_dir. |
|
327 | 327 | |
|
328 | 328 | This is useful for embedded shells. |
|
329 | 329 | """ |
|
330 | 330 | if ipython_dir is None: |
|
331 | 331 | ipython_dir = get_ipython_dir() |
|
332 | 332 | |
|
333 | 333 | profile_dir = os.path.join(ipython_dir, 'profile_default') |
|
334 | 334 | app = TerminalIPythonApp() |
|
335 | 335 | app.config_file_paths.append(profile_dir) |
|
336 | 336 | app.load_config_file() |
|
337 | 337 | return app.config |
|
338 | 338 | |
|
339 | 339 | launch_new_instance = TerminalIPythonApp.launch_instance |
|
340 | 340 | |
|
341 | 341 | |
|
342 | 342 | if __name__ == '__main__': |
|
343 | 343 | launch_new_instance() |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: file was removed | |
This diff has been collapsed as it changes many lines, (608 lines changed) Show them Hide them |
|
1 | NO CONTENT: file was removed | |
The requested commit or file is too big and content was truncated. Show full diff |
General Comments 0
You need to be logged in to leave comments.
Login now