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 |
|
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 |
|
NO CONTENT: new file 100644, binary diff hidden |
1 | NO CONTENT: new file 100644 |
|
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 | name: Run MyPy |
|
1 | name: Run MyPy | |
2 |
|
2 | |||
3 | on: |
|
3 | on: | |
4 | push: |
|
4 | push: | |
5 | branches: [ main, 7.x] |
|
5 | branches: [ main, 7.x] | |
6 | pull_request: |
|
6 | pull_request: | |
7 | branches: [ main, 7.x] |
|
7 | branches: [ main, 7.x] | |
8 |
|
8 | |||
9 | permissions: |
|
9 | permissions: | |
10 | contents: read |
|
10 | contents: read | |
11 |
|
11 | |||
12 | jobs: |
|
12 | jobs: | |
13 | build: |
|
13 | build: | |
14 |
|
14 | |||
15 | runs-on: ubuntu-latest |
|
15 | runs-on: ubuntu-latest | |
16 | strategy: |
|
16 | strategy: | |
17 | matrix: |
|
17 | matrix: | |
18 | python-version: ["3.x"] |
|
18 | python-version: ["3.x"] | |
19 |
|
19 | |||
20 | steps: |
|
20 | steps: | |
21 | - uses: actions/checkout@v3 |
|
21 | - uses: actions/checkout@v3 | |
22 | - name: Set up Python ${{ matrix.python-version }} |
|
22 | - name: Set up Python ${{ matrix.python-version }} | |
23 | uses: actions/setup-python@v4 |
|
23 | uses: actions/setup-python@v4 | |
24 | with: |
|
24 | with: | |
25 | python-version: ${{ matrix.python-version }} |
|
25 | python-version: ${{ matrix.python-version }} | |
26 | - name: Install dependencies |
|
26 | - name: Install dependencies | |
27 | run: | |
|
27 | run: | | |
28 | python -m pip install --upgrade pip |
|
28 | python -m pip install --upgrade pip | |
29 | pip install mypy pyflakes flake8 |
|
29 | pip install mypy pyflakes flake8 | |
30 | - name: Lint with mypy |
|
30 | - name: Lint with mypy | |
31 | run: | |
|
31 | run: | | |
|
32 | set -e | |||
32 | mypy -p IPython.terminal |
|
33 | mypy -p IPython.terminal | |
33 | mypy -p IPython.core.magics |
|
34 | mypy -p IPython.core.magics | |
34 | mypy -p IPython.core.guarded_eval |
|
35 | mypy -p IPython.core.guarded_eval | |
35 | mypy -p IPython.core.completer |
|
36 | mypy -p IPython.core.completer | |
36 | - name: Lint with pyflakes |
|
37 | - name: Lint with pyflakes | |
37 | run: | |
|
38 | run: | | |
|
39 | set -e | |||
38 | flake8 IPython/core/magics/script.py |
|
40 | flake8 IPython/core/magics/script.py | |
39 | flake8 IPython/core/magics/packaging.py |
|
41 | flake8 IPython/core/magics/packaging.py |
@@ -1,489 +1,488 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | """ |
|
2 | """ | |
3 | An application for IPython. |
|
3 | An application for IPython. | |
4 |
|
4 | |||
5 | All top-level applications should use the classes in this module for |
|
5 | All top-level applications should use the classes in this module for | |
6 | handling configuration and creating configurables. |
|
6 | handling configuration and creating configurables. | |
7 |
|
7 | |||
8 | The job of an :class:`Application` is to create the master configuration |
|
8 | The job of an :class:`Application` is to create the master configuration | |
9 | object and then create the configurable objects, passing the config to them. |
|
9 | object and then create the configurable objects, passing the config to them. | |
10 | """ |
|
10 | """ | |
11 |
|
11 | |||
12 | # Copyright (c) IPython Development Team. |
|
12 | # Copyright (c) IPython Development Team. | |
13 | # Distributed under the terms of the Modified BSD License. |
|
13 | # Distributed under the terms of the Modified BSD License. | |
14 |
|
14 | |||
15 | import atexit |
|
15 | import atexit | |
16 | from copy import deepcopy |
|
16 | from copy import deepcopy | |
17 | import logging |
|
17 | import logging | |
18 | import os |
|
18 | import os | |
19 | import shutil |
|
19 | import shutil | |
20 | import sys |
|
20 | import sys | |
21 |
|
21 | |||
22 | from pathlib import Path |
|
22 | from pathlib import Path | |
23 |
|
23 | |||
24 | from traitlets.config.application import Application, catch_config_error |
|
24 | from traitlets.config.application import Application, catch_config_error | |
25 | from traitlets.config.loader import ConfigFileNotFound, PyFileConfigLoader |
|
25 | from traitlets.config.loader import ConfigFileNotFound, PyFileConfigLoader | |
26 | from IPython.core import release, crashhandler |
|
26 | from IPython.core import release, crashhandler | |
27 | from IPython.core.profiledir import ProfileDir, ProfileDirError |
|
27 | from IPython.core.profiledir import ProfileDir, ProfileDirError | |
28 | from IPython.paths import get_ipython_dir, get_ipython_package_dir |
|
28 | from IPython.paths import get_ipython_dir, get_ipython_package_dir | |
29 | from IPython.utils.path import ensure_dir_exists |
|
29 | from IPython.utils.path import ensure_dir_exists | |
30 | from traitlets import ( |
|
30 | from traitlets import ( | |
31 | List, Unicode, Type, Bool, Set, Instance, Undefined, |
|
31 | List, Unicode, Type, Bool, Set, Instance, Undefined, | |
32 | default, observe, |
|
32 | default, observe, | |
33 | ) |
|
33 | ) | |
34 |
|
34 | |||
35 | if os.name == "nt": |
|
35 | if os.name == "nt": | |
36 | programdata = os.environ.get("PROGRAMDATA", None) |
|
36 | programdata = os.environ.get("PROGRAMDATA", None) | |
37 | if programdata is not None: |
|
37 | if programdata is not None: | |
38 | SYSTEM_CONFIG_DIRS = [str(Path(programdata) / "ipython")] |
|
38 | SYSTEM_CONFIG_DIRS = [str(Path(programdata) / "ipython")] | |
39 | else: # PROGRAMDATA is not defined by default on XP. |
|
39 | else: # PROGRAMDATA is not defined by default on XP. | |
40 | SYSTEM_CONFIG_DIRS = [] |
|
40 | SYSTEM_CONFIG_DIRS = [] | |
41 | else: |
|
41 | else: | |
42 | SYSTEM_CONFIG_DIRS = [ |
|
42 | SYSTEM_CONFIG_DIRS = [ | |
43 | "/usr/local/etc/ipython", |
|
43 | "/usr/local/etc/ipython", | |
44 | "/etc/ipython", |
|
44 | "/etc/ipython", | |
45 | ] |
|
45 | ] | |
46 |
|
46 | |||
47 |
|
47 | |||
48 | ENV_CONFIG_DIRS = [] |
|
48 | ENV_CONFIG_DIRS = [] | |
49 | _env_config_dir = os.path.join(sys.prefix, 'etc', 'ipython') |
|
49 | _env_config_dir = os.path.join(sys.prefix, 'etc', 'ipython') | |
50 | if _env_config_dir not in SYSTEM_CONFIG_DIRS: |
|
50 | if _env_config_dir not in SYSTEM_CONFIG_DIRS: | |
51 | # only add ENV_CONFIG if sys.prefix is not already included |
|
51 | # only add ENV_CONFIG if sys.prefix is not already included | |
52 | ENV_CONFIG_DIRS.append(_env_config_dir) |
|
52 | ENV_CONFIG_DIRS.append(_env_config_dir) | |
53 |
|
53 | |||
54 |
|
54 | |||
55 | _envvar = os.environ.get('IPYTHON_SUPPRESS_CONFIG_ERRORS') |
|
55 | _envvar = os.environ.get('IPYTHON_SUPPRESS_CONFIG_ERRORS') | |
56 | if _envvar in {None, ''}: |
|
56 | if _envvar in {None, ''}: | |
57 | IPYTHON_SUPPRESS_CONFIG_ERRORS = None |
|
57 | IPYTHON_SUPPRESS_CONFIG_ERRORS = None | |
58 | else: |
|
58 | else: | |
59 | if _envvar.lower() in {'1','true'}: |
|
59 | if _envvar.lower() in {'1','true'}: | |
60 | IPYTHON_SUPPRESS_CONFIG_ERRORS = True |
|
60 | IPYTHON_SUPPRESS_CONFIG_ERRORS = True | |
61 | elif _envvar.lower() in {'0','false'} : |
|
61 | elif _envvar.lower() in {'0','false'} : | |
62 | IPYTHON_SUPPRESS_CONFIG_ERRORS = False |
|
62 | IPYTHON_SUPPRESS_CONFIG_ERRORS = False | |
63 | else: |
|
63 | else: | |
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 ) |
|
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 | # aliases and flags |
|
66 | # aliases and flags | |
67 |
|
67 | |||
68 | base_aliases = {} |
|
68 | base_aliases = {} | |
69 | if isinstance(Application.aliases, dict): |
|
69 | if isinstance(Application.aliases, dict): | |
70 | # traitlets 5 |
|
70 | # traitlets 5 | |
71 | base_aliases.update(Application.aliases) |
|
71 | base_aliases.update(Application.aliases) | |
72 | base_aliases.update( |
|
72 | base_aliases.update( | |
73 | { |
|
73 | { | |
74 | "profile-dir": "ProfileDir.location", |
|
74 | "profile-dir": "ProfileDir.location", | |
75 | "profile": "BaseIPythonApplication.profile", |
|
75 | "profile": "BaseIPythonApplication.profile", | |
76 | "ipython-dir": "BaseIPythonApplication.ipython_dir", |
|
76 | "ipython-dir": "BaseIPythonApplication.ipython_dir", | |
77 | "log-level": "Application.log_level", |
|
77 | "log-level": "Application.log_level", | |
78 | "config": "BaseIPythonApplication.extra_config_file", |
|
78 | "config": "BaseIPythonApplication.extra_config_file", | |
79 | } |
|
79 | } | |
80 | ) |
|
80 | ) | |
81 |
|
81 | |||
82 | base_flags = dict() |
|
82 | base_flags = dict() | |
83 | if isinstance(Application.flags, dict): |
|
83 | if isinstance(Application.flags, dict): | |
84 | # traitlets 5 |
|
84 | # traitlets 5 | |
85 | base_flags.update(Application.flags) |
|
85 | base_flags.update(Application.flags) | |
86 | base_flags.update( |
|
86 | base_flags.update( | |
87 | dict( |
|
87 | dict( | |
88 | debug=( |
|
88 | debug=( | |
89 | {"Application": {"log_level": logging.DEBUG}}, |
|
89 | {"Application": {"log_level": logging.DEBUG}}, | |
90 | "set log level to logging.DEBUG (maximize logging output)", |
|
90 | "set log level to logging.DEBUG (maximize logging output)", | |
91 | ), |
|
91 | ), | |
92 | quiet=( |
|
92 | quiet=( | |
93 | {"Application": {"log_level": logging.CRITICAL}}, |
|
93 | {"Application": {"log_level": logging.CRITICAL}}, | |
94 | "set log level to logging.CRITICAL (minimize logging output)", |
|
94 | "set log level to logging.CRITICAL (minimize logging output)", | |
95 | ), |
|
95 | ), | |
96 | init=( |
|
96 | init=( | |
97 | { |
|
97 | { | |
98 | "BaseIPythonApplication": { |
|
98 | "BaseIPythonApplication": { | |
99 | "copy_config_files": True, |
|
99 | "copy_config_files": True, | |
100 | "auto_create": True, |
|
100 | "auto_create": True, | |
101 | } |
|
101 | } | |
102 | }, |
|
102 | }, | |
103 | """Initialize profile with default config files. This is equivalent |
|
103 | """Initialize profile with default config files. This is equivalent | |
104 | to running `ipython profile create <profile>` prior to startup. |
|
104 | to running `ipython profile create <profile>` prior to startup. | |
105 | """, |
|
105 | """, | |
106 | ), |
|
106 | ), | |
107 | ) |
|
107 | ) | |
108 | ) |
|
108 | ) | |
109 |
|
109 | |||
110 |
|
110 | |||
111 | class ProfileAwareConfigLoader(PyFileConfigLoader): |
|
111 | class ProfileAwareConfigLoader(PyFileConfigLoader): | |
112 | """A Python file config loader that is aware of IPython profiles.""" |
|
112 | """A Python file config loader that is aware of IPython profiles.""" | |
113 | def load_subconfig(self, fname, path=None, profile=None): |
|
113 | def load_subconfig(self, fname, path=None, profile=None): | |
114 | if profile is not None: |
|
114 | if profile is not None: | |
115 | try: |
|
115 | try: | |
116 | profile_dir = ProfileDir.find_profile_dir_by_name( |
|
116 | profile_dir = ProfileDir.find_profile_dir_by_name( | |
117 | get_ipython_dir(), |
|
117 | get_ipython_dir(), | |
118 | profile, |
|
118 | profile, | |
119 | ) |
|
119 | ) | |
120 | except ProfileDirError: |
|
120 | except ProfileDirError: | |
121 | return |
|
121 | return | |
122 | path = profile_dir.location |
|
122 | path = profile_dir.location | |
123 | return super(ProfileAwareConfigLoader, self).load_subconfig(fname, path=path) |
|
123 | return super(ProfileAwareConfigLoader, self).load_subconfig(fname, path=path) | |
124 |
|
124 | |||
125 | class BaseIPythonApplication(Application): |
|
125 | class BaseIPythonApplication(Application): | |
126 |
|
126 | name = "ipython" | ||
127 | name = u'ipython' |
|
127 | description = "IPython: an enhanced interactive Python shell." | |
128 | description = Unicode(u'IPython: an enhanced interactive Python shell.') |
|
|||
129 | version = Unicode(release.version) |
|
128 | version = Unicode(release.version) | |
130 |
|
129 | |||
131 | aliases = base_aliases |
|
130 | aliases = base_aliases | |
132 | flags = base_flags |
|
131 | flags = base_flags | |
133 | classes = List([ProfileDir]) |
|
132 | classes = List([ProfileDir]) | |
134 |
|
133 | |||
135 | # enable `load_subconfig('cfg.py', profile='name')` |
|
134 | # enable `load_subconfig('cfg.py', profile='name')` | |
136 | python_config_loader_class = ProfileAwareConfigLoader |
|
135 | python_config_loader_class = ProfileAwareConfigLoader | |
137 |
|
136 | |||
138 | # Track whether the config_file has changed, |
|
137 | # Track whether the config_file has changed, | |
139 | # because some logic happens only if we aren't using the default. |
|
138 | # because some logic happens only if we aren't using the default. | |
140 | config_file_specified = Set() |
|
139 | config_file_specified = Set() | |
141 |
|
140 | |||
142 | config_file_name = Unicode() |
|
141 | config_file_name = Unicode() | |
143 | @default('config_file_name') |
|
142 | @default('config_file_name') | |
144 | def _config_file_name_default(self): |
|
143 | def _config_file_name_default(self): | |
145 | return self.name.replace('-','_') + u'_config.py' |
|
144 | return self.name.replace('-','_') + u'_config.py' | |
146 | @observe('config_file_name') |
|
145 | @observe('config_file_name') | |
147 | def _config_file_name_changed(self, change): |
|
146 | def _config_file_name_changed(self, change): | |
148 | if change['new'] != change['old']: |
|
147 | if change['new'] != change['old']: | |
149 | self.config_file_specified.add(change['new']) |
|
148 | self.config_file_specified.add(change['new']) | |
150 |
|
149 | |||
151 | # The directory that contains IPython's builtin profiles. |
|
150 | # The directory that contains IPython's builtin profiles. | |
152 | builtin_profile_dir = Unicode( |
|
151 | builtin_profile_dir = Unicode( | |
153 | os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default') |
|
152 | os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default') | |
154 | ) |
|
153 | ) | |
155 |
|
154 | |||
156 | config_file_paths = List(Unicode()) |
|
155 | config_file_paths = List(Unicode()) | |
157 | @default('config_file_paths') |
|
156 | @default('config_file_paths') | |
158 | def _config_file_paths_default(self): |
|
157 | def _config_file_paths_default(self): | |
159 | return [] |
|
158 | return [] | |
160 |
|
159 | |||
161 | extra_config_file = Unicode( |
|
160 | extra_config_file = Unicode( | |
162 | help="""Path to an extra config file to load. |
|
161 | help="""Path to an extra config file to load. | |
163 |
|
162 | |||
164 | If specified, load this config file in addition to any other IPython config. |
|
163 | If specified, load this config file in addition to any other IPython config. | |
165 | """).tag(config=True) |
|
164 | """).tag(config=True) | |
166 | @observe('extra_config_file') |
|
165 | @observe('extra_config_file') | |
167 | def _extra_config_file_changed(self, change): |
|
166 | def _extra_config_file_changed(self, change): | |
168 | old = change['old'] |
|
167 | old = change['old'] | |
169 | new = change['new'] |
|
168 | new = change['new'] | |
170 | try: |
|
169 | try: | |
171 | self.config_files.remove(old) |
|
170 | self.config_files.remove(old) | |
172 | except ValueError: |
|
171 | except ValueError: | |
173 | pass |
|
172 | pass | |
174 | self.config_file_specified.add(new) |
|
173 | self.config_file_specified.add(new) | |
175 | self.config_files.append(new) |
|
174 | self.config_files.append(new) | |
176 |
|
175 | |||
177 | profile = Unicode(u'default', |
|
176 | profile = Unicode(u'default', | |
178 | help="""The IPython profile to use.""" |
|
177 | help="""The IPython profile to use.""" | |
179 | ).tag(config=True) |
|
178 | ).tag(config=True) | |
180 |
|
179 | |||
181 | @observe('profile') |
|
180 | @observe('profile') | |
182 | def _profile_changed(self, change): |
|
181 | def _profile_changed(self, change): | |
183 | self.builtin_profile_dir = os.path.join( |
|
182 | self.builtin_profile_dir = os.path.join( | |
184 | get_ipython_package_dir(), u'config', u'profile', change['new'] |
|
183 | get_ipython_package_dir(), u'config', u'profile', change['new'] | |
185 | ) |
|
184 | ) | |
186 |
|
185 | |||
187 | add_ipython_dir_to_sys_path = Bool( |
|
186 | add_ipython_dir_to_sys_path = Bool( | |
188 | False, |
|
187 | False, | |
189 | """Should the IPython profile directory be added to sys path ? |
|
188 | """Should the IPython profile directory be added to sys path ? | |
190 |
|
189 | |||
191 | This option was non-existing before IPython 8.0, and ipython_dir was added to |
|
190 | This option was non-existing before IPython 8.0, and ipython_dir was added to | |
192 | sys path to allow import of extensions present there. This was historical |
|
191 | sys path to allow import of extensions present there. This was historical | |
193 | baggage from when pip did not exist. This now default to false, |
|
192 | baggage from when pip did not exist. This now default to false, | |
194 | but can be set to true for legacy reasons. |
|
193 | but can be set to true for legacy reasons. | |
195 | """, |
|
194 | """, | |
196 | ).tag(config=True) |
|
195 | ).tag(config=True) | |
197 |
|
196 | |||
198 | ipython_dir = Unicode( |
|
197 | ipython_dir = Unicode( | |
199 | help=""" |
|
198 | help=""" | |
200 | The name of the IPython directory. This directory is used for logging |
|
199 | The name of the IPython directory. This directory is used for logging | |
201 | configuration (through profiles), history storage, etc. The default |
|
200 | configuration (through profiles), history storage, etc. The default | |
202 | is usually $HOME/.ipython. This option can also be specified through |
|
201 | is usually $HOME/.ipython. This option can also be specified through | |
203 | the environment variable IPYTHONDIR. |
|
202 | the environment variable IPYTHONDIR. | |
204 | """ |
|
203 | """ | |
205 | ).tag(config=True) |
|
204 | ).tag(config=True) | |
206 | @default('ipython_dir') |
|
205 | @default('ipython_dir') | |
207 | def _ipython_dir_default(self): |
|
206 | def _ipython_dir_default(self): | |
208 | d = get_ipython_dir() |
|
207 | d = get_ipython_dir() | |
209 | self._ipython_dir_changed({ |
|
208 | self._ipython_dir_changed({ | |
210 | 'name': 'ipython_dir', |
|
209 | 'name': 'ipython_dir', | |
211 | 'old': d, |
|
210 | 'old': d, | |
212 | 'new': d, |
|
211 | 'new': d, | |
213 | }) |
|
212 | }) | |
214 | return d |
|
213 | return d | |
215 |
|
214 | |||
216 | _in_init_profile_dir = False |
|
215 | _in_init_profile_dir = False | |
217 | profile_dir = Instance(ProfileDir, allow_none=True) |
|
216 | profile_dir = Instance(ProfileDir, allow_none=True) | |
218 | @default('profile_dir') |
|
217 | @default('profile_dir') | |
219 | def _profile_dir_default(self): |
|
218 | def _profile_dir_default(self): | |
220 | # avoid recursion |
|
219 | # avoid recursion | |
221 | if self._in_init_profile_dir: |
|
220 | if self._in_init_profile_dir: | |
222 | return |
|
221 | return | |
223 | # profile_dir requested early, force initialization |
|
222 | # profile_dir requested early, force initialization | |
224 | self.init_profile_dir() |
|
223 | self.init_profile_dir() | |
225 | return self.profile_dir |
|
224 | return self.profile_dir | |
226 |
|
225 | |||
227 | overwrite = Bool(False, |
|
226 | overwrite = Bool(False, | |
228 | help="""Whether to overwrite existing config files when copying""" |
|
227 | help="""Whether to overwrite existing config files when copying""" | |
229 | ).tag(config=True) |
|
228 | ).tag(config=True) | |
230 | auto_create = Bool(False, |
|
229 | auto_create = Bool(False, | |
231 | help="""Whether to create profile dir if it doesn't exist""" |
|
230 | help="""Whether to create profile dir if it doesn't exist""" | |
232 | ).tag(config=True) |
|
231 | ).tag(config=True) | |
233 |
|
232 | |||
234 | config_files = List(Unicode()) |
|
233 | config_files = List(Unicode()) | |
235 | @default('config_files') |
|
234 | @default('config_files') | |
236 | def _config_files_default(self): |
|
235 | def _config_files_default(self): | |
237 | return [self.config_file_name] |
|
236 | return [self.config_file_name] | |
238 |
|
237 | |||
239 | copy_config_files = Bool(False, |
|
238 | copy_config_files = Bool(False, | |
240 | help="""Whether to install the default config files into the profile dir. |
|
239 | help="""Whether to install the default config files into the profile dir. | |
241 | If a new profile is being created, and IPython contains config files for that |
|
240 | If a new profile is being created, and IPython contains config files for that | |
242 | profile, then they will be staged into the new directory. Otherwise, |
|
241 | profile, then they will be staged into the new directory. Otherwise, | |
243 | default config files will be automatically generated. |
|
242 | default config files will be automatically generated. | |
244 | """).tag(config=True) |
|
243 | """).tag(config=True) | |
245 |
|
244 | |||
246 | verbose_crash = Bool(False, |
|
245 | verbose_crash = Bool(False, | |
247 | help="""Create a massive crash report when IPython encounters what may be an |
|
246 | help="""Create a massive crash report when IPython encounters what may be an | |
248 | internal error. The default is to append a short message to the |
|
247 | internal error. The default is to append a short message to the | |
249 | usual traceback""").tag(config=True) |
|
248 | usual traceback""").tag(config=True) | |
250 |
|
249 | |||
251 | # The class to use as the crash handler. |
|
250 | # The class to use as the crash handler. | |
252 | crash_handler_class = Type(crashhandler.CrashHandler) |
|
251 | crash_handler_class = Type(crashhandler.CrashHandler) | |
253 |
|
252 | |||
254 | @catch_config_error |
|
253 | @catch_config_error | |
255 | def __init__(self, **kwargs): |
|
254 | def __init__(self, **kwargs): | |
256 | super(BaseIPythonApplication, self).__init__(**kwargs) |
|
255 | super(BaseIPythonApplication, self).__init__(**kwargs) | |
257 | # ensure current working directory exists |
|
256 | # ensure current working directory exists | |
258 | try: |
|
257 | try: | |
259 | os.getcwd() |
|
258 | os.getcwd() | |
260 | except: |
|
259 | except: | |
261 | # exit if cwd doesn't exist |
|
260 | # exit if cwd doesn't exist | |
262 | self.log.error("Current working directory doesn't exist.") |
|
261 | self.log.error("Current working directory doesn't exist.") | |
263 | self.exit(1) |
|
262 | self.exit(1) | |
264 |
|
263 | |||
265 | #------------------------------------------------------------------------- |
|
264 | #------------------------------------------------------------------------- | |
266 | # Various stages of Application creation |
|
265 | # Various stages of Application creation | |
267 | #------------------------------------------------------------------------- |
|
266 | #------------------------------------------------------------------------- | |
268 |
|
267 | |||
269 | def init_crash_handler(self): |
|
268 | def init_crash_handler(self): | |
270 | """Create a crash handler, typically setting sys.excepthook to it.""" |
|
269 | """Create a crash handler, typically setting sys.excepthook to it.""" | |
271 | self.crash_handler = self.crash_handler_class(self) |
|
270 | self.crash_handler = self.crash_handler_class(self) | |
272 | sys.excepthook = self.excepthook |
|
271 | sys.excepthook = self.excepthook | |
273 | def unset_crashhandler(): |
|
272 | def unset_crashhandler(): | |
274 | sys.excepthook = sys.__excepthook__ |
|
273 | sys.excepthook = sys.__excepthook__ | |
275 | atexit.register(unset_crashhandler) |
|
274 | atexit.register(unset_crashhandler) | |
276 |
|
275 | |||
277 | def excepthook(self, etype, evalue, tb): |
|
276 | def excepthook(self, etype, evalue, tb): | |
278 | """this is sys.excepthook after init_crashhandler |
|
277 | """this is sys.excepthook after init_crashhandler | |
279 |
|
278 | |||
280 | set self.verbose_crash=True to use our full crashhandler, instead of |
|
279 | set self.verbose_crash=True to use our full crashhandler, instead of | |
281 | a regular traceback with a short message (crash_handler_lite) |
|
280 | a regular traceback with a short message (crash_handler_lite) | |
282 | """ |
|
281 | """ | |
283 |
|
282 | |||
284 | if self.verbose_crash: |
|
283 | if self.verbose_crash: | |
285 | return self.crash_handler(etype, evalue, tb) |
|
284 | return self.crash_handler(etype, evalue, tb) | |
286 | else: |
|
285 | else: | |
287 | return crashhandler.crash_handler_lite(etype, evalue, tb) |
|
286 | return crashhandler.crash_handler_lite(etype, evalue, tb) | |
288 |
|
287 | |||
289 | @observe('ipython_dir') |
|
288 | @observe('ipython_dir') | |
290 | def _ipython_dir_changed(self, change): |
|
289 | def _ipython_dir_changed(self, change): | |
291 | old = change['old'] |
|
290 | old = change['old'] | |
292 | new = change['new'] |
|
291 | new = change['new'] | |
293 | if old is not Undefined: |
|
292 | if old is not Undefined: | |
294 | str_old = os.path.abspath(old) |
|
293 | str_old = os.path.abspath(old) | |
295 | if str_old in sys.path: |
|
294 | if str_old in sys.path: | |
296 | sys.path.remove(str_old) |
|
295 | sys.path.remove(str_old) | |
297 | if self.add_ipython_dir_to_sys_path: |
|
296 | if self.add_ipython_dir_to_sys_path: | |
298 | str_path = os.path.abspath(new) |
|
297 | str_path = os.path.abspath(new) | |
299 | sys.path.append(str_path) |
|
298 | sys.path.append(str_path) | |
300 | ensure_dir_exists(new) |
|
299 | ensure_dir_exists(new) | |
301 | readme = os.path.join(new, "README") |
|
300 | readme = os.path.join(new, "README") | |
302 | readme_src = os.path.join( |
|
301 | readme_src = os.path.join( | |
303 | get_ipython_package_dir(), "config", "profile", "README" |
|
302 | get_ipython_package_dir(), "config", "profile", "README" | |
304 | ) |
|
303 | ) | |
305 | if not os.path.exists(readme) and os.path.exists(readme_src): |
|
304 | if not os.path.exists(readme) and os.path.exists(readme_src): | |
306 | shutil.copy(readme_src, readme) |
|
305 | shutil.copy(readme_src, readme) | |
307 | for d in ("extensions", "nbextensions"): |
|
306 | for d in ("extensions", "nbextensions"): | |
308 | path = os.path.join(new, d) |
|
307 | path = os.path.join(new, d) | |
309 | try: |
|
308 | try: | |
310 | ensure_dir_exists(path) |
|
309 | ensure_dir_exists(path) | |
311 | except OSError as e: |
|
310 | except OSError as e: | |
312 | # this will not be EEXIST |
|
311 | # this will not be EEXIST | |
313 | self.log.error("couldn't create path %s: %s", path, e) |
|
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 | def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS): |
|
315 | def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS): | |
317 | """Load the config file. |
|
316 | """Load the config file. | |
318 |
|
317 | |||
319 | By default, errors in loading config are handled, and a warning |
|
318 | By default, errors in loading config are handled, and a warning | |
320 | printed on screen. For testing, the suppress_errors option is set |
|
319 | printed on screen. For testing, the suppress_errors option is set | |
321 | to False, so errors will make tests fail. |
|
320 | to False, so errors will make tests fail. | |
322 |
|
321 | |||
323 | `suppress_errors` default value is to be `None` in which case the |
|
322 | `suppress_errors` default value is to be `None` in which case the | |
324 | behavior default to the one of `traitlets.Application`. |
|
323 | behavior default to the one of `traitlets.Application`. | |
325 |
|
324 | |||
326 | The default value can be set : |
|
325 | The default value can be set : | |
327 | - to `False` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '0', or 'false' (case insensitive). |
|
326 | - to `False` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '0', or 'false' (case insensitive). | |
328 | - to `True` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '1' or 'true' (case insensitive). |
|
327 | - to `True` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '1' or 'true' (case insensitive). | |
329 | - to `None` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '' (empty string) or leaving it unset. |
|
328 | - to `None` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '' (empty string) or leaving it unset. | |
330 |
|
329 | |||
331 | Any other value are invalid, and will make IPython exit with a non-zero return code. |
|
330 | Any other value are invalid, and will make IPython exit with a non-zero return code. | |
332 | """ |
|
331 | """ | |
333 |
|
332 | |||
334 |
|
333 | |||
335 | self.log.debug("Searching path %s for config files", self.config_file_paths) |
|
334 | self.log.debug("Searching path %s for config files", self.config_file_paths) | |
336 | base_config = 'ipython_config.py' |
|
335 | base_config = 'ipython_config.py' | |
337 | self.log.debug("Attempting to load config file: %s" % |
|
336 | self.log.debug("Attempting to load config file: %s" % | |
338 | base_config) |
|
337 | base_config) | |
339 | try: |
|
338 | try: | |
340 | if suppress_errors is not None: |
|
339 | if suppress_errors is not None: | |
341 | old_value = Application.raise_config_file_errors |
|
340 | old_value = Application.raise_config_file_errors | |
342 | Application.raise_config_file_errors = not suppress_errors; |
|
341 | Application.raise_config_file_errors = not suppress_errors; | |
343 | Application.load_config_file( |
|
342 | Application.load_config_file( | |
344 | self, |
|
343 | self, | |
345 | base_config, |
|
344 | base_config, | |
346 | path=self.config_file_paths |
|
345 | path=self.config_file_paths | |
347 | ) |
|
346 | ) | |
348 | except ConfigFileNotFound: |
|
347 | except ConfigFileNotFound: | |
349 | # ignore errors loading parent |
|
348 | # ignore errors loading parent | |
350 | self.log.debug("Config file %s not found", base_config) |
|
349 | self.log.debug("Config file %s not found", base_config) | |
351 | pass |
|
350 | pass | |
352 | if suppress_errors is not None: |
|
351 | if suppress_errors is not None: | |
353 | Application.raise_config_file_errors = old_value |
|
352 | Application.raise_config_file_errors = old_value | |
354 |
|
353 | |||
355 | for config_file_name in self.config_files: |
|
354 | for config_file_name in self.config_files: | |
356 | if not config_file_name or config_file_name == base_config: |
|
355 | if not config_file_name or config_file_name == base_config: | |
357 | continue |
|
356 | continue | |
358 | self.log.debug("Attempting to load config file: %s" % |
|
357 | self.log.debug("Attempting to load config file: %s" % | |
359 | self.config_file_name) |
|
358 | self.config_file_name) | |
360 | try: |
|
359 | try: | |
361 | Application.load_config_file( |
|
360 | Application.load_config_file( | |
362 | self, |
|
361 | self, | |
363 | config_file_name, |
|
362 | config_file_name, | |
364 | path=self.config_file_paths |
|
363 | path=self.config_file_paths | |
365 | ) |
|
364 | ) | |
366 | except ConfigFileNotFound: |
|
365 | except ConfigFileNotFound: | |
367 | # Only warn if the default config file was NOT being used. |
|
366 | # Only warn if the default config file was NOT being used. | |
368 | if config_file_name in self.config_file_specified: |
|
367 | if config_file_name in self.config_file_specified: | |
369 | msg = self.log.warning |
|
368 | msg = self.log.warning | |
370 | else: |
|
369 | else: | |
371 | msg = self.log.debug |
|
370 | msg = self.log.debug | |
372 | msg("Config file not found, skipping: %s", config_file_name) |
|
371 | msg("Config file not found, skipping: %s", config_file_name) | |
373 | except Exception: |
|
372 | except Exception: | |
374 | # For testing purposes. |
|
373 | # For testing purposes. | |
375 | if not suppress_errors: |
|
374 | if not suppress_errors: | |
376 | raise |
|
375 | raise | |
377 | self.log.warning("Error loading config file: %s" % |
|
376 | self.log.warning("Error loading config file: %s" % | |
378 | self.config_file_name, exc_info=True) |
|
377 | self.config_file_name, exc_info=True) | |
379 |
|
378 | |||
380 | def init_profile_dir(self): |
|
379 | def init_profile_dir(self): | |
381 | """initialize the profile dir""" |
|
380 | """initialize the profile dir""" | |
382 | self._in_init_profile_dir = True |
|
381 | self._in_init_profile_dir = True | |
383 | if self.profile_dir is not None: |
|
382 | if self.profile_dir is not None: | |
384 | # already ran |
|
383 | # already ran | |
385 | return |
|
384 | return | |
386 | if 'ProfileDir.location' not in self.config: |
|
385 | if 'ProfileDir.location' not in self.config: | |
387 | # location not specified, find by profile name |
|
386 | # location not specified, find by profile name | |
388 | try: |
|
387 | try: | |
389 | p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config) |
|
388 | p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config) | |
390 | except ProfileDirError: |
|
389 | except ProfileDirError: | |
391 | # not found, maybe create it (always create default profile) |
|
390 | # not found, maybe create it (always create default profile) | |
392 | if self.auto_create or self.profile == 'default': |
|
391 | if self.auto_create or self.profile == 'default': | |
393 | try: |
|
392 | try: | |
394 | p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config) |
|
393 | p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config) | |
395 | except ProfileDirError: |
|
394 | except ProfileDirError: | |
396 | self.log.fatal("Could not create profile: %r"%self.profile) |
|
395 | self.log.fatal("Could not create profile: %r"%self.profile) | |
397 | self.exit(1) |
|
396 | self.exit(1) | |
398 | else: |
|
397 | else: | |
399 | self.log.info("Created profile dir: %r"%p.location) |
|
398 | self.log.info("Created profile dir: %r"%p.location) | |
400 | else: |
|
399 | else: | |
401 | self.log.fatal("Profile %r not found."%self.profile) |
|
400 | self.log.fatal("Profile %r not found."%self.profile) | |
402 | self.exit(1) |
|
401 | self.exit(1) | |
403 | else: |
|
402 | else: | |
404 |
self.log.debug( |
|
403 | self.log.debug("Using existing profile dir: %r", p.location) | |
405 | else: |
|
404 | else: | |
406 | location = self.config.ProfileDir.location |
|
405 | location = self.config.ProfileDir.location | |
407 | # location is fully specified |
|
406 | # location is fully specified | |
408 | try: |
|
407 | try: | |
409 | p = ProfileDir.find_profile_dir(location, self.config) |
|
408 | p = ProfileDir.find_profile_dir(location, self.config) | |
410 | except ProfileDirError: |
|
409 | except ProfileDirError: | |
411 | # not found, maybe create it |
|
410 | # not found, maybe create it | |
412 | if self.auto_create: |
|
411 | if self.auto_create: | |
413 | try: |
|
412 | try: | |
414 | p = ProfileDir.create_profile_dir(location, self.config) |
|
413 | p = ProfileDir.create_profile_dir(location, self.config) | |
415 | except ProfileDirError: |
|
414 | except ProfileDirError: | |
416 | self.log.fatal("Could not create profile directory: %r"%location) |
|
415 | self.log.fatal("Could not create profile directory: %r"%location) | |
417 | self.exit(1) |
|
416 | self.exit(1) | |
418 | else: |
|
417 | else: | |
419 | self.log.debug("Creating new profile dir: %r"%location) |
|
418 | self.log.debug("Creating new profile dir: %r"%location) | |
420 | else: |
|
419 | else: | |
421 | self.log.fatal("Profile directory %r not found."%location) |
|
420 | self.log.fatal("Profile directory %r not found."%location) | |
422 | self.exit(1) |
|
421 | self.exit(1) | |
423 | else: |
|
422 | else: | |
424 |
self.log.debug( |
|
423 | self.log.debug("Using existing profile dir: %r", p.location) | |
425 | # if profile_dir is specified explicitly, set profile name |
|
424 | # if profile_dir is specified explicitly, set profile name | |
426 | dir_name = os.path.basename(p.location) |
|
425 | dir_name = os.path.basename(p.location) | |
427 | if dir_name.startswith('profile_'): |
|
426 | if dir_name.startswith('profile_'): | |
428 | self.profile = dir_name[8:] |
|
427 | self.profile = dir_name[8:] | |
429 |
|
428 | |||
430 | self.profile_dir = p |
|
429 | self.profile_dir = p | |
431 | self.config_file_paths.append(p.location) |
|
430 | self.config_file_paths.append(p.location) | |
432 | self._in_init_profile_dir = False |
|
431 | self._in_init_profile_dir = False | |
433 |
|
432 | |||
434 | def init_config_files(self): |
|
433 | def init_config_files(self): | |
435 | """[optionally] copy default config files into profile dir.""" |
|
434 | """[optionally] copy default config files into profile dir.""" | |
436 | self.config_file_paths.extend(ENV_CONFIG_DIRS) |
|
435 | self.config_file_paths.extend(ENV_CONFIG_DIRS) | |
437 | self.config_file_paths.extend(SYSTEM_CONFIG_DIRS) |
|
436 | self.config_file_paths.extend(SYSTEM_CONFIG_DIRS) | |
438 | # copy config files |
|
437 | # copy config files | |
439 | path = Path(self.builtin_profile_dir) |
|
438 | path = Path(self.builtin_profile_dir) | |
440 | if self.copy_config_files: |
|
439 | if self.copy_config_files: | |
441 | src = self.profile |
|
440 | src = self.profile | |
442 |
|
441 | |||
443 | cfg = self.config_file_name |
|
442 | cfg = self.config_file_name | |
444 | if path and (path / cfg).exists(): |
|
443 | if path and (path / cfg).exists(): | |
445 | self.log.warning( |
|
444 | self.log.warning( | |
446 | "Staging %r from %s into %r [overwrite=%s]" |
|
445 | "Staging %r from %s into %r [overwrite=%s]" | |
447 | % (cfg, src, self.profile_dir.location, self.overwrite) |
|
446 | % (cfg, src, self.profile_dir.location, self.overwrite) | |
448 | ) |
|
447 | ) | |
449 | self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite) |
|
448 | self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite) | |
450 | else: |
|
449 | else: | |
451 | self.stage_default_config_file() |
|
450 | self.stage_default_config_file() | |
452 | else: |
|
451 | else: | |
453 | # Still stage *bundled* config files, but not generated ones |
|
452 | # Still stage *bundled* config files, but not generated ones | |
454 | # This is necessary for `ipython profile=sympy` to load the profile |
|
453 | # This is necessary for `ipython profile=sympy` to load the profile | |
455 | # on the first go |
|
454 | # on the first go | |
456 | files = path.glob("*.py") |
|
455 | files = path.glob("*.py") | |
457 | for fullpath in files: |
|
456 | for fullpath in files: | |
458 | cfg = fullpath.name |
|
457 | cfg = fullpath.name | |
459 | if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False): |
|
458 | if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False): | |
460 | # file was copied |
|
459 | # file was copied | |
461 | self.log.warning("Staging bundled %s from %s into %r"%( |
|
460 | self.log.warning("Staging bundled %s from %s into %r"%( | |
462 | cfg, self.profile, self.profile_dir.location) |
|
461 | cfg, self.profile, self.profile_dir.location) | |
463 | ) |
|
462 | ) | |
464 |
|
463 | |||
465 |
|
464 | |||
466 | def stage_default_config_file(self): |
|
465 | def stage_default_config_file(self): | |
467 | """auto generate default config file, and stage it into the profile.""" |
|
466 | """auto generate default config file, and stage it into the profile.""" | |
468 | s = self.generate_config_file() |
|
467 | s = self.generate_config_file() | |
469 | config_file = Path(self.profile_dir.location) / self.config_file_name |
|
468 | config_file = Path(self.profile_dir.location) / self.config_file_name | |
470 | if self.overwrite or not config_file.exists(): |
|
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 | config_file.write_text(s, encoding="utf-8") |
|
471 | config_file.write_text(s, encoding="utf-8") | |
473 |
|
472 | |||
474 | @catch_config_error |
|
473 | @catch_config_error | |
475 | def initialize(self, argv=None): |
|
474 | def initialize(self, argv=None): | |
476 | # don't hook up crash handler before parsing command-line |
|
475 | # don't hook up crash handler before parsing command-line | |
477 | self.parse_command_line(argv) |
|
476 | self.parse_command_line(argv) | |
478 | self.init_crash_handler() |
|
477 | self.init_crash_handler() | |
479 | if self.subapp is not None: |
|
478 | if self.subapp is not None: | |
480 | # stop here if subapp is taking over |
|
479 | # stop here if subapp is taking over | |
481 | return |
|
480 | return | |
482 | # save a copy of CLI config to re-load after config files |
|
481 | # save a copy of CLI config to re-load after config files | |
483 | # so that it has highest priority |
|
482 | # so that it has highest priority | |
484 | cl_config = deepcopy(self.config) |
|
483 | cl_config = deepcopy(self.config) | |
485 | self.init_profile_dir() |
|
484 | self.init_profile_dir() | |
486 | self.init_config_files() |
|
485 | self.init_config_files() | |
487 | self.load_config_file() |
|
486 | self.load_config_file() | |
488 | # enforce cl-opts override configfile opts: |
|
487 | # enforce cl-opts override configfile opts: | |
489 | self.update_config(cl_config) |
|
488 | self.update_config(cl_config) |
@@ -1,325 +1,331 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | """Displayhook for IPython. |
|
2 | """Displayhook for IPython. | |
3 |
|
3 | |||
4 | This defines a callable class that IPython uses for `sys.displayhook`. |
|
4 | This defines a callable class that IPython uses for `sys.displayhook`. | |
5 | """ |
|
5 | """ | |
6 |
|
6 | |||
7 | # Copyright (c) IPython Development Team. |
|
7 | # Copyright (c) IPython Development Team. | |
8 | # Distributed under the terms of the Modified BSD License. |
|
8 | # Distributed under the terms of the Modified BSD License. | |
9 |
|
9 | |||
10 | import builtins as builtin_mod |
|
10 | import builtins as builtin_mod | |
11 | import sys |
|
11 | import sys | |
12 | import io as _io |
|
12 | import io as _io | |
13 | import tokenize |
|
13 | import tokenize | |
14 |
|
14 | |||
15 | from traitlets.config.configurable import Configurable |
|
15 | from traitlets.config.configurable import Configurable | |
16 | from traitlets import Instance, Float |
|
16 | from traitlets import Instance, Float | |
17 | from warnings import warn |
|
17 | from warnings import warn | |
18 |
|
18 | |||
19 | # TODO: Move the various attributes (cache_size, [others now moved]). Some |
|
19 | # TODO: Move the various attributes (cache_size, [others now moved]). Some | |
20 | # of these are also attributes of InteractiveShell. They should be on ONE object |
|
20 | # of these are also attributes of InteractiveShell. They should be on ONE object | |
21 | # only and the other objects should ask that one object for their values. |
|
21 | # only and the other objects should ask that one object for their values. | |
22 |
|
22 | |||
23 | class DisplayHook(Configurable): |
|
23 | class DisplayHook(Configurable): | |
24 | """The custom IPython displayhook to replace sys.displayhook. |
|
24 | """The custom IPython displayhook to replace sys.displayhook. | |
25 |
|
25 | |||
26 | This class does many things, but the basic idea is that it is a callable |
|
26 | This class does many things, but the basic idea is that it is a callable | |
27 | that gets called anytime user code returns a value. |
|
27 | that gets called anytime user code returns a value. | |
28 | """ |
|
28 | """ | |
29 |
|
29 | |||
30 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', |
|
30 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', | |
31 | allow_none=True) |
|
31 | allow_none=True) | |
32 | exec_result = Instance('IPython.core.interactiveshell.ExecutionResult', |
|
32 | exec_result = Instance('IPython.core.interactiveshell.ExecutionResult', | |
33 | allow_none=True) |
|
33 | allow_none=True) | |
34 | cull_fraction = Float(0.2) |
|
34 | cull_fraction = Float(0.2) | |
35 |
|
35 | |||
36 | def __init__(self, shell=None, cache_size=1000, **kwargs): |
|
36 | def __init__(self, shell=None, cache_size=1000, **kwargs): | |
37 | super(DisplayHook, self).__init__(shell=shell, **kwargs) |
|
37 | super(DisplayHook, self).__init__(shell=shell, **kwargs) | |
38 | cache_size_min = 3 |
|
38 | cache_size_min = 3 | |
39 | if cache_size <= 0: |
|
39 | if cache_size <= 0: | |
40 | self.do_full_cache = 0 |
|
40 | self.do_full_cache = 0 | |
41 | cache_size = 0 |
|
41 | cache_size = 0 | |
42 | elif cache_size < cache_size_min: |
|
42 | elif cache_size < cache_size_min: | |
43 | self.do_full_cache = 0 |
|
43 | self.do_full_cache = 0 | |
44 | cache_size = 0 |
|
44 | cache_size = 0 | |
45 | warn('caching was disabled (min value for cache size is %s).' % |
|
45 | warn('caching was disabled (min value for cache size is %s).' % | |
46 | cache_size_min,stacklevel=3) |
|
46 | cache_size_min,stacklevel=3) | |
47 | else: |
|
47 | else: | |
48 | self.do_full_cache = 1 |
|
48 | self.do_full_cache = 1 | |
49 |
|
49 | |||
50 | self.cache_size = cache_size |
|
50 | self.cache_size = cache_size | |
51 |
|
51 | |||
52 | # we need a reference to the user-level namespace |
|
52 | # we need a reference to the user-level namespace | |
53 | self.shell = shell |
|
53 | self.shell = shell | |
54 |
|
54 | |||
55 | self._,self.__,self.___ = '','','' |
|
55 | self._,self.__,self.___ = '','','' | |
56 |
|
56 | |||
57 | # these are deliberately global: |
|
57 | # these are deliberately global: | |
58 | to_user_ns = {'_':self._,'__':self.__,'___':self.___} |
|
58 | to_user_ns = {'_':self._,'__':self.__,'___':self.___} | |
59 | self.shell.user_ns.update(to_user_ns) |
|
59 | self.shell.user_ns.update(to_user_ns) | |
60 |
|
60 | |||
61 | @property |
|
61 | @property | |
62 | def prompt_count(self): |
|
62 | def prompt_count(self): | |
63 | return self.shell.execution_count |
|
63 | return self.shell.execution_count | |
64 |
|
64 | |||
65 | #------------------------------------------------------------------------- |
|
65 | #------------------------------------------------------------------------- | |
66 | # Methods used in __call__. Override these methods to modify the behavior |
|
66 | # Methods used in __call__. Override these methods to modify the behavior | |
67 | # of the displayhook. |
|
67 | # of the displayhook. | |
68 | #------------------------------------------------------------------------- |
|
68 | #------------------------------------------------------------------------- | |
69 |
|
69 | |||
70 | def check_for_underscore(self): |
|
70 | def check_for_underscore(self): | |
71 | """Check if the user has set the '_' variable by hand.""" |
|
71 | """Check if the user has set the '_' variable by hand.""" | |
72 | # If something injected a '_' variable in __builtin__, delete |
|
72 | # If something injected a '_' variable in __builtin__, delete | |
73 | # ipython's automatic one so we don't clobber that. gettext() in |
|
73 | # ipython's automatic one so we don't clobber that. gettext() in | |
74 | # particular uses _, so we need to stay away from it. |
|
74 | # particular uses _, so we need to stay away from it. | |
75 | if '_' in builtin_mod.__dict__: |
|
75 | if '_' in builtin_mod.__dict__: | |
76 | try: |
|
76 | try: | |
77 | user_value = self.shell.user_ns['_'] |
|
77 | user_value = self.shell.user_ns['_'] | |
78 | if user_value is not self._: |
|
78 | if user_value is not self._: | |
79 | return |
|
79 | return | |
80 | del self.shell.user_ns['_'] |
|
80 | del self.shell.user_ns['_'] | |
81 | except KeyError: |
|
81 | except KeyError: | |
82 | pass |
|
82 | pass | |
83 |
|
83 | |||
84 | def quiet(self): |
|
84 | def quiet(self): | |
85 | """Should we silence the display hook because of ';'?""" |
|
85 | """Should we silence the display hook because of ';'?""" | |
86 | # do not print output if input ends in ';' |
|
86 | # do not print output if input ends in ';' | |
87 |
|
87 | |||
88 | try: |
|
88 | try: | |
89 | cell = self.shell.history_manager.input_hist_parsed[-1] |
|
89 | cell = self.shell.history_manager.input_hist_parsed[-1] | |
90 | except IndexError: |
|
90 | except IndexError: | |
91 | # some uses of ipshellembed may fail here |
|
91 | # some uses of ipshellembed may fail here | |
92 | return False |
|
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 | tokens = list(tokenize.generate_tokens(sio.readline)) |
|
101 | tokens = list(tokenize.generate_tokens(sio.readline)) | |
96 |
|
102 | |||
97 | for token in reversed(tokens): |
|
103 | for token in reversed(tokens): | |
98 | if token[0] in (tokenize.ENDMARKER, tokenize.NL, tokenize.NEWLINE, tokenize.COMMENT): |
|
104 | if token[0] in (tokenize.ENDMARKER, tokenize.NL, tokenize.NEWLINE, tokenize.COMMENT): | |
99 | continue |
|
105 | continue | |
100 | if (token[0] == tokenize.OP) and (token[1] == ';'): |
|
106 | if (token[0] == tokenize.OP) and (token[1] == ';'): | |
101 | return True |
|
107 | return True | |
102 | else: |
|
108 | else: | |
103 | return False |
|
109 | return False | |
104 |
|
110 | |||
105 | def start_displayhook(self): |
|
111 | def start_displayhook(self): | |
106 | """Start the displayhook, initializing resources.""" |
|
112 | """Start the displayhook, initializing resources.""" | |
107 | pass |
|
113 | pass | |
108 |
|
114 | |||
109 | def write_output_prompt(self): |
|
115 | def write_output_prompt(self): | |
110 | """Write the output prompt. |
|
116 | """Write the output prompt. | |
111 |
|
117 | |||
112 | The default implementation simply writes the prompt to |
|
118 | The default implementation simply writes the prompt to | |
113 | ``sys.stdout``. |
|
119 | ``sys.stdout``. | |
114 | """ |
|
120 | """ | |
115 | # Use write, not print which adds an extra space. |
|
121 | # Use write, not print which adds an extra space. | |
116 | sys.stdout.write(self.shell.separate_out) |
|
122 | sys.stdout.write(self.shell.separate_out) | |
117 | outprompt = 'Out[{}]: '.format(self.shell.execution_count) |
|
123 | outprompt = 'Out[{}]: '.format(self.shell.execution_count) | |
118 | if self.do_full_cache: |
|
124 | if self.do_full_cache: | |
119 | sys.stdout.write(outprompt) |
|
125 | sys.stdout.write(outprompt) | |
120 |
|
126 | |||
121 | def compute_format_data(self, result): |
|
127 | def compute_format_data(self, result): | |
122 | """Compute format data of the object to be displayed. |
|
128 | """Compute format data of the object to be displayed. | |
123 |
|
129 | |||
124 | The format data is a generalization of the :func:`repr` of an object. |
|
130 | The format data is a generalization of the :func:`repr` of an object. | |
125 | In the default implementation the format data is a :class:`dict` of |
|
131 | In the default implementation the format data is a :class:`dict` of | |
126 | key value pair where the keys are valid MIME types and the values |
|
132 | key value pair where the keys are valid MIME types and the values | |
127 | are JSON'able data structure containing the raw data for that MIME |
|
133 | are JSON'able data structure containing the raw data for that MIME | |
128 | type. It is up to frontends to determine pick a MIME to to use and |
|
134 | type. It is up to frontends to determine pick a MIME to to use and | |
129 | display that data in an appropriate manner. |
|
135 | display that data in an appropriate manner. | |
130 |
|
136 | |||
131 | This method only computes the format data for the object and should |
|
137 | This method only computes the format data for the object and should | |
132 | NOT actually print or write that to a stream. |
|
138 | NOT actually print or write that to a stream. | |
133 |
|
139 | |||
134 | Parameters |
|
140 | Parameters | |
135 | ---------- |
|
141 | ---------- | |
136 | result : object |
|
142 | result : object | |
137 | The Python object passed to the display hook, whose format will be |
|
143 | The Python object passed to the display hook, whose format will be | |
138 | computed. |
|
144 | computed. | |
139 |
|
145 | |||
140 | Returns |
|
146 | Returns | |
141 | ------- |
|
147 | ------- | |
142 | (format_dict, md_dict) : dict |
|
148 | (format_dict, md_dict) : dict | |
143 | format_dict is a :class:`dict` whose keys are valid MIME types and values are |
|
149 | format_dict is a :class:`dict` whose keys are valid MIME types and values are | |
144 | JSON'able raw data for that MIME type. It is recommended that |
|
150 | JSON'able raw data for that MIME type. It is recommended that | |
145 | all return values of this should always include the "text/plain" |
|
151 | all return values of this should always include the "text/plain" | |
146 | MIME type representation of the object. |
|
152 | MIME type representation of the object. | |
147 | md_dict is a :class:`dict` with the same MIME type keys |
|
153 | md_dict is a :class:`dict` with the same MIME type keys | |
148 | of metadata associated with each output. |
|
154 | of metadata associated with each output. | |
149 |
|
155 | |||
150 | """ |
|
156 | """ | |
151 | return self.shell.display_formatter.format(result) |
|
157 | return self.shell.display_formatter.format(result) | |
152 |
|
158 | |||
153 | # This can be set to True by the write_output_prompt method in a subclass |
|
159 | # This can be set to True by the write_output_prompt method in a subclass | |
154 | prompt_end_newline = False |
|
160 | prompt_end_newline = False | |
155 |
|
161 | |||
156 | def write_format_data(self, format_dict, md_dict=None) -> None: |
|
162 | def write_format_data(self, format_dict, md_dict=None) -> None: | |
157 | """Write the format data dict to the frontend. |
|
163 | """Write the format data dict to the frontend. | |
158 |
|
164 | |||
159 | This default version of this method simply writes the plain text |
|
165 | This default version of this method simply writes the plain text | |
160 | representation of the object to ``sys.stdout``. Subclasses should |
|
166 | representation of the object to ``sys.stdout``. Subclasses should | |
161 | override this method to send the entire `format_dict` to the |
|
167 | override this method to send the entire `format_dict` to the | |
162 | frontends. |
|
168 | frontends. | |
163 |
|
169 | |||
164 | Parameters |
|
170 | Parameters | |
165 | ---------- |
|
171 | ---------- | |
166 | format_dict : dict |
|
172 | format_dict : dict | |
167 | The format dict for the object passed to `sys.displayhook`. |
|
173 | The format dict for the object passed to `sys.displayhook`. | |
168 | md_dict : dict (optional) |
|
174 | md_dict : dict (optional) | |
169 | The metadata dict to be associated with the display data. |
|
175 | The metadata dict to be associated with the display data. | |
170 | """ |
|
176 | """ | |
171 | if 'text/plain' not in format_dict: |
|
177 | if 'text/plain' not in format_dict: | |
172 | # nothing to do |
|
178 | # nothing to do | |
173 | return |
|
179 | return | |
174 | # We want to print because we want to always make sure we have a |
|
180 | # We want to print because we want to always make sure we have a | |
175 | # newline, even if all the prompt separators are ''. This is the |
|
181 | # newline, even if all the prompt separators are ''. This is the | |
176 | # standard IPython behavior. |
|
182 | # standard IPython behavior. | |
177 | result_repr = format_dict['text/plain'] |
|
183 | result_repr = format_dict['text/plain'] | |
178 | if '\n' in result_repr: |
|
184 | if '\n' in result_repr: | |
179 | # So that multi-line strings line up with the left column of |
|
185 | # So that multi-line strings line up with the left column of | |
180 | # the screen, instead of having the output prompt mess up |
|
186 | # the screen, instead of having the output prompt mess up | |
181 | # their first line. |
|
187 | # their first line. | |
182 | # We use the prompt template instead of the expanded prompt |
|
188 | # We use the prompt template instead of the expanded prompt | |
183 | # because the expansion may add ANSI escapes that will interfere |
|
189 | # because the expansion may add ANSI escapes that will interfere | |
184 | # with our ability to determine whether or not we should add |
|
190 | # with our ability to determine whether or not we should add | |
185 | # a newline. |
|
191 | # a newline. | |
186 | if not self.prompt_end_newline: |
|
192 | if not self.prompt_end_newline: | |
187 | # But avoid extraneous empty lines. |
|
193 | # But avoid extraneous empty lines. | |
188 | result_repr = '\n' + result_repr |
|
194 | result_repr = '\n' + result_repr | |
189 |
|
195 | |||
190 | try: |
|
196 | try: | |
191 | print(result_repr) |
|
197 | print(result_repr) | |
192 | except UnicodeEncodeError: |
|
198 | except UnicodeEncodeError: | |
193 | # If a character is not supported by the terminal encoding replace |
|
199 | # If a character is not supported by the terminal encoding replace | |
194 | # it with its \u or \x representation |
|
200 | # it with its \u or \x representation | |
195 | print(result_repr.encode(sys.stdout.encoding,'backslashreplace').decode(sys.stdout.encoding)) |
|
201 | print(result_repr.encode(sys.stdout.encoding,'backslashreplace').decode(sys.stdout.encoding)) | |
196 |
|
202 | |||
197 | def update_user_ns(self, result): |
|
203 | def update_user_ns(self, result): | |
198 | """Update user_ns with various things like _, __, _1, etc.""" |
|
204 | """Update user_ns with various things like _, __, _1, etc.""" | |
199 |
|
205 | |||
200 | # Avoid recursive reference when displaying _oh/Out |
|
206 | # Avoid recursive reference when displaying _oh/Out | |
201 | if self.cache_size and result is not self.shell.user_ns['_oh']: |
|
207 | if self.cache_size and result is not self.shell.user_ns['_oh']: | |
202 | if len(self.shell.user_ns['_oh']) >= self.cache_size and self.do_full_cache: |
|
208 | if len(self.shell.user_ns['_oh']) >= self.cache_size and self.do_full_cache: | |
203 | self.cull_cache() |
|
209 | self.cull_cache() | |
204 |
|
210 | |||
205 | # Don't overwrite '_' and friends if '_' is in __builtin__ |
|
211 | # Don't overwrite '_' and friends if '_' is in __builtin__ | |
206 | # (otherwise we cause buggy behavior for things like gettext). and |
|
212 | # (otherwise we cause buggy behavior for things like gettext). and | |
207 | # do not overwrite _, __ or ___ if one of these has been assigned |
|
213 | # do not overwrite _, __ or ___ if one of these has been assigned | |
208 | # by the user. |
|
214 | # by the user. | |
209 | update_unders = True |
|
215 | update_unders = True | |
210 | for unders in ['_'*i for i in range(1,4)]: |
|
216 | for unders in ['_'*i for i in range(1,4)]: | |
211 | if not unders in self.shell.user_ns: |
|
217 | if not unders in self.shell.user_ns: | |
212 | continue |
|
218 | continue | |
213 | if getattr(self, unders) is not self.shell.user_ns.get(unders): |
|
219 | if getattr(self, unders) is not self.shell.user_ns.get(unders): | |
214 | update_unders = False |
|
220 | update_unders = False | |
215 |
|
221 | |||
216 | self.___ = self.__ |
|
222 | self.___ = self.__ | |
217 | self.__ = self._ |
|
223 | self.__ = self._ | |
218 | self._ = result |
|
224 | self._ = result | |
219 |
|
225 | |||
220 | if ('_' not in builtin_mod.__dict__) and (update_unders): |
|
226 | if ('_' not in builtin_mod.__dict__) and (update_unders): | |
221 | self.shell.push({'_':self._, |
|
227 | self.shell.push({'_':self._, | |
222 | '__':self.__, |
|
228 | '__':self.__, | |
223 | '___':self.___}, interactive=False) |
|
229 | '___':self.___}, interactive=False) | |
224 |
|
230 | |||
225 | # hackish access to top-level namespace to create _1,_2... dynamically |
|
231 | # hackish access to top-level namespace to create _1,_2... dynamically | |
226 | to_main = {} |
|
232 | to_main = {} | |
227 | if self.do_full_cache: |
|
233 | if self.do_full_cache: | |
228 | new_result = '_%s' % self.prompt_count |
|
234 | new_result = '_%s' % self.prompt_count | |
229 | to_main[new_result] = result |
|
235 | to_main[new_result] = result | |
230 | self.shell.push(to_main, interactive=False) |
|
236 | self.shell.push(to_main, interactive=False) | |
231 | self.shell.user_ns['_oh'][self.prompt_count] = result |
|
237 | self.shell.user_ns['_oh'][self.prompt_count] = result | |
232 |
|
238 | |||
233 | def fill_exec_result(self, result): |
|
239 | def fill_exec_result(self, result): | |
234 | if self.exec_result is not None: |
|
240 | if self.exec_result is not None: | |
235 | self.exec_result.result = result |
|
241 | self.exec_result.result = result | |
236 |
|
242 | |||
237 | def log_output(self, format_dict): |
|
243 | def log_output(self, format_dict): | |
238 | """Log the output.""" |
|
244 | """Log the output.""" | |
239 | if 'text/plain' not in format_dict: |
|
245 | if 'text/plain' not in format_dict: | |
240 | # nothing to do |
|
246 | # nothing to do | |
241 | return |
|
247 | return | |
242 | if self.shell.logger.log_output: |
|
248 | if self.shell.logger.log_output: | |
243 | self.shell.logger.log_write(format_dict['text/plain'], 'output') |
|
249 | self.shell.logger.log_write(format_dict['text/plain'], 'output') | |
244 | self.shell.history_manager.output_hist_reprs[self.prompt_count] = \ |
|
250 | self.shell.history_manager.output_hist_reprs[self.prompt_count] = \ | |
245 | format_dict['text/plain'] |
|
251 | format_dict['text/plain'] | |
246 |
|
252 | |||
247 | def finish_displayhook(self): |
|
253 | def finish_displayhook(self): | |
248 | """Finish up all displayhook activities.""" |
|
254 | """Finish up all displayhook activities.""" | |
249 | sys.stdout.write(self.shell.separate_out2) |
|
255 | sys.stdout.write(self.shell.separate_out2) | |
250 | sys.stdout.flush() |
|
256 | sys.stdout.flush() | |
251 |
|
257 | |||
252 | def __call__(self, result=None): |
|
258 | def __call__(self, result=None): | |
253 | """Printing with history cache management. |
|
259 | """Printing with history cache management. | |
254 |
|
260 | |||
255 | This is invoked every time the interpreter needs to print, and is |
|
261 | This is invoked every time the interpreter needs to print, and is | |
256 | activated by setting the variable sys.displayhook to it. |
|
262 | activated by setting the variable sys.displayhook to it. | |
257 | """ |
|
263 | """ | |
258 | self.check_for_underscore() |
|
264 | self.check_for_underscore() | |
259 | if result is not None and not self.quiet(): |
|
265 | if result is not None and not self.quiet(): | |
260 | self.start_displayhook() |
|
266 | self.start_displayhook() | |
261 | self.write_output_prompt() |
|
267 | self.write_output_prompt() | |
262 | format_dict, md_dict = self.compute_format_data(result) |
|
268 | format_dict, md_dict = self.compute_format_data(result) | |
263 | self.update_user_ns(result) |
|
269 | self.update_user_ns(result) | |
264 | self.fill_exec_result(result) |
|
270 | self.fill_exec_result(result) | |
265 | if format_dict: |
|
271 | if format_dict: | |
266 | self.write_format_data(format_dict, md_dict) |
|
272 | self.write_format_data(format_dict, md_dict) | |
267 | self.log_output(format_dict) |
|
273 | self.log_output(format_dict) | |
268 | self.finish_displayhook() |
|
274 | self.finish_displayhook() | |
269 |
|
275 | |||
270 | def cull_cache(self): |
|
276 | def cull_cache(self): | |
271 | """Output cache is full, cull the oldest entries""" |
|
277 | """Output cache is full, cull the oldest entries""" | |
272 | oh = self.shell.user_ns.get('_oh', {}) |
|
278 | oh = self.shell.user_ns.get('_oh', {}) | |
273 | sz = len(oh) |
|
279 | sz = len(oh) | |
274 | cull_count = max(int(sz * self.cull_fraction), 2) |
|
280 | cull_count = max(int(sz * self.cull_fraction), 2) | |
275 | warn('Output cache limit (currently {sz} entries) hit.\n' |
|
281 | warn('Output cache limit (currently {sz} entries) hit.\n' | |
276 | 'Flushing oldest {cull_count} entries.'.format(sz=sz, cull_count=cull_count)) |
|
282 | 'Flushing oldest {cull_count} entries.'.format(sz=sz, cull_count=cull_count)) | |
277 |
|
283 | |||
278 | for i, n in enumerate(sorted(oh)): |
|
284 | for i, n in enumerate(sorted(oh)): | |
279 | if i >= cull_count: |
|
285 | if i >= cull_count: | |
280 | break |
|
286 | break | |
281 | self.shell.user_ns.pop('_%i' % n, None) |
|
287 | self.shell.user_ns.pop('_%i' % n, None) | |
282 | oh.pop(n, None) |
|
288 | oh.pop(n, None) | |
283 |
|
289 | |||
284 |
|
290 | |||
285 | def flush(self): |
|
291 | def flush(self): | |
286 | if not self.do_full_cache: |
|
292 | if not self.do_full_cache: | |
287 | raise ValueError("You shouldn't have reached the cache flush " |
|
293 | raise ValueError("You shouldn't have reached the cache flush " | |
288 | "if full caching is not enabled!") |
|
294 | "if full caching is not enabled!") | |
289 | # delete auto-generated vars from global namespace |
|
295 | # delete auto-generated vars from global namespace | |
290 |
|
296 | |||
291 | for n in range(1,self.prompt_count + 1): |
|
297 | for n in range(1,self.prompt_count + 1): | |
292 | key = '_'+repr(n) |
|
298 | key = '_'+repr(n) | |
293 | try: |
|
299 | try: | |
294 | del self.shell.user_ns[key] |
|
300 | del self.shell.user_ns[key] | |
295 | except: pass |
|
301 | except: pass | |
296 | # In some embedded circumstances, the user_ns doesn't have the |
|
302 | # In some embedded circumstances, the user_ns doesn't have the | |
297 | # '_oh' key set up. |
|
303 | # '_oh' key set up. | |
298 | oh = self.shell.user_ns.get('_oh', None) |
|
304 | oh = self.shell.user_ns.get('_oh', None) | |
299 | if oh is not None: |
|
305 | if oh is not None: | |
300 | oh.clear() |
|
306 | oh.clear() | |
301 |
|
307 | |||
302 | # Release our own references to objects: |
|
308 | # Release our own references to objects: | |
303 | self._, self.__, self.___ = '', '', '' |
|
309 | self._, self.__, self.___ = '', '', '' | |
304 |
|
310 | |||
305 | if '_' not in builtin_mod.__dict__: |
|
311 | if '_' not in builtin_mod.__dict__: | |
306 | self.shell.user_ns.update({'_':self._,'__':self.__,'___':self.___}) |
|
312 | self.shell.user_ns.update({'_':self._,'__':self.__,'___':self.___}) | |
307 | import gc |
|
313 | import gc | |
308 | # TODO: Is this really needed? |
|
314 | # TODO: Is this really needed? | |
309 | # IronPython blocks here forever |
|
315 | # IronPython blocks here forever | |
310 | if sys.platform != "cli": |
|
316 | if sys.platform != "cli": | |
311 | gc.collect() |
|
317 | gc.collect() | |
312 |
|
318 | |||
313 |
|
319 | |||
314 | class CapturingDisplayHook(object): |
|
320 | class CapturingDisplayHook(object): | |
315 | def __init__(self, shell, outputs=None): |
|
321 | def __init__(self, shell, outputs=None): | |
316 | self.shell = shell |
|
322 | self.shell = shell | |
317 | if outputs is None: |
|
323 | if outputs is None: | |
318 | outputs = [] |
|
324 | outputs = [] | |
319 | self.outputs = outputs |
|
325 | self.outputs = outputs | |
320 |
|
326 | |||
321 | def __call__(self, result=None): |
|
327 | def __call__(self, result=None): | |
322 | if result is None: |
|
328 | if result is None: | |
323 | return |
|
329 | return | |
324 | format_dict, md_dict = self.shell.display_formatter.format(result) |
|
330 | format_dict, md_dict = self.shell.display_formatter.format(result) | |
325 | self.outputs.append({ 'data': format_dict, 'metadata': md_dict }) |
|
331 | self.outputs.append({ 'data': format_dict, 'metadata': md_dict }) |
1 | NO CONTENT: modified file |
|
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 | """Logger class for IPython's logging facilities. |
|
1 | """Logger class for IPython's logging facilities. | |
2 | """ |
|
2 | """ | |
3 |
|
3 | |||
4 | #***************************************************************************** |
|
4 | #***************************************************************************** | |
5 | # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de> and |
|
5 | # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de> and | |
6 | # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu> |
|
6 | # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu> | |
7 | # |
|
7 | # | |
8 | # Distributed under the terms of the BSD License. The full license is in |
|
8 | # Distributed under the terms of the BSD License. The full license is in | |
9 | # the file COPYING, distributed as part of this software. |
|
9 | # the file COPYING, distributed as part of this software. | |
10 | #***************************************************************************** |
|
10 | #***************************************************************************** | |
11 |
|
11 | |||
12 | #**************************************************************************** |
|
12 | #**************************************************************************** | |
13 | # Modules and globals |
|
13 | # Modules and globals | |
14 |
|
14 | |||
15 | # Python standard modules |
|
15 | # Python standard modules | |
16 | import glob |
|
16 | import glob | |
17 | import io |
|
17 | import io | |
18 | import os |
|
18 | import os | |
19 | import time |
|
19 | import time | |
20 |
|
20 | |||
21 |
|
21 | |||
22 | #**************************************************************************** |
|
22 | #**************************************************************************** | |
23 | # FIXME: This class isn't a mixin anymore, but it still needs attributes from |
|
23 | # FIXME: This class isn't a mixin anymore, but it still needs attributes from | |
24 | # ipython and does input cache management. Finish cleanup later... |
|
24 | # ipython and does input cache management. Finish cleanup later... | |
25 |
|
25 | |||
26 | class Logger(object): |
|
26 | class Logger(object): | |
27 | """A Logfile class with different policies for file creation""" |
|
27 | """A Logfile class with different policies for file creation""" | |
28 |
|
28 | |||
29 | def __init__(self, home_dir, logfname='Logger.log', loghead=u'', |
|
29 | def __init__(self, home_dir, logfname='Logger.log', loghead=u'', | |
30 | logmode='over'): |
|
30 | logmode='over'): | |
31 |
|
31 | |||
32 | # this is the full ipython instance, we need some attributes from it |
|
32 | # this is the full ipython instance, we need some attributes from it | |
33 | # which won't exist until later. What a mess, clean up later... |
|
33 | # which won't exist until later. What a mess, clean up later... | |
34 | self.home_dir = home_dir |
|
34 | self.home_dir = home_dir | |
35 |
|
35 | |||
36 | self.logfname = logfname |
|
36 | self.logfname = logfname | |
37 | self.loghead = loghead |
|
37 | self.loghead = loghead | |
38 | self.logmode = logmode |
|
38 | self.logmode = logmode | |
39 | self.logfile = None |
|
39 | self.logfile = None | |
40 |
|
40 | |||
41 | # Whether to log raw or processed input |
|
41 | # Whether to log raw or processed input | |
42 | self.log_raw_input = False |
|
42 | self.log_raw_input = False | |
43 |
|
43 | |||
44 | # whether to also log output |
|
44 | # whether to also log output | |
45 | self.log_output = False |
|
45 | self.log_output = False | |
46 |
|
46 | |||
47 | # whether to put timestamps before each log entry |
|
47 | # whether to put timestamps before each log entry | |
48 | self.timestamp = False |
|
48 | self.timestamp = False | |
49 |
|
49 | |||
50 | # activity control flags |
|
50 | # activity control flags | |
51 | self.log_active = False |
|
51 | self.log_active = False | |
52 |
|
52 | |||
53 | # logmode is a validated property |
|
53 | # logmode is a validated property | |
54 | def _set_mode(self,mode): |
|
54 | def _set_mode(self,mode): | |
55 | if mode not in ['append','backup','global','over','rotate']: |
|
55 | if mode not in ['append','backup','global','over','rotate']: | |
56 | raise ValueError('invalid log mode %s given' % mode) |
|
56 | raise ValueError('invalid log mode %s given' % mode) | |
57 | self._logmode = mode |
|
57 | self._logmode = mode | |
58 |
|
58 | |||
59 | def _get_mode(self): |
|
59 | def _get_mode(self): | |
60 | return self._logmode |
|
60 | return self._logmode | |
61 |
|
61 | |||
62 | logmode = property(_get_mode,_set_mode) |
|
62 | logmode = property(_get_mode,_set_mode) | |
63 |
|
63 | |||
64 | def logstart(self, logfname=None, loghead=None, logmode=None, |
|
64 | def logstart(self, logfname=None, loghead=None, logmode=None, | |
65 | log_output=False, timestamp=False, log_raw_input=False): |
|
65 | log_output=False, timestamp=False, log_raw_input=False): | |
66 | """Generate a new log-file with a default header. |
|
66 | """Generate a new log-file with a default header. | |
67 |
|
67 | |||
68 | Raises RuntimeError if the log has already been started""" |
|
68 | Raises RuntimeError if the log has already been started""" | |
69 |
|
69 | |||
70 | if self.logfile is not None: |
|
70 | if self.logfile is not None: | |
71 | raise RuntimeError('Log file is already active: %s' % |
|
71 | raise RuntimeError('Log file is already active: %s' % | |
72 | self.logfname) |
|
72 | self.logfname) | |
73 |
|
73 | |||
74 | # The parameters can override constructor defaults |
|
74 | # The parameters can override constructor defaults | |
75 | if logfname is not None: self.logfname = logfname |
|
75 | if logfname is not None: self.logfname = logfname | |
76 | if loghead is not None: self.loghead = loghead |
|
76 | if loghead is not None: self.loghead = loghead | |
77 | if logmode is not None: self.logmode = logmode |
|
77 | if logmode is not None: self.logmode = logmode | |
78 |
|
78 | |||
79 | # Parameters not part of the constructor |
|
79 | # Parameters not part of the constructor | |
80 | self.timestamp = timestamp |
|
80 | self.timestamp = timestamp | |
81 | self.log_output = log_output |
|
81 | self.log_output = log_output | |
82 | self.log_raw_input = log_raw_input |
|
82 | self.log_raw_input = log_raw_input | |
83 |
|
83 | |||
84 | # init depending on the log mode requested |
|
84 | # init depending on the log mode requested | |
85 | isfile = os.path.isfile |
|
85 | isfile = os.path.isfile | |
86 | logmode = self.logmode |
|
86 | logmode = self.logmode | |
87 |
|
87 | |||
88 | if logmode == 'append': |
|
88 | if logmode == 'append': | |
89 | self.logfile = io.open(self.logfname, 'a', encoding='utf-8') |
|
89 | self.logfile = io.open(self.logfname, 'a', encoding='utf-8') | |
90 |
|
90 | |||
91 | elif logmode == 'backup': |
|
91 | elif logmode == 'backup': | |
92 | if isfile(self.logfname): |
|
92 | if isfile(self.logfname): | |
93 | backup_logname = self.logfname+'~' |
|
93 | backup_logname = self.logfname+'~' | |
94 | # Manually remove any old backup, since os.rename may fail |
|
94 | # Manually remove any old backup, since os.rename may fail | |
95 | # under Windows. |
|
95 | # under Windows. | |
96 | if isfile(backup_logname): |
|
96 | if isfile(backup_logname): | |
97 | os.remove(backup_logname) |
|
97 | os.remove(backup_logname) | |
98 | os.rename(self.logfname,backup_logname) |
|
98 | os.rename(self.logfname,backup_logname) | |
99 | self.logfile = io.open(self.logfname, 'w', encoding='utf-8') |
|
99 | self.logfile = io.open(self.logfname, 'w', encoding='utf-8') | |
100 |
|
100 | |||
101 | elif logmode == 'global': |
|
101 | elif logmode == 'global': | |
102 | self.logfname = os.path.join(self.home_dir,self.logfname) |
|
102 | self.logfname = os.path.join(self.home_dir,self.logfname) | |
103 | self.logfile = io.open(self.logfname, 'a', encoding='utf-8') |
|
103 | self.logfile = io.open(self.logfname, 'a', encoding='utf-8') | |
104 |
|
104 | |||
105 | elif logmode == 'over': |
|
105 | elif logmode == 'over': | |
106 | if isfile(self.logfname): |
|
106 | if isfile(self.logfname): | |
107 | os.remove(self.logfname) |
|
107 | os.remove(self.logfname) | |
108 | self.logfile = io.open(self.logfname,'w', encoding='utf-8') |
|
108 | self.logfile = io.open(self.logfname,'w', encoding='utf-8') | |
109 |
|
109 | |||
110 | elif logmode == 'rotate': |
|
110 | elif logmode == 'rotate': | |
111 | if isfile(self.logfname): |
|
111 | if isfile(self.logfname): | |
112 | if isfile(self.logfname+'.001~'): |
|
112 | if isfile(self.logfname+'.001~'): | |
113 | old = glob.glob(self.logfname+'.*~') |
|
113 | old = glob.glob(self.logfname+'.*~') | |
114 | old.sort() |
|
114 | old.sort() | |
115 | old.reverse() |
|
115 | old.reverse() | |
116 | for f in old: |
|
116 | for f in old: | |
117 | root, ext = os.path.splitext(f) |
|
117 | root, ext = os.path.splitext(f) | |
118 | num = int(ext[1:-1])+1 |
|
118 | num = int(ext[1:-1])+1 | |
119 | os.rename(f, root+'.'+repr(num).zfill(3)+'~') |
|
119 | os.rename(f, root+'.'+repr(num).zfill(3)+'~') | |
120 | os.rename(self.logfname, self.logfname+'.001~') |
|
120 | os.rename(self.logfname, self.logfname+'.001~') | |
121 | self.logfile = io.open(self.logfname, 'w', encoding='utf-8') |
|
121 | self.logfile = io.open(self.logfname, 'w', encoding='utf-8') | |
122 |
|
122 | |||
123 | if logmode != 'append': |
|
123 | if logmode != 'append': | |
124 | self.logfile.write(self.loghead) |
|
124 | self.logfile.write(self.loghead) | |
125 |
|
125 | |||
126 | self.logfile.flush() |
|
126 | self.logfile.flush() | |
127 | self.log_active = True |
|
127 | self.log_active = True | |
128 |
|
128 | |||
129 | def switch_log(self,val): |
|
129 | def switch_log(self,val): | |
130 | """Switch logging on/off. val should be ONLY a boolean.""" |
|
130 | """Switch logging on/off. val should be ONLY a boolean.""" | |
131 |
|
131 | |||
132 | if val not in [False,True,0,1]: |
|
132 | if val not in [False,True,0,1]: | |
133 | raise ValueError('Call switch_log ONLY with a boolean argument, ' |
|
133 | raise ValueError('Call switch_log ONLY with a boolean argument, ' | |
134 | 'not with: %s' % val) |
|
134 | 'not with: %s' % val) | |
135 |
|
135 | |||
136 | label = {0:'OFF',1:'ON',False:'OFF',True:'ON'} |
|
136 | label = {0:'OFF',1:'ON',False:'OFF',True:'ON'} | |
137 |
|
137 | |||
138 | if self.logfile is None: |
|
138 | if self.logfile is None: | |
139 | print(""" |
|
139 | print(""" | |
140 | Logging hasn't been started yet (use logstart for that). |
|
140 | Logging hasn't been started yet (use logstart for that). | |
141 |
|
141 | |||
142 | %logon/%logoff are for temporarily starting and stopping logging for a logfile |
|
142 | %logon/%logoff are for temporarily starting and stopping logging for a logfile | |
143 | which already exists. But you must first start the logging process with |
|
143 | which already exists. But you must first start the logging process with | |
144 | %logstart (optionally giving a logfile name).""") |
|
144 | %logstart (optionally giving a logfile name).""") | |
145 |
|
145 | |||
146 | else: |
|
146 | else: | |
147 | if self.log_active == val: |
|
147 | if self.log_active == val: | |
148 | print('Logging is already',label[val]) |
|
148 | print('Logging is already',label[val]) | |
149 | else: |
|
149 | else: | |
150 | print('Switching logging',label[val]) |
|
150 | print('Switching logging',label[val]) | |
151 | self.log_active = not self.log_active |
|
151 | self.log_active = not self.log_active | |
152 | self.log_active_out = self.log_active |
|
152 | self.log_active_out = self.log_active | |
153 |
|
153 | |||
154 | def logstate(self): |
|
154 | def logstate(self): | |
155 | """Print a status message about the logger.""" |
|
155 | """Print a status message about the logger.""" | |
156 | if self.logfile is None: |
|
156 | if self.logfile is None: | |
157 | print('Logging has not been activated.') |
|
157 | print('Logging has not been activated.') | |
158 | else: |
|
158 | else: | |
159 | state = self.log_active and 'active' or 'temporarily suspended' |
|
159 | state = self.log_active and 'active' or 'temporarily suspended' | |
160 | print('Filename :', self.logfname) |
|
160 | print('Filename :', self.logfname) | |
161 | print('Mode :', self.logmode) |
|
161 | print('Mode :', self.logmode) | |
162 | print('Output logging :', self.log_output) |
|
162 | print('Output logging :', self.log_output) | |
163 | print('Raw input log :', self.log_raw_input) |
|
163 | print('Raw input log :', self.log_raw_input) | |
164 | print('Timestamping :', self.timestamp) |
|
164 | print('Timestamping :', self.timestamp) | |
165 | print('State :', state) |
|
165 | print('State :', state) | |
166 |
|
166 | |||
167 | def log(self, line_mod, line_ori): |
|
167 | def log(self, line_mod, line_ori): | |
168 | """Write the sources to a log. |
|
168 | """Write the sources to a log. | |
169 |
|
169 | |||
170 | Inputs: |
|
170 | Inputs: | |
171 |
|
171 | |||
172 | - line_mod: possibly modified input, such as the transformations made |
|
172 | - line_mod: possibly modified input, such as the transformations made | |
173 | by input prefilters or input handlers of various kinds. This should |
|
173 | by input prefilters or input handlers of various kinds. This should | |
174 | always be valid Python. |
|
174 | always be valid Python. | |
175 |
|
175 | |||
176 | - line_ori: unmodified input line from the user. This is not |
|
176 | - line_ori: unmodified input line from the user. This is not | |
177 | necessarily valid Python. |
|
177 | necessarily valid Python. | |
178 | """ |
|
178 | """ | |
179 |
|
179 | |||
180 | # Write the log line, but decide which one according to the |
|
180 | # Write the log line, but decide which one according to the | |
181 | # log_raw_input flag, set when the log is started. |
|
181 | # log_raw_input flag, set when the log is started. | |
182 | if self.log_raw_input: |
|
182 | if self.log_raw_input: | |
183 | self.log_write(line_ori) |
|
183 | self.log_write(line_ori) | |
184 | else: |
|
184 | else: | |
185 | self.log_write(line_mod) |
|
185 | self.log_write(line_mod) | |
186 |
|
186 | |||
187 | def log_write(self, data, kind='input'): |
|
187 | def log_write(self, data, kind='input'): | |
188 | """Write data to the log file, if active""" |
|
188 | """Write data to the log file, if active""" | |
189 |
|
189 | |||
190 | #print 'data: %r' % data # dbg |
|
190 | #print 'data: %r' % data # dbg | |
191 | if self.log_active and data: |
|
191 | if self.log_active and data: | |
192 | write = self.logfile.write |
|
192 | write = self.logfile.write | |
193 | if kind=='input': |
|
193 | if kind=='input': | |
194 | if self.timestamp: |
|
194 | if self.timestamp: | |
195 | write(time.strftime('# %a, %d %b %Y %H:%M:%S\n', time.localtime())) |
|
195 | write(time.strftime('# %a, %d %b %Y %H:%M:%S\n', time.localtime())) | |
196 | write(data) |
|
196 | write(data) | |
197 | elif kind=='output' and self.log_output: |
|
197 | elif kind=='output' and self.log_output: | |
198 | odata = u'\n'.join([u'#[Out]# %s' % s |
|
198 | odata = u'\n'.join([u'#[Out]# %s' % s | |
199 | for s in data.splitlines()]) |
|
199 | for s in data.splitlines()]) | |
200 | write(u'%s\n' % odata) |
|
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 | def logstop(self): |
|
212 | def logstop(self): | |
204 | """Fully stop logging and close log file. |
|
213 | """Fully stop logging and close log file. | |
205 |
|
214 | |||
206 | In order to start logging again, a new logstart() call needs to be |
|
215 | In order to start logging again, a new logstart() call needs to be | |
207 | made, possibly (though not necessarily) with a new filename, mode and |
|
216 | made, possibly (though not necessarily) with a new filename, mode and | |
208 | other options.""" |
|
217 | other options.""" | |
209 |
|
218 | |||
210 | if self.logfile is not None: |
|
219 | if self.logfile is not None: | |
211 | self.logfile.close() |
|
220 | self.logfile.close() | |
212 | self.logfile = None |
|
221 | self.logfile = None | |
213 | else: |
|
222 | else: | |
214 | print("Logging hadn't been started.") |
|
223 | print("Logging hadn't been started.") | |
215 | self.log_active = False |
|
224 | self.log_active = False | |
216 |
|
225 | |||
217 | # For backwards compatibility, in case anyone was using this. |
|
226 | # For backwards compatibility, in case anyone was using this. | |
218 | close_log = logstop |
|
227 | close_log = logstop |
@@ -1,746 +1,757 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | """Magic functions for InteractiveShell. |
|
2 | """Magic functions for InteractiveShell. | |
3 | """ |
|
3 | """ | |
4 |
|
4 | |||
5 | #----------------------------------------------------------------------------- |
|
5 | #----------------------------------------------------------------------------- | |
6 | # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de> and |
|
6 | # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de> and | |
7 | # Copyright (C) 2001 Fernando Perez <fperez@colorado.edu> |
|
7 | # Copyright (C) 2001 Fernando Perez <fperez@colorado.edu> | |
8 | # Copyright (C) 2008 The IPython Development Team |
|
8 | # Copyright (C) 2008 The IPython Development Team | |
9 |
|
9 | |||
10 | # Distributed under the terms of the BSD License. The full license is in |
|
10 | # Distributed under the terms of the BSD License. The full license is in | |
11 | # the file COPYING, distributed as part of this software. |
|
11 | # the file COPYING, distributed as part of this software. | |
12 | #----------------------------------------------------------------------------- |
|
12 | #----------------------------------------------------------------------------- | |
13 |
|
13 | |||
14 | import os |
|
14 | import os | |
15 | import re |
|
15 | import re | |
16 | import sys |
|
16 | import sys | |
17 | from getopt import getopt, GetoptError |
|
17 | from getopt import getopt, GetoptError | |
18 |
|
18 | |||
19 | from traitlets.config.configurable import Configurable |
|
19 | from traitlets.config.configurable import Configurable | |
20 | from . import oinspect |
|
20 | from . import oinspect | |
21 | from .error import UsageError |
|
21 | from .error import UsageError | |
22 | from .inputtransformer2 import ESC_MAGIC, ESC_MAGIC2 |
|
22 | from .inputtransformer2 import ESC_MAGIC, ESC_MAGIC2 | |
23 | from ..utils.ipstruct import Struct |
|
23 | from ..utils.ipstruct import Struct | |
24 | from ..utils.process import arg_split |
|
24 | from ..utils.process import arg_split | |
25 | from ..utils.text import dedent |
|
25 | from ..utils.text import dedent | |
26 | from traitlets import Bool, Dict, Instance, observe |
|
26 | from traitlets import Bool, Dict, Instance, observe | |
27 | from logging import error |
|
27 | from logging import error | |
28 |
|
28 | |||
29 | #----------------------------------------------------------------------------- |
|
29 | #----------------------------------------------------------------------------- | |
30 | # Globals |
|
30 | # Globals | |
31 | #----------------------------------------------------------------------------- |
|
31 | #----------------------------------------------------------------------------- | |
32 |
|
32 | |||
33 | # A dict we'll use for each class that has magics, used as temporary storage to |
|
33 | # A dict we'll use for each class that has magics, used as temporary storage to | |
34 | # pass information between the @line/cell_magic method decorators and the |
|
34 | # pass information between the @line/cell_magic method decorators and the | |
35 | # @magics_class class decorator, because the method decorators have no |
|
35 | # @magics_class class decorator, because the method decorators have no | |
36 | # access to the class when they run. See for more details: |
|
36 | # access to the class when they run. See for more details: | |
37 | # http://stackoverflow.com/questions/2366713/can-a-python-decorator-of-an-instance-method-access-the-class |
|
37 | # http://stackoverflow.com/questions/2366713/can-a-python-decorator-of-an-instance-method-access-the-class | |
38 |
|
38 | |||
39 | magics = dict(line={}, cell={}) |
|
39 | magics = dict(line={}, cell={}) | |
40 |
|
40 | |||
41 | magic_kinds = ('line', 'cell') |
|
41 | magic_kinds = ('line', 'cell') | |
42 | magic_spec = ('line', 'cell', 'line_cell') |
|
42 | magic_spec = ('line', 'cell', 'line_cell') | |
43 | magic_escapes = dict(line=ESC_MAGIC, cell=ESC_MAGIC2) |
|
43 | magic_escapes = dict(line=ESC_MAGIC, cell=ESC_MAGIC2) | |
44 |
|
44 | |||
45 | #----------------------------------------------------------------------------- |
|
45 | #----------------------------------------------------------------------------- | |
46 | # Utility classes and functions |
|
46 | # Utility classes and functions | |
47 | #----------------------------------------------------------------------------- |
|
47 | #----------------------------------------------------------------------------- | |
48 |
|
48 | |||
49 | class Bunch: pass |
|
49 | class Bunch: pass | |
50 |
|
50 | |||
51 |
|
51 | |||
52 | def on_off(tag): |
|
52 | def on_off(tag): | |
53 | """Return an ON/OFF string for a 1/0 input. Simple utility function.""" |
|
53 | """Return an ON/OFF string for a 1/0 input. Simple utility function.""" | |
54 | return ['OFF','ON'][tag] |
|
54 | return ['OFF','ON'][tag] | |
55 |
|
55 | |||
56 |
|
56 | |||
57 | def compress_dhist(dh): |
|
57 | def compress_dhist(dh): | |
58 | """Compress a directory history into a new one with at most 20 entries. |
|
58 | """Compress a directory history into a new one with at most 20 entries. | |
59 |
|
59 | |||
60 | Return a new list made from the first and last 10 elements of dhist after |
|
60 | Return a new list made from the first and last 10 elements of dhist after | |
61 | removal of duplicates. |
|
61 | removal of duplicates. | |
62 | """ |
|
62 | """ | |
63 | head, tail = dh[:-10], dh[-10:] |
|
63 | head, tail = dh[:-10], dh[-10:] | |
64 |
|
64 | |||
65 | newhead = [] |
|
65 | newhead = [] | |
66 | done = set() |
|
66 | done = set() | |
67 | for h in head: |
|
67 | for h in head: | |
68 | if h in done: |
|
68 | if h in done: | |
69 | continue |
|
69 | continue | |
70 | newhead.append(h) |
|
70 | newhead.append(h) | |
71 | done.add(h) |
|
71 | done.add(h) | |
72 |
|
72 | |||
73 | return newhead + tail |
|
73 | return newhead + tail | |
74 |
|
74 | |||
75 |
|
75 | |||
76 | def needs_local_scope(func): |
|
76 | def needs_local_scope(func): | |
77 | """Decorator to mark magic functions which need to local scope to run.""" |
|
77 | """Decorator to mark magic functions which need to local scope to run.""" | |
78 | func.needs_local_scope = True |
|
78 | func.needs_local_scope = True | |
79 | return func |
|
79 | return func | |
80 |
|
80 | |||
81 | #----------------------------------------------------------------------------- |
|
81 | #----------------------------------------------------------------------------- | |
82 | # Class and method decorators for registering magics |
|
82 | # Class and method decorators for registering magics | |
83 | #----------------------------------------------------------------------------- |
|
83 | #----------------------------------------------------------------------------- | |
84 |
|
84 | |||
85 | def magics_class(cls): |
|
85 | def magics_class(cls): | |
86 | """Class decorator for all subclasses of the main Magics class. |
|
86 | """Class decorator for all subclasses of the main Magics class. | |
87 |
|
87 | |||
88 | Any class that subclasses Magics *must* also apply this decorator, to |
|
88 | Any class that subclasses Magics *must* also apply this decorator, to | |
89 | ensure that all the methods that have been decorated as line/cell magics |
|
89 | ensure that all the methods that have been decorated as line/cell magics | |
90 | get correctly registered in the class instance. This is necessary because |
|
90 | get correctly registered in the class instance. This is necessary because | |
91 | when method decorators run, the class does not exist yet, so they |
|
91 | when method decorators run, the class does not exist yet, so they | |
92 | temporarily store their information into a module global. Application of |
|
92 | temporarily store their information into a module global. Application of | |
93 | this class decorator copies that global data to the class instance and |
|
93 | this class decorator copies that global data to the class instance and | |
94 | clears the global. |
|
94 | clears the global. | |
95 |
|
95 | |||
96 | Obviously, this mechanism is not thread-safe, which means that the |
|
96 | Obviously, this mechanism is not thread-safe, which means that the | |
97 | *creation* of subclasses of Magic should only be done in a single-thread |
|
97 | *creation* of subclasses of Magic should only be done in a single-thread | |
98 | context. Instantiation of the classes has no restrictions. Given that |
|
98 | context. Instantiation of the classes has no restrictions. Given that | |
99 | these classes are typically created at IPython startup time and before user |
|
99 | these classes are typically created at IPython startup time and before user | |
100 | application code becomes active, in practice this should not pose any |
|
100 | application code becomes active, in practice this should not pose any | |
101 | problems. |
|
101 | problems. | |
102 | """ |
|
102 | """ | |
103 | cls.registered = True |
|
103 | cls.registered = True | |
104 | cls.magics = dict(line = magics['line'], |
|
104 | cls.magics = dict(line = magics['line'], | |
105 | cell = magics['cell']) |
|
105 | cell = magics['cell']) | |
106 | magics['line'] = {} |
|
106 | magics['line'] = {} | |
107 | magics['cell'] = {} |
|
107 | magics['cell'] = {} | |
108 | return cls |
|
108 | return cls | |
109 |
|
109 | |||
110 |
|
110 | |||
111 | def record_magic(dct, magic_kind, magic_name, func): |
|
111 | def record_magic(dct, magic_kind, magic_name, func): | |
112 | """Utility function to store a function as a magic of a specific kind. |
|
112 | """Utility function to store a function as a magic of a specific kind. | |
113 |
|
113 | |||
114 | Parameters |
|
114 | Parameters | |
115 | ---------- |
|
115 | ---------- | |
116 | dct : dict |
|
116 | dct : dict | |
117 | A dictionary with 'line' and 'cell' subdicts. |
|
117 | A dictionary with 'line' and 'cell' subdicts. | |
118 | magic_kind : str |
|
118 | magic_kind : str | |
119 | Kind of magic to be stored. |
|
119 | Kind of magic to be stored. | |
120 | magic_name : str |
|
120 | magic_name : str | |
121 | Key to store the magic as. |
|
121 | Key to store the magic as. | |
122 | func : function |
|
122 | func : function | |
123 | Callable object to store. |
|
123 | Callable object to store. | |
124 | """ |
|
124 | """ | |
125 | if magic_kind == 'line_cell': |
|
125 | if magic_kind == 'line_cell': | |
126 | dct['line'][magic_name] = dct['cell'][magic_name] = func |
|
126 | dct['line'][magic_name] = dct['cell'][magic_name] = func | |
127 | else: |
|
127 | else: | |
128 | dct[magic_kind][magic_name] = func |
|
128 | dct[magic_kind][magic_name] = func | |
129 |
|
129 | |||
130 |
|
130 | |||
131 | def validate_type(magic_kind): |
|
131 | def validate_type(magic_kind): | |
132 | """Ensure that the given magic_kind is valid. |
|
132 | """Ensure that the given magic_kind is valid. | |
133 |
|
133 | |||
134 | Check that the given magic_kind is one of the accepted spec types (stored |
|
134 | Check that the given magic_kind is one of the accepted spec types (stored | |
135 | in the global `magic_spec`), raise ValueError otherwise. |
|
135 | in the global `magic_spec`), raise ValueError otherwise. | |
136 | """ |
|
136 | """ | |
137 | if magic_kind not in magic_spec: |
|
137 | if magic_kind not in magic_spec: | |
138 | raise ValueError('magic_kind must be one of %s, %s given' % |
|
138 | raise ValueError('magic_kind must be one of %s, %s given' % | |
139 | magic_kinds, magic_kind) |
|
139 | magic_kinds, magic_kind) | |
140 |
|
140 | |||
141 |
|
141 | |||
142 | # The docstrings for the decorator below will be fairly similar for the two |
|
142 | # The docstrings for the decorator below will be fairly similar for the two | |
143 | # types (method and function), so we generate them here once and reuse the |
|
143 | # types (method and function), so we generate them here once and reuse the | |
144 | # templates below. |
|
144 | # templates below. | |
145 | _docstring_template = \ |
|
145 | _docstring_template = \ | |
146 | """Decorate the given {0} as {1} magic. |
|
146 | """Decorate the given {0} as {1} magic. | |
147 |
|
147 | |||
148 | The decorator can be used with or without arguments, as follows. |
|
148 | The decorator can be used with or without arguments, as follows. | |
149 |
|
149 | |||
150 | i) without arguments: it will create a {1} magic named as the {0} being |
|
150 | i) without arguments: it will create a {1} magic named as the {0} being | |
151 | decorated:: |
|
151 | decorated:: | |
152 |
|
152 | |||
153 | @deco |
|
153 | @deco | |
154 | def foo(...) |
|
154 | def foo(...) | |
155 |
|
155 | |||
156 | will create a {1} magic named `foo`. |
|
156 | will create a {1} magic named `foo`. | |
157 |
|
157 | |||
158 | ii) with one string argument: which will be used as the actual name of the |
|
158 | ii) with one string argument: which will be used as the actual name of the | |
159 | resulting magic:: |
|
159 | resulting magic:: | |
160 |
|
160 | |||
161 | @deco('bar') |
|
161 | @deco('bar') | |
162 | def foo(...) |
|
162 | def foo(...) | |
163 |
|
163 | |||
164 | will create a {1} magic named `bar`. |
|
164 | will create a {1} magic named `bar`. | |
165 |
|
165 | |||
166 | To register a class magic use ``Interactiveshell.register_magic(class or instance)``. |
|
166 | To register a class magic use ``Interactiveshell.register_magic(class or instance)``. | |
167 | """ |
|
167 | """ | |
168 |
|
168 | |||
169 | # These two are decorator factories. While they are conceptually very similar, |
|
169 | # These two are decorator factories. While they are conceptually very similar, | |
170 | # there are enough differences in the details that it's simpler to have them |
|
170 | # there are enough differences in the details that it's simpler to have them | |
171 | # written as completely standalone functions rather than trying to share code |
|
171 | # written as completely standalone functions rather than trying to share code | |
172 | # and make a single one with convoluted logic. |
|
172 | # and make a single one with convoluted logic. | |
173 |
|
173 | |||
174 | def _method_magic_marker(magic_kind): |
|
174 | def _method_magic_marker(magic_kind): | |
175 | """Decorator factory for methods in Magics subclasses. |
|
175 | """Decorator factory for methods in Magics subclasses. | |
176 | """ |
|
176 | """ | |
177 |
|
177 | |||
178 | validate_type(magic_kind) |
|
178 | validate_type(magic_kind) | |
179 |
|
179 | |||
180 | # This is a closure to capture the magic_kind. We could also use a class, |
|
180 | # This is a closure to capture the magic_kind. We could also use a class, | |
181 | # but it's overkill for just that one bit of state. |
|
181 | # but it's overkill for just that one bit of state. | |
182 | def magic_deco(arg): |
|
182 | def magic_deco(arg): | |
183 | if callable(arg): |
|
183 | if callable(arg): | |
184 | # "Naked" decorator call (just @foo, no args) |
|
184 | # "Naked" decorator call (just @foo, no args) | |
185 | func = arg |
|
185 | func = arg | |
186 | name = func.__name__ |
|
186 | name = func.__name__ | |
187 | retval = arg |
|
187 | retval = arg | |
188 | record_magic(magics, magic_kind, name, name) |
|
188 | record_magic(magics, magic_kind, name, name) | |
189 | elif isinstance(arg, str): |
|
189 | elif isinstance(arg, str): | |
190 | # Decorator called with arguments (@foo('bar')) |
|
190 | # Decorator called with arguments (@foo('bar')) | |
191 | name = arg |
|
191 | name = arg | |
192 | def mark(func, *a, **kw): |
|
192 | def mark(func, *a, **kw): | |
193 | record_magic(magics, magic_kind, name, func.__name__) |
|
193 | record_magic(magics, magic_kind, name, func.__name__) | |
194 | return func |
|
194 | return func | |
195 | retval = mark |
|
195 | retval = mark | |
196 | else: |
|
196 | else: | |
197 | raise TypeError("Decorator can only be called with " |
|
197 | raise TypeError("Decorator can only be called with " | |
198 | "string or function") |
|
198 | "string or function") | |
199 | return retval |
|
199 | return retval | |
200 |
|
200 | |||
201 | # Ensure the resulting decorator has a usable docstring |
|
201 | # Ensure the resulting decorator has a usable docstring | |
202 | magic_deco.__doc__ = _docstring_template.format('method', magic_kind) |
|
202 | magic_deco.__doc__ = _docstring_template.format('method', magic_kind) | |
203 | return magic_deco |
|
203 | return magic_deco | |
204 |
|
204 | |||
205 |
|
205 | |||
206 | def _function_magic_marker(magic_kind): |
|
206 | def _function_magic_marker(magic_kind): | |
207 | """Decorator factory for standalone functions. |
|
207 | """Decorator factory for standalone functions. | |
208 | """ |
|
208 | """ | |
209 | validate_type(magic_kind) |
|
209 | validate_type(magic_kind) | |
210 |
|
210 | |||
211 | # This is a closure to capture the magic_kind. We could also use a class, |
|
211 | # This is a closure to capture the magic_kind. We could also use a class, | |
212 | # but it's overkill for just that one bit of state. |
|
212 | # but it's overkill for just that one bit of state. | |
213 | def magic_deco(arg): |
|
213 | def magic_deco(arg): | |
214 | # Find get_ipython() in the caller's namespace |
|
214 | # Find get_ipython() in the caller's namespace | |
215 | caller = sys._getframe(1) |
|
215 | caller = sys._getframe(1) | |
216 | for ns in ['f_locals', 'f_globals', 'f_builtins']: |
|
216 | for ns in ['f_locals', 'f_globals', 'f_builtins']: | |
217 | get_ipython = getattr(caller, ns).get('get_ipython') |
|
217 | get_ipython = getattr(caller, ns).get('get_ipython') | |
218 | if get_ipython is not None: |
|
218 | if get_ipython is not None: | |
219 | break |
|
219 | break | |
220 | else: |
|
220 | else: | |
221 | raise NameError('Decorator can only run in context where ' |
|
221 | raise NameError('Decorator can only run in context where ' | |
222 | '`get_ipython` exists') |
|
222 | '`get_ipython` exists') | |
223 |
|
223 | |||
224 | ip = get_ipython() |
|
224 | ip = get_ipython() | |
225 |
|
225 | |||
226 | if callable(arg): |
|
226 | if callable(arg): | |
227 | # "Naked" decorator call (just @foo, no args) |
|
227 | # "Naked" decorator call (just @foo, no args) | |
228 | func = arg |
|
228 | func = arg | |
229 | name = func.__name__ |
|
229 | name = func.__name__ | |
230 | ip.register_magic_function(func, magic_kind, name) |
|
230 | ip.register_magic_function(func, magic_kind, name) | |
231 | retval = arg |
|
231 | retval = arg | |
232 | elif isinstance(arg, str): |
|
232 | elif isinstance(arg, str): | |
233 | # Decorator called with arguments (@foo('bar')) |
|
233 | # Decorator called with arguments (@foo('bar')) | |
234 | name = arg |
|
234 | name = arg | |
235 | def mark(func, *a, **kw): |
|
235 | def mark(func, *a, **kw): | |
236 | ip.register_magic_function(func, magic_kind, name) |
|
236 | ip.register_magic_function(func, magic_kind, name) | |
237 | return func |
|
237 | return func | |
238 | retval = mark |
|
238 | retval = mark | |
239 | else: |
|
239 | else: | |
240 | raise TypeError("Decorator can only be called with " |
|
240 | raise TypeError("Decorator can only be called with " | |
241 | "string or function") |
|
241 | "string or function") | |
242 | return retval |
|
242 | return retval | |
243 |
|
243 | |||
244 | # Ensure the resulting decorator has a usable docstring |
|
244 | # Ensure the resulting decorator has a usable docstring | |
245 | ds = _docstring_template.format('function', magic_kind) |
|
245 | ds = _docstring_template.format('function', magic_kind) | |
246 |
|
246 | |||
247 | ds += dedent(""" |
|
247 | ds += dedent(""" | |
248 | Note: this decorator can only be used in a context where IPython is already |
|
248 | Note: this decorator can only be used in a context where IPython is already | |
249 | active, so that the `get_ipython()` call succeeds. You can therefore use |
|
249 | active, so that the `get_ipython()` call succeeds. You can therefore use | |
250 | it in your startup files loaded after IPython initializes, but *not* in the |
|
250 | it in your startup files loaded after IPython initializes, but *not* in the | |
251 | IPython configuration file itself, which is executed before IPython is |
|
251 | IPython configuration file itself, which is executed before IPython is | |
252 | fully up and running. Any file located in the `startup` subdirectory of |
|
252 | fully up and running. Any file located in the `startup` subdirectory of | |
253 | your configuration profile will be OK in this sense. |
|
253 | your configuration profile will be OK in this sense. | |
254 | """) |
|
254 | """) | |
255 |
|
255 | |||
256 | magic_deco.__doc__ = ds |
|
256 | magic_deco.__doc__ = ds | |
257 | return magic_deco |
|
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 | def no_var_expand(magic_func): |
|
264 | def no_var_expand(magic_func): | |
264 | """Mark a magic function as not needing variable expansion |
|
265 | """Mark a magic function as not needing variable expansion | |
265 |
|
266 | |||
266 | By default, IPython interprets `{a}` or `$a` in the line passed to magics |
|
267 | By default, IPython interprets `{a}` or `$a` in the line passed to magics | |
267 | as variables that should be interpolated from the interactive namespace |
|
268 | as variables that should be interpolated from the interactive namespace | |
268 | before passing the line to the magic function. |
|
269 | before passing the line to the magic function. | |
269 | This is not always desirable, e.g. when the magic executes Python code |
|
270 | This is not always desirable, e.g. when the magic executes Python code | |
270 | (%timeit, %time, etc.). |
|
271 | (%timeit, %time, etc.). | |
271 | Decorate magics with `@no_var_expand` to opt-out of variable expansion. |
|
272 | Decorate magics with `@no_var_expand` to opt-out of variable expansion. | |
272 |
|
273 | |||
273 | .. versionadded:: 7.3 |
|
274 | .. versionadded:: 7.3 | |
274 | """ |
|
275 | """ | |
275 | setattr(magic_func, MAGIC_NO_VAR_EXPAND_ATTR, True) |
|
276 | setattr(magic_func, MAGIC_NO_VAR_EXPAND_ATTR, True) | |
276 | return magic_func |
|
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 | # Create the actual decorators for public use |
|
290 | # Create the actual decorators for public use | |
280 |
|
291 | |||
281 | # These three are used to decorate methods in class definitions |
|
292 | # These three are used to decorate methods in class definitions | |
282 | line_magic = _method_magic_marker('line') |
|
293 | line_magic = _method_magic_marker('line') | |
283 | cell_magic = _method_magic_marker('cell') |
|
294 | cell_magic = _method_magic_marker('cell') | |
284 | line_cell_magic = _method_magic_marker('line_cell') |
|
295 | line_cell_magic = _method_magic_marker('line_cell') | |
285 |
|
296 | |||
286 | # These three decorate standalone functions and perform the decoration |
|
297 | # These three decorate standalone functions and perform the decoration | |
287 | # immediately. They can only run where get_ipython() works |
|
298 | # immediately. They can only run where get_ipython() works | |
288 | register_line_magic = _function_magic_marker('line') |
|
299 | register_line_magic = _function_magic_marker('line') | |
289 | register_cell_magic = _function_magic_marker('cell') |
|
300 | register_cell_magic = _function_magic_marker('cell') | |
290 | register_line_cell_magic = _function_magic_marker('line_cell') |
|
301 | register_line_cell_magic = _function_magic_marker('line_cell') | |
291 |
|
302 | |||
292 | #----------------------------------------------------------------------------- |
|
303 | #----------------------------------------------------------------------------- | |
293 | # Core Magic classes |
|
304 | # Core Magic classes | |
294 | #----------------------------------------------------------------------------- |
|
305 | #----------------------------------------------------------------------------- | |
295 |
|
306 | |||
296 | class MagicsManager(Configurable): |
|
307 | class MagicsManager(Configurable): | |
297 | """Object that handles all magic-related functionality for IPython. |
|
308 | """Object that handles all magic-related functionality for IPython. | |
298 | """ |
|
309 | """ | |
299 | # Non-configurable class attributes |
|
310 | # Non-configurable class attributes | |
300 |
|
311 | |||
301 | # A two-level dict, first keyed by magic type, then by magic function, and |
|
312 | # A two-level dict, first keyed by magic type, then by magic function, and | |
302 | # holding the actual callable object as value. This is the dict used for |
|
313 | # holding the actual callable object as value. This is the dict used for | |
303 | # magic function dispatch |
|
314 | # magic function dispatch | |
304 | magics = Dict() |
|
315 | magics = Dict() | |
305 | lazy_magics = Dict( |
|
316 | lazy_magics = Dict( | |
306 | help=""" |
|
317 | help=""" | |
307 | Mapping from magic names to modules to load. |
|
318 | Mapping from magic names to modules to load. | |
308 |
|
319 | |||
309 | This can be used in IPython/IPykernel configuration to declare lazy magics |
|
320 | This can be used in IPython/IPykernel configuration to declare lazy magics | |
310 | that will only be imported/registered on first use. |
|
321 | that will only be imported/registered on first use. | |
311 |
|
322 | |||
312 | For example:: |
|
323 | For example:: | |
313 |
|
324 | |||
314 | c.MagicsManager.lazy_magics = { |
|
325 | c.MagicsManager.lazy_magics = { | |
315 | "my_magic": "slow.to.import", |
|
326 | "my_magic": "slow.to.import", | |
316 | "my_other_magic": "also.slow", |
|
327 | "my_other_magic": "also.slow", | |
317 | } |
|
328 | } | |
318 |
|
329 | |||
319 | On first invocation of `%my_magic`, `%%my_magic`, `%%my_other_magic` or |
|
330 | On first invocation of `%my_magic`, `%%my_magic`, `%%my_other_magic` or | |
320 | `%%my_other_magic`, the corresponding module will be loaded as an ipython |
|
331 | `%%my_other_magic`, the corresponding module will be loaded as an ipython | |
321 | extensions as if you had previously done `%load_ext ipython`. |
|
332 | extensions as if you had previously done `%load_ext ipython`. | |
322 |
|
333 | |||
323 | Magics names should be without percent(s) as magics can be both cell |
|
334 | Magics names should be without percent(s) as magics can be both cell | |
324 | and line magics. |
|
335 | and line magics. | |
325 |
|
336 | |||
326 | Lazy loading happen relatively late in execution process, and |
|
337 | Lazy loading happen relatively late in execution process, and | |
327 | complex extensions that manipulate Python/IPython internal state or global state |
|
338 | complex extensions that manipulate Python/IPython internal state or global state | |
328 | might not support lazy loading. |
|
339 | might not support lazy loading. | |
329 | """ |
|
340 | """ | |
330 | ).tag( |
|
341 | ).tag( | |
331 | config=True, |
|
342 | config=True, | |
332 | ) |
|
343 | ) | |
333 |
|
344 | |||
334 | # A registry of the original objects that we've been given holding magics. |
|
345 | # A registry of the original objects that we've been given holding magics. | |
335 | registry = Dict() |
|
346 | registry = Dict() | |
336 |
|
347 | |||
337 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True) |
|
348 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True) | |
338 |
|
349 | |||
339 | auto_magic = Bool(True, help= |
|
350 | auto_magic = Bool(True, help= | |
340 | "Automatically call line magics without requiring explicit % prefix" |
|
351 | "Automatically call line magics without requiring explicit % prefix" | |
341 | ).tag(config=True) |
|
352 | ).tag(config=True) | |
342 | @observe('auto_magic') |
|
353 | @observe('auto_magic') | |
343 | def _auto_magic_changed(self, change): |
|
354 | def _auto_magic_changed(self, change): | |
344 | self.shell.automagic = change['new'] |
|
355 | self.shell.automagic = change['new'] | |
345 |
|
356 | |||
346 | _auto_status = [ |
|
357 | _auto_status = [ | |
347 | 'Automagic is OFF, % prefix IS needed for line magics.', |
|
358 | 'Automagic is OFF, % prefix IS needed for line magics.', | |
348 | 'Automagic is ON, % prefix IS NOT needed for line magics.'] |
|
359 | 'Automagic is ON, % prefix IS NOT needed for line magics.'] | |
349 |
|
360 | |||
350 | user_magics = Instance('IPython.core.magics.UserMagics', allow_none=True) |
|
361 | user_magics = Instance('IPython.core.magics.UserMagics', allow_none=True) | |
351 |
|
362 | |||
352 | def __init__(self, shell=None, config=None, user_magics=None, **traits): |
|
363 | def __init__(self, shell=None, config=None, user_magics=None, **traits): | |
353 |
|
364 | |||
354 | super(MagicsManager, self).__init__(shell=shell, config=config, |
|
365 | super(MagicsManager, self).__init__(shell=shell, config=config, | |
355 | user_magics=user_magics, **traits) |
|
366 | user_magics=user_magics, **traits) | |
356 | self.magics = dict(line={}, cell={}) |
|
367 | self.magics = dict(line={}, cell={}) | |
357 | # Let's add the user_magics to the registry for uniformity, so *all* |
|
368 | # Let's add the user_magics to the registry for uniformity, so *all* | |
358 | # registered magic containers can be found there. |
|
369 | # registered magic containers can be found there. | |
359 | self.registry[user_magics.__class__.__name__] = user_magics |
|
370 | self.registry[user_magics.__class__.__name__] = user_magics | |
360 |
|
371 | |||
361 | def auto_status(self): |
|
372 | def auto_status(self): | |
362 | """Return descriptive string with automagic status.""" |
|
373 | """Return descriptive string with automagic status.""" | |
363 | return self._auto_status[self.auto_magic] |
|
374 | return self._auto_status[self.auto_magic] | |
364 |
|
375 | |||
365 | def lsmagic(self): |
|
376 | def lsmagic(self): | |
366 | """Return a dict of currently available magic functions. |
|
377 | """Return a dict of currently available magic functions. | |
367 |
|
378 | |||
368 | The return dict has the keys 'line' and 'cell', corresponding to the |
|
379 | The return dict has the keys 'line' and 'cell', corresponding to the | |
369 | two types of magics we support. Each value is a list of names. |
|
380 | two types of magics we support. Each value is a list of names. | |
370 | """ |
|
381 | """ | |
371 | return self.magics |
|
382 | return self.magics | |
372 |
|
383 | |||
373 | def lsmagic_docs(self, brief=False, missing=''): |
|
384 | def lsmagic_docs(self, brief=False, missing=''): | |
374 | """Return dict of documentation of magic functions. |
|
385 | """Return dict of documentation of magic functions. | |
375 |
|
386 | |||
376 | The return dict has the keys 'line' and 'cell', corresponding to the |
|
387 | The return dict has the keys 'line' and 'cell', corresponding to the | |
377 | two types of magics we support. Each value is a dict keyed by magic |
|
388 | two types of magics we support. Each value is a dict keyed by magic | |
378 | name whose value is the function docstring. If a docstring is |
|
389 | name whose value is the function docstring. If a docstring is | |
379 | unavailable, the value of `missing` is used instead. |
|
390 | unavailable, the value of `missing` is used instead. | |
380 |
|
391 | |||
381 | If brief is True, only the first line of each docstring will be returned. |
|
392 | If brief is True, only the first line of each docstring will be returned. | |
382 | """ |
|
393 | """ | |
383 | docs = {} |
|
394 | docs = {} | |
384 | for m_type in self.magics: |
|
395 | for m_type in self.magics: | |
385 | m_docs = {} |
|
396 | m_docs = {} | |
386 | for m_name, m_func in self.magics[m_type].items(): |
|
397 | for m_name, m_func in self.magics[m_type].items(): | |
387 | if m_func.__doc__: |
|
398 | if m_func.__doc__: | |
388 | if brief: |
|
399 | if brief: | |
389 | m_docs[m_name] = m_func.__doc__.split('\n', 1)[0] |
|
400 | m_docs[m_name] = m_func.__doc__.split('\n', 1)[0] | |
390 | else: |
|
401 | else: | |
391 | m_docs[m_name] = m_func.__doc__.rstrip() |
|
402 | m_docs[m_name] = m_func.__doc__.rstrip() | |
392 | else: |
|
403 | else: | |
393 | m_docs[m_name] = missing |
|
404 | m_docs[m_name] = missing | |
394 | docs[m_type] = m_docs |
|
405 | docs[m_type] = m_docs | |
395 | return docs |
|
406 | return docs | |
396 |
|
407 | |||
397 | def register_lazy(self, name: str, fully_qualified_name: str): |
|
408 | def register_lazy(self, name: str, fully_qualified_name: str): | |
398 | """ |
|
409 | """ | |
399 | Lazily register a magic via an extension. |
|
410 | Lazily register a magic via an extension. | |
400 |
|
411 | |||
401 |
|
412 | |||
402 | Parameters |
|
413 | Parameters | |
403 | ---------- |
|
414 | ---------- | |
404 | name : str |
|
415 | name : str | |
405 | Name of the magic you wish to register. |
|
416 | Name of the magic you wish to register. | |
406 | fully_qualified_name : |
|
417 | fully_qualified_name : | |
407 | Fully qualified name of the module/submodule that should be loaded |
|
418 | Fully qualified name of the module/submodule that should be loaded | |
408 | as an extensions when the magic is first called. |
|
419 | as an extensions when the magic is first called. | |
409 | It is assumed that loading this extensions will register the given |
|
420 | It is assumed that loading this extensions will register the given | |
410 | magic. |
|
421 | magic. | |
411 | """ |
|
422 | """ | |
412 |
|
423 | |||
413 | self.lazy_magics[name] = fully_qualified_name |
|
424 | self.lazy_magics[name] = fully_qualified_name | |
414 |
|
425 | |||
415 | def register(self, *magic_objects): |
|
426 | def register(self, *magic_objects): | |
416 | """Register one or more instances of Magics. |
|
427 | """Register one or more instances of Magics. | |
417 |
|
428 | |||
418 | Take one or more classes or instances of classes that subclass the main |
|
429 | Take one or more classes or instances of classes that subclass the main | |
419 | `core.Magic` class, and register them with IPython to use the magic |
|
430 | `core.Magic` class, and register them with IPython to use the magic | |
420 | functions they provide. The registration process will then ensure that |
|
431 | functions they provide. The registration process will then ensure that | |
421 | any methods that have decorated to provide line and/or cell magics will |
|
432 | any methods that have decorated to provide line and/or cell magics will | |
422 | be recognized with the `%x`/`%%x` syntax as a line/cell magic |
|
433 | be recognized with the `%x`/`%%x` syntax as a line/cell magic | |
423 | respectively. |
|
434 | respectively. | |
424 |
|
435 | |||
425 | If classes are given, they will be instantiated with the default |
|
436 | If classes are given, they will be instantiated with the default | |
426 | constructor. If your classes need a custom constructor, you should |
|
437 | constructor. If your classes need a custom constructor, you should | |
427 | instanitate them first and pass the instance. |
|
438 | instanitate them first and pass the instance. | |
428 |
|
439 | |||
429 | The provided arguments can be an arbitrary mix of classes and instances. |
|
440 | The provided arguments can be an arbitrary mix of classes and instances. | |
430 |
|
441 | |||
431 | Parameters |
|
442 | Parameters | |
432 | ---------- |
|
443 | ---------- | |
433 | *magic_objects : one or more classes or instances |
|
444 | *magic_objects : one or more classes or instances | |
434 | """ |
|
445 | """ | |
435 | # Start by validating them to ensure they have all had their magic |
|
446 | # Start by validating them to ensure they have all had their magic | |
436 | # methods registered at the instance level |
|
447 | # methods registered at the instance level | |
437 | for m in magic_objects: |
|
448 | for m in magic_objects: | |
438 | if not m.registered: |
|
449 | if not m.registered: | |
439 | raise ValueError("Class of magics %r was constructed without " |
|
450 | raise ValueError("Class of magics %r was constructed without " | |
440 | "the @register_magics class decorator") |
|
451 | "the @register_magics class decorator") | |
441 | if isinstance(m, type): |
|
452 | if isinstance(m, type): | |
442 | # If we're given an uninstantiated class |
|
453 | # If we're given an uninstantiated class | |
443 | m = m(shell=self.shell) |
|
454 | m = m(shell=self.shell) | |
444 |
|
455 | |||
445 | # Now that we have an instance, we can register it and update the |
|
456 | # Now that we have an instance, we can register it and update the | |
446 | # table of callables |
|
457 | # table of callables | |
447 | self.registry[m.__class__.__name__] = m |
|
458 | self.registry[m.__class__.__name__] = m | |
448 | for mtype in magic_kinds: |
|
459 | for mtype in magic_kinds: | |
449 | self.magics[mtype].update(m.magics[mtype]) |
|
460 | self.magics[mtype].update(m.magics[mtype]) | |
450 |
|
461 | |||
451 | def register_function(self, func, magic_kind='line', magic_name=None): |
|
462 | def register_function(self, func, magic_kind='line', magic_name=None): | |
452 | """Expose a standalone function as magic function for IPython. |
|
463 | """Expose a standalone function as magic function for IPython. | |
453 |
|
464 | |||
454 | This will create an IPython magic (line, cell or both) from a |
|
465 | This will create an IPython magic (line, cell or both) from a | |
455 | standalone function. The functions should have the following |
|
466 | standalone function. The functions should have the following | |
456 | signatures: |
|
467 | signatures: | |
457 |
|
468 | |||
458 | * For line magics: `def f(line)` |
|
469 | * For line magics: `def f(line)` | |
459 | * For cell magics: `def f(line, cell)` |
|
470 | * For cell magics: `def f(line, cell)` | |
460 | * For a function that does both: `def f(line, cell=None)` |
|
471 | * For a function that does both: `def f(line, cell=None)` | |
461 |
|
472 | |||
462 | In the latter case, the function will be called with `cell==None` when |
|
473 | In the latter case, the function will be called with `cell==None` when | |
463 | invoked as `%f`, and with cell as a string when invoked as `%%f`. |
|
474 | invoked as `%f`, and with cell as a string when invoked as `%%f`. | |
464 |
|
475 | |||
465 | Parameters |
|
476 | Parameters | |
466 | ---------- |
|
477 | ---------- | |
467 | func : callable |
|
478 | func : callable | |
468 | Function to be registered as a magic. |
|
479 | Function to be registered as a magic. | |
469 | magic_kind : str |
|
480 | magic_kind : str | |
470 | Kind of magic, one of 'line', 'cell' or 'line_cell' |
|
481 | Kind of magic, one of 'line', 'cell' or 'line_cell' | |
471 | magic_name : optional str |
|
482 | magic_name : optional str | |
472 | If given, the name the magic will have in the IPython namespace. By |
|
483 | If given, the name the magic will have in the IPython namespace. By | |
473 | default, the name of the function itself is used. |
|
484 | default, the name of the function itself is used. | |
474 | """ |
|
485 | """ | |
475 |
|
486 | |||
476 | # Create the new method in the user_magics and register it in the |
|
487 | # Create the new method in the user_magics and register it in the | |
477 | # global table |
|
488 | # global table | |
478 | validate_type(magic_kind) |
|
489 | validate_type(magic_kind) | |
479 | magic_name = func.__name__ if magic_name is None else magic_name |
|
490 | magic_name = func.__name__ if magic_name is None else magic_name | |
480 | setattr(self.user_magics, magic_name, func) |
|
491 | setattr(self.user_magics, magic_name, func) | |
481 | record_magic(self.magics, magic_kind, magic_name, func) |
|
492 | record_magic(self.magics, magic_kind, magic_name, func) | |
482 |
|
493 | |||
483 | def register_alias(self, alias_name, magic_name, magic_kind='line', magic_params=None): |
|
494 | def register_alias(self, alias_name, magic_name, magic_kind='line', magic_params=None): | |
484 | """Register an alias to a magic function. |
|
495 | """Register an alias to a magic function. | |
485 |
|
496 | |||
486 | The alias is an instance of :class:`MagicAlias`, which holds the |
|
497 | The alias is an instance of :class:`MagicAlias`, which holds the | |
487 | name and kind of the magic it should call. Binding is done at |
|
498 | name and kind of the magic it should call. Binding is done at | |
488 | call time, so if the underlying magic function is changed the alias |
|
499 | call time, so if the underlying magic function is changed the alias | |
489 | will call the new function. |
|
500 | will call the new function. | |
490 |
|
501 | |||
491 | Parameters |
|
502 | Parameters | |
492 | ---------- |
|
503 | ---------- | |
493 | alias_name : str |
|
504 | alias_name : str | |
494 | The name of the magic to be registered. |
|
505 | The name of the magic to be registered. | |
495 | magic_name : str |
|
506 | magic_name : str | |
496 | The name of an existing magic. |
|
507 | The name of an existing magic. | |
497 | magic_kind : str |
|
508 | magic_kind : str | |
498 | Kind of magic, one of 'line' or 'cell' |
|
509 | Kind of magic, one of 'line' or 'cell' | |
499 | """ |
|
510 | """ | |
500 |
|
511 | |||
501 | # `validate_type` is too permissive, as it allows 'line_cell' |
|
512 | # `validate_type` is too permissive, as it allows 'line_cell' | |
502 | # which we do not handle. |
|
513 | # which we do not handle. | |
503 | if magic_kind not in magic_kinds: |
|
514 | if magic_kind not in magic_kinds: | |
504 | raise ValueError('magic_kind must be one of %s, %s given' % |
|
515 | raise ValueError('magic_kind must be one of %s, %s given' % | |
505 | magic_kinds, magic_kind) |
|
516 | magic_kinds, magic_kind) | |
506 |
|
517 | |||
507 | alias = MagicAlias(self.shell, magic_name, magic_kind, magic_params) |
|
518 | alias = MagicAlias(self.shell, magic_name, magic_kind, magic_params) | |
508 | setattr(self.user_magics, alias_name, alias) |
|
519 | setattr(self.user_magics, alias_name, alias) | |
509 | record_magic(self.magics, magic_kind, alias_name, alias) |
|
520 | record_magic(self.magics, magic_kind, alias_name, alias) | |
510 |
|
521 | |||
511 | # Key base class that provides the central functionality for magics. |
|
522 | # Key base class that provides the central functionality for magics. | |
512 |
|
523 | |||
513 |
|
524 | |||
514 | class Magics(Configurable): |
|
525 | class Magics(Configurable): | |
515 | """Base class for implementing magic functions. |
|
526 | """Base class for implementing magic functions. | |
516 |
|
527 | |||
517 | Shell functions which can be reached as %function_name. All magic |
|
528 | Shell functions which can be reached as %function_name. All magic | |
518 | functions should accept a string, which they can parse for their own |
|
529 | functions should accept a string, which they can parse for their own | |
519 | needs. This can make some functions easier to type, eg `%cd ../` |
|
530 | needs. This can make some functions easier to type, eg `%cd ../` | |
520 | vs. `%cd("../")` |
|
531 | vs. `%cd("../")` | |
521 |
|
532 | |||
522 | Classes providing magic functions need to subclass this class, and they |
|
533 | Classes providing magic functions need to subclass this class, and they | |
523 | MUST: |
|
534 | MUST: | |
524 |
|
535 | |||
525 | - Use the method decorators `@line_magic` and `@cell_magic` to decorate |
|
536 | - Use the method decorators `@line_magic` and `@cell_magic` to decorate | |
526 | individual methods as magic functions, AND |
|
537 | individual methods as magic functions, AND | |
527 |
|
538 | |||
528 | - Use the class decorator `@magics_class` to ensure that the magic |
|
539 | - Use the class decorator `@magics_class` to ensure that the magic | |
529 | methods are properly registered at the instance level upon instance |
|
540 | methods are properly registered at the instance level upon instance | |
530 | initialization. |
|
541 | initialization. | |
531 |
|
542 | |||
532 | See :mod:`magic_functions` for examples of actual implementation classes. |
|
543 | See :mod:`magic_functions` for examples of actual implementation classes. | |
533 | """ |
|
544 | """ | |
534 | # Dict holding all command-line options for each magic. |
|
545 | # Dict holding all command-line options for each magic. | |
535 | options_table = None |
|
546 | options_table = None | |
536 | # Dict for the mapping of magic names to methods, set by class decorator |
|
547 | # Dict for the mapping of magic names to methods, set by class decorator | |
537 | magics = None |
|
548 | magics = None | |
538 | # Flag to check that the class decorator was properly applied |
|
549 | # Flag to check that the class decorator was properly applied | |
539 | registered = False |
|
550 | registered = False | |
540 | # Instance of IPython shell |
|
551 | # Instance of IPython shell | |
541 | shell = None |
|
552 | shell = None | |
542 |
|
553 | |||
543 | def __init__(self, shell=None, **kwargs): |
|
554 | def __init__(self, shell=None, **kwargs): | |
544 | if not(self.__class__.registered): |
|
555 | if not(self.__class__.registered): | |
545 | raise ValueError('Magics subclass without registration - ' |
|
556 | raise ValueError('Magics subclass without registration - ' | |
546 | 'did you forget to apply @magics_class?') |
|
557 | 'did you forget to apply @magics_class?') | |
547 | if shell is not None: |
|
558 | if shell is not None: | |
548 | if hasattr(shell, 'configurables'): |
|
559 | if hasattr(shell, 'configurables'): | |
549 | shell.configurables.append(self) |
|
560 | shell.configurables.append(self) | |
550 | if hasattr(shell, 'config'): |
|
561 | if hasattr(shell, 'config'): | |
551 | kwargs.setdefault('parent', shell) |
|
562 | kwargs.setdefault('parent', shell) | |
552 |
|
563 | |||
553 | self.shell = shell |
|
564 | self.shell = shell | |
554 | self.options_table = {} |
|
565 | self.options_table = {} | |
555 | # The method decorators are run when the instance doesn't exist yet, so |
|
566 | # The method decorators are run when the instance doesn't exist yet, so | |
556 | # they can only record the names of the methods they are supposed to |
|
567 | # they can only record the names of the methods they are supposed to | |
557 | # grab. Only now, that the instance exists, can we create the proper |
|
568 | # grab. Only now, that the instance exists, can we create the proper | |
558 | # mapping to bound methods. So we read the info off the original names |
|
569 | # mapping to bound methods. So we read the info off the original names | |
559 | # table and replace each method name by the actual bound method. |
|
570 | # table and replace each method name by the actual bound method. | |
560 | # But we mustn't clobber the *class* mapping, in case of multiple instances. |
|
571 | # But we mustn't clobber the *class* mapping, in case of multiple instances. | |
561 | class_magics = self.magics |
|
572 | class_magics = self.magics | |
562 | self.magics = {} |
|
573 | self.magics = {} | |
563 | for mtype in magic_kinds: |
|
574 | for mtype in magic_kinds: | |
564 | tab = self.magics[mtype] = {} |
|
575 | tab = self.magics[mtype] = {} | |
565 | cls_tab = class_magics[mtype] |
|
576 | cls_tab = class_magics[mtype] | |
566 | for magic_name, meth_name in cls_tab.items(): |
|
577 | for magic_name, meth_name in cls_tab.items(): | |
567 | if isinstance(meth_name, str): |
|
578 | if isinstance(meth_name, str): | |
568 | # it's a method name, grab it |
|
579 | # it's a method name, grab it | |
569 | tab[magic_name] = getattr(self, meth_name) |
|
580 | tab[magic_name] = getattr(self, meth_name) | |
570 | else: |
|
581 | else: | |
571 | # it's the real thing |
|
582 | # it's the real thing | |
572 | tab[magic_name] = meth_name |
|
583 | tab[magic_name] = meth_name | |
573 | # Configurable **needs** to be initiated at the end or the config |
|
584 | # Configurable **needs** to be initiated at the end or the config | |
574 | # magics get screwed up. |
|
585 | # magics get screwed up. | |
575 | super(Magics, self).__init__(**kwargs) |
|
586 | super(Magics, self).__init__(**kwargs) | |
576 |
|
587 | |||
577 | def arg_err(self,func): |
|
588 | def arg_err(self,func): | |
578 | """Print docstring if incorrect arguments were passed""" |
|
589 | """Print docstring if incorrect arguments were passed""" | |
579 | print('Error in arguments:') |
|
590 | print('Error in arguments:') | |
580 | print(oinspect.getdoc(func)) |
|
591 | print(oinspect.getdoc(func)) | |
581 |
|
592 | |||
582 | def format_latex(self, strng): |
|
593 | def format_latex(self, strng): | |
583 | """Format a string for latex inclusion.""" |
|
594 | """Format a string for latex inclusion.""" | |
584 |
|
595 | |||
585 | # Characters that need to be escaped for latex: |
|
596 | # Characters that need to be escaped for latex: | |
586 | escape_re = re.compile(r'(%|_|\$|#|&)',re.MULTILINE) |
|
597 | escape_re = re.compile(r'(%|_|\$|#|&)',re.MULTILINE) | |
587 | # Magic command names as headers: |
|
598 | # Magic command names as headers: | |
588 | cmd_name_re = re.compile(r'^(%s.*?):' % ESC_MAGIC, |
|
599 | cmd_name_re = re.compile(r'^(%s.*?):' % ESC_MAGIC, | |
589 | re.MULTILINE) |
|
600 | re.MULTILINE) | |
590 | # Magic commands |
|
601 | # Magic commands | |
591 | cmd_re = re.compile(r'(?P<cmd>%s.+?\b)(?!\}\}:)' % ESC_MAGIC, |
|
602 | cmd_re = re.compile(r'(?P<cmd>%s.+?\b)(?!\}\}:)' % ESC_MAGIC, | |
592 | re.MULTILINE) |
|
603 | re.MULTILINE) | |
593 | # Paragraph continue |
|
604 | # Paragraph continue | |
594 | par_re = re.compile(r'\\$',re.MULTILINE) |
|
605 | par_re = re.compile(r'\\$',re.MULTILINE) | |
595 |
|
606 | |||
596 | # The "\n" symbol |
|
607 | # The "\n" symbol | |
597 | newline_re = re.compile(r'\\n') |
|
608 | newline_re = re.compile(r'\\n') | |
598 |
|
609 | |||
599 | # Now build the string for output: |
|
610 | # Now build the string for output: | |
600 | #strng = cmd_name_re.sub(r'\n\\texttt{\\textsl{\\large \1}}:',strng) |
|
611 | #strng = cmd_name_re.sub(r'\n\\texttt{\\textsl{\\large \1}}:',strng) | |
601 | strng = cmd_name_re.sub(r'\n\\bigskip\n\\texttt{\\textbf{ \1}}:', |
|
612 | strng = cmd_name_re.sub(r'\n\\bigskip\n\\texttt{\\textbf{ \1}}:', | |
602 | strng) |
|
613 | strng) | |
603 | strng = cmd_re.sub(r'\\texttt{\g<cmd>}',strng) |
|
614 | strng = cmd_re.sub(r'\\texttt{\g<cmd>}',strng) | |
604 | strng = par_re.sub(r'\\\\',strng) |
|
615 | strng = par_re.sub(r'\\\\',strng) | |
605 | strng = escape_re.sub(r'\\\1',strng) |
|
616 | strng = escape_re.sub(r'\\\1',strng) | |
606 | strng = newline_re.sub(r'\\textbackslash{}n',strng) |
|
617 | strng = newline_re.sub(r'\\textbackslash{}n',strng) | |
607 | return strng |
|
618 | return strng | |
608 |
|
619 | |||
609 | def parse_options(self, arg_str, opt_str, *long_opts, **kw): |
|
620 | def parse_options(self, arg_str, opt_str, *long_opts, **kw): | |
610 | """Parse options passed to an argument string. |
|
621 | """Parse options passed to an argument string. | |
611 |
|
622 | |||
612 | The interface is similar to that of :func:`getopt.getopt`, but it |
|
623 | The interface is similar to that of :func:`getopt.getopt`, but it | |
613 | returns a :class:`~IPython.utils.struct.Struct` with the options as keys |
|
624 | returns a :class:`~IPython.utils.struct.Struct` with the options as keys | |
614 | and the stripped argument string still as a string. |
|
625 | and the stripped argument string still as a string. | |
615 |
|
626 | |||
616 | arg_str is quoted as a true sys.argv vector by using shlex.split. |
|
627 | arg_str is quoted as a true sys.argv vector by using shlex.split. | |
617 | This allows us to easily expand variables, glob files, quote |
|
628 | This allows us to easily expand variables, glob files, quote | |
618 | arguments, etc. |
|
629 | arguments, etc. | |
619 |
|
630 | |||
620 | Parameters |
|
631 | Parameters | |
621 | ---------- |
|
632 | ---------- | |
622 | arg_str : str |
|
633 | arg_str : str | |
623 | The arguments to parse. |
|
634 | The arguments to parse. | |
624 | opt_str : str |
|
635 | opt_str : str | |
625 | The options specification. |
|
636 | The options specification. | |
626 | mode : str, default 'string' |
|
637 | mode : str, default 'string' | |
627 | If given as 'list', the argument string is returned as a list (split |
|
638 | If given as 'list', the argument string is returned as a list (split | |
628 | on whitespace) instead of a string. |
|
639 | on whitespace) instead of a string. | |
629 | list_all : bool, default False |
|
640 | list_all : bool, default False | |
630 | Put all option values in lists. Normally only options |
|
641 | Put all option values in lists. Normally only options | |
631 | appearing more than once are put in a list. |
|
642 | appearing more than once are put in a list. | |
632 | posix : bool, default True |
|
643 | posix : bool, default True | |
633 | Whether to split the input line in POSIX mode or not, as per the |
|
644 | Whether to split the input line in POSIX mode or not, as per the | |
634 | conventions outlined in the :mod:`shlex` module from the standard |
|
645 | conventions outlined in the :mod:`shlex` module from the standard | |
635 | library. |
|
646 | library. | |
636 | """ |
|
647 | """ | |
637 |
|
648 | |||
638 | # inject default options at the beginning of the input line |
|
649 | # inject default options at the beginning of the input line | |
639 | caller = sys._getframe(1).f_code.co_name |
|
650 | caller = sys._getframe(1).f_code.co_name | |
640 | arg_str = '%s %s' % (self.options_table.get(caller,''),arg_str) |
|
651 | arg_str = '%s %s' % (self.options_table.get(caller,''),arg_str) | |
641 |
|
652 | |||
642 | mode = kw.get('mode','string') |
|
653 | mode = kw.get('mode','string') | |
643 | if mode not in ['string','list']: |
|
654 | if mode not in ['string','list']: | |
644 | raise ValueError('incorrect mode given: %s' % mode) |
|
655 | raise ValueError('incorrect mode given: %s' % mode) | |
645 | # Get options |
|
656 | # Get options | |
646 | list_all = kw.get('list_all',0) |
|
657 | list_all = kw.get('list_all',0) | |
647 | posix = kw.get('posix', os.name == 'posix') |
|
658 | posix = kw.get('posix', os.name == 'posix') | |
648 | strict = kw.get('strict', True) |
|
659 | strict = kw.get('strict', True) | |
649 |
|
660 | |||
650 | preserve_non_opts = kw.get("preserve_non_opts", False) |
|
661 | preserve_non_opts = kw.get("preserve_non_opts", False) | |
651 | remainder_arg_str = arg_str |
|
662 | remainder_arg_str = arg_str | |
652 |
|
663 | |||
653 | # Check if we have more than one argument to warrant extra processing: |
|
664 | # Check if we have more than one argument to warrant extra processing: | |
654 | odict = {} # Dictionary with options |
|
665 | odict = {} # Dictionary with options | |
655 | args = arg_str.split() |
|
666 | args = arg_str.split() | |
656 | if len(args) >= 1: |
|
667 | if len(args) >= 1: | |
657 | # If the list of inputs only has 0 or 1 thing in it, there's no |
|
668 | # If the list of inputs only has 0 or 1 thing in it, there's no | |
658 | # need to look for options |
|
669 | # need to look for options | |
659 | argv = arg_split(arg_str, posix, strict) |
|
670 | argv = arg_split(arg_str, posix, strict) | |
660 | # Do regular option processing |
|
671 | # Do regular option processing | |
661 | try: |
|
672 | try: | |
662 | opts,args = getopt(argv, opt_str, long_opts) |
|
673 | opts,args = getopt(argv, opt_str, long_opts) | |
663 | except GetoptError as e: |
|
674 | except GetoptError as e: | |
664 | raise UsageError( |
|
675 | raise UsageError( | |
665 | '%s ( allowed: "%s" %s)' % (e.msg, opt_str, " ".join(long_opts)) |
|
676 | '%s ( allowed: "%s" %s)' % (e.msg, opt_str, " ".join(long_opts)) | |
666 | ) from e |
|
677 | ) from e | |
667 | for o, a in opts: |
|
678 | for o, a in opts: | |
668 | if mode == "string" and preserve_non_opts: |
|
679 | if mode == "string" and preserve_non_opts: | |
669 | # remove option-parts from the original args-string and preserve remaining-part. |
|
680 | # remove option-parts from the original args-string and preserve remaining-part. | |
670 | # This relies on the arg_split(...) and getopt(...)'s impl spec, that the parsed options are |
|
681 | # This relies on the arg_split(...) and getopt(...)'s impl spec, that the parsed options are | |
671 | # returned in the original order. |
|
682 | # returned in the original order. | |
672 | remainder_arg_str = remainder_arg_str.replace(o, "", 1).replace( |
|
683 | remainder_arg_str = remainder_arg_str.replace(o, "", 1).replace( | |
673 | a, "", 1 |
|
684 | a, "", 1 | |
674 | ) |
|
685 | ) | |
675 | if o.startswith("--"): |
|
686 | if o.startswith("--"): | |
676 | o = o[2:] |
|
687 | o = o[2:] | |
677 | else: |
|
688 | else: | |
678 | o = o[1:] |
|
689 | o = o[1:] | |
679 | try: |
|
690 | try: | |
680 | odict[o].append(a) |
|
691 | odict[o].append(a) | |
681 | except AttributeError: |
|
692 | except AttributeError: | |
682 | odict[o] = [odict[o],a] |
|
693 | odict[o] = [odict[o],a] | |
683 | except KeyError: |
|
694 | except KeyError: | |
684 | if list_all: |
|
695 | if list_all: | |
685 | odict[o] = [a] |
|
696 | odict[o] = [a] | |
686 | else: |
|
697 | else: | |
687 | odict[o] = a |
|
698 | odict[o] = a | |
688 |
|
699 | |||
689 | # Prepare opts,args for return |
|
700 | # Prepare opts,args for return | |
690 | opts = Struct(odict) |
|
701 | opts = Struct(odict) | |
691 | if mode == 'string': |
|
702 | if mode == 'string': | |
692 | if preserve_non_opts: |
|
703 | if preserve_non_opts: | |
693 | args = remainder_arg_str.lstrip() |
|
704 | args = remainder_arg_str.lstrip() | |
694 | else: |
|
705 | else: | |
695 | args = " ".join(args) |
|
706 | args = " ".join(args) | |
696 |
|
707 | |||
697 | return opts,args |
|
708 | return opts,args | |
698 |
|
709 | |||
699 | def default_option(self, fn, optstr): |
|
710 | def default_option(self, fn, optstr): | |
700 | """Make an entry in the options_table for fn, with value optstr""" |
|
711 | """Make an entry in the options_table for fn, with value optstr""" | |
701 |
|
712 | |||
702 | if fn not in self.lsmagic(): |
|
713 | if fn not in self.lsmagic(): | |
703 | error("%s is not a magic function" % fn) |
|
714 | error("%s is not a magic function" % fn) | |
704 | self.options_table[fn] = optstr |
|
715 | self.options_table[fn] = optstr | |
705 |
|
716 | |||
706 |
|
717 | |||
707 | class MagicAlias(object): |
|
718 | class MagicAlias(object): | |
708 | """An alias to another magic function. |
|
719 | """An alias to another magic function. | |
709 |
|
720 | |||
710 | An alias is determined by its magic name and magic kind. Lookup |
|
721 | An alias is determined by its magic name and magic kind. Lookup | |
711 | is done at call time, so if the underlying magic changes the alias |
|
722 | is done at call time, so if the underlying magic changes the alias | |
712 | will call the new function. |
|
723 | will call the new function. | |
713 |
|
724 | |||
714 | Use the :meth:`MagicsManager.register_alias` method or the |
|
725 | Use the :meth:`MagicsManager.register_alias` method or the | |
715 | `%alias_magic` magic function to create and register a new alias. |
|
726 | `%alias_magic` magic function to create and register a new alias. | |
716 | """ |
|
727 | """ | |
717 | def __init__(self, shell, magic_name, magic_kind, magic_params=None): |
|
728 | def __init__(self, shell, magic_name, magic_kind, magic_params=None): | |
718 | self.shell = shell |
|
729 | self.shell = shell | |
719 | self.magic_name = magic_name |
|
730 | self.magic_name = magic_name | |
720 | self.magic_params = magic_params |
|
731 | self.magic_params = magic_params | |
721 | self.magic_kind = magic_kind |
|
732 | self.magic_kind = magic_kind | |
722 |
|
733 | |||
723 | self.pretty_target = '%s%s' % (magic_escapes[self.magic_kind], self.magic_name) |
|
734 | self.pretty_target = '%s%s' % (magic_escapes[self.magic_kind], self.magic_name) | |
724 | self.__doc__ = "Alias for `%s`." % self.pretty_target |
|
735 | self.__doc__ = "Alias for `%s`." % self.pretty_target | |
725 |
|
736 | |||
726 | self._in_call = False |
|
737 | self._in_call = False | |
727 |
|
738 | |||
728 | def __call__(self, *args, **kwargs): |
|
739 | def __call__(self, *args, **kwargs): | |
729 | """Call the magic alias.""" |
|
740 | """Call the magic alias.""" | |
730 | fn = self.shell.find_magic(self.magic_name, self.magic_kind) |
|
741 | fn = self.shell.find_magic(self.magic_name, self.magic_kind) | |
731 | if fn is None: |
|
742 | if fn is None: | |
732 | raise UsageError("Magic `%s` not found." % self.pretty_target) |
|
743 | raise UsageError("Magic `%s` not found." % self.pretty_target) | |
733 |
|
744 | |||
734 | # Protect against infinite recursion. |
|
745 | # Protect against infinite recursion. | |
735 | if self._in_call: |
|
746 | if self._in_call: | |
736 | raise UsageError("Infinite recursion detected; " |
|
747 | raise UsageError("Infinite recursion detected; " | |
737 | "magic aliases cannot call themselves.") |
|
748 | "magic aliases cannot call themselves.") | |
738 | self._in_call = True |
|
749 | self._in_call = True | |
739 | try: |
|
750 | try: | |
740 | if self.magic_params: |
|
751 | if self.magic_params: | |
741 | args_list = list(args) |
|
752 | args_list = list(args) | |
742 | args_list[0] = self.magic_params + " " + args[0] |
|
753 | args_list[0] = self.magic_params + " " + args[0] | |
743 | args = tuple(args_list) |
|
754 | args = tuple(args_list) | |
744 | return fn(*args, **kwargs) |
|
755 | return fn(*args, **kwargs) | |
745 | finally: |
|
756 | finally: | |
746 | self._in_call = False |
|
757 | self._in_call = False |
@@ -1,1510 +1,1512 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | """Implementation of execution-related magic functions.""" |
|
2 | """Implementation of execution-related magic functions.""" | |
3 |
|
3 | |||
4 | # Copyright (c) IPython Development Team. |
|
4 | # Copyright (c) IPython Development Team. | |
5 | # Distributed under the terms of the Modified BSD License. |
|
5 | # Distributed under the terms of the Modified BSD License. | |
6 |
|
6 | |||
7 |
|
7 | |||
8 | import ast |
|
8 | import ast | |
9 | import bdb |
|
9 | import bdb | |
10 | import builtins as builtin_mod |
|
10 | import builtins as builtin_mod | |
11 | import cProfile as profile |
|
11 | import cProfile as profile | |
12 | import gc |
|
12 | import gc | |
13 | import itertools |
|
13 | import itertools | |
14 | import math |
|
14 | import math | |
15 | import os |
|
15 | import os | |
16 | import pstats |
|
16 | import pstats | |
17 | import re |
|
17 | import re | |
18 | import shlex |
|
18 | import shlex | |
19 | import sys |
|
19 | import sys | |
20 | import time |
|
20 | import time | |
21 | import timeit |
|
21 | import timeit | |
22 | from ast import Module |
|
22 | from ast import Module | |
23 | from io import StringIO |
|
23 | from io import StringIO | |
24 | from logging import error |
|
24 | from logging import error | |
25 | from pathlib import Path |
|
25 | from pathlib import Path | |
26 | from pdb import Restart |
|
26 | from pdb import Restart | |
27 | from warnings import warn |
|
27 | from warnings import warn | |
28 |
|
28 | |||
29 | from IPython.core import magic_arguments, oinspect, page |
|
29 | from IPython.core import magic_arguments, oinspect, page | |
30 | from IPython.core.error import UsageError |
|
30 | from IPython.core.error import UsageError | |
31 | from IPython.core.macro import Macro |
|
31 | from IPython.core.macro import Macro | |
32 | from IPython.core.magic import ( |
|
32 | from IPython.core.magic import ( | |
33 | Magics, |
|
33 | Magics, | |
34 | cell_magic, |
|
34 | cell_magic, | |
35 | line_cell_magic, |
|
35 | line_cell_magic, | |
36 | line_magic, |
|
36 | line_magic, | |
37 | magics_class, |
|
37 | magics_class, | |
38 | needs_local_scope, |
|
38 | needs_local_scope, | |
39 | no_var_expand, |
|
39 | no_var_expand, | |
|
40 | output_can_be_silenced, | |||
40 | on_off, |
|
41 | on_off, | |
41 | ) |
|
42 | ) | |
42 | from IPython.testing.skipdoctest import skip_doctest |
|
43 | from IPython.testing.skipdoctest import skip_doctest | |
43 | from IPython.utils.capture import capture_output |
|
44 | from IPython.utils.capture import capture_output | |
44 | from IPython.utils.contexts import preserve_keys |
|
45 | from IPython.utils.contexts import preserve_keys | |
45 | from IPython.utils.ipstruct import Struct |
|
46 | from IPython.utils.ipstruct import Struct | |
46 | from IPython.utils.module_paths import find_mod |
|
47 | from IPython.utils.module_paths import find_mod | |
47 | from IPython.utils.path import get_py_filename, shellglob |
|
48 | from IPython.utils.path import get_py_filename, shellglob | |
48 | from IPython.utils.timing import clock, clock2 |
|
49 | from IPython.utils.timing import clock, clock2 | |
49 |
|
50 | |||
50 | #----------------------------------------------------------------------------- |
|
51 | #----------------------------------------------------------------------------- | |
51 | # Magic implementation classes |
|
52 | # Magic implementation classes | |
52 | #----------------------------------------------------------------------------- |
|
53 | #----------------------------------------------------------------------------- | |
53 |
|
54 | |||
54 |
|
55 | |||
55 | class TimeitResult(object): |
|
56 | class TimeitResult(object): | |
56 | """ |
|
57 | """ | |
57 | Object returned by the timeit magic with info about the run. |
|
58 | Object returned by the timeit magic with info about the run. | |
58 |
|
59 | |||
59 | Contains the following attributes : |
|
60 | Contains the following attributes : | |
60 |
|
61 | |||
61 | loops: (int) number of loops done per measurement |
|
62 | loops: (int) number of loops done per measurement | |
62 | repeat: (int) number of times the measurement has been repeated |
|
63 | repeat: (int) number of times the measurement has been repeated | |
63 | best: (float) best execution time / number |
|
64 | best: (float) best execution time / number | |
64 | all_runs: (list of float) execution time of each run (in s) |
|
65 | all_runs: (list of float) execution time of each run (in s) | |
65 | compile_time: (float) time of statement compilation (s) |
|
66 | compile_time: (float) time of statement compilation (s) | |
66 |
|
67 | |||
67 | """ |
|
68 | """ | |
68 | def __init__(self, loops, repeat, best, worst, all_runs, compile_time, precision): |
|
69 | def __init__(self, loops, repeat, best, worst, all_runs, compile_time, precision): | |
69 | self.loops = loops |
|
70 | self.loops = loops | |
70 | self.repeat = repeat |
|
71 | self.repeat = repeat | |
71 | self.best = best |
|
72 | self.best = best | |
72 | self.worst = worst |
|
73 | self.worst = worst | |
73 | self.all_runs = all_runs |
|
74 | self.all_runs = all_runs | |
74 | self.compile_time = compile_time |
|
75 | self.compile_time = compile_time | |
75 | self._precision = precision |
|
76 | self._precision = precision | |
76 | self.timings = [ dt / self.loops for dt in all_runs] |
|
77 | self.timings = [ dt / self.loops for dt in all_runs] | |
77 |
|
78 | |||
78 | @property |
|
79 | @property | |
79 | def average(self): |
|
80 | def average(self): | |
80 | return math.fsum(self.timings) / len(self.timings) |
|
81 | return math.fsum(self.timings) / len(self.timings) | |
81 |
|
82 | |||
82 | @property |
|
83 | @property | |
83 | def stdev(self): |
|
84 | def stdev(self): | |
84 | mean = self.average |
|
85 | mean = self.average | |
85 | return (math.fsum([(x - mean) ** 2 for x in self.timings]) / len(self.timings)) ** 0.5 |
|
86 | return (math.fsum([(x - mean) ** 2 for x in self.timings]) / len(self.timings)) ** 0.5 | |
86 |
|
87 | |||
87 | def __str__(self): |
|
88 | def __str__(self): | |
88 | pm = '+-' |
|
89 | pm = '+-' | |
89 | if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding: |
|
90 | if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding: | |
90 | try: |
|
91 | try: | |
91 | u'\xb1'.encode(sys.stdout.encoding) |
|
92 | u'\xb1'.encode(sys.stdout.encoding) | |
92 | pm = u'\xb1' |
|
93 | pm = u'\xb1' | |
93 | except: |
|
94 | except: | |
94 | pass |
|
95 | pass | |
95 | return "{mean} {pm} {std} per loop (mean {pm} std. dev. of {runs} run{run_plural}, {loops:,} loop{loop_plural} each)".format( |
|
96 | return "{mean} {pm} {std} per loop (mean {pm} std. dev. of {runs} run{run_plural}, {loops:,} loop{loop_plural} each)".format( | |
96 | pm=pm, |
|
97 | pm=pm, | |
97 | runs=self.repeat, |
|
98 | runs=self.repeat, | |
98 | loops=self.loops, |
|
99 | loops=self.loops, | |
99 | loop_plural="" if self.loops == 1 else "s", |
|
100 | loop_plural="" if self.loops == 1 else "s", | |
100 | run_plural="" if self.repeat == 1 else "s", |
|
101 | run_plural="" if self.repeat == 1 else "s", | |
101 | mean=_format_time(self.average, self._precision), |
|
102 | mean=_format_time(self.average, self._precision), | |
102 | std=_format_time(self.stdev, self._precision), |
|
103 | std=_format_time(self.stdev, self._precision), | |
103 | ) |
|
104 | ) | |
104 |
|
105 | |||
105 | def _repr_pretty_(self, p , cycle): |
|
106 | def _repr_pretty_(self, p , cycle): | |
106 | unic = self.__str__() |
|
107 | unic = self.__str__() | |
107 | p.text(u'<TimeitResult : '+unic+u'>') |
|
108 | p.text(u'<TimeitResult : '+unic+u'>') | |
108 |
|
109 | |||
109 |
|
110 | |||
110 | class TimeitTemplateFiller(ast.NodeTransformer): |
|
111 | class TimeitTemplateFiller(ast.NodeTransformer): | |
111 | """Fill in the AST template for timing execution. |
|
112 | """Fill in the AST template for timing execution. | |
112 |
|
113 | |||
113 | This is quite closely tied to the template definition, which is in |
|
114 | This is quite closely tied to the template definition, which is in | |
114 | :meth:`ExecutionMagics.timeit`. |
|
115 | :meth:`ExecutionMagics.timeit`. | |
115 | """ |
|
116 | """ | |
116 | def __init__(self, ast_setup, ast_stmt): |
|
117 | def __init__(self, ast_setup, ast_stmt): | |
117 | self.ast_setup = ast_setup |
|
118 | self.ast_setup = ast_setup | |
118 | self.ast_stmt = ast_stmt |
|
119 | self.ast_stmt = ast_stmt | |
119 |
|
120 | |||
120 | def visit_FunctionDef(self, node): |
|
121 | def visit_FunctionDef(self, node): | |
121 | "Fill in the setup statement" |
|
122 | "Fill in the setup statement" | |
122 | self.generic_visit(node) |
|
123 | self.generic_visit(node) | |
123 | if node.name == "inner": |
|
124 | if node.name == "inner": | |
124 | node.body[:1] = self.ast_setup.body |
|
125 | node.body[:1] = self.ast_setup.body | |
125 |
|
126 | |||
126 | return node |
|
127 | return node | |
127 |
|
128 | |||
128 | def visit_For(self, node): |
|
129 | def visit_For(self, node): | |
129 | "Fill in the statement to be timed" |
|
130 | "Fill in the statement to be timed" | |
130 | if getattr(getattr(node.body[0], 'value', None), 'id', None) == 'stmt': |
|
131 | if getattr(getattr(node.body[0], 'value', None), 'id', None) == 'stmt': | |
131 | node.body = self.ast_stmt.body |
|
132 | node.body = self.ast_stmt.body | |
132 | return node |
|
133 | return node | |
133 |
|
134 | |||
134 |
|
135 | |||
135 | class Timer(timeit.Timer): |
|
136 | class Timer(timeit.Timer): | |
136 | """Timer class that explicitly uses self.inner |
|
137 | """Timer class that explicitly uses self.inner | |
137 |
|
138 | |||
138 | which is an undocumented implementation detail of CPython, |
|
139 | which is an undocumented implementation detail of CPython, | |
139 | not shared by PyPy. |
|
140 | not shared by PyPy. | |
140 | """ |
|
141 | """ | |
141 | # Timer.timeit copied from CPython 3.4.2 |
|
142 | # Timer.timeit copied from CPython 3.4.2 | |
142 | def timeit(self, number=timeit.default_number): |
|
143 | def timeit(self, number=timeit.default_number): | |
143 | """Time 'number' executions of the main statement. |
|
144 | """Time 'number' executions of the main statement. | |
144 |
|
145 | |||
145 | To be precise, this executes the setup statement once, and |
|
146 | To be precise, this executes the setup statement once, and | |
146 | then returns the time it takes to execute the main statement |
|
147 | then returns the time it takes to execute the main statement | |
147 | a number of times, as a float measured in seconds. The |
|
148 | a number of times, as a float measured in seconds. The | |
148 | argument is the number of times through the loop, defaulting |
|
149 | argument is the number of times through the loop, defaulting | |
149 | to one million. The main statement, the setup statement and |
|
150 | to one million. The main statement, the setup statement and | |
150 | the timer function to be used are passed to the constructor. |
|
151 | the timer function to be used are passed to the constructor. | |
151 | """ |
|
152 | """ | |
152 | it = itertools.repeat(None, number) |
|
153 | it = itertools.repeat(None, number) | |
153 | gcold = gc.isenabled() |
|
154 | gcold = gc.isenabled() | |
154 | gc.disable() |
|
155 | gc.disable() | |
155 | try: |
|
156 | try: | |
156 | timing = self.inner(it, self.timer) |
|
157 | timing = self.inner(it, self.timer) | |
157 | finally: |
|
158 | finally: | |
158 | if gcold: |
|
159 | if gcold: | |
159 | gc.enable() |
|
160 | gc.enable() | |
160 | return timing |
|
161 | return timing | |
161 |
|
162 | |||
162 |
|
163 | |||
163 | @magics_class |
|
164 | @magics_class | |
164 | class ExecutionMagics(Magics): |
|
165 | class ExecutionMagics(Magics): | |
165 | """Magics related to code execution, debugging, profiling, etc. |
|
166 | """Magics related to code execution, debugging, profiling, etc. | |
166 |
|
167 | |||
167 | """ |
|
168 | """ | |
168 |
|
169 | |||
169 | def __init__(self, shell): |
|
170 | def __init__(self, shell): | |
170 | super(ExecutionMagics, self).__init__(shell) |
|
171 | super(ExecutionMagics, self).__init__(shell) | |
171 | # Default execution function used to actually run user code. |
|
172 | # Default execution function used to actually run user code. | |
172 | self.default_runner = None |
|
173 | self.default_runner = None | |
173 |
|
174 | |||
174 | @skip_doctest |
|
175 | @skip_doctest | |
175 | @no_var_expand |
|
176 | @no_var_expand | |
176 | @line_cell_magic |
|
177 | @line_cell_magic | |
177 | def prun(self, parameter_s='', cell=None): |
|
178 | def prun(self, parameter_s='', cell=None): | |
178 |
|
179 | |||
179 | """Run a statement through the python code profiler. |
|
180 | """Run a statement through the python code profiler. | |
180 |
|
181 | |||
181 | Usage, in line mode: |
|
182 | Usage, in line mode: | |
182 | %prun [options] statement |
|
183 | %prun [options] statement | |
183 |
|
184 | |||
184 | Usage, in cell mode: |
|
185 | Usage, in cell mode: | |
185 | %%prun [options] [statement] |
|
186 | %%prun [options] [statement] | |
186 | code... |
|
187 | code... | |
187 | code... |
|
188 | code... | |
188 |
|
189 | |||
189 | In cell mode, the additional code lines are appended to the (possibly |
|
190 | In cell mode, the additional code lines are appended to the (possibly | |
190 | empty) statement in the first line. Cell mode allows you to easily |
|
191 | empty) statement in the first line. Cell mode allows you to easily | |
191 | profile multiline blocks without having to put them in a separate |
|
192 | profile multiline blocks without having to put them in a separate | |
192 | function. |
|
193 | function. | |
193 |
|
194 | |||
194 | The given statement (which doesn't require quote marks) is run via the |
|
195 | The given statement (which doesn't require quote marks) is run via the | |
195 | python profiler in a manner similar to the profile.run() function. |
|
196 | python profiler in a manner similar to the profile.run() function. | |
196 | Namespaces are internally managed to work correctly; profile.run |
|
197 | Namespaces are internally managed to work correctly; profile.run | |
197 | cannot be used in IPython because it makes certain assumptions about |
|
198 | cannot be used in IPython because it makes certain assumptions about | |
198 | namespaces which do not hold under IPython. |
|
199 | namespaces which do not hold under IPython. | |
199 |
|
200 | |||
200 | Options: |
|
201 | Options: | |
201 |
|
202 | |||
202 | -l <limit> |
|
203 | -l <limit> | |
203 | you can place restrictions on what or how much of the |
|
204 | you can place restrictions on what or how much of the | |
204 | profile gets printed. The limit value can be: |
|
205 | profile gets printed. The limit value can be: | |
205 |
|
206 | |||
206 | * A string: only information for function names containing this string |
|
207 | * A string: only information for function names containing this string | |
207 | is printed. |
|
208 | is printed. | |
208 |
|
209 | |||
209 | * An integer: only these many lines are printed. |
|
210 | * An integer: only these many lines are printed. | |
210 |
|
211 | |||
211 | * A float (between 0 and 1): this fraction of the report is printed |
|
212 | * A float (between 0 and 1): this fraction of the report is printed | |
212 | (for example, use a limit of 0.4 to see the topmost 40% only). |
|
213 | (for example, use a limit of 0.4 to see the topmost 40% only). | |
213 |
|
214 | |||
214 | You can combine several limits with repeated use of the option. For |
|
215 | You can combine several limits with repeated use of the option. For | |
215 | example, ``-l __init__ -l 5`` will print only the topmost 5 lines of |
|
216 | example, ``-l __init__ -l 5`` will print only the topmost 5 lines of | |
216 | information about class constructors. |
|
217 | information about class constructors. | |
217 |
|
218 | |||
218 | -r |
|
219 | -r | |
219 | return the pstats.Stats object generated by the profiling. This |
|
220 | return the pstats.Stats object generated by the profiling. This | |
220 | object has all the information about the profile in it, and you can |
|
221 | object has all the information about the profile in it, and you can | |
221 | later use it for further analysis or in other functions. |
|
222 | later use it for further analysis or in other functions. | |
222 |
|
223 | |||
223 | -s <key> |
|
224 | -s <key> | |
224 | sort profile by given key. You can provide more than one key |
|
225 | sort profile by given key. You can provide more than one key | |
225 | by using the option several times: '-s key1 -s key2 -s key3...'. The |
|
226 | by using the option several times: '-s key1 -s key2 -s key3...'. The | |
226 | default sorting key is 'time'. |
|
227 | default sorting key is 'time'. | |
227 |
|
228 | |||
228 | The following is copied verbatim from the profile documentation |
|
229 | The following is copied verbatim from the profile documentation | |
229 | referenced below: |
|
230 | referenced below: | |
230 |
|
231 | |||
231 | When more than one key is provided, additional keys are used as |
|
232 | When more than one key is provided, additional keys are used as | |
232 | secondary criteria when the there is equality in all keys selected |
|
233 | secondary criteria when the there is equality in all keys selected | |
233 | before them. |
|
234 | before them. | |
234 |
|
235 | |||
235 | Abbreviations can be used for any key names, as long as the |
|
236 | Abbreviations can be used for any key names, as long as the | |
236 | abbreviation is unambiguous. The following are the keys currently |
|
237 | abbreviation is unambiguous. The following are the keys currently | |
237 | defined: |
|
238 | defined: | |
238 |
|
239 | |||
239 | ============ ===================== |
|
240 | ============ ===================== | |
240 | Valid Arg Meaning |
|
241 | Valid Arg Meaning | |
241 | ============ ===================== |
|
242 | ============ ===================== | |
242 | "calls" call count |
|
243 | "calls" call count | |
243 | "cumulative" cumulative time |
|
244 | "cumulative" cumulative time | |
244 | "file" file name |
|
245 | "file" file name | |
245 | "module" file name |
|
246 | "module" file name | |
246 | "pcalls" primitive call count |
|
247 | "pcalls" primitive call count | |
247 | "line" line number |
|
248 | "line" line number | |
248 | "name" function name |
|
249 | "name" function name | |
249 | "nfl" name/file/line |
|
250 | "nfl" name/file/line | |
250 | "stdname" standard name |
|
251 | "stdname" standard name | |
251 | "time" internal time |
|
252 | "time" internal time | |
252 | ============ ===================== |
|
253 | ============ ===================== | |
253 |
|
254 | |||
254 | Note that all sorts on statistics are in descending order (placing |
|
255 | Note that all sorts on statistics are in descending order (placing | |
255 | most time consuming items first), where as name, file, and line number |
|
256 | most time consuming items first), where as name, file, and line number | |
256 | searches are in ascending order (i.e., alphabetical). The subtle |
|
257 | searches are in ascending order (i.e., alphabetical). The subtle | |
257 | distinction between "nfl" and "stdname" is that the standard name is a |
|
258 | distinction between "nfl" and "stdname" is that the standard name is a | |
258 | sort of the name as printed, which means that the embedded line |
|
259 | sort of the name as printed, which means that the embedded line | |
259 | numbers get compared in an odd way. For example, lines 3, 20, and 40 |
|
260 | numbers get compared in an odd way. For example, lines 3, 20, and 40 | |
260 | would (if the file names were the same) appear in the string order |
|
261 | would (if the file names were the same) appear in the string order | |
261 | "20" "3" and "40". In contrast, "nfl" does a numeric compare of the |
|
262 | "20" "3" and "40". In contrast, "nfl" does a numeric compare of the | |
262 | line numbers. In fact, sort_stats("nfl") is the same as |
|
263 | line numbers. In fact, sort_stats("nfl") is the same as | |
263 | sort_stats("name", "file", "line"). |
|
264 | sort_stats("name", "file", "line"). | |
264 |
|
265 | |||
265 | -T <filename> |
|
266 | -T <filename> | |
266 | save profile results as shown on screen to a text |
|
267 | save profile results as shown on screen to a text | |
267 | file. The profile is still shown on screen. |
|
268 | file. The profile is still shown on screen. | |
268 |
|
269 | |||
269 | -D <filename> |
|
270 | -D <filename> | |
270 | save (via dump_stats) profile statistics to given |
|
271 | save (via dump_stats) profile statistics to given | |
271 | filename. This data is in a format understood by the pstats module, and |
|
272 | filename. This data is in a format understood by the pstats module, and | |
272 | is generated by a call to the dump_stats() method of profile |
|
273 | is generated by a call to the dump_stats() method of profile | |
273 | objects. The profile is still shown on screen. |
|
274 | objects. The profile is still shown on screen. | |
274 |
|
275 | |||
275 | -q |
|
276 | -q | |
276 | suppress output to the pager. Best used with -T and/or -D above. |
|
277 | suppress output to the pager. Best used with -T and/or -D above. | |
277 |
|
278 | |||
278 | If you want to run complete programs under the profiler's control, use |
|
279 | If you want to run complete programs under the profiler's control, use | |
279 | ``%run -p [prof_opts] filename.py [args to program]`` where prof_opts |
|
280 | ``%run -p [prof_opts] filename.py [args to program]`` where prof_opts | |
280 | contains profiler specific options as described here. |
|
281 | contains profiler specific options as described here. | |
281 |
|
282 | |||
282 | You can read the complete documentation for the profile module with:: |
|
283 | You can read the complete documentation for the profile module with:: | |
283 |
|
284 | |||
284 | In [1]: import profile; profile.help() |
|
285 | In [1]: import profile; profile.help() | |
285 |
|
286 | |||
286 | .. versionchanged:: 7.3 |
|
287 | .. versionchanged:: 7.3 | |
287 | User variables are no longer expanded, |
|
288 | User variables are no longer expanded, | |
288 | the magic line is always left unmodified. |
|
289 | the magic line is always left unmodified. | |
289 |
|
290 | |||
290 | """ |
|
291 | """ | |
291 | opts, arg_str = self.parse_options(parameter_s, 'D:l:rs:T:q', |
|
292 | opts, arg_str = self.parse_options(parameter_s, 'D:l:rs:T:q', | |
292 | list_all=True, posix=False) |
|
293 | list_all=True, posix=False) | |
293 | if cell is not None: |
|
294 | if cell is not None: | |
294 | arg_str += '\n' + cell |
|
295 | arg_str += '\n' + cell | |
295 | arg_str = self.shell.transform_cell(arg_str) |
|
296 | arg_str = self.shell.transform_cell(arg_str) | |
296 | return self._run_with_profiler(arg_str, opts, self.shell.user_ns) |
|
297 | return self._run_with_profiler(arg_str, opts, self.shell.user_ns) | |
297 |
|
298 | |||
298 | def _run_with_profiler(self, code, opts, namespace): |
|
299 | def _run_with_profiler(self, code, opts, namespace): | |
299 | """ |
|
300 | """ | |
300 | Run `code` with profiler. Used by ``%prun`` and ``%run -p``. |
|
301 | Run `code` with profiler. Used by ``%prun`` and ``%run -p``. | |
301 |
|
302 | |||
302 | Parameters |
|
303 | Parameters | |
303 | ---------- |
|
304 | ---------- | |
304 | code : str |
|
305 | code : str | |
305 | Code to be executed. |
|
306 | Code to be executed. | |
306 | opts : Struct |
|
307 | opts : Struct | |
307 | Options parsed by `self.parse_options`. |
|
308 | Options parsed by `self.parse_options`. | |
308 | namespace : dict |
|
309 | namespace : dict | |
309 | A dictionary for Python namespace (e.g., `self.shell.user_ns`). |
|
310 | A dictionary for Python namespace (e.g., `self.shell.user_ns`). | |
310 |
|
311 | |||
311 | """ |
|
312 | """ | |
312 |
|
313 | |||
313 | # Fill default values for unspecified options: |
|
314 | # Fill default values for unspecified options: | |
314 | opts.merge(Struct(D=[''], l=[], s=['time'], T=[''])) |
|
315 | opts.merge(Struct(D=[''], l=[], s=['time'], T=[''])) | |
315 |
|
316 | |||
316 | prof = profile.Profile() |
|
317 | prof = profile.Profile() | |
317 | try: |
|
318 | try: | |
318 | prof = prof.runctx(code, namespace, namespace) |
|
319 | prof = prof.runctx(code, namespace, namespace) | |
319 | sys_exit = '' |
|
320 | sys_exit = '' | |
320 | except SystemExit: |
|
321 | except SystemExit: | |
321 | sys_exit = """*** SystemExit exception caught in code being profiled.""" |
|
322 | sys_exit = """*** SystemExit exception caught in code being profiled.""" | |
322 |
|
323 | |||
323 | stats = pstats.Stats(prof).strip_dirs().sort_stats(*opts.s) |
|
324 | stats = pstats.Stats(prof).strip_dirs().sort_stats(*opts.s) | |
324 |
|
325 | |||
325 | lims = opts.l |
|
326 | lims = opts.l | |
326 | if lims: |
|
327 | if lims: | |
327 | lims = [] # rebuild lims with ints/floats/strings |
|
328 | lims = [] # rebuild lims with ints/floats/strings | |
328 | for lim in opts.l: |
|
329 | for lim in opts.l: | |
329 | try: |
|
330 | try: | |
330 | lims.append(int(lim)) |
|
331 | lims.append(int(lim)) | |
331 | except ValueError: |
|
332 | except ValueError: | |
332 | try: |
|
333 | try: | |
333 | lims.append(float(lim)) |
|
334 | lims.append(float(lim)) | |
334 | except ValueError: |
|
335 | except ValueError: | |
335 | lims.append(lim) |
|
336 | lims.append(lim) | |
336 |
|
337 | |||
337 | # Trap output. |
|
338 | # Trap output. | |
338 | stdout_trap = StringIO() |
|
339 | stdout_trap = StringIO() | |
339 | stats_stream = stats.stream |
|
340 | stats_stream = stats.stream | |
340 | try: |
|
341 | try: | |
341 | stats.stream = stdout_trap |
|
342 | stats.stream = stdout_trap | |
342 | stats.print_stats(*lims) |
|
343 | stats.print_stats(*lims) | |
343 | finally: |
|
344 | finally: | |
344 | stats.stream = stats_stream |
|
345 | stats.stream = stats_stream | |
345 |
|
346 | |||
346 | output = stdout_trap.getvalue() |
|
347 | output = stdout_trap.getvalue() | |
347 | output = output.rstrip() |
|
348 | output = output.rstrip() | |
348 |
|
349 | |||
349 | if 'q' not in opts: |
|
350 | if 'q' not in opts: | |
350 | page.page(output) |
|
351 | page.page(output) | |
351 | print(sys_exit, end=' ') |
|
352 | print(sys_exit, end=' ') | |
352 |
|
353 | |||
353 | dump_file = opts.D[0] |
|
354 | dump_file = opts.D[0] | |
354 | text_file = opts.T[0] |
|
355 | text_file = opts.T[0] | |
355 | if dump_file: |
|
356 | if dump_file: | |
356 | prof.dump_stats(dump_file) |
|
357 | prof.dump_stats(dump_file) | |
357 | print( |
|
358 | print( | |
358 | f"\n*** Profile stats marshalled to file {repr(dump_file)}.{sys_exit}" |
|
359 | f"\n*** Profile stats marshalled to file {repr(dump_file)}.{sys_exit}" | |
359 | ) |
|
360 | ) | |
360 | if text_file: |
|
361 | if text_file: | |
361 | pfile = Path(text_file) |
|
362 | pfile = Path(text_file) | |
362 | pfile.touch(exist_ok=True) |
|
363 | pfile.touch(exist_ok=True) | |
363 | pfile.write_text(output, encoding="utf-8") |
|
364 | pfile.write_text(output, encoding="utf-8") | |
364 |
|
365 | |||
365 | print( |
|
366 | print( | |
366 | f"\n*** Profile printout saved to text file {repr(text_file)}.{sys_exit}" |
|
367 | f"\n*** Profile printout saved to text file {repr(text_file)}.{sys_exit}" | |
367 | ) |
|
368 | ) | |
368 |
|
369 | |||
369 | if 'r' in opts: |
|
370 | if 'r' in opts: | |
370 | return stats |
|
371 | return stats | |
371 |
|
372 | |||
372 | return None |
|
373 | return None | |
373 |
|
374 | |||
374 | @line_magic |
|
375 | @line_magic | |
375 | def pdb(self, parameter_s=''): |
|
376 | def pdb(self, parameter_s=''): | |
376 | """Control the automatic calling of the pdb interactive debugger. |
|
377 | """Control the automatic calling of the pdb interactive debugger. | |
377 |
|
378 | |||
378 | Call as '%pdb on', '%pdb 1', '%pdb off' or '%pdb 0'. If called without |
|
379 | Call as '%pdb on', '%pdb 1', '%pdb off' or '%pdb 0'. If called without | |
379 | argument it works as a toggle. |
|
380 | argument it works as a toggle. | |
380 |
|
381 | |||
381 | When an exception is triggered, IPython can optionally call the |
|
382 | When an exception is triggered, IPython can optionally call the | |
382 | interactive pdb debugger after the traceback printout. %pdb toggles |
|
383 | interactive pdb debugger after the traceback printout. %pdb toggles | |
383 | this feature on and off. |
|
384 | this feature on and off. | |
384 |
|
385 | |||
385 | The initial state of this feature is set in your configuration |
|
386 | The initial state of this feature is set in your configuration | |
386 | file (the option is ``InteractiveShell.pdb``). |
|
387 | file (the option is ``InteractiveShell.pdb``). | |
387 |
|
388 | |||
388 | If you want to just activate the debugger AFTER an exception has fired, |
|
389 | If you want to just activate the debugger AFTER an exception has fired, | |
389 | without having to type '%pdb on' and rerunning your code, you can use |
|
390 | without having to type '%pdb on' and rerunning your code, you can use | |
390 | the %debug magic.""" |
|
391 | the %debug magic.""" | |
391 |
|
392 | |||
392 | par = parameter_s.strip().lower() |
|
393 | par = parameter_s.strip().lower() | |
393 |
|
394 | |||
394 | if par: |
|
395 | if par: | |
395 | try: |
|
396 | try: | |
396 | new_pdb = {'off':0,'0':0,'on':1,'1':1}[par] |
|
397 | new_pdb = {'off':0,'0':0,'on':1,'1':1}[par] | |
397 | except KeyError: |
|
398 | except KeyError: | |
398 | print ('Incorrect argument. Use on/1, off/0, ' |
|
399 | print ('Incorrect argument. Use on/1, off/0, ' | |
399 | 'or nothing for a toggle.') |
|
400 | 'or nothing for a toggle.') | |
400 | return |
|
401 | return | |
401 | else: |
|
402 | else: | |
402 | # toggle |
|
403 | # toggle | |
403 | new_pdb = not self.shell.call_pdb |
|
404 | new_pdb = not self.shell.call_pdb | |
404 |
|
405 | |||
405 | # set on the shell |
|
406 | # set on the shell | |
406 | self.shell.call_pdb = new_pdb |
|
407 | self.shell.call_pdb = new_pdb | |
407 | print('Automatic pdb calling has been turned',on_off(new_pdb)) |
|
408 | print('Automatic pdb calling has been turned',on_off(new_pdb)) | |
408 |
|
409 | |||
409 | @magic_arguments.magic_arguments() |
|
410 | @magic_arguments.magic_arguments() | |
410 | @magic_arguments.argument('--breakpoint', '-b', metavar='FILE:LINE', |
|
411 | @magic_arguments.argument('--breakpoint', '-b', metavar='FILE:LINE', | |
411 | help=""" |
|
412 | help=""" | |
412 | Set break point at LINE in FILE. |
|
413 | Set break point at LINE in FILE. | |
413 | """ |
|
414 | """ | |
414 | ) |
|
415 | ) | |
415 | @magic_arguments.argument('statement', nargs='*', |
|
416 | @magic_arguments.argument('statement', nargs='*', | |
416 | help=""" |
|
417 | help=""" | |
417 | Code to run in debugger. |
|
418 | Code to run in debugger. | |
418 | You can omit this in cell magic mode. |
|
419 | You can omit this in cell magic mode. | |
419 | """ |
|
420 | """ | |
420 | ) |
|
421 | ) | |
421 | @no_var_expand |
|
422 | @no_var_expand | |
422 | @line_cell_magic |
|
423 | @line_cell_magic | |
423 | def debug(self, line='', cell=None): |
|
424 | def debug(self, line='', cell=None): | |
424 | """Activate the interactive debugger. |
|
425 | """Activate the interactive debugger. | |
425 |
|
426 | |||
426 | This magic command support two ways of activating debugger. |
|
427 | This magic command support two ways of activating debugger. | |
427 | One is to activate debugger before executing code. This way, you |
|
428 | One is to activate debugger before executing code. This way, you | |
428 | can set a break point, to step through the code from the point. |
|
429 | can set a break point, to step through the code from the point. | |
429 | You can use this mode by giving statements to execute and optionally |
|
430 | You can use this mode by giving statements to execute and optionally | |
430 | a breakpoint. |
|
431 | a breakpoint. | |
431 |
|
432 | |||
432 | The other one is to activate debugger in post-mortem mode. You can |
|
433 | The other one is to activate debugger in post-mortem mode. You can | |
433 | activate this mode simply running %debug without any argument. |
|
434 | activate this mode simply running %debug without any argument. | |
434 | If an exception has just occurred, this lets you inspect its stack |
|
435 | If an exception has just occurred, this lets you inspect its stack | |
435 | frames interactively. Note that this will always work only on the last |
|
436 | frames interactively. Note that this will always work only on the last | |
436 | traceback that occurred, so you must call this quickly after an |
|
437 | traceback that occurred, so you must call this quickly after an | |
437 | exception that you wish to inspect has fired, because if another one |
|
438 | exception that you wish to inspect has fired, because if another one | |
438 | occurs, it clobbers the previous one. |
|
439 | occurs, it clobbers the previous one. | |
439 |
|
440 | |||
440 | If you want IPython to automatically do this on every exception, see |
|
441 | If you want IPython to automatically do this on every exception, see | |
441 | the %pdb magic for more details. |
|
442 | the %pdb magic for more details. | |
442 |
|
443 | |||
443 | .. versionchanged:: 7.3 |
|
444 | .. versionchanged:: 7.3 | |
444 | When running code, user variables are no longer expanded, |
|
445 | When running code, user variables are no longer expanded, | |
445 | the magic line is always left unmodified. |
|
446 | the magic line is always left unmodified. | |
446 |
|
447 | |||
447 | """ |
|
448 | """ | |
448 | args = magic_arguments.parse_argstring(self.debug, line) |
|
449 | args = magic_arguments.parse_argstring(self.debug, line) | |
449 |
|
450 | |||
450 | if not (args.breakpoint or args.statement or cell): |
|
451 | if not (args.breakpoint or args.statement or cell): | |
451 | self._debug_post_mortem() |
|
452 | self._debug_post_mortem() | |
452 | elif not (args.breakpoint or cell): |
|
453 | elif not (args.breakpoint or cell): | |
453 | # If there is no breakpoints, the line is just code to execute |
|
454 | # If there is no breakpoints, the line is just code to execute | |
454 | self._debug_exec(line, None) |
|
455 | self._debug_exec(line, None) | |
455 | else: |
|
456 | else: | |
456 | # Here we try to reconstruct the code from the output of |
|
457 | # Here we try to reconstruct the code from the output of | |
457 | # parse_argstring. This might not work if the code has spaces |
|
458 | # parse_argstring. This might not work if the code has spaces | |
458 | # For example this fails for `print("a b")` |
|
459 | # For example this fails for `print("a b")` | |
459 | code = "\n".join(args.statement) |
|
460 | code = "\n".join(args.statement) | |
460 | if cell: |
|
461 | if cell: | |
461 | code += "\n" + cell |
|
462 | code += "\n" + cell | |
462 | self._debug_exec(code, args.breakpoint) |
|
463 | self._debug_exec(code, args.breakpoint) | |
463 |
|
464 | |||
464 | def _debug_post_mortem(self): |
|
465 | def _debug_post_mortem(self): | |
465 | self.shell.debugger(force=True) |
|
466 | self.shell.debugger(force=True) | |
466 |
|
467 | |||
467 | def _debug_exec(self, code, breakpoint): |
|
468 | def _debug_exec(self, code, breakpoint): | |
468 | if breakpoint: |
|
469 | if breakpoint: | |
469 | (filename, bp_line) = breakpoint.rsplit(':', 1) |
|
470 | (filename, bp_line) = breakpoint.rsplit(':', 1) | |
470 | bp_line = int(bp_line) |
|
471 | bp_line = int(bp_line) | |
471 | else: |
|
472 | else: | |
472 | (filename, bp_line) = (None, None) |
|
473 | (filename, bp_line) = (None, None) | |
473 | self._run_with_debugger(code, self.shell.user_ns, filename, bp_line) |
|
474 | self._run_with_debugger(code, self.shell.user_ns, filename, bp_line) | |
474 |
|
475 | |||
475 | @line_magic |
|
476 | @line_magic | |
476 | def tb(self, s): |
|
477 | def tb(self, s): | |
477 | """Print the last traceback. |
|
478 | """Print the last traceback. | |
478 |
|
479 | |||
479 | Optionally, specify an exception reporting mode, tuning the |
|
480 | Optionally, specify an exception reporting mode, tuning the | |
480 | verbosity of the traceback. By default the currently-active exception |
|
481 | verbosity of the traceback. By default the currently-active exception | |
481 | mode is used. See %xmode for changing exception reporting modes. |
|
482 | mode is used. See %xmode for changing exception reporting modes. | |
482 |
|
483 | |||
483 | Valid modes: Plain, Context, Verbose, and Minimal. |
|
484 | Valid modes: Plain, Context, Verbose, and Minimal. | |
484 | """ |
|
485 | """ | |
485 | interactive_tb = self.shell.InteractiveTB |
|
486 | interactive_tb = self.shell.InteractiveTB | |
486 | if s: |
|
487 | if s: | |
487 | # Switch exception reporting mode for this one call. |
|
488 | # Switch exception reporting mode for this one call. | |
488 | # Ensure it is switched back. |
|
489 | # Ensure it is switched back. | |
489 | def xmode_switch_err(name): |
|
490 | def xmode_switch_err(name): | |
490 | warn('Error changing %s exception modes.\n%s' % |
|
491 | warn('Error changing %s exception modes.\n%s' % | |
491 | (name,sys.exc_info()[1])) |
|
492 | (name,sys.exc_info()[1])) | |
492 |
|
493 | |||
493 | new_mode = s.strip().capitalize() |
|
494 | new_mode = s.strip().capitalize() | |
494 | original_mode = interactive_tb.mode |
|
495 | original_mode = interactive_tb.mode | |
495 | try: |
|
496 | try: | |
496 | try: |
|
497 | try: | |
497 | interactive_tb.set_mode(mode=new_mode) |
|
498 | interactive_tb.set_mode(mode=new_mode) | |
498 | except Exception: |
|
499 | except Exception: | |
499 | xmode_switch_err('user') |
|
500 | xmode_switch_err('user') | |
500 | else: |
|
501 | else: | |
501 | self.shell.showtraceback() |
|
502 | self.shell.showtraceback() | |
502 | finally: |
|
503 | finally: | |
503 | interactive_tb.set_mode(mode=original_mode) |
|
504 | interactive_tb.set_mode(mode=original_mode) | |
504 | else: |
|
505 | else: | |
505 | self.shell.showtraceback() |
|
506 | self.shell.showtraceback() | |
506 |
|
507 | |||
507 | @skip_doctest |
|
508 | @skip_doctest | |
508 | @line_magic |
|
509 | @line_magic | |
509 | def run(self, parameter_s='', runner=None, |
|
510 | def run(self, parameter_s='', runner=None, | |
510 | file_finder=get_py_filename): |
|
511 | file_finder=get_py_filename): | |
511 | """Run the named file inside IPython as a program. |
|
512 | """Run the named file inside IPython as a program. | |
512 |
|
513 | |||
513 | Usage:: |
|
514 | Usage:: | |
514 |
|
515 | |||
515 | %run [-n -i -e -G] |
|
516 | %run [-n -i -e -G] | |
516 | [( -t [-N<N>] | -d [-b<N>] | -p [profile options] )] |
|
517 | [( -t [-N<N>] | -d [-b<N>] | -p [profile options] )] | |
517 | ( -m mod | filename ) [args] |
|
518 | ( -m mod | filename ) [args] | |
518 |
|
519 | |||
519 | The filename argument should be either a pure Python script (with |
|
520 | The filename argument should be either a pure Python script (with | |
520 | extension ``.py``), or a file with custom IPython syntax (such as |
|
521 | extension ``.py``), or a file with custom IPython syntax (such as | |
521 | magics). If the latter, the file can be either a script with ``.ipy`` |
|
522 | magics). If the latter, the file can be either a script with ``.ipy`` | |
522 | extension, or a Jupyter notebook with ``.ipynb`` extension. When running |
|
523 | extension, or a Jupyter notebook with ``.ipynb`` extension. When running | |
523 | a Jupyter notebook, the output from print statements and other |
|
524 | a Jupyter notebook, the output from print statements and other | |
524 | displayed objects will appear in the terminal (even matplotlib figures |
|
525 | displayed objects will appear in the terminal (even matplotlib figures | |
525 | will open, if a terminal-compliant backend is being used). Note that, |
|
526 | will open, if a terminal-compliant backend is being used). Note that, | |
526 | at the system command line, the ``jupyter run`` command offers similar |
|
527 | at the system command line, the ``jupyter run`` command offers similar | |
527 | functionality for executing notebooks (albeit currently with some |
|
528 | functionality for executing notebooks (albeit currently with some | |
528 | differences in supported options). |
|
529 | differences in supported options). | |
529 |
|
530 | |||
530 | Parameters after the filename are passed as command-line arguments to |
|
531 | Parameters after the filename are passed as command-line arguments to | |
531 | the program (put in sys.argv). Then, control returns to IPython's |
|
532 | the program (put in sys.argv). Then, control returns to IPython's | |
532 | prompt. |
|
533 | prompt. | |
533 |
|
534 | |||
534 | This is similar to running at a system prompt ``python file args``, |
|
535 | This is similar to running at a system prompt ``python file args``, | |
535 | but with the advantage of giving you IPython's tracebacks, and of |
|
536 | but with the advantage of giving you IPython's tracebacks, and of | |
536 | loading all variables into your interactive namespace for further use |
|
537 | loading all variables into your interactive namespace for further use | |
537 | (unless -p is used, see below). |
|
538 | (unless -p is used, see below). | |
538 |
|
539 | |||
539 | The file is executed in a namespace initially consisting only of |
|
540 | The file is executed in a namespace initially consisting only of | |
540 | ``__name__=='__main__'`` and sys.argv constructed as indicated. It thus |
|
541 | ``__name__=='__main__'`` and sys.argv constructed as indicated. It thus | |
541 | sees its environment as if it were being run as a stand-alone program |
|
542 | sees its environment as if it were being run as a stand-alone program | |
542 | (except for sharing global objects such as previously imported |
|
543 | (except for sharing global objects such as previously imported | |
543 | modules). But after execution, the IPython interactive namespace gets |
|
544 | modules). But after execution, the IPython interactive namespace gets | |
544 | updated with all variables defined in the program (except for __name__ |
|
545 | updated with all variables defined in the program (except for __name__ | |
545 | and sys.argv). This allows for very convenient loading of code for |
|
546 | and sys.argv). This allows for very convenient loading of code for | |
546 | interactive work, while giving each program a 'clean sheet' to run in. |
|
547 | interactive work, while giving each program a 'clean sheet' to run in. | |
547 |
|
548 | |||
548 | Arguments are expanded using shell-like glob match. Patterns |
|
549 | Arguments are expanded using shell-like glob match. Patterns | |
549 | '*', '?', '[seq]' and '[!seq]' can be used. Additionally, |
|
550 | '*', '?', '[seq]' and '[!seq]' can be used. Additionally, | |
550 | tilde '~' will be expanded into user's home directory. Unlike |
|
551 | tilde '~' will be expanded into user's home directory. Unlike | |
551 | real shells, quotation does not suppress expansions. Use |
|
552 | real shells, quotation does not suppress expansions. Use | |
552 | *two* back slashes (e.g. ``\\\\*``) to suppress expansions. |
|
553 | *two* back slashes (e.g. ``\\\\*``) to suppress expansions. | |
553 | To completely disable these expansions, you can use -G flag. |
|
554 | To completely disable these expansions, you can use -G flag. | |
554 |
|
555 | |||
555 | On Windows systems, the use of single quotes `'` when specifying |
|
556 | On Windows systems, the use of single quotes `'` when specifying | |
556 | a file is not supported. Use double quotes `"`. |
|
557 | a file is not supported. Use double quotes `"`. | |
557 |
|
558 | |||
558 | Options: |
|
559 | Options: | |
559 |
|
560 | |||
560 | -n |
|
561 | -n | |
561 | __name__ is NOT set to '__main__', but to the running file's name |
|
562 | __name__ is NOT set to '__main__', but to the running file's name | |
562 | without extension (as python does under import). This allows running |
|
563 | without extension (as python does under import). This allows running | |
563 | scripts and reloading the definitions in them without calling code |
|
564 | scripts and reloading the definitions in them without calling code | |
564 | protected by an ``if __name__ == "__main__"`` clause. |
|
565 | protected by an ``if __name__ == "__main__"`` clause. | |
565 |
|
566 | |||
566 | -i |
|
567 | -i | |
567 | run the file in IPython's namespace instead of an empty one. This |
|
568 | run the file in IPython's namespace instead of an empty one. This | |
568 | is useful if you are experimenting with code written in a text editor |
|
569 | is useful if you are experimenting with code written in a text editor | |
569 | which depends on variables defined interactively. |
|
570 | which depends on variables defined interactively. | |
570 |
|
571 | |||
571 | -e |
|
572 | -e | |
572 | ignore sys.exit() calls or SystemExit exceptions in the script |
|
573 | ignore sys.exit() calls or SystemExit exceptions in the script | |
573 | being run. This is particularly useful if IPython is being used to |
|
574 | being run. This is particularly useful if IPython is being used to | |
574 | run unittests, which always exit with a sys.exit() call. In such |
|
575 | run unittests, which always exit with a sys.exit() call. In such | |
575 | cases you are interested in the output of the test results, not in |
|
576 | cases you are interested in the output of the test results, not in | |
576 | seeing a traceback of the unittest module. |
|
577 | seeing a traceback of the unittest module. | |
577 |
|
578 | |||
578 | -t |
|
579 | -t | |
579 | print timing information at the end of the run. IPython will give |
|
580 | print timing information at the end of the run. IPython will give | |
580 | you an estimated CPU time consumption for your script, which under |
|
581 | you an estimated CPU time consumption for your script, which under | |
581 | Unix uses the resource module to avoid the wraparound problems of |
|
582 | Unix uses the resource module to avoid the wraparound problems of | |
582 | time.clock(). Under Unix, an estimate of time spent on system tasks |
|
583 | time.clock(). Under Unix, an estimate of time spent on system tasks | |
583 | is also given (for Windows platforms this is reported as 0.0). |
|
584 | is also given (for Windows platforms this is reported as 0.0). | |
584 |
|
585 | |||
585 | If -t is given, an additional ``-N<N>`` option can be given, where <N> |
|
586 | If -t is given, an additional ``-N<N>`` option can be given, where <N> | |
586 | must be an integer indicating how many times you want the script to |
|
587 | must be an integer indicating how many times you want the script to | |
587 | run. The final timing report will include total and per run results. |
|
588 | run. The final timing report will include total and per run results. | |
588 |
|
589 | |||
589 | For example (testing the script uniq_stable.py):: |
|
590 | For example (testing the script uniq_stable.py):: | |
590 |
|
591 | |||
591 | In [1]: run -t uniq_stable |
|
592 | In [1]: run -t uniq_stable | |
592 |
|
593 | |||
593 | IPython CPU timings (estimated): |
|
594 | IPython CPU timings (estimated): | |
594 | User : 0.19597 s. |
|
595 | User : 0.19597 s. | |
595 | System: 0.0 s. |
|
596 | System: 0.0 s. | |
596 |
|
597 | |||
597 | In [2]: run -t -N5 uniq_stable |
|
598 | In [2]: run -t -N5 uniq_stable | |
598 |
|
599 | |||
599 | IPython CPU timings (estimated): |
|
600 | IPython CPU timings (estimated): | |
600 | Total runs performed: 5 |
|
601 | Total runs performed: 5 | |
601 | Times : Total Per run |
|
602 | Times : Total Per run | |
602 | User : 0.910862 s, 0.1821724 s. |
|
603 | User : 0.910862 s, 0.1821724 s. | |
603 | System: 0.0 s, 0.0 s. |
|
604 | System: 0.0 s, 0.0 s. | |
604 |
|
605 | |||
605 | -d |
|
606 | -d | |
606 | run your program under the control of pdb, the Python debugger. |
|
607 | run your program under the control of pdb, the Python debugger. | |
607 | This allows you to execute your program step by step, watch variables, |
|
608 | This allows you to execute your program step by step, watch variables, | |
608 | etc. Internally, what IPython does is similar to calling:: |
|
609 | etc. Internally, what IPython does is similar to calling:: | |
609 |
|
610 | |||
610 | pdb.run('execfile("YOURFILENAME")') |
|
611 | pdb.run('execfile("YOURFILENAME")') | |
611 |
|
612 | |||
612 | with a breakpoint set on line 1 of your file. You can change the line |
|
613 | with a breakpoint set on line 1 of your file. You can change the line | |
613 | number for this automatic breakpoint to be <N> by using the -bN option |
|
614 | number for this automatic breakpoint to be <N> by using the -bN option | |
614 | (where N must be an integer). For example:: |
|
615 | (where N must be an integer). For example:: | |
615 |
|
616 | |||
616 | %run -d -b40 myscript |
|
617 | %run -d -b40 myscript | |
617 |
|
618 | |||
618 | will set the first breakpoint at line 40 in myscript.py. Note that |
|
619 | will set the first breakpoint at line 40 in myscript.py. Note that | |
619 | the first breakpoint must be set on a line which actually does |
|
620 | the first breakpoint must be set on a line which actually does | |
620 | something (not a comment or docstring) for it to stop execution. |
|
621 | something (not a comment or docstring) for it to stop execution. | |
621 |
|
622 | |||
622 | Or you can specify a breakpoint in a different file:: |
|
623 | Or you can specify a breakpoint in a different file:: | |
623 |
|
624 | |||
624 | %run -d -b myotherfile.py:20 myscript |
|
625 | %run -d -b myotherfile.py:20 myscript | |
625 |
|
626 | |||
626 | When the pdb debugger starts, you will see a (Pdb) prompt. You must |
|
627 | When the pdb debugger starts, you will see a (Pdb) prompt. You must | |
627 | first enter 'c' (without quotes) to start execution up to the first |
|
628 | first enter 'c' (without quotes) to start execution up to the first | |
628 | breakpoint. |
|
629 | breakpoint. | |
629 |
|
630 | |||
630 | Entering 'help' gives information about the use of the debugger. You |
|
631 | Entering 'help' gives information about the use of the debugger. You | |
631 | can easily see pdb's full documentation with "import pdb;pdb.help()" |
|
632 | can easily see pdb's full documentation with "import pdb;pdb.help()" | |
632 | at a prompt. |
|
633 | at a prompt. | |
633 |
|
634 | |||
634 | -p |
|
635 | -p | |
635 | run program under the control of the Python profiler module (which |
|
636 | run program under the control of the Python profiler module (which | |
636 | prints a detailed report of execution times, function calls, etc). |
|
637 | prints a detailed report of execution times, function calls, etc). | |
637 |
|
638 | |||
638 | You can pass other options after -p which affect the behavior of the |
|
639 | You can pass other options after -p which affect the behavior of the | |
639 | profiler itself. See the docs for %prun for details. |
|
640 | profiler itself. See the docs for %prun for details. | |
640 |
|
641 | |||
641 | In this mode, the program's variables do NOT propagate back to the |
|
642 | In this mode, the program's variables do NOT propagate back to the | |
642 | IPython interactive namespace (because they remain in the namespace |
|
643 | IPython interactive namespace (because they remain in the namespace | |
643 | where the profiler executes them). |
|
644 | where the profiler executes them). | |
644 |
|
645 | |||
645 | Internally this triggers a call to %prun, see its documentation for |
|
646 | Internally this triggers a call to %prun, see its documentation for | |
646 | details on the options available specifically for profiling. |
|
647 | details on the options available specifically for profiling. | |
647 |
|
648 | |||
648 | There is one special usage for which the text above doesn't apply: |
|
649 | There is one special usage for which the text above doesn't apply: | |
649 | if the filename ends with .ipy[nb], the file is run as ipython script, |
|
650 | if the filename ends with .ipy[nb], the file is run as ipython script, | |
650 | just as if the commands were written on IPython prompt. |
|
651 | just as if the commands were written on IPython prompt. | |
651 |
|
652 | |||
652 | -m |
|
653 | -m | |
653 | specify module name to load instead of script path. Similar to |
|
654 | specify module name to load instead of script path. Similar to | |
654 | the -m option for the python interpreter. Use this option last if you |
|
655 | the -m option for the python interpreter. Use this option last if you | |
655 | want to combine with other %run options. Unlike the python interpreter |
|
656 | want to combine with other %run options. Unlike the python interpreter | |
656 | only source modules are allowed no .pyc or .pyo files. |
|
657 | only source modules are allowed no .pyc or .pyo files. | |
657 | For example:: |
|
658 | For example:: | |
658 |
|
659 | |||
659 | %run -m example |
|
660 | %run -m example | |
660 |
|
661 | |||
661 | will run the example module. |
|
662 | will run the example module. | |
662 |
|
663 | |||
663 | -G |
|
664 | -G | |
664 | disable shell-like glob expansion of arguments. |
|
665 | disable shell-like glob expansion of arguments. | |
665 |
|
666 | |||
666 | """ |
|
667 | """ | |
667 |
|
668 | |||
668 | # Logic to handle issue #3664 |
|
669 | # Logic to handle issue #3664 | |
669 | # Add '--' after '-m <module_name>' to ignore additional args passed to a module. |
|
670 | # Add '--' after '-m <module_name>' to ignore additional args passed to a module. | |
670 | if '-m' in parameter_s and '--' not in parameter_s: |
|
671 | if '-m' in parameter_s and '--' not in parameter_s: | |
671 | argv = shlex.split(parameter_s, posix=(os.name == 'posix')) |
|
672 | argv = shlex.split(parameter_s, posix=(os.name == 'posix')) | |
672 | for idx, arg in enumerate(argv): |
|
673 | for idx, arg in enumerate(argv): | |
673 | if arg and arg.startswith('-') and arg != '-': |
|
674 | if arg and arg.startswith('-') and arg != '-': | |
674 | if arg == '-m': |
|
675 | if arg == '-m': | |
675 | argv.insert(idx + 2, '--') |
|
676 | argv.insert(idx + 2, '--') | |
676 | break |
|
677 | break | |
677 | else: |
|
678 | else: | |
678 | # Positional arg, break |
|
679 | # Positional arg, break | |
679 | break |
|
680 | break | |
680 | parameter_s = ' '.join(shlex.quote(arg) for arg in argv) |
|
681 | parameter_s = ' '.join(shlex.quote(arg) for arg in argv) | |
681 |
|
682 | |||
682 | # get arguments and set sys.argv for program to be run. |
|
683 | # get arguments and set sys.argv for program to be run. | |
683 | opts, arg_lst = self.parse_options(parameter_s, |
|
684 | opts, arg_lst = self.parse_options(parameter_s, | |
684 | 'nidtN:b:pD:l:rs:T:em:G', |
|
685 | 'nidtN:b:pD:l:rs:T:em:G', | |
685 | mode='list', list_all=1) |
|
686 | mode='list', list_all=1) | |
686 | if "m" in opts: |
|
687 | if "m" in opts: | |
687 | modulename = opts["m"][0] |
|
688 | modulename = opts["m"][0] | |
688 | modpath = find_mod(modulename) |
|
689 | modpath = find_mod(modulename) | |
689 | if modpath is None: |
|
690 | if modpath is None: | |
690 | msg = '%r is not a valid modulename on sys.path'%modulename |
|
691 | msg = '%r is not a valid modulename on sys.path'%modulename | |
691 | raise Exception(msg) |
|
692 | raise Exception(msg) | |
692 | arg_lst = [modpath] + arg_lst |
|
693 | arg_lst = [modpath] + arg_lst | |
693 | try: |
|
694 | try: | |
694 | fpath = None # initialize to make sure fpath is in scope later |
|
695 | fpath = None # initialize to make sure fpath is in scope later | |
695 | fpath = arg_lst[0] |
|
696 | fpath = arg_lst[0] | |
696 | filename = file_finder(fpath) |
|
697 | filename = file_finder(fpath) | |
697 | except IndexError as e: |
|
698 | except IndexError as e: | |
698 | msg = 'you must provide at least a filename.' |
|
699 | msg = 'you must provide at least a filename.' | |
699 | raise Exception(msg) from e |
|
700 | raise Exception(msg) from e | |
700 | except IOError as e: |
|
701 | except IOError as e: | |
701 | try: |
|
702 | try: | |
702 | msg = str(e) |
|
703 | msg = str(e) | |
703 | except UnicodeError: |
|
704 | except UnicodeError: | |
704 | msg = e.message |
|
705 | msg = e.message | |
705 | if os.name == 'nt' and re.match(r"^'.*'$",fpath): |
|
706 | if os.name == 'nt' and re.match(r"^'.*'$",fpath): | |
706 | warn('For Windows, use double quotes to wrap a filename: %run "mypath\\myfile.py"') |
|
707 | warn('For Windows, use double quotes to wrap a filename: %run "mypath\\myfile.py"') | |
707 | raise Exception(msg) from e |
|
708 | raise Exception(msg) from e | |
708 | except TypeError: |
|
709 | except TypeError: | |
709 | if fpath in sys.meta_path: |
|
710 | if fpath in sys.meta_path: | |
710 | filename = "" |
|
711 | filename = "" | |
711 | else: |
|
712 | else: | |
712 | raise |
|
713 | raise | |
713 |
|
714 | |||
714 | if filename.lower().endswith(('.ipy', '.ipynb')): |
|
715 | if filename.lower().endswith(('.ipy', '.ipynb')): | |
715 | with preserve_keys(self.shell.user_ns, '__file__'): |
|
716 | with preserve_keys(self.shell.user_ns, '__file__'): | |
716 | self.shell.user_ns['__file__'] = filename |
|
717 | self.shell.user_ns['__file__'] = filename | |
717 | self.shell.safe_execfile_ipy(filename, raise_exceptions=True) |
|
718 | self.shell.safe_execfile_ipy(filename, raise_exceptions=True) | |
718 | return |
|
719 | return | |
719 |
|
720 | |||
720 | # Control the response to exit() calls made by the script being run |
|
721 | # Control the response to exit() calls made by the script being run | |
721 | exit_ignore = 'e' in opts |
|
722 | exit_ignore = 'e' in opts | |
722 |
|
723 | |||
723 | # Make sure that the running script gets a proper sys.argv as if it |
|
724 | # Make sure that the running script gets a proper sys.argv as if it | |
724 | # were run from a system shell. |
|
725 | # were run from a system shell. | |
725 | save_argv = sys.argv # save it for later restoring |
|
726 | save_argv = sys.argv # save it for later restoring | |
726 |
|
727 | |||
727 | if 'G' in opts: |
|
728 | if 'G' in opts: | |
728 | args = arg_lst[1:] |
|
729 | args = arg_lst[1:] | |
729 | else: |
|
730 | else: | |
730 | # tilde and glob expansion |
|
731 | # tilde and glob expansion | |
731 | args = shellglob(map(os.path.expanduser, arg_lst[1:])) |
|
732 | args = shellglob(map(os.path.expanduser, arg_lst[1:])) | |
732 |
|
733 | |||
733 | sys.argv = [filename] + args # put in the proper filename |
|
734 | sys.argv = [filename] + args # put in the proper filename | |
734 |
|
735 | |||
735 | if 'n' in opts: |
|
736 | if 'n' in opts: | |
736 | name = Path(filename).stem |
|
737 | name = Path(filename).stem | |
737 | else: |
|
738 | else: | |
738 | name = '__main__' |
|
739 | name = '__main__' | |
739 |
|
740 | |||
740 | if 'i' in opts: |
|
741 | if 'i' in opts: | |
741 | # Run in user's interactive namespace |
|
742 | # Run in user's interactive namespace | |
742 | prog_ns = self.shell.user_ns |
|
743 | prog_ns = self.shell.user_ns | |
743 | __name__save = self.shell.user_ns['__name__'] |
|
744 | __name__save = self.shell.user_ns['__name__'] | |
744 | prog_ns['__name__'] = name |
|
745 | prog_ns['__name__'] = name | |
745 | main_mod = self.shell.user_module |
|
746 | main_mod = self.shell.user_module | |
746 |
|
747 | |||
747 | # Since '%run foo' emulates 'python foo.py' at the cmd line, we must |
|
748 | # Since '%run foo' emulates 'python foo.py' at the cmd line, we must | |
748 | # set the __file__ global in the script's namespace |
|
749 | # set the __file__ global in the script's namespace | |
749 | # TK: Is this necessary in interactive mode? |
|
750 | # TK: Is this necessary in interactive mode? | |
750 | prog_ns['__file__'] = filename |
|
751 | prog_ns['__file__'] = filename | |
751 | else: |
|
752 | else: | |
752 | # Run in a fresh, empty namespace |
|
753 | # Run in a fresh, empty namespace | |
753 |
|
754 | |||
754 | # The shell MUST hold a reference to prog_ns so after %run |
|
755 | # The shell MUST hold a reference to prog_ns so after %run | |
755 | # exits, the python deletion mechanism doesn't zero it out |
|
756 | # exits, the python deletion mechanism doesn't zero it out | |
756 | # (leaving dangling references). See interactiveshell for details |
|
757 | # (leaving dangling references). See interactiveshell for details | |
757 | main_mod = self.shell.new_main_mod(filename, name) |
|
758 | main_mod = self.shell.new_main_mod(filename, name) | |
758 | prog_ns = main_mod.__dict__ |
|
759 | prog_ns = main_mod.__dict__ | |
759 |
|
760 | |||
760 | # pickle fix. See interactiveshell for an explanation. But we need to |
|
761 | # pickle fix. See interactiveshell for an explanation. But we need to | |
761 | # make sure that, if we overwrite __main__, we replace it at the end |
|
762 | # make sure that, if we overwrite __main__, we replace it at the end | |
762 | main_mod_name = prog_ns['__name__'] |
|
763 | main_mod_name = prog_ns['__name__'] | |
763 |
|
764 | |||
764 | if main_mod_name == '__main__': |
|
765 | if main_mod_name == '__main__': | |
765 | restore_main = sys.modules['__main__'] |
|
766 | restore_main = sys.modules['__main__'] | |
766 | else: |
|
767 | else: | |
767 | restore_main = False |
|
768 | restore_main = False | |
768 |
|
769 | |||
769 | # This needs to be undone at the end to prevent holding references to |
|
770 | # This needs to be undone at the end to prevent holding references to | |
770 | # every single object ever created. |
|
771 | # every single object ever created. | |
771 | sys.modules[main_mod_name] = main_mod |
|
772 | sys.modules[main_mod_name] = main_mod | |
772 |
|
773 | |||
773 | if 'p' in opts or 'd' in opts: |
|
774 | if 'p' in opts or 'd' in opts: | |
774 | if 'm' in opts: |
|
775 | if 'm' in opts: | |
775 | code = 'run_module(modulename, prog_ns)' |
|
776 | code = 'run_module(modulename, prog_ns)' | |
776 | code_ns = { |
|
777 | code_ns = { | |
777 | 'run_module': self.shell.safe_run_module, |
|
778 | 'run_module': self.shell.safe_run_module, | |
778 | 'prog_ns': prog_ns, |
|
779 | 'prog_ns': prog_ns, | |
779 | 'modulename': modulename, |
|
780 | 'modulename': modulename, | |
780 | } |
|
781 | } | |
781 | else: |
|
782 | else: | |
782 | if 'd' in opts: |
|
783 | if 'd' in opts: | |
783 | # allow exceptions to raise in debug mode |
|
784 | # allow exceptions to raise in debug mode | |
784 | code = 'execfile(filename, prog_ns, raise_exceptions=True)' |
|
785 | code = 'execfile(filename, prog_ns, raise_exceptions=True)' | |
785 | else: |
|
786 | else: | |
786 | code = 'execfile(filename, prog_ns)' |
|
787 | code = 'execfile(filename, prog_ns)' | |
787 | code_ns = { |
|
788 | code_ns = { | |
788 | 'execfile': self.shell.safe_execfile, |
|
789 | 'execfile': self.shell.safe_execfile, | |
789 | 'prog_ns': prog_ns, |
|
790 | 'prog_ns': prog_ns, | |
790 | 'filename': get_py_filename(filename), |
|
791 | 'filename': get_py_filename(filename), | |
791 | } |
|
792 | } | |
792 |
|
793 | |||
793 | try: |
|
794 | try: | |
794 | stats = None |
|
795 | stats = None | |
795 | if 'p' in opts: |
|
796 | if 'p' in opts: | |
796 | stats = self._run_with_profiler(code, opts, code_ns) |
|
797 | stats = self._run_with_profiler(code, opts, code_ns) | |
797 | else: |
|
798 | else: | |
798 | if 'd' in opts: |
|
799 | if 'd' in opts: | |
799 | bp_file, bp_line = parse_breakpoint( |
|
800 | bp_file, bp_line = parse_breakpoint( | |
800 | opts.get('b', ['1'])[0], filename) |
|
801 | opts.get('b', ['1'])[0], filename) | |
801 | self._run_with_debugger( |
|
802 | self._run_with_debugger( | |
802 | code, code_ns, filename, bp_line, bp_file) |
|
803 | code, code_ns, filename, bp_line, bp_file) | |
803 | else: |
|
804 | else: | |
804 | if 'm' in opts: |
|
805 | if 'm' in opts: | |
805 | def run(): |
|
806 | def run(): | |
806 | self.shell.safe_run_module(modulename, prog_ns) |
|
807 | self.shell.safe_run_module(modulename, prog_ns) | |
807 | else: |
|
808 | else: | |
808 | if runner is None: |
|
809 | if runner is None: | |
809 | runner = self.default_runner |
|
810 | runner = self.default_runner | |
810 | if runner is None: |
|
811 | if runner is None: | |
811 | runner = self.shell.safe_execfile |
|
812 | runner = self.shell.safe_execfile | |
812 |
|
813 | |||
813 | def run(): |
|
814 | def run(): | |
814 | runner(filename, prog_ns, prog_ns, |
|
815 | runner(filename, prog_ns, prog_ns, | |
815 | exit_ignore=exit_ignore) |
|
816 | exit_ignore=exit_ignore) | |
816 |
|
817 | |||
817 | if 't' in opts: |
|
818 | if 't' in opts: | |
818 | # timed execution |
|
819 | # timed execution | |
819 | try: |
|
820 | try: | |
820 | nruns = int(opts['N'][0]) |
|
821 | nruns = int(opts['N'][0]) | |
821 | if nruns < 1: |
|
822 | if nruns < 1: | |
822 | error('Number of runs must be >=1') |
|
823 | error('Number of runs must be >=1') | |
823 | return |
|
824 | return | |
824 | except (KeyError): |
|
825 | except (KeyError): | |
825 | nruns = 1 |
|
826 | nruns = 1 | |
826 | self._run_with_timing(run, nruns) |
|
827 | self._run_with_timing(run, nruns) | |
827 | else: |
|
828 | else: | |
828 | # regular execution |
|
829 | # regular execution | |
829 | run() |
|
830 | run() | |
830 |
|
831 | |||
831 | if 'i' in opts: |
|
832 | if 'i' in opts: | |
832 | self.shell.user_ns['__name__'] = __name__save |
|
833 | self.shell.user_ns['__name__'] = __name__save | |
833 | else: |
|
834 | else: | |
834 | # update IPython interactive namespace |
|
835 | # update IPython interactive namespace | |
835 |
|
836 | |||
836 | # Some forms of read errors on the file may mean the |
|
837 | # Some forms of read errors on the file may mean the | |
837 | # __name__ key was never set; using pop we don't have to |
|
838 | # __name__ key was never set; using pop we don't have to | |
838 | # worry about a possible KeyError. |
|
839 | # worry about a possible KeyError. | |
839 | prog_ns.pop('__name__', None) |
|
840 | prog_ns.pop('__name__', None) | |
840 |
|
841 | |||
841 | with preserve_keys(self.shell.user_ns, '__file__'): |
|
842 | with preserve_keys(self.shell.user_ns, '__file__'): | |
842 | self.shell.user_ns.update(prog_ns) |
|
843 | self.shell.user_ns.update(prog_ns) | |
843 | finally: |
|
844 | finally: | |
844 | # It's a bit of a mystery why, but __builtins__ can change from |
|
845 | # It's a bit of a mystery why, but __builtins__ can change from | |
845 | # being a module to becoming a dict missing some key data after |
|
846 | # being a module to becoming a dict missing some key data after | |
846 | # %run. As best I can see, this is NOT something IPython is doing |
|
847 | # %run. As best I can see, this is NOT something IPython is doing | |
847 | # at all, and similar problems have been reported before: |
|
848 | # at all, and similar problems have been reported before: | |
848 | # http://coding.derkeiler.com/Archive/Python/comp.lang.python/2004-10/0188.html |
|
849 | # http://coding.derkeiler.com/Archive/Python/comp.lang.python/2004-10/0188.html | |
849 | # Since this seems to be done by the interpreter itself, the best |
|
850 | # Since this seems to be done by the interpreter itself, the best | |
850 | # we can do is to at least restore __builtins__ for the user on |
|
851 | # we can do is to at least restore __builtins__ for the user on | |
851 | # exit. |
|
852 | # exit. | |
852 | self.shell.user_ns['__builtins__'] = builtin_mod |
|
853 | self.shell.user_ns['__builtins__'] = builtin_mod | |
853 |
|
854 | |||
854 | # Ensure key global structures are restored |
|
855 | # Ensure key global structures are restored | |
855 | sys.argv = save_argv |
|
856 | sys.argv = save_argv | |
856 | if restore_main: |
|
857 | if restore_main: | |
857 | sys.modules['__main__'] = restore_main |
|
858 | sys.modules['__main__'] = restore_main | |
858 | if '__mp_main__' in sys.modules: |
|
859 | if '__mp_main__' in sys.modules: | |
859 | sys.modules['__mp_main__'] = restore_main |
|
860 | sys.modules['__mp_main__'] = restore_main | |
860 | else: |
|
861 | else: | |
861 | # Remove from sys.modules the reference to main_mod we'd |
|
862 | # Remove from sys.modules the reference to main_mod we'd | |
862 | # added. Otherwise it will trap references to objects |
|
863 | # added. Otherwise it will trap references to objects | |
863 | # contained therein. |
|
864 | # contained therein. | |
864 | del sys.modules[main_mod_name] |
|
865 | del sys.modules[main_mod_name] | |
865 |
|
866 | |||
866 | return stats |
|
867 | return stats | |
867 |
|
868 | |||
868 | def _run_with_debugger(self, code, code_ns, filename=None, |
|
869 | def _run_with_debugger(self, code, code_ns, filename=None, | |
869 | bp_line=None, bp_file=None): |
|
870 | bp_line=None, bp_file=None): | |
870 | """ |
|
871 | """ | |
871 | Run `code` in debugger with a break point. |
|
872 | Run `code` in debugger with a break point. | |
872 |
|
873 | |||
873 | Parameters |
|
874 | Parameters | |
874 | ---------- |
|
875 | ---------- | |
875 | code : str |
|
876 | code : str | |
876 | Code to execute. |
|
877 | Code to execute. | |
877 | code_ns : dict |
|
878 | code_ns : dict | |
878 | A namespace in which `code` is executed. |
|
879 | A namespace in which `code` is executed. | |
879 | filename : str |
|
880 | filename : str | |
880 | `code` is ran as if it is in `filename`. |
|
881 | `code` is ran as if it is in `filename`. | |
881 | bp_line : int, optional |
|
882 | bp_line : int, optional | |
882 | Line number of the break point. |
|
883 | Line number of the break point. | |
883 | bp_file : str, optional |
|
884 | bp_file : str, optional | |
884 | Path to the file in which break point is specified. |
|
885 | Path to the file in which break point is specified. | |
885 | `filename` is used if not given. |
|
886 | `filename` is used if not given. | |
886 |
|
887 | |||
887 | Raises |
|
888 | Raises | |
888 | ------ |
|
889 | ------ | |
889 | UsageError |
|
890 | UsageError | |
890 | If the break point given by `bp_line` is not valid. |
|
891 | If the break point given by `bp_line` is not valid. | |
891 |
|
892 | |||
892 | """ |
|
893 | """ | |
893 | deb = self.shell.InteractiveTB.pdb |
|
894 | deb = self.shell.InteractiveTB.pdb | |
894 | if not deb: |
|
895 | if not deb: | |
895 | self.shell.InteractiveTB.pdb = self.shell.InteractiveTB.debugger_cls() |
|
896 | self.shell.InteractiveTB.pdb = self.shell.InteractiveTB.debugger_cls() | |
896 | deb = self.shell.InteractiveTB.pdb |
|
897 | deb = self.shell.InteractiveTB.pdb | |
897 |
|
898 | |||
898 | # deb.checkline() fails if deb.curframe exists but is None; it can |
|
899 | # deb.checkline() fails if deb.curframe exists but is None; it can | |
899 | # handle it not existing. https://github.com/ipython/ipython/issues/10028 |
|
900 | # handle it not existing. https://github.com/ipython/ipython/issues/10028 | |
900 | if hasattr(deb, 'curframe'): |
|
901 | if hasattr(deb, 'curframe'): | |
901 | del deb.curframe |
|
902 | del deb.curframe | |
902 |
|
903 | |||
903 | # reset Breakpoint state, which is moronically kept |
|
904 | # reset Breakpoint state, which is moronically kept | |
904 | # in a class |
|
905 | # in a class | |
905 | bdb.Breakpoint.next = 1 |
|
906 | bdb.Breakpoint.next = 1 | |
906 | bdb.Breakpoint.bplist = {} |
|
907 | bdb.Breakpoint.bplist = {} | |
907 | bdb.Breakpoint.bpbynumber = [None] |
|
908 | bdb.Breakpoint.bpbynumber = [None] | |
908 | deb.clear_all_breaks() |
|
909 | deb.clear_all_breaks() | |
909 | if bp_line is not None: |
|
910 | if bp_line is not None: | |
910 | # Set an initial breakpoint to stop execution |
|
911 | # Set an initial breakpoint to stop execution | |
911 | maxtries = 10 |
|
912 | maxtries = 10 | |
912 | bp_file = bp_file or filename |
|
913 | bp_file = bp_file or filename | |
913 | checkline = deb.checkline(bp_file, bp_line) |
|
914 | checkline = deb.checkline(bp_file, bp_line) | |
914 | if not checkline: |
|
915 | if not checkline: | |
915 | for bp in range(bp_line + 1, bp_line + maxtries + 1): |
|
916 | for bp in range(bp_line + 1, bp_line + maxtries + 1): | |
916 | if deb.checkline(bp_file, bp): |
|
917 | if deb.checkline(bp_file, bp): | |
917 | break |
|
918 | break | |
918 | else: |
|
919 | else: | |
919 | msg = ("\nI failed to find a valid line to set " |
|
920 | msg = ("\nI failed to find a valid line to set " | |
920 | "a breakpoint\n" |
|
921 | "a breakpoint\n" | |
921 | "after trying up to line: %s.\n" |
|
922 | "after trying up to line: %s.\n" | |
922 | "Please set a valid breakpoint manually " |
|
923 | "Please set a valid breakpoint manually " | |
923 | "with the -b option." % bp) |
|
924 | "with the -b option." % bp) | |
924 | raise UsageError(msg) |
|
925 | raise UsageError(msg) | |
925 | # if we find a good linenumber, set the breakpoint |
|
926 | # if we find a good linenumber, set the breakpoint | |
926 | deb.do_break('%s:%s' % (bp_file, bp_line)) |
|
927 | deb.do_break('%s:%s' % (bp_file, bp_line)) | |
927 |
|
928 | |||
928 | if filename: |
|
929 | if filename: | |
929 | # Mimic Pdb._runscript(...) |
|
930 | # Mimic Pdb._runscript(...) | |
930 | deb._wait_for_mainpyfile = True |
|
931 | deb._wait_for_mainpyfile = True | |
931 | deb.mainpyfile = deb.canonic(filename) |
|
932 | deb.mainpyfile = deb.canonic(filename) | |
932 |
|
933 | |||
933 | # Start file run |
|
934 | # Start file run | |
934 | print("NOTE: Enter 'c' at the %s prompt to continue execution." % deb.prompt) |
|
935 | print("NOTE: Enter 'c' at the %s prompt to continue execution." % deb.prompt) | |
935 | try: |
|
936 | try: | |
936 | if filename: |
|
937 | if filename: | |
937 | # save filename so it can be used by methods on the deb object |
|
938 | # save filename so it can be used by methods on the deb object | |
938 | deb._exec_filename = filename |
|
939 | deb._exec_filename = filename | |
939 | while True: |
|
940 | while True: | |
940 | try: |
|
941 | try: | |
941 | trace = sys.gettrace() |
|
942 | trace = sys.gettrace() | |
942 | deb.run(code, code_ns) |
|
943 | deb.run(code, code_ns) | |
943 | except Restart: |
|
944 | except Restart: | |
944 | print("Restarting") |
|
945 | print("Restarting") | |
945 | if filename: |
|
946 | if filename: | |
946 | deb._wait_for_mainpyfile = True |
|
947 | deb._wait_for_mainpyfile = True | |
947 | deb.mainpyfile = deb.canonic(filename) |
|
948 | deb.mainpyfile = deb.canonic(filename) | |
948 | continue |
|
949 | continue | |
949 | else: |
|
950 | else: | |
950 | break |
|
951 | break | |
951 | finally: |
|
952 | finally: | |
952 | sys.settrace(trace) |
|
953 | sys.settrace(trace) | |
953 |
|
954 | |||
954 |
|
955 | |||
955 | except: |
|
956 | except: | |
956 | etype, value, tb = sys.exc_info() |
|
957 | etype, value, tb = sys.exc_info() | |
957 | # Skip three frames in the traceback: the %run one, |
|
958 | # Skip three frames in the traceback: the %run one, | |
958 | # one inside bdb.py, and the command-line typed by the |
|
959 | # one inside bdb.py, and the command-line typed by the | |
959 | # user (run by exec in pdb itself). |
|
960 | # user (run by exec in pdb itself). | |
960 | self.shell.InteractiveTB(etype, value, tb, tb_offset=3) |
|
961 | self.shell.InteractiveTB(etype, value, tb, tb_offset=3) | |
961 |
|
962 | |||
962 | @staticmethod |
|
963 | @staticmethod | |
963 | def _run_with_timing(run, nruns): |
|
964 | def _run_with_timing(run, nruns): | |
964 | """ |
|
965 | """ | |
965 | Run function `run` and print timing information. |
|
966 | Run function `run` and print timing information. | |
966 |
|
967 | |||
967 | Parameters |
|
968 | Parameters | |
968 | ---------- |
|
969 | ---------- | |
969 | run : callable |
|
970 | run : callable | |
970 | Any callable object which takes no argument. |
|
971 | Any callable object which takes no argument. | |
971 | nruns : int |
|
972 | nruns : int | |
972 | Number of times to execute `run`. |
|
973 | Number of times to execute `run`. | |
973 |
|
974 | |||
974 | """ |
|
975 | """ | |
975 | twall0 = time.perf_counter() |
|
976 | twall0 = time.perf_counter() | |
976 | if nruns == 1: |
|
977 | if nruns == 1: | |
977 | t0 = clock2() |
|
978 | t0 = clock2() | |
978 | run() |
|
979 | run() | |
979 | t1 = clock2() |
|
980 | t1 = clock2() | |
980 | t_usr = t1[0] - t0[0] |
|
981 | t_usr = t1[0] - t0[0] | |
981 | t_sys = t1[1] - t0[1] |
|
982 | t_sys = t1[1] - t0[1] | |
982 | print("\nIPython CPU timings (estimated):") |
|
983 | print("\nIPython CPU timings (estimated):") | |
983 | print(" User : %10.2f s." % t_usr) |
|
984 | print(" User : %10.2f s." % t_usr) | |
984 | print(" System : %10.2f s." % t_sys) |
|
985 | print(" System : %10.2f s." % t_sys) | |
985 | else: |
|
986 | else: | |
986 | runs = range(nruns) |
|
987 | runs = range(nruns) | |
987 | t0 = clock2() |
|
988 | t0 = clock2() | |
988 | for nr in runs: |
|
989 | for nr in runs: | |
989 | run() |
|
990 | run() | |
990 | t1 = clock2() |
|
991 | t1 = clock2() | |
991 | t_usr = t1[0] - t0[0] |
|
992 | t_usr = t1[0] - t0[0] | |
992 | t_sys = t1[1] - t0[1] |
|
993 | t_sys = t1[1] - t0[1] | |
993 | print("\nIPython CPU timings (estimated):") |
|
994 | print("\nIPython CPU timings (estimated):") | |
994 | print("Total runs performed:", nruns) |
|
995 | print("Total runs performed:", nruns) | |
995 | print(" Times : %10s %10s" % ('Total', 'Per run')) |
|
996 | print(" Times : %10s %10s" % ('Total', 'Per run')) | |
996 | print(" User : %10.2f s, %10.2f s." % (t_usr, t_usr / nruns)) |
|
997 | print(" User : %10.2f s, %10.2f s." % (t_usr, t_usr / nruns)) | |
997 | print(" System : %10.2f s, %10.2f s." % (t_sys, t_sys / nruns)) |
|
998 | print(" System : %10.2f s, %10.2f s." % (t_sys, t_sys / nruns)) | |
998 | twall1 = time.perf_counter() |
|
999 | twall1 = time.perf_counter() | |
999 | print("Wall time: %10.2f s." % (twall1 - twall0)) |
|
1000 | print("Wall time: %10.2f s." % (twall1 - twall0)) | |
1000 |
|
1001 | |||
1001 | @skip_doctest |
|
1002 | @skip_doctest | |
1002 | @no_var_expand |
|
1003 | @no_var_expand | |
1003 | @line_cell_magic |
|
1004 | @line_cell_magic | |
1004 | @needs_local_scope |
|
1005 | @needs_local_scope | |
1005 | def timeit(self, line='', cell=None, local_ns=None): |
|
1006 | def timeit(self, line='', cell=None, local_ns=None): | |
1006 | """Time execution of a Python statement or expression |
|
1007 | """Time execution of a Python statement or expression | |
1007 |
|
1008 | |||
1008 | Usage, in line mode: |
|
1009 | Usage, in line mode: | |
1009 | %timeit [-n<N> -r<R> [-t|-c] -q -p<P> -o] statement |
|
1010 | %timeit [-n<N> -r<R> [-t|-c] -q -p<P> -o] statement | |
1010 | or in cell mode: |
|
1011 | or in cell mode: | |
1011 | %%timeit [-n<N> -r<R> [-t|-c] -q -p<P> -o] setup_code |
|
1012 | %%timeit [-n<N> -r<R> [-t|-c] -q -p<P> -o] setup_code | |
1012 | code |
|
1013 | code | |
1013 | code... |
|
1014 | code... | |
1014 |
|
1015 | |||
1015 | Time execution of a Python statement or expression using the timeit |
|
1016 | Time execution of a Python statement or expression using the timeit | |
1016 | module. This function can be used both as a line and cell magic: |
|
1017 | module. This function can be used both as a line and cell magic: | |
1017 |
|
1018 | |||
1018 | - In line mode you can time a single-line statement (though multiple |
|
1019 | - In line mode you can time a single-line statement (though multiple | |
1019 | ones can be chained with using semicolons). |
|
1020 | ones can be chained with using semicolons). | |
1020 |
|
1021 | |||
1021 | - In cell mode, the statement in the first line is used as setup code |
|
1022 | - In cell mode, the statement in the first line is used as setup code | |
1022 | (executed but not timed) and the body of the cell is timed. The cell |
|
1023 | (executed but not timed) and the body of the cell is timed. The cell | |
1023 | body has access to any variables created in the setup code. |
|
1024 | body has access to any variables created in the setup code. | |
1024 |
|
1025 | |||
1025 | Options: |
|
1026 | Options: | |
1026 | -n<N>: execute the given statement <N> times in a loop. If <N> is not |
|
1027 | -n<N>: execute the given statement <N> times in a loop. If <N> is not | |
1027 | provided, <N> is determined so as to get sufficient accuracy. |
|
1028 | provided, <N> is determined so as to get sufficient accuracy. | |
1028 |
|
1029 | |||
1029 | -r<R>: number of repeats <R>, each consisting of <N> loops, and take the |
|
1030 | -r<R>: number of repeats <R>, each consisting of <N> loops, and take the | |
1030 | best result. |
|
1031 | best result. | |
1031 | Default: 7 |
|
1032 | Default: 7 | |
1032 |
|
1033 | |||
1033 | -t: use time.time to measure the time, which is the default on Unix. |
|
1034 | -t: use time.time to measure the time, which is the default on Unix. | |
1034 | This function measures wall time. |
|
1035 | This function measures wall time. | |
1035 |
|
1036 | |||
1036 | -c: use time.clock to measure the time, which is the default on |
|
1037 | -c: use time.clock to measure the time, which is the default on | |
1037 | Windows and measures wall time. On Unix, resource.getrusage is used |
|
1038 | Windows and measures wall time. On Unix, resource.getrusage is used | |
1038 | instead and returns the CPU user time. |
|
1039 | instead and returns the CPU user time. | |
1039 |
|
1040 | |||
1040 | -p<P>: use a precision of <P> digits to display the timing result. |
|
1041 | -p<P>: use a precision of <P> digits to display the timing result. | |
1041 | Default: 3 |
|
1042 | Default: 3 | |
1042 |
|
1043 | |||
1043 | -q: Quiet, do not print result. |
|
1044 | -q: Quiet, do not print result. | |
1044 |
|
1045 | |||
1045 | -o: return a TimeitResult that can be stored in a variable to inspect |
|
1046 | -o: return a TimeitResult that can be stored in a variable to inspect | |
1046 | the result in more details. |
|
1047 | the result in more details. | |
1047 |
|
1048 | |||
1048 | .. versionchanged:: 7.3 |
|
1049 | .. versionchanged:: 7.3 | |
1049 | User variables are no longer expanded, |
|
1050 | User variables are no longer expanded, | |
1050 | the magic line is always left unmodified. |
|
1051 | the magic line is always left unmodified. | |
1051 |
|
1052 | |||
1052 | Examples |
|
1053 | Examples | |
1053 | -------- |
|
1054 | -------- | |
1054 | :: |
|
1055 | :: | |
1055 |
|
1056 | |||
1056 | In [1]: %timeit pass |
|
1057 | In [1]: %timeit pass | |
1057 | 8.26 ns ± 0.12 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each) |
|
1058 | 8.26 ns ± 0.12 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each) | |
1058 |
|
1059 | |||
1059 | In [2]: u = None |
|
1060 | In [2]: u = None | |
1060 |
|
1061 | |||
1061 | In [3]: %timeit u is None |
|
1062 | In [3]: %timeit u is None | |
1062 | 29.9 ns ± 0.643 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each) |
|
1063 | 29.9 ns ± 0.643 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each) | |
1063 |
|
1064 | |||
1064 | In [4]: %timeit -r 4 u == None |
|
1065 | In [4]: %timeit -r 4 u == None | |
1065 |
|
1066 | |||
1066 | In [5]: import time |
|
1067 | In [5]: import time | |
1067 |
|
1068 | |||
1068 | In [6]: %timeit -n1 time.sleep(2) |
|
1069 | In [6]: %timeit -n1 time.sleep(2) | |
1069 |
|
1070 | |||
1070 | The times reported by %timeit will be slightly higher than those |
|
1071 | The times reported by %timeit will be slightly higher than those | |
1071 | reported by the timeit.py script when variables are accessed. This is |
|
1072 | reported by the timeit.py script when variables are accessed. This is | |
1072 | due to the fact that %timeit executes the statement in the namespace |
|
1073 | due to the fact that %timeit executes the statement in the namespace | |
1073 | of the shell, compared with timeit.py, which uses a single setup |
|
1074 | of the shell, compared with timeit.py, which uses a single setup | |
1074 | statement to import function or create variables. Generally, the bias |
|
1075 | statement to import function or create variables. Generally, the bias | |
1075 | does not matter as long as results from timeit.py are not mixed with |
|
1076 | does not matter as long as results from timeit.py are not mixed with | |
1076 | those from %timeit.""" |
|
1077 | those from %timeit.""" | |
1077 |
|
1078 | |||
1078 | opts, stmt = self.parse_options( |
|
1079 | opts, stmt = self.parse_options( | |
1079 | line, "n:r:tcp:qo", posix=False, strict=False, preserve_non_opts=True |
|
1080 | line, "n:r:tcp:qo", posix=False, strict=False, preserve_non_opts=True | |
1080 | ) |
|
1081 | ) | |
1081 | if stmt == "" and cell is None: |
|
1082 | if stmt == "" and cell is None: | |
1082 | return |
|
1083 | return | |
1083 |
|
1084 | |||
1084 | timefunc = timeit.default_timer |
|
1085 | timefunc = timeit.default_timer | |
1085 | number = int(getattr(opts, "n", 0)) |
|
1086 | number = int(getattr(opts, "n", 0)) | |
1086 | default_repeat = 7 if timeit.default_repeat < 7 else timeit.default_repeat |
|
1087 | default_repeat = 7 if timeit.default_repeat < 7 else timeit.default_repeat | |
1087 | repeat = int(getattr(opts, "r", default_repeat)) |
|
1088 | repeat = int(getattr(opts, "r", default_repeat)) | |
1088 | precision = int(getattr(opts, "p", 3)) |
|
1089 | precision = int(getattr(opts, "p", 3)) | |
1089 | quiet = 'q' in opts |
|
1090 | quiet = 'q' in opts | |
1090 | return_result = 'o' in opts |
|
1091 | return_result = 'o' in opts | |
1091 | if hasattr(opts, "t"): |
|
1092 | if hasattr(opts, "t"): | |
1092 | timefunc = time.time |
|
1093 | timefunc = time.time | |
1093 | if hasattr(opts, "c"): |
|
1094 | if hasattr(opts, "c"): | |
1094 | timefunc = clock |
|
1095 | timefunc = clock | |
1095 |
|
1096 | |||
1096 | timer = Timer(timer=timefunc) |
|
1097 | timer = Timer(timer=timefunc) | |
1097 | # this code has tight coupling to the inner workings of timeit.Timer, |
|
1098 | # this code has tight coupling to the inner workings of timeit.Timer, | |
1098 | # but is there a better way to achieve that the code stmt has access |
|
1099 | # but is there a better way to achieve that the code stmt has access | |
1099 | # to the shell namespace? |
|
1100 | # to the shell namespace? | |
1100 | transform = self.shell.transform_cell |
|
1101 | transform = self.shell.transform_cell | |
1101 |
|
1102 | |||
1102 | if cell is None: |
|
1103 | if cell is None: | |
1103 | # called as line magic |
|
1104 | # called as line magic | |
1104 | ast_setup = self.shell.compile.ast_parse("pass") |
|
1105 | ast_setup = self.shell.compile.ast_parse("pass") | |
1105 | ast_stmt = self.shell.compile.ast_parse(transform(stmt)) |
|
1106 | ast_stmt = self.shell.compile.ast_parse(transform(stmt)) | |
1106 | else: |
|
1107 | else: | |
1107 | ast_setup = self.shell.compile.ast_parse(transform(stmt)) |
|
1108 | ast_setup = self.shell.compile.ast_parse(transform(stmt)) | |
1108 | ast_stmt = self.shell.compile.ast_parse(transform(cell)) |
|
1109 | ast_stmt = self.shell.compile.ast_parse(transform(cell)) | |
1109 |
|
1110 | |||
1110 | ast_setup = self.shell.transform_ast(ast_setup) |
|
1111 | ast_setup = self.shell.transform_ast(ast_setup) | |
1111 | ast_stmt = self.shell.transform_ast(ast_stmt) |
|
1112 | ast_stmt = self.shell.transform_ast(ast_stmt) | |
1112 |
|
1113 | |||
1113 | # Check that these compile to valid Python code *outside* the timer func |
|
1114 | # Check that these compile to valid Python code *outside* the timer func | |
1114 | # Invalid code may become valid when put inside the function & loop, |
|
1115 | # Invalid code may become valid when put inside the function & loop, | |
1115 | # which messes up error messages. |
|
1116 | # which messes up error messages. | |
1116 | # https://github.com/ipython/ipython/issues/10636 |
|
1117 | # https://github.com/ipython/ipython/issues/10636 | |
1117 | self.shell.compile(ast_setup, "<magic-timeit-setup>", "exec") |
|
1118 | self.shell.compile(ast_setup, "<magic-timeit-setup>", "exec") | |
1118 | self.shell.compile(ast_stmt, "<magic-timeit-stmt>", "exec") |
|
1119 | self.shell.compile(ast_stmt, "<magic-timeit-stmt>", "exec") | |
1119 |
|
1120 | |||
1120 | # This codestring is taken from timeit.template - we fill it in as an |
|
1121 | # This codestring is taken from timeit.template - we fill it in as an | |
1121 | # AST, so that we can apply our AST transformations to the user code |
|
1122 | # AST, so that we can apply our AST transformations to the user code | |
1122 | # without affecting the timing code. |
|
1123 | # without affecting the timing code. | |
1123 | timeit_ast_template = ast.parse('def inner(_it, _timer):\n' |
|
1124 | timeit_ast_template = ast.parse('def inner(_it, _timer):\n' | |
1124 | ' setup\n' |
|
1125 | ' setup\n' | |
1125 | ' _t0 = _timer()\n' |
|
1126 | ' _t0 = _timer()\n' | |
1126 | ' for _i in _it:\n' |
|
1127 | ' for _i in _it:\n' | |
1127 | ' stmt\n' |
|
1128 | ' stmt\n' | |
1128 | ' _t1 = _timer()\n' |
|
1129 | ' _t1 = _timer()\n' | |
1129 | ' return _t1 - _t0\n') |
|
1130 | ' return _t1 - _t0\n') | |
1130 |
|
1131 | |||
1131 | timeit_ast = TimeitTemplateFiller(ast_setup, ast_stmt).visit(timeit_ast_template) |
|
1132 | timeit_ast = TimeitTemplateFiller(ast_setup, ast_stmt).visit(timeit_ast_template) | |
1132 | timeit_ast = ast.fix_missing_locations(timeit_ast) |
|
1133 | timeit_ast = ast.fix_missing_locations(timeit_ast) | |
1133 |
|
1134 | |||
1134 | # Track compilation time so it can be reported if too long |
|
1135 | # Track compilation time so it can be reported if too long | |
1135 | # Minimum time above which compilation time will be reported |
|
1136 | # Minimum time above which compilation time will be reported | |
1136 | tc_min = 0.1 |
|
1137 | tc_min = 0.1 | |
1137 |
|
1138 | |||
1138 | t0 = clock() |
|
1139 | t0 = clock() | |
1139 | code = self.shell.compile(timeit_ast, "<magic-timeit>", "exec") |
|
1140 | code = self.shell.compile(timeit_ast, "<magic-timeit>", "exec") | |
1140 | tc = clock()-t0 |
|
1141 | tc = clock()-t0 | |
1141 |
|
1142 | |||
1142 | ns = {} |
|
1143 | ns = {} | |
1143 | glob = self.shell.user_ns |
|
1144 | glob = self.shell.user_ns | |
1144 | # handles global vars with same name as local vars. We store them in conflict_globs. |
|
1145 | # handles global vars with same name as local vars. We store them in conflict_globs. | |
1145 | conflict_globs = {} |
|
1146 | conflict_globs = {} | |
1146 | if local_ns and cell is None: |
|
1147 | if local_ns and cell is None: | |
1147 | for var_name, var_val in glob.items(): |
|
1148 | for var_name, var_val in glob.items(): | |
1148 | if var_name in local_ns: |
|
1149 | if var_name in local_ns: | |
1149 | conflict_globs[var_name] = var_val |
|
1150 | conflict_globs[var_name] = var_val | |
1150 | glob.update(local_ns) |
|
1151 | glob.update(local_ns) | |
1151 |
|
1152 | |||
1152 | exec(code, glob, ns) |
|
1153 | exec(code, glob, ns) | |
1153 | timer.inner = ns["inner"] |
|
1154 | timer.inner = ns["inner"] | |
1154 |
|
1155 | |||
1155 | # This is used to check if there is a huge difference between the |
|
1156 | # This is used to check if there is a huge difference between the | |
1156 | # best and worst timings. |
|
1157 | # best and worst timings. | |
1157 | # Issue: https://github.com/ipython/ipython/issues/6471 |
|
1158 | # Issue: https://github.com/ipython/ipython/issues/6471 | |
1158 | if number == 0: |
|
1159 | if number == 0: | |
1159 | # determine number so that 0.2 <= total time < 2.0 |
|
1160 | # determine number so that 0.2 <= total time < 2.0 | |
1160 | for index in range(0, 10): |
|
1161 | for index in range(0, 10): | |
1161 | number = 10 ** index |
|
1162 | number = 10 ** index | |
1162 | time_number = timer.timeit(number) |
|
1163 | time_number = timer.timeit(number) | |
1163 | if time_number >= 0.2: |
|
1164 | if time_number >= 0.2: | |
1164 | break |
|
1165 | break | |
1165 |
|
1166 | |||
1166 | all_runs = timer.repeat(repeat, number) |
|
1167 | all_runs = timer.repeat(repeat, number) | |
1167 | best = min(all_runs) / number |
|
1168 | best = min(all_runs) / number | |
1168 | worst = max(all_runs) / number |
|
1169 | worst = max(all_runs) / number | |
1169 | timeit_result = TimeitResult(number, repeat, best, worst, all_runs, tc, precision) |
|
1170 | timeit_result = TimeitResult(number, repeat, best, worst, all_runs, tc, precision) | |
1170 |
|
1171 | |||
1171 | # Restore global vars from conflict_globs |
|
1172 | # Restore global vars from conflict_globs | |
1172 | if conflict_globs: |
|
1173 | if conflict_globs: | |
1173 | glob.update(conflict_globs) |
|
1174 | glob.update(conflict_globs) | |
1174 |
|
1175 | |||
1175 | if not quiet : |
|
1176 | if not quiet : | |
1176 | # Check best timing is greater than zero to avoid a |
|
1177 | # Check best timing is greater than zero to avoid a | |
1177 | # ZeroDivisionError. |
|
1178 | # ZeroDivisionError. | |
1178 | # In cases where the slowest timing is lesser than a microsecond |
|
1179 | # In cases where the slowest timing is lesser than a microsecond | |
1179 | # we assume that it does not really matter if the fastest |
|
1180 | # we assume that it does not really matter if the fastest | |
1180 | # timing is 4 times faster than the slowest timing or not. |
|
1181 | # timing is 4 times faster than the slowest timing or not. | |
1181 | if worst > 4 * best and best > 0 and worst > 1e-6: |
|
1182 | if worst > 4 * best and best > 0 and worst > 1e-6: | |
1182 | print("The slowest run took %0.2f times longer than the " |
|
1183 | print("The slowest run took %0.2f times longer than the " | |
1183 | "fastest. This could mean that an intermediate result " |
|
1184 | "fastest. This could mean that an intermediate result " | |
1184 | "is being cached." % (worst / best)) |
|
1185 | "is being cached." % (worst / best)) | |
1185 |
|
1186 | |||
1186 | print( timeit_result ) |
|
1187 | print( timeit_result ) | |
1187 |
|
1188 | |||
1188 | if tc > tc_min: |
|
1189 | if tc > tc_min: | |
1189 | print("Compiler time: %.2f s" % tc) |
|
1190 | print("Compiler time: %.2f s" % tc) | |
1190 | if return_result: |
|
1191 | if return_result: | |
1191 | return timeit_result |
|
1192 | return timeit_result | |
1192 |
|
1193 | |||
1193 | @skip_doctest |
|
1194 | @skip_doctest | |
1194 | @no_var_expand |
|
1195 | @no_var_expand | |
1195 | @needs_local_scope |
|
1196 | @needs_local_scope | |
1196 | @line_cell_magic |
|
1197 | @line_cell_magic | |
|
1198 | @output_can_be_silenced | |||
1197 | def time(self,line='', cell=None, local_ns=None): |
|
1199 | def time(self,line='', cell=None, local_ns=None): | |
1198 | """Time execution of a Python statement or expression. |
|
1200 | """Time execution of a Python statement or expression. | |
1199 |
|
1201 | |||
1200 | The CPU and wall clock times are printed, and the value of the |
|
1202 | The CPU and wall clock times are printed, and the value of the | |
1201 | expression (if any) is returned. Note that under Win32, system time |
|
1203 | expression (if any) is returned. Note that under Win32, system time | |
1202 | is always reported as 0, since it can not be measured. |
|
1204 | is always reported as 0, since it can not be measured. | |
1203 |
|
1205 | |||
1204 | This function can be used both as a line and cell magic: |
|
1206 | This function can be used both as a line and cell magic: | |
1205 |
|
1207 | |||
1206 | - In line mode you can time a single-line statement (though multiple |
|
1208 | - In line mode you can time a single-line statement (though multiple | |
1207 | ones can be chained with using semicolons). |
|
1209 | ones can be chained with using semicolons). | |
1208 |
|
1210 | |||
1209 | - In cell mode, you can time the cell body (a directly |
|
1211 | - In cell mode, you can time the cell body (a directly | |
1210 | following statement raises an error). |
|
1212 | following statement raises an error). | |
1211 |
|
1213 | |||
1212 | This function provides very basic timing functionality. Use the timeit |
|
1214 | This function provides very basic timing functionality. Use the timeit | |
1213 | magic for more control over the measurement. |
|
1215 | magic for more control over the measurement. | |
1214 |
|
1216 | |||
1215 | .. versionchanged:: 7.3 |
|
1217 | .. versionchanged:: 7.3 | |
1216 | User variables are no longer expanded, |
|
1218 | User variables are no longer expanded, | |
1217 | the magic line is always left unmodified. |
|
1219 | the magic line is always left unmodified. | |
1218 |
|
1220 | |||
1219 | Examples |
|
1221 | Examples | |
1220 | -------- |
|
1222 | -------- | |
1221 | :: |
|
1223 | :: | |
1222 |
|
1224 | |||
1223 | In [1]: %time 2**128 |
|
1225 | In [1]: %time 2**128 | |
1224 | CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s |
|
1226 | CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s | |
1225 | Wall time: 0.00 |
|
1227 | Wall time: 0.00 | |
1226 | Out[1]: 340282366920938463463374607431768211456L |
|
1228 | Out[1]: 340282366920938463463374607431768211456L | |
1227 |
|
1229 | |||
1228 | In [2]: n = 1000000 |
|
1230 | In [2]: n = 1000000 | |
1229 |
|
1231 | |||
1230 | In [3]: %time sum(range(n)) |
|
1232 | In [3]: %time sum(range(n)) | |
1231 | CPU times: user 1.20 s, sys: 0.05 s, total: 1.25 s |
|
1233 | CPU times: user 1.20 s, sys: 0.05 s, total: 1.25 s | |
1232 | Wall time: 1.37 |
|
1234 | Wall time: 1.37 | |
1233 | Out[3]: 499999500000L |
|
1235 | Out[3]: 499999500000L | |
1234 |
|
1236 | |||
1235 | In [4]: %time print 'hello world' |
|
1237 | In [4]: %time print 'hello world' | |
1236 | hello world |
|
1238 | hello world | |
1237 | CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s |
|
1239 | CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s | |
1238 | Wall time: 0.00 |
|
1240 | Wall time: 0.00 | |
1239 |
|
1241 | |||
1240 | .. note:: |
|
1242 | .. note:: | |
1241 | The time needed by Python to compile the given expression will be |
|
1243 | The time needed by Python to compile the given expression will be | |
1242 | reported if it is more than 0.1s. |
|
1244 | reported if it is more than 0.1s. | |
1243 |
|
1245 | |||
1244 | In the example below, the actual exponentiation is done by Python |
|
1246 | In the example below, the actual exponentiation is done by Python | |
1245 | at compilation time, so while the expression can take a noticeable |
|
1247 | at compilation time, so while the expression can take a noticeable | |
1246 | amount of time to compute, that time is purely due to the |
|
1248 | amount of time to compute, that time is purely due to the | |
1247 | compilation:: |
|
1249 | compilation:: | |
1248 |
|
1250 | |||
1249 | In [5]: %time 3**9999; |
|
1251 | In [5]: %time 3**9999; | |
1250 | CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s |
|
1252 | CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s | |
1251 | Wall time: 0.00 s |
|
1253 | Wall time: 0.00 s | |
1252 |
|
1254 | |||
1253 | In [6]: %time 3**999999; |
|
1255 | In [6]: %time 3**999999; | |
1254 | CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s |
|
1256 | CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s | |
1255 | Wall time: 0.00 s |
|
1257 | Wall time: 0.00 s | |
1256 | Compiler : 0.78 s |
|
1258 | Compiler : 0.78 s | |
1257 | """ |
|
1259 | """ | |
1258 | # fail immediately if the given expression can't be compiled |
|
1260 | # fail immediately if the given expression can't be compiled | |
1259 |
|
1261 | |||
1260 | if line and cell: |
|
1262 | if line and cell: | |
1261 | raise UsageError("Can't use statement directly after '%%time'!") |
|
1263 | raise UsageError("Can't use statement directly after '%%time'!") | |
1262 |
|
1264 | |||
1263 | if cell: |
|
1265 | if cell: | |
1264 | expr = self.shell.transform_cell(cell) |
|
1266 | expr = self.shell.transform_cell(cell) | |
1265 | else: |
|
1267 | else: | |
1266 | expr = self.shell.transform_cell(line) |
|
1268 | expr = self.shell.transform_cell(line) | |
1267 |
|
1269 | |||
1268 | # Minimum time above which parse time will be reported |
|
1270 | # Minimum time above which parse time will be reported | |
1269 | tp_min = 0.1 |
|
1271 | tp_min = 0.1 | |
1270 |
|
1272 | |||
1271 | t0 = clock() |
|
1273 | t0 = clock() | |
1272 | expr_ast = self.shell.compile.ast_parse(expr) |
|
1274 | expr_ast = self.shell.compile.ast_parse(expr) | |
1273 | tp = clock()-t0 |
|
1275 | tp = clock()-t0 | |
1274 |
|
1276 | |||
1275 | # Apply AST transformations |
|
1277 | # Apply AST transformations | |
1276 | expr_ast = self.shell.transform_ast(expr_ast) |
|
1278 | expr_ast = self.shell.transform_ast(expr_ast) | |
1277 |
|
1279 | |||
1278 | # Minimum time above which compilation time will be reported |
|
1280 | # Minimum time above which compilation time will be reported | |
1279 | tc_min = 0.1 |
|
1281 | tc_min = 0.1 | |
1280 |
|
1282 | |||
1281 | expr_val=None |
|
1283 | expr_val=None | |
1282 | if len(expr_ast.body)==1 and isinstance(expr_ast.body[0], ast.Expr): |
|
1284 | if len(expr_ast.body)==1 and isinstance(expr_ast.body[0], ast.Expr): | |
1283 | mode = 'eval' |
|
1285 | mode = 'eval' | |
1284 | source = '<timed eval>' |
|
1286 | source = '<timed eval>' | |
1285 | expr_ast = ast.Expression(expr_ast.body[0].value) |
|
1287 | expr_ast = ast.Expression(expr_ast.body[0].value) | |
1286 | else: |
|
1288 | else: | |
1287 | mode = 'exec' |
|
1289 | mode = 'exec' | |
1288 | source = '<timed exec>' |
|
1290 | source = '<timed exec>' | |
1289 | # multi-line %%time case |
|
1291 | # multi-line %%time case | |
1290 | if len(expr_ast.body) > 1 and isinstance(expr_ast.body[-1], ast.Expr): |
|
1292 | if len(expr_ast.body) > 1 and isinstance(expr_ast.body[-1], ast.Expr): | |
1291 | expr_val= expr_ast.body[-1] |
|
1293 | expr_val= expr_ast.body[-1] | |
1292 | expr_ast = expr_ast.body[:-1] |
|
1294 | expr_ast = expr_ast.body[:-1] | |
1293 | expr_ast = Module(expr_ast, []) |
|
1295 | expr_ast = Module(expr_ast, []) | |
1294 | expr_val = ast.Expression(expr_val.value) |
|
1296 | expr_val = ast.Expression(expr_val.value) | |
1295 |
|
1297 | |||
1296 | t0 = clock() |
|
1298 | t0 = clock() | |
1297 | code = self.shell.compile(expr_ast, source, mode) |
|
1299 | code = self.shell.compile(expr_ast, source, mode) | |
1298 | tc = clock()-t0 |
|
1300 | tc = clock()-t0 | |
1299 |
|
1301 | |||
1300 | # skew measurement as little as possible |
|
1302 | # skew measurement as little as possible | |
1301 | glob = self.shell.user_ns |
|
1303 | glob = self.shell.user_ns | |
1302 | wtime = time.time |
|
1304 | wtime = time.time | |
1303 | # time execution |
|
1305 | # time execution | |
1304 | wall_st = wtime() |
|
1306 | wall_st = wtime() | |
1305 | if mode=='eval': |
|
1307 | if mode=='eval': | |
1306 | st = clock2() |
|
1308 | st = clock2() | |
1307 | try: |
|
1309 | try: | |
1308 | out = eval(code, glob, local_ns) |
|
1310 | out = eval(code, glob, local_ns) | |
1309 | except: |
|
1311 | except: | |
1310 | self.shell.showtraceback() |
|
1312 | self.shell.showtraceback() | |
1311 | return |
|
1313 | return | |
1312 | end = clock2() |
|
1314 | end = clock2() | |
1313 | else: |
|
1315 | else: | |
1314 | st = clock2() |
|
1316 | st = clock2() | |
1315 | try: |
|
1317 | try: | |
1316 | exec(code, glob, local_ns) |
|
1318 | exec(code, glob, local_ns) | |
1317 | out=None |
|
1319 | out=None | |
1318 | # multi-line %%time case |
|
1320 | # multi-line %%time case | |
1319 | if expr_val is not None: |
|
1321 | if expr_val is not None: | |
1320 | code_2 = self.shell.compile(expr_val, source, 'eval') |
|
1322 | code_2 = self.shell.compile(expr_val, source, 'eval') | |
1321 | out = eval(code_2, glob, local_ns) |
|
1323 | out = eval(code_2, glob, local_ns) | |
1322 | except: |
|
1324 | except: | |
1323 | self.shell.showtraceback() |
|
1325 | self.shell.showtraceback() | |
1324 | return |
|
1326 | return | |
1325 | end = clock2() |
|
1327 | end = clock2() | |
1326 |
|
1328 | |||
1327 | wall_end = wtime() |
|
1329 | wall_end = wtime() | |
1328 | # Compute actual times and report |
|
1330 | # Compute actual times and report | |
1329 | wall_time = wall_end - wall_st |
|
1331 | wall_time = wall_end - wall_st | |
1330 | cpu_user = end[0] - st[0] |
|
1332 | cpu_user = end[0] - st[0] | |
1331 | cpu_sys = end[1] - st[1] |
|
1333 | cpu_sys = end[1] - st[1] | |
1332 | cpu_tot = cpu_user + cpu_sys |
|
1334 | cpu_tot = cpu_user + cpu_sys | |
1333 | # On windows cpu_sys is always zero, so only total is displayed |
|
1335 | # On windows cpu_sys is always zero, so only total is displayed | |
1334 | if sys.platform != "win32": |
|
1336 | if sys.platform != "win32": | |
1335 | print( |
|
1337 | print( | |
1336 | f"CPU times: user {_format_time(cpu_user)}, sys: {_format_time(cpu_sys)}, total: {_format_time(cpu_tot)}" |
|
1338 | f"CPU times: user {_format_time(cpu_user)}, sys: {_format_time(cpu_sys)}, total: {_format_time(cpu_tot)}" | |
1337 | ) |
|
1339 | ) | |
1338 | else: |
|
1340 | else: | |
1339 | print(f"CPU times: total: {_format_time(cpu_tot)}") |
|
1341 | print(f"CPU times: total: {_format_time(cpu_tot)}") | |
1340 | print(f"Wall time: {_format_time(wall_time)}") |
|
1342 | print(f"Wall time: {_format_time(wall_time)}") | |
1341 | if tc > tc_min: |
|
1343 | if tc > tc_min: | |
1342 | print(f"Compiler : {_format_time(tc)}") |
|
1344 | print(f"Compiler : {_format_time(tc)}") | |
1343 | if tp > tp_min: |
|
1345 | if tp > tp_min: | |
1344 | print(f"Parser : {_format_time(tp)}") |
|
1346 | print(f"Parser : {_format_time(tp)}") | |
1345 | return out |
|
1347 | return out | |
1346 |
|
1348 | |||
1347 | @skip_doctest |
|
1349 | @skip_doctest | |
1348 | @line_magic |
|
1350 | @line_magic | |
1349 | def macro(self, parameter_s=''): |
|
1351 | def macro(self, parameter_s=''): | |
1350 | """Define a macro for future re-execution. It accepts ranges of history, |
|
1352 | """Define a macro for future re-execution. It accepts ranges of history, | |
1351 | filenames or string objects. |
|
1353 | filenames or string objects. | |
1352 |
|
1354 | |||
1353 | Usage:\\ |
|
1355 | Usage:\\ | |
1354 | %macro [options] name n1-n2 n3-n4 ... n5 .. n6 ... |
|
1356 | %macro [options] name n1-n2 n3-n4 ... n5 .. n6 ... | |
1355 |
|
1357 | |||
1356 | Options: |
|
1358 | Options: | |
1357 |
|
1359 | |||
1358 | -r: use 'raw' input. By default, the 'processed' history is used, |
|
1360 | -r: use 'raw' input. By default, the 'processed' history is used, | |
1359 | so that magics are loaded in their transformed version to valid |
|
1361 | so that magics are loaded in their transformed version to valid | |
1360 | Python. If this option is given, the raw input as typed at the |
|
1362 | Python. If this option is given, the raw input as typed at the | |
1361 | command line is used instead. |
|
1363 | command line is used instead. | |
1362 |
|
1364 | |||
1363 | -q: quiet macro definition. By default, a tag line is printed |
|
1365 | -q: quiet macro definition. By default, a tag line is printed | |
1364 | to indicate the macro has been created, and then the contents of |
|
1366 | to indicate the macro has been created, and then the contents of | |
1365 | the macro are printed. If this option is given, then no printout |
|
1367 | the macro are printed. If this option is given, then no printout | |
1366 | is produced once the macro is created. |
|
1368 | is produced once the macro is created. | |
1367 |
|
1369 | |||
1368 | This will define a global variable called `name` which is a string |
|
1370 | This will define a global variable called `name` which is a string | |
1369 | made of joining the slices and lines you specify (n1,n2,... numbers |
|
1371 | made of joining the slices and lines you specify (n1,n2,... numbers | |
1370 | above) from your input history into a single string. This variable |
|
1372 | above) from your input history into a single string. This variable | |
1371 | acts like an automatic function which re-executes those lines as if |
|
1373 | acts like an automatic function which re-executes those lines as if | |
1372 | you had typed them. You just type 'name' at the prompt and the code |
|
1374 | you had typed them. You just type 'name' at the prompt and the code | |
1373 | executes. |
|
1375 | executes. | |
1374 |
|
1376 | |||
1375 | The syntax for indicating input ranges is described in %history. |
|
1377 | The syntax for indicating input ranges is described in %history. | |
1376 |
|
1378 | |||
1377 | Note: as a 'hidden' feature, you can also use traditional python slice |
|
1379 | Note: as a 'hidden' feature, you can also use traditional python slice | |
1378 | notation, where N:M means numbers N through M-1. |
|
1380 | notation, where N:M means numbers N through M-1. | |
1379 |
|
1381 | |||
1380 | For example, if your history contains (print using %hist -n ):: |
|
1382 | For example, if your history contains (print using %hist -n ):: | |
1381 |
|
1383 | |||
1382 | 44: x=1 |
|
1384 | 44: x=1 | |
1383 | 45: y=3 |
|
1385 | 45: y=3 | |
1384 | 46: z=x+y |
|
1386 | 46: z=x+y | |
1385 | 47: print x |
|
1387 | 47: print x | |
1386 | 48: a=5 |
|
1388 | 48: a=5 | |
1387 | 49: print 'x',x,'y',y |
|
1389 | 49: print 'x',x,'y',y | |
1388 |
|
1390 | |||
1389 | you can create a macro with lines 44 through 47 (included) and line 49 |
|
1391 | you can create a macro with lines 44 through 47 (included) and line 49 | |
1390 | called my_macro with:: |
|
1392 | called my_macro with:: | |
1391 |
|
1393 | |||
1392 | In [55]: %macro my_macro 44-47 49 |
|
1394 | In [55]: %macro my_macro 44-47 49 | |
1393 |
|
1395 | |||
1394 | Now, typing `my_macro` (without quotes) will re-execute all this code |
|
1396 | Now, typing `my_macro` (without quotes) will re-execute all this code | |
1395 | in one pass. |
|
1397 | in one pass. | |
1396 |
|
1398 | |||
1397 | You don't need to give the line-numbers in order, and any given line |
|
1399 | You don't need to give the line-numbers in order, and any given line | |
1398 | number can appear multiple times. You can assemble macros with any |
|
1400 | number can appear multiple times. You can assemble macros with any | |
1399 | lines from your input history in any order. |
|
1401 | lines from your input history in any order. | |
1400 |
|
1402 | |||
1401 | The macro is a simple object which holds its value in an attribute, |
|
1403 | The macro is a simple object which holds its value in an attribute, | |
1402 | but IPython's display system checks for macros and executes them as |
|
1404 | but IPython's display system checks for macros and executes them as | |
1403 | code instead of printing them when you type their name. |
|
1405 | code instead of printing them when you type their name. | |
1404 |
|
1406 | |||
1405 | You can view a macro's contents by explicitly printing it with:: |
|
1407 | You can view a macro's contents by explicitly printing it with:: | |
1406 |
|
1408 | |||
1407 | print macro_name |
|
1409 | print macro_name | |
1408 |
|
1410 | |||
1409 | """ |
|
1411 | """ | |
1410 | opts,args = self.parse_options(parameter_s,'rq',mode='list') |
|
1412 | opts,args = self.parse_options(parameter_s,'rq',mode='list') | |
1411 | if not args: # List existing macros |
|
1413 | if not args: # List existing macros | |
1412 | return sorted(k for k,v in self.shell.user_ns.items() if isinstance(v, Macro)) |
|
1414 | return sorted(k for k,v in self.shell.user_ns.items() if isinstance(v, Macro)) | |
1413 | if len(args) == 1: |
|
1415 | if len(args) == 1: | |
1414 | raise UsageError( |
|
1416 | raise UsageError( | |
1415 | "%macro insufficient args; usage '%macro name n1-n2 n3-4...") |
|
1417 | "%macro insufficient args; usage '%macro name n1-n2 n3-4...") | |
1416 | name, codefrom = args[0], " ".join(args[1:]) |
|
1418 | name, codefrom = args[0], " ".join(args[1:]) | |
1417 |
|
1419 | |||
1418 | #print 'rng',ranges # dbg |
|
1420 | #print 'rng',ranges # dbg | |
1419 | try: |
|
1421 | try: | |
1420 | lines = self.shell.find_user_code(codefrom, 'r' in opts) |
|
1422 | lines = self.shell.find_user_code(codefrom, 'r' in opts) | |
1421 | except (ValueError, TypeError) as e: |
|
1423 | except (ValueError, TypeError) as e: | |
1422 | print(e.args[0]) |
|
1424 | print(e.args[0]) | |
1423 | return |
|
1425 | return | |
1424 | macro = Macro(lines) |
|
1426 | macro = Macro(lines) | |
1425 | self.shell.define_macro(name, macro) |
|
1427 | self.shell.define_macro(name, macro) | |
1426 | if not ( 'q' in opts) : |
|
1428 | if not ( 'q' in opts) : | |
1427 | print('Macro `%s` created. To execute, type its name (without quotes).' % name) |
|
1429 | print('Macro `%s` created. To execute, type its name (without quotes).' % name) | |
1428 | print('=== Macro contents: ===') |
|
1430 | print('=== Macro contents: ===') | |
1429 | print(macro, end=' ') |
|
1431 | print(macro, end=' ') | |
1430 |
|
1432 | |||
1431 | @magic_arguments.magic_arguments() |
|
1433 | @magic_arguments.magic_arguments() | |
1432 | @magic_arguments.argument('output', type=str, default='', nargs='?', |
|
1434 | @magic_arguments.argument('output', type=str, default='', nargs='?', | |
1433 | help="""The name of the variable in which to store output. |
|
1435 | help="""The name of the variable in which to store output. | |
1434 | This is a utils.io.CapturedIO object with stdout/err attributes |
|
1436 | This is a utils.io.CapturedIO object with stdout/err attributes | |
1435 | for the text of the captured output. |
|
1437 | for the text of the captured output. | |
1436 |
|
1438 | |||
1437 | CapturedOutput also has a show() method for displaying the output, |
|
1439 | CapturedOutput also has a show() method for displaying the output, | |
1438 | and __call__ as well, so you can use that to quickly display the |
|
1440 | and __call__ as well, so you can use that to quickly display the | |
1439 | output. |
|
1441 | output. | |
1440 |
|
1442 | |||
1441 | If unspecified, captured output is discarded. |
|
1443 | If unspecified, captured output is discarded. | |
1442 | """ |
|
1444 | """ | |
1443 | ) |
|
1445 | ) | |
1444 | @magic_arguments.argument('--no-stderr', action="store_true", |
|
1446 | @magic_arguments.argument('--no-stderr', action="store_true", | |
1445 | help="""Don't capture stderr.""" |
|
1447 | help="""Don't capture stderr.""" | |
1446 | ) |
|
1448 | ) | |
1447 | @magic_arguments.argument('--no-stdout', action="store_true", |
|
1449 | @magic_arguments.argument('--no-stdout', action="store_true", | |
1448 | help="""Don't capture stdout.""" |
|
1450 | help="""Don't capture stdout.""" | |
1449 | ) |
|
1451 | ) | |
1450 | @magic_arguments.argument('--no-display', action="store_true", |
|
1452 | @magic_arguments.argument('--no-display', action="store_true", | |
1451 | help="""Don't capture IPython's rich display.""" |
|
1453 | help="""Don't capture IPython's rich display.""" | |
1452 | ) |
|
1454 | ) | |
1453 | @cell_magic |
|
1455 | @cell_magic | |
1454 | def capture(self, line, cell): |
|
1456 | def capture(self, line, cell): | |
1455 | """run the cell, capturing stdout, stderr, and IPython's rich display() calls.""" |
|
1457 | """run the cell, capturing stdout, stderr, and IPython's rich display() calls.""" | |
1456 | args = magic_arguments.parse_argstring(self.capture, line) |
|
1458 | args = magic_arguments.parse_argstring(self.capture, line) | |
1457 | out = not args.no_stdout |
|
1459 | out = not args.no_stdout | |
1458 | err = not args.no_stderr |
|
1460 | err = not args.no_stderr | |
1459 | disp = not args.no_display |
|
1461 | disp = not args.no_display | |
1460 | with capture_output(out, err, disp) as io: |
|
1462 | with capture_output(out, err, disp) as io: | |
1461 | self.shell.run_cell(cell) |
|
1463 | self.shell.run_cell(cell) | |
1462 | if args.output: |
|
1464 | if args.output: | |
1463 | self.shell.user_ns[args.output] = io |
|
1465 | self.shell.user_ns[args.output] = io | |
1464 |
|
1466 | |||
1465 | def parse_breakpoint(text, current_file): |
|
1467 | def parse_breakpoint(text, current_file): | |
1466 | '''Returns (file, line) for file:line and (current_file, line) for line''' |
|
1468 | '''Returns (file, line) for file:line and (current_file, line) for line''' | |
1467 | colon = text.find(':') |
|
1469 | colon = text.find(':') | |
1468 | if colon == -1: |
|
1470 | if colon == -1: | |
1469 | return current_file, int(text) |
|
1471 | return current_file, int(text) | |
1470 | else: |
|
1472 | else: | |
1471 | return text[:colon], int(text[colon+1:]) |
|
1473 | return text[:colon], int(text[colon+1:]) | |
1472 |
|
1474 | |||
1473 | def _format_time(timespan, precision=3): |
|
1475 | def _format_time(timespan, precision=3): | |
1474 | """Formats the timespan in a human readable form""" |
|
1476 | """Formats the timespan in a human readable form""" | |
1475 |
|
1477 | |||
1476 | if timespan >= 60.0: |
|
1478 | if timespan >= 60.0: | |
1477 | # we have more than a minute, format that in a human readable form |
|
1479 | # we have more than a minute, format that in a human readable form | |
1478 | # Idea from http://snipplr.com/view/5713/ |
|
1480 | # Idea from http://snipplr.com/view/5713/ | |
1479 | parts = [("d", 60*60*24),("h", 60*60),("min", 60), ("s", 1)] |
|
1481 | parts = [("d", 60*60*24),("h", 60*60),("min", 60), ("s", 1)] | |
1480 | time = [] |
|
1482 | time = [] | |
1481 | leftover = timespan |
|
1483 | leftover = timespan | |
1482 | for suffix, length in parts: |
|
1484 | for suffix, length in parts: | |
1483 | value = int(leftover / length) |
|
1485 | value = int(leftover / length) | |
1484 | if value > 0: |
|
1486 | if value > 0: | |
1485 | leftover = leftover % length |
|
1487 | leftover = leftover % length | |
1486 | time.append(u'%s%s' % (str(value), suffix)) |
|
1488 | time.append(u'%s%s' % (str(value), suffix)) | |
1487 | if leftover < 1: |
|
1489 | if leftover < 1: | |
1488 | break |
|
1490 | break | |
1489 | return " ".join(time) |
|
1491 | return " ".join(time) | |
1490 |
|
1492 | |||
1491 |
|
1493 | |||
1492 | # Unfortunately the unicode 'micro' symbol can cause problems in |
|
1494 | # Unfortunately the unicode 'micro' symbol can cause problems in | |
1493 | # certain terminals. |
|
1495 | # certain terminals. | |
1494 | # See bug: https://bugs.launchpad.net/ipython/+bug/348466 |
|
1496 | # See bug: https://bugs.launchpad.net/ipython/+bug/348466 | |
1495 | # Try to prevent crashes by being more secure than it needs to |
|
1497 | # Try to prevent crashes by being more secure than it needs to | |
1496 | # E.g. eclipse is able to print a µ, but has no sys.stdout.encoding set. |
|
1498 | # E.g. eclipse is able to print a µ, but has no sys.stdout.encoding set. | |
1497 | units = [u"s", u"ms",u'us',"ns"] # the save value |
|
1499 | units = [u"s", u"ms",u'us',"ns"] # the save value | |
1498 | if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding: |
|
1500 | if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding: | |
1499 | try: |
|
1501 | try: | |
1500 | u'\xb5'.encode(sys.stdout.encoding) |
|
1502 | u'\xb5'.encode(sys.stdout.encoding) | |
1501 | units = [u"s", u"ms",u'\xb5s',"ns"] |
|
1503 | units = [u"s", u"ms",u'\xb5s',"ns"] | |
1502 | except: |
|
1504 | except: | |
1503 | pass |
|
1505 | pass | |
1504 | scaling = [1, 1e3, 1e6, 1e9] |
|
1506 | scaling = [1, 1e3, 1e6, 1e9] | |
1505 |
|
1507 | |||
1506 | if timespan > 0.0: |
|
1508 | if timespan > 0.0: | |
1507 | order = min(-int(math.floor(math.log10(timespan)) // 3), 3) |
|
1509 | order = min(-int(math.floor(math.log10(timespan)) // 3), 3) | |
1508 | else: |
|
1510 | else: | |
1509 | order = 3 |
|
1511 | order = 3 | |
1510 | return u"%.*g %s" % (precision, timespan * scaling[order], units[order]) |
|
1512 | return u"%.*g %s" % (precision, timespan * scaling[order], units[order]) |
@@ -1,855 +1,855 b'' | |||||
1 | """Implementation of magic functions for interaction with the OS. |
|
1 | """Implementation of magic functions for interaction with the OS. | |
2 |
|
2 | |||
3 | Note: this module is named 'osm' instead of 'os' to avoid a collision with the |
|
3 | Note: this module is named 'osm' instead of 'os' to avoid a collision with the | |
4 | builtin. |
|
4 | builtin. | |
5 | """ |
|
5 | """ | |
6 | # Copyright (c) IPython Development Team. |
|
6 | # Copyright (c) IPython Development Team. | |
7 | # Distributed under the terms of the Modified BSD License. |
|
7 | # Distributed under the terms of the Modified BSD License. | |
8 |
|
8 | |||
9 | import io |
|
9 | import io | |
10 | import os |
|
10 | import os | |
11 | import pathlib |
|
11 | import pathlib | |
12 | import re |
|
12 | import re | |
13 | import sys |
|
13 | import sys | |
14 | from pprint import pformat |
|
14 | from pprint import pformat | |
15 |
|
15 | |||
16 | from IPython.core import magic_arguments |
|
16 | from IPython.core import magic_arguments | |
17 | from IPython.core import oinspect |
|
17 | from IPython.core import oinspect | |
18 | from IPython.core import page |
|
18 | from IPython.core import page | |
19 | from IPython.core.alias import AliasError, Alias |
|
19 | from IPython.core.alias import AliasError, Alias | |
20 | from IPython.core.error import UsageError |
|
20 | from IPython.core.error import UsageError | |
21 | from IPython.core.magic import ( |
|
21 | from IPython.core.magic import ( | |
22 | Magics, compress_dhist, magics_class, line_magic, cell_magic, line_cell_magic |
|
22 | Magics, compress_dhist, magics_class, line_magic, cell_magic, line_cell_magic | |
23 | ) |
|
23 | ) | |
24 | from IPython.testing.skipdoctest import skip_doctest |
|
24 | from IPython.testing.skipdoctest import skip_doctest | |
25 | from IPython.utils.openpy import source_to_unicode |
|
25 | from IPython.utils.openpy import source_to_unicode | |
26 | from IPython.utils.process import abbrev_cwd |
|
26 | from IPython.utils.process import abbrev_cwd | |
27 | from IPython.utils.terminal import set_term_title |
|
27 | from IPython.utils.terminal import set_term_title | |
28 | from traitlets import Bool |
|
28 | from traitlets import Bool | |
29 | from warnings import warn |
|
29 | from warnings import warn | |
30 |
|
30 | |||
31 |
|
31 | |||
32 | @magics_class |
|
32 | @magics_class | |
33 | class OSMagics(Magics): |
|
33 | class OSMagics(Magics): | |
34 | """Magics to interact with the underlying OS (shell-type functionality). |
|
34 | """Magics to interact with the underlying OS (shell-type functionality). | |
35 | """ |
|
35 | """ | |
36 |
|
36 | |||
37 | cd_force_quiet = Bool(False, |
|
37 | cd_force_quiet = Bool(False, | |
38 | help="Force %cd magic to be quiet even if -q is not passed." |
|
38 | help="Force %cd magic to be quiet even if -q is not passed." | |
39 | ).tag(config=True) |
|
39 | ).tag(config=True) | |
40 |
|
40 | |||
41 | def __init__(self, shell=None, **kwargs): |
|
41 | def __init__(self, shell=None, **kwargs): | |
42 |
|
42 | |||
43 | # Now define isexec in a cross platform manner. |
|
43 | # Now define isexec in a cross platform manner. | |
44 | self.is_posix = False |
|
44 | self.is_posix = False | |
45 | self.execre = None |
|
45 | self.execre = None | |
46 | if os.name == 'posix': |
|
46 | if os.name == 'posix': | |
47 | self.is_posix = True |
|
47 | self.is_posix = True | |
48 | else: |
|
48 | else: | |
49 | try: |
|
49 | try: | |
50 | winext = os.environ['pathext'].replace(';','|').replace('.','') |
|
50 | winext = os.environ['pathext'].replace(';','|').replace('.','') | |
51 | except KeyError: |
|
51 | except KeyError: | |
52 | winext = 'exe|com|bat|py' |
|
52 | winext = 'exe|com|bat|py' | |
53 | try: |
|
53 | try: | |
54 | self.execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE) |
|
54 | self.execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE) | |
55 | except re.error: |
|
55 | except re.error: | |
56 | warn("Seems like your pathext environmental " |
|
56 | warn("Seems like your pathext environmental " | |
57 | "variable is malformed. Please check it to " |
|
57 | "variable is malformed. Please check it to " | |
58 | "enable a proper handle of file extensions " |
|
58 | "enable a proper handle of file extensions " | |
59 | "managed for your system") |
|
59 | "managed for your system") | |
60 | winext = 'exe|com|bat|py' |
|
60 | winext = 'exe|com|bat|py' | |
61 | self.execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE) |
|
61 | self.execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE) | |
62 |
|
62 | |||
63 | # call up the chain |
|
63 | # call up the chain | |
64 | super().__init__(shell=shell, **kwargs) |
|
64 | super().__init__(shell=shell, **kwargs) | |
65 |
|
65 | |||
66 |
|
66 | |||
67 | def _isexec_POSIX(self, file): |
|
67 | def _isexec_POSIX(self, file): | |
68 | """ |
|
68 | """ | |
69 | Test for executable on a POSIX system |
|
69 | Test for executable on a POSIX system | |
70 | """ |
|
70 | """ | |
71 | if os.access(file.path, os.X_OK): |
|
71 | if os.access(file.path, os.X_OK): | |
72 | # will fail on maxOS if access is not X_OK |
|
72 | # will fail on maxOS if access is not X_OK | |
73 | return file.is_file() |
|
73 | return file.is_file() | |
74 | return False |
|
74 | return False | |
75 |
|
75 | |||
76 |
|
76 | |||
77 |
|
77 | |||
78 | def _isexec_WIN(self, file): |
|
78 | def _isexec_WIN(self, file): | |
79 | """ |
|
79 | """ | |
80 | Test for executable file on non POSIX system |
|
80 | Test for executable file on non POSIX system | |
81 | """ |
|
81 | """ | |
82 | return file.is_file() and self.execre.match(file.name) is not None |
|
82 | return file.is_file() and self.execre.match(file.name) is not None | |
83 |
|
83 | |||
84 | def isexec(self, file): |
|
84 | def isexec(self, file): | |
85 | """ |
|
85 | """ | |
86 | Test for executable file on non POSIX system |
|
86 | Test for executable file on non POSIX system | |
87 | """ |
|
87 | """ | |
88 | if self.is_posix: |
|
88 | if self.is_posix: | |
89 | return self._isexec_POSIX(file) |
|
89 | return self._isexec_POSIX(file) | |
90 | else: |
|
90 | else: | |
91 | return self._isexec_WIN(file) |
|
91 | return self._isexec_WIN(file) | |
92 |
|
92 | |||
93 |
|
93 | |||
94 | @skip_doctest |
|
94 | @skip_doctest | |
95 | @line_magic |
|
95 | @line_magic | |
96 | def alias(self, parameter_s=''): |
|
96 | def alias(self, parameter_s=''): | |
97 | """Define an alias for a system command. |
|
97 | """Define an alias for a system command. | |
98 |
|
98 | |||
99 | '%alias alias_name cmd' defines 'alias_name' as an alias for 'cmd' |
|
99 | '%alias alias_name cmd' defines 'alias_name' as an alias for 'cmd' | |
100 |
|
100 | |||
101 | Then, typing 'alias_name params' will execute the system command 'cmd |
|
101 | Then, typing 'alias_name params' will execute the system command 'cmd | |
102 | params' (from your underlying operating system). |
|
102 | params' (from your underlying operating system). | |
103 |
|
103 | |||
104 | Aliases have lower precedence than magic functions and Python normal |
|
104 | Aliases have lower precedence than magic functions and Python normal | |
105 | variables, so if 'foo' is both a Python variable and an alias, the |
|
105 | variables, so if 'foo' is both a Python variable and an alias, the | |
106 | alias can not be executed until 'del foo' removes the Python variable. |
|
106 | alias can not be executed until 'del foo' removes the Python variable. | |
107 |
|
107 | |||
108 | You can use the %l specifier in an alias definition to represent the |
|
108 | You can use the %l specifier in an alias definition to represent the | |
109 | whole line when the alias is called. For example:: |
|
109 | whole line when the alias is called. For example:: | |
110 |
|
110 | |||
111 | In [2]: alias bracket echo "Input in brackets: <%l>" |
|
111 | In [2]: alias bracket echo "Input in brackets: <%l>" | |
112 | In [3]: bracket hello world |
|
112 | In [3]: bracket hello world | |
113 | Input in brackets: <hello world> |
|
113 | Input in brackets: <hello world> | |
114 |
|
114 | |||
115 | You can also define aliases with parameters using %s specifiers (one |
|
115 | You can also define aliases with parameters using %s specifiers (one | |
116 | per parameter):: |
|
116 | per parameter):: | |
117 |
|
117 | |||
118 | In [1]: alias parts echo first %s second %s |
|
118 | In [1]: alias parts echo first %s second %s | |
119 | In [2]: %parts A B |
|
119 | In [2]: %parts A B | |
120 | first A second B |
|
120 | first A second B | |
121 | In [3]: %parts A |
|
121 | In [3]: %parts A | |
122 | Incorrect number of arguments: 2 expected. |
|
122 | Incorrect number of arguments: 2 expected. | |
123 | parts is an alias to: 'echo first %s second %s' |
|
123 | parts is an alias to: 'echo first %s second %s' | |
124 |
|
124 | |||
125 | Note that %l and %s are mutually exclusive. You can only use one or |
|
125 | Note that %l and %s are mutually exclusive. You can only use one or | |
126 | the other in your aliases. |
|
126 | the other in your aliases. | |
127 |
|
127 | |||
128 | Aliases expand Python variables just like system calls using ! or !! |
|
128 | Aliases expand Python variables just like system calls using ! or !! | |
129 | do: all expressions prefixed with '$' get expanded. For details of |
|
129 | do: all expressions prefixed with '$' get expanded. For details of | |
130 | the semantic rules, see PEP-215: |
|
130 | the semantic rules, see PEP-215: | |
131 | https://peps.python.org/pep-0215/. This is the library used by |
|
131 | https://peps.python.org/pep-0215/. This is the library used by | |
132 | IPython for variable expansion. If you want to access a true shell |
|
132 | IPython for variable expansion. If you want to access a true shell | |
133 | variable, an extra $ is necessary to prevent its expansion by |
|
133 | variable, an extra $ is necessary to prevent its expansion by | |
134 | IPython:: |
|
134 | IPython:: | |
135 |
|
135 | |||
136 | In [6]: alias show echo |
|
136 | In [6]: alias show echo | |
137 | In [7]: PATH='A Python string' |
|
137 | In [7]: PATH='A Python string' | |
138 | In [8]: show $PATH |
|
138 | In [8]: show $PATH | |
139 | A Python string |
|
139 | A Python string | |
140 | In [9]: show $$PATH |
|
140 | In [9]: show $$PATH | |
141 | /usr/local/lf9560/bin:/usr/local/intel/compiler70/ia32/bin:... |
|
141 | /usr/local/lf9560/bin:/usr/local/intel/compiler70/ia32/bin:... | |
142 |
|
142 | |||
143 | You can use the alias facility to access all of $PATH. See the %rehashx |
|
143 | You can use the alias facility to access all of $PATH. See the %rehashx | |
144 | function, which automatically creates aliases for the contents of your |
|
144 | function, which automatically creates aliases for the contents of your | |
145 | $PATH. |
|
145 | $PATH. | |
146 |
|
146 | |||
147 | If called with no parameters, %alias prints the current alias table |
|
147 | If called with no parameters, %alias prints the current alias table | |
148 | for your system. For posix systems, the default aliases are 'cat', |
|
148 | for your system. For posix systems, the default aliases are 'cat', | |
149 | 'cp', 'mv', 'rm', 'rmdir', and 'mkdir', and other platform-specific |
|
149 | 'cp', 'mv', 'rm', 'rmdir', and 'mkdir', and other platform-specific | |
150 | aliases are added. For windows-based systems, the default aliases are |
|
150 | aliases are added. For windows-based systems, the default aliases are | |
151 | 'copy', 'ddir', 'echo', 'ls', 'ldir', 'mkdir', 'ren', and 'rmdir'. |
|
151 | 'copy', 'ddir', 'echo', 'ls', 'ldir', 'mkdir', 'ren', and 'rmdir'. | |
152 |
|
152 | |||
153 | You can see the definition of alias by adding a question mark in the |
|
153 | You can see the definition of alias by adding a question mark in the | |
154 | end:: |
|
154 | end:: | |
155 |
|
155 | |||
156 | In [1]: cat? |
|
156 | In [1]: cat? | |
157 | Repr: <alias cat for 'cat'>""" |
|
157 | Repr: <alias cat for 'cat'>""" | |
158 |
|
158 | |||
159 | par = parameter_s.strip() |
|
159 | par = parameter_s.strip() | |
160 | if not par: |
|
160 | if not par: | |
161 | aliases = sorted(self.shell.alias_manager.aliases) |
|
161 | aliases = sorted(self.shell.alias_manager.aliases) | |
162 | # stored = self.shell.db.get('stored_aliases', {} ) |
|
162 | # stored = self.shell.db.get('stored_aliases', {} ) | |
163 | # for k, v in stored: |
|
163 | # for k, v in stored: | |
164 | # atab.append(k, v[0]) |
|
164 | # atab.append(k, v[0]) | |
165 |
|
165 | |||
166 | print("Total number of aliases:", len(aliases)) |
|
166 | print("Total number of aliases:", len(aliases)) | |
167 | sys.stdout.flush() |
|
167 | sys.stdout.flush() | |
168 | return aliases |
|
168 | return aliases | |
169 |
|
169 | |||
170 | # Now try to define a new one |
|
170 | # Now try to define a new one | |
171 | try: |
|
171 | try: | |
172 | alias,cmd = par.split(None, 1) |
|
172 | alias,cmd = par.split(None, 1) | |
173 | except TypeError: |
|
173 | except TypeError: | |
174 | print(oinspect.getdoc(self.alias)) |
|
174 | print(oinspect.getdoc(self.alias)) | |
175 | return |
|
175 | return | |
176 |
|
176 | |||
177 | try: |
|
177 | try: | |
178 | self.shell.alias_manager.define_alias(alias, cmd) |
|
178 | self.shell.alias_manager.define_alias(alias, cmd) | |
179 | except AliasError as e: |
|
179 | except AliasError as e: | |
180 | print(e) |
|
180 | print(e) | |
181 | # end magic_alias |
|
181 | # end magic_alias | |
182 |
|
182 | |||
183 | @line_magic |
|
183 | @line_magic | |
184 | def unalias(self, parameter_s=''): |
|
184 | def unalias(self, parameter_s=''): | |
185 | """Remove an alias""" |
|
185 | """Remove an alias""" | |
186 |
|
186 | |||
187 | aname = parameter_s.strip() |
|
187 | aname = parameter_s.strip() | |
188 | try: |
|
188 | try: | |
189 | self.shell.alias_manager.undefine_alias(aname) |
|
189 | self.shell.alias_manager.undefine_alias(aname) | |
190 | except ValueError as e: |
|
190 | except ValueError as e: | |
191 | print(e) |
|
191 | print(e) | |
192 | return |
|
192 | return | |
193 |
|
193 | |||
194 | stored = self.shell.db.get('stored_aliases', {} ) |
|
194 | stored = self.shell.db.get('stored_aliases', {} ) | |
195 | if aname in stored: |
|
195 | if aname in stored: | |
196 | print("Removing %stored alias",aname) |
|
196 | print("Removing %stored alias",aname) | |
197 | del stored[aname] |
|
197 | del stored[aname] | |
198 | self.shell.db['stored_aliases'] = stored |
|
198 | self.shell.db['stored_aliases'] = stored | |
199 |
|
199 | |||
200 | @line_magic |
|
200 | @line_magic | |
201 | def rehashx(self, parameter_s=''): |
|
201 | def rehashx(self, parameter_s=''): | |
202 | """Update the alias table with all executable files in $PATH. |
|
202 | """Update the alias table with all executable files in $PATH. | |
203 |
|
203 | |||
204 | rehashx explicitly checks that every entry in $PATH is a file |
|
204 | rehashx explicitly checks that every entry in $PATH is a file | |
205 | with execute access (os.X_OK). |
|
205 | with execute access (os.X_OK). | |
206 |
|
206 | |||
207 | Under Windows, it checks executability as a match against a |
|
207 | Under Windows, it checks executability as a match against a | |
208 | '|'-separated string of extensions, stored in the IPython config |
|
208 | '|'-separated string of extensions, stored in the IPython config | |
209 | variable win_exec_ext. This defaults to 'exe|com|bat'. |
|
209 | variable win_exec_ext. This defaults to 'exe|com|bat'. | |
210 |
|
210 | |||
211 | This function also resets the root module cache of module completer, |
|
211 | This function also resets the root module cache of module completer, | |
212 | used on slow filesystems. |
|
212 | used on slow filesystems. | |
213 | """ |
|
213 | """ | |
214 | from IPython.core.alias import InvalidAliasError |
|
214 | from IPython.core.alias import InvalidAliasError | |
215 |
|
215 | |||
216 | # for the benefit of module completer in ipy_completers.py |
|
216 | # for the benefit of module completer in ipy_completers.py | |
217 | del self.shell.db['rootmodules_cache'] |
|
217 | del self.shell.db['rootmodules_cache'] | |
218 |
|
218 | |||
219 | path = [os.path.abspath(os.path.expanduser(p)) for p in |
|
219 | path = [os.path.abspath(os.path.expanduser(p)) for p in | |
220 | os.environ.get('PATH','').split(os.pathsep)] |
|
220 | os.environ.get('PATH','').split(os.pathsep)] | |
221 |
|
221 | |||
222 | syscmdlist = [] |
|
222 | syscmdlist = [] | |
223 | savedir = os.getcwd() |
|
223 | savedir = os.getcwd() | |
224 |
|
224 | |||
225 | # Now walk the paths looking for executables to alias. |
|
225 | # Now walk the paths looking for executables to alias. | |
226 | try: |
|
226 | try: | |
227 | # write the whole loop for posix/Windows so we don't have an if in |
|
227 | # write the whole loop for posix/Windows so we don't have an if in | |
228 | # the innermost part |
|
228 | # the innermost part | |
229 | if self.is_posix: |
|
229 | if self.is_posix: | |
230 | for pdir in path: |
|
230 | for pdir in path: | |
231 | try: |
|
231 | try: | |
232 | os.chdir(pdir) |
|
232 | os.chdir(pdir) | |
233 | except OSError: |
|
233 | except OSError: | |
234 | continue |
|
234 | continue | |
235 |
|
235 | |||
236 | # for python 3.6+ rewrite to: with os.scandir(pdir) as dirlist: |
|
236 | # for python 3.6+ rewrite to: with os.scandir(pdir) as dirlist: | |
237 | dirlist = os.scandir(path=pdir) |
|
237 | dirlist = os.scandir(path=pdir) | |
238 | for ff in dirlist: |
|
238 | for ff in dirlist: | |
239 | if self.isexec(ff): |
|
239 | if self.isexec(ff): | |
240 | fname = ff.name |
|
240 | fname = ff.name | |
241 | try: |
|
241 | try: | |
242 | # Removes dots from the name since ipython |
|
242 | # Removes dots from the name since ipython | |
243 | # will assume names with dots to be python. |
|
243 | # will assume names with dots to be python. | |
244 | if not self.shell.alias_manager.is_alias(fname): |
|
244 | if not self.shell.alias_manager.is_alias(fname): | |
245 | self.shell.alias_manager.define_alias( |
|
245 | self.shell.alias_manager.define_alias( | |
246 | fname.replace('.',''), fname) |
|
246 | fname.replace('.',''), fname) | |
247 | except InvalidAliasError: |
|
247 | except InvalidAliasError: | |
248 | pass |
|
248 | pass | |
249 | else: |
|
249 | else: | |
250 | syscmdlist.append(fname) |
|
250 | syscmdlist.append(fname) | |
251 | else: |
|
251 | else: | |
252 | no_alias = Alias.blacklist |
|
252 | no_alias = Alias.blacklist | |
253 | for pdir in path: |
|
253 | for pdir in path: | |
254 | try: |
|
254 | try: | |
255 | os.chdir(pdir) |
|
255 | os.chdir(pdir) | |
256 | except OSError: |
|
256 | except OSError: | |
257 | continue |
|
257 | continue | |
258 |
|
258 | |||
259 | # for python 3.6+ rewrite to: with os.scandir(pdir) as dirlist: |
|
259 | # for python 3.6+ rewrite to: with os.scandir(pdir) as dirlist: | |
260 | dirlist = os.scandir(pdir) |
|
260 | dirlist = os.scandir(pdir) | |
261 | for ff in dirlist: |
|
261 | for ff in dirlist: | |
262 | fname = ff.name |
|
262 | fname = ff.name | |
263 | base, ext = os.path.splitext(fname) |
|
263 | base, ext = os.path.splitext(fname) | |
264 | if self.isexec(ff) and base.lower() not in no_alias: |
|
264 | if self.isexec(ff) and base.lower() not in no_alias: | |
265 | if ext.lower() == '.exe': |
|
265 | if ext.lower() == '.exe': | |
266 | fname = base |
|
266 | fname = base | |
267 | try: |
|
267 | try: | |
268 | # Removes dots from the name since ipython |
|
268 | # Removes dots from the name since ipython | |
269 | # will assume names with dots to be python. |
|
269 | # will assume names with dots to be python. | |
270 | self.shell.alias_manager.define_alias( |
|
270 | self.shell.alias_manager.define_alias( | |
271 | base.lower().replace('.',''), fname) |
|
271 | base.lower().replace('.',''), fname) | |
272 | except InvalidAliasError: |
|
272 | except InvalidAliasError: | |
273 | pass |
|
273 | pass | |
274 | syscmdlist.append(fname) |
|
274 | syscmdlist.append(fname) | |
275 |
|
275 | |||
276 | self.shell.db['syscmdlist'] = syscmdlist |
|
276 | self.shell.db['syscmdlist'] = syscmdlist | |
277 | finally: |
|
277 | finally: | |
278 | os.chdir(savedir) |
|
278 | os.chdir(savedir) | |
279 |
|
279 | |||
280 | @skip_doctest |
|
280 | @skip_doctest | |
281 | @line_magic |
|
281 | @line_magic | |
282 | def pwd(self, parameter_s=''): |
|
282 | def pwd(self, parameter_s=''): | |
283 | """Return the current working directory path. |
|
283 | """Return the current working directory path. | |
284 |
|
284 | |||
285 | Examples |
|
285 | Examples | |
286 | -------- |
|
286 | -------- | |
287 | :: |
|
287 | :: | |
288 |
|
288 | |||
289 | In [9]: pwd |
|
289 | In [9]: pwd | |
290 | Out[9]: '/home/tsuser/sprint/ipython' |
|
290 | Out[9]: '/home/tsuser/sprint/ipython' | |
291 | """ |
|
291 | """ | |
292 | try: |
|
292 | try: | |
293 | return os.getcwd() |
|
293 | return os.getcwd() | |
294 | except FileNotFoundError as e: |
|
294 | except FileNotFoundError as e: | |
295 | raise UsageError("CWD no longer exists - please use %cd to change directory.") from e |
|
295 | raise UsageError("CWD no longer exists - please use %cd to change directory.") from e | |
296 |
|
296 | |||
297 | @skip_doctest |
|
297 | @skip_doctest | |
298 | @line_magic |
|
298 | @line_magic | |
299 | def cd(self, parameter_s=''): |
|
299 | def cd(self, parameter_s=''): | |
300 | """Change the current working directory. |
|
300 | """Change the current working directory. | |
301 |
|
301 | |||
302 | This command automatically maintains an internal list of directories |
|
302 | This command automatically maintains an internal list of directories | |
303 | you visit during your IPython session, in the variable ``_dh``. The |
|
303 | you visit during your IPython session, in the variable ``_dh``. The | |
304 | command :magic:`%dhist` shows this history nicely formatted. You can |
|
304 | command :magic:`%dhist` shows this history nicely formatted. You can | |
305 | also do ``cd -<tab>`` to see directory history conveniently. |
|
305 | also do ``cd -<tab>`` to see directory history conveniently. | |
306 | Usage: |
|
306 | Usage: | |
307 |
|
307 | |||
308 | - ``cd 'dir'``: changes to directory 'dir'. |
|
308 | - ``cd 'dir'``: changes to directory 'dir'. | |
309 | - ``cd -``: changes to the last visited directory. |
|
309 | - ``cd -``: changes to the last visited directory. | |
310 | - ``cd -<n>``: changes to the n-th directory in the directory history. |
|
310 | - ``cd -<n>``: changes to the n-th directory in the directory history. | |
311 | - ``cd --foo``: change to directory that matches 'foo' in history |
|
311 | - ``cd --foo``: change to directory that matches 'foo' in history | |
312 | - ``cd -b <bookmark_name>``: jump to a bookmark set by %bookmark |
|
312 | - ``cd -b <bookmark_name>``: jump to a bookmark set by %bookmark | |
313 | - Hitting a tab key after ``cd -b`` allows you to tab-complete |
|
313 | - Hitting a tab key after ``cd -b`` allows you to tab-complete | |
314 | bookmark names. |
|
314 | bookmark names. | |
315 |
|
315 | |||
316 | .. note:: |
|
316 | .. note:: | |
317 | ``cd <bookmark_name>`` is enough if there is no directory |
|
317 | ``cd <bookmark_name>`` is enough if there is no directory | |
318 | ``<bookmark_name>``, but a bookmark with the name exists. |
|
318 | ``<bookmark_name>``, but a bookmark with the name exists. | |
319 |
|
319 | |||
320 | Options: |
|
320 | Options: | |
321 |
|
321 | |||
322 | -q Be quiet. Do not print the working directory after the |
|
322 | -q Be quiet. Do not print the working directory after the | |
323 | cd command is executed. By default IPython's cd |
|
323 | cd command is executed. By default IPython's cd | |
324 | command does print this directory, since the default |
|
324 | command does print this directory, since the default | |
325 | prompts do not display path information. |
|
325 | prompts do not display path information. | |
326 |
|
326 | |||
327 | .. note:: |
|
327 | .. note:: | |
328 | Note that ``!cd`` doesn't work for this purpose because the shell |
|
328 | Note that ``!cd`` doesn't work for this purpose because the shell | |
329 | where ``!command`` runs is immediately discarded after executing |
|
329 | where ``!command`` runs is immediately discarded after executing | |
330 | 'command'. |
|
330 | 'command'. | |
331 |
|
331 | |||
332 | Examples |
|
332 | Examples | |
333 | -------- |
|
333 | -------- | |
334 | :: |
|
334 | :: | |
335 |
|
335 | |||
336 | In [10]: cd parent/child |
|
336 | In [10]: cd parent/child | |
337 | /home/tsuser/parent/child |
|
337 | /home/tsuser/parent/child | |
338 | """ |
|
338 | """ | |
339 |
|
339 | |||
340 | try: |
|
340 | try: | |
341 | oldcwd = os.getcwd() |
|
341 | oldcwd = os.getcwd() | |
342 | except FileNotFoundError: |
|
342 | except FileNotFoundError: | |
343 | # Happens if the CWD has been deleted. |
|
343 | # Happens if the CWD has been deleted. | |
344 | oldcwd = None |
|
344 | oldcwd = None | |
345 |
|
345 | |||
346 | numcd = re.match(r'(-)(\d+)$',parameter_s) |
|
346 | numcd = re.match(r'(-)(\d+)$',parameter_s) | |
347 | # jump in directory history by number |
|
347 | # jump in directory history by number | |
348 | if numcd: |
|
348 | if numcd: | |
349 | nn = int(numcd.group(2)) |
|
349 | nn = int(numcd.group(2)) | |
350 | try: |
|
350 | try: | |
351 | ps = self.shell.user_ns['_dh'][nn] |
|
351 | ps = self.shell.user_ns['_dh'][nn] | |
352 | except IndexError: |
|
352 | except IndexError: | |
353 | print('The requested directory does not exist in history.') |
|
353 | print('The requested directory does not exist in history.') | |
354 | return |
|
354 | return | |
355 | else: |
|
355 | else: | |
356 | opts = {} |
|
356 | opts = {} | |
357 | elif parameter_s.startswith('--'): |
|
357 | elif parameter_s.startswith('--'): | |
358 | ps = None |
|
358 | ps = None | |
359 | fallback = None |
|
359 | fallback = None | |
360 | pat = parameter_s[2:] |
|
360 | pat = parameter_s[2:] | |
361 | dh = self.shell.user_ns['_dh'] |
|
361 | dh = self.shell.user_ns['_dh'] | |
362 | # first search only by basename (last component) |
|
362 | # first search only by basename (last component) | |
363 | for ent in reversed(dh): |
|
363 | for ent in reversed(dh): | |
364 | if pat in os.path.basename(ent) and os.path.isdir(ent): |
|
364 | if pat in os.path.basename(ent) and os.path.isdir(ent): | |
365 | ps = ent |
|
365 | ps = ent | |
366 | break |
|
366 | break | |
367 |
|
367 | |||
368 | if fallback is None and pat in ent and os.path.isdir(ent): |
|
368 | if fallback is None and pat in ent and os.path.isdir(ent): | |
369 | fallback = ent |
|
369 | fallback = ent | |
370 |
|
370 | |||
371 | # if we have no last part match, pick the first full path match |
|
371 | # if we have no last part match, pick the first full path match | |
372 | if ps is None: |
|
372 | if ps is None: | |
373 | ps = fallback |
|
373 | ps = fallback | |
374 |
|
374 | |||
375 | if ps is None: |
|
375 | if ps is None: | |
376 | print("No matching entry in directory history") |
|
376 | print("No matching entry in directory history") | |
377 | return |
|
377 | return | |
378 | else: |
|
378 | else: | |
379 | opts = {} |
|
379 | opts = {} | |
380 |
|
380 | |||
381 |
|
381 | |||
382 | else: |
|
382 | else: | |
383 | opts, ps = self.parse_options(parameter_s, 'qb', mode='string') |
|
383 | opts, ps = self.parse_options(parameter_s, 'qb', mode='string') | |
384 | # jump to previous |
|
384 | # jump to previous | |
385 | if ps == '-': |
|
385 | if ps == '-': | |
386 | try: |
|
386 | try: | |
387 | ps = self.shell.user_ns['_dh'][-2] |
|
387 | ps = self.shell.user_ns['_dh'][-2] | |
388 | except IndexError as e: |
|
388 | except IndexError as e: | |
389 | raise UsageError('%cd -: No previous directory to change to.') from e |
|
389 | raise UsageError('%cd -: No previous directory to change to.') from e | |
390 | # jump to bookmark if needed |
|
390 | # jump to bookmark if needed | |
391 | else: |
|
391 | else: | |
392 | if not os.path.isdir(ps) or 'b' in opts: |
|
392 | if not os.path.isdir(ps) or 'b' in opts: | |
393 | bkms = self.shell.db.get('bookmarks', {}) |
|
393 | bkms = self.shell.db.get('bookmarks', {}) | |
394 |
|
394 | |||
395 | if ps in bkms: |
|
395 | if ps in bkms: | |
396 | target = bkms[ps] |
|
396 | target = bkms[ps] | |
397 | print('(bookmark:%s) -> %s' % (ps, target)) |
|
397 | print('(bookmark:%s) -> %s' % (ps, target)) | |
398 | ps = target |
|
398 | ps = target | |
399 | else: |
|
399 | else: | |
400 | if 'b' in opts: |
|
400 | if 'b' in opts: | |
401 | raise UsageError("Bookmark '%s' not found. " |
|
401 | raise UsageError("Bookmark '%s' not found. " | |
402 | "Use '%%bookmark -l' to see your bookmarks." % ps) |
|
402 | "Use '%%bookmark -l' to see your bookmarks." % ps) | |
403 |
|
403 | |||
404 | # at this point ps should point to the target dir |
|
404 | # at this point ps should point to the target dir | |
405 | if ps: |
|
405 | if ps: | |
406 | try: |
|
406 | try: | |
407 | os.chdir(os.path.expanduser(ps)) |
|
407 | os.chdir(os.path.expanduser(ps)) | |
408 | if hasattr(self.shell, 'term_title') and self.shell.term_title: |
|
408 | if hasattr(self.shell, 'term_title') and self.shell.term_title: | |
409 | set_term_title(self.shell.term_title_format.format(cwd=abbrev_cwd())) |
|
409 | set_term_title(self.shell.term_title_format.format(cwd=abbrev_cwd())) | |
410 | except OSError: |
|
410 | except OSError: | |
411 | print(sys.exc_info()[1]) |
|
411 | print(sys.exc_info()[1]) | |
412 | else: |
|
412 | else: | |
413 | cwd = pathlib.Path.cwd() |
|
413 | cwd = pathlib.Path.cwd() | |
414 | dhist = self.shell.user_ns['_dh'] |
|
414 | dhist = self.shell.user_ns['_dh'] | |
415 | if oldcwd != cwd: |
|
415 | if oldcwd != cwd: | |
416 | dhist.append(cwd) |
|
416 | dhist.append(cwd) | |
417 | self.shell.db['dhist'] = compress_dhist(dhist)[-100:] |
|
417 | self.shell.db['dhist'] = compress_dhist(dhist)[-100:] | |
418 |
|
418 | |||
419 | else: |
|
419 | else: | |
420 | os.chdir(self.shell.home_dir) |
|
420 | os.chdir(self.shell.home_dir) | |
421 | if hasattr(self.shell, 'term_title') and self.shell.term_title: |
|
421 | if hasattr(self.shell, 'term_title') and self.shell.term_title: | |
422 | set_term_title(self.shell.term_title_format.format(cwd="~")) |
|
422 | set_term_title(self.shell.term_title_format.format(cwd="~")) | |
423 | cwd = pathlib.Path.cwd() |
|
423 | cwd = pathlib.Path.cwd() | |
424 | dhist = self.shell.user_ns['_dh'] |
|
424 | dhist = self.shell.user_ns['_dh'] | |
425 |
|
425 | |||
426 | if oldcwd != cwd: |
|
426 | if oldcwd != cwd: | |
427 | dhist.append(cwd) |
|
427 | dhist.append(cwd) | |
428 | self.shell.db['dhist'] = compress_dhist(dhist)[-100:] |
|
428 | self.shell.db['dhist'] = compress_dhist(dhist)[-100:] | |
429 | if not 'q' in opts and not self.cd_force_quiet and self.shell.user_ns['_dh']: |
|
429 | if not 'q' in opts and not self.cd_force_quiet and self.shell.user_ns['_dh']: | |
430 | print(self.shell.user_ns['_dh'][-1]) |
|
430 | print(self.shell.user_ns['_dh'][-1]) | |
431 |
|
431 | |||
432 | @line_magic |
|
432 | @line_magic | |
433 | def env(self, parameter_s=''): |
|
433 | def env(self, parameter_s=''): | |
434 | """Get, set, or list environment variables. |
|
434 | """Get, set, or list environment variables. | |
435 |
|
435 | |||
436 | Usage:\\ |
|
436 | Usage:\\ | |
437 |
|
437 | |||
438 | :``%env``: lists all environment variables/values |
|
438 | :``%env``: lists all environment variables/values | |
439 | :``%env var``: get value for var |
|
439 | :``%env var``: get value for var | |
440 | :``%env var val``: set value for var |
|
440 | :``%env var val``: set value for var | |
441 | :``%env var=val``: set value for var |
|
441 | :``%env var=val``: set value for var | |
442 | :``%env var=$val``: set value for var, using python expansion if possible |
|
442 | :``%env var=$val``: set value for var, using python expansion if possible | |
443 | """ |
|
443 | """ | |
444 | if parameter_s.strip(): |
|
444 | if parameter_s.strip(): | |
445 | split = '=' if '=' in parameter_s else ' ' |
|
445 | split = '=' if '=' in parameter_s else ' ' | |
446 | bits = parameter_s.split(split) |
|
446 | bits = parameter_s.split(split) | |
447 | if len(bits) == 1: |
|
447 | if len(bits) == 1: | |
448 | key = parameter_s.strip() |
|
448 | key = parameter_s.strip() | |
449 | if key in os.environ: |
|
449 | if key in os.environ: | |
450 | return os.environ[key] |
|
450 | return os.environ[key] | |
451 | else: |
|
451 | else: | |
452 | err = "Environment does not have key: {0}".format(key) |
|
452 | err = "Environment does not have key: {0}".format(key) | |
453 | raise UsageError(err) |
|
453 | raise UsageError(err) | |
454 | if len(bits) > 1: |
|
454 | if len(bits) > 1: | |
455 | return self.set_env(parameter_s) |
|
455 | return self.set_env(parameter_s) | |
456 | env = dict(os.environ) |
|
456 | env = dict(os.environ) | |
457 | # hide likely secrets when printing the whole environment |
|
457 | # hide likely secrets when printing the whole environment | |
458 | for key in list(env): |
|
458 | for key in list(env): | |
459 | if any(s in key.lower() for s in ('key', 'token', 'secret')): |
|
459 | if any(s in key.lower() for s in ('key', 'token', 'secret')): | |
460 | env[key] = '<hidden>' |
|
460 | env[key] = '<hidden>' | |
461 |
|
461 | |||
462 | return env |
|
462 | return env | |
463 |
|
463 | |||
464 | @line_magic |
|
464 | @line_magic | |
465 | def set_env(self, parameter_s): |
|
465 | def set_env(self, parameter_s): | |
466 | """Set environment variables. Assumptions are that either "val" is a |
|
466 | """Set environment variables. Assumptions are that either "val" is a | |
467 | name in the user namespace, or val is something that evaluates to a |
|
467 | name in the user namespace, or val is something that evaluates to a | |
468 | string. |
|
468 | string. | |
469 |
|
469 | |||
470 | Usage:\\ |
|
470 | Usage:\\ | |
471 | %set_env var val: set value for var |
|
471 | :``%set_env var val``: set value for var | |
472 | %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 |
|
473 | :``%set_env var=$val``: set value for var, using python expansion if possible | |
474 | """ |
|
474 | """ | |
475 | split = '=' if '=' in parameter_s else ' ' |
|
475 | split = '=' if '=' in parameter_s else ' ' | |
476 | bits = parameter_s.split(split, 1) |
|
476 | bits = parameter_s.split(split, 1) | |
477 | if not parameter_s.strip() or len(bits)<2: |
|
477 | if not parameter_s.strip() or len(bits)<2: | |
478 | raise UsageError("usage is 'set_env var=val'") |
|
478 | raise UsageError("usage is 'set_env var=val'") | |
479 | var = bits[0].strip() |
|
479 | var = bits[0].strip() | |
480 | val = bits[1].strip() |
|
480 | val = bits[1].strip() | |
481 | if re.match(r'.*\s.*', var): |
|
481 | if re.match(r'.*\s.*', var): | |
482 | # an environment variable with whitespace is almost certainly |
|
482 | # an environment variable with whitespace is almost certainly | |
483 | # not what the user intended. what's more likely is the wrong |
|
483 | # not what the user intended. what's more likely is the wrong | |
484 | # split was chosen, ie for "set_env cmd_args A=B", we chose |
|
484 | # split was chosen, ie for "set_env cmd_args A=B", we chose | |
485 | # '=' for the split and should have chosen ' '. to get around |
|
485 | # '=' for the split and should have chosen ' '. to get around | |
486 | # this, users should just assign directly to os.environ or use |
|
486 | # this, users should just assign directly to os.environ or use | |
487 | # standard magic {var} expansion. |
|
487 | # standard magic {var} expansion. | |
488 | err = "refusing to set env var with whitespace: '{0}'" |
|
488 | err = "refusing to set env var with whitespace: '{0}'" | |
489 | err = err.format(val) |
|
489 | err = err.format(val) | |
490 | raise UsageError(err) |
|
490 | raise UsageError(err) | |
491 | os.environ[var] = val |
|
491 | os.environ[var] = val | |
492 | print('env: {0}={1}'.format(var,val)) |
|
492 | print('env: {0}={1}'.format(var,val)) | |
493 |
|
493 | |||
494 | @line_magic |
|
494 | @line_magic | |
495 | def pushd(self, parameter_s=''): |
|
495 | def pushd(self, parameter_s=''): | |
496 | """Place the current dir on stack and change directory. |
|
496 | """Place the current dir on stack and change directory. | |
497 |
|
497 | |||
498 | Usage:\\ |
|
498 | Usage:\\ | |
499 | %pushd ['dirname'] |
|
499 | %pushd ['dirname'] | |
500 | """ |
|
500 | """ | |
501 |
|
501 | |||
502 | dir_s = self.shell.dir_stack |
|
502 | dir_s = self.shell.dir_stack | |
503 | tgt = os.path.expanduser(parameter_s) |
|
503 | tgt = os.path.expanduser(parameter_s) | |
504 | cwd = os.getcwd().replace(self.shell.home_dir,'~') |
|
504 | cwd = os.getcwd().replace(self.shell.home_dir,'~') | |
505 | if tgt: |
|
505 | if tgt: | |
506 | self.cd(parameter_s) |
|
506 | self.cd(parameter_s) | |
507 | dir_s.insert(0,cwd) |
|
507 | dir_s.insert(0,cwd) | |
508 | return self.shell.run_line_magic('dirs', '') |
|
508 | return self.shell.run_line_magic('dirs', '') | |
509 |
|
509 | |||
510 | @line_magic |
|
510 | @line_magic | |
511 | def popd(self, parameter_s=''): |
|
511 | def popd(self, parameter_s=''): | |
512 | """Change to directory popped off the top of the stack. |
|
512 | """Change to directory popped off the top of the stack. | |
513 | """ |
|
513 | """ | |
514 | if not self.shell.dir_stack: |
|
514 | if not self.shell.dir_stack: | |
515 | raise UsageError("%popd on empty stack") |
|
515 | raise UsageError("%popd on empty stack") | |
516 | top = self.shell.dir_stack.pop(0) |
|
516 | top = self.shell.dir_stack.pop(0) | |
517 | self.cd(top) |
|
517 | self.cd(top) | |
518 | print("popd ->",top) |
|
518 | print("popd ->",top) | |
519 |
|
519 | |||
520 | @line_magic |
|
520 | @line_magic | |
521 | def dirs(self, parameter_s=''): |
|
521 | def dirs(self, parameter_s=''): | |
522 | """Return the current directory stack.""" |
|
522 | """Return the current directory stack.""" | |
523 |
|
523 | |||
524 | return self.shell.dir_stack |
|
524 | return self.shell.dir_stack | |
525 |
|
525 | |||
526 | @line_magic |
|
526 | @line_magic | |
527 | def dhist(self, parameter_s=''): |
|
527 | def dhist(self, parameter_s=''): | |
528 | """Print your history of visited directories. |
|
528 | """Print your history of visited directories. | |
529 |
|
529 | |||
530 | %dhist -> print full history\\ |
|
530 | %dhist -> print full history\\ | |
531 | %dhist n -> print last n entries only\\ |
|
531 | %dhist n -> print last n entries only\\ | |
532 | %dhist n1 n2 -> print entries between n1 and n2 (n2 not included)\\ |
|
532 | %dhist n1 n2 -> print entries between n1 and n2 (n2 not included)\\ | |
533 |
|
533 | |||
534 | This history is automatically maintained by the %cd command, and |
|
534 | This history is automatically maintained by the %cd command, and | |
535 | always available as the global list variable _dh. You can use %cd -<n> |
|
535 | always available as the global list variable _dh. You can use %cd -<n> | |
536 | to go to directory number <n>. |
|
536 | to go to directory number <n>. | |
537 |
|
537 | |||
538 | Note that most of time, you should view directory history by entering |
|
538 | Note that most of time, you should view directory history by entering | |
539 | cd -<TAB>. |
|
539 | cd -<TAB>. | |
540 |
|
540 | |||
541 | """ |
|
541 | """ | |
542 |
|
542 | |||
543 | dh = self.shell.user_ns['_dh'] |
|
543 | dh = self.shell.user_ns['_dh'] | |
544 | if parameter_s: |
|
544 | if parameter_s: | |
545 | try: |
|
545 | try: | |
546 | args = map(int,parameter_s.split()) |
|
546 | args = map(int,parameter_s.split()) | |
547 | except: |
|
547 | except: | |
548 | self.arg_err(self.dhist) |
|
548 | self.arg_err(self.dhist) | |
549 | return |
|
549 | return | |
550 | if len(args) == 1: |
|
550 | if len(args) == 1: | |
551 | ini,fin = max(len(dh)-(args[0]),0),len(dh) |
|
551 | ini,fin = max(len(dh)-(args[0]),0),len(dh) | |
552 | elif len(args) == 2: |
|
552 | elif len(args) == 2: | |
553 | ini,fin = args |
|
553 | ini,fin = args | |
554 | fin = min(fin, len(dh)) |
|
554 | fin = min(fin, len(dh)) | |
555 | else: |
|
555 | else: | |
556 | self.arg_err(self.dhist) |
|
556 | self.arg_err(self.dhist) | |
557 | return |
|
557 | return | |
558 | else: |
|
558 | else: | |
559 | ini,fin = 0,len(dh) |
|
559 | ini,fin = 0,len(dh) | |
560 | print('Directory history (kept in _dh)') |
|
560 | print('Directory history (kept in _dh)') | |
561 | for i in range(ini, fin): |
|
561 | for i in range(ini, fin): | |
562 | print("%d: %s" % (i, dh[i])) |
|
562 | print("%d: %s" % (i, dh[i])) | |
563 |
|
563 | |||
564 | @skip_doctest |
|
564 | @skip_doctest | |
565 | @line_magic |
|
565 | @line_magic | |
566 | def sc(self, parameter_s=''): |
|
566 | def sc(self, parameter_s=''): | |
567 | """Shell capture - run shell command and capture output (DEPRECATED use !). |
|
567 | """Shell capture - run shell command and capture output (DEPRECATED use !). | |
568 |
|
568 | |||
569 | DEPRECATED. Suboptimal, retained for backwards compatibility. |
|
569 | DEPRECATED. Suboptimal, retained for backwards compatibility. | |
570 |
|
570 | |||
571 | You should use the form 'var = !command' instead. Example: |
|
571 | You should use the form 'var = !command' instead. Example: | |
572 |
|
572 | |||
573 | "%sc -l myfiles = ls ~" should now be written as |
|
573 | "%sc -l myfiles = ls ~" should now be written as | |
574 |
|
574 | |||
575 | "myfiles = !ls ~" |
|
575 | "myfiles = !ls ~" | |
576 |
|
576 | |||
577 | myfiles.s, myfiles.l and myfiles.n still apply as documented |
|
577 | myfiles.s, myfiles.l and myfiles.n still apply as documented | |
578 | below. |
|
578 | below. | |
579 |
|
579 | |||
580 | -- |
|
580 | -- | |
581 | %sc [options] varname=command |
|
581 | %sc [options] varname=command | |
582 |
|
582 | |||
583 | IPython will run the given command using commands.getoutput(), and |
|
583 | IPython will run the given command using commands.getoutput(), and | |
584 | will then update the user's interactive namespace with a variable |
|
584 | will then update the user's interactive namespace with a variable | |
585 | called varname, containing the value of the call. Your command can |
|
585 | called varname, containing the value of the call. Your command can | |
586 | contain shell wildcards, pipes, etc. |
|
586 | contain shell wildcards, pipes, etc. | |
587 |
|
587 | |||
588 | The '=' sign in the syntax is mandatory, and the variable name you |
|
588 | The '=' sign in the syntax is mandatory, and the variable name you | |
589 | supply must follow Python's standard conventions for valid names. |
|
589 | supply must follow Python's standard conventions for valid names. | |
590 |
|
590 | |||
591 | (A special format without variable name exists for internal use) |
|
591 | (A special format without variable name exists for internal use) | |
592 |
|
592 | |||
593 | Options: |
|
593 | Options: | |
594 |
|
594 | |||
595 | -l: list output. Split the output on newlines into a list before |
|
595 | -l: list output. Split the output on newlines into a list before | |
596 | assigning it to the given variable. By default the output is stored |
|
596 | assigning it to the given variable. By default the output is stored | |
597 | as a single string. |
|
597 | as a single string. | |
598 |
|
598 | |||
599 | -v: verbose. Print the contents of the variable. |
|
599 | -v: verbose. Print the contents of the variable. | |
600 |
|
600 | |||
601 | In most cases you should not need to split as a list, because the |
|
601 | In most cases you should not need to split as a list, because the | |
602 | returned value is a special type of string which can automatically |
|
602 | returned value is a special type of string which can automatically | |
603 | provide its contents either as a list (split on newlines) or as a |
|
603 | provide its contents either as a list (split on newlines) or as a | |
604 | space-separated string. These are convenient, respectively, either |
|
604 | space-separated string. These are convenient, respectively, either | |
605 | for sequential processing or to be passed to a shell command. |
|
605 | for sequential processing or to be passed to a shell command. | |
606 |
|
606 | |||
607 | For example:: |
|
607 | For example:: | |
608 |
|
608 | |||
609 | # Capture into variable a |
|
609 | # Capture into variable a | |
610 | In [1]: sc a=ls *py |
|
610 | In [1]: sc a=ls *py | |
611 |
|
611 | |||
612 | # a is a string with embedded newlines |
|
612 | # a is a string with embedded newlines | |
613 | In [2]: a |
|
613 | In [2]: a | |
614 | Out[2]: 'setup.py\\nwin32_manual_post_install.py' |
|
614 | Out[2]: 'setup.py\\nwin32_manual_post_install.py' | |
615 |
|
615 | |||
616 | # which can be seen as a list: |
|
616 | # which can be seen as a list: | |
617 | In [3]: a.l |
|
617 | In [3]: a.l | |
618 | Out[3]: ['setup.py', 'win32_manual_post_install.py'] |
|
618 | Out[3]: ['setup.py', 'win32_manual_post_install.py'] | |
619 |
|
619 | |||
620 | # or as a whitespace-separated string: |
|
620 | # or as a whitespace-separated string: | |
621 | In [4]: a.s |
|
621 | In [4]: a.s | |
622 | Out[4]: 'setup.py win32_manual_post_install.py' |
|
622 | Out[4]: 'setup.py win32_manual_post_install.py' | |
623 |
|
623 | |||
624 | # a.s is useful to pass as a single command line: |
|
624 | # a.s is useful to pass as a single command line: | |
625 | In [5]: !wc -l $a.s |
|
625 | In [5]: !wc -l $a.s | |
626 | 146 setup.py |
|
626 | 146 setup.py | |
627 | 130 win32_manual_post_install.py |
|
627 | 130 win32_manual_post_install.py | |
628 | 276 total |
|
628 | 276 total | |
629 |
|
629 | |||
630 | # while the list form is useful to loop over: |
|
630 | # while the list form is useful to loop over: | |
631 | In [6]: for f in a.l: |
|
631 | In [6]: for f in a.l: | |
632 | ...: !wc -l $f |
|
632 | ...: !wc -l $f | |
633 | ...: |
|
633 | ...: | |
634 | 146 setup.py |
|
634 | 146 setup.py | |
635 | 130 win32_manual_post_install.py |
|
635 | 130 win32_manual_post_install.py | |
636 |
|
636 | |||
637 | Similarly, the lists returned by the -l option are also special, in |
|
637 | Similarly, the lists returned by the -l option are also special, in | |
638 | the sense that you can equally invoke the .s attribute on them to |
|
638 | the sense that you can equally invoke the .s attribute on them to | |
639 | automatically get a whitespace-separated string from their contents:: |
|
639 | automatically get a whitespace-separated string from their contents:: | |
640 |
|
640 | |||
641 | In [7]: sc -l b=ls *py |
|
641 | In [7]: sc -l b=ls *py | |
642 |
|
642 | |||
643 | In [8]: b |
|
643 | In [8]: b | |
644 | Out[8]: ['setup.py', 'win32_manual_post_install.py'] |
|
644 | Out[8]: ['setup.py', 'win32_manual_post_install.py'] | |
645 |
|
645 | |||
646 | In [9]: b.s |
|
646 | In [9]: b.s | |
647 | Out[9]: 'setup.py win32_manual_post_install.py' |
|
647 | Out[9]: 'setup.py win32_manual_post_install.py' | |
648 |
|
648 | |||
649 | In summary, both the lists and strings used for output capture have |
|
649 | In summary, both the lists and strings used for output capture have | |
650 | the following special attributes:: |
|
650 | the following special attributes:: | |
651 |
|
651 | |||
652 | .l (or .list) : value as list. |
|
652 | .l (or .list) : value as list. | |
653 | .n (or .nlstr): value as newline-separated string. |
|
653 | .n (or .nlstr): value as newline-separated string. | |
654 | .s (or .spstr): value as space-separated string. |
|
654 | .s (or .spstr): value as space-separated string. | |
655 | """ |
|
655 | """ | |
656 |
|
656 | |||
657 | opts,args = self.parse_options(parameter_s, 'lv') |
|
657 | opts,args = self.parse_options(parameter_s, 'lv') | |
658 | # Try to get a variable name and command to run |
|
658 | # Try to get a variable name and command to run | |
659 | try: |
|
659 | try: | |
660 | # the variable name must be obtained from the parse_options |
|
660 | # the variable name must be obtained from the parse_options | |
661 | # output, which uses shlex.split to strip options out. |
|
661 | # output, which uses shlex.split to strip options out. | |
662 | var,_ = args.split('=', 1) |
|
662 | var,_ = args.split('=', 1) | |
663 | var = var.strip() |
|
663 | var = var.strip() | |
664 | # But the command has to be extracted from the original input |
|
664 | # But the command has to be extracted from the original input | |
665 | # parameter_s, not on what parse_options returns, to avoid the |
|
665 | # parameter_s, not on what parse_options returns, to avoid the | |
666 | # quote stripping which shlex.split performs on it. |
|
666 | # quote stripping which shlex.split performs on it. | |
667 | _,cmd = parameter_s.split('=', 1) |
|
667 | _,cmd = parameter_s.split('=', 1) | |
668 | except ValueError: |
|
668 | except ValueError: | |
669 | var,cmd = '','' |
|
669 | var,cmd = '','' | |
670 | # If all looks ok, proceed |
|
670 | # If all looks ok, proceed | |
671 | split = 'l' in opts |
|
671 | split = 'l' in opts | |
672 | out = self.shell.getoutput(cmd, split=split) |
|
672 | out = self.shell.getoutput(cmd, split=split) | |
673 | if 'v' in opts: |
|
673 | if 'v' in opts: | |
674 | print('%s ==\n%s' % (var, pformat(out))) |
|
674 | print('%s ==\n%s' % (var, pformat(out))) | |
675 | if var: |
|
675 | if var: | |
676 | self.shell.user_ns.update({var:out}) |
|
676 | self.shell.user_ns.update({var:out}) | |
677 | else: |
|
677 | else: | |
678 | return out |
|
678 | return out | |
679 |
|
679 | |||
680 | @line_cell_magic |
|
680 | @line_cell_magic | |
681 | def sx(self, line='', cell=None): |
|
681 | def sx(self, line='', cell=None): | |
682 | """Shell execute - run shell command and capture output (!! is short-hand). |
|
682 | """Shell execute - run shell command and capture output (!! is short-hand). | |
683 |
|
683 | |||
684 | %sx command |
|
684 | %sx command | |
685 |
|
685 | |||
686 | IPython will run the given command using commands.getoutput(), and |
|
686 | IPython will run the given command using commands.getoutput(), and | |
687 | return the result formatted as a list (split on '\\n'). Since the |
|
687 | return the result formatted as a list (split on '\\n'). Since the | |
688 | output is _returned_, it will be stored in ipython's regular output |
|
688 | output is _returned_, it will be stored in ipython's regular output | |
689 | cache Out[N] and in the '_N' automatic variables. |
|
689 | cache Out[N] and in the '_N' automatic variables. | |
690 |
|
690 | |||
691 | Notes: |
|
691 | Notes: | |
692 |
|
692 | |||
693 | 1) If an input line begins with '!!', then %sx is automatically |
|
693 | 1) If an input line begins with '!!', then %sx is automatically | |
694 | invoked. That is, while:: |
|
694 | invoked. That is, while:: | |
695 |
|
695 | |||
696 | !ls |
|
696 | !ls | |
697 |
|
697 | |||
698 | causes ipython to simply issue system('ls'), typing:: |
|
698 | causes ipython to simply issue system('ls'), typing:: | |
699 |
|
699 | |||
700 | !!ls |
|
700 | !!ls | |
701 |
|
701 | |||
702 | is a shorthand equivalent to:: |
|
702 | is a shorthand equivalent to:: | |
703 |
|
703 | |||
704 | %sx ls |
|
704 | %sx ls | |
705 |
|
705 | |||
706 | 2) %sx differs from %sc in that %sx automatically splits into a list, |
|
706 | 2) %sx differs from %sc in that %sx automatically splits into a list, | |
707 | like '%sc -l'. The reason for this is to make it as easy as possible |
|
707 | like '%sc -l'. The reason for this is to make it as easy as possible | |
708 | to process line-oriented shell output via further python commands. |
|
708 | to process line-oriented shell output via further python commands. | |
709 | %sc is meant to provide much finer control, but requires more |
|
709 | %sc is meant to provide much finer control, but requires more | |
710 | typing. |
|
710 | typing. | |
711 |
|
711 | |||
712 | 3) Just like %sc -l, this is a list with special attributes: |
|
712 | 3) Just like %sc -l, this is a list with special attributes: | |
713 | :: |
|
713 | :: | |
714 |
|
714 | |||
715 | .l (or .list) : value as list. |
|
715 | .l (or .list) : value as list. | |
716 | .n (or .nlstr): value as newline-separated string. |
|
716 | .n (or .nlstr): value as newline-separated string. | |
717 | .s (or .spstr): value as whitespace-separated string. |
|
717 | .s (or .spstr): value as whitespace-separated string. | |
718 |
|
718 | |||
719 | This is very useful when trying to use such lists as arguments to |
|
719 | This is very useful when trying to use such lists as arguments to | |
720 | system commands.""" |
|
720 | system commands.""" | |
721 |
|
721 | |||
722 | if cell is None: |
|
722 | if cell is None: | |
723 | # line magic |
|
723 | # line magic | |
724 | return self.shell.getoutput(line) |
|
724 | return self.shell.getoutput(line) | |
725 | else: |
|
725 | else: | |
726 | opts,args = self.parse_options(line, '', 'out=') |
|
726 | opts,args = self.parse_options(line, '', 'out=') | |
727 | output = self.shell.getoutput(cell) |
|
727 | output = self.shell.getoutput(cell) | |
728 | out_name = opts.get('out', opts.get('o')) |
|
728 | out_name = opts.get('out', opts.get('o')) | |
729 | if out_name: |
|
729 | if out_name: | |
730 | self.shell.user_ns[out_name] = output |
|
730 | self.shell.user_ns[out_name] = output | |
731 | else: |
|
731 | else: | |
732 | return output |
|
732 | return output | |
733 |
|
733 | |||
734 | system = line_cell_magic('system')(sx) |
|
734 | system = line_cell_magic('system')(sx) | |
735 | bang = cell_magic('!')(sx) |
|
735 | bang = cell_magic('!')(sx) | |
736 |
|
736 | |||
737 | @line_magic |
|
737 | @line_magic | |
738 | def bookmark(self, parameter_s=''): |
|
738 | def bookmark(self, parameter_s=''): | |
739 | """Manage IPython's bookmark system. |
|
739 | """Manage IPython's bookmark system. | |
740 |
|
740 | |||
741 | %bookmark <name> - set bookmark to current dir |
|
741 | %bookmark <name> - set bookmark to current dir | |
742 | %bookmark <name> <dir> - set bookmark to <dir> |
|
742 | %bookmark <name> <dir> - set bookmark to <dir> | |
743 | %bookmark -l - list all bookmarks |
|
743 | %bookmark -l - list all bookmarks | |
744 | %bookmark -d <name> - remove bookmark |
|
744 | %bookmark -d <name> - remove bookmark | |
745 | %bookmark -r - remove all bookmarks |
|
745 | %bookmark -r - remove all bookmarks | |
746 |
|
746 | |||
747 | You can later on access a bookmarked folder with:: |
|
747 | You can later on access a bookmarked folder with:: | |
748 |
|
748 | |||
749 | %cd -b <name> |
|
749 | %cd -b <name> | |
750 |
|
750 | |||
751 | or simply '%cd <name>' if there is no directory called <name> AND |
|
751 | or simply '%cd <name>' if there is no directory called <name> AND | |
752 | there is such a bookmark defined. |
|
752 | there is such a bookmark defined. | |
753 |
|
753 | |||
754 | Your bookmarks persist through IPython sessions, but they are |
|
754 | Your bookmarks persist through IPython sessions, but they are | |
755 | associated with each profile.""" |
|
755 | associated with each profile.""" | |
756 |
|
756 | |||
757 | opts,args = self.parse_options(parameter_s,'drl',mode='list') |
|
757 | opts,args = self.parse_options(parameter_s,'drl',mode='list') | |
758 | if len(args) > 2: |
|
758 | if len(args) > 2: | |
759 | raise UsageError("%bookmark: too many arguments") |
|
759 | raise UsageError("%bookmark: too many arguments") | |
760 |
|
760 | |||
761 | bkms = self.shell.db.get('bookmarks',{}) |
|
761 | bkms = self.shell.db.get('bookmarks',{}) | |
762 |
|
762 | |||
763 | if 'd' in opts: |
|
763 | if 'd' in opts: | |
764 | try: |
|
764 | try: | |
765 | todel = args[0] |
|
765 | todel = args[0] | |
766 | except IndexError as e: |
|
766 | except IndexError as e: | |
767 | raise UsageError( |
|
767 | raise UsageError( | |
768 | "%bookmark -d: must provide a bookmark to delete") from e |
|
768 | "%bookmark -d: must provide a bookmark to delete") from e | |
769 | else: |
|
769 | else: | |
770 | try: |
|
770 | try: | |
771 | del bkms[todel] |
|
771 | del bkms[todel] | |
772 | except KeyError as e: |
|
772 | except KeyError as e: | |
773 | raise UsageError( |
|
773 | raise UsageError( | |
774 | "%%bookmark -d: Can't delete bookmark '%s'" % todel) from e |
|
774 | "%%bookmark -d: Can't delete bookmark '%s'" % todel) from e | |
775 |
|
775 | |||
776 | elif 'r' in opts: |
|
776 | elif 'r' in opts: | |
777 | bkms = {} |
|
777 | bkms = {} | |
778 | elif 'l' in opts: |
|
778 | elif 'l' in opts: | |
779 | bks = sorted(bkms) |
|
779 | bks = sorted(bkms) | |
780 | if bks: |
|
780 | if bks: | |
781 | size = max(map(len, bks)) |
|
781 | size = max(map(len, bks)) | |
782 | else: |
|
782 | else: | |
783 | size = 0 |
|
783 | size = 0 | |
784 | fmt = '%-'+str(size)+'s -> %s' |
|
784 | fmt = '%-'+str(size)+'s -> %s' | |
785 | print('Current bookmarks:') |
|
785 | print('Current bookmarks:') | |
786 | for bk in bks: |
|
786 | for bk in bks: | |
787 | print(fmt % (bk, bkms[bk])) |
|
787 | print(fmt % (bk, bkms[bk])) | |
788 | else: |
|
788 | else: | |
789 | if not args: |
|
789 | if not args: | |
790 | raise UsageError("%bookmark: You must specify the bookmark name") |
|
790 | raise UsageError("%bookmark: You must specify the bookmark name") | |
791 | elif len(args)==1: |
|
791 | elif len(args)==1: | |
792 | bkms[args[0]] = os.getcwd() |
|
792 | bkms[args[0]] = os.getcwd() | |
793 | elif len(args)==2: |
|
793 | elif len(args)==2: | |
794 | bkms[args[0]] = args[1] |
|
794 | bkms[args[0]] = args[1] | |
795 | self.shell.db['bookmarks'] = bkms |
|
795 | self.shell.db['bookmarks'] = bkms | |
796 |
|
796 | |||
797 | @line_magic |
|
797 | @line_magic | |
798 | def pycat(self, parameter_s=''): |
|
798 | def pycat(self, parameter_s=''): | |
799 | """Show a syntax-highlighted file through a pager. |
|
799 | """Show a syntax-highlighted file through a pager. | |
800 |
|
800 | |||
801 | This magic is similar to the cat utility, but it will assume the file |
|
801 | This magic is similar to the cat utility, but it will assume the file | |
802 | to be Python source and will show it with syntax highlighting. |
|
802 | to be Python source and will show it with syntax highlighting. | |
803 |
|
803 | |||
804 | This magic command can either take a local filename, an url, |
|
804 | This magic command can either take a local filename, an url, | |
805 | an history range (see %history) or a macro as argument. |
|
805 | an history range (see %history) or a macro as argument. | |
806 |
|
806 | |||
807 | If no parameter is given, prints out history of current session up to |
|
807 | If no parameter is given, prints out history of current session up to | |
808 | this point. :: |
|
808 | this point. :: | |
809 |
|
809 | |||
810 | %pycat myscript.py |
|
810 | %pycat myscript.py | |
811 | %pycat 7-27 |
|
811 | %pycat 7-27 | |
812 | %pycat myMacro |
|
812 | %pycat myMacro | |
813 | %pycat http://www.example.com/myscript.py |
|
813 | %pycat http://www.example.com/myscript.py | |
814 | """ |
|
814 | """ | |
815 | try: |
|
815 | try: | |
816 | cont = self.shell.find_user_code(parameter_s, skip_encoding_cookie=False) |
|
816 | cont = self.shell.find_user_code(parameter_s, skip_encoding_cookie=False) | |
817 | except (ValueError, IOError): |
|
817 | except (ValueError, IOError): | |
818 | print("Error: no such file, variable, URL, history range or macro") |
|
818 | print("Error: no such file, variable, URL, history range or macro") | |
819 | return |
|
819 | return | |
820 |
|
820 | |||
821 | page.page(self.shell.pycolorize(source_to_unicode(cont))) |
|
821 | page.page(self.shell.pycolorize(source_to_unicode(cont))) | |
822 |
|
822 | |||
823 | @magic_arguments.magic_arguments() |
|
823 | @magic_arguments.magic_arguments() | |
824 | @magic_arguments.argument( |
|
824 | @magic_arguments.argument( | |
825 | '-a', '--append', action='store_true', default=False, |
|
825 | '-a', '--append', action='store_true', default=False, | |
826 | help='Append contents of the cell to an existing file. ' |
|
826 | help='Append contents of the cell to an existing file. ' | |
827 | 'The file will be created if it does not exist.' |
|
827 | 'The file will be created if it does not exist.' | |
828 | ) |
|
828 | ) | |
829 | @magic_arguments.argument( |
|
829 | @magic_arguments.argument( | |
830 | 'filename', type=str, |
|
830 | 'filename', type=str, | |
831 | help='file to write' |
|
831 | help='file to write' | |
832 | ) |
|
832 | ) | |
833 | @cell_magic |
|
833 | @cell_magic | |
834 | def writefile(self, line, cell): |
|
834 | def writefile(self, line, cell): | |
835 | """Write the contents of the cell to a file. |
|
835 | """Write the contents of the cell to a file. | |
836 |
|
836 | |||
837 | The file will be overwritten unless the -a (--append) flag is specified. |
|
837 | The file will be overwritten unless the -a (--append) flag is specified. | |
838 | """ |
|
838 | """ | |
839 | args = magic_arguments.parse_argstring(self.writefile, line) |
|
839 | args = magic_arguments.parse_argstring(self.writefile, line) | |
840 | if re.match(r'^(\'.*\')|(".*")$', args.filename): |
|
840 | if re.match(r'^(\'.*\')|(".*")$', args.filename): | |
841 | filename = os.path.expanduser(args.filename[1:-1]) |
|
841 | filename = os.path.expanduser(args.filename[1:-1]) | |
842 | else: |
|
842 | else: | |
843 | filename = os.path.expanduser(args.filename) |
|
843 | filename = os.path.expanduser(args.filename) | |
844 |
|
844 | |||
845 | if os.path.exists(filename): |
|
845 | if os.path.exists(filename): | |
846 | if args.append: |
|
846 | if args.append: | |
847 | print("Appending to %s" % filename) |
|
847 | print("Appending to %s" % filename) | |
848 | else: |
|
848 | else: | |
849 | print("Overwriting %s" % filename) |
|
849 | print("Overwriting %s" % filename) | |
850 | else: |
|
850 | else: | |
851 | print("Writing %s" % filename) |
|
851 | print("Writing %s" % filename) | |
852 |
|
852 | |||
853 | mode = 'a' if args.append else 'w' |
|
853 | mode = 'a' if args.append else 'w' | |
854 | with io.open(filename, mode, encoding='utf-8') as f: |
|
854 | with io.open(filename, mode, encoding='utf-8') as f: | |
855 | f.write(cell) |
|
855 | f.write(cell) |
@@ -1,169 +1,169 b'' | |||||
1 | """Implementation of magic functions for matplotlib/pylab support. |
|
1 | """Implementation of magic functions for matplotlib/pylab support. | |
2 | """ |
|
2 | """ | |
3 | #----------------------------------------------------------------------------- |
|
3 | #----------------------------------------------------------------------------- | |
4 | # Copyright (c) 2012 The IPython Development Team. |
|
4 | # Copyright (c) 2012 The IPython Development Team. | |
5 | # |
|
5 | # | |
6 | # Distributed under the terms of the Modified BSD License. |
|
6 | # Distributed under the terms of the Modified BSD License. | |
7 | # |
|
7 | # | |
8 | # The full license is in the file COPYING.txt, distributed with this software. |
|
8 | # The full license is in the file COPYING.txt, distributed with this software. | |
9 | #----------------------------------------------------------------------------- |
|
9 | #----------------------------------------------------------------------------- | |
10 |
|
10 | |||
11 | #----------------------------------------------------------------------------- |
|
11 | #----------------------------------------------------------------------------- | |
12 | # Imports |
|
12 | # Imports | |
13 | #----------------------------------------------------------------------------- |
|
13 | #----------------------------------------------------------------------------- | |
14 |
|
14 | |||
15 | # Our own packages |
|
15 | # Our own packages | |
16 | from traitlets.config.application import Application |
|
16 | from traitlets.config.application import Application | |
17 | from IPython.core import magic_arguments |
|
17 | from IPython.core import magic_arguments | |
18 | from IPython.core.magic import Magics, magics_class, line_magic |
|
18 | from IPython.core.magic import Magics, magics_class, line_magic | |
19 | from IPython.testing.skipdoctest import skip_doctest |
|
19 | from IPython.testing.skipdoctest import skip_doctest | |
20 | from warnings import warn |
|
20 | from warnings import warn | |
21 | from IPython.core.pylabtools import backends |
|
21 | from IPython.core.pylabtools import backends | |
22 |
|
22 | |||
23 | #----------------------------------------------------------------------------- |
|
23 | #----------------------------------------------------------------------------- | |
24 | # Magic implementation classes |
|
24 | # Magic implementation classes | |
25 | #----------------------------------------------------------------------------- |
|
25 | #----------------------------------------------------------------------------- | |
26 |
|
26 | |||
27 | magic_gui_arg = magic_arguments.argument( |
|
27 | magic_gui_arg = magic_arguments.argument( | |
28 | 'gui', nargs='?', |
|
28 | 'gui', nargs='?', | |
29 | help="""Name of the matplotlib backend to use %s. |
|
29 | help="""Name of the matplotlib backend to use %s. | |
30 | If given, the corresponding matplotlib backend is used, |
|
30 | If given, the corresponding matplotlib backend is used, | |
31 | otherwise it will be matplotlib's default |
|
31 | otherwise it will be matplotlib's default | |
32 | (which you can set in your matplotlib config file). |
|
32 | (which you can set in your matplotlib config file). | |
33 | """ % str(tuple(sorted(backends.keys()))) |
|
33 | """ % str(tuple(sorted(backends.keys()))) | |
34 | ) |
|
34 | ) | |
35 |
|
35 | |||
36 |
|
36 | |||
37 | @magics_class |
|
37 | @magics_class | |
38 | class PylabMagics(Magics): |
|
38 | class PylabMagics(Magics): | |
39 | """Magics related to matplotlib's pylab support""" |
|
39 | """Magics related to matplotlib's pylab support""" | |
40 |
|
40 | |||
41 | @skip_doctest |
|
41 | @skip_doctest | |
42 | @line_magic |
|
42 | @line_magic | |
43 | @magic_arguments.magic_arguments() |
|
43 | @magic_arguments.magic_arguments() | |
44 | @magic_arguments.argument('-l', '--list', action='store_true', |
|
44 | @magic_arguments.argument('-l', '--list', action='store_true', | |
45 | help='Show available matplotlib backends') |
|
45 | help='Show available matplotlib backends') | |
46 | @magic_gui_arg |
|
46 | @magic_gui_arg | |
47 | def matplotlib(self, line=''): |
|
47 | def matplotlib(self, line=''): | |
48 | """Set up matplotlib to work interactively. |
|
48 | """Set up matplotlib to work interactively. | |
49 |
|
49 | |||
50 | This function lets you activate matplotlib interactive support |
|
50 | This function lets you activate matplotlib interactive support | |
51 | at any point during an IPython session. It does not import anything |
|
51 | at any point during an IPython session. It does not import anything | |
52 | into the interactive namespace. |
|
52 | into the interactive namespace. | |
53 |
|
53 | |||
54 | If you are using the inline matplotlib backend in the IPython Notebook |
|
54 | If you are using the inline matplotlib backend in the IPython Notebook | |
55 | you can set which figure formats are enabled using the following:: |
|
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 | In [2]: set_matplotlib_formats('pdf', 'svg') |
|
59 | In [2]: set_matplotlib_formats('pdf', 'svg') | |
60 |
|
60 | |||
61 | The default for inline figures sets `bbox_inches` to 'tight'. This can |
|
61 | The default for inline figures sets `bbox_inches` to 'tight'. This can | |
62 | cause discrepancies between the displayed image and the identical |
|
62 | cause discrepancies between the displayed image and the identical | |
63 | image created using `savefig`. This behavior can be disabled using the |
|
63 | image created using `savefig`. This behavior can be disabled using the | |
64 | `%config` magic:: |
|
64 | `%config` magic:: | |
65 |
|
65 | |||
66 | In [3]: %config InlineBackend.print_figure_kwargs = {'bbox_inches':None} |
|
66 | In [3]: %config InlineBackend.print_figure_kwargs = {'bbox_inches':None} | |
67 |
|
67 | |||
68 | In addition, see the docstring of |
|
68 | In addition, see the docstrings of | |
69 |
` |
|
69 | `matplotlib_inline.backend_inline.set_matplotlib_formats` and | |
70 |
` |
|
70 | `matplotlib_inline.backend_inline.set_matplotlib_close` for more information on | |
71 | changing additional behaviors of the inline backend. |
|
71 | changing additional behaviors of the inline backend. | |
72 |
|
72 | |||
73 | Examples |
|
73 | Examples | |
74 | -------- |
|
74 | -------- | |
75 | To enable the inline backend for usage with the IPython Notebook:: |
|
75 | To enable the inline backend for usage with the IPython Notebook:: | |
76 |
|
76 | |||
77 | In [1]: %matplotlib inline |
|
77 | In [1]: %matplotlib inline | |
78 |
|
78 | |||
79 | In this case, where the matplotlib default is TkAgg:: |
|
79 | In this case, where the matplotlib default is TkAgg:: | |
80 |
|
80 | |||
81 | In [2]: %matplotlib |
|
81 | In [2]: %matplotlib | |
82 | Using matplotlib backend: TkAgg |
|
82 | Using matplotlib backend: TkAgg | |
83 |
|
83 | |||
84 | But you can explicitly request a different GUI backend:: |
|
84 | But you can explicitly request a different GUI backend:: | |
85 |
|
85 | |||
86 | In [3]: %matplotlib qt |
|
86 | In [3]: %matplotlib qt | |
87 |
|
87 | |||
88 | You can list the available backends using the -l/--list option:: |
|
88 | You can list the available backends using the -l/--list option:: | |
89 |
|
89 | |||
90 | In [4]: %matplotlib --list |
|
90 | In [4]: %matplotlib --list | |
91 | Available matplotlib backends: ['osx', 'qt4', 'qt5', 'gtk3', 'gtk4', 'notebook', 'wx', 'qt', 'nbagg', |
|
91 | Available matplotlib backends: ['osx', 'qt4', 'qt5', 'gtk3', 'gtk4', 'notebook', 'wx', 'qt', 'nbagg', | |
92 | 'gtk', 'tk', 'inline'] |
|
92 | 'gtk', 'tk', 'inline'] | |
93 | """ |
|
93 | """ | |
94 | args = magic_arguments.parse_argstring(self.matplotlib, line) |
|
94 | args = magic_arguments.parse_argstring(self.matplotlib, line) | |
95 | if args.list: |
|
95 | if args.list: | |
96 | backends_list = list(backends.keys()) |
|
96 | backends_list = list(backends.keys()) | |
97 | print("Available matplotlib backends: %s" % backends_list) |
|
97 | print("Available matplotlib backends: %s" % backends_list) | |
98 | else: |
|
98 | else: | |
99 | gui, backend = self.shell.enable_matplotlib(args.gui.lower() if isinstance(args.gui, str) else args.gui) |
|
99 | gui, backend = self.shell.enable_matplotlib(args.gui.lower() if isinstance(args.gui, str) else args.gui) | |
100 | self._show_matplotlib_backend(args.gui, backend) |
|
100 | self._show_matplotlib_backend(args.gui, backend) | |
101 |
|
101 | |||
102 | @skip_doctest |
|
102 | @skip_doctest | |
103 | @line_magic |
|
103 | @line_magic | |
104 | @magic_arguments.magic_arguments() |
|
104 | @magic_arguments.magic_arguments() | |
105 | @magic_arguments.argument( |
|
105 | @magic_arguments.argument( | |
106 | '--no-import-all', action='store_true', default=None, |
|
106 | '--no-import-all', action='store_true', default=None, | |
107 | help="""Prevent IPython from performing ``import *`` into the interactive namespace. |
|
107 | help="""Prevent IPython from performing ``import *`` into the interactive namespace. | |
108 |
|
108 | |||
109 | You can govern the default behavior of this flag with the |
|
109 | You can govern the default behavior of this flag with the | |
110 | InteractiveShellApp.pylab_import_all configurable. |
|
110 | InteractiveShellApp.pylab_import_all configurable. | |
111 | """ |
|
111 | """ | |
112 | ) |
|
112 | ) | |
113 | @magic_gui_arg |
|
113 | @magic_gui_arg | |
114 | def pylab(self, line=''): |
|
114 | def pylab(self, line=''): | |
115 | """Load numpy and matplotlib to work interactively. |
|
115 | """Load numpy and matplotlib to work interactively. | |
116 |
|
116 | |||
117 | This function lets you activate pylab (matplotlib, numpy and |
|
117 | This function lets you activate pylab (matplotlib, numpy and | |
118 | interactive support) at any point during an IPython session. |
|
118 | interactive support) at any point during an IPython session. | |
119 |
|
119 | |||
120 | %pylab makes the following imports:: |
|
120 | %pylab makes the following imports:: | |
121 |
|
121 | |||
122 | import numpy |
|
122 | import numpy | |
123 | import matplotlib |
|
123 | import matplotlib | |
124 | from matplotlib import pylab, mlab, pyplot |
|
124 | from matplotlib import pylab, mlab, pyplot | |
125 | np = numpy |
|
125 | np = numpy | |
126 | plt = pyplot |
|
126 | plt = pyplot | |
127 |
|
127 | |||
128 | from IPython.display import display |
|
128 | from IPython.display import display | |
129 | from IPython.core.pylabtools import figsize, getfigs |
|
129 | from IPython.core.pylabtools import figsize, getfigs | |
130 |
|
130 | |||
131 | from pylab import * |
|
131 | from pylab import * | |
132 | from numpy import * |
|
132 | from numpy import * | |
133 |
|
133 | |||
134 | If you pass `--no-import-all`, the last two `*` imports will be excluded. |
|
134 | If you pass `--no-import-all`, the last two `*` imports will be excluded. | |
135 |
|
135 | |||
136 | See the %matplotlib magic for more details about activating matplotlib |
|
136 | See the %matplotlib magic for more details about activating matplotlib | |
137 | without affecting the interactive namespace. |
|
137 | without affecting the interactive namespace. | |
138 | """ |
|
138 | """ | |
139 | args = magic_arguments.parse_argstring(self.pylab, line) |
|
139 | args = magic_arguments.parse_argstring(self.pylab, line) | |
140 | if args.no_import_all is None: |
|
140 | if args.no_import_all is None: | |
141 | # get default from Application |
|
141 | # get default from Application | |
142 | if Application.initialized(): |
|
142 | if Application.initialized(): | |
143 | app = Application.instance() |
|
143 | app = Application.instance() | |
144 | try: |
|
144 | try: | |
145 | import_all = app.pylab_import_all |
|
145 | import_all = app.pylab_import_all | |
146 | except AttributeError: |
|
146 | except AttributeError: | |
147 | import_all = True |
|
147 | import_all = True | |
148 | else: |
|
148 | else: | |
149 | # nothing specified, no app - default True |
|
149 | # nothing specified, no app - default True | |
150 | import_all = True |
|
150 | import_all = True | |
151 | else: |
|
151 | else: | |
152 | # invert no-import flag |
|
152 | # invert no-import flag | |
153 | import_all = not args.no_import_all |
|
153 | import_all = not args.no_import_all | |
154 |
|
154 | |||
155 | gui, backend, clobbered = self.shell.enable_pylab(args.gui, import_all=import_all) |
|
155 | gui, backend, clobbered = self.shell.enable_pylab(args.gui, import_all=import_all) | |
156 | self._show_matplotlib_backend(args.gui, backend) |
|
156 | self._show_matplotlib_backend(args.gui, backend) | |
157 | print( |
|
157 | print( | |
158 | "%pylab is deprecated, use %matplotlib inline and import the required libraries." |
|
158 | "%pylab is deprecated, use %matplotlib inline and import the required libraries." | |
159 | ) |
|
159 | ) | |
160 | print("Populating the interactive namespace from numpy and matplotlib") |
|
160 | print("Populating the interactive namespace from numpy and matplotlib") | |
161 | if clobbered: |
|
161 | if clobbered: | |
162 | warn("pylab import has clobbered these variables: %s" % clobbered + |
|
162 | warn("pylab import has clobbered these variables: %s" % clobbered + | |
163 | "\n`%matplotlib` prevents importing * from pylab and numpy" |
|
163 | "\n`%matplotlib` prevents importing * from pylab and numpy" | |
164 | ) |
|
164 | ) | |
165 |
|
165 | |||
166 | def _show_matplotlib_backend(self, gui, backend): |
|
166 | def _show_matplotlib_backend(self, gui, backend): | |
167 | """show matplotlib message backend message""" |
|
167 | """show matplotlib message backend message""" | |
168 | if not gui or gui == 'auto': |
|
168 | if not gui or gui == 'auto': | |
169 | print("Using matplotlib backend: %s" % backend) |
|
169 | print("Using matplotlib backend: %s" % backend) |
@@ -1,362 +1,362 b'' | |||||
1 | """Magic functions for running cells in various scripts.""" |
|
1 | """Magic functions for running cells in various scripts.""" | |
2 |
|
2 | |||
3 | # Copyright (c) IPython Development Team. |
|
3 | # Copyright (c) IPython Development Team. | |
4 | # Distributed under the terms of the Modified BSD License. |
|
4 | # Distributed under the terms of the Modified BSD License. | |
5 |
|
5 | |||
6 | import asyncio |
|
6 | import asyncio | |
7 | import atexit |
|
7 | import atexit | |
8 | import errno |
|
8 | import errno | |
9 | import os |
|
9 | import os | |
10 | import signal |
|
10 | import signal | |
11 | import sys |
|
11 | import sys | |
12 | import time |
|
12 | import time | |
13 | from subprocess import CalledProcessError |
|
13 | from subprocess import CalledProcessError | |
14 | from threading import Thread |
|
14 | from threading import Thread | |
15 |
|
15 | |||
16 | from traitlets import Any, Dict, List, default |
|
16 | from traitlets import Any, Dict, List, default | |
17 |
|
17 | |||
18 | from IPython.core import magic_arguments |
|
18 | from IPython.core import magic_arguments | |
19 | from IPython.core.async_helpers import _AsyncIOProxy |
|
19 | from IPython.core.async_helpers import _AsyncIOProxy | |
20 | from IPython.core.magic import Magics, cell_magic, line_magic, magics_class |
|
20 | from IPython.core.magic import Magics, cell_magic, line_magic, magics_class | |
21 | from IPython.utils.process import arg_split |
|
21 | from IPython.utils.process import arg_split | |
22 |
|
22 | |||
23 | #----------------------------------------------------------------------------- |
|
23 | #----------------------------------------------------------------------------- | |
24 | # Magic implementation classes |
|
24 | # Magic implementation classes | |
25 | #----------------------------------------------------------------------------- |
|
25 | #----------------------------------------------------------------------------- | |
26 |
|
26 | |||
27 | def script_args(f): |
|
27 | def script_args(f): | |
28 | """single decorator for adding script args""" |
|
28 | """single decorator for adding script args""" | |
29 | args = [ |
|
29 | args = [ | |
30 | magic_arguments.argument( |
|
30 | magic_arguments.argument( | |
31 | '--out', type=str, |
|
31 | '--out', type=str, | |
32 | help="""The variable in which to store stdout from the script. |
|
32 | help="""The variable in which to store stdout from the script. | |
33 | If the script is backgrounded, this will be the stdout *pipe*, |
|
33 | If the script is backgrounded, this will be the stdout *pipe*, | |
34 | instead of the stderr text itself and will not be auto closed. |
|
34 | instead of the stderr text itself and will not be auto closed. | |
35 | """ |
|
35 | """ | |
36 | ), |
|
36 | ), | |
37 | magic_arguments.argument( |
|
37 | magic_arguments.argument( | |
38 | '--err', type=str, |
|
38 | '--err', type=str, | |
39 | help="""The variable in which to store stderr from the script. |
|
39 | help="""The variable in which to store stderr from the script. | |
40 | If the script is backgrounded, this will be the stderr *pipe*, |
|
40 | If the script is backgrounded, this will be the stderr *pipe*, | |
41 | instead of the stderr text itself and will not be autoclosed. |
|
41 | instead of the stderr text itself and will not be autoclosed. | |
42 | """ |
|
42 | """ | |
43 | ), |
|
43 | ), | |
44 | magic_arguments.argument( |
|
44 | magic_arguments.argument( | |
45 | '--bg', action="store_true", |
|
45 | '--bg', action="store_true", | |
46 | help="""Whether to run the script in the background. |
|
46 | help="""Whether to run the script in the background. | |
47 | If given, the only way to see the output of the command is |
|
47 | If given, the only way to see the output of the command is | |
48 | with --out/err. |
|
48 | with --out/err. | |
49 | """ |
|
49 | """ | |
50 | ), |
|
50 | ), | |
51 | magic_arguments.argument( |
|
51 | magic_arguments.argument( | |
52 | '--proc', type=str, |
|
52 | '--proc', type=str, | |
53 | help="""The variable in which to store Popen instance. |
|
53 | help="""The variable in which to store Popen instance. | |
54 | This is used only when --bg option is given. |
|
54 | This is used only when --bg option is given. | |
55 | """ |
|
55 | """ | |
56 | ), |
|
56 | ), | |
57 | magic_arguments.argument( |
|
57 | magic_arguments.argument( | |
58 | '--no-raise-error', action="store_false", dest='raise_error', |
|
58 | '--no-raise-error', action="store_false", dest='raise_error', | |
59 | help="""Whether you should raise an error message in addition to |
|
59 | help="""Whether you should raise an error message in addition to | |
60 | a stream on stderr if you get a nonzero exit code. |
|
60 | a stream on stderr if you get a nonzero exit code. | |
61 | """, |
|
61 | """, | |
62 | ), |
|
62 | ), | |
63 | ] |
|
63 | ] | |
64 | for arg in args: |
|
64 | for arg in args: | |
65 | f = arg(f) |
|
65 | f = arg(f) | |
66 | return f |
|
66 | return f | |
67 |
|
67 | |||
68 |
|
68 | |||
69 | @magics_class |
|
69 | @magics_class | |
70 | class ScriptMagics(Magics): |
|
70 | class ScriptMagics(Magics): | |
71 | """Magics for talking to scripts |
|
71 | """Magics for talking to scripts | |
72 |
|
72 | |||
73 | This defines a base `%%script` cell magic for running a cell |
|
73 | This defines a base `%%script` cell magic for running a cell | |
74 | with a program in a subprocess, and registers a few top-level |
|
74 | with a program in a subprocess, and registers a few top-level | |
75 | magics that call %%script with common interpreters. |
|
75 | magics that call %%script with common interpreters. | |
76 | """ |
|
76 | """ | |
77 |
|
77 | |||
78 | event_loop = Any( |
|
78 | event_loop = Any( | |
79 | help=""" |
|
79 | help=""" | |
80 | The event loop on which to run subprocesses |
|
80 | The event loop on which to run subprocesses | |
81 |
|
81 | |||
82 | Not the main event loop, |
|
82 | Not the main event loop, | |
83 | because we want to be able to make blocking calls |
|
83 | because we want to be able to make blocking calls | |
84 | and have certain requirements we don't want to impose on the main loop. |
|
84 | and have certain requirements we don't want to impose on the main loop. | |
85 | """ |
|
85 | """ | |
86 | ) |
|
86 | ) | |
87 |
|
87 | |||
88 | script_magics = List( |
|
88 | script_magics = List( | |
89 | help="""Extra script cell magics to define |
|
89 | help="""Extra script cell magics to define | |
90 |
|
90 | |||
91 | This generates simple wrappers of `%%script foo` as `%%foo`. |
|
91 | This generates simple wrappers of `%%script foo` as `%%foo`. | |
92 |
|
92 | |||
93 | If you want to add script magics that aren't on your path, |
|
93 | If you want to add script magics that aren't on your path, | |
94 | specify them in script_paths |
|
94 | specify them in script_paths | |
95 | """, |
|
95 | """, | |
96 | ).tag(config=True) |
|
96 | ).tag(config=True) | |
97 | @default('script_magics') |
|
97 | @default('script_magics') | |
98 | def _script_magics_default(self): |
|
98 | def _script_magics_default(self): | |
99 | """default to a common list of programs""" |
|
99 | """default to a common list of programs""" | |
100 |
|
100 | |||
101 | defaults = [ |
|
101 | defaults = [ | |
102 | 'sh', |
|
102 | 'sh', | |
103 | 'bash', |
|
103 | 'bash', | |
104 | 'perl', |
|
104 | 'perl', | |
105 | 'ruby', |
|
105 | 'ruby', | |
106 | 'python', |
|
106 | 'python', | |
107 | 'python2', |
|
107 | 'python2', | |
108 | 'python3', |
|
108 | 'python3', | |
109 | 'pypy', |
|
109 | 'pypy', | |
110 | ] |
|
110 | ] | |
111 | if os.name == 'nt': |
|
111 | if os.name == 'nt': | |
112 | defaults.extend([ |
|
112 | defaults.extend([ | |
113 | 'cmd', |
|
113 | 'cmd', | |
114 | ]) |
|
114 | ]) | |
115 |
|
115 | |||
116 | return defaults |
|
116 | return defaults | |
117 |
|
117 | |||
118 | script_paths = Dict( |
|
118 | script_paths = Dict( | |
119 | help="""Dict mapping short 'ruby' names to full paths, such as '/opt/secret/bin/ruby' |
|
119 | help="""Dict mapping short 'ruby' names to full paths, such as '/opt/secret/bin/ruby' | |
120 |
|
120 | |||
121 | Only necessary for items in script_magics where the default path will not |
|
121 | Only necessary for items in script_magics where the default path will not | |
122 | find the right interpreter. |
|
122 | find the right interpreter. | |
123 | """ |
|
123 | """ | |
124 | ).tag(config=True) |
|
124 | ).tag(config=True) | |
125 |
|
125 | |||
126 | def __init__(self, shell=None): |
|
126 | def __init__(self, shell=None): | |
127 | super(ScriptMagics, self).__init__(shell=shell) |
|
127 | super(ScriptMagics, self).__init__(shell=shell) | |
128 | self._generate_script_magics() |
|
128 | self._generate_script_magics() | |
129 | self.bg_processes = [] |
|
129 | self.bg_processes = [] | |
130 | atexit.register(self.kill_bg_processes) |
|
130 | atexit.register(self.kill_bg_processes) | |
131 |
|
131 | |||
132 | def __del__(self): |
|
132 | def __del__(self): | |
133 | self.kill_bg_processes() |
|
133 | self.kill_bg_processes() | |
134 |
|
134 | |||
135 | def _generate_script_magics(self): |
|
135 | def _generate_script_magics(self): | |
136 | cell_magics = self.magics['cell'] |
|
136 | cell_magics = self.magics['cell'] | |
137 | for name in self.script_magics: |
|
137 | for name in self.script_magics: | |
138 | cell_magics[name] = self._make_script_magic(name) |
|
138 | cell_magics[name] = self._make_script_magic(name) | |
139 |
|
139 | |||
140 | def _make_script_magic(self, name): |
|
140 | def _make_script_magic(self, name): | |
141 | """make a named magic, that calls %%script with a particular program""" |
|
141 | """make a named magic, that calls %%script with a particular program""" | |
142 | # expand to explicit path if necessary: |
|
142 | # expand to explicit path if necessary: | |
143 | script = self.script_paths.get(name, name) |
|
143 | script = self.script_paths.get(name, name) | |
144 |
|
144 | |||
145 | @magic_arguments.magic_arguments() |
|
145 | @magic_arguments.magic_arguments() | |
146 | @script_args |
|
146 | @script_args | |
147 | def named_script_magic(line, cell): |
|
147 | def named_script_magic(line, cell): | |
148 | # if line, add it as cl-flags |
|
148 | # if line, add it as cl-flags | |
149 | if line: |
|
149 | if line: | |
150 | line = "%s %s" % (script, line) |
|
150 | line = "%s %s" % (script, line) | |
151 | else: |
|
151 | else: | |
152 | line = script |
|
152 | line = script | |
153 | return self.shebang(line, cell) |
|
153 | return self.shebang(line, cell) | |
154 |
|
154 | |||
155 | # write a basic docstring: |
|
155 | # write a basic docstring: | |
156 | named_script_magic.__doc__ = \ |
|
156 | named_script_magic.__doc__ = \ | |
157 | """%%{name} script magic |
|
157 | """%%{name} script magic | |
158 |
|
158 | |||
159 | Run cells with {script} in a subprocess. |
|
159 | Run cells with {script} in a subprocess. | |
160 |
|
160 | |||
161 | This is a shortcut for `%%script {script}` |
|
161 | This is a shortcut for `%%script {script}` | |
162 | """.format(**locals()) |
|
162 | """.format(**locals()) | |
163 |
|
163 | |||
164 | return named_script_magic |
|
164 | return named_script_magic | |
165 |
|
165 | |||
166 | @magic_arguments.magic_arguments() |
|
166 | @magic_arguments.magic_arguments() | |
167 | @script_args |
|
167 | @script_args | |
168 | @cell_magic("script") |
|
168 | @cell_magic("script") | |
169 | def shebang(self, line, cell): |
|
169 | def shebang(self, line, cell): | |
170 | """Run a cell via a shell command |
|
170 | """Run a cell via a shell command | |
171 |
|
171 | |||
172 | The `%%script` line is like the #! line of script, |
|
172 | The `%%script` line is like the #! line of script, | |
173 | specifying a program (bash, perl, ruby, etc.) with which to run. |
|
173 | specifying a program (bash, perl, ruby, etc.) with which to run. | |
174 |
|
174 | |||
175 | The rest of the cell is run by that program. |
|
175 | The rest of the cell is run by that program. | |
176 |
|
176 | |||
177 | Examples |
|
177 | Examples | |
178 | -------- |
|
178 | -------- | |
179 | :: |
|
179 | :: | |
180 |
|
180 | |||
181 | In [1]: %%script bash |
|
181 | In [1]: %%script bash | |
182 | ...: for i in 1 2 3; do |
|
182 | ...: for i in 1 2 3; do | |
183 | ...: echo $i |
|
183 | ...: echo $i | |
184 | ...: done |
|
184 | ...: done | |
185 | 1 |
|
185 | 1 | |
186 | 2 |
|
186 | 2 | |
187 | 3 |
|
187 | 3 | |
188 | """ |
|
188 | """ | |
189 |
|
189 | |||
190 | # Create the event loop in which to run script magics |
|
190 | # Create the event loop in which to run script magics | |
191 | # this operates on a background thread |
|
191 | # this operates on a background thread | |
192 | if self.event_loop is None: |
|
192 | if self.event_loop is None: | |
193 | if sys.platform == "win32": |
|
193 | if sys.platform == "win32": | |
194 | # don't override the current policy, |
|
194 | # don't override the current policy, | |
195 | # just create an event loop |
|
195 | # just create an event loop | |
196 | event_loop = asyncio.WindowsProactorEventLoopPolicy().new_event_loop() |
|
196 | event_loop = asyncio.WindowsProactorEventLoopPolicy().new_event_loop() | |
197 | else: |
|
197 | else: | |
198 | event_loop = asyncio.new_event_loop() |
|
198 | event_loop = asyncio.new_event_loop() | |
199 | self.event_loop = event_loop |
|
199 | self.event_loop = event_loop | |
200 |
|
200 | |||
201 | # start the loop in a background thread |
|
201 | # start the loop in a background thread | |
202 | asyncio_thread = Thread(target=event_loop.run_forever, daemon=True) |
|
202 | asyncio_thread = Thread(target=event_loop.run_forever, daemon=True) | |
203 | asyncio_thread.start() |
|
203 | asyncio_thread.start() | |
204 | else: |
|
204 | else: | |
205 | event_loop = self.event_loop |
|
205 | event_loop = self.event_loop | |
206 |
|
206 | |||
207 | def in_thread(coro): |
|
207 | def in_thread(coro): | |
208 | """Call a coroutine on the asyncio thread""" |
|
208 | """Call a coroutine on the asyncio thread""" | |
209 | return asyncio.run_coroutine_threadsafe(coro, event_loop).result() |
|
209 | return asyncio.run_coroutine_threadsafe(coro, event_loop).result() | |
210 |
|
210 | |||
211 | async def _handle_stream(stream, stream_arg, file_object): |
|
211 | async def _handle_stream(stream, stream_arg, file_object): | |
212 | while True: |
|
212 | while True: | |
213 | line = (await stream.readline()).decode("utf8") |
|
213 | line = (await stream.readline()).decode("utf8", errors="replace") | |
214 | if not line: |
|
214 | if not line: | |
215 | break |
|
215 | break | |
216 | if stream_arg: |
|
216 | if stream_arg: | |
217 | self.shell.user_ns[stream_arg] = line |
|
217 | self.shell.user_ns[stream_arg] = line | |
218 | else: |
|
218 | else: | |
219 | file_object.write(line) |
|
219 | file_object.write(line) | |
220 | file_object.flush() |
|
220 | file_object.flush() | |
221 |
|
221 | |||
222 | async def _stream_communicate(process, cell): |
|
222 | async def _stream_communicate(process, cell): | |
223 | process.stdin.write(cell) |
|
223 | process.stdin.write(cell) | |
224 | process.stdin.close() |
|
224 | process.stdin.close() | |
225 | stdout_task = asyncio.create_task( |
|
225 | stdout_task = asyncio.create_task( | |
226 | _handle_stream(process.stdout, args.out, sys.stdout) |
|
226 | _handle_stream(process.stdout, args.out, sys.stdout) | |
227 | ) |
|
227 | ) | |
228 | stderr_task = asyncio.create_task( |
|
228 | stderr_task = asyncio.create_task( | |
229 | _handle_stream(process.stderr, args.err, sys.stderr) |
|
229 | _handle_stream(process.stderr, args.err, sys.stderr) | |
230 | ) |
|
230 | ) | |
231 | await asyncio.wait([stdout_task, stderr_task]) |
|
231 | await asyncio.wait([stdout_task, stderr_task]) | |
232 | await process.wait() |
|
232 | await process.wait() | |
233 |
|
233 | |||
234 | argv = arg_split(line, posix=not sys.platform.startswith("win")) |
|
234 | argv = arg_split(line, posix=not sys.platform.startswith("win")) | |
235 | args, cmd = self.shebang.parser.parse_known_args(argv) |
|
235 | args, cmd = self.shebang.parser.parse_known_args(argv) | |
236 |
|
236 | |||
237 | try: |
|
237 | try: | |
238 | p = in_thread( |
|
238 | p = in_thread( | |
239 | asyncio.create_subprocess_exec( |
|
239 | asyncio.create_subprocess_exec( | |
240 | *cmd, |
|
240 | *cmd, | |
241 | stdout=asyncio.subprocess.PIPE, |
|
241 | stdout=asyncio.subprocess.PIPE, | |
242 | stderr=asyncio.subprocess.PIPE, |
|
242 | stderr=asyncio.subprocess.PIPE, | |
243 | stdin=asyncio.subprocess.PIPE, |
|
243 | stdin=asyncio.subprocess.PIPE, | |
244 | ) |
|
244 | ) | |
245 | ) |
|
245 | ) | |
246 | except OSError as e: |
|
246 | except OSError as e: | |
247 | if e.errno == errno.ENOENT: |
|
247 | if e.errno == errno.ENOENT: | |
248 | print("Couldn't find program: %r" % cmd[0]) |
|
248 | print("Couldn't find program: %r" % cmd[0]) | |
249 | return |
|
249 | return | |
250 | else: |
|
250 | else: | |
251 | raise |
|
251 | raise | |
252 |
|
252 | |||
253 | if not cell.endswith('\n'): |
|
253 | if not cell.endswith('\n'): | |
254 | cell += '\n' |
|
254 | cell += '\n' | |
255 | cell = cell.encode('utf8', 'replace') |
|
255 | cell = cell.encode('utf8', 'replace') | |
256 | if args.bg: |
|
256 | if args.bg: | |
257 | self.bg_processes.append(p) |
|
257 | self.bg_processes.append(p) | |
258 | self._gc_bg_processes() |
|
258 | self._gc_bg_processes() | |
259 | to_close = [] |
|
259 | to_close = [] | |
260 | if args.out: |
|
260 | if args.out: | |
261 | self.shell.user_ns[args.out] = _AsyncIOProxy(p.stdout, event_loop) |
|
261 | self.shell.user_ns[args.out] = _AsyncIOProxy(p.stdout, event_loop) | |
262 | else: |
|
262 | else: | |
263 | to_close.append(p.stdout) |
|
263 | to_close.append(p.stdout) | |
264 | if args.err: |
|
264 | if args.err: | |
265 | self.shell.user_ns[args.err] = _AsyncIOProxy(p.stderr, event_loop) |
|
265 | self.shell.user_ns[args.err] = _AsyncIOProxy(p.stderr, event_loop) | |
266 | else: |
|
266 | else: | |
267 | to_close.append(p.stderr) |
|
267 | to_close.append(p.stderr) | |
268 | event_loop.call_soon_threadsafe( |
|
268 | event_loop.call_soon_threadsafe( | |
269 | lambda: asyncio.Task(self._run_script(p, cell, to_close)) |
|
269 | lambda: asyncio.Task(self._run_script(p, cell, to_close)) | |
270 | ) |
|
270 | ) | |
271 | if args.proc: |
|
271 | if args.proc: | |
272 | proc_proxy = _AsyncIOProxy(p, event_loop) |
|
272 | proc_proxy = _AsyncIOProxy(p, event_loop) | |
273 | proc_proxy.stdout = _AsyncIOProxy(p.stdout, event_loop) |
|
273 | proc_proxy.stdout = _AsyncIOProxy(p.stdout, event_loop) | |
274 | proc_proxy.stderr = _AsyncIOProxy(p.stderr, event_loop) |
|
274 | proc_proxy.stderr = _AsyncIOProxy(p.stderr, event_loop) | |
275 | self.shell.user_ns[args.proc] = proc_proxy |
|
275 | self.shell.user_ns[args.proc] = proc_proxy | |
276 | return |
|
276 | return | |
277 |
|
277 | |||
278 | try: |
|
278 | try: | |
279 | in_thread(_stream_communicate(p, cell)) |
|
279 | in_thread(_stream_communicate(p, cell)) | |
280 | except KeyboardInterrupt: |
|
280 | except KeyboardInterrupt: | |
281 | try: |
|
281 | try: | |
282 | p.send_signal(signal.SIGINT) |
|
282 | p.send_signal(signal.SIGINT) | |
283 | in_thread(asyncio.wait_for(p.wait(), timeout=0.1)) |
|
283 | in_thread(asyncio.wait_for(p.wait(), timeout=0.1)) | |
284 | if p.returncode is not None: |
|
284 | if p.returncode is not None: | |
285 | print("Process is interrupted.") |
|
285 | print("Process is interrupted.") | |
286 | return |
|
286 | return | |
287 | p.terminate() |
|
287 | p.terminate() | |
288 | in_thread(asyncio.wait_for(p.wait(), timeout=0.1)) |
|
288 | in_thread(asyncio.wait_for(p.wait(), timeout=0.1)) | |
289 | if p.returncode is not None: |
|
289 | if p.returncode is not None: | |
290 | print("Process is terminated.") |
|
290 | print("Process is terminated.") | |
291 | return |
|
291 | return | |
292 | p.kill() |
|
292 | p.kill() | |
293 | print("Process is killed.") |
|
293 | print("Process is killed.") | |
294 | except OSError: |
|
294 | except OSError: | |
295 | pass |
|
295 | pass | |
296 | except Exception as e: |
|
296 | except Exception as e: | |
297 | print("Error while terminating subprocess (pid=%i): %s" % (p.pid, e)) |
|
297 | print("Error while terminating subprocess (pid=%i): %s" % (p.pid, e)) | |
298 | return |
|
298 | return | |
299 |
|
299 | |||
300 | if args.raise_error and p.returncode != 0: |
|
300 | if args.raise_error and p.returncode != 0: | |
301 | # If we get here and p.returncode is still None, we must have |
|
301 | # If we get here and p.returncode is still None, we must have | |
302 | # killed it but not yet seen its return code. We don't wait for it, |
|
302 | # killed it but not yet seen its return code. We don't wait for it, | |
303 | # in case it's stuck in uninterruptible sleep. -9 = SIGKILL |
|
303 | # in case it's stuck in uninterruptible sleep. -9 = SIGKILL | |
304 | rc = p.returncode or -9 |
|
304 | rc = p.returncode or -9 | |
305 | raise CalledProcessError(rc, cell) |
|
305 | raise CalledProcessError(rc, cell) | |
306 |
|
306 | |||
307 | shebang.__skip_doctest__ = os.name != "posix" |
|
307 | shebang.__skip_doctest__ = os.name != "posix" | |
308 |
|
308 | |||
309 | async def _run_script(self, p, cell, to_close): |
|
309 | async def _run_script(self, p, cell, to_close): | |
310 | """callback for running the script in the background""" |
|
310 | """callback for running the script in the background""" | |
311 |
|
311 | |||
312 | p.stdin.write(cell) |
|
312 | p.stdin.write(cell) | |
313 | await p.stdin.drain() |
|
313 | await p.stdin.drain() | |
314 | p.stdin.close() |
|
314 | p.stdin.close() | |
315 | await p.stdin.wait_closed() |
|
315 | await p.stdin.wait_closed() | |
316 | await p.wait() |
|
316 | await p.wait() | |
317 | # asyncio read pipes have no close |
|
317 | # asyncio read pipes have no close | |
318 | # but we should drain the data anyway |
|
318 | # but we should drain the data anyway | |
319 | for s in to_close: |
|
319 | for s in to_close: | |
320 | await s.read() |
|
320 | await s.read() | |
321 | self._gc_bg_processes() |
|
321 | self._gc_bg_processes() | |
322 |
|
322 | |||
323 | @line_magic("killbgscripts") |
|
323 | @line_magic("killbgscripts") | |
324 | def killbgscripts(self, _nouse_=''): |
|
324 | def killbgscripts(self, _nouse_=''): | |
325 | """Kill all BG processes started by %%script and its family.""" |
|
325 | """Kill all BG processes started by %%script and its family.""" | |
326 | self.kill_bg_processes() |
|
326 | self.kill_bg_processes() | |
327 | print("All background processes were killed.") |
|
327 | print("All background processes were killed.") | |
328 |
|
328 | |||
329 | def kill_bg_processes(self): |
|
329 | def kill_bg_processes(self): | |
330 | """Kill all BG processes which are still running.""" |
|
330 | """Kill all BG processes which are still running.""" | |
331 | if not self.bg_processes: |
|
331 | if not self.bg_processes: | |
332 | return |
|
332 | return | |
333 | for p in self.bg_processes: |
|
333 | for p in self.bg_processes: | |
334 | if p.returncode is None: |
|
334 | if p.returncode is None: | |
335 | try: |
|
335 | try: | |
336 | p.send_signal(signal.SIGINT) |
|
336 | p.send_signal(signal.SIGINT) | |
337 | except: |
|
337 | except: | |
338 | pass |
|
338 | pass | |
339 | time.sleep(0.1) |
|
339 | time.sleep(0.1) | |
340 | self._gc_bg_processes() |
|
340 | self._gc_bg_processes() | |
341 | if not self.bg_processes: |
|
341 | if not self.bg_processes: | |
342 | return |
|
342 | return | |
343 | for p in self.bg_processes: |
|
343 | for p in self.bg_processes: | |
344 | if p.returncode is None: |
|
344 | if p.returncode is None: | |
345 | try: |
|
345 | try: | |
346 | p.terminate() |
|
346 | p.terminate() | |
347 | except: |
|
347 | except: | |
348 | pass |
|
348 | pass | |
349 | time.sleep(0.1) |
|
349 | time.sleep(0.1) | |
350 | self._gc_bg_processes() |
|
350 | self._gc_bg_processes() | |
351 | if not self.bg_processes: |
|
351 | if not self.bg_processes: | |
352 | return |
|
352 | return | |
353 | for p in self.bg_processes: |
|
353 | for p in self.bg_processes: | |
354 | if p.returncode is None: |
|
354 | if p.returncode is None: | |
355 | try: |
|
355 | try: | |
356 | p.kill() |
|
356 | p.kill() | |
357 | except: |
|
357 | except: | |
358 | pass |
|
358 | pass | |
359 | self._gc_bg_processes() |
|
359 | self._gc_bg_processes() | |
360 |
|
360 | |||
361 | def _gc_bg_processes(self): |
|
361 | def _gc_bg_processes(self): | |
362 | self.bg_processes = [p for p in self.bg_processes if p.returncode is None] |
|
362 | self.bg_processes = [p for p in self.bg_processes if p.returncode is None] |
@@ -1,54 +1,54 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | """Release data for the IPython project.""" |
|
2 | """Release data for the IPython project.""" | |
3 |
|
3 | |||
4 | #----------------------------------------------------------------------------- |
|
4 | #----------------------------------------------------------------------------- | |
5 | # Copyright (c) 2008, IPython Development Team. |
|
5 | # Copyright (c) 2008, IPython Development Team. | |
6 | # Copyright (c) 2001, Fernando Perez <fernando.perez@colorado.edu> |
|
6 | # Copyright (c) 2001, Fernando Perez <fernando.perez@colorado.edu> | |
7 | # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de> |
|
7 | # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de> | |
8 | # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu> |
|
8 | # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu> | |
9 | # |
|
9 | # | |
10 | # Distributed under the terms of the Modified BSD License. |
|
10 | # Distributed under the terms of the Modified BSD License. | |
11 | # |
|
11 | # | |
12 | # The full license is in the file COPYING.txt, distributed with this software. |
|
12 | # The full license is in the file COPYING.txt, distributed with this software. | |
13 | #----------------------------------------------------------------------------- |
|
13 | #----------------------------------------------------------------------------- | |
14 |
|
14 | |||
15 | # IPython version information. An empty _version_extra corresponds to a full |
|
15 | # IPython version information. An empty _version_extra corresponds to a full | |
16 | # release. 'dev' as a _version_extra string means this is a development |
|
16 | # release. 'dev' as a _version_extra string means this is a development | |
17 | # version |
|
17 | # version | |
18 | _version_major = 8 |
|
18 | _version_major = 8 | |
19 |
_version_minor = |
|
19 | _version_minor = 10 | |
20 | _version_patch = 0 |
|
20 | _version_patch = 0 | |
21 | _version_extra = ".dev" |
|
21 | _version_extra = ".dev" | |
22 | # _version_extra = "rc1" |
|
22 | # _version_extra = "rc1" | |
23 | # _version_extra = "" # Uncomment this for full releases |
|
23 | # _version_extra = "" # Uncomment this for full releases | |
24 |
|
24 | |||
25 | # Construct full version string from these. |
|
25 | # Construct full version string from these. | |
26 | _ver = [_version_major, _version_minor, _version_patch] |
|
26 | _ver = [_version_major, _version_minor, _version_patch] | |
27 |
|
27 | |||
28 | __version__ = '.'.join(map(str, _ver)) |
|
28 | __version__ = '.'.join(map(str, _ver)) | |
29 | if _version_extra: |
|
29 | if _version_extra: | |
30 | __version__ = __version__ + _version_extra |
|
30 | __version__ = __version__ + _version_extra | |
31 |
|
31 | |||
32 | version = __version__ # backwards compatibility name |
|
32 | version = __version__ # backwards compatibility name | |
33 | version_info = (_version_major, _version_minor, _version_patch, _version_extra) |
|
33 | version_info = (_version_major, _version_minor, _version_patch, _version_extra) | |
34 |
|
34 | |||
35 | # Change this when incrementing the kernel protocol version |
|
35 | # Change this when incrementing the kernel protocol version | |
36 | kernel_protocol_version_info = (5, 0) |
|
36 | kernel_protocol_version_info = (5, 0) | |
37 | kernel_protocol_version = "%i.%i" % kernel_protocol_version_info |
|
37 | kernel_protocol_version = "%i.%i" % kernel_protocol_version_info | |
38 |
|
38 | |||
39 | license = "BSD-3-Clause" |
|
39 | license = "BSD-3-Clause" | |
40 |
|
40 | |||
41 | authors = {'Fernando' : ('Fernando Perez','fperez.net@gmail.com'), |
|
41 | authors = {'Fernando' : ('Fernando Perez','fperez.net@gmail.com'), | |
42 | 'Janko' : ('Janko Hauser','jhauser@zscout.de'), |
|
42 | 'Janko' : ('Janko Hauser','jhauser@zscout.de'), | |
43 | 'Nathan' : ('Nathaniel Gray','n8gray@caltech.edu'), |
|
43 | 'Nathan' : ('Nathaniel Gray','n8gray@caltech.edu'), | |
44 | 'Ville' : ('Ville Vainio','vivainio@gmail.com'), |
|
44 | 'Ville' : ('Ville Vainio','vivainio@gmail.com'), | |
45 | 'Brian' : ('Brian E Granger', 'ellisonbg@gmail.com'), |
|
45 | 'Brian' : ('Brian E Granger', 'ellisonbg@gmail.com'), | |
46 | 'Min' : ('Min Ragan-Kelley', 'benjaminrk@gmail.com'), |
|
46 | 'Min' : ('Min Ragan-Kelley', 'benjaminrk@gmail.com'), | |
47 | 'Thomas' : ('Thomas A. Kluyver', 'takowl@gmail.com'), |
|
47 | 'Thomas' : ('Thomas A. Kluyver', 'takowl@gmail.com'), | |
48 | 'Jorgen' : ('Jorgen Stenarson', 'jorgen.stenarson@bostream.nu'), |
|
48 | 'Jorgen' : ('Jorgen Stenarson', 'jorgen.stenarson@bostream.nu'), | |
49 | 'Matthias' : ('Matthias Bussonnier', 'bussonniermatthias@gmail.com'), |
|
49 | 'Matthias' : ('Matthias Bussonnier', 'bussonniermatthias@gmail.com'), | |
50 | } |
|
50 | } | |
51 |
|
51 | |||
52 | author = 'The IPython Development Team' |
|
52 | author = 'The IPython Development Team' | |
53 |
|
53 | |||
54 | author_email = 'ipython-dev@python.org' |
|
54 | author_email = 'ipython-dev@python.org' |
@@ -1,451 +1,451 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | """ |
|
2 | """ | |
3 | A mixin for :class:`~IPython.core.application.Application` classes that |
|
3 | A mixin for :class:`~IPython.core.application.Application` classes that | |
4 | launch InteractiveShell instances, load extensions, etc. |
|
4 | launch InteractiveShell instances, load extensions, etc. | |
5 | """ |
|
5 | """ | |
6 |
|
6 | |||
7 | # Copyright (c) IPython Development Team. |
|
7 | # Copyright (c) IPython Development Team. | |
8 | # Distributed under the terms of the Modified BSD License. |
|
8 | # Distributed under the terms of the Modified BSD License. | |
9 |
|
9 | |||
10 | import glob |
|
10 | import glob | |
11 | from itertools import chain |
|
11 | from itertools import chain | |
12 | import os |
|
12 | import os | |
13 | import sys |
|
13 | import sys | |
14 |
|
14 | |||
15 | from traitlets.config.application import boolean_flag |
|
15 | from traitlets.config.application import boolean_flag | |
16 | from traitlets.config.configurable import Configurable |
|
16 | from traitlets.config.configurable import Configurable | |
17 | from traitlets.config.loader import Config |
|
17 | from traitlets.config.loader import Config | |
18 | from IPython.core.application import SYSTEM_CONFIG_DIRS, ENV_CONFIG_DIRS |
|
18 | from IPython.core.application import SYSTEM_CONFIG_DIRS, ENV_CONFIG_DIRS | |
19 | from IPython.core import pylabtools |
|
19 | from IPython.core import pylabtools | |
20 | from IPython.utils.contexts import preserve_keys |
|
20 | from IPython.utils.contexts import preserve_keys | |
21 | from IPython.utils.path import filefind |
|
21 | from IPython.utils.path import filefind | |
22 | from traitlets import ( |
|
22 | from traitlets import ( | |
23 | Unicode, Instance, List, Bool, CaselessStrEnum, observe, |
|
23 | Unicode, Instance, List, Bool, CaselessStrEnum, observe, | |
24 | DottedObjectName, |
|
24 | DottedObjectName, | |
25 | ) |
|
25 | ) | |
26 | from IPython.terminal import pt_inputhooks |
|
26 | from IPython.terminal import pt_inputhooks | |
27 |
|
27 | |||
28 | #----------------------------------------------------------------------------- |
|
28 | #----------------------------------------------------------------------------- | |
29 | # Aliases and Flags |
|
29 | # Aliases and Flags | |
30 | #----------------------------------------------------------------------------- |
|
30 | #----------------------------------------------------------------------------- | |
31 |
|
31 | |||
32 | gui_keys = tuple(sorted(pt_inputhooks.backends) + sorted(pt_inputhooks.aliases)) |
|
32 | gui_keys = tuple(sorted(pt_inputhooks.backends) + sorted(pt_inputhooks.aliases)) | |
33 |
|
33 | |||
34 | backend_keys = sorted(pylabtools.backends.keys()) |
|
34 | backend_keys = sorted(pylabtools.backends.keys()) | |
35 | backend_keys.insert(0, 'auto') |
|
35 | backend_keys.insert(0, 'auto') | |
36 |
|
36 | |||
37 | shell_flags = {} |
|
37 | shell_flags = {} | |
38 |
|
38 | |||
39 | addflag = lambda *args: shell_flags.update(boolean_flag(*args)) |
|
39 | addflag = lambda *args: shell_flags.update(boolean_flag(*args)) | |
40 | addflag('autoindent', 'InteractiveShell.autoindent', |
|
40 | addflag('autoindent', 'InteractiveShell.autoindent', | |
41 | 'Turn on autoindenting.', 'Turn off autoindenting.' |
|
41 | 'Turn on autoindenting.', 'Turn off autoindenting.' | |
42 | ) |
|
42 | ) | |
43 | addflag('automagic', 'InteractiveShell.automagic', |
|
43 | addflag('automagic', 'InteractiveShell.automagic', | |
44 | """Turn on the auto calling of magic commands. Type %%magic at the |
|
44 | """Turn on the auto calling of magic commands. Type %%magic at the | |
45 | IPython prompt for more information.""", |
|
45 | IPython prompt for more information.""", | |
46 | 'Turn off the auto calling of magic commands.' |
|
46 | 'Turn off the auto calling of magic commands.' | |
47 | ) |
|
47 | ) | |
48 | addflag('pdb', 'InteractiveShell.pdb', |
|
48 | addflag('pdb', 'InteractiveShell.pdb', | |
49 | "Enable auto calling the pdb debugger after every exception.", |
|
49 | "Enable auto calling the pdb debugger after every exception.", | |
50 | "Disable auto calling the pdb debugger after every exception." |
|
50 | "Disable auto calling the pdb debugger after every exception." | |
51 | ) |
|
51 | ) | |
52 | addflag('pprint', 'PlainTextFormatter.pprint', |
|
52 | addflag('pprint', 'PlainTextFormatter.pprint', | |
53 | "Enable auto pretty printing of results.", |
|
53 | "Enable auto pretty printing of results.", | |
54 | "Disable auto pretty printing of results." |
|
54 | "Disable auto pretty printing of results." | |
55 | ) |
|
55 | ) | |
56 | addflag('color-info', 'InteractiveShell.color_info', |
|
56 | addflag('color-info', 'InteractiveShell.color_info', | |
57 | """IPython can display information about objects via a set of functions, |
|
57 | """IPython can display information about objects via a set of functions, | |
58 | and optionally can use colors for this, syntax highlighting |
|
58 | and optionally can use colors for this, syntax highlighting | |
59 | source code and various other elements. This is on by default, but can cause |
|
59 | source code and various other elements. This is on by default, but can cause | |
60 | problems with some pagers. If you see such problems, you can disable the |
|
60 | problems with some pagers. If you see such problems, you can disable the | |
61 | colours.""", |
|
61 | colours.""", | |
62 | "Disable using colors for info related things." |
|
62 | "Disable using colors for info related things." | |
63 | ) |
|
63 | ) | |
64 | addflag('ignore-cwd', 'InteractiveShellApp.ignore_cwd', |
|
64 | addflag('ignore-cwd', 'InteractiveShellApp.ignore_cwd', | |
65 | "Exclude the current working directory from sys.path", |
|
65 | "Exclude the current working directory from sys.path", | |
66 | "Include the current working directory in sys.path", |
|
66 | "Include the current working directory in sys.path", | |
67 | ) |
|
67 | ) | |
68 | nosep_config = Config() |
|
68 | nosep_config = Config() | |
69 | nosep_config.InteractiveShell.separate_in = '' |
|
69 | nosep_config.InteractiveShell.separate_in = '' | |
70 | nosep_config.InteractiveShell.separate_out = '' |
|
70 | nosep_config.InteractiveShell.separate_out = '' | |
71 | nosep_config.InteractiveShell.separate_out2 = '' |
|
71 | nosep_config.InteractiveShell.separate_out2 = '' | |
72 |
|
72 | |||
73 | shell_flags['nosep']=(nosep_config, "Eliminate all spacing between prompts.") |
|
73 | shell_flags['nosep']=(nosep_config, "Eliminate all spacing between prompts.") | |
74 | shell_flags['pylab'] = ( |
|
74 | shell_flags['pylab'] = ( | |
75 | {'InteractiveShellApp' : {'pylab' : 'auto'}}, |
|
75 | {'InteractiveShellApp' : {'pylab' : 'auto'}}, | |
76 | """Pre-load matplotlib and numpy for interactive use with |
|
76 | """Pre-load matplotlib and numpy for interactive use with | |
77 | the default matplotlib backend.""" |
|
77 | the default matplotlib backend.""" | |
78 | ) |
|
78 | ) | |
79 | shell_flags['matplotlib'] = ( |
|
79 | shell_flags['matplotlib'] = ( | |
80 | {'InteractiveShellApp' : {'matplotlib' : 'auto'}}, |
|
80 | {'InteractiveShellApp' : {'matplotlib' : 'auto'}}, | |
81 | """Configure matplotlib for interactive use with |
|
81 | """Configure matplotlib for interactive use with | |
82 | the default matplotlib backend.""" |
|
82 | the default matplotlib backend.""" | |
83 | ) |
|
83 | ) | |
84 |
|
84 | |||
85 | # it's possible we don't want short aliases for *all* of these: |
|
85 | # it's possible we don't want short aliases for *all* of these: | |
86 | shell_aliases = dict( |
|
86 | shell_aliases = dict( | |
87 | autocall='InteractiveShell.autocall', |
|
87 | autocall='InteractiveShell.autocall', | |
88 | colors='InteractiveShell.colors', |
|
88 | colors='InteractiveShell.colors', | |
89 | logfile='InteractiveShell.logfile', |
|
89 | logfile='InteractiveShell.logfile', | |
90 | logappend='InteractiveShell.logappend', |
|
90 | logappend='InteractiveShell.logappend', | |
91 | c='InteractiveShellApp.code_to_run', |
|
91 | c='InteractiveShellApp.code_to_run', | |
92 | m='InteractiveShellApp.module_to_run', |
|
92 | m='InteractiveShellApp.module_to_run', | |
93 | ext="InteractiveShellApp.extra_extensions", |
|
93 | ext="InteractiveShellApp.extra_extensions", | |
94 | gui='InteractiveShellApp.gui', |
|
94 | gui='InteractiveShellApp.gui', | |
95 | pylab='InteractiveShellApp.pylab', |
|
95 | pylab='InteractiveShellApp.pylab', | |
96 | matplotlib='InteractiveShellApp.matplotlib', |
|
96 | matplotlib='InteractiveShellApp.matplotlib', | |
97 | ) |
|
97 | ) | |
98 | shell_aliases['cache-size'] = 'InteractiveShell.cache_size' |
|
98 | shell_aliases['cache-size'] = 'InteractiveShell.cache_size' | |
99 |
|
99 | |||
100 | #----------------------------------------------------------------------------- |
|
100 | #----------------------------------------------------------------------------- | |
101 | # Main classes and functions |
|
101 | # Main classes and functions | |
102 | #----------------------------------------------------------------------------- |
|
102 | #----------------------------------------------------------------------------- | |
103 |
|
103 | |||
104 | class InteractiveShellApp(Configurable): |
|
104 | class InteractiveShellApp(Configurable): | |
105 | """A Mixin for applications that start InteractiveShell instances. |
|
105 | """A Mixin for applications that start InteractiveShell instances. | |
106 |
|
106 | |||
107 | Provides configurables for loading extensions and executing files |
|
107 | Provides configurables for loading extensions and executing files | |
108 | as part of configuring a Shell environment. |
|
108 | as part of configuring a Shell environment. | |
109 |
|
109 | |||
110 | The following methods should be called by the :meth:`initialize` method |
|
110 | The following methods should be called by the :meth:`initialize` method | |
111 | of the subclass: |
|
111 | of the subclass: | |
112 |
|
112 | |||
113 | - :meth:`init_path` |
|
113 | - :meth:`init_path` | |
114 | - :meth:`init_shell` (to be implemented by the subclass) |
|
114 | - :meth:`init_shell` (to be implemented by the subclass) | |
115 | - :meth:`init_gui_pylab` |
|
115 | - :meth:`init_gui_pylab` | |
116 | - :meth:`init_extensions` |
|
116 | - :meth:`init_extensions` | |
117 | - :meth:`init_code` |
|
117 | - :meth:`init_code` | |
118 | """ |
|
118 | """ | |
119 | extensions = List(Unicode(), |
|
119 | extensions = List(Unicode(), | |
120 | help="A list of dotted module names of IPython extensions to load." |
|
120 | help="A list of dotted module names of IPython extensions to load." | |
121 | ).tag(config=True) |
|
121 | ).tag(config=True) | |
122 |
|
122 | |||
123 | extra_extensions = List( |
|
123 | extra_extensions = List( | |
124 | DottedObjectName(), |
|
124 | DottedObjectName(), | |
125 | help=""" |
|
125 | help=""" | |
126 | Dotted module name(s) of one or more IPython extensions to load. |
|
126 | Dotted module name(s) of one or more IPython extensions to load. | |
127 |
|
127 | |||
128 | For specifying extra extensions to load on the command-line. |
|
128 | For specifying extra extensions to load on the command-line. | |
129 |
|
129 | |||
130 | .. versionadded:: 7.10 |
|
130 | .. versionadded:: 7.10 | |
131 | """, |
|
131 | """, | |
132 | ).tag(config=True) |
|
132 | ).tag(config=True) | |
133 |
|
133 | |||
134 | reraise_ipython_extension_failures = Bool(False, |
|
134 | reraise_ipython_extension_failures = Bool(False, | |
135 | help="Reraise exceptions encountered loading IPython extensions?", |
|
135 | help="Reraise exceptions encountered loading IPython extensions?", | |
136 | ).tag(config=True) |
|
136 | ).tag(config=True) | |
137 |
|
137 | |||
138 | # Extensions that are always loaded (not configurable) |
|
138 | # Extensions that are always loaded (not configurable) | |
139 | default_extensions = List(Unicode(), [u'storemagic']).tag(config=False) |
|
139 | default_extensions = List(Unicode(), [u'storemagic']).tag(config=False) | |
140 |
|
140 | |||
141 | hide_initial_ns = Bool(True, |
|
141 | hide_initial_ns = Bool(True, | |
142 | help="""Should variables loaded at startup (by startup files, exec_lines, etc.) |
|
142 | help="""Should variables loaded at startup (by startup files, exec_lines, etc.) | |
143 | be hidden from tools like %who?""" |
|
143 | be hidden from tools like %who?""" | |
144 | ).tag(config=True) |
|
144 | ).tag(config=True) | |
145 |
|
145 | |||
146 | exec_files = List(Unicode(), |
|
146 | exec_files = List(Unicode(), | |
147 | help="""List of files to run at IPython startup.""" |
|
147 | help="""List of files to run at IPython startup.""" | |
148 | ).tag(config=True) |
|
148 | ).tag(config=True) | |
149 | exec_PYTHONSTARTUP = Bool(True, |
|
149 | exec_PYTHONSTARTUP = Bool(True, | |
150 | help="""Run the file referenced by the PYTHONSTARTUP environment |
|
150 | help="""Run the file referenced by the PYTHONSTARTUP environment | |
151 | variable at IPython startup.""" |
|
151 | variable at IPython startup.""" | |
152 | ).tag(config=True) |
|
152 | ).tag(config=True) | |
153 | file_to_run = Unicode('', |
|
153 | file_to_run = Unicode('', | |
154 | help="""A file to be run""").tag(config=True) |
|
154 | help="""A file to be run""").tag(config=True) | |
155 |
|
155 | |||
156 | exec_lines = List(Unicode(), |
|
156 | exec_lines = List(Unicode(), | |
157 | help="""lines of code to run at IPython startup.""" |
|
157 | help="""lines of code to run at IPython startup.""" | |
158 | ).tag(config=True) |
|
158 | ).tag(config=True) | |
159 | code_to_run = Unicode('', |
|
159 | code_to_run = Unicode('', | |
160 | help="Execute the given command string." |
|
160 | help="Execute the given command string." | |
161 | ).tag(config=True) |
|
161 | ).tag(config=True) | |
162 | module_to_run = Unicode('', |
|
162 | module_to_run = Unicode('', | |
163 | help="Run the module as a script." |
|
163 | help="Run the module as a script." | |
164 | ).tag(config=True) |
|
164 | ).tag(config=True) | |
165 | gui = CaselessStrEnum(gui_keys, allow_none=True, |
|
165 | gui = CaselessStrEnum(gui_keys, allow_none=True, | |
166 | help="Enable GUI event loop integration with any of {0}.".format(gui_keys) |
|
166 | help="Enable GUI event loop integration with any of {0}.".format(gui_keys) | |
167 | ).tag(config=True) |
|
167 | ).tag(config=True) | |
168 | matplotlib = CaselessStrEnum(backend_keys, allow_none=True, |
|
168 | matplotlib = CaselessStrEnum(backend_keys, allow_none=True, | |
169 | help="""Configure matplotlib for interactive use with |
|
169 | help="""Configure matplotlib for interactive use with | |
170 | the default matplotlib backend.""" |
|
170 | the default matplotlib backend.""" | |
171 | ).tag(config=True) |
|
171 | ).tag(config=True) | |
172 | pylab = CaselessStrEnum(backend_keys, allow_none=True, |
|
172 | pylab = CaselessStrEnum(backend_keys, allow_none=True, | |
173 | help="""Pre-load matplotlib and numpy for interactive use, |
|
173 | help="""Pre-load matplotlib and numpy for interactive use, | |
174 | selecting a particular matplotlib backend and loop integration. |
|
174 | selecting a particular matplotlib backend and loop integration. | |
175 | """ |
|
175 | """ | |
176 | ).tag(config=True) |
|
176 | ).tag(config=True) | |
177 | pylab_import_all = Bool(True, |
|
177 | pylab_import_all = Bool(True, | |
178 | help="""If true, IPython will populate the user namespace with numpy, pylab, etc. |
|
178 | help="""If true, IPython will populate the user namespace with numpy, pylab, etc. | |
179 | and an ``import *`` is done from numpy and pylab, when using pylab mode. |
|
179 | and an ``import *`` is done from numpy and pylab, when using pylab mode. | |
180 |
|
180 | |||
181 | When False, pylab mode should not import any names into the user namespace. |
|
181 | When False, pylab mode should not import any names into the user namespace. | |
182 | """ |
|
182 | """ | |
183 | ).tag(config=True) |
|
183 | ).tag(config=True) | |
184 | ignore_cwd = Bool( |
|
184 | ignore_cwd = Bool( | |
185 | False, |
|
185 | False, | |
186 | help="""If True, IPython will not add the current working directory to sys.path. |
|
186 | help="""If True, IPython will not add the current working directory to sys.path. | |
187 | When False, the current working directory is added to sys.path, allowing imports |
|
187 | When False, the current working directory is added to sys.path, allowing imports | |
188 | of modules defined in the current directory.""" |
|
188 | of modules defined in the current directory.""" | |
189 | ).tag(config=True) |
|
189 | ).tag(config=True) | |
190 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', |
|
190 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', | |
191 | allow_none=True) |
|
191 | allow_none=True) | |
192 | # whether interact-loop should start |
|
192 | # whether interact-loop should start | |
193 | interact = Bool(True) |
|
193 | interact = Bool(True) | |
194 |
|
194 | |||
195 | user_ns = Instance(dict, args=None, allow_none=True) |
|
195 | user_ns = Instance(dict, args=None, allow_none=True) | |
196 | @observe('user_ns') |
|
196 | @observe('user_ns') | |
197 | def _user_ns_changed(self, change): |
|
197 | def _user_ns_changed(self, change): | |
198 | if self.shell is not None: |
|
198 | if self.shell is not None: | |
199 | self.shell.user_ns = change['new'] |
|
199 | self.shell.user_ns = change['new'] | |
200 | self.shell.init_user_ns() |
|
200 | self.shell.init_user_ns() | |
201 |
|
201 | |||
202 | def init_path(self): |
|
202 | def init_path(self): | |
203 | """Add current working directory, '', to sys.path |
|
203 | """Add current working directory, '', to sys.path | |
204 |
|
204 | |||
205 | Unlike Python's default, we insert before the first `site-packages` |
|
205 | Unlike Python's default, we insert before the first `site-packages` | |
206 | or `dist-packages` directory, |
|
206 | or `dist-packages` directory, | |
207 | so that it is after the standard library. |
|
207 | so that it is after the standard library. | |
208 |
|
208 | |||
209 | .. versionchanged:: 7.2 |
|
209 | .. versionchanged:: 7.2 | |
210 | Try to insert after the standard library, instead of first. |
|
210 | Try to insert after the standard library, instead of first. | |
211 | .. versionchanged:: 8.0 |
|
211 | .. versionchanged:: 8.0 | |
212 | Allow optionally not including the current directory in sys.path |
|
212 | Allow optionally not including the current directory in sys.path | |
213 | """ |
|
213 | """ | |
214 | if '' in sys.path or self.ignore_cwd: |
|
214 | if '' in sys.path or self.ignore_cwd: | |
215 | return |
|
215 | return | |
216 | for idx, path in enumerate(sys.path): |
|
216 | for idx, path in enumerate(sys.path): | |
217 | parent, last_part = os.path.split(path) |
|
217 | parent, last_part = os.path.split(path) | |
218 | if last_part in {'site-packages', 'dist-packages'}: |
|
218 | if last_part in {'site-packages', 'dist-packages'}: | |
219 | break |
|
219 | break | |
220 | else: |
|
220 | else: | |
221 | # no site-packages or dist-packages found (?!) |
|
221 | # no site-packages or dist-packages found (?!) | |
222 | # back to original behavior of inserting at the front |
|
222 | # back to original behavior of inserting at the front | |
223 | idx = 0 |
|
223 | idx = 0 | |
224 | sys.path.insert(idx, '') |
|
224 | sys.path.insert(idx, '') | |
225 |
|
225 | |||
226 | def init_shell(self): |
|
226 | def init_shell(self): | |
227 | raise NotImplementedError("Override in subclasses") |
|
227 | raise NotImplementedError("Override in subclasses") | |
228 |
|
228 | |||
229 | def init_gui_pylab(self): |
|
229 | def init_gui_pylab(self): | |
230 | """Enable GUI event loop integration, taking pylab into account.""" |
|
230 | """Enable GUI event loop integration, taking pylab into account.""" | |
231 | enable = False |
|
231 | enable = False | |
232 | shell = self.shell |
|
232 | shell = self.shell | |
233 | if self.pylab: |
|
233 | if self.pylab: | |
234 | enable = lambda key: shell.enable_pylab(key, import_all=self.pylab_import_all) |
|
234 | enable = lambda key: shell.enable_pylab(key, import_all=self.pylab_import_all) | |
235 | key = self.pylab |
|
235 | key = self.pylab | |
236 | elif self.matplotlib: |
|
236 | elif self.matplotlib: | |
237 | enable = shell.enable_matplotlib |
|
237 | enable = shell.enable_matplotlib | |
238 | key = self.matplotlib |
|
238 | key = self.matplotlib | |
239 | elif self.gui: |
|
239 | elif self.gui: | |
240 | enable = shell.enable_gui |
|
240 | enable = shell.enable_gui | |
241 | key = self.gui |
|
241 | key = self.gui | |
242 |
|
242 | |||
243 | if not enable: |
|
243 | if not enable: | |
244 | return |
|
244 | return | |
245 |
|
245 | |||
246 | try: |
|
246 | try: | |
247 | r = enable(key) |
|
247 | r = enable(key) | |
248 | except ImportError: |
|
248 | except ImportError: | |
249 | self.log.warning("Eventloop or matplotlib integration failed. Is matplotlib installed?") |
|
249 | self.log.warning("Eventloop or matplotlib integration failed. Is matplotlib installed?") | |
250 | self.shell.showtraceback() |
|
250 | self.shell.showtraceback() | |
251 | return |
|
251 | return | |
252 | except Exception: |
|
252 | except Exception: | |
253 | self.log.warning("GUI event loop or pylab initialization failed") |
|
253 | self.log.warning("GUI event loop or pylab initialization failed") | |
254 | self.shell.showtraceback() |
|
254 | self.shell.showtraceback() | |
255 | return |
|
255 | return | |
256 |
|
256 | |||
257 | if isinstance(r, tuple): |
|
257 | if isinstance(r, tuple): | |
258 | gui, backend = r[:2] |
|
258 | gui, backend = r[:2] | |
259 | self.log.info("Enabling GUI event loop integration, " |
|
259 | self.log.info("Enabling GUI event loop integration, " | |
260 | "eventloop=%s, matplotlib=%s", gui, backend) |
|
260 | "eventloop=%s, matplotlib=%s", gui, backend) | |
261 | if key == "auto": |
|
261 | if key == "auto": | |
262 | print("Using matplotlib backend: %s" % backend) |
|
262 | print("Using matplotlib backend: %s" % backend) | |
263 | else: |
|
263 | else: | |
264 | gui = r |
|
264 | gui = r | |
265 | self.log.info("Enabling GUI event loop integration, " |
|
265 | self.log.info("Enabling GUI event loop integration, " | |
266 | "eventloop=%s", gui) |
|
266 | "eventloop=%s", gui) | |
267 |
|
267 | |||
268 | def init_extensions(self): |
|
268 | def init_extensions(self): | |
269 | """Load all IPython extensions in IPythonApp.extensions. |
|
269 | """Load all IPython extensions in IPythonApp.extensions. | |
270 |
|
270 | |||
271 | This uses the :meth:`ExtensionManager.load_extensions` to load all |
|
271 | This uses the :meth:`ExtensionManager.load_extensions` to load all | |
272 | the extensions listed in ``self.extensions``. |
|
272 | the extensions listed in ``self.extensions``. | |
273 | """ |
|
273 | """ | |
274 | try: |
|
274 | try: | |
275 | self.log.debug("Loading IPython extensions...") |
|
275 | self.log.debug("Loading IPython extensions...") | |
276 | extensions = ( |
|
276 | extensions = ( | |
277 | self.default_extensions + self.extensions + self.extra_extensions |
|
277 | self.default_extensions + self.extensions + self.extra_extensions | |
278 | ) |
|
278 | ) | |
279 | for ext in extensions: |
|
279 | for ext in extensions: | |
280 | try: |
|
280 | try: | |
281 |
self.log.info("Loading IPython extension: %s" |
|
281 | self.log.info("Loading IPython extension: %s", ext) | |
282 | self.shell.extension_manager.load_extension(ext) |
|
282 | self.shell.extension_manager.load_extension(ext) | |
283 | except: |
|
283 | except: | |
284 | if self.reraise_ipython_extension_failures: |
|
284 | if self.reraise_ipython_extension_failures: | |
285 | raise |
|
285 | raise | |
286 | msg = ("Error in loading extension: {ext}\n" |
|
286 | msg = ("Error in loading extension: {ext}\n" | |
287 | "Check your config files in {location}".format( |
|
287 | "Check your config files in {location}".format( | |
288 | ext=ext, |
|
288 | ext=ext, | |
289 | location=self.profile_dir.location |
|
289 | location=self.profile_dir.location | |
290 | )) |
|
290 | )) | |
291 | self.log.warning(msg, exc_info=True) |
|
291 | self.log.warning(msg, exc_info=True) | |
292 | except: |
|
292 | except: | |
293 | if self.reraise_ipython_extension_failures: |
|
293 | if self.reraise_ipython_extension_failures: | |
294 | raise |
|
294 | raise | |
295 | self.log.warning("Unknown error in loading extensions:", exc_info=True) |
|
295 | self.log.warning("Unknown error in loading extensions:", exc_info=True) | |
296 |
|
296 | |||
297 | def init_code(self): |
|
297 | def init_code(self): | |
298 | """run the pre-flight code, specified via exec_lines""" |
|
298 | """run the pre-flight code, specified via exec_lines""" | |
299 | self._run_startup_files() |
|
299 | self._run_startup_files() | |
300 | self._run_exec_lines() |
|
300 | self._run_exec_lines() | |
301 | self._run_exec_files() |
|
301 | self._run_exec_files() | |
302 |
|
302 | |||
303 | # Hide variables defined here from %who etc. |
|
303 | # Hide variables defined here from %who etc. | |
304 | if self.hide_initial_ns: |
|
304 | if self.hide_initial_ns: | |
305 | self.shell.user_ns_hidden.update(self.shell.user_ns) |
|
305 | self.shell.user_ns_hidden.update(self.shell.user_ns) | |
306 |
|
306 | |||
307 | # command-line execution (ipython -i script.py, ipython -m module) |
|
307 | # command-line execution (ipython -i script.py, ipython -m module) | |
308 | # should *not* be excluded from %whos |
|
308 | # should *not* be excluded from %whos | |
309 | self._run_cmd_line_code() |
|
309 | self._run_cmd_line_code() | |
310 | self._run_module() |
|
310 | self._run_module() | |
311 |
|
311 | |||
312 | # flush output, so itwon't be attached to the first cell |
|
312 | # flush output, so itwon't be attached to the first cell | |
313 | sys.stdout.flush() |
|
313 | sys.stdout.flush() | |
314 | sys.stderr.flush() |
|
314 | sys.stderr.flush() | |
315 | self.shell._sys_modules_keys = set(sys.modules.keys()) |
|
315 | self.shell._sys_modules_keys = set(sys.modules.keys()) | |
316 |
|
316 | |||
317 | def _run_exec_lines(self): |
|
317 | def _run_exec_lines(self): | |
318 | """Run lines of code in IPythonApp.exec_lines in the user's namespace.""" |
|
318 | """Run lines of code in IPythonApp.exec_lines in the user's namespace.""" | |
319 | if not self.exec_lines: |
|
319 | if not self.exec_lines: | |
320 | return |
|
320 | return | |
321 | try: |
|
321 | try: | |
322 | self.log.debug("Running code from IPythonApp.exec_lines...") |
|
322 | self.log.debug("Running code from IPythonApp.exec_lines...") | |
323 | for line in self.exec_lines: |
|
323 | for line in self.exec_lines: | |
324 | try: |
|
324 | try: | |
325 | self.log.info("Running code in user namespace: %s" % |
|
325 | self.log.info("Running code in user namespace: %s" % | |
326 | line) |
|
326 | line) | |
327 | self.shell.run_cell(line, store_history=False) |
|
327 | self.shell.run_cell(line, store_history=False) | |
328 | except: |
|
328 | except: | |
329 | self.log.warning("Error in executing line in user " |
|
329 | self.log.warning("Error in executing line in user " | |
330 | "namespace: %s" % line) |
|
330 | "namespace: %s" % line) | |
331 | self.shell.showtraceback() |
|
331 | self.shell.showtraceback() | |
332 | except: |
|
332 | except: | |
333 | self.log.warning("Unknown error in handling IPythonApp.exec_lines:") |
|
333 | self.log.warning("Unknown error in handling IPythonApp.exec_lines:") | |
334 | self.shell.showtraceback() |
|
334 | self.shell.showtraceback() | |
335 |
|
335 | |||
336 | def _exec_file(self, fname, shell_futures=False): |
|
336 | def _exec_file(self, fname, shell_futures=False): | |
337 | try: |
|
337 | try: | |
338 | full_filename = filefind(fname, [u'.', self.ipython_dir]) |
|
338 | full_filename = filefind(fname, [u'.', self.ipython_dir]) | |
339 | except IOError: |
|
339 | except IOError: | |
340 | self.log.warning("File not found: %r"%fname) |
|
340 | self.log.warning("File not found: %r"%fname) | |
341 | return |
|
341 | return | |
342 | # Make sure that the running script gets a proper sys.argv as if it |
|
342 | # Make sure that the running script gets a proper sys.argv as if it | |
343 | # were run from a system shell. |
|
343 | # were run from a system shell. | |
344 | save_argv = sys.argv |
|
344 | save_argv = sys.argv | |
345 | sys.argv = [full_filename] + self.extra_args[1:] |
|
345 | sys.argv = [full_filename] + self.extra_args[1:] | |
346 | try: |
|
346 | try: | |
347 | if os.path.isfile(full_filename): |
|
347 | if os.path.isfile(full_filename): | |
348 | self.log.info("Running file in user namespace: %s" % |
|
348 | self.log.info("Running file in user namespace: %s" % | |
349 | full_filename) |
|
349 | full_filename) | |
350 | # Ensure that __file__ is always defined to match Python |
|
350 | # Ensure that __file__ is always defined to match Python | |
351 | # behavior. |
|
351 | # behavior. | |
352 | with preserve_keys(self.shell.user_ns, '__file__'): |
|
352 | with preserve_keys(self.shell.user_ns, '__file__'): | |
353 | self.shell.user_ns['__file__'] = fname |
|
353 | self.shell.user_ns['__file__'] = fname | |
354 | if full_filename.endswith('.ipy') or full_filename.endswith('.ipynb'): |
|
354 | if full_filename.endswith('.ipy') or full_filename.endswith('.ipynb'): | |
355 | self.shell.safe_execfile_ipy(full_filename, |
|
355 | self.shell.safe_execfile_ipy(full_filename, | |
356 | shell_futures=shell_futures) |
|
356 | shell_futures=shell_futures) | |
357 | else: |
|
357 | else: | |
358 | # default to python, even without extension |
|
358 | # default to python, even without extension | |
359 | self.shell.safe_execfile(full_filename, |
|
359 | self.shell.safe_execfile(full_filename, | |
360 | self.shell.user_ns, |
|
360 | self.shell.user_ns, | |
361 | shell_futures=shell_futures, |
|
361 | shell_futures=shell_futures, | |
362 | raise_exceptions=True) |
|
362 | raise_exceptions=True) | |
363 | finally: |
|
363 | finally: | |
364 | sys.argv = save_argv |
|
364 | sys.argv = save_argv | |
365 |
|
365 | |||
366 | def _run_startup_files(self): |
|
366 | def _run_startup_files(self): | |
367 | """Run files from profile startup directory""" |
|
367 | """Run files from profile startup directory""" | |
368 | startup_dirs = [self.profile_dir.startup_dir] + [ |
|
368 | startup_dirs = [self.profile_dir.startup_dir] + [ | |
369 | os.path.join(p, 'startup') for p in chain(ENV_CONFIG_DIRS, SYSTEM_CONFIG_DIRS) |
|
369 | os.path.join(p, 'startup') for p in chain(ENV_CONFIG_DIRS, SYSTEM_CONFIG_DIRS) | |
370 | ] |
|
370 | ] | |
371 | startup_files = [] |
|
371 | startup_files = [] | |
372 |
|
372 | |||
373 | if self.exec_PYTHONSTARTUP and os.environ.get('PYTHONSTARTUP', False) and \ |
|
373 | if self.exec_PYTHONSTARTUP and os.environ.get('PYTHONSTARTUP', False) and \ | |
374 | not (self.file_to_run or self.code_to_run or self.module_to_run): |
|
374 | not (self.file_to_run or self.code_to_run or self.module_to_run): | |
375 | python_startup = os.environ['PYTHONSTARTUP'] |
|
375 | python_startup = os.environ['PYTHONSTARTUP'] | |
376 | self.log.debug("Running PYTHONSTARTUP file %s...", python_startup) |
|
376 | self.log.debug("Running PYTHONSTARTUP file %s...", python_startup) | |
377 | try: |
|
377 | try: | |
378 | self._exec_file(python_startup) |
|
378 | self._exec_file(python_startup) | |
379 | except: |
|
379 | except: | |
380 | self.log.warning("Unknown error in handling PYTHONSTARTUP file %s:", python_startup) |
|
380 | self.log.warning("Unknown error in handling PYTHONSTARTUP file %s:", python_startup) | |
381 | self.shell.showtraceback() |
|
381 | self.shell.showtraceback() | |
382 | for startup_dir in startup_dirs[::-1]: |
|
382 | for startup_dir in startup_dirs[::-1]: | |
383 | startup_files += glob.glob(os.path.join(startup_dir, '*.py')) |
|
383 | startup_files += glob.glob(os.path.join(startup_dir, '*.py')) | |
384 | startup_files += glob.glob(os.path.join(startup_dir, '*.ipy')) |
|
384 | startup_files += glob.glob(os.path.join(startup_dir, '*.ipy')) | |
385 | if not startup_files: |
|
385 | if not startup_files: | |
386 | return |
|
386 | return | |
387 |
|
387 | |||
388 | self.log.debug("Running startup files from %s...", startup_dir) |
|
388 | self.log.debug("Running startup files from %s...", startup_dir) | |
389 | try: |
|
389 | try: | |
390 | for fname in sorted(startup_files): |
|
390 | for fname in sorted(startup_files): | |
391 | self._exec_file(fname) |
|
391 | self._exec_file(fname) | |
392 | except: |
|
392 | except: | |
393 | self.log.warning("Unknown error in handling startup files:") |
|
393 | self.log.warning("Unknown error in handling startup files:") | |
394 | self.shell.showtraceback() |
|
394 | self.shell.showtraceback() | |
395 |
|
395 | |||
396 | def _run_exec_files(self): |
|
396 | def _run_exec_files(self): | |
397 | """Run files from IPythonApp.exec_files""" |
|
397 | """Run files from IPythonApp.exec_files""" | |
398 | if not self.exec_files: |
|
398 | if not self.exec_files: | |
399 | return |
|
399 | return | |
400 |
|
400 | |||
401 | self.log.debug("Running files in IPythonApp.exec_files...") |
|
401 | self.log.debug("Running files in IPythonApp.exec_files...") | |
402 | try: |
|
402 | try: | |
403 | for fname in self.exec_files: |
|
403 | for fname in self.exec_files: | |
404 | self._exec_file(fname) |
|
404 | self._exec_file(fname) | |
405 | except: |
|
405 | except: | |
406 | self.log.warning("Unknown error in handling IPythonApp.exec_files:") |
|
406 | self.log.warning("Unknown error in handling IPythonApp.exec_files:") | |
407 | self.shell.showtraceback() |
|
407 | self.shell.showtraceback() | |
408 |
|
408 | |||
409 | def _run_cmd_line_code(self): |
|
409 | def _run_cmd_line_code(self): | |
410 | """Run code or file specified at the command-line""" |
|
410 | """Run code or file specified at the command-line""" | |
411 | if self.code_to_run: |
|
411 | if self.code_to_run: | |
412 | line = self.code_to_run |
|
412 | line = self.code_to_run | |
413 | try: |
|
413 | try: | |
414 | self.log.info("Running code given at command line (c=): %s" % |
|
414 | self.log.info("Running code given at command line (c=): %s" % | |
415 | line) |
|
415 | line) | |
416 | self.shell.run_cell(line, store_history=False) |
|
416 | self.shell.run_cell(line, store_history=False) | |
417 | except: |
|
417 | except: | |
418 | self.log.warning("Error in executing line in user namespace: %s" % |
|
418 | self.log.warning("Error in executing line in user namespace: %s" % | |
419 | line) |
|
419 | line) | |
420 | self.shell.showtraceback() |
|
420 | self.shell.showtraceback() | |
421 | if not self.interact: |
|
421 | if not self.interact: | |
422 | self.exit(1) |
|
422 | self.exit(1) | |
423 |
|
423 | |||
424 | # Like Python itself, ignore the second if the first of these is present |
|
424 | # Like Python itself, ignore the second if the first of these is present | |
425 | elif self.file_to_run: |
|
425 | elif self.file_to_run: | |
426 | fname = self.file_to_run |
|
426 | fname = self.file_to_run | |
427 | if os.path.isdir(fname): |
|
427 | if os.path.isdir(fname): | |
428 | fname = os.path.join(fname, "__main__.py") |
|
428 | fname = os.path.join(fname, "__main__.py") | |
429 | if not os.path.exists(fname): |
|
429 | if not os.path.exists(fname): | |
430 | self.log.warning("File '%s' doesn't exist", fname) |
|
430 | self.log.warning("File '%s' doesn't exist", fname) | |
431 | if not self.interact: |
|
431 | if not self.interact: | |
432 | self.exit(2) |
|
432 | self.exit(2) | |
433 | try: |
|
433 | try: | |
434 | self._exec_file(fname, shell_futures=True) |
|
434 | self._exec_file(fname, shell_futures=True) | |
435 | except: |
|
435 | except: | |
436 | self.shell.showtraceback(tb_offset=4) |
|
436 | self.shell.showtraceback(tb_offset=4) | |
437 | if not self.interact: |
|
437 | if not self.interact: | |
438 | self.exit(1) |
|
438 | self.exit(1) | |
439 |
|
439 | |||
440 | def _run_module(self): |
|
440 | def _run_module(self): | |
441 | """Run module specified at the command-line.""" |
|
441 | """Run module specified at the command-line.""" | |
442 | if self.module_to_run: |
|
442 | if self.module_to_run: | |
443 | # Make sure that the module gets a proper sys.argv as if it were |
|
443 | # Make sure that the module gets a proper sys.argv as if it were | |
444 | # run using `python -m`. |
|
444 | # run using `python -m`. | |
445 | save_argv = sys.argv |
|
445 | save_argv = sys.argv | |
446 | sys.argv = [sys.executable] + self.extra_args |
|
446 | sys.argv = [sys.executable] + self.extra_args | |
447 | try: |
|
447 | try: | |
448 | self.shell.safe_run_module(self.module_to_run, |
|
448 | self.shell.safe_run_module(self.module_to_run, | |
449 | self.shell.user_ns) |
|
449 | self.shell.user_ns) | |
450 | finally: |
|
450 | finally: | |
451 | sys.argv = save_argv |
|
451 | sys.argv = save_argv |
@@ -1,1454 +1,1513 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | """Tests for various magic functions.""" |
|
2 | """Tests for various magic functions.""" | |
3 |
|
3 | |||
4 | import gc |
|
4 | import gc | |
5 | import io |
|
5 | import io | |
6 | import os |
|
6 | import os | |
7 | import re |
|
7 | import re | |
8 | import shlex |
|
8 | import shlex | |
9 | import sys |
|
9 | import sys | |
10 | import warnings |
|
10 | import warnings | |
11 | from importlib import invalidate_caches |
|
11 | from importlib import invalidate_caches | |
12 | from io import StringIO |
|
12 | from io import StringIO | |
13 | from pathlib import Path |
|
13 | from pathlib import Path | |
14 | from textwrap import dedent |
|
14 | from textwrap import dedent | |
15 | from unittest import TestCase, mock |
|
15 | from unittest import TestCase, mock | |
16 |
|
16 | |||
17 | import pytest |
|
17 | import pytest | |
18 |
|
18 | |||
19 | from IPython import get_ipython |
|
19 | from IPython import get_ipython | |
20 | from IPython.core import magic |
|
20 | from IPython.core import magic | |
21 | from IPython.core.error import UsageError |
|
21 | from IPython.core.error import UsageError | |
22 | from IPython.core.magic import ( |
|
22 | from IPython.core.magic import ( | |
23 | Magics, |
|
23 | Magics, | |
24 | cell_magic, |
|
24 | cell_magic, | |
25 | line_magic, |
|
25 | line_magic, | |
26 | magics_class, |
|
26 | magics_class, | |
27 | register_cell_magic, |
|
27 | register_cell_magic, | |
28 | register_line_magic, |
|
28 | register_line_magic, | |
29 | ) |
|
29 | ) | |
30 | from IPython.core.magics import code, execution, logging, osm, script |
|
30 | from IPython.core.magics import code, execution, logging, osm, script | |
31 | from IPython.testing import decorators as dec |
|
31 | from IPython.testing import decorators as dec | |
32 | from IPython.testing import tools as tt |
|
32 | from IPython.testing import tools as tt | |
33 | from IPython.utils.io import capture_output |
|
33 | from IPython.utils.io import capture_output | |
34 | from IPython.utils.process import find_cmd |
|
34 | from IPython.utils.process import find_cmd | |
35 | from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory |
|
35 | from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory | |
36 | from IPython.utils.syspathcontext import prepended_to_syspath |
|
36 | from IPython.utils.syspathcontext import prepended_to_syspath | |
37 |
|
37 | |||
38 | from .test_debugger import PdbTestInput |
|
38 | from .test_debugger import PdbTestInput | |
39 |
|
39 | |||
40 | from tempfile import NamedTemporaryFile |
|
40 | from tempfile import NamedTemporaryFile | |
41 |
|
41 | |||
42 | @magic.magics_class |
|
42 | @magic.magics_class | |
43 | class DummyMagics(magic.Magics): pass |
|
43 | class DummyMagics(magic.Magics): pass | |
44 |
|
44 | |||
45 | def test_extract_code_ranges(): |
|
45 | def test_extract_code_ranges(): | |
46 | instr = "1 3 5-6 7-9 10:15 17: :10 10- -13 :" |
|
46 | instr = "1 3 5-6 7-9 10:15 17: :10 10- -13 :" | |
47 | expected = [ |
|
47 | expected = [ | |
48 | (0, 1), |
|
48 | (0, 1), | |
49 | (2, 3), |
|
49 | (2, 3), | |
50 | (4, 6), |
|
50 | (4, 6), | |
51 | (6, 9), |
|
51 | (6, 9), | |
52 | (9, 14), |
|
52 | (9, 14), | |
53 | (16, None), |
|
53 | (16, None), | |
54 | (None, 9), |
|
54 | (None, 9), | |
55 | (9, None), |
|
55 | (9, None), | |
56 | (None, 13), |
|
56 | (None, 13), | |
57 | (None, None), |
|
57 | (None, None), | |
58 | ] |
|
58 | ] | |
59 | actual = list(code.extract_code_ranges(instr)) |
|
59 | actual = list(code.extract_code_ranges(instr)) | |
60 | assert actual == expected |
|
60 | assert actual == expected | |
61 |
|
61 | |||
62 | def test_extract_symbols(): |
|
62 | def test_extract_symbols(): | |
63 | source = """import foo\na = 10\ndef b():\n return 42\n\n\nclass A: pass\n\n\n""" |
|
63 | source = """import foo\na = 10\ndef b():\n return 42\n\n\nclass A: pass\n\n\n""" | |
64 | symbols_args = ["a", "b", "A", "A,b", "A,a", "z"] |
|
64 | symbols_args = ["a", "b", "A", "A,b", "A,a", "z"] | |
65 | expected = [([], ['a']), |
|
65 | expected = [([], ['a']), | |
66 | (["def b():\n return 42\n"], []), |
|
66 | (["def b():\n return 42\n"], []), | |
67 | (["class A: pass\n"], []), |
|
67 | (["class A: pass\n"], []), | |
68 | (["class A: pass\n", "def b():\n return 42\n"], []), |
|
68 | (["class A: pass\n", "def b():\n return 42\n"], []), | |
69 | (["class A: pass\n"], ['a']), |
|
69 | (["class A: pass\n"], ['a']), | |
70 | ([], ['z'])] |
|
70 | ([], ['z'])] | |
71 | for symbols, exp in zip(symbols_args, expected): |
|
71 | for symbols, exp in zip(symbols_args, expected): | |
72 | assert code.extract_symbols(source, symbols) == exp |
|
72 | assert code.extract_symbols(source, symbols) == exp | |
73 |
|
73 | |||
74 |
|
74 | |||
75 | def test_extract_symbols_raises_exception_with_non_python_code(): |
|
75 | def test_extract_symbols_raises_exception_with_non_python_code(): | |
76 | source = ("=begin A Ruby program :)=end\n" |
|
76 | source = ("=begin A Ruby program :)=end\n" | |
77 | "def hello\n" |
|
77 | "def hello\n" | |
78 | "puts 'Hello world'\n" |
|
78 | "puts 'Hello world'\n" | |
79 | "end") |
|
79 | "end") | |
80 | with pytest.raises(SyntaxError): |
|
80 | with pytest.raises(SyntaxError): | |
81 | code.extract_symbols(source, "hello") |
|
81 | code.extract_symbols(source, "hello") | |
82 |
|
82 | |||
83 |
|
83 | |||
84 | def test_magic_not_found(): |
|
84 | def test_magic_not_found(): | |
85 | # magic not found raises UsageError |
|
85 | # magic not found raises UsageError | |
86 | with pytest.raises(UsageError): |
|
86 | with pytest.raises(UsageError): | |
87 | _ip.run_line_magic("doesntexist", "") |
|
87 | _ip.run_line_magic("doesntexist", "") | |
88 |
|
88 | |||
89 | # ensure result isn't success when a magic isn't found |
|
89 | # ensure result isn't success when a magic isn't found | |
90 | result = _ip.run_cell('%doesntexist') |
|
90 | result = _ip.run_cell('%doesntexist') | |
91 | assert isinstance(result.error_in_exec, UsageError) |
|
91 | assert isinstance(result.error_in_exec, UsageError) | |
92 |
|
92 | |||
93 |
|
93 | |||
94 | def test_cell_magic_not_found(): |
|
94 | def test_cell_magic_not_found(): | |
95 | # magic not found raises UsageError |
|
95 | # magic not found raises UsageError | |
96 | with pytest.raises(UsageError): |
|
96 | with pytest.raises(UsageError): | |
97 | _ip.run_cell_magic('doesntexist', 'line', 'cell') |
|
97 | _ip.run_cell_magic('doesntexist', 'line', 'cell') | |
98 |
|
98 | |||
99 | # ensure result isn't success when a magic isn't found |
|
99 | # ensure result isn't success when a magic isn't found | |
100 | result = _ip.run_cell('%%doesntexist') |
|
100 | result = _ip.run_cell('%%doesntexist') | |
101 | assert isinstance(result.error_in_exec, UsageError) |
|
101 | assert isinstance(result.error_in_exec, UsageError) | |
102 |
|
102 | |||
103 |
|
103 | |||
104 | def test_magic_error_status(): |
|
104 | def test_magic_error_status(): | |
105 | def fail(shell): |
|
105 | def fail(shell): | |
106 | 1/0 |
|
106 | 1/0 | |
107 | _ip.register_magic_function(fail) |
|
107 | _ip.register_magic_function(fail) | |
108 | result = _ip.run_cell('%fail') |
|
108 | result = _ip.run_cell('%fail') | |
109 | assert isinstance(result.error_in_exec, ZeroDivisionError) |
|
109 | assert isinstance(result.error_in_exec, ZeroDivisionError) | |
110 |
|
110 | |||
111 |
|
111 | |||
112 | def test_config(): |
|
112 | def test_config(): | |
113 | """ test that config magic does not raise |
|
113 | """ test that config magic does not raise | |
114 | can happen if Configurable init is moved too early into |
|
114 | can happen if Configurable init is moved too early into | |
115 | Magics.__init__ as then a Config object will be registered as a |
|
115 | Magics.__init__ as then a Config object will be registered as a | |
116 | magic. |
|
116 | magic. | |
117 | """ |
|
117 | """ | |
118 | ## should not raise. |
|
118 | ## should not raise. | |
119 | _ip.run_line_magic("config", "") |
|
119 | _ip.run_line_magic("config", "") | |
120 |
|
120 | |||
121 |
|
121 | |||
122 | def test_config_available_configs(): |
|
122 | def test_config_available_configs(): | |
123 | """ test that config magic prints available configs in unique and |
|
123 | """ test that config magic prints available configs in unique and | |
124 | sorted order. """ |
|
124 | sorted order. """ | |
125 | with capture_output() as captured: |
|
125 | with capture_output() as captured: | |
126 | _ip.run_line_magic("config", "") |
|
126 | _ip.run_line_magic("config", "") | |
127 |
|
127 | |||
128 | stdout = captured.stdout |
|
128 | stdout = captured.stdout | |
129 | config_classes = stdout.strip().split('\n')[1:] |
|
129 | config_classes = stdout.strip().split('\n')[1:] | |
130 | assert config_classes == sorted(set(config_classes)) |
|
130 | assert config_classes == sorted(set(config_classes)) | |
131 |
|
131 | |||
132 | def test_config_print_class(): |
|
132 | def test_config_print_class(): | |
133 | """ test that config with a classname prints the class's options. """ |
|
133 | """ test that config with a classname prints the class's options. """ | |
134 | with capture_output() as captured: |
|
134 | with capture_output() as captured: | |
135 | _ip.run_line_magic("config", "TerminalInteractiveShell") |
|
135 | _ip.run_line_magic("config", "TerminalInteractiveShell") | |
136 |
|
136 | |||
137 | stdout = captured.stdout |
|
137 | stdout = captured.stdout | |
138 | assert re.match( |
|
138 | assert re.match( | |
139 | "TerminalInteractiveShell.* options", stdout.splitlines()[0] |
|
139 | "TerminalInteractiveShell.* options", stdout.splitlines()[0] | |
140 | ), f"{stdout}\n\n1st line of stdout not like 'TerminalInteractiveShell.* options'" |
|
140 | ), f"{stdout}\n\n1st line of stdout not like 'TerminalInteractiveShell.* options'" | |
141 |
|
141 | |||
142 |
|
142 | |||
143 | def test_rehashx(): |
|
143 | def test_rehashx(): | |
144 | # clear up everything |
|
144 | # clear up everything | |
145 | _ip.alias_manager.clear_aliases() |
|
145 | _ip.alias_manager.clear_aliases() | |
146 | del _ip.db['syscmdlist'] |
|
146 | del _ip.db['syscmdlist'] | |
147 |
|
147 | |||
148 | _ip.run_line_magic("rehashx", "") |
|
148 | _ip.run_line_magic("rehashx", "") | |
149 | # Practically ALL ipython development systems will have more than 10 aliases |
|
149 | # Practically ALL ipython development systems will have more than 10 aliases | |
150 |
|
150 | |||
151 | assert len(_ip.alias_manager.aliases) > 10 |
|
151 | assert len(_ip.alias_manager.aliases) > 10 | |
152 | for name, cmd in _ip.alias_manager.aliases: |
|
152 | for name, cmd in _ip.alias_manager.aliases: | |
153 | # we must strip dots from alias names |
|
153 | # we must strip dots from alias names | |
154 | assert "." not in name |
|
154 | assert "." not in name | |
155 |
|
155 | |||
156 | # rehashx must fill up syscmdlist |
|
156 | # rehashx must fill up syscmdlist | |
157 | scoms = _ip.db['syscmdlist'] |
|
157 | scoms = _ip.db['syscmdlist'] | |
158 | assert len(scoms) > 10 |
|
158 | assert len(scoms) > 10 | |
159 |
|
159 | |||
160 |
|
160 | |||
161 | def test_magic_parse_options(): |
|
161 | def test_magic_parse_options(): | |
162 | """Test that we don't mangle paths when parsing magic options.""" |
|
162 | """Test that we don't mangle paths when parsing magic options.""" | |
163 | ip = get_ipython() |
|
163 | ip = get_ipython() | |
164 | path = 'c:\\x' |
|
164 | path = 'c:\\x' | |
165 | m = DummyMagics(ip) |
|
165 | m = DummyMagics(ip) | |
166 | opts = m.parse_options('-f %s' % path,'f:')[0] |
|
166 | opts = m.parse_options('-f %s' % path,'f:')[0] | |
167 | # argv splitting is os-dependent |
|
167 | # argv splitting is os-dependent | |
168 | if os.name == 'posix': |
|
168 | if os.name == 'posix': | |
169 | expected = 'c:x' |
|
169 | expected = 'c:x' | |
170 | else: |
|
170 | else: | |
171 | expected = path |
|
171 | expected = path | |
172 | assert opts["f"] == expected |
|
172 | assert opts["f"] == expected | |
173 |
|
173 | |||
174 |
|
174 | |||
175 | def test_magic_parse_long_options(): |
|
175 | def test_magic_parse_long_options(): | |
176 | """Magic.parse_options can handle --foo=bar long options""" |
|
176 | """Magic.parse_options can handle --foo=bar long options""" | |
177 | ip = get_ipython() |
|
177 | ip = get_ipython() | |
178 | m = DummyMagics(ip) |
|
178 | m = DummyMagics(ip) | |
179 | opts, _ = m.parse_options("--foo --bar=bubble", "a", "foo", "bar=") |
|
179 | opts, _ = m.parse_options("--foo --bar=bubble", "a", "foo", "bar=") | |
180 | assert "foo" in opts |
|
180 | assert "foo" in opts | |
181 | assert "bar" in opts |
|
181 | assert "bar" in opts | |
182 | assert opts["bar"] == "bubble" |
|
182 | assert opts["bar"] == "bubble" | |
183 |
|
183 | |||
184 |
|
184 | |||
185 | def doctest_hist_f(): |
|
185 | def doctest_hist_f(): | |
186 | """Test %hist -f with temporary filename. |
|
186 | """Test %hist -f with temporary filename. | |
187 |
|
187 | |||
188 | In [9]: import tempfile |
|
188 | In [9]: import tempfile | |
189 |
|
189 | |||
190 | In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-') |
|
190 | In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-') | |
191 |
|
191 | |||
192 | In [11]: %hist -nl -f $tfile 3 |
|
192 | In [11]: %hist -nl -f $tfile 3 | |
193 |
|
193 | |||
194 | In [13]: import os; os.unlink(tfile) |
|
194 | In [13]: import os; os.unlink(tfile) | |
195 | """ |
|
195 | """ | |
196 |
|
196 | |||
197 |
|
197 | |||
198 | def doctest_hist_op(): |
|
198 | def doctest_hist_op(): | |
199 | """Test %hist -op |
|
199 | """Test %hist -op | |
200 |
|
200 | |||
201 | In [1]: class b(float): |
|
201 | In [1]: class b(float): | |
202 | ...: pass |
|
202 | ...: pass | |
203 | ...: |
|
203 | ...: | |
204 |
|
204 | |||
205 | In [2]: class s(object): |
|
205 | In [2]: class s(object): | |
206 | ...: def __str__(self): |
|
206 | ...: def __str__(self): | |
207 | ...: return 's' |
|
207 | ...: return 's' | |
208 | ...: |
|
208 | ...: | |
209 |
|
209 | |||
210 | In [3]: |
|
210 | In [3]: | |
211 |
|
211 | |||
212 | In [4]: class r(b): |
|
212 | In [4]: class r(b): | |
213 | ...: def __repr__(self): |
|
213 | ...: def __repr__(self): | |
214 | ...: return 'r' |
|
214 | ...: return 'r' | |
215 | ...: |
|
215 | ...: | |
216 |
|
216 | |||
217 | In [5]: class sr(s,r): pass |
|
217 | In [5]: class sr(s,r): pass | |
218 | ...: |
|
218 | ...: | |
219 |
|
219 | |||
220 | In [6]: |
|
220 | In [6]: | |
221 |
|
221 | |||
222 | In [7]: bb=b() |
|
222 | In [7]: bb=b() | |
223 |
|
223 | |||
224 | In [8]: ss=s() |
|
224 | In [8]: ss=s() | |
225 |
|
225 | |||
226 | In [9]: rr=r() |
|
226 | In [9]: rr=r() | |
227 |
|
227 | |||
228 | In [10]: ssrr=sr() |
|
228 | In [10]: ssrr=sr() | |
229 |
|
229 | |||
230 | In [11]: 4.5 |
|
230 | In [11]: 4.5 | |
231 | Out[11]: 4.5 |
|
231 | Out[11]: 4.5 | |
232 |
|
232 | |||
233 | In [12]: str(ss) |
|
233 | In [12]: str(ss) | |
234 | Out[12]: 's' |
|
234 | Out[12]: 's' | |
235 |
|
235 | |||
236 | In [13]: |
|
236 | In [13]: | |
237 |
|
237 | |||
238 | In [14]: %hist -op |
|
238 | In [14]: %hist -op | |
239 | >>> class b: |
|
239 | >>> class b: | |
240 | ... pass |
|
240 | ... pass | |
241 | ... |
|
241 | ... | |
242 | >>> class s(b): |
|
242 | >>> class s(b): | |
243 | ... def __str__(self): |
|
243 | ... def __str__(self): | |
244 | ... return 's' |
|
244 | ... return 's' | |
245 | ... |
|
245 | ... | |
246 | >>> |
|
246 | >>> | |
247 | >>> class r(b): |
|
247 | >>> class r(b): | |
248 | ... def __repr__(self): |
|
248 | ... def __repr__(self): | |
249 | ... return 'r' |
|
249 | ... return 'r' | |
250 | ... |
|
250 | ... | |
251 | >>> class sr(s,r): pass |
|
251 | >>> class sr(s,r): pass | |
252 | >>> |
|
252 | >>> | |
253 | >>> bb=b() |
|
253 | >>> bb=b() | |
254 | >>> ss=s() |
|
254 | >>> ss=s() | |
255 | >>> rr=r() |
|
255 | >>> rr=r() | |
256 | >>> ssrr=sr() |
|
256 | >>> ssrr=sr() | |
257 | >>> 4.5 |
|
257 | >>> 4.5 | |
258 | 4.5 |
|
258 | 4.5 | |
259 | >>> str(ss) |
|
259 | >>> str(ss) | |
260 | 's' |
|
260 | 's' | |
261 | >>> |
|
261 | >>> | |
262 | """ |
|
262 | """ | |
263 |
|
263 | |||
264 | def test_hist_pof(): |
|
264 | def test_hist_pof(): | |
265 | ip = get_ipython() |
|
265 | ip = get_ipython() | |
266 | ip.run_cell("1+2", store_history=True) |
|
266 | ip.run_cell("1+2", store_history=True) | |
267 | #raise Exception(ip.history_manager.session_number) |
|
267 | #raise Exception(ip.history_manager.session_number) | |
268 | #raise Exception(list(ip.history_manager._get_range_session())) |
|
268 | #raise Exception(list(ip.history_manager._get_range_session())) | |
269 | with TemporaryDirectory() as td: |
|
269 | with TemporaryDirectory() as td: | |
270 | tf = os.path.join(td, 'hist.py') |
|
270 | tf = os.path.join(td, 'hist.py') | |
271 | ip.run_line_magic('history', '-pof %s' % tf) |
|
271 | ip.run_line_magic('history', '-pof %s' % tf) | |
272 | assert os.path.isfile(tf) |
|
272 | assert os.path.isfile(tf) | |
273 |
|
273 | |||
274 |
|
274 | |||
275 | def test_macro(): |
|
275 | def test_macro(): | |
276 | ip = get_ipython() |
|
276 | ip = get_ipython() | |
277 | ip.history_manager.reset() # Clear any existing history. |
|
277 | ip.history_manager.reset() # Clear any existing history. | |
278 | cmds = ["a=1", "def b():\n return a**2", "print(a,b())"] |
|
278 | cmds = ["a=1", "def b():\n return a**2", "print(a,b())"] | |
279 | for i, cmd in enumerate(cmds, start=1): |
|
279 | for i, cmd in enumerate(cmds, start=1): | |
280 | ip.history_manager.store_inputs(i, cmd) |
|
280 | ip.history_manager.store_inputs(i, cmd) | |
281 | ip.run_line_magic("macro", "test 1-3") |
|
281 | ip.run_line_magic("macro", "test 1-3") | |
282 | assert ip.user_ns["test"].value == "\n".join(cmds) + "\n" |
|
282 | assert ip.user_ns["test"].value == "\n".join(cmds) + "\n" | |
283 |
|
283 | |||
284 | # List macros |
|
284 | # List macros | |
285 | assert "test" in ip.run_line_magic("macro", "") |
|
285 | assert "test" in ip.run_line_magic("macro", "") | |
286 |
|
286 | |||
287 |
|
287 | |||
288 | def test_macro_run(): |
|
288 | def test_macro_run(): | |
289 | """Test that we can run a multi-line macro successfully.""" |
|
289 | """Test that we can run a multi-line macro successfully.""" | |
290 | ip = get_ipython() |
|
290 | ip = get_ipython() | |
291 | ip.history_manager.reset() |
|
291 | ip.history_manager.reset() | |
292 | cmds = ["a=10", "a+=1", "print(a)", "%macro test 2-3"] |
|
292 | cmds = ["a=10", "a+=1", "print(a)", "%macro test 2-3"] | |
293 | for cmd in cmds: |
|
293 | for cmd in cmds: | |
294 | ip.run_cell(cmd, store_history=True) |
|
294 | ip.run_cell(cmd, store_history=True) | |
295 | assert ip.user_ns["test"].value == "a+=1\nprint(a)\n" |
|
295 | assert ip.user_ns["test"].value == "a+=1\nprint(a)\n" | |
296 | with tt.AssertPrints("12"): |
|
296 | with tt.AssertPrints("12"): | |
297 | ip.run_cell("test") |
|
297 | ip.run_cell("test") | |
298 | with tt.AssertPrints("13"): |
|
298 | with tt.AssertPrints("13"): | |
299 | ip.run_cell("test") |
|
299 | ip.run_cell("test") | |
300 |
|
300 | |||
301 |
|
301 | |||
302 | def test_magic_magic(): |
|
302 | def test_magic_magic(): | |
303 | """Test %magic""" |
|
303 | """Test %magic""" | |
304 | ip = get_ipython() |
|
304 | ip = get_ipython() | |
305 | with capture_output() as captured: |
|
305 | with capture_output() as captured: | |
306 | ip.run_line_magic("magic", "") |
|
306 | ip.run_line_magic("magic", "") | |
307 |
|
307 | |||
308 | stdout = captured.stdout |
|
308 | stdout = captured.stdout | |
309 | assert "%magic" in stdout |
|
309 | assert "%magic" in stdout | |
310 | assert "IPython" in stdout |
|
310 | assert "IPython" in stdout | |
311 | assert "Available" in stdout |
|
311 | assert "Available" in stdout | |
312 |
|
312 | |||
313 |
|
313 | |||
314 | @dec.skipif_not_numpy |
|
314 | @dec.skipif_not_numpy | |
315 | def test_numpy_reset_array_undec(): |
|
315 | def test_numpy_reset_array_undec(): | |
316 | "Test '%reset array' functionality" |
|
316 | "Test '%reset array' functionality" | |
317 | _ip.ex("import numpy as np") |
|
317 | _ip.ex("import numpy as np") | |
318 | _ip.ex("a = np.empty(2)") |
|
318 | _ip.ex("a = np.empty(2)") | |
319 | assert "a" in _ip.user_ns |
|
319 | assert "a" in _ip.user_ns | |
320 | _ip.run_line_magic("reset", "-f array") |
|
320 | _ip.run_line_magic("reset", "-f array") | |
321 | assert "a" not in _ip.user_ns |
|
321 | assert "a" not in _ip.user_ns | |
322 |
|
322 | |||
323 |
|
323 | |||
324 | def test_reset_out(): |
|
324 | def test_reset_out(): | |
325 | "Test '%reset out' magic" |
|
325 | "Test '%reset out' magic" | |
326 | _ip.run_cell("parrot = 'dead'", store_history=True) |
|
326 | _ip.run_cell("parrot = 'dead'", store_history=True) | |
327 | # test '%reset -f out', make an Out prompt |
|
327 | # test '%reset -f out', make an Out prompt | |
328 | _ip.run_cell("parrot", store_history=True) |
|
328 | _ip.run_cell("parrot", store_history=True) | |
329 | assert "dead" in [_ip.user_ns[x] for x in ("_", "__", "___")] |
|
329 | assert "dead" in [_ip.user_ns[x] for x in ("_", "__", "___")] | |
330 | _ip.run_line_magic("reset", "-f out") |
|
330 | _ip.run_line_magic("reset", "-f out") | |
331 | assert "dead" not in [_ip.user_ns[x] for x in ("_", "__", "___")] |
|
331 | assert "dead" not in [_ip.user_ns[x] for x in ("_", "__", "___")] | |
332 | assert len(_ip.user_ns["Out"]) == 0 |
|
332 | assert len(_ip.user_ns["Out"]) == 0 | |
333 |
|
333 | |||
334 |
|
334 | |||
335 | def test_reset_in(): |
|
335 | def test_reset_in(): | |
336 | "Test '%reset in' magic" |
|
336 | "Test '%reset in' magic" | |
337 | # test '%reset -f in' |
|
337 | # test '%reset -f in' | |
338 | _ip.run_cell("parrot", store_history=True) |
|
338 | _ip.run_cell("parrot", store_history=True) | |
339 | assert "parrot" in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")] |
|
339 | assert "parrot" in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")] | |
340 | _ip.run_line_magic("reset", "-f in") |
|
340 | _ip.run_line_magic("reset", "-f in") | |
341 | assert "parrot" not in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")] |
|
341 | assert "parrot" not in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")] | |
342 | assert len(set(_ip.user_ns["In"])) == 1 |
|
342 | assert len(set(_ip.user_ns["In"])) == 1 | |
343 |
|
343 | |||
344 |
|
344 | |||
345 | def test_reset_dhist(): |
|
345 | def test_reset_dhist(): | |
346 | "Test '%reset dhist' magic" |
|
346 | "Test '%reset dhist' magic" | |
347 | _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing |
|
347 | _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing | |
348 | _ip.run_line_magic("cd", os.path.dirname(pytest.__file__)) |
|
348 | _ip.run_line_magic("cd", os.path.dirname(pytest.__file__)) | |
349 | _ip.run_line_magic("cd", "-") |
|
349 | _ip.run_line_magic("cd", "-") | |
350 | assert len(_ip.user_ns["_dh"]) > 0 |
|
350 | assert len(_ip.user_ns["_dh"]) > 0 | |
351 | _ip.run_line_magic("reset", "-f dhist") |
|
351 | _ip.run_line_magic("reset", "-f dhist") | |
352 | assert len(_ip.user_ns["_dh"]) == 0 |
|
352 | assert len(_ip.user_ns["_dh"]) == 0 | |
353 | _ip.run_cell("_dh = [d for d in tmp]") # restore |
|
353 | _ip.run_cell("_dh = [d for d in tmp]") # restore | |
354 |
|
354 | |||
355 |
|
355 | |||
356 | def test_reset_in_length(): |
|
356 | def test_reset_in_length(): | |
357 | "Test that '%reset in' preserves In[] length" |
|
357 | "Test that '%reset in' preserves In[] length" | |
358 | _ip.run_cell("print 'foo'") |
|
358 | _ip.run_cell("print 'foo'") | |
359 | _ip.run_cell("reset -f in") |
|
359 | _ip.run_cell("reset -f in") | |
360 | assert len(_ip.user_ns["In"]) == _ip.displayhook.prompt_count + 1 |
|
360 | assert len(_ip.user_ns["In"]) == _ip.displayhook.prompt_count + 1 | |
361 |
|
361 | |||
362 |
|
362 | |||
363 | class TestResetErrors(TestCase): |
|
363 | class TestResetErrors(TestCase): | |
364 |
|
364 | |||
365 | def test_reset_redefine(self): |
|
365 | def test_reset_redefine(self): | |
366 |
|
366 | |||
367 | @magics_class |
|
367 | @magics_class | |
368 | class KernelMagics(Magics): |
|
368 | class KernelMagics(Magics): | |
369 | @line_magic |
|
369 | @line_magic | |
370 | def less(self, shell): pass |
|
370 | def less(self, shell): pass | |
371 |
|
371 | |||
372 | _ip.register_magics(KernelMagics) |
|
372 | _ip.register_magics(KernelMagics) | |
373 |
|
373 | |||
374 | with self.assertLogs() as cm: |
|
374 | with self.assertLogs() as cm: | |
375 | # hack, we want to just capture logs, but assertLogs fails if not |
|
375 | # hack, we want to just capture logs, but assertLogs fails if not | |
376 | # logs get produce. |
|
376 | # logs get produce. | |
377 | # so log one things we ignore. |
|
377 | # so log one things we ignore. | |
378 | import logging as log_mod |
|
378 | import logging as log_mod | |
379 | log = log_mod.getLogger() |
|
379 | log = log_mod.getLogger() | |
380 | log.info('Nothing') |
|
380 | log.info('Nothing') | |
381 | # end hack. |
|
381 | # end hack. | |
382 | _ip.run_cell("reset -f") |
|
382 | _ip.run_cell("reset -f") | |
383 |
|
383 | |||
384 | assert len(cm.output) == 1 |
|
384 | assert len(cm.output) == 1 | |
385 | for out in cm.output: |
|
385 | for out in cm.output: | |
386 | assert "Invalid alias" not in out |
|
386 | assert "Invalid alias" not in out | |
387 |
|
387 | |||
388 | def test_tb_syntaxerror(): |
|
388 | def test_tb_syntaxerror(): | |
389 | """test %tb after a SyntaxError""" |
|
389 | """test %tb after a SyntaxError""" | |
390 | ip = get_ipython() |
|
390 | ip = get_ipython() | |
391 | ip.run_cell("for") |
|
391 | ip.run_cell("for") | |
392 |
|
392 | |||
393 | # trap and validate stdout |
|
393 | # trap and validate stdout | |
394 | save_stdout = sys.stdout |
|
394 | save_stdout = sys.stdout | |
395 | try: |
|
395 | try: | |
396 | sys.stdout = StringIO() |
|
396 | sys.stdout = StringIO() | |
397 | ip.run_cell("%tb") |
|
397 | ip.run_cell("%tb") | |
398 | out = sys.stdout.getvalue() |
|
398 | out = sys.stdout.getvalue() | |
399 | finally: |
|
399 | finally: | |
400 | sys.stdout = save_stdout |
|
400 | sys.stdout = save_stdout | |
401 | # trim output, and only check the last line |
|
401 | # trim output, and only check the last line | |
402 | last_line = out.rstrip().splitlines()[-1].strip() |
|
402 | last_line = out.rstrip().splitlines()[-1].strip() | |
403 | assert last_line == "SyntaxError: invalid syntax" |
|
403 | assert last_line == "SyntaxError: invalid syntax" | |
404 |
|
404 | |||
405 |
|
405 | |||
406 | def test_time(): |
|
406 | def test_time(): | |
407 | ip = get_ipython() |
|
407 | ip = get_ipython() | |
408 |
|
408 | |||
409 | with tt.AssertPrints("Wall time: "): |
|
409 | with tt.AssertPrints("Wall time: "): | |
410 | ip.run_cell("%time None") |
|
410 | ip.run_cell("%time None") | |
411 |
|
411 | |||
412 | ip.run_cell("def f(kmjy):\n" |
|
412 | ip.run_cell("def f(kmjy):\n" | |
413 | " %time print (2*kmjy)") |
|
413 | " %time print (2*kmjy)") | |
414 |
|
414 | |||
415 | with tt.AssertPrints("Wall time: "): |
|
415 | with tt.AssertPrints("Wall time: "): | |
416 | with tt.AssertPrints("hihi", suppress=False): |
|
416 | with tt.AssertPrints("hihi", suppress=False): | |
417 | ip.run_cell("f('hi')") |
|
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 | def test_time_last_not_expression(): |
|
478 | def test_time_last_not_expression(): | |
420 | ip.run_cell("%%time\n" |
|
479 | ip.run_cell("%%time\n" | |
421 | "var_1 = 1\n" |
|
480 | "var_1 = 1\n" | |
422 | "var_2 = 2\n") |
|
481 | "var_2 = 2\n") | |
423 | assert ip.user_ns['var_1'] == 1 |
|
482 | assert ip.user_ns['var_1'] == 1 | |
424 | del ip.user_ns['var_1'] |
|
483 | del ip.user_ns['var_1'] | |
425 | assert ip.user_ns['var_2'] == 2 |
|
484 | assert ip.user_ns['var_2'] == 2 | |
426 | del ip.user_ns['var_2'] |
|
485 | del ip.user_ns['var_2'] | |
427 |
|
486 | |||
428 |
|
487 | |||
429 | @dec.skip_win32 |
|
488 | @dec.skip_win32 | |
430 | def test_time2(): |
|
489 | def test_time2(): | |
431 | ip = get_ipython() |
|
490 | ip = get_ipython() | |
432 |
|
491 | |||
433 | with tt.AssertPrints("CPU times: user "): |
|
492 | with tt.AssertPrints("CPU times: user "): | |
434 | ip.run_cell("%time None") |
|
493 | ip.run_cell("%time None") | |
435 |
|
494 | |||
436 | def test_time3(): |
|
495 | def test_time3(): | |
437 | """Erroneous magic function calls, issue gh-3334""" |
|
496 | """Erroneous magic function calls, issue gh-3334""" | |
438 | ip = get_ipython() |
|
497 | ip = get_ipython() | |
439 | ip.user_ns.pop('run', None) |
|
498 | ip.user_ns.pop('run', None) | |
440 |
|
499 | |||
441 | with tt.AssertNotPrints("not found", channel='stderr'): |
|
500 | with tt.AssertNotPrints("not found", channel='stderr'): | |
442 | ip.run_cell("%%time\n" |
|
501 | ip.run_cell("%%time\n" | |
443 | "run = 0\n" |
|
502 | "run = 0\n" | |
444 | "run += 1") |
|
503 | "run += 1") | |
445 |
|
504 | |||
446 | def test_multiline_time(): |
|
505 | def test_multiline_time(): | |
447 | """Make sure last statement from time return a value.""" |
|
506 | """Make sure last statement from time return a value.""" | |
448 | ip = get_ipython() |
|
507 | ip = get_ipython() | |
449 | ip.user_ns.pop('run', None) |
|
508 | ip.user_ns.pop('run', None) | |
450 |
|
509 | |||
451 | ip.run_cell( |
|
510 | ip.run_cell( | |
452 | dedent( |
|
511 | dedent( | |
453 | """\ |
|
512 | """\ | |
454 | %%time |
|
513 | %%time | |
455 | a = "ho" |
|
514 | a = "ho" | |
456 | b = "hey" |
|
515 | b = "hey" | |
457 | a+b |
|
516 | a+b | |
458 | """ |
|
517 | """ | |
459 | ) |
|
518 | ) | |
460 | ) |
|
519 | ) | |
461 | assert ip.user_ns_hidden["_"] == "hohey" |
|
520 | assert ip.user_ns_hidden["_"] == "hohey" | |
462 |
|
521 | |||
463 |
|
522 | |||
464 | def test_time_local_ns(): |
|
523 | def test_time_local_ns(): | |
465 | """ |
|
524 | """ | |
466 | Test that local_ns is actually global_ns when running a cell magic |
|
525 | Test that local_ns is actually global_ns when running a cell magic | |
467 | """ |
|
526 | """ | |
468 | ip = get_ipython() |
|
527 | ip = get_ipython() | |
469 | ip.run_cell("%%time\n" "myvar = 1") |
|
528 | ip.run_cell("%%time\n" "myvar = 1") | |
470 | assert ip.user_ns["myvar"] == 1 |
|
529 | assert ip.user_ns["myvar"] == 1 | |
471 | del ip.user_ns["myvar"] |
|
530 | del ip.user_ns["myvar"] | |
472 |
|
531 | |||
473 |
|
532 | |||
474 | def test_doctest_mode(): |
|
533 | def test_doctest_mode(): | |
475 | "Toggle doctest_mode twice, it should be a no-op and run without error" |
|
534 | "Toggle doctest_mode twice, it should be a no-op and run without error" | |
476 | _ip.run_line_magic("doctest_mode", "") |
|
535 | _ip.run_line_magic("doctest_mode", "") | |
477 | _ip.run_line_magic("doctest_mode", "") |
|
536 | _ip.run_line_magic("doctest_mode", "") | |
478 |
|
537 | |||
479 |
|
538 | |||
480 | def test_parse_options(): |
|
539 | def test_parse_options(): | |
481 | """Tests for basic options parsing in magics.""" |
|
540 | """Tests for basic options parsing in magics.""" | |
482 | # These are only the most minimal of tests, more should be added later. At |
|
541 | # These are only the most minimal of tests, more should be added later. At | |
483 | # the very least we check that basic text/unicode calls work OK. |
|
542 | # the very least we check that basic text/unicode calls work OK. | |
484 | m = DummyMagics(_ip) |
|
543 | m = DummyMagics(_ip) | |
485 | assert m.parse_options("foo", "")[1] == "foo" |
|
544 | assert m.parse_options("foo", "")[1] == "foo" | |
486 | assert m.parse_options("foo", "")[1] == "foo" |
|
545 | assert m.parse_options("foo", "")[1] == "foo" | |
487 |
|
546 | |||
488 |
|
547 | |||
489 | def test_parse_options_preserve_non_option_string(): |
|
548 | def test_parse_options_preserve_non_option_string(): | |
490 | """Test to assert preservation of non-option part of magic-block, while parsing magic options.""" |
|
549 | """Test to assert preservation of non-option part of magic-block, while parsing magic options.""" | |
491 | m = DummyMagics(_ip) |
|
550 | m = DummyMagics(_ip) | |
492 | opts, stmt = m.parse_options( |
|
551 | opts, stmt = m.parse_options( | |
493 | " -n1 -r 13 _ = 314 + foo", "n:r:", preserve_non_opts=True |
|
552 | " -n1 -r 13 _ = 314 + foo", "n:r:", preserve_non_opts=True | |
494 | ) |
|
553 | ) | |
495 | assert opts == {"n": "1", "r": "13"} |
|
554 | assert opts == {"n": "1", "r": "13"} | |
496 | assert stmt == "_ = 314 + foo" |
|
555 | assert stmt == "_ = 314 + foo" | |
497 |
|
556 | |||
498 |
|
557 | |||
499 | def test_run_magic_preserve_code_block(): |
|
558 | def test_run_magic_preserve_code_block(): | |
500 | """Test to assert preservation of non-option part of magic-block, while running magic.""" |
|
559 | """Test to assert preservation of non-option part of magic-block, while running magic.""" | |
501 | _ip.user_ns["spaces"] = [] |
|
560 | _ip.user_ns["spaces"] = [] | |
502 | _ip.run_line_magic( |
|
561 | _ip.run_line_magic( | |
503 | "timeit", "-n1 -r1 spaces.append([s.count(' ') for s in ['document']])" |
|
562 | "timeit", "-n1 -r1 spaces.append([s.count(' ') for s in ['document']])" | |
504 | ) |
|
563 | ) | |
505 | assert _ip.user_ns["spaces"] == [[0]] |
|
564 | assert _ip.user_ns["spaces"] == [[0]] | |
506 |
|
565 | |||
507 |
|
566 | |||
508 | def test_dirops(): |
|
567 | def test_dirops(): | |
509 | """Test various directory handling operations.""" |
|
568 | """Test various directory handling operations.""" | |
510 | # curpath = lambda :os.path.splitdrive(os.getcwd())[1].replace('\\','/') |
|
569 | # curpath = lambda :os.path.splitdrive(os.getcwd())[1].replace('\\','/') | |
511 | curpath = os.getcwd |
|
570 | curpath = os.getcwd | |
512 | startdir = os.getcwd() |
|
571 | startdir = os.getcwd() | |
513 | ipdir = os.path.realpath(_ip.ipython_dir) |
|
572 | ipdir = os.path.realpath(_ip.ipython_dir) | |
514 | try: |
|
573 | try: | |
515 | _ip.run_line_magic("cd", '"%s"' % ipdir) |
|
574 | _ip.run_line_magic("cd", '"%s"' % ipdir) | |
516 | assert curpath() == ipdir |
|
575 | assert curpath() == ipdir | |
517 | _ip.run_line_magic("cd", "-") |
|
576 | _ip.run_line_magic("cd", "-") | |
518 | assert curpath() == startdir |
|
577 | assert curpath() == startdir | |
519 | _ip.run_line_magic("pushd", '"%s"' % ipdir) |
|
578 | _ip.run_line_magic("pushd", '"%s"' % ipdir) | |
520 | assert curpath() == ipdir |
|
579 | assert curpath() == ipdir | |
521 | _ip.run_line_magic("popd", "") |
|
580 | _ip.run_line_magic("popd", "") | |
522 | assert curpath() == startdir |
|
581 | assert curpath() == startdir | |
523 | finally: |
|
582 | finally: | |
524 | os.chdir(startdir) |
|
583 | os.chdir(startdir) | |
525 |
|
584 | |||
526 |
|
585 | |||
527 | def test_cd_force_quiet(): |
|
586 | def test_cd_force_quiet(): | |
528 | """Test OSMagics.cd_force_quiet option""" |
|
587 | """Test OSMagics.cd_force_quiet option""" | |
529 | _ip.config.OSMagics.cd_force_quiet = True |
|
588 | _ip.config.OSMagics.cd_force_quiet = True | |
530 | osmagics = osm.OSMagics(shell=_ip) |
|
589 | osmagics = osm.OSMagics(shell=_ip) | |
531 |
|
590 | |||
532 | startdir = os.getcwd() |
|
591 | startdir = os.getcwd() | |
533 | ipdir = os.path.realpath(_ip.ipython_dir) |
|
592 | ipdir = os.path.realpath(_ip.ipython_dir) | |
534 |
|
593 | |||
535 | try: |
|
594 | try: | |
536 | with tt.AssertNotPrints(ipdir): |
|
595 | with tt.AssertNotPrints(ipdir): | |
537 | osmagics.cd('"%s"' % ipdir) |
|
596 | osmagics.cd('"%s"' % ipdir) | |
538 | with tt.AssertNotPrints(startdir): |
|
597 | with tt.AssertNotPrints(startdir): | |
539 | osmagics.cd('-') |
|
598 | osmagics.cd('-') | |
540 | finally: |
|
599 | finally: | |
541 | os.chdir(startdir) |
|
600 | os.chdir(startdir) | |
542 |
|
601 | |||
543 |
|
602 | |||
544 | def test_xmode(): |
|
603 | def test_xmode(): | |
545 | # Calling xmode three times should be a no-op |
|
604 | # Calling xmode three times should be a no-op | |
546 | xmode = _ip.InteractiveTB.mode |
|
605 | xmode = _ip.InteractiveTB.mode | |
547 | for i in range(4): |
|
606 | for i in range(4): | |
548 | _ip.run_line_magic("xmode", "") |
|
607 | _ip.run_line_magic("xmode", "") | |
549 | assert _ip.InteractiveTB.mode == xmode |
|
608 | assert _ip.InteractiveTB.mode == xmode | |
550 |
|
609 | |||
551 | def test_reset_hard(): |
|
610 | def test_reset_hard(): | |
552 | monitor = [] |
|
611 | monitor = [] | |
553 | class A(object): |
|
612 | class A(object): | |
554 | def __del__(self): |
|
613 | def __del__(self): | |
555 | monitor.append(1) |
|
614 | monitor.append(1) | |
556 | def __repr__(self): |
|
615 | def __repr__(self): | |
557 | return "<A instance>" |
|
616 | return "<A instance>" | |
558 |
|
617 | |||
559 | _ip.user_ns["a"] = A() |
|
618 | _ip.user_ns["a"] = A() | |
560 | _ip.run_cell("a") |
|
619 | _ip.run_cell("a") | |
561 |
|
620 | |||
562 | assert monitor == [] |
|
621 | assert monitor == [] | |
563 | _ip.run_line_magic("reset", "-f") |
|
622 | _ip.run_line_magic("reset", "-f") | |
564 | assert monitor == [1] |
|
623 | assert monitor == [1] | |
565 |
|
624 | |||
566 | class TestXdel(tt.TempFileMixin): |
|
625 | class TestXdel(tt.TempFileMixin): | |
567 | def test_xdel(self): |
|
626 | def test_xdel(self): | |
568 | """Test that references from %run are cleared by xdel.""" |
|
627 | """Test that references from %run are cleared by xdel.""" | |
569 | src = ("class A(object):\n" |
|
628 | src = ("class A(object):\n" | |
570 | " monitor = []\n" |
|
629 | " monitor = []\n" | |
571 | " def __del__(self):\n" |
|
630 | " def __del__(self):\n" | |
572 | " self.monitor.append(1)\n" |
|
631 | " self.monitor.append(1)\n" | |
573 | "a = A()\n") |
|
632 | "a = A()\n") | |
574 | self.mktmp(src) |
|
633 | self.mktmp(src) | |
575 | # %run creates some hidden references... |
|
634 | # %run creates some hidden references... | |
576 | _ip.run_line_magic("run", "%s" % self.fname) |
|
635 | _ip.run_line_magic("run", "%s" % self.fname) | |
577 | # ... as does the displayhook. |
|
636 | # ... as does the displayhook. | |
578 | _ip.run_cell("a") |
|
637 | _ip.run_cell("a") | |
579 |
|
638 | |||
580 | monitor = _ip.user_ns["A"].monitor |
|
639 | monitor = _ip.user_ns["A"].monitor | |
581 | assert monitor == [] |
|
640 | assert monitor == [] | |
582 |
|
641 | |||
583 | _ip.run_line_magic("xdel", "a") |
|
642 | _ip.run_line_magic("xdel", "a") | |
584 |
|
643 | |||
585 | # Check that a's __del__ method has been called. |
|
644 | # Check that a's __del__ method has been called. | |
586 | gc.collect(0) |
|
645 | gc.collect(0) | |
587 | assert monitor == [1] |
|
646 | assert monitor == [1] | |
588 |
|
647 | |||
589 | def doctest_who(): |
|
648 | def doctest_who(): | |
590 | """doctest for %who |
|
649 | """doctest for %who | |
591 |
|
650 | |||
592 | In [1]: %reset -sf |
|
651 | In [1]: %reset -sf | |
593 |
|
652 | |||
594 | In [2]: alpha = 123 |
|
653 | In [2]: alpha = 123 | |
595 |
|
654 | |||
596 | In [3]: beta = 'beta' |
|
655 | In [3]: beta = 'beta' | |
597 |
|
656 | |||
598 | In [4]: %who int |
|
657 | In [4]: %who int | |
599 | alpha |
|
658 | alpha | |
600 |
|
659 | |||
601 | In [5]: %who str |
|
660 | In [5]: %who str | |
602 | beta |
|
661 | beta | |
603 |
|
662 | |||
604 | In [6]: %whos |
|
663 | In [6]: %whos | |
605 | Variable Type Data/Info |
|
664 | Variable Type Data/Info | |
606 | ---------------------------- |
|
665 | ---------------------------- | |
607 | alpha int 123 |
|
666 | alpha int 123 | |
608 | beta str beta |
|
667 | beta str beta | |
609 |
|
668 | |||
610 | In [7]: %who_ls |
|
669 | In [7]: %who_ls | |
611 | Out[7]: ['alpha', 'beta'] |
|
670 | Out[7]: ['alpha', 'beta'] | |
612 | """ |
|
671 | """ | |
613 |
|
672 | |||
614 | def test_whos(): |
|
673 | def test_whos(): | |
615 | """Check that whos is protected against objects where repr() fails.""" |
|
674 | """Check that whos is protected against objects where repr() fails.""" | |
616 | class A(object): |
|
675 | class A(object): | |
617 | def __repr__(self): |
|
676 | def __repr__(self): | |
618 | raise Exception() |
|
677 | raise Exception() | |
619 | _ip.user_ns['a'] = A() |
|
678 | _ip.user_ns['a'] = A() | |
620 | _ip.run_line_magic("whos", "") |
|
679 | _ip.run_line_magic("whos", "") | |
621 |
|
680 | |||
622 | def doctest_precision(): |
|
681 | def doctest_precision(): | |
623 | """doctest for %precision |
|
682 | """doctest for %precision | |
624 |
|
683 | |||
625 | In [1]: f = get_ipython().display_formatter.formatters['text/plain'] |
|
684 | In [1]: f = get_ipython().display_formatter.formatters['text/plain'] | |
626 |
|
685 | |||
627 | In [2]: %precision 5 |
|
686 | In [2]: %precision 5 | |
628 | Out[2]: '%.5f' |
|
687 | Out[2]: '%.5f' | |
629 |
|
688 | |||
630 | In [3]: f.float_format |
|
689 | In [3]: f.float_format | |
631 | Out[3]: '%.5f' |
|
690 | Out[3]: '%.5f' | |
632 |
|
691 | |||
633 | In [4]: %precision %e |
|
692 | In [4]: %precision %e | |
634 | Out[4]: '%e' |
|
693 | Out[4]: '%e' | |
635 |
|
694 | |||
636 | In [5]: f(3.1415927) |
|
695 | In [5]: f(3.1415927) | |
637 | Out[5]: '3.141593e+00' |
|
696 | Out[5]: '3.141593e+00' | |
638 | """ |
|
697 | """ | |
639 |
|
698 | |||
640 | def test_debug_magic(): |
|
699 | def test_debug_magic(): | |
641 | """Test debugging a small code with %debug |
|
700 | """Test debugging a small code with %debug | |
642 |
|
701 | |||
643 | In [1]: with PdbTestInput(['c']): |
|
702 | In [1]: with PdbTestInput(['c']): | |
644 | ...: %debug print("a b") #doctest: +ELLIPSIS |
|
703 | ...: %debug print("a b") #doctest: +ELLIPSIS | |
645 | ...: |
|
704 | ...: | |
646 | ... |
|
705 | ... | |
647 | ipdb> c |
|
706 | ipdb> c | |
648 | a b |
|
707 | a b | |
649 | In [2]: |
|
708 | In [2]: | |
650 | """ |
|
709 | """ | |
651 |
|
710 | |||
652 | def test_psearch(): |
|
711 | def test_psearch(): | |
653 | with tt.AssertPrints("dict.fromkeys"): |
|
712 | with tt.AssertPrints("dict.fromkeys"): | |
654 | _ip.run_cell("dict.fr*?") |
|
713 | _ip.run_cell("dict.fr*?") | |
655 | with tt.AssertPrints("π.is_integer"): |
|
714 | with tt.AssertPrints("π.is_integer"): | |
656 | _ip.run_cell("π = 3.14;\nπ.is_integ*?") |
|
715 | _ip.run_cell("π = 3.14;\nπ.is_integ*?") | |
657 |
|
716 | |||
658 | def test_timeit_shlex(): |
|
717 | def test_timeit_shlex(): | |
659 | """test shlex issues with timeit (#1109)""" |
|
718 | """test shlex issues with timeit (#1109)""" | |
660 | _ip.ex("def f(*a,**kw): pass") |
|
719 | _ip.ex("def f(*a,**kw): pass") | |
661 | _ip.run_line_magic("timeit", '-n1 "this is a bug".count(" ")') |
|
720 | _ip.run_line_magic("timeit", '-n1 "this is a bug".count(" ")') | |
662 | _ip.run_line_magic("timeit", '-r1 -n1 f(" ", 1)') |
|
721 | _ip.run_line_magic("timeit", '-r1 -n1 f(" ", 1)') | |
663 | _ip.run_line_magic("timeit", '-r1 -n1 f(" ", 1, " ", 2, " ")') |
|
722 | _ip.run_line_magic("timeit", '-r1 -n1 f(" ", 1, " ", 2, " ")') | |
664 | _ip.run_line_magic("timeit", '-r1 -n1 ("a " + "b")') |
|
723 | _ip.run_line_magic("timeit", '-r1 -n1 ("a " + "b")') | |
665 | _ip.run_line_magic("timeit", '-r1 -n1 f("a " + "b")') |
|
724 | _ip.run_line_magic("timeit", '-r1 -n1 f("a " + "b")') | |
666 | _ip.run_line_magic("timeit", '-r1 -n1 f("a " + "b ")') |
|
725 | _ip.run_line_magic("timeit", '-r1 -n1 f("a " + "b ")') | |
667 |
|
726 | |||
668 |
|
727 | |||
669 | def test_timeit_special_syntax(): |
|
728 | def test_timeit_special_syntax(): | |
670 | "Test %%timeit with IPython special syntax" |
|
729 | "Test %%timeit with IPython special syntax" | |
671 | @register_line_magic |
|
730 | @register_line_magic | |
672 | def lmagic(line): |
|
731 | def lmagic(line): | |
673 | ip = get_ipython() |
|
732 | ip = get_ipython() | |
674 | ip.user_ns['lmagic_out'] = line |
|
733 | ip.user_ns['lmagic_out'] = line | |
675 |
|
734 | |||
676 | # line mode test |
|
735 | # line mode test | |
677 | _ip.run_line_magic("timeit", "-n1 -r1 %lmagic my line") |
|
736 | _ip.run_line_magic("timeit", "-n1 -r1 %lmagic my line") | |
678 | assert _ip.user_ns["lmagic_out"] == "my line" |
|
737 | assert _ip.user_ns["lmagic_out"] == "my line" | |
679 | # cell mode test |
|
738 | # cell mode test | |
680 | _ip.run_cell_magic("timeit", "-n1 -r1", "%lmagic my line2") |
|
739 | _ip.run_cell_magic("timeit", "-n1 -r1", "%lmagic my line2") | |
681 | assert _ip.user_ns["lmagic_out"] == "my line2" |
|
740 | assert _ip.user_ns["lmagic_out"] == "my line2" | |
682 |
|
741 | |||
683 |
|
742 | |||
684 | def test_timeit_return(): |
|
743 | def test_timeit_return(): | |
685 | """ |
|
744 | """ | |
686 | test whether timeit -o return object |
|
745 | test whether timeit -o return object | |
687 | """ |
|
746 | """ | |
688 |
|
747 | |||
689 | res = _ip.run_line_magic('timeit','-n10 -r10 -o 1') |
|
748 | res = _ip.run_line_magic('timeit','-n10 -r10 -o 1') | |
690 | assert(res is not None) |
|
749 | assert(res is not None) | |
691 |
|
750 | |||
692 | def test_timeit_quiet(): |
|
751 | def test_timeit_quiet(): | |
693 | """ |
|
752 | """ | |
694 | test quiet option of timeit magic |
|
753 | test quiet option of timeit magic | |
695 | """ |
|
754 | """ | |
696 | with tt.AssertNotPrints("loops"): |
|
755 | with tt.AssertNotPrints("loops"): | |
697 | _ip.run_cell("%timeit -n1 -r1 -q 1") |
|
756 | _ip.run_cell("%timeit -n1 -r1 -q 1") | |
698 |
|
757 | |||
699 | def test_timeit_return_quiet(): |
|
758 | def test_timeit_return_quiet(): | |
700 | with tt.AssertNotPrints("loops"): |
|
759 | with tt.AssertNotPrints("loops"): | |
701 | res = _ip.run_line_magic('timeit', '-n1 -r1 -q -o 1') |
|
760 | res = _ip.run_line_magic('timeit', '-n1 -r1 -q -o 1') | |
702 | assert (res is not None) |
|
761 | assert (res is not None) | |
703 |
|
762 | |||
704 | def test_timeit_invalid_return(): |
|
763 | def test_timeit_invalid_return(): | |
705 | with pytest.raises(SyntaxError): |
|
764 | with pytest.raises(SyntaxError): | |
706 | _ip.run_line_magic('timeit', 'return') |
|
765 | _ip.run_line_magic('timeit', 'return') | |
707 |
|
766 | |||
708 | @dec.skipif(execution.profile is None) |
|
767 | @dec.skipif(execution.profile is None) | |
709 | def test_prun_special_syntax(): |
|
768 | def test_prun_special_syntax(): | |
710 | "Test %%prun with IPython special syntax" |
|
769 | "Test %%prun with IPython special syntax" | |
711 | @register_line_magic |
|
770 | @register_line_magic | |
712 | def lmagic(line): |
|
771 | def lmagic(line): | |
713 | ip = get_ipython() |
|
772 | ip = get_ipython() | |
714 | ip.user_ns['lmagic_out'] = line |
|
773 | ip.user_ns['lmagic_out'] = line | |
715 |
|
774 | |||
716 | # line mode test |
|
775 | # line mode test | |
717 | _ip.run_line_magic("prun", "-q %lmagic my line") |
|
776 | _ip.run_line_magic("prun", "-q %lmagic my line") | |
718 | assert _ip.user_ns["lmagic_out"] == "my line" |
|
777 | assert _ip.user_ns["lmagic_out"] == "my line" | |
719 | # cell mode test |
|
778 | # cell mode test | |
720 | _ip.run_cell_magic("prun", "-q", "%lmagic my line2") |
|
779 | _ip.run_cell_magic("prun", "-q", "%lmagic my line2") | |
721 | assert _ip.user_ns["lmagic_out"] == "my line2" |
|
780 | assert _ip.user_ns["lmagic_out"] == "my line2" | |
722 |
|
781 | |||
723 |
|
782 | |||
724 | @dec.skipif(execution.profile is None) |
|
783 | @dec.skipif(execution.profile is None) | |
725 | def test_prun_quotes(): |
|
784 | def test_prun_quotes(): | |
726 | "Test that prun does not clobber string escapes (GH #1302)" |
|
785 | "Test that prun does not clobber string escapes (GH #1302)" | |
727 | _ip.magic(r"prun -q x = '\t'") |
|
786 | _ip.magic(r"prun -q x = '\t'") | |
728 | assert _ip.user_ns["x"] == "\t" |
|
787 | assert _ip.user_ns["x"] == "\t" | |
729 |
|
788 | |||
730 |
|
789 | |||
731 | def test_extension(): |
|
790 | def test_extension(): | |
732 | # Debugging information for failures of this test |
|
791 | # Debugging information for failures of this test | |
733 | print('sys.path:') |
|
792 | print('sys.path:') | |
734 | for p in sys.path: |
|
793 | for p in sys.path: | |
735 | print(' ', p) |
|
794 | print(' ', p) | |
736 | print('CWD', os.getcwd()) |
|
795 | print('CWD', os.getcwd()) | |
737 |
|
796 | |||
738 | pytest.raises(ImportError, _ip.magic, "load_ext daft_extension") |
|
797 | pytest.raises(ImportError, _ip.magic, "load_ext daft_extension") | |
739 | daft_path = os.path.join(os.path.dirname(__file__), "daft_extension") |
|
798 | daft_path = os.path.join(os.path.dirname(__file__), "daft_extension") | |
740 | sys.path.insert(0, daft_path) |
|
799 | sys.path.insert(0, daft_path) | |
741 | try: |
|
800 | try: | |
742 | _ip.user_ns.pop('arq', None) |
|
801 | _ip.user_ns.pop('arq', None) | |
743 | invalidate_caches() # Clear import caches |
|
802 | invalidate_caches() # Clear import caches | |
744 | _ip.run_line_magic("load_ext", "daft_extension") |
|
803 | _ip.run_line_magic("load_ext", "daft_extension") | |
745 | assert _ip.user_ns["arq"] == 185 |
|
804 | assert _ip.user_ns["arq"] == 185 | |
746 | _ip.run_line_magic("unload_ext", "daft_extension") |
|
805 | _ip.run_line_magic("unload_ext", "daft_extension") | |
747 | assert 'arq' not in _ip.user_ns |
|
806 | assert 'arq' not in _ip.user_ns | |
748 | finally: |
|
807 | finally: | |
749 | sys.path.remove(daft_path) |
|
808 | sys.path.remove(daft_path) | |
750 |
|
809 | |||
751 |
|
810 | |||
752 | def test_notebook_export_json(): |
|
811 | def test_notebook_export_json(): | |
753 | pytest.importorskip("nbformat") |
|
812 | pytest.importorskip("nbformat") | |
754 | _ip = get_ipython() |
|
813 | _ip = get_ipython() | |
755 | _ip.history_manager.reset() # Clear any existing history. |
|
814 | _ip.history_manager.reset() # Clear any existing history. | |
756 | cmds = ["a=1", "def b():\n return a**2", "print('noël, été', b())"] |
|
815 | cmds = ["a=1", "def b():\n return a**2", "print('noël, été', b())"] | |
757 | for i, cmd in enumerate(cmds, start=1): |
|
816 | for i, cmd in enumerate(cmds, start=1): | |
758 | _ip.history_manager.store_inputs(i, cmd) |
|
817 | _ip.history_manager.store_inputs(i, cmd) | |
759 | with TemporaryDirectory() as td: |
|
818 | with TemporaryDirectory() as td: | |
760 | outfile = os.path.join(td, "nb.ipynb") |
|
819 | outfile = os.path.join(td, "nb.ipynb") | |
761 | _ip.run_line_magic("notebook", "%s" % outfile) |
|
820 | _ip.run_line_magic("notebook", "%s" % outfile) | |
762 |
|
821 | |||
763 |
|
822 | |||
764 | class TestEnv(TestCase): |
|
823 | class TestEnv(TestCase): | |
765 |
|
824 | |||
766 | def test_env(self): |
|
825 | def test_env(self): | |
767 | env = _ip.run_line_magic("env", "") |
|
826 | env = _ip.run_line_magic("env", "") | |
768 | self.assertTrue(isinstance(env, dict)) |
|
827 | self.assertTrue(isinstance(env, dict)) | |
769 |
|
828 | |||
770 | def test_env_secret(self): |
|
829 | def test_env_secret(self): | |
771 | env = _ip.run_line_magic("env", "") |
|
830 | env = _ip.run_line_magic("env", "") | |
772 | hidden = "<hidden>" |
|
831 | hidden = "<hidden>" | |
773 | with mock.patch.dict( |
|
832 | with mock.patch.dict( | |
774 | os.environ, |
|
833 | os.environ, | |
775 | { |
|
834 | { | |
776 | "API_KEY": "abc123", |
|
835 | "API_KEY": "abc123", | |
777 | "SECRET_THING": "ssshhh", |
|
836 | "SECRET_THING": "ssshhh", | |
778 | "JUPYTER_TOKEN": "", |
|
837 | "JUPYTER_TOKEN": "", | |
779 | "VAR": "abc" |
|
838 | "VAR": "abc" | |
780 | } |
|
839 | } | |
781 | ): |
|
840 | ): | |
782 | env = _ip.run_line_magic("env", "") |
|
841 | env = _ip.run_line_magic("env", "") | |
783 | assert env["API_KEY"] == hidden |
|
842 | assert env["API_KEY"] == hidden | |
784 | assert env["SECRET_THING"] == hidden |
|
843 | assert env["SECRET_THING"] == hidden | |
785 | assert env["JUPYTER_TOKEN"] == hidden |
|
844 | assert env["JUPYTER_TOKEN"] == hidden | |
786 | assert env["VAR"] == "abc" |
|
845 | assert env["VAR"] == "abc" | |
787 |
|
846 | |||
788 | def test_env_get_set_simple(self): |
|
847 | def test_env_get_set_simple(self): | |
789 | env = _ip.run_line_magic("env", "var val1") |
|
848 | env = _ip.run_line_magic("env", "var val1") | |
790 | self.assertEqual(env, None) |
|
849 | self.assertEqual(env, None) | |
791 | self.assertEqual(os.environ["var"], "val1") |
|
850 | self.assertEqual(os.environ["var"], "val1") | |
792 | self.assertEqual(_ip.run_line_magic("env", "var"), "val1") |
|
851 | self.assertEqual(_ip.run_line_magic("env", "var"), "val1") | |
793 | env = _ip.run_line_magic("env", "var=val2") |
|
852 | env = _ip.run_line_magic("env", "var=val2") | |
794 | self.assertEqual(env, None) |
|
853 | self.assertEqual(env, None) | |
795 | self.assertEqual(os.environ['var'], 'val2') |
|
854 | self.assertEqual(os.environ['var'], 'val2') | |
796 |
|
855 | |||
797 | def test_env_get_set_complex(self): |
|
856 | def test_env_get_set_complex(self): | |
798 | env = _ip.run_line_magic("env", "var 'val1 '' 'val2") |
|
857 | env = _ip.run_line_magic("env", "var 'val1 '' 'val2") | |
799 | self.assertEqual(env, None) |
|
858 | self.assertEqual(env, None) | |
800 | self.assertEqual(os.environ['var'], "'val1 '' 'val2") |
|
859 | self.assertEqual(os.environ['var'], "'val1 '' 'val2") | |
801 | self.assertEqual(_ip.run_line_magic("env", "var"), "'val1 '' 'val2") |
|
860 | self.assertEqual(_ip.run_line_magic("env", "var"), "'val1 '' 'val2") | |
802 | env = _ip.run_line_magic("env", 'var=val2 val3="val4') |
|
861 | env = _ip.run_line_magic("env", 'var=val2 val3="val4') | |
803 | self.assertEqual(env, None) |
|
862 | self.assertEqual(env, None) | |
804 | self.assertEqual(os.environ['var'], 'val2 val3="val4') |
|
863 | self.assertEqual(os.environ['var'], 'val2 val3="val4') | |
805 |
|
864 | |||
806 | def test_env_set_bad_input(self): |
|
865 | def test_env_set_bad_input(self): | |
807 | self.assertRaises(UsageError, lambda: _ip.run_line_magic("set_env", "var")) |
|
866 | self.assertRaises(UsageError, lambda: _ip.run_line_magic("set_env", "var")) | |
808 |
|
867 | |||
809 | def test_env_set_whitespace(self): |
|
868 | def test_env_set_whitespace(self): | |
810 | self.assertRaises(UsageError, lambda: _ip.run_line_magic("env", "var A=B")) |
|
869 | self.assertRaises(UsageError, lambda: _ip.run_line_magic("env", "var A=B")) | |
811 |
|
870 | |||
812 |
|
871 | |||
813 | class CellMagicTestCase(TestCase): |
|
872 | class CellMagicTestCase(TestCase): | |
814 |
|
873 | |||
815 | def check_ident(self, magic): |
|
874 | def check_ident(self, magic): | |
816 | # Manually called, we get the result |
|
875 | # Manually called, we get the result | |
817 | out = _ip.run_cell_magic(magic, "a", "b") |
|
876 | out = _ip.run_cell_magic(magic, "a", "b") | |
818 | assert out == ("a", "b") |
|
877 | assert out == ("a", "b") | |
819 | # Via run_cell, it goes into the user's namespace via displayhook |
|
878 | # Via run_cell, it goes into the user's namespace via displayhook | |
820 | _ip.run_cell("%%" + magic + " c\nd\n") |
|
879 | _ip.run_cell("%%" + magic + " c\nd\n") | |
821 | assert _ip.user_ns["_"] == ("c", "d\n") |
|
880 | assert _ip.user_ns["_"] == ("c", "d\n") | |
822 |
|
881 | |||
823 | def test_cell_magic_func_deco(self): |
|
882 | def test_cell_magic_func_deco(self): | |
824 | "Cell magic using simple decorator" |
|
883 | "Cell magic using simple decorator" | |
825 | @register_cell_magic |
|
884 | @register_cell_magic | |
826 | def cellm(line, cell): |
|
885 | def cellm(line, cell): | |
827 | return line, cell |
|
886 | return line, cell | |
828 |
|
887 | |||
829 | self.check_ident('cellm') |
|
888 | self.check_ident('cellm') | |
830 |
|
889 | |||
831 | def test_cell_magic_reg(self): |
|
890 | def test_cell_magic_reg(self): | |
832 | "Cell magic manually registered" |
|
891 | "Cell magic manually registered" | |
833 | def cellm(line, cell): |
|
892 | def cellm(line, cell): | |
834 | return line, cell |
|
893 | return line, cell | |
835 |
|
894 | |||
836 | _ip.register_magic_function(cellm, 'cell', 'cellm2') |
|
895 | _ip.register_magic_function(cellm, 'cell', 'cellm2') | |
837 | self.check_ident('cellm2') |
|
896 | self.check_ident('cellm2') | |
838 |
|
897 | |||
839 | def test_cell_magic_class(self): |
|
898 | def test_cell_magic_class(self): | |
840 | "Cell magics declared via a class" |
|
899 | "Cell magics declared via a class" | |
841 | @magics_class |
|
900 | @magics_class | |
842 | class MyMagics(Magics): |
|
901 | class MyMagics(Magics): | |
843 |
|
902 | |||
844 | @cell_magic |
|
903 | @cell_magic | |
845 | def cellm3(self, line, cell): |
|
904 | def cellm3(self, line, cell): | |
846 | return line, cell |
|
905 | return line, cell | |
847 |
|
906 | |||
848 | _ip.register_magics(MyMagics) |
|
907 | _ip.register_magics(MyMagics) | |
849 | self.check_ident('cellm3') |
|
908 | self.check_ident('cellm3') | |
850 |
|
909 | |||
851 | def test_cell_magic_class2(self): |
|
910 | def test_cell_magic_class2(self): | |
852 | "Cell magics declared via a class, #2" |
|
911 | "Cell magics declared via a class, #2" | |
853 | @magics_class |
|
912 | @magics_class | |
854 | class MyMagics2(Magics): |
|
913 | class MyMagics2(Magics): | |
855 |
|
914 | |||
856 | @cell_magic('cellm4') |
|
915 | @cell_magic('cellm4') | |
857 | def cellm33(self, line, cell): |
|
916 | def cellm33(self, line, cell): | |
858 | return line, cell |
|
917 | return line, cell | |
859 |
|
918 | |||
860 | _ip.register_magics(MyMagics2) |
|
919 | _ip.register_magics(MyMagics2) | |
861 | self.check_ident('cellm4') |
|
920 | self.check_ident('cellm4') | |
862 | # Check that nothing is registered as 'cellm33' |
|
921 | # Check that nothing is registered as 'cellm33' | |
863 | c33 = _ip.find_cell_magic('cellm33') |
|
922 | c33 = _ip.find_cell_magic('cellm33') | |
864 | assert c33 == None |
|
923 | assert c33 == None | |
865 |
|
924 | |||
866 | def test_file(): |
|
925 | def test_file(): | |
867 | """Basic %%writefile""" |
|
926 | """Basic %%writefile""" | |
868 | ip = get_ipython() |
|
927 | ip = get_ipython() | |
869 | with TemporaryDirectory() as td: |
|
928 | with TemporaryDirectory() as td: | |
870 | fname = os.path.join(td, "file1") |
|
929 | fname = os.path.join(td, "file1") | |
871 | ip.run_cell_magic( |
|
930 | ip.run_cell_magic( | |
872 | "writefile", |
|
931 | "writefile", | |
873 | fname, |
|
932 | fname, | |
874 | "\n".join( |
|
933 | "\n".join( | |
875 | [ |
|
934 | [ | |
876 | "line1", |
|
935 | "line1", | |
877 | "line2", |
|
936 | "line2", | |
878 | ] |
|
937 | ] | |
879 | ), |
|
938 | ), | |
880 | ) |
|
939 | ) | |
881 | s = Path(fname).read_text(encoding="utf-8") |
|
940 | s = Path(fname).read_text(encoding="utf-8") | |
882 | assert "line1\n" in s |
|
941 | assert "line1\n" in s | |
883 | assert "line2" in s |
|
942 | assert "line2" in s | |
884 |
|
943 | |||
885 |
|
944 | |||
886 | @dec.skip_win32 |
|
945 | @dec.skip_win32 | |
887 | def test_file_single_quote(): |
|
946 | def test_file_single_quote(): | |
888 | """Basic %%writefile with embedded single quotes""" |
|
947 | """Basic %%writefile with embedded single quotes""" | |
889 | ip = get_ipython() |
|
948 | ip = get_ipython() | |
890 | with TemporaryDirectory() as td: |
|
949 | with TemporaryDirectory() as td: | |
891 | fname = os.path.join(td, "'file1'") |
|
950 | fname = os.path.join(td, "'file1'") | |
892 | ip.run_cell_magic( |
|
951 | ip.run_cell_magic( | |
893 | "writefile", |
|
952 | "writefile", | |
894 | fname, |
|
953 | fname, | |
895 | "\n".join( |
|
954 | "\n".join( | |
896 | [ |
|
955 | [ | |
897 | "line1", |
|
956 | "line1", | |
898 | "line2", |
|
957 | "line2", | |
899 | ] |
|
958 | ] | |
900 | ), |
|
959 | ), | |
901 | ) |
|
960 | ) | |
902 | s = Path(fname).read_text(encoding="utf-8") |
|
961 | s = Path(fname).read_text(encoding="utf-8") | |
903 | assert "line1\n" in s |
|
962 | assert "line1\n" in s | |
904 | assert "line2" in s |
|
963 | assert "line2" in s | |
905 |
|
964 | |||
906 |
|
965 | |||
907 | @dec.skip_win32 |
|
966 | @dec.skip_win32 | |
908 | def test_file_double_quote(): |
|
967 | def test_file_double_quote(): | |
909 | """Basic %%writefile with embedded double quotes""" |
|
968 | """Basic %%writefile with embedded double quotes""" | |
910 | ip = get_ipython() |
|
969 | ip = get_ipython() | |
911 | with TemporaryDirectory() as td: |
|
970 | with TemporaryDirectory() as td: | |
912 | fname = os.path.join(td, '"file1"') |
|
971 | fname = os.path.join(td, '"file1"') | |
913 | ip.run_cell_magic( |
|
972 | ip.run_cell_magic( | |
914 | "writefile", |
|
973 | "writefile", | |
915 | fname, |
|
974 | fname, | |
916 | "\n".join( |
|
975 | "\n".join( | |
917 | [ |
|
976 | [ | |
918 | "line1", |
|
977 | "line1", | |
919 | "line2", |
|
978 | "line2", | |
920 | ] |
|
979 | ] | |
921 | ), |
|
980 | ), | |
922 | ) |
|
981 | ) | |
923 | s = Path(fname).read_text(encoding="utf-8") |
|
982 | s = Path(fname).read_text(encoding="utf-8") | |
924 | assert "line1\n" in s |
|
983 | assert "line1\n" in s | |
925 | assert "line2" in s |
|
984 | assert "line2" in s | |
926 |
|
985 | |||
927 |
|
986 | |||
928 | def test_file_var_expand(): |
|
987 | def test_file_var_expand(): | |
929 | """%%writefile $filename""" |
|
988 | """%%writefile $filename""" | |
930 | ip = get_ipython() |
|
989 | ip = get_ipython() | |
931 | with TemporaryDirectory() as td: |
|
990 | with TemporaryDirectory() as td: | |
932 | fname = os.path.join(td, "file1") |
|
991 | fname = os.path.join(td, "file1") | |
933 | ip.user_ns["filename"] = fname |
|
992 | ip.user_ns["filename"] = fname | |
934 | ip.run_cell_magic( |
|
993 | ip.run_cell_magic( | |
935 | "writefile", |
|
994 | "writefile", | |
936 | "$filename", |
|
995 | "$filename", | |
937 | "\n".join( |
|
996 | "\n".join( | |
938 | [ |
|
997 | [ | |
939 | "line1", |
|
998 | "line1", | |
940 | "line2", |
|
999 | "line2", | |
941 | ] |
|
1000 | ] | |
942 | ), |
|
1001 | ), | |
943 | ) |
|
1002 | ) | |
944 | s = Path(fname).read_text(encoding="utf-8") |
|
1003 | s = Path(fname).read_text(encoding="utf-8") | |
945 | assert "line1\n" in s |
|
1004 | assert "line1\n" in s | |
946 | assert "line2" in s |
|
1005 | assert "line2" in s | |
947 |
|
1006 | |||
948 |
|
1007 | |||
949 | def test_file_unicode(): |
|
1008 | def test_file_unicode(): | |
950 | """%%writefile with unicode cell""" |
|
1009 | """%%writefile with unicode cell""" | |
951 | ip = get_ipython() |
|
1010 | ip = get_ipython() | |
952 | with TemporaryDirectory() as td: |
|
1011 | with TemporaryDirectory() as td: | |
953 | fname = os.path.join(td, 'file1') |
|
1012 | fname = os.path.join(td, 'file1') | |
954 | ip.run_cell_magic("writefile", fname, u'\n'.join([ |
|
1013 | ip.run_cell_magic("writefile", fname, u'\n'.join([ | |
955 | u'liné1', |
|
1014 | u'liné1', | |
956 | u'liné2', |
|
1015 | u'liné2', | |
957 | ])) |
|
1016 | ])) | |
958 | with io.open(fname, encoding='utf-8') as f: |
|
1017 | with io.open(fname, encoding='utf-8') as f: | |
959 | s = f.read() |
|
1018 | s = f.read() | |
960 | assert "liné1\n" in s |
|
1019 | assert "liné1\n" in s | |
961 | assert "liné2" in s |
|
1020 | assert "liné2" in s | |
962 |
|
1021 | |||
963 |
|
1022 | |||
964 | def test_file_amend(): |
|
1023 | def test_file_amend(): | |
965 | """%%writefile -a amends files""" |
|
1024 | """%%writefile -a amends files""" | |
966 | ip = get_ipython() |
|
1025 | ip = get_ipython() | |
967 | with TemporaryDirectory() as td: |
|
1026 | with TemporaryDirectory() as td: | |
968 | fname = os.path.join(td, "file2") |
|
1027 | fname = os.path.join(td, "file2") | |
969 | ip.run_cell_magic( |
|
1028 | ip.run_cell_magic( | |
970 | "writefile", |
|
1029 | "writefile", | |
971 | fname, |
|
1030 | fname, | |
972 | "\n".join( |
|
1031 | "\n".join( | |
973 | [ |
|
1032 | [ | |
974 | "line1", |
|
1033 | "line1", | |
975 | "line2", |
|
1034 | "line2", | |
976 | ] |
|
1035 | ] | |
977 | ), |
|
1036 | ), | |
978 | ) |
|
1037 | ) | |
979 | ip.run_cell_magic( |
|
1038 | ip.run_cell_magic( | |
980 | "writefile", |
|
1039 | "writefile", | |
981 | "-a %s" % fname, |
|
1040 | "-a %s" % fname, | |
982 | "\n".join( |
|
1041 | "\n".join( | |
983 | [ |
|
1042 | [ | |
984 | "line3", |
|
1043 | "line3", | |
985 | "line4", |
|
1044 | "line4", | |
986 | ] |
|
1045 | ] | |
987 | ), |
|
1046 | ), | |
988 | ) |
|
1047 | ) | |
989 | s = Path(fname).read_text(encoding="utf-8") |
|
1048 | s = Path(fname).read_text(encoding="utf-8") | |
990 | assert "line1\n" in s |
|
1049 | assert "line1\n" in s | |
991 | assert "line3\n" in s |
|
1050 | assert "line3\n" in s | |
992 |
|
1051 | |||
993 |
|
1052 | |||
994 | def test_file_spaces(): |
|
1053 | def test_file_spaces(): | |
995 | """%%file with spaces in filename""" |
|
1054 | """%%file with spaces in filename""" | |
996 | ip = get_ipython() |
|
1055 | ip = get_ipython() | |
997 | with TemporaryWorkingDirectory() as td: |
|
1056 | with TemporaryWorkingDirectory() as td: | |
998 | fname = "file name" |
|
1057 | fname = "file name" | |
999 | ip.run_cell_magic( |
|
1058 | ip.run_cell_magic( | |
1000 | "file", |
|
1059 | "file", | |
1001 | '"%s"' % fname, |
|
1060 | '"%s"' % fname, | |
1002 | "\n".join( |
|
1061 | "\n".join( | |
1003 | [ |
|
1062 | [ | |
1004 | "line1", |
|
1063 | "line1", | |
1005 | "line2", |
|
1064 | "line2", | |
1006 | ] |
|
1065 | ] | |
1007 | ), |
|
1066 | ), | |
1008 | ) |
|
1067 | ) | |
1009 | s = Path(fname).read_text(encoding="utf-8") |
|
1068 | s = Path(fname).read_text(encoding="utf-8") | |
1010 | assert "line1\n" in s |
|
1069 | assert "line1\n" in s | |
1011 | assert "line2" in s |
|
1070 | assert "line2" in s | |
1012 |
|
1071 | |||
1013 |
|
1072 | |||
1014 | def test_script_config(): |
|
1073 | def test_script_config(): | |
1015 | ip = get_ipython() |
|
1074 | ip = get_ipython() | |
1016 | ip.config.ScriptMagics.script_magics = ['whoda'] |
|
1075 | ip.config.ScriptMagics.script_magics = ['whoda'] | |
1017 | sm = script.ScriptMagics(shell=ip) |
|
1076 | sm = script.ScriptMagics(shell=ip) | |
1018 | assert "whoda" in sm.magics["cell"] |
|
1077 | assert "whoda" in sm.magics["cell"] | |
1019 |
|
1078 | |||
1020 |
|
1079 | |||
1021 | def test_script_out(): |
|
1080 | def test_script_out(): | |
1022 | ip = get_ipython() |
|
1081 | ip = get_ipython() | |
1023 | ip.run_cell_magic("script", f"--out output {sys.executable}", "print('hi')") |
|
1082 | ip.run_cell_magic("script", f"--out output {sys.executable}", "print('hi')") | |
1024 | assert ip.user_ns["output"].strip() == "hi" |
|
1083 | assert ip.user_ns["output"].strip() == "hi" | |
1025 |
|
1084 | |||
1026 |
|
1085 | |||
1027 | def test_script_err(): |
|
1086 | def test_script_err(): | |
1028 | ip = get_ipython() |
|
1087 | ip = get_ipython() | |
1029 | ip.run_cell_magic( |
|
1088 | ip.run_cell_magic( | |
1030 | "script", |
|
1089 | "script", | |
1031 | f"--err error {sys.executable}", |
|
1090 | f"--err error {sys.executable}", | |
1032 | "import sys; print('hello', file=sys.stderr)", |
|
1091 | "import sys; print('hello', file=sys.stderr)", | |
1033 | ) |
|
1092 | ) | |
1034 | assert ip.user_ns["error"].strip() == "hello" |
|
1093 | assert ip.user_ns["error"].strip() == "hello" | |
1035 |
|
1094 | |||
1036 |
|
1095 | |||
1037 | def test_script_out_err(): |
|
1096 | def test_script_out_err(): | |
1038 |
|
1097 | |||
1039 | ip = get_ipython() |
|
1098 | ip = get_ipython() | |
1040 | ip.run_cell_magic( |
|
1099 | ip.run_cell_magic( | |
1041 | "script", |
|
1100 | "script", | |
1042 | f"--out output --err error {sys.executable}", |
|
1101 | f"--out output --err error {sys.executable}", | |
1043 | "\n".join( |
|
1102 | "\n".join( | |
1044 | [ |
|
1103 | [ | |
1045 | "import sys", |
|
1104 | "import sys", | |
1046 | "print('hi')", |
|
1105 | "print('hi')", | |
1047 | "print('hello', file=sys.stderr)", |
|
1106 | "print('hello', file=sys.stderr)", | |
1048 | ] |
|
1107 | ] | |
1049 | ), |
|
1108 | ), | |
1050 | ) |
|
1109 | ) | |
1051 | assert ip.user_ns["output"].strip() == "hi" |
|
1110 | assert ip.user_ns["output"].strip() == "hi" | |
1052 | assert ip.user_ns["error"].strip() == "hello" |
|
1111 | assert ip.user_ns["error"].strip() == "hello" | |
1053 |
|
1112 | |||
1054 |
|
1113 | |||
1055 | async def test_script_bg_out(): |
|
1114 | async def test_script_bg_out(): | |
1056 | ip = get_ipython() |
|
1115 | ip = get_ipython() | |
1057 | ip.run_cell_magic("script", f"--bg --out output {sys.executable}", "print('hi')") |
|
1116 | ip.run_cell_magic("script", f"--bg --out output {sys.executable}", "print('hi')") | |
1058 | assert (await ip.user_ns["output"].read()).strip() == b"hi" |
|
1117 | assert (await ip.user_ns["output"].read()).strip() == b"hi" | |
1059 | assert ip.user_ns["output"].at_eof() |
|
1118 | assert ip.user_ns["output"].at_eof() | |
1060 |
|
1119 | |||
1061 |
|
1120 | |||
1062 | async def test_script_bg_err(): |
|
1121 | async def test_script_bg_err(): | |
1063 | ip = get_ipython() |
|
1122 | ip = get_ipython() | |
1064 | ip.run_cell_magic( |
|
1123 | ip.run_cell_magic( | |
1065 | "script", |
|
1124 | "script", | |
1066 | f"--bg --err error {sys.executable}", |
|
1125 | f"--bg --err error {sys.executable}", | |
1067 | "import sys; print('hello', file=sys.stderr)", |
|
1126 | "import sys; print('hello', file=sys.stderr)", | |
1068 | ) |
|
1127 | ) | |
1069 | assert (await ip.user_ns["error"].read()).strip() == b"hello" |
|
1128 | assert (await ip.user_ns["error"].read()).strip() == b"hello" | |
1070 | assert ip.user_ns["error"].at_eof() |
|
1129 | assert ip.user_ns["error"].at_eof() | |
1071 |
|
1130 | |||
1072 |
|
1131 | |||
1073 | async def test_script_bg_out_err(): |
|
1132 | async def test_script_bg_out_err(): | |
1074 | ip = get_ipython() |
|
1133 | ip = get_ipython() | |
1075 | ip.run_cell_magic( |
|
1134 | ip.run_cell_magic( | |
1076 | "script", |
|
1135 | "script", | |
1077 | f"--bg --out output --err error {sys.executable}", |
|
1136 | f"--bg --out output --err error {sys.executable}", | |
1078 | "\n".join( |
|
1137 | "\n".join( | |
1079 | [ |
|
1138 | [ | |
1080 | "import sys", |
|
1139 | "import sys", | |
1081 | "print('hi')", |
|
1140 | "print('hi')", | |
1082 | "print('hello', file=sys.stderr)", |
|
1141 | "print('hello', file=sys.stderr)", | |
1083 | ] |
|
1142 | ] | |
1084 | ), |
|
1143 | ), | |
1085 | ) |
|
1144 | ) | |
1086 | assert (await ip.user_ns["output"].read()).strip() == b"hi" |
|
1145 | assert (await ip.user_ns["output"].read()).strip() == b"hi" | |
1087 | assert (await ip.user_ns["error"].read()).strip() == b"hello" |
|
1146 | assert (await ip.user_ns["error"].read()).strip() == b"hello" | |
1088 | assert ip.user_ns["output"].at_eof() |
|
1147 | assert ip.user_ns["output"].at_eof() | |
1089 | assert ip.user_ns["error"].at_eof() |
|
1148 | assert ip.user_ns["error"].at_eof() | |
1090 |
|
1149 | |||
1091 |
|
1150 | |||
1092 | async def test_script_bg_proc(): |
|
1151 | async def test_script_bg_proc(): | |
1093 | ip = get_ipython() |
|
1152 | ip = get_ipython() | |
1094 | ip.run_cell_magic( |
|
1153 | ip.run_cell_magic( | |
1095 | "script", |
|
1154 | "script", | |
1096 | f"--bg --out output --proc p {sys.executable}", |
|
1155 | f"--bg --out output --proc p {sys.executable}", | |
1097 | "\n".join( |
|
1156 | "\n".join( | |
1098 | [ |
|
1157 | [ | |
1099 | "import sys", |
|
1158 | "import sys", | |
1100 | "print('hi')", |
|
1159 | "print('hi')", | |
1101 | "print('hello', file=sys.stderr)", |
|
1160 | "print('hello', file=sys.stderr)", | |
1102 | ] |
|
1161 | ] | |
1103 | ), |
|
1162 | ), | |
1104 | ) |
|
1163 | ) | |
1105 | p = ip.user_ns["p"] |
|
1164 | p = ip.user_ns["p"] | |
1106 | await p.wait() |
|
1165 | await p.wait() | |
1107 | assert p.returncode == 0 |
|
1166 | assert p.returncode == 0 | |
1108 | assert (await p.stdout.read()).strip() == b"hi" |
|
1167 | assert (await p.stdout.read()).strip() == b"hi" | |
1109 | # not captured, so empty |
|
1168 | # not captured, so empty | |
1110 | assert (await p.stderr.read()) == b"" |
|
1169 | assert (await p.stderr.read()) == b"" | |
1111 | assert p.stdout.at_eof() |
|
1170 | assert p.stdout.at_eof() | |
1112 | assert p.stderr.at_eof() |
|
1171 | assert p.stderr.at_eof() | |
1113 |
|
1172 | |||
1114 |
|
1173 | |||
1115 | def test_script_defaults(): |
|
1174 | def test_script_defaults(): | |
1116 | ip = get_ipython() |
|
1175 | ip = get_ipython() | |
1117 | for cmd in ['sh', 'bash', 'perl', 'ruby']: |
|
1176 | for cmd in ['sh', 'bash', 'perl', 'ruby']: | |
1118 | try: |
|
1177 | try: | |
1119 | find_cmd(cmd) |
|
1178 | find_cmd(cmd) | |
1120 | except Exception: |
|
1179 | except Exception: | |
1121 | pass |
|
1180 | pass | |
1122 | else: |
|
1181 | else: | |
1123 | assert cmd in ip.magics_manager.magics["cell"] |
|
1182 | assert cmd in ip.magics_manager.magics["cell"] | |
1124 |
|
1183 | |||
1125 |
|
1184 | |||
1126 | @magics_class |
|
1185 | @magics_class | |
1127 | class FooFoo(Magics): |
|
1186 | class FooFoo(Magics): | |
1128 | """class with both %foo and %%foo magics""" |
|
1187 | """class with both %foo and %%foo magics""" | |
1129 | @line_magic('foo') |
|
1188 | @line_magic('foo') | |
1130 | def line_foo(self, line): |
|
1189 | def line_foo(self, line): | |
1131 | "I am line foo" |
|
1190 | "I am line foo" | |
1132 | pass |
|
1191 | pass | |
1133 |
|
1192 | |||
1134 | @cell_magic("foo") |
|
1193 | @cell_magic("foo") | |
1135 | def cell_foo(self, line, cell): |
|
1194 | def cell_foo(self, line, cell): | |
1136 | "I am cell foo, not line foo" |
|
1195 | "I am cell foo, not line foo" | |
1137 | pass |
|
1196 | pass | |
1138 |
|
1197 | |||
1139 | def test_line_cell_info(): |
|
1198 | def test_line_cell_info(): | |
1140 | """%%foo and %foo magics are distinguishable to inspect""" |
|
1199 | """%%foo and %foo magics are distinguishable to inspect""" | |
1141 | ip = get_ipython() |
|
1200 | ip = get_ipython() | |
1142 | ip.magics_manager.register(FooFoo) |
|
1201 | ip.magics_manager.register(FooFoo) | |
1143 | oinfo = ip.object_inspect("foo") |
|
1202 | oinfo = ip.object_inspect("foo") | |
1144 | assert oinfo["found"] is True |
|
1203 | assert oinfo["found"] is True | |
1145 | assert oinfo["ismagic"] is True |
|
1204 | assert oinfo["ismagic"] is True | |
1146 |
|
1205 | |||
1147 | oinfo = ip.object_inspect("%%foo") |
|
1206 | oinfo = ip.object_inspect("%%foo") | |
1148 | assert oinfo["found"] is True |
|
1207 | assert oinfo["found"] is True | |
1149 | assert oinfo["ismagic"] is True |
|
1208 | assert oinfo["ismagic"] is True | |
1150 | assert oinfo["docstring"] == FooFoo.cell_foo.__doc__ |
|
1209 | assert oinfo["docstring"] == FooFoo.cell_foo.__doc__ | |
1151 |
|
1210 | |||
1152 | oinfo = ip.object_inspect("%foo") |
|
1211 | oinfo = ip.object_inspect("%foo") | |
1153 | assert oinfo["found"] is True |
|
1212 | assert oinfo["found"] is True | |
1154 | assert oinfo["ismagic"] is True |
|
1213 | assert oinfo["ismagic"] is True | |
1155 | assert oinfo["docstring"] == FooFoo.line_foo.__doc__ |
|
1214 | assert oinfo["docstring"] == FooFoo.line_foo.__doc__ | |
1156 |
|
1215 | |||
1157 |
|
1216 | |||
1158 | def test_multiple_magics(): |
|
1217 | def test_multiple_magics(): | |
1159 | ip = get_ipython() |
|
1218 | ip = get_ipython() | |
1160 | foo1 = FooFoo(ip) |
|
1219 | foo1 = FooFoo(ip) | |
1161 | foo2 = FooFoo(ip) |
|
1220 | foo2 = FooFoo(ip) | |
1162 | mm = ip.magics_manager |
|
1221 | mm = ip.magics_manager | |
1163 | mm.register(foo1) |
|
1222 | mm.register(foo1) | |
1164 | assert mm.magics["line"]["foo"].__self__ is foo1 |
|
1223 | assert mm.magics["line"]["foo"].__self__ is foo1 | |
1165 | mm.register(foo2) |
|
1224 | mm.register(foo2) | |
1166 | assert mm.magics["line"]["foo"].__self__ is foo2 |
|
1225 | assert mm.magics["line"]["foo"].__self__ is foo2 | |
1167 |
|
1226 | |||
1168 |
|
1227 | |||
1169 | def test_alias_magic(): |
|
1228 | def test_alias_magic(): | |
1170 | """Test %alias_magic.""" |
|
1229 | """Test %alias_magic.""" | |
1171 | ip = get_ipython() |
|
1230 | ip = get_ipython() | |
1172 | mm = ip.magics_manager |
|
1231 | mm = ip.magics_manager | |
1173 |
|
1232 | |||
1174 | # Basic operation: both cell and line magics are created, if possible. |
|
1233 | # Basic operation: both cell and line magics are created, if possible. | |
1175 | ip.run_line_magic("alias_magic", "timeit_alias timeit") |
|
1234 | ip.run_line_magic("alias_magic", "timeit_alias timeit") | |
1176 | assert "timeit_alias" in mm.magics["line"] |
|
1235 | assert "timeit_alias" in mm.magics["line"] | |
1177 | assert "timeit_alias" in mm.magics["cell"] |
|
1236 | assert "timeit_alias" in mm.magics["cell"] | |
1178 |
|
1237 | |||
1179 | # --cell is specified, line magic not created. |
|
1238 | # --cell is specified, line magic not created. | |
1180 | ip.run_line_magic("alias_magic", "--cell timeit_cell_alias timeit") |
|
1239 | ip.run_line_magic("alias_magic", "--cell timeit_cell_alias timeit") | |
1181 | assert "timeit_cell_alias" not in mm.magics["line"] |
|
1240 | assert "timeit_cell_alias" not in mm.magics["line"] | |
1182 | assert "timeit_cell_alias" in mm.magics["cell"] |
|
1241 | assert "timeit_cell_alias" in mm.magics["cell"] | |
1183 |
|
1242 | |||
1184 | # Test that line alias is created successfully. |
|
1243 | # Test that line alias is created successfully. | |
1185 | ip.run_line_magic("alias_magic", "--line env_alias env") |
|
1244 | ip.run_line_magic("alias_magic", "--line env_alias env") | |
1186 | assert ip.run_line_magic("env", "") == ip.run_line_magic("env_alias", "") |
|
1245 | assert ip.run_line_magic("env", "") == ip.run_line_magic("env_alias", "") | |
1187 |
|
1246 | |||
1188 | # Test that line alias with parameters passed in is created successfully. |
|
1247 | # Test that line alias with parameters passed in is created successfully. | |
1189 | ip.run_line_magic( |
|
1248 | ip.run_line_magic( | |
1190 | "alias_magic", "--line history_alias history --params " + shlex.quote("3") |
|
1249 | "alias_magic", "--line history_alias history --params " + shlex.quote("3") | |
1191 | ) |
|
1250 | ) | |
1192 | assert "history_alias" in mm.magics["line"] |
|
1251 | assert "history_alias" in mm.magics["line"] | |
1193 |
|
1252 | |||
1194 |
|
1253 | |||
1195 | def test_save(): |
|
1254 | def test_save(): | |
1196 | """Test %save.""" |
|
1255 | """Test %save.""" | |
1197 | ip = get_ipython() |
|
1256 | ip = get_ipython() | |
1198 | ip.history_manager.reset() # Clear any existing history. |
|
1257 | ip.history_manager.reset() # Clear any existing history. | |
1199 | cmds = ["a=1", "def b():\n return a**2", "print(a, b())"] |
|
1258 | cmds = ["a=1", "def b():\n return a**2", "print(a, b())"] | |
1200 | for i, cmd in enumerate(cmds, start=1): |
|
1259 | for i, cmd in enumerate(cmds, start=1): | |
1201 | ip.history_manager.store_inputs(i, cmd) |
|
1260 | ip.history_manager.store_inputs(i, cmd) | |
1202 | with TemporaryDirectory() as tmpdir: |
|
1261 | with TemporaryDirectory() as tmpdir: | |
1203 | file = os.path.join(tmpdir, "testsave.py") |
|
1262 | file = os.path.join(tmpdir, "testsave.py") | |
1204 | ip.run_line_magic("save", "%s 1-10" % file) |
|
1263 | ip.run_line_magic("save", "%s 1-10" % file) | |
1205 | content = Path(file).read_text(encoding="utf-8") |
|
1264 | content = Path(file).read_text(encoding="utf-8") | |
1206 | assert content.count(cmds[0]) == 1 |
|
1265 | assert content.count(cmds[0]) == 1 | |
1207 | assert "coding: utf-8" in content |
|
1266 | assert "coding: utf-8" in content | |
1208 | ip.run_line_magic("save", "-a %s 1-10" % file) |
|
1267 | ip.run_line_magic("save", "-a %s 1-10" % file) | |
1209 | content = Path(file).read_text(encoding="utf-8") |
|
1268 | content = Path(file).read_text(encoding="utf-8") | |
1210 | assert content.count(cmds[0]) == 2 |
|
1269 | assert content.count(cmds[0]) == 2 | |
1211 | assert "coding: utf-8" in content |
|
1270 | assert "coding: utf-8" in content | |
1212 |
|
1271 | |||
1213 |
|
1272 | |||
1214 | def test_save_with_no_args(): |
|
1273 | def test_save_with_no_args(): | |
1215 | ip = get_ipython() |
|
1274 | ip = get_ipython() | |
1216 | ip.history_manager.reset() # Clear any existing history. |
|
1275 | ip.history_manager.reset() # Clear any existing history. | |
1217 | cmds = ["a=1", "def b():\n return a**2", "print(a, b())", "%save"] |
|
1276 | cmds = ["a=1", "def b():\n return a**2", "print(a, b())", "%save"] | |
1218 | for i, cmd in enumerate(cmds, start=1): |
|
1277 | for i, cmd in enumerate(cmds, start=1): | |
1219 | ip.history_manager.store_inputs(i, cmd) |
|
1278 | ip.history_manager.store_inputs(i, cmd) | |
1220 |
|
1279 | |||
1221 | with TemporaryDirectory() as tmpdir: |
|
1280 | with TemporaryDirectory() as tmpdir: | |
1222 | path = os.path.join(tmpdir, "testsave.py") |
|
1281 | path = os.path.join(tmpdir, "testsave.py") | |
1223 | ip.run_line_magic("save", path) |
|
1282 | ip.run_line_magic("save", path) | |
1224 | content = Path(path).read_text(encoding="utf-8") |
|
1283 | content = Path(path).read_text(encoding="utf-8") | |
1225 | expected_content = dedent( |
|
1284 | expected_content = dedent( | |
1226 | """\ |
|
1285 | """\ | |
1227 | # coding: utf-8 |
|
1286 | # coding: utf-8 | |
1228 | a=1 |
|
1287 | a=1 | |
1229 | def b(): |
|
1288 | def b(): | |
1230 | return a**2 |
|
1289 | return a**2 | |
1231 | print(a, b()) |
|
1290 | print(a, b()) | |
1232 | """ |
|
1291 | """ | |
1233 | ) |
|
1292 | ) | |
1234 | assert content == expected_content |
|
1293 | assert content == expected_content | |
1235 |
|
1294 | |||
1236 |
|
1295 | |||
1237 | def test_store(): |
|
1296 | def test_store(): | |
1238 | """Test %store.""" |
|
1297 | """Test %store.""" | |
1239 | ip = get_ipython() |
|
1298 | ip = get_ipython() | |
1240 | ip.run_line_magic('load_ext', 'storemagic') |
|
1299 | ip.run_line_magic('load_ext', 'storemagic') | |
1241 |
|
1300 | |||
1242 | # make sure the storage is empty |
|
1301 | # make sure the storage is empty | |
1243 | ip.run_line_magic("store", "-z") |
|
1302 | ip.run_line_magic("store", "-z") | |
1244 | ip.user_ns["var"] = 42 |
|
1303 | ip.user_ns["var"] = 42 | |
1245 | ip.run_line_magic("store", "var") |
|
1304 | ip.run_line_magic("store", "var") | |
1246 | ip.user_ns["var"] = 39 |
|
1305 | ip.user_ns["var"] = 39 | |
1247 | ip.run_line_magic("store", "-r") |
|
1306 | ip.run_line_magic("store", "-r") | |
1248 | assert ip.user_ns["var"] == 42 |
|
1307 | assert ip.user_ns["var"] == 42 | |
1249 |
|
1308 | |||
1250 | ip.run_line_magic("store", "-d var") |
|
1309 | ip.run_line_magic("store", "-d var") | |
1251 | ip.user_ns["var"] = 39 |
|
1310 | ip.user_ns["var"] = 39 | |
1252 | ip.run_line_magic("store", "-r") |
|
1311 | ip.run_line_magic("store", "-r") | |
1253 | assert ip.user_ns["var"] == 39 |
|
1312 | assert ip.user_ns["var"] == 39 | |
1254 |
|
1313 | |||
1255 |
|
1314 | |||
1256 | def _run_edit_test(arg_s, exp_filename=None, |
|
1315 | def _run_edit_test(arg_s, exp_filename=None, | |
1257 | exp_lineno=-1, |
|
1316 | exp_lineno=-1, | |
1258 | exp_contents=None, |
|
1317 | exp_contents=None, | |
1259 | exp_is_temp=None): |
|
1318 | exp_is_temp=None): | |
1260 | ip = get_ipython() |
|
1319 | ip = get_ipython() | |
1261 | M = code.CodeMagics(ip) |
|
1320 | M = code.CodeMagics(ip) | |
1262 | last_call = ['',''] |
|
1321 | last_call = ['',''] | |
1263 | opts,args = M.parse_options(arg_s,'prxn:') |
|
1322 | opts,args = M.parse_options(arg_s,'prxn:') | |
1264 | filename, lineno, is_temp = M._find_edit_target(ip, args, opts, last_call) |
|
1323 | filename, lineno, is_temp = M._find_edit_target(ip, args, opts, last_call) | |
1265 |
|
1324 | |||
1266 | if exp_filename is not None: |
|
1325 | if exp_filename is not None: | |
1267 | assert exp_filename == filename |
|
1326 | assert exp_filename == filename | |
1268 | if exp_contents is not None: |
|
1327 | if exp_contents is not None: | |
1269 | with io.open(filename, 'r', encoding='utf-8') as f: |
|
1328 | with io.open(filename, 'r', encoding='utf-8') as f: | |
1270 | contents = f.read() |
|
1329 | contents = f.read() | |
1271 | assert exp_contents == contents |
|
1330 | assert exp_contents == contents | |
1272 | if exp_lineno != -1: |
|
1331 | if exp_lineno != -1: | |
1273 | assert exp_lineno == lineno |
|
1332 | assert exp_lineno == lineno | |
1274 | if exp_is_temp is not None: |
|
1333 | if exp_is_temp is not None: | |
1275 | assert exp_is_temp == is_temp |
|
1334 | assert exp_is_temp == is_temp | |
1276 |
|
1335 | |||
1277 |
|
1336 | |||
1278 | def test_edit_interactive(): |
|
1337 | def test_edit_interactive(): | |
1279 | """%edit on interactively defined objects""" |
|
1338 | """%edit on interactively defined objects""" | |
1280 | ip = get_ipython() |
|
1339 | ip = get_ipython() | |
1281 | n = ip.execution_count |
|
1340 | n = ip.execution_count | |
1282 | ip.run_cell("def foo(): return 1", store_history=True) |
|
1341 | ip.run_cell("def foo(): return 1", store_history=True) | |
1283 |
|
1342 | |||
1284 | with pytest.raises(code.InteractivelyDefined) as e: |
|
1343 | with pytest.raises(code.InteractivelyDefined) as e: | |
1285 | _run_edit_test("foo") |
|
1344 | _run_edit_test("foo") | |
1286 | assert e.value.index == n |
|
1345 | assert e.value.index == n | |
1287 |
|
1346 | |||
1288 |
|
1347 | |||
1289 | def test_edit_cell(): |
|
1348 | def test_edit_cell(): | |
1290 | """%edit [cell id]""" |
|
1349 | """%edit [cell id]""" | |
1291 | ip = get_ipython() |
|
1350 | ip = get_ipython() | |
1292 |
|
1351 | |||
1293 | ip.run_cell("def foo(): return 1", store_history=True) |
|
1352 | ip.run_cell("def foo(): return 1", store_history=True) | |
1294 |
|
1353 | |||
1295 | # test |
|
1354 | # test | |
1296 | _run_edit_test("1", exp_contents=ip.user_ns['In'][1], exp_is_temp=True) |
|
1355 | _run_edit_test("1", exp_contents=ip.user_ns['In'][1], exp_is_temp=True) | |
1297 |
|
1356 | |||
1298 | def test_edit_fname(): |
|
1357 | def test_edit_fname(): | |
1299 | """%edit file""" |
|
1358 | """%edit file""" | |
1300 | # test |
|
1359 | # test | |
1301 | _run_edit_test("test file.py", exp_filename="test file.py") |
|
1360 | _run_edit_test("test file.py", exp_filename="test file.py") | |
1302 |
|
1361 | |||
1303 | def test_bookmark(): |
|
1362 | def test_bookmark(): | |
1304 | ip = get_ipython() |
|
1363 | ip = get_ipython() | |
1305 | ip.run_line_magic('bookmark', 'bmname') |
|
1364 | ip.run_line_magic('bookmark', 'bmname') | |
1306 | with tt.AssertPrints('bmname'): |
|
1365 | with tt.AssertPrints('bmname'): | |
1307 | ip.run_line_magic('bookmark', '-l') |
|
1366 | ip.run_line_magic('bookmark', '-l') | |
1308 | ip.run_line_magic('bookmark', '-d bmname') |
|
1367 | ip.run_line_magic('bookmark', '-d bmname') | |
1309 |
|
1368 | |||
1310 | def test_ls_magic(): |
|
1369 | def test_ls_magic(): | |
1311 | ip = get_ipython() |
|
1370 | ip = get_ipython() | |
1312 | json_formatter = ip.display_formatter.formatters['application/json'] |
|
1371 | json_formatter = ip.display_formatter.formatters['application/json'] | |
1313 | json_formatter.enabled = True |
|
1372 | json_formatter.enabled = True | |
1314 | lsmagic = ip.run_line_magic("lsmagic", "") |
|
1373 | lsmagic = ip.run_line_magic("lsmagic", "") | |
1315 | with warnings.catch_warnings(record=True) as w: |
|
1374 | with warnings.catch_warnings(record=True) as w: | |
1316 | j = json_formatter(lsmagic) |
|
1375 | j = json_formatter(lsmagic) | |
1317 | assert sorted(j) == ["cell", "line"] |
|
1376 | assert sorted(j) == ["cell", "line"] | |
1318 | assert w == [] # no warnings |
|
1377 | assert w == [] # no warnings | |
1319 |
|
1378 | |||
1320 |
|
1379 | |||
1321 | def test_strip_initial_indent(): |
|
1380 | def test_strip_initial_indent(): | |
1322 | def sii(s): |
|
1381 | def sii(s): | |
1323 | lines = s.splitlines() |
|
1382 | lines = s.splitlines() | |
1324 | return '\n'.join(code.strip_initial_indent(lines)) |
|
1383 | return '\n'.join(code.strip_initial_indent(lines)) | |
1325 |
|
1384 | |||
1326 | assert sii(" a = 1\nb = 2") == "a = 1\nb = 2" |
|
1385 | assert sii(" a = 1\nb = 2") == "a = 1\nb = 2" | |
1327 | assert sii(" a\n b\nc") == "a\n b\nc" |
|
1386 | assert sii(" a\n b\nc") == "a\n b\nc" | |
1328 | assert sii("a\n b") == "a\n b" |
|
1387 | assert sii("a\n b") == "a\n b" | |
1329 |
|
1388 | |||
1330 | def test_logging_magic_quiet_from_arg(): |
|
1389 | def test_logging_magic_quiet_from_arg(): | |
1331 | _ip.config.LoggingMagics.quiet = False |
|
1390 | _ip.config.LoggingMagics.quiet = False | |
1332 | lm = logging.LoggingMagics(shell=_ip) |
|
1391 | lm = logging.LoggingMagics(shell=_ip) | |
1333 | with TemporaryDirectory() as td: |
|
1392 | with TemporaryDirectory() as td: | |
1334 | try: |
|
1393 | try: | |
1335 | with tt.AssertNotPrints(re.compile("Activating.*")): |
|
1394 | with tt.AssertNotPrints(re.compile("Activating.*")): | |
1336 | lm.logstart('-q {}'.format( |
|
1395 | lm.logstart('-q {}'.format( | |
1337 | os.path.join(td, "quiet_from_arg.log"))) |
|
1396 | os.path.join(td, "quiet_from_arg.log"))) | |
1338 | finally: |
|
1397 | finally: | |
1339 | _ip.logger.logstop() |
|
1398 | _ip.logger.logstop() | |
1340 |
|
1399 | |||
1341 | def test_logging_magic_quiet_from_config(): |
|
1400 | def test_logging_magic_quiet_from_config(): | |
1342 | _ip.config.LoggingMagics.quiet = True |
|
1401 | _ip.config.LoggingMagics.quiet = True | |
1343 | lm = logging.LoggingMagics(shell=_ip) |
|
1402 | lm = logging.LoggingMagics(shell=_ip) | |
1344 | with TemporaryDirectory() as td: |
|
1403 | with TemporaryDirectory() as td: | |
1345 | try: |
|
1404 | try: | |
1346 | with tt.AssertNotPrints(re.compile("Activating.*")): |
|
1405 | with tt.AssertNotPrints(re.compile("Activating.*")): | |
1347 | lm.logstart(os.path.join(td, "quiet_from_config.log")) |
|
1406 | lm.logstart(os.path.join(td, "quiet_from_config.log")) | |
1348 | finally: |
|
1407 | finally: | |
1349 | _ip.logger.logstop() |
|
1408 | _ip.logger.logstop() | |
1350 |
|
1409 | |||
1351 |
|
1410 | |||
1352 | def test_logging_magic_not_quiet(): |
|
1411 | def test_logging_magic_not_quiet(): | |
1353 | _ip.config.LoggingMagics.quiet = False |
|
1412 | _ip.config.LoggingMagics.quiet = False | |
1354 | lm = logging.LoggingMagics(shell=_ip) |
|
1413 | lm = logging.LoggingMagics(shell=_ip) | |
1355 | with TemporaryDirectory() as td: |
|
1414 | with TemporaryDirectory() as td: | |
1356 | try: |
|
1415 | try: | |
1357 | with tt.AssertPrints(re.compile("Activating.*")): |
|
1416 | with tt.AssertPrints(re.compile("Activating.*")): | |
1358 | lm.logstart(os.path.join(td, "not_quiet.log")) |
|
1417 | lm.logstart(os.path.join(td, "not_quiet.log")) | |
1359 | finally: |
|
1418 | finally: | |
1360 | _ip.logger.logstop() |
|
1419 | _ip.logger.logstop() | |
1361 |
|
1420 | |||
1362 |
|
1421 | |||
1363 | def test_time_no_var_expand(): |
|
1422 | def test_time_no_var_expand(): | |
1364 | _ip.user_ns["a"] = 5 |
|
1423 | _ip.user_ns["a"] = 5 | |
1365 | _ip.user_ns["b"] = [] |
|
1424 | _ip.user_ns["b"] = [] | |
1366 | _ip.run_line_magic("time", 'b.append("{a}")') |
|
1425 | _ip.run_line_magic("time", 'b.append("{a}")') | |
1367 | assert _ip.user_ns["b"] == ["{a}"] |
|
1426 | assert _ip.user_ns["b"] == ["{a}"] | |
1368 |
|
1427 | |||
1369 |
|
1428 | |||
1370 | # this is slow, put at the end for local testing. |
|
1429 | # this is slow, put at the end for local testing. | |
1371 | def test_timeit_arguments(): |
|
1430 | def test_timeit_arguments(): | |
1372 | "Test valid timeit arguments, should not cause SyntaxError (GH #1269)" |
|
1431 | "Test valid timeit arguments, should not cause SyntaxError (GH #1269)" | |
1373 | _ip.run_line_magic("timeit", "-n1 -r1 a=('#')") |
|
1432 | _ip.run_line_magic("timeit", "-n1 -r1 a=('#')") | |
1374 |
|
1433 | |||
1375 |
|
1434 | |||
1376 | MINIMAL_LAZY_MAGIC = """ |
|
1435 | MINIMAL_LAZY_MAGIC = """ | |
1377 | from IPython.core.magic import ( |
|
1436 | from IPython.core.magic import ( | |
1378 | Magics, |
|
1437 | Magics, | |
1379 | magics_class, |
|
1438 | magics_class, | |
1380 | line_magic, |
|
1439 | line_magic, | |
1381 | cell_magic, |
|
1440 | cell_magic, | |
1382 | ) |
|
1441 | ) | |
1383 |
|
1442 | |||
1384 |
|
1443 | |||
1385 | @magics_class |
|
1444 | @magics_class | |
1386 | class LazyMagics(Magics): |
|
1445 | class LazyMagics(Magics): | |
1387 | @line_magic |
|
1446 | @line_magic | |
1388 | def lazy_line(self, line): |
|
1447 | def lazy_line(self, line): | |
1389 | print("Lazy Line") |
|
1448 | print("Lazy Line") | |
1390 |
|
1449 | |||
1391 | @cell_magic |
|
1450 | @cell_magic | |
1392 | def lazy_cell(self, line, cell): |
|
1451 | def lazy_cell(self, line, cell): | |
1393 | print("Lazy Cell") |
|
1452 | print("Lazy Cell") | |
1394 |
|
1453 | |||
1395 |
|
1454 | |||
1396 | def load_ipython_extension(ipython): |
|
1455 | def load_ipython_extension(ipython): | |
1397 | ipython.register_magics(LazyMagics) |
|
1456 | ipython.register_magics(LazyMagics) | |
1398 | """ |
|
1457 | """ | |
1399 |
|
1458 | |||
1400 |
|
1459 | |||
1401 | def test_lazy_magics(): |
|
1460 | def test_lazy_magics(): | |
1402 | with pytest.raises(UsageError): |
|
1461 | with pytest.raises(UsageError): | |
1403 | ip.run_line_magic("lazy_line", "") |
|
1462 | ip.run_line_magic("lazy_line", "") | |
1404 |
|
1463 | |||
1405 | startdir = os.getcwd() |
|
1464 | startdir = os.getcwd() | |
1406 |
|
1465 | |||
1407 | with TemporaryDirectory() as tmpdir: |
|
1466 | with TemporaryDirectory() as tmpdir: | |
1408 | with prepended_to_syspath(tmpdir): |
|
1467 | with prepended_to_syspath(tmpdir): | |
1409 | ptempdir = Path(tmpdir) |
|
1468 | ptempdir = Path(tmpdir) | |
1410 | tf = ptempdir / "lazy_magic_module.py" |
|
1469 | tf = ptempdir / "lazy_magic_module.py" | |
1411 | tf.write_text(MINIMAL_LAZY_MAGIC) |
|
1470 | tf.write_text(MINIMAL_LAZY_MAGIC) | |
1412 | ip.magics_manager.register_lazy("lazy_line", Path(tf.name).name[:-3]) |
|
1471 | ip.magics_manager.register_lazy("lazy_line", Path(tf.name).name[:-3]) | |
1413 | with tt.AssertPrints("Lazy Line"): |
|
1472 | with tt.AssertPrints("Lazy Line"): | |
1414 | ip.run_line_magic("lazy_line", "") |
|
1473 | ip.run_line_magic("lazy_line", "") | |
1415 |
|
1474 | |||
1416 |
|
1475 | |||
1417 | TEST_MODULE = """ |
|
1476 | TEST_MODULE = """ | |
1418 | print('Loaded my_tmp') |
|
1477 | print('Loaded my_tmp') | |
1419 | if __name__ == "__main__": |
|
1478 | if __name__ == "__main__": | |
1420 | print('I just ran a script') |
|
1479 | print('I just ran a script') | |
1421 | """ |
|
1480 | """ | |
1422 |
|
1481 | |||
1423 | def test_run_module_from_import_hook(): |
|
1482 | def test_run_module_from_import_hook(): | |
1424 | "Test that a module can be loaded via an import hook" |
|
1483 | "Test that a module can be loaded via an import hook" | |
1425 | with TemporaryDirectory() as tmpdir: |
|
1484 | with TemporaryDirectory() as tmpdir: | |
1426 | fullpath = os.path.join(tmpdir, "my_tmp.py") |
|
1485 | fullpath = os.path.join(tmpdir, "my_tmp.py") | |
1427 | Path(fullpath).write_text(TEST_MODULE, encoding="utf-8") |
|
1486 | Path(fullpath).write_text(TEST_MODULE, encoding="utf-8") | |
1428 |
|
1487 | |||
1429 | import importlib.abc |
|
1488 | import importlib.abc | |
1430 | import importlib.util |
|
1489 | import importlib.util | |
1431 |
|
1490 | |||
1432 | class MyTempImporter(importlib.abc.MetaPathFinder, importlib.abc.SourceLoader): |
|
1491 | class MyTempImporter(importlib.abc.MetaPathFinder, importlib.abc.SourceLoader): | |
1433 | def find_spec(self, fullname, path, target=None): |
|
1492 | def find_spec(self, fullname, path, target=None): | |
1434 | if fullname == "my_tmp": |
|
1493 | if fullname == "my_tmp": | |
1435 | return importlib.util.spec_from_loader(fullname, self) |
|
1494 | return importlib.util.spec_from_loader(fullname, self) | |
1436 |
|
1495 | |||
1437 | def get_filename(self, fullname): |
|
1496 | def get_filename(self, fullname): | |
1438 | assert fullname == "my_tmp" |
|
1497 | assert fullname == "my_tmp" | |
1439 | return fullpath |
|
1498 | return fullpath | |
1440 |
|
1499 | |||
1441 | def get_data(self, path): |
|
1500 | def get_data(self, path): | |
1442 | assert Path(path).samefile(fullpath) |
|
1501 | assert Path(path).samefile(fullpath) | |
1443 | return Path(fullpath).read_text(encoding="utf-8") |
|
1502 | return Path(fullpath).read_text(encoding="utf-8") | |
1444 |
|
1503 | |||
1445 | sys.meta_path.insert(0, MyTempImporter()) |
|
1504 | sys.meta_path.insert(0, MyTempImporter()) | |
1446 |
|
1505 | |||
1447 | with capture_output() as captured: |
|
1506 | with capture_output() as captured: | |
1448 | _ip.run_line_magic("run", "-m my_tmp") |
|
1507 | _ip.run_line_magic("run", "-m my_tmp") | |
1449 | _ip.run_cell("import my_tmp") |
|
1508 | _ip.run_cell("import my_tmp") | |
1450 |
|
1509 | |||
1451 | output = "Loaded my_tmp\nI just ran a script\nLoaded my_tmp\n" |
|
1510 | output = "Loaded my_tmp\nI just ran a script\nLoaded my_tmp\n" | |
1452 | assert output == captured.stdout |
|
1511 | assert output == captured.stdout | |
1453 |
|
1512 | |||
1454 | sys.meta_path.pop(0) |
|
1513 | sys.meta_path.pop(0) |
@@ -1,773 +1,808 b'' | |||||
1 | """IPython terminal interface using prompt_toolkit""" |
|
1 | """IPython terminal interface using prompt_toolkit""" | |
2 |
|
2 | |||
3 | import asyncio |
|
3 | import asyncio | |
4 | import os |
|
4 | import os | |
5 | import sys |
|
5 | import sys | |
6 | from warnings import warn |
|
6 | from warnings import warn | |
|
7 | from typing import Union as UnionType | |||
7 |
|
8 | |||
8 | from IPython.core.async_helpers import get_asyncio_loop |
|
9 | from IPython.core.async_helpers import get_asyncio_loop | |
9 | from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC |
|
10 | from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC | |
10 | from IPython.utils.py3compat import input |
|
11 | from IPython.utils.py3compat import input | |
11 | from IPython.utils.terminal import toggle_set_term_title, set_term_title, restore_term_title |
|
12 | from IPython.utils.terminal import toggle_set_term_title, set_term_title, restore_term_title | |
12 | from IPython.utils.process import abbrev_cwd |
|
13 | from IPython.utils.process import abbrev_cwd | |
13 | from traitlets import ( |
|
14 | from traitlets import ( | |
14 | Bool, |
|
15 | Bool, | |
15 | Unicode, |
|
16 | Unicode, | |
16 | Dict, |
|
17 | Dict, | |
17 | Integer, |
|
18 | Integer, | |
18 | observe, |
|
19 | observe, | |
19 | Instance, |
|
20 | Instance, | |
20 | Type, |
|
21 | Type, | |
21 | default, |
|
22 | default, | |
22 | Enum, |
|
23 | Enum, | |
23 | Union, |
|
24 | Union, | |
24 | Any, |
|
25 | Any, | |
25 | validate, |
|
26 | validate, | |
26 | Float, |
|
27 | Float, | |
27 | ) |
|
28 | ) | |
28 |
|
29 | |||
29 | from prompt_toolkit.auto_suggest import AutoSuggestFromHistory |
|
30 | from prompt_toolkit.auto_suggest import AutoSuggestFromHistory | |
30 | from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode |
|
31 | from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode | |
31 | from prompt_toolkit.filters import (HasFocus, Condition, IsDone) |
|
32 | from prompt_toolkit.filters import (HasFocus, Condition, IsDone) | |
32 | from prompt_toolkit.formatted_text import PygmentsTokens |
|
33 | from prompt_toolkit.formatted_text import PygmentsTokens | |
33 | from prompt_toolkit.history import History |
|
34 | from prompt_toolkit.history import History | |
34 | from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor |
|
35 | from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor | |
35 | from prompt_toolkit.output import ColorDepth |
|
36 | from prompt_toolkit.output import ColorDepth | |
36 | from prompt_toolkit.patch_stdout import patch_stdout |
|
37 | from prompt_toolkit.patch_stdout import patch_stdout | |
37 | from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text |
|
38 | from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text | |
38 | from prompt_toolkit.styles import DynamicStyle, merge_styles |
|
39 | from prompt_toolkit.styles import DynamicStyle, merge_styles | |
39 | from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict |
|
40 | from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict | |
40 | from prompt_toolkit import __version__ as ptk_version |
|
41 | from prompt_toolkit import __version__ as ptk_version | |
41 |
|
42 | |||
42 | from pygments.styles import get_style_by_name |
|
43 | from pygments.styles import get_style_by_name | |
43 | from pygments.style import Style |
|
44 | from pygments.style import Style | |
44 | from pygments.token import Token |
|
45 | from pygments.token import Token | |
45 |
|
46 | |||
46 | from .debugger import TerminalPdb, Pdb |
|
47 | from .debugger import TerminalPdb, Pdb | |
47 | from .magics import TerminalMagics |
|
48 | from .magics import TerminalMagics | |
48 | from .pt_inputhooks import get_inputhook_name_and_func |
|
49 | from .pt_inputhooks import get_inputhook_name_and_func | |
49 | from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook |
|
50 | from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook | |
50 | from .ptutils import IPythonPTCompleter, IPythonPTLexer |
|
51 | from .ptutils import IPythonPTCompleter, IPythonPTLexer | |
51 | from .shortcuts import create_ipython_shortcuts |
|
52 | from .shortcuts import create_ipython_shortcuts | |
|
53 | from .shortcuts.auto_suggest import ( | |||
|
54 | NavigableAutoSuggestFromHistory, | |||
|
55 | AppendAutoSuggestionInAnyLine, | |||
|
56 | ) | |||
52 |
|
57 | |||
53 | PTK3 = ptk_version.startswith('3.') |
|
58 | PTK3 = ptk_version.startswith('3.') | |
54 |
|
59 | |||
55 |
|
60 | |||
56 | class _NoStyle(Style): pass |
|
61 | class _NoStyle(Style): pass | |
57 |
|
62 | |||
58 |
|
63 | |||
59 |
|
64 | |||
60 | _style_overrides_light_bg = { |
|
65 | _style_overrides_light_bg = { | |
61 | Token.Prompt: '#ansibrightblue', |
|
66 | Token.Prompt: '#ansibrightblue', | |
62 | Token.PromptNum: '#ansiblue bold', |
|
67 | Token.PromptNum: '#ansiblue bold', | |
63 | Token.OutPrompt: '#ansibrightred', |
|
68 | Token.OutPrompt: '#ansibrightred', | |
64 | Token.OutPromptNum: '#ansired bold', |
|
69 | Token.OutPromptNum: '#ansired bold', | |
65 | } |
|
70 | } | |
66 |
|
71 | |||
67 | _style_overrides_linux = { |
|
72 | _style_overrides_linux = { | |
68 | Token.Prompt: '#ansibrightgreen', |
|
73 | Token.Prompt: '#ansibrightgreen', | |
69 | Token.PromptNum: '#ansigreen bold', |
|
74 | Token.PromptNum: '#ansigreen bold', | |
70 | Token.OutPrompt: '#ansibrightred', |
|
75 | Token.OutPrompt: '#ansibrightred', | |
71 | Token.OutPromptNum: '#ansired bold', |
|
76 | Token.OutPromptNum: '#ansired bold', | |
72 | } |
|
77 | } | |
73 |
|
78 | |||
74 | def get_default_editor(): |
|
79 | def get_default_editor(): | |
75 | try: |
|
80 | try: | |
76 | return os.environ['EDITOR'] |
|
81 | return os.environ['EDITOR'] | |
77 | except KeyError: |
|
82 | except KeyError: | |
78 | pass |
|
83 | pass | |
79 | except UnicodeError: |
|
84 | except UnicodeError: | |
80 | warn("$EDITOR environment variable is not pure ASCII. Using platform " |
|
85 | warn("$EDITOR environment variable is not pure ASCII. Using platform " | |
81 | "default editor.") |
|
86 | "default editor.") | |
82 |
|
87 | |||
83 | if os.name == 'posix': |
|
88 | if os.name == 'posix': | |
84 | return 'vi' # the only one guaranteed to be there! |
|
89 | return 'vi' # the only one guaranteed to be there! | |
85 | else: |
|
90 | else: | |
86 | return 'notepad' # same in Windows! |
|
91 | return 'notepad' # same in Windows! | |
87 |
|
92 | |||
88 | # conservatively check for tty |
|
93 | # conservatively check for tty | |
89 | # overridden streams can result in things like: |
|
94 | # overridden streams can result in things like: | |
90 | # - sys.stdin = None |
|
95 | # - sys.stdin = None | |
91 | # - no isatty method |
|
96 | # - no isatty method | |
92 | for _name in ('stdin', 'stdout', 'stderr'): |
|
97 | for _name in ('stdin', 'stdout', 'stderr'): | |
93 | _stream = getattr(sys, _name) |
|
98 | _stream = getattr(sys, _name) | |
94 | try: |
|
99 | try: | |
95 | if not _stream or not hasattr(_stream, "isatty") or not _stream.isatty(): |
|
100 | if not _stream or not hasattr(_stream, "isatty") or not _stream.isatty(): | |
96 | _is_tty = False |
|
101 | _is_tty = False | |
97 | break |
|
102 | break | |
98 | except ValueError: |
|
103 | except ValueError: | |
99 | # stream is closed |
|
104 | # stream is closed | |
100 | _is_tty = False |
|
105 | _is_tty = False | |
101 | break |
|
106 | break | |
102 | else: |
|
107 | else: | |
103 | _is_tty = True |
|
108 | _is_tty = True | |
104 |
|
109 | |||
105 |
|
110 | |||
106 | _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty) |
|
111 | _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty) | |
107 |
|
112 | |||
108 | def black_reformat_handler(text_before_cursor): |
|
113 | def black_reformat_handler(text_before_cursor): | |
109 | """ |
|
114 | """ | |
110 | We do not need to protect against error, |
|
115 | We do not need to protect against error, | |
111 | this is taken care at a higher level where any reformat error is ignored. |
|
116 | this is taken care at a higher level where any reformat error is ignored. | |
112 | Indeed we may call reformatting on incomplete code. |
|
117 | Indeed we may call reformatting on incomplete code. | |
113 | """ |
|
118 | """ | |
114 | import black |
|
119 | import black | |
115 |
|
120 | |||
116 | formatted_text = black.format_str(text_before_cursor, mode=black.FileMode()) |
|
121 | formatted_text = black.format_str(text_before_cursor, mode=black.FileMode()) | |
117 | if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"): |
|
122 | if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"): | |
118 | formatted_text = formatted_text[:-1] |
|
123 | formatted_text = formatted_text[:-1] | |
119 | return formatted_text |
|
124 | return formatted_text | |
120 |
|
125 | |||
121 |
|
126 | |||
122 | def yapf_reformat_handler(text_before_cursor): |
|
127 | def yapf_reformat_handler(text_before_cursor): | |
123 | from yapf.yapflib import file_resources |
|
128 | from yapf.yapflib import file_resources | |
124 | from yapf.yapflib import yapf_api |
|
129 | from yapf.yapflib import yapf_api | |
125 |
|
130 | |||
126 | style_config = file_resources.GetDefaultStyleForDir(os.getcwd()) |
|
131 | style_config = file_resources.GetDefaultStyleForDir(os.getcwd()) | |
127 | formatted_text, was_formatted = yapf_api.FormatCode( |
|
132 | formatted_text, was_formatted = yapf_api.FormatCode( | |
128 | text_before_cursor, style_config=style_config |
|
133 | text_before_cursor, style_config=style_config | |
129 | ) |
|
134 | ) | |
130 | if was_formatted: |
|
135 | if was_formatted: | |
131 | if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"): |
|
136 | if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"): | |
132 | formatted_text = formatted_text[:-1] |
|
137 | formatted_text = formatted_text[:-1] | |
133 | return formatted_text |
|
138 | return formatted_text | |
134 | else: |
|
139 | else: | |
135 | return text_before_cursor |
|
140 | return text_before_cursor | |
136 |
|
141 | |||
137 |
|
142 | |||
138 | class PtkHistoryAdapter(History): |
|
143 | class PtkHistoryAdapter(History): | |
139 | """ |
|
144 | """ | |
140 | Prompt toolkit has it's own way of handling history, Where it assumes it can |
|
145 | Prompt toolkit has it's own way of handling history, Where it assumes it can | |
141 | Push/pull from history. |
|
146 | Push/pull from history. | |
142 |
|
147 | |||
143 | """ |
|
148 | """ | |
144 |
|
149 | |||
145 | def __init__(self, shell): |
|
150 | def __init__(self, shell): | |
146 | super().__init__() |
|
151 | super().__init__() | |
147 | self.shell = shell |
|
152 | self.shell = shell | |
148 | self._refresh() |
|
153 | self._refresh() | |
149 |
|
154 | |||
150 | def append_string(self, string): |
|
155 | def append_string(self, string): | |
151 | # we rely on sql for that. |
|
156 | # we rely on sql for that. | |
152 | self._loaded = False |
|
157 | self._loaded = False | |
153 | self._refresh() |
|
158 | self._refresh() | |
154 |
|
159 | |||
155 | def _refresh(self): |
|
160 | def _refresh(self): | |
156 | if not self._loaded: |
|
161 | if not self._loaded: | |
157 | self._loaded_strings = list(self.load_history_strings()) |
|
162 | self._loaded_strings = list(self.load_history_strings()) | |
158 |
|
163 | |||
159 | def load_history_strings(self): |
|
164 | def load_history_strings(self): | |
160 | last_cell = "" |
|
165 | last_cell = "" | |
161 | res = [] |
|
166 | res = [] | |
162 | for __, ___, cell in self.shell.history_manager.get_tail( |
|
167 | for __, ___, cell in self.shell.history_manager.get_tail( | |
163 | self.shell.history_load_length, include_latest=True |
|
168 | self.shell.history_load_length, include_latest=True | |
164 | ): |
|
169 | ): | |
165 | # Ignore blank lines and consecutive duplicates |
|
170 | # Ignore blank lines and consecutive duplicates | |
166 | cell = cell.rstrip() |
|
171 | cell = cell.rstrip() | |
167 | if cell and (cell != last_cell): |
|
172 | if cell and (cell != last_cell): | |
168 | res.append(cell) |
|
173 | res.append(cell) | |
169 | last_cell = cell |
|
174 | last_cell = cell | |
170 | yield from res[::-1] |
|
175 | yield from res[::-1] | |
171 |
|
176 | |||
172 | def store_string(self, string: str) -> None: |
|
177 | def store_string(self, string: str) -> None: | |
173 | pass |
|
178 | pass | |
174 |
|
179 | |||
175 | class TerminalInteractiveShell(InteractiveShell): |
|
180 | class TerminalInteractiveShell(InteractiveShell): | |
176 | mime_renderers = Dict().tag(config=True) |
|
181 | mime_renderers = Dict().tag(config=True) | |
177 |
|
182 | |||
178 | space_for_menu = Integer(6, help='Number of line at the bottom of the screen ' |
|
183 | space_for_menu = Integer(6, help='Number of line at the bottom of the screen ' | |
179 | 'to reserve for the tab completion menu, ' |
|
184 | 'to reserve for the tab completion menu, ' | |
180 | 'search history, ...etc, the height of ' |
|
185 | 'search history, ...etc, the height of ' | |
181 | 'these menus will at most this value. ' |
|
186 | 'these menus will at most this value. ' | |
182 | 'Increase it is you prefer long and skinny ' |
|
187 | 'Increase it is you prefer long and skinny ' | |
183 | 'menus, decrease for short and wide.' |
|
188 | 'menus, decrease for short and wide.' | |
184 | ).tag(config=True) |
|
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 | debugger_history = None |
|
195 | debugger_history = None | |
188 |
|
196 | |||
189 | debugger_history_file = Unicode( |
|
197 | debugger_history_file = Unicode( | |
190 | "~/.pdbhistory", help="File in which to store and read history" |
|
198 | "~/.pdbhistory", help="File in which to store and read history" | |
191 | ).tag(config=True) |
|
199 | ).tag(config=True) | |
192 |
|
200 | |||
193 | simple_prompt = Bool(_use_simple_prompt, |
|
201 | simple_prompt = Bool(_use_simple_prompt, | |
194 | help="""Use `raw_input` for the REPL, without completion and prompt colors. |
|
202 | help="""Use `raw_input` for the REPL, without completion and prompt colors. | |
195 |
|
203 | |||
196 | Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are: |
|
204 | Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are: | |
197 | IPython own testing machinery, and emacs inferior-shell integration through elpy. |
|
205 | IPython own testing machinery, and emacs inferior-shell integration through elpy. | |
198 |
|
206 | |||
199 | This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT` |
|
207 | This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT` | |
200 | environment variable is set, or the current terminal is not a tty.""" |
|
208 | environment variable is set, or the current terminal is not a tty.""" | |
201 | ).tag(config=True) |
|
209 | ).tag(config=True) | |
202 |
|
210 | |||
203 | @property |
|
211 | @property | |
204 | def debugger_cls(self): |
|
212 | def debugger_cls(self): | |
205 | return Pdb if self.simple_prompt else TerminalPdb |
|
213 | return Pdb if self.simple_prompt else TerminalPdb | |
206 |
|
214 | |||
207 | confirm_exit = Bool(True, |
|
215 | confirm_exit = Bool(True, | |
208 | help=""" |
|
216 | help=""" | |
209 | Set to confirm when you try to exit IPython with an EOF (Control-D |
|
217 | Set to confirm when you try to exit IPython with an EOF (Control-D | |
210 | in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit', |
|
218 | in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit', | |
211 | you can force a direct exit without any confirmation.""", |
|
219 | you can force a direct exit without any confirmation.""", | |
212 | ).tag(config=True) |
|
220 | ).tag(config=True) | |
213 |
|
221 | |||
214 | editing_mode = Unicode('emacs', |
|
222 | editing_mode = Unicode('emacs', | |
215 | help="Shortcut style to use at the prompt. 'vi' or 'emacs'.", |
|
223 | help="Shortcut style to use at the prompt. 'vi' or 'emacs'.", | |
216 | ).tag(config=True) |
|
224 | ).tag(config=True) | |
217 |
|
225 | |||
218 | emacs_bindings_in_vi_insert_mode = Bool( |
|
226 | emacs_bindings_in_vi_insert_mode = Bool( | |
219 | True, |
|
227 | True, | |
220 | help="Add shortcuts from 'emacs' insert mode to 'vi' insert mode.", |
|
228 | help="Add shortcuts from 'emacs' insert mode to 'vi' insert mode.", | |
221 | ).tag(config=True) |
|
229 | ).tag(config=True) | |
222 |
|
230 | |||
223 | modal_cursor = Bool( |
|
231 | modal_cursor = Bool( | |
224 | True, |
|
232 | True, | |
225 | help=""" |
|
233 | help=""" | |
226 | Cursor shape changes depending on vi mode: beam in vi insert mode, |
|
234 | Cursor shape changes depending on vi mode: beam in vi insert mode, | |
227 | block in nav mode, underscore in replace mode.""", |
|
235 | block in nav mode, underscore in replace mode.""", | |
228 | ).tag(config=True) |
|
236 | ).tag(config=True) | |
229 |
|
237 | |||
230 | ttimeoutlen = Float( |
|
238 | ttimeoutlen = Float( | |
231 | 0.01, |
|
239 | 0.01, | |
232 | help="""The time in milliseconds that is waited for a key code |
|
240 | help="""The time in milliseconds that is waited for a key code | |
233 | to complete.""", |
|
241 | to complete.""", | |
234 | ).tag(config=True) |
|
242 | ).tag(config=True) | |
235 |
|
243 | |||
236 | timeoutlen = Float( |
|
244 | timeoutlen = Float( | |
237 | 0.5, |
|
245 | 0.5, | |
238 | help="""The time in milliseconds that is waited for a mapped key |
|
246 | help="""The time in milliseconds that is waited for a mapped key | |
239 | sequence to complete.""", |
|
247 | sequence to complete.""", | |
240 | ).tag(config=True) |
|
248 | ).tag(config=True) | |
241 |
|
249 | |||
242 | autoformatter = Unicode( |
|
250 | autoformatter = Unicode( | |
243 | None, |
|
251 | None, | |
244 | help="Autoformatter to reformat Terminal code. Can be `'black'`, `'yapf'` or `None`", |
|
252 | help="Autoformatter to reformat Terminal code. Can be `'black'`, `'yapf'` or `None`", | |
245 | allow_none=True |
|
253 | allow_none=True | |
246 | ).tag(config=True) |
|
254 | ).tag(config=True) | |
247 |
|
255 | |||
248 | auto_match = Bool( |
|
256 | auto_match = Bool( | |
249 | False, |
|
257 | False, | |
250 | help=""" |
|
258 | help=""" | |
251 | Automatically add/delete closing bracket or quote when opening bracket or quote is entered/deleted. |
|
259 | Automatically add/delete closing bracket or quote when opening bracket or quote is entered/deleted. | |
252 | Brackets: (), [], {} |
|
260 | Brackets: (), [], {} | |
253 | Quotes: '', \"\" |
|
261 | Quotes: '', \"\" | |
254 | """, |
|
262 | """, | |
255 | ).tag(config=True) |
|
263 | ).tag(config=True) | |
256 |
|
264 | |||
257 | mouse_support = Bool(False, |
|
265 | mouse_support = Bool(False, | |
258 | help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)" |
|
266 | help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)" | |
259 | ).tag(config=True) |
|
267 | ).tag(config=True) | |
260 |
|
268 | |||
261 | # We don't load the list of styles for the help string, because loading |
|
269 | # We don't load the list of styles for the help string, because loading | |
262 | # Pygments plugins takes time and can cause unexpected errors. |
|
270 | # Pygments plugins takes time and can cause unexpected errors. | |
263 | highlighting_style = Union([Unicode('legacy'), Type(klass=Style)], |
|
271 | highlighting_style = Union([Unicode('legacy'), Type(klass=Style)], | |
264 | help="""The name or class of a Pygments style to use for syntax |
|
272 | help="""The name or class of a Pygments style to use for syntax | |
265 | highlighting. To see available styles, run `pygmentize -L styles`.""" |
|
273 | highlighting. To see available styles, run `pygmentize -L styles`.""" | |
266 | ).tag(config=True) |
|
274 | ).tag(config=True) | |
267 |
|
275 | |||
268 | @validate('editing_mode') |
|
276 | @validate('editing_mode') | |
269 | def _validate_editing_mode(self, proposal): |
|
277 | def _validate_editing_mode(self, proposal): | |
270 | if proposal['value'].lower() == 'vim': |
|
278 | if proposal['value'].lower() == 'vim': | |
271 | proposal['value']= 'vi' |
|
279 | proposal['value']= 'vi' | |
272 | elif proposal['value'].lower() == 'default': |
|
280 | elif proposal['value'].lower() == 'default': | |
273 | proposal['value']= 'emacs' |
|
281 | proposal['value']= 'emacs' | |
274 |
|
282 | |||
275 | if hasattr(EditingMode, proposal['value'].upper()): |
|
283 | if hasattr(EditingMode, proposal['value'].upper()): | |
276 | return proposal['value'].lower() |
|
284 | return proposal['value'].lower() | |
277 |
|
285 | |||
278 | return self.editing_mode |
|
286 | return self.editing_mode | |
279 |
|
287 | |||
280 |
|
288 | |||
281 | @observe('editing_mode') |
|
289 | @observe('editing_mode') | |
282 | def _editing_mode(self, change): |
|
290 | def _editing_mode(self, change): | |
283 | if self.pt_app: |
|
291 | if self.pt_app: | |
284 | self.pt_app.editing_mode = getattr(EditingMode, change.new.upper()) |
|
292 | self.pt_app.editing_mode = getattr(EditingMode, change.new.upper()) | |
285 |
|
293 | |||
286 | def _set_formatter(self, formatter): |
|
294 | def _set_formatter(self, formatter): | |
287 | if formatter is None: |
|
295 | if formatter is None: | |
288 | self.reformat_handler = lambda x:x |
|
296 | self.reformat_handler = lambda x:x | |
289 | elif formatter == 'black': |
|
297 | elif formatter == 'black': | |
290 | self.reformat_handler = black_reformat_handler |
|
298 | self.reformat_handler = black_reformat_handler | |
291 | elif formatter == "yapf": |
|
299 | elif formatter == "yapf": | |
292 | self.reformat_handler = yapf_reformat_handler |
|
300 | self.reformat_handler = yapf_reformat_handler | |
293 | else: |
|
301 | else: | |
294 | raise ValueError |
|
302 | raise ValueError | |
295 |
|
303 | |||
296 | @observe("autoformatter") |
|
304 | @observe("autoformatter") | |
297 | def _autoformatter_changed(self, change): |
|
305 | def _autoformatter_changed(self, change): | |
298 | formatter = change.new |
|
306 | formatter = change.new | |
299 | self._set_formatter(formatter) |
|
307 | self._set_formatter(formatter) | |
300 |
|
308 | |||
301 | @observe('highlighting_style') |
|
309 | @observe('highlighting_style') | |
302 | @observe('colors') |
|
310 | @observe('colors') | |
303 | def _highlighting_style_changed(self, change): |
|
311 | def _highlighting_style_changed(self, change): | |
304 | self.refresh_style() |
|
312 | self.refresh_style() | |
305 |
|
313 | |||
306 | def refresh_style(self): |
|
314 | def refresh_style(self): | |
307 | self._style = self._make_style_from_name_or_cls(self.highlighting_style) |
|
315 | self._style = self._make_style_from_name_or_cls(self.highlighting_style) | |
308 |
|
316 | |||
309 |
|
317 | |||
310 | highlighting_style_overrides = Dict( |
|
318 | highlighting_style_overrides = Dict( | |
311 | help="Override highlighting format for specific tokens" |
|
319 | help="Override highlighting format for specific tokens" | |
312 | ).tag(config=True) |
|
320 | ).tag(config=True) | |
313 |
|
321 | |||
314 | true_color = Bool(False, |
|
322 | true_color = Bool(False, | |
315 | help="""Use 24bit colors instead of 256 colors in prompt highlighting. |
|
323 | help="""Use 24bit colors instead of 256 colors in prompt highlighting. | |
316 | If your terminal supports true color, the following command should |
|
324 | If your terminal supports true color, the following command should | |
317 | print ``TRUECOLOR`` in orange:: |
|
325 | print ``TRUECOLOR`` in orange:: | |
318 |
|
326 | |||
319 | printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\" |
|
327 | printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\" | |
320 | """, |
|
328 | """, | |
321 | ).tag(config=True) |
|
329 | ).tag(config=True) | |
322 |
|
330 | |||
323 | editor = Unicode(get_default_editor(), |
|
331 | editor = Unicode(get_default_editor(), | |
324 | help="Set the editor used by IPython (default to $EDITOR/vi/notepad)." |
|
332 | help="Set the editor used by IPython (default to $EDITOR/vi/notepad)." | |
325 | ).tag(config=True) |
|
333 | ).tag(config=True) | |
326 |
|
334 | |||
327 | prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True) |
|
335 | prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True) | |
328 |
|
336 | |||
329 | prompts = Instance(Prompts) |
|
337 | prompts = Instance(Prompts) | |
330 |
|
338 | |||
331 | @default('prompts') |
|
339 | @default('prompts') | |
332 | def _prompts_default(self): |
|
340 | def _prompts_default(self): | |
333 | return self.prompts_class(self) |
|
341 | return self.prompts_class(self) | |
334 |
|
342 | |||
335 | # @observe('prompts') |
|
343 | # @observe('prompts') | |
336 | # def _(self, change): |
|
344 | # def _(self, change): | |
337 | # self._update_layout() |
|
345 | # self._update_layout() | |
338 |
|
346 | |||
339 | @default('displayhook_class') |
|
347 | @default('displayhook_class') | |
340 | def _displayhook_class_default(self): |
|
348 | def _displayhook_class_default(self): | |
341 | return RichPromptDisplayHook |
|
349 | return RichPromptDisplayHook | |
342 |
|
350 | |||
343 | term_title = Bool(True, |
|
351 | term_title = Bool(True, | |
344 | help="Automatically set the terminal title" |
|
352 | help="Automatically set the terminal title" | |
345 | ).tag(config=True) |
|
353 | ).tag(config=True) | |
346 |
|
354 | |||
347 | term_title_format = Unicode("IPython: {cwd}", |
|
355 | term_title_format = Unicode("IPython: {cwd}", | |
348 | help="Customize the terminal title format. This is a python format string. " + |
|
356 | help="Customize the terminal title format. This is a python format string. " + | |
349 | "Available substitutions are: {cwd}." |
|
357 | "Available substitutions are: {cwd}." | |
350 | ).tag(config=True) |
|
358 | ).tag(config=True) | |
351 |
|
359 | |||
352 | display_completions = Enum(('column', 'multicolumn','readlinelike'), |
|
360 | display_completions = Enum(('column', 'multicolumn','readlinelike'), | |
353 | help= ( "Options for displaying tab completions, 'column', 'multicolumn', and " |
|
361 | help= ( "Options for displaying tab completions, 'column', 'multicolumn', and " | |
354 | "'readlinelike'. These options are for `prompt_toolkit`, see " |
|
362 | "'readlinelike'. These options are for `prompt_toolkit`, see " | |
355 | "`prompt_toolkit` documentation for more information." |
|
363 | "`prompt_toolkit` documentation for more information." | |
356 | ), |
|
364 | ), | |
357 | default_value='multicolumn').tag(config=True) |
|
365 | default_value='multicolumn').tag(config=True) | |
358 |
|
366 | |||
359 | highlight_matching_brackets = Bool(True, |
|
367 | highlight_matching_brackets = Bool(True, | |
360 | help="Highlight matching brackets.", |
|
368 | help="Highlight matching brackets.", | |
361 | ).tag(config=True) |
|
369 | ).tag(config=True) | |
362 |
|
370 | |||
363 | extra_open_editor_shortcuts = Bool(False, |
|
371 | extra_open_editor_shortcuts = Bool(False, | |
364 | help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. " |
|
372 | help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. " | |
365 | "This is in addition to the F2 binding, which is always enabled." |
|
373 | "This is in addition to the F2 binding, which is always enabled." | |
366 | ).tag(config=True) |
|
374 | ).tag(config=True) | |
367 |
|
375 | |||
368 | handle_return = Any(None, |
|
376 | handle_return = Any(None, | |
369 | help="Provide an alternative handler to be called when the user presses " |
|
377 | help="Provide an alternative handler to be called when the user presses " | |
370 | "Return. This is an advanced option intended for debugging, which " |
|
378 | "Return. This is an advanced option intended for debugging, which " | |
371 | "may be changed or removed in later releases." |
|
379 | "may be changed or removed in later releases." | |
372 | ).tag(config=True) |
|
380 | ).tag(config=True) | |
373 |
|
381 | |||
374 | enable_history_search = Bool(True, |
|
382 | enable_history_search = Bool(True, | |
375 | help="Allows to enable/disable the prompt toolkit history search" |
|
383 | help="Allows to enable/disable the prompt toolkit history search" | |
376 | ).tag(config=True) |
|
384 | ).tag(config=True) | |
377 |
|
385 | |||
378 | autosuggestions_provider = Unicode( |
|
386 | autosuggestions_provider = Unicode( | |
379 | "AutoSuggestFromHistory", |
|
387 | "NavigableAutoSuggestFromHistory", | |
380 | help="Specifies from which source automatic suggestions are provided. " |
|
388 | help="Specifies from which source automatic suggestions are provided. " | |
381 |
"Can be set to `'AutoSuggestFromHistory |
|
389 | "Can be set to ``'NavigableAutoSuggestFromHistory'`` (:kbd:`up` and " | |
382 |
" |
|
390 | ":kbd:`down` swap suggestions), ``'AutoSuggestFromHistory'``, " | |
|
391 | " or ``None`` to disable automatic suggestions. " | |||
|
392 | "Default is `'NavigableAutoSuggestFromHistory`'.", | |||
383 | allow_none=True, |
|
393 | allow_none=True, | |
384 | ).tag(config=True) |
|
394 | ).tag(config=True) | |
385 |
|
395 | |||
386 | def _set_autosuggestions(self, provider): |
|
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 | if provider is None: |
|
402 | if provider is None: | |
388 | self.auto_suggest = None |
|
403 | self.auto_suggest = None | |
389 | elif provider == "AutoSuggestFromHistory": |
|
404 | elif provider == "AutoSuggestFromHistory": | |
390 | self.auto_suggest = AutoSuggestFromHistory() |
|
405 | self.auto_suggest = AutoSuggestFromHistory() | |
|
406 | elif provider == "NavigableAutoSuggestFromHistory": | |||
|
407 | self.auto_suggest = NavigableAutoSuggestFromHistory() | |||
391 | else: |
|
408 | else: | |
392 | raise ValueError("No valid provider.") |
|
409 | raise ValueError("No valid provider.") | |
393 | if self.pt_app: |
|
410 | if self.pt_app: | |
394 | self.pt_app.auto_suggest = self.auto_suggest |
|
411 | self.pt_app.auto_suggest = self.auto_suggest | |
395 |
|
412 | |||
396 | @observe("autosuggestions_provider") |
|
413 | @observe("autosuggestions_provider") | |
397 | def _autosuggestions_provider_changed(self, change): |
|
414 | def _autosuggestions_provider_changed(self, change): | |
398 | provider = change.new |
|
415 | provider = change.new | |
399 | self._set_autosuggestions(provider) |
|
416 | self._set_autosuggestions(provider) | |
400 |
|
417 | |||
401 | prompt_includes_vi_mode = Bool(True, |
|
418 | prompt_includes_vi_mode = Bool(True, | |
402 | help="Display the current vi mode (when using vi editing mode)." |
|
419 | help="Display the current vi mode (when using vi editing mode)." | |
403 | ).tag(config=True) |
|
420 | ).tag(config=True) | |
404 |
|
421 | |||
405 | @observe('term_title') |
|
422 | @observe('term_title') | |
406 | def init_term_title(self, change=None): |
|
423 | def init_term_title(self, change=None): | |
407 | # Enable or disable the terminal title. |
|
424 | # Enable or disable the terminal title. | |
408 | if self.term_title and _is_tty: |
|
425 | if self.term_title and _is_tty: | |
409 | toggle_set_term_title(True) |
|
426 | toggle_set_term_title(True) | |
410 | set_term_title(self.term_title_format.format(cwd=abbrev_cwd())) |
|
427 | set_term_title(self.term_title_format.format(cwd=abbrev_cwd())) | |
411 | else: |
|
428 | else: | |
412 | toggle_set_term_title(False) |
|
429 | toggle_set_term_title(False) | |
413 |
|
430 | |||
414 | def restore_term_title(self): |
|
431 | def restore_term_title(self): | |
415 | if self.term_title and _is_tty: |
|
432 | if self.term_title and _is_tty: | |
416 | restore_term_title() |
|
433 | restore_term_title() | |
417 |
|
434 | |||
418 | def init_display_formatter(self): |
|
435 | def init_display_formatter(self): | |
419 | super(TerminalInteractiveShell, self).init_display_formatter() |
|
436 | super(TerminalInteractiveShell, self).init_display_formatter() | |
420 | # terminal only supports plain text |
|
437 | # terminal only supports plain text | |
421 | self.display_formatter.active_types = ["text/plain"] |
|
438 | self.display_formatter.active_types = ["text/plain"] | |
422 |
|
439 | |||
423 | def init_prompt_toolkit_cli(self): |
|
440 | def init_prompt_toolkit_cli(self): | |
424 | if self.simple_prompt: |
|
441 | if self.simple_prompt: | |
425 | # Fall back to plain non-interactive output for tests. |
|
442 | # Fall back to plain non-interactive output for tests. | |
426 | # This is very limited. |
|
443 | # This is very limited. | |
427 | def prompt(): |
|
444 | def prompt(): | |
428 | prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens()) |
|
445 | prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens()) | |
429 | lines = [input(prompt_text)] |
|
446 | lines = [input(prompt_text)] | |
430 | prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens()) |
|
447 | prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens()) | |
431 | while self.check_complete('\n'.join(lines))[0] == 'incomplete': |
|
448 | while self.check_complete('\n'.join(lines))[0] == 'incomplete': | |
432 | lines.append( input(prompt_continuation) ) |
|
449 | lines.append( input(prompt_continuation) ) | |
433 | return '\n'.join(lines) |
|
450 | return '\n'.join(lines) | |
434 | self.prompt_for_code = prompt |
|
451 | self.prompt_for_code = prompt | |
435 | return |
|
452 | return | |
436 |
|
453 | |||
437 | # Set up keyboard shortcuts |
|
454 | # Set up keyboard shortcuts | |
438 | key_bindings = create_ipython_shortcuts(self) |
|
455 | key_bindings = create_ipython_shortcuts(self) | |
439 |
|
456 | |||
440 |
|
457 | |||
441 | # Pre-populate history from IPython's history database |
|
458 | # Pre-populate history from IPython's history database | |
442 | history = PtkHistoryAdapter(self) |
|
459 | history = PtkHistoryAdapter(self) | |
443 |
|
460 | |||
444 | self._style = self._make_style_from_name_or_cls(self.highlighting_style) |
|
461 | self._style = self._make_style_from_name_or_cls(self.highlighting_style) | |
445 | self.style = DynamicStyle(lambda: self._style) |
|
462 | self.style = DynamicStyle(lambda: self._style) | |
446 |
|
463 | |||
447 | editing_mode = getattr(EditingMode, self.editing_mode.upper()) |
|
464 | editing_mode = getattr(EditingMode, self.editing_mode.upper()) | |
448 |
|
465 | |||
449 | self.pt_loop = asyncio.new_event_loop() |
|
466 | self.pt_loop = asyncio.new_event_loop() | |
450 | self.pt_app = PromptSession( |
|
467 | self.pt_app = PromptSession( | |
451 | auto_suggest=self.auto_suggest, |
|
468 | auto_suggest=self.auto_suggest, | |
452 | editing_mode=editing_mode, |
|
469 | editing_mode=editing_mode, | |
453 | key_bindings=key_bindings, |
|
470 | key_bindings=key_bindings, | |
454 | history=history, |
|
471 | history=history, | |
455 | completer=IPythonPTCompleter(shell=self), |
|
472 | completer=IPythonPTCompleter(shell=self), | |
456 | enable_history_search=self.enable_history_search, |
|
473 | enable_history_search=self.enable_history_search, | |
457 | style=self.style, |
|
474 | style=self.style, | |
458 | include_default_pygments_style=False, |
|
475 | include_default_pygments_style=False, | |
459 | mouse_support=self.mouse_support, |
|
476 | mouse_support=self.mouse_support, | |
460 | enable_open_in_editor=self.extra_open_editor_shortcuts, |
|
477 | enable_open_in_editor=self.extra_open_editor_shortcuts, | |
461 | color_depth=self.color_depth, |
|
478 | color_depth=self.color_depth, | |
462 | tempfile_suffix=".py", |
|
479 | tempfile_suffix=".py", | |
463 | **self._extra_prompt_options() |
|
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 | def _make_style_from_name_or_cls(self, name_or_cls): |
|
485 | def _make_style_from_name_or_cls(self, name_or_cls): | |
467 | """ |
|
486 | """ | |
468 | Small wrapper that make an IPython compatible style from a style name |
|
487 | Small wrapper that make an IPython compatible style from a style name | |
469 |
|
488 | |||
470 | We need that to add style for prompt ... etc. |
|
489 | We need that to add style for prompt ... etc. | |
471 | """ |
|
490 | """ | |
472 | style_overrides = {} |
|
491 | style_overrides = {} | |
473 | if name_or_cls == 'legacy': |
|
492 | if name_or_cls == 'legacy': | |
474 | legacy = self.colors.lower() |
|
493 | legacy = self.colors.lower() | |
475 | if legacy == 'linux': |
|
494 | if legacy == 'linux': | |
476 | style_cls = get_style_by_name('monokai') |
|
495 | style_cls = get_style_by_name('monokai') | |
477 | style_overrides = _style_overrides_linux |
|
496 | style_overrides = _style_overrides_linux | |
478 | elif legacy == 'lightbg': |
|
497 | elif legacy == 'lightbg': | |
479 | style_overrides = _style_overrides_light_bg |
|
498 | style_overrides = _style_overrides_light_bg | |
480 | style_cls = get_style_by_name('pastie') |
|
499 | style_cls = get_style_by_name('pastie') | |
481 | elif legacy == 'neutral': |
|
500 | elif legacy == 'neutral': | |
482 | # The default theme needs to be visible on both a dark background |
|
501 | # The default theme needs to be visible on both a dark background | |
483 | # and a light background, because we can't tell what the terminal |
|
502 | # and a light background, because we can't tell what the terminal | |
484 | # looks like. These tweaks to the default theme help with that. |
|
503 | # looks like. These tweaks to the default theme help with that. | |
485 | style_cls = get_style_by_name('default') |
|
504 | style_cls = get_style_by_name('default') | |
486 | style_overrides.update({ |
|
505 | style_overrides.update({ | |
487 | Token.Number: '#ansigreen', |
|
506 | Token.Number: '#ansigreen', | |
488 | Token.Operator: 'noinherit', |
|
507 | Token.Operator: 'noinherit', | |
489 | Token.String: '#ansiyellow', |
|
508 | Token.String: '#ansiyellow', | |
490 | Token.Name.Function: '#ansiblue', |
|
509 | Token.Name.Function: '#ansiblue', | |
491 | Token.Name.Class: 'bold #ansiblue', |
|
510 | Token.Name.Class: 'bold #ansiblue', | |
492 | Token.Name.Namespace: 'bold #ansiblue', |
|
511 | Token.Name.Namespace: 'bold #ansiblue', | |
493 | Token.Name.Variable.Magic: '#ansiblue', |
|
512 | Token.Name.Variable.Magic: '#ansiblue', | |
494 | Token.Prompt: '#ansigreen', |
|
513 | Token.Prompt: '#ansigreen', | |
495 | Token.PromptNum: '#ansibrightgreen bold', |
|
514 | Token.PromptNum: '#ansibrightgreen bold', | |
496 | Token.OutPrompt: '#ansired', |
|
515 | Token.OutPrompt: '#ansired', | |
497 | Token.OutPromptNum: '#ansibrightred bold', |
|
516 | Token.OutPromptNum: '#ansibrightred bold', | |
498 | }) |
|
517 | }) | |
499 |
|
518 | |||
500 | # Hack: Due to limited color support on the Windows console |
|
519 | # Hack: Due to limited color support on the Windows console | |
501 | # the prompt colors will be wrong without this |
|
520 | # the prompt colors will be wrong without this | |
502 | if os.name == 'nt': |
|
521 | if os.name == 'nt': | |
503 | style_overrides.update({ |
|
522 | style_overrides.update({ | |
504 | Token.Prompt: '#ansidarkgreen', |
|
523 | Token.Prompt: '#ansidarkgreen', | |
505 | Token.PromptNum: '#ansigreen bold', |
|
524 | Token.PromptNum: '#ansigreen bold', | |
506 | Token.OutPrompt: '#ansidarkred', |
|
525 | Token.OutPrompt: '#ansidarkred', | |
507 | Token.OutPromptNum: '#ansired bold', |
|
526 | Token.OutPromptNum: '#ansired bold', | |
508 | }) |
|
527 | }) | |
509 | elif legacy =='nocolor': |
|
528 | elif legacy =='nocolor': | |
510 | style_cls=_NoStyle |
|
529 | style_cls=_NoStyle | |
511 | style_overrides = {} |
|
530 | style_overrides = {} | |
512 | else : |
|
531 | else : | |
513 | raise ValueError('Got unknown colors: ', legacy) |
|
532 | raise ValueError('Got unknown colors: ', legacy) | |
514 | else : |
|
533 | else : | |
515 | if isinstance(name_or_cls, str): |
|
534 | if isinstance(name_or_cls, str): | |
516 | style_cls = get_style_by_name(name_or_cls) |
|
535 | style_cls = get_style_by_name(name_or_cls) | |
517 | else: |
|
536 | else: | |
518 | style_cls = name_or_cls |
|
537 | style_cls = name_or_cls | |
519 | style_overrides = { |
|
538 | style_overrides = { | |
520 | Token.Prompt: '#ansigreen', |
|
539 | Token.Prompt: '#ansigreen', | |
521 | Token.PromptNum: '#ansibrightgreen bold', |
|
540 | Token.PromptNum: '#ansibrightgreen bold', | |
522 | Token.OutPrompt: '#ansired', |
|
541 | Token.OutPrompt: '#ansired', | |
523 | Token.OutPromptNum: '#ansibrightred bold', |
|
542 | Token.OutPromptNum: '#ansibrightred bold', | |
524 | } |
|
543 | } | |
525 | style_overrides.update(self.highlighting_style_overrides) |
|
544 | style_overrides.update(self.highlighting_style_overrides) | |
526 | style = merge_styles([ |
|
545 | style = merge_styles([ | |
527 | style_from_pygments_cls(style_cls), |
|
546 | style_from_pygments_cls(style_cls), | |
528 | style_from_pygments_dict(style_overrides), |
|
547 | style_from_pygments_dict(style_overrides), | |
529 | ]) |
|
548 | ]) | |
530 |
|
549 | |||
531 | return style |
|
550 | return style | |
532 |
|
551 | |||
533 | @property |
|
552 | @property | |
534 | def pt_complete_style(self): |
|
553 | def pt_complete_style(self): | |
535 | return { |
|
554 | return { | |
536 | 'multicolumn': CompleteStyle.MULTI_COLUMN, |
|
555 | 'multicolumn': CompleteStyle.MULTI_COLUMN, | |
537 | 'column': CompleteStyle.COLUMN, |
|
556 | 'column': CompleteStyle.COLUMN, | |
538 | 'readlinelike': CompleteStyle.READLINE_LIKE, |
|
557 | 'readlinelike': CompleteStyle.READLINE_LIKE, | |
539 | }[self.display_completions] |
|
558 | }[self.display_completions] | |
540 |
|
559 | |||
541 | @property |
|
560 | @property | |
542 | def color_depth(self): |
|
561 | def color_depth(self): | |
543 | return (ColorDepth.TRUE_COLOR if self.true_color else None) |
|
562 | return (ColorDepth.TRUE_COLOR if self.true_color else None) | |
544 |
|
563 | |||
545 | def _extra_prompt_options(self): |
|
564 | def _extra_prompt_options(self): | |
546 | """ |
|
565 | """ | |
547 | Return the current layout option for the current Terminal InteractiveShell |
|
566 | Return the current layout option for the current Terminal InteractiveShell | |
548 | """ |
|
567 | """ | |
549 | def get_message(): |
|
568 | def get_message(): | |
550 | return PygmentsTokens(self.prompts.in_prompt_tokens()) |
|
569 | return PygmentsTokens(self.prompts.in_prompt_tokens()) | |
551 |
|
570 | |||
552 | if self.editing_mode == 'emacs': |
|
571 | if self.editing_mode == 'emacs': | |
553 | # with emacs mode the prompt is (usually) static, so we call only |
|
572 | # with emacs mode the prompt is (usually) static, so we call only | |
554 | # the function once. With VI mode it can toggle between [ins] and |
|
573 | # the function once. With VI mode it can toggle between [ins] and | |
555 | # [nor] so we can't precompute. |
|
574 | # [nor] so we can't precompute. | |
556 | # here I'm going to favor the default keybinding which almost |
|
575 | # here I'm going to favor the default keybinding which almost | |
557 | # everybody uses to decrease CPU usage. |
|
576 | # everybody uses to decrease CPU usage. | |
558 | # if we have issues with users with custom Prompts we can see how to |
|
577 | # if we have issues with users with custom Prompts we can see how to | |
559 | # work around this. |
|
578 | # work around this. | |
560 | get_message = get_message() |
|
579 | get_message = get_message() | |
561 |
|
580 | |||
562 | options = { |
|
581 | options = { | |
563 |
|
|
582 | "complete_in_thread": False, | |
564 |
|
|
583 | "lexer": IPythonPTLexer(), | |
565 |
|
|
584 | "reserve_space_for_menu": self.space_for_menu, | |
566 |
|
|
585 | "message": get_message, | |
567 |
|
|
586 | "prompt_continuation": ( | |
568 |
|
|
587 | lambda width, lineno, is_soft_wrap: PygmentsTokens( | |
569 |
|
|
588 | self.prompts.continuation_prompt_tokens(width) | |
570 |
|
|
589 | ) | |
571 | 'complete_style': self.pt_complete_style, |
|
590 | ), | |
572 |
|
591 | "multiline": True, | ||
|
592 | "complete_style": self.pt_complete_style, | |||
|
593 | "input_processors": [ | |||
573 | # Highlight matching brackets, but only when this setting is |
|
594 | # Highlight matching brackets, but only when this setting is | |
574 | # enabled, and only when the DEFAULT_BUFFER has the focus. |
|
595 | # enabled, and only when the DEFAULT_BUFFER has the focus. | |
575 |
|
|
596 | ConditionalProcessor( | |
576 |
|
|
597 | processor=HighlightMatchingBracketProcessor(chars="[](){}"), | |
577 |
|
|
598 | filter=HasFocus(DEFAULT_BUFFER) | |
578 | Condition(lambda: self.highlight_matching_brackets))], |
|
599 | & ~IsDone() | |
579 | } |
|
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 | if not PTK3: |
|
615 | if not PTK3: | |
581 | options['inputhook'] = self.inputhook |
|
616 | options['inputhook'] = self.inputhook | |
582 |
|
617 | |||
583 | return options |
|
618 | return options | |
584 |
|
619 | |||
585 | def prompt_for_code(self): |
|
620 | def prompt_for_code(self): | |
586 | if self.rl_next_input: |
|
621 | if self.rl_next_input: | |
587 | default = self.rl_next_input |
|
622 | default = self.rl_next_input | |
588 | self.rl_next_input = None |
|
623 | self.rl_next_input = None | |
589 | else: |
|
624 | else: | |
590 | default = '' |
|
625 | default = '' | |
591 |
|
626 | |||
592 | # In order to make sure that asyncio code written in the |
|
627 | # In order to make sure that asyncio code written in the | |
593 | # interactive shell doesn't interfere with the prompt, we run the |
|
628 | # interactive shell doesn't interfere with the prompt, we run the | |
594 | # prompt in a different event loop. |
|
629 | # prompt in a different event loop. | |
595 | # If we don't do this, people could spawn coroutine with a |
|
630 | # If we don't do this, people could spawn coroutine with a | |
596 | # while/true inside which will freeze the prompt. |
|
631 | # while/true inside which will freeze the prompt. | |
597 |
|
632 | |||
598 | policy = asyncio.get_event_loop_policy() |
|
633 | policy = asyncio.get_event_loop_policy() | |
599 | old_loop = get_asyncio_loop() |
|
634 | old_loop = get_asyncio_loop() | |
600 |
|
635 | |||
601 | # FIXME: prompt_toolkit is using the deprecated `asyncio.get_event_loop` |
|
636 | # FIXME: prompt_toolkit is using the deprecated `asyncio.get_event_loop` | |
602 | # to get the current event loop. |
|
637 | # to get the current event loop. | |
603 | # This will probably be replaced by an attribute or input argument, |
|
638 | # This will probably be replaced by an attribute or input argument, | |
604 | # at which point we can stop calling the soon-to-be-deprecated `set_event_loop` here. |
|
639 | # at which point we can stop calling the soon-to-be-deprecated `set_event_loop` here. | |
605 | if old_loop is not self.pt_loop: |
|
640 | if old_loop is not self.pt_loop: | |
606 | policy.set_event_loop(self.pt_loop) |
|
641 | policy.set_event_loop(self.pt_loop) | |
607 | try: |
|
642 | try: | |
608 | with patch_stdout(raw=True): |
|
643 | with patch_stdout(raw=True): | |
609 | text = self.pt_app.prompt( |
|
644 | text = self.pt_app.prompt( | |
610 | default=default, |
|
645 | default=default, | |
611 | **self._extra_prompt_options()) |
|
646 | **self._extra_prompt_options()) | |
612 | finally: |
|
647 | finally: | |
613 | # Restore the original event loop. |
|
648 | # Restore the original event loop. | |
614 | if old_loop is not None and old_loop is not self.pt_loop: |
|
649 | if old_loop is not None and old_loop is not self.pt_loop: | |
615 | policy.set_event_loop(old_loop) |
|
650 | policy.set_event_loop(old_loop) | |
616 |
|
651 | |||
617 | return text |
|
652 | return text | |
618 |
|
653 | |||
619 | def enable_win_unicode_console(self): |
|
654 | def enable_win_unicode_console(self): | |
620 | # Since IPython 7.10 doesn't support python < 3.6 and PEP 528, Python uses the unicode APIs for the Windows |
|
655 | # Since IPython 7.10 doesn't support python < 3.6 and PEP 528, Python uses the unicode APIs for the Windows | |
621 | # console by default, so WUC shouldn't be needed. |
|
656 | # console by default, so WUC shouldn't be needed. | |
622 | warn("`enable_win_unicode_console` is deprecated since IPython 7.10, does not do anything and will be removed in the future", |
|
657 | warn("`enable_win_unicode_console` is deprecated since IPython 7.10, does not do anything and will be removed in the future", | |
623 | DeprecationWarning, |
|
658 | DeprecationWarning, | |
624 | stacklevel=2) |
|
659 | stacklevel=2) | |
625 |
|
660 | |||
626 | def init_io(self): |
|
661 | def init_io(self): | |
627 | if sys.platform not in {'win32', 'cli'}: |
|
662 | if sys.platform not in {'win32', 'cli'}: | |
628 | return |
|
663 | return | |
629 |
|
664 | |||
630 | import colorama |
|
665 | import colorama | |
631 | colorama.init() |
|
666 | colorama.init() | |
632 |
|
667 | |||
633 | def init_magics(self): |
|
668 | def init_magics(self): | |
634 | super(TerminalInteractiveShell, self).init_magics() |
|
669 | super(TerminalInteractiveShell, self).init_magics() | |
635 | self.register_magics(TerminalMagics) |
|
670 | self.register_magics(TerminalMagics) | |
636 |
|
671 | |||
637 | def init_alias(self): |
|
672 | def init_alias(self): | |
638 | # The parent class defines aliases that can be safely used with any |
|
673 | # The parent class defines aliases that can be safely used with any | |
639 | # frontend. |
|
674 | # frontend. | |
640 | super(TerminalInteractiveShell, self).init_alias() |
|
675 | super(TerminalInteractiveShell, self).init_alias() | |
641 |
|
676 | |||
642 | # Now define aliases that only make sense on the terminal, because they |
|
677 | # Now define aliases that only make sense on the terminal, because they | |
643 | # need direct access to the console in a way that we can't emulate in |
|
678 | # need direct access to the console in a way that we can't emulate in | |
644 | # GUI or web frontend |
|
679 | # GUI or web frontend | |
645 | if os.name == 'posix': |
|
680 | if os.name == 'posix': | |
646 | for cmd in ('clear', 'more', 'less', 'man'): |
|
681 | for cmd in ('clear', 'more', 'less', 'man'): | |
647 | self.alias_manager.soft_define_alias(cmd, cmd) |
|
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 | super(TerminalInteractiveShell, self).__init__(*args, **kwargs) |
|
686 | super(TerminalInteractiveShell, self).__init__(*args, **kwargs) | |
652 | self._set_autosuggestions(self.autosuggestions_provider) |
|
687 | self._set_autosuggestions(self.autosuggestions_provider) | |
653 | self.init_prompt_toolkit_cli() |
|
688 | self.init_prompt_toolkit_cli() | |
654 | self.init_term_title() |
|
689 | self.init_term_title() | |
655 | self.keep_running = True |
|
690 | self.keep_running = True | |
656 | self._set_formatter(self.autoformatter) |
|
691 | self._set_formatter(self.autoformatter) | |
657 |
|
692 | |||
658 |
|
693 | |||
659 | def ask_exit(self): |
|
694 | def ask_exit(self): | |
660 | self.keep_running = False |
|
695 | self.keep_running = False | |
661 |
|
696 | |||
662 | rl_next_input = None |
|
697 | rl_next_input = None | |
663 |
|
698 | |||
664 | def interact(self): |
|
699 | def interact(self): | |
665 | self.keep_running = True |
|
700 | self.keep_running = True | |
666 | while self.keep_running: |
|
701 | while self.keep_running: | |
667 | print(self.separate_in, end='') |
|
702 | print(self.separate_in, end='') | |
668 |
|
703 | |||
669 | try: |
|
704 | try: | |
670 | code = self.prompt_for_code() |
|
705 | code = self.prompt_for_code() | |
671 | except EOFError: |
|
706 | except EOFError: | |
672 | if (not self.confirm_exit) \ |
|
707 | if (not self.confirm_exit) \ | |
673 | or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'): |
|
708 | or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'): | |
674 | self.ask_exit() |
|
709 | self.ask_exit() | |
675 |
|
710 | |||
676 | else: |
|
711 | else: | |
677 | if code: |
|
712 | if code: | |
678 | self.run_cell(code, store_history=True) |
|
713 | self.run_cell(code, store_history=True) | |
679 |
|
714 | |||
680 | def mainloop(self): |
|
715 | def mainloop(self): | |
681 | # An extra layer of protection in case someone mashing Ctrl-C breaks |
|
716 | # An extra layer of protection in case someone mashing Ctrl-C breaks | |
682 | # out of our internal code. |
|
717 | # out of our internal code. | |
683 | while True: |
|
718 | while True: | |
684 | try: |
|
719 | try: | |
685 | self.interact() |
|
720 | self.interact() | |
686 | break |
|
721 | break | |
687 | except KeyboardInterrupt as e: |
|
722 | except KeyboardInterrupt as e: | |
688 | print("\n%s escaped interact()\n" % type(e).__name__) |
|
723 | print("\n%s escaped interact()\n" % type(e).__name__) | |
689 | finally: |
|
724 | finally: | |
690 | # An interrupt during the eventloop will mess up the |
|
725 | # An interrupt during the eventloop will mess up the | |
691 | # internal state of the prompt_toolkit library. |
|
726 | # internal state of the prompt_toolkit library. | |
692 | # Stopping the eventloop fixes this, see |
|
727 | # Stopping the eventloop fixes this, see | |
693 | # https://github.com/ipython/ipython/pull/9867 |
|
728 | # https://github.com/ipython/ipython/pull/9867 | |
694 | if hasattr(self, '_eventloop'): |
|
729 | if hasattr(self, '_eventloop'): | |
695 | self._eventloop.stop() |
|
730 | self._eventloop.stop() | |
696 |
|
731 | |||
697 | self.restore_term_title() |
|
732 | self.restore_term_title() | |
698 |
|
733 | |||
699 | # try to call some at-exit operation optimistically as some things can't |
|
734 | # try to call some at-exit operation optimistically as some things can't | |
700 | # be done during interpreter shutdown. this is technically inaccurate as |
|
735 | # be done during interpreter shutdown. this is technically inaccurate as | |
701 | # this make mainlool not re-callable, but that should be a rare if not |
|
736 | # this make mainlool not re-callable, but that should be a rare if not | |
702 | # in existent use case. |
|
737 | # in existent use case. | |
703 |
|
738 | |||
704 | self._atexit_once() |
|
739 | self._atexit_once() | |
705 |
|
740 | |||
706 |
|
741 | |||
707 | _inputhook = None |
|
742 | _inputhook = None | |
708 | def inputhook(self, context): |
|
743 | def inputhook(self, context): | |
709 | if self._inputhook is not None: |
|
744 | if self._inputhook is not None: | |
710 | self._inputhook(context) |
|
745 | self._inputhook(context) | |
711 |
|
746 | |||
712 | active_eventloop = None |
|
747 | active_eventloop = None | |
713 | def enable_gui(self, gui=None): |
|
748 | def enable_gui(self, gui=None): | |
714 | if gui and (gui not in {"inline", "webagg"}): |
|
749 | if gui and (gui not in {"inline", "webagg"}): | |
715 | self.active_eventloop, self._inputhook = get_inputhook_name_and_func(gui) |
|
750 | self.active_eventloop, self._inputhook = get_inputhook_name_and_func(gui) | |
716 | else: |
|
751 | else: | |
717 | self.active_eventloop = self._inputhook = None |
|
752 | self.active_eventloop = self._inputhook = None | |
718 |
|
753 | |||
719 | # For prompt_toolkit 3.0. We have to create an asyncio event loop with |
|
754 | # For prompt_toolkit 3.0. We have to create an asyncio event loop with | |
720 | # this inputhook. |
|
755 | # this inputhook. | |
721 | if PTK3: |
|
756 | if PTK3: | |
722 | import asyncio |
|
757 | import asyncio | |
723 | from prompt_toolkit.eventloop import new_eventloop_with_inputhook |
|
758 | from prompt_toolkit.eventloop import new_eventloop_with_inputhook | |
724 |
|
759 | |||
725 | if gui == 'asyncio': |
|
760 | if gui == 'asyncio': | |
726 | # When we integrate the asyncio event loop, run the UI in the |
|
761 | # When we integrate the asyncio event loop, run the UI in the | |
727 | # same event loop as the rest of the code. don't use an actual |
|
762 | # same event loop as the rest of the code. don't use an actual | |
728 | # input hook. (Asyncio is not made for nesting event loops.) |
|
763 | # input hook. (Asyncio is not made for nesting event loops.) | |
729 | self.pt_loop = get_asyncio_loop() |
|
764 | self.pt_loop = get_asyncio_loop() | |
730 |
|
765 | |||
731 | elif self._inputhook: |
|
766 | elif self._inputhook: | |
732 | # If an inputhook was set, create a new asyncio event loop with |
|
767 | # If an inputhook was set, create a new asyncio event loop with | |
733 | # this inputhook for the prompt. |
|
768 | # this inputhook for the prompt. | |
734 | self.pt_loop = new_eventloop_with_inputhook(self._inputhook) |
|
769 | self.pt_loop = new_eventloop_with_inputhook(self._inputhook) | |
735 | else: |
|
770 | else: | |
736 | # When there's no inputhook, run the prompt in a separate |
|
771 | # When there's no inputhook, run the prompt in a separate | |
737 | # asyncio event loop. |
|
772 | # asyncio event loop. | |
738 | self.pt_loop = asyncio.new_event_loop() |
|
773 | self.pt_loop = asyncio.new_event_loop() | |
739 |
|
774 | |||
740 | # Run !system commands directly, not through pipes, so terminal programs |
|
775 | # Run !system commands directly, not through pipes, so terminal programs | |
741 | # work correctly. |
|
776 | # work correctly. | |
742 | system = InteractiveShell.system_raw |
|
777 | system = InteractiveShell.system_raw | |
743 |
|
778 | |||
744 | def auto_rewrite_input(self, cmd): |
|
779 | def auto_rewrite_input(self, cmd): | |
745 | """Overridden from the parent class to use fancy rewriting prompt""" |
|
780 | """Overridden from the parent class to use fancy rewriting prompt""" | |
746 | if not self.show_rewritten_input: |
|
781 | if not self.show_rewritten_input: | |
747 | return |
|
782 | return | |
748 |
|
783 | |||
749 | tokens = self.prompts.rewrite_prompt_tokens() |
|
784 | tokens = self.prompts.rewrite_prompt_tokens() | |
750 | if self.pt_app: |
|
785 | if self.pt_app: | |
751 | print_formatted_text(PygmentsTokens(tokens), end='', |
|
786 | print_formatted_text(PygmentsTokens(tokens), end='', | |
752 | style=self.pt_app.app.style) |
|
787 | style=self.pt_app.app.style) | |
753 | print(cmd) |
|
788 | print(cmd) | |
754 | else: |
|
789 | else: | |
755 | prompt = ''.join(s for t, s in tokens) |
|
790 | prompt = ''.join(s for t, s in tokens) | |
756 | print(prompt, cmd, sep='') |
|
791 | print(prompt, cmd, sep='') | |
757 |
|
792 | |||
758 | _prompts_before = None |
|
793 | _prompts_before = None | |
759 | def switch_doctest_mode(self, mode): |
|
794 | def switch_doctest_mode(self, mode): | |
760 | """Switch prompts to classic for %doctest_mode""" |
|
795 | """Switch prompts to classic for %doctest_mode""" | |
761 | if mode: |
|
796 | if mode: | |
762 | self._prompts_before = self.prompts |
|
797 | self._prompts_before = self.prompts | |
763 | self.prompts = ClassicPrompts(self) |
|
798 | self.prompts = ClassicPrompts(self) | |
764 | elif self._prompts_before: |
|
799 | elif self._prompts_before: | |
765 | self.prompts = self._prompts_before |
|
800 | self.prompts = self._prompts_before | |
766 | self._prompts_before = None |
|
801 | self._prompts_before = None | |
767 | # self._update_layout() |
|
802 | # self._update_layout() | |
768 |
|
803 | |||
769 |
|
804 | |||
770 | InteractiveShellABC.register(TerminalInteractiveShell) |
|
805 | InteractiveShellABC.register(TerminalInteractiveShell) | |
771 |
|
806 | |||
772 | if __name__ == '__main__': |
|
807 | if __name__ == '__main__': | |
773 | TerminalInteractiveShell.instance().interact() |
|
808 | TerminalInteractiveShell.instance().interact() |
@@ -1,343 +1,343 b'' | |||||
1 | #!/usr/bin/env python |
|
1 | #!/usr/bin/env python | |
2 | # encoding: utf-8 |
|
2 | # encoding: utf-8 | |
3 | """ |
|
3 | """ | |
4 | The :class:`~traitlets.config.application.Application` object for the command |
|
4 | The :class:`~traitlets.config.application.Application` object for the command | |
5 | line :command:`ipython` program. |
|
5 | line :command:`ipython` program. | |
6 | """ |
|
6 | """ | |
7 |
|
7 | |||
8 | # Copyright (c) IPython Development Team. |
|
8 | # Copyright (c) IPython Development Team. | |
9 | # Distributed under the terms of the Modified BSD License. |
|
9 | # Distributed under the terms of the Modified BSD License. | |
10 |
|
10 | |||
11 |
|
11 | |||
12 | import logging |
|
12 | import logging | |
13 | import os |
|
13 | import os | |
14 | import sys |
|
14 | import sys | |
15 | import warnings |
|
15 | import warnings | |
16 |
|
16 | |||
17 | from traitlets.config.loader import Config |
|
17 | from traitlets.config.loader import Config | |
18 | from traitlets.config.application import boolean_flag, catch_config_error |
|
18 | from traitlets.config.application import boolean_flag, catch_config_error | |
19 | from IPython.core import release |
|
19 | from IPython.core import release | |
20 | from IPython.core import usage |
|
20 | from IPython.core import usage | |
21 | from IPython.core.completer import IPCompleter |
|
21 | from IPython.core.completer import IPCompleter | |
22 | from IPython.core.crashhandler import CrashHandler |
|
22 | from IPython.core.crashhandler import CrashHandler | |
23 | from IPython.core.formatters import PlainTextFormatter |
|
23 | from IPython.core.formatters import PlainTextFormatter | |
24 | from IPython.core.history import HistoryManager |
|
24 | from IPython.core.history import HistoryManager | |
25 | from IPython.core.application import ( |
|
25 | from IPython.core.application import ( | |
26 | ProfileDir, BaseIPythonApplication, base_flags, base_aliases |
|
26 | ProfileDir, BaseIPythonApplication, base_flags, base_aliases | |
27 | ) |
|
27 | ) | |
28 | from IPython.core.magic import MagicsManager |
|
28 | from IPython.core.magic import MagicsManager | |
29 | from IPython.core.magics import ( |
|
29 | from IPython.core.magics import ( | |
30 | ScriptMagics, LoggingMagics |
|
30 | ScriptMagics, LoggingMagics | |
31 | ) |
|
31 | ) | |
32 | from IPython.core.shellapp import ( |
|
32 | from IPython.core.shellapp import ( | |
33 | InteractiveShellApp, shell_flags, shell_aliases |
|
33 | InteractiveShellApp, shell_flags, shell_aliases | |
34 | ) |
|
34 | ) | |
35 | from IPython.extensions.storemagic import StoreMagics |
|
35 | from IPython.extensions.storemagic import StoreMagics | |
36 | from .interactiveshell import TerminalInteractiveShell |
|
36 | from .interactiveshell import TerminalInteractiveShell | |
37 | from IPython.paths import get_ipython_dir |
|
37 | from IPython.paths import get_ipython_dir | |
38 | from traitlets import ( |
|
38 | from traitlets import ( | |
39 | Bool, List, default, observe, Type |
|
39 | Bool, List, default, observe, Type | |
40 | ) |
|
40 | ) | |
41 |
|
41 | |||
42 | #----------------------------------------------------------------------------- |
|
42 | #----------------------------------------------------------------------------- | |
43 | # Globals, utilities and helpers |
|
43 | # Globals, utilities and helpers | |
44 | #----------------------------------------------------------------------------- |
|
44 | #----------------------------------------------------------------------------- | |
45 |
|
45 | |||
46 | _examples = """ |
|
46 | _examples = """ | |
47 | ipython --matplotlib # enable matplotlib integration |
|
47 | ipython --matplotlib # enable matplotlib integration | |
48 | ipython --matplotlib=qt # enable matplotlib integration with qt4 backend |
|
48 | ipython --matplotlib=qt # enable matplotlib integration with qt4 backend | |
49 |
|
49 | |||
50 | ipython --log-level=DEBUG # set logging to DEBUG |
|
50 | ipython --log-level=DEBUG # set logging to DEBUG | |
51 | ipython --profile=foo # start with profile foo |
|
51 | ipython --profile=foo # start with profile foo | |
52 |
|
52 | |||
53 | ipython profile create foo # create profile foo w/ default config files |
|
53 | ipython profile create foo # create profile foo w/ default config files | |
54 | ipython help profile # show the help for the profile subcmd |
|
54 | ipython help profile # show the help for the profile subcmd | |
55 |
|
55 | |||
56 | ipython locate # print the path to the IPython directory |
|
56 | ipython locate # print the path to the IPython directory | |
57 | ipython locate profile foo # print the path to the directory for profile `foo` |
|
57 | ipython locate profile foo # print the path to the directory for profile `foo` | |
58 | """ |
|
58 | """ | |
59 |
|
59 | |||
60 | #----------------------------------------------------------------------------- |
|
60 | #----------------------------------------------------------------------------- | |
61 | # Crash handler for this application |
|
61 | # Crash handler for this application | |
62 | #----------------------------------------------------------------------------- |
|
62 | #----------------------------------------------------------------------------- | |
63 |
|
63 | |||
64 | class IPAppCrashHandler(CrashHandler): |
|
64 | class IPAppCrashHandler(CrashHandler): | |
65 | """sys.excepthook for IPython itself, leaves a detailed report on disk.""" |
|
65 | """sys.excepthook for IPython itself, leaves a detailed report on disk.""" | |
66 |
|
66 | |||
67 | def __init__(self, app): |
|
67 | def __init__(self, app): | |
68 | contact_name = release.author |
|
68 | contact_name = release.author | |
69 | contact_email = release.author_email |
|
69 | contact_email = release.author_email | |
70 | bug_tracker = 'https://github.com/ipython/ipython/issues' |
|
70 | bug_tracker = 'https://github.com/ipython/ipython/issues' | |
71 | super(IPAppCrashHandler,self).__init__( |
|
71 | super(IPAppCrashHandler,self).__init__( | |
72 | app, contact_name, contact_email, bug_tracker |
|
72 | app, contact_name, contact_email, bug_tracker | |
73 | ) |
|
73 | ) | |
74 |
|
74 | |||
75 | def make_report(self,traceback): |
|
75 | def make_report(self,traceback): | |
76 | """Return a string containing a crash report.""" |
|
76 | """Return a string containing a crash report.""" | |
77 |
|
77 | |||
78 | sec_sep = self.section_sep |
|
78 | sec_sep = self.section_sep | |
79 | # Start with parent report |
|
79 | # Start with parent report | |
80 | report = [super(IPAppCrashHandler, self).make_report(traceback)] |
|
80 | report = [super(IPAppCrashHandler, self).make_report(traceback)] | |
81 | # Add interactive-specific info we may have |
|
81 | # Add interactive-specific info we may have | |
82 | rpt_add = report.append |
|
82 | rpt_add = report.append | |
83 | try: |
|
83 | try: | |
84 | rpt_add(sec_sep+"History of session input:") |
|
84 | rpt_add(sec_sep+"History of session input:") | |
85 | for line in self.app.shell.user_ns['_ih']: |
|
85 | for line in self.app.shell.user_ns['_ih']: | |
86 | rpt_add(line) |
|
86 | rpt_add(line) | |
87 | rpt_add('\n*** Last line of input (may not be in above history):\n') |
|
87 | rpt_add('\n*** Last line of input (may not be in above history):\n') | |
88 | rpt_add(self.app.shell._last_input_line+'\n') |
|
88 | rpt_add(self.app.shell._last_input_line+'\n') | |
89 | except: |
|
89 | except: | |
90 | pass |
|
90 | pass | |
91 |
|
91 | |||
92 | return ''.join(report) |
|
92 | return ''.join(report) | |
93 |
|
93 | |||
94 | #----------------------------------------------------------------------------- |
|
94 | #----------------------------------------------------------------------------- | |
95 | # Aliases and Flags |
|
95 | # Aliases and Flags | |
96 | #----------------------------------------------------------------------------- |
|
96 | #----------------------------------------------------------------------------- | |
97 | flags = dict(base_flags) |
|
97 | flags = dict(base_flags) | |
98 | flags.update(shell_flags) |
|
98 | flags.update(shell_flags) | |
99 | frontend_flags = {} |
|
99 | frontend_flags = {} | |
100 | addflag = lambda *args: frontend_flags.update(boolean_flag(*args)) |
|
100 | addflag = lambda *args: frontend_flags.update(boolean_flag(*args)) | |
101 | addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax', |
|
101 | addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax', | |
102 | 'Turn on auto editing of files with syntax errors.', |
|
102 | 'Turn on auto editing of files with syntax errors.', | |
103 | 'Turn off auto editing of files with syntax errors.' |
|
103 | 'Turn off auto editing of files with syntax errors.' | |
104 | ) |
|
104 | ) | |
105 | addflag('simple-prompt', 'TerminalInteractiveShell.simple_prompt', |
|
105 | addflag('simple-prompt', 'TerminalInteractiveShell.simple_prompt', | |
106 | "Force simple minimal prompt using `raw_input`", |
|
106 | "Force simple minimal prompt using `raw_input`", | |
107 | "Use a rich interactive prompt with prompt_toolkit", |
|
107 | "Use a rich interactive prompt with prompt_toolkit", | |
108 | ) |
|
108 | ) | |
109 |
|
109 | |||
110 | addflag('banner', 'TerminalIPythonApp.display_banner', |
|
110 | addflag('banner', 'TerminalIPythonApp.display_banner', | |
111 | "Display a banner upon starting IPython.", |
|
111 | "Display a banner upon starting IPython.", | |
112 | "Don't display a banner upon starting IPython." |
|
112 | "Don't display a banner upon starting IPython." | |
113 | ) |
|
113 | ) | |
114 | addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit', |
|
114 | addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit', | |
115 | """Set to confirm when you try to exit IPython with an EOF (Control-D |
|
115 | """Set to confirm when you try to exit IPython with an EOF (Control-D | |
116 | in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit', |
|
116 | in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit', | |
117 | you can force a direct exit without any confirmation.""", |
|
117 | you can force a direct exit without any confirmation.""", | |
118 | "Don't prompt the user when exiting." |
|
118 | "Don't prompt the user when exiting." | |
119 | ) |
|
119 | ) | |
120 | addflag('term-title', 'TerminalInteractiveShell.term_title', |
|
120 | addflag('term-title', 'TerminalInteractiveShell.term_title', | |
121 | "Enable auto setting the terminal title.", |
|
121 | "Enable auto setting the terminal title.", | |
122 | "Disable auto setting the terminal title." |
|
122 | "Disable auto setting the terminal title." | |
123 | ) |
|
123 | ) | |
124 | classic_config = Config() |
|
124 | classic_config = Config() | |
125 | classic_config.InteractiveShell.cache_size = 0 |
|
125 | classic_config.InteractiveShell.cache_size = 0 | |
126 | classic_config.PlainTextFormatter.pprint = False |
|
126 | classic_config.PlainTextFormatter.pprint = False | |
127 | classic_config.TerminalInteractiveShell.prompts_class='IPython.terminal.prompts.ClassicPrompts' |
|
127 | classic_config.TerminalInteractiveShell.prompts_class='IPython.terminal.prompts.ClassicPrompts' | |
128 | classic_config.InteractiveShell.separate_in = '' |
|
128 | classic_config.InteractiveShell.separate_in = '' | |
129 | classic_config.InteractiveShell.separate_out = '' |
|
129 | classic_config.InteractiveShell.separate_out = '' | |
130 | classic_config.InteractiveShell.separate_out2 = '' |
|
130 | classic_config.InteractiveShell.separate_out2 = '' | |
131 | classic_config.InteractiveShell.colors = 'NoColor' |
|
131 | classic_config.InteractiveShell.colors = 'NoColor' | |
132 | classic_config.InteractiveShell.xmode = 'Plain' |
|
132 | classic_config.InteractiveShell.xmode = 'Plain' | |
133 |
|
133 | |||
134 | frontend_flags['classic']=( |
|
134 | frontend_flags['classic']=( | |
135 | classic_config, |
|
135 | classic_config, | |
136 | "Gives IPython a similar feel to the classic Python prompt." |
|
136 | "Gives IPython a similar feel to the classic Python prompt." | |
137 | ) |
|
137 | ) | |
138 | # # log doesn't make so much sense this way anymore |
|
138 | # # log doesn't make so much sense this way anymore | |
139 | # paa('--log','-l', |
|
139 | # paa('--log','-l', | |
140 | # action='store_true', dest='InteractiveShell.logstart', |
|
140 | # action='store_true', dest='InteractiveShell.logstart', | |
141 | # help="Start logging to the default log file (./ipython_log.py).") |
|
141 | # help="Start logging to the default log file (./ipython_log.py).") | |
142 | # |
|
142 | # | |
143 | # # quick is harder to implement |
|
143 | # # quick is harder to implement | |
144 | frontend_flags['quick']=( |
|
144 | frontend_flags['quick']=( | |
145 | {'TerminalIPythonApp' : {'quick' : True}}, |
|
145 | {'TerminalIPythonApp' : {'quick' : True}}, | |
146 | "Enable quick startup with no config files." |
|
146 | "Enable quick startup with no config files." | |
147 | ) |
|
147 | ) | |
148 |
|
148 | |||
149 | frontend_flags['i'] = ( |
|
149 | frontend_flags['i'] = ( | |
150 | {'TerminalIPythonApp' : {'force_interact' : True}}, |
|
150 | {'TerminalIPythonApp' : {'force_interact' : True}}, | |
151 | """If running code from the command line, become interactive afterwards. |
|
151 | """If running code from the command line, become interactive afterwards. | |
152 | It is often useful to follow this with `--` to treat remaining flags as |
|
152 | It is often useful to follow this with `--` to treat remaining flags as | |
153 | script arguments. |
|
153 | script arguments. | |
154 | """ |
|
154 | """ | |
155 | ) |
|
155 | ) | |
156 | flags.update(frontend_flags) |
|
156 | flags.update(frontend_flags) | |
157 |
|
157 | |||
158 | aliases = dict(base_aliases) |
|
158 | aliases = dict(base_aliases) | |
159 | aliases.update(shell_aliases) |
|
159 | aliases.update(shell_aliases) # type: ignore[arg-type] | |
160 |
|
160 | |||
161 | #----------------------------------------------------------------------------- |
|
161 | #----------------------------------------------------------------------------- | |
162 | # Main classes and functions |
|
162 | # Main classes and functions | |
163 | #----------------------------------------------------------------------------- |
|
163 | #----------------------------------------------------------------------------- | |
164 |
|
164 | |||
165 |
|
165 | |||
166 | class LocateIPythonApp(BaseIPythonApplication): |
|
166 | class LocateIPythonApp(BaseIPythonApplication): | |
167 | description = """print the path to the IPython dir""" |
|
167 | description = """print the path to the IPython dir""" | |
168 | subcommands = dict( |
|
168 | subcommands = dict( | |
169 | profile=('IPython.core.profileapp.ProfileLocate', |
|
169 | profile=('IPython.core.profileapp.ProfileLocate', | |
170 | "print the path to an IPython profile directory", |
|
170 | "print the path to an IPython profile directory", | |
171 | ), |
|
171 | ), | |
172 | ) |
|
172 | ) | |
173 | def start(self): |
|
173 | def start(self): | |
174 | if self.subapp is not None: |
|
174 | if self.subapp is not None: | |
175 | return self.subapp.start() |
|
175 | return self.subapp.start() | |
176 | else: |
|
176 | else: | |
177 | print(self.ipython_dir) |
|
177 | print(self.ipython_dir) | |
178 |
|
178 | |||
179 |
|
179 | |||
180 | class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp): |
|
180 | class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp): | |
181 | name = u'ipython' |
|
181 | name = u'ipython' | |
182 | description = usage.cl_usage |
|
182 | description = usage.cl_usage | |
183 | crash_handler_class = IPAppCrashHandler |
|
183 | crash_handler_class = IPAppCrashHandler # typing: ignore[assignment] | |
184 | examples = _examples |
|
184 | examples = _examples | |
185 |
|
185 | |||
186 | flags = flags |
|
186 | flags = flags | |
187 | aliases = aliases |
|
187 | aliases = aliases | |
188 | classes = List() |
|
188 | classes = List() | |
189 |
|
189 | |||
190 | interactive_shell_class = Type( |
|
190 | interactive_shell_class = Type( | |
191 | klass=object, # use default_value otherwise which only allow subclasses. |
|
191 | klass=object, # use default_value otherwise which only allow subclasses. | |
192 | default_value=TerminalInteractiveShell, |
|
192 | default_value=TerminalInteractiveShell, | |
193 | help="Class to use to instantiate the TerminalInteractiveShell object. Useful for custom Frontends" |
|
193 | help="Class to use to instantiate the TerminalInteractiveShell object. Useful for custom Frontends" | |
194 | ).tag(config=True) |
|
194 | ).tag(config=True) | |
195 |
|
195 | |||
196 | @default('classes') |
|
196 | @default('classes') | |
197 | def _classes_default(self): |
|
197 | def _classes_default(self): | |
198 | """This has to be in a method, for TerminalIPythonApp to be available.""" |
|
198 | """This has to be in a method, for TerminalIPythonApp to be available.""" | |
199 | return [ |
|
199 | return [ | |
200 | InteractiveShellApp, # ShellApp comes before TerminalApp, because |
|
200 | InteractiveShellApp, # ShellApp comes before TerminalApp, because | |
201 | self.__class__, # it will also affect subclasses (e.g. QtConsole) |
|
201 | self.__class__, # it will also affect subclasses (e.g. QtConsole) | |
202 | TerminalInteractiveShell, |
|
202 | TerminalInteractiveShell, | |
203 | HistoryManager, |
|
203 | HistoryManager, | |
204 | MagicsManager, |
|
204 | MagicsManager, | |
205 | ProfileDir, |
|
205 | ProfileDir, | |
206 | PlainTextFormatter, |
|
206 | PlainTextFormatter, | |
207 | IPCompleter, |
|
207 | IPCompleter, | |
208 | ScriptMagics, |
|
208 | ScriptMagics, | |
209 | LoggingMagics, |
|
209 | LoggingMagics, | |
210 | StoreMagics, |
|
210 | StoreMagics, | |
211 | ] |
|
211 | ] | |
212 |
|
212 | |||
213 | subcommands = dict( |
|
213 | subcommands = dict( | |
214 | profile = ("IPython.core.profileapp.ProfileApp", |
|
214 | profile = ("IPython.core.profileapp.ProfileApp", | |
215 | "Create and manage IPython profiles." |
|
215 | "Create and manage IPython profiles." | |
216 | ), |
|
216 | ), | |
217 | kernel = ("ipykernel.kernelapp.IPKernelApp", |
|
217 | kernel = ("ipykernel.kernelapp.IPKernelApp", | |
218 | "Start a kernel without an attached frontend." |
|
218 | "Start a kernel without an attached frontend." | |
219 | ), |
|
219 | ), | |
220 | locate=('IPython.terminal.ipapp.LocateIPythonApp', |
|
220 | locate=('IPython.terminal.ipapp.LocateIPythonApp', | |
221 | LocateIPythonApp.description |
|
221 | LocateIPythonApp.description | |
222 | ), |
|
222 | ), | |
223 | history=('IPython.core.historyapp.HistoryApp', |
|
223 | history=('IPython.core.historyapp.HistoryApp', | |
224 | "Manage the IPython history database." |
|
224 | "Manage the IPython history database." | |
225 | ), |
|
225 | ), | |
226 | ) |
|
226 | ) | |
227 |
|
227 | |||
228 |
|
228 | |||
229 | # *do* autocreate requested profile, but don't create the config file. |
|
229 | # *do* autocreate requested profile, but don't create the config file. | |
230 | auto_create=Bool(True) |
|
230 | auto_create=Bool(True) | |
231 | # configurables |
|
231 | # configurables | |
232 | quick = Bool(False, |
|
232 | quick = Bool(False, | |
233 | help="""Start IPython quickly by skipping the loading of config files.""" |
|
233 | help="""Start IPython quickly by skipping the loading of config files.""" | |
234 | ).tag(config=True) |
|
234 | ).tag(config=True) | |
235 | @observe('quick') |
|
235 | @observe('quick') | |
236 | def _quick_changed(self, change): |
|
236 | def _quick_changed(self, change): | |
237 | if change['new']: |
|
237 | if change['new']: | |
238 | self.load_config_file = lambda *a, **kw: None |
|
238 | self.load_config_file = lambda *a, **kw: None | |
239 |
|
239 | |||
240 | display_banner = Bool(True, |
|
240 | display_banner = Bool(True, | |
241 | help="Whether to display a banner upon starting IPython." |
|
241 | help="Whether to display a banner upon starting IPython." | |
242 | ).tag(config=True) |
|
242 | ).tag(config=True) | |
243 |
|
243 | |||
244 | # if there is code of files to run from the cmd line, don't interact |
|
244 | # if there is code of files to run from the cmd line, don't interact | |
245 | # unless the --i flag (App.force_interact) is true. |
|
245 | # unless the --i flag (App.force_interact) is true. | |
246 | force_interact = Bool(False, |
|
246 | force_interact = Bool(False, | |
247 | help="""If a command or file is given via the command-line, |
|
247 | help="""If a command or file is given via the command-line, | |
248 | e.g. 'ipython foo.py', start an interactive shell after executing the |
|
248 | e.g. 'ipython foo.py', start an interactive shell after executing the | |
249 | file or command.""" |
|
249 | file or command.""" | |
250 | ).tag(config=True) |
|
250 | ).tag(config=True) | |
251 | @observe('force_interact') |
|
251 | @observe('force_interact') | |
252 | def _force_interact_changed(self, change): |
|
252 | def _force_interact_changed(self, change): | |
253 | if change['new']: |
|
253 | if change['new']: | |
254 | self.interact = True |
|
254 | self.interact = True | |
255 |
|
255 | |||
256 | @observe('file_to_run', 'code_to_run', 'module_to_run') |
|
256 | @observe('file_to_run', 'code_to_run', 'module_to_run') | |
257 | def _file_to_run_changed(self, change): |
|
257 | def _file_to_run_changed(self, change): | |
258 | new = change['new'] |
|
258 | new = change['new'] | |
259 | if new: |
|
259 | if new: | |
260 | self.something_to_run = True |
|
260 | self.something_to_run = True | |
261 | if new and not self.force_interact: |
|
261 | if new and not self.force_interact: | |
262 | self.interact = False |
|
262 | self.interact = False | |
263 |
|
263 | |||
264 | # internal, not-configurable |
|
264 | # internal, not-configurable | |
265 | something_to_run=Bool(False) |
|
265 | something_to_run=Bool(False) | |
266 |
|
266 | |||
267 | @catch_config_error |
|
267 | @catch_config_error | |
268 | def initialize(self, argv=None): |
|
268 | def initialize(self, argv=None): | |
269 | """Do actions after construct, but before starting the app.""" |
|
269 | """Do actions after construct, but before starting the app.""" | |
270 | super(TerminalIPythonApp, self).initialize(argv) |
|
270 | super(TerminalIPythonApp, self).initialize(argv) | |
271 | if self.subapp is not None: |
|
271 | if self.subapp is not None: | |
272 | # don't bother initializing further, starting subapp |
|
272 | # don't bother initializing further, starting subapp | |
273 | return |
|
273 | return | |
274 | # print self.extra_args |
|
274 | # print self.extra_args | |
275 | if self.extra_args and not self.something_to_run: |
|
275 | if self.extra_args and not self.something_to_run: | |
276 | self.file_to_run = self.extra_args[0] |
|
276 | self.file_to_run = self.extra_args[0] | |
277 | self.init_path() |
|
277 | self.init_path() | |
278 | # create the shell |
|
278 | # create the shell | |
279 | self.init_shell() |
|
279 | self.init_shell() | |
280 | # and draw the banner |
|
280 | # and draw the banner | |
281 | self.init_banner() |
|
281 | self.init_banner() | |
282 | # Now a variety of things that happen after the banner is printed. |
|
282 | # Now a variety of things that happen after the banner is printed. | |
283 | self.init_gui_pylab() |
|
283 | self.init_gui_pylab() | |
284 | self.init_extensions() |
|
284 | self.init_extensions() | |
285 | self.init_code() |
|
285 | self.init_code() | |
286 |
|
286 | |||
287 | def init_shell(self): |
|
287 | def init_shell(self): | |
288 | """initialize the InteractiveShell instance""" |
|
288 | """initialize the InteractiveShell instance""" | |
289 | # Create an InteractiveShell instance. |
|
289 | # Create an InteractiveShell instance. | |
290 | # shell.display_banner should always be False for the terminal |
|
290 | # shell.display_banner should always be False for the terminal | |
291 | # based app, because we call shell.show_banner() by hand below |
|
291 | # based app, because we call shell.show_banner() by hand below | |
292 | # so the banner shows *before* all extension loading stuff. |
|
292 | # so the banner shows *before* all extension loading stuff. | |
293 | self.shell = self.interactive_shell_class.instance(parent=self, |
|
293 | self.shell = self.interactive_shell_class.instance(parent=self, | |
294 | profile_dir=self.profile_dir, |
|
294 | profile_dir=self.profile_dir, | |
295 | ipython_dir=self.ipython_dir, user_ns=self.user_ns) |
|
295 | ipython_dir=self.ipython_dir, user_ns=self.user_ns) | |
296 | self.shell.configurables.append(self) |
|
296 | self.shell.configurables.append(self) | |
297 |
|
297 | |||
298 | def init_banner(self): |
|
298 | def init_banner(self): | |
299 | """optionally display the banner""" |
|
299 | """optionally display the banner""" | |
300 | if self.display_banner and self.interact: |
|
300 | if self.display_banner and self.interact: | |
301 | self.shell.show_banner() |
|
301 | self.shell.show_banner() | |
302 | # Make sure there is a space below the banner. |
|
302 | # Make sure there is a space below the banner. | |
303 | if self.log_level <= logging.INFO: print() |
|
303 | if self.log_level <= logging.INFO: print() | |
304 |
|
304 | |||
305 | def _pylab_changed(self, name, old, new): |
|
305 | def _pylab_changed(self, name, old, new): | |
306 | """Replace --pylab='inline' with --pylab='auto'""" |
|
306 | """Replace --pylab='inline' with --pylab='auto'""" | |
307 | if new == 'inline': |
|
307 | if new == 'inline': | |
308 | warnings.warn("'inline' not available as pylab backend, " |
|
308 | warnings.warn("'inline' not available as pylab backend, " | |
309 | "using 'auto' instead.") |
|
309 | "using 'auto' instead.") | |
310 | self.pylab = 'auto' |
|
310 | self.pylab = 'auto' | |
311 |
|
311 | |||
312 | def start(self): |
|
312 | def start(self): | |
313 | if self.subapp is not None: |
|
313 | if self.subapp is not None: | |
314 | return self.subapp.start() |
|
314 | return self.subapp.start() | |
315 | # perform any prexec steps: |
|
315 | # perform any prexec steps: | |
316 | if self.interact: |
|
316 | if self.interact: | |
317 | self.log.debug("Starting IPython's mainloop...") |
|
317 | self.log.debug("Starting IPython's mainloop...") | |
318 | self.shell.mainloop() |
|
318 | self.shell.mainloop() | |
319 | else: |
|
319 | else: | |
320 | self.log.debug("IPython not interactive...") |
|
320 | self.log.debug("IPython not interactive...") | |
321 | self.shell.restore_term_title() |
|
321 | self.shell.restore_term_title() | |
322 | if not self.shell.last_execution_succeeded: |
|
322 | if not self.shell.last_execution_succeeded: | |
323 | sys.exit(1) |
|
323 | sys.exit(1) | |
324 |
|
324 | |||
325 | def load_default_config(ipython_dir=None): |
|
325 | def load_default_config(ipython_dir=None): | |
326 | """Load the default config file from the default ipython_dir. |
|
326 | """Load the default config file from the default ipython_dir. | |
327 |
|
327 | |||
328 | This is useful for embedded shells. |
|
328 | This is useful for embedded shells. | |
329 | """ |
|
329 | """ | |
330 | if ipython_dir is None: |
|
330 | if ipython_dir is None: | |
331 | ipython_dir = get_ipython_dir() |
|
331 | ipython_dir = get_ipython_dir() | |
332 |
|
332 | |||
333 | profile_dir = os.path.join(ipython_dir, 'profile_default') |
|
333 | profile_dir = os.path.join(ipython_dir, 'profile_default') | |
334 | app = TerminalIPythonApp() |
|
334 | app = TerminalIPythonApp() | |
335 | app.config_file_paths.append(profile_dir) |
|
335 | app.config_file_paths.append(profile_dir) | |
336 | app.load_config_file() |
|
336 | app.load_config_file() | |
337 | return app.config |
|
337 | return app.config | |
338 |
|
338 | |||
339 | launch_new_instance = TerminalIPythonApp.launch_instance |
|
339 | launch_new_instance = TerminalIPythonApp.launch_instance | |
340 |
|
340 | |||
341 |
|
341 | |||
342 | if __name__ == '__main__': |
|
342 | if __name__ == '__main__': | |
343 | launch_new_instance() |
|
343 | launch_new_instance() |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
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 |
|
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 |
|
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