Working version of Community Imports

This commit is contained in:
Tim Bentley 2024-01-24 17:56:58 +00:00 committed by Raoul Snyman
parent b5fe3bde41
commit a4a05630e9
3 changed files with 60 additions and 7 deletions

View File

@ -28,7 +28,9 @@ import logging
import re
import sys
import traceback
from ipaddress import IPv4Address, IPv6Address, AddressValueError
from pathlib import Path
from shutil import which
from PyQt5 import QtGui
@ -100,16 +102,20 @@ def trace_error_handler(logger):
logger.error(log_string)
def path_to_module(path):
def path_to_module(path: Path, community: bool = None) -> str:
"""
Convert a path to a module name (i.e openlp.core.common)
:param pathlib.Path path: The path to convert to a module name.
:param bool False community: Are we in Community Mode?
:return: The module name.
:rtype: str
"""
module_path = path.with_suffix('')
return 'openlp.' + '.'.join(module_path.parts)
if community:
return 'contrib.' + '.'.join(module_path.parts)
else:
return 'openlp.' + '.'.join(module_path.parts)
def import_openlp_module(module_name):
@ -119,7 +125,7 @@ def import_openlp_module(module_name):
importlib.import_module(module_name)
def extension_loader(glob_pattern, excluded_files=None):
def extension_loader(glob_pattern: str, excluded_files: list = None, community: bool = False) -> None:
"""
A utility function to find and load OpenLP extensions, such as plugins, presentation and media controllers and
importers.
@ -127,16 +133,22 @@ def extension_loader(glob_pattern, excluded_files=None):
:param str glob_pattern: A glob pattern used to find the extension(s) to be imported. Should be relative to the
application directory. i.e. plugins/*/*plugin.py
:param list[str] | None excluded_files: A list of file names to exclude that the glob pattern may find.
:param bool | False community: are we using the community directory path
:rtype: None
"""
from openlp.core.common.applocation import AppLocation
app_dir = AppLocation.get_directory(AppLocation.AppDir)
if community:
app_dir = AppLocation.get_directory(AppLocation.DataDir)
sys.path.insert(0, str(app_dir))
app_dir = app_dir / 'contrib'
else:
app_dir = AppLocation.get_directory(AppLocation.AppDir)
for extension_path in app_dir.glob(glob_pattern):
extension_path = extension_path.relative_to(app_dir)
if extension_path.name in (excluded_files or []):
continue
log.debug('Attempting to import %s', extension_path)
module_name = path_to_module(extension_path)
module_name = path_to_module(extension_path, community)
try:
import_openlp_module(module_name)
except (ImportError, OSError):

View File

@ -56,6 +56,7 @@ class PluginManager(RegistryBase, LogMixin, RegistryProperties):
"""
glob_pattern = os.path.join('plugins', '*', '[!.]*plugin.py')
extension_loader(glob_pattern)
extension_loader(glob_pattern, community=True)
plugin_classes = Plugin.__subclasses__()
for p in plugin_classes:
try:

View File

@ -22,12 +22,13 @@
Functional tests to test the :mod:`~openlp.core.common` module
"""
import os
import pytest
import sys
from io import BytesIO
from pathlib import Path
from unittest.mock import MagicMock, PropertyMock, call, patch
import pytest
from openlp.core.common import Singleton, add_actions, clean_filename, clean_button_text, de_hump, delete_file, \
extension_loader, get_file_encoding, get_filesystem_encoding, get_uno_command, get_uno_instance, md5_hash, \
normalize_str, path_to_module, qmd5_hash, sha256_file_hash, trace_error_handler, verify_ip_address
@ -86,6 +87,31 @@ def test_extension_loader_files_found():
# files listed in the `excluded_files` argument
mocked_import_module.assert_has_calls([call('openlp.import_dir.file1'),
call('openlp.import_dir.file4')])
assert "/app/dir/community" not in sys.path, "Community path has been added to the application sys.path"
def test_extension_loader_files_found_community():
"""
Test the `extension_loader` function when it successfully finds and loads some files
"""
# GIVEN: A mocked `Path.glob` method which returns a list of files
with patch('openlp.core.common.applocation.AppLocation.get_directory',
return_value=Path('/', 'app', 'dir')), \
patch.object(Path, 'glob', return_value=[
Path('/', 'app', 'dir', 'contrib', 'import_dir', 'file1.py'),
Path('/', 'app', 'dir', 'contrib', 'import_dir', 'file2.py'),
Path('/', 'app', 'dir', 'contrib', 'import_dir', 'file3.py'),
Path('/', 'app', 'dir', 'contrib', 'import_dir', 'file4.py')]), \
patch('openlp.core.common.import_openlp_module') as mocked_import_module:
# WHEN: Calling `extension_loader` with a list of files to exclude
extension_loader('glob', ['file2.py', 'file3.py'], True)
# THEN: `extension_loader` should only try to import the files that are matched by the blob, excluding the
# files listed in the `excluded_files` argument
mocked_import_module.assert_has_calls([call('contrib.import_dir.file1'),
call('contrib.import_dir.file4')])
assert "/app/dir" in sys.path, "app/dir path has not been added to the application sys.path"
def test_extension_loader_import_error():
@ -168,6 +194,20 @@ def test_path_to_module():
assert result == 'openlp.core.ui.media.vlcplayer'
def test_path_to_module_community():
"""
Test `path_to_module` when supplied with a `Path` object
"""
# GIVEN: A `Path` object
path = Path('core', 'ui', 'media', 'vlcplayer.py')
# WHEN: Calling path_to_module with the `Path` object
result = path_to_module(path, True)
# THEN: path_to_module should return the module name
assert result == 'contrib.core.ui.media.vlcplayer'
def test_trace_error_handler():
"""
Test the trace_error_handler() method