##// END OF EJS Templates
Move optional dependencies to a separate set
Nikita Kniazev -
Show More
@@ -1,50 +1,55 b''
1 1 name: Run tests
2 2
3 3 on:
4 4 push:
5 5 pull_request:
6 6 # Run weekly on Monday at 1:23 UTC
7 7 schedule:
8 8 - cron: '23 1 * * 1'
9 9 workflow_dispatch:
10 10
11 11
12 12 jobs:
13 13 test:
14 14 runs-on: ${{ matrix.os }}
15 15 strategy:
16 16 matrix:
17 17 os: [ubuntu-latest]
18 18 python-version: ["3.7", "3.8", "3.9", "3.10"]
19 deps: [test_extra]
19 20 # Test all on ubuntu, test ends on macos
20 21 include:
21 22 - os: macos-latest
22 23 python-version: "3.7"
24 deps: test_extra
23 25 - os: macos-latest
24 26 python-version: "3.10"
27 deps: test_extra
28 # Tests minimal dependencies set
29 - os: ubuntu-latest
30 python-version: "3.10"
31 deps: test
25 32
26 33 steps:
27 34 - uses: actions/checkout@v2
28 35 - name: Set up Python ${{ matrix.python-version }}
29 36 uses: actions/setup-python@v2
30 37 with:
31 38 python-version: ${{ matrix.python-version }}
32 39 - name: Install latex
33 if: runner.os == 'Linux'
40 if: runner.os == 'Linux' && matrix.deps == 'test_extra'
34 41 run: sudo apt-get -yq -o Acquire::Retries=3 --no-install-suggests --no-install-recommends install texlive dvipng
35 42 - name: Install and update Python dependencies
36 43 run: |
37 44 python -m pip install --upgrade pip setuptools wheel
38 python -m pip install --upgrade -e file://$PWD#egg=ipython[test]
39 python -m pip install --upgrade --upgrade-strategy eager trio curio
40 python -m pip install --upgrade pytest pytest-cov pytest-trio 'matplotlib!=3.2.0' pandas
41 python -m pip install --upgrade check-manifest pytest-cov anyio
45 python -m pip install --upgrade -e .[${{ matrix.deps }}]
46 python -m pip install --upgrade check-manifest pytest-cov
42 47 - name: Check manifest
43 48 run: check-manifest
44 49 - name: pytest
45 50 env:
46 51 COLUMNS: 120
47 52 run: |
48 53 pytest --color=yes -v --cov --cov-report=xml
49 54 - name: Upload coverage to Codecov
50 55 uses: codecov/codecov-action@v2
@@ -1,1364 +1,1365 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Tests for various magic functions."""
3 3
4 4 import asyncio
5 5 import io
6 6 import os
7 7 import re
8 8 import shlex
9 9 import sys
10 10 import warnings
11 11 from importlib import invalidate_caches
12 12 from io import StringIO
13 13 from pathlib import Path
14 14 from textwrap import dedent
15 15 from unittest import TestCase, mock
16 16
17 17 import pytest
18 18
19 19 from IPython import get_ipython
20 20 from IPython.core import magic
21 21 from IPython.core.error import UsageError
22 22 from IPython.core.magic import (
23 23 Magics,
24 24 cell_magic,
25 25 line_magic,
26 26 magics_class,
27 27 register_cell_magic,
28 28 register_line_magic,
29 29 )
30 30 from IPython.core.magics import code, execution, logging, osm, script
31 31 from IPython.testing import decorators as dec
32 32 from IPython.testing import tools as tt
33 33 from IPython.utils.io import capture_output
34 34 from IPython.utils.process import find_cmd
35 35 from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
36 36
37 37 from .test_debugger import PdbTestInput
38 38
39 39
40 40 @magic.magics_class
41 41 class DummyMagics(magic.Magics): pass
42 42
43 43 def test_extract_code_ranges():
44 44 instr = "1 3 5-6 7-9 10:15 17: :10 10- -13 :"
45 45 expected = [
46 46 (0, 1),
47 47 (2, 3),
48 48 (4, 6),
49 49 (6, 9),
50 50 (9, 14),
51 51 (16, None),
52 52 (None, 9),
53 53 (9, None),
54 54 (None, 13),
55 55 (None, None),
56 56 ]
57 57 actual = list(code.extract_code_ranges(instr))
58 58 assert actual == expected
59 59
60 60 def test_extract_symbols():
61 61 source = """import foo\na = 10\ndef b():\n return 42\n\n\nclass A: pass\n\n\n"""
62 62 symbols_args = ["a", "b", "A", "A,b", "A,a", "z"]
63 63 expected = [([], ['a']),
64 64 (["def b():\n return 42\n"], []),
65 65 (["class A: pass\n"], []),
66 66 (["class A: pass\n", "def b():\n return 42\n"], []),
67 67 (["class A: pass\n"], ['a']),
68 68 ([], ['z'])]
69 69 for symbols, exp in zip(symbols_args, expected):
70 70 assert code.extract_symbols(source, symbols) == exp
71 71
72 72
73 73 def test_extract_symbols_raises_exception_with_non_python_code():
74 74 source = ("=begin A Ruby program :)=end\n"
75 75 "def hello\n"
76 76 "puts 'Hello world'\n"
77 77 "end")
78 78 with pytest.raises(SyntaxError):
79 79 code.extract_symbols(source, "hello")
80 80
81 81
82 82 def test_magic_not_found():
83 83 # magic not found raises UsageError
84 84 with pytest.raises(UsageError):
85 85 _ip.magic('doesntexist')
86 86
87 87 # ensure result isn't success when a magic isn't found
88 88 result = _ip.run_cell('%doesntexist')
89 89 assert isinstance(result.error_in_exec, UsageError)
90 90
91 91
92 92 def test_cell_magic_not_found():
93 93 # magic not found raises UsageError
94 94 with pytest.raises(UsageError):
95 95 _ip.run_cell_magic('doesntexist', 'line', 'cell')
96 96
97 97 # ensure result isn't success when a magic isn't found
98 98 result = _ip.run_cell('%%doesntexist')
99 99 assert isinstance(result.error_in_exec, UsageError)
100 100
101 101
102 102 def test_magic_error_status():
103 103 def fail(shell):
104 104 1/0
105 105 _ip.register_magic_function(fail)
106 106 result = _ip.run_cell('%fail')
107 107 assert isinstance(result.error_in_exec, ZeroDivisionError)
108 108
109 109
110 110 def test_config():
111 111 """ test that config magic does not raise
112 112 can happen if Configurable init is moved too early into
113 113 Magics.__init__ as then a Config object will be registered as a
114 114 magic.
115 115 """
116 116 ## should not raise.
117 117 _ip.magic('config')
118 118
119 119 def test_config_available_configs():
120 120 """ test that config magic prints available configs in unique and
121 121 sorted order. """
122 122 with capture_output() as captured:
123 123 _ip.magic('config')
124 124
125 125 stdout = captured.stdout
126 126 config_classes = stdout.strip().split('\n')[1:]
127 127 assert config_classes == sorted(set(config_classes))
128 128
129 129 def test_config_print_class():
130 130 """ test that config with a classname prints the class's options. """
131 131 with capture_output() as captured:
132 132 _ip.magic('config TerminalInteractiveShell')
133 133
134 134 stdout = captured.stdout
135 135 assert re.match(
136 136 "TerminalInteractiveShell.* options", stdout.splitlines()[0]
137 137 ), f"{stdout}\n\n1st line of stdout not like 'TerminalInteractiveShell.* options'"
138 138
139 139
140 140 def test_rehashx():
141 141 # clear up everything
142 142 _ip.alias_manager.clear_aliases()
143 143 del _ip.db['syscmdlist']
144 144
145 145 _ip.magic('rehashx')
146 146 # Practically ALL ipython development systems will have more than 10 aliases
147 147
148 148 assert len(_ip.alias_manager.aliases) > 10
149 149 for name, cmd in _ip.alias_manager.aliases:
150 150 # we must strip dots from alias names
151 151 assert "." not in name
152 152
153 153 # rehashx must fill up syscmdlist
154 154 scoms = _ip.db['syscmdlist']
155 155 assert len(scoms) > 10
156 156
157 157
158 158 def test_magic_parse_options():
159 159 """Test that we don't mangle paths when parsing magic options."""
160 160 ip = get_ipython()
161 161 path = 'c:\\x'
162 162 m = DummyMagics(ip)
163 163 opts = m.parse_options('-f %s' % path,'f:')[0]
164 164 # argv splitting is os-dependent
165 165 if os.name == 'posix':
166 166 expected = 'c:x'
167 167 else:
168 168 expected = path
169 169 assert opts["f"] == expected
170 170
171 171
172 172 def test_magic_parse_long_options():
173 173 """Magic.parse_options can handle --foo=bar long options"""
174 174 ip = get_ipython()
175 175 m = DummyMagics(ip)
176 176 opts, _ = m.parse_options("--foo --bar=bubble", "a", "foo", "bar=")
177 177 assert "foo" in opts
178 178 assert "bar" in opts
179 179 assert opts["bar"] == "bubble"
180 180
181 181
182 182 def doctest_hist_f():
183 183 """Test %hist -f with temporary filename.
184 184
185 185 In [9]: import tempfile
186 186
187 187 In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-')
188 188
189 189 In [11]: %hist -nl -f $tfile 3
190 190
191 191 In [13]: import os; os.unlink(tfile)
192 192 """
193 193
194 194
195 195 def doctest_hist_op():
196 196 """Test %hist -op
197 197
198 198 In [1]: class b(float):
199 199 ...: pass
200 200 ...:
201 201
202 202 In [2]: class s(object):
203 203 ...: def __str__(self):
204 204 ...: return 's'
205 205 ...:
206 206
207 207 In [3]:
208 208
209 209 In [4]: class r(b):
210 210 ...: def __repr__(self):
211 211 ...: return 'r'
212 212 ...:
213 213
214 214 In [5]: class sr(s,r): pass
215 215 ...:
216 216
217 217 In [6]:
218 218
219 219 In [7]: bb=b()
220 220
221 221 In [8]: ss=s()
222 222
223 223 In [9]: rr=r()
224 224
225 225 In [10]: ssrr=sr()
226 226
227 227 In [11]: 4.5
228 228 Out[11]: 4.5
229 229
230 230 In [12]: str(ss)
231 231 Out[12]: 's'
232 232
233 233 In [13]:
234 234
235 235 In [14]: %hist -op
236 236 >>> class b:
237 237 ... pass
238 238 ...
239 239 >>> class s(b):
240 240 ... def __str__(self):
241 241 ... return 's'
242 242 ...
243 243 >>>
244 244 >>> class r(b):
245 245 ... def __repr__(self):
246 246 ... return 'r'
247 247 ...
248 248 >>> class sr(s,r): pass
249 249 >>>
250 250 >>> bb=b()
251 251 >>> ss=s()
252 252 >>> rr=r()
253 253 >>> ssrr=sr()
254 254 >>> 4.5
255 255 4.5
256 256 >>> str(ss)
257 257 's'
258 258 >>>
259 259 """
260 260
261 261 def test_hist_pof():
262 262 ip = get_ipython()
263 263 ip.run_cell("1+2", store_history=True)
264 264 #raise Exception(ip.history_manager.session_number)
265 265 #raise Exception(list(ip.history_manager._get_range_session()))
266 266 with TemporaryDirectory() as td:
267 267 tf = os.path.join(td, 'hist.py')
268 268 ip.run_line_magic('history', '-pof %s' % tf)
269 269 assert os.path.isfile(tf)
270 270
271 271
272 272 def test_macro():
273 273 ip = get_ipython()
274 274 ip.history_manager.reset() # Clear any existing history.
275 275 cmds = ["a=1", "def b():\n return a**2", "print(a,b())"]
276 276 for i, cmd in enumerate(cmds, start=1):
277 277 ip.history_manager.store_inputs(i, cmd)
278 278 ip.magic("macro test 1-3")
279 279 assert ip.user_ns["test"].value == "\n".join(cmds) + "\n"
280 280
281 281 # List macros
282 282 assert "test" in ip.magic("macro")
283 283
284 284
285 285 def test_macro_run():
286 286 """Test that we can run a multi-line macro successfully."""
287 287 ip = get_ipython()
288 288 ip.history_manager.reset()
289 289 cmds = ["a=10", "a+=1", "print(a)", "%macro test 2-3"]
290 290 for cmd in cmds:
291 291 ip.run_cell(cmd, store_history=True)
292 292 assert ip.user_ns["test"].value == "a+=1\nprint(a)\n"
293 293 with tt.AssertPrints("12"):
294 294 ip.run_cell("test")
295 295 with tt.AssertPrints("13"):
296 296 ip.run_cell("test")
297 297
298 298
299 299 def test_magic_magic():
300 300 """Test %magic"""
301 301 ip = get_ipython()
302 302 with capture_output() as captured:
303 303 ip.magic("magic")
304 304
305 305 stdout = captured.stdout
306 306 assert "%magic" in stdout
307 307 assert "IPython" in stdout
308 308 assert "Available" in stdout
309 309
310 310
311 311 @dec.skipif_not_numpy
312 312 def test_numpy_reset_array_undec():
313 313 "Test '%reset array' functionality"
314 314 _ip.ex("import numpy as np")
315 315 _ip.ex("a = np.empty(2)")
316 316 assert "a" in _ip.user_ns
317 317 _ip.magic("reset -f array")
318 318 assert "a" not in _ip.user_ns
319 319
320 320
321 321 def test_reset_out():
322 322 "Test '%reset out' magic"
323 323 _ip.run_cell("parrot = 'dead'", store_history=True)
324 324 # test '%reset -f out', make an Out prompt
325 325 _ip.run_cell("parrot", store_history=True)
326 326 assert "dead" in [_ip.user_ns[x] for x in ("_", "__", "___")]
327 327 _ip.magic("reset -f out")
328 328 assert "dead" not in [_ip.user_ns[x] for x in ("_", "__", "___")]
329 329 assert len(_ip.user_ns["Out"]) == 0
330 330
331 331
332 332 def test_reset_in():
333 333 "Test '%reset in' magic"
334 334 # test '%reset -f in'
335 335 _ip.run_cell("parrot", store_history=True)
336 336 assert "parrot" in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
337 337 _ip.magic("%reset -f in")
338 338 assert "parrot" not in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
339 339 assert len(set(_ip.user_ns["In"])) == 1
340 340
341 341
342 342 def test_reset_dhist():
343 343 "Test '%reset dhist' magic"
344 344 _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing
345 345 _ip.magic("cd " + os.path.dirname(pytest.__file__))
346 346 _ip.magic("cd -")
347 347 assert len(_ip.user_ns["_dh"]) > 0
348 348 _ip.magic("reset -f dhist")
349 349 assert len(_ip.user_ns["_dh"]) == 0
350 350 _ip.run_cell("_dh = [d for d in tmp]") # restore
351 351
352 352
353 353 def test_reset_in_length():
354 354 "Test that '%reset in' preserves In[] length"
355 355 _ip.run_cell("print 'foo'")
356 356 _ip.run_cell("reset -f in")
357 357 assert len(_ip.user_ns["In"]) == _ip.displayhook.prompt_count + 1
358 358
359 359
360 360 class TestResetErrors(TestCase):
361 361
362 362 def test_reset_redefine(self):
363 363
364 364 @magics_class
365 365 class KernelMagics(Magics):
366 366 @line_magic
367 367 def less(self, shell): pass
368 368
369 369 _ip.register_magics(KernelMagics)
370 370
371 371 with self.assertLogs() as cm:
372 372 # hack, we want to just capture logs, but assertLogs fails if not
373 373 # logs get produce.
374 374 # so log one things we ignore.
375 375 import logging as log_mod
376 376 log = log_mod.getLogger()
377 377 log.info('Nothing')
378 378 # end hack.
379 379 _ip.run_cell("reset -f")
380 380
381 381 assert len(cm.output) == 1
382 382 for out in cm.output:
383 383 assert "Invalid alias" not in out
384 384
385 385 def test_tb_syntaxerror():
386 386 """test %tb after a SyntaxError"""
387 387 ip = get_ipython()
388 388 ip.run_cell("for")
389 389
390 390 # trap and validate stdout
391 391 save_stdout = sys.stdout
392 392 try:
393 393 sys.stdout = StringIO()
394 394 ip.run_cell("%tb")
395 395 out = sys.stdout.getvalue()
396 396 finally:
397 397 sys.stdout = save_stdout
398 398 # trim output, and only check the last line
399 399 last_line = out.rstrip().splitlines()[-1].strip()
400 400 assert last_line == "SyntaxError: invalid syntax"
401 401
402 402
403 403 def test_time():
404 404 ip = get_ipython()
405 405
406 406 with tt.AssertPrints("Wall time: "):
407 407 ip.run_cell("%time None")
408 408
409 409 ip.run_cell("def f(kmjy):\n"
410 410 " %time print (2*kmjy)")
411 411
412 412 with tt.AssertPrints("Wall time: "):
413 413 with tt.AssertPrints("hihi", suppress=False):
414 414 ip.run_cell("f('hi')")
415 415
416 416 def test_time_last_not_expression():
417 417 ip.run_cell("%%time\n"
418 418 "var_1 = 1\n"
419 419 "var_2 = 2\n")
420 420 assert ip.user_ns['var_1'] == 1
421 421 del ip.user_ns['var_1']
422 422 assert ip.user_ns['var_2'] == 2
423 423 del ip.user_ns['var_2']
424 424
425 425
426 426 @dec.skip_win32
427 427 def test_time2():
428 428 ip = get_ipython()
429 429
430 430 with tt.AssertPrints("CPU times: user "):
431 431 ip.run_cell("%time None")
432 432
433 433 def test_time3():
434 434 """Erroneous magic function calls, issue gh-3334"""
435 435 ip = get_ipython()
436 436 ip.user_ns.pop('run', None)
437 437
438 438 with tt.AssertNotPrints("not found", channel='stderr'):
439 439 ip.run_cell("%%time\n"
440 440 "run = 0\n"
441 441 "run += 1")
442 442
443 443 def test_multiline_time():
444 444 """Make sure last statement from time return a value."""
445 445 ip = get_ipython()
446 446 ip.user_ns.pop('run', None)
447 447
448 448 ip.run_cell(dedent("""\
449 449 %%time
450 450 a = "ho"
451 451 b = "hey"
452 452 a+b
453 453 """
454 454 )
455 455 )
456 456 assert ip.user_ns_hidden["_"] == "hohey"
457 457
458 458
459 459 def test_time_local_ns():
460 460 """
461 461 Test that local_ns is actually global_ns when running a cell magic
462 462 """
463 463 ip = get_ipython()
464 464 ip.run_cell("%%time\n" "myvar = 1")
465 465 assert ip.user_ns["myvar"] == 1
466 466 del ip.user_ns["myvar"]
467 467
468 468
469 469 def test_doctest_mode():
470 470 "Toggle doctest_mode twice, it should be a no-op and run without error"
471 471 _ip.magic('doctest_mode')
472 472 _ip.magic('doctest_mode')
473 473
474 474
475 475 def test_parse_options():
476 476 """Tests for basic options parsing in magics."""
477 477 # These are only the most minimal of tests, more should be added later. At
478 478 # the very least we check that basic text/unicode calls work OK.
479 479 m = DummyMagics(_ip)
480 480 assert m.parse_options("foo", "")[1] == "foo"
481 481 assert m.parse_options("foo", "")[1] == "foo"
482 482
483 483
484 484 def test_parse_options_preserve_non_option_string():
485 485 """Test to assert preservation of non-option part of magic-block, while parsing magic options."""
486 486 m = DummyMagics(_ip)
487 487 opts, stmt = m.parse_options(
488 488 " -n1 -r 13 _ = 314 + foo", "n:r:", preserve_non_opts=True
489 489 )
490 490 assert opts == {"n": "1", "r": "13"}
491 491 assert stmt == "_ = 314 + foo"
492 492
493 493
494 494 def test_run_magic_preserve_code_block():
495 495 """Test to assert preservation of non-option part of magic-block, while running magic."""
496 496 _ip.user_ns["spaces"] = []
497 497 _ip.magic("timeit -n1 -r1 spaces.append([s.count(' ') for s in ['document']])")
498 498 assert _ip.user_ns["spaces"] == [[0]]
499 499
500 500
501 501 def test_dirops():
502 502 """Test various directory handling operations."""
503 503 # curpath = lambda :os.path.splitdrive(os.getcwd())[1].replace('\\','/')
504 504 curpath = os.getcwd
505 505 startdir = os.getcwd()
506 506 ipdir = os.path.realpath(_ip.ipython_dir)
507 507 try:
508 508 _ip.magic('cd "%s"' % ipdir)
509 509 assert curpath() == ipdir
510 510 _ip.magic('cd -')
511 511 assert curpath() == startdir
512 512 _ip.magic('pushd "%s"' % ipdir)
513 513 assert curpath() == ipdir
514 514 _ip.magic('popd')
515 515 assert curpath() == startdir
516 516 finally:
517 517 os.chdir(startdir)
518 518
519 519
520 520 def test_cd_force_quiet():
521 521 """Test OSMagics.cd_force_quiet option"""
522 522 _ip.config.OSMagics.cd_force_quiet = True
523 523 osmagics = osm.OSMagics(shell=_ip)
524 524
525 525 startdir = os.getcwd()
526 526 ipdir = os.path.realpath(_ip.ipython_dir)
527 527
528 528 try:
529 529 with tt.AssertNotPrints(ipdir):
530 530 osmagics.cd('"%s"' % ipdir)
531 531 with tt.AssertNotPrints(startdir):
532 532 osmagics.cd('-')
533 533 finally:
534 534 os.chdir(startdir)
535 535
536 536
537 537 def test_xmode():
538 538 # Calling xmode three times should be a no-op
539 539 xmode = _ip.InteractiveTB.mode
540 540 for i in range(4):
541 541 _ip.magic("xmode")
542 542 assert _ip.InteractiveTB.mode == xmode
543 543
544 544 def test_reset_hard():
545 545 monitor = []
546 546 class A(object):
547 547 def __del__(self):
548 548 monitor.append(1)
549 549 def __repr__(self):
550 550 return "<A instance>"
551 551
552 552 _ip.user_ns["a"] = A()
553 553 _ip.run_cell("a")
554 554
555 555 assert monitor == []
556 556 _ip.magic("reset -f")
557 557 assert monitor == [1]
558 558
559 559 class TestXdel(tt.TempFileMixin):
560 560 def test_xdel(self):
561 561 """Test that references from %run are cleared by xdel."""
562 562 src = ("class A(object):\n"
563 563 " monitor = []\n"
564 564 " def __del__(self):\n"
565 565 " self.monitor.append(1)\n"
566 566 "a = A()\n")
567 567 self.mktmp(src)
568 568 # %run creates some hidden references...
569 569 _ip.magic("run %s" % self.fname)
570 570 # ... as does the displayhook.
571 571 _ip.run_cell("a")
572 572
573 573 monitor = _ip.user_ns["A"].monitor
574 574 assert monitor == []
575 575
576 576 _ip.magic("xdel a")
577 577
578 578 # Check that a's __del__ method has been called.
579 579 assert monitor == [1]
580 580
581 581 def doctest_who():
582 582 """doctest for %who
583 583
584 584 In [1]: %reset -sf
585 585
586 586 In [2]: alpha = 123
587 587
588 588 In [3]: beta = 'beta'
589 589
590 590 In [4]: %who int
591 591 alpha
592 592
593 593 In [5]: %who str
594 594 beta
595 595
596 596 In [6]: %whos
597 597 Variable Type Data/Info
598 598 ----------------------------
599 599 alpha int 123
600 600 beta str beta
601 601
602 602 In [7]: %who_ls
603 603 Out[7]: ['alpha', 'beta']
604 604 """
605 605
606 606 def test_whos():
607 607 """Check that whos is protected against objects where repr() fails."""
608 608 class A(object):
609 609 def __repr__(self):
610 610 raise Exception()
611 611 _ip.user_ns['a'] = A()
612 612 _ip.magic("whos")
613 613
614 614 def doctest_precision():
615 615 """doctest for %precision
616 616
617 617 In [1]: f = get_ipython().display_formatter.formatters['text/plain']
618 618
619 619 In [2]: %precision 5
620 620 Out[2]: '%.5f'
621 621
622 622 In [3]: f.float_format
623 623 Out[3]: '%.5f'
624 624
625 625 In [4]: %precision %e
626 626 Out[4]: '%e'
627 627
628 628 In [5]: f(3.1415927)
629 629 Out[5]: '3.141593e+00'
630 630 """
631 631
632 632 def test_debug_magic():
633 633 """Test debugging a small code with %debug
634 634
635 635 In [1]: with PdbTestInput(['c']):
636 636 ...: %debug print("a b") #doctest: +ELLIPSIS
637 637 ...:
638 638 ...
639 639 ipdb> c
640 640 a b
641 641 In [2]:
642 642 """
643 643
644 644 def test_psearch():
645 645 with tt.AssertPrints("dict.fromkeys"):
646 646 _ip.run_cell("dict.fr*?")
647 647 with tt.AssertPrints("Ο€.is_integer"):
648 648 _ip.run_cell("Ο€ = 3.14;\nΟ€.is_integ*?")
649 649
650 650 def test_timeit_shlex():
651 651 """test shlex issues with timeit (#1109)"""
652 652 _ip.ex("def f(*a,**kw): pass")
653 653 _ip.magic('timeit -n1 "this is a bug".count(" ")')
654 654 _ip.magic('timeit -r1 -n1 f(" ", 1)')
655 655 _ip.magic('timeit -r1 -n1 f(" ", 1, " ", 2, " ")')
656 656 _ip.magic('timeit -r1 -n1 ("a " + "b")')
657 657 _ip.magic('timeit -r1 -n1 f("a " + "b")')
658 658 _ip.magic('timeit -r1 -n1 f("a " + "b ")')
659 659
660 660
661 661 def test_timeit_special_syntax():
662 662 "Test %%timeit with IPython special syntax"
663 663 @register_line_magic
664 664 def lmagic(line):
665 665 ip = get_ipython()
666 666 ip.user_ns['lmagic_out'] = line
667 667
668 668 # line mode test
669 669 _ip.run_line_magic("timeit", "-n1 -r1 %lmagic my line")
670 670 assert _ip.user_ns["lmagic_out"] == "my line"
671 671 # cell mode test
672 672 _ip.run_cell_magic("timeit", "-n1 -r1", "%lmagic my line2")
673 673 assert _ip.user_ns["lmagic_out"] == "my line2"
674 674
675 675
676 676 def test_timeit_return():
677 677 """
678 678 test whether timeit -o return object
679 679 """
680 680
681 681 res = _ip.run_line_magic('timeit','-n10 -r10 -o 1')
682 682 assert(res is not None)
683 683
684 684 def test_timeit_quiet():
685 685 """
686 686 test quiet option of timeit magic
687 687 """
688 688 with tt.AssertNotPrints("loops"):
689 689 _ip.run_cell("%timeit -n1 -r1 -q 1")
690 690
691 691 def test_timeit_return_quiet():
692 692 with tt.AssertNotPrints("loops"):
693 693 res = _ip.run_line_magic('timeit', '-n1 -r1 -q -o 1')
694 694 assert (res is not None)
695 695
696 696 def test_timeit_invalid_return():
697 697 with pytest.raises(SyntaxError):
698 698 _ip.run_line_magic('timeit', 'return')
699 699
700 700 @dec.skipif(execution.profile is None)
701 701 def test_prun_special_syntax():
702 702 "Test %%prun with IPython special syntax"
703 703 @register_line_magic
704 704 def lmagic(line):
705 705 ip = get_ipython()
706 706 ip.user_ns['lmagic_out'] = line
707 707
708 708 # line mode test
709 709 _ip.run_line_magic("prun", "-q %lmagic my line")
710 710 assert _ip.user_ns["lmagic_out"] == "my line"
711 711 # cell mode test
712 712 _ip.run_cell_magic("prun", "-q", "%lmagic my line2")
713 713 assert _ip.user_ns["lmagic_out"] == "my line2"
714 714
715 715
716 716 @dec.skipif(execution.profile is None)
717 717 def test_prun_quotes():
718 718 "Test that prun does not clobber string escapes (GH #1302)"
719 719 _ip.magic(r"prun -q x = '\t'")
720 720 assert _ip.user_ns["x"] == "\t"
721 721
722 722
723 723 def test_extension():
724 724 # Debugging information for failures of this test
725 725 print('sys.path:')
726 726 for p in sys.path:
727 727 print(' ', p)
728 728 print('CWD', os.getcwd())
729 729
730 730 pytest.raises(ImportError, _ip.magic, "load_ext daft_extension")
731 731 daft_path = os.path.join(os.path.dirname(__file__), "daft_extension")
732 732 sys.path.insert(0, daft_path)
733 733 try:
734 734 _ip.user_ns.pop('arq', None)
735 735 invalidate_caches() # Clear import caches
736 736 _ip.magic("load_ext daft_extension")
737 737 assert _ip.user_ns["arq"] == 185
738 738 _ip.magic("unload_ext daft_extension")
739 739 assert 'arq' not in _ip.user_ns
740 740 finally:
741 741 sys.path.remove(daft_path)
742 742
743 743
744 744 def test_notebook_export_json():
745 pytest.importorskip("nbformat")
745 746 _ip = get_ipython()
746 747 _ip.history_manager.reset() # Clear any existing history.
747 748 cmds = ["a=1", "def b():\n return a**2", "print('noΓ«l, Γ©tΓ©', b())"]
748 749 for i, cmd in enumerate(cmds, start=1):
749 750 _ip.history_manager.store_inputs(i, cmd)
750 751 with TemporaryDirectory() as td:
751 752 outfile = os.path.join(td, "nb.ipynb")
752 753 _ip.magic("notebook -e %s" % outfile)
753 754
754 755
755 756 class TestEnv(TestCase):
756 757
757 758 def test_env(self):
758 759 env = _ip.magic("env")
759 760 self.assertTrue(isinstance(env, dict))
760 761
761 762 def test_env_secret(self):
762 763 env = _ip.magic("env")
763 764 hidden = "<hidden>"
764 765 with mock.patch.dict(
765 766 os.environ,
766 767 {
767 768 "API_KEY": "abc123",
768 769 "SECRET_THING": "ssshhh",
769 770 "JUPYTER_TOKEN": "",
770 771 "VAR": "abc"
771 772 }
772 773 ):
773 774 env = _ip.magic("env")
774 775 assert env["API_KEY"] == hidden
775 776 assert env["SECRET_THING"] == hidden
776 777 assert env["JUPYTER_TOKEN"] == hidden
777 778 assert env["VAR"] == "abc"
778 779
779 780 def test_env_get_set_simple(self):
780 781 env = _ip.magic("env var val1")
781 782 self.assertEqual(env, None)
782 783 self.assertEqual(os.environ['var'], 'val1')
783 784 self.assertEqual(_ip.magic("env var"), 'val1')
784 785 env = _ip.magic("env var=val2")
785 786 self.assertEqual(env, None)
786 787 self.assertEqual(os.environ['var'], 'val2')
787 788
788 789 def test_env_get_set_complex(self):
789 790 env = _ip.magic("env var 'val1 '' 'val2")
790 791 self.assertEqual(env, None)
791 792 self.assertEqual(os.environ['var'], "'val1 '' 'val2")
792 793 self.assertEqual(_ip.magic("env var"), "'val1 '' 'val2")
793 794 env = _ip.magic('env var=val2 val3="val4')
794 795 self.assertEqual(env, None)
795 796 self.assertEqual(os.environ['var'], 'val2 val3="val4')
796 797
797 798 def test_env_set_bad_input(self):
798 799 self.assertRaises(UsageError, lambda: _ip.magic("set_env var"))
799 800
800 801 def test_env_set_whitespace(self):
801 802 self.assertRaises(UsageError, lambda: _ip.magic("env var A=B"))
802 803
803 804
804 805 class CellMagicTestCase(TestCase):
805 806
806 807 def check_ident(self, magic):
807 808 # Manually called, we get the result
808 809 out = _ip.run_cell_magic(magic, "a", "b")
809 810 assert out == ("a", "b")
810 811 # Via run_cell, it goes into the user's namespace via displayhook
811 812 _ip.run_cell("%%" + magic + " c\nd\n")
812 813 assert _ip.user_ns["_"] == ("c", "d\n")
813 814
814 815 def test_cell_magic_func_deco(self):
815 816 "Cell magic using simple decorator"
816 817 @register_cell_magic
817 818 def cellm(line, cell):
818 819 return line, cell
819 820
820 821 self.check_ident('cellm')
821 822
822 823 def test_cell_magic_reg(self):
823 824 "Cell magic manually registered"
824 825 def cellm(line, cell):
825 826 return line, cell
826 827
827 828 _ip.register_magic_function(cellm, 'cell', 'cellm2')
828 829 self.check_ident('cellm2')
829 830
830 831 def test_cell_magic_class(self):
831 832 "Cell magics declared via a class"
832 833 @magics_class
833 834 class MyMagics(Magics):
834 835
835 836 @cell_magic
836 837 def cellm3(self, line, cell):
837 838 return line, cell
838 839
839 840 _ip.register_magics(MyMagics)
840 841 self.check_ident('cellm3')
841 842
842 843 def test_cell_magic_class2(self):
843 844 "Cell magics declared via a class, #2"
844 845 @magics_class
845 846 class MyMagics2(Magics):
846 847
847 848 @cell_magic('cellm4')
848 849 def cellm33(self, line, cell):
849 850 return line, cell
850 851
851 852 _ip.register_magics(MyMagics2)
852 853 self.check_ident('cellm4')
853 854 # Check that nothing is registered as 'cellm33'
854 855 c33 = _ip.find_cell_magic('cellm33')
855 856 assert c33 == None
856 857
857 858 def test_file():
858 859 """Basic %%writefile"""
859 860 ip = get_ipython()
860 861 with TemporaryDirectory() as td:
861 862 fname = os.path.join(td, 'file1')
862 863 ip.run_cell_magic("writefile", fname, u'\n'.join([
863 864 'line1',
864 865 'line2',
865 866 ]))
866 867 s = Path(fname).read_text()
867 868 assert "line1\n" in s
868 869 assert "line2" in s
869 870
870 871
871 872 @dec.skip_win32
872 873 def test_file_single_quote():
873 874 """Basic %%writefile with embedded single quotes"""
874 875 ip = get_ipython()
875 876 with TemporaryDirectory() as td:
876 877 fname = os.path.join(td, '\'file1\'')
877 878 ip.run_cell_magic("writefile", fname, u'\n'.join([
878 879 'line1',
879 880 'line2',
880 881 ]))
881 882 s = Path(fname).read_text()
882 883 assert "line1\n" in s
883 884 assert "line2" in s
884 885
885 886
886 887 @dec.skip_win32
887 888 def test_file_double_quote():
888 889 """Basic %%writefile with embedded double quotes"""
889 890 ip = get_ipython()
890 891 with TemporaryDirectory() as td:
891 892 fname = os.path.join(td, '"file1"')
892 893 ip.run_cell_magic("writefile", fname, u'\n'.join([
893 894 'line1',
894 895 'line2',
895 896 ]))
896 897 s = Path(fname).read_text()
897 898 assert "line1\n" in s
898 899 assert "line2" in s
899 900
900 901
901 902 def test_file_var_expand():
902 903 """%%writefile $filename"""
903 904 ip = get_ipython()
904 905 with TemporaryDirectory() as td:
905 906 fname = os.path.join(td, 'file1')
906 907 ip.user_ns['filename'] = fname
907 908 ip.run_cell_magic("writefile", '$filename', u'\n'.join([
908 909 'line1',
909 910 'line2',
910 911 ]))
911 912 s = Path(fname).read_text()
912 913 assert "line1\n" in s
913 914 assert "line2" in s
914 915
915 916
916 917 def test_file_unicode():
917 918 """%%writefile with unicode cell"""
918 919 ip = get_ipython()
919 920 with TemporaryDirectory() as td:
920 921 fname = os.path.join(td, 'file1')
921 922 ip.run_cell_magic("writefile", fname, u'\n'.join([
922 923 u'linΓ©1',
923 924 u'linΓ©2',
924 925 ]))
925 926 with io.open(fname, encoding='utf-8') as f:
926 927 s = f.read()
927 928 assert "linΓ©1\n" in s
928 929 assert "linΓ©2" in s
929 930
930 931
931 932 def test_file_amend():
932 933 """%%writefile -a amends files"""
933 934 ip = get_ipython()
934 935 with TemporaryDirectory() as td:
935 936 fname = os.path.join(td, 'file2')
936 937 ip.run_cell_magic("writefile", fname, u'\n'.join([
937 938 'line1',
938 939 'line2',
939 940 ]))
940 941 ip.run_cell_magic("writefile", "-a %s" % fname, u'\n'.join([
941 942 'line3',
942 943 'line4',
943 944 ]))
944 945 s = Path(fname).read_text()
945 946 assert "line1\n" in s
946 947 assert "line3\n" in s
947 948
948 949
949 950 def test_file_spaces():
950 951 """%%file with spaces in filename"""
951 952 ip = get_ipython()
952 953 with TemporaryWorkingDirectory() as td:
953 954 fname = "file name"
954 955 ip.run_cell_magic("file", '"%s"'%fname, u'\n'.join([
955 956 'line1',
956 957 'line2',
957 958 ]))
958 959 s = Path(fname).read_text()
959 960 assert "line1\n" in s
960 961 assert "line2" in s
961 962
962 963
963 964 def test_script_config():
964 965 ip = get_ipython()
965 966 ip.config.ScriptMagics.script_magics = ['whoda']
966 967 sm = script.ScriptMagics(shell=ip)
967 968 assert "whoda" in sm.magics["cell"]
968 969
969 970
970 971 @pytest.fixture
971 972 def event_loop():
972 973 yield asyncio.get_event_loop_policy().get_event_loop()
973 974
974 975
975 976 @dec.skip_iptest_but_not_pytest
976 977 @dec.skip_win32
977 978 @pytest.mark.skipif(
978 979 sys.platform == "win32", reason="This test does not run under Windows"
979 980 )
980 981 def test_script_out(event_loop):
981 982 assert event_loop.is_running() is False
982 983
983 984 ip = get_ipython()
984 985 ip.run_cell_magic("script", "--out output sh", "echo 'hi'")
985 986 assert event_loop.is_running() is False
986 987 assert ip.user_ns["output"] == "hi\n"
987 988
988 989
989 990 @dec.skip_iptest_but_not_pytest
990 991 @dec.skip_win32
991 992 @pytest.mark.skipif(
992 993 sys.platform == "win32", reason="This test does not run under Windows"
993 994 )
994 995 def test_script_err(event_loop):
995 996 ip = get_ipython()
996 997 assert event_loop.is_running() is False
997 998 ip.run_cell_magic("script", "--err error sh", "echo 'hello' >&2")
998 999 assert event_loop.is_running() is False
999 1000 assert ip.user_ns["error"] == "hello\n"
1000 1001
1001 1002
1002 1003 @dec.skip_iptest_but_not_pytest
1003 1004 @dec.skip_win32
1004 1005 @pytest.mark.skipif(
1005 1006 sys.platform == "win32", reason="This test does not run under Windows"
1006 1007 )
1007 1008 def test_script_out_err():
1008 1009
1009 1010 ip = get_ipython()
1010 1011 ip.run_cell_magic(
1011 1012 "script", "--out output --err error sh", "echo 'hi'\necho 'hello' >&2"
1012 1013 )
1013 1014 assert ip.user_ns["output"] == "hi\n"
1014 1015 assert ip.user_ns["error"] == "hello\n"
1015 1016
1016 1017
1017 1018 @dec.skip_iptest_but_not_pytest
1018 1019 @dec.skip_win32
1019 1020 @pytest.mark.skipif(
1020 1021 sys.platform == "win32", reason="This test does not run under Windows"
1021 1022 )
1022 1023 async def test_script_bg_out(event_loop):
1023 1024 ip = get_ipython()
1024 1025 ip.run_cell_magic("script", "--bg --out output sh", "echo 'hi'")
1025 1026 assert (await ip.user_ns["output"].read()) == b"hi\n"
1026 1027 ip.user_ns["output"].close()
1027 1028 event_loop.stop()
1028 1029
1029 1030
1030 1031 @dec.skip_iptest_but_not_pytest
1031 1032 @dec.skip_win32
1032 1033 @pytest.mark.skipif(
1033 1034 sys.platform == "win32", reason="This test does not run under Windows"
1034 1035 )
1035 1036 async def test_script_bg_err():
1036 1037 ip = get_ipython()
1037 1038 ip.run_cell_magic("script", "--bg --err error sh", "echo 'hello' >&2")
1038 1039 assert (await ip.user_ns["error"].read()) == b"hello\n"
1039 1040 ip.user_ns["error"].close()
1040 1041
1041 1042
1042 1043 @dec.skip_iptest_but_not_pytest
1043 1044 @dec.skip_win32
1044 1045 @pytest.mark.skipif(
1045 1046 sys.platform == "win32", reason="This test does not run under Windows"
1046 1047 )
1047 1048 async def test_script_bg_out_err():
1048 1049 ip = get_ipython()
1049 1050 ip.run_cell_magic(
1050 1051 "script", "--bg --out output --err error sh", "echo 'hi'\necho 'hello' >&2"
1051 1052 )
1052 1053 assert (await ip.user_ns["output"].read()) == b"hi\n"
1053 1054 assert (await ip.user_ns["error"].read()) == b"hello\n"
1054 1055 ip.user_ns["output"].close()
1055 1056 ip.user_ns["error"].close()
1056 1057
1057 1058
1058 1059 def test_script_defaults():
1059 1060 ip = get_ipython()
1060 1061 for cmd in ['sh', 'bash', 'perl', 'ruby']:
1061 1062 try:
1062 1063 find_cmd(cmd)
1063 1064 except Exception:
1064 1065 pass
1065 1066 else:
1066 1067 assert cmd in ip.magics_manager.magics["cell"]
1067 1068
1068 1069
1069 1070 @magics_class
1070 1071 class FooFoo(Magics):
1071 1072 """class with both %foo and %%foo magics"""
1072 1073 @line_magic('foo')
1073 1074 def line_foo(self, line):
1074 1075 "I am line foo"
1075 1076 pass
1076 1077
1077 1078 @cell_magic("foo")
1078 1079 def cell_foo(self, line, cell):
1079 1080 "I am cell foo, not line foo"
1080 1081 pass
1081 1082
1082 1083 def test_line_cell_info():
1083 1084 """%%foo and %foo magics are distinguishable to inspect"""
1084 1085 ip = get_ipython()
1085 1086 ip.magics_manager.register(FooFoo)
1086 1087 oinfo = ip.object_inspect("foo")
1087 1088 assert oinfo["found"] is True
1088 1089 assert oinfo["ismagic"] is True
1089 1090
1090 1091 oinfo = ip.object_inspect("%%foo")
1091 1092 assert oinfo["found"] is True
1092 1093 assert oinfo["ismagic"] is True
1093 1094 assert oinfo["docstring"] == FooFoo.cell_foo.__doc__
1094 1095
1095 1096 oinfo = ip.object_inspect("%foo")
1096 1097 assert oinfo["found"] is True
1097 1098 assert oinfo["ismagic"] is True
1098 1099 assert oinfo["docstring"] == FooFoo.line_foo.__doc__
1099 1100
1100 1101
1101 1102 def test_multiple_magics():
1102 1103 ip = get_ipython()
1103 1104 foo1 = FooFoo(ip)
1104 1105 foo2 = FooFoo(ip)
1105 1106 mm = ip.magics_manager
1106 1107 mm.register(foo1)
1107 1108 assert mm.magics["line"]["foo"].__self__ is foo1
1108 1109 mm.register(foo2)
1109 1110 assert mm.magics["line"]["foo"].__self__ is foo2
1110 1111
1111 1112
1112 1113 def test_alias_magic():
1113 1114 """Test %alias_magic."""
1114 1115 ip = get_ipython()
1115 1116 mm = ip.magics_manager
1116 1117
1117 1118 # Basic operation: both cell and line magics are created, if possible.
1118 1119 ip.run_line_magic("alias_magic", "timeit_alias timeit")
1119 1120 assert "timeit_alias" in mm.magics["line"]
1120 1121 assert "timeit_alias" in mm.magics["cell"]
1121 1122
1122 1123 # --cell is specified, line magic not created.
1123 1124 ip.run_line_magic("alias_magic", "--cell timeit_cell_alias timeit")
1124 1125 assert "timeit_cell_alias" not in mm.magics["line"]
1125 1126 assert "timeit_cell_alias" in mm.magics["cell"]
1126 1127
1127 1128 # Test that line alias is created successfully.
1128 1129 ip.run_line_magic("alias_magic", "--line env_alias env")
1129 1130 assert ip.run_line_magic("env", "") == ip.run_line_magic("env_alias", "")
1130 1131
1131 1132 # Test that line alias with parameters passed in is created successfully.
1132 1133 ip.run_line_magic(
1133 1134 "alias_magic", "--line history_alias history --params " + shlex.quote("3")
1134 1135 )
1135 1136 assert "history_alias" in mm.magics["line"]
1136 1137
1137 1138
1138 1139 def test_save():
1139 1140 """Test %save."""
1140 1141 ip = get_ipython()
1141 1142 ip.history_manager.reset() # Clear any existing history.
1142 1143 cmds = ["a=1", "def b():\n return a**2", "print(a, b())"]
1143 1144 for i, cmd in enumerate(cmds, start=1):
1144 1145 ip.history_manager.store_inputs(i, cmd)
1145 1146 with TemporaryDirectory() as tmpdir:
1146 1147 file = os.path.join(tmpdir, "testsave.py")
1147 1148 ip.run_line_magic("save", "%s 1-10" % file)
1148 1149 content = Path(file).read_text()
1149 1150 assert content.count(cmds[0]) == 1
1150 1151 assert "coding: utf-8" in content
1151 1152 ip.run_line_magic("save", "-a %s 1-10" % file)
1152 1153 content = Path(file).read_text()
1153 1154 assert content.count(cmds[0]) == 2
1154 1155 assert "coding: utf-8" in content
1155 1156
1156 1157
1157 1158 def test_save_with_no_args():
1158 1159 ip = get_ipython()
1159 1160 ip.history_manager.reset() # Clear any existing history.
1160 1161 cmds = ["a=1", "def b():\n return a**2", "print(a, b())", "%save"]
1161 1162 for i, cmd in enumerate(cmds, start=1):
1162 1163 ip.history_manager.store_inputs(i, cmd)
1163 1164
1164 1165 with TemporaryDirectory() as tmpdir:
1165 1166 path = os.path.join(tmpdir, "testsave.py")
1166 1167 ip.run_line_magic("save", path)
1167 1168 content = Path(path).read_text()
1168 1169 expected_content = dedent(
1169 1170 """\
1170 1171 # coding: utf-8
1171 1172 a=1
1172 1173 def b():
1173 1174 return a**2
1174 1175 print(a, b())
1175 1176 """
1176 1177 )
1177 1178 assert content == expected_content
1178 1179
1179 1180
1180 1181 def test_store():
1181 1182 """Test %store."""
1182 1183 ip = get_ipython()
1183 1184 ip.run_line_magic('load_ext', 'storemagic')
1184 1185
1185 1186 # make sure the storage is empty
1186 1187 ip.run_line_magic("store", "-z")
1187 1188 ip.user_ns["var"] = 42
1188 1189 ip.run_line_magic("store", "var")
1189 1190 ip.user_ns["var"] = 39
1190 1191 ip.run_line_magic("store", "-r")
1191 1192 assert ip.user_ns["var"] == 42
1192 1193
1193 1194 ip.run_line_magic("store", "-d var")
1194 1195 ip.user_ns["var"] = 39
1195 1196 ip.run_line_magic("store", "-r")
1196 1197 assert ip.user_ns["var"] == 39
1197 1198
1198 1199
1199 1200 def _run_edit_test(arg_s, exp_filename=None,
1200 1201 exp_lineno=-1,
1201 1202 exp_contents=None,
1202 1203 exp_is_temp=None):
1203 1204 ip = get_ipython()
1204 1205 M = code.CodeMagics(ip)
1205 1206 last_call = ['','']
1206 1207 opts,args = M.parse_options(arg_s,'prxn:')
1207 1208 filename, lineno, is_temp = M._find_edit_target(ip, args, opts, last_call)
1208 1209
1209 1210 if exp_filename is not None:
1210 1211 assert exp_filename == filename
1211 1212 if exp_contents is not None:
1212 1213 with io.open(filename, 'r', encoding='utf-8') as f:
1213 1214 contents = f.read()
1214 1215 assert exp_contents == contents
1215 1216 if exp_lineno != -1:
1216 1217 assert exp_lineno == lineno
1217 1218 if exp_is_temp is not None:
1218 1219 assert exp_is_temp == is_temp
1219 1220
1220 1221
1221 1222 def test_edit_interactive():
1222 1223 """%edit on interactively defined objects"""
1223 1224 ip = get_ipython()
1224 1225 n = ip.execution_count
1225 1226 ip.run_cell("def foo(): return 1", store_history=True)
1226 1227
1227 1228 with pytest.raises(code.InteractivelyDefined) as e:
1228 1229 _run_edit_test("foo")
1229 1230 assert e.value.index == n
1230 1231
1231 1232
1232 1233 def test_edit_cell():
1233 1234 """%edit [cell id]"""
1234 1235 ip = get_ipython()
1235 1236
1236 1237 ip.run_cell("def foo(): return 1", store_history=True)
1237 1238
1238 1239 # test
1239 1240 _run_edit_test("1", exp_contents=ip.user_ns['In'][1], exp_is_temp=True)
1240 1241
1241 1242 def test_edit_fname():
1242 1243 """%edit file"""
1243 1244 # test
1244 1245 _run_edit_test("test file.py", exp_filename="test file.py")
1245 1246
1246 1247 def test_bookmark():
1247 1248 ip = get_ipython()
1248 1249 ip.run_line_magic('bookmark', 'bmname')
1249 1250 with tt.AssertPrints('bmname'):
1250 1251 ip.run_line_magic('bookmark', '-l')
1251 1252 ip.run_line_magic('bookmark', '-d bmname')
1252 1253
1253 1254 def test_ls_magic():
1254 1255 ip = get_ipython()
1255 1256 json_formatter = ip.display_formatter.formatters['application/json']
1256 1257 json_formatter.enabled = True
1257 1258 lsmagic = ip.magic('lsmagic')
1258 1259 with warnings.catch_warnings(record=True) as w:
1259 1260 j = json_formatter(lsmagic)
1260 1261 assert sorted(j) == ["cell", "line"]
1261 1262 assert w == [] # no warnings
1262 1263
1263 1264
1264 1265 def test_strip_initial_indent():
1265 1266 def sii(s):
1266 1267 lines = s.splitlines()
1267 1268 return '\n'.join(code.strip_initial_indent(lines))
1268 1269
1269 1270 assert sii(" a = 1\nb = 2") == "a = 1\nb = 2"
1270 1271 assert sii(" a\n b\nc") == "a\n b\nc"
1271 1272 assert sii("a\n b") == "a\n b"
1272 1273
1273 1274 def test_logging_magic_quiet_from_arg():
1274 1275 _ip.config.LoggingMagics.quiet = False
1275 1276 lm = logging.LoggingMagics(shell=_ip)
1276 1277 with TemporaryDirectory() as td:
1277 1278 try:
1278 1279 with tt.AssertNotPrints(re.compile("Activating.*")):
1279 1280 lm.logstart('-q {}'.format(
1280 1281 os.path.join(td, "quiet_from_arg.log")))
1281 1282 finally:
1282 1283 _ip.logger.logstop()
1283 1284
1284 1285 def test_logging_magic_quiet_from_config():
1285 1286 _ip.config.LoggingMagics.quiet = True
1286 1287 lm = logging.LoggingMagics(shell=_ip)
1287 1288 with TemporaryDirectory() as td:
1288 1289 try:
1289 1290 with tt.AssertNotPrints(re.compile("Activating.*")):
1290 1291 lm.logstart(os.path.join(td, "quiet_from_config.log"))
1291 1292 finally:
1292 1293 _ip.logger.logstop()
1293 1294
1294 1295
1295 1296 def test_logging_magic_not_quiet():
1296 1297 _ip.config.LoggingMagics.quiet = False
1297 1298 lm = logging.LoggingMagics(shell=_ip)
1298 1299 with TemporaryDirectory() as td:
1299 1300 try:
1300 1301 with tt.AssertPrints(re.compile("Activating.*")):
1301 1302 lm.logstart(os.path.join(td, "not_quiet.log"))
1302 1303 finally:
1303 1304 _ip.logger.logstop()
1304 1305
1305 1306
1306 1307 def test_time_no_var_expand():
1307 1308 _ip.user_ns['a'] = 5
1308 1309 _ip.user_ns['b'] = []
1309 1310 _ip.magic('time b.append("{a}")')
1310 1311 assert _ip.user_ns['b'] == ['{a}']
1311 1312
1312 1313
1313 1314 # this is slow, put at the end for local testing.
1314 1315 def test_timeit_arguments():
1315 1316 "Test valid timeit arguments, should not cause SyntaxError (GH #1269)"
1316 1317 if sys.version_info < (3,7):
1317 1318 _ip.magic("timeit -n1 -r1 ('#')")
1318 1319 else:
1319 1320 # 3.7 optimize no-op statement like above out, and complain there is
1320 1321 # nothing in the for loop.
1321 1322 _ip.magic("timeit -n1 -r1 a=('#')")
1322 1323
1323 1324
1324 1325 TEST_MODULE = """
1325 1326 print('Loaded my_tmp')
1326 1327 if __name__ == "__main__":
1327 1328 print('I just ran a script')
1328 1329 """
1329 1330
1330 1331
1331 1332 def test_run_module_from_import_hook():
1332 1333 "Test that a module can be loaded via an import hook"
1333 1334 with TemporaryDirectory() as tmpdir:
1334 1335 fullpath = os.path.join(tmpdir, 'my_tmp.py')
1335 1336 Path(fullpath).write_text(TEST_MODULE)
1336 1337
1337 1338 import importlib.abc
1338 1339 import importlib.util
1339 1340
1340 1341 class MyTempImporter(importlib.abc.MetaPathFinder, importlib.abc.SourceLoader):
1341 1342 def find_spec(self, fullname, path, target=None):
1342 1343 if fullname == "my_tmp":
1343 1344 return importlib.util.spec_from_loader(fullname, self)
1344 1345
1345 1346 def get_filename(self, fullname):
1346 1347 if fullname != "my_tmp":
1347 1348 raise ImportError(f"unexpected module name '{fullname}'")
1348 1349 return fullpath
1349 1350
1350 1351 def get_data(self, path):
1351 1352 if not Path(path).samefile(fullpath):
1352 1353 raise OSError(f"expected path '{fullpath}', got '{path}'")
1353 1354 return Path(fullpath).read_text()
1354 1355
1355 1356 sys.meta_path.insert(0, MyTempImporter())
1356 1357
1357 1358 with capture_output() as captured:
1358 1359 _ip.magic("run -m my_tmp")
1359 1360 _ip.run_cell("import my_tmp")
1360 1361
1361 1362 output = "Loaded my_tmp\nI just ran a script\nLoaded my_tmp\n"
1362 1363 assert output == captured.stdout
1363 1364
1364 1365 sys.meta_path.pop(0)
@@ -1,599 +1,601 b''
1 1 # encoding: utf-8
2 2 """Tests for code execution (%run and related), which is particularly tricky.
3 3
4 4 Because of how %run manages namespaces, and the fact that we are trying here to
5 5 verify subtle object deletion and reference counting issues, the %run tests
6 6 will be kept in this separate file. This makes it easier to aggregate in one
7 7 place the tricks needed to handle it; most other magics are much easier to test
8 8 and we do so in a common test_magic file.
9 9
10 10 Note that any test using `run -i` should make sure to do a `reset` afterwards,
11 11 as otherwise it may influence later tests.
12 12 """
13 13
14 14 # Copyright (c) IPython Development Team.
15 15 # Distributed under the terms of the Modified BSD License.
16 16
17 17
18 18
19 19 import functools
20 20 import os
21 21 from os.path import join as pjoin
22 22 import random
23 23 import string
24 24 import sys
25 25 import textwrap
26 26 import unittest
27 27 from unittest.mock import patch
28 28
29 29 import pytest
30 30
31 31 from IPython.testing import decorators as dec
32 32 from IPython.testing import tools as tt
33 33 from IPython.utils.io import capture_output
34 34 from IPython.utils.tempdir import TemporaryDirectory
35 35 from IPython.core import debugger
36 36
37 37 def doctest_refbug():
38 38 """Very nasty problem with references held by multiple runs of a script.
39 39 See: https://github.com/ipython/ipython/issues/141
40 40
41 41 In [1]: _ip.clear_main_mod_cache()
42 42 # random
43 43
44 44 In [2]: %run refbug
45 45
46 46 In [3]: call_f()
47 47 lowercased: hello
48 48
49 49 In [4]: %run refbug
50 50
51 51 In [5]: call_f()
52 52 lowercased: hello
53 53 lowercased: hello
54 54 """
55 55
56 56
57 57 def doctest_run_builtins():
58 58 r"""Check that %run doesn't damage __builtins__.
59 59
60 60 In [1]: import tempfile
61 61
62 62 In [2]: bid1 = id(__builtins__)
63 63
64 64 In [3]: fname = tempfile.mkstemp('.py')[1]
65 65
66 66 In [3]: f = open(fname,'w')
67 67
68 68 In [4]: dummy= f.write('pass\n')
69 69
70 70 In [5]: f.flush()
71 71
72 72 In [6]: t1 = type(__builtins__)
73 73
74 74 In [7]: %run $fname
75 75
76 76 In [7]: f.close()
77 77
78 78 In [8]: bid2 = id(__builtins__)
79 79
80 80 In [9]: t2 = type(__builtins__)
81 81
82 82 In [10]: t1 == t2
83 83 Out[10]: True
84 84
85 85 In [10]: bid1 == bid2
86 86 Out[10]: True
87 87
88 88 In [12]: try:
89 89 ....: os.unlink(fname)
90 90 ....: except:
91 91 ....: pass
92 92 ....:
93 93 """
94 94
95 95
96 96 def doctest_run_option_parser():
97 97 r"""Test option parser in %run.
98 98
99 99 In [1]: %run print_argv.py
100 100 []
101 101
102 102 In [2]: %run print_argv.py print*.py
103 103 ['print_argv.py']
104 104
105 105 In [3]: %run -G print_argv.py print*.py
106 106 ['print*.py']
107 107
108 108 """
109 109
110 110
111 111 @dec.skip_win32
112 112 def doctest_run_option_parser_for_posix():
113 113 r"""Test option parser in %run (Linux/OSX specific).
114 114
115 115 You need double quote to escape glob in POSIX systems:
116 116
117 117 In [1]: %run print_argv.py print\\*.py
118 118 ['print*.py']
119 119
120 120 You can't use quote to escape glob in POSIX systems:
121 121
122 122 In [2]: %run print_argv.py 'print*.py'
123 123 ['print_argv.py']
124 124
125 125 """
126 126
127 127
128 128 doctest_run_option_parser_for_posix.__skip_doctest__ = sys.platform == "win32"
129 129
130 130
131 131 @dec.skip_if_not_win32
132 132 def doctest_run_option_parser_for_windows():
133 133 r"""Test option parser in %run (Windows specific).
134 134
135 135 In Windows, you can't escape ``*` `by backslash:
136 136
137 137 In [1]: %run print_argv.py print\\*.py
138 138 ['print\\\\*.py']
139 139
140 140 You can use quote to escape glob:
141 141
142 142 In [2]: %run print_argv.py 'print*.py'
143 143 ["'print*.py'"]
144 144
145 145 """
146 146
147 147
148 148 doctest_run_option_parser_for_windows.__skip_doctest__ = sys.platform != "win32"
149 149
150 150
151 151 def doctest_reset_del():
152 152 """Test that resetting doesn't cause errors in __del__ methods.
153 153
154 154 In [2]: class A(object):
155 155 ...: def __del__(self):
156 156 ...: print(str("Hi"))
157 157 ...:
158 158
159 159 In [3]: a = A()
160 160
161 161 In [4]: get_ipython().reset()
162 162 Hi
163 163
164 164 In [5]: 1+1
165 165 Out[5]: 2
166 166 """
167 167
168 168 # For some tests, it will be handy to organize them in a class with a common
169 169 # setup that makes a temp file
170 170
171 171 class TestMagicRunPass(tt.TempFileMixin):
172 172
173 173 def setUp(self):
174 174 content = "a = [1,2,3]\nb = 1"
175 175 self.mktmp(content)
176 176
177 177 def run_tmpfile(self):
178 178 _ip = get_ipython()
179 179 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
180 180 # See below and ticket https://bugs.launchpad.net/bugs/366353
181 181 _ip.magic('run %s' % self.fname)
182 182
183 183 def run_tmpfile_p(self):
184 184 _ip = get_ipython()
185 185 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
186 186 # See below and ticket https://bugs.launchpad.net/bugs/366353
187 187 _ip.magic('run -p %s' % self.fname)
188 188
189 189 def test_builtins_id(self):
190 190 """Check that %run doesn't damage __builtins__ """
191 191 _ip = get_ipython()
192 192 # Test that the id of __builtins__ is not modified by %run
193 193 bid1 = id(_ip.user_ns['__builtins__'])
194 194 self.run_tmpfile()
195 195 bid2 = id(_ip.user_ns['__builtins__'])
196 196 assert bid1 == bid2
197 197
198 198 def test_builtins_type(self):
199 199 """Check that the type of __builtins__ doesn't change with %run.
200 200
201 201 However, the above could pass if __builtins__ was already modified to
202 202 be a dict (it should be a module) by a previous use of %run. So we
203 203 also check explicitly that it really is a module:
204 204 """
205 205 _ip = get_ipython()
206 206 self.run_tmpfile()
207 207 assert type(_ip.user_ns["__builtins__"]) == type(sys)
208 208
209 209 def test_run_profile(self):
210 210 """Test that the option -p, which invokes the profiler, do not
211 211 crash by invoking execfile"""
212 212 self.run_tmpfile_p()
213 213
214 214 def test_run_debug_twice(self):
215 215 # https://github.com/ipython/ipython/issues/10028
216 216 _ip = get_ipython()
217 217 with tt.fake_input(['c']):
218 218 _ip.magic('run -d %s' % self.fname)
219 219 with tt.fake_input(['c']):
220 220 _ip.magic('run -d %s' % self.fname)
221 221
222 222 def test_run_debug_twice_with_breakpoint(self):
223 223 """Make a valid python temp file."""
224 224 _ip = get_ipython()
225 225 with tt.fake_input(['b 2', 'c', 'c']):
226 226 _ip.magic('run -d %s' % self.fname)
227 227
228 228 with tt.fake_input(['c']):
229 229 with tt.AssertNotPrints('KeyError'):
230 230 _ip.magic('run -d %s' % self.fname)
231 231
232 232
233 233 class TestMagicRunSimple(tt.TempFileMixin):
234 234
235 235 def test_simpledef(self):
236 236 """Test that simple class definitions work."""
237 237 src = ("class foo: pass\n"
238 238 "def f(): return foo()")
239 239 self.mktmp(src)
240 240 _ip.magic("run %s" % self.fname)
241 241 _ip.run_cell("t = isinstance(f(), foo)")
242 242 assert _ip.user_ns["t"] is True
243 243
244 244 def test_obj_del(self):
245 245 """Test that object's __del__ methods are called on exit."""
246 246 src = ("class A(object):\n"
247 247 " def __del__(self):\n"
248 248 " print('object A deleted')\n"
249 249 "a = A()\n")
250 250 self.mktmp(src)
251 251 err = None
252 252 tt.ipexec_validate(self.fname, 'object A deleted', err)
253 253
254 254 def test_aggressive_namespace_cleanup(self):
255 255 """Test that namespace cleanup is not too aggressive GH-238
256 256
257 257 Returning from another run magic deletes the namespace"""
258 258 # see ticket https://github.com/ipython/ipython/issues/238
259 259
260 260 with tt.TempFileMixin() as empty:
261 261 empty.mktmp("")
262 262 # On Windows, the filename will have \users in it, so we need to use the
263 263 # repr so that the \u becomes \\u.
264 264 src = (
265 265 "ip = get_ipython()\n"
266 266 "for i in range(5):\n"
267 267 " try:\n"
268 268 " ip.magic(%r)\n"
269 269 " except NameError as e:\n"
270 270 " print(i)\n"
271 271 " break\n" % ("run " + empty.fname)
272 272 )
273 273 self.mktmp(src)
274 274 _ip.magic("run %s" % self.fname)
275 275 _ip.run_cell("ip == get_ipython()")
276 276 assert _ip.user_ns["i"] == 4
277 277
278 278 def test_run_second(self):
279 279 """Test that running a second file doesn't clobber the first, gh-3547"""
280 280 self.mktmp("avar = 1\n" "def afunc():\n" " return avar\n")
281 281
282 282 with tt.TempFileMixin() as empty:
283 283 empty.mktmp("")
284 284
285 285 _ip.magic("run %s" % self.fname)
286 286 _ip.magic("run %s" % empty.fname)
287 287 assert _ip.user_ns["afunc"]() == 1
288 288
289 289 @dec.skip_win32
290 290 def test_tclass(self):
291 291 mydir = os.path.dirname(__file__)
292 292 tc = os.path.join(mydir, 'tclass')
293 293 src = ("%%run '%s' C-first\n"
294 294 "%%run '%s' C-second\n"
295 295 "%%run '%s' C-third\n") % (tc, tc, tc)
296 296 self.mktmp(src, '.ipy')
297 297 out = """\
298 298 ARGV 1-: ['C-first']
299 299 ARGV 1-: ['C-second']
300 300 tclass.py: deleting object: C-first
301 301 ARGV 1-: ['C-third']
302 302 tclass.py: deleting object: C-second
303 303 tclass.py: deleting object: C-third
304 304 """
305 305 err = None
306 306 tt.ipexec_validate(self.fname, out, err)
307 307
308 308 def test_run_i_after_reset(self):
309 309 """Check that %run -i still works after %reset (gh-693)"""
310 310 src = "yy = zz\n"
311 311 self.mktmp(src)
312 312 _ip.run_cell("zz = 23")
313 313 try:
314 314 _ip.magic("run -i %s" % self.fname)
315 315 assert _ip.user_ns["yy"] == 23
316 316 finally:
317 317 _ip.magic('reset -f')
318 318
319 319 _ip.run_cell("zz = 23")
320 320 try:
321 321 _ip.magic("run -i %s" % self.fname)
322 322 assert _ip.user_ns["yy"] == 23
323 323 finally:
324 324 _ip.magic('reset -f')
325 325
326 326 def test_unicode(self):
327 327 """Check that files in odd encodings are accepted."""
328 328 mydir = os.path.dirname(__file__)
329 329 na = os.path.join(mydir, 'nonascii.py')
330 330 _ip.magic('run "%s"' % na)
331 331 assert _ip.user_ns["u"] == "ΠŽΡ‚β„–Π€"
332 332
333 333 def test_run_py_file_attribute(self):
334 334 """Test handling of `__file__` attribute in `%run <file>.py`."""
335 335 src = "t = __file__\n"
336 336 self.mktmp(src)
337 337 _missing = object()
338 338 file1 = _ip.user_ns.get('__file__', _missing)
339 339 _ip.magic('run %s' % self.fname)
340 340 file2 = _ip.user_ns.get('__file__', _missing)
341 341
342 342 # Check that __file__ was equal to the filename in the script's
343 343 # namespace.
344 344 assert _ip.user_ns["t"] == self.fname
345 345
346 346 # Check that __file__ was not leaked back into user_ns.
347 347 assert file1 == file2
348 348
349 349 def test_run_ipy_file_attribute(self):
350 350 """Test handling of `__file__` attribute in `%run <file.ipy>`."""
351 351 src = "t = __file__\n"
352 352 self.mktmp(src, ext='.ipy')
353 353 _missing = object()
354 354 file1 = _ip.user_ns.get('__file__', _missing)
355 355 _ip.magic('run %s' % self.fname)
356 356 file2 = _ip.user_ns.get('__file__', _missing)
357 357
358 358 # Check that __file__ was equal to the filename in the script's
359 359 # namespace.
360 360 assert _ip.user_ns["t"] == self.fname
361 361
362 362 # Check that __file__ was not leaked back into user_ns.
363 363 assert file1 == file2
364 364
365 365 def test_run_formatting(self):
366 366 """ Test that %run -t -N<N> does not raise a TypeError for N > 1."""
367 367 src = "pass"
368 368 self.mktmp(src)
369 369 _ip.magic('run -t -N 1 %s' % self.fname)
370 370 _ip.magic('run -t -N 10 %s' % self.fname)
371 371
372 372 def test_ignore_sys_exit(self):
373 373 """Test the -e option to ignore sys.exit()"""
374 374 src = "import sys; sys.exit(1)"
375 375 self.mktmp(src)
376 376 with tt.AssertPrints('SystemExit'):
377 377 _ip.magic('run %s' % self.fname)
378 378
379 379 with tt.AssertNotPrints('SystemExit'):
380 380 _ip.magic('run -e %s' % self.fname)
381 381
382 382 def test_run_nb(self):
383 383 """Test %run notebook.ipynb"""
384 pytest.importorskip("nbformat")
384 385 from nbformat import v4, writes
385 386 nb = v4.new_notebook(
386 387 cells=[
387 388 v4.new_markdown_cell("The Ultimate Question of Everything"),
388 389 v4.new_code_cell("answer=42")
389 390 ]
390 391 )
391 392 src = writes(nb, version=4)
392 393 self.mktmp(src, ext='.ipynb')
393 394
394 395 _ip.magic("run %s" % self.fname)
395 396
396 397 assert _ip.user_ns["answer"] == 42
397 398
398 399 def test_run_nb_error(self):
399 400 """Test %run notebook.ipynb error"""
401 pytest.importorskip("nbformat")
400 402 from nbformat import v4, writes
401 403 # %run when a file name isn't provided
402 404 pytest.raises(Exception, _ip.magic, "run")
403 405
404 406 # %run when a file doesn't exist
405 407 pytest.raises(Exception, _ip.magic, "run foobar.ipynb")
406 408
407 409 # %run on a notebook with an error
408 410 nb = v4.new_notebook(
409 411 cells=[
410 412 v4.new_code_cell("0/0")
411 413 ]
412 414 )
413 415 src = writes(nb, version=4)
414 416 self.mktmp(src, ext='.ipynb')
415 417 pytest.raises(Exception, _ip.magic, "run %s" % self.fname)
416 418
417 419 def test_file_options(self):
418 420 src = ('import sys\n'
419 421 'a = " ".join(sys.argv[1:])\n')
420 422 self.mktmp(src)
421 423 test_opts = "-x 3 --verbose"
422 424 _ip.run_line_magic("run", "{0} {1}".format(self.fname, test_opts))
423 425 assert _ip.user_ns["a"] == test_opts
424 426
425 427
426 428 class TestMagicRunWithPackage(unittest.TestCase):
427 429
428 430 def writefile(self, name, content):
429 431 path = os.path.join(self.tempdir.name, name)
430 432 d = os.path.dirname(path)
431 433 if not os.path.isdir(d):
432 434 os.makedirs(d)
433 435 with open(path, 'w') as f:
434 436 f.write(textwrap.dedent(content))
435 437
436 438 def setUp(self):
437 439 self.package = package = 'tmp{0}'.format(''.join([random.choice(string.ascii_letters) for i in range(10)]))
438 440 """Temporary (probably) valid python package name."""
439 441
440 442 self.value = int(random.random() * 10000)
441 443
442 444 self.tempdir = TemporaryDirectory()
443 445 self.__orig_cwd = os.getcwd()
444 446 sys.path.insert(0, self.tempdir.name)
445 447
446 448 self.writefile(os.path.join(package, '__init__.py'), '')
447 449 self.writefile(os.path.join(package, 'sub.py'), """
448 450 x = {0!r}
449 451 """.format(self.value))
450 452 self.writefile(os.path.join(package, 'relative.py'), """
451 453 from .sub import x
452 454 """)
453 455 self.writefile(os.path.join(package, 'absolute.py'), """
454 456 from {0}.sub import x
455 457 """.format(package))
456 458 self.writefile(os.path.join(package, 'args.py'), """
457 459 import sys
458 460 a = " ".join(sys.argv[1:])
459 461 """.format(package))
460 462
461 463 def tearDown(self):
462 464 os.chdir(self.__orig_cwd)
463 465 sys.path[:] = [p for p in sys.path if p != self.tempdir.name]
464 466 self.tempdir.cleanup()
465 467
466 468 def check_run_submodule(self, submodule, opts=''):
467 469 _ip.user_ns.pop('x', None)
468 470 _ip.magic('run {2} -m {0}.{1}'.format(self.package, submodule, opts))
469 471 self.assertEqual(_ip.user_ns['x'], self.value,
470 472 'Variable `x` is not loaded from module `{0}`.'
471 473 .format(submodule))
472 474
473 475 def test_run_submodule_with_absolute_import(self):
474 476 self.check_run_submodule('absolute')
475 477
476 478 def test_run_submodule_with_relative_import(self):
477 479 """Run submodule that has a relative import statement (#2727)."""
478 480 self.check_run_submodule('relative')
479 481
480 482 def test_prun_submodule_with_absolute_import(self):
481 483 self.check_run_submodule('absolute', '-p')
482 484
483 485 def test_prun_submodule_with_relative_import(self):
484 486 self.check_run_submodule('relative', '-p')
485 487
486 488 def with_fake_debugger(func):
487 489 @functools.wraps(func)
488 490 def wrapper(*args, **kwds):
489 491 with patch.object(debugger.Pdb, 'run', staticmethod(eval)):
490 492 return func(*args, **kwds)
491 493 return wrapper
492 494
493 495 @with_fake_debugger
494 496 def test_debug_run_submodule_with_absolute_import(self):
495 497 self.check_run_submodule('absolute', '-d')
496 498
497 499 @with_fake_debugger
498 500 def test_debug_run_submodule_with_relative_import(self):
499 501 self.check_run_submodule('relative', '-d')
500 502
501 503 def test_module_options(self):
502 504 _ip.user_ns.pop("a", None)
503 505 test_opts = "-x abc -m test"
504 506 _ip.run_line_magic("run", "-m {0}.args {1}".format(self.package, test_opts))
505 507 assert _ip.user_ns["a"] == test_opts
506 508
507 509 def test_module_options_with_separator(self):
508 510 _ip.user_ns.pop("a", None)
509 511 test_opts = "-x abc -m test"
510 512 _ip.run_line_magic("run", "-m {0}.args -- {1}".format(self.package, test_opts))
511 513 assert _ip.user_ns["a"] == test_opts
512 514
513 515
514 516 def test_run__name__():
515 517 with TemporaryDirectory() as td:
516 518 path = pjoin(td, 'foo.py')
517 519 with open(path, 'w') as f:
518 520 f.write("q = __name__")
519 521
520 522 _ip.user_ns.pop("q", None)
521 523 _ip.magic("run {}".format(path))
522 524 assert _ip.user_ns.pop("q") == "__main__"
523 525
524 526 _ip.magic("run -n {}".format(path))
525 527 assert _ip.user_ns.pop("q") == "foo"
526 528
527 529 try:
528 530 _ip.magic("run -i -n {}".format(path))
529 531 assert _ip.user_ns.pop("q") == "foo"
530 532 finally:
531 533 _ip.magic('reset -f')
532 534
533 535
534 536 def test_run_tb():
535 537 """Test traceback offset in %run"""
536 538 with TemporaryDirectory() as td:
537 539 path = pjoin(td, 'foo.py')
538 540 with open(path, 'w') as f:
539 541 f.write('\n'.join([
540 542 "def foo():",
541 543 " return bar()",
542 544 "def bar():",
543 545 " raise RuntimeError('hello!')",
544 546 "foo()",
545 547 ]))
546 548 with capture_output() as io:
547 549 _ip.magic('run {}'.format(path))
548 550 out = io.stdout
549 551 assert "execfile" not in out
550 552 assert "RuntimeError" in out
551 553 assert out.count("---->") == 3
552 554 del ip.user_ns['bar']
553 555 del ip.user_ns['foo']
554 556
555 557
556 558 def test_multiprocessing_run():
557 559 """Set we can run mutiprocesgin without messing up up main namespace
558 560
559 561 Note that import `nose.tools as nt` mdify the value s
560 562 sys.module['__mp_main__'] so we need to temporarily set it to None to test
561 563 the issue.
562 564 """
563 565 with TemporaryDirectory() as td:
564 566 mpm = sys.modules.get('__mp_main__')
565 567 sys.modules['__mp_main__'] = None
566 568 try:
567 569 path = pjoin(td, 'test.py')
568 570 with open(path, 'w') as f:
569 571 f.write("import multiprocessing\nprint('hoy')")
570 572 with capture_output() as io:
571 573 _ip.run_line_magic('run', path)
572 574 _ip.run_cell("i_m_undefined")
573 575 out = io.stdout
574 576 assert "hoy" in out
575 577 assert "AttributeError" not in out
576 578 assert "NameError" in out
577 579 assert out.count("---->") == 1
578 580 except:
579 581 raise
580 582 finally:
581 583 sys.modules['__mp_main__'] = mpm
582 584
583 585
584 586 def test_script_tb():
585 587 """Test traceback offset in `ipython script.py`"""
586 588 with TemporaryDirectory() as td:
587 589 path = pjoin(td, 'foo.py')
588 590 with open(path, 'w') as f:
589 591 f.write('\n'.join([
590 592 "def foo():",
591 593 " return bar()",
592 594 "def bar():",
593 595 " raise RuntimeError('hello!')",
594 596 "foo()",
595 597 ]))
596 598 out, err = tt.ipexec(path)
597 599 assert "execfile" not in out
598 600 assert "RuntimeError" in out
599 601 assert out.count("---->") == 3
@@ -1,674 +1,677 b''
1 1 """Various display related classes.
2 2
3 3 Authors : MinRK, gregcaporaso, dannystaple
4 4 """
5 5 from html import escape as html_escape
6 6 from os.path import exists, isfile, splitext, abspath, join, isdir
7 7 from os import walk, sep, fsdecode
8 8
9 9 from IPython.core.display import DisplayObject, TextDisplayObject
10 10
11 11 from typing import Tuple, Iterable
12 12
13 13 __all__ = ['Audio', 'IFrame', 'YouTubeVideo', 'VimeoVideo', 'ScribdDocument',
14 14 'FileLink', 'FileLinks', 'Code']
15 15
16 16
17 17 class Audio(DisplayObject):
18 18 """Create an audio object.
19 19
20 20 When this object is returned by an input cell or passed to the
21 21 display function, it will result in Audio controls being displayed
22 22 in the frontend (only works in the notebook).
23 23
24 24 Parameters
25 25 ----------
26 26 data : numpy array, list, unicode, str or bytes
27 27 Can be one of
28 28
29 29 * Numpy 1d array containing the desired waveform (mono)
30 30 * Numpy 2d array containing waveforms for each channel.
31 31 Shape=(NCHAN, NSAMPLES). For the standard channel order, see
32 32 http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx
33 33 * List of float or integer representing the waveform (mono)
34 34 * String containing the filename
35 35 * Bytestring containing raw PCM data or
36 36 * URL pointing to a file on the web.
37 37
38 38 If the array option is used, the waveform will be normalized.
39 39
40 40 If a filename or url is used, the format support will be browser
41 41 dependent.
42 42 url : unicode
43 43 A URL to download the data from.
44 44 filename : unicode
45 45 Path to a local file to load the data from.
46 46 embed : boolean
47 47 Should the audio data be embedded using a data URI (True) or should
48 48 the original source be referenced. Set this to True if you want the
49 49 audio to playable later with no internet connection in the notebook.
50 50
51 51 Default is `True`, unless the keyword argument `url` is set, then
52 52 default value is `False`.
53 53 rate : integer
54 54 The sampling rate of the raw data.
55 55 Only required when data parameter is being used as an array
56 56 autoplay : bool
57 57 Set to True if the audio should immediately start playing.
58 58 Default is `False`.
59 59 normalize : bool
60 60 Whether audio should be normalized (rescaled) to the maximum possible
61 61 range. Default is `True`. When set to `False`, `data` must be between
62 62 -1 and 1 (inclusive), otherwise an error is raised.
63 63 Applies only when `data` is a list or array of samples; other types of
64 64 audio are never normalized.
65 65
66 66 Examples
67 67 --------
68 68
69 >>> import pytest
70 >>> np = pytest.importorskip("numpy")
71
69 72 Generate a sound
70 73
71 74 >>> import numpy as np
72 75 >>> framerate = 44100
73 76 >>> t = np.linspace(0,5,framerate*5)
74 77 >>> data = np.sin(2*np.pi*220*t) + np.sin(2*np.pi*224*t)
75 78 >>> Audio(data, rate=framerate)
76 79 <IPython.lib.display.Audio object>
77 80
78 81 Can also do stereo or more channels
79 82
80 83 >>> dataleft = np.sin(2*np.pi*220*t)
81 84 >>> dataright = np.sin(2*np.pi*224*t)
82 85 >>> Audio([dataleft, dataright], rate=framerate)
83 86 <IPython.lib.display.Audio object>
84 87
85 88 From URL:
86 89
87 90 >>> Audio("http://www.nch.com.au/acm/8k16bitpcm.wav") # doctest: +SKIP
88 91 >>> Audio(url="http://www.w3schools.com/html/horse.ogg") # doctest: +SKIP
89 92
90 93 From a File:
91 94
92 95 >>> Audio('/path/to/sound.wav') # doctest: +SKIP
93 96 >>> Audio(filename='/path/to/sound.ogg') # doctest: +SKIP
94 97
95 98 From Bytes:
96 99
97 100 >>> Audio(b'RAW_WAV_DATA..') # doctest: +SKIP
98 101 >>> Audio(data=b'RAW_WAV_DATA..') # doctest: +SKIP
99 102
100 103 See Also
101 104 --------
102 105 ipywidgets.Audio
103 106
104 107 AUdio widget with more more flexibility and options.
105 108
106 109 """
107 110 _read_flags = 'rb'
108 111
109 112 def __init__(self, data=None, filename=None, url=None, embed=None, rate=None, autoplay=False, normalize=True, *,
110 113 element_id=None):
111 114 if filename is None and url is None and data is None:
112 115 raise ValueError("No audio data found. Expecting filename, url, or data.")
113 116 if embed is False and url is None:
114 117 raise ValueError("No url found. Expecting url when embed=False")
115 118
116 119 if url is not None and embed is not True:
117 120 self.embed = False
118 121 else:
119 122 self.embed = True
120 123 self.autoplay = autoplay
121 124 self.element_id = element_id
122 125 super(Audio, self).__init__(data=data, url=url, filename=filename)
123 126
124 127 if self.data is not None and not isinstance(self.data, bytes):
125 128 if rate is None:
126 129 raise ValueError("rate must be specified when data is a numpy array or list of audio samples.")
127 130 self.data = Audio._make_wav(data, rate, normalize)
128 131
129 132 def reload(self):
130 133 """Reload the raw data from file or URL."""
131 134 import mimetypes
132 135 if self.embed:
133 136 super(Audio, self).reload()
134 137
135 138 if self.filename is not None:
136 139 self.mimetype = mimetypes.guess_type(self.filename)[0]
137 140 elif self.url is not None:
138 141 self.mimetype = mimetypes.guess_type(self.url)[0]
139 142 else:
140 143 self.mimetype = "audio/wav"
141 144
142 145 @staticmethod
143 146 def _make_wav(data, rate, normalize):
144 147 """ Transform a numpy array to a PCM bytestring """
145 148 from io import BytesIO
146 149 import wave
147 150
148 151 try:
149 152 scaled, nchan = Audio._validate_and_normalize_with_numpy(data, normalize)
150 153 except ImportError:
151 154 scaled, nchan = Audio._validate_and_normalize_without_numpy(data, normalize)
152 155
153 156 fp = BytesIO()
154 157 waveobj = wave.open(fp,mode='wb')
155 158 waveobj.setnchannels(nchan)
156 159 waveobj.setframerate(rate)
157 160 waveobj.setsampwidth(2)
158 161 waveobj.setcomptype('NONE','NONE')
159 162 waveobj.writeframes(scaled)
160 163 val = fp.getvalue()
161 164 waveobj.close()
162 165
163 166 return val
164 167
165 168 @staticmethod
166 169 def _validate_and_normalize_with_numpy(data, normalize) -> Tuple[bytes, int]:
167 170 import numpy as np
168 171
169 172 data = np.array(data, dtype=float)
170 173 if len(data.shape) == 1:
171 174 nchan = 1
172 175 elif len(data.shape) == 2:
173 176 # In wave files,channels are interleaved. E.g.,
174 177 # "L1R1L2R2..." for stereo. See
175 178 # http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx
176 179 # for channel ordering
177 180 nchan = data.shape[0]
178 181 data = data.T.ravel()
179 182 else:
180 183 raise ValueError('Array audio input must be a 1D or 2D array')
181 184
182 185 max_abs_value = np.max(np.abs(data))
183 186 normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize)
184 187 scaled = data / normalization_factor * 32767
185 188 return scaled.astype("<h").tobytes(), nchan
186 189
187 190 @staticmethod
188 191 def _validate_and_normalize_without_numpy(data, normalize):
189 192 import array
190 193 import sys
191 194
192 195 data = array.array('f', data)
193 196
194 197 try:
195 198 max_abs_value = float(max([abs(x) for x in data]))
196 199 except TypeError as e:
197 200 raise TypeError('Only lists of mono audio are '
198 201 'supported if numpy is not installed') from e
199 202
200 203 normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize)
201 204 scaled = array.array('h', [int(x / normalization_factor * 32767) for x in data])
202 205 if sys.byteorder == 'big':
203 206 scaled.byteswap()
204 207 nchan = 1
205 208 return scaled.tobytes(), nchan
206 209
207 210 @staticmethod
208 211 def _get_normalization_factor(max_abs_value, normalize):
209 212 if not normalize and max_abs_value > 1:
210 213 raise ValueError('Audio data must be between -1 and 1 when normalize=False.')
211 214 return max_abs_value if normalize else 1
212 215
213 216 def _data_and_metadata(self):
214 217 """shortcut for returning metadata with url information, if defined"""
215 218 md = {}
216 219 if self.url:
217 220 md['url'] = self.url
218 221 if md:
219 222 return self.data, md
220 223 else:
221 224 return self.data
222 225
223 226 def _repr_html_(self):
224 227 src = """
225 228 <audio {element_id} controls="controls" {autoplay}>
226 229 <source src="{src}" type="{type}" />
227 230 Your browser does not support the audio element.
228 231 </audio>
229 232 """
230 233 return src.format(src=self.src_attr(), type=self.mimetype, autoplay=self.autoplay_attr(),
231 234 element_id=self.element_id_attr())
232 235
233 236 def src_attr(self):
234 237 import base64
235 238 if self.embed and (self.data is not None):
236 239 data = base64=base64.b64encode(self.data).decode('ascii')
237 240 return """data:{type};base64,{base64}""".format(type=self.mimetype,
238 241 base64=data)
239 242 elif self.url is not None:
240 243 return self.url
241 244 else:
242 245 return ""
243 246
244 247 def autoplay_attr(self):
245 248 if(self.autoplay):
246 249 return 'autoplay="autoplay"'
247 250 else:
248 251 return ''
249 252
250 253 def element_id_attr(self):
251 254 if (self.element_id):
252 255 return 'id="{element_id}"'.format(element_id=self.element_id)
253 256 else:
254 257 return ''
255 258
256 259 class IFrame(object):
257 260 """
258 261 Generic class to embed an iframe in an IPython notebook
259 262 """
260 263
261 264 iframe = """
262 265 <iframe
263 266 width="{width}"
264 267 height="{height}"
265 268 src="{src}{params}"
266 269 frameborder="0"
267 270 allowfullscreen
268 271 {extras}
269 272 ></iframe>
270 273 """
271 274
272 275 def __init__(self, src, width, height, extras: Iterable[str] = None, **kwargs):
273 276 if extras is None:
274 277 extras = []
275 278
276 279 self.src = src
277 280 self.width = width
278 281 self.height = height
279 282 self.extras = extras
280 283 self.params = kwargs
281 284
282 285 def _repr_html_(self):
283 286 """return the embed iframe"""
284 287 if self.params:
285 288 from urllib.parse import urlencode
286 289 params = "?" + urlencode(self.params)
287 290 else:
288 291 params = ""
289 292 return self.iframe.format(
290 293 src=self.src,
291 294 width=self.width,
292 295 height=self.height,
293 296 params=params,
294 297 extras=" ".join(self.extras),
295 298 )
296 299
297 300
298 301 class YouTubeVideo(IFrame):
299 302 """Class for embedding a YouTube Video in an IPython session, based on its video id.
300 303
301 304 e.g. to embed the video from https://www.youtube.com/watch?v=foo , you would
302 305 do::
303 306
304 307 vid = YouTubeVideo("foo")
305 308 display(vid)
306 309
307 310 To start from 30 seconds::
308 311
309 312 vid = YouTubeVideo("abc", start=30)
310 313 display(vid)
311 314
312 315 To calculate seconds from time as hours, minutes, seconds use
313 316 :class:`datetime.timedelta`::
314 317
315 318 start=int(timedelta(hours=1, minutes=46, seconds=40).total_seconds())
316 319
317 320 Other parameters can be provided as documented at
318 321 https://developers.google.com/youtube/player_parameters#Parameters
319 322
320 323 When converting the notebook using nbconvert, a jpeg representation of the video
321 324 will be inserted in the document.
322 325 """
323 326
324 327 def __init__(self, id, width=400, height=300, allow_autoplay=False, **kwargs):
325 328 self.id=id
326 329 src = "https://www.youtube.com/embed/{0}".format(id)
327 330 if allow_autoplay:
328 331 extras = list(kwargs.get("extras", [])) + ['allow="autoplay"']
329 332 kwargs.update(autoplay=1, extras=extras)
330 333 super(YouTubeVideo, self).__init__(src, width, height, **kwargs)
331 334
332 335 def _repr_jpeg_(self):
333 336 # Deferred import
334 337 from urllib.request import urlopen
335 338
336 339 try:
337 340 return urlopen("https://img.youtube.com/vi/{id}/hqdefault.jpg".format(id=self.id)).read()
338 341 except IOError:
339 342 return None
340 343
341 344 class VimeoVideo(IFrame):
342 345 """
343 346 Class for embedding a Vimeo video in an IPython session, based on its video id.
344 347 """
345 348
346 349 def __init__(self, id, width=400, height=300, **kwargs):
347 350 src="https://player.vimeo.com/video/{0}".format(id)
348 351 super(VimeoVideo, self).__init__(src, width, height, **kwargs)
349 352
350 353 class ScribdDocument(IFrame):
351 354 """
352 355 Class for embedding a Scribd document in an IPython session
353 356
354 357 Use the start_page params to specify a starting point in the document
355 358 Use the view_mode params to specify display type one off scroll | slideshow | book
356 359
357 360 e.g to Display Wes' foundational paper about PANDAS in book mode from page 3
358 361
359 362 ScribdDocument(71048089, width=800, height=400, start_page=3, view_mode="book")
360 363 """
361 364
362 365 def __init__(self, id, width=400, height=300, **kwargs):
363 366 src="https://www.scribd.com/embeds/{0}/content".format(id)
364 367 super(ScribdDocument, self).__init__(src, width, height, **kwargs)
365 368
366 369 class FileLink(object):
367 370 """Class for embedding a local file link in an IPython session, based on path
368 371
369 372 e.g. to embed a link that was generated in the IPython notebook as my/data.txt
370 373
371 374 you would do::
372 375
373 376 local_file = FileLink("my/data.txt")
374 377 display(local_file)
375 378
376 379 or in the HTML notebook, just::
377 380
378 381 FileLink("my/data.txt")
379 382 """
380 383
381 384 html_link_str = "<a href='%s' target='_blank'>%s</a>"
382 385
383 386 def __init__(self,
384 387 path,
385 388 url_prefix='',
386 389 result_html_prefix='',
387 390 result_html_suffix='<br>'):
388 391 """
389 392 Parameters
390 393 ----------
391 394 path : str
392 395 path to the file or directory that should be formatted
393 396 url_prefix : str
394 397 prefix to be prepended to all files to form a working link [default:
395 398 '']
396 399 result_html_prefix : str
397 400 text to append to beginning to link [default: '']
398 401 result_html_suffix : str
399 402 text to append at the end of link [default: '<br>']
400 403 """
401 404 if isdir(path):
402 405 raise ValueError("Cannot display a directory using FileLink. "
403 406 "Use FileLinks to display '%s'." % path)
404 407 self.path = fsdecode(path)
405 408 self.url_prefix = url_prefix
406 409 self.result_html_prefix = result_html_prefix
407 410 self.result_html_suffix = result_html_suffix
408 411
409 412 def _format_path(self):
410 413 fp = ''.join([self.url_prefix, html_escape(self.path)])
411 414 return ''.join([self.result_html_prefix,
412 415 self.html_link_str % \
413 416 (fp, html_escape(self.path, quote=False)),
414 417 self.result_html_suffix])
415 418
416 419 def _repr_html_(self):
417 420 """return html link to file
418 421 """
419 422 if not exists(self.path):
420 423 return ("Path (<tt>%s</tt>) doesn't exist. "
421 424 "It may still be in the process of "
422 425 "being generated, or you may have the "
423 426 "incorrect path." % self.path)
424 427
425 428 return self._format_path()
426 429
427 430 def __repr__(self):
428 431 """return absolute path to file
429 432 """
430 433 return abspath(self.path)
431 434
432 435 class FileLinks(FileLink):
433 436 """Class for embedding local file links in an IPython session, based on path
434 437
435 438 e.g. to embed links to files that were generated in the IPython notebook
436 439 under ``my/data``, you would do::
437 440
438 441 local_files = FileLinks("my/data")
439 442 display(local_files)
440 443
441 444 or in the HTML notebook, just::
442 445
443 446 FileLinks("my/data")
444 447 """
445 448 def __init__(self,
446 449 path,
447 450 url_prefix='',
448 451 included_suffixes=None,
449 452 result_html_prefix='',
450 453 result_html_suffix='<br>',
451 454 notebook_display_formatter=None,
452 455 terminal_display_formatter=None,
453 456 recursive=True):
454 457 """
455 458 See :class:`FileLink` for the ``path``, ``url_prefix``,
456 459 ``result_html_prefix`` and ``result_html_suffix`` parameters.
457 460
458 461 included_suffixes : list
459 462 Filename suffixes to include when formatting output [default: include
460 463 all files]
461 464
462 465 notebook_display_formatter : function
463 466 Used to format links for display in the notebook. See discussion of
464 467 formatter functions below.
465 468
466 469 terminal_display_formatter : function
467 470 Used to format links for display in the terminal. See discussion of
468 471 formatter functions below.
469 472
470 473 Formatter functions must be of the form::
471 474
472 475 f(dirname, fnames, included_suffixes)
473 476
474 477 dirname : str
475 478 The name of a directory
476 479 fnames : list
477 480 The files in that directory
478 481 included_suffixes : list
479 482 The file suffixes that should be included in the output (passing None
480 483 meansto include all suffixes in the output in the built-in formatters)
481 484 recursive : boolean
482 485 Whether to recurse into subdirectories. Default is True.
483 486
484 487 The function should return a list of lines that will be printed in the
485 488 notebook (if passing notebook_display_formatter) or the terminal (if
486 489 passing terminal_display_formatter). This function is iterated over for
487 490 each directory in self.path. Default formatters are in place, can be
488 491 passed here to support alternative formatting.
489 492
490 493 """
491 494 if isfile(path):
492 495 raise ValueError("Cannot display a file using FileLinks. "
493 496 "Use FileLink to display '%s'." % path)
494 497 self.included_suffixes = included_suffixes
495 498 # remove trailing slashes for more consistent output formatting
496 499 path = path.rstrip('/')
497 500
498 501 self.path = path
499 502 self.url_prefix = url_prefix
500 503 self.result_html_prefix = result_html_prefix
501 504 self.result_html_suffix = result_html_suffix
502 505
503 506 self.notebook_display_formatter = \
504 507 notebook_display_formatter or self._get_notebook_display_formatter()
505 508 self.terminal_display_formatter = \
506 509 terminal_display_formatter or self._get_terminal_display_formatter()
507 510
508 511 self.recursive = recursive
509 512
510 513 def _get_display_formatter(self,
511 514 dirname_output_format,
512 515 fname_output_format,
513 516 fp_format,
514 517 fp_cleaner=None):
515 518 """ generate built-in formatter function
516 519
517 520 this is used to define both the notebook and terminal built-in
518 521 formatters as they only differ by some wrapper text for each entry
519 522
520 523 dirname_output_format: string to use for formatting directory
521 524 names, dirname will be substituted for a single "%s" which
522 525 must appear in this string
523 526 fname_output_format: string to use for formatting file names,
524 527 if a single "%s" appears in the string, fname will be substituted
525 528 if two "%s" appear in the string, the path to fname will be
526 529 substituted for the first and fname will be substituted for the
527 530 second
528 531 fp_format: string to use for formatting filepaths, must contain
529 532 exactly two "%s" and the dirname will be substituted for the first
530 533 and fname will be substituted for the second
531 534 """
532 535 def f(dirname, fnames, included_suffixes=None):
533 536 result = []
534 537 # begin by figuring out which filenames, if any,
535 538 # are going to be displayed
536 539 display_fnames = []
537 540 for fname in fnames:
538 541 if (isfile(join(dirname,fname)) and
539 542 (included_suffixes is None or
540 543 splitext(fname)[1] in included_suffixes)):
541 544 display_fnames.append(fname)
542 545
543 546 if len(display_fnames) == 0:
544 547 # if there are no filenames to display, don't print anything
545 548 # (not even the directory name)
546 549 pass
547 550 else:
548 551 # otherwise print the formatted directory name followed by
549 552 # the formatted filenames
550 553 dirname_output_line = dirname_output_format % dirname
551 554 result.append(dirname_output_line)
552 555 for fname in display_fnames:
553 556 fp = fp_format % (dirname,fname)
554 557 if fp_cleaner is not None:
555 558 fp = fp_cleaner(fp)
556 559 try:
557 560 # output can include both a filepath and a filename...
558 561 fname_output_line = fname_output_format % (fp, fname)
559 562 except TypeError:
560 563 # ... or just a single filepath
561 564 fname_output_line = fname_output_format % fname
562 565 result.append(fname_output_line)
563 566 return result
564 567 return f
565 568
566 569 def _get_notebook_display_formatter(self,
567 570 spacer="&nbsp;&nbsp;"):
568 571 """ generate function to use for notebook formatting
569 572 """
570 573 dirname_output_format = \
571 574 self.result_html_prefix + "%s/" + self.result_html_suffix
572 575 fname_output_format = \
573 576 self.result_html_prefix + spacer + self.html_link_str + self.result_html_suffix
574 577 fp_format = self.url_prefix + '%s/%s'
575 578 if sep == "\\":
576 579 # Working on a platform where the path separator is "\", so
577 580 # must convert these to "/" for generating a URI
578 581 def fp_cleaner(fp):
579 582 # Replace all occurrences of backslash ("\") with a forward
580 583 # slash ("/") - this is necessary on windows when a path is
581 584 # provided as input, but we must link to a URI
582 585 return fp.replace('\\','/')
583 586 else:
584 587 fp_cleaner = None
585 588
586 589 return self._get_display_formatter(dirname_output_format,
587 590 fname_output_format,
588 591 fp_format,
589 592 fp_cleaner)
590 593
591 594 def _get_terminal_display_formatter(self,
592 595 spacer=" "):
593 596 """ generate function to use for terminal formatting
594 597 """
595 598 dirname_output_format = "%s/"
596 599 fname_output_format = spacer + "%s"
597 600 fp_format = '%s/%s'
598 601
599 602 return self._get_display_formatter(dirname_output_format,
600 603 fname_output_format,
601 604 fp_format)
602 605
603 606 def _format_path(self):
604 607 result_lines = []
605 608 if self.recursive:
606 609 walked_dir = list(walk(self.path))
607 610 else:
608 611 walked_dir = [next(walk(self.path))]
609 612 walked_dir.sort()
610 613 for dirname, subdirs, fnames in walked_dir:
611 614 result_lines += self.notebook_display_formatter(dirname, fnames, self.included_suffixes)
612 615 return '\n'.join(result_lines)
613 616
614 617 def __repr__(self):
615 618 """return newline-separated absolute paths
616 619 """
617 620 result_lines = []
618 621 if self.recursive:
619 622 walked_dir = list(walk(self.path))
620 623 else:
621 624 walked_dir = [next(walk(self.path))]
622 625 walked_dir.sort()
623 626 for dirname, subdirs, fnames in walked_dir:
624 627 result_lines += self.terminal_display_formatter(dirname, fnames, self.included_suffixes)
625 628 return '\n'.join(result_lines)
626 629
627 630
628 631 class Code(TextDisplayObject):
629 632 """Display syntax-highlighted source code.
630 633
631 634 This uses Pygments to highlight the code for HTML and Latex output.
632 635
633 636 Parameters
634 637 ----------
635 638 data : str
636 639 The code as a string
637 640 url : str
638 641 A URL to fetch the code from
639 642 filename : str
640 643 A local filename to load the code from
641 644 language : str
642 645 The short name of a Pygments lexer to use for highlighting.
643 646 If not specified, it will guess the lexer based on the filename
644 647 or the code. Available lexers: http://pygments.org/docs/lexers/
645 648 """
646 649 def __init__(self, data=None, url=None, filename=None, language=None):
647 650 self.language = language
648 651 super().__init__(data=data, url=url, filename=filename)
649 652
650 653 def _get_lexer(self):
651 654 if self.language:
652 655 from pygments.lexers import get_lexer_by_name
653 656 return get_lexer_by_name(self.language)
654 657 elif self.filename:
655 658 from pygments.lexers import get_lexer_for_filename
656 659 return get_lexer_for_filename(self.filename)
657 660 else:
658 661 from pygments.lexers import guess_lexer
659 662 return guess_lexer(self.data)
660 663
661 664 def __repr__(self):
662 665 return self.data
663 666
664 667 def _repr_html_(self):
665 668 from pygments import highlight
666 669 from pygments.formatters import HtmlFormatter
667 670 fmt = HtmlFormatter()
668 671 style = '<style>{}</style>'.format(fmt.get_style_defs('.output_html'))
669 672 return style + highlight(self.data, self._get_lexer(), fmt)
670 673
671 674 def _repr_latex_(self):
672 675 from pygments import highlight
673 676 from pygments.formatters import LatexFormatter
674 677 return highlight(self.data, self._get_lexer(), LatexFormatter())
@@ -1,28 +1,30 b''
1 1 """Test help output of various IPython entry points"""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 import pytest
6 7 import IPython.testing.tools as tt
7 8
8 9
9 10 def test_ipython_help():
10 11 tt.help_all_output_test()
11 12
12 13 def test_profile_help():
13 14 tt.help_all_output_test("profile")
14 15
15 16 def test_profile_list_help():
16 17 tt.help_all_output_test("profile list")
17 18
18 19 def test_profile_create_help():
19 20 tt.help_all_output_test("profile create")
20 21
21 22 def test_locate_help():
22 23 tt.help_all_output_test("locate")
23 24
24 25 def test_locate_profile_help():
25 26 tt.help_all_output_test("locate profile")
26 27
27 28 def test_trust_help():
29 pytest.importorskip("nbformat")
28 30 tt.help_all_output_test("trust")
@@ -1,40 +1,40 b''
1 1 build: false
2 2 matrix:
3 3 fast_finish: true # immediately finish build once one of the jobs fails.
4 4
5 5 environment:
6 6 global:
7 7 APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2022'
8 8 COLUMNS: 120 # Appveyor web viwer window width is 130 chars
9 9
10 10 matrix:
11 11
12 12 - PYTHON: "C:\\Python310-x64"
13 13 PYTHON_VERSION: "3.10.x"
14 14 PYTHON_ARCH: "64"
15 15
16 16 - PYTHON: "C:\\Python37-x64"
17 17 PYTHON_VERSION: "3.7.x"
18 18 PYTHON_ARCH: "64"
19 19
20 20 - PYTHON: "C:\\Python38"
21 21 PYTHON_VERSION: "3.8.x"
22 22 PYTHON_ARCH: "32"
23 23
24 24 - PYTHON: "C:\\Python38-x64"
25 25 PYTHON_VERSION: "3.8.x"
26 26 PYTHON_ARCH: "64"
27 27
28 28 init:
29 29 - "ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH%"
30 30
31 31 install:
32 32 - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
33 33 - python -m pip install --upgrade setuptools pip
34 - pip install pytest pytest-cov pytest-trio matplotlib pandas
35 - pip install -e .[test]
34 - pip install pytest-cov
35 - pip install -e .[test_extra]
36 36 test_script:
37 37 - pytest --color=yes -ra --cov --cov-report=xml
38 38 on_finish:
39 39 - curl -Os https://uploader.codecov.io/latest/windows/codecov.exe
40 40 - codecov -e PYTHON_VERSION,PYTHON_ARCH
@@ -1,271 +1,280 b''
1 1 #!/usr/bin/env python3
2 2 # -*- coding: utf-8 -*-
3 3 """Setup script for IPython.
4 4
5 5 Under Posix environments it works like a typical setup.py script.
6 6 Under Windows, the command sdist is not supported, since IPython
7 7 requires utilities which are not available under Windows."""
8 8
9 9 #-----------------------------------------------------------------------------
10 10 # Copyright (c) 2008-2011, IPython Development Team.
11 11 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
12 12 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
13 13 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
14 14 #
15 15 # Distributed under the terms of the Modified BSD License.
16 16 #
17 17 # The full license is in the file COPYING.rst, distributed with this software.
18 18 #-----------------------------------------------------------------------------
19 19
20 20 import os
21 21 import sys
22 22 from pathlib import Path
23 23
24 24 # **Python version check**
25 25 #
26 26 # This check is also made in IPython/__init__, don't forget to update both when
27 27 # changing Python version requirements.
28 28 if sys.version_info < (3, 7):
29 29 pip_message = 'This may be due to an out of date pip. Make sure you have pip >= 9.0.1.'
30 30 try:
31 31 import pip
32 32 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
33 33 if pip_version < (9, 0, 1) :
34 34 pip_message = 'Your pip version is out of date, please install pip >= 9.0.1. '\
35 35 'pip {} detected.'.format(pip.__version__)
36 36 else:
37 37 # pip is new enough - it must be something else
38 38 pip_message = ''
39 39 except Exception:
40 40 pass
41 41
42 42
43 43 error = """
44 44 IPython 7.17+ supports Python 3.7 and above, following NEP 29.
45 45 When using Python 2.7, please install IPython 5.x LTS Long Term Support version.
46 46 Python 3.3 and 3.4 were supported up to IPython 6.x.
47 47 Python 3.5 was supported with IPython 7.0 to 7.9.
48 48 Python 3.6 was supported with IPython up to 7.16.
49 49
50 50 See IPython `README.rst` file for more information:
51 51
52 52 https://github.com/ipython/ipython/blob/master/README.rst
53 53
54 54 Python {py} detected.
55 55 {pip}
56 56 """.format(py=sys.version_info, pip=pip_message )
57 57
58 58 print(error, file=sys.stderr)
59 59 sys.exit(1)
60 60
61 61 # At least we're on the python version we need, move on.
62 62
63 63 # BEFORE importing distutils, remove MANIFEST. distutils doesn't properly
64 64 # update it when the contents of directories change.
65 65 if Path("MANIFEST").exists():
66 66 Path("MANIFEST").unlink()
67 67
68 68 from distutils.core import setup
69 69
70 70 # Our own imports
71 71 from setupbase import target_update
72 72
73 73 from setupbase import (
74 74 setup_args,
75 75 find_packages,
76 76 find_package_data,
77 77 check_package_data_first,
78 78 find_entry_points,
79 79 build_scripts_entrypt,
80 80 find_data_files,
81 81 git_prebuild,
82 82 install_symlinked,
83 83 install_lib_symlink,
84 84 install_scripts_for_symlink,
85 85 unsymlink,
86 86 )
87 87
88 88 #-------------------------------------------------------------------------------
89 89 # Handle OS specific things
90 90 #-------------------------------------------------------------------------------
91 91
92 92 if os.name in ('nt','dos'):
93 93 os_name = 'windows'
94 94 else:
95 95 os_name = os.name
96 96
97 97 # Under Windows, 'sdist' has not been supported. Now that the docs build with
98 98 # Sphinx it might work, but let's not turn it on until someone confirms that it
99 99 # actually works.
100 100 if os_name == 'windows' and 'sdist' in sys.argv:
101 101 print('The sdist command is not available under Windows. Exiting.')
102 102 sys.exit(1)
103 103
104 104
105 105 #-------------------------------------------------------------------------------
106 106 # Things related to the IPython documentation
107 107 #-------------------------------------------------------------------------------
108 108
109 109 # update the manuals when building a source dist
110 110 if len(sys.argv) >= 2 and sys.argv[1] in ('sdist','bdist_rpm'):
111 111
112 112 # List of things to be updated. Each entry is a triplet of args for
113 113 # target_update()
114 114 to_update = [
115 115 ('docs/man/ipython.1.gz',
116 116 ['docs/man/ipython.1'],
117 117 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz'),
118 118 ]
119 119
120 120
121 121 [ target_update(*t) for t in to_update ]
122 122
123 123 #---------------------------------------------------------------------------
124 124 # Find all the packages, package data, and data_files
125 125 #---------------------------------------------------------------------------
126 126
127 127 packages = find_packages()
128 128 package_data = find_package_data()
129 129
130 130 data_files = find_data_files()
131 131
132 132 setup_args['packages'] = packages
133 133 setup_args['package_data'] = package_data
134 134 setup_args['data_files'] = data_files
135 135
136 136 #---------------------------------------------------------------------------
137 137 # custom distutils commands
138 138 #---------------------------------------------------------------------------
139 139 # imports here, so they are after setuptools import if there was one
140 140 from distutils.command.sdist import sdist
141 141
142 142 setup_args['cmdclass'] = {
143 143 'build_py': \
144 144 check_package_data_first(git_prebuild('IPython')),
145 145 'sdist' : git_prebuild('IPython', sdist),
146 146 'symlink': install_symlinked,
147 147 'install_lib_symlink': install_lib_symlink,
148 148 'install_scripts_sym': install_scripts_for_symlink,
149 149 'unsymlink': unsymlink,
150 150 }
151 151
152 152
153 153 #---------------------------------------------------------------------------
154 154 # Handle scripts, dependencies, and setuptools specific things
155 155 #---------------------------------------------------------------------------
156 156
157 157 # For some commands, use setuptools. Note that we do NOT list install here!
158 158 # If you want a setuptools-enhanced install, just run 'setupegg.py install'
159 159 needs_setuptools = {'develop', 'release', 'bdist_egg', 'bdist_rpm',
160 160 'bdist', 'bdist_dumb', 'bdist_wininst', 'bdist_wheel',
161 161 'egg_info', 'easy_install', 'upload', 'install_egg_info',
162 162 }
163 163
164 164 if len(needs_setuptools.intersection(sys.argv)) > 0:
165 165 import setuptools
166 166
167 167 # This dict is used for passing extra arguments that are setuptools
168 168 # specific to setup
169 169 setuptools_extra_args = {}
170 170
171 171 # setuptools requirements
172 172
173 173 extras_require = dict(
174 174 parallel=["ipyparallel"],
175 175 qtconsole=["qtconsole"],
176 176 doc=["Sphinx>=1.3"],
177 177 test=[
178 178 "pytest",
179 179 "testpath",
180 180 "pygments",
181 ],
182 test_extra=[
183 "pytest",
184 "testpath",
185 "curio",
186 "matplotlib!=3.2.0",
181 187 "nbformat",
182 188 "ipykernel",
183 189 "numpy>=1.17",
190 "pandas",
191 "pygments",
192 "trio",
184 193 ],
185 194 terminal=[],
186 195 kernel=["ipykernel"],
187 196 nbformat=["nbformat"],
188 197 notebook=["notebook", "ipywidgets"],
189 198 nbconvert=["nbconvert"],
190 199 )
191 200
192 201 install_requires = [
193 202 "setuptools>=18.5",
194 203 "jedi>=0.16",
195 204 "decorator",
196 205 "pickleshare",
197 206 "traitlets>=4.2",
198 207 "prompt_toolkit>=2.0.0,<3.1.0,!=3.0.0,!=3.0.1",
199 208 "pygments",
200 209 "backcall",
201 210 "stack_data",
202 211 "matplotlib-inline",
203 212 ]
204 213
205 214 # Platform-specific dependencies:
206 215 # This is the correct way to specify these,
207 216 # but requires pip >= 6. pip < 6 ignores these.
208 217
209 218 extras_require.update(
210 219 {
211 220 ':sys_platform != "win32"': ["pexpect>4.3"],
212 221 ':sys_platform == "darwin"': ["appnope"],
213 222 ':sys_platform == "win32"': ["colorama"],
214 223 }
215 224 )
216 225 # FIXME: re-specify above platform dependencies for pip < 6
217 226 # These would result in non-portable bdists.
218 227 if not any(arg.startswith('bdist') for arg in sys.argv):
219 228 if sys.platform == 'darwin':
220 229 install_requires.extend(['appnope'])
221 230
222 231 if not sys.platform.startswith("win"):
223 232 install_requires.append("pexpect>4.3")
224 233
225 234 # workaround pypa/setuptools#147, where setuptools misspells
226 235 # platform_python_implementation as python_implementation
227 236 if 'setuptools' in sys.modules:
228 237 for key in list(extras_require):
229 238 if 'platform_python_implementation' in key:
230 239 new_key = key.replace('platform_python_implementation', 'python_implementation')
231 240 extras_require[new_key] = extras_require.pop(key)
232 241
233 242 everything = set()
234 243 for key, deps in extras_require.items():
235 244 if ':' not in key:
236 245 everything.update(deps)
237 246 extras_require['all'] = list(sorted(everything))
238 247
239 248 if 'setuptools' in sys.modules:
240 249 setuptools_extra_args['python_requires'] = '>=3.7'
241 250 setuptools_extra_args['zip_safe'] = False
242 251 setuptools_extra_args['entry_points'] = {
243 252 'console_scripts': find_entry_points(),
244 253 'pygments.lexers': [
245 254 'ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer',
246 255 'ipython = IPython.lib.lexers:IPythonLexer',
247 256 'ipython3 = IPython.lib.lexers:IPython3Lexer',
248 257 ],
249 258 }
250 259 setup_args['extras_require'] = extras_require
251 260 setup_args['install_requires'] = install_requires
252 261
253 262 else:
254 263 # scripts has to be a non-empty list, or install_scripts isn't called
255 264 setup_args['scripts'] = [e.split('=')[0].strip() for e in find_entry_points()]
256 265
257 266 setup_args['cmdclass']['build_scripts'] = build_scripts_entrypt
258 267
259 268 #---------------------------------------------------------------------------
260 269 # Do the actual setup now
261 270 #---------------------------------------------------------------------------
262 271
263 272 setup_args.update(setuptools_extra_args)
264 273
265 274
266 275
267 276 def main():
268 277 setup(**setup_args)
269 278
270 279 if __name__ == '__main__':
271 280 main()
General Comments 0
You need to be logged in to leave comments. Login now