##// END OF EJS Templates
Merge pull request #11330 from hongshaoyang/patch-1...
Matthias Bussonnier -
r24696:2f25f7dd merge
parent child Browse files
Show More
@@ -1,831 +1,834 b''
1 1 """Implementation of magic functions for interaction with the OS.
2 2
3 3 Note: this module is named 'osm' instead of 'os' to avoid a collision with the
4 4 builtin.
5 5 """
6 6 # Copyright (c) IPython Development Team.
7 7 # Distributed under the terms of the Modified BSD License.
8 8
9 9 import io
10 10 import os
11 11 import re
12 12 import sys
13 13 from pprint import pformat
14 14
15 15 from IPython.core import magic_arguments
16 16 from IPython.core import oinspect
17 17 from IPython.core import page
18 18 from IPython.core.alias import AliasError, Alias
19 19 from IPython.core.error import UsageError
20 20 from IPython.core.magic import (
21 21 Magics, compress_dhist, magics_class, line_magic, cell_magic, line_cell_magic
22 22 )
23 23 from IPython.testing.skipdoctest import skip_doctest
24 24 from IPython.utils.openpy import source_to_unicode
25 25 from IPython.utils.process import abbrev_cwd
26 26 from IPython.utils.terminal import set_term_title
27 27
28 28
29 29 @magics_class
30 30 class OSMagics(Magics):
31 31 """Magics to interact with the underlying OS (shell-type functionality).
32 32 """
33 33
34 34 def __init__(self, shell=None, **kwargs):
35 35
36 36 # Now define isexec in a cross platform manner.
37 37 self.is_posix = False
38 38 self.execre = None
39 39 if os.name == 'posix':
40 40 self.is_posix = True
41 41 else:
42 42 try:
43 43 winext = os.environ['pathext'].replace(';','|').replace('.','')
44 44 except KeyError:
45 45 winext = 'exe|com|bat|py'
46 46
47 47 self.execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE)
48 48
49 49 # call up the chain
50 50 super().__init__(shell=shell, **kwargs)
51 51
52 52
53 53 @skip_doctest
54 54 def _isexec_POSIX(self, file):
55 55 """
56 56 Test for executible on a POSIX system
57 57 """
58 58 return file.is_file() and os.access(file.path, os.X_OK)
59 59
60 60
61 61 @skip_doctest
62 62 def _isexec_WIN(self, file):
63 63 """
64 64 Test for executible file on non POSIX system
65 65 """
66 66 return file.is_file() and self.execre.match(file.name) is not None
67 67
68 68 @skip_doctest
69 69 def isexec(self, file):
70 70 """
71 71 Test for executible file on non POSIX system
72 72 """
73 73 if self.is_posix:
74 74 return self._isexec_POSIX(file)
75 75 else:
76 76 return self._isexec_WIN(file)
77 77
78 78
79 79 @skip_doctest
80 80 @line_magic
81 81 def alias(self, parameter_s=''):
82 82 """Define an alias for a system command.
83 83
84 84 '%alias alias_name cmd' defines 'alias_name' as an alias for 'cmd'
85 85
86 86 Then, typing 'alias_name params' will execute the system command 'cmd
87 87 params' (from your underlying operating system).
88 88
89 89 Aliases have lower precedence than magic functions and Python normal
90 90 variables, so if 'foo' is both a Python variable and an alias, the
91 91 alias can not be executed until 'del foo' removes the Python variable.
92 92
93 93 You can use the %l specifier in an alias definition to represent the
94 94 whole line when the alias is called. For example::
95 95
96 96 In [2]: alias bracket echo "Input in brackets: <%l>"
97 97 In [3]: bracket hello world
98 98 Input in brackets: <hello world>
99 99
100 100 You can also define aliases with parameters using %s specifiers (one
101 101 per parameter)::
102 102
103 103 In [1]: alias parts echo first %s second %s
104 104 In [2]: %parts A B
105 105 first A second B
106 106 In [3]: %parts A
107 107 Incorrect number of arguments: 2 expected.
108 108 parts is an alias to: 'echo first %s second %s'
109 109
110 110 Note that %l and %s are mutually exclusive. You can only use one or
111 111 the other in your aliases.
112 112
113 113 Aliases expand Python variables just like system calls using ! or !!
114 114 do: all expressions prefixed with '$' get expanded. For details of
115 115 the semantic rules, see PEP-215:
116 116 http://www.python.org/peps/pep-0215.html. This is the library used by
117 117 IPython for variable expansion. If you want to access a true shell
118 118 variable, an extra $ is necessary to prevent its expansion by
119 119 IPython::
120 120
121 121 In [6]: alias show echo
122 122 In [7]: PATH='A Python string'
123 123 In [8]: show $PATH
124 124 A Python string
125 125 In [9]: show $$PATH
126 126 /usr/local/lf9560/bin:/usr/local/intel/compiler70/ia32/bin:...
127 127
128 128 You can use the alias facility to access all of $PATH. See the %rehashx
129 129 function, which automatically creates aliases for the contents of your
130 130 $PATH.
131 131
132 132 If called with no parameters, %alias prints the current alias table
133 133 for your system. For posix systems, the default aliases are 'cat',
134 134 'cp', 'mv', 'rm', 'rmdir', and 'mkdir', and other platform-specific
135 135 aliases are added. For windows-based systems, the default aliases are
136 136 'copy', 'ddir', 'echo', 'ls', 'ldir', 'mkdir', 'ren', and 'rmdir'.
137 137
138 138 You can see the definition of alias by adding a question mark in the
139 139 end::
140 140
141 141 In [1]: cat?
142 142 Repr: <alias cat for 'cat'>"""
143 143
144 144 par = parameter_s.strip()
145 145 if not par:
146 146 aliases = sorted(self.shell.alias_manager.aliases)
147 147 # stored = self.shell.db.get('stored_aliases', {} )
148 148 # for k, v in stored:
149 149 # atab.append(k, v[0])
150 150
151 151 print("Total number of aliases:", len(aliases))
152 152 sys.stdout.flush()
153 153 return aliases
154 154
155 155 # Now try to define a new one
156 156 try:
157 157 alias,cmd = par.split(None, 1)
158 158 except TypeError:
159 159 print(oinspect.getdoc(self.alias))
160 160 return
161 161
162 162 try:
163 163 self.shell.alias_manager.define_alias(alias, cmd)
164 164 except AliasError as e:
165 165 print(e)
166 166 # end magic_alias
167 167
168 168 @line_magic
169 169 def unalias(self, parameter_s=''):
170 170 """Remove an alias"""
171 171
172 172 aname = parameter_s.strip()
173 173 try:
174 174 self.shell.alias_manager.undefine_alias(aname)
175 175 except ValueError as e:
176 176 print(e)
177 177 return
178 178
179 179 stored = self.shell.db.get('stored_aliases', {} )
180 180 if aname in stored:
181 181 print("Removing %stored alias",aname)
182 182 del stored[aname]
183 183 self.shell.db['stored_aliases'] = stored
184 184
185 185 @line_magic
186 186 def rehashx(self, parameter_s=''):
187 187 """Update the alias table with all executable files in $PATH.
188 188
189 189 rehashx explicitly checks that every entry in $PATH is a file
190 190 with execute access (os.X_OK).
191 191
192 192 Under Windows, it checks executability as a match against a
193 193 '|'-separated string of extensions, stored in the IPython config
194 194 variable win_exec_ext. This defaults to 'exe|com|bat'.
195 195
196 196 This function also resets the root module cache of module completer,
197 197 used on slow filesystems.
198 198 """
199 199 from IPython.core.alias import InvalidAliasError
200 200
201 201 # for the benefit of module completer in ipy_completers.py
202 202 del self.shell.db['rootmodules_cache']
203 203
204 204 path = [os.path.abspath(os.path.expanduser(p)) for p in
205 205 os.environ.get('PATH','').split(os.pathsep)]
206 206
207 207 syscmdlist = []
208 208 savedir = os.getcwd()
209 209
210 210 # Now walk the paths looking for executables to alias.
211 211 try:
212 212 # write the whole loop for posix/Windows so we don't have an if in
213 213 # the innermost part
214 214 if self.is_posix:
215 215 for pdir in path:
216 216 try:
217 217 os.chdir(pdir)
218 218 except OSError:
219 219 continue
220 220
221 221 # for python 3.6+ rewrite to: with os.scandir(pdir) as dirlist:
222 222 dirlist = os.scandir(path=pdir)
223 223 for ff in dirlist:
224 224 if self.isexec(ff):
225 225 fname = ff.name
226 226 try:
227 227 # Removes dots from the name since ipython
228 228 # will assume names with dots to be python.
229 229 if not self.shell.alias_manager.is_alias(fname):
230 230 self.shell.alias_manager.define_alias(
231 231 fname.replace('.',''), fname)
232 232 except InvalidAliasError:
233 233 pass
234 234 else:
235 235 syscmdlist.append(fname)
236 236 else:
237 237 no_alias = Alias.blacklist
238 238 for pdir in path:
239 239 try:
240 240 os.chdir(pdir)
241 241 except OSError:
242 242 continue
243 243
244 244 # for python 3.6+ rewrite to: with os.scandir(pdir) as dirlist:
245 245 dirlist = os.scandir(pdir)
246 246 for ff in dirlist:
247 247 fname = ff.name
248 248 base, ext = os.path.splitext(fname)
249 249 if self.isexec(ff) and base.lower() not in no_alias:
250 250 if ext.lower() == '.exe':
251 251 fname = base
252 252 try:
253 253 # Removes dots from the name since ipython
254 254 # will assume names with dots to be python.
255 255 self.shell.alias_manager.define_alias(
256 256 base.lower().replace('.',''), fname)
257 257 except InvalidAliasError:
258 258 pass
259 259 syscmdlist.append(fname)
260 260
261 261 self.shell.db['syscmdlist'] = syscmdlist
262 262 finally:
263 263 os.chdir(savedir)
264 264
265 265 @skip_doctest
266 266 @line_magic
267 267 def pwd(self, parameter_s=''):
268 268 """Return the current working directory path.
269 269
270 270 Examples
271 271 --------
272 272 ::
273 273
274 274 In [9]: pwd
275 275 Out[9]: '/home/tsuser/sprint/ipython'
276 276 """
277 277 try:
278 278 return os.getcwd()
279 279 except FileNotFoundError:
280 280 raise UsageError("CWD no longer exists - please use %cd to change directory.")
281 281
282 282 @skip_doctest
283 283 @line_magic
284 284 def cd(self, parameter_s=''):
285 285 """Change the current working directory.
286 286
287 287 This command automatically maintains an internal list of directories
288 288 you visit during your IPython session, in the variable _dh. The
289 289 command %dhist shows this history nicely formatted. You can also
290 290 do 'cd -<tab>' to see directory history conveniently.
291 291
292 292 Usage:
293 293
294 294 cd 'dir': changes to directory 'dir'.
295 295
296 296 cd -: changes to the last visited directory.
297 297
298 298 cd -<n>: changes to the n-th directory in the directory history.
299 299
300 300 cd --foo: change to directory that matches 'foo' in history
301 301
302 302 cd -b <bookmark_name>: jump to a bookmark set by %bookmark
303 303 (note: cd <bookmark_name> is enough if there is no
304 304 directory <bookmark_name>, but a bookmark with the name exists.)
305 305 'cd -b <tab>' allows you to tab-complete bookmark names.
306 306
307 307 Options:
308 308
309 309 -q: quiet. Do not print the working directory after the cd command is
310 310 executed. By default IPython's cd command does print this directory,
311 311 since the default prompts do not display path information.
312 312
313 313 Note that !cd doesn't work for this purpose because the shell where
314 314 !command runs is immediately discarded after executing 'command'.
315 315
316 316 Examples
317 317 --------
318 318 ::
319 319
320 320 In [10]: cd parent/child
321 321 /home/tsuser/parent/child
322 322 """
323 323
324 324 try:
325 325 oldcwd = os.getcwd()
326 326 except FileNotFoundError:
327 327 # Happens if the CWD has been deleted.
328 328 oldcwd = None
329 329
330 330 numcd = re.match(r'(-)(\d+)$',parameter_s)
331 331 # jump in directory history by number
332 332 if numcd:
333 333 nn = int(numcd.group(2))
334 334 try:
335 335 ps = self.shell.user_ns['_dh'][nn]
336 336 except IndexError:
337 337 print('The requested directory does not exist in history.')
338 338 return
339 339 else:
340 340 opts = {}
341 341 elif parameter_s.startswith('--'):
342 342 ps = None
343 343 fallback = None
344 344 pat = parameter_s[2:]
345 345 dh = self.shell.user_ns['_dh']
346 346 # first search only by basename (last component)
347 347 for ent in reversed(dh):
348 348 if pat in os.path.basename(ent) and os.path.isdir(ent):
349 349 ps = ent
350 350 break
351 351
352 352 if fallback is None and pat in ent and os.path.isdir(ent):
353 353 fallback = ent
354 354
355 355 # if we have no last part match, pick the first full path match
356 356 if ps is None:
357 357 ps = fallback
358 358
359 359 if ps is None:
360 360 print("No matching entry in directory history")
361 361 return
362 362 else:
363 363 opts = {}
364 364
365 365
366 366 else:
367 367 opts, ps = self.parse_options(parameter_s, 'qb', mode='string')
368 368 # jump to previous
369 369 if ps == '-':
370 370 try:
371 371 ps = self.shell.user_ns['_dh'][-2]
372 372 except IndexError:
373 373 raise UsageError('%cd -: No previous directory to change to.')
374 374 # jump to bookmark if needed
375 375 else:
376 376 if not os.path.isdir(ps) or 'b' in opts:
377 377 bkms = self.shell.db.get('bookmarks', {})
378 378
379 379 if ps in bkms:
380 380 target = bkms[ps]
381 381 print('(bookmark:%s) -> %s' % (ps, target))
382 382 ps = target
383 383 else:
384 384 if 'b' in opts:
385 385 raise UsageError("Bookmark '%s' not found. "
386 386 "Use '%%bookmark -l' to see your bookmarks." % ps)
387 387
388 388 # at this point ps should point to the target dir
389 389 if ps:
390 390 try:
391 391 os.chdir(os.path.expanduser(ps))
392 392 if hasattr(self.shell, 'term_title') and self.shell.term_title:
393 393 set_term_title(self.shell.term_title_format.format(cwd=abbrev_cwd()))
394 394 except OSError:
395 395 print(sys.exc_info()[1])
396 396 else:
397 397 cwd = os.getcwd()
398 398 dhist = self.shell.user_ns['_dh']
399 399 if oldcwd != cwd:
400 400 dhist.append(cwd)
401 401 self.shell.db['dhist'] = compress_dhist(dhist)[-100:]
402 402
403 403 else:
404 404 os.chdir(self.shell.home_dir)
405 405 if hasattr(self.shell, 'term_title') and self.shell.term_title:
406 406 set_term_title(self.shell.term_title_format.format(cwd="~"))
407 407 cwd = os.getcwd()
408 408 dhist = self.shell.user_ns['_dh']
409 409
410 410 if oldcwd != cwd:
411 411 dhist.append(cwd)
412 412 self.shell.db['dhist'] = compress_dhist(dhist)[-100:]
413 413 if not 'q' in opts and self.shell.user_ns['_dh']:
414 414 print(self.shell.user_ns['_dh'][-1])
415 415
416 416 @line_magic
417 417 def env(self, parameter_s=''):
418 418 """Get, set, or list environment variables.
419 419
420 420 Usage:\\
421 421
422 422 %env: lists all environment variables/values
423 423 %env var: get value for var
424 424 %env var val: set value for var
425 425 %env var=val: set value for var
426 426 %env var=$val: set value for var, using python expansion if possible
427 427 """
428 428 if parameter_s.strip():
429 429 split = '=' if '=' in parameter_s else ' '
430 430 bits = parameter_s.split(split)
431 431 if len(bits) == 1:
432 432 key = parameter_s.strip()
433 433 if key in os.environ:
434 434 return os.environ[key]
435 435 else:
436 436 err = "Environment does not have key: {0}".format(key)
437 437 raise UsageError(err)
438 438 if len(bits) > 1:
439 439 return self.set_env(parameter_s)
440 440 return dict(os.environ)
441 441
442 442 @line_magic
443 443 def set_env(self, parameter_s):
444 444 """Set environment variables. Assumptions are that either "val" is a
445 445 name in the user namespace, or val is something that evaluates to a
446 446 string.
447 447
448 448 Usage:\\
449 449 %set_env var val: set value for var
450 450 %set_env var=val: set value for var
451 451 %set_env var=$val: set value for var, using python expansion if possible
452 452 """
453 453 split = '=' if '=' in parameter_s else ' '
454 454 bits = parameter_s.split(split, 1)
455 455 if not parameter_s.strip() or len(bits)<2:
456 456 raise UsageError("usage is 'set_env var=val'")
457 457 var = bits[0].strip()
458 458 val = bits[1].strip()
459 459 if re.match(r'.*\s.*', var):
460 460 # an environment variable with whitespace is almost certainly
461 461 # not what the user intended. what's more likely is the wrong
462 462 # split was chosen, ie for "set_env cmd_args A=B", we chose
463 463 # '=' for the split and should have chosen ' '. to get around
464 464 # this, users should just assign directly to os.environ or use
465 465 # standard magic {var} expansion.
466 466 err = "refusing to set env var with whitespace: '{0}'"
467 467 err = err.format(val)
468 468 raise UsageError(err)
469 469 os.environ[var] = val
470 470 print('env: {0}={1}'.format(var,val))
471 471
472 472 @line_magic
473 473 def pushd(self, parameter_s=''):
474 474 """Place the current dir on stack and change directory.
475 475
476 476 Usage:\\
477 477 %pushd ['dirname']
478 478 """
479 479
480 480 dir_s = self.shell.dir_stack
481 481 tgt = os.path.expanduser(parameter_s)
482 482 cwd = os.getcwd().replace(self.shell.home_dir,'~')
483 483 if tgt:
484 484 self.cd(parameter_s)
485 485 dir_s.insert(0,cwd)
486 486 return self.shell.magic('dirs')
487 487
488 488 @line_magic
489 489 def popd(self, parameter_s=''):
490 490 """Change to directory popped off the top of the stack.
491 491 """
492 492 if not self.shell.dir_stack:
493 493 raise UsageError("%popd on empty stack")
494 494 top = self.shell.dir_stack.pop(0)
495 495 self.cd(top)
496 496 print("popd ->",top)
497 497
498 498 @line_magic
499 499 def dirs(self, parameter_s=''):
500 500 """Return the current directory stack."""
501 501
502 502 return self.shell.dir_stack
503 503
504 504 @line_magic
505 505 def dhist(self, parameter_s=''):
506 506 """Print your history of visited directories.
507 507
508 508 %dhist -> print full history\\
509 509 %dhist n -> print last n entries only\\
510 510 %dhist n1 n2 -> print entries between n1 and n2 (n2 not included)\\
511 511
512 512 This history is automatically maintained by the %cd command, and
513 513 always available as the global list variable _dh. You can use %cd -<n>
514 514 to go to directory number <n>.
515 515
516 516 Note that most of time, you should view directory history by entering
517 517 cd -<TAB>.
518 518
519 519 """
520 520
521 521 dh = self.shell.user_ns['_dh']
522 522 if parameter_s:
523 523 try:
524 524 args = map(int,parameter_s.split())
525 525 except:
526 526 self.arg_err(self.dhist)
527 527 return
528 528 if len(args) == 1:
529 529 ini,fin = max(len(dh)-(args[0]),0),len(dh)
530 530 elif len(args) == 2:
531 531 ini,fin = args
532 532 fin = min(fin, len(dh))
533 533 else:
534 534 self.arg_err(self.dhist)
535 535 return
536 536 else:
537 537 ini,fin = 0,len(dh)
538 538 print('Directory history (kept in _dh)')
539 539 for i in range(ini, fin):
540 540 print("%d: %s" % (i, dh[i]))
541 541
542 542 @skip_doctest
543 543 @line_magic
544 544 def sc(self, parameter_s=''):
545 545 """Shell capture - run shell command and capture output (DEPRECATED use !).
546 546
547 547 DEPRECATED. Suboptimal, retained for backwards compatibility.
548 548
549 549 You should use the form 'var = !command' instead. Example:
550 550
551 551 "%sc -l myfiles = ls ~" should now be written as
552 552
553 553 "myfiles = !ls ~"
554 554
555 555 myfiles.s, myfiles.l and myfiles.n still apply as documented
556 556 below.
557 557
558 558 --
559 559 %sc [options] varname=command
560 560
561 561 IPython will run the given command using commands.getoutput(), and
562 562 will then update the user's interactive namespace with a variable
563 563 called varname, containing the value of the call. Your command can
564 564 contain shell wildcards, pipes, etc.
565 565
566 566 The '=' sign in the syntax is mandatory, and the variable name you
567 567 supply must follow Python's standard conventions for valid names.
568 568
569 569 (A special format without variable name exists for internal use)
570 570
571 571 Options:
572 572
573 573 -l: list output. Split the output on newlines into a list before
574 574 assigning it to the given variable. By default the output is stored
575 575 as a single string.
576 576
577 577 -v: verbose. Print the contents of the variable.
578 578
579 579 In most cases you should not need to split as a list, because the
580 580 returned value is a special type of string which can automatically
581 581 provide its contents either as a list (split on newlines) or as a
582 582 space-separated string. These are convenient, respectively, either
583 583 for sequential processing or to be passed to a shell command.
584 584
585 585 For example::
586 586
587 587 # Capture into variable a
588 588 In [1]: sc a=ls *py
589 589
590 590 # a is a string with embedded newlines
591 591 In [2]: a
592 592 Out[2]: 'setup.py\\nwin32_manual_post_install.py'
593 593
594 594 # which can be seen as a list:
595 595 In [3]: a.l
596 596 Out[3]: ['setup.py', 'win32_manual_post_install.py']
597 597
598 598 # or as a whitespace-separated string:
599 599 In [4]: a.s
600 600 Out[4]: 'setup.py win32_manual_post_install.py'
601 601
602 602 # a.s is useful to pass as a single command line:
603 603 In [5]: !wc -l $a.s
604 604 146 setup.py
605 605 130 win32_manual_post_install.py
606 606 276 total
607 607
608 608 # while the list form is useful to loop over:
609 609 In [6]: for f in a.l:
610 610 ...: !wc -l $f
611 611 ...:
612 612 146 setup.py
613 613 130 win32_manual_post_install.py
614 614
615 615 Similarly, the lists returned by the -l option are also special, in
616 616 the sense that you can equally invoke the .s attribute on them to
617 617 automatically get a whitespace-separated string from their contents::
618 618
619 619 In [7]: sc -l b=ls *py
620 620
621 621 In [8]: b
622 622 Out[8]: ['setup.py', 'win32_manual_post_install.py']
623 623
624 624 In [9]: b.s
625 625 Out[9]: 'setup.py win32_manual_post_install.py'
626 626
627 627 In summary, both the lists and strings used for output capture have
628 628 the following special attributes::
629 629
630 630 .l (or .list) : value as list.
631 631 .n (or .nlstr): value as newline-separated string.
632 632 .s (or .spstr): value as space-separated string.
633 633 """
634 634
635 635 opts,args = self.parse_options(parameter_s, 'lv')
636 636 # Try to get a variable name and command to run
637 637 try:
638 638 # the variable name must be obtained from the parse_options
639 639 # output, which uses shlex.split to strip options out.
640 640 var,_ = args.split('=', 1)
641 641 var = var.strip()
642 642 # But the command has to be extracted from the original input
643 643 # parameter_s, not on what parse_options returns, to avoid the
644 644 # quote stripping which shlex.split performs on it.
645 645 _,cmd = parameter_s.split('=', 1)
646 646 except ValueError:
647 647 var,cmd = '',''
648 648 # If all looks ok, proceed
649 649 split = 'l' in opts
650 650 out = self.shell.getoutput(cmd, split=split)
651 651 if 'v' in opts:
652 652 print('%s ==\n%s' % (var, pformat(out)))
653 653 if var:
654 654 self.shell.user_ns.update({var:out})
655 655 else:
656 656 return out
657 657
658 658 @line_cell_magic
659 659 def sx(self, line='', cell=None):
660 660 """Shell execute - run shell command and capture output (!! is short-hand).
661 661
662 662 %sx command
663 663
664 664 IPython will run the given command using commands.getoutput(), and
665 665 return the result formatted as a list (split on '\\n'). Since the
666 666 output is _returned_, it will be stored in ipython's regular output
667 667 cache Out[N] and in the '_N' automatic variables.
668 668
669 669 Notes:
670 670
671 671 1) If an input line begins with '!!', then %sx is automatically
672 672 invoked. That is, while::
673 673
674 674 !ls
675 675
676 676 causes ipython to simply issue system('ls'), typing::
677 677
678 678 !!ls
679 679
680 680 is a shorthand equivalent to::
681 681
682 682 %sx ls
683 683
684 684 2) %sx differs from %sc in that %sx automatically splits into a list,
685 685 like '%sc -l'. The reason for this is to make it as easy as possible
686 686 to process line-oriented shell output via further python commands.
687 687 %sc is meant to provide much finer control, but requires more
688 688 typing.
689 689
690 690 3) Just like %sc -l, this is a list with special attributes:
691 691 ::
692 692
693 693 .l (or .list) : value as list.
694 694 .n (or .nlstr): value as newline-separated string.
695 695 .s (or .spstr): value as whitespace-separated string.
696 696
697 697 This is very useful when trying to use such lists as arguments to
698 698 system commands."""
699 699
700 700 if cell is None:
701 701 # line magic
702 702 return self.shell.getoutput(line)
703 703 else:
704 704 opts,args = self.parse_options(line, '', 'out=')
705 705 output = self.shell.getoutput(cell)
706 706 out_name = opts.get('out', opts.get('o'))
707 707 if out_name:
708 708 self.shell.user_ns[out_name] = output
709 709 else:
710 710 return output
711 711
712 712 system = line_cell_magic('system')(sx)
713 713 bang = cell_magic('!')(sx)
714 714
715 715 @line_magic
716 716 def bookmark(self, parameter_s=''):
717 717 """Manage IPython's bookmark system.
718 718
719 719 %bookmark <name> - set bookmark to current dir
720 720 %bookmark <name> <dir> - set bookmark to <dir>
721 721 %bookmark -l - list all bookmarks
722 722 %bookmark -d <name> - remove bookmark
723 723 %bookmark -r - remove all bookmarks
724 724
725 725 You can later on access a bookmarked folder with::
726 726
727 727 %cd -b <name>
728 728
729 729 or simply '%cd <name>' if there is no directory called <name> AND
730 730 there is such a bookmark defined.
731 731
732 732 Your bookmarks persist through IPython sessions, but they are
733 733 associated with each profile."""
734 734
735 735 opts,args = self.parse_options(parameter_s,'drl',mode='list')
736 736 if len(args) > 2:
737 737 raise UsageError("%bookmark: too many arguments")
738 738
739 739 bkms = self.shell.db.get('bookmarks',{})
740 740
741 741 if 'd' in opts:
742 742 try:
743 743 todel = args[0]
744 744 except IndexError:
745 745 raise UsageError(
746 746 "%bookmark -d: must provide a bookmark to delete")
747 747 else:
748 748 try:
749 749 del bkms[todel]
750 750 except KeyError:
751 751 raise UsageError(
752 752 "%%bookmark -d: Can't delete bookmark '%s'" % todel)
753 753
754 754 elif 'r' in opts:
755 755 bkms = {}
756 756 elif 'l' in opts:
757 757 bks = sorted(bkms)
758 758 if bks:
759 759 size = max(map(len, bks))
760 760 else:
761 761 size = 0
762 762 fmt = '%-'+str(size)+'s -> %s'
763 763 print('Current bookmarks:')
764 764 for bk in bks:
765 765 print(fmt % (bk, bkms[bk]))
766 766 else:
767 767 if not args:
768 768 raise UsageError("%bookmark: You must specify the bookmark name")
769 769 elif len(args)==1:
770 770 bkms[args[0]] = os.getcwd()
771 771 elif len(args)==2:
772 772 bkms[args[0]] = args[1]
773 773 self.shell.db['bookmarks'] = bkms
774 774
775 775 @line_magic
776 776 def pycat(self, parameter_s=''):
777 777 """Show a syntax-highlighted file through a pager.
778 778
779 779 This magic is similar to the cat utility, but it will assume the file
780 780 to be Python source and will show it with syntax highlighting.
781 781
782 782 This magic command can either take a local filename, an url,
783 783 an history range (see %history) or a macro as argument ::
784 784
785 785 %pycat myscript.py
786 786 %pycat 7-27
787 787 %pycat myMacro
788 788 %pycat http://www.example.com/myscript.py
789 789 """
790 790 if not parameter_s:
791 791 raise UsageError('Missing filename, URL, input history range, '
792 792 'or macro.')
793 793
794 794 try :
795 795 cont = self.shell.find_user_code(parameter_s, skip_encoding_cookie=False)
796 796 except (ValueError, IOError):
797 797 print("Error: no such file, variable, URL, history range or macro")
798 798 return
799 799
800 800 page.page(self.shell.pycolorize(source_to_unicode(cont)))
801 801
802 802 @magic_arguments.magic_arguments()
803 803 @magic_arguments.argument(
804 804 '-a', '--append', action='store_true', default=False,
805 805 help='Append contents of the cell to an existing file. '
806 806 'The file will be created if it does not exist.'
807 807 )
808 808 @magic_arguments.argument(
809 809 'filename', type=str,
810 810 help='file to write'
811 811 )
812 812 @cell_magic
813 813 def writefile(self, line, cell):
814 814 """Write the contents of the cell to a file.
815 815
816 816 The file will be overwritten unless the -a (--append) flag is specified.
817 817 """
818 818 args = magic_arguments.parse_argstring(self.writefile, line)
819 filename = os.path.expanduser(args.filename)
820
819 if re.match(r'[\'*\']|["*"]', args.filename):
820 filename = os.path.expanduser(args.filename[1:-1])
821 else:
822 filename = os.path.expanduser(args.filename)
823
821 824 if os.path.exists(filename):
822 825 if args.append:
823 826 print("Appending to %s" % filename)
824 827 else:
825 828 print("Overwriting %s" % filename)
826 829 else:
827 830 print("Writing %s" % filename)
828 831
829 832 mode = 'a' if args.append else 'w'
830 833 with io.open(filename, mode, encoding='utf-8') as f:
831 834 f.write(cell)
@@ -1,1080 +1,1094 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Tests for various magic functions.
3 3
4 4 Needs to be run by nose (to make ipython session available).
5 5 """
6 6
7 7 import io
8 8 import os
9 9 import re
10 10 import sys
11 11 import warnings
12 12 from unittest import TestCase
13 13 from importlib import invalidate_caches
14 14 from io import StringIO
15 15
16 16 import nose.tools as nt
17 17
18 18 import shlex
19 19
20 20 from IPython import get_ipython
21 21 from IPython.core import magic
22 22 from IPython.core.error import UsageError
23 23 from IPython.core.magic import (Magics, magics_class, line_magic,
24 24 cell_magic,
25 25 register_line_magic, register_cell_magic)
26 26 from IPython.core.magics import execution, script, code, logging
27 27 from IPython.testing import decorators as dec
28 28 from IPython.testing import tools as tt
29 29 from IPython.utils.io import capture_output
30 from IPython.utils.tempdir import TemporaryDirectory
30 from IPython.utils.tempdir import (TemporaryDirectory,
31 TemporaryWorkingDirectory)
31 32 from IPython.utils.process import find_cmd
32 33
33 34
34 35
35 36 _ip = get_ipython()
36 37
37 38 @magic.magics_class
38 39 class DummyMagics(magic.Magics): pass
39 40
40 41 def test_extract_code_ranges():
41 42 instr = "1 3 5-6 7-9 10:15 17: :10 10- -13 :"
42 43 expected = [(0, 1),
43 44 (2, 3),
44 45 (4, 6),
45 46 (6, 9),
46 47 (9, 14),
47 48 (16, None),
48 49 (None, 9),
49 50 (9, None),
50 51 (None, 13),
51 52 (None, None)]
52 53 actual = list(code.extract_code_ranges(instr))
53 54 nt.assert_equal(actual, expected)
54 55
55 56 def test_extract_symbols():
56 57 source = """import foo\na = 10\ndef b():\n return 42\n\n\nclass A: pass\n\n\n"""
57 58 symbols_args = ["a", "b", "A", "A,b", "A,a", "z"]
58 59 expected = [([], ['a']),
59 60 (["def b():\n return 42\n"], []),
60 61 (["class A: pass\n"], []),
61 62 (["class A: pass\n", "def b():\n return 42\n"], []),
62 63 (["class A: pass\n"], ['a']),
63 64 ([], ['z'])]
64 65 for symbols, exp in zip(symbols_args, expected):
65 66 nt.assert_equal(code.extract_symbols(source, symbols), exp)
66 67
67 68
68 69 def test_extract_symbols_raises_exception_with_non_python_code():
69 70 source = ("=begin A Ruby program :)=end\n"
70 71 "def hello\n"
71 72 "puts 'Hello world'\n"
72 73 "end")
73 74 with nt.assert_raises(SyntaxError):
74 75 code.extract_symbols(source, "hello")
75 76
76 77
77 78 def test_magic_not_found():
78 79 # magic not found raises UsageError
79 80 with nt.assert_raises(UsageError):
80 81 _ip.magic('doesntexist')
81 82
82 83 # ensure result isn't success when a magic isn't found
83 84 result = _ip.run_cell('%doesntexist')
84 85 assert isinstance(result.error_in_exec, UsageError)
85 86
86 87
87 88 def test_cell_magic_not_found():
88 89 # magic not found raises UsageError
89 90 with nt.assert_raises(UsageError):
90 91 _ip.run_cell_magic('doesntexist', 'line', 'cell')
91 92
92 93 # ensure result isn't success when a magic isn't found
93 94 result = _ip.run_cell('%%doesntexist')
94 95 assert isinstance(result.error_in_exec, UsageError)
95 96
96 97
97 98 def test_magic_error_status():
98 99 def fail(shell):
99 100 1/0
100 101 _ip.register_magic_function(fail)
101 102 result = _ip.run_cell('%fail')
102 103 assert isinstance(result.error_in_exec, ZeroDivisionError)
103 104
104 105
105 106 def test_config():
106 107 """ test that config magic does not raise
107 108 can happen if Configurable init is moved too early into
108 109 Magics.__init__ as then a Config object will be registered as a
109 110 magic.
110 111 """
111 112 ## should not raise.
112 113 _ip.magic('config')
113 114
114 115 def test_config_available_configs():
115 116 """ test that config magic prints available configs in unique and
116 117 sorted order. """
117 118 with capture_output() as captured:
118 119 _ip.magic('config')
119 120
120 121 stdout = captured.stdout
121 122 config_classes = stdout.strip().split('\n')[1:]
122 123 nt.assert_list_equal(config_classes, sorted(set(config_classes)))
123 124
124 125 def test_config_print_class():
125 126 """ test that config with a classname prints the class's options. """
126 127 with capture_output() as captured:
127 128 _ip.magic('config TerminalInteractiveShell')
128 129
129 130 stdout = captured.stdout
130 131 if not re.match("TerminalInteractiveShell.* options", stdout.splitlines()[0]):
131 132 print(stdout)
132 133 raise AssertionError("1st line of stdout not like "
133 134 "'TerminalInteractiveShell.* options'")
134 135
135 136 def test_rehashx():
136 137 # clear up everything
137 138 _ip.alias_manager.clear_aliases()
138 139 del _ip.db['syscmdlist']
139 140
140 141 _ip.magic('rehashx')
141 142 # Practically ALL ipython development systems will have more than 10 aliases
142 143
143 144 nt.assert_true(len(_ip.alias_manager.aliases) > 10)
144 145 for name, cmd in _ip.alias_manager.aliases:
145 146 # we must strip dots from alias names
146 147 nt.assert_not_in('.', name)
147 148
148 149 # rehashx must fill up syscmdlist
149 150 scoms = _ip.db['syscmdlist']
150 151 nt.assert_true(len(scoms) > 10)
151 152
152 153
153 154 def test_magic_parse_options():
154 155 """Test that we don't mangle paths when parsing magic options."""
155 156 ip = get_ipython()
156 157 path = 'c:\\x'
157 158 m = DummyMagics(ip)
158 159 opts = m.parse_options('-f %s' % path,'f:')[0]
159 160 # argv splitting is os-dependent
160 161 if os.name == 'posix':
161 162 expected = 'c:x'
162 163 else:
163 164 expected = path
164 165 nt.assert_equal(opts['f'], expected)
165 166
166 167 def test_magic_parse_long_options():
167 168 """Magic.parse_options can handle --foo=bar long options"""
168 169 ip = get_ipython()
169 170 m = DummyMagics(ip)
170 171 opts, _ = m.parse_options('--foo --bar=bubble', 'a', 'foo', 'bar=')
171 172 nt.assert_in('foo', opts)
172 173 nt.assert_in('bar', opts)
173 174 nt.assert_equal(opts['bar'], "bubble")
174 175
175 176
176 177 @dec.skip_without('sqlite3')
177 178 def doctest_hist_f():
178 179 """Test %hist -f with temporary filename.
179 180
180 181 In [9]: import tempfile
181 182
182 183 In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-')
183 184
184 185 In [11]: %hist -nl -f $tfile 3
185 186
186 187 In [13]: import os; os.unlink(tfile)
187 188 """
188 189
189 190
190 191 @dec.skip_without('sqlite3')
191 192 def doctest_hist_r():
192 193 """Test %hist -r
193 194
194 195 XXX - This test is not recording the output correctly. For some reason, in
195 196 testing mode the raw history isn't getting populated. No idea why.
196 197 Disabling the output checking for now, though at least we do run it.
197 198
198 199 In [1]: 'hist' in _ip.lsmagic()
199 200 Out[1]: True
200 201
201 202 In [2]: x=1
202 203
203 204 In [3]: %hist -rl 2
204 205 x=1 # random
205 206 %hist -r 2
206 207 """
207 208
208 209
209 210 @dec.skip_without('sqlite3')
210 211 def doctest_hist_op():
211 212 """Test %hist -op
212 213
213 214 In [1]: class b(float):
214 215 ...: pass
215 216 ...:
216 217
217 218 In [2]: class s(object):
218 219 ...: def __str__(self):
219 220 ...: return 's'
220 221 ...:
221 222
222 223 In [3]:
223 224
224 225 In [4]: class r(b):
225 226 ...: def __repr__(self):
226 227 ...: return 'r'
227 228 ...:
228 229
229 230 In [5]: class sr(s,r): pass
230 231 ...:
231 232
232 233 In [6]:
233 234
234 235 In [7]: bb=b()
235 236
236 237 In [8]: ss=s()
237 238
238 239 In [9]: rr=r()
239 240
240 241 In [10]: ssrr=sr()
241 242
242 243 In [11]: 4.5
243 244 Out[11]: 4.5
244 245
245 246 In [12]: str(ss)
246 247 Out[12]: 's'
247 248
248 249 In [13]:
249 250
250 251 In [14]: %hist -op
251 252 >>> class b:
252 253 ... pass
253 254 ...
254 255 >>> class s(b):
255 256 ... def __str__(self):
256 257 ... return 's'
257 258 ...
258 259 >>>
259 260 >>> class r(b):
260 261 ... def __repr__(self):
261 262 ... return 'r'
262 263 ...
263 264 >>> class sr(s,r): pass
264 265 >>>
265 266 >>> bb=b()
266 267 >>> ss=s()
267 268 >>> rr=r()
268 269 >>> ssrr=sr()
269 270 >>> 4.5
270 271 4.5
271 272 >>> str(ss)
272 273 's'
273 274 >>>
274 275 """
275 276
276 277 def test_hist_pof():
277 278 ip = get_ipython()
278 279 ip.run_cell(u"1+2", store_history=True)
279 280 #raise Exception(ip.history_manager.session_number)
280 281 #raise Exception(list(ip.history_manager._get_range_session()))
281 282 with TemporaryDirectory() as td:
282 283 tf = os.path.join(td, 'hist.py')
283 284 ip.run_line_magic('history', '-pof %s' % tf)
284 285 assert os.path.isfile(tf)
285 286
286 287
287 288 @dec.skip_without('sqlite3')
288 289 def test_macro():
289 290 ip = get_ipython()
290 291 ip.history_manager.reset() # Clear any existing history.
291 292 cmds = ["a=1", "def b():\n return a**2", "print(a,b())"]
292 293 for i, cmd in enumerate(cmds, start=1):
293 294 ip.history_manager.store_inputs(i, cmd)
294 295 ip.magic("macro test 1-3")
295 296 nt.assert_equal(ip.user_ns["test"].value, "\n".join(cmds)+"\n")
296 297
297 298 # List macros
298 299 nt.assert_in("test", ip.magic("macro"))
299 300
300 301
301 302 @dec.skip_without('sqlite3')
302 303 def test_macro_run():
303 304 """Test that we can run a multi-line macro successfully."""
304 305 ip = get_ipython()
305 306 ip.history_manager.reset()
306 307 cmds = ["a=10", "a+=1", "print(a)", "%macro test 2-3"]
307 308 for cmd in cmds:
308 309 ip.run_cell(cmd, store_history=True)
309 310 nt.assert_equal(ip.user_ns["test"].value, "a+=1\nprint(a)\n")
310 311 with tt.AssertPrints("12"):
311 312 ip.run_cell("test")
312 313 with tt.AssertPrints("13"):
313 314 ip.run_cell("test")
314 315
315 316
316 317 def test_magic_magic():
317 318 """Test %magic"""
318 319 ip = get_ipython()
319 320 with capture_output() as captured:
320 321 ip.magic("magic")
321 322
322 323 stdout = captured.stdout
323 324 nt.assert_in('%magic', stdout)
324 325 nt.assert_in('IPython', stdout)
325 326 nt.assert_in('Available', stdout)
326 327
327 328
328 329 @dec.skipif_not_numpy
329 330 def test_numpy_reset_array_undec():
330 331 "Test '%reset array' functionality"
331 332 _ip.ex('import numpy as np')
332 333 _ip.ex('a = np.empty(2)')
333 334 nt.assert_in('a', _ip.user_ns)
334 335 _ip.magic('reset -f array')
335 336 nt.assert_not_in('a', _ip.user_ns)
336 337
337 338 def test_reset_out():
338 339 "Test '%reset out' magic"
339 340 _ip.run_cell("parrot = 'dead'", store_history=True)
340 341 # test '%reset -f out', make an Out prompt
341 342 _ip.run_cell("parrot", store_history=True)
342 343 nt.assert_true('dead' in [_ip.user_ns[x] for x in ('_','__','___')])
343 344 _ip.magic('reset -f out')
344 345 nt.assert_false('dead' in [_ip.user_ns[x] for x in ('_','__','___')])
345 346 nt.assert_equal(len(_ip.user_ns['Out']), 0)
346 347
347 348 def test_reset_in():
348 349 "Test '%reset in' magic"
349 350 # test '%reset -f in'
350 351 _ip.run_cell("parrot", store_history=True)
351 352 nt.assert_true('parrot' in [_ip.user_ns[x] for x in ('_i','_ii','_iii')])
352 353 _ip.magic('%reset -f in')
353 354 nt.assert_false('parrot' in [_ip.user_ns[x] for x in ('_i','_ii','_iii')])
354 355 nt.assert_equal(len(set(_ip.user_ns['In'])), 1)
355 356
356 357 def test_reset_dhist():
357 358 "Test '%reset dhist' magic"
358 359 _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing
359 360 _ip.magic('cd ' + os.path.dirname(nt.__file__))
360 361 _ip.magic('cd -')
361 362 nt.assert_true(len(_ip.user_ns['_dh']) > 0)
362 363 _ip.magic('reset -f dhist')
363 364 nt.assert_equal(len(_ip.user_ns['_dh']), 0)
364 365 _ip.run_cell("_dh = [d for d in tmp]") #restore
365 366
366 367 def test_reset_in_length():
367 368 "Test that '%reset in' preserves In[] length"
368 369 _ip.run_cell("print 'foo'")
369 370 _ip.run_cell("reset -f in")
370 371 nt.assert_equal(len(_ip.user_ns['In']), _ip.displayhook.prompt_count+1)
371 372
372 373 def test_tb_syntaxerror():
373 374 """test %tb after a SyntaxError"""
374 375 ip = get_ipython()
375 376 ip.run_cell("for")
376 377
377 378 # trap and validate stdout
378 379 save_stdout = sys.stdout
379 380 try:
380 381 sys.stdout = StringIO()
381 382 ip.run_cell("%tb")
382 383 out = sys.stdout.getvalue()
383 384 finally:
384 385 sys.stdout = save_stdout
385 386 # trim output, and only check the last line
386 387 last_line = out.rstrip().splitlines()[-1].strip()
387 388 nt.assert_equal(last_line, "SyntaxError: invalid syntax")
388 389
389 390
390 391 def test_time():
391 392 ip = get_ipython()
392 393
393 394 with tt.AssertPrints("Wall time: "):
394 395 ip.run_cell("%time None")
395 396
396 397 ip.run_cell("def f(kmjy):\n"
397 398 " %time print (2*kmjy)")
398 399
399 400 with tt.AssertPrints("Wall time: "):
400 401 with tt.AssertPrints("hihi", suppress=False):
401 402 ip.run_cell("f('hi')")
402 403
403 404
404 405 @dec.skip_win32
405 406 def test_time2():
406 407 ip = get_ipython()
407 408
408 409 with tt.AssertPrints("CPU times: user "):
409 410 ip.run_cell("%time None")
410 411
411 412 def test_time3():
412 413 """Erroneous magic function calls, issue gh-3334"""
413 414 ip = get_ipython()
414 415 ip.user_ns.pop('run', None)
415 416
416 417 with tt.AssertNotPrints("not found", channel='stderr'):
417 418 ip.run_cell("%%time\n"
418 419 "run = 0\n"
419 420 "run += 1")
420 421
421 422 def test_doctest_mode():
422 423 "Toggle doctest_mode twice, it should be a no-op and run without error"
423 424 _ip.magic('doctest_mode')
424 425 _ip.magic('doctest_mode')
425 426
426 427
427 428 def test_parse_options():
428 429 """Tests for basic options parsing in magics."""
429 430 # These are only the most minimal of tests, more should be added later. At
430 431 # the very least we check that basic text/unicode calls work OK.
431 432 m = DummyMagics(_ip)
432 433 nt.assert_equal(m.parse_options('foo', '')[1], 'foo')
433 434 nt.assert_equal(m.parse_options(u'foo', '')[1], u'foo')
434 435
435 436
436 437 def test_dirops():
437 438 """Test various directory handling operations."""
438 439 # curpath = lambda :os.path.splitdrive(os.getcwd())[1].replace('\\','/')
439 440 curpath = os.getcwd
440 441 startdir = os.getcwd()
441 442 ipdir = os.path.realpath(_ip.ipython_dir)
442 443 try:
443 444 _ip.magic('cd "%s"' % ipdir)
444 445 nt.assert_equal(curpath(), ipdir)
445 446 _ip.magic('cd -')
446 447 nt.assert_equal(curpath(), startdir)
447 448 _ip.magic('pushd "%s"' % ipdir)
448 449 nt.assert_equal(curpath(), ipdir)
449 450 _ip.magic('popd')
450 451 nt.assert_equal(curpath(), startdir)
451 452 finally:
452 453 os.chdir(startdir)
453 454
454 455
455 456 def test_xmode():
456 457 # Calling xmode three times should be a no-op
457 458 xmode = _ip.InteractiveTB.mode
458 459 for i in range(3):
459 460 _ip.magic("xmode")
460 461 nt.assert_equal(_ip.InteractiveTB.mode, xmode)
461 462
462 463 def test_reset_hard():
463 464 monitor = []
464 465 class A(object):
465 466 def __del__(self):
466 467 monitor.append(1)
467 468 def __repr__(self):
468 469 return "<A instance>"
469 470
470 471 _ip.user_ns["a"] = A()
471 472 _ip.run_cell("a")
472 473
473 474 nt.assert_equal(monitor, [])
474 475 _ip.magic("reset -f")
475 476 nt.assert_equal(monitor, [1])
476 477
477 478 class TestXdel(tt.TempFileMixin):
478 479 def test_xdel(self):
479 480 """Test that references from %run are cleared by xdel."""
480 481 src = ("class A(object):\n"
481 482 " monitor = []\n"
482 483 " def __del__(self):\n"
483 484 " self.monitor.append(1)\n"
484 485 "a = A()\n")
485 486 self.mktmp(src)
486 487 # %run creates some hidden references...
487 488 _ip.magic("run %s" % self.fname)
488 489 # ... as does the displayhook.
489 490 _ip.run_cell("a")
490 491
491 492 monitor = _ip.user_ns["A"].monitor
492 493 nt.assert_equal(monitor, [])
493 494
494 495 _ip.magic("xdel a")
495 496
496 497 # Check that a's __del__ method has been called.
497 498 nt.assert_equal(monitor, [1])
498 499
499 500 def doctest_who():
500 501 """doctest for %who
501 502
502 503 In [1]: %reset -f
503 504
504 505 In [2]: alpha = 123
505 506
506 507 In [3]: beta = 'beta'
507 508
508 509 In [4]: %who int
509 510 alpha
510 511
511 512 In [5]: %who str
512 513 beta
513 514
514 515 In [6]: %whos
515 516 Variable Type Data/Info
516 517 ----------------------------
517 518 alpha int 123
518 519 beta str beta
519 520
520 521 In [7]: %who_ls
521 522 Out[7]: ['alpha', 'beta']
522 523 """
523 524
524 525 def test_whos():
525 526 """Check that whos is protected against objects where repr() fails."""
526 527 class A(object):
527 528 def __repr__(self):
528 529 raise Exception()
529 530 _ip.user_ns['a'] = A()
530 531 _ip.magic("whos")
531 532
532 533 def doctest_precision():
533 534 """doctest for %precision
534 535
535 536 In [1]: f = get_ipython().display_formatter.formatters['text/plain']
536 537
537 538 In [2]: %precision 5
538 539 Out[2]: '%.5f'
539 540
540 541 In [3]: f.float_format
541 542 Out[3]: '%.5f'
542 543
543 544 In [4]: %precision %e
544 545 Out[4]: '%e'
545 546
546 547 In [5]: f(3.1415927)
547 548 Out[5]: '3.141593e+00'
548 549 """
549 550
550 551 def test_psearch():
551 552 with tt.AssertPrints("dict.fromkeys"):
552 553 _ip.run_cell("dict.fr*?")
553 554
554 555 def test_timeit_shlex():
555 556 """test shlex issues with timeit (#1109)"""
556 557 _ip.ex("def f(*a,**kw): pass")
557 558 _ip.magic('timeit -n1 "this is a bug".count(" ")')
558 559 _ip.magic('timeit -r1 -n1 f(" ", 1)')
559 560 _ip.magic('timeit -r1 -n1 f(" ", 1, " ", 2, " ")')
560 561 _ip.magic('timeit -r1 -n1 ("a " + "b")')
561 562 _ip.magic('timeit -r1 -n1 f("a " + "b")')
562 563 _ip.magic('timeit -r1 -n1 f("a " + "b ")')
563 564
564 565
565 566 def test_timeit_special_syntax():
566 567 "Test %%timeit with IPython special syntax"
567 568 @register_line_magic
568 569 def lmagic(line):
569 570 ip = get_ipython()
570 571 ip.user_ns['lmagic_out'] = line
571 572
572 573 # line mode test
573 574 _ip.run_line_magic('timeit', '-n1 -r1 %lmagic my line')
574 575 nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line')
575 576 # cell mode test
576 577 _ip.run_cell_magic('timeit', '-n1 -r1', '%lmagic my line2')
577 578 nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line2')
578 579
579 580 def test_timeit_return():
580 581 """
581 582 test whether timeit -o return object
582 583 """
583 584
584 585 res = _ip.run_line_magic('timeit','-n10 -r10 -o 1')
585 586 assert(res is not None)
586 587
587 588 def test_timeit_quiet():
588 589 """
589 590 test quiet option of timeit magic
590 591 """
591 592 with tt.AssertNotPrints("loops"):
592 593 _ip.run_cell("%timeit -n1 -r1 -q 1")
593 594
594 595 def test_timeit_return_quiet():
595 596 with tt.AssertNotPrints("loops"):
596 597 res = _ip.run_line_magic('timeit', '-n1 -r1 -q -o 1')
597 598 assert (res is not None)
598 599
599 600 def test_timeit_invalid_return():
600 601 with nt.assert_raises_regex(SyntaxError, "outside function"):
601 602 _ip.run_line_magic('timeit', 'return')
602 603
603 604 @dec.skipif(execution.profile is None)
604 605 def test_prun_special_syntax():
605 606 "Test %%prun with IPython special syntax"
606 607 @register_line_magic
607 608 def lmagic(line):
608 609 ip = get_ipython()
609 610 ip.user_ns['lmagic_out'] = line
610 611
611 612 # line mode test
612 613 _ip.run_line_magic('prun', '-q %lmagic my line')
613 614 nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line')
614 615 # cell mode test
615 616 _ip.run_cell_magic('prun', '-q', '%lmagic my line2')
616 617 nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line2')
617 618
618 619 @dec.skipif(execution.profile is None)
619 620 def test_prun_quotes():
620 621 "Test that prun does not clobber string escapes (GH #1302)"
621 622 _ip.magic(r"prun -q x = '\t'")
622 623 nt.assert_equal(_ip.user_ns['x'], '\t')
623 624
624 625 def test_extension():
625 626 # Debugging information for failures of this test
626 627 print('sys.path:')
627 628 for p in sys.path:
628 629 print(' ', p)
629 630 print('CWD', os.getcwd())
630 631
631 632 nt.assert_raises(ImportError, _ip.magic, "load_ext daft_extension")
632 633 daft_path = os.path.join(os.path.dirname(__file__), "daft_extension")
633 634 sys.path.insert(0, daft_path)
634 635 try:
635 636 _ip.user_ns.pop('arq', None)
636 637 invalidate_caches() # Clear import caches
637 638 _ip.magic("load_ext daft_extension")
638 639 nt.assert_equal(_ip.user_ns['arq'], 185)
639 640 _ip.magic("unload_ext daft_extension")
640 641 assert 'arq' not in _ip.user_ns
641 642 finally:
642 643 sys.path.remove(daft_path)
643 644
644 645
645 646 def test_notebook_export_json():
646 647 _ip = get_ipython()
647 648 _ip.history_manager.reset() # Clear any existing history.
648 649 cmds = [u"a=1", u"def b():\n return a**2", u"print('noΓ«l, Γ©tΓ©', b())"]
649 650 for i, cmd in enumerate(cmds, start=1):
650 651 _ip.history_manager.store_inputs(i, cmd)
651 652 with TemporaryDirectory() as td:
652 653 outfile = os.path.join(td, "nb.ipynb")
653 654 _ip.magic("notebook -e %s" % outfile)
654 655
655 656
656 657 class TestEnv(TestCase):
657 658
658 659 def test_env(self):
659 660 env = _ip.magic("env")
660 661 self.assertTrue(isinstance(env, dict))
661 662
662 663 def test_env_get_set_simple(self):
663 664 env = _ip.magic("env var val1")
664 665 self.assertEqual(env, None)
665 666 self.assertEqual(os.environ['var'], 'val1')
666 667 self.assertEqual(_ip.magic("env var"), 'val1')
667 668 env = _ip.magic("env var=val2")
668 669 self.assertEqual(env, None)
669 670 self.assertEqual(os.environ['var'], 'val2')
670 671
671 672 def test_env_get_set_complex(self):
672 673 env = _ip.magic("env var 'val1 '' 'val2")
673 674 self.assertEqual(env, None)
674 675 self.assertEqual(os.environ['var'], "'val1 '' 'val2")
675 676 self.assertEqual(_ip.magic("env var"), "'val1 '' 'val2")
676 677 env = _ip.magic('env var=val2 val3="val4')
677 678 self.assertEqual(env, None)
678 679 self.assertEqual(os.environ['var'], 'val2 val3="val4')
679 680
680 681 def test_env_set_bad_input(self):
681 682 self.assertRaises(UsageError, lambda: _ip.magic("set_env var"))
682 683
683 684 def test_env_set_whitespace(self):
684 685 self.assertRaises(UsageError, lambda: _ip.magic("env var A=B"))
685 686
686 687
687 688 class CellMagicTestCase(TestCase):
688 689
689 690 def check_ident(self, magic):
690 691 # Manually called, we get the result
691 692 out = _ip.run_cell_magic(magic, 'a', 'b')
692 693 nt.assert_equal(out, ('a','b'))
693 694 # Via run_cell, it goes into the user's namespace via displayhook
694 695 _ip.run_cell('%%' + magic +' c\nd\n')
695 696 nt.assert_equal(_ip.user_ns['_'], ('c','d\n'))
696 697
697 698 def test_cell_magic_func_deco(self):
698 699 "Cell magic using simple decorator"
699 700 @register_cell_magic
700 701 def cellm(line, cell):
701 702 return line, cell
702 703
703 704 self.check_ident('cellm')
704 705
705 706 def test_cell_magic_reg(self):
706 707 "Cell magic manually registered"
707 708 def cellm(line, cell):
708 709 return line, cell
709 710
710 711 _ip.register_magic_function(cellm, 'cell', 'cellm2')
711 712 self.check_ident('cellm2')
712 713
713 714 def test_cell_magic_class(self):
714 715 "Cell magics declared via a class"
715 716 @magics_class
716 717 class MyMagics(Magics):
717 718
718 719 @cell_magic
719 720 def cellm3(self, line, cell):
720 721 return line, cell
721 722
722 723 _ip.register_magics(MyMagics)
723 724 self.check_ident('cellm3')
724 725
725 726 def test_cell_magic_class2(self):
726 727 "Cell magics declared via a class, #2"
727 728 @magics_class
728 729 class MyMagics2(Magics):
729 730
730 731 @cell_magic('cellm4')
731 732 def cellm33(self, line, cell):
732 733 return line, cell
733 734
734 735 _ip.register_magics(MyMagics2)
735 736 self.check_ident('cellm4')
736 737 # Check that nothing is registered as 'cellm33'
737 738 c33 = _ip.find_cell_magic('cellm33')
738 739 nt.assert_equal(c33, None)
739 740
740 741 def test_file():
741 742 """Basic %%writefile"""
742 743 ip = get_ipython()
743 744 with TemporaryDirectory() as td:
744 745 fname = os.path.join(td, 'file1')
745 746 ip.run_cell_magic("writefile", fname, u'\n'.join([
746 747 'line1',
747 748 'line2',
748 749 ]))
749 750 with open(fname) as f:
750 751 s = f.read()
751 752 nt.assert_in('line1\n', s)
752 753 nt.assert_in('line2', s)
753 754
754 755 def test_file_var_expand():
755 756 """%%writefile $filename"""
756 757 ip = get_ipython()
757 758 with TemporaryDirectory() as td:
758 759 fname = os.path.join(td, 'file1')
759 760 ip.user_ns['filename'] = fname
760 761 ip.run_cell_magic("writefile", '$filename', u'\n'.join([
761 762 'line1',
762 763 'line2',
763 764 ]))
764 765 with open(fname) as f:
765 766 s = f.read()
766 767 nt.assert_in('line1\n', s)
767 768 nt.assert_in('line2', s)
768 769
769 770 def test_file_unicode():
770 771 """%%writefile with unicode cell"""
771 772 ip = get_ipython()
772 773 with TemporaryDirectory() as td:
773 774 fname = os.path.join(td, 'file1')
774 775 ip.run_cell_magic("writefile", fname, u'\n'.join([
775 776 u'linΓ©1',
776 777 u'linΓ©2',
777 778 ]))
778 779 with io.open(fname, encoding='utf-8') as f:
779 780 s = f.read()
780 781 nt.assert_in(u'linΓ©1\n', s)
781 782 nt.assert_in(u'linΓ©2', s)
782 783
783 784 def test_file_amend():
784 785 """%%writefile -a amends files"""
785 786 ip = get_ipython()
786 787 with TemporaryDirectory() as td:
787 788 fname = os.path.join(td, 'file2')
788 789 ip.run_cell_magic("writefile", fname, u'\n'.join([
789 790 'line1',
790 791 'line2',
791 792 ]))
792 793 ip.run_cell_magic("writefile", "-a %s" % fname, u'\n'.join([
793 794 'line3',
794 795 'line4',
795 796 ]))
796 797 with open(fname) as f:
797 798 s = f.read()
798 799 nt.assert_in('line1\n', s)
799 800 nt.assert_in('line3\n', s)
800
801
802 def test_file_spaces():
803 """%%file with spaces in filename"""
804 ip = get_ipython()
805 with TemporaryWorkingDirectory() as td:
806 fname = "file name"
807 ip.run_cell_magic("file", '"%s"'%fname, u'\n'.join([
808 'line1',
809 'line2',
810 ]))
811 with open(fname) as f:
812 s = f.read()
813 nt.assert_in('line1\n', s)
814 nt.assert_in('line2', s)
801 815
802 816 def test_script_config():
803 817 ip = get_ipython()
804 818 ip.config.ScriptMagics.script_magics = ['whoda']
805 819 sm = script.ScriptMagics(shell=ip)
806 820 nt.assert_in('whoda', sm.magics['cell'])
807 821
808 822 @dec.skip_win32
809 823 def test_script_out():
810 824 ip = get_ipython()
811 825 ip.run_cell_magic("script", "--out output sh", "echo 'hi'")
812 826 nt.assert_equal(ip.user_ns['output'], 'hi\n')
813 827
814 828 @dec.skip_win32
815 829 def test_script_err():
816 830 ip = get_ipython()
817 831 ip.run_cell_magic("script", "--err error sh", "echo 'hello' >&2")
818 832 nt.assert_equal(ip.user_ns['error'], 'hello\n')
819 833
820 834 @dec.skip_win32
821 835 def test_script_out_err():
822 836 ip = get_ipython()
823 837 ip.run_cell_magic("script", "--out output --err error sh", "echo 'hi'\necho 'hello' >&2")
824 838 nt.assert_equal(ip.user_ns['output'], 'hi\n')
825 839 nt.assert_equal(ip.user_ns['error'], 'hello\n')
826 840
827 841 @dec.skip_win32
828 842 def test_script_bg_out():
829 843 ip = get_ipython()
830 844 ip.run_cell_magic("script", "--bg --out output sh", "echo 'hi'")
831 845
832 846 nt.assert_equal(ip.user_ns['output'].read(), b'hi\n')
833 847 ip.user_ns['output'].close()
834 848
835 849 @dec.skip_win32
836 850 def test_script_bg_err():
837 851 ip = get_ipython()
838 852 ip.run_cell_magic("script", "--bg --err error sh", "echo 'hello' >&2")
839 853 nt.assert_equal(ip.user_ns['error'].read(), b'hello\n')
840 854 ip.user_ns['error'].close()
841 855
842 856 @dec.skip_win32
843 857 def test_script_bg_out_err():
844 858 ip = get_ipython()
845 859 ip.run_cell_magic("script", "--bg --out output --err error sh", "echo 'hi'\necho 'hello' >&2")
846 860 nt.assert_equal(ip.user_ns['output'].read(), b'hi\n')
847 861 nt.assert_equal(ip.user_ns['error'].read(), b'hello\n')
848 862 ip.user_ns['output'].close()
849 863 ip.user_ns['error'].close()
850 864
851 865 def test_script_defaults():
852 866 ip = get_ipython()
853 867 for cmd in ['sh', 'bash', 'perl', 'ruby']:
854 868 try:
855 869 find_cmd(cmd)
856 870 except Exception:
857 871 pass
858 872 else:
859 873 nt.assert_in(cmd, ip.magics_manager.magics['cell'])
860 874
861 875
862 876 @magics_class
863 877 class FooFoo(Magics):
864 878 """class with both %foo and %%foo magics"""
865 879 @line_magic('foo')
866 880 def line_foo(self, line):
867 881 "I am line foo"
868 882 pass
869 883
870 884 @cell_magic("foo")
871 885 def cell_foo(self, line, cell):
872 886 "I am cell foo, not line foo"
873 887 pass
874 888
875 889 def test_line_cell_info():
876 890 """%%foo and %foo magics are distinguishable to inspect"""
877 891 ip = get_ipython()
878 892 ip.magics_manager.register(FooFoo)
879 893 oinfo = ip.object_inspect('foo')
880 894 nt.assert_true(oinfo['found'])
881 895 nt.assert_true(oinfo['ismagic'])
882 896
883 897 oinfo = ip.object_inspect('%%foo')
884 898 nt.assert_true(oinfo['found'])
885 899 nt.assert_true(oinfo['ismagic'])
886 900 nt.assert_equal(oinfo['docstring'], FooFoo.cell_foo.__doc__)
887 901
888 902 oinfo = ip.object_inspect('%foo')
889 903 nt.assert_true(oinfo['found'])
890 904 nt.assert_true(oinfo['ismagic'])
891 905 nt.assert_equal(oinfo['docstring'], FooFoo.line_foo.__doc__)
892 906
893 907 def test_multiple_magics():
894 908 ip = get_ipython()
895 909 foo1 = FooFoo(ip)
896 910 foo2 = FooFoo(ip)
897 911 mm = ip.magics_manager
898 912 mm.register(foo1)
899 913 nt.assert_true(mm.magics['line']['foo'].__self__ is foo1)
900 914 mm.register(foo2)
901 915 nt.assert_true(mm.magics['line']['foo'].__self__ is foo2)
902 916
903 917 def test_alias_magic():
904 918 """Test %alias_magic."""
905 919 ip = get_ipython()
906 920 mm = ip.magics_manager
907 921
908 922 # Basic operation: both cell and line magics are created, if possible.
909 923 ip.run_line_magic('alias_magic', 'timeit_alias timeit')
910 924 nt.assert_in('timeit_alias', mm.magics['line'])
911 925 nt.assert_in('timeit_alias', mm.magics['cell'])
912 926
913 927 # --cell is specified, line magic not created.
914 928 ip.run_line_magic('alias_magic', '--cell timeit_cell_alias timeit')
915 929 nt.assert_not_in('timeit_cell_alias', mm.magics['line'])
916 930 nt.assert_in('timeit_cell_alias', mm.magics['cell'])
917 931
918 932 # Test that line alias is created successfully.
919 933 ip.run_line_magic('alias_magic', '--line env_alias env')
920 934 nt.assert_equal(ip.run_line_magic('env', ''),
921 935 ip.run_line_magic('env_alias', ''))
922 936
923 937 # Test that line alias with parameters passed in is created successfully.
924 938 ip.run_line_magic('alias_magic', '--line history_alias history --params ' + shlex.quote('3'))
925 939 nt.assert_in('history_alias', mm.magics['line'])
926 940
927 941
928 942 def test_save():
929 943 """Test %save."""
930 944 ip = get_ipython()
931 945 ip.history_manager.reset() # Clear any existing history.
932 946 cmds = [u"a=1", u"def b():\n return a**2", u"print(a, b())"]
933 947 for i, cmd in enumerate(cmds, start=1):
934 948 ip.history_manager.store_inputs(i, cmd)
935 949 with TemporaryDirectory() as tmpdir:
936 950 file = os.path.join(tmpdir, "testsave.py")
937 951 ip.run_line_magic("save", "%s 1-10" % file)
938 952 with open(file) as f:
939 953 content = f.read()
940 954 nt.assert_equal(content.count(cmds[0]), 1)
941 955 nt.assert_in('coding: utf-8', content)
942 956 ip.run_line_magic("save", "-a %s 1-10" % file)
943 957 with open(file) as f:
944 958 content = f.read()
945 959 nt.assert_equal(content.count(cmds[0]), 2)
946 960 nt.assert_in('coding: utf-8', content)
947 961
948 962
949 963 def test_store():
950 964 """Test %store."""
951 965 ip = get_ipython()
952 966 ip.run_line_magic('load_ext', 'storemagic')
953 967
954 968 # make sure the storage is empty
955 969 ip.run_line_magic('store', '-z')
956 970 ip.user_ns['var'] = 42
957 971 ip.run_line_magic('store', 'var')
958 972 ip.user_ns['var'] = 39
959 973 ip.run_line_magic('store', '-r')
960 974 nt.assert_equal(ip.user_ns['var'], 42)
961 975
962 976 ip.run_line_magic('store', '-d var')
963 977 ip.user_ns['var'] = 39
964 978 ip.run_line_magic('store' , '-r')
965 979 nt.assert_equal(ip.user_ns['var'], 39)
966 980
967 981
968 982 def _run_edit_test(arg_s, exp_filename=None,
969 983 exp_lineno=-1,
970 984 exp_contents=None,
971 985 exp_is_temp=None):
972 986 ip = get_ipython()
973 987 M = code.CodeMagics(ip)
974 988 last_call = ['','']
975 989 opts,args = M.parse_options(arg_s,'prxn:')
976 990 filename, lineno, is_temp = M._find_edit_target(ip, args, opts, last_call)
977 991
978 992 if exp_filename is not None:
979 993 nt.assert_equal(exp_filename, filename)
980 994 if exp_contents is not None:
981 995 with io.open(filename, 'r', encoding='utf-8') as f:
982 996 contents = f.read()
983 997 nt.assert_equal(exp_contents, contents)
984 998 if exp_lineno != -1:
985 999 nt.assert_equal(exp_lineno, lineno)
986 1000 if exp_is_temp is not None:
987 1001 nt.assert_equal(exp_is_temp, is_temp)
988 1002
989 1003
990 1004 def test_edit_interactive():
991 1005 """%edit on interactively defined objects"""
992 1006 ip = get_ipython()
993 1007 n = ip.execution_count
994 1008 ip.run_cell(u"def foo(): return 1", store_history=True)
995 1009
996 1010 try:
997 1011 _run_edit_test("foo")
998 1012 except code.InteractivelyDefined as e:
999 1013 nt.assert_equal(e.index, n)
1000 1014 else:
1001 1015 raise AssertionError("Should have raised InteractivelyDefined")
1002 1016
1003 1017
1004 1018 def test_edit_cell():
1005 1019 """%edit [cell id]"""
1006 1020 ip = get_ipython()
1007 1021
1008 1022 ip.run_cell(u"def foo(): return 1", store_history=True)
1009 1023
1010 1024 # test
1011 1025 _run_edit_test("1", exp_contents=ip.user_ns['In'][1], exp_is_temp=True)
1012 1026
1013 1027 def test_bookmark():
1014 1028 ip = get_ipython()
1015 1029 ip.run_line_magic('bookmark', 'bmname')
1016 1030 with tt.AssertPrints('bmname'):
1017 1031 ip.run_line_magic('bookmark', '-l')
1018 1032 ip.run_line_magic('bookmark', '-d bmname')
1019 1033
1020 1034 def test_ls_magic():
1021 1035 ip = get_ipython()
1022 1036 json_formatter = ip.display_formatter.formatters['application/json']
1023 1037 json_formatter.enabled = True
1024 1038 lsmagic = ip.magic('lsmagic')
1025 1039 with warnings.catch_warnings(record=True) as w:
1026 1040 j = json_formatter(lsmagic)
1027 1041 nt.assert_equal(sorted(j), ['cell', 'line'])
1028 1042 nt.assert_equal(w, []) # no warnings
1029 1043
1030 1044 def test_strip_initial_indent():
1031 1045 def sii(s):
1032 1046 lines = s.splitlines()
1033 1047 return '\n'.join(code.strip_initial_indent(lines))
1034 1048
1035 1049 nt.assert_equal(sii(" a = 1\nb = 2"), "a = 1\nb = 2")
1036 1050 nt.assert_equal(sii(" a\n b\nc"), "a\n b\nc")
1037 1051 nt.assert_equal(sii("a\n b"), "a\n b")
1038 1052
1039 1053 def test_logging_magic_quiet_from_arg():
1040 1054 _ip.config.LoggingMagics.quiet = False
1041 1055 lm = logging.LoggingMagics(shell=_ip)
1042 1056 with TemporaryDirectory() as td:
1043 1057 try:
1044 1058 with tt.AssertNotPrints(re.compile("Activating.*")):
1045 1059 lm.logstart('-q {}'.format(
1046 1060 os.path.join(td, "quiet_from_arg.log")))
1047 1061 finally:
1048 1062 _ip.logger.logstop()
1049 1063
1050 1064 def test_logging_magic_quiet_from_config():
1051 1065 _ip.config.LoggingMagics.quiet = True
1052 1066 lm = logging.LoggingMagics(shell=_ip)
1053 1067 with TemporaryDirectory() as td:
1054 1068 try:
1055 1069 with tt.AssertNotPrints(re.compile("Activating.*")):
1056 1070 lm.logstart(os.path.join(td, "quiet_from_config.log"))
1057 1071 finally:
1058 1072 _ip.logger.logstop()
1059 1073
1060 1074 def test_logging_magic_not_quiet():
1061 1075 _ip.config.LoggingMagics.quiet = False
1062 1076 lm = logging.LoggingMagics(shell=_ip)
1063 1077 with TemporaryDirectory() as td:
1064 1078 try:
1065 1079 with tt.AssertPrints(re.compile("Activating.*")):
1066 1080 lm.logstart(os.path.join(td, "not_quiet.log"))
1067 1081 finally:
1068 1082 _ip.logger.logstop()
1069 1083
1070 1084 ##
1071 1085 # this is slow, put at the end for local testing.
1072 1086 ##
1073 1087 def test_timeit_arguments():
1074 1088 "Test valid timeit arguments, should not cause SyntaxError (GH #1269)"
1075 1089 if sys.version_info < (3,7):
1076 1090 _ip.magic("timeit ('#')")
1077 1091 else:
1078 1092 # 3.7 optimize no-op statement like above out, and complain there is
1079 1093 # nothing in the for loop.
1080 1094 _ip.magic("timeit a=('#')")
General Comments 0
You need to be logged in to leave comments. Login now