Python scripted module development reload feature for multiple files

Is there a way how to customize/override default development feature - “Reload” - behavior, during scripted module development? I separated code into multiple files but “Reload” seems to reload just the root python file, not the whole folder.

It’s a very good question - I did the original scripted module reload code many years ago and it took a while to sort out all the tweaks needed to get it working, so I never went back and looked into the options multiple-file reload, which is too bad because it tends to make me put more than I should in the main scripted module file.

Probably the same techniques that work for the main module would also work for imported files and maybe there’s even a way to discover the dependencies automatically and reload them all or maybe the scripted module could provide a list of code to reload. It may also be that with the python3 transition there will be new ways to do this, I haven’t looked. Would love to see a PR for this.

1 Like

It may be worth revisiting the approach by looking at the autoreload module from ipython.

See https://github.com/ipython/ipython/blob/master/IPython/extensions/autoreload.py

1 Like

Reloading is implemented fully in Python, so you can override the default implementation and reload additional modules, too. Since there are usually dependencies between classes, you typically need to click reload 2-3 times to update all referenced objects in al objects.

Example:

class ValveAnnulusAnalysisWidget(ScriptedLoadableModuleWidget):
  ...
  def onReload(self):
    logging.debug("Reloading ValveAnnulusAnalysis")
    packageName='HeartValveLib'
    submoduleNames=['util', 'LeafletModel', 'SmoothCurve', 'ValveRoi', 'PapillaryModel', 'ValveModel', 'HeartValves']
    import imp
    f, filename, description = imp.find_module(packageName)
    package = imp.load_module(packageName, f, filename, description)
    for submoduleName in submoduleNames:
      f, filename, description = imp.find_module(submoduleName, package.__path__)
      try:
          imp.load_module(packageName+'.'+submoduleName, f, filename, description)
      finally:
          f.close()
    ScriptedLoadableModuleWidget.onReload(self)
2 Likes

Thank you, @lassoan, for your example. Could you please add more information about how modules are registered and initiated within mentioned scripted module? I wasn’t able to me this work.

My scripted module structure looks as follows:

CustomScriptedModule/
    CustomScriptedModule.py
    CustomScriptedModuleLib/
        __init__.py
        CustomScriptedModuleWidget.py
        OtherModule1.py
        OtherModule2.py
        ...

Init file contains:

from CustomScriptedModuleWidget import *
from OtherModule1 import *
from OtherModule2 import *
...

Replace “HeartValveLib” by “CustomScriptedModuleLib”; ‘util’ by ‘OtherModule1’, ‘LeafletModel’ by ‘OtherModule2’, etc.

You must keep the module widget class in CustomScriptedModule.py. If you want to simplify widget creation, then choose “scripteddesigner” module template in the Extension Wizard and use Qt Designer to create your GUI.

Thanks, this works perfectly.

1 Like

This looks nice @lassoan - any reason not to make submoduleNames an property and move this implementation into ScriptedLoadableModuleWidget ?

Yes, it would be nice to add this to the module base class. There could be a list of tuples set in the module widget class constructor to define package and submodule names to reload - something like this:

self.packagesToReload = (
  ('packageName1', ('submoduleNameA', 'submoduleNameB')),
  ('packageName2', ('submoduleNameA', 'submoduleNameB', 'submoduleNameC'))
  )

Sounds good - I’ll probably do that next time I run into this. (Unless someone beats me to it!)

IDK if you are aware that the package imp is deprecated in python since version 3.4 and will be removed. it is recommended to move to importlib library. However, I did not succeed yet to make it work properly while reloading.

Thank you for looking into this. Probably we’ll get to this when imp is actually removed, starts to misbehave, or we need to rework module loading for some other reason anyway.

In some experimental code I found a handy workaround for this. I put this in my module’s Logic class so that every time I reload the module I get a fresh copy of the Lib code.

  def __init__(self):
    import VudoLib, VudoLib.Vudo
    self.VudoModule = importlib.reload(VudoLib.Vudo)

It should probably be made conditional on being in debug mode, since it loads the Lib twice at startup, but that’s usually a very small penalty in practice. The import has to happen before the reload because reload only works on something that’s already been loaded. I didn’t find anything that actually does the initial load via an api.

1 Like