openlp-core team mailing list archive
-
openlp-core team
-
Mailing list archive
-
Message #31950
[Merge] lp:~phill-ridout/openlp/pathlib2 into lp:openlp
Phill has proposed merging lp:~phill-ridout/openlp/pathlib2 into lp:openlp.
Requested reviews:
Tim Bentley (trb143)
Raoul Snyman (raoul-snyman)
For more details, see:
https://code.launchpad.net/~phill-ridout/openlp/pathlib2/+merge/328805
Part 2
Changed the pathedit widget over to using pathlib
Added a 'patched' file dialog
Added a few utility methods
lp:~phill-ridout/openlp/pathlib2 (revision 2763)
[SUCCESS] https://ci.openlp.io/job/Branch-01-Pull/2125/
[SUCCESS] https://ci.openlp.io/job/Branch-02-Functional-Tests/2033/
[SUCCESS] https://ci.openlp.io/job/Branch-03-Interface-Tests/1938/
[SUCCESS] https://ci.openlp.io/job/Branch-04a-Code_Analysis/1315/
[SUCCESS] https://ci.openlp.io/job/Branch-04b-Test_Coverage/1157/
[SUCCESS] https://ci.openlp.io/job/Branch-04c-Code_Analysis2/287/
[FAILURE] https://ci.openlp.io/job/Branch-05-AppVeyor-Tests/132/
Stopping after failure
--
Your team OpenLP Core is subscribed to branch lp:openlp.
=== added file 'openlp/core/common/path.py'
--- openlp/core/common/path.py 1970-01-01 00:00:00 +0000
+++ openlp/core/common/path.py 2017-08-09 20:16:07 +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/__init__.py'
--- openlp/core/lib/__init__.py 2017-08-06 07:23:26 +0000
+++ openlp/core/lib/__init__.py 2017-08-09 20:16:07 +0000
@@ -608,8 +608,42 @@
return list_to_string
+def replace_params(args, kwargs, params):
+ """
+ Apply a transformation function to the specified args or kwargs
+
+ :param args: Positional arguments
+ :type args: (,)
+
+ :param kwargs: Key Word arguments
+ :type kwargs: dict
+
+ :param params: A tuple of tuples with the position and the key word to replace.
+ :type params: ((int, str, path_to_str),)
+
+ :return: The modified positional and keyword arguments
+ :rtype: (tuple, dict)
+
+
+ Usage:
+ Take a method with the following signature, and assume we which to apply the str function to arg2:
+ def method(arg1=None, arg2=None, arg3=None)
+
+ As arg2 can be specified postitionally as the second argument (1 with a zero index) or as a keyword, the we
+ would call this function as follows:
+
+ replace_params(args, kwargs, ((1, 'arg2', str),))
+ """
+ args = list(args)
+ for position, key_word, transform in params:
+ if len(args) > position:
+ args[position] = transform(args[position])
+ elif key_word in kwargs:
+ kwargs[key_word] = transform(kwargs[key_word])
+ return tuple(args), kwargs
+
+
from .exceptions import ValidationError
-from .filedialog import FileDialog
from .screen import ScreenList
from .formattingtags import FormattingTags
from .plugin import PluginStatus, StringContent, Plugin
=== removed file 'openlp/core/lib/filedialog.py'
--- openlp/core/lib/filedialog.py 2016-12-31 11:01:36 +0000
+++ openlp/core/lib/filedialog.py 1970-01-01 00:00:00 +0000
@@ -1,58 +0,0 @@
-# -*- 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 #
-###############################################################################
-"""
-Provide a work around for a bug in QFileDialog <https://bugs.launchpad.net/openlp/+bug/1209515>
-"""
-import logging
-import os
-from urllib import parse
-
-from PyQt5 import QtWidgets
-
-from openlp.core.common import UiStrings
-
-log = logging.getLogger(__name__)
-
-
-class FileDialog(QtWidgets.QFileDialog):
- """
- Subclass QFileDialog to work round a bug
- """
- @staticmethod
- def getOpenFileNames(parent, *args, **kwargs):
- """
- Reimplement getOpenFileNames to fix the way it returns some file names that url encoded when selecting multiple
- files
- """
- files, filter_used = QtWidgets.QFileDialog.getOpenFileNames(parent, *args, **kwargs)
- file_list = []
- for file in files:
- if not os.path.exists(file):
- log.info('File not found. Attempting to unquote.')
- file = parse.unquote(file)
- if not os.path.exists(file):
- log.error('File {text} not found.'.format(text=file))
- QtWidgets.QMessageBox.information(parent, UiStrings().FileNotFound,
- UiStrings().FileNotFoundMessage.format(name=file))
- continue
- file_list.append(file)
- return file_list
=== modified file 'openlp/core/lib/mediamanageritem.py'
--- openlp/core/lib/mediamanageritem.py 2017-02-18 07:23:15 +0000
+++ openlp/core/lib/mediamanageritem.py 2017-08-09 20:16:07 +0000
@@ -26,12 +26,14 @@
import os
import re
-from PyQt5 import QtCore, QtGui, QtWidgets
+from PyQt5 import QtCore, QtWidgets
from openlp.core.common import Registry, RegistryProperties, Settings, UiStrings, translate
-from openlp.core.lib import FileDialog, ServiceItem, StringContent, ServiceItemContext
+from openlp.core.common.path import path_to_str, str_to_path
+from openlp.core.lib import ServiceItem, StringContent, ServiceItemContext
from openlp.core.lib.searchedit import SearchEdit
from openlp.core.lib.ui import create_widget_action, critical_error_message_box
+from openlp.core.ui.lib.filedialog import FileDialog
from openlp.core.ui.lib.listwidgetwithdnd import ListWidgetWithDnD
from openlp.core.ui.lib.toolbar import OpenLPToolbar
@@ -309,13 +311,14 @@
"""
Add a file to the list widget to make it available for showing
"""
- files = FileDialog.getOpenFileNames(self, self.on_new_prompt,
- Settings().value(self.settings_section + '/last directory'),
- self.on_new_file_masks)
- log.info('New files(s) {files}'.format(files=files))
- if files:
+ file_paths, selected_filter = FileDialog.getOpenFileNames(
+ self, self.on_new_prompt,
+ str_to_path(Settings().value(self.settings_section + '/last directory')),
+ self.on_new_file_masks)
+ log.info('New files(s) {file_paths}'.format(file_paths=file_paths))
+ if file_paths:
self.application.set_busy_cursor()
- self.validate_and_load(files)
+ self.validate_and_load([path_to_str(path) for path in file_paths])
self.application.set_normal_cursor()
def load_file(self, data):
=== modified file 'openlp/core/ui/advancedtab.py'
--- openlp/core/ui/advancedtab.py 2017-08-01 20:59:41 +0000
+++ openlp/core/ui/advancedtab.py 2017-08-09 20:16:07 +0000
@@ -30,6 +30,7 @@
from openlp.core.common import AppLocation, Settings, SlideLimits, UiStrings, translate
from openlp.core.common.languagemanager import format_time
+from openlp.core.common.path import path_to_str
from openlp.core.lib import SettingsTab, build_icon
from openlp.core.ui.lib import PathEdit, PathType
@@ -156,7 +157,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=str(AppLocation.get_directory(AppLocation.DataDir)))
+ default_path=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 +374,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 = str(AppLocation.get_data_path())
+ self.data_directory_path_edit.path = 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,12 +498,12 @@
'closed.').format(path=new_data_path),
defaultButton=QtWidgets.QMessageBox.No)
if answer != QtWidgets.QMessageBox.Yes:
- self.data_directory_path_edit.path = str(AppLocation.get_data_path())
+ self.data_directory_path_edit.path = AppLocation.get_data_path()
return
# Check if data already exists here.
- self.check_data_overwrite(new_data_path)
+ self.check_data_overwrite(path_to_str(new_data_path))
# Save the new location.
- self.main_window.set_new_data_path(new_data_path)
+ self.main_window.set_new_data_path(path_to_str(new_data_path))
self.data_directory_cancel_button.show()
def on_data_directory_copy_check_box_toggled(self):
@@ -550,7 +551,7 @@
"""
Cancel the data directory location change
"""
- self.data_directory_path_edit.path = str(AppLocation.get_data_path())
+ self.data_directory_path_edit.path = 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/generaltab.py'
--- openlp/core/ui/generaltab.py 2017-05-22 19:56:54 +0000
+++ openlp/core/ui/generaltab.py 2017-08-09 20:16:07 +0000
@@ -23,10 +23,12 @@
The general tab of the configuration dialog.
"""
import logging
+from pathlib import Path
from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import Registry, Settings, UiStrings, translate, get_images_filter
+from openlp.core.common.path import path_to_str, str_to_path
from openlp.core.lib import SettingsTab, ScreenList
from openlp.core.ui.lib import ColorButton, PathEdit
@@ -172,7 +174,8 @@
self.logo_layout.setObjectName('logo_layout')
self.logo_file_label = QtWidgets.QLabel(self.logo_group_box)
self.logo_file_label.setObjectName('logo_file_label')
- self.logo_file_path_edit = PathEdit(self.logo_group_box, default_path=':/graphics/openlp-splash-screen.png')
+ self.logo_file_path_edit = PathEdit(self.logo_group_box,
+ default_path=Path(':/graphics/openlp-splash-screen.png'))
self.logo_layout.addRow(self.logo_file_label, self.logo_file_path_edit)
self.logo_color_label = QtWidgets.QLabel(self.logo_group_box)
self.logo_color_label.setObjectName('logo_color_label')
@@ -266,7 +269,7 @@
self.audio_group_box.setTitle(translate('OpenLP.GeneralTab', 'Background Audio'))
self.start_paused_check_box.setText(translate('OpenLP.GeneralTab', 'Start background audio paused'))
self.repeat_list_check_box.setText(translate('OpenLP.GeneralTab', 'Repeat track list'))
- self.logo_file_path_edit.dialog_caption = dialog_caption = translate('OpenLP.AdvancedTab', 'Select Logo File')
+ self.logo_file_path_edit.dialog_caption = translate('OpenLP.AdvancedTab', 'Select Logo File')
self.logo_file_path_edit.filters = '{text};;{names} (*)'.format(
text=get_images_filter(), names=UiStrings().AllFiles)
@@ -291,7 +294,7 @@
self.auto_open_check_box.setChecked(settings.value('auto open'))
self.show_splash_check_box.setChecked(settings.value('show splash'))
self.logo_background_color = settings.value('logo background color')
- self.logo_file_path_edit.path = settings.value('logo file')
+ self.logo_file_path_edit.path = str_to_path(settings.value('logo file'))
self.logo_hide_on_startup_check_box.setChecked(settings.value('logo hide on startup'))
self.logo_color_button.color = self.logo_background_color
self.check_for_updates_check_box.setChecked(settings.value('update check'))
@@ -325,7 +328,7 @@
settings.setValue('auto open', self.auto_open_check_box.isChecked())
settings.setValue('show splash', self.show_splash_check_box.isChecked())
settings.setValue('logo background color', self.logo_background_color)
- settings.setValue('logo file', self.logo_file_path_edit.path)
+ settings.setValue('logo file', path_to_str(self.logo_file_path_edit.path))
settings.setValue('logo hide on startup', self.logo_hide_on_startup_check_box.isChecked())
settings.setValue('update check', self.check_for_updates_check_box.isChecked())
settings.setValue('save prompt', self.save_check_service_check_box.isChecked())
=== added file 'openlp/core/ui/lib/filedialog.py'
--- openlp/core/ui/lib/filedialog.py 1970-01-01 00:00:00 +0000
+++ openlp/core/ui/lib/filedialog.py 2017-08-09 20:16:07 +0000
@@ -0,0 +1,114 @@
+# -*- 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 #
+###############################################################################
+""" Patch the QFileDialog so it accepts and returns Path objects"""
+from functools import wraps
+from pathlib import Path
+
+from PyQt5 import QtWidgets
+
+from openlp.core.common.path import path_to_str, str_to_path
+from openlp.core.lib import replace_params
+
+
+class FileDialog(QtWidgets.QFileDialog):
+ @classmethod
+ def getExistingDirectory(cls, *args, **kwargs):
+ """
+ Wraps `getExistingDirectory` so that it can be called with, and return Path objects
+
+ :type parent: QtWidgets.QWidget or None
+ :type caption: str
+ :type directory: pathlib.Path
+ :type options: QtWidgets.QFileDialog.Options
+ :rtype: tuple[Path, str]
+ """
+ args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
+
+ return_value = super().getExistingDirectory(*args, **kwargs)
+
+ # getExistingDirectory returns a str that represents the path. The string is empty if the user cancels the
+ # dialog.
+ return str_to_path(return_value)
+
+ @classmethod
+ def getOpenFileName(cls, *args, **kwargs):
+ """
+ Wraps `getOpenFileName` so that it can be called with, and return Path objects
+
+ :type parent: QtWidgets.QWidget or None
+ :type caption: str
+ :type directory: pathlib.Path
+ :type filter: str
+ :type initialFilter: str
+ :type options: QtWidgets.QFileDialog.Options
+ :rtype: tuple[Path, str]
+ """
+ args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
+
+ file_name, selected_filter = super().getOpenFileName(*args, **kwargs)
+
+ # getOpenFileName returns a tuple. The first item is a str that represents the path. The string is empty if
+ # the user cancels the dialog.
+ return str_to_path(file_name), selected_filter
+
+ @classmethod
+ def getOpenFileNames(cls, *args, **kwargs):
+ """
+ Wrap `getOpenFileNames` so that it can be called with, and return Path objects
+
+ :type parent: QtWidgets.QWidget or None
+ :type caption: str
+ :type directory: pathlib.Path
+ :type filter: str
+ :type initialFilter: str
+ :type options: QtWidgets.QFileDialog.Options
+ :rtype: tuple[list[Path], str]
+ """
+ args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
+
+ file_names, selected_filter = super().getOpenFileNames(*args, **kwargs)
+
+ # getSaveFileName returns a tuple. The first item is a list of str's that represents the path. The list is
+ # empty if the user cancels the dialog.
+ paths = [str_to_path(path) for path in file_names]
+ return paths, selected_filter
+
+ @classmethod
+ def getSaveFileName(cls, *args, **kwargs):
+ """
+ Wrap `getSaveFileName` so that it can be called with, and return Path objects
+
+ :type parent: QtWidgets.QWidget or None
+ :type caption: str
+ :type directory: pathlib.Path
+ :type filter: str
+ :type initialFilter: str
+ :type options: QtWidgets.QFileDialog.Options
+ :rtype: tuple[Path or None, str]
+ """
+ args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
+
+ file_name, selected_filter = super().getSaveFileName(*args, **kwargs)
+
+ # getSaveFileName returns a tuple. The first item represents the path as a str. The string is empty if the user
+ # cancels the dialog.
+ return str_to_path(file_name), selected_filter
=== modified file 'openlp/core/ui/lib/pathedit.py' (properties changed: +x to -x)
--- openlp/core/ui/lib/pathedit.py 2017-07-04 23:13:51 +0000
+++ openlp/core/ui/lib/pathedit.py 2017-08-09 20:16:07 +0000
@@ -20,12 +20,14 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
from enum import Enum
-import os.path
+from pathlib import Path
from PyQt5 import QtCore, QtWidgets
from openlp.core.common import UiStrings, translate
+from openlp.core.common.path import path_to_str, str_to_path
from openlp.core.lib import build_icon
+from openlp.core.ui.lib.filedialog import FileDialog
class PathType(Enum):
@@ -38,11 +40,11 @@
The :class:`~openlp.core.ui.lib.pathedit.PathEdit` class subclasses QWidget to create a custom widget for use when
a file or directory needs to be selected.
"""
- pathChanged = QtCore.pyqtSignal(str)
+ pathChanged = QtCore.pyqtSignal(Path)
def __init__(self, parent=None, path_type=PathType.Files, default_path=None, dialog_caption=None, show_revert=True):
"""
- Initalise the PathEdit widget
+ Initialise the PathEdit widget
:param parent: The parent of the widget. This is just passed to the super method.
:type parent: QWidget or None
@@ -51,9 +53,9 @@
:type dialog_caption: str
:param default_path: The default path. This is set as the path when the revert button is clicked
- :type default_path: str
+ :type default_path: pathlib.Path
- :param show_revert: Used to determin if the 'revert button' should be visible.
+ :param show_revert: Used to determine if the 'revert button' should be visible.
:type show_revert: bool
:return: None
@@ -79,7 +81,6 @@
widget_layout = QtWidgets.QHBoxLayout()
widget_layout.setContentsMargins(0, 0, 0, 0)
self.line_edit = QtWidgets.QLineEdit(self)
- self.line_edit.setText(self._path)
widget_layout.addWidget(self.line_edit)
self.browse_button = QtWidgets.QToolButton(self)
self.browse_button.setIcon(build_icon(':/general/general_open.png'))
@@ -101,7 +102,7 @@
A property getter method to return the selected path.
:return: The selected path
- :rtype: str
+ :rtype: pathlib.Path
"""
return self._path
@@ -111,11 +112,15 @@
A Property setter method to set the selected path
:param path: The path to set the widget to
- :type path: str
+ :type path: pathlib.Path
+
+ :return: None
+ :rtype: None
"""
self._path = path
- self.line_edit.setText(path)
- self.line_edit.setToolTip(path)
+ text = path_to_str(path)
+ self.line_edit.setText(text)
+ self.line_edit.setToolTip(text)
@property
def path_type(self):
@@ -124,7 +129,7 @@
selecting a file or directory.
:return: The type selected
- :rtype: Enum of PathEdit
+ :rtype: PathType
"""
return self._path_type
@@ -133,8 +138,11 @@
"""
A Property setter method to set the path type
- :param path: The type of path to select
- :type path: Enum of PathEdit
+ :param path_type: The type of path to select
+ :type path_type: PathType
+
+ :return: None
+ :rtype: None
"""
self._path_type = path_type
self.update_button_tool_tips()
@@ -142,7 +150,9 @@
def update_button_tool_tips(self):
"""
Called to update the tooltips on the buttons. This is changing path types, and when the widget is initalised
+
:return: None
+ :rtype: None
"""
if self._path_type == PathType.Directories:
self.browse_button.setToolTip(translate('OpenLP.PathEdit', 'Browse for directory.'))
@@ -156,21 +166,21 @@
A handler to handle a click on the browse button.
Show the QFileDialog and process the input from the user
+
:return: None
+ :rtype: None
"""
caption = self.dialog_caption
- path = ''
+ path = None
if self._path_type == PathType.Directories:
if not caption:
caption = translate('OpenLP.PathEdit', 'Select Directory')
- path = QtWidgets.QFileDialog.getExistingDirectory(self, caption,
- self._path, QtWidgets.QFileDialog.ShowDirsOnly)
+ path = FileDialog.getExistingDirectory(self, caption, self._path, FileDialog.ShowDirsOnly)
elif self._path_type == PathType.Files:
if not caption:
caption = self.dialog_caption = translate('OpenLP.PathEdit', 'Select File')
- path, filter_used = QtWidgets.QFileDialog.getOpenFileName(self, caption, self._path, self.filters)
+ path, filter_used = FileDialog.getOpenFileName(self, caption, self._path, self.filters)
if path:
- path = os.path.normpath(path)
self.on_new_path(path)
def on_revert_button_clicked(self):
@@ -178,16 +188,21 @@
A handler to handle a click on the revert button.
Set the new path to the value of the default_path instance variable.
+
:return: None
+ :rtype: None
"""
self.on_new_path(self.default_path)
def on_line_edit_editing_finished(self):
"""
A handler to handle when the line edit has finished being edited.
+
:return: None
+ :rtype: None
"""
- self.on_new_path(self.line_edit.text())
+ path = str_to_path(self.line_edit.text())
+ self.on_new_path(path)
def on_new_path(self, path):
"""
@@ -196,9 +211,10 @@
Emits the pathChanged Signal
:param path: The new path
- :type path: str
+ :type path: pathlib.Path
:return: None
+ :rtype: None
"""
if self._path != path:
self.path = path
=== modified file 'openlp/core/ui/themeform.py'
--- openlp/core/ui/themeform.py 2017-05-30 18:50:39 +0000
+++ openlp/core/ui/themeform.py 2017-08-09 20:16:07 +0000
@@ -28,6 +28,7 @@
from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import Registry, RegistryProperties, UiStrings, translate, get_images_filter, is_not_image_file
+from openlp.core.common.path import path_to_str, str_to_path
from openlp.core.lib.theme import BackgroundType, BackgroundGradientType
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui import ThemeLayoutForm
@@ -316,11 +317,11 @@
self.setField('background_type', 1)
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Image):
self.image_color_button.color = self.theme.background_border_color
- self.image_path_edit.path = self.theme.background_filename
+ self.image_path_edit.path = str_to_path(self.theme.background_filename)
self.setField('background_type', 2)
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Video):
self.video_color_button.color = self.theme.background_border_color
- self.video_path_edit.path = self.theme.background_filename
+ self.video_path_edit.path = str_to_path(self.theme.background_filename)
self.setField('background_type', 4)
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Transparent):
self.setField('background_type', 3)
@@ -448,18 +449,18 @@
"""
self.theme.background_end_color = color
- def on_image_path_edit_path_changed(self, filename):
+ def on_image_path_edit_path_changed(self, file_path):
"""
Background Image button pushed.
"""
- self.theme.background_filename = filename
+ self.theme.background_filename = path_to_str(file_path)
self.set_background_page_values()
- def on_video_path_edit_path_changed(self, filename):
+ def on_video_path_edit_path_changed(self, file_path):
"""
Background video button pushed.
"""
- self.theme.background_filename = filename
+ self.theme.background_filename = path_to_str(file_path)
self.set_background_page_values()
def on_main_color_changed(self, color):
=== modified file 'openlp/core/ui/thememanager.py'
--- openlp/core/ui/thememanager.py 2017-08-03 04:21:19 +0000
+++ openlp/core/ui/thememanager.py 2017-08-09 20:16:07 +0000
@@ -22,7 +22,6 @@
"""
The Theme Manager manages adding, deleteing and modifying of themes.
"""
-import json
import os
import zipfile
import shutil
@@ -32,12 +31,14 @@
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, OpenLPMixin, RegistryMixin, \
UiStrings, check_directory_exists, translate, is_win, get_filesystem_encoding, delete_file
-from openlp.core.lib import FileDialog, ImageSource, ValidationError, get_text_file_string, build_icon, \
+from openlp.core.common.path import path_to_str, str_to_path
+from openlp.core.lib import ImageSource, ValidationError, get_text_file_string, build_icon, \
check_item_selected, create_thumb, validate_thumb
from openlp.core.lib.theme import Theme, BackgroundType
from openlp.core.lib.ui import critical_error_message_box, create_widget_action
from openlp.core.ui import FileRenameForm, ThemeForm
from openlp.core.ui.lib import OpenLPToolbar
+from openlp.core.ui.lib.filedialog import FileDialog
from openlp.core.common.languagemanager import get_locale_key
@@ -424,15 +425,17 @@
those files. This process will only load version 2 themes.
:param field:
"""
- files = FileDialog.getOpenFileNames(self,
- translate('OpenLP.ThemeManager', 'Select Theme Import File'),
- Settings().value(self.settings_section + '/last directory import'),
- translate('OpenLP.ThemeManager', 'OpenLP Themes (*.otz)'))
- self.log_info('New Themes {name}'.format(name=str(files)))
- if not files:
+ file_paths, selected_filter = FileDialog.getOpenFileNames(
+ self,
+ translate('OpenLP.ThemeManager', 'Select Theme Import File'),
+ str_to_path(Settings().value(self.settings_section + '/last directory import')),
+ translate('OpenLP.ThemeManager', 'OpenLP Themes (*.otz)'))
+ self.log_info('New Themes {file_paths}'.format(file_paths=file_paths))
+ if not file_paths:
return
self.application.set_busy_cursor()
- for file_name in files:
+ for file_path in file_paths:
+ file_name = path_to_str(file_path)
Settings().setValue(self.settings_section + '/last directory import', str(file_name))
self.unzip_theme(file_name, self.path)
self.load_themes()
=== modified file 'openlp/core/ui/themewizard.py'
--- openlp/core/ui/themewizard.py 2017-05-22 18:22:43 +0000
+++ openlp/core/ui/themewizard.py 2017-08-09 20:16:07 +0000
@@ -22,6 +22,8 @@
"""
The Create/Edit theme wizard
"""
+from pathlib import Path
+
from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import UiStrings, translate, is_macosx
=== modified file 'openlp/plugins/bibles/lib/importers/http.py'
--- openlp/plugins/bibles/lib/importers/http.py 2017-08-02 06:10:26 +0000
+++ openlp/plugins/bibles/lib/importers/http.py 2017-08-09 20:16:07 +0000
@@ -255,7 +255,7 @@
chapter=chapter,
version=version)
soup = get_soup_for_bible_ref(
- 'http://biblegateway.com/passage/?{url}'.format(url=url_params),
+ 'http://www.biblegateway.com/passage/?{url}'.format(url=url_params),
pre_parse_regex=r'<meta name.*?/>', pre_parse_substitute='')
if not soup:
return None
@@ -284,7 +284,7 @@
"""
log.debug('BGExtract.get_books_from_http("{version}")'.format(version=version))
url_params = urllib.parse.urlencode({'action': 'getVersionInfo', 'vid': '{version}'.format(version=version)})
- reference_url = 'http://biblegateway.com/versions/?{url}#books'.format(url=url_params)
+ reference_url = 'http://www.biblegateway.com/versions/?{url}#books'.format(url=url_params)
page = get_web_page(reference_url)
if not page:
send_error_message('download')
=== modified file 'openlp/plugins/presentations/lib/presentationtab.py'
--- openlp/plugins/presentations/lib/presentationtab.py 2017-05-22 18:27:40 +0000
+++ openlp/plugins/presentations/lib/presentationtab.py 2017-08-09 20:16:07 +0000
@@ -20,10 +20,11 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
-from PyQt5 import QtGui, QtWidgets
+from PyQt5 import QtWidgets
from openlp.core.common import Settings, UiStrings, translate
-from openlp.core.lib import SettingsTab, build_icon
+from openlp.core.common.path import path_to_str, str_to_path
+from openlp.core.lib import SettingsTab
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.lib import PathEdit
from openlp.plugins.presentations.lib.pdfcontroller import PdfController
@@ -156,7 +157,7 @@
self.program_path_edit.setEnabled(enable_pdf_program)
pdf_program = Settings().value(self.settings_section + '/pdf_program')
if pdf_program:
- self.program_path_edit.path = pdf_program
+ self.program_path_edit.path = str_to_path(pdf_program)
def save(self):
"""
@@ -192,7 +193,7 @@
Settings().setValue(setting_key, self.ppt_window_check_box.checkState())
changed = True
# Save pdf-settings
- pdf_program = self.program_path_edit.path
+ pdf_program = path_to_str(self.program_path_edit.path)
enable_pdf_program = self.pdf_program_check_box.checkState()
# If the given program is blank disable using the program
if pdf_program == '':
@@ -219,12 +220,13 @@
checkbox.setEnabled(controller.is_available())
self.set_controller_text(checkbox, controller)
- def on_program_path_edit_path_changed(self, filename):
+ def on_program_path_edit_path_changed(self, new_path):
"""
Select the mudraw or ghostscript binary that should be used.
"""
- if filename:
- if not PdfController.process_check_binary(filename):
+ new_path = path_to_str(new_path)
+ if new_path:
+ if not PdfController.process_check_binary(new_path):
critical_error_message_box(UiStrings().Error,
translate('PresentationPlugin.PresentationTab',
'The program is not ghostscript or mudraw which is required.'))
=== modified file 'openlp/plugins/songs/forms/editsongform.py'
--- openlp/plugins/songs/forms/editsongform.py 2017-08-01 20:59:41 +0000
+++ openlp/plugins/songs/forms/editsongform.py 2017-08-09 20:16:07 +0000
@@ -28,12 +28,15 @@
import re
import os
import shutil
+from pathlib import Path
from PyQt5 import QtCore, QtWidgets
from openlp.core.common import Registry, RegistryProperties, AppLocation, UiStrings, check_directory_exists, translate
-from openlp.core.lib import FileDialog, PluginStatus, MediaType, create_separated_list
+from openlp.core.common.path import path_to_str
+from openlp.core.lib import PluginStatus, MediaType, create_separated_list
from openlp.core.lib.ui import set_case_insensitive_completer, critical_error_message_box, find_and_set_in_combo_box
+from openlp.core.ui.lib.filedialog import FileDialog
from openlp.core.common.languagemanager import get_natural_key
from openlp.plugins.songs.lib import VerseType, clean_song
from openlp.plugins.songs.lib.db import Book, Song, Author, AuthorType, Topic, MediaFile, SongBookEntry
@@ -925,9 +928,10 @@
Loads file(s) from the filesystem.
"""
filters = '{text} (*)'.format(text=UiStrings().AllFiles)
- file_names = FileDialog.getOpenFileNames(self, translate('SongsPlugin.EditSongForm', 'Open File(s)'), '',
- filters)
- for filename in file_names:
+ file_paths, selected_filter = FileDialog.getOpenFileNames(
+ self, translate('SongsPlugin.EditSongForm', 'Open File(s)'), Path(), filters)
+ for file_path in file_paths:
+ filename = path_to_str(file_path)
item = QtWidgets.QListWidgetItem(os.path.split(str(filename))[1])
item.setData(QtCore.Qt.UserRole, filename)
self.audio_list_widget.addItem(item)
=== modified file 'openlp/plugins/songs/forms/songimportform.py'
--- openlp/plugins/songs/forms/songimportform.py 2017-05-30 18:42:35 +0000
+++ openlp/plugins/songs/forms/songimportform.py 2017-08-09 20:16:07 +0000
@@ -29,8 +29,9 @@
from PyQt5 import QtCore, QtWidgets
from openlp.core.common import RegistryProperties, Settings, UiStrings, translate
-from openlp.core.lib import FileDialog
+from openlp.core.common.path import path_to_str, str_to_path
from openlp.core.lib.ui import critical_error_message_box
+from openlp.core.ui.lib.filedialog import FileDialog
from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
from openlp.plugins.songs.lib.importer import SongFormat, SongFormatSelect
@@ -237,10 +238,11 @@
if filters:
filters += ';;'
filters += '{text} (*)'.format(text=UiStrings().AllFiles)
- file_names = FileDialog.getOpenFileNames(
+ file_paths, selected_filter = FileDialog.getOpenFileNames(
self, title,
- Settings().value(self.plugin.settings_section + '/last directory import'), filters)
- if file_names:
+ str_to_path(Settings().value(self.plugin.settings_section + '/last directory import')), filters)
+ if file_paths:
+ file_names = [path_to_str(file_path) for file_path in file_paths]
listbox.addItems(file_names)
Settings().setValue(self.plugin.settings_section + '/last directory import',
os.path.split(str(file_names[0]))[0])
=== modified file 'openlp/plugins/songusage/forms/songusagedetailform.py'
--- openlp/plugins/songusage/forms/songusagedetailform.py 2017-06-04 12:14:23 +0000
+++ openlp/plugins/songusage/forms/songusagedetailform.py 2017-08-09 20:16:07 +0000
@@ -27,6 +27,7 @@
from sqlalchemy.sql import and_
from openlp.core.common import RegistryProperties, Settings, check_directory_exists, translate
+from openlp.core.common.path import path_to_str, str_to_path
from openlp.core.lib.ui import critical_error_message_box
from openlp.plugins.songusage.lib.db import SongUsageItem
from .songusagedetaildialog import Ui_SongUsageDetailDialog
@@ -55,20 +56,21 @@
"""
self.from_date_calendar.setSelectedDate(Settings().value(self.plugin.settings_section + '/from date'))
self.to_date_calendar.setSelectedDate(Settings().value(self.plugin.settings_section + '/to date'))
- self.report_path_edit.path = Settings().value(self.plugin.settings_section + '/last directory export')
+ self.report_path_edit.path = str_to_path(
+ Settings().value(self.plugin.settings_section + '/last directory export'))
def on_report_path_edit_path_changed(self, file_path):
"""
Triggered when the Directory selection button is clicked
"""
- Settings().setValue(self.plugin.settings_section + '/last directory export', file_path)
+ Settings().setValue(self.plugin.settings_section + '/last directory export', path_to_str(file_path))
def accept(self):
"""
Ok was triggered so lets save the data and run the report
"""
log.debug('accept')
- path = self.report_path_edit.path
+ path = path_to_str(self.report_path_edit.path)
if not path:
self.main_window.error_message(
translate('SongUsagePlugin.SongUsageDetailForm', 'Output Path Not Selected'),
=== added file 'tests/functional/openlp_core_common/test_path.py'
--- tests/functional/openlp_core_common/test_path.py 1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_core_common/test_path.py 2017-08-09 20:16:07 +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.common.path package.
+"""
+import os
+from pathlib import Path
+from unittest import TestCase
+
+from openlp.core.common.path import path_to_str, str_to_path
+
+
+class TestPath(TestCase):
+ """
+ Tests for the :mod:`openlp.core.common.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)
=== modified file 'tests/functional/openlp_core_lib/test_file_dialog.py'
--- tests/functional/openlp_core_lib/test_file_dialog.py 2017-04-24 05:17:55 +0000
+++ tests/functional/openlp_core_lib/test_file_dialog.py 2017-08-09 20:16:07 +0000
@@ -20,12 +20,10 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
-Package to test the openlp.core.lib.filedialog package.
+Package to test the openlp.core.ui.lib.filedialog package.
"""
from unittest import TestCase
-from unittest.mock import MagicMock, call, patch
-
-from openlp.core.lib.filedialog import FileDialog
+from unittest.mock import MagicMock, patch
class TestFileDialog(TestCase):
@@ -33,9 +31,9 @@
Test the functions in the :mod:`filedialog` module.
"""
def setUp(self):
- self.os_patcher = patch('openlp.core.lib.filedialog.os')
- self.qt_gui_patcher = patch('openlp.core.lib.filedialog.QtWidgets')
- self.ui_strings_patcher = patch('openlp.core.lib.filedialog.UiStrings')
+ self.os_patcher = patch('openlp.core.ui.lib.filedialog.os')
+ self.qt_gui_patcher = patch('openlp.core.ui.lib.filedialog.QtWidgets')
+ self.ui_strings_patcher = patch('openlp.core.ui.lib.filedialog.UiStrings')
self.mocked_os = self.os_patcher.start()
self.mocked_qt_gui = self.qt_gui_patcher.start()
self.mocked_ui_strings = self.ui_strings_patcher.start()
@@ -45,52 +43,3 @@
self.os_patcher.stop()
self.qt_gui_patcher.stop()
self.ui_strings_patcher.stop()
-
- def test_get_open_file_names_canceled(self):
- """
- Test that FileDialog.getOpenFileNames() returns and empty QStringList when QFileDialog is canceled
- (returns an empty QStringList)
- """
- self.mocked_os.reset_mock()
-
- # GIVEN: An empty QStringList as a return value from QFileDialog.getOpenFileNames
- self.mocked_qt_gui.QFileDialog.getOpenFileNames.return_value = ([], [])
-
- # WHEN: FileDialog.getOpenFileNames is called
- result = FileDialog.getOpenFileNames(self.mocked_parent)
-
- # THEN: The returned value should be an empty QStringList and os.path.exists should not have been called
- assert not self.mocked_os.path.exists.called
- self.assertEqual(result, [],
- 'FileDialog.getOpenFileNames should return and empty list when QFileDialog.getOpenFileNames '
- 'is canceled')
-
- def test_returned_file_list(self):
- """
- Test that FileDialog.getOpenFileNames handles a list of files properly when QFileList.getOpenFileNames
- returns a good file name, a url encoded file name and a non-existing file
- """
- self.mocked_os.rest_mock()
- self.mocked_qt_gui.reset_mock()
-
- # GIVEN: A List of known values as a return value from QFileDialog.getOpenFileNames and a list of valid file
- # names.
- self.mocked_qt_gui.QFileDialog.getOpenFileNames.return_value = ([
- '/Valid File', '/url%20encoded%20file%20%231', '/non-existing'], [])
- self.mocked_os.path.exists.side_effect = lambda file_name: file_name in [
- '/Valid File', '/url encoded file #1']
- self.mocked_ui_strings().FileNotFound = 'File Not Found'
- self.mocked_ui_strings().FileNotFoundMessage = 'File {name} not found.\nPlease try selecting it individually.'
-
- # WHEN: FileDialog.getOpenFileNames is called
- result = FileDialog.getOpenFileNames(self.mocked_parent)
-
- # THEN: os.path.exists should have been called with known args. QmessageBox.information should have been
- # called. The returned result should correlate with the input.
- call_list = [call('/Valid File'), call('/url%20encoded%20file%20%231'), call('/url encoded file #1'),
- call('/non-existing'), call('/non-existing')]
- self.mocked_os.path.exists.assert_has_calls(call_list)
- self.mocked_qt_gui.QMessageBox.information.assert_called_with(
- self.mocked_parent, 'File Not Found',
- 'File /non-existing not found.\nPlease try selecting it individually.')
- self.assertEqual(result, ['/Valid File', '/url encoded file #1'], 'The returned file list is incorrect')
=== modified file 'tests/functional/openlp_core_lib/test_lib.py'
--- tests/functional/openlp_core_lib/test_lib.py 2017-05-17 20:06:45 +0000
+++ tests/functional/openlp_core_lib/test_lib.py 2017-08-09 20:16:07 +0000
@@ -29,10 +29,9 @@
from PyQt5 import QtCore, QtGui
-from openlp.core.lib import FormattingTags, expand_chords_for_printing
-from openlp.core.lib import build_icon, check_item_selected, clean_tags, create_thumb, create_separated_list, \
- expand_tags, get_text_file_string, image_to_byte, resize_image, str_to_bool, validate_thumb, expand_chords, \
- compare_chord_lyric, find_formatting_tags
+from openlp.core.lib import FormattingTags, build_icon, check_item_selected, clean_tags, compare_chord_lyric, \
+ create_separated_list, create_thumb, expand_chords, expand_chords_for_printing, expand_tags, find_formatting_tags, \
+ get_text_file_string, image_to_byte, replace_params, resize_image, str_to_bool, validate_thumb
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources'))
@@ -652,6 +651,38 @@
mocked_os.stat.assert_any_call(thumb_path)
assert result is False, 'The result should be False'
+ def test_replace_params_no_params(self):
+ """
+ Test replace_params when called with and empty tuple instead of parameters to replace
+ """
+ # GIVEN: Some test data
+ test_args = (1, 2)
+ test_kwargs = {'arg3': 3, 'arg4': 4}
+ test_params = tuple()
+
+ # WHEN: Calling replace_params
+ result_args, result_kwargs = replace_params(test_args, test_kwargs, test_params)
+
+ # THEN: The positional and keyword args should not have changed
+ self.assertEqual(test_args, result_args)
+ self.assertEqual(test_kwargs, result_kwargs)
+
+ def test_replace_params_params(self):
+ """
+ Test replace_params when given a positional and a keyword argument to change
+ """
+ # GIVEN: Some test data
+ test_args = (1, 2)
+ test_kwargs = {'arg3': 3, 'arg4': 4}
+ test_params = ((1, 'arg2', str), (2, 'arg3', str))
+
+ # WHEN: Calling replace_params
+ result_args, result_kwargs = replace_params(test_args, test_kwargs, test_params)
+
+ # THEN: The positional and keyword args should have have changed
+ self.assertEqual(result_args, (1, '2'))
+ self.assertEqual(result_kwargs, {'arg3': '3', 'arg4': 4})
+
def test_resize_thumb(self):
"""
Test the resize_thumb() function
=== modified file 'tests/functional/openlp_core_ui/test_themeform.py'
--- tests/functional/openlp_core_ui/test_themeform.py 2017-05-14 07:15:29 +0000
+++ tests/functional/openlp_core_ui/test_themeform.py 2017-08-09 20:16:07 +0000
@@ -22,6 +22,7 @@
"""
Package to test the openlp.core.ui.themeform package.
"""
+from pathlib import Path
from unittest import TestCase
from unittest.mock import MagicMock, patch
@@ -45,7 +46,7 @@
self.instance.theme = MagicMock()
# WHEN: `on_image_path_edit_path_changed` is clicked
- self.instance.on_image_path_edit_path_changed('/new/pat.h')
+ self.instance.on_image_path_edit_path_changed(Path('/', 'new', 'pat.h'))
# THEN: The theme background file should be set and `set_background_page_values` should have been called
self.assertEqual(self.instance.theme.background_filename, '/new/pat.h')
=== renamed file 'tests/functional/openlp_core_ui_lib/test_color_button.py' => 'tests/functional/openlp_core_ui_lib/test_colorbutton.py'
=== added file 'tests/functional/openlp_core_ui_lib/test_filedialog.py'
--- tests/functional/openlp_core_ui_lib/test_filedialog.py 1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_core_ui_lib/test_filedialog.py 2017-08-09 20:16:07 +0000
@@ -0,0 +1,188 @@
+import os
+from unittest import TestCase
+from unittest.mock import patch
+from pathlib import Path
+
+from PyQt5 import QtWidgets
+
+from openlp.core.ui.lib.filedialog import FileDialog
+
+
+class TestFileDialogPatches(TestCase):
+ """
+ Tests for the :mod:`openlp.core.ui.lib.filedialogpatches` module
+ """
+
+ def test_file_dialog(self):
+ """
+ Test that the :class:`FileDialog` instantiates correctly
+ """
+ # GIVEN: The FileDialog class
+ # WHEN: Creating an instance
+ instance = FileDialog()
+
+ # THEN: The instance should be an instance of QFileDialog
+ self.assertIsInstance(instance, QtWidgets.QFileDialog)
+
+ def test_get_existing_directory_user_abort(self):
+ """
+ Test that `getExistingDirectory` handles the case when the user cancels the dialog
+ """
+ # GIVEN: FileDialog with a mocked QDialog.getExistingDirectory method
+ # WHEN: Calling FileDialog.getExistingDirectory and the user cancels the dialog returns a empty string
+ with patch('PyQt5.QtWidgets.QFileDialog.getExistingDirectory', return_value=''):
+ result = FileDialog.getExistingDirectory()
+
+ # THEN: The result should be None
+ self.assertEqual(result, None)
+
+ def test_get_existing_directory_user_accepts(self):
+ """
+ Test that `getExistingDirectory` handles the case when the user accepts the dialog
+ """
+ # GIVEN: FileDialog with a mocked QDialog.getExistingDirectory method
+ # WHEN: Calling FileDialog.getExistingDirectory, the user chooses a file and accepts the dialog (it returns a
+ # string pointing to the directory)
+ with patch('PyQt5.QtWidgets.QFileDialog.getExistingDirectory', return_value=os.path.join('test', 'dir')):
+ result = FileDialog.getExistingDirectory()
+
+ # THEN: getExistingDirectory() should return a Path object pointing to the chosen file
+ self.assertEqual(result, Path('test', 'dir'))
+
+ def test_get_existing_directory_param_order(self):
+ """
+ Test that `getExistingDirectory` passes the parameters to `QFileDialog.getExistingDirectory` in the correct
+ order
+ """
+ # GIVEN: FileDialog
+ with patch('openlp.core.ui.lib.filedialog.QtWidgets.QFileDialog.getExistingDirectory', return_value='') \
+ as mocked_get_existing_directory:
+
+ # WHEN: Calling the getExistingDirectory method with all parameters set
+ FileDialog.getExistingDirectory('Parent', 'Caption', Path('test', 'dir'), 'Options')
+
+ # THEN: The `QFileDialog.getExistingDirectory` should have been called with the parameters in the correct
+ # order
+ mocked_get_existing_directory.assert_called_once_with('Parent', 'Caption', os.path.join('test', 'dir'),
+ 'Options')
+
+ def test_get_open_file_name_user_abort(self):
+ """
+ Test that `getOpenFileName` handles the case when the user cancels the dialog
+ """
+ # GIVEN: FileDialog with a mocked QDialog.getOpenFileName method
+ # WHEN: Calling FileDialog.getOpenFileName and the user cancels the dialog (it returns a tuple with the first
+ # value set as an empty string)
+ with patch('PyQt5.QtWidgets.QFileDialog.getOpenFileName', return_value=('', '')):
+ result = FileDialog.getOpenFileName()
+
+ # THEN: First value should be None
+ self.assertEqual(result[0], None)
+
+ def test_get_open_file_name_user_accepts(self):
+ """
+ Test that `getOpenFileName` handles the case when the user accepts the dialog
+ """
+ # GIVEN: FileDialog with a mocked QDialog.getOpenFileName method
+ # WHEN: Calling FileDialog.getOpenFileName, the user chooses a file and accepts the dialog (it returns a
+ # tuple with the first value set as an string pointing to the file)
+ with patch('PyQt5.QtWidgets.QFileDialog.getOpenFileName',
+ return_value=(os.path.join('test', 'chosen.file'), '')):
+ result = FileDialog.getOpenFileName()
+
+ # THEN: getOpenFileName() should return a tuple with the first value set to a Path object pointing to the
+ # chosen file
+ self.assertEqual(result[0], Path('test', 'chosen.file'))
+
+ def test_get_open_file_name_selected_filter(self):
+ """
+ Test that `getOpenFileName` does not modify the selectedFilter as returned by `QFileDialog.getOpenFileName`
+ """
+ # GIVEN: FileDialog with a mocked QDialog.get_save_file_name method
+ # WHEN: Calling FileDialog.getOpenFileName, and `QFileDialog.getOpenFileName` returns a known `selectedFilter`
+ with patch('PyQt5.QtWidgets.QFileDialog.getOpenFileName', return_value=('', 'selected filter')):
+ result = FileDialog.getOpenFileName()
+
+ # THEN: getOpenFileName() should return a tuple with the second value set to a the selected filter
+ self.assertEqual(result[1], 'selected filter')
+
+ def test_get_open_file_names_user_abort(self):
+ """
+ Test that `getOpenFileNames` handles the case when the user cancels the dialog
+ """
+ # GIVEN: FileDialog with a mocked QDialog.getOpenFileNames method
+ # WHEN: Calling FileDialog.getOpenFileNames and the user cancels the dialog (it returns a tuple with the first
+ # value set as an empty list)
+ with patch('PyQt5.QtWidgets.QFileDialog.getOpenFileNames', return_value=([], '')):
+ result = FileDialog.getOpenFileNames()
+
+ # THEN: First value should be an empty list
+ self.assertEqual(result[0], [])
+
+ def test_get_open_file_names_user_accepts(self):
+ """
+ Test that `getOpenFileNames` handles the case when the user accepts the dialog
+ """
+ # GIVEN: FileDialog with a mocked QDialog.getOpenFileNames method
+ # WHEN: Calling FileDialog.getOpenFileNames, the user chooses some files and accepts the dialog (it returns a
+ # tuple with the first value set as a list of strings pointing to the file)
+ with patch('PyQt5.QtWidgets.QFileDialog.getOpenFileNames',
+ return_value=([os.path.join('test', 'chosen.file1'), os.path.join('test', 'chosen.file2')], '')):
+ result = FileDialog.getOpenFileNames()
+
+ # THEN: getOpenFileNames() should return a tuple with the first value set to a list of Path objects pointing
+ # to the chosen file
+ self.assertEqual(result[0], [Path('test', 'chosen.file1'), Path('test', 'chosen.file2')])
+
+ def test_get_open_file_names_selected_filter(self):
+ """
+ Test that `getOpenFileNames` does not modify the selectedFilter as returned by `QFileDialog.getOpenFileNames`
+ """
+ # GIVEN: FileDialog with a mocked QDialog.getOpenFileNames method
+ # WHEN: Calling FileDialog.getOpenFileNames, and `QFileDialog.getOpenFileNames` returns a known
+ # `selectedFilter`
+ with patch('PyQt5.QtWidgets.QFileDialog.getOpenFileNames', return_value=([], 'selected filter')):
+ result = FileDialog.getOpenFileNames()
+
+ # THEN: getOpenFileNames() should return a tuple with the second value set to a the selected filter
+ self.assertEqual(result[1], 'selected filter')
+
+ def test_get_save_file_name_user_abort(self):
+ """
+ Test that `getSaveFileName` handles the case when the user cancels the dialog
+ """
+ # GIVEN: FileDialog with a mocked QDialog.get_save_file_name method
+ # WHEN: Calling FileDialog.getSaveFileName and the user cancels the dialog (it returns a tuple with the first
+ # value set as an empty string)
+ with patch('PyQt5.QtWidgets.QFileDialog.getSaveFileName', return_value=('', '')):
+ result = FileDialog.getSaveFileName()
+
+ # THEN: First value should be None
+ self.assertEqual(result[0], None)
+
+ def test_get_save_file_name_user_accepts(self):
+ """
+ Test that `getSaveFileName` handles the case when the user accepts the dialog
+ """
+ # GIVEN: FileDialog with a mocked QDialog.getSaveFileName method
+ # WHEN: Calling FileDialog.getSaveFileName, the user chooses a file and accepts the dialog (it returns a
+ # tuple with the first value set as an string pointing to the file)
+ with patch('PyQt5.QtWidgets.QFileDialog.getSaveFileName',
+ return_value=(os.path.join('test', 'chosen.file'), '')):
+ result = FileDialog.getSaveFileName()
+
+ # THEN: getSaveFileName() should return a tuple with the first value set to a Path object pointing to the
+ # chosen file
+ self.assertEqual(result[0], Path('test', 'chosen.file'))
+
+ def test_get_save_file_name_selected_filter(self):
+ """
+ Test that `getSaveFileName` does not modify the selectedFilter as returned by `QFileDialog.getSaveFileName`
+ """
+ # GIVEN: FileDialog with a mocked QDialog.get_save_file_name method
+ # WHEN: Calling FileDialog.getSaveFileName, and `QFileDialog.getSaveFileName` returns a known `selectedFilter`
+ with patch('PyQt5.QtWidgets.QFileDialog.getSaveFileName', return_value=('', 'selected filter')):
+ result = FileDialog.getSaveFileName()
+
+ # THEN: getSaveFileName() should return a tuple with the second value set to a the selected filter
+ self.assertEqual(result[1], 'selected filter')
=== renamed file 'tests/functional/openlp_core_ui_lib/test_path_edit.py' => 'tests/functional/openlp_core_ui_lib/test_pathedit.py'
--- tests/functional/openlp_core_ui_lib/test_path_edit.py 2017-05-13 07:35:39 +0000
+++ tests/functional/openlp_core_ui_lib/test_pathedit.py 2017-08-09 20:16:07 +0000
@@ -22,12 +22,13 @@
"""
This module contains tests for the openlp.core.ui.lib.pathedit module
"""
+import os
+from pathlib import Path
from unittest import TestCase
-
-from PyQt5 import QtWidgets
+from unittest.mock import MagicMock, PropertyMock, patch
from openlp.core.ui.lib import PathEdit, PathType
-from unittest.mock import MagicMock, PropertyMock, patch
+from openlp.core.ui.lib.filedialog import FileDialog
class TestPathEdit(TestCase):
@@ -43,11 +44,11 @@
Test the `path` property getter.
"""
# GIVEN: An instance of PathEdit with the `_path` instance variable set
- self.widget._path = 'getter/test/pat.h'
+ self.widget._path = Path('getter', 'test', 'pat.h')
# WHEN: Reading the `path` property
# THEN: The value that we set should be returned
- self.assertEqual(self.widget.path, 'getter/test/pat.h')
+ self.assertEqual(self.widget.path, Path('getter', 'test', 'pat.h'))
def test_path_setter(self):
"""
@@ -57,13 +58,13 @@
self.widget.line_edit = MagicMock()
# WHEN: Writing to the `path` property
- self.widget.path = 'setter/test/pat.h'
+ self.widget.path = Path('setter', 'test', 'pat.h')
# THEN: The `_path` instance variable should be set with the test data. The `line_edit` text and tooltip
# should have also been set.
- self.assertEqual(self.widget._path, 'setter/test/pat.h')
- self.widget.line_edit.setToolTip.assert_called_once_with('setter/test/pat.h')
- self.widget.line_edit.setText.assert_called_once_with('setter/test/pat.h')
+ self.assertEqual(self.widget._path, Path('setter', 'test', 'pat.h'))
+ self.widget.line_edit.setToolTip.assert_called_once_with(os.path.join('setter', 'test', 'pat.h'))
+ self.widget.line_edit.setText.assert_called_once_with(os.path.join('setter', 'test', 'pat.h'))
def test_path_type_getter(self):
"""
@@ -125,22 +126,20 @@
"""
# GIVEN: An instance of PathEdit with the `path_type` set to `Directories` and a mocked
# QFileDialog.getExistingDirectory
- with patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getExistingDirectory', return_value='') as \
+ with patch('openlp.core.ui.lib.pathedit.FileDialog.getExistingDirectory', return_value=None) as \
mocked_get_existing_directory, \
- patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getOpenFileName') as \
- mocked_get_open_file_name, \
- patch('openlp.core.ui.lib.pathedit.os.path.normpath') as mocked_normpath:
+ patch('openlp.core.ui.lib.pathedit.FileDialog.getOpenFileName') as mocked_get_open_file_name:
self.widget._path_type = PathType.Directories
- self.widget._path = 'test/path/'
+ self.widget._path = Path('test', 'path')
# WHEN: Calling on_browse_button_clicked
self.widget.on_browse_button_clicked()
# THEN: The FileDialog.getExistingDirectory should have been called with the default caption
- mocked_get_existing_directory.assert_called_once_with(self.widget, 'Select Directory', 'test/path/',
- QtWidgets.QFileDialog.ShowDirsOnly)
+ mocked_get_existing_directory.assert_called_once_with(self.widget, 'Select Directory',
+ Path('test', 'path'),
+ FileDialog.ShowDirsOnly)
self.assertFalse(mocked_get_open_file_name.called)
- self.assertFalse(mocked_normpath.called)
def test_on_browse_button_clicked_directory_custom_caption(self):
"""
@@ -149,45 +148,40 @@
"""
# GIVEN: An instance of PathEdit with the `path_type` set to `Directories` and a mocked
# QFileDialog.getExistingDirectory with `default_caption` set.
- with patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getExistingDirectory', return_value='') as \
+ with patch('openlp.core.ui.lib.pathedit.FileDialog.getExistingDirectory', return_value=None) as \
mocked_get_existing_directory, \
- patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getOpenFileName') as \
- mocked_get_open_file_name, \
- patch('openlp.core.ui.lib.pathedit.os.path.normpath') as mocked_normpath:
+ patch('openlp.core.ui.lib.pathedit.FileDialog.getOpenFileName') as mocked_get_open_file_name:
self.widget._path_type = PathType.Directories
- self.widget._path = 'test/path/'
+ self.widget._path = Path('test', 'path')
self.widget.dialog_caption = 'Directory Caption'
# WHEN: Calling on_browse_button_clicked
self.widget.on_browse_button_clicked()
# THEN: The FileDialog.getExistingDirectory should have been called with the custom caption
- mocked_get_existing_directory.assert_called_once_with(self.widget, 'Directory Caption', 'test/path/',
- QtWidgets.QFileDialog.ShowDirsOnly)
+ mocked_get_existing_directory.assert_called_once_with(self.widget, 'Directory Caption',
+ Path('test', 'path'),
+ FileDialog.ShowDirsOnly)
self.assertFalse(mocked_get_open_file_name.called)
- self.assertFalse(mocked_normpath.called)
def test_on_browse_button_clicked_file(self):
"""
Test the `browse_button` `clicked` handler on_browse_button_clicked when the `path_type` is set to Files.
"""
# GIVEN: An instance of PathEdit with the `path_type` set to `Files` and a mocked QFileDialog.getOpenFileName
- with patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getExistingDirectory') as \
- mocked_get_existing_directory, \
- patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getOpenFileName', return_value=('', '')) as \
- mocked_get_open_file_name, \
- patch('openlp.core.ui.lib.pathedit.os.path.normpath') as mocked_normpath:
+ with patch('openlp.core.ui.lib.pathedit.FileDialog.getExistingDirectory') as mocked_get_existing_directory, \
+ patch('openlp.core.ui.lib.pathedit.FileDialog.getOpenFileName', return_value=(None, '')) as \
+ mocked_get_open_file_name:
self.widget._path_type = PathType.Files
- self.widget._path = 'test/pat.h'
+ self.widget._path = Path('test', 'pat.h')
# WHEN: Calling on_browse_button_clicked
self.widget.on_browse_button_clicked()
# THEN: The FileDialog.getOpenFileName should have been called with the default caption
- mocked_get_open_file_name.assert_called_once_with(self.widget, 'Select File', 'test/pat.h',
+ mocked_get_open_file_name.assert_called_once_with(self.widget, 'Select File', Path('test', 'pat.h'),
self.widget.filters)
self.assertFalse(mocked_get_existing_directory.called)
- self.assertFalse(mocked_normpath.called)
def test_on_browse_button_clicked_file_custom_caption(self):
"""
@@ -196,23 +190,20 @@
"""
# GIVEN: An instance of PathEdit with the `path_type` set to `Files` and a mocked QFileDialog.getOpenFileName
# with `default_caption` set.
- with patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getExistingDirectory') as \
- mocked_get_existing_directory, \
- patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getOpenFileName', return_value=('', '')) as \
- mocked_get_open_file_name, \
- patch('openlp.core.ui.lib.pathedit.os.path.normpath') as mocked_normpath:
+ with patch('openlp.core.ui.lib.pathedit.FileDialog.getExistingDirectory') as mocked_get_existing_directory, \
+ patch('openlp.core.ui.lib.pathedit.FileDialog.getOpenFileName', return_value=(None, '')) as \
+ mocked_get_open_file_name:
self.widget._path_type = PathType.Files
- self.widget._path = 'test/pat.h'
+ self.widget._path = Path('test', 'pat.h')
self.widget.dialog_caption = 'File Caption'
# WHEN: Calling on_browse_button_clicked
self.widget.on_browse_button_clicked()
# THEN: The FileDialog.getOpenFileName should have been called with the custom caption
- mocked_get_open_file_name.assert_called_once_with(self.widget, 'File Caption', 'test/pat.h',
+ mocked_get_open_file_name.assert_called_once_with(self.widget, 'File Caption', Path('test', 'pat.h'),
self.widget.filters)
self.assertFalse(mocked_get_existing_directory.called)
- self.assertFalse(mocked_normpath.called)
def test_on_browse_button_clicked_user_cancels(self):
"""
@@ -221,16 +212,14 @@
"""
# GIVEN: An instance of PathEdit with a mocked QFileDialog.getOpenFileName which returns an empty str for the
# file path.
- with patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getOpenFileName', return_value=('', '')) as \
- mocked_get_open_file_name, \
- patch('openlp.core.ui.lib.pathedit.os.path.normpath') as mocked_normpath:
+ with patch('openlp.core.ui.lib.pathedit.FileDialog.getOpenFileName', return_value=(None, '')) as \
+ mocked_get_open_file_name:
# WHEN: Calling on_browse_button_clicked
self.widget.on_browse_button_clicked()
# THEN: normpath should not have been called
self.assertTrue(mocked_get_open_file_name.called)
- self.assertFalse(mocked_normpath.called)
def test_on_browse_button_clicked_user_accepts(self):
"""
@@ -239,9 +228,8 @@
"""
# GIVEN: An instance of PathEdit with a mocked QFileDialog.getOpenFileName which returns a str for the file
# path.
- with patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getOpenFileName',
- return_value=('/test/pat.h', '')) as mocked_get_open_file_name, \
- patch('openlp.core.ui.lib.pathedit.os.path.normpath') as mocked_normpath, \
+ with patch('openlp.core.ui.lib.pathedit.FileDialog.getOpenFileName',
+ return_value=(Path('test', 'pat.h'), '')) as mocked_get_open_file_name, \
patch.object(self.widget, 'on_new_path'):
# WHEN: Calling on_browse_button_clicked
@@ -249,7 +237,6 @@
# THEN: normpath and `on_new_path` should have been called
self.assertTrue(mocked_get_open_file_name.called)
- mocked_normpath.assert_called_once_with('/test/pat.h')
self.assertTrue(self.widget.on_new_path.called)
def test_on_revert_button_clicked(self):
@@ -258,13 +245,13 @@
"""
# GIVEN: An instance of PathEdit with a mocked `on_new_path`, and the `default_path` set.
with patch.object(self.widget, 'on_new_path') as mocked_on_new_path:
- self.widget.default_path = '/default/pat.h'
+ self.widget.default_path = Path('default', 'pat.h')
# WHEN: Calling `on_revert_button_clicked`
self.widget.on_revert_button_clicked()
# THEN: on_new_path should have been called with the default path
- mocked_on_new_path.assert_called_once_with('/default/pat.h')
+ mocked_on_new_path.assert_called_once_with(Path('default', 'pat.h'))
def test_on_line_edit_editing_finished(self):
"""
@@ -272,13 +259,13 @@
"""
# GIVEN: An instance of PathEdit with a mocked `line_edit` and `on_new_path`.
with patch.object(self.widget, 'on_new_path') as mocked_on_new_path:
- self.widget.line_edit = MagicMock(**{'text.return_value': '/test/pat.h'})
+ self.widget.line_edit = MagicMock(**{'text.return_value': 'test/pat.h'})
# WHEN: Calling `on_line_edit_editing_finished`
self.widget.on_line_edit_editing_finished()
# THEN: on_new_path should have been called with the path enetered in `line_edit`
- mocked_on_new_path.assert_called_once_with('/test/pat.h')
+ mocked_on_new_path.assert_called_once_with(Path('test', 'pat.h'))
def test_on_new_path_no_change(self):
"""
@@ -286,11 +273,11 @@
"""
# GIVEN: An instance of PathEdit with a test path and mocked `pathChanged` signal
with patch('openlp.core.ui.lib.pathedit.PathEdit.path', new_callable=PropertyMock):
- self.widget._path = '/old/test/pat.h'
+ self.widget._path = Path('/old', 'test', 'pat.h')
self.widget.pathChanged = MagicMock()
# WHEN: Calling `on_new_path` with the same path as the existing path
- self.widget.on_new_path('/old/test/pat.h')
+ self.widget.on_new_path(Path('/old', 'test', 'pat.h'))
# THEN: The `pathChanged` signal should not be emitted
self.assertFalse(self.widget.pathChanged.emit.called)
@@ -301,11 +288,11 @@
"""
# GIVEN: An instance of PathEdit with a test path and mocked `pathChanged` signal
with patch('openlp.core.ui.lib.pathedit.PathEdit.path', new_callable=PropertyMock):
- self.widget._path = '/old/test/pat.h'
+ self.widget._path = Path('/old', 'test', 'pat.h')
self.widget.pathChanged = MagicMock()
# WHEN: Calling `on_new_path` with the a new path
- self.widget.on_new_path('/new/test/pat.h')
+ self.widget.on_new_path(Path('/new', 'test', 'pat.h'))
# THEN: The `pathChanged` signal should be emitted
- self.widget.pathChanged.emit.assert_called_once_with('/new/test/pat.h')
+ self.widget.pathChanged.emit.assert_called_once_with(Path('/new', 'test', 'pat.h'))
Follow ups