plugin: clean up imports, style
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
#
|
#
|
||||||
# Electrum - lightweight Bitcoin client
|
# Electrum - lightweight Bitcoin client
|
||||||
# Copyright (C) 2015 Thomas Voegtlin
|
# Copyright (C) 2015-2024 Thomas Voegtlin
|
||||||
#
|
#
|
||||||
# Permission is hereby granted, free of charge, to any person
|
# Permission is hereby granted, free of charge, to any person
|
||||||
# obtaining a copy of this software and associated documentation files
|
# obtaining a copy of this software and associated documentation files
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
# SOFTWARE.
|
# SOFTWARE.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import pkgutil
|
import pkgutil
|
||||||
import importlib.util
|
import importlib.util
|
||||||
@@ -29,16 +30,14 @@ import time
|
|||||||
import threading
|
import threading
|
||||||
import traceback
|
import traceback
|
||||||
import sys
|
import sys
|
||||||
import json
|
import aiohttp
|
||||||
|
|
||||||
from typing import (NamedTuple, Any, Union, TYPE_CHECKING, Optional, Tuple,
|
from typing import (NamedTuple, Any, Union, TYPE_CHECKING, Optional, Tuple,
|
||||||
Dict, Iterable, List, Sequence, Callable, TypeVar, Mapping, Set)
|
Dict, Iterable, List, Sequence, Callable, TypeVar, Mapping)
|
||||||
import concurrent
|
import concurrent
|
||||||
import zipimport
|
import zipimport
|
||||||
from concurrent import futures
|
from concurrent import futures
|
||||||
from functools import wraps, partial
|
from functools import wraps, partial
|
||||||
from enum import IntEnum
|
|
||||||
from packaging.version import parse as parse_version
|
|
||||||
from electrum.version import ELECTRUM_VERSION
|
|
||||||
|
|
||||||
from .i18n import _
|
from .i18n import _
|
||||||
from .util import (profiler, DaemonThread, UserCancelled, ThreadJob, UserFacingException)
|
from .util import (profiler, DaemonThread, UserCancelled, ThreadJob, UserFacingException)
|
||||||
@@ -59,7 +58,6 @@ hook_names = set()
|
|||||||
hooks = {}
|
hooks = {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Plugins(DaemonThread):
|
class Plugins(DaemonThread):
|
||||||
|
|
||||||
LOGGING_SHORTCUT = 'p'
|
LOGGING_SHORTCUT = 'p'
|
||||||
@@ -89,7 +87,7 @@ class Plugins(DaemonThread):
|
|||||||
def descriptions(self):
|
def descriptions(self):
|
||||||
return dict(list(self.internal_plugin_metadata.items()) + list(self.external_plugin_metadata.items()))
|
return dict(list(self.internal_plugin_metadata.items()) + list(self.external_plugin_metadata.items()))
|
||||||
|
|
||||||
def find_internal_plugins(self) -> Mapping[str, dict]:
|
def find_internal_plugins(self):
|
||||||
"""Populates self.internal_plugin_metadata
|
"""Populates self.internal_plugin_metadata
|
||||||
"""
|
"""
|
||||||
iter_modules = list(pkgutil.iter_modules([self.pkgpath]))
|
iter_modules = list(pkgutil.iter_modules([self.pkgpath]))
|
||||||
@@ -165,10 +163,9 @@ class Plugins(DaemonThread):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
async def download_external_plugin(self, name):
|
async def download_external_plugin(self, name):
|
||||||
import aiohttp
|
|
||||||
metadata = self.external_plugin_metadata.get(name)
|
metadata = self.external_plugin_metadata.get(name)
|
||||||
if metadata is None:
|
if metadata is None:
|
||||||
raise Exception("unknown external plugin %s" % name)
|
raise Exception(f"unknown external plugin {name}")
|
||||||
url = metadata['download_url']
|
url = metadata['download_url']
|
||||||
filename = self.external_plugin_path(name)
|
filename = self.external_plugin_path(name)
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
@@ -179,7 +176,7 @@ class Plugins(DaemonThread):
|
|||||||
fd.write(chunk)
|
fd.write(chunk)
|
||||||
if not self.check_plugin_hash(name):
|
if not self.check_plugin_hash(name):
|
||||||
os.unlink(filename)
|
os.unlink(filename)
|
||||||
raise Exception("wrong plugin hash %s" % name)
|
raise Exception(f"wrong plugin hash {name}")
|
||||||
|
|
||||||
def load_external_plugin(self, name):
|
def load_external_plugin(self, name):
|
||||||
if name in self.plugins:
|
if name in self.plugins:
|
||||||
@@ -188,31 +185,30 @@ class Plugins(DaemonThread):
|
|||||||
# on startup, or added by manual user installation after that point.
|
# on startup, or added by manual user installation after that point.
|
||||||
metadata = self.external_plugin_metadata.get(name)
|
metadata = self.external_plugin_metadata.get(name)
|
||||||
if metadata is None:
|
if metadata is None:
|
||||||
self.logger.exception("attempted to load unknown external plugin %s" % name)
|
self.logger.exception(f"attempted to load unknown external plugin {name}")
|
||||||
return
|
return
|
||||||
filename = self.external_plugin_path(name)
|
filename = self.external_plugin_path(name)
|
||||||
if not os.path.exists(filename):
|
if not os.path.exists(filename):
|
||||||
return
|
return
|
||||||
if not self.check_plugin_hash(name):
|
if not self.check_plugin_hash(name):
|
||||||
self.logger.exception("wrong hash for plugin '%s'" % name)
|
self.logger.exception(f"wrong hash for plugin '{name}'")
|
||||||
os.unlink(filename)
|
os.unlink(filename)
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
zipfile = zipimport.zipimporter(filename)
|
zipfile = zipimport.zipimporter(filename)
|
||||||
except zipimport.ZipImportError:
|
except zipimport.ZipImportError:
|
||||||
self.logger.exception("unable to load zip plugin '%s'" % filename)
|
self.logger.exception(f"unable to load zip plugin '{filename}'")
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
module = zipfile.load_module(name)
|
module = zipfile.load_module(name)
|
||||||
except zipimport.ZipImportError as e:
|
except zipimport.ZipImportError as e:
|
||||||
self.logger.exception(f"unable to load zip plugin '{filename}' package '{name}'")
|
self.logger.exception(f"unable to load zip plugin '{filename}' package '{name}'")
|
||||||
return
|
return
|
||||||
sys.modules['electrum_external_plugins.'+ name] = module
|
sys.modules['electrum_external_plugins.' + name] = module
|
||||||
full_name = f'electrum_external_plugins.{name}.{self.gui_name}'
|
full_name = f'electrum_external_plugins.{name}.{self.gui_name}'
|
||||||
spec = importlib.util.find_spec(full_name)
|
spec = importlib.util.find_spec(full_name)
|
||||||
if spec is None:
|
if spec is None:
|
||||||
raise RuntimeError("%s implementation for %s plugin not found"
|
raise RuntimeError(f"{self.gui_name} implementation for {name} plugin not found")
|
||||||
% (self.gui_name, name))
|
|
||||||
module = importlib.util.module_from_spec(spec)
|
module = importlib.util.module_from_spec(spec)
|
||||||
self._register_module(spec, module)
|
self._register_module(spec, module)
|
||||||
if sys.version_info >= (3, 10):
|
if sys.version_info >= (3, 10):
|
||||||
@@ -248,7 +244,7 @@ class Plugins(DaemonThread):
|
|||||||
try:
|
try:
|
||||||
self.load_external_plugin(name)
|
self.load_external_plugin(name)
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
traceback.print_exc(file=sys.stdout) # shouldn't this be... suppressed unless -v?
|
traceback.print_exc(file=sys.stdout) # shouldn't this be... suppressed unless -v?
|
||||||
self.logger.exception(f"cannot initialize plugin {name} {e!r}")
|
self.logger.exception(f"cannot initialize plugin {name} {e!r}")
|
||||||
|
|
||||||
def get(self, name):
|
def get(self, name):
|
||||||
@@ -271,11 +267,10 @@ class Plugins(DaemonThread):
|
|||||||
def load_internal_plugin(self, name) -> 'BasePlugin':
|
def load_internal_plugin(self, name) -> 'BasePlugin':
|
||||||
if name in self.plugins:
|
if name in self.plugins:
|
||||||
return self.plugins[name]
|
return self.plugins[name]
|
||||||
full_name = f'electrum.plugins.{name}' + f'.{self.gui_name}'
|
full_name = f'electrum.plugins.{name}.{self.gui_name}'
|
||||||
spec = importlib.util.find_spec(full_name)
|
spec = importlib.util.find_spec(full_name)
|
||||||
if spec is None:
|
if spec is None:
|
||||||
raise RuntimeError("%s implementation for %s plugin not found"
|
raise RuntimeError(f"{self.gui_name} implementation for {name} plugin not found")
|
||||||
% (self.gui_name, name))
|
|
||||||
try:
|
try:
|
||||||
module = importlib.util.module_from_spec(spec)
|
module = importlib.util.module_from_spec(spec)
|
||||||
spec.loader.exec_module(module)
|
spec.loader.exec_module(module)
|
||||||
@@ -350,6 +345,7 @@ class Plugins(DaemonThread):
|
|||||||
def register_wallet_type(self, name, gui_good, wallet_type):
|
def register_wallet_type(self, name, gui_good, wallet_type):
|
||||||
from .wallet import register_wallet_type, register_constructor
|
from .wallet import register_wallet_type, register_constructor
|
||||||
self.logger.info(f"registering wallet type {(wallet_type, name)}")
|
self.logger.info(f"registering wallet type {(wallet_type, name)}")
|
||||||
|
|
||||||
def loader():
|
def loader():
|
||||||
plugin = self.get_plugin(name)
|
plugin = self.get_plugin(name)
|
||||||
register_constructor(wallet_type, plugin.wallet_class)
|
register_constructor(wallet_type, plugin.wallet_class)
|
||||||
@@ -358,6 +354,7 @@ class Plugins(DaemonThread):
|
|||||||
|
|
||||||
def register_keystore(self, name, gui_good, details):
|
def register_keystore(self, name, gui_good, details):
|
||||||
from .keystore import register_keystore
|
from .keystore import register_keystore
|
||||||
|
|
||||||
def dynamic_constructor(d):
|
def dynamic_constructor(d):
|
||||||
return self.get_plugin(name).keystore_class(d)
|
return self.get_plugin(name).keystore_class(d)
|
||||||
if details[0] == 'hardware':
|
if details[0] == 'hardware':
|
||||||
@@ -406,7 +403,7 @@ class BasePlugin(Logger):
|
|||||||
self.parent = parent # type: Plugins # The plugins object
|
self.parent = parent # type: Plugins # The plugins object
|
||||||
self.name = name
|
self.name = name
|
||||||
self.config = config
|
self.config = config
|
||||||
self.wallet = None # fixme: this field should not exist
|
self.wallet = None # fixme: this field should not exist
|
||||||
Logger.__init__(self)
|
Logger.__init__(self)
|
||||||
# add self to hooks
|
# add self to hooks
|
||||||
for k in dir(self):
|
for k in dir(self):
|
||||||
@@ -443,7 +440,7 @@ class BasePlugin(Logger):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
def is_enabled(self):
|
def is_enabled(self):
|
||||||
return self.is_available() and self.config.get('enable_plugin_'+self.name) is True
|
return self.is_available() and self.config.get('enable_plugin_' + self.name) is True
|
||||||
|
|
||||||
def is_available(self):
|
def is_available(self):
|
||||||
return True
|
return True
|
||||||
@@ -466,6 +463,7 @@ class BasePlugin(Logger):
|
|||||||
s = myfile.read()
|
s = myfile.read()
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
class DeviceUnpairableError(UserFacingException): pass
|
class DeviceUnpairableError(UserFacingException): pass
|
||||||
class HardwarePluginLibraryUnavailable(Exception): pass
|
class HardwarePluginLibraryUnavailable(Exception): pass
|
||||||
class CannotAutoSelectDevice(Exception): pass
|
class CannotAutoSelectDevice(Exception): pass
|
||||||
@@ -551,7 +549,7 @@ def assert_runs_in_hwd_thread():
|
|||||||
|
|
||||||
|
|
||||||
class DeviceMgr(ThreadJob):
|
class DeviceMgr(ThreadJob):
|
||||||
'''Manages hardware clients. A client communicates over a hardware
|
"""Manages hardware clients. A client communicates over a hardware
|
||||||
channel with the device.
|
channel with the device.
|
||||||
|
|
||||||
In addition to tracking device HID IDs, the device manager tracks
|
In addition to tracking device HID IDs, the device manager tracks
|
||||||
@@ -579,7 +577,7 @@ class DeviceMgr(ThreadJob):
|
|||||||
the HID IDs.
|
the HID IDs.
|
||||||
|
|
||||||
This plugin is thread-safe. Currently only devices supported by
|
This plugin is thread-safe. Currently only devices supported by
|
||||||
hidapi are implemented.'''
|
hidapi are implemented."""
|
||||||
|
|
||||||
def __init__(self, config: SimpleConfig):
|
def __init__(self, config: SimpleConfig):
|
||||||
ThreadJob.__init__(self)
|
ThreadJob.__init__(self)
|
||||||
@@ -691,7 +689,7 @@ class DeviceMgr(ThreadJob):
|
|||||||
allow_user_interaction: bool = True) -> Optional['HardwareClientBase']:
|
allow_user_interaction: bool = True) -> Optional['HardwareClientBase']:
|
||||||
self.logger.info("getting client for keystore")
|
self.logger.info("getting client for keystore")
|
||||||
if handler is None:
|
if handler is None:
|
||||||
raise Exception(_("Handler not found for") + ' ' + plugin.name + '\n' + _("A library is probably missing."))
|
raise Exception(_("Handler not found for {}").format(plugin.name) + '\n' + _("A library is probably missing."))
|
||||||
handler.update_status(False)
|
handler.update_status(False)
|
||||||
pcode = keystore.pairing_code()
|
pcode = keystore.pairing_code()
|
||||||
client = None
|
client = None
|
||||||
@@ -756,7 +754,7 @@ class DeviceMgr(ThreadJob):
|
|||||||
try:
|
try:
|
||||||
client_xpub = client.get_xpub(derivation, xtype)
|
client_xpub = client.get_xpub(derivation, xtype)
|
||||||
except (UserCancelled, RuntimeError):
|
except (UserCancelled, RuntimeError):
|
||||||
# Bad / cancelled PIN / passphrase
|
# Bad / cancelled PIN / passphrase
|
||||||
client_xpub = None
|
client_xpub = None
|
||||||
if client_xpub == xpub:
|
if client_xpub == xpub:
|
||||||
keystore.opportunistically_fill_in_missing_info_from_device(client)
|
keystore.opportunistically_fill_in_missing_info_from_device(client)
|
||||||
@@ -928,8 +926,7 @@ class DeviceMgr(ThreadJob):
|
|||||||
try:
|
try:
|
||||||
new_devices = f()
|
new_devices = f()
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
self.logger.error('custom device enum failed. func {}, error {}'
|
self.logger.error(f'custom device enum failed. func {str(f)}, error {e!r}')
|
||||||
.format(str(f), repr(e)))
|
|
||||||
else:
|
else:
|
||||||
devices.extend(new_devices)
|
devices.extend(new_devices)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user