##// END OF EJS Templates
Update example for %alias_magic (#14276)...
Matthias Bussonnier -
r28556:28fc51eb merge
parent child Browse files
Show More
@@ -1,663 +1,663 b''
1 1 """Implementation of basic magic functions."""
2 2
3 3
4 4 from logging import error
5 5 import io
6 6 import os
7 7 from pprint import pformat
8 8 import sys
9 9 from warnings import warn
10 10
11 11 from traitlets.utils.importstring import import_item
12 12 from IPython.core import magic_arguments, page
13 13 from IPython.core.error import UsageError
14 14 from IPython.core.magic import Magics, magics_class, line_magic, magic_escapes
15 15 from IPython.utils.text import format_screen, dedent, indent
16 16 from IPython.testing.skipdoctest import skip_doctest
17 17 from IPython.utils.ipstruct import Struct
18 18
19 19
20 20 class MagicsDisplay(object):
21 21 def __init__(self, magics_manager, ignore=None):
22 22 self.ignore = ignore if ignore else []
23 23 self.magics_manager = magics_manager
24 24
25 25 def _lsmagic(self):
26 26 """The main implementation of the %lsmagic"""
27 27 mesc = magic_escapes['line']
28 28 cesc = magic_escapes['cell']
29 29 mman = self.magics_manager
30 30 magics = mman.lsmagic()
31 31 out = ['Available line magics:',
32 32 mesc + (' '+mesc).join(sorted([m for m,v in magics['line'].items() if (v not in self.ignore)])),
33 33 '',
34 34 'Available cell magics:',
35 35 cesc + (' '+cesc).join(sorted([m for m,v in magics['cell'].items() if (v not in self.ignore)])),
36 36 '',
37 37 mman.auto_status()]
38 38 return '\n'.join(out)
39 39
40 40 def _repr_pretty_(self, p, cycle):
41 41 p.text(self._lsmagic())
42 42
43 43 def __str__(self):
44 44 return self._lsmagic()
45 45
46 46 def _jsonable(self):
47 47 """turn magics dict into jsonable dict of the same structure
48 48
49 49 replaces object instances with their class names as strings
50 50 """
51 51 magic_dict = {}
52 52 mman = self.magics_manager
53 53 magics = mman.lsmagic()
54 54 for key, subdict in magics.items():
55 55 d = {}
56 56 magic_dict[key] = d
57 57 for name, obj in subdict.items():
58 58 try:
59 59 classname = obj.__self__.__class__.__name__
60 60 except AttributeError:
61 61 classname = 'Other'
62 62
63 63 d[name] = classname
64 64 return magic_dict
65 65
66 66 def _repr_json_(self):
67 67 return self._jsonable()
68 68
69 69
70 70 @magics_class
71 71 class BasicMagics(Magics):
72 72 """Magics that provide central IPython functionality.
73 73
74 74 These are various magics that don't fit into specific categories but that
75 75 are all part of the base 'IPython experience'."""
76 76
77 77 @skip_doctest
78 78 @magic_arguments.magic_arguments()
79 79 @magic_arguments.argument(
80 80 '-l', '--line', action='store_true',
81 81 help="""Create a line magic alias."""
82 82 )
83 83 @magic_arguments.argument(
84 84 '-c', '--cell', action='store_true',
85 85 help="""Create a cell magic alias."""
86 86 )
87 87 @magic_arguments.argument(
88 88 'name',
89 89 help="""Name of the magic to be created."""
90 90 )
91 91 @magic_arguments.argument(
92 92 'target',
93 93 help="""Name of the existing line or cell magic."""
94 94 )
95 95 @magic_arguments.argument(
96 96 '-p', '--params', default=None,
97 97 help="""Parameters passed to the magic function."""
98 98 )
99 99 @line_magic
100 100 def alias_magic(self, line=''):
101 101 """Create an alias for an existing line or cell magic.
102 102
103 103 Examples
104 104 --------
105 105 ::
106 106
107 107 In [1]: %alias_magic t timeit
108 108 Created `%t` as an alias for `%timeit`.
109 109 Created `%%t` as an alias for `%%timeit`.
110 110
111 111 In [2]: %t -n1 pass
112 112 107 ns Β± 43.6 ns per loop (mean Β± std. dev. of 7 runs, 1 loop each)
113 113
114 114 In [3]: %%t -n1
115 115 ...: pass
116 116 ...:
117 117 107 ns Β± 58.3 ns per loop (mean Β± std. dev. of 7 runs, 1 loop each)
118 118
119 119 In [4]: %alias_magic --cell whereami pwd
120 120 UsageError: Cell magic function `%%pwd` not found.
121 121 In [5]: %alias_magic --line whereami pwd
122 122 Created `%whereami` as an alias for `%pwd`.
123 123
124 124 In [6]: %whereami
125 125 Out[6]: '/home/testuser'
126 126
127 In [7]: %alias_magic h history "-p -l 30" --line
127 In [7]: %alias_magic h history -p "-l 30" --line
128 128 Created `%h` as an alias for `%history -l 30`.
129 129 """
130 130
131 131 args = magic_arguments.parse_argstring(self.alias_magic, line)
132 132 shell = self.shell
133 133 mman = self.shell.magics_manager
134 134 escs = ''.join(magic_escapes.values())
135 135
136 136 target = args.target.lstrip(escs)
137 137 name = args.name.lstrip(escs)
138 138
139 139 params = args.params
140 140 if (params and
141 141 ((params.startswith('"') and params.endswith('"'))
142 142 or (params.startswith("'") and params.endswith("'")))):
143 143 params = params[1:-1]
144 144
145 145 # Find the requested magics.
146 146 m_line = shell.find_magic(target, 'line')
147 147 m_cell = shell.find_magic(target, 'cell')
148 148 if args.line and m_line is None:
149 149 raise UsageError('Line magic function `%s%s` not found.' %
150 150 (magic_escapes['line'], target))
151 151 if args.cell and m_cell is None:
152 152 raise UsageError('Cell magic function `%s%s` not found.' %
153 153 (magic_escapes['cell'], target))
154 154
155 155 # If --line and --cell are not specified, default to the ones
156 156 # that are available.
157 157 if not args.line and not args.cell:
158 158 if not m_line and not m_cell:
159 159 raise UsageError(
160 160 'No line or cell magic with name `%s` found.' % target
161 161 )
162 162 args.line = bool(m_line)
163 163 args.cell = bool(m_cell)
164 164
165 165 params_str = "" if params is None else " " + params
166 166
167 167 if args.line:
168 168 mman.register_alias(name, target, 'line', params)
169 169 print('Created `%s%s` as an alias for `%s%s%s`.' % (
170 170 magic_escapes['line'], name,
171 171 magic_escapes['line'], target, params_str))
172 172
173 173 if args.cell:
174 174 mman.register_alias(name, target, 'cell', params)
175 175 print('Created `%s%s` as an alias for `%s%s%s`.' % (
176 176 magic_escapes['cell'], name,
177 177 magic_escapes['cell'], target, params_str))
178 178
179 179 @line_magic
180 180 def lsmagic(self, parameter_s=''):
181 181 """List currently available magic functions."""
182 182 return MagicsDisplay(self.shell.magics_manager, ignore=[])
183 183
184 184 def _magic_docs(self, brief=False, rest=False):
185 185 """Return docstrings from magic functions."""
186 186 mman = self.shell.magics_manager
187 187 docs = mman.lsmagic_docs(brief, missing='No documentation')
188 188
189 189 if rest:
190 190 format_string = '**%s%s**::\n\n%s\n\n'
191 191 else:
192 192 format_string = '%s%s:\n%s\n'
193 193
194 194 return ''.join(
195 195 [format_string % (magic_escapes['line'], fname,
196 196 indent(dedent(fndoc)))
197 197 for fname, fndoc in sorted(docs['line'].items())]
198 198 +
199 199 [format_string % (magic_escapes['cell'], fname,
200 200 indent(dedent(fndoc)))
201 201 for fname, fndoc in sorted(docs['cell'].items())]
202 202 )
203 203
204 204 @line_magic
205 205 def magic(self, parameter_s=''):
206 206 """Print information about the magic function system.
207 207
208 208 Supported formats: -latex, -brief, -rest
209 209 """
210 210
211 211 mode = ''
212 212 try:
213 213 mode = parameter_s.split()[0][1:]
214 214 except IndexError:
215 215 pass
216 216
217 217 brief = (mode == 'brief')
218 218 rest = (mode == 'rest')
219 219 magic_docs = self._magic_docs(brief, rest)
220 220
221 221 if mode == 'latex':
222 222 print(self.format_latex(magic_docs))
223 223 return
224 224 else:
225 225 magic_docs = format_screen(magic_docs)
226 226
227 227 out = ["""
228 228 IPython's 'magic' functions
229 229 ===========================
230 230
231 231 The magic function system provides a series of functions which allow you to
232 232 control the behavior of IPython itself, plus a lot of system-type
233 233 features. There are two kinds of magics, line-oriented and cell-oriented.
234 234
235 235 Line magics are prefixed with the % character and work much like OS
236 236 command-line calls: they get as an argument the rest of the line, where
237 237 arguments are passed without parentheses or quotes. For example, this will
238 238 time the given statement::
239 239
240 240 %timeit range(1000)
241 241
242 242 Cell magics are prefixed with a double %%, and they are functions that get as
243 243 an argument not only the rest of the line, but also the lines below it in a
244 244 separate argument. These magics are called with two arguments: the rest of the
245 245 call line and the body of the cell, consisting of the lines below the first.
246 246 For example::
247 247
248 248 %%timeit x = numpy.random.randn((100, 100))
249 249 numpy.linalg.svd(x)
250 250
251 251 will time the execution of the numpy svd routine, running the assignment of x
252 252 as part of the setup phase, which is not timed.
253 253
254 254 In a line-oriented client (the terminal or Qt console IPython), starting a new
255 255 input with %% will automatically enter cell mode, and IPython will continue
256 256 reading input until a blank line is given. In the notebook, simply type the
257 257 whole cell as one entity, but keep in mind that the %% escape can only be at
258 258 the very start of the cell.
259 259
260 260 NOTE: If you have 'automagic' enabled (via the command line option or with the
261 261 %automagic function), you don't need to type in the % explicitly for line
262 262 magics; cell magics always require an explicit '%%' escape. By default,
263 263 IPython ships with automagic on, so you should only rarely need the % escape.
264 264
265 265 Example: typing '%cd mydir' (without the quotes) changes your working directory
266 266 to 'mydir', if it exists.
267 267
268 268 For a list of the available magic functions, use %lsmagic. For a description
269 269 of any of them, type %magic_name?, e.g. '%cd?'.
270 270
271 271 Currently the magic system has the following functions:""",
272 272 magic_docs,
273 273 "Summary of magic functions (from %slsmagic):" % magic_escapes['line'],
274 274 str(self.lsmagic()),
275 275 ]
276 276 page.page('\n'.join(out))
277 277
278 278
279 279 @line_magic
280 280 def page(self, parameter_s=''):
281 281 """Pretty print the object and display it through a pager.
282 282
283 283 %page [options] OBJECT
284 284
285 285 If no object is given, use _ (last output).
286 286
287 287 Options:
288 288
289 289 -r: page str(object), don't pretty-print it."""
290 290
291 291 # After a function contributed by Olivier Aubert, slightly modified.
292 292
293 293 # Process options/args
294 294 opts, args = self.parse_options(parameter_s, 'r')
295 295 raw = 'r' in opts
296 296
297 297 oname = args and args or '_'
298 298 info = self.shell._ofind(oname)
299 299 if info.found:
300 300 if raw:
301 301 txt = str(info.obj)
302 302 else:
303 303 txt = pformat(info.obj)
304 304 page.page(txt)
305 305 else:
306 306 print('Object `%s` not found' % oname)
307 307
308 308 @line_magic
309 309 def pprint(self, parameter_s=''):
310 310 """Toggle pretty printing on/off."""
311 311 ptformatter = self.shell.display_formatter.formatters['text/plain']
312 312 ptformatter.pprint = bool(1 - ptformatter.pprint)
313 313 print('Pretty printing has been turned',
314 314 ['OFF','ON'][ptformatter.pprint])
315 315
316 316 @line_magic
317 317 def colors(self, parameter_s=''):
318 318 """Switch color scheme for prompts, info system and exception handlers.
319 319
320 320 Currently implemented schemes: NoColor, Linux, LightBG.
321 321
322 322 Color scheme names are not case-sensitive.
323 323
324 324 Examples
325 325 --------
326 326 To get a plain black and white terminal::
327 327
328 328 %colors nocolor
329 329 """
330 330 def color_switch_err(name):
331 331 warn('Error changing %s color schemes.\n%s' %
332 332 (name, sys.exc_info()[1]), stacklevel=2)
333 333
334 334
335 335 new_scheme = parameter_s.strip()
336 336 if not new_scheme:
337 337 raise UsageError(
338 338 "%colors: you must specify a color scheme. See '%colors?'")
339 339 # local shortcut
340 340 shell = self.shell
341 341
342 342 # Set shell colour scheme
343 343 try:
344 344 shell.colors = new_scheme
345 345 shell.refresh_style()
346 346 except:
347 347 color_switch_err('shell')
348 348
349 349 # Set exception colors
350 350 try:
351 351 shell.InteractiveTB.set_colors(scheme = new_scheme)
352 352 shell.SyntaxTB.set_colors(scheme = new_scheme)
353 353 except:
354 354 color_switch_err('exception')
355 355
356 356 # Set info (for 'object?') colors
357 357 if shell.color_info:
358 358 try:
359 359 shell.inspector.set_active_scheme(new_scheme)
360 360 except:
361 361 color_switch_err('object inspector')
362 362 else:
363 363 shell.inspector.set_active_scheme('NoColor')
364 364
365 365 @line_magic
366 366 def xmode(self, parameter_s=''):
367 367 """Switch modes for the exception handlers.
368 368
369 369 Valid modes: Plain, Context, Verbose, and Minimal.
370 370
371 371 If called without arguments, acts as a toggle.
372 372
373 373 When in verbose mode the value `--show` (and `--hide`)
374 374 will respectively show (or hide) frames with ``__tracebackhide__ =
375 375 True`` value set.
376 376 """
377 377
378 378 def xmode_switch_err(name):
379 379 warn('Error changing %s exception modes.\n%s' %
380 380 (name,sys.exc_info()[1]))
381 381
382 382 shell = self.shell
383 383 if parameter_s.strip() == "--show":
384 384 shell.InteractiveTB.skip_hidden = False
385 385 return
386 386 if parameter_s.strip() == "--hide":
387 387 shell.InteractiveTB.skip_hidden = True
388 388 return
389 389
390 390 new_mode = parameter_s.strip().capitalize()
391 391 try:
392 392 shell.InteractiveTB.set_mode(mode=new_mode)
393 393 print('Exception reporting mode:',shell.InteractiveTB.mode)
394 394 except:
395 395 xmode_switch_err('user')
396 396
397 397 @line_magic
398 398 def quickref(self, arg):
399 399 """ Show a quick reference sheet """
400 400 from IPython.core.usage import quick_reference
401 401 qr = quick_reference + self._magic_docs(brief=True)
402 402 page.page(qr)
403 403
404 404 @line_magic
405 405 def doctest_mode(self, parameter_s=''):
406 406 """Toggle doctest mode on and off.
407 407
408 408 This mode is intended to make IPython behave as much as possible like a
409 409 plain Python shell, from the perspective of how its prompts, exceptions
410 410 and output look. This makes it easy to copy and paste parts of a
411 411 session into doctests. It does so by:
412 412
413 413 - Changing the prompts to the classic ``>>>`` ones.
414 414 - Changing the exception reporting mode to 'Plain'.
415 415 - Disabling pretty-printing of output.
416 416
417 417 Note that IPython also supports the pasting of code snippets that have
418 418 leading '>>>' and '...' prompts in them. This means that you can paste
419 419 doctests from files or docstrings (even if they have leading
420 420 whitespace), and the code will execute correctly. You can then use
421 421 '%history -t' to see the translated history; this will give you the
422 422 input after removal of all the leading prompts and whitespace, which
423 423 can be pasted back into an editor.
424 424
425 425 With these features, you can switch into this mode easily whenever you
426 426 need to do testing and changes to doctests, without having to leave
427 427 your existing IPython session.
428 428 """
429 429
430 430 # Shorthands
431 431 shell = self.shell
432 432 meta = shell.meta
433 433 disp_formatter = self.shell.display_formatter
434 434 ptformatter = disp_formatter.formatters['text/plain']
435 435 # dstore is a data store kept in the instance metadata bag to track any
436 436 # changes we make, so we can undo them later.
437 437 dstore = meta.setdefault('doctest_mode',Struct())
438 438 save_dstore = dstore.setdefault
439 439
440 440 # save a few values we'll need to recover later
441 441 mode = save_dstore('mode',False)
442 442 save_dstore('rc_pprint',ptformatter.pprint)
443 443 save_dstore('xmode',shell.InteractiveTB.mode)
444 444 save_dstore('rc_separate_out',shell.separate_out)
445 445 save_dstore('rc_separate_out2',shell.separate_out2)
446 446 save_dstore('rc_separate_in',shell.separate_in)
447 447 save_dstore('rc_active_types',disp_formatter.active_types)
448 448
449 449 if not mode:
450 450 # turn on
451 451
452 452 # Prompt separators like plain python
453 453 shell.separate_in = ''
454 454 shell.separate_out = ''
455 455 shell.separate_out2 = ''
456 456
457 457
458 458 ptformatter.pprint = False
459 459 disp_formatter.active_types = ['text/plain']
460 460
461 461 shell.magic('xmode Plain')
462 462 else:
463 463 # turn off
464 464 shell.separate_in = dstore.rc_separate_in
465 465
466 466 shell.separate_out = dstore.rc_separate_out
467 467 shell.separate_out2 = dstore.rc_separate_out2
468 468
469 469 ptformatter.pprint = dstore.rc_pprint
470 470 disp_formatter.active_types = dstore.rc_active_types
471 471
472 472 shell.magic('xmode ' + dstore.xmode)
473 473
474 474 # mode here is the state before we switch; switch_doctest_mode takes
475 475 # the mode we're switching to.
476 476 shell.switch_doctest_mode(not mode)
477 477
478 478 # Store new mode and inform
479 479 dstore.mode = bool(not mode)
480 480 mode_label = ['OFF','ON'][dstore.mode]
481 481 print('Doctest mode is:', mode_label)
482 482
483 483 @line_magic
484 484 def gui(self, parameter_s=''):
485 485 """Enable or disable IPython GUI event loop integration.
486 486
487 487 %gui [GUINAME]
488 488
489 489 This magic replaces IPython's threaded shells that were activated
490 490 using the (pylab/wthread/etc.) command line flags. GUI toolkits
491 491 can now be enabled at runtime and keyboard
492 492 interrupts should work without any problems. The following toolkits
493 493 are supported: wxPython, PyQt4, PyGTK, Tk and Cocoa (OSX)::
494 494
495 495 %gui wx # enable wxPython event loop integration
496 496 %gui qt # enable PyQt/PySide event loop integration
497 497 # with the latest version available.
498 498 %gui qt6 # enable PyQt6/PySide6 event loop integration
499 499 %gui qt5 # enable PyQt5/PySide2 event loop integration
500 500 %gui gtk # enable PyGTK event loop integration
501 501 %gui gtk3 # enable Gtk3 event loop integration
502 502 %gui gtk4 # enable Gtk4 event loop integration
503 503 %gui tk # enable Tk event loop integration
504 504 %gui osx # enable Cocoa event loop integration
505 505 # (requires %matplotlib 1.1)
506 506 %gui # disable all event loop integration
507 507
508 508 WARNING: after any of these has been called you can simply create
509 509 an application object, but DO NOT start the event loop yourself, as
510 510 we have already handled that.
511 511 """
512 512 opts, arg = self.parse_options(parameter_s, '')
513 513 if arg=='': arg = None
514 514 try:
515 515 return self.shell.enable_gui(arg)
516 516 except Exception as e:
517 517 # print simple error message, rather than traceback if we can't
518 518 # hook up the GUI
519 519 error(str(e))
520 520
521 521 @skip_doctest
522 522 @line_magic
523 523 def precision(self, s=''):
524 524 """Set floating point precision for pretty printing.
525 525
526 526 Can set either integer precision or a format string.
527 527
528 528 If numpy has been imported and precision is an int,
529 529 numpy display precision will also be set, via ``numpy.set_printoptions``.
530 530
531 531 If no argument is given, defaults will be restored.
532 532
533 533 Examples
534 534 --------
535 535 ::
536 536
537 537 In [1]: from math import pi
538 538
539 539 In [2]: %precision 3
540 540 Out[2]: '%.3f'
541 541
542 542 In [3]: pi
543 543 Out[3]: 3.142
544 544
545 545 In [4]: %precision %i
546 546 Out[4]: '%i'
547 547
548 548 In [5]: pi
549 549 Out[5]: 3
550 550
551 551 In [6]: %precision %e
552 552 Out[6]: '%e'
553 553
554 554 In [7]: pi**10
555 555 Out[7]: 9.364805e+04
556 556
557 557 In [8]: %precision
558 558 Out[8]: '%r'
559 559
560 560 In [9]: pi**10
561 561 Out[9]: 93648.047476082982
562 562 """
563 563 ptformatter = self.shell.display_formatter.formatters['text/plain']
564 564 ptformatter.float_precision = s
565 565 return ptformatter.float_format
566 566
567 567 @magic_arguments.magic_arguments()
568 568 @magic_arguments.argument(
569 569 'filename', type=str,
570 570 help='Notebook name or filename'
571 571 )
572 572 @line_magic
573 573 def notebook(self, s):
574 574 """Export and convert IPython notebooks.
575 575
576 576 This function can export the current IPython history to a notebook file.
577 577 For example, to export the history to "foo.ipynb" do "%notebook foo.ipynb".
578 578 """
579 579 args = magic_arguments.parse_argstring(self.notebook, s)
580 580 outfname = os.path.expanduser(args.filename)
581 581
582 582 from nbformat import write, v4
583 583
584 584 cells = []
585 585 hist = list(self.shell.history_manager.get_range())
586 586 if(len(hist)<=1):
587 587 raise ValueError('History is empty, cannot export')
588 588 for session, execution_count, source in hist[:-1]:
589 589 cells.append(v4.new_code_cell(
590 590 execution_count=execution_count,
591 591 source=source
592 592 ))
593 593 nb = v4.new_notebook(cells=cells)
594 594 with io.open(outfname, "w", encoding="utf-8") as f:
595 595 write(nb, f, version=4)
596 596
597 597 @magics_class
598 598 class AsyncMagics(BasicMagics):
599 599
600 600 @line_magic
601 601 def autoawait(self, parameter_s):
602 602 """
603 603 Allow to change the status of the autoawait option.
604 604
605 605 This allow you to set a specific asynchronous code runner.
606 606
607 607 If no value is passed, print the currently used asynchronous integration
608 608 and whether it is activated.
609 609
610 610 It can take a number of value evaluated in the following order:
611 611
612 612 - False/false/off deactivate autoawait integration
613 613 - True/true/on activate autoawait integration using configured default
614 614 loop
615 615 - asyncio/curio/trio activate autoawait integration and use integration
616 616 with said library.
617 617
618 618 - `sync` turn on the pseudo-sync integration (mostly used for
619 619 `IPython.embed()` which does not run IPython with a real eventloop and
620 620 deactivate running asynchronous code. Turning on Asynchronous code with
621 621 the pseudo sync loop is undefined behavior and may lead IPython to crash.
622 622
623 623 If the passed parameter does not match any of the above and is a python
624 624 identifier, get said object from user namespace and set it as the
625 625 runner, and activate autoawait.
626 626
627 627 If the object is a fully qualified object name, attempt to import it and
628 628 set it as the runner, and activate autoawait.
629 629
630 630 The exact behavior of autoawait is experimental and subject to change
631 631 across version of IPython and Python.
632 632 """
633 633
634 634 param = parameter_s.strip()
635 635 d = {True: "on", False: "off"}
636 636
637 637 if not param:
638 638 print("IPython autoawait is `{}`, and set to use `{}`".format(
639 639 d[self.shell.autoawait],
640 640 self.shell.loop_runner
641 641 ))
642 642 return None
643 643
644 644 if param.lower() in ('false', 'off'):
645 645 self.shell.autoawait = False
646 646 return None
647 647 if param.lower() in ('true', 'on'):
648 648 self.shell.autoawait = True
649 649 return None
650 650
651 651 if param in self.shell.loop_runner_map:
652 652 self.shell.loop_runner, self.shell.autoawait = self.shell.loop_runner_map[param]
653 653 return None
654 654
655 655 if param in self.shell.user_ns :
656 656 self.shell.loop_runner = self.shell.user_ns[param]
657 657 self.shell.autoawait = True
658 658 return None
659 659
660 660 runner = import_item(param)
661 661
662 662 self.shell.loop_runner = runner
663 663 self.shell.autoawait = True
General Comments 0
You need to be logged in to leave comments. Login now