From 88307357ec3798e6d78720c74bae46d68c622bb8 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 22 Nov 2019 22:59:33 +0100 Subject: [PATCH] add some type hints mostly related to hw wallets --- electrum/base_wizard.py | 10 ++++----- electrum/keystore.py | 17 ++++++++------ electrum/plugin.py | 2 +- electrum/plugins/hw_wallet/plugin.py | 3 ++- electrum/plugins/hw_wallet/qt.py | 33 ++++++++++++++++++---------- 5 files changed, 39 insertions(+), 26 deletions(-) diff --git a/electrum/base_wizard.py b/electrum/base_wizard.py index 3e58dfb0c..e64713ed7 100644 --- a/electrum/base_wizard.py +++ b/electrum/base_wizard.py @@ -34,7 +34,7 @@ from . import bitcoin from . import keystore from . import mnemonic from .bip32 import is_bip32_derivation, xpub_type, normalize_bip32_derivation, BIP32Node -from .keystore import bip44_derivation, purpose48_derivation +from .keystore import bip44_derivation, purpose48_derivation, Hardware_KeyStore from .wallet import (Imported_Wallet, Standard_Wallet, Multisig_Wallet, wallet_types, Wallet, Abstract_Wallet) from .storage import (WalletStorage, StorageEncryptionVersion, @@ -47,7 +47,7 @@ from .logging import Logger from .plugins.hw_wallet.plugin import OutdatedHwFirmwareException, HW_PluginBase if TYPE_CHECKING: - from .plugin import DeviceInfo + from .plugin import DeviceInfo, BasePlugin # hardware device setup purpose @@ -84,7 +84,7 @@ class BaseWizard(Logger): self.data = {} self.pw_args = None # type: Optional[WizardWalletPasswordSetting] self._stack = [] # type: List[WizardStackItem] - self.plugin = None + self.plugin = None # type: Optional[BasePlugin] self.keystores = [] self.is_kivy = config.get('gui') == 'kivy' self.seed_type = None @@ -532,9 +532,9 @@ class BaseWizard(Logger): encrypt_keystore = any(k.may_have_password() for k in self.keystores) # note: the following condition ("if") is duplicated logic from # wallet.get_available_storage_encryption_version() - if self.wallet_type == 'standard' and isinstance(self.keystores[0], keystore.Hardware_KeyStore): + if self.wallet_type == 'standard' and isinstance(self.keystores[0], Hardware_KeyStore): # offer encrypting with a pw derived from the hw device - k = self.keystores[0] + k = self.keystores[0] # type: Hardware_KeyStore try: k.handler = self.plugin.create_handler(self) password = k.get_password_for_storage_encryption() diff --git a/electrum/keystore.py b/electrum/keystore.py index 9904319c2..31211d6c1 100644 --- a/electrum/keystore.py +++ b/electrum/keystore.py @@ -44,23 +44,25 @@ from .plugin import run_hook from .logging import Logger if TYPE_CHECKING: + from .gui.qt.util import TaskThread from .transaction import Transaction, PartialTransaction, PartialTxInput, PartialTxOutput from .plugins.hw_wallet import HW_PluginBase, HardwareClientBase class KeyStore(Logger): + type: str def __init__(self): Logger.__init__(self) self.is_requesting_to_be_rewritten_to_wallet_file = False # type: bool - def has_seed(self): + def has_seed(self) -> bool: return False - def is_watching_only(self): + def is_watching_only(self) -> bool: return False - def can_import(self): + def can_import(self) -> bool: return False def get_type_text(self) -> str: @@ -85,12 +87,12 @@ class KeyStore(Logger): keypairs[pubkey.hex()] = derivation return keypairs - def can_sign(self, tx): + def can_sign(self, tx) -> bool: if self.is_watching_only(): return False return bool(self.get_tx_derivations(tx)) - def ready_to_sign(self): + def ready_to_sign(self) -> bool: return not self.is_watching_only() def dump(self) -> dict: @@ -629,6 +631,7 @@ class Hardware_KeyStore(KeyStore, Xpub): hw_type: str device: str plugin: 'HW_PluginBase' + thread: Optional['TaskThread'] = None type = 'hardware' @@ -684,7 +687,7 @@ class Hardware_KeyStore(KeyStore, Xpub): assert not self.has_seed() return False - def get_password_for_storage_encryption(self): + def get_password_for_storage_encryption(self) -> str: from .storage import get_derivation_used_for_hw_device_encryption client = self.plugin.get_client(self) derivation = get_derivation_used_for_hw_device_encryption() @@ -692,7 +695,7 @@ class Hardware_KeyStore(KeyStore, Xpub): password = self.get_pubkey_from_xpub(xpub, ()) return password - def has_usable_connection_with_device(self): + def has_usable_connection_with_device(self) -> bool: if not hasattr(self, 'plugin'): return False client = self.plugin.get_client(self, force_pair=False) diff --git a/electrum/plugin.py b/electrum/plugin.py index 423a7df05..f8524e4a3 100644 --- a/electrum/plugin.py +++ b/electrum/plugin.py @@ -352,7 +352,7 @@ class DeviceMgr(ThreadJob): ThreadJob.__init__(self) # Keyed by xpub. The value is the device id # has been paired, and None otherwise. - self.xpub_ids = {} + self.xpub_ids = {} # type: Dict[str, str] # A list of clients. The key is the client, the value is # a (path, id_) pair. self.clients = {} # type: Dict[HardwareClientBase, Tuple[Union[str, bytes], str]] diff --git a/electrum/plugins/hw_wallet/plugin.py b/electrum/plugins/hw_wallet/plugin.py index 04281d89f..81603ed55 100644 --- a/electrum/plugins/hw_wallet/plugin.py +++ b/electrum/plugins/hw_wallet/plugin.py @@ -40,6 +40,7 @@ if TYPE_CHECKING: class HW_PluginBase(BasePlugin): keystore_class: Type['Hardware_KeyStore'] + libraries_available: bool minimum_library = (0, ) @@ -211,7 +212,7 @@ def get_xpubs_and_der_suffixes_from_txinout(tx: PartialTransaction, def only_hook_if_libraries_available(func): # note: this decorator must wrap @hook, not the other way around, # as 'hook' uses the name of the function it wraps - def wrapper(self, *args, **kwargs): + def wrapper(self: 'HW_PluginBase', *args, **kwargs): if not self.libraries_available: return None return func(self, *args, **kwargs) return wrapper diff --git a/electrum/plugins/hw_wallet/qt.py b/electrum/plugins/hw_wallet/qt.py index 44c8e41a1..897f8d034 100644 --- a/electrum/plugins/hw_wallet/qt.py +++ b/electrum/plugins/hw_wallet/qt.py @@ -26,6 +26,7 @@ import threading from functools import partial +from typing import TYPE_CHECKING, Union, Optional, Callable, Any from PyQt5.QtCore import QObject, pyqtSignal from PyQt5.QtWidgets import QVBoxLayout, QLineEdit, QHBoxLayout, QLabel @@ -33,12 +34,18 @@ from PyQt5.QtWidgets import QVBoxLayout, QLineEdit, QHBoxLayout, QLabel from electrum.gui.qt.password_dialog import PasswordLayout, PW_PASSPHRASE from electrum.gui.qt.util import (read_QIcon, WWLabel, OkButton, WindowModalDialog, Buttons, CancelButton, TaskThread, char_width_in_lineedit) +from electrum.gui.qt.main_window import StatusBarButton, ElectrumWindow from electrum.i18n import _ from electrum.logging import Logger -from electrum.util import parse_URI, InvalidBitcoinURI +from electrum.util import parse_URI, InvalidBitcoinURI, UserCancelled +from electrum.plugin import hook, DeviceUnpairableError -from .plugin import OutdatedHwFirmwareException +from .plugin import OutdatedHwFirmwareException, HW_PluginBase + +if TYPE_CHECKING: + from electrum.wallet import Abstract_Wallet + from electrum.keystore import Hardware_KeyStore # The trickiest thing about this handler was getting windows properly @@ -190,15 +197,10 @@ class QtHandlerBase(QObject, Logger): self.done.set() - -from electrum.plugin import hook -from electrum.util import UserCancelled -from electrum.gui.qt.main_window import StatusBarButton - class QtPluginBase(object): @hook - def load_wallet(self, wallet, window): + def load_wallet(self: Union['QtPluginBase', HW_PluginBase], wallet: 'Abstract_Wallet', window: ElectrumWindow): for keystore in wallet.get_keystores(): if not isinstance(keystore, self.keystore_class): continue @@ -220,7 +222,8 @@ class QtPluginBase(object): # Trigger a pairing keystore.thread.add(partial(self.get_client, keystore)) - def on_task_thread_error(self, window, keystore, exc_info): + def on_task_thread_error(self: Union['QtPluginBase', HW_PluginBase], window: ElectrumWindow, + keystore: 'Hardware_KeyStore', exc_info): e = exc_info[1] if isinstance(e, OutdatedHwFirmwareException): if window.question(e.text_ignore_old_fw_and_continue(), title=_("Outdated device firmware")): @@ -236,7 +239,8 @@ class QtPluginBase(object): else: window.on_error(exc_info) - def choose_device(self, window, keystore): + def choose_device(self: Union['QtPluginBase', HW_PluginBase], window: ElectrumWindow, + keystore: 'Hardware_KeyStore') -> Optional[str]: '''This dialog box should be usable even if the user has forgotten their PIN or it is in bootloader mode.''' device_id = self.device_manager().xpub_id(keystore.xpub) @@ -248,10 +252,12 @@ class QtPluginBase(object): device_id = info.device.id_ return device_id - def show_settings_dialog(self, window, keystore): + def show_settings_dialog(self, window: ElectrumWindow, keystore: 'Hardware_KeyStore') -> None: device_id = self.choose_device(window, keystore) - def add_show_address_on_hw_device_button_for_receive_addr(self, wallet, keystore, main_window): + def add_show_address_on_hw_device_button_for_receive_addr(self, wallet: 'Abstract_Wallet', + keystore: 'Hardware_KeyStore', + main_window: ElectrumWindow): plugin = keystore.plugin receive_address_e = main_window.receive_address_e @@ -267,3 +273,6 @@ class QtPluginBase(object): keystore.thread.add(partial(plugin.show_address, wallet, addr, keystore)) dev_name = f"{plugin.device} ({keystore.label})" receive_address_e.addButton("eye1.png", show_address, _("Show on {}").format(dev_name)) + + def create_handler(self, window: ElectrumWindow) -> 'QtHandlerBase': + raise NotImplementedError()