-
Notifications
You must be signed in to change notification settings - Fork 458
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ENH: expose module load/unload API #1227
base: master
Are you sure you want to change the base?
Conversation
Exposing these methods allows programmatic control over module loading and unloading, which can be useful for giving users access to experimental modules, like with the following script: ``` import os import shutil archiveFilePath = os.path.join(slicer.app.temporaryPath, "master.zip") outputDir = os.path.join(slicer.app.temporaryPath, "SlicerAnimator") try: os.remove(archiveFilePath) except FileNotFoundError: pass try: shutil.rmtree(outputDir) except FileNotFoundError: pass os.mkdir(outputDir) slicer.util.downloadAndExtractArchive( url = "https://github.com/pieper/SlicerAnimator/archive/master.zip", archiveFilePath = archiveFilePath, outputDir = outputDir) modulePath = os.path.join(outputDir, "SlicerAnimator-master", "Animator", "Animator.py") factoryManager = slicer.app.moduleManager().factoryManager() factoryManager.registerModule(qt.QFileInfo(modulePath)) factoryManager.instantiateModule("Animator") factoryManager.loadModule("Animator") slicer.util.selectModule("Animator") slicer.util.delayDisplay("Waiting") factoryManager.unloadModule("Animator") ```
@jcfr - I didn't find any issues that would make me think these methods need to be protected. Do you agree? No odd behavior or crashes I could see. The motivation for this is being able to easily share "Lab" modules directly out of github repositories. This is for SlicerMorph, where Murat wants Sara to be able to iterate quickly with users. Once this works I plan to write a LabManager module that allows users to easily install and update scripted modules on the fly. |
Note that this is already implemented in extension wizard: Loading individual modules manually is not ideal:
So, I would keep using the method implemented in ExtensionWizard, but probably move it to slicer.util so that it can be found more easily. What I really miss is that I cannot drag-and-drop a module folder or module .py file to the main window to load it/add it to additional module paths (I have to open the Application settings dialog and choose the Modules section). It would be just a few ten lines of code to add a qSlicerFileDialog (similar to DICOMFileDialog) that would offer to add the folder to the additional module paths / load the modules instantly - if the user drag-and-drops .py files or folder containing .py files to the main window. It could be even smarter and detect if a .py file is a Slicer module or a script and offer to run the script instead of loading it as a module. |
Yes, I looked at the ExtensionWizard for ideas, but it's mixed with the gui there and not reusable. Moving the logic of it to The drag and drop idea is fine too, but also not related to this PR specifically (except I don't think you could implement it cleanly without these changes to the API). I can implement dynamic scripted module loading from a repository with the existing API without and satisfy my use case, but without unloading it's a little constrained. The point of this PR is that we already have a public method for We also have There are always ways developers can misuse any of these APIs, but this PR enables very useful functionality and doesn't make things more dangerous. If you have dependencies, they still need to be managed and that may not fit this use case. @lassoan I don't understand your point "normally you can only load all modules from a folder; it is not nice to temporarily change it" - can you clarify? |
Fully agree. It should be no problem to move that function over to slicer.util.
I agree, it just came to my mind, as I always miss this but not that much so that I would take the time, but the work you do now is very close to this, so it could make sense to implement in one step.
I think the methods are protected to discourage loading modules one by one (because developers may forget about dependency between modules) and to simplify the API (we have to worry about breaking backward compatibility if there are less public methods). You can just call LoadModules with a single module in the list if you want to load a specific module.
Unloading a module is tough. In general, it does not work, as modules do not have API to release dependencies and reconnect to them; and it may also be problematic due to circular dependencies between modules. "Reload" button in scripted modules widget header often does not work correctly which is OK for developers (they can click "Restart" next to it when things get really out of control). But I would not recommend users to reload modules without restarting Slicer (loading a module dynamically is fine, just not unloading).
"Loading individual .py module files" vs. "adding a folder to additional module paths and loading all .py module files in it" gives slightly different results (e.g., due to adding of folder to Python paths, potential presence of extra .py files in the folder, module dependency resolution, etc.). Module developers might not fully understand these subtle differences and why their module works in one way and not the other. I would recommend to restrict the API usage to:
These APIs are already publicly exposed and available in Python. |
👍 |
Thanks for your comments @lassoan but I will take issue with your conclusions. Restarting after unloading a module may be required, but I don't see any reason to assume that as a general rule. Certainly for most scripted modules that would not be the case. Restarting Slicer is a major waste of time for many use cases. For example if you want to try various versions of a scripted module I think you should be allowed to swap them in and out without restarting slicer and reloading your data. I suppose a workaround is to give each version a unique module name and never unload. That sounds like a hacky workaround. Another reason I don't like protected methods like Looking at the implementation here I don't see anything likely to break in this class if we allow Regarding your comment about loading directories vs individual modules, the |
LoadModule left as public by accident (or as a temporary workaround) see comments in the source files for details. I'm perfectly fine to expose features that don't always work to developers. But this unload feature would be used by users, who would just have the impression that Slicer is unstable. By the way, how this unload/load differs from reload? It would be nice if we would only need to maintain one mechanism for live module updates. |
Just to clarify, I'm fine if this change is merged as is. It is just an experimental feature anyway. Just brought up the points above to give a chance to find simpler and more robust solution. |
Exposing these methods allows programmatic control over
module loading and unloading, which can be useful for giving
users access to experimental modules, like with the following
script: