← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~phill-ridout/openlp/pathlib1 into lp:openlp

 

Phill has proposed merging lp:~phill-ridout/openlp/pathlib1 into lp:openlp.

Requested reviews:
  Raoul Snyman (raoul-snyman)
  Tomas Groth (tomasgroth)
  OpenLP Core (openlp-core)

For more details, see:
https://code.launchpad.net/~phill-ridout/openlp/pathlib1/+merge/328435

This is the first in a series of merges to switch to the pathlib module. As per tgc's request I am splitting this down into smaller parts for merging. As such, this merge works in itself, but is incomplete.

Please bear this in mind when reviewing

Also contains a fix for bible gateway

lp:~phill-ridout/openlp/pathlib1 (revision 2757)
[SUCCESS] https://ci.openlp.io/job/Branch-01-Pull/2108/
[SUCCESS] https://ci.openlp.io/job/Branch-02-Functional-Tests/2018/
[SUCCESS] https://ci.openlp.io/job/Branch-03-Interface-Tests/1929/
[SUCCESS] https://ci.openlp.io/job/Branch-04a-Code_Analysis/1306/
[SUCCESS] https://ci.openlp.io/job/Branch-04b-Test_Coverage/1150/
[SUCCESS] https://ci.openlp.io/job/Branch-04c-Code_Analysis2/280/
[SUCCESS] https://ci.openlp.io/job/Branch-05-AppVeyor-Tests/125/

-- 
Your team OpenLP Core is requested to review the proposed merge of lp:~phill-ridout/openlp/pathlib1 into lp:openlp.
=== modified file 'openlp/core/__init__.py'
--- openlp/core/__init__.py	2017-05-30 18:42:35 +0000
+++ openlp/core/__init__.py	2017-08-02 06:25:28 +0000
@@ -181,7 +181,7 @@
         """
         Check if the data folder path exists.
         """
-        data_folder_path = AppLocation.get_data_path()
+        data_folder_path = str(AppLocation.get_data_path())
         if not os.path.exists(data_folder_path):
             log.critical('Database was not found in: ' + data_folder_path)
             status = QtWidgets.QMessageBox.critical(None, translate('OpenLP', 'Data Directory Error'),
@@ -253,7 +253,7 @@
                                                                   'a backup of the old data folder?'),
                                               defaultButton=QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes:
                 # Create copy of data folder
-                data_folder_path = AppLocation.get_data_path()
+                data_folder_path = str(AppLocation.get_data_path())
                 timestamp = time.strftime("%Y%m%d-%H%M%S")
                 data_folder_backup_path = data_folder_path + '-' + timestamp
                 try:
@@ -390,7 +390,7 @@
         application.setApplicationName('OpenLPPortable')
         Settings.setDefaultFormat(Settings.IniFormat)
         # Get location OpenLPPortable.ini
-        application_path = AppLocation.get_directory(AppLocation.AppDir)
+        application_path = str(AppLocation.get_directory(AppLocation.AppDir))
         set_up_logging(os.path.abspath(os.path.join(application_path, '..', '..', 'Other')))
         log.info('Running portable')
         portable_settings_file = os.path.abspath(os.path.join(application_path, '..', '..', 'Data', 'OpenLP.ini'))
@@ -407,7 +407,7 @@
         portable_settings.sync()
     else:
         application.setApplicationName('OpenLP')
-        set_up_logging(AppLocation.get_directory(AppLocation.CacheDir))
+        set_up_logging(str(AppLocation.get_directory(AppLocation.CacheDir)))
     Registry.create()
     Registry().register('application', application)
     application.setApplicationVersion(get_application_version()['version'])

=== modified file 'openlp/core/common/__init__.py'
--- openlp/core/common/__init__.py	2017-05-24 20:01:46 +0000
+++ openlp/core/common/__init__.py	2017-08-02 06:25:28 +0000
@@ -95,9 +95,9 @@
     :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)
+    base_dir_path = AppLocation.get_directory(AppLocation.AppDir).parent
+    for extension_path in base_dir_path.glob(glob_pattern):
+        extension_path = extension_path.relative_to(base_dir_path)
         if extension_path.name in excluded_files:
             continue
         module_name = path_to_module(extension_path)

=== modified file 'openlp/core/common/applocation.py'
--- openlp/core/common/applocation.py	2016-12-31 11:01:36 +0000
+++ openlp/core/common/applocation.py	2017-08-02 06:25:28 +0000
@@ -25,6 +25,7 @@
 import logging
 import os
 import sys
+from pathlib import Path
 
 from openlp.core.common import Settings, is_win, is_macosx
 
@@ -42,6 +43,9 @@
 
 log = logging.getLogger(__name__)
 
+FROZEN_APP_PATH = Path(sys.argv[0]).parent
+APP_PATH = Path(openlp.__file__).parent
+
 
 class AppLocation(object):
     """
@@ -63,20 +67,19 @@
         Return the appropriate directory according to the directory type.
 
         :param dir_type: The directory type you want, for instance the data directory. Default *AppLocation.AppDir*
+        :type dir_type: AppLocation Enum
+
+        :return: The requested path
+        :rtype: pathlib.Path
         """
-        if dir_type == AppLocation.AppDir:
-            return get_frozen_path(os.path.abspath(os.path.dirname(sys.argv[0])), os.path.dirname(openlp.__file__))
+        if dir_type == AppLocation.AppDir or dir_type == AppLocation.VersionDir:
+            return get_frozen_path(FROZEN_APP_PATH, APP_PATH)
         elif dir_type == AppLocation.PluginsDir:
-            app_path = os.path.abspath(os.path.dirname(sys.argv[0]))
-            return get_frozen_path(os.path.join(app_path, 'plugins'),
-                                   os.path.join(os.path.dirname(openlp.__file__), 'plugins'))
-        elif dir_type == AppLocation.VersionDir:
-            return get_frozen_path(os.path.abspath(os.path.dirname(sys.argv[0])), os.path.dirname(openlp.__file__))
+            return get_frozen_path(FROZEN_APP_PATH, APP_PATH) / 'plugins'
         elif dir_type == AppLocation.LanguageDir:
-            app_path = get_frozen_path(os.path.abspath(os.path.dirname(sys.argv[0])), _get_os_dir_path(dir_type))
-            return os.path.join(app_path, 'i18n')
+            return get_frozen_path(FROZEN_APP_PATH, _get_os_dir_path(dir_type)) / 'i18n'
         elif dir_type == AppLocation.DataDir and AppLocation.BaseDir:
-            return os.path.join(AppLocation.BaseDir, 'data')
+            return Path(AppLocation.BaseDir, 'data')
         else:
             return _get_os_dir_path(dir_type)
 
@@ -84,84 +87,97 @@
     def get_data_path():
         """
         Return the path OpenLP stores all its data under.
+
+        :return: The data path to use.
+        :rtype: pathlib.Path
         """
         # Check if we have a different data location.
         if Settings().contains('advanced/data path'):
-            path = Settings().value('advanced/data path')
+            path = Path(Settings().value('advanced/data path'))
         else:
             path = AppLocation.get_directory(AppLocation.DataDir)
-            check_directory_exists(path)
-        return os.path.normpath(path)
+            check_directory_exists(str(path))
+        return path
 
     @staticmethod
-    def get_files(section=None, extension=None):
+    def get_files(section=None, extension=''):
         """
         Get a list of files from the data files path.
 
-        :param section: Defaults to *None*. The section of code getting the files - used to load from a section's
-            data subdirectory.
-        :param extension:
-            Defaults to *None*. The extension to search for. For example::
-
-                '.png'
+        :param section: Defaults to *None*. The section of code getting the files - used to load from a section's data
+        subdirectory.
+        :type section: None | str
+
+        :param extension: Defaults to ''. The extension to search for. For example::
+            '.png'
+        :type extension: str
+
+        :return: List of files found.
+        :rtype: list[pathlib.Path]
         """
         path = AppLocation.get_data_path()
         if section:
-            path = os.path.join(path, section)
+            path = path / section
         try:
-            files = os.listdir(path)
+            file_paths = path.glob('*' + extension)
+            return [file_path.relative_to(path) for file_path in file_paths]
         except OSError:
             return []
-        if extension:
-            return [filename for filename in files if extension == os.path.splitext(filename)[1]]
-        else:
-            # no filtering required
-            return files
 
     @staticmethod
     def get_section_data_path(section):
         """
         Return the path a particular module stores its data under.
+
+        :type section: str
+
+        :rtype: pathlib.Path
         """
-        data_path = AppLocation.get_data_path()
-        path = os.path.join(data_path, section)
-        check_directory_exists(path)
+        path = AppLocation.get_data_path() / section
+        check_directory_exists(str(path))
         return path
 
 
 def _get_os_dir_path(dir_type):
     """
     Return a path based on which OS and environment we are running in.
+
+    :param dir_type: AppLocation Enum of the requested path type
+    :type dir_type: AppLocation Enum
+
+    :return: The requested path
+    :rtype: pathlib.Path
     """
     # If running from source, return the language directory from the source directory
     if dir_type == AppLocation.LanguageDir:
-        directory = os.path.abspath(os.path.join(os.path.dirname(openlp.__file__), '..', 'resources'))
-        if os.path.exists(directory):
+        directory = Path(os.path.abspath(os.path.join(os.path.dirname(openlp.__file__), '..', 'resources')))
+        if directory.exists():
             return directory
     if is_win():
+        openlp_folder_path = Path(os.getenv('APPDATA'), 'openlp')
         if dir_type == AppLocation.DataDir:
-            return os.path.join(str(os.getenv('APPDATA')), 'openlp', 'data')
+            return openlp_folder_path / 'data'
         elif dir_type == AppLocation.LanguageDir:
             return os.path.dirname(openlp.__file__)
-        return os.path.join(str(os.getenv('APPDATA')), 'openlp')
+        return openlp_folder_path
     elif is_macosx():
+        openlp_folder_path = Path(os.getenv('HOME'), 'Library', 'Application Support', 'openlp')
         if dir_type == AppLocation.DataDir:
-            return os.path.join(str(os.getenv('HOME')), 'Library', 'Application Support', 'openlp', 'Data')
+            return openlp_folder_path / 'Data'
         elif dir_type == AppLocation.LanguageDir:
             return os.path.dirname(openlp.__file__)
-        return os.path.join(str(os.getenv('HOME')), 'Library', 'Application Support', 'openlp')
+        return openlp_folder_path
     else:
         if dir_type == AppLocation.LanguageDir:
-            for prefix in ['/usr/local', '/usr']:
-                directory = os.path.join(prefix, 'share', 'openlp')
-                if os.path.exists(directory):
-                    return directory
-            return os.path.join('/usr', 'share', 'openlp')
+            directory = Path('/usr', 'local', 'share', 'openlp')
+            if directory.exists():
+                return directory
+            return Path('/usr', 'share', 'openlp')
         if XDG_BASE_AVAILABLE:
-            if dir_type == AppLocation.DataDir:
-                return os.path.join(str(BaseDirectory.xdg_data_home), 'openlp')
+            if dir_type == AppLocation.DataDir or dir_type == AppLocation.CacheDir:
+                return Path(BaseDirectory.xdg_data_home, 'openlp')
             elif dir_type == AppLocation.CacheDir:
-                return os.path.join(str(BaseDirectory.xdg_cache_home), 'openlp')
+                return Path(BaseDirectory.xdg_cache_home, 'openlp')
         if dir_type == AppLocation.DataDir:
-            return os.path.join(str(os.getenv('HOME')), '.openlp', 'data')
-        return os.path.join(str(os.getenv('HOME')), '.openlp')
+            return Path(os.getenv('HOME'), '.openlp', 'data')
+        return Path(os.getenv('HOME'), '.openlp')

=== modified file 'openlp/core/common/languagemanager.py'
--- openlp/core/common/languagemanager.py	2017-05-30 18:42:35 +0000
+++ openlp/core/common/languagemanager.py	2017-08-02 06:25:28 +0000
@@ -53,7 +53,7 @@
         """
         if LanguageManager.auto_language:
             language = QtCore.QLocale.system().name()
-        lang_path = AppLocation.get_directory(AppLocation.LanguageDir)
+        lang_path = str(AppLocation.get_directory(AppLocation.LanguageDir))
         app_translator = QtCore.QTranslator()
         app_translator.load(language, lang_path)
         # A translator for buttons and other default strings provided by Qt.
@@ -72,7 +72,7 @@
         Find all available language files in this OpenLP install
         """
         log.debug('Translation files: {files}'.format(files=AppLocation.get_directory(AppLocation.LanguageDir)))
-        trans_dir = QtCore.QDir(AppLocation.get_directory(AppLocation.LanguageDir))
+        trans_dir = QtCore.QDir(str(AppLocation.get_directory(AppLocation.LanguageDir)))
         file_names = trans_dir.entryList(['*.qm'], QtCore.QDir.Files, QtCore.QDir.Name)
         # Remove qm files from the list which start with "qt".
         file_names = [file_ for file_ in file_names if not file_.startswith('qt')]

=== modified file 'openlp/core/common/versionchecker.py'
--- openlp/core/common/versionchecker.py	2016-07-01 21:17:20 +0000
+++ openlp/core/common/versionchecker.py	2017-08-02 06:25:28 +0000
@@ -95,7 +95,7 @@
             full_version = '{tag}-bzr{tree}'.format(tag=tag_version.strip(), tree=tree_revision.strip())
     else:
         # We're not running the development version, let's use the file.
-        file_path = AppLocation.get_directory(AppLocation.VersionDir)
+        file_path = str(AppLocation.get_directory(AppLocation.VersionDir))
         file_path = os.path.join(file_path, '.version')
         version_file = None
         try:

=== modified file 'openlp/core/lib/db.py'
--- openlp/core/lib/db.py	2017-06-09 14:04:52 +0000
+++ openlp/core/lib/db.py	2017-08-02 06:25:28 +0000
@@ -274,9 +274,9 @@
     :param db_file_name: The database file name. Defaults to None resulting in the plugin_name being used.
     """
     if db_file_name:
-        db_file_path = os.path.join(AppLocation.get_section_data_path(plugin_name), db_file_name)
+        db_file_path = os.path.join(str(AppLocation.get_section_data_path(plugin_name)), db_file_name)
     else:
-        db_file_path = os.path.join(AppLocation.get_section_data_path(plugin_name), plugin_name)
+        db_file_path = os.path.join(str(AppLocation.get_section_data_path(plugin_name)), plugin_name)
     return delete_file(db_file_path)
 
 

=== added file 'openlp/core/lib/path.py'
--- openlp/core/lib/path.py	1970-01-01 00:00:00 +0000
+++ openlp/core/lib/path.py	2017-08-02 06:25:28 +0000
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2017 OpenLP Developers                                   #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it     #
+# under the terms of the GNU General Public License as published by the Free  #
+# Software Foundation; version 2 of the License.                              #
+#                                                                             #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
+# more details.                                                               #
+#                                                                             #
+# You should have received a copy of the GNU General Public License along     #
+# with this program; if not, write to the Free Software Foundation, Inc., 59  #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
+###############################################################################
+
+from pathlib import Path
+
+
+def path_to_str(path):
+    """
+    A utility function to convert a Path object or NoneType to a string equivalent.
+
+    :param path: The value to convert to a string
+    :type: pathlib.Path or None
+
+    :return: An empty string if :param:`path` is None, else a string representation of the :param:`path`
+    :rtype: str
+    """
+    if not isinstance(path, Path) and path is not None:
+        raise TypeError('parameter \'path\' must be of type Path or NoneType')
+    if path is None:
+        return ''
+    else:
+        return str(path)
+
+
+def str_to_path(string):
+    """
+    A utility function to convert a str object to a Path or NoneType.
+
+    This function is of particular use because initating a Path object with an empty string causes the Path object to
+    point to the current working directory.
+
+    :param string: The string to convert
+    :type string: str
+
+    :return: None if :param:`string` is empty, or a Path object representation of :param:`string`
+    :rtype: pathlib.Path or None
+    """
+    if not isinstance(string, str):
+        raise TypeError('parameter \'string\' must be of type str')
+    if string == '':
+        return None
+    return Path(string)

=== modified file 'openlp/core/lib/pluginmanager.py'
--- openlp/core/lib/pluginmanager.py	2017-05-15 10:09:59 +0000
+++ openlp/core/lib/pluginmanager.py	2017-08-02 06:25:28 +0000
@@ -40,7 +40,7 @@
         """
         super(PluginManager, self).__init__(parent)
         self.log_info('Plugin manager Initialising')
-        self.base_path = os.path.abspath(AppLocation.get_directory(AppLocation.PluginsDir))
+        self.base_path = os.path.abspath(str(AppLocation.get_directory(AppLocation.PluginsDir)))
         self.log_debug('Base path {path}'.format(path=self.base_path))
         self.plugins = []
         self.log_info('Plugin manager Initialised')

=== modified file 'openlp/core/lib/serviceitem.py'
--- openlp/core/lib/serviceitem.py	2017-05-17 20:06:45 +0000
+++ openlp/core/lib/serviceitem.py	2017-08-02 06:25:28 +0000
@@ -335,7 +335,7 @@
         if image and not self.has_original_files and self.name == 'presentations':
             file_location = os.path.join(path, file_name)
             file_location_hash = md5_hash(file_location.encode('utf-8'))
-            image = os.path.join(AppLocation.get_section_data_path(self.name), 'thumbnails',
+            image = os.path.join(str(AppLocation.get_section_data_path(self.name)), 'thumbnails',
                                  file_location_hash, ntpath.basename(image))
         self._raw_frames.append({'title': file_name, 'image': image, 'path': path,
                                  'display_title': display_title, 'notes': notes})

=== modified file 'openlp/core/lib/theme.py'
--- openlp/core/lib/theme.py	2017-05-24 19:55:30 +0000
+++ openlp/core/lib/theme.py	2017-08-02 06:25:28 +0000
@@ -158,7 +158,7 @@
         Initialise the theme object.
         """
         # basic theme object with defaults
-        json_dir = os.path.join(AppLocation.get_directory(AppLocation.AppDir), 'core', 'lib', 'json')
+        json_dir = os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)), 'core', 'lib', 'json')
         json_file = os.path.join(json_dir, 'theme.json')
         jsn = get_text_file_string(json_file)
         jsn = json.loads(jsn)

=== modified file 'openlp/core/ui/advancedtab.py'
--- openlp/core/ui/advancedtab.py	2017-05-30 18:50:39 +0000
+++ openlp/core/ui/advancedtab.py	2017-08-02 06:25:28 +0000
@@ -156,7 +156,7 @@
         self.data_directory_new_label = QtWidgets.QLabel(self.data_directory_group_box)
         self.data_directory_new_label.setObjectName('data_directory_current_label')
         self.data_directory_path_edit = PathEdit(self.data_directory_group_box, path_type=PathType.Directories,
-                                                 default_path=AppLocation.get_directory(AppLocation.DataDir))
+                                                 default_path=str(AppLocation.get_directory(AppLocation.DataDir)))
         self.data_directory_layout.addRow(self.data_directory_new_label, self.data_directory_path_edit)
         self.new_data_directory_has_files_label = QtWidgets.QLabel(self.data_directory_group_box)
         self.new_data_directory_has_files_label.setObjectName('new_data_directory_has_files_label')
@@ -373,7 +373,7 @@
         self.new_data_directory_has_files_label.hide()
         self.data_directory_cancel_button.hide()
         # Since data location can be changed, make sure the path is present.
-        self.data_directory_path_edit.path = AppLocation.get_data_path()
+        self.data_directory_path_edit.path = str(AppLocation.get_data_path())
         # Don't allow data directory move if running portable.
         if settings.value('advanced/is portable'):
             self.data_directory_group_box.hide()
@@ -497,7 +497,7 @@
                                                           'closed.').format(path=new_data_path),
                                                 defaultButton=QtWidgets.QMessageBox.No)
         if answer != QtWidgets.QMessageBox.Yes:
-            self.data_directory_path_edit.path = AppLocation.get_data_path()
+            self.data_directory_path_edit.path = str(AppLocation.get_data_path())
             return
         # Check if data already exists here.
         self.check_data_overwrite(new_data_path)
@@ -550,7 +550,7 @@
         """
         Cancel the data directory location change
         """
-        self.data_directory_path_edit.path = AppLocation.get_data_path()
+        self.data_directory_path_edit.path = str(AppLocation.get_data_path())
         self.data_directory_copy_check_box.setChecked(False)
         self.main_window.set_new_data_path(None)
         self.main_window.set_copy_data(False)

=== modified file 'openlp/core/ui/firsttimeform.py'
--- openlp/core/ui/firsttimeform.py	2017-05-30 18:42:35 +0000
+++ openlp/core/ui/firsttimeform.py	2017-08-02 06:25:28 +0000
@@ -554,8 +554,8 @@
         """
         # Build directories for downloads
         songs_destination = os.path.join(gettempdir(), 'openlp')
-        bibles_destination = AppLocation.get_section_data_path('bibles')
-        themes_destination = AppLocation.get_section_data_path('themes')
+        bibles_destination = str(AppLocation.get_section_data_path('bibles'))
+        themes_destination = str(AppLocation.get_section_data_path('themes'))
         missed_files = []
         # Download songs
         for i in range(self.songs_list_widget.count()):

=== modified file 'openlp/core/ui/maindisplay.py'
--- openlp/core/ui/maindisplay.py	2017-06-04 12:26:50 +0000
+++ openlp/core/ui/maindisplay.py	2017-08-02 06:25:28 +0000
@@ -484,7 +484,7 @@
                 service_item = ServiceItem()
                 service_item.title = 'webkit'
                 service_item.processor = 'webkit'
-                path = os.path.join(AppLocation.get_section_data_path('themes'),
+                path = os.path.join(str(AppLocation.get_section_data_path('themes')),
                                     self.service_item.theme_data.theme_name)
                 service_item.add_from_command(path,
                                               self.service_item.theme_data.background_filename,

=== modified file 'openlp/core/ui/mainwindow.py'
--- openlp/core/ui/mainwindow.py	2017-05-30 18:42:35 +0000
+++ openlp/core/ui/mainwindow.py	2017-08-02 06:25:28 +0000
@@ -305,9 +305,9 @@
         # Give QT Extra Hint that this is an About Menu Item
         self.about_item.setMenuRole(QtWidgets.QAction.AboutRole)
         if is_win():
-            self.local_help_file = os.path.join(AppLocation.get_directory(AppLocation.AppDir), 'OpenLP.chm')
+            self.local_help_file = os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)), 'OpenLP.chm')
         elif is_macosx():
-            self.local_help_file = os.path.join(AppLocation.get_directory(AppLocation.AppDir),
+            self.local_help_file = os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)),
                                                 '..', 'Resources', 'OpenLP.help')
         self.user_manual_item = create_action(main_window, 'userManualItem', icon=':/system/system_help_contents.png',
                                               can_shortcuts=True, category=UiStrings().Help,
@@ -788,7 +788,7 @@
         """
         Open data folder
         """
-        path = AppLocation.get_data_path()
+        path = str(AppLocation.get_data_path())
         QtGui.QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(path))
 
     def on_update_theme_images(self):
@@ -1438,7 +1438,7 @@
         settings = QtCore.QSettings()
         settings.setValue('advanced/data path', self.new_data_path)
         # Check if the new data path is our default.
-        if self.new_data_path == AppLocation.get_directory(AppLocation.DataDir):
+        if self.new_data_path == str(AppLocation.get_directory(AppLocation.DataDir)):
             settings.remove('advanced/data path')
         self.application.set_normal_cursor()
 

=== modified file 'openlp/core/ui/printserviceform.py'
--- openlp/core/ui/printserviceform.py	2017-06-04 12:14:23 +0000
+++ openlp/core/ui/printserviceform.py	2017-08-02 06:25:28 +0000
@@ -176,7 +176,7 @@
         html_data = self._add_element('html')
         self._add_element('head', parent=html_data)
         self._add_element('title', self.title_line_edit.text(), html_data.head)
-        css_path = os.path.join(AppLocation.get_data_path(), 'serviceprint', 'service_print.css')
+        css_path = os.path.join(str(AppLocation.get_data_path()), 'serviceprint', 'service_print.css')
         custom_css = get_text_file_string(css_path)
         if not custom_css:
             custom_css = DEFAULT_CSS

=== modified file 'openlp/core/ui/servicemanager.py'
--- openlp/core/ui/servicemanager.py	2017-06-27 17:42:54 +0000
+++ openlp/core/ui/servicemanager.py	2017-08-02 06:25:28 +0000
@@ -223,7 +223,7 @@
         self.service_manager_list.itemExpanded.connect(self.expanded)
         # Last little bits of setting up
         self.service_theme = Settings().value(self.main_window.service_manager_settings_section + '/service theme')
-        self.service_path = AppLocation.get_section_data_path('servicemanager')
+        self.service_path = str(AppLocation.get_section_data_path('servicemanager'))
         # build the drag and drop context menu
         self.dnd_menu = QtWidgets.QMenu()
         self.new_action = self.dnd_menu.addAction(translate('OpenLP.ServiceManager', '&Add New Item'))

=== modified file 'openlp/core/ui/thememanager.py'
--- openlp/core/ui/thememanager.py	2017-06-01 06:18:47 +0000
+++ openlp/core/ui/thememanager.py	2017-08-02 06:25:28 +0000
@@ -159,7 +159,7 @@
         """
         Set up the theme path variables
         """
-        self.path = AppLocation.get_section_data_path(self.settings_section)
+        self.path = str(AppLocation.get_section_data_path(self.settings_section))
         check_directory_exists(self.path)
         self.thumb_path = os.path.join(self.path, 'thumbnails')
         check_directory_exists(self.thumb_path)
@@ -445,7 +445,7 @@
         self.application.set_busy_cursor()
         files = AppLocation.get_files(self.settings_section, '.otz')
         for theme_file in files:
-            theme_file = os.path.join(self.path, theme_file)
+            theme_file = os.path.join(self.path, str(theme_file))
             self.unzip_theme(theme_file, self.path)
             delete_file(theme_file)
         files = AppLocation.get_files(self.settings_section, '.png')
@@ -470,6 +470,7 @@
         files.sort(key=lambda file_name: get_locale_key(str(file_name)))
         # now process the file list of png files
         for name in files:
+            name = str(name)
             # check to see file is in theme root directory
             theme = os.path.join(self.path, name)
             if os.path.exists(theme):

=== modified file 'openlp/plugins/bibles/forms/bibleimportform.py'
--- openlp/plugins/bibles/forms/bibleimportform.py	2017-05-30 18:50:39 +0000
+++ openlp/plugins/bibles/forms/bibleimportform.py	2017-08-02 06:25:28 +0000
@@ -584,7 +584,7 @@
         elif self.currentPage() == self.license_details_page:
             license_version = self.field('license_version')
             license_copyright = self.field('license_copyright')
-            path = AppLocation.get_section_data_path('bibles')
+            path = str(AppLocation.get_section_data_path('bibles'))
             if not license_version:
                 critical_error_message_box(
                     UiStrings().EmptyField,

=== modified file 'openlp/plugins/bibles/lib/db.py'
--- openlp/plugins/bibles/lib/db.py	2017-06-01 06:18:47 +0000
+++ openlp/plugins/bibles/lib/db.py	2017-08-02 06:25:28 +0000
@@ -470,7 +470,7 @@
         Return the cursor object. Instantiate one if it doesn't exist yet.
         """
         if BiblesResourcesDB.cursor is None:
-            file_path = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir),
+            file_path = os.path.join(str(AppLocation.get_directory(AppLocation.PluginsDir)),
                                      'bibles', 'resources', 'bibles_resources.sqlite')
             conn = sqlite3.connect(file_path)
             BiblesResourcesDB.cursor = conn.cursor()
@@ -759,7 +759,7 @@
         """
         if AlternativeBookNamesDB.cursor is None:
             file_path = os.path.join(
-                AppLocation.get_directory(AppLocation.DataDir), 'bibles', 'alternative_book_names.sqlite')
+                str(AppLocation.get_directory(AppLocation.DataDir)), 'bibles', 'alternative_book_names.sqlite')
             if not os.path.exists(file_path):
                 # create new DB, create table alternative_book_names
                 AlternativeBookNamesDB.conn = sqlite3.connect(file_path)

=== modified file 'openlp/plugins/bibles/lib/importers/http.py'
--- openlp/plugins/bibles/lib/importers/http.py	2017-05-30 18:42:35 +0000
+++ openlp/plugins/bibles/lib/importers/http.py	2017-08-02 06:25:28 +0000
@@ -325,7 +325,7 @@
         returns a list in the form [(biblename, biblekey, language_code)]
         """
         log.debug('BGExtract.get_bibles_from_http')
-        bible_url = 'https://biblegateway.com/versions/'
+        bible_url = 'https://www.biblegateway.com/versions/'
         soup = get_soup_for_bible_ref(bible_url)
         if not soup:
             return None
@@ -773,7 +773,7 @@
         return None
     try:
         page = get_web_page(reference_url, header, True)
-    except:
+    except Exception as e:
         page = None
     if not page:
         send_error_message('download')

=== modified file 'openlp/plugins/bibles/lib/manager.py'
--- openlp/plugins/bibles/lib/manager.py	2017-05-26 13:30:54 +0000
+++ openlp/plugins/bibles/lib/manager.py	2017-08-02 06:25:28 +0000
@@ -111,7 +111,7 @@
         self.settings_section = 'bibles'
         self.web = 'Web'
         self.db_cache = None
-        self.path = AppLocation.get_section_data_path(self.settings_section)
+        self.path = str(AppLocation.get_section_data_path(self.settings_section))
         self.proxy_name = Settings().value(self.settings_section + '/proxy name')
         self.suffix = '.sqlite'
         self.import_wizard = None
@@ -124,7 +124,7 @@
         of HTTPBible is loaded instead of the BibleDB class.
         """
         log.debug('Reload bibles')
-        files = AppLocation.get_files(self.settings_section, self.suffix)
+        files = [str(file) for file in AppLocation.get_files(self.settings_section, self.suffix)]
         if 'alternative_book_names.sqlite' in files:
             files.remove('alternative_book_names.sqlite')
         log.debug('Bible Files {text}'.format(text=files))

=== modified file 'openlp/plugins/images/lib/mediaitem.py'
--- openlp/plugins/images/lib/mediaitem.py	2017-05-30 18:42:35 +0000
+++ openlp/plugins/images/lib/mediaitem.py	2017-08-02 06:25:28 +0000
@@ -98,7 +98,7 @@
         self.list_view.setIconSize(QtCore.QSize(88, 50))
         self.list_view.setIndentation(self.list_view.default_indentation)
         self.list_view.allow_internal_dnd = True
-        self.service_path = os.path.join(AppLocation.get_section_data_path(self.settings_section), 'thumbnails')
+        self.service_path = os.path.join(str(AppLocation.get_section_data_path(self.settings_section)), 'thumbnails')
         check_directory_exists(self.service_path)
         # Load images from the database
         self.load_full_list(

=== modified file 'openlp/plugins/media/lib/mediaitem.py'
--- openlp/plugins/media/lib/mediaitem.py	2016-12-31 11:01:36 +0000
+++ openlp/plugins/media/lib/mediaitem.py	2017-08-02 06:25:28 +0000
@@ -300,7 +300,7 @@
         Initialize media item.
         """
         self.list_view.clear()
-        self.service_path = os.path.join(AppLocation.get_section_data_path(self.settings_section), 'thumbnails')
+        self.service_path = os.path.join(str(AppLocation.get_section_data_path(self.settings_section)), 'thumbnails')
         check_directory_exists(self.service_path)
         self.load_list(Settings().value(self.settings_section + '/media files'))
         self.rebuild_players()

=== modified file 'openlp/plugins/media/mediaplugin.py'
--- openlp/plugins/media/mediaplugin.py	2016-12-31 11:01:36 +0000
+++ openlp/plugins/media/mediaplugin.py	2017-08-02 06:25:28 +0000
@@ -75,7 +75,7 @@
         exists = process_check_binary('mediainfo')
         # If mediainfo is not in the path, try to find it in the application folder
         if not exists:
-            exists = process_check_binary(os.path.join(AppLocation.get_directory(AppLocation.AppDir), 'mediainfo'))
+            exists = process_check_binary(os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)), 'mediainfo'))
         return exists
 
     def app_startup(self):

=== modified file 'openlp/plugins/presentations/lib/pdfcontroller.py'
--- openlp/plugins/presentations/lib/pdfcontroller.py	2017-05-30 18:50:39 +0000
+++ openlp/plugins/presentations/lib/pdfcontroller.py	2017-08-02 06:25:28 +0000
@@ -122,10 +122,10 @@
                 self.mutoolbin = pdf_program
         else:
             # Fallback to autodetection
-            application_path = AppLocation.get_directory(AppLocation.AppDir)
+            application_path = str(AppLocation.get_directory(AppLocation.AppDir))
             if is_win():
                 # for windows we only accept mudraw.exe or mutool.exe in the base folder
-                application_path = AppLocation.get_directory(AppLocation.AppDir)
+                application_path = str(AppLocation.get_directory(AppLocation.AppDir))
                 if os.path.isfile(os.path.join(application_path, 'mudraw.exe')):
                     self.mudrawbin = os.path.join(application_path, 'mudraw.exe')
                 elif os.path.isfile(os.path.join(application_path, 'mutool.exe')):
@@ -142,7 +142,7 @@
                         self.gsbin = which('gs')
                 # Last option: check if mudraw or mutool is placed in OpenLP base folder
                 if not self.mudrawbin and not self.mutoolbin and not self.gsbin:
-                    application_path = AppLocation.get_directory(AppLocation.AppDir)
+                    application_path = str(AppLocation.get_directory(AppLocation.AppDir))
                     if os.path.isfile(os.path.join(application_path, 'mudraw')):
                         self.mudrawbin = os.path.join(application_path, 'mudraw')
                     elif os.path.isfile(os.path.join(application_path, 'mutool')):
@@ -199,8 +199,8 @@
         :return: The resolution dpi to be used.
         """
         # Use a postscript script to get size of the pdf. It is assumed that all pages have same size
-        gs_resolution_script = AppLocation.get_directory(
-            AppLocation.PluginsDir) + '/presentations/lib/ghostscript_get_resolution.ps'
+        gs_resolution_script = str(AppLocation.get_directory(
+            AppLocation.PluginsDir)) + '/presentations/lib/ghostscript_get_resolution.ps'
         # Run the script on the pdf to get the size
         runlog = []
         try:

=== modified file 'openlp/plugins/presentations/lib/pptviewcontroller.py'
--- openlp/plugins/presentations/lib/pptviewcontroller.py	2017-05-14 10:11:10 +0000
+++ openlp/plugins/presentations/lib/pptviewcontroller.py	2017-08-02 06:25:28 +0000
@@ -85,7 +85,7 @@
             if self.process:
                 return
             log.debug('start PPTView')
-            dll_path = os.path.join(AppLocation.get_directory(AppLocation.AppDir),
+            dll_path = os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)),
                                     'plugins', 'presentations', 'lib', 'pptviewlib', 'pptviewlib.dll')
             self.process = cdll.LoadLibrary(dll_path)
             if log.isEnabledFor(logging.DEBUG):

=== modified file 'openlp/plugins/presentations/lib/presentationcontroller.py'
--- openlp/plugins/presentations/lib/presentationcontroller.py	2016-12-31 11:01:36 +0000
+++ openlp/plugins/presentations/lib/presentationcontroller.py	2017-08-02 06:25:28 +0000
@@ -415,8 +415,9 @@
         self.document_class = document_class
         self.settings_section = self.plugin.settings_section
         self.available = None
-        self.temp_folder = os.path.join(AppLocation.get_section_data_path(self.settings_section), name)
-        self.thumbnail_folder = os.path.join(AppLocation.get_section_data_path(self.settings_section), 'thumbnails')
+        self.temp_folder = os.path.join(str(AppLocation.get_section_data_path(self.settings_section)), name)
+        self.thumbnail_folder = os.path.join(
+            str(AppLocation.get_section_data_path(self.settings_section)), 'thumbnails')
         self.thumbnail_prefix = 'slide'
         check_directory_exists(self.thumbnail_folder)
         check_directory_exists(self.temp_folder)

=== modified file 'openlp/plugins/remotes/lib/httprouter.py'
--- openlp/plugins/remotes/lib/httprouter.py	2017-01-25 21:17:27 +0000
+++ openlp/plugins/remotes/lib/httprouter.py	2017-08-02 06:25:28 +0000
@@ -171,8 +171,8 @@
         ]
         self.settings_section = 'remotes'
         self.translate()
-        self.html_dir = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir), 'remotes', 'html')
-        self.config_dir = os.path.join(AppLocation.get_data_path(), 'stages')
+        self.html_dir = os.path.join(str(AppLocation.get_directory(AppLocation.PluginsDir)), 'remotes', 'html')
+        self.config_dir = os.path.join(str(AppLocation.get_data_path()), 'stages')
 
     def do_post_processor(self):
         """
@@ -456,7 +456,7 @@
             if controller_name in supported_controllers:
                 full_path = urllib.parse.unquote(file_name)
                 if '..' not in full_path:  # no hacking please
-                    full_path = os.path.normpath(os.path.join(AppLocation.get_section_data_path(controller_name),
+                    full_path = os.path.normpath(os.path.join(str(AppLocation.get_section_data_path(controller_name)),
                                                               'thumbnails/' + full_path))
                     if os.path.exists(full_path):
                         path, just_file_name = os.path.split(full_path)
@@ -565,7 +565,7 @@
                 elif current_item.is_image() and not frame.get('image', '') and Settings().value('remotes/thumbnails'):
                     item['tag'] = str(index + 1)
                     thumbnail_path = os.path.join('images', 'thumbnails', frame['title'])
-                    full_thumbnail_path = os.path.join(AppLocation.get_data_path(), thumbnail_path)
+                    full_thumbnail_path = os.path.join(str(AppLocation.get_data_path()), thumbnail_path)
                     # Create thumbnail if it doesn't exists
                     if not os.path.exists(full_thumbnail_path):
                         create_thumb(current_item.get_frame_path(index), full_thumbnail_path, False)
@@ -582,7 +582,7 @@
                     if current_item.is_capable(ItemCapabilities.HasThumbnails) and \
                             Settings().value('remotes/thumbnails'):
                         # If the file is under our app directory tree send the portion after the match
-                        data_path = AppLocation.get_data_path()
+                        data_path = str(AppLocation.get_data_path())
                         if frame['image'][0:len(data_path)] == data_path:
                             item['img'] = urllib.request.pathname2url(frame['image'][len(data_path):])
                     item['text'] = str(frame['title'])

=== modified file 'openlp/plugins/songs/forms/editsongform.py'
--- openlp/plugins/songs/forms/editsongform.py	2017-06-09 06:06:49 +0000
+++ openlp/plugins/songs/forms/editsongform.py	2017-08-02 06:25:28 +0000
@@ -1065,7 +1065,7 @@
         self.manager.save_object(self.song)
         audio_files = [a.file_name for a in self.song.media_files]
         log.debug(audio_files)
-        save_path = os.path.join(AppLocation.get_section_data_path(self.media_item.plugin.name), 'audio',
+        save_path = os.path.join(str(AppLocation.get_section_data_path(self.media_item.plugin.name)), 'audio',
                                  str(self.song.id))
         check_directory_exists(save_path)
         self.song.media_files = []

=== modified file 'openlp/plugins/songs/lib/__init__.py'
--- openlp/plugins/songs/lib/__init__.py	2017-05-22 19:07:07 +0000
+++ openlp/plugins/songs/lib/__init__.py	2017-08-02 06:25:28 +0000
@@ -538,7 +538,7 @@
         except OSError:
             log.exception('Could not remove file: {name}'.format(name=media_file.file_name))
     try:
-        save_path = os.path.join(AppLocation.get_section_data_path(song_plugin.name), 'audio', str(song_id))
+        save_path = os.path.join(str(AppLocation.get_section_data_path(song_plugin.name)), 'audio', str(song_id))
         if os.path.exists(save_path):
             os.rmdir(save_path)
     except OSError:

=== modified file 'openlp/plugins/songs/lib/importers/songimport.py'
--- openlp/plugins/songs/lib/importers/songimport.py	2017-05-30 18:42:35 +0000
+++ openlp/plugins/songs/lib/importers/songimport.py	2017-08-02 06:25:28 +0000
@@ -421,7 +421,7 @@
         :param filename: The file to copy.
         """
         if not hasattr(self, 'save_path'):
-            self.save_path = os.path.join(AppLocation.get_section_data_path(self.import_wizard.plugin.name),
+            self.save_path = os.path.join(str(AppLocation.get_section_data_path(self.import_wizard.plugin.name)),
                                           'audio', str(song_id))
         check_directory_exists(self.save_path)
         if not filename.startswith(self.save_path):

=== modified file 'openlp/plugins/songs/lib/mediaitem.py'
--- openlp/plugins/songs/lib/mediaitem.py	2017-06-09 06:06:49 +0000
+++ openlp/plugins/songs/lib/mediaitem.py	2017-08-02 06:25:28 +0000
@@ -88,9 +88,9 @@
         song.media_files = []
         for i, bga in enumerate(item.background_audio):
             dest_file = os.path.join(
-                AppLocation.get_section_data_path(self.plugin.name), 'audio', str(song.id), os.path.split(bga)[1])
+                str(AppLocation.get_section_data_path(self.plugin.name)), 'audio', str(song.id), os.path.split(bga)[1])
             check_directory_exists(os.path.split(dest_file)[0])
-            shutil.copyfile(os.path.join(AppLocation.get_section_data_path('servicemanager'), bga), dest_file)
+            shutil.copyfile(os.path.join(str(AppLocation.get_section_data_path('servicemanager')), bga), dest_file)
             song.media_files.append(MediaFile.populate(weight=i, file_name=dest_file))
         self.plugin.manager.save_object(song, True)
 
@@ -533,7 +533,8 @@
                                                                       'copy', 'For song cloning'))
             # Copy audio files from the old to the new song
             if len(old_song.media_files) > 0:
-                save_path = os.path.join(AppLocation.get_section_data_path(self.plugin.name), 'audio', str(new_song.id))
+                save_path = os.path.join(
+                    str(AppLocation.get_section_data_path(self.plugin.name)), 'audio', str(new_song.id))
                 check_directory_exists(save_path)
                 for media_file in old_song.media_files:
                     new_media_file_name = os.path.join(save_path, os.path.basename(media_file.file_name))

=== modified file 'tests/functional/openlp_core_common/test_applocation.py'
--- tests/functional/openlp_core_common/test_applocation.py	2017-04-24 05:17:55 +0000
+++ tests/functional/openlp_core_common/test_applocation.py	2017-08-02 06:25:28 +0000
@@ -24,8 +24,9 @@
 """
 import copy
 import os
+from pathlib import Path
 from unittest import TestCase
-from unittest.mock import patch
+from unittest.mock import MagicMock, patch
 
 from openlp.core.common import AppLocation, get_frozen_path
 
@@ -64,56 +65,51 @@
         """
         Test the AppLocation.get_data_path() method when a custom location is set in the settings
         """
-        with patch('openlp.core.common.applocation.Settings') as mocked_class,\
-                patch('openlp.core.common.applocation.os') as mocked_os:
-            # GIVEN: A mocked out Settings class which returns a custom data location
-            mocked_settings = mocked_class.return_value
-            mocked_settings.contains.return_value = True
-            mocked_settings.value.return_value.toString.return_value = 'custom/dir'
-            mocked_os.path.normpath.return_value = 'custom/dir'
+        # GIVEN: A mocked out Settings class which returns a custom data location
+        mocked_settings_instance = MagicMock(
+            **{'contains.return_value': True, 'value.return_value': Path('custom', 'dir')})
+        with patch('openlp.core.common.applocation.Settings', return_value=mocked_settings_instance):
 
             # WHEN: we call AppLocation.get_data_path()
             data_path = AppLocation.get_data_path()
 
             # THEN: the mocked Settings methods were called and the value returned was our set up value
-            mocked_settings.contains.assert_called_with('advanced/data path')
-            mocked_settings.value.assert_called_with('advanced/data path')
-            self.assertEqual('custom/dir', data_path, 'Result should be "custom/dir"')
+            mocked_settings_instance.contains.assert_called_with('advanced/data path')
+            mocked_settings_instance.value.assert_called_with('advanced/data path')
+            self.assertEqual(Path('custom', 'dir'), data_path, 'Result should be "custom/dir"')
 
     def test_get_files_no_section_no_extension(self):
         """
         Test the AppLocation.get_files() method with no parameters passed.
         """
-        with patch('openlp.core.common.AppLocation.get_data_path') as mocked_get_data_path, \
-                patch('openlp.core.common.applocation.os.listdir') as mocked_listdir:
-            # GIVEN: Our mocked modules/methods.
-            mocked_get_data_path.return_value = 'test/dir'
-            mocked_listdir.return_value = copy.deepcopy(FILE_LIST)
+        # GIVEN: Our mocked modules/methods.
+        with patch.object(Path, 'glob', return_value=[Path('/dir/file5.mp3'), Path('/dir/file6.mp3')]) as mocked_glob, \
+                patch('openlp.core.common.AppLocation.get_data_path', return_value=Path('/dir')):
 
             # When: Get the list of files.
             result = AppLocation.get_files()
 
+            # Then: Check if the section parameter was used correctly, and the glob argument was passed.
+            mocked_glob.assert_called_once_with('*')
+
             # Then: check if the file lists are identical.
-            self.assertListEqual(FILE_LIST, result, 'The file lists should be identical.')
+            self.assertListEqual([Path('file5.mp3'), Path('file6.mp3')], result, 'The file lists should be identical.')
 
     def test_get_files(self):
         """
         Test the AppLocation.get_files() method with all parameters passed.
         """
-        with patch('openlp.core.common.AppLocation.get_data_path') as mocked_get_data_path, \
-                patch('openlp.core.common.applocation.os.listdir') as mocked_listdir:
-            # GIVEN: Our mocked modules/methods.
-            mocked_get_data_path.return_value = os.path.join('test', 'dir')
-            mocked_listdir.return_value = copy.deepcopy(FILE_LIST)
+        # GIVEN: Our mocked modules/methods.
+        with patch.object(Path, 'glob', return_value=[Path('/dir/section/file5.mp3'), Path('/dir/section/file6.mp3')]) \
+                as mocked_glob, \
+                patch('openlp.core.common.AppLocation.get_data_path', return_value=Path('/dir')):
 
             # When: Get the list of files.
             result = AppLocation.get_files('section', '.mp3')
 
-            # Then: Check if the section parameter was used correctly.
-            mocked_listdir.assert_called_with(os.path.join('test', 'dir', 'section'))
-
-            # Then: check if the file lists are identical.
-            self.assertListEqual(['file5.mp3', 'file6.mp3'], result, 'The file lists should be identical.')
+            # Then: The section parameter was used correctly, and the glob argument was passed..
+            mocked_glob.assert_called_once_with('*.mp3')
+            self.assertListEqual([Path('file5.mp3'), Path('file6.mp3')], result, 'The file lists should be identical.')
 
     def test_get_section_data_path(self):
         """
@@ -122,7 +118,7 @@
         with patch('openlp.core.common.AppLocation.get_data_path') as mocked_get_data_path, \
                 patch('openlp.core.common.applocation.check_directory_exists') as mocked_check_directory_exists:
             # GIVEN: A mocked out AppLocation.get_data_path()
-            mocked_get_data_path.return_value = os.path.join('test', 'dir')
+            mocked_get_data_path.return_value = Path('test', 'dir')
             mocked_check_directory_exists.return_value = True
 
             # WHEN: we call AppLocation.get_data_path()
@@ -130,7 +126,7 @@
 
             # THEN: check that all the correct methods were called, and the result is correct
             mocked_check_directory_exists.assert_called_with(os.path.join('test', 'dir', 'section'))
-            self.assertEqual(os.path.join('test', 'dir', 'section'), data_path, 'Result should be "test/dir/section"')
+            self.assertEqual(Path('test', 'dir', 'section'), data_path, 'Result should be "test/dir/section"')
 
     def test_get_directory_for_app_dir(self):
         """
@@ -138,13 +134,13 @@
         """
         # GIVEN: A mocked out _get_frozen_path function
         with patch('openlp.core.common.applocation.get_frozen_path') as mocked_get_frozen_path:
-            mocked_get_frozen_path.return_value = os.path.join('app', 'dir')
+            mocked_get_frozen_path.return_value = Path('app', 'dir')
 
             # WHEN: We call AppLocation.get_directory
             directory = AppLocation.get_directory(AppLocation.AppDir)
 
             # THEN: check that the correct directory is returned
-            self.assertEqual(os.path.join('app', 'dir'), directory, 'Directory should be "app/dir"')
+            self.assertEqual(Path('app', 'dir'), directory, 'Directory should be "app/dir"')
 
     def test_get_directory_for_plugins_dir(self):
         """
@@ -157,7 +153,7 @@
                 patch('openlp.core.common.applocation.sys') as mocked_sys:
             mocked_abspath.return_value = os.path.join('plugins', 'dir')
             mocked_split.return_value = ['openlp']
-            mocked_get_frozen_path.return_value = os.path.join('plugins', 'dir')
+            mocked_get_frozen_path.return_value = Path('dir')
             mocked_sys.frozen = 1
             mocked_sys.argv = ['openlp']
 
@@ -165,7 +161,7 @@
             directory = AppLocation.get_directory(AppLocation.PluginsDir)
 
             # THEN: The correct directory should be returned
-            self.assertEqual(os.path.join('plugins', 'dir'), directory, 'Directory should be "plugins/dir"')
+            self.assertEqual(Path('dir', 'plugins'), directory, 'Directory should be "dir/plugins"')
 
     def test_get_frozen_path_in_unfrozen_app(self):
         """

=== modified file 'tests/functional/openlp_core_common/test_common.py'
--- tests/functional/openlp_core_common/test_common.py	2017-05-15 10:15:32 +0000
+++ tests/functional/openlp_core_common/test_common.py	2017-08-02 06:25:28 +0000
@@ -79,7 +79,7 @@
         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'), \
+        with patch('openlp.core.common.AppLocation.get_directory', return_value=Path('/', 'app', 'dir', 'openlp')), \
                 patch.object(common.Path, 'glob', return_value=[]), \
                 patch('openlp.core.common.importlib.import_module') as mocked_import_module:
 
@@ -94,11 +94,12 @@
         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')]), \
+        with patch('openlp.core.common.AppLocation.get_directory', return_value=Path('/', '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
@@ -113,8 +114,9 @@
         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')]), \
+        with patch('openlp.core.common.AppLocation.get_directory', return_value=Path('/', '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:
 
@@ -129,8 +131,9 @@
         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')]), \
+        with patch('openlp.core.common.AppLocation.get_directory', return_value=Path('/', '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:
 

=== added file 'tests/functional/openlp_core_lib/test_path.py'
--- tests/functional/openlp_core_lib/test_path.py	1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_core_lib/test_path.py	2017-08-02 06:25:28 +0000
@@ -0,0 +1,88 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2017 OpenLP Developers                                   #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it     #
+# under the terms of the GNU General Public License as published by the Free  #
+# Software Foundation; version 2 of the License.                              #
+#                                                                             #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
+# more details.                                                               #
+#                                                                             #
+# You should have received a copy of the GNU General Public License along     #
+# with this program; if not, write to the Free Software Foundation, Inc., 59  #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
+###############################################################################
+"""
+Package to test the openlp.core.lib.path package.
+"""
+import os
+from pathlib import Path
+from unittest import TestCase
+
+from openlp.core.lib.path import path_to_str, str_to_path
+
+
+class TestPath(TestCase):
+    """
+    Tests for the :mod:`openlp.core.lib.path` module
+    """
+
+    def test_path_to_str_type_error(self):
+        """
+        Test that `path_to_str` raises a type error when called with an invalid type
+        """
+        # GIVEN: The `path_to_str` function
+        # WHEN: Calling `path_to_str` with an invalid Type
+        # THEN: A TypeError should have been raised
+        with self.assertRaises(TypeError):
+            path_to_str(str())
+
+    def test_path_to_str_none(self):
+        """
+        Test that `path_to_str` correctly converts the path parameter when passed with None
+        """
+        # GIVEN: The `path_to_str` function
+        # WHEN: Calling the `path_to_str` function with None
+        result = path_to_str(None)
+
+        # THEN: `path_to_str` should return an empty string
+        self.assertEqual(result, '')
+
+    def test_path_to_str_path_object(self):
+        """
+        Test that `path_to_str` correctly converts the path parameter when passed a Path object
+        """
+        # GIVEN: The `path_to_str` function
+        # WHEN: Calling the `path_to_str` function with a Path object
+        result = path_to_str(Path('test/path'))
+
+        # THEN: `path_to_str` should return a string representation of the Path object
+        self.assertEqual(result, os.path.join('test', 'path'))
+
+    def test_str_to_path_type_error(self):
+        """
+        Test that `str_to_path` raises a type error when called with an invalid type
+        """
+        # GIVEN: The `str_to_path` function
+        # WHEN: Calling `str_to_path` with an invalid Type
+        # THEN: A TypeError should have been raised
+        with self.assertRaises(TypeError):
+            str_to_path(Path())
+
+    def test_str_to_path_empty_str(self):
+        """
+        Test that `str_to_path` correctly converts the string parameter when passed with and empty string
+        """
+        # GIVEN: The `str_to_path` function
+        # WHEN: Calling the `str_to_path` function with None
+        result = str_to_path('')
+
+        # THEN: `path_to_str` should return None
+        self.assertEqual(result, None)