Skip to content

Commit d16d3b5

Browse files
authored
Merge cc5366b into 85efa6d
2 parents 85efa6d + cc5366b commit d16d3b5

2 files changed

Lines changed: 43 additions & 5 deletions

File tree

source/addonHandler/__init__.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# A part of NonVisual Desktop Access (NVDA)
22
# Copyright (C) 2012-2022 Rui Batista, NV Access Limited, Noelia Ruiz Martínez,
3-
# Joseph Lee, Babbage B.V., Arnold Loubriat, Łukasz Golonka
3+
# Joseph Lee, Babbage B.V., Arnold Loubriat, Łukasz Golonka, Leonard de Ruijter
44
# This file is covered by the GNU General Public License.
55
# See the file COPYING for more details.
66

@@ -21,7 +21,7 @@
2121
import zipfile
2222
from configobj import ConfigObj
2323
from configobj.validate import Validator
24-
from .packaging import initializeModulePackagePaths
24+
from .packaging import initializeAddonsNamespacePackage, initializeModulePackagePaths
2525
import config
2626
import languageHandler
2727
from logHandler import log
@@ -30,6 +30,7 @@
3030
from . import addonVersionCheck
3131
from .addonVersionCheck import isAddonCompatible
3232
import extensionPoints
33+
from types import ModuleType
3334

3435

3536
MANIFEST_FILENAME = "manifest.ini"
@@ -168,6 +169,7 @@ def initialize():
168169
getAvailableAddons(refresh=True, isFirstLoad=True)
169170
state.cleanupRemovedDisabledAddons()
170171
state.save()
172+
initializeAddonsNamespacePackage()
171173
initializeModulePackagePaths()
172174

173175

@@ -395,16 +397,16 @@ def completeRemove(self,runUninstallTask=True):
395397
_blockedAddons.discard(self.name)
396398
state.save()
397399

398-
def addToPackagePath(self, package):
400+
def addToPackagePath(self, package: ModuleType):
399401
""" Adds this L{Addon} extensions to the specific package path if those exist.
400402
This allows the addon to "run" / be available because the package is able to search its path,
401403
looking for particular modules. This is used by the following:
402404
- `globalPlugins`
403405
- `appModules`
404406
- `synthDrivers`
405407
- `brailleDisplayDrivers`
408+
- `visionEnhancementProviders`
406409
@param package: the python module representing the package.
407-
@type package: python module.
408410
"""
409411
# #3090: Ensure that we don't add disabled / blocked add-ons to package path.
410412
# By returning here the addon does not "run"/ become active / registered.

source/addonHandler/packaging.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@
77
"""
88

99
import os.path
10-
from typing import Optional
10+
from typing import Optional, List
1111
from types import ModuleType
1212
import globalVars
1313
import config
14+
import sys
15+
import importlib
1416

17+
ADDONS_MODULE_NAME = "addons"
18+
"""The name of an importable module that nessts add-on code for every active add-on."""
1519

1620
def initializeModulePackagePaths():
1721
"""Initializes the module package paths for drivers and plugins.
@@ -60,3 +64,35 @@ def addDirsToPythonPackagePath(module: ModuleType, subdir: Optional[str] = None)
6064
pathList = [fullPath]
6165
pathList.extend(module.__path__)
6266
module.__path__ = pathList
67+
68+
69+
def _createModule(moduleName: str, submoduleSearchLocations: Optional[List[str]] = None):
70+
"""Creates a module with the given moduleName and adds it to sys.modules.
71+
This ensures that the module can be imported.
72+
@param moduleName: The name of the module, e.g. addons or addons.example.
73+
@param submoduleSearchLocations: Can be provided if the module has to be a python namespace package.
74+
"""
75+
if moduleName in sys.modules:
76+
# module already initialized
77+
return
78+
spec = importlib._bootstrap.ModuleSpec(moduleName, None)
79+
if submoduleSearchLocations:
80+
spec.submodule_search_locations = submoduleSearchLocations
81+
module = importlib.util.module_from_spec(spec)
82+
sys.modules[module.__name__] = module
83+
84+
85+
def initializeAddonsNamespacePackage():
86+
"""Initializes the addons namespace package.
87+
This ensures that all python code in an add-on can be imported, even if code lives
88+
outside one of the standard package paths, such as appModules or globalPlugins.
89+
For example, if an add-on named example has a python module called lib,
90+
that module can be imported with `from addons.example import lib`
91+
"""
92+
# First, ensure that there is a placeholder addons module that does nothing,
93+
# i.e. it has no associated path but is solely there to nest add-ons under it.
94+
_createModule(ADDONS_MODULE_NAME)
95+
96+
from . import getRunningAddons
97+
for addon in getRunningAddons():
98+
_createModule(f"{ADDONS_MODULE_NAME}.{addon.name}", [addon.path])

0 commit comments

Comments
 (0)