lint: ruff cleanup pass across bal/ and tests/

- Sort imports and fix pyproject ruff config (per-file ignores for
  intentional Qt/core exceptions)
- Mark Heirs.validate_* helpers as @staticmethod
- Clean up dead code, rename shadowing vars, use raise ... from
- Add AGENTS.md with env/lint/test/release guidance
This commit is contained in:
2026-07-31 16:03:17 -04:00
parent 649910e599
commit 08394f4868
48 changed files with 494 additions and 292 deletions

View File

@@ -9,11 +9,12 @@ to "check in" before the locktime expires. This module turns the event data
into an RFC-5545 .ics file and opens it with the OS default application.
"""
from .common import *
from .common import _, _logger # underscore names are not re-exported by "import *"
from PyQt6.QtGui import QAction
from PyQt6.QtWidgets import QToolButton
from .common import *
from .common import _, _logger # underscore names are not re-exported by "import *"
class BalCalendarButton(QToolButton):
"""A QToolButton with a dropdown menu for .ics calendar file actions.
@@ -79,7 +80,8 @@ class BalCalendarButton(QToolButton):
path = self._ensure_ics()
if not path:
return
import shlex, subprocess
import shlex
import subprocess
if self._bal_window.bal_plugin.is_basic_mode():
app = self._bal_window.bal_plugin.CALENDAR_APP.default
else:

View File

@@ -27,65 +27,129 @@ from decimal import Decimal
from functools import partial
from typing import Any, Callable, Mapping, Optional, Union
from electrum.bitcoin import (NLOCKTIME_BLOCKHEIGHT_MAX, NLOCKTIME_MAX,
NLOCKTIME_MIN)
from electrum.bitcoin import NLOCKTIME_BLOCKHEIGHT_MAX, NLOCKTIME_MAX, NLOCKTIME_MIN
from electrum.gui.qt.amountedit import BTCAmountEdit
from electrum.gui.qt.main_window import ElectrumWindow, StatusBarButton
from electrum.gui.qt.my_treeview import MyTreeView
from electrum.gui.qt.password_dialog import PasswordDialog
from electrum.gui.qt.transaction_dialog import TxDialog
from electrum.gui.qt.util import (Buttons, CancelButton, ColorScheme,
EnterButton, HelpButton, MessageBoxMixin,
OkButton, TaskThread, WindowModalDialog,
char_width_in_lineedit, getSaveFileName,
import_meta_gui, read_QIcon_from_bytes,
read_QPixmap_from_bytes, webopen)
from electrum.gui.qt.util import (
Buttons,
CancelButton,
ColorScheme,
EnterButton,
HelpButton,
MessageBoxMixin,
OkButton,
TaskThread,
WindowModalDialog,
char_width_in_lineedit,
getSaveFileName,
import_meta_gui,
read_QIcon_from_bytes,
read_QPixmap_from_bytes,
webopen,
)
from electrum.i18n import _
from electrum.logging import get_logger
from electrum.network import BestEffortRequestFailed, Network, TxBroadcastError
from electrum.payment_identifier import PaymentIdentifier
from electrum.plugin import hook
from electrum.transaction import SerializationError, Transaction, tx_from_any
from electrum.util import (DECIMAL_POINT, FileExportFailed, UserCancelled,
decimal_point_to_base_unit_name, read_json_file,
write_json_file)
from PyQt6.QtCore import (QDateTime, QModelIndex, QPersistentModelIndex, QSize,
Qt, QTimer, pyqtSignal)
from PyQt6.QtGui import (QColor, QPainter, QPalette, QStandardItem,
QStandardItemModel)
from PyQt6.QtWidgets import (QAbstractItemView, QAbstractSpinBox, QCheckBox,
QComboBox, QDateTimeEdit, QGridLayout, QHBoxLayout,
QInputDialog, QLabel, QLineEdit, QTextEdit, QMenu,
QMenuBar, QPushButton, QScrollArea, QSizePolicy,
QSpinBox, QStackedWidget, QStyle, QStyleOptionFrame,
QVBoxLayout, QWidget, QDialog)
from electrum.util import (
DECIMAL_POINT,
FileExportFailed,
UserCancelled,
decimal_point_to_base_unit_name,
read_json_file,
write_json_file,
)
from PyQt6.QtCore import (
QDateTime,
QModelIndex,
QPersistentModelIndex,
QSize,
Qt,
QTimer,
pyqtSignal,
)
from PyQt6.QtGui import QColor, QPainter, QPalette, QStandardItem, QStandardItemModel
from PyQt6.QtWidgets import (
QAbstractItemView,
QAbstractSpinBox,
QCheckBox,
QComboBox,
QDateTimeEdit,
QDialog,
QGridLayout,
QHBoxLayout,
QInputDialog,
QLabel,
QLineEdit,
QMenu,
QMenuBar,
QPushButton,
QScrollArea,
QSizePolicy,
QSpinBox,
QStackedWidget,
QStyle,
QStyleOptionFrame,
QTextEdit,
QVBoxLayout,
QWidget,
)
from ...core.heirs import (
HEIR_DUST_AMOUNT,
HEIR_REAL_AMOUNT,
OP_RETURN_PREFIX,
HeirAmountIsDustException,
Heirs,
WillExecutorFeeTooHighException,
get_op_return_hex,
is_op_return_address,
validate_op_return_hex,
)
# --- Core (GUI-free) logic layer ---
from ...core.plugin_base import BalPlugin, BalTimestamp
from ...core.heirs import (HEIR_DUST_AMOUNT, HEIR_REAL_AMOUNT,
HeirAmountIsDustException, Heirs,
OP_RETURN_PREFIX, is_op_return_address,
get_op_return_hex, validate_op_return_hex,
WillExecutorFeeTooHighException)
from ...core.util import Util
from ...core.will import (AmountException, HeirChangeException,
HeirNotFoundException, NoHeirsException,
NotCompleteWillException, NoWillExecutorNotPresent,
TxFeesChangedException, Will,
WillexecutorChangeException, WillExecutorNotPresent,
WillExpiredException, WillItem, WillPostponedException)
from ...core.willexecutors import Willexecutors
from ...core.willexecutors import is_onion_url, is_tor_active # noqa: F401
from ...core.will import (
AmountException,
HeirChangeException,
HeirNotFoundException,
NoHeirsException,
NotCompleteWillException,
NoWillExecutorNotPresent,
TxFeesChangedException,
Will,
WillexecutorChangeException,
WillExecutorNotPresent,
WillExpiredException,
WillItem,
WillPostponedException,
)
from ...core.willexecutors import ( # noqa: F401
Willexecutors,
is_onion_url,
is_tor_active,
)
# --- Presentation helpers ---
from .theme import server_status_text, server_status_tooltip, status_color
from .window_utils import (bring_to_front, show_modal, show_on_top,
stop_thread, top_level_of)
from .window_utils import (
bring_to_front,
show_modal,
show_on_top,
stop_thread,
top_level_of,
)
_logger = get_logger(__name__)
class shown_cv:
class shown_cv: # noqa: N801 (intentionally lowercase: mirrors Qt signal naming)
_type = bool
def __init__(self, value):

View File

@@ -17,13 +17,16 @@ the few list classes they reference are imported lazily inside the methods that
use them (see ``lists`` imports below).
"""
from .calendar import BalCalendar, BalCalendarButton
from .common import *
from .common import _, _logger # underscore names are not re-exported by "import *"
from .widgets import (BalCheckBox, BalLineEdit, BalTextEdit, BalTxFeesWidget,
LockTimeWidget, PercAmountEdit, ThresholdTimeWidget,
WillSettingsWidget, WillWidget, basic_reminder_offsets,
compute_reminder_offsets)
from .calendar import BalCalendar, BalCalendarButton
from .widgets import (
WillSettingsWidget,
WillWidget,
basic_reminder_offsets,
compute_reminder_offsets,
)
# NOTE: list views (HeirListWidget, PreviewList, WillExecutorWidget) are
# imported lazily where needed to avoid a dialogs<->lists import cycle.
@@ -31,9 +34,7 @@ from .calendar import BalCalendar, BalCalendarButton
class BalDialog(QDialog,MessageBoxMixin):
_stopping = False
def __init__(self, parent, bal_plugin, title=None, icon="icons/bal16x16.png"):
import signal
from PyQt6.QtCore import QMetaObject, Qt
from PyQt6.QtWidgets import QApplication
def handler(signum, frame):
QMetaObject.invokeMethod(self, "close", Qt.ConnectionType.QueuedConnection)
@@ -49,7 +50,7 @@ class BalDialog(QDialog,MessageBoxMixin):
self.setWindowTitle(title)
# WindowModalDialog.__init__(self,parent)
self.setWindowIcon(read_QIcon_from_bytes(bal_plugin.read_file(icon)))
def closeEvent(self, event):
self._stopping = True
# NOTE: we deliberately do NOT stop ``self.thread`` here.
@@ -701,7 +702,7 @@ class BalBuildWillDialog(BalDialog):
return None, Will.invalidate_will(
self.bal_window.willitems, self.bal_window.wallet, fee_per_byte
)
except NoHeirsException as e:
except NoHeirsException:
_logger.debug("no heirs")
self.msg_set_checking("No Heirs")
except NotCompleteWillException as e:

View File

@@ -14,12 +14,13 @@ construction) for all business actions, so the heavy logic stays in ``window``
and ``dialogs``.
"""
from PyQt6.QtWidgets import QLineEdit as _QLineEdit
from PyQt6.QtWidgets import QMessageBox, QStyledItemDelegate
from .common import *
from .common import _, _logger # underscore names are not re-exported by "import *"
from .widgets import BalCheckBox, PercAmountEdit, WillSettingsWidget
from PyQt6.QtWidgets import QMessageBox
from PyQt6.QtWidgets import QStyledItemDelegate, QLineEdit as _QLineEdit
from .dialogs import BalBuildWillDialog, BalDialog
from .widgets import BalCheckBox, WillSettingsWidget
class HeirListWidget(MyTreeView, MessageBoxMixin):
@@ -194,7 +195,7 @@ class HeirListWidget(MyTreeView, MessageBoxMixin):
set_current = QPersistentModelIndex(idx)
try:
self.will_settings_widget.on_locktime_change()
except Exception as e:
except Exception:
pass
self.set_current_idx(set_current)
# FIXME refresh loses sort order; so set "default" here:
@@ -214,15 +215,15 @@ class HeirListWidget(MyTreeView, MessageBoxMixin):
menu.addAction(_("Import"), self.bal_window.import_heirs)
menu.addAction(_("Export"), lambda: self.bal_window.export_heirs())
newHeirButton = QPushButton(_("New Heir"))
newHeirButton.clicked.connect(self.bal_window.new_heir_dialog)
new_heir_button = QPushButton(_("New Heir"))
new_heir_button.clicked.connect(self.bal_window.new_heir_dialog)
widget = QWidget(self)
layout = QHBoxLayout(widget)
self.will_settings_widget = WillSettingsWidget(self.bal_window, self)
layout.addWidget(self.will_settings_widget)
layout.addWidget(newHeirButton)
layout.addWidget(new_heir_button)
toolbar.insertWidget(2, widget)
@@ -281,7 +282,7 @@ class PreviewList(MyTreeView, MessageBoxMixin):
self.setModel(QStandardItemModel(self))
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
self.sortByColumn(self.Columns.NAME, Qt.SortOrder.AscendingOrder)
except Exception as e:
except Exception:
pass
self.setSortingEnabled(True)

View File

@@ -15,14 +15,17 @@ and cached in ``self.bal_windows``.
"""
from electrum.gui.qt.main_window import StatusBarButton
from PyQt6.QtWidgets import QLayout
from .common import *
from .common import _, _logger # underscore names are not re-exported by "import *"
from .common import read_QIcon_from_bytes
from .common import ( # underscore names are not re-exported by "import *"
_,
_logger,
read_QIcon_from_bytes,
)
from .dialogs import BalDialog
from .widgets import BalCheckBox, BalLineEdit, BalSpinBox, BalTextEdit
from .window import BalWindow
from .dialogs import BalDialog
from PyQt6.QtWidgets import QLayout
def _window_key(window):
@@ -88,9 +91,11 @@ class Plugin(BalPlugin):
except Exception:
return []
try:
from electrum.gui.qt.plugins_dialog import PluginsDialog
from electrum.gui.qt.plugins_dialog import (
PluginsDialog as plugins_dialog, # noqa: N813
)
except Exception:
PluginsDialog = None
plugins_dialog = None
app = QApplication.instance()
if app is None:
return []
@@ -106,7 +111,7 @@ class Plugin(BalPlugin):
for w in app.topLevelWidgets():
try:
is_match = False
if PluginsDialog is not None and isinstance(w, PluginsDialog):
if plugins_dialog is not None and isinstance(w, plugins_dialog):
is_match = True
elif type(w).__name__ == "PluginsDialog":
is_match = True
@@ -142,17 +147,17 @@ class Plugin(BalPlugin):
each is guarded independently.
"""
try:
from PyQt6.QtWidgets import QDialog
from PyQt6.QtWidgets import QDialog as qdialog # noqa: N813
except Exception:
QDialog = None
qdialog = None
# 1) reject() / done(): the reliable way to end an exec() modal loop.
if QDialog is not None and isinstance(d, QDialog):
if qdialog is not None and isinstance(d, qdialog):
try:
d.reject()
except Exception as e:
_logger.debug("reject() failed: {}".format(e))
try:
d.done(QDialog.DialogCode.Rejected)
d.done(qdialog.DialogCode.Rejected)
except Exception as e:
_logger.debug("done() failed: {}".format(e))
# 2) close(): covers non-QDialog top-levels and is a harmless extra.
@@ -172,9 +177,9 @@ class Plugin(BalPlugin):
it and closes it themselves (it must not linger in the background).
"""
try:
from PyQt6.QtCore import QTimer
from PyQt6.QtCore import QTimer as qtimer # noqa: N813
except Exception:
QTimer = None
qtimer = None
# Schedule of retry delays (ms) measured from each call.
retry_delays = [400, 800, 1500]
dialogs = Plugin._find_plugins_manager_dialogs()
@@ -190,8 +195,8 @@ class Plugin(BalPlugin):
if not still_open:
_logger.info("plugins dialog closed successfully")
return
if attempt < len(retry_delays) and QTimer is not None:
QTimer.singleShot(
if attempt < len(retry_delays) and qtimer is not None:
qtimer.singleShot(
retry_delays[attempt],
lambda: Plugin._handle_plugins_manager_dialog(attempt + 1),
)

View File

@@ -18,9 +18,9 @@ Contents:
* WillWidget - single will-tx box
"""
from .calendar import BalCalendar, BalCalendarButton
from .common import *
from .common import _, _logger # underscore names are not re-exported by "import *"
from .calendar import BalCalendar, BalCalendarButton
def compute_reminder_offsets(days, count):
@@ -690,7 +690,7 @@ class LockTimeDateEdit(QDateTimeEdit, _LockTimeEditor):
return
try:
x = int(x)
except Exception as e:
except Exception:
x = QDateTime.currentDateTime().timestamp()
finally:
# Use the overflow-safe converter: on Windows datetime.fromtimestamp
@@ -1395,15 +1395,15 @@ class PercAmountEdit(BTCAmountEdit):
if self.base_unit:
panel = QStyleOptionFrame()
self.initStyleOption(panel)
textRect = self.style().subElementRect(
text_rect = self.style().subElementRect(
QStyle.SubElement.SE_LineEditContents, panel, self
)
textRect.adjust(2, 0, -10, 0)
text_rect.adjust(2, 0, -10, 0)
painter = QPainter(self)
painter.setPen(ColorScheme.GRAY.as_color())
if len(self.text()) == 0:
painter.drawText(
textRect,
text_rect,
int(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter),
self.base_unit() + " or perc value",
)

View File

@@ -18,11 +18,16 @@ import threading
from .common import *
from .common import _, _logger # underscore names are not re-exported by "import *"
from .widgets import LockTimeWidget, PercAmountEdit, WillSettingsWidget
from .dialogs import (BalBlockingWaitingDialog, BalBuildWillDialog, BalDialog,
BalWaitingDialog, BalWizardDialog, WillDetailDialog,
WillExecutorDialog)
from .lists import HeirListWidget, PreviewList, WillExecutorWidget
from .dialogs import (
BalBuildWillDialog,
BalDialog,
BalWaitingDialog,
BalWizardDialog,
WillDetailDialog,
WillExecutorDialog,
)
from .lists import HeirListWidget, PreviewList
from .widgets import LockTimeWidget, PercAmountEdit
class BalWindow: