openlp-core team mailing list archive
-
openlp-core team
-
Mailing list archive
-
Message #31524
[Merge] lp:~phill-ridout/openlp/import-depreciations into lp:openlp
Phill has proposed merging lp:~phill-ridout/openlp/import-depreciations into lp:openlp.
Requested reviews:
Tim Bentley (trb143)
Phill (phill-ridout)
For more details, see:
https://code.launchpad.net/~phill-ridout/openlp/import-depreciations/+merge/324045
Fix the depreciated code, and refactor it.
lp:~phill-ridout/openlp/import-depreciations (revision 2736)
[SUCCESS] https://ci.openlp.io/job/Branch-01-Pull/2013/
[SUCCESS] https://ci.openlp.io/job/Branch-02-Functional-Tests/1923/
[SUCCESS] https://ci.openlp.io/job/Branch-03-Interface-Tests/1859/
[SUCCESS] https://ci.openlp.io/job/Branch-04a-Code_Analysis/1239/
[SUCCESS] https://ci.openlp.io/job/Branch-04b-Test_Coverage/1097/
[SUCCESS] https://ci.openlp.io/job/Branch-04c-Code_Analysis2/226/
[FAILURE] https://ci.openlp.io/job/Branch-05-AppVeyor-Tests/74/
Stopping after failure
--
Your team OpenLP Core is subscribed to branch lp:openlp.
=== modified file 'openlp/core/common/__init__.py'
--- openlp/core/common/__init__.py 2016-12-31 11:01:36 +0000
+++ openlp/core/common/__init__.py 2017-05-15 10:31:30 +0000
@@ -24,7 +24,7 @@
OpenLP work.
"""
import hashlib
-
+import importlib
import logging
import os
import re
@@ -32,6 +32,7 @@
import traceback
from chardet.universaldetector import UniversalDetector
from ipaddress import IPv4Address, IPv6Address, AddressValueError
+from pathlib import Path
from shutil import which
from subprocess import check_output, CalledProcessError, STDOUT
@@ -79,6 +80,49 @@
log.exception('failed to check if directory exists or create directory')
+def extension_loader(glob_pattern, excluded_files=[]):
+ """
+ A utility function to find and load OpenLP extensions, such as plugins, presentation and media controllers and
+ importers.
+
+ :param glob_pattern: A glob pattern used to find the extension(s) to be imported. Should be relative to the
+ application directory. i.e. openlp/plugins/*/*plugin.py
+ :type glob_pattern: str
+
+ :param excluded_files: A list of file names to exclude that the glob pattern may find.
+ :type excluded_files: list of strings
+
+ :return: None
+ :rtype: None
+ """
+ app_dir = Path(AppLocation.get_directory(AppLocation.AppDir)).parent
+ for extension_path in app_dir.glob(glob_pattern):
+ extension_path = extension_path.relative_to(app_dir)
+ if extension_path.name in excluded_files:
+ continue
+ module_name = path_to_module(extension_path)
+ try:
+ importlib.import_module(module_name)
+ except (ImportError, OSError):
+ # On some platforms importing vlc.py might cause OSError exceptions. (e.g. Mac OS X)
+ log.warning('Failed to import {module_name} on path {extension_path}'
+ .format(module_name=module_name, extension_path=str(extension_path)))
+
+
+def path_to_module(path):
+ """
+ Convert a path to a module name (i.e openlp.core.common)
+
+ :param path: The path to convert to a module name.
+ :type path: Path
+
+ :return: The module name.
+ :rtype: str
+ """
+ module_path = path.with_suffix('')
+ return '.'.join(module_path.parts)
+
+
def get_frozen_path(frozen_option, non_frozen_option):
"""
Return a path based on the system status.
=== modified file 'openlp/core/lib/pluginmanager.py'
--- openlp/core/lib/pluginmanager.py 2016-12-31 11:01:36 +0000
+++ openlp/core/lib/pluginmanager.py 2017-05-15 10:31:30 +0000
@@ -23,10 +23,9 @@
Provide plugin management
"""
import os
-import imp
from openlp.core.lib import Plugin, PluginStatus
-from openlp.core.common import AppLocation, RegistryProperties, OpenLPMixin, RegistryMixin
+from openlp.core.common import AppLocation, RegistryProperties, OpenLPMixin, RegistryMixin, extension_loader
class PluginManager(RegistryMixin, OpenLPMixin, RegistryProperties):
@@ -70,32 +69,8 @@
"""
Scan a directory for objects inheriting from the ``Plugin`` class.
"""
- start_depth = len(os.path.abspath(self.base_path).split(os.sep))
- present_plugin_dir = os.path.join(self.base_path, 'presentations')
- self.log_debug('finding plugins in {path} at depth {depth:d}'.format(path=self.base_path, depth=start_depth))
- for root, dirs, files in os.walk(self.base_path):
- for name in files:
- if name.endswith('.py') and not name.startswith('__'):
- path = os.path.abspath(os.path.join(root, name))
- this_depth = len(path.split(os.sep))
- if this_depth - start_depth > 2:
- # skip anything lower down
- break
- module_name = name[:-3]
- # import the modules
- self.log_debug('Importing {name} from {root}. Depth {depth:d}'.format(name=module_name,
- root=root,
- depth=this_depth))
- try:
- # Use the "imp" library to try to get around a problem with the PyUNO library which
- # monkey-patches the __import__ function to do some magic. This causes issues with our tests.
- # First, try to find the module we want to import, searching the directory in root
- fp, path_name, description = imp.find_module(module_name, [root])
- # Then load the module (do the actual import) using the details from find_module()
- imp.load_module(module_name, fp, path_name, description)
- except ImportError as e:
- self.log_exception('Failed to import module {name} on path {path}: '
- '{args}'.format(name=module_name, path=path, args=e.args[0]))
+ glob_pattern = os.path.join('openlp', 'plugins', '*', '*plugin.py')
+ extension_loader(glob_pattern)
plugin_classes = Plugin.__subclasses__()
plugin_objects = []
for p in plugin_classes:
=== modified file 'openlp/core/ui/media/mediacontroller.py'
--- openlp/core/ui/media/mediacontroller.py 2016-12-31 11:01:36 +0000
+++ openlp/core/ui/media/mediacontroller.py 2017-05-15 10:31:30 +0000
@@ -28,7 +28,8 @@
import datetime
from PyQt5 import QtCore, QtWidgets
-from openlp.core.common import OpenLPMixin, Registry, RegistryMixin, RegistryProperties, Settings, UiStrings, translate
+from openlp.core.common import OpenLPMixin, Registry, RegistryMixin, RegistryProperties, Settings, UiStrings, \
+ extension_loader, translate
from openlp.core.lib import ItemCapabilities
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.common import AppLocation
@@ -39,6 +40,7 @@
parse_optical_path
from openlp.core.ui.lib.toolbar import OpenLPToolbar
+
log = logging.getLogger(__name__)
TICK_TIME = 200
@@ -172,19 +174,9 @@
Check to see if we have any media Player's available.
"""
log.debug('_check_available_media_players')
- controller_dir = os.path.join(AppLocation.get_directory(AppLocation.AppDir), 'core', 'ui', 'media')
- for filename in os.listdir(controller_dir):
- if filename.endswith('player.py') and filename != 'mediaplayer.py':
- path = os.path.join(controller_dir, filename)
- if os.path.isfile(path):
- module_name = 'openlp.core.ui.media.' + os.path.splitext(filename)[0]
- log.debug('Importing controller %s', module_name)
- try:
- __import__(module_name, globals(), locals(), [])
- # On some platforms importing vlc.py might cause
- # also OSError exceptions. (e.g. Mac OS X)
- except (ImportError, OSError):
- log.warning('Failed to import %s on path %s', module_name, path)
+ controller_dir = os.path.join('openlp', 'core', 'ui', 'media')
+ glob_pattern = os.path.join(controller_dir, '*player.py')
+ extension_loader(glob_pattern, ['mediaplayer.py'])
player_classes = MediaPlayer.__subclasses__()
for player_class in player_classes:
self.register_players(player_class(self))
=== modified file 'openlp/plugins/presentations/lib/impresscontroller.py'
--- openlp/plugins/presentations/lib/impresscontroller.py 2016-12-31 11:01:36 +0000
+++ openlp/plugins/presentations/lib/impresscontroller.py 2017-05-15 10:31:30 +0000
@@ -58,7 +58,8 @@
from openlp.core.lib import ScreenList
from openlp.core.common import get_uno_command, get_uno_instance
-from .presentationcontroller import PresentationController, PresentationDocument, TextType
+from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument, \
+ TextType
log = logging.getLogger(__name__)
=== modified file 'openlp/plugins/presentations/lib/pdfcontroller.py'
--- openlp/plugins/presentations/lib/pdfcontroller.py 2016-12-31 11:01:36 +0000
+++ openlp/plugins/presentations/lib/pdfcontroller.py 2017-05-15 10:31:30 +0000
@@ -29,7 +29,7 @@
from openlp.core.common import AppLocation, check_binary_exists
from openlp.core.common import Settings, is_win
from openlp.core.lib import ScreenList
-from .presentationcontroller import PresentationController, PresentationDocument
+from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument
if is_win():
from subprocess import STARTUPINFO, STARTF_USESHOWWINDOW
=== modified file 'openlp/plugins/presentations/lib/powerpointcontroller.py'
--- openlp/plugins/presentations/lib/powerpointcontroller.py 2016-12-31 11:01:36 +0000
+++ openlp/plugins/presentations/lib/powerpointcontroller.py 2017-05-15 10:31:30 +0000
@@ -43,7 +43,7 @@
from openlp.core.lib import ScreenList
from openlp.core.lib.ui import UiStrings, critical_error_message_box, translate
from openlp.core.common import trace_error_handler, Registry
-from .presentationcontroller import PresentationController, PresentationDocument
+from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument
log = logging.getLogger(__name__)
=== modified file 'openlp/plugins/presentations/lib/pptviewcontroller.py'
--- openlp/plugins/presentations/lib/pptviewcontroller.py 2016-12-31 11:01:36 +0000
+++ openlp/plugins/presentations/lib/pptviewcontroller.py 2017-05-15 10:31:30 +0000
@@ -35,7 +35,7 @@
from openlp.core.common import AppLocation
from openlp.core.lib import ScreenList
-from .presentationcontroller import PresentationController, PresentationDocument
+from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument
log = logging.getLogger(__name__)
=== modified file 'openlp/plugins/presentations/lib/presentationtab.py'
--- openlp/plugins/presentations/lib/presentationtab.py 2016-12-31 11:01:36 +0000
+++ openlp/plugins/presentations/lib/presentationtab.py 2017-05-15 10:31:30 +0000
@@ -25,7 +25,7 @@
from openlp.core.common import Settings, UiStrings, translate
from openlp.core.lib import SettingsTab, build_icon
from openlp.core.lib.ui import critical_error_message_box
-from .pdfcontroller import PdfController
+from openlp.plugins.presentations.lib.pdfcontroller import PdfController
class PresentationTab(SettingsTab):
=== modified file 'openlp/plugins/presentations/presentationplugin.py'
--- openlp/plugins/presentations/presentationplugin.py 2016-12-31 11:01:36 +0000
+++ openlp/plugins/presentations/presentationplugin.py 2017-05-15 10:31:30 +0000
@@ -20,19 +20,18 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
-The :mod:`presentationplugin` module provides the ability for OpenLP to display presentations from a variety of document
-formats.
+The :mod:`openlp.plugins.presentations.presentationplugin` module provides the ability for OpenLP to display
+presentations from a variety of document formats.
"""
import os
import logging
from PyQt5 import QtCore
-from openlp.core.common import AppLocation, translate
+from openlp.core.common import AppLocation, extension_loader, translate
from openlp.core.lib import Plugin, StringContent, build_icon
from openlp.plugins.presentations.lib import PresentationController, PresentationMediaItem, PresentationTab
-
log = logging.getLogger(__name__)
@@ -122,17 +121,9 @@
Check to see if we have any presentation software available. If not do not install the plugin.
"""
log.debug('check_pre_conditions')
- controller_dir = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir), 'presentations', 'lib')
- for filename in os.listdir(controller_dir):
- if filename.endswith('controller.py') and filename != 'presentationcontroller.py':
- path = os.path.join(controller_dir, filename)
- if os.path.isfile(path):
- module_name = 'openlp.plugins.presentations.lib.' + os.path.splitext(filename)[0]
- log.debug('Importing controller {name}'.format(name=module_name))
- try:
- __import__(module_name, globals(), locals(), [])
- except ImportError:
- log.warning('Failed to import {name} on path {path}'.format(name=module_name, path=path))
+ controller_dir = os.path.join('openlp', 'plugins', 'presentations', 'lib')
+ glob_pattern = os.path.join(controller_dir, '*controller.py')
+ extension_loader(glob_pattern, ['presentationcontroller.py'])
controller_classes = PresentationController.__subclasses__()
for controller_class in controller_classes:
controller = controller_class(self)
=== modified file 'tests/functional/openlp_core_common/test_common.py'
--- tests/functional/openlp_core_common/test_common.py 2017-04-24 05:17:55 +0000
+++ tests/functional/openlp_core_common/test_common.py 2017-05-15 10:31:30 +0000
@@ -22,11 +22,13 @@
"""
Functional tests to test the AppLocation class and related methods.
"""
+from pathlib import Path
from unittest import TestCase
-from unittest.mock import MagicMock, patch
+from unittest.mock import MagicMock, call, patch
-from openlp.core.common import check_directory_exists, de_hump, trace_error_handler, translate, is_win, is_macosx, \
- is_linux, clean_button_text
+from openlp.core import common
+from openlp.core.common import check_directory_exists, clean_button_text, de_hump, extension_loader, is_macosx, \
+ is_linux, is_win, path_to_module, trace_error_handler, translate
class TestCommonFunctions(TestCase):
@@ -72,6 +74,72 @@
mocked_exists.assert_called_with(directory_to_check)
self.assertRaises(ValueError, check_directory_exists, directory_to_check)
+ def test_extension_loader_no_files_found(self):
+ """
+ Test the `extension_loader` function when no files are found
+ """
+ # GIVEN: A mocked `Path.glob` method which does not match any files
+ with patch('openlp.core.common.AppLocation.get_directory', return_value='/app/dir/openlp'), \
+ patch.object(common.Path, 'glob', return_value=[]), \
+ patch('openlp.core.common.importlib.import_module') as mocked_import_module:
+
+ # WHEN: Calling `extension_loader`
+ extension_loader('glob', ['file2.py', 'file3.py'])
+
+ # THEN: `extension_loader` should not try to import any files
+ self.assertFalse(mocked_import_module.called)
+
+ def test_extension_loader_files_found(self):
+ """
+ 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.get_directory', return_value='/app/dir/openlp'), \
+ patch.object(common.Path, 'glob', return_value=[Path('/app/dir/openlp/import_dir/file1.py'),
+ Path('/app/dir/openlp/import_dir/file2.py'),
+ Path('/app/dir/openlp/import_dir/file3.py'),
+ Path('/app/dir/openlp/import_dir/file4.py')]), \
+ patch('openlp.core.common.importlib.import_module') as mocked_import_module:
+
+ # WHEN: Calling `extension_loader` with a list of files to exclude
+ extension_loader('glob', ['file2.py', 'file3.py'])
+
+ # 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('openlp.import_dir.file1'), call('openlp.import_dir.file4')])
+
+ def test_extension_loader_import_error(self):
+ """
+ Test the `extension_loader` function when `SourceFileLoader` raises a `ImportError`
+ """
+ # GIVEN: A mocked `import_module` which raises an `ImportError`
+ with patch('openlp.core.common.AppLocation.get_directory', return_value='/app/dir/openlp'), \
+ patch.object(common.Path, 'glob', return_value=[Path('/app/dir/openlp/import_dir/file1.py')]), \
+ patch('openlp.core.common.importlib.import_module', side_effect=ImportError()), \
+ patch('openlp.core.common.log') as mocked_logger:
+
+ # WHEN: Calling `extension_loader`
+ extension_loader('glob')
+
+ # THEN: The `ImportError` should be caught and logged
+ self.assertTrue(mocked_logger.warning.called)
+
+ def test_extension_loader_os_error(self):
+ """
+ Test the `extension_loader` function when `import_module` raises a `ImportError`
+ """
+ # GIVEN: A mocked `SourceFileLoader` which raises an `OSError`
+ with patch('openlp.core.common.AppLocation.get_directory', return_value='/app/dir/openlp'), \
+ patch.object(common.Path, 'glob', return_value=[Path('/app/dir/openlp/import_dir/file1.py')]), \
+ patch('openlp.core.common.importlib.import_module', side_effect=OSError()), \
+ patch('openlp.core.common.log') as mocked_logger:
+
+ # WHEN: Calling `extension_loader`
+ extension_loader('glob')
+
+ # THEN: The `OSError` should be caught and logged
+ self.assertTrue(mocked_logger.warning.called)
+
def test_de_hump_conversion(self):
"""
Test the de_hump function with a class name
@@ -83,7 +151,7 @@
new_string = de_hump(string)
# THEN: the new string should be converted to python format
- self.assertTrue(new_string == "my_class", 'The class name should have been converted')
+ self.assertEqual(new_string, "my_class", 'The class name should have been converted')
def test_de_hump_static(self):
"""
@@ -96,7 +164,20 @@
new_string = de_hump(string)
# THEN: the new string should be converted to python format
- self.assertTrue(new_string == "my_class", 'The class name should have been preserved')
+ self.assertEqual(new_string, "my_class", 'The class name should have been preserved')
+
+ def test_path_to_module(self):
+ """
+ Test `path_to_module` when supplied with a `Path` object
+ """
+ # GIVEN: A `Path` object
+ path = Path('openlp/core/ui/media/webkitplayer.py')
+
+ # WHEN: Calling path_to_module with the `Path` object
+ result = path_to_module(path)
+
+ # THEN: path_to_module should return the module name
+ self.assertEqual(result, 'openlp.core.ui.media.webkitplayer')
def test_trace_error_handler(self):
"""
Follow ups