[QGIS-Developer] Understanding inter-plugin dependencies and plugin loading - what can a plugin expect from a dependency?

Sebastian M. Ernst ernst at pleiszenburg.de
Thu Apr 23 11:46:46 PDT 2020


Hi all,

working my way through the current plugin (dependency) installer
mechanism, I have a few questions.

Bottom line: What kind of assumptions can a plugin safely make about its
dependencies and their initialization (from QGIS' perspective)?

Imagine two plugins: "PluginA" and "PluginB". PluginA depends on
PluginB, i.e. if a user was installing PluginA, current QGIS would ask
him to install PluginB as well.

Now the plugin system in QGIS does three things actually:

- Installing and uninstalling plugins
- Loading (i.e. importing, in Python terms) and unloading plugins
- Starting and stopping (`initGui` + friends and `unload`)

In comparison, typical package managers usually do not care about the
loading and starting part. This is because usually a package installer
does run outside of the scope of the stuff that is using the packages.

So if a user was installing PluginA in QGIS, the following sequence
would be triggered:

- Install PluginA
- Find dependencies of PluginA: PluginB
- Install PluginB
- Load PluginB
- Start PluginB
- Load PluginA
- Start PluginA

PluginB would be loaded (imported) and started before PluginA. Depending
on the use-case, this can make a lot of sense. Unfortunately, if a user
was restarting QGIS, the loading and starting sequence would depend on
(as it appears) the order of plugin folders on the filesystem which can
be more or less "random". So here, PluginA can not rely on PluginB to
have been loaded and/or started in advance at the moment.

This leads to many potential problems, some of which were discussed here
(Nyall's comment being the most interesting one for me):

https://github.com/qgis/QGIS-Enhancement-Proposals/issues/132#issuecomment-460136324

So the question now is: How is PluginA supposed to interact with PluginB?

Thanks to the way `sys.path` is configured in QGIS, PluginA could easily
run `import PluginB` and it would actually work. QGIS has a modified
`builtins.__import__` method which would even track this import. This
would sort of allow to treat PluginB as a "normal" Python package and be
equivalent to `qgis.utils.loadPlugin("PliginB")`, for better or for worse.

A possible alternative is that PluginA requires PluginB to initialize
itself through `classFactory` (or `serverClassFactory`). PluginA's best
(and probably safest) option then would be to run
`qgis.utils.startPlugin("PluginB")` (or `startProcessingPlugin` /
`startServerPlugin`). PluginA could now basically access the instance of
PluginB's class through `qgis.utils.plugins["PluginB"]` (or
`server_plugins`).

All of the above is fragile in many ways and - as far as I can tell -
more or less undocumented. How much of the described API in `qgis.utils`
is supposed to be available to "the public", i.e. to plugins?

As far as I can see, only one plugin (for QGIS 3.8 or later) in the
plugin repository actually uses inter-plugin dependencies (properly), so
there are not many examples of how things should look like. QEP 132 also
is not really detailed about the exact behavior and assumptions a
plugin's author can make. Fortunately, the current lack of "downstream"
users and the lack of "established" documentation opens the option to
"fix" the system without breaking too much stuff if desired.

Final questions:

- How is the *current* plugin-dependency system supposed to be used /
not supposed to be used from your perspective?
- How do you envision an actual plugin-dependency system to work (with
respect to interactions between plugins)?
- Does it make sense to care about plugin dependencies when QGIS is
loading and starting plugins (so dependencies are loaded & started first)?

Best regards,
Sebastian


More information about the QGIS-Developer mailing list