Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/IPython/terminal/embed.py: 24%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

199 statements  

1""" 

2An embedded IPython shell. 

3""" 

4# Copyright (c) IPython Development Team. 

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

6 

7 

8import sys 

9import warnings 

10 

11from IPython.core import ultratb, compilerop 

12from IPython.core import magic_arguments 

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

14from IPython.core.interactiveshell import InteractiveShell, make_main_module_type 

15from IPython.terminal.interactiveshell import TerminalInteractiveShell 

16from IPython.terminal.ipapp import load_default_config 

17 

18from traitlets import Bool, CBool, Unicode 

19from IPython.utils.io import ask_yes_no 

20 

21 

22class _EmbedGlobals(dict): 

23 """Globals namespace for an embedded shell. 

24 

25 Code typed in an embedded shell is compiled as module-level code, so 

26 any new nested scope it creates (a lambda, a generator expression, a 

27 comprehension, a function body...) looks its free variables up in 

28 ``globals()``, not in the local namespace the shell was embedded in. 

29 With a plain module ``__dict__`` as globals this makes the caller's 

30 local variables invisible to those scopes (gh-136):: 

31 

32 def f(): 

33 x = 1 

34 embed() # then type: (lambda: x)() -> NameError 

35 

36 This dict subclass keeps the interpreter's normal, C-level storage as 

37 a snapshot of the caller module's globals, but resolves reads through 

38 the caller's local namespace first, mimicking the closure lookup the 

39 code would have had if it were written in place. The interpreter only 

40 honors this ``__getitem__`` override because the shell passes a dict 

41 *subclass* to ``exec``, which disables the exact-dict fast path of 

42 ``LOAD_GLOBAL``. 

43 

44 ``STORE_GLOBAL``/``DELETE_GLOBAL`` bypass ``__setitem__`` overrides 

45 and mutate the C-level storage directly, so :meth:`sync_to_module` 

46 propagates those (rare) mutations back to the real module on exit. 

47 """ 

48 

49 def __init__(self, module_dict, local_ns): 

50 super().__init__(module_dict) 

51 self._module_dict = module_dict 

52 self._local_ns = local_ns 

53 self._snapshot = dict(module_dict) 

54 

55 def __getitem__(self, key): 

56 try: 

57 return self._local_ns[key] 

58 except KeyError: 

59 pass 

60 try: 

61 return dict.__getitem__(self, key) 

62 except KeyError: 

63 pass 

64 # Fall back to the live module dict, so globals set in the real 

65 # module while the shell is active are visible; raises KeyError, 

66 # letting LOAD_GLOBAL continue to builtins. 

67 return self._module_dict[key] 

68 

69 def sync_to_module(self): 

70 """Write back mutations of our snapshot to the real module.""" 

71 for key, value in dict.items(self): 

72 if key not in self._snapshot or self._snapshot[key] is not value: 

73 self._module_dict[key] = value 

74 for key in self._snapshot: 

75 if not dict.__contains__(self, key): 

76 self._module_dict.pop(key, None) 

77 

78 

79class KillEmbedded(Exception):pass 

80 

81# kept for backward compatibility as IPython 6 was released with 

82# the typo. See https://github.com/ipython/ipython/pull/10706 

83KillEmbeded = KillEmbedded 

84 

85# This is an additional magic that is exposed in embedded shells. 

86@magics_class 

87class EmbeddedMagics(Magics): 

88 

89 @line_magic 

90 @magic_arguments.magic_arguments() 

91 @magic_arguments.argument('-i', '--instance', action='store_true', 

92 help='Kill instance instead of call location') 

93 @magic_arguments.argument('-x', '--exit', action='store_true', 

94 help='Also exit the current session') 

95 @magic_arguments.argument('-y', '--yes', action='store_true', 

96 help='Do not ask confirmation') 

97 def kill_embedded(self, parameter_s=''): 

98 """%kill_embedded : deactivate for good the current embedded IPython 

99 

100 This function (after asking for confirmation) sets an internal flag so 

101 that an embedded IPython will never activate again for the given call 

102 location. This is useful to permanently disable a shell that is being 

103 called inside a loop: once you've figured out what you needed from it, 

104 you may then kill it and the program will then continue to run without 

105 the interactive shell interfering again. 

106 

107 Kill Instance Option: 

108 

109 If for some reasons you need to kill the location where the instance 

110 is created and not called, for example if you create a single 

111 instance in one place and debug in many locations, you can use the 

112 ``--instance`` option to kill this specific instance. Like for the 

113 ``call location`` killing an "instance" should work even if it is 

114 recreated within a loop. 

115 

116 .. note:: 

117 

118 This was the default behavior before IPython 5.2 

119 

120 """ 

121 

122 args = magic_arguments.parse_argstring(self.kill_embedded, parameter_s) 

123 print(args) 

124 if args.instance: 

125 # let no ask 

126 if not args.yes: 

127 kill = ask_yes_no( 

128 "Are you sure you want to kill this embedded instance? [y/N] ", 'n') 

129 else: 

130 kill = True 

131 if kill: 

132 self.shell._disable_init_location() 

133 print("This embedded IPython instance will not reactivate anymore " 

134 "once you exit.") 

135 else: 

136 if not args.yes: 

137 kill = ask_yes_no( 

138 "Are you sure you want to kill this embedded call_location? [y/N] ", 'n') 

139 else: 

140 kill = True 

141 if kill: 

142 self.shell.embedded_active = False 

143 print("This embedded IPython call location will not reactivate anymore " 

144 "once you exit.") 

145 

146 if args.exit: 

147 # Ask-exit does not really ask, it just set internals flags to exit 

148 # on next loop. 

149 self.shell.ask_exit() 

150 

151 

152 @line_magic 

153 def exit_raise(self, parameter_s=''): 

154 """%exit_raise Make the current embedded kernel exit and raise and exception. 

155 

156 This function sets an internal flag so that an embedded IPython will 

157 raise a `IPython.terminal.embed.KillEmbedded` Exception on exit, and then exit the current I. This is 

158 useful to permanently exit a loop that create IPython embed instance. 

159 """ 

160 

161 self.shell.should_raise = True 

162 self.shell.ask_exit() 

163 

164 

165class InteractiveShellEmbed(TerminalInteractiveShell): 

166 

167 dummy_mode = Bool(False) 

168 exit_msg = Unicode('') 

169 embedded = CBool(True) 

170 should_raise = CBool(False) 

171 # Like the base class display_banner is not configurable, but here it 

172 # is True by default. 

173 display_banner = CBool(True) 

174 exit_msg = Unicode() 

175 

176 # When embedding, by default we don't change the terminal title 

177 term_title = Bool(False, 

178 help="Automatically set the terminal title" 

179 ).tag(config=True) 

180 

181 _inactive_locations: set[str] = set() 

182 

183 def _disable_init_location(self): 

184 """Disable the current Instance creation location""" 

185 InteractiveShellEmbed._inactive_locations.add(self._init_location_id) 

186 

187 @property 

188 def embedded_active(self): 

189 return (self._call_location_id not in InteractiveShellEmbed._inactive_locations)\ 

190 and (self._init_location_id not in InteractiveShellEmbed._inactive_locations) 

191 

192 @embedded_active.setter 

193 def embedded_active(self, value): 

194 if value: 

195 InteractiveShellEmbed._inactive_locations.discard( 

196 self._call_location_id) 

197 InteractiveShellEmbed._inactive_locations.discard( 

198 self._init_location_id) 

199 else: 

200 InteractiveShellEmbed._inactive_locations.add( 

201 self._call_location_id) 

202 

203 def __init__(self, **kw): 

204 assert ( 

205 "user_global_ns" not in kw 

206 ), "Key word argument `user_global_ns` has been replaced by `user_module` since IPython 4.0." 

207 # temporary fix for https://github.com/ipython/ipython/issues/14164 

208 cls = type(self) 

209 if cls._instance is None: 

210 for subclass in cls._walk_mro(): 

211 subclass._instance = self 

212 cls._instance = self 

213 

214 clid = kw.pop('_init_location_id', None) 

215 if not clid: 

216 frame = sys._getframe(1) 

217 clid = '{}:{}'.format(frame.f_code.co_filename, frame.f_lineno) 

218 self._init_location_id = clid 

219 

220 super().__init__(**kw) 

221 

222 # don't use the ipython crash handler so that user exceptions aren't 

223 # trapped 

224 sys.excepthook = ultratb.FormattedTB( 

225 theme_name=self.colors, 

226 mode=self.xmode, 

227 call_pdb=self.pdb, 

228 ) 

229 

230 def init_sys_modules(self): 

231 """ 

232 Explicitly overwrite :mod:`IPython.core.interactiveshell` to do nothing. 

233 """ 

234 pass 

235 

236 def init_magics(self): 

237 super().init_magics() 

238 self.register_magics(EmbeddedMagics) 

239 

240 def __call__( 

241 self, 

242 header="", 

243 local_ns=None, 

244 module=None, 

245 dummy=None, 

246 stack_depth=1, 

247 compile_flags=None, 

248 **kw, 

249 ): 

250 """Activate the interactive interpreter. 

251 

252 __call__(self,header='',local_ns=None,module=None,dummy=None) -> Start 

253 the interpreter shell with the given local and global namespaces, and 

254 optionally print a header string at startup. 

255 

256 The shell can be globally activated/deactivated using the 

257 dummy_mode attribute. This allows you to turn off a shell used 

258 for debugging globally. 

259 

260 However, *each* time you call the shell you can override the current 

261 state of dummy_mode with the optional keyword parameter 'dummy'. For 

262 example, if you set dummy mode on with IPShell.dummy_mode = True, you 

263 can still have a specific call work by making it as IPShell(dummy=False). 

264 """ 

265 

266 # we are called, set the underlying interactiveshell not to exit. 

267 self.keep_running = True 

268 

269 # If the user has turned it off, go away 

270 clid = kw.pop('_call_location_id', None) 

271 if not clid: 

272 frame = sys._getframe(1) 

273 clid = '{}:{}'.format(frame.f_code.co_filename, frame.f_lineno) 

274 self._call_location_id = clid 

275 

276 if not self.embedded_active: 

277 return 

278 

279 # Normal exits from interactive mode set this flag, so the shell can't 

280 # re-enter (it checks this variable at the start of interactive mode). 

281 self.exit_now = False 

282 

283 # Allow the dummy parameter to override the global __dummy_mode 

284 if dummy or (dummy != 0 and self.dummy_mode): 

285 return 

286 

287 # self.banner is auto computed 

288 if header: 

289 self.old_banner2 = self.banner2 

290 self.banner2 = self.banner2 + '\n' + header + '\n' 

291 else: 

292 self.old_banner2 = '' 

293 

294 if self.display_banner: 

295 self.show_banner() 

296 

297 # Call the embedding code with a stack depth of 1 so it can skip over 

298 # our call and get the original caller's namespaces. 

299 self.mainloop( 

300 local_ns, module, stack_depth=stack_depth, compile_flags=compile_flags 

301 ) 

302 

303 self.banner2 = self.old_banner2 

304 

305 if self.exit_msg is not None: 

306 print(self.exit_msg) 

307 

308 if self.should_raise: 

309 raise KillEmbedded('Embedded IPython raising error, as user requested.') 

310 

311 def mainloop( 

312 self, 

313 local_ns=None, 

314 module=None, 

315 stack_depth=0, 

316 compile_flags=None, 

317 ): 

318 """Embeds IPython into a running python program. 

319 

320 Parameters 

321 ---------- 

322 local_ns, module 

323 Working local namespace (a dict) and module (a module or similar 

324 object). If given as None, they are automatically taken from the scope 

325 where the shell was called, so that program variables become visible. 

326 stack_depth : int 

327 How many levels in the stack to go to looking for namespaces (when 

328 local_ns or module is None). This allows an intermediate caller to 

329 make sure that this function gets the namespace from the intended 

330 level in the stack. By default (0) it will get its locals and globals 

331 from the immediate caller. 

332 compile_flags 

333 A bit field identifying the __future__ features 

334 that are enabled, as passed to the builtin :func:`compile` function. 

335 If given as None, they are automatically taken from the scope where 

336 the shell was called. 

337 

338 """ 

339 

340 # Get locals and globals from caller 

341 if ((local_ns is None or module is None or compile_flags is None) 

342 and self.default_user_namespaces): 

343 call_frame = sys._getframe(stack_depth).f_back 

344 

345 if local_ns is None: 

346 local_ns = call_frame.f_locals 

347 if module is None: 

348 global_ns = call_frame.f_globals 

349 try: 

350 module = sys.modules[global_ns['__name__']] 

351 except KeyError: 

352 warnings.warn("Failed to get module %s" % \ 

353 global_ns.get('__name__', 'unknown module') 

354 ) 

355 module = make_main_module_type(global_ns)() 

356 if compile_flags is None: 

357 compile_flags = (call_frame.f_code.co_flags & 

358 compilerop.PyCF_MASK) 

359 

360 # Save original namespace and module so we can restore them after 

361 # embedding; otherwise the shell doesn't shut down correctly. 

362 orig_user_module = self.user_module 

363 orig_user_ns = self.user_ns 

364 orig_compile_flags = self.compile.flags 

365 

366 # Update namespaces and fire up interpreter 

367 

368 # The global one is easy, we can just throw it in 

369 if module is not None: 

370 self.user_module = module 

371 

372 # But the user/local one is tricky: ipython needs it to store internal 

373 # data, but we also need the locals. We'll throw our hidden variables 

374 # like _ih and get_ipython() into the local namespace, but delete them 

375 # later. 

376 embed_globals = None 

377 if local_ns is not None: 

378 reentrant_local_ns = {k: v for (k, v) in local_ns.items() if k not in self.user_ns_hidden.keys()} 

379 self.user_ns = reentrant_local_ns 

380 self.init_user_ns() 

381 

382 # Replace the module's globals with a namespace that falls back 

383 # to the local one, so that nested scopes created interactively 

384 # (lambdas, generator expressions, comprehensions, functions) 

385 # can see the caller's local variables (gh-136). 

386 embed_globals = _EmbedGlobals(self.user_global_ns, reentrant_local_ns) 

387 self.user_module = make_main_module_type(embed_globals)() 

388 

389 # Compiler flags 

390 if compile_flags is not None: 

391 self.compile.flags = compile_flags 

392 

393 # make sure the tab-completer has the correct frame information, so it 

394 # actually completes using the frame's locals/globals 

395 self.set_completer_frame() 

396 

397 with self.builtin_trap, self.display_trap: 

398 self.interact() 

399 

400 # now, purge out the local namespace of IPython's hidden variables. 

401 if local_ns is not None: 

402 local_ns.update({k: v for (k, v) in self.user_ns.items() if k not in self.user_ns_hidden.keys()}) 

403 # and propagate `global` assignments made by functions defined in 

404 # the shell back to the real module. 

405 if embed_globals is not None: 

406 embed_globals.sync_to_module() 

407 

408 

409 # Restore original namespace so shell can shut down when we exit. 

410 self.user_module = orig_user_module 

411 self.user_ns = orig_user_ns 

412 self.compile.flags = orig_compile_flags 

413 

414 

415def embed(*, header="", compile_flags=None, **kwargs): 

416 """Call this to embed IPython at the current point in your program. 

417 

418 The first invocation of this will create a :class:`terminal.embed.InteractiveShellEmbed` 

419 instance and then call it. Consecutive calls just call the already 

420 created instance. 

421 

422 If you don't want the kernel to initialize the namespace 

423 from the scope of the surrounding function, 

424 and/or you want to load full IPython configuration, 

425 you probably want `IPython.start_ipython()` instead. 

426 

427 Here is a simple example:: 

428 

429 from IPython import embed 

430 a = 10 

431 b = 20 

432 embed(header='First time') 

433 c = 30 

434 d = 40 

435 embed() 

436 

437 Parameters 

438 ---------- 

439 

440 header : str 

441 Optional header string to print at startup. 

442 compile_flags 

443 Passed to the `compile_flags` parameter of :py:meth:`terminal.embed.InteractiveShellEmbed.mainloop()`, 

444 which is called when the :class:`terminal.embed.InteractiveShellEmbed` instance is called. 

445 **kwargs : various, optional 

446 Any other kwargs will be passed to the :class:`terminal.embed.InteractiveShellEmbed` constructor. 

447 Full customization can be done by passing a traitlets :class:`Config` in as the 

448 `config` argument (see :ref:`configure_start_ipython` and :ref:`terminal_options`). 

449 """ 

450 config = kwargs.get('config') 

451 if config is None: 

452 config = load_default_config() 

453 config.InteractiveShellEmbed = config.TerminalInteractiveShell 

454 kwargs["config"] = config 

455 using = kwargs.get("using", "sync") 

456 colors = kwargs.pop("colors", "nocolor") 

457 if using: 

458 kwargs["config"].update( 

459 { 

460 "TerminalInteractiveShell": { 

461 "loop_runner": using, 

462 "colors": colors, 

463 "autoawait": using != "sync", 

464 } 

465 } 

466 ) 

467 # save ps1/ps2 if defined 

468 ps1 = None 

469 ps2 = None 

470 try: 

471 ps1 = sys.ps1 

472 ps2 = sys.ps2 

473 except AttributeError: 

474 pass 

475 #save previous instance 

476 saved_shell_instance = InteractiveShell._instance 

477 if saved_shell_instance is not None: 

478 cls = type(saved_shell_instance) 

479 cls.clear_instance() 

480 frame = sys._getframe(1) 

481 shell = InteractiveShellEmbed.instance(_init_location_id='{}:{}'.format( 

482 frame.f_code.co_filename, frame.f_lineno), **kwargs) 

483 shell(header=header, stack_depth=2, compile_flags=compile_flags, 

484 _call_location_id='{}:{}'.format(frame.f_code.co_filename, frame.f_lineno)) 

485 InteractiveShellEmbed.clear_instance() 

486 #restore previous instance 

487 if saved_shell_instance is not None: 

488 cls = type(saved_shell_instance) 

489 cls.clear_instance() 

490 for subclass in cls._walk_mro(): 

491 subclass._instance = saved_shell_instance 

492 if ps1 is not None: 

493 sys.ps1 = ps1 

494 sys.ps2 = ps2