Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/IPython/core/magics/code.py: 16%

313 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-20 06:09 +0000

1"""Implementation of code management magic functions. 

2""" 

3#----------------------------------------------------------------------------- 

4# Copyright (c) 2012 The IPython Development Team. 

5# 

6# Distributed under the terms of the Modified BSD License. 

7# 

8# The full license is in the file COPYING.txt, distributed with this software. 

9#----------------------------------------------------------------------------- 

10 

11#----------------------------------------------------------------------------- 

12# Imports 

13#----------------------------------------------------------------------------- 

14 

15# Stdlib 

16import inspect 

17import io 

18import os 

19import re 

20import sys 

21import ast 

22from itertools import chain 

23from urllib.request import Request, urlopen 

24from urllib.parse import urlencode 

25from pathlib import Path 

26 

27# Our own packages 

28from IPython.core.error import TryNext, StdinNotImplementedError, UsageError 

29from IPython.core.macro import Macro 

30from IPython.core.magic import Magics, magics_class, line_magic 

31from IPython.core.oinspect import find_file, find_source_lines 

32from IPython.core.release import version 

33from IPython.testing.skipdoctest import skip_doctest 

34from IPython.utils.contexts import preserve_keys 

35from IPython.utils.path import get_py_filename 

36from warnings import warn 

37from logging import error 

38from IPython.utils.text import get_text_list 

39 

40#----------------------------------------------------------------------------- 

41# Magic implementation classes 

42#----------------------------------------------------------------------------- 

43 

44# Used for exception handling in magic_edit 

45class MacroToEdit(ValueError): pass 

46 

47ipython_input_pat = re.compile(r"<ipython\-input\-(\d+)-[a-z\d]+>$") 

48 

49# To match, e.g. 8-10 1:5 :10 3- 

50range_re = re.compile(r""" 

51(?P<start>\d+)? 

52((?P<sep>[\-:]) 

53 (?P<end>\d+)?)? 

54$""", re.VERBOSE) 

55 

56 

57def extract_code_ranges(ranges_str): 

58 """Turn a string of range for %%load into 2-tuples of (start, stop) 

59 ready to use as a slice of the content split by lines. 

60 

61 Examples 

62 -------- 

63 list(extract_input_ranges("5-10 2")) 

64 [(4, 10), (1, 2)] 

65 """ 

66 for range_str in ranges_str.split(): 

67 rmatch = range_re.match(range_str) 

68 if not rmatch: 

69 continue 

70 sep = rmatch.group("sep") 

71 start = rmatch.group("start") 

72 end = rmatch.group("end") 

73 

74 if sep == '-': 

75 start = int(start) - 1 if start else None 

76 end = int(end) if end else None 

77 elif sep == ':': 

78 start = int(start) - 1 if start else None 

79 end = int(end) - 1 if end else None 

80 else: 

81 end = int(start) 

82 start = int(start) - 1 

83 yield (start, end) 

84 

85 

86def extract_symbols(code, symbols): 

87 """ 

88 Return a tuple (blocks, not_found) 

89 where ``blocks`` is a list of code fragments 

90 for each symbol parsed from code, and ``not_found`` are 

91 symbols not found in the code. 

92 

93 For example:: 

94 

95 In [1]: code = '''a = 10 

96 ...: def b(): return 42 

97 ...: class A: pass''' 

98 

99 In [2]: extract_symbols(code, 'A,b,z') 

100 Out[2]: (['class A: pass\\n', 'def b(): return 42\\n'], ['z']) 

101 """ 

102 symbols = symbols.split(',') 

103 

104 # this will raise SyntaxError if code isn't valid Python 

105 py_code = ast.parse(code) 

106 

107 marks = [(getattr(s, 'name', None), s.lineno) for s in py_code.body] 

108 code = code.split('\n') 

109 

110 symbols_lines = {} 

111 

112 # we already know the start_lineno of each symbol (marks).  

113 # To find each end_lineno, we traverse in reverse order until each  

114 # non-blank line 

115 end = len(code) 

116 for name, start in reversed(marks): 

117 while not code[end - 1].strip(): 

118 end -= 1 

119 if name: 

120 symbols_lines[name] = (start - 1, end) 

121 end = start - 1 

122 

123 # Now symbols_lines is a map 

124 # {'symbol_name': (start_lineno, end_lineno), ...} 

125 

126 # fill a list with chunks of codes for each requested symbol 

127 blocks = [] 

128 not_found = [] 

129 for symbol in symbols: 

130 if symbol in symbols_lines: 

131 start, end = symbols_lines[symbol] 

132 blocks.append('\n'.join(code[start:end]) + '\n') 

133 else: 

134 not_found.append(symbol) 

135 

136 return blocks, not_found 

137 

138def strip_initial_indent(lines): 

139 """For %load, strip indent from lines until finding an unindented line. 

140 

141 https://github.com/ipython/ipython/issues/9775 

142 """ 

143 indent_re = re.compile(r'\s+') 

144 

145 it = iter(lines) 

146 first_line = next(it) 

147 indent_match = indent_re.match(first_line) 

148 

149 if indent_match: 

150 # First line was indented 

151 indent = indent_match.group() 

152 yield first_line[len(indent):] 

153 

154 for line in it: 

155 if line.startswith(indent): 

156 yield line[len(indent):] 

157 else: 

158 # Less indented than the first line - stop dedenting 

159 yield line 

160 break 

161 else: 

162 yield first_line 

163 

164 # Pass the remaining lines through without dedenting 

165 for line in it: 

166 yield line 

167 

168 

169class InteractivelyDefined(Exception): 

170 """Exception for interactively defined variable in magic_edit""" 

171 def __init__(self, index): 

172 self.index = index 

173 

174 

175@magics_class 

176class CodeMagics(Magics): 

177 """Magics related to code management (loading, saving, editing, ...).""" 

178 

179 def __init__(self, *args, **kwargs): 

180 self._knowntemps = set() 

181 super(CodeMagics, self).__init__(*args, **kwargs) 

182 

183 @line_magic 

184 def save(self, parameter_s=''): 

185 """Save a set of lines or a macro to a given filename. 

186 

187 Usage:\\ 

188 %save [options] filename [history] 

189 

190 Options: 

191 

192 -r: use 'raw' input. By default, the 'processed' history is used, 

193 so that magics are loaded in their transformed version to valid 

194 Python. If this option is given, the raw input as typed as the 

195 command line is used instead. 

196  

197 -f: force overwrite. If file exists, %save will prompt for overwrite 

198 unless -f is given. 

199 

200 -a: append to the file instead of overwriting it. 

201 

202 The history argument uses the same syntax as %history for input ranges, 

203 then saves the lines to the filename you specify. 

204 

205 If no ranges are specified, saves history of the current session up to 

206 this point. 

207 

208 It adds a '.py' extension to the file if you don't do so yourself, and 

209 it asks for confirmation before overwriting existing files. 

210 

211 If `-r` option is used, the default extension is `.ipy`. 

212 """ 

213 

214 opts,args = self.parse_options(parameter_s,'fra',mode='list') 

215 if not args: 

216 raise UsageError('Missing filename.') 

217 raw = 'r' in opts 

218 force = 'f' in opts 

219 append = 'a' in opts 

220 mode = 'a' if append else 'w' 

221 ext = '.ipy' if raw else '.py' 

222 fname, codefrom = args[0], " ".join(args[1:]) 

223 if not fname.endswith(('.py','.ipy')): 

224 fname += ext 

225 fname = os.path.expanduser(fname) 

226 file_exists = os.path.isfile(fname) 

227 if file_exists and not force and not append: 

228 try: 

229 overwrite = self.shell.ask_yes_no('File `%s` exists. Overwrite (y/[N])? ' % fname, default='n') 

230 except StdinNotImplementedError: 

231 print("File `%s` exists. Use `%%save -f %s` to force overwrite" % (fname, parameter_s)) 

232 return 

233 if not overwrite : 

234 print('Operation cancelled.') 

235 return 

236 try: 

237 cmds = self.shell.find_user_code(codefrom,raw) 

238 except (TypeError, ValueError) as e: 

239 print(e.args[0]) 

240 return 

241 with io.open(fname, mode, encoding="utf-8") as f: 

242 if not file_exists or not append: 

243 f.write("# coding: utf-8\n") 

244 f.write(cmds) 

245 # make sure we end on a newline 

246 if not cmds.endswith('\n'): 

247 f.write('\n') 

248 print('The following commands were written to file `%s`:' % fname) 

249 print(cmds) 

250 

251 @line_magic 

252 def pastebin(self, parameter_s=''): 

253 """Upload code to dpaste.com, returning the URL. 

254 

255 Usage:\\ 

256 %pastebin [-d "Custom description"][-e 24] 1-7 

257 

258 The argument can be an input history range, a filename, or the name of a 

259 string or macro. 

260 

261 If no arguments are given, uploads the history of this session up to 

262 this point. 

263 

264 Options: 

265 

266 -d: Pass a custom description. The default will say 

267 "Pasted from IPython". 

268 -e: Pass number of days for the link to be expired. 

269 The default will be 7 days. 

270 """ 

271 opts, args = self.parse_options(parameter_s, "d:e:") 

272 

273 try: 

274 code = self.shell.find_user_code(args) 

275 except (ValueError, TypeError) as e: 

276 print(e.args[0]) 

277 return 

278 

279 expiry_days = 7 

280 try: 

281 expiry_days = int(opts.get("e", 7)) 

282 except ValueError as e: 

283 print(e.args[0].capitalize()) 

284 return 

285 if expiry_days < 1 or expiry_days > 365: 

286 print("Expiry days should be in range of 1 to 365") 

287 return 

288 

289 post_data = urlencode( 

290 { 

291 "title": opts.get("d", "Pasted from IPython"), 

292 "syntax": "python", 

293 "content": code, 

294 "expiry_days": expiry_days, 

295 } 

296 ).encode("utf-8") 

297 

298 request = Request( 

299 "https://dpaste.com/api/v2/", 

300 headers={"User-Agent": "IPython v{}".format(version)}, 

301 ) 

302 response = urlopen(request, post_data) 

303 return response.headers.get('Location') 

304 

305 @line_magic 

306 def loadpy(self, arg_s): 

307 """Alias of `%load` 

308 

309 `%loadpy` has gained some flexibility and dropped the requirement of a `.py` 

310 extension. So it has been renamed simply into %load. You can look at 

311 `%load`'s docstring for more info. 

312 """ 

313 self.load(arg_s) 

314 

315 @line_magic 

316 def load(self, arg_s): 

317 """Load code into the current frontend. 

318 

319 Usage:\\ 

320 %load [options] source 

321 

322 where source can be a filename, URL, input history range, macro, or 

323 element in the user namespace 

324 

325 If no arguments are given, loads the history of this session up to this 

326 point. 

327 

328 Options: 

329 

330 -r <lines>: Specify lines or ranges of lines to load from the source. 

331 Ranges could be specified as x-y (x..y) or in python-style x:y  

332 (x..(y-1)). Both limits x and y can be left blank (meaning the  

333 beginning and end of the file, respectively). 

334 

335 -s <symbols>: Specify function or classes to load from python source.  

336 

337 -y : Don't ask confirmation for loading source above 200 000 characters. 

338 

339 -n : Include the user's namespace when searching for source code. 

340 

341 This magic command can either take a local filename, a URL, an history 

342 range (see %history) or a macro as argument, it will prompt for 

343 confirmation before loading source with more than 200 000 characters, unless 

344 -y flag is passed or if the frontend does not support raw_input:: 

345 

346 %load 

347 %load myscript.py 

348 %load 7-27 

349 %load myMacro 

350 %load http://www.example.com/myscript.py 

351 %load -r 5-10 myscript.py 

352 %load -r 10-20,30,40: foo.py 

353 %load -s MyClass,wonder_function myscript.py 

354 %load -n MyClass 

355 %load -n my_module.wonder_function 

356 """ 

357 opts,args = self.parse_options(arg_s,'yns:r:') 

358 search_ns = 'n' in opts 

359 contents = self.shell.find_user_code(args, search_ns=search_ns) 

360 

361 if 's' in opts: 

362 try: 

363 blocks, not_found = extract_symbols(contents, opts['s']) 

364 except SyntaxError: 

365 # non python code 

366 error("Unable to parse the input as valid Python code") 

367 return 

368 

369 if len(not_found) == 1: 

370 warn('The symbol `%s` was not found' % not_found[0]) 

371 elif len(not_found) > 1: 

372 warn('The symbols %s were not found' % get_text_list(not_found, 

373 wrap_item_with='`') 

374 ) 

375 

376 contents = '\n'.join(blocks) 

377 

378 if 'r' in opts: 

379 ranges = opts['r'].replace(',', ' ') 

380 lines = contents.split('\n') 

381 slices = extract_code_ranges(ranges) 

382 contents = [lines[slice(*slc)] for slc in slices] 

383 contents = '\n'.join(strip_initial_indent(chain.from_iterable(contents))) 

384 

385 l = len(contents) 

386 

387 # 200 000 is ~ 2500 full 80 character lines 

388 # so in average, more than 5000 lines 

389 if l > 200000 and 'y' not in opts: 

390 try: 

391 ans = self.shell.ask_yes_no(("The text you're trying to load seems pretty big"\ 

392 " (%d characters). Continue (y/[N]) ?" % l), default='n' ) 

393 except StdinNotImplementedError: 

394 #assume yes if raw input not implemented 

395 ans = True 

396 

397 if ans is False : 

398 print('Operation cancelled.') 

399 return 

400 

401 contents = "# %load {}\n".format(arg_s) + contents 

402 

403 self.shell.set_next_input(contents, replace=True) 

404 

405 @staticmethod 

406 def _find_edit_target(shell, args, opts, last_call): 

407 """Utility method used by magic_edit to find what to edit.""" 

408 

409 def make_filename(arg): 

410 "Make a filename from the given args" 

411 try: 

412 filename = get_py_filename(arg) 

413 except IOError: 

414 # If it ends with .py but doesn't already exist, assume we want 

415 # a new file. 

416 if arg.endswith('.py'): 

417 filename = arg 

418 else: 

419 filename = None 

420 return filename 

421 

422 # Set a few locals from the options for convenience: 

423 opts_prev = 'p' in opts 

424 opts_raw = 'r' in opts 

425 

426 # custom exceptions 

427 class DataIsObject(Exception): pass 

428 

429 # Default line number value 

430 lineno = opts.get('n',None) 

431 

432 if opts_prev: 

433 args = '_%s' % last_call[0] 

434 if args not in shell.user_ns: 

435 args = last_call[1] 

436 

437 # by default this is done with temp files, except when the given 

438 # arg is a filename 

439 use_temp = True 

440 

441 data = '' 

442 

443 # First, see if the arguments should be a filename. 

444 filename = make_filename(args) 

445 if filename: 

446 use_temp = False 

447 elif args: 

448 # Mode where user specifies ranges of lines, like in %macro. 

449 data = shell.extract_input_lines(args, opts_raw) 

450 if not data: 

451 try: 

452 # Load the parameter given as a variable. If not a string, 

453 # process it as an object instead (below) 

454 

455 #print '*** args',args,'type',type(args) # dbg 

456 data = eval(args, shell.user_ns) 

457 if not isinstance(data, str): 

458 raise DataIsObject 

459 

460 except (NameError,SyntaxError): 

461 # given argument is not a variable, try as a filename 

462 filename = make_filename(args) 

463 if filename is None: 

464 warn("Argument given (%s) can't be found as a variable " 

465 "or as a filename." % args) 

466 return (None, None, None) 

467 use_temp = False 

468 

469 except DataIsObject as e: 

470 # macros have a special edit function 

471 if isinstance(data, Macro): 

472 raise MacroToEdit(data) from e 

473 

474 # For objects, try to edit the file where they are defined 

475 filename = find_file(data) 

476 if filename: 

477 if 'fakemodule' in filename.lower() and \ 

478 inspect.isclass(data): 

479 # class created by %edit? Try to find source 

480 # by looking for method definitions instead, the 

481 # __module__ in those classes is FakeModule. 

482 attrs = [getattr(data, aname) for aname in dir(data)] 

483 for attr in attrs: 

484 if not inspect.ismethod(attr): 

485 continue 

486 filename = find_file(attr) 

487 if filename and \ 

488 'fakemodule' not in filename.lower(): 

489 # change the attribute to be the edit 

490 # target instead 

491 data = attr 

492 break 

493 

494 m = ipython_input_pat.match(os.path.basename(filename)) 

495 if m: 

496 raise InteractivelyDefined(int(m.groups()[0])) from e 

497 

498 datafile = 1 

499 if filename is None: 

500 filename = make_filename(args) 

501 datafile = 1 

502 if filename is not None: 

503 # only warn about this if we get a real name 

504 warn('Could not find file where `%s` is defined.\n' 

505 'Opening a file named `%s`' % (args, filename)) 

506 # Now, make sure we can actually read the source (if it was 

507 # in a temp file it's gone by now). 

508 if datafile: 

509 if lineno is None: 

510 lineno = find_source_lines(data) 

511 if lineno is None: 

512 filename = make_filename(args) 

513 if filename is None: 

514 warn('The file where `%s` was defined ' 

515 'cannot be read or found.' % data) 

516 return (None, None, None) 

517 use_temp = False 

518 

519 if use_temp: 

520 filename = shell.mktempfile(data) 

521 print('IPython will make a temporary file named:',filename) 

522 

523 # use last_call to remember the state of the previous call, but don't 

524 # let it be clobbered by successive '-p' calls. 

525 try: 

526 last_call[0] = shell.displayhook.prompt_count 

527 if not opts_prev: 

528 last_call[1] = args 

529 except: 

530 pass 

531 

532 

533 return filename, lineno, use_temp 

534 

535 def _edit_macro(self,mname,macro): 

536 """open an editor with the macro data in a file""" 

537 filename = self.shell.mktempfile(macro.value) 

538 self.shell.hooks.editor(filename) 

539 

540 # and make a new macro object, to replace the old one 

541 mvalue = Path(filename).read_text(encoding="utf-8") 

542 self.shell.user_ns[mname] = Macro(mvalue) 

543 

544 @skip_doctest 

545 @line_magic 

546 def edit(self, parameter_s='',last_call=['','']): 

547 """Bring up an editor and execute the resulting code. 

548 

549 Usage: 

550 %edit [options] [args] 

551 

552 %edit runs IPython's editor hook. The default version of this hook is 

553 set to call the editor specified by your $EDITOR environment variable. 

554 If this isn't found, it will default to vi under Linux/Unix and to 

555 notepad under Windows. See the end of this docstring for how to change 

556 the editor hook. 

557 

558 You can also set the value of this editor via the 

559 ``TerminalInteractiveShell.editor`` option in your configuration file. 

560 This is useful if you wish to use a different editor from your typical 

561 default with IPython (and for Windows users who typically don't set 

562 environment variables). 

563 

564 This command allows you to conveniently edit multi-line code right in 

565 your IPython session. 

566 

567 If called without arguments, %edit opens up an empty editor with a 

568 temporary file and will execute the contents of this file when you 

569 close it (don't forget to save it!). 

570 

571 

572 Options: 

573 

574 -n <number>: open the editor at a specified line number. By default, 

575 the IPython editor hook uses the unix syntax 'editor +N filename', but 

576 you can configure this by providing your own modified hook if your 

577 favorite editor supports line-number specifications with a different 

578 syntax. 

579 

580 -p: this will call the editor with the same data as the previous time 

581 it was used, regardless of how long ago (in your current session) it 

582 was. 

583 

584 -r: use 'raw' input. This option only applies to input taken from the 

585 user's history. By default, the 'processed' history is used, so that 

586 magics are loaded in their transformed version to valid Python. If 

587 this option is given, the raw input as typed as the command line is 

588 used instead. When you exit the editor, it will be executed by 

589 IPython's own processor. 

590 

591 -x: do not execute the edited code immediately upon exit. This is 

592 mainly useful if you are editing programs which need to be called with 

593 command line arguments, which you can then do using %run. 

594 

595 

596 Arguments: 

597 

598 If arguments are given, the following possibilities exist: 

599 

600 - If the argument is a filename, IPython will load that into the 

601 editor. It will execute its contents with execfile() when you exit, 

602 loading any code in the file into your interactive namespace. 

603 

604 - The arguments are ranges of input history, e.g. "7 ~1/4-6". 

605 The syntax is the same as in the %history magic. 

606 

607 - If the argument is a string variable, its contents are loaded 

608 into the editor. You can thus edit any string which contains 

609 python code (including the result of previous edits). 

610 

611 - If the argument is the name of an object (other than a string), 

612 IPython will try to locate the file where it was defined and open the 

613 editor at the point where it is defined. You can use `%edit function` 

614 to load an editor exactly at the point where 'function' is defined, 

615 edit it and have the file be executed automatically. 

616 

617 - If the object is a macro (see %macro for details), this opens up your 

618 specified editor with a temporary file containing the macro's data. 

619 Upon exit, the macro is reloaded with the contents of the file. 

620 

621 Note: opening at an exact line is only supported under Unix, and some 

622 editors (like kedit and gedit up to Gnome 2.8) do not understand the 

623 '+NUMBER' parameter necessary for this feature. Good editors like 

624 (X)Emacs, vi, jed, pico and joe all do. 

625 

626 After executing your code, %edit will return as output the code you 

627 typed in the editor (except when it was an existing file). This way 

628 you can reload the code in further invocations of %edit as a variable, 

629 via _<NUMBER> or Out[<NUMBER>], where <NUMBER> is the prompt number of 

630 the output. 

631 

632 Note that %edit is also available through the alias %ed. 

633 

634 This is an example of creating a simple function inside the editor and 

635 then modifying it. First, start up the editor:: 

636 

637 In [1]: edit 

638 Editing... done. Executing edited code... 

639 Out[1]: 'def foo():\\n print "foo() was defined in an editing 

640 session"\\n' 

641 

642 We can then call the function foo():: 

643 

644 In [2]: foo() 

645 foo() was defined in an editing session 

646 

647 Now we edit foo. IPython automatically loads the editor with the 

648 (temporary) file where foo() was previously defined:: 

649 

650 In [3]: edit foo 

651 Editing... done. Executing edited code... 

652 

653 And if we call foo() again we get the modified version:: 

654 

655 In [4]: foo() 

656 foo() has now been changed! 

657 

658 Here is an example of how to edit a code snippet successive 

659 times. First we call the editor:: 

660 

661 In [5]: edit 

662 Editing... done. Executing edited code... 

663 hello 

664 Out[5]: "print 'hello'\\n" 

665 

666 Now we call it again with the previous output (stored in _):: 

667 

668 In [6]: edit _ 

669 Editing... done. Executing edited code... 

670 hello world 

671 Out[6]: "print 'hello world'\\n" 

672 

673 Now we call it with the output #8 (stored in _8, also as Out[8]):: 

674 

675 In [7]: edit _8 

676 Editing... done. Executing edited code... 

677 hello again 

678 Out[7]: "print 'hello again'\\n" 

679 

680 

681 Changing the default editor hook: 

682 

683 If you wish to write your own editor hook, you can put it in a 

684 configuration file which you load at startup time. The default hook 

685 is defined in the IPython.core.hooks module, and you can use that as a 

686 starting example for further modifications. That file also has 

687 general instructions on how to set a new hook for use once you've 

688 defined it.""" 

689 opts,args = self.parse_options(parameter_s,'prxn:') 

690 

691 try: 

692 filename, lineno, is_temp = self._find_edit_target(self.shell, 

693 args, opts, last_call) 

694 except MacroToEdit as e: 

695 self._edit_macro(args, e.args[0]) 

696 return 

697 except InteractivelyDefined as e: 

698 print("Editing In[%i]" % e.index) 

699 args = str(e.index) 

700 filename, lineno, is_temp = self._find_edit_target(self.shell, 

701 args, opts, last_call) 

702 if filename is None: 

703 # nothing was found, warnings have already been issued, 

704 # just give up. 

705 return 

706 

707 if is_temp: 

708 self._knowntemps.add(filename) 

709 elif (filename in self._knowntemps): 

710 is_temp = True 

711 

712 

713 # do actual editing here 

714 print('Editing...', end=' ') 

715 sys.stdout.flush() 

716 filepath = Path(filename) 

717 try: 

718 # Quote filenames that may have spaces in them when opening 

719 # the editor 

720 quoted = filename = str(filepath.absolute()) 

721 if " " in quoted: 

722 quoted = "'%s'" % quoted 

723 self.shell.hooks.editor(quoted, lineno) 

724 except TryNext: 

725 warn('Could not open editor') 

726 return 

727 

728 # XXX TODO: should this be generalized for all string vars? 

729 # For now, this is special-cased to blocks created by cpaste 

730 if args.strip() == "pasted_block": 

731 self.shell.user_ns["pasted_block"] = filepath.read_text(encoding="utf-8") 

732 

733 if 'x' in opts: # -x prevents actual execution 

734 print() 

735 else: 

736 print('done. Executing edited code...') 

737 with preserve_keys(self.shell.user_ns, '__file__'): 

738 if not is_temp: 

739 self.shell.user_ns["__file__"] = filename 

740 if "r" in opts: # Untranslated IPython code 

741 source = filepath.read_text(encoding="utf-8") 

742 self.shell.run_cell(source, store_history=False) 

743 else: 

744 self.shell.safe_execfile(filename, self.shell.user_ns, 

745 self.shell.user_ns) 

746 

747 if is_temp: 

748 try: 

749 return filepath.read_text(encoding="utf-8") 

750 except IOError as msg: 

751 if Path(msg.filename) == filepath: 

752 warn('File not found. Did you forget to save?') 

753 return 

754 else: 

755 self.shell.showtraceback()