Skip to content

Commit

Permalink
Merge pull request #1851 from freedomofpress/pkg_resources
Browse files Browse the repository at this point in the history
Stop using pkg_resources
  • Loading branch information
rocodes authored Feb 22, 2024
2 parents 52c5205 + 0659565 commit c27f367
Show file tree
Hide file tree
Showing 10 changed files with 47 additions and 69 deletions.
6 changes: 2 additions & 4 deletions client/securedrop_client/gui/auth/dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import logging
from gettext import gettext as _

from pkg_resources import resource_string
from PyQt5.QtCore import QSize, Qt
from PyQt5.QtGui import QBrush, QPalette
from PyQt5.QtWidgets import (
Expand All @@ -40,7 +39,7 @@
from securedrop_client.gui.base import PasswordEdit
from securedrop_client.gui.base.checkbox import SDCheckBox
from securedrop_client.logic import Controller
from securedrop_client.resources import load_image
from securedrop_client.resources import load_image, load_relative_css

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -83,8 +82,7 @@ def __init__(self, parent: QWidget) -> None:
form = QWidget()

form.setObjectName("LoginDialog_form")
styles = resource_string(__name__, "dialog.css").decode("utf-8")
self.setStyleSheet(styles)
self.setStyleSheet(load_relative_css(__file__, "dialog.css"))

form_layout = QVBoxLayout()
form.setLayout(form_layout)
Expand Down
6 changes: 3 additions & 3 deletions client/securedrop_client/gui/auth/sign_in/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@

from gettext import gettext as _

from pkg_resources import resource_string
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QColor, QCursor
from PyQt5.QtWidgets import QGraphicsDropShadowEffect, QPushButton

from securedrop_client.resources import load_relative_css


class SignInButton(QPushButton):
"""
Expand All @@ -36,8 +37,7 @@ def __init__(self) -> None:

# Set css id
self.setObjectName("SignInButton")
styles = resource_string(__name__, "button.css").decode("utf-8")
self.setStyleSheet(styles)
self.setStyleSheet(load_relative_css(__file__, "button.css"))

self.setFixedHeight(40)
self.setFixedWidth(140)
Expand Down
5 changes: 2 additions & 3 deletions client/securedrop_client/gui/auth/sign_in/error_bar.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""

from pkg_resources import resource_string
from PyQt5.QtCore import QSize
from PyQt5.QtWidgets import QHBoxLayout, QWidget

from securedrop_client.gui.base import SecureQLabel, SvgLabel
from securedrop_client.resources import load_relative_css


class LoginErrorBar(QWidget):
Expand All @@ -33,8 +33,7 @@ def __init__(self) -> None:
super().__init__()

self.setObjectName("LoginErrorBar")
styles = resource_string(__name__, "error_bar.css").decode("utf-8")
self.setStyleSheet(styles)
self.setStyleSheet(load_relative_css(__file__, "error_bar.css"))

# Set layout
layout = QHBoxLayout(self)
Expand Down
6 changes: 3 additions & 3 deletions client/securedrop_client/gui/base/checkbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,21 @@

from gettext import gettext as _

from pkg_resources import resource_string
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtGui import QCursor, QFont, QMouseEvent
from PyQt5.QtWidgets import QCheckBox, QFrame, QHBoxLayout, QLabel, QWidget

from securedrop_client.resources import load_relative_css


class SDCheckBox(QWidget):
clicked = pyqtSignal()
CHECKBOX_CSS = resource_string(__name__, "checkbox.css").decode("utf-8")
PASSPHRASE_LABEL_SPACING = 1

def __init__(self) -> None:
super().__init__()
self.setObjectName("ShowPassphrase_widget")
self.setStyleSheet(self.CHECKBOX_CSS)
self.setStyleSheet(load_relative_css(__file__, "checkbox.css"))

font = QFont()
font.setLetterSpacing(QFont.AbsoluteSpacing, self.PASSPHRASE_LABEL_SPACING)
Expand Down
9 changes: 4 additions & 5 deletions client/securedrop_client/gui/base/dialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

from gettext import gettext as _

from pkg_resources import resource_string
from PyQt5.QtCore import QSize, Qt
from PyQt5.QtGui import QIcon, QKeyEvent, QPixmap
from PyQt5.QtWidgets import (
Expand All @@ -34,13 +33,13 @@
)

from securedrop_client.gui.base.misc import SvgLabel
from securedrop_client.resources import load_movie
from securedrop_client.resources import load_movie, load_relative_css


class ModalDialog(QDialog):
DIALOG_CSS = resource_string(__name__, "dialogs.css").decode("utf-8")
BUTTON_CSS = resource_string(__name__, "dialog_button.css").decode("utf-8")
ERROR_DETAILS_CSS = resource_string(__name__, "dialog_message.css").decode("utf-8")
DIALOG_CSS = load_relative_css(__file__, "dialogs.css")
BUTTON_CSS = load_relative_css(__file__, "dialog_button.css")
ERROR_DETAILS_CSS = load_relative_css(__file__, "dialog_message.css")

MARGIN = 40
NO_MARGIN = 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from gettext import gettext as _
from typing import List, Optional

from pkg_resources import resource_string
from PyQt5.QtCore import QSize, Qt, pyqtSlot
from PyQt5.QtGui import QIcon, QKeyEvent
from PyQt5.QtWidgets import QAbstractButton # noqa: F401
Expand All @@ -20,7 +19,7 @@
PassphraseWizardPage,
PreflightPage,
)
from securedrop_client.resources import load_movie
from securedrop_client.resources import load_movie, load_relative_css

logger = logging.getLogger(__name__)

Expand All @@ -34,8 +33,6 @@ class ExportWizard(QWizard):
NO_MARGIN = 0
FILENAME_WIDTH_PX = 260
FILE_OPTIONS_FONT_SPACING = 1.6
BUTTON_CSS = resource_string(__name__, "wizard_button.css").decode("utf-8")
WIZARD_CSS = resource_string(__name__, "wizard.css").decode("utf-8")

# If the drive is unlocked, we don't need a passphrase; if we do need one,
# it's populated later.
Expand Down Expand Up @@ -97,6 +94,7 @@ def _style_buttons(self) -> None:
self.button_animation = load_movie("activestate-wide.gif")
self.button_animation.setScaledSize(QSize(32, 32))
self.button_animation.frameChanged.connect(self._animate_activestate)
button_stylesheet = load_relative_css(__file__, "wizard_button.css")

# Buttons
self.next_button = self.button(QWizard.WizardButton.NextButton) # type: QAbstractButton
Expand All @@ -105,27 +103,27 @@ def _style_buttons(self) -> None:
self.finish_button = self.button(QWizard.WizardButton.FinishButton) # type: QAbstractButton

self.next_button.setObjectName("QWizardButton_PrimaryButton")
self.next_button.setStyleSheet(self.BUTTON_CSS)
self.next_button.setStyleSheet(button_stylesheet)
self.next_button.setMinimumSize(QSize(142, 40))
self.next_button.setMaximumHeight(40)
self.next_button.setIconSize(QSize(21, 21))
self.next_button.clicked.connect(self.request_export)
self.next_button.setFixedSize(QSize(142, 40))

self.cancel_button.setObjectName("QWizardButton_GenericButton")
self.cancel_button.setStyleSheet(self.BUTTON_CSS)
self.cancel_button.setStyleSheet(button_stylesheet)
self.cancel_button.setMinimumSize(QSize(142, 40))
self.cancel_button.setMaximumHeight(40)
self.cancel_button.setFixedSize(QSize(142, 40))

self.back_button.setObjectName("QWizardButton_GenericButton")
self.back_button.setStyleSheet(self.BUTTON_CSS)
self.back_button.setStyleSheet(button_stylesheet)
self.back_button.setMinimumSize(QSize(142, 40))
self.back_button.setMaximumHeight(40)
self.back_button.setFixedSize(QSize(142, 40))

self.finish_button.setObjectName("QWizardButton_GenericButton")
self.finish_button.setStyleSheet(self.BUTTON_CSS)
self.finish_button.setStyleSheet(button_stylesheet)
self.finish_button.setMinimumSize(QSize(142, 40))
self.finish_button.setMaximumHeight(40)
self.finish_button.setFixedSize(QSize(142, 40))
Expand All @@ -149,7 +147,7 @@ def _set_layout(self) -> None:
title = ("Export %(summary)s") % {"summary": self.summary_text}
self.setWindowTitle(title)
self.setObjectName("QWizard_export")
self.setStyleSheet(self.WIZARD_CSS)
self.setStyleSheet(load_relative_css(__file__, "wizard.css"))
self.setModal(False)
self.setOptions(
QWizard.NoBackButtonOnLastPage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from gettext import gettext as _
from typing import Optional

from pkg_resources import resource_string
from PyQt5.QtCore import QSize, Qt, pyqtSlot
from PyQt5.QtGui import QColor, QFont, QPixmap
from PyQt5.QtWidgets import (
Expand All @@ -21,7 +20,7 @@
from securedrop_client.gui.base.checkbox import SDCheckBox
from securedrop_client.gui.base.misc import SvgLabel
from securedrop_client.gui.conversation.export.export_wizard_constants import STATUS_MESSAGES, Pages
from securedrop_client.resources import load_movie
from securedrop_client.resources import load_movie, load_relative_css

logger = logging.getLogger(__name__)

Expand All @@ -47,9 +46,6 @@ class ExportWizardPage(QWizardPage):
appears on recoverable error to help the user advance to the next stage)
"""

WIZARD_CSS = resource_string(__name__, "wizard.css").decode("utf-8")
ERROR_DETAILS_CSS = resource_string(__name__, "wizard_message.css").decode("utf-8")

MARGIN = 40
PASSPHRASE_LABEL_SPACING = 0.5
NO_MARGIN = 0
Expand Down Expand Up @@ -94,7 +90,8 @@ def _build_layout(self) -> QVBoxLayout:
Create parent layout, draw elements, return parent layout
"""
self.setObjectName("QWizard_export_page")
self.setStyleSheet(self.WIZARD_CSS)
self.setStyleSheet(load_relative_css(__file__, "wizard.css"))

parent_layout = QVBoxLayout(self)
parent_layout.setContentsMargins(self.MARGIN, self.MARGIN, self.MARGIN, self.MARGIN)

Expand Down Expand Up @@ -139,7 +136,7 @@ def _build_layout(self) -> QVBoxLayout:
# Widget for displaying error messages (hidden by default)
self.error_details = QLabel()
self.error_details.setObjectName("QWizard_error_details")
self.error_details.setStyleSheet(self.ERROR_DETAILS_CSS)
self.error_details.setStyleSheet(load_relative_css(__file__, "wizard_message.css"))
self.error_details.setContentsMargins(
self.NO_MARGIN, self.NO_MARGIN, self.NO_MARGIN, self.NO_MARGIN
)
Expand Down
5 changes: 2 additions & 3 deletions client/securedrop_client/gui/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from securedrop_client.gui.auth import LoginDialog
from securedrop_client.gui.widgets import LeftPane, MainView, TopPane
from securedrop_client.logic import Controller
from securedrop_client.resources import load_css, load_font, load_icon
from securedrop_client.resources import load_all_fonts, load_css, load_icon

logger = logging.getLogger(__name__)

Expand All @@ -59,8 +59,7 @@ def __init__(
place for details / message contents / forms.
"""
super().__init__()
load_font("Montserrat")
load_font("Source_Sans_Pro")
load_all_fonts()
self.setStyleSheet(load_css("sdclient.css"))
self.setWindowTitle(_("SecureDrop Client {}").format(__version__))
self.setWindowIcon(load_icon(self.icon))
Expand Down
32 changes: 20 additions & 12 deletions client/securedrop_client/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,33 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""

import os
from pathlib import Path
from typing import Optional

from pkg_resources import resource_filename, resource_string
from PyQt5.QtCore import QDir
from PyQt5.QtGui import QFontDatabase, QIcon, QMovie, QPixmap
from PyQt5.QtSvg import QSvgWidget

RESOURCES_DIR = Path(__file__).parent

# Add resource directories to the search path.
QDir.addSearchPath("images", resource_filename(__name__, "images"))
QDir.addSearchPath("css", resource_filename(__name__, "css"))
QDir.addSearchPath("images", str(RESOURCES_DIR / "images"))
QDir.addSearchPath("css", str(RESOURCES_DIR / "css"))


def path(name: str, resource_dir: str = "images/") -> str:
def path(name: str) -> str:
"""
Return the filename for the referenced image.
Qt uses unix path conventions.
"""
return resource_filename(__name__, resource_dir + name)
return str(RESOURCES_DIR / "images" / name)


def load_font(font_folder_name: str) -> None:
directory = resource_filename(__name__, "fonts/") + font_folder_name
for filename in os.listdir(directory):
if filename.endswith(".ttf"):
QFontDatabase.addApplicationFont(directory + "/" + filename)
def load_all_fonts() -> None:
"""Load all the fonts in the fonts/ directory"""
for font in (RESOURCES_DIR / "fonts").glob("**/*.ttf"):
QFontDatabase.addApplicationFont(str(font.absolute()))


def load_icon(
Expand Down Expand Up @@ -134,7 +134,15 @@ def load_css(name: str) -> str:
"""
Return the contents of the referenced CSS file in the resources.
"""
return resource_string(__name__, "css/" + name).decode("utf-8")
return (RESOURCES_DIR / "css" / name).read_text()


def load_relative_css(file: str, name: str) -> str:
"""
Load CSS that's in the same directory as the file calling this.
The first argument should be __name__ and the second is the name of the CSS
"""
return (Path(file).parent / name).read_text()


def load_movie(name: str) -> QMovie:
Expand Down
20 changes: 0 additions & 20 deletions client/tests/test_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,6 @@
from tests.helper import app # noqa: F401


def test_path(mocker):
"""
Ensure the resource_filename function is called with the expected args and
the path function under test returns its result.
"""
r = mocker.patch("securedrop_client.resources.resource_filename", return_value="bar")
assert securedrop_client.resources.path("foo") == "bar"
r.assert_called_once_with(securedrop_client.resources.__name__, "images/foo")


def test_load_icon():
"""
Check the load_icon function returns the expected QIcon object.
Expand Down Expand Up @@ -52,16 +42,6 @@ def test_load_image():
assert isinstance(result, QPixmap)


def test_load_css(mocker):
"""
Ensure the resource_string function is called with the expected args and
the load_css function returns its result.
"""
rs = mocker.patch("securedrop_client.resources.resource_string", return_value=b"foo")
assert "foo" == securedrop_client.resources.load_css("foo")
rs.assert_called_once_with(securedrop_client.resources.__name__, "css/foo")


def test_load_movie():
"""
Check the load_movie function returns the expected QMovie object.
Expand Down

0 comments on commit c27f367

Please sign in to comment.