Merge lp:~phill-ridout/openlp/import-depreciations into lp:openlp

Proposed by Phill
Status: Superseded
Proposed branch: lp:~phill-ridout/openlp/import-depreciations
Merge into: lp:openlp
Diff against target: 346 lines (+118/-64)
10 files modified
openlp/core/common/__init__.py (+34/-1)
openlp/core/lib/pluginmanager.py (+3/-28)
openlp/core/ui/media/mediacontroller.py (+5/-13)
openlp/plugins/presentations/lib/impresscontroller.py (+2/-1)
openlp/plugins/presentations/lib/pdfcontroller.py (+1/-1)
openlp/plugins/presentations/lib/powerpointcontroller.py (+1/-1)
openlp/plugins/presentations/lib/pptviewcontroller.py (+1/-1)
openlp/plugins/presentations/lib/presentationtab.py (+1/-1)
openlp/plugins/presentations/presentationplugin.py (+5/-14)
tests/functional/openlp_core_common/test_common.py (+65/-3)
To merge this branch: bzr merge lp:~phill-ridout/openlp/import-depreciations
Reviewer Review Type Date Requested Status
Tim Bentley Needs Fixing
Review via email: mp+324024@code.launchpad.net

This proposal has been superseded by a proposal from 2017-05-14.

Description of the change

To post a comment you must log in.
Revision history for this message
Tim Bentley (trb143) wrote :

Looks good but has issues with trunk!

review: Needs Fixing
2733. By Phill

Update and refactor dynamic import code

2734. By Phill

Reworked the extension_loader function

2735. By Phill

head

2736. By Phill

pep

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== 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-14 07:11:54 +0000
@@ -23,8 +23,9 @@
23The :mod:`common` module contains most of the components and libraries that make23The :mod:`common` module contains most of the components and libraries that make
24OpenLP work.24OpenLP work.
25"""25"""
26import glob
26import hashlib27import hashlib
2728import importlib
28import logging29import logging
29import os30import os
30import re31import re
@@ -79,6 +80,38 @@
79 log.exception('failed to check if directory exists or create directory')80 log.exception('failed to check if directory exists or create directory')
8081
8182
83def extension_loader(glob_pattern, excluded_files=[]):
84 """
85 A utility function to find and load OpenLP extensions, such as plugins, presentation and media controllers and
86 importers.
87
88 :param glob_pattern: A glob pattern used to find the extension(s) to be imported.
89 i.e. openlp_app_dir/plugins/*/*plugin.py
90 :type glob_pattern: str
91 :param excluded_files: A list of file names to exclude that the glob pattern may find.
92 :type excluded_files: list of strings
93
94 :return: None
95 :rtype: None
96 """
97 for extension_path in glob.iglob(glob_pattern):
98 filename = os.path.split(extension_path)[1]
99 if filename in excluded_files:
100 continue
101 module_name = os.path.splitext(filename)[0]
102 try:
103 loader = importlib.machinery.SourceFileLoader(module_name, extension_path)
104 loader.load_module()
105 # TODO: A better way to do this (once we drop python 3.4 support)
106 # spec = importlib.util.spec_from_file_location('what.ever', 'foo.py')
107 # module = importlib.util.module_from_spec(spec)
108 # spec.loader.exec_module(module)
109 except (ImportError, OSError):
110 # On some platforms importing vlc.py might cause OSError exceptions. (e.g. Mac OS X)
111 log.warning('Failed to import {module_name} on path {extension_path}'
112 .format(module_name=module_name, extension_path=extension_path))
113
114
82def get_frozen_path(frozen_option, non_frozen_option):115def get_frozen_path(frozen_option, non_frozen_option):
83 """116 """
84 Return a path based on the system status.117 Return a path based on the system status.
85118
=== 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-14 07:11:54 +0000
@@ -23,10 +23,9 @@
23Provide plugin management23Provide plugin management
24"""24"""
25import os25import os
26import imp
2726
28from openlp.core.lib import Plugin, PluginStatus27from openlp.core.lib import Plugin, PluginStatus
29from openlp.core.common import AppLocation, RegistryProperties, OpenLPMixin, RegistryMixin28from openlp.core.common import AppLocation, RegistryProperties, OpenLPMixin, RegistryMixin, extension_loader
3029
3130
32class PluginManager(RegistryMixin, OpenLPMixin, RegistryProperties):31class PluginManager(RegistryMixin, OpenLPMixin, RegistryProperties):
@@ -70,32 +69,8 @@
70 """69 """
71 Scan a directory for objects inheriting from the ``Plugin`` class.70 Scan a directory for objects inheriting from the ``Plugin`` class.
72 """71 """
73 start_depth = len(os.path.abspath(self.base_path).split(os.sep))72 glob_pattern = os.path.join(self.base_path, '*', '*plugin.py')
74 present_plugin_dir = os.path.join(self.base_path, 'presentations')73 extension_loader(glob_pattern)
75 self.log_debug('finding plugins in {path} at depth {depth:d}'.format(path=self.base_path, depth=start_depth))
76 for root, dirs, files in os.walk(self.base_path):
77 for name in files:
78 if name.endswith('.py') and not name.startswith('__'):
79 path = os.path.abspath(os.path.join(root, name))
80 this_depth = len(path.split(os.sep))
81 if this_depth - start_depth > 2:
82 # skip anything lower down
83 break
84 module_name = name[:-3]
85 # import the modules
86 self.log_debug('Importing {name} from {root}. Depth {depth:d}'.format(name=module_name,
87 root=root,
88 depth=this_depth))
89 try:
90 # Use the "imp" library to try to get around a problem with the PyUNO library which
91 # monkey-patches the __import__ function to do some magic. This causes issues with our tests.
92 # First, try to find the module we want to import, searching the directory in root
93 fp, path_name, description = imp.find_module(module_name, [root])
94 # Then load the module (do the actual import) using the details from find_module()
95 imp.load_module(module_name, fp, path_name, description)
96 except ImportError as e:
97 self.log_exception('Failed to import module {name} on path {path}: '
98 '{args}'.format(name=module_name, path=path, args=e.args[0]))
99 plugin_classes = Plugin.__subclasses__()74 plugin_classes = Plugin.__subclasses__()
100 plugin_objects = []75 plugin_objects = []
101 for p in plugin_classes:76 for p in plugin_classes:
10277
=== 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-14 07:11:54 +0000
@@ -28,7 +28,8 @@
28import datetime28import datetime
29from PyQt5 import QtCore, QtWidgets29from PyQt5 import QtCore, QtWidgets
3030
31from openlp.core.common import OpenLPMixin, Registry, RegistryMixin, RegistryProperties, Settings, UiStrings, translate31from openlp.core.common import OpenLPMixin, Registry, RegistryMixin, RegistryProperties, Settings, UiStrings, \
32 extension_loader, translate
32from openlp.core.lib import ItemCapabilities33from openlp.core.lib import ItemCapabilities
33from openlp.core.lib.ui import critical_error_message_box34from openlp.core.lib.ui import critical_error_message_box
34from openlp.core.common import AppLocation35from openlp.core.common import AppLocation
@@ -39,6 +40,7 @@
39 parse_optical_path40 parse_optical_path
40from openlp.core.ui.lib.toolbar import OpenLPToolbar41from openlp.core.ui.lib.toolbar import OpenLPToolbar
4142
43
42log = logging.getLogger(__name__)44log = logging.getLogger(__name__)
4345
44TICK_TIME = 20046TICK_TIME = 200
@@ -173,18 +175,8 @@
173 """175 """
174 log.debug('_check_available_media_players')176 log.debug('_check_available_media_players')
175 controller_dir = os.path.join(AppLocation.get_directory(AppLocation.AppDir), 'core', 'ui', 'media')177 controller_dir = os.path.join(AppLocation.get_directory(AppLocation.AppDir), 'core', 'ui', 'media')
176 for filename in os.listdir(controller_dir):178 glob_pattern = os.path.join(controller_dir, '*player.py')
177 if filename.endswith('player.py') and filename != 'mediaplayer.py':179 extension_loader(glob_pattern, ['mediaplayer.py'])
178 path = os.path.join(controller_dir, filename)
179 if os.path.isfile(path):
180 module_name = 'openlp.core.ui.media.' + os.path.splitext(filename)[0]
181 log.debug('Importing controller %s', module_name)
182 try:
183 __import__(module_name, globals(), locals(), [])
184 # On some platforms importing vlc.py might cause
185 # also OSError exceptions. (e.g. Mac OS X)
186 except (ImportError, OSError):
187 log.warning('Failed to import %s on path %s', module_name, path)
188 player_classes = MediaPlayer.__subclasses__()180 player_classes = MediaPlayer.__subclasses__()
189 for player_class in player_classes:181 for player_class in player_classes:
190 self.register_players(player_class(self))182 self.register_players(player_class(self))
191183
=== 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-14 07:11:54 +0000
@@ -58,7 +58,8 @@
5858
59from openlp.core.lib import ScreenList59from openlp.core.lib import ScreenList
60from openlp.core.common import get_uno_command, get_uno_instance60from openlp.core.common import get_uno_command, get_uno_instance
61from .presentationcontroller import PresentationController, PresentationDocument, TextType61from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument, \
62 TextType
6263
6364
64log = logging.getLogger(__name__)65log = logging.getLogger(__name__)
6566
=== 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-14 07:11:54 +0000
@@ -29,7 +29,7 @@
29from openlp.core.common import AppLocation, check_binary_exists29from openlp.core.common import AppLocation, check_binary_exists
30from openlp.core.common import Settings, is_win30from openlp.core.common import Settings, is_win
31from openlp.core.lib import ScreenList31from openlp.core.lib import ScreenList
32from .presentationcontroller import PresentationController, PresentationDocument32from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument
3333
34if is_win():34if is_win():
35 from subprocess import STARTUPINFO, STARTF_USESHOWWINDOW35 from subprocess import STARTUPINFO, STARTF_USESHOWWINDOW
3636
=== 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-14 07:11:54 +0000
@@ -43,7 +43,7 @@
43from openlp.core.lib import ScreenList43from openlp.core.lib import ScreenList
44from openlp.core.lib.ui import UiStrings, critical_error_message_box, translate44from openlp.core.lib.ui import UiStrings, critical_error_message_box, translate
45from openlp.core.common import trace_error_handler, Registry45from openlp.core.common import trace_error_handler, Registry
46from .presentationcontroller import PresentationController, PresentationDocument46from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument
4747
48log = logging.getLogger(__name__)48log = logging.getLogger(__name__)
4949
5050
=== 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-14 07:11:54 +0000
@@ -35,7 +35,7 @@
3535
36from openlp.core.common import AppLocation36from openlp.core.common import AppLocation
37from openlp.core.lib import ScreenList37from openlp.core.lib import ScreenList
38from .presentationcontroller import PresentationController, PresentationDocument38from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument
3939
4040
41log = logging.getLogger(__name__)41log = logging.getLogger(__name__)
4242
=== 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-14 07:11:54 +0000
@@ -25,7 +25,7 @@
25from openlp.core.common import Settings, UiStrings, translate25from openlp.core.common import Settings, UiStrings, translate
26from openlp.core.lib import SettingsTab, build_icon26from openlp.core.lib import SettingsTab, build_icon
27from openlp.core.lib.ui import critical_error_message_box27from openlp.core.lib.ui import critical_error_message_box
28from .pdfcontroller import PdfController28from openlp.plugins.presentations.lib.pdfcontroller import PdfController
2929
3030
31class PresentationTab(SettingsTab):31class PresentationTab(SettingsTab):
3232
=== 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-14 07:11:54 +0000
@@ -20,19 +20,18 @@
20# Temple Place, Suite 330, Boston, MA 02111-1307 USA #20# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
21###############################################################################21###############################################################################
22"""22"""
23The :mod:`presentationplugin` module provides the ability for OpenLP to display presentations from a variety of document23The :mod:`openlp.plugins.presentations.presentationplugin` module provides the ability for OpenLP to display
24formats.24presentations from a variety of document formats.
25"""25"""
26import os26import os
27import logging27import logging
2828
29from PyQt5 import QtCore29from PyQt5 import QtCore
3030
31from openlp.core.common import AppLocation, translate31from openlp.core.common import AppLocation, extension_loader, translate
32from openlp.core.lib import Plugin, StringContent, build_icon32from openlp.core.lib import Plugin, StringContent, build_icon
33from openlp.plugins.presentations.lib import PresentationController, PresentationMediaItem, PresentationTab33from openlp.plugins.presentations.lib import PresentationController, PresentationMediaItem, PresentationTab
3434
35
36log = logging.getLogger(__name__)35log = logging.getLogger(__name__)
3736
3837
@@ -123,16 +122,8 @@
123 """122 """
124 log.debug('check_pre_conditions')123 log.debug('check_pre_conditions')
125 controller_dir = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir), 'presentations', 'lib')124 controller_dir = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir), 'presentations', 'lib')
126 for filename in os.listdir(controller_dir):125 glob_pattern = os.path.join(controller_dir, '*controller.py')
127 if filename.endswith('controller.py') and filename != 'presentationcontroller.py':126 extension_loader(glob_pattern, ['presentationcontroller.py'])
128 path = os.path.join(controller_dir, filename)
129 if os.path.isfile(path):
130 module_name = 'openlp.plugins.presentations.lib.' + os.path.splitext(filename)[0]
131 log.debug('Importing controller {name}'.format(name=module_name))
132 try:
133 __import__(module_name, globals(), locals(), [])
134 except ImportError:
135 log.warning('Failed to import {name} on path {path}'.format(name=module_name, path=path))
136 controller_classes = PresentationController.__subclasses__()127 controller_classes = PresentationController.__subclasses__()
137 for controller_class in controller_classes:128 for controller_class in controller_classes:
138 controller = controller_class(self)129 controller = controller_class(self)
139130
=== 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-14 07:11:54 +0000
@@ -23,10 +23,10 @@
23Functional tests to test the AppLocation class and related methods.23Functional tests to test the AppLocation class and related methods.
24"""24"""
25from unittest import TestCase25from unittest import TestCase
26from unittest.mock import MagicMock, patch26from unittest.mock import MagicMock, call, patch
2727
28from openlp.core.common import check_directory_exists, de_hump, trace_error_handler, translate, is_win, is_macosx, \28from openlp.core.common import check_directory_exists, clean_button_text, de_hump, extension_loader, is_macosx, \
29 is_linux, clean_button_text29 is_linux, is_win, trace_error_handler, translate
3030
3131
32class TestCommonFunctions(TestCase):32class TestCommonFunctions(TestCase):
@@ -72,6 +72,68 @@
72 mocked_exists.assert_called_with(directory_to_check)72 mocked_exists.assert_called_with(directory_to_check)
73 self.assertRaises(ValueError, check_directory_exists, directory_to_check)73 self.assertRaises(ValueError, check_directory_exists, directory_to_check)
7474
75 def test_extension_loader_no_files_found(self):
76 """
77 Test the `extension_loader` function when no files are found
78 """
79 # GIVEN: A mocked `iglob` function which does not match any files
80 with patch('openlp.core.common.glob.iglob', return_value=[]), \
81 patch('openlp.core.common.importlib.machinery.SourceFileLoader') as mocked_source_file_loader:
82
83 # WHEN: Calling `extension_loader`
84 extension_loader('glob', ['file2.py', 'file3.py'])
85
86 # THEN: `extension_loader` should not try to import any files
87 self.assertFalse(mocked_source_file_loader.called)
88
89 def test_extension_loader_files_found(self):
90 """
91 Test the `extension_loader` function when it successfully finds and loads some files
92 """
93 # GIVEN: A mocked `iglob` function which returns a list of files
94 with patch('openlp.core.common.glob.iglob', return_value=['import_dir/file1.py', 'import_dir/file2.py',
95 'import_dir/file3.py', 'import_dir/file4.py']), \
96 patch('openlp.core.common.importlib.machinery.SourceFileLoader') as mocked_source_file_loader:
97
98 # WHEN: Calling `extension_loader` with a list of files to exclude
99 extension_loader('glob', ['file2.py', 'file3.py'])
100
101 # THEN: `extension_loader` should only try to import the files that are matched by the blob, excluding the
102 # files listed in the `excluded_files` argument
103 mocked_source_file_loader.assert_has_calls([call('file1', 'import_dir/file1.py'), call().load_module(),
104 call('file4', 'import_dir/file4.py'), call().load_module()])
105
106 def test_extension_loader_import_error(self):
107 """
108 Test the `extension_loader` function when `SourceFileLoader` raises a `ImportError`
109 """
110 # GIVEN: A mocked `SourceFileLoader` which raises an `ImportError`
111 with patch('openlp.core.common.glob.iglob', return_value=['import_dir/file1.py', 'import_dir/file2.py',
112 'import_dir/file3.py', 'import_dir/file4.py']), \
113 patch('openlp.core.common.importlib.machinery.SourceFileLoader', side_effect=ImportError()), \
114 patch('openlp.core.common.log') as mocked_logger:
115
116 # WHEN: Calling `extension_loader`
117 extension_loader('glob')
118
119 # THEN: The `ImportError` should be caught and logged
120 self.assertTrue(mocked_logger.warning.called)
121
122 def test_extension_loader_os_error(self):
123 """
124 Test the `extension_loader` function when `SourceFileLoader` raises a `ImportError`
125 """
126 # GIVEN: A mocked `SourceFileLoader` which raises an `OSError`
127 with patch('openlp.core.common.glob.iglob', return_value=['import_dir/file1.py']), \
128 patch('openlp.core.common.importlib.machinery.SourceFileLoader', side_effect=OSError()), \
129 patch('openlp.core.common.log') as mocked_logger:
130
131 # WHEN: Calling `extension_loader`
132 extension_loader('glob')
133
134 # THEN: The `OSError` should be caught and logged
135 self.assertTrue(mocked_logger.warning.called)
136
75 def test_de_hump_conversion(self):137 def test_de_hump_conversion(self):
76 """138 """
77 Test the de_hump function with a class name139 Test the de_hump function with a class name