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

173 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-26 06:07 +0000

1# encoding: utf-8 

2""" 

3An embedded IPython shell. 

4""" 

5# Copyright (c) IPython Development Team. 

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

7 

8 

9import sys 

10import warnings 

11 

12from IPython.core import ultratb, compilerop 

13from IPython.core import magic_arguments 

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

15from IPython.core.interactiveshell import DummyMod, InteractiveShell 

16from IPython.terminal.interactiveshell import TerminalInteractiveShell 

17from IPython.terminal.ipapp import load_default_config 

18 

19from traitlets import Bool, CBool, Unicode 

20from IPython.utils.io import ask_yes_no 

21 

22from typing import Set 

23 

24class KillEmbedded(Exception):pass 

25 

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

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

28KillEmbeded = KillEmbedded 

29 

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

31@magics_class 

32class EmbeddedMagics(Magics): 

33 

34 @line_magic 

35 @magic_arguments.magic_arguments() 

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

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

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

39 help='Also exit the current session') 

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

41 help='Do not ask confirmation') 

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

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

44 

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

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

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

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

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

50 the interactive shell interfering again. 

51 

52 Kill Instance Option: 

53 

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

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

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

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

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

59 recreated within a loop. 

60 

61 .. note:: 

62 

63 This was the default behavior before IPython 5.2 

64 

65 """ 

66 

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

68 print(args) 

69 if args.instance: 

70 # let no ask 

71 if not args.yes: 

72 kill = ask_yes_no( 

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

74 else: 

75 kill = True 

76 if kill: 

77 self.shell._disable_init_location() 

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

79 "once you exit.") 

80 else: 

81 if not args.yes: 

82 kill = ask_yes_no( 

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

84 else: 

85 kill = True 

86 if kill: 

87 self.shell.embedded_active = False 

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

89 "once you exit.") 

90 

91 if args.exit: 

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

93 # on next loop. 

94 self.shell.ask_exit() 

95 

96 

97 @line_magic 

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

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

100 

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

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

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

104 """ 

105 

106 self.shell.should_raise = True 

107 self.shell.ask_exit() 

108 

109 

110class _Sentinel: 

111 def __init__(self, repr): 

112 assert isinstance(repr, str) 

113 self.repr = repr 

114 

115 def __repr__(self): 

116 return repr 

117 

118 

119class InteractiveShellEmbed(TerminalInteractiveShell): 

120 

121 dummy_mode = Bool(False) 

122 exit_msg = Unicode('') 

123 embedded = CBool(True) 

124 should_raise = CBool(False) 

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

126 # is True by default. 

127 display_banner = CBool(True) 

128 exit_msg = Unicode() 

129 

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

131 term_title = Bool(False, 

132 help="Automatically set the terminal title" 

133 ).tag(config=True) 

134 

135 _inactive_locations: Set[str] = set() 

136 

137 def _disable_init_location(self): 

138 """Disable the current Instance creation location""" 

139 InteractiveShellEmbed._inactive_locations.add(self._init_location_id) 

140 

141 @property 

142 def embedded_active(self): 

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

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

145 

146 @embedded_active.setter 

147 def embedded_active(self, value): 

148 if value: 

149 InteractiveShellEmbed._inactive_locations.discard( 

150 self._call_location_id) 

151 InteractiveShellEmbed._inactive_locations.discard( 

152 self._init_location_id) 

153 else: 

154 InteractiveShellEmbed._inactive_locations.add( 

155 self._call_location_id) 

156 

157 def __init__(self, **kw): 

158 assert ( 

159 "user_global_ns" not in kw 

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

161 

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

163 if not clid: 

164 frame = sys._getframe(1) 

165 clid = '%s:%s' % (frame.f_code.co_filename, frame.f_lineno) 

166 self._init_location_id = clid 

167 

168 super(InteractiveShellEmbed,self).__init__(**kw) 

169 

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

171 # trapped 

172 sys.excepthook = ultratb.FormattedTB(color_scheme=self.colors, 

173 mode=self.xmode, 

174 call_pdb=self.pdb) 

175 

176 def init_sys_modules(self): 

177 """ 

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

179 """ 

180 pass 

181 

182 def init_magics(self): 

183 super(InteractiveShellEmbed, self).init_magics() 

184 self.register_magics(EmbeddedMagics) 

185 

186 def __call__( 

187 self, 

188 header="", 

189 local_ns=None, 

190 module=None, 

191 dummy=None, 

192 stack_depth=1, 

193 compile_flags=None, 

194 **kw 

195 ): 

196 """Activate the interactive interpreter. 

197 

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

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

200 optionally print a header string at startup. 

201 

202 The shell can be globally activated/deactivated using the 

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

204 for debugging globally. 

205 

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

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

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

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

210 """ 

211 

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

213 self.keep_running = True 

214 

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

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

217 if not clid: 

218 frame = sys._getframe(1) 

219 clid = '%s:%s' % (frame.f_code.co_filename, frame.f_lineno) 

220 self._call_location_id = clid 

221 

222 if not self.embedded_active: 

223 return 

224 

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

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

227 self.exit_now = False 

228 

229 # Allow the dummy parameter to override the global __dummy_mode 

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

231 return 

232 

233 # self.banner is auto computed 

234 if header: 

235 self.old_banner2 = self.banner2 

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

237 else: 

238 self.old_banner2 = '' 

239 

240 if self.display_banner: 

241 self.show_banner() 

242 

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

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

245 self.mainloop( 

246 local_ns, module, stack_depth=stack_depth, compile_flags=compile_flags 

247 ) 

248 

249 self.banner2 = self.old_banner2 

250 

251 if self.exit_msg is not None: 

252 print(self.exit_msg) 

253 

254 if self.should_raise: 

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

256 

257 def mainloop( 

258 self, 

259 local_ns=None, 

260 module=None, 

261 stack_depth=0, 

262 compile_flags=None, 

263 ): 

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

265 

266 Parameters 

267 ---------- 

268 local_ns, module 

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

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

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

272 stack_depth : int 

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

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

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

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

277 from the immediate caller. 

278 compile_flags 

279 A bit field identifying the __future__ features 

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

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

282 the shell was called. 

283 

284 """ 

285 

286 # Get locals and globals from caller 

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

288 and self.default_user_namespaces): 

289 call_frame = sys._getframe(stack_depth).f_back 

290 

291 if local_ns is None: 

292 local_ns = call_frame.f_locals 

293 if module is None: 

294 global_ns = call_frame.f_globals 

295 try: 

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

297 except KeyError: 

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

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

300 ) 

301 module = DummyMod() 

302 module.__dict__ = global_ns 

303 if compile_flags is None: 

304 compile_flags = (call_frame.f_code.co_flags & 

305 compilerop.PyCF_MASK) 

306 

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

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

309 orig_user_module = self.user_module 

310 orig_user_ns = self.user_ns 

311 orig_compile_flags = self.compile.flags 

312 

313 # Update namespaces and fire up interpreter 

314 

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

316 if module is not None: 

317 self.user_module = module 

318 

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

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

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

322 # later. 

323 if local_ns is not None: 

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

325 self.user_ns = reentrant_local_ns 

326 self.init_user_ns() 

327 

328 # Compiler flags 

329 if compile_flags is not None: 

330 self.compile.flags = compile_flags 

331 

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

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

334 self.set_completer_frame() 

335 

336 with self.builtin_trap, self.display_trap: 

337 self.interact() 

338 

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

340 if local_ns is not None: 

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

342 

343 

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

345 self.user_module = orig_user_module 

346 self.user_ns = orig_user_ns 

347 self.compile.flags = orig_compile_flags 

348 

349 

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

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

352 

353 The first invocation of this will create an :class:`InteractiveShellEmbed` 

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

355 created instance. 

356 

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

358 from the scope of the surrounding function, 

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

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

361 

362 Here is a simple example:: 

363 

364 from IPython import embed 

365 a = 10 

366 b = 20 

367 embed(header='First time') 

368 c = 30 

369 d = 40 

370 embed() 

371 

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

373 config argument. 

374 """ 

375 config = kwargs.get('config') 

376 if config is None: 

377 config = load_default_config() 

378 config.InteractiveShellEmbed = config.TerminalInteractiveShell 

379 kwargs['config'] = config 

380 using = kwargs.get('using', 'sync') 

381 if using : 

382 kwargs['config'].update({'TerminalInteractiveShell':{'loop_runner':using, 'colors':'NoColor', 'autoawait': using!='sync'}}) 

383 #save ps1/ps2 if defined 

384 ps1 = None 

385 ps2 = None 

386 try: 

387 ps1 = sys.ps1 

388 ps2 = sys.ps2 

389 except AttributeError: 

390 pass 

391 #save previous instance 

392 saved_shell_instance = InteractiveShell._instance 

393 if saved_shell_instance is not None: 

394 cls = type(saved_shell_instance) 

395 cls.clear_instance() 

396 frame = sys._getframe(1) 

397 shell = InteractiveShellEmbed.instance(_init_location_id='%s:%s' % ( 

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

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

400 _call_location_id='%s:%s' % (frame.f_code.co_filename, frame.f_lineno)) 

401 InteractiveShellEmbed.clear_instance() 

402 #restore previous instance 

403 if saved_shell_instance is not None: 

404 cls = type(saved_shell_instance) 

405 cls.clear_instance() 

406 for subclass in cls._walk_mro(): 

407 subclass._instance = saved_shell_instance 

408 if ps1 is not None: 

409 sys.ps1 = ps1 

410 sys.ps2 = ps2