allow importing add-on code from an addons namespace#14405
Conversation
See test results for failed build of commit d16d3b5292 |
Co-authored-by: Sean Budd <seanbudd123@gmail.com>
|
@LeonarddeR The implementation so far looks good. This might be a good candidate for an automated test. It's functionality that would be easy for NVDA core devs to forget about. It would need to be a system test, perhaps two simple add-ons, one that shares the code from the other and does something like announces the "hello world" message sourced from the other add-on. One other aspect to consider here is managing dependencies between add-ons. This will become a new problem, add-ons that only work when others are installed. Users will need a way to find out what dependencies to install and receive warnings when the dependencies aren't met. |
I think that interdependent add-ons are out of scope for this solution. Note that this possibility is already available. If add-on 1 bundles a globalPlugin and add-on 2 bundles a synthDriver, it is possible to do |
Co-authored-by: Cyrille Bougot <cyrille.bougot2@laposte.net>
|
Looking at the system tests library stuff, it all seems a bit hacky to me. There is a special function that constructs a scratchpad from several modules. Why was this made like this? Should we consider making this an add-on instead? |
While it was possible before, this PR is introducing interdependence between add-ons as a first-class use-case. Regardless of whether it's in scope of this PR or not, this is a problem that will need to be solved either in the add-ons or through NVDA add-on management. It's a tricky problem, the phrase "dependency hell" exists for a reason. Consider:
It's very tempting to say this isn't a path we want to go down, add-ons should just duplicate dependencies they need.
Off topic for this PR, but happy for alternatives to be considered in a new issue/PR. Using the scratchpad approach was taken because it was considered simpler while fundamentally providing the same outcome. This approach means there is no need for an add-on packaging step and no add-on install step. While working on the tests, and potentially changing libraries, the files need to be redeployed to ensure the tests run with the latest version. This happens for every 'start NVDA'. |
Interesting idea. I will research this further. A similar approach is used for bundling installTasks with an add-on. |
|
I discovered a pretty straight forward method to do this already. |
…om add-ons (#14481) Replaces #14405 Summary of the issue: Addons have the ability to import custom code from the add-ons directory. This is used to load install tasks, for example. This mechanism was based on a deprecated solution in pkgutil and allowed importing modules using backslashes, resulting in malformed module identifiers in sys.modules. Description of user facing changes Importing submodules with loadModule should be done with a dot separated name (i.e. loadModule("lib.example") instead of loadModule("lib\\example") loadModule now raises an exception when a module can't be found Description of development approach We no longer rely on pkgutil.ImpImporter. Testing strategy: Tested importing a lib module from an example addon as well as modules without init.py and submodules. Also tested modules with malformed content, resulting in tracebacks. Known issues with pull request: This would break cases where add-ons would use loadModule with a module name containing backslashes to import submodules. However, this scenario was broken anyway since it added malformed names to sys.modules. Note that this is strictly API breaking but I don't think noone was using it anyway. As loadModule now raises exceptions, add-on authors need to account for that when using this API.
Cc @josephsl
Link to issue number:
Closes #14359
Summary of the issue:
It is currently only possible to import add-on code from the standard NVDA plugin paths, like appModules or globalPlugins. Therefore, there is no elegant way add-ons can store code that has to be shared between appModules and globalPlugins, for example.
Description of user facing changes
@feerrenrut suggested the following approach in #14359 (comment):
The final solution is slightly different in that it adds an
addonspart in the naming scheme: Given an addon calledmyAddonyou can import code from alibdirectory within the add-on with `from addons.myAddon.lib import comInterfaces.Description of development approach
This solution depends heavily on importlib. It generates a blank module spec based on a module name and optional path, and constructs a module from that spec. The module is inserted in sys.modules. The addons themselves are simulated Native Namespace Packages. Long story short, these are python packages that don't require an
__init__.pyfile.Testing strategy:
Created an addon called
example. In the addon folder, added a folder calledliband in there a module calledtest.py. Successfully imported the module withfrom addons.example.lib import testKnown issues with pull request:
None known
Change log entries:
For Developers
Code Review Checklist: