Skip to content

Commit

Permalink
Stop using pkg_resources
Browse files Browse the repository at this point in the history
pkg_resources is deprecated and we're supposed to use
importlib.resources instead. Unfortunately it's very awkward
to use because it wants to support being able to read from
possibly compressed archives or other Python package distribution
formats.

Since our distribution mechanism is Debian packages, we can avoid
all of this entirely and just read the files off disk like any
other file.

Take the opportunity to simplify font loading: instead of hardcoding
each font, just load every TTF in the fonts/ directory by using a
recursive glob.

And then drop some tests that don't test anything useful

Fixes #1672.
Fixes #1836.
  • Loading branch information
legoktm committed Feb 19, 2024
1 parent c426c73 commit 70760ca
Show file tree
Hide file tree
Showing 9 changed files with 38 additions and 56 deletions.
6 changes: 3 additions & 3 deletions client/securedrop_client/gui/auth/dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
"""
import logging
from gettext import gettext as _
from pathlib import Path

from pkg_resources import resource_string
from PyQt5.QtCore import QSize, Qt
from PyQt5.QtGui import QBrush, QPalette
from PyQt5.QtWidgets import (
Expand Down Expand Up @@ -82,8 +82,8 @@ def __init__(self, parent: QWidget) -> None:
form = QWidget()

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

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 @@ -17,8 +17,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from gettext import gettext as _
from pathlib import Path

from pkg_resources import resource_string
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QColor, QCursor
from PyQt5.QtWidgets import QGraphicsDropShadowEffect, QPushButton
Expand All @@ -35,8 +35,8 @@ def __init__(self) -> None:

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

self.setFixedHeight(40)
self.setFixedWidth(140)
Expand Down
7 changes: 4 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 @@ -16,7 +16,8 @@
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from pkg_resources import resource_string
from pathlib import Path

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

Expand All @@ -32,8 +33,8 @@ def __init__(self) -> None:
super().__init__()

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

# 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 @@ -6,22 +6,22 @@
Present in the Sign-in and Export Dialog.
"""
from gettext import gettext as _
from pathlib import Path

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


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)
css = Path(__file__).parent / "checkbox.css"
self.setStyleSheet(css.read_text())

font = QFont()
font.setLetterSpacing(QFont.AbsoluteSpacing, self.PASSPHRASE_LABEL_SPACING)
Expand Down
13 changes: 8 additions & 5 deletions client/securedrop_client/gui/base/dialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from gettext import gettext as _
from pathlib import Path

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 @@ -36,10 +36,13 @@
from securedrop_client.resources import load_movie


def load_css(name: str) -> str:
return (Path(__file__).parent / name).read_text()


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")
BUTTON_CSS = load_css("dialog_button.css")
ERROR_DETAILS_CSS = load_css("dialog_message.css")

MARGIN = 40
NO_MARGIN = 0
Expand All @@ -48,7 +51,7 @@ def __init__(self, show_header: bool = True, dangerous: bool = False) -> None:
parent = QApplication.activeWindow()
super().__init__(parent)
self.setObjectName("ModalDialog")
self.setStyleSheet(self.DIALOG_CSS)
self.setStyleSheet(load_css("dialogs.css"))
self.setModal(True)

self.show_header = show_header
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
A dialog that allows journalists to export sensitive files to a USB drive.
"""
from gettext import gettext as _
from pathlib import Path
from typing import Optional

from pkg_resources import resource_string
from PyQt5.QtCore import QSize, Qt, pyqtSlot
from PyQt5.QtGui import QColor, QFont
from PyQt5.QtWidgets import QGraphicsDropShadowEffect, QLineEdit, QVBoxLayout, QWidget
Expand All @@ -17,15 +17,14 @@


class FileDialog(ModalDialog):
DIALOG_CSS = resource_string(__name__, "dialog.css").decode("utf-8")

PASSPHRASE_LABEL_SPACING = 0.5
NO_MARGIN = 0
FILENAME_WIDTH_PX = 260

def __init__(self, device: Device, file_uuid: str, file_name: str) -> None:
super().__init__()
self.setStyleSheet(self.DIALOG_CSS)
css = Path(__file__).parent / "dialog.css"
self.setStyleSheet(css.read_text())

self._device = device
self.file_uuid = file_uuid
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_css, load_all_fonts, 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
24 changes: 12 additions & 12 deletions client/securedrop_client/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,33 +17,33 @@
You should have received a copy of the GNU Affero General Public License
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 @@ -133,7 +133,7 @@ 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_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 @@ -8,16 +8,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 @@ -51,16 +41,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 70760ca

Please sign in to comment.