##// 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 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 all modules imported with %aimport every time before executing
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 parameter_s == "0":
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 1
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 1
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 1
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