Show More
@@ -0,0 +1,26 b'' | |||
|
1 | Autoreload verbosity | |
|
2 | ==================== | |
|
3 | ||
|
4 | We introduce more descriptive names for the ``%autoreload`` parameter: | |
|
5 | ||
|
6 | - ``%autoreload now`` (also ``%autoreload``) - perform autoreload immediately. | |
|
7 | - ``%autoreload off`` (also ``%autoreload 0``) - turn off autoreload. | |
|
8 | - ``%autoreload explicit`` (also ``%autoreload 1``) - turn on autoreload only for modules | |
|
9 | whitelisted by ``%aimport`` statements. | |
|
10 | - ``%autoreload all`` (also ``%autoreload 2``) - turn on autoreload for all modules except those | |
|
11 | blacklisted by ``%aimport`` statements. | |
|
12 | - ``%autoreload complete`` (also ``%autoreload 3``) - all the fatures of ``all`` but also adding new | |
|
13 | objects from the imported modules (see | |
|
14 | IPython/extensions/tests/test_autoreload.py::test_autoload_newly_added_objects). | |
|
15 | ||
|
16 | The original designations (e.g. "2") still work, and these new ones are case-insensitive. | |
|
17 | ||
|
18 | Additionally, the option ``--print`` or ``-p`` can be added to the line to print the names of | |
|
19 | modules being reloaded. Similarly, ``--log`` or ``-l`` will output the names to the logger at INFO | |
|
20 | level. Both can be used simultaneously. | |
|
21 | ||
|
22 | The parsing logic for ``%aimport`` is now improved such that modules can be whitelisted and | |
|
23 | blacklisted in the same line, e.g. it's now possible to call ``%aimport os, -math`` to include | |
|
24 | ``os`` for ``%autoreload explicit`` and exclude ``math`` for modes ``all`` and ``complete``. | |
|
25 | ||
|
26 |
@@ -29,29 +29,33 b' Usage' | |||
|
29 | 29 | |
|
30 | 30 | The following magic commands are provided: |
|
31 | 31 | |
|
32 | ``%autoreload`` | |
|
32 | ``%autoreload``, ``%autoreload now`` | |
|
33 | 33 | |
|
34 | 34 | Reload all modules (except those excluded by ``%aimport``) |
|
35 | 35 | automatically now. |
|
36 | 36 | |
|
37 | ``%autoreload 0`` | |
|
37 | ``%autoreload 0``, ``%autoreload off`` | |
|
38 | 38 | |
|
39 | 39 | Disable automatic reloading. |
|
40 | 40 | |
|
41 | ``%autoreload 1`` | |
|
41 | ``%autoreload 1``, ``%autoreload explicit`` | |
|
42 | 42 | |
|
43 | 43 | Reload all modules imported with ``%aimport`` every time before |
|
44 | 44 | executing the Python code typed. |
|
45 | 45 | |
|
46 | ``%autoreload 2`` | |
|
46 | ``%autoreload 2``, ``%autoreload all`` | |
|
47 | 47 | |
|
48 | 48 | Reload all modules (except those excluded by ``%aimport``) every |
|
49 | 49 | time before executing the Python code typed. |
|
50 | 50 | |
|
51 | ``%autoreload 3`` | |
|
51 | ``%autoreload 3``, ``%autoreload complete`` | |
|
52 | 52 | |
|
53 | Reload all modules AND autoload newly added objects | |
|
54 | every time before executing the Python code typed. | |
|
53 | Same as 2/all, but also adds any new objects in the module. See | |
|
54 | unit test at IPython/extensions/tests/test_autoreload.py::test_autoload_newly_added_objects | |
|
55 | ||
|
56 | Adding ``--print`` or ``-p`` to the ``%autoreload`` line will print autoreload activity to | |
|
57 | standard out. ``--log`` or ``-l`` will do it to the log at INFO level; both can be used | |
|
58 | simultaneously. | |
|
55 | 59 | |
|
56 | 60 | ``%aimport`` |
|
57 | 61 | |
@@ -101,6 +105,9 b' Some of the known remaining caveats are:' | |||
|
101 | 105 | - Reloading a module, or importing the same module by a different name, creates new Enums. These may look the same, but are not. |
|
102 | 106 | """ |
|
103 | 107 | |
|
108 | from IPython.core import magic_arguments | |
|
109 | from IPython.core.magic import Magics, magics_class, line_magic | |
|
110 | ||
|
104 | 111 | __skip_doctest__ = True |
|
105 | 112 | |
|
106 | 113 | # ----------------------------------------------------------------------------- |
@@ -125,6 +132,7 b' import traceback' | |||
|
125 | 132 | import types |
|
126 | 133 | import weakref |
|
127 | 134 | import gc |
|
135 | import logging | |
|
128 | 136 | from importlib import import_module, reload |
|
129 | 137 | from importlib.util import source_from_cache |
|
130 | 138 | |
@@ -156,6 +164,9 b' class ModuleReloader:' | |||
|
156 | 164 | self.modules_mtimes = {} |
|
157 | 165 | self.shell = shell |
|
158 | 166 | |
|
167 | # Reporting callable for verbosity | |
|
168 | self._report = lambda msg: None # by default, be quiet. | |
|
169 | ||
|
159 | 170 | # Cache module modification times |
|
160 | 171 | self.check(check_all=True, do_reload=False) |
|
161 | 172 | |
@@ -254,6 +265,7 b' class ModuleReloader:' | |||
|
254 | 265 | |
|
255 | 266 | # If we've reached this point, we should try to reload the module |
|
256 | 267 | if do_reload: |
|
268 | self._report(f"Reloading '{modname}'.") | |
|
257 | 269 | try: |
|
258 | 270 | if self.autoload_obj: |
|
259 | 271 | superreload(m, reload, self.old_objects, self.shell) |
@@ -495,8 +507,6 b' def superreload(module, reload=reload, old_objects=None, shell=None):' | |||
|
495 | 507 | # IPython connectivity |
|
496 | 508 | # ------------------------------------------------------------------------------ |
|
497 | 509 | |
|
498 | from IPython.core.magic import Magics, magics_class, line_magic | |
|
499 | ||
|
500 | 510 | |
|
501 | 511 | @magics_class |
|
502 | 512 | class AutoreloadMagics(Magics): |
@@ -508,24 +518,67 b' class AutoreloadMagics(Magics):' | |||
|
508 | 518 | self.loaded_modules = set(sys.modules) |
|
509 | 519 | |
|
510 | 520 | @line_magic |
|
511 | def autoreload(self, parameter_s=""): | |
|
521 | @magic_arguments.magic_arguments() | |
|
522 | @magic_arguments.argument( | |
|
523 | "mode", | |
|
524 | type=str, | |
|
525 | default="now", | |
|
526 | nargs="?", | |
|
527 | help="""blank or 'now' - Reload all modules (except those excluded by %%aimport) | |
|
528 | automatically now. | |
|
529 | ||
|
530 | '0' or 'off' - Disable automatic reloading. | |
|
531 | ||
|
532 | '1' or 'explicit' - Reload only modules imported with %%aimport every | |
|
533 | time before executing the Python code typed. | |
|
534 | ||
|
535 | '2' or 'all' - Reload all modules (except those excluded by %%aimport) | |
|
536 | every time before executing the Python code typed. | |
|
537 | ||
|
538 | '3' or 'complete' - Same as 2/all, but also but also adds any new | |
|
539 | objects in the module. | |
|
540 | """, | |
|
541 | ) | |
|
542 | @magic_arguments.argument( | |
|
543 | "-p", | |
|
544 | "--print", | |
|
545 | action="store_true", | |
|
546 | default=False, | |
|
547 | help="Show autoreload activity using `print` statements", | |
|
548 | ) | |
|
549 | @magic_arguments.argument( | |
|
550 | "-l", | |
|
551 | "--log", | |
|
552 | action="store_true", | |
|
553 | default=False, | |
|
554 | help="Show autoreload activity using the logger", | |
|
555 | ) | |
|
556 | def autoreload(self, line=""): | |
|
512 | 557 | r"""%autoreload => Reload modules automatically |
|
513 | 558 | |
|
514 | %autoreload | |
|
559 | %autoreload or %autoreload now | |
|
515 | 560 | Reload all modules (except those excluded by %aimport) automatically |
|
516 | 561 | now. |
|
517 | 562 | |
|
518 | %autoreload 0 | |
|
563 | %autoreload 0 or %autoreload off | |
|
519 | 564 | Disable automatic reloading. |
|
520 | 565 | |
|
521 | %autoreload 1 | |
|
522 |
Reload |
|
|
566 | %autoreload 1 or %autoreload explicit | |
|
567 | Reload only modules imported with %aimport every time before executing | |
|
523 | 568 | the Python code typed. |
|
524 | 569 | |
|
525 | %autoreload 2 | |
|
570 | %autoreload 2 or %autoreload all | |
|
526 | 571 | Reload all modules (except those excluded by %aimport) every time |
|
527 | 572 | before executing the Python code typed. |
|
528 | 573 | |
|
574 | %autoreload 3 or %autoreload complete | |
|
575 | Same as 2/all, but also but also adds any new objects in the module. See | |
|
576 | unit test at IPython/extensions/tests/test_autoreload.py::test_autoload_newly_added_objects | |
|
577 | ||
|
578 | The optional arguments --print and --log control display of autoreload activity. The default | |
|
579 | is to act silently; --print (or -p) will print out the names of modules that are being | |
|
580 | reloaded, and --log (or -l) outputs them to the log at INFO level. | |
|
581 | ||
|
529 | 582 | Reloading Python modules in a reliable way is in general |
|
530 | 583 | difficult, and unexpected things may occur. %autoreload tries to |
|
531 | 584 | work around common pitfalls by replacing function code objects and |
@@ -552,21 +605,47 b' class AutoreloadMagics(Magics):' | |||
|
552 | 605 | autoreloaded. |
|
553 | 606 | |
|
554 | 607 | """ |
|
555 | if parameter_s == "": | |
|
608 | args = magic_arguments.parse_argstring(self.autoreload, line) | |
|
609 | mode = args.mode.lower() | |
|
610 | ||
|
611 | p = print | |
|
612 | ||
|
613 | logger = logging.getLogger("autoreload") | |
|
614 | ||
|
615 | l = logger.info | |
|
616 | ||
|
617 | def pl(msg): | |
|
618 | p(msg) | |
|
619 | l(msg) | |
|
620 | ||
|
621 | if args.print is False and args.log is False: | |
|
622 | self._reloader._report = lambda msg: None | |
|
623 | elif args.print is True: | |
|
624 | if args.log is True: | |
|
625 | self._reloader._report = pl | |
|
626 | else: | |
|
627 | self._reloader._report = p | |
|
628 | elif args.log is True: | |
|
629 | self._reloader._report = l | |
|
630 | ||
|
631 | if mode == "" or mode == "now": | |
|
556 | 632 | self._reloader.check(True) |
|
557 |
elif |
|
|
633 | elif mode == "0" or mode == "off": | |
|
558 | 634 | self._reloader.enabled = False |
|
559 | elif parameter_s == "1": | |
|
635 | elif mode == "1" or mode == "explicit": | |
|
636 | self._reloader.enabled = True | |
|
560 | 637 | self._reloader.check_all = False |
|
638 | self._reloader.autoload_obj = False | |
|
639 | elif mode == "2" or mode == "all": | |
|
561 | 640 | self._reloader.enabled = True |
|
562 | elif parameter_s == "2": | |
|
563 | 641 | self._reloader.check_all = True |
|
642 | self._reloader.autoload_obj = False | |
|
643 | elif mode == "3" or mode == "complete": | |
|
564 | 644 | self._reloader.enabled = True |
|
565 | self._reloader.enabled = True | |
|
566 | elif parameter_s == "3": | |
|
567 | 645 | self._reloader.check_all = True |
|
568 | self._reloader.enabled = True | |
|
569 | 646 | self._reloader.autoload_obj = True |
|
647 | else: | |
|
648 | raise ValueError(f'Unrecognized autoreload mode "{mode}".') | |
|
570 | 649 | |
|
571 | 650 | @line_magic |
|
572 | 651 | def aimport(self, parameter_s="", stream=None): |
@@ -576,13 +655,14 b' class AutoreloadMagics(Magics):' | |||
|
576 | 655 | List modules to automatically import and not to import. |
|
577 | 656 | |
|
578 | 657 | %aimport foo |
|
579 |
Import module 'foo' and mark it to be autoreloaded for %autoreload |
|
|
658 | Import module 'foo' and mark it to be autoreloaded for %autoreload explicit | |
|
580 | 659 | |
|
581 | 660 | %aimport foo, bar |
|
582 |
Import modules 'foo', 'bar' and mark them to be autoreloaded for %autoreload |
|
|
661 | Import modules 'foo', 'bar' and mark them to be autoreloaded for %autoreload explicit | |
|
583 | 662 | |
|
584 | %aimport -foo | |
|
585 |
Mark module 'foo' to not be autoreloaded for %autoreload |
|
|
663 | %aimport -foo, bar | |
|
664 | Mark module 'foo' to not be autoreloaded for %autoreload explicit, all, or complete, and 'bar' | |
|
665 | to be autoreloaded for mode explicit. | |
|
586 | 666 | """ |
|
587 | 667 | modname = parameter_s |
|
588 | 668 | if not modname: |
@@ -595,15 +675,16 b' class AutoreloadMagics(Magics):' | |||
|
595 | 675 | else: |
|
596 | 676 | stream.write("Modules to reload:\n%s\n" % " ".join(to_reload)) |
|
597 | 677 | stream.write("\nModules to skip:\n%s\n" % " ".join(to_skip)) |
|
598 | elif modname.startswith("-"): | |
|
599 | modname = modname[1:] | |
|
600 | self._reloader.mark_module_skipped(modname) | |
|
601 | 678 | else: |
|
602 | 679 | for _module in [_.strip() for _ in modname.split(",")]: |
|
603 | top_module, top_name = self._reloader.aimport_module(_module) | |
|
604 | ||
|
605 | # Inject module to user namespace | |
|
606 | self.shell.push({top_name: top_module}) | |
|
680 | if _module.startswith("-"): | |
|
681 | _module = _module[1:].strip() | |
|
682 | self._reloader.mark_module_skipped(_module) | |
|
683 | else: | |
|
684 | top_module, top_name = self._reloader.aimport_module(_module) | |
|
685 | ||
|
686 | # Inject module to user namespace | |
|
687 | self.shell.push({top_name: top_module}) | |
|
607 | 688 | |
|
608 | 689 | def pre_run_cell(self): |
|
609 | 690 | if self._reloader.enabled: |
@@ -22,6 +22,7 b' import shutil' | |||
|
22 | 22 | import random |
|
23 | 23 | import time |
|
24 | 24 | from io import StringIO |
|
25 | from dataclasses import dataclass | |
|
25 | 26 | |
|
26 | 27 | import IPython.testing.tools as tt |
|
27 | 28 | |
@@ -310,6 +311,7 b' class TestAutoreload(Fixture):' | |||
|
310 | 311 | self.shell.run_code("pass") # trigger another reload |
|
311 | 312 | |
|
312 | 313 | def test_autoload_newly_added_objects(self): |
|
314 | # All of these fail with %autoreload 2 | |
|
313 | 315 | self.shell.magic_autoreload("3") |
|
314 | 316 | mod_code = """ |
|
315 | 317 | def func1(): pass |
@@ -393,6 +395,95 b' class TestAutoreload(Fixture):' | |||
|
393 | 395 | self.shell.run_code("t = ExtTest(); assert t.meth() == 'ext'") |
|
394 | 396 | self.shell.run_code("assert ext_int == 2") |
|
395 | 397 | |
|
398 | def test_verbose_names(self): | |
|
399 | # Asserts correspondense between original mode names and their verbose equivalents. | |
|
400 | @dataclass | |
|
401 | class AutoreloadSettings: | |
|
402 | check_all: bool | |
|
403 | enabled: bool | |
|
404 | autoload_obj: bool | |
|
405 | ||
|
406 | def gather_settings(mode): | |
|
407 | self.shell.magic_autoreload(mode) | |
|
408 | module_reloader = self.shell.auto_magics._reloader | |
|
409 | return AutoreloadSettings( | |
|
410 | module_reloader.check_all, | |
|
411 | module_reloader.enabled, | |
|
412 | module_reloader.autoload_obj, | |
|
413 | ) | |
|
414 | ||
|
415 | assert gather_settings("0") == gather_settings("off") | |
|
416 | assert gather_settings("0") == gather_settings("OFF") # Case insensitive | |
|
417 | assert gather_settings("1") == gather_settings("explicit") | |
|
418 | assert gather_settings("2") == gather_settings("all") | |
|
419 | assert gather_settings("3") == gather_settings("complete") | |
|
420 | ||
|
421 | # And an invalid mode name raises an exception. | |
|
422 | with self.assertRaises(ValueError): | |
|
423 | self.shell.magic_autoreload("4") | |
|
424 | ||
|
425 | def test_aimport_parsing(self): | |
|
426 | # Modules can be included or excluded all in one line. | |
|
427 | module_reloader = self.shell.auto_magics._reloader | |
|
428 | self.shell.magic_aimport("os") # import and mark `os` for auto-reload. | |
|
429 | assert module_reloader.modules["os"] is True | |
|
430 | assert "os" not in module_reloader.skip_modules.keys() | |
|
431 | ||
|
432 | self.shell.magic_aimport("-math") # forbid autoreloading of `math` | |
|
433 | assert module_reloader.skip_modules["math"] is True | |
|
434 | assert "math" not in module_reloader.modules.keys() | |
|
435 | ||
|
436 | self.shell.magic_aimport( | |
|
437 | "-os, math" | |
|
438 | ) # Can do this all in one line; wasn't possible before. | |
|
439 | assert module_reloader.modules["math"] is True | |
|
440 | assert "math" not in module_reloader.skip_modules.keys() | |
|
441 | assert module_reloader.skip_modules["os"] is True | |
|
442 | assert "os" not in module_reloader.modules.keys() | |
|
443 | ||
|
444 | def test_autoreload_output(self): | |
|
445 | self.shell.magic_autoreload("complete") | |
|
446 | mod_code = """ | |
|
447 | def func1(): pass | |
|
448 | """ | |
|
449 | mod_name, mod_fn = self.new_module(mod_code) | |
|
450 | self.shell.run_code(f"import {mod_name}") | |
|
451 | with tt.AssertPrints("", channel="stdout"): # no output; this is default | |
|
452 | self.shell.run_code("pass") | |
|
453 | ||
|
454 | self.shell.magic_autoreload("complete --print") | |
|
455 | self.write_file(mod_fn, mod_code) # "modify" the module | |
|
456 | with tt.AssertPrints( | |
|
457 | f"Reloading '{mod_name}'.", channel="stdout" | |
|
458 | ): # see something printed out | |
|
459 | self.shell.run_code("pass") | |
|
460 | ||
|
461 | self.shell.magic_autoreload("complete -p") | |
|
462 | self.write_file(mod_fn, mod_code) # "modify" the module | |
|
463 | with tt.AssertPrints( | |
|
464 | f"Reloading '{mod_name}'.", channel="stdout" | |
|
465 | ): # see something printed out | |
|
466 | self.shell.run_code("pass") | |
|
467 | ||
|
468 | self.shell.magic_autoreload("complete --print --log") | |
|
469 | self.write_file(mod_fn, mod_code) # "modify" the module | |
|
470 | with tt.AssertPrints( | |
|
471 | f"Reloading '{mod_name}'.", channel="stdout" | |
|
472 | ): # see something printed out | |
|
473 | self.shell.run_code("pass") | |
|
474 | ||
|
475 | self.shell.magic_autoreload("complete --print --log") | |
|
476 | self.write_file(mod_fn, mod_code) # "modify" the module | |
|
477 | with self.assertLogs(logger="autoreload") as lo: # see something printed out | |
|
478 | self.shell.run_code("pass") | |
|
479 | assert lo.output == [f"INFO:autoreload:Reloading '{mod_name}'."] | |
|
480 | ||
|
481 | self.shell.magic_autoreload("complete -l") | |
|
482 | self.write_file(mod_fn, mod_code) # "modify" the module | |
|
483 | with self.assertLogs(logger="autoreload") as lo: # see something printed out | |
|
484 | self.shell.run_code("pass") | |
|
485 | assert lo.output == [f"INFO:autoreload:Reloading '{mod_name}'."] | |
|
486 | ||
|
396 | 487 | def _check_smoketest(self, use_aimport=True): |
|
397 | 488 | """ |
|
398 | 489 | Functional test for the automatic reloader using either |
General Comments 0
You need to be logged in to leave comments.
Login now