From d579a47a842cd36b15296014074f380b3138c53f Mon Sep 17 00:00:00 2001 From: deeplow Date: Thu, 21 Jul 2022 11:39:08 +0100 Subject: [PATCH] add type hints (1st pass: non problematic cases) --- dangerzone/cli.py | 9 +++-- dangerzone/common.py | 7 ++-- dangerzone/global_common.py | 13 +++---- dangerzone/gui/__init__.py | 31 ++++++++-------- dangerzone/gui/common.py | 28 +++++++++------ dangerzone/gui/main_window.py | 67 ++++++++++++++++++++--------------- dangerzone/settings.py | 6 ++-- 7 files changed, 92 insertions(+), 69 deletions(-) diff --git a/dangerzone/cli.py b/dangerzone/cli.py index 4840740..c7b6320 100644 --- a/dangerzone/cli.py +++ b/dangerzone/cli.py @@ -2,6 +2,7 @@ import json import logging import os import sys +from typing import Optional import click from colorama import Fore, Style @@ -11,7 +12,7 @@ from .container import convert from .global_common import GlobalCommon -def print_header(s): +def print_header(s: str) -> None: click.echo("") click.echo(Style.BRIGHT + s) @@ -20,7 +21,9 @@ def print_header(s): @click.option("--output-filename", help="Default is filename ending with -safe.pdf") @click.option("--ocr-lang", help="Language to OCR, defaults to none") @click.argument("filename", required=True) -def cli_main(output_filename, ocr_lang, filename): +def cli_main( + output_filename: Optional[str], ocr_lang: Optional[str], filename: str +) -> None: setup_logging() global_common = GlobalCommon() common = Common() @@ -92,7 +95,7 @@ def cli_main(output_filename, ocr_lang, filename): # Convert the document print_header("Converting document to safe PDF") - def stdout_callback(line): + def stdout_callback(line: str) -> None: try: status = json.loads(line) s = Style.BRIGHT + Fore.CYAN + f"{status['percentage']}% " diff --git a/dangerzone/common.py b/dangerzone/common.py index 8f376cf..8f48018 100644 --- a/dangerzone/common.py +++ b/dangerzone/common.py @@ -2,6 +2,7 @@ import os import platform import stat import tempfile +from typing import Optional import appdirs @@ -11,7 +12,7 @@ class Common(object): The Common class is a singleton of shared functionality throughout an open dangerzone window """ - def __init__(self): + def __init__(self) -> None: # Name of input and out files - self.input_filename = None - self.output_filename = None + self.input_filename: Optional[str] = None + self.output_filename: Optional[str] = None diff --git a/dangerzone/global_common.py b/dangerzone/global_common.py index 05f1dbf..35f4da9 100644 --- a/dangerzone/global_common.py +++ b/dangerzone/global_common.py @@ -8,6 +8,7 @@ import platform import shutil import subprocess import sys +from typing import Optional import appdirs import colorama @@ -24,7 +25,7 @@ class GlobalCommon(object): The GlobalCommon class is a singleton of shared functionality throughout the app """ - def __init__(self): + def __init__(self) -> None: # Version try: with open(self.get_resource_path("version.txt")) as f: @@ -210,7 +211,7 @@ class GlobalCommon(object): # Load settings self.settings = Settings(self) - def display_banner(self): + def display_banner(self) -> None: """ Raw ASCII art example: ╭──────────────────────────╮ @@ -391,7 +392,7 @@ class GlobalCommon(object): else: return shutil.which("docker") - def get_resource_path(self, filename): + def get_resource_path(self, filename: str) -> str: if getattr(sys, "dangerzone_dev", False): # Look for resources directory relative to python file project_root = pathlib.Path(__file__).parent.parent @@ -420,12 +421,12 @@ class GlobalCommon(object): else: return None - def install_container(self): + def install_container(self) -> Optional[bool]: """ Make sure the podman container is installed. Linux only. """ if self.is_container_installed(): - return + return None # Load the container into podman log.info("Installing Dangerzone container image...") @@ -454,7 +455,7 @@ class GlobalCommon(object): log.info("Container image installed") return True - def is_container_installed(self): + def is_container_installed(self) -> bool: """ See if the podman container is installed. Linux only. """ diff --git a/dangerzone/gui/__init__.py b/dangerzone/gui/__init__.py index c7be4f8..89241bd 100644 --- a/dangerzone/gui/__init__.py +++ b/dangerzone/gui/__init__.py @@ -4,6 +4,7 @@ import platform import signal import sys import uuid +from typing import Dict, NoReturn, Optional, TextIO import click from PySide2 import QtCore, QtWidgets @@ -21,14 +22,14 @@ class ApplicationWrapper(QtCore.QObject): new_window = QtCore.Signal() application_activated = QtCore.Signal() - def __init__(self): + def __init__(self) -> None: super(ApplicationWrapper, self).__init__() self.app = QtWidgets.QApplication() self.app.setQuitOnLastWindowClosed(False) self.original_event = self.app.event - def monkeypatch_event(event): + def monkeypatch_event(event: QtCore.QEvent) -> bool: # In macOS, handle the file open event if event.type() == QtCore.QEvent.FileOpen: # Skip file open events in dev mode @@ -46,7 +47,7 @@ class ApplicationWrapper(QtCore.QObject): @click.command() @click.argument("filename", required=False) -def gui_main(filename): +def gui_main(filename: str) -> bool: setup_logging() if platform.system() == "Darwin": @@ -61,16 +62,16 @@ def gui_main(filename): from strip_ansi import strip_ansi class StdoutFilter: - def __init__(self, stream): + def __init__(self, stream: TextIO) -> None: self.stream = stream - def __getattr__(self, attr_name): + def __getattr__(self, attr_name): # type: ignore [no-untyped-def] return getattr(self.stream, attr_name) - def write(self, data): + def write(self, data: str) -> None: self.stream.write(strip_ansi(data)) - def flush(self): + def flush(self) -> None: self.stream.flush() sys.stdout = StdoutFilter(sys.stdout) @@ -90,15 +91,15 @@ def gui_main(filename): # Create the system tray systray = SysTray(global_common, gui_common, app, app_wrapper) - closed_windows = {} - windows = {} + closed_windows: Dict[str, MainWindow] = {} + windows: Dict[str, MainWindow] = {} - def delete_window(window_id): + def delete_window(window_id: str) -> None: closed_windows[window_id] = windows[window_id] del windows[window_id] # Open a document in a window - def select_document(filename=None): + def select_document(filename: Optional[str] = None) -> bool: if ( len(windows) == 1 and windows[list(windows.keys())[0]].common.input_filename == None @@ -112,16 +113,16 @@ def gui_main(filename): if filename: # Validate filename - filename = os.path.abspath(os.path.expanduser(filename)) + file_path: str = os.path.abspath(os.path.expanduser(filename)) try: - open(filename, "rb") + open(file_path, "rb") except FileNotFoundError: click.echo("File not found") return False except PermissionError: click.echo("Permission denied") return False - window.common.input_filename = filename + window.common.input_filename = file_path window.content_widget.doc_selection_widget.document_selected.emit() return True @@ -135,7 +136,7 @@ def gui_main(filename): return True # Open a new window, if all windows are closed - def application_activated(): + def application_activated() -> None: if len(windows) == 0: select_document() diff --git a/dangerzone/gui/common.py b/dangerzone/gui/common.py index 97df9a7..358f939 100644 --- a/dangerzone/gui/common.py +++ b/dangerzone/gui/common.py @@ -16,6 +16,7 @@ elif platform.system() == "Linux": import getpass from xdg.DesktopEntry import DesktopEntry +from ..global_common import GlobalCommon from ..settings import Settings log = logging.getLogger(__name__) @@ -26,7 +27,7 @@ class GuiCommon(object): The GuiCommon class is a singleton of shared functionality for the GUI """ - def __init__(self, app, global_common): + def __init__(self, app, global_common: GlobalCommon) -> None: # Qt app self.app = app @@ -42,14 +43,14 @@ class GuiCommon(object): # Are we done waiting (for Docker Desktop to be installed, or for container to install) self.is_waiting_finished = False - def get_window_icon(self): + def get_window_icon(self) -> QtGui.QIcon: if platform.system() == "Windows": path = self.global_common.get_resource_path("dangerzone.ico") else: path = self.global_common.get_resource_path("icon.png") return QtGui.QIcon(path) - def open_pdf_viewer(self, filename): + def open_pdf_viewer(self, filename: str) -> None: if platform.system() == "Darwin": # Open in Preview args = ["open", "-a", "Preview.app", filename] @@ -79,8 +80,8 @@ class GuiCommon(object): log.info(Fore.YELLOW + "> " + Fore.CYAN + args_str) subprocess.Popen(args) - def _find_pdf_viewers(self): - pdf_viewers = {} + def _find_pdf_viewers(self) -> dict[str, str]: + pdf_viewers: dict[str, str] = {} if platform.system() == "Linux": # Find all .desktop files for search_path in [ @@ -111,8 +112,13 @@ class GuiCommon(object): class Alert(QtWidgets.QDialog): def __init__( - self, gui_common, global_common, message, ok_text="Ok", extra_button_text=None - ): + self, + gui_common: GuiCommon, + global_common: GlobalCommon, + message: str, + ok_text: str = "Ok", + extra_button_text: str = None, + ) -> None: super(Alert, self).__init__() self.global_common = global_common self.gui_common = gui_common @@ -167,14 +173,14 @@ class Alert(QtWidgets.QDialog): layout.addLayout(buttons_layout) self.setLayout(layout) - def clicked_ok(self): + def clicked_ok(self) -> None: self.done(QtWidgets.QDialog.Accepted) - def clicked_extra(self): + def clicked_extra(self) -> None: self.done(2) - def clicked_cancel(self): + def clicked_cancel(self) -> None: self.done(QtWidgets.QDialog.Rejected) - def launch(self): + def launch(self) -> int: return self.exec_() diff --git a/dangerzone/gui/main_window.py b/dangerzone/gui/main_window.py index 1b0de69..f15e901 100644 --- a/dangerzone/gui/main_window.py +++ b/dangerzone/gui/main_window.py @@ -5,12 +5,15 @@ import platform import shutil import subprocess import tempfile +from typing import Optional from colorama import Fore, Style from PySide2 import QtCore, QtGui, QtWidgets from ..common import Common from ..container import convert +from ..global_common import GlobalCommon +from .common import GuiCommon log = logging.getLogger(__name__) @@ -18,7 +21,9 @@ log = logging.getLogger(__name__) class MainWindow(QtWidgets.QMainWindow): delete_window = QtCore.Signal(str) - def __init__(self, global_common, gui_common, window_id): + def __init__( + self, global_common: GlobalCommon, gui_common: GuiCommon, window_id: str + ) -> None: super(MainWindow, self).__init__() self.global_common = global_common self.gui_common = gui_common @@ -78,12 +83,12 @@ class MainWindow(QtWidgets.QMainWindow): self.show() - def waiting_finished(self): + def waiting_finished(self) -> None: self.gui_common.is_waiting_finished = True self.waiting_widget.hide() self.content_widget.show() - def closeEvent(self, e): + def closeEvent(self, e: QtGui.QCloseEvent) -> None: e.accept() self.delete_window.emit(self.window_id) @@ -94,11 +99,11 @@ class MainWindow(QtWidgets.QMainWindow): class InstallContainerThread(QtCore.QThread): finished = QtCore.Signal() - def __init__(self, global_common): + def __init__(self, global_common: GlobalCommon) -> None: super(InstallContainerThread, self).__init__() self.global_common = global_common - def run(self): + def run(self) -> None: self.global_common.install_container() self.finished.emit() @@ -115,7 +120,7 @@ class WaitingWidget(QtWidgets.QWidget): # - "install_container" finished = QtCore.Signal() - def __init__(self, global_common, gui_common): + def __init__(self, global_common: GlobalCommon, gui_common: GuiCommon) -> None: super(WaitingWidget, self).__init__() self.global_common = global_common self.gui_common = gui_common @@ -148,8 +153,8 @@ class WaitingWidget(QtWidgets.QWidget): # Check the state self.check_state() - def check_state(self): - state = None + def check_state(self) -> None: + state: Optional[str] = None # Can we find the container runtime binary binary if platform.system() == "Linux": @@ -180,7 +185,7 @@ class WaitingWidget(QtWidgets.QWidget): # Update the state self.state_change(state) - def state_change(self, state): + def state_change(self, state: str) -> None: if state == "not_installed": self.label.setText( "Dangerzone Requires Docker Desktop

Download Docker Desktop, install it, and open it." @@ -204,7 +209,9 @@ class WaitingWidget(QtWidgets.QWidget): class ContentWidget(QtWidgets.QWidget): close_window = QtCore.Signal() - def __init__(self, global_common, gui_common, common): + def __init__( + self, global_common: GlobalCommon, gui_common: GuiCommon, common: Common + ) -> None: super(ContentWidget, self).__init__() self.global_common = global_common @@ -244,22 +251,22 @@ class ContentWidget(QtWidgets.QWidget): layout.addWidget(self.convert_widget, stretch=1) self.setLayout(layout) - def document_selected(self): + def document_selected(self) -> None: self.doc_selection_widget.hide() self.settings_widget.show() - def start_clicked(self): + def start_clicked(self) -> None: self.settings_widget.hide() self.convert_widget.show() - def _close_window(self): + def _close_window(self) -> None: self.close_window.emit() class DocSelectionWidget(QtWidgets.QWidget): document_selected = QtCore.Signal() - def __init__(self, common): + def __init__(self, common: Common) -> None: super(DocSelectionWidget, self).__init__() self.common = common @@ -286,7 +293,7 @@ class DocSelectionWidget(QtWidgets.QWidget): layout.addStretch() self.setLayout(layout) - def dangerous_doc_button_clicked(self): + def dangerous_doc_button_clicked(self) -> None: filename = QtWidgets.QFileDialog.getOpenFileName( self, "Open document", @@ -302,7 +309,9 @@ class SettingsWidget(QtWidgets.QWidget): start_clicked = QtCore.Signal() close_window = QtCore.Signal() - def __init__(self, global_common, gui_common, common): + def __init__( + self, global_common: GlobalCommon, gui_common: GuiCommon, common: Common + ) -> None: super(SettingsWidget, self).__init__() self.global_common = global_common self.gui_common = gui_common @@ -424,7 +433,7 @@ class SettingsWidget(QtWidgets.QWidget): if index != -1: self.open_combobox.setCurrentIndex(index) - def update_ui(self): + def update_ui(self) -> None: if platform.system() == "Windows": # Because the save checkbox is always checked in Windows, the # start button can be enabled @@ -439,7 +448,7 @@ class SettingsWidget(QtWidgets.QWidget): else: self.start_button.setEnabled(False) - def document_selected(self): + def document_selected(self) -> None: # Update the danger doc label self.dangerous_doc_label.setText( f"Suspicious: {os.path.basename(self.common.input_filename)}" @@ -450,7 +459,7 @@ class SettingsWidget(QtWidgets.QWidget): self.common.output_filename = output_filename self.save_lineedit.setText(os.path.basename(output_filename)) - def save_browse_button_clicked(self): + def save_browse_button_clicked(self) -> None: filename = QtWidgets.QFileDialog.getSaveFileName( self, "Save safe PDF as...", @@ -461,7 +470,7 @@ class SettingsWidget(QtWidgets.QWidget): self.common.output_filename = filename[0] self.save_lineedit.setText(os.path.basename(self.common.output_filename)) - def start_button_clicked(self): + def start_button_clicked(self) -> None: if self.common.output_filename is None: # If not saving, then save it to a temp file instead tmp = tempfile.mkstemp(suffix=".pdf", prefix="dangerzone_") @@ -493,13 +502,13 @@ class ConvertThread(QtCore.QThread): finished = QtCore.Signal(bool) update = QtCore.Signal(bool, str, int) - def __init__(self, global_common, common): + def __init__(self, global_common: GlobalCommon, common: Common) -> None: super(ConvertThread, self).__init__() self.global_common = global_common self.common = common self.error = False - def run(self): + def run(self) -> None: if self.global_common.settings.get("ocr"): ocr_lang = self.global_common.ocr_languages[ self.global_common.settings.get("ocr_language") @@ -515,7 +524,7 @@ class ConvertThread(QtCore.QThread): ): self.finished.emit(self.error) - def stdout_callback(self, line): + def stdout_callback(self, line: str) -> None: try: status = json.loads(line) except: @@ -541,7 +550,9 @@ class ConvertThread(QtCore.QThread): class ConvertWidget(QtWidgets.QWidget): close_window = QtCore.Signal() - def __init__(self, global_common, gui_common, common): + def __init__( + self, global_common: GlobalCommon, gui_common: GuiCommon, common: Common + ) -> None: super(ConvertWidget, self).__init__() self.global_common = global_common self.gui_common = gui_common @@ -588,19 +599,19 @@ class ConvertWidget(QtWidgets.QWidget): layout.addStretch() self.setLayout(layout) - def document_selected(self): + def document_selected(self) -> None: # Update the danger doc label self.dangerous_doc_label.setText( f"Suspicious: {os.path.basename(self.common.input_filename)}" ) - def start(self): + def start(self) -> None: self.convert_t = ConvertThread(self.global_common, self.common) self.convert_t.update.connect(self.update) self.convert_t.finished.connect(self.all_done) self.convert_t.start() - def update(self, error, text, percentage): + def update(self, error, text, percentage) -> None: if error: self.error = True self.error_image.show() @@ -609,7 +620,7 @@ class ConvertWidget(QtWidgets.QWidget): self.label.setText(text) self.progress.setValue(percentage) - def all_done(self): + def all_done(self) -> None: if self.error: return diff --git a/dangerzone/settings.py b/dangerzone/settings.py index 9c125f1..1aab4cc 100644 --- a/dangerzone/settings.py +++ b/dangerzone/settings.py @@ -19,13 +19,13 @@ class Settings: self.load() - def get(self, key): + def get(self, key: str): return self.settings[key] - def set(self, key, val): + def set(self, key: str, val) -> None: self.settings[key] = val - def load(self): + def load(self) -> None: if os.path.isfile(self.settings_filename): # If the settings file exists, load it try: