##// END OF EJS Templates
Shaperilio/autoreload verbosity (#13774)...
Fernando Perez -
r28121:0725b4e7 merge
parent child Browse files
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 The following magic commands are provided:
30 The following magic commands are provided:
31
31
32 ``%autoreload``
32 ``%autoreload``, ``%autoreload now``
33
33
34 Reload all modules (except those excluded by ``%aimport``)
34 Reload all modules (except those excluded by ``%aimport``)
35 automatically now.
35 automatically now.
36
36
37 ``%autoreload 0``
37 ``%autoreload 0``, ``%autoreload off``
38
38
39 Disable automatic reloading.
39 Disable automatic reloading.
40
40
41 ``%autoreload 1``
41 ``%autoreload 1``, ``%autoreload explicit``
42
42
43 Reload all modules imported with ``%aimport`` every time before
43 Reload all modules imported with ``%aimport`` every time before
44 executing the Python code typed.
44 executing the Python code typed.
45
45
46 ``%autoreload 2``
46 ``%autoreload 2``, ``%autoreload all``
47
47
48 Reload all modules (except those excluded by ``%aimport``) every
48 Reload all modules (except those excluded by ``%aimport``) every
49 time before executing the Python code typed.
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
53 Same as 2/all, but also adds any new objects in the module. See
54 every time before executing the Python code typed.
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 ``%aimport``
60 ``%aimport``
57
61
@@ -101,6 +105,9 b' Some of the known remaining caveats are:'
101 - Reloading a module, or importing the same module by a different name, creates new Enums. These may look the same, but are not.
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 __skip_doctest__ = True
111 __skip_doctest__ = True
105
112
106 # -----------------------------------------------------------------------------
113 # -----------------------------------------------------------------------------
@@ -125,6 +132,7 b' import traceback'
125 import types
132 import types
126 import weakref
133 import weakref
127 import gc
134 import gc
135 import logging
128 from importlib import import_module, reload
136 from importlib import import_module, reload
129 from importlib.util import source_from_cache
137 from importlib.util import source_from_cache
130
138
@@ -156,6 +164,9 b' class ModuleReloader:'
156 self.modules_mtimes = {}
164 self.modules_mtimes = {}
157 self.shell = shell
165 self.shell = shell
158
166
167 # Reporting callable for verbosity
168 self._report = lambda msg: None # by default, be quiet.
169
159 # Cache module modification times
170 # Cache module modification times
160 self.check(check_all=True, do_reload=False)
171 self.check(check_all=True, do_reload=False)
161
172
@@ -254,6 +265,7 b' class ModuleReloader:'
254
265
255 # If we've reached this point, we should try to reload the module
266 # If we've reached this point, we should try to reload the module
256 if do_reload:
267 if do_reload:
268 self._report(f"Reloading '{modname}'.")
257 try:
269 try:
258 if self.autoload_obj:
270 if self.autoload_obj:
259 superreload(m, reload, self.old_objects, self.shell)
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 # IPython connectivity
507 # IPython connectivity
496 # ------------------------------------------------------------------------------
508 # ------------------------------------------------------------------------------
497
509
498 from IPython.core.magic import Magics, magics_class, line_magic
499
500
510
501 @magics_class
511 @magics_class
502 class AutoreloadMagics(Magics):
512 class AutoreloadMagics(Magics):
@@ -508,24 +518,67 b' class AutoreloadMagics(Magics):'
508 self.loaded_modules = set(sys.modules)
518 self.loaded_modules = set(sys.modules)
509
519
510 @line_magic
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 r"""%autoreload => Reload modules automatically
557 r"""%autoreload => Reload modules automatically
513
558
514 %autoreload
559 %autoreload or %autoreload now
515 Reload all modules (except those excluded by %aimport) automatically
560 Reload all modules (except those excluded by %aimport) automatically
516 now.
561 now.
517
562
518 %autoreload 0
563 %autoreload 0 or %autoreload off
519 Disable automatic reloading.
564 Disable automatic reloading.
520
565
521 %autoreload 1
566 %autoreload 1 or %autoreload explicit
522 Reload all modules imported with %aimport every time before executing
567 Reload only modules imported with %aimport every time before executing
523 the Python code typed.
568 the Python code typed.
524
569
525 %autoreload 2
570 %autoreload 2 or %autoreload all
526 Reload all modules (except those excluded by %aimport) every time
571 Reload all modules (except those excluded by %aimport) every time
527 before executing the Python code typed.
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 Reloading Python modules in a reliable way is in general
582 Reloading Python modules in a reliable way is in general
530 difficult, and unexpected things may occur. %autoreload tries to
583 difficult, and unexpected things may occur. %autoreload tries to
531 work around common pitfalls by replacing function code objects and
584 work around common pitfalls by replacing function code objects and
@@ -552,21 +605,47 b' class AutoreloadMagics(Magics):'
552 autoreloaded.
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 self._reloader.check(True)
632 self._reloader.check(True)
557 elif parameter_s == "0":
633 elif mode == "0" or mode == "off":
558 self._reloader.enabled = False
634 self._reloader.enabled = False
559 elif parameter_s == "1":
635 elif mode == "1" or mode == "explicit":
636 self._reloader.enabled = True
560 self._reloader.check_all = False
637 self._reloader.check_all = False
638 self._reloader.autoload_obj = False
639 elif mode == "2" or mode == "all":
561 self._reloader.enabled = True
640 self._reloader.enabled = True
562 elif parameter_s == "2":
563 self._reloader.check_all = True
641 self._reloader.check_all = True
642 self._reloader.autoload_obj = False
643 elif mode == "3" or mode == "complete":
564 self._reloader.enabled = True
644 self._reloader.enabled = True
565 self._reloader.enabled = True
566 elif parameter_s == "3":
567 self._reloader.check_all = True
645 self._reloader.check_all = True
568 self._reloader.enabled = True
569 self._reloader.autoload_obj = True
646 self._reloader.autoload_obj = True
647 else:
648 raise ValueError(f'Unrecognized autoreload mode "{mode}".')
570
649
571 @line_magic
650 @line_magic
572 def aimport(self, parameter_s="", stream=None):
651 def aimport(self, parameter_s="", stream=None):
@@ -576,13 +655,14 b' class AutoreloadMagics(Magics):'
576 List modules to automatically import and not to import.
655 List modules to automatically import and not to import.
577
656
578 %aimport foo
657 %aimport foo
579 Import module 'foo' and mark it to be autoreloaded for %autoreload 1
658 Import module 'foo' and mark it to be autoreloaded for %autoreload explicit
580
659
581 %aimport foo, bar
660 %aimport foo, bar
582 Import modules 'foo', 'bar' and mark them to be autoreloaded for %autoreload 1
661 Import modules 'foo', 'bar' and mark them to be autoreloaded for %autoreload explicit
583
662
584 %aimport -foo
663 %aimport -foo, bar
585 Mark module 'foo' to not be autoreloaded for %autoreload 1
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 modname = parameter_s
667 modname = parameter_s
588 if not modname:
668 if not modname:
@@ -595,15 +675,16 b' class AutoreloadMagics(Magics):'
595 else:
675 else:
596 stream.write("Modules to reload:\n%s\n" % " ".join(to_reload))
676 stream.write("Modules to reload:\n%s\n" % " ".join(to_reload))
597 stream.write("\nModules to skip:\n%s\n" % " ".join(to_skip))
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 else:
678 else:
602 for _module in [_.strip() for _ in modname.split(",")]:
679 for _module in [_.strip() for _ in modname.split(",")]:
603 top_module, top_name = self._reloader.aimport_module(_module)
680 if _module.startswith("-"):
604
681 _module = _module[1:].strip()
605 # Inject module to user namespace
682 self._reloader.mark_module_skipped(_module)
606 self.shell.push({top_name: top_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 def pre_run_cell(self):
689 def pre_run_cell(self):
609 if self._reloader.enabled:
690 if self._reloader.enabled:
@@ -22,6 +22,7 b' import shutil'
22 import random
22 import random
23 import time
23 import time
24 from io import StringIO
24 from io import StringIO
25 from dataclasses import dataclass
25
26
26 import IPython.testing.tools as tt
27 import IPython.testing.tools as tt
27
28
@@ -310,6 +311,7 b' class TestAutoreload(Fixture):'
310 self.shell.run_code("pass") # trigger another reload
311 self.shell.run_code("pass") # trigger another reload
311
312
312 def test_autoload_newly_added_objects(self):
313 def test_autoload_newly_added_objects(self):
314 # All of these fail with %autoreload 2
313 self.shell.magic_autoreload("3")
315 self.shell.magic_autoreload("3")
314 mod_code = """
316 mod_code = """
315 def func1(): pass
317 def func1(): pass
@@ -393,6 +395,95 b' class TestAutoreload(Fixture):'
393 self.shell.run_code("t = ExtTest(); assert t.meth() == 'ext'")
395 self.shell.run_code("t = ExtTest(); assert t.meth() == 'ext'")
394 self.shell.run_code("assert ext_int == 2")
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 def _check_smoketest(self, use_aimport=True):
487 def _check_smoketest(self, use_aimport=True):
397 """
488 """
398 Functional test for the automatic reloader using either
489 Functional test for the automatic reloader using either
General Comments 0
You need to be logged in to leave comments. Login now