diff --git a/buzz/cli.py b/buzz/cli.py index dba9612c43..db3260d735 100644 --- a/buzz/cli.py +++ b/buzz/cli.py @@ -10,7 +10,7 @@ TranscriptionModel, ModelDownloader, ) -from buzz.store.keyring_store import KeyringStore +from buzz.store.keyring_store import get_password, Key from buzz.transcriber.transcriber import ( Task, FileTranscriptionTask, @@ -180,9 +180,7 @@ def parse(app: Application, parser: QCommandLineParser): model.model_type == ModelType.OPEN_AI_WHISPER_API and openai_access_token == "" ): - openai_access_token = KeyringStore().get_password( - key=KeyringStore.Key.OPENAI_API_KEY - ) + openai_access_token = get_password(key=Key.OPENAI_API_KEY) if openai_access_token == "": raise CommandLineError("No OpenAI access token found") diff --git a/buzz/store/keyring_store.py b/buzz/store/keyring_store.py index c3286952a2..329a264eb6 100644 --- a/buzz/store/keyring_store.py +++ b/buzz/store/keyring_store.py @@ -2,27 +2,24 @@ import logging import keyring -from keyring.errors import KeyringLocked, KeyringError, PasswordSetError from buzz.settings.settings import APP_NAME -class KeyringStore: - class Key(enum.Enum): - OPENAI_API_KEY = "OpenAI API key" +class Key(enum.Enum): + OPENAI_API_KEY = "OpenAI API key" - def get_password(self, key: Key) -> str: - try: - password = keyring.get_password(APP_NAME, username=key.value) - if password is None: - return "" - return password - except (KeyringLocked, KeyringError) as exc: - logging.warning("Unable to read from keyring: %s", exc) + +def get_password(key: Key) -> str | None: + try: + password = keyring.get_password(APP_NAME, username=key.value) + if password is None: return "" + return password + except Exception as exc: + logging.warning("Unable to read from keyring: %s", exc) + return "" + - def set_password(self, username: Key, password: str) -> None: - try: - keyring.set_password(APP_NAME, username.value, password) - except (KeyringLocked, PasswordSetError) as exc: - logging.error("Unable to write to keyring: %s", exc) +def set_password(username: Key, password: str) -> None: + keyring.set_password(APP_NAME, username.value, password) diff --git a/buzz/widgets/main_window.py b/buzz/widgets/main_window.py index 34fd2d4ce1..1e71e9a241 100644 --- a/buzz/widgets/main_window.py +++ b/buzz/widgets/main_window.py @@ -20,7 +20,7 @@ from buzz.locale import _ from buzz.settings.settings import APP_NAME, Settings from buzz.settings.shortcut_settings import ShortcutSettings -from buzz.store.keyring_store import KeyringStore +from buzz.store.keyring_store import set_password, Key from buzz.transcriber.transcriber import ( FileTranscriptionTask, TranscriptionOptions, @@ -248,7 +248,13 @@ def open_file_transcriber_widget( @staticmethod def on_openai_access_token_changed(access_token: str): - KeyringStore().set_password(KeyringStore.Key.OPENAI_API_KEY, access_token) + try: + set_password(Key.OPENAI_API_KEY, access_token) + except Exception as exc: + logging.error("Unable to write to keyring: %s", exc) + QMessageBox.critical( + None, _("Error"), _("Unable to save OpenAI API key to keyring") + ) def open_transcript_viewer(self): selected_rows = self.table_widget.selectionModel().selectedRows() diff --git a/buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py b/buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py index 3d77b28667..e1936198ad 100644 --- a/buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py +++ b/buzz/widgets/preferences_dialog/folder_watch_preferences_widget.py @@ -11,7 +11,7 @@ QVBoxLayout, ) -from buzz.store.keyring_store import KeyringStore +from buzz.store.keyring_store import Key, get_password from buzz.transcriber.transcriber import ( TranscriptionOptions, FileTranscriptionOptions, @@ -67,9 +67,7 @@ def __init__( output_folder_row.addWidget(self.output_folder_line_edit) output_folder_row.addWidget(output_folder_browse_button) - openai_access_token = KeyringStore().get_password( - KeyringStore.Key.OPENAI_API_KEY - ) + openai_access_token = get_password(Key.OPENAI_API_KEY) ( transcription_options, file_transcription_options, diff --git a/buzz/widgets/preferences_dialog/general_preferences_widget.py b/buzz/widgets/preferences_dialog/general_preferences_widget.py index e9f85605ce..5ba5669206 100644 --- a/buzz/widgets/preferences_dialog/general_preferences_widget.py +++ b/buzz/widgets/preferences_dialog/general_preferences_widget.py @@ -5,7 +5,7 @@ from openai import AuthenticationError, OpenAI from buzz.settings.settings import Settings -from buzz.store.keyring_store import KeyringStore +from buzz.store.keyring_store import get_password, Key from buzz.widgets.line_edit import LineEdit from buzz.widgets.openai_api_key_line_edit import OpenAIAPIKeyLineEdit @@ -15,14 +15,11 @@ class GeneralPreferencesWidget(QWidget): def __init__( self, - keyring_store=KeyringStore(), parent: Optional[QWidget] = None, ): super().__init__(parent) - self.openai_api_key = keyring_store.get_password( - KeyringStore.Key.OPENAI_API_KEY - ) + self.openai_api_key = get_password(Key.OPENAI_API_KEY) layout = QFormLayout(self) diff --git a/buzz/widgets/transcriber/file_transcriber_widget.py b/buzz/widgets/transcriber/file_transcriber_widget.py index dab865ff72..b0fdab35e3 100644 --- a/buzz/widgets/transcriber/file_transcriber_widget.py +++ b/buzz/widgets/transcriber/file_transcriber_widget.py @@ -13,7 +13,7 @@ from buzz.model_loader import ModelDownloader from buzz.paths import file_path_as_title from buzz.settings.settings import Settings -from buzz.store.keyring_store import KeyringStore +from buzz.store.keyring_store import get_password, Key from buzz.transcriber.transcriber import ( FileTranscriptionOptions, TranscriptionOptions, @@ -52,9 +52,7 @@ def __init__( self.setWindowTitle(self.get_title()) - openai_access_token = KeyringStore().get_password( - KeyringStore.Key.OPENAI_API_KEY - ) + openai_access_token = get_password(Key.OPENAI_API_KEY) preferences = self.load_preferences() diff --git a/buzz/widgets/transcription_task_folder_watcher.py b/buzz/widgets/transcription_task_folder_watcher.py index d230db0ac4..4ce08a3860 100644 --- a/buzz/widgets/transcription_task_folder_watcher.py +++ b/buzz/widgets/transcription_task_folder_watcher.py @@ -4,7 +4,7 @@ from PyQt6.QtCore import QFileSystemWatcher, pyqtSignal, QObject -from buzz.store.keyring_store import KeyringStore +from buzz.store.keyring_store import Key, get_password from buzz.transcriber.transcriber import FileTranscriptionTask from buzz.widgets.preferences_dialog.models.folder_watch_preferences import ( FolderWatchPreferences, @@ -49,9 +49,7 @@ def find_tasks(self): ): continue - openai_access_token = KeyringStore().get_password( - KeyringStore.Key.OPENAI_API_KEY - ) + openai_access_token = get_password(Key.OPENAI_API_KEY) ( transcription_options, file_transcription_options, diff --git a/poetry.lock b/poetry.lock index df7ebfb3b1..6ca95fc51b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1917,6 +1917,23 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +[[package]] +name = "pytest-mock" +version = "3.12.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.8" +files = [ + { file = "pytest-mock-3.12.0.tar.gz", hash = "sha256:31a40f038c22cad32287bb43932054451ff5583ff094bca6f675df2f8bc1a6e9" }, + { file = "pytest_mock-3.12.0-py3-none-any.whl", hash = "sha256:0972719a7263072da3a21c7f4773069bcc7486027d7e8e1f81d98a47e701bc4f" }, +] + +[package.dependencies] +pytest = ">=5.0" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + [[package]] name = "pytest-qt" version = "4.4.0" @@ -2834,4 +2851,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.9.13,<3.11" -content-hash = "af0faf240d7f659c167f3f644a35ccb17e7c70787be015f2d7276391e38591bb" +content-hash = "91d2a0fc397a34a3808a1ad2d0a5bd3b225b0ea03213ccc7a18e29b5def21bd9" diff --git a/pyproject.toml b/pyproject.toml index 5fc96e6774..9cf56c1c4d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,7 @@ pytest = "^7.1.3" pytest-cov = "^4.0.0" pytest-qt = "^4.1.0" pytest-xvfb = "^2.0.0" +pytest-mock = "^3.12.0" pylint = "^2.15.5" pre-commit = "^2.20.0" pytest-benchmark = "^4.0.0" diff --git a/tests/widgets/preferences_dialog/general_preferences_widget_test.py b/tests/widgets/preferences_dialog/general_preferences_widget_test.py index 573ab6843f..8654017b26 100644 --- a/tests/widgets/preferences_dialog/general_preferences_widget_test.py +++ b/tests/widgets/preferences_dialog/general_preferences_widget_test.py @@ -1,16 +1,18 @@ -from unittest.mock import Mock - from PyQt6.QtWidgets import QPushButton, QMessageBox, QLineEdit -from buzz.store.keyring_store import KeyringStore from buzz.widgets.preferences_dialog.general_preferences_widget import ( GeneralPreferencesWidget, ) class TestGeneralPreferencesWidget: - def test_should_disable_test_button_if_no_api_key(self, qtbot): - widget = GeneralPreferencesWidget(keyring_store=self.get_keyring_store("")) + def test_should_disable_test_button_if_no_api_key(self, qtbot, mocker): + mocker.patch( + "buzz.widgets.preferences_dialog.general_preferences_widget.get_password", + return_value="", + ) + + widget = GeneralPreferencesWidget() qtbot.add_widget(widget) test_button = widget.findChild(QPushButton) @@ -25,10 +27,13 @@ def test_should_disable_test_button_if_no_api_key(self, qtbot): assert test_button.isEnabled() - def test_should_test_openai_api_key(self, qtbot): - widget = GeneralPreferencesWidget( - keyring_store=self.get_keyring_store("wrong-api-key"), + def test_should_test_openai_api_key(self, qtbot, mocker): + mocker.patch( + "buzz.widgets.preferences_dialog.general_preferences_widget.get_password", + return_value="wrong-api-key", ) + + widget = GeneralPreferencesWidget() qtbot.add_widget(widget) test_button = widget.findChild(QPushButton) @@ -36,21 +41,16 @@ def test_should_test_openai_api_key(self, qtbot): test_button.click() - mock = Mock() - QMessageBox.warning = mock + message_box_warning_mock = mocker.Mock() + QMessageBox.warning = message_box_warning_mock def mock_called(): - mock.assert_called() - assert mock.call_args[0][1] == "OpenAI API Key Test" + message_box_warning_mock.assert_called() + assert message_box_warning_mock.call_args[0][1] == "OpenAI API Key Test" assert ( - mock.call_args[0][2] - == "Incorrect API key provided: wrong-ap*-key. You can find your API key at https://platform.openai.com/account/api-keys." + message_box_warning_mock.call_args[0][2] + == "Incorrect API key provided: wrong-ap*-key. You can find your " + "API key at https://platform.openai.com/account/api-keys." ) qtbot.waitUntil(mock_called) - - @staticmethod - def get_keyring_store(password: str): - keyring_store = Mock(KeyringStore) - keyring_store.get_password.return_value = password - return keyring_store