From 08394f4868d1ff74ca0607a00a3b1a97e356c8fe Mon Sep 17 00:00:00 2001 From: svatantrya Date: Fri, 31 Jul 2026 16:03:17 -0400 Subject: [PATCH] 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 --- AGENTS.md | 76 ++++++++++++ bal/core/heirs.py | 17 ++- bal/core/plugin_base.py | 6 +- bal/core/util.py | 6 +- bal/core/will.py | 15 +-- bal/core/willexecutors.py | 11 +- bal/gui/qt/calendar.py | 8 +- bal/gui/qt/common.py | 138 ++++++++++++++++------ bal/gui/qt/dialogs.py | 19 +-- bal/gui/qt/lists.py | 17 +-- bal/gui/qt/plugin.py | 35 +++--- bal/gui/qt/widgets.py | 10 +- bal/gui/qt/window.py | 15 ++- pyproject.toml | 12 ++ tests/gui_fixes_test.py | 8 +- tests/parallel_ping_test.py | 54 ++++----- tests/preview_build_will_dialog.py | 2 +- tests/preview_building_will_close_btn.py | 11 +- tests/preview_we_rows.py | 2 +- tests/preview_wizard_button.py | 15 ++- tests/preview_wizard_settings_align.py | 17 ++- tests/sim_update_flows.py | 15 ++- tests/smoke_test.py | 24 ++-- tests/test_anticipate_manual_locktime.py | 8 +- tests/test_anticipate_past_locktime.py | 4 +- tests/test_core_heirs.py | 41 ++++--- tests/test_core_heirs_extra.py | 11 +- tests/test_core_plugin_base.py | 7 +- tests/test_core_util.py | 35 +++--- tests/test_core_will.py | 28 +++-- tests/test_core_will_extra.py | 7 +- tests/test_core_will_invalidate.py | 7 +- tests/test_group_b_auto_sign.py | 1 - tests/test_group_c_settings.py | 1 - tests/test_group_d_alarms.py | 1 - tests/test_group_e_karen7_invalidate.py | 8 +- tests/test_group_e_mock_karen7.py | 5 +- tests/test_group_f_heir_change_rebuild.py | 3 +- tests/test_group_g_basic_calendar.py | 3 +- tests/test_group_i_basic_checkalive.py | 1 - tests/test_gui_calendar.py | 2 - tests/test_gui_common.py | 27 ++--- tests/test_gui_theme.py | 2 +- tests/test_gui_widgets.py | 4 +- tests/test_gui_window_utils.py | 7 +- tests/test_no_willexecutor_karen7.py | 17 +-- tests/test_reproduce_none_type.py | 13 +- tests/windows_overflow_test.py | 10 +- 48 files changed, 494 insertions(+), 292 deletions(-) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..cff4580 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,76 @@ +# AGENTS.md + +BAL — Bitcoin After Life, an Electrum plugin (inheritance / dead-man's-switch). +Source-of-truth docs: `README.md`, `HANDOFF.md`, `COMPATIBILITY.md`. + +## Environments (critical) + +Two separate venvs; using the wrong one is the #1 mistake. + +- **Runtime env** (Electrum + PyQt6, has `electrum` importable): + `source /home/steal/devel/bal/electrum/env/bin/activate` + This is an editable install of the Electrum 4.8.0 checkout at + `/home/steal/devel/bal/electrum`. Use it for anything that imports + `electrum`, runs GUI code, or runs tests. +- **Lint venv** (repo-local `venv/`): ruff, black, flake8 only. It cannot + import `electrum` or `PyQt6`. Do NOT use it to run tests. + +The plugin's `bal/` directory is symlinked into +`electrum/electrum/plugins/bal` (internal-plugin install used during dev). + +## Test & verify + +Tests are **standalone scripts**, not pytest. Each `tests/test_*.py` file runs +its `test_*` functions from `if __name__ == "__main__"`. Run a file directly: + +```bash +source /home/steal/devel/bal/electrum/env/bin/activate +python3 tests/test_core_heirs.py # core, no Qt needed +QT_QPA_PLATFORM=offscreen python3 tests/test_gui_common.py # GUI tests need offscreen +``` + +- Most core tests run offline (no wallet/network). Some files + (`test_group_*.py`, `test_no_willexecutor_karen7.py`, `parallel_ping_test.py`) + exercise will-executor/network flows and need the live servers — don't rely on + them for quick verification. +- `tests/smoke_test.py` proves clean import under real Electrum: + `QT_QPA_PLATFORM=offscreen python3 tests/smoke_test.py electrum.plugins.bal` +- `tests/external_zip_test.py` loads the built zip the way Electrum's plugin + dialog does (`electrum_external_plugins.bal`); run it after `build_zip.py`. + +## Lint / typecheck + +- **Ruff is NOT clean** (hundreds of pre-existing errors in `bal/` and + `tests/`). Do not run `--fix` wholesale and do not try to silence everything; + just avoid adding new violations. Config: `pyproject.toml` (line-length 88, + E501 ignored). +- Lint via the repo venv: `/home/steal/devel/bal/bal-electrum-plugin/venv/bin/ruff` +- Typecheck: `pyright` (npm, `node_modules/`), config `pyrightconfig.json` + (`extraPaths: ["../electrum"]`). Pyright reports many false positives on + dynamically-attached attrs (e.g. `self.window`, `BalPlugin.*`); don't chase + them. + +## Architecture + +- `bal/core/` = GUI-free logic (`heirs.py`, `will.py`, `willexecutors.py`, + `plugin_base.py`, `util.py`). Must never import Qt. +- `bal/gui/qt/` = PyQt6 layer. `window.py` is the per-wallet controller, + `plugin.py` is the Electrum `@hooks` entry, `qt.py` is a zipimport shim. +- `bal/manifest.json` = version source of truth (Electrum reads it; also read by + `make-release.sh`). +- Compatibility constraint: must support Electrum **4.7.2 and 4.8.0**; the DB + registration API differs between them (`json_db.register_dict` vs + `stored_dict.register_name`). + +## Build / release + +```bash +python3 build_zip.py # -> bal-electrum-plugin.zip (deterministic, prints sha256) +./make-release.sh [v0.x.y] # bump manifest version, tag, sign, push Gitea release +``` + +- `make-release.sh` requires gpg and Gitea credentials (`~/.git-credentials` + or `GITEA_USER`/`GITEA_TOKEN`). It bumps `bal/manifest.json` — bump the + version there, never invent a new source of truth. +- Remote is Gitea (`origin` = bitcoin-after.life). `.env` holds a Gitea token + (gitignored, never commit it). diff --git a/bal/core/heirs.py b/bal/core/heirs.py index ab50346..101559b 100644 --- a/bal/core/heirs.py +++ b/bal/core/heirs.py @@ -102,7 +102,7 @@ def validate_op_return_hex(data_hex: str) -> None: try: data = bytes.fromhex(data_hex) except ValueError: - raise NotAnAddress(f"OP_RETURN data is not valid hex: {data_hex}") + raise NotAnAddress(f"OP_RETURN data is not valid hex: {data_hex}") from None if len(data) > 80: raise NotAnAddress( f"OP_RETURN data too long ({len(data)} bytes, max 80)" @@ -205,7 +205,7 @@ def prepare_transactions(locktimes, available_utxos, fees, wallet): change = get_change_output(wallet, in_amount, out_amount, fee) if change: outputs.append(change) - for i in range(0, 100): + for _ in range(0, 100): random.shuffle(outputs) #op_return_text = "Hello Bal!" @@ -281,7 +281,7 @@ def invalidate_inheritance_transactions(wallet): del dtxs[txid] utxos = {} - for txid, tx in dtxs.items(): + for _, tx in dtxs.items(): get_utxos_from_inputs(tx.inputs(), tx, utxos) utxos = sorted(utxos.items(), key=lambda item: len(item[1])) @@ -727,7 +727,7 @@ class Heirs(dict, Logger): ) break except Exception: - raise e + raise total_fees = 0 total_fees_real = 0 total_in = 0 @@ -853,6 +853,7 @@ class Heirs(dict, Logger): except AttributeError: return None + @staticmethod def validate_address(address): if is_op_return_address(address): data_hex = address[len(OP_RETURN_PREFIX):] @@ -862,24 +863,27 @@ class Heirs(dict, Logger): raise NotAnAddress(f"not an address,{address}") return address + @staticmethod def validate_amount(amount): try: famount = float(amount[:-1]) if Util.is_perc(amount) else float(amount) if famount <= 0.00000001: raise AmountNotValid(f"amount have to be positive {famount} < 0") except Exception as e: - raise AmountNotValid(f"amount not properly formatted, {e}") + raise AmountNotValid(f"amount not properly formatted, {e}") from e return amount + @staticmethod def validate_locktime(locktime, timestamp_to_check=False): try: if timestamp_to_check: if Util.parse_locktime_string(locktime, None) < timestamp_to_check: raise HeirExpiredException() except Exception as e: - raise LocktimeNotValid(f"locktime string not properly formatted, {e}") + raise LocktimeNotValid(f"locktime string not properly formatted, {e}") from e return locktime + @staticmethod def validate_heir(k, v, timestamp_to_check=False): address = Heirs.validate_address(v[HEIR_ADDRESS]) if is_op_return_address(v[HEIR_ADDRESS]): @@ -889,6 +893,7 @@ class Heirs(dict, Logger): locktime = Heirs.validate_locktime(v[HEIR_LOCKTIME], timestamp_to_check) return (address, amount, locktime) + @staticmethod def _validate(data, timestamp_to_check=False): for k, v in list(data.items()): diff --git a/bal/core/plugin_base.py b/bal/core/plugin_base.py index f3d17bd..809a7ec 100644 --- a/bal/core/plugin_base.py +++ b/bal/core/plugin_base.py @@ -477,14 +477,14 @@ class BalTimestamp: We clamp out-of-range timestamps to INT32_MAX, mirroring Electrum's own ``get_max_allowed_timestamp`` workaround (see Electrum issue #6170). """ - INT32_MAX = 2 ** 31 - 1 + int32_max = 2 ** 31 - 1 try: return datetime.fromtimestamp(ts) except (OSError, OverflowError, ValueError): try: - return datetime.fromtimestamp(min(int(ts), INT32_MAX)) + return datetime.fromtimestamp(min(int(ts), int32_max)) except (OSError, OverflowError, ValueError): - return datetime.fromtimestamp(INT32_MAX) + return datetime.fromtimestamp(int32_max) def to_date(self, from_date=None, reverse=False): """Resolve to a ``datetime``. diff --git a/bal/core/util.py b/bal/core/util.py index c1070fa..d8bfb69 100644 --- a/bal/core/util.py +++ b/bal/core/util.py @@ -400,14 +400,14 @@ class Util: def get_lowest_valid_tx(available_utxos, will): """Placeholder kept from the original code (sorts the will by locktime).""" will = sorted(will.items(), key=lambda x: x[1]["tx"].locktime) - for txid, willitem in will.items(): + for _txid, _willitem in will.items(): pass @staticmethod def get_locktimes(will): """Return the distinct locktimes used by the transactions in ``will``.""" locktimes = {} - for txid, willitem in will.items(): + for _, willitem in will.items(): locktimes[willitem["tx"].locktime] = True return locktimes.keys() @@ -446,7 +446,7 @@ class Util: def get_will_spent_utxos(will): """Collect every input spent by any transaction in ``will``.""" utxos = [] - for txid, willitem in will.items(): + for _, willitem in will.items(): utxos += willitem["tx"].inputs() return utxos diff --git a/bal/core/will.py b/bal/core/will.py index 8b0597d..54b572c 100644 --- a/bal/core/will.py +++ b/bal/core/will.py @@ -43,9 +43,9 @@ from electrum.util import ( bfh, ) +from .heirs import WillExecutorFeeTooHighException from .util import Util from .willexecutors import Willexecutors -from .heirs import WillExecutorFeeTooHighException MIN_LOCKTIME = 1 MIN_BLOCK = 1 @@ -220,13 +220,8 @@ class Will: if ow.we["url"] == nw.we["url"]: if int(ow.we["base_fee"]) > int(nw.we["base_fee"]): return anticipate - else: - if int(ow.tx_fees) != int(nw.tx_fees): - return anticipate - else: - ow.tx.locktime - else: - ow.tx.locktime + elif int(ow.tx_fees) != int(nw.tx_fees): + return anticipate else: if nw.we == ow.we: if not Util.cmp_heirs_by_values(ow.heirs, nw.heirs, [0, 3]): @@ -512,7 +507,7 @@ class Will: @staticmethod def is_new(will): - for wid, w in will.items(): + for _wid, w in will.items(): if w.get_status("VALID") and not w.get_status("COMPLETE"): return True @@ -784,7 +779,7 @@ class Will: timestamp_to_check: Reference UNIX timestamp (usually "now"). """ _logger.info("check if some transaction is expired") - for prevout_str, wid in all_inputs_min_locktime.items(): + for _inputs, wid in all_inputs_min_locktime.items(): for w in wid: if w[1].get_status("VALID"): locktime = int(wid[0][1].tx.locktime) diff --git a/bal/core/willexecutors.py b/bal/core/willexecutors.py index e4d3802..cea2e8d 100644 --- a/bal/core/willexecutors.py +++ b/bal/core/willexecutors.py @@ -389,7 +389,7 @@ class Willexecutors: except Exception as e: _logger.debug(f"error:{e}") if str(e) == "already present": - raise Willexecutors.AlreadyPresentException() + raise Willexecutors.AlreadyPresentException() from None out = False willexecutor["broadcast_status"] = _("Failed") @@ -485,8 +485,7 @@ class Willexecutors: Returns: The same ``willexecutors`` mapping, updated in place. """ - from concurrent.futures import ThreadPoolExecutor, wait - from concurrent.futures import FIRST_COMPLETED + from concurrent.futures import FIRST_COMPLETED, ThreadPoolExecutor, wait items = list(willexecutors.items()) if not items: @@ -566,8 +565,7 @@ class Willexecutors: Returns ``{url: (ok, exception_or_None)}`` for the servers that answered in time (timed-out servers are reported via ``on_timeout``). """ - from concurrent.futures import ThreadPoolExecutor, wait - from concurrent.futures import FIRST_COMPLETED + from concurrent.futures import FIRST_COMPLETED, ThreadPoolExecutor, wait targets = [(url, we) for url, we in willexecutors.items() if "txs" in we] results = {} @@ -684,8 +682,7 @@ class Willexecutors: Returns ``{wid: (result_or_None, exception_or_None)}`` for the servers that answered in time. """ - from concurrent.futures import ThreadPoolExecutor, wait - from concurrent.futures import FIRST_COMPLETED + from concurrent.futures import FIRST_COMPLETED, ThreadPoolExecutor, wait targets = [(wid, url) for wid, url in items if url] results = {} diff --git a/bal/gui/qt/calendar.py b/bal/gui/qt/calendar.py index 82fe601..2bef8ee 100644 --- a/bal/gui/qt/calendar.py +++ b/bal/gui/qt/calendar.py @@ -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: diff --git a/bal/gui/qt/common.py b/bal/gui/qt/common.py index 58045b7..4545dab 100644 --- a/bal/gui/qt/common.py +++ b/bal/gui/qt/common.py @@ -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): diff --git a/bal/gui/qt/dialogs.py b/bal/gui/qt/dialogs.py index 9acdc3f..3a0578f 100644 --- a/bal/gui/qt/dialogs.py +++ b/bal/gui/qt/dialogs.py @@ -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: diff --git a/bal/gui/qt/lists.py b/bal/gui/qt/lists.py index 2bbb9c8..940c04e 100644 --- a/bal/gui/qt/lists.py +++ b/bal/gui/qt/lists.py @@ -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) diff --git a/bal/gui/qt/plugin.py b/bal/gui/qt/plugin.py index 25a1629..1d480eb 100644 --- a/bal/gui/qt/plugin.py +++ b/bal/gui/qt/plugin.py @@ -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), ) diff --git a/bal/gui/qt/widgets.py b/bal/gui/qt/widgets.py index b998d63..f653103 100644 --- a/bal/gui/qt/widgets.py +++ b/bal/gui/qt/widgets.py @@ -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", ) diff --git a/bal/gui/qt/window.py b/bal/gui/qt/window.py index 20a5495..56c959b 100644 --- a/bal/gui/qt/window.py +++ b/bal/gui/qt/window.py @@ -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: diff --git a/pyproject.toml b/pyproject.toml index 94075bc..3e15b5c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,3 +5,15 @@ target-version = "py312" [tool.ruff.lint] select =["E", "W", "F", "I", "N", "B"] ignore = ["E501"] + +[tool.ruff.lint.per-file-ignores] +"bal/gui/qt/*.py" = ["F403", "F405"] # intentional `from .common import *` hub +"bal/gui/qt/common.py" = ["F401"] # re-exports consumed via `import *` +"bal/gui/qt/dialogs.py" = ["N802"] # Qt overrides: closeEvent/hideEvent/getText +"bal/gui/qt/lists.py" = ["N802"] # Qt overrides: createEditor/setEditorData/setModelData +"bal/gui/qt/widgets.py" = ["N802", "N815"] # Qt overrides + Qt signal attrs (valueChanged, ...) +"bal/gui/qt/window.py" = ["N802"] # getMsg +"bal/core/heirs.py" = ["N818", "N802"] # public exception names + buildTransactions API +"bal/core/will.py" = ["N818"] # public exception names +"bal/core/willexecutors.py" = ["N818"] # public exception names +"tests/*.py" = ["N802", "E402"] # deliberate UPPER_CASE helpers + sys.path-before-import diff --git a/tests/gui_fixes_test.py b/tests/gui_fixes_test.py index 27c187e..741603e 100644 --- a/tests/gui_fixes_test.py +++ b/tests/gui_fixes_test.py @@ -33,7 +33,8 @@ def _active_source_without_strings(module) -> str: if isinstance(node.value, str) and hasattr(node, "end_lineno"): self.spans.append((node.lineno, node.end_lineno)) self.generic_visit(node) - s = _S(); s.visit(tree) + s = _S() + s.visit(tree) drop = set() for a, b in s.spans: drop.update(range(a, b + 1)) @@ -45,12 +46,13 @@ def _active_source_without_strings(module) -> str: def main(pkg: str) -> int: from PyQt6.QtWidgets import QApplication, QDialog, QWidget - app = QApplication.instance() or QApplication(sys.argv) + _app = QApplication.instance() or QApplication(sys.argv) wu = importlib.import_module(pkg + ".gui.qt.window_utils") # top_level_of: returns the top-level container of a child widget - w = QWidget(); child = QWidget(w) + w = QWidget() + child = QWidget(w) assert wu.top_level_of(child) is w assert wu.top_level_of(None) is None print("[OK] top_level_of") diff --git a/tests/parallel_ping_test.py b/tests/parallel_ping_test.py index 6041f27..90b7e09 100644 --- a/tests/parallel_ping_test.py +++ b/tests/parallel_ping_test.py @@ -31,7 +31,7 @@ N = 8 # number of servers def main(): we_mod = importlib.import_module(f"{PKG}.core.willexecutors") - W = we_mod.Willexecutors + we_cls = we_mod.Willexecutors # ---- 1) ping_servers_parallel: time ~= slowest, not sum ---- def slow_get_info(url, we, **kwargs): @@ -43,8 +43,8 @@ def main(): we["status"] = 200 return we - orig_get_info = W.get_info_task - W.get_info_task = staticmethod(slow_get_info) + orig_get_info = we_cls.get_info_task + we_cls.get_info_task = staticmethod(slow_get_info) try: wes = {} for i in range(N): @@ -57,7 +57,7 @@ def main(): seen.append((url, ok)) start = time.time() - W.ping_servers_parallel(wes, on_each=on_each, max_workers=N) + we_cls.ping_servers_parallel(wes, on_each=on_each, max_workers=N) elapsed = time.time() - start # Sequential would take ~ N * SLOW. Parallel must be far less. @@ -81,15 +81,15 @@ def main(): assert we["status"] == "KO", (url, we) print("[OK] ping results written back into the willexecutors mapping") finally: - W.get_info_task = orig_get_info + we_cls.get_info_task = orig_get_info # ---- 2) push_transactions_parallel: time ~= slowest, not sum ---- def slow_push(we, **kwargs): time.sleep(SLOW) return "fail" not in we["url"] - orig_push = W.push_transactions_to_willexecutor - W.push_transactions_to_willexecutor = staticmethod(slow_push) + orig_push = we_cls.push_transactions_to_willexecutor + we_cls.push_transactions_to_willexecutor = staticmethod(slow_push) try: wes = {} for i in range(N): @@ -106,7 +106,7 @@ def main(): pushed.append((url, ok)) start = time.time() - results = W.push_transactions_parallel(wes, on_each=on_each_push, + results = we_cls.push_transactions_parallel(wes, on_each=on_each_push, max_workers=N) elapsed = time.time() - start @@ -117,11 +117,11 @@ def main(): f"(sequential would be ~{sequential:.2f}s)") assert len(results) == N, results - for url, (ok, exc) in results.items(): + for url, (ok, _exc) in results.items(): assert ok == ("good" in url), (url, ok) print("[OK] push results correct for every server") finally: - W.push_transactions_to_willexecutor = orig_push + we_cls.push_transactions_to_willexecutor = orig_push # ---- 2b) global deadline: a hung server must not block past `deadline` ---- def hanging_push(we, **kwargs): @@ -129,8 +129,8 @@ def main(): time.sleep(10) return True - orig_push2 = W.push_transactions_to_willexecutor - W.push_transactions_to_willexecutor = staticmethod(hanging_push) + orig_push2 = we_cls.push_transactions_to_willexecutor + we_cls.push_transactions_to_willexecutor = staticmethod(hanging_push) try: wes = { "https://fast.example": { @@ -146,7 +146,7 @@ def main(): return True time.sleep(10) return True - W.push_transactions_to_willexecutor = staticmethod(fast_or_hang) + we_cls.push_transactions_to_willexecutor = staticmethod(fast_or_hang) timed_out = [] @@ -154,7 +154,7 @@ def main(): timed_out.append(url) start = time.time() - W.push_transactions_parallel( + we_cls.push_transactions_parallel( wes, max_workers=2, deadline=1.0, on_timeout=on_timeout ) elapsed = time.time() - start @@ -163,7 +163,7 @@ def main(): print(f"[OK] global deadline enforced: returned in {elapsed:.1f}s, " f"hung server reported via on_timeout") finally: - W.push_transactions_to_willexecutor = orig_push2 + we_cls.push_transactions_to_willexecutor = orig_push2 # ---- 2c) on_tick is fired periodically from the CALLING thread ---- # The elapsed-time counter is driven by an on_tick callback called from the @@ -175,8 +175,8 @@ def main(): time.sleep(SLOW * 6) # ~3s, long enough for several ticks return True - orig_push3 = W.push_transactions_to_willexecutor - W.push_transactions_to_willexecutor = staticmethod(slow_push2) + orig_push3 = we_cls.push_transactions_to_willexecutor + we_cls.push_transactions_to_willexecutor = staticmethod(slow_push2) try: wes = { "https://tick.example": { @@ -191,7 +191,7 @@ def main(): ticks.append(time.time()) tick_threads.add(threading.current_thread()) - W.push_transactions_parallel( + we_cls.push_transactions_parallel( wes, max_workers=1, on_tick=on_tick, tick_interval=0.5 ) # ~3s push with 0.5s ticks => at least a few ticks. @@ -202,7 +202,7 @@ def main(): ) print(f"[OK] on_tick fired {len(ticks)} times from the calling thread") finally: - W.push_transactions_to_willexecutor = orig_push3 + we_cls.push_transactions_to_willexecutor = orig_push3 # ---- 2d) check_transactions_parallel: parallel + deadline + on_tick ---- # Pressing "Check" verifies each will-executor still holds its tx. This used @@ -214,8 +214,8 @@ def main(): time.sleep(SLOW) return {"tx": "ok"} if "good" in url else None - orig_check = W.check_transaction - W.check_transaction = staticmethod(slow_check) + orig_check = we_cls.check_transaction + we_cls.check_transaction = staticmethod(slow_check) try: targets = [] for i in range(N): @@ -228,7 +228,7 @@ def main(): checked.append((wid, res)) start = time.time() - results = W.check_transactions_parallel( + results = we_cls.check_transactions_parallel( targets, on_each=on_each_check, max_workers=N ) elapsed = time.time() - start @@ -239,7 +239,7 @@ def main(): print(f"[OK] check parallel: {elapsed:.2f}s for {N} servers " f"(sequential would be ~{sequential:.2f}s)") finally: - W.check_transaction = orig_check + we_cls.check_transaction = orig_check # 2d-bis) global deadline + on_tick from the calling thread def hanging_check(txid, url, **kwargs): @@ -248,8 +248,8 @@ def main(): time.sleep(10) return {"tx": "ok"} - orig_check2 = W.check_transaction - W.check_transaction = staticmethod(hanging_check) + orig_check2 = we_cls.check_transaction + we_cls.check_transaction = staticmethod(hanging_check) try: targets = [ ("idf", "https://fast.example"), @@ -268,7 +268,7 @@ def main(): tick_threads.add(threading.current_thread()) start = time.time() - W.check_transactions_parallel( + we_cls.check_transactions_parallel( targets, max_workers=2, deadline=2.0, on_timeout=on_timeout_check, on_tick=on_tick_check, tick_interval=0.5, @@ -282,7 +282,7 @@ def main(): print(f"[OK] check global deadline enforced ({elapsed:.1f}s), on_tick " f"fired {len(ticks)}x from the calling thread") finally: - W.check_transaction = orig_check2 + we_cls.check_transaction = orig_check2 # ---- 3) the wizard's loop_push must use the parallel helper ---- # The "Building Will" wizard broadcasts via BalBuildWillDialog.loop_push. diff --git a/tests/preview_build_will_dialog.py b/tests/preview_build_will_dialog.py index 20d677a..ed64602 100644 --- a/tests/preview_build_will_dialog.py +++ b/tests/preview_build_will_dialog.py @@ -17,8 +17,8 @@ import sys os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") -from PyQt6.QtWidgets import QApplication, QLabel, QWidget, QVBoxLayout from PyQt6.QtCore import Qt +from PyQt6.QtWidgets import QApplication, QLabel, QVBoxLayout, QWidget # Same colors as BalBuildWillDialog COLOR_WARNING = "#cfa808" diff --git a/tests/preview_building_will_close_btn.py b/tests/preview_building_will_close_btn.py index 939fa96..f7c4319 100644 --- a/tests/preview_building_will_close_btn.py +++ b/tests/preview_building_will_close_btn.py @@ -17,10 +17,15 @@ import os os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") -from PyQt6.QtWidgets import ( # noqa: E402 - QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, -) from PyQt6.QtCore import Qt # noqa: E402 +from PyQt6.QtWidgets import ( # noqa: E402 + QApplication, + QHBoxLayout, + QLabel, + QPushButton, + QVBoxLayout, + QWidget, +) COLOR_OK = "#05ad05" diff --git a/tests/preview_we_rows.py b/tests/preview_we_rows.py index 91da189..95f7b12 100644 --- a/tests/preview_we_rows.py +++ b/tests/preview_we_rows.py @@ -21,8 +21,8 @@ import sys os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") -from PyQt6.QtWidgets import QApplication, QLabel, QWidget, QVBoxLayout from PyQt6.QtCore import Qt +from PyQt6.QtWidgets import QApplication, QLabel, QVBoxLayout, QWidget COLOR_ERROR = "#ff0000" COLOR_OK = "#05ad05" diff --git a/tests/preview_wizard_button.py b/tests/preview_wizard_button.py index a11c6ee..79a5f45 100644 --- a/tests/preview_wizard_button.py +++ b/tests/preview_wizard_button.py @@ -20,12 +20,17 @@ import os os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") -from PyQt6.QtWidgets import ( # noqa: E402 - QApplication, QWidget, QHBoxLayout, QPushButton, QComboBox, QLineEdit, - QLabel, -) +from PyQt6.QtCore import QSize # noqa: E402 from PyQt6.QtGui import QIcon, QPixmap # noqa: E402 -from PyQt6.QtCore import QSize, Qt # noqa: E402 +from PyQt6.QtWidgets import ( # noqa: E402 + QApplication, + QComboBox, + QHBoxLayout, + QLabel, + QLineEdit, + QPushButton, + QWidget, +) ICON_PATH = os.path.join(os.path.dirname(__file__), "..", "bal", "icons", "wizard.png") diff --git a/tests/preview_wizard_settings_align.py b/tests/preview_wizard_settings_align.py index 4870b16..5aa9858 100644 --- a/tests/preview_wizard_settings_align.py +++ b/tests/preview_wizard_settings_align.py @@ -27,12 +27,19 @@ import os os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") -from PyQt6.QtWidgets import ( # noqa: E402 - QApplication, QWidget, QVBoxLayout, QHBoxLayout, QToolButton, QComboBox, - QLineEdit, QSpinBox, QLabel, -) -from PyQt6.QtGui import QFontMetrics # noqa: E402 from PyQt6.QtCore import Qt # noqa: E402 +from PyQt6.QtGui import QFontMetrics # noqa: E402 +from PyQt6.QtWidgets import ( # noqa: E402 + QApplication, + QComboBox, + QHBoxLayout, + QLabel, + QLineEdit, + QSpinBox, + QToolButton, + QVBoxLayout, + QWidget, +) def _char_w(): diff --git a/tests/sim_update_flows.py b/tests/sim_update_flows.py index 70d324e..fb65e58 100644 --- a/tests/sim_update_flows.py +++ b/tests/sim_update_flows.py @@ -21,18 +21,21 @@ Run: QT_QPA_PLATFORM=offscreen PYTHONPATH=electrum-src python3 tests/sim_update_flows.py """ -import sys -import os import copy +import os +import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) from bal.core.will import ( - WillItem, Will, - NotCompleteWillException, HeirNotFoundException, NoHeirsException, - TxFeesChangedException, WillExpiredException, + HeirNotFoundException, + NoHeirsException, + NotCompleteWillException, + TxFeesChangedException, + Will, + WillExpiredException, + WillItem, ) -from bal.core.util import Util # A valid serialized tx (1 input + 1 output, version 2). Its nLockTime is 0. _VALID_TX_HEX = ( diff --git a/tests/smoke_test.py b/tests/smoke_test.py index e21d2a0..c01b51a 100644 --- a/tests/smoke_test.py +++ b/tests/smoke_test.py @@ -26,30 +26,28 @@ def main(): from PyQt6.QtWidgets import QApplication # noqa _app = QApplication.instance() or QApplication([]) - results = {} - # 1) Core modules import (these must be GUI-free). bal = imp_core("bal", "core.plugin_base") util = imp_core("util", "core.util") heirs = imp_core("heirs", "core.heirs") will = imp_core("will", "core.will") - we = imp_core("willexecutors", "core.willexecutors") + _we = imp_core("willexecutors", "core.willexecutors") # 2) GUI module imports. qt = imp_gui() # 3) Behaviour checks (pure logic, must be identical across versions). - BalTimestamp = bal.BalTimestamp - assert BalTimestamp("30d").duration_to_days() == 30, "BalTimestamp 30d" - assert BalTimestamp("1y").duration_to_days() == 365, "BalTimestamp 1y" - assert str(BalTimestamp("7d")) == "7d", "BalTimestamp str" + bal_timestamp = bal.BalTimestamp + assert bal_timestamp("30d").duration_to_days() == 30, "BalTimestamp 30d" + assert bal_timestamp("1y").duration_to_days() == 365, "BalTimestamp 1y" + assert str(bal_timestamp("7d")) == "7d", "BalTimestamp str" - Util = util.Util - assert Util.is_perc("50%") is True - assert Util.is_perc("100") is False - assert Util.text_to_hex("BAL") == "42414c" - assert Util.hex_to_text("42414c") == "BAL" - assert Util.int_locktime(days=1) == 86400 + util_cls = util.Util + assert util_cls.is_perc("50%") is True + assert util_cls.is_perc("100") is False + assert util_cls.text_to_hex("BAL") == "42414c" + assert util_cls.hex_to_text("42414c") == "BAL" + assert util_cls.int_locktime(days=1) == 86400 # heirs constants must keep the same column layout (very delicate!) assert heirs.HEIR_ADDRESS == 0 diff --git a/tests/test_anticipate_manual_locktime.py b/tests/test_anticipate_manual_locktime.py index 8e9b5eb..75a6c53 100644 --- a/tests/test_anticipate_manual_locktime.py +++ b/tests/test_anticipate_manual_locktime.py @@ -27,19 +27,19 @@ Run: tests/test_anticipate_manual_locktime.py -q """ -import sys -import os import copy +import os +import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) import pytest # noqa: E402 from bal.core.will import ( # noqa: E402 - WillItem, - Will, NotCompleteWillException, + Will, WillExpiredException, + WillItem, ) # A valid serialized tx (1 input + 1 output, version 2). diff --git a/tests/test_anticipate_past_locktime.py b/tests/test_anticipate_past_locktime.py index 9b4bf6a..d0d1d9a 100644 --- a/tests/test_anticipate_past_locktime.py +++ b/tests/test_anticipate_past_locktime.py @@ -22,13 +22,13 @@ whether a fix is needed. Run: python3 -m pytest tests/test_anticipate_past_locktime.py -q """ -import sys import os +import sys import time sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) -from bal.core.util import Util, LOCKTIME_THRESHOLD +from bal.core.util import LOCKTIME_THRESHOLD, Util # --------------------------------------------------------------------------- diff --git a/tests/test_core_heirs.py b/tests/test_core_heirs.py index 69756c2..c292066 100644 --- a/tests/test_core_heirs.py +++ b/tests/test_core_heirs.py @@ -9,25 +9,34 @@ Run: python3 tests/test_core_heirs.py """ -import sys import os +import sys + sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) from bal.core.heirs import ( - HEIR_ADDRESS, HEIR_AMOUNT, HEIR_LOCKTIME, HEIR_REAL_AMOUNT, - HEIR_DUST_AMOUNT, TRANSACTION_LABEL, + HEIR_ADDRESS, + HEIR_AMOUNT, + HEIR_DUST_AMOUNT, + HEIR_LOCKTIME, + HEIR_REAL_AMOUNT, OP_RETURN_PREFIX, - create_op_return_script, - is_op_return_address, get_op_return_hex, validate_op_return_hex, + TRANSACTION_LABEL, AliasNotFoundException, - NotAnAddress, AmountNotValid, LocktimeNotValid, - HeirExpiredException, HeirAmountIsDustException, - NoHeirsException, WillExecutorFeeException, + AmountNotValid, BalanceTooLowException, + HeirAmountIsDustException, Heirs, + LocktimeNotValid, + NoHeirsException, + NotAnAddress, + WillExecutorFeeException, + create_op_return_script, + get_op_return_hex, + is_op_return_address, + validate_op_return_hex, ) - # ------------------------------------------------------------------ # # Constants # ------------------------------------------------------------------ # @@ -70,7 +79,7 @@ def test_op_return_empty(): def test_op_return_too_big(): try: create_op_return_script("ab" * 81) # 81 bytes > max 80 - assert False, "expected ValueError" + raise AssertionError("expected ValueError") except ValueError: pass @@ -179,13 +188,13 @@ def test_validate_amount(): # Invalid try: Heirs.validate_amount("0.000000001") - assert False, "expected AmountNotValid" + raise AssertionError("expected AmountNotValid") except AmountNotValid: pass try: Heirs.validate_amount("-1") - assert False, "expected AmountNotValid" + raise AssertionError("expected AmountNotValid") except AmountNotValid: pass @@ -209,7 +218,7 @@ def test_validate_locktime_expired(): past = int(time.time()) - 86400 # yesterday try: Heirs.validate_locktime(str(past), timestamp_to_check=past + 1) - assert False, "expected LocktimeNotValid" + raise AssertionError("expected LocktimeNotValid") except LocktimeNotValid: pass @@ -289,7 +298,7 @@ def test_validate_op_return_hex_valid(): def test_validate_op_return_hex_invalid(): try: validate_op_return_hex("nothex!!") - assert False, "expected NotAnAddress" + raise AssertionError("expected NotAnAddress") except NotAnAddress: pass @@ -297,7 +306,7 @@ def test_validate_op_return_hex_invalid(): def test_validate_op_return_hex_too_long(): try: validate_op_return_hex("ab" * 81) - assert False, "expected NotAnAddress" + raise AssertionError("expected NotAnAddress") except NotAnAddress: pass @@ -355,4 +364,4 @@ if __name__ == "__main__": if name.startswith("test_"): globals()[name]() print(f" [OK] {name}") - print(f"[OK] All heirs tests passed") + print("[OK] All heirs tests passed") diff --git a/tests/test_core_heirs_extra.py b/tests/test_core_heirs_extra.py index 8b519a6..322278a 100644 --- a/tests/test_core_heirs_extra.py +++ b/tests/test_core_heirs_extra.py @@ -8,19 +8,20 @@ Run: QT_QPA_PLATFORM=offscreen python3 tests/test_core_heirs_extra.py """ -import sys import os +import sys from unittest.mock import MagicMock, patch sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) from bal.core.heirs import ( - Heirs, create_op_return_script, reduce_outputs, - HEIR_ADDRESS, HEIR_AMOUNT, HEIR_LOCKTIME, HEIR_REAL_AMOUNT, + HEIR_AMOUNT, + Heirs, + create_op_return_script, + reduce_outputs, ) from bal.core.willexecutors import Willexecutors - # ------------------------------------------------------------------ # # Heirs db-dependent methods # ------------------------------------------------------------------ # @@ -188,7 +189,7 @@ def test_validate_address_invalid(): from bal.core.heirs import NotAnAddress try: Heirs.validate_address("bad") - assert False, "should have raised" + raise AssertionError("should have raised") except NotAnAddress: pass diff --git a/tests/test_core_plugin_base.py b/tests/test_core_plugin_base.py index e9e4a68..37305d0 100644 --- a/tests/test_core_plugin_base.py +++ b/tests/test_core_plugin_base.py @@ -8,14 +8,15 @@ Run: python3 tests/test_core_plugin_base.py """ -import sys import os +import sys import time + sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) -from datetime import datetime, date, timedelta -from bal.core.plugin_base import BalTimestamp, BalPlugin, BalConfig +from datetime import date, datetime, timedelta +from bal.core.plugin_base import BalConfig, BalPlugin, BalTimestamp # ------------------------------------------------------------------ # # BalTimestamp diff --git a/tests/test_core_util.py b/tests/test_core_util.py index 8da886b..c58fd7d 100644 --- a/tests/test_core_util.py +++ b/tests/test_core_util.py @@ -9,13 +9,14 @@ Run: python3 tests/test_core_util.py """ -import sys import os +import sys + sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) import pytest -from bal.core.util import Util, LOCKTIME_THRESHOLD +from bal.core.util import Util def test_locktime_to_str(): @@ -40,7 +41,7 @@ def test_str_to_locktime(): # the block-height suffix "b" was removed (A1): "144b" is no longer a valid # relative locktime, so it is NOT passed through unchanged. - with pytest.raises(Exception): + with pytest.raises(ValueError): Util.str_to_locktime("144b") # integer string -> int @@ -347,44 +348,44 @@ def test_in_utxo(): def test_cmp_output(): - class O: + class Obj: def __init__(self, addr, val): self.address = addr self.value = val - assert Util.cmp_output(O("a", 100), O("a", 100)) is True - assert Util.cmp_output(O("a", 100), O("b", 100)) is False - assert Util.cmp_output(O("a", 100), O("a", 200)) is False + assert Util.cmp_output(Obj("a", 100), Obj("a", 100)) is True + assert Util.cmp_output(Obj("a", 100), Obj("b", 100)) is False + assert Util.cmp_output(Obj("a", 100), Obj("a", 200)) is False def test_in_output(): - class O: + class Obj: def __init__(self, addr, val): self.address = addr self.value = val - outputs = [O("a", 100), O("b", 200)] - assert Util.in_output(O("a", 100), outputs) is True - assert Util.in_output(O("z", 999), outputs) is False - assert Util.in_output(O("a", 100), []) is False + outputs = [Obj("a", 100), Obj("b", 200)] + assert Util.in_output(Obj("a", 100), outputs) is True + assert Util.in_output(Obj("z", 999), outputs) is False + assert Util.in_output(Obj("a", 100), []) is False def test_din_output(): - class O: + class Obj: def __init__(self, addr, val): self.address = addr self.value = val - outputs = [O("a", 100), O("b", 200)] + outputs = [Obj("a", 100), Obj("b", 200)] # same amount AND same address - same_amt, same_addr = Util.din_output(O("a", 100), outputs) + same_amt, same_addr = Util.din_output(Obj("a", 100), outputs) assert same_amt is True and same_addr is True # same amount but different address - same_amt, same_addr = Util.din_output(O("c", 100), outputs) + same_amt, same_addr = Util.din_output(Obj("c", 100), outputs) assert same_amt is True and same_addr is False # different amount - same_amt, same_addr = Util.din_output(O("z", 999), outputs) + same_amt, same_addr = Util.din_output(Obj("z", 999), outputs) assert same_amt is False and same_addr is False diff --git a/tests/test_core_will.py b/tests/test_core_will.py index 3c422ea..b07230a 100644 --- a/tests/test_core_will.py +++ b/tests/test_core_will.py @@ -8,13 +8,13 @@ Run: python3 tests/test_core_will.py """ -import sys -import os import copy +import os +import sys + sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) -from bal.core.will import WillItem, Will -from bal.core.willexecutors import Willexecutors +from bal.core.will import Will, WillItem # A valid serialized Bitcoin transaction hex (1 input + 1 P2PKH output, version 2) _VALID_TX_HEX = ( @@ -332,11 +332,19 @@ def test_will_check_tx_height(): def test_exceptions(): from bal.core.will import ( - WillException, WillExpiredException, NotCompleteWillException, - HeirChangeException, TxFeesChangedException, HeirNotFoundException, - WillexecutorChangeException, NoWillExecutorNotPresent, - WillExecutorNotPresent, NoHeirsException, - AmountException, PercAmountException, FixedAmountException, + AmountException, + FixedAmountException, + HeirChangeException, + HeirNotFoundException, + NoHeirsException, + NotCompleteWillException, + NoWillExecutorNotPresent, + PercAmountException, + TxFeesChangedException, + WillException, + WillexecutorChangeException, + WillExecutorNotPresent, + WillExpiredException, WillPostponedException, ) @@ -375,4 +383,4 @@ if __name__ == "__main__": if name.startswith("test_"): globals()[name]() print(f" [OK] {name}") - print(f"[OK] All Will tests passed") + print("[OK] All Will tests passed") diff --git a/tests/test_core_will_extra.py b/tests/test_core_will_extra.py index 050b0af..71fffaf 100644 --- a/tests/test_core_will_extra.py +++ b/tests/test_core_will_extra.py @@ -8,15 +8,16 @@ Run: QT_QPA_PLATFORM=offscreen python3 tests/test_core_will_extra.py """ -import sys import os -from unittest.mock import MagicMock, patch, PropertyMock, call +import sys +from unittest.mock import MagicMock, patch sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) -from bal.core.will import Will, WillItem from electrum.transaction import Transaction +from bal.core.will import Will, WillItem + _VALID_TX_HEX = ( "01000000012a5c9a94fcde98f5581cd00162c60a13936ceb75389ea65b" "f38633b424eb4031000000006c493046022100a82bbc57a0136751e543" diff --git a/tests/test_core_will_invalidate.py b/tests/test_core_will_invalidate.py index 768a15e..e752660 100644 --- a/tests/test_core_will_invalidate.py +++ b/tests/test_core_will_invalidate.py @@ -22,15 +22,14 @@ import os import sys from unittest.mock import MagicMock, patch -import pytest - sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) -from bal.core.will import Will, WillItem - # Patch Transaction.add_info_from_wallet so WillItem can parse the tx hex # without a live Electrum wallet connection. from electrum.transaction import Transaction + +from bal.core.will import Will, WillItem + _patcher = patch.object(Transaction, "add_info_from_wallet") _patcher.start() diff --git a/tests/test_group_b_auto_sign.py b/tests/test_group_b_auto_sign.py index 0c5b504..a95ccd0 100644 --- a/tests/test_group_b_auto_sign.py +++ b/tests/test_group_b_auto_sign.py @@ -28,7 +28,6 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) from bal.core.plugin_base import BalConfig from bal.core.willexecutors import Willexecutors - # ------------------------------------------------------------------ # # Mocks # ------------------------------------------------------------------ # diff --git a/tests/test_group_c_settings.py b/tests/test_group_c_settings.py index 4f200eb..f284431 100644 --- a/tests/test_group_c_settings.py +++ b/tests/test_group_c_settings.py @@ -27,7 +27,6 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) from bal.core.plugin_base import BalConfig - # ------------------------------------------------------------------ # # Mocks # ------------------------------------------------------------------ # diff --git a/tests/test_group_d_alarms.py b/tests/test_group_d_alarms.py index 4b798f5..430b5d5 100644 --- a/tests/test_group_d_alarms.py +++ b/tests/test_group_d_alarms.py @@ -27,7 +27,6 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) from bal.core.plugin_base import BalConfig from bal.gui.qt.widgets import compute_reminder_offsets - # ------------------------------------------------------------------ # # Mocks # ------------------------------------------------------------------ # diff --git a/tests/test_group_e_karen7_invalidate.py b/tests/test_group_e_karen7_invalidate.py index 9ab508e..4fd9a3f 100644 --- a/tests/test_group_e_karen7_invalidate.py +++ b/tests/test_group_e_karen7_invalidate.py @@ -25,7 +25,6 @@ import copy import json import os import sys -import warnings import pytest @@ -42,15 +41,12 @@ from electrum import bitcoin from electrum.transaction import ( PartialTransaction, PartialTxInput, - PartialTxOutput, TxOutpoint, ) from electrum.util import bfh from bal.core.heirs import Heirs from bal.core.will import Will, WillItem -from bal.core.willexecutors import Willexecutors - # ------------------------------------------------------------------ # # Load karen7 wallet data @@ -166,7 +162,7 @@ def _build_utxo_value_map(data): for txid, outputs in txo.items(): if not isinstance(outputs, dict): continue - for addr, out_map in outputs.items(): + for _, out_map in outputs.items(): if not isinstance(out_map, dict): continue for idx, info in out_map.items(): @@ -188,7 +184,7 @@ def _populate_input_values(will, utxo_value_map): This is the equivalent of what ``add_info_from_wallet`` does in the real flow: looking up the UTXO value and attaching it to the input. """ - for wid, wi in will.items(): + for _, wi in will.items(): for txin in wi.tx.inputs(): prevout_str = txin.prevout.to_str() if txin._trusted_value_sats is None and prevout_str in utxo_value_map: diff --git a/tests/test_group_e_mock_karen7.py b/tests/test_group_e_mock_karen7.py index 208e91e..00d86c3 100644 --- a/tests/test_group_e_mock_karen7.py +++ b/tests/test_group_e_mock_karen7.py @@ -36,13 +36,12 @@ import pytest # below the repo root that contains the ``bal`` package). sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) -from bal.core.will import WillItem, Will, HeirNotFoundException from bal.core.heirs import Heirs +from bal.core.will import HeirNotFoundException, Will, WillItem from bal.core.willexecutors import Willexecutors from bal.gui.qt.calendar import BalCalendar from bal.gui.qt.widgets import compute_reminder_offsets - # A valid serialized Bitcoin transaction hex (1 input + 1 P2PKH output, # version 2). Reused from test_core_will.py so WillItem can parse a real tx. _VALID_TX_HEX = ( @@ -550,7 +549,7 @@ def test_e5_build_with_real_wallet_heirs_and_utxos(): for txid, outputs in txo.items(): if not isinstance(outputs, dict): continue - for addr, out_map in outputs.items(): + for _, out_map in outputs.items(): if not isinstance(out_map, dict): continue for idx, info in out_map.items(): diff --git a/tests/test_group_f_heir_change_rebuild.py b/tests/test_group_f_heir_change_rebuild.py index f270554..ad45aac 100644 --- a/tests/test_group_f_heir_change_rebuild.py +++ b/tests/test_group_f_heir_change_rebuild.py @@ -16,8 +16,9 @@ Run: python3 tests/test_group_f_heir_change_rebuild.py """ -import sys import os +import sys + sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) from bal.core.will import Will diff --git a/tests/test_group_g_basic_calendar.py b/tests/test_group_g_basic_calendar.py index 2b4333f..6f40834 100644 --- a/tests/test_group_g_basic_calendar.py +++ b/tests/test_group_g_basic_calendar.py @@ -24,8 +24,7 @@ import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) -from bal.gui.qt.widgets import (BASIC_REMINDER_OFFSETS, - basic_reminder_offsets) +from bal.gui.qt.widgets import BASIC_REMINDER_OFFSETS, basic_reminder_offsets def test_basic_offsets_all_future(): diff --git a/tests/test_group_i_basic_checkalive.py b/tests/test_group_i_basic_checkalive.py index c5131e0..906e6b0 100644 --- a/tests/test_group_i_basic_checkalive.py +++ b/tests/test_group_i_basic_checkalive.py @@ -32,7 +32,6 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) from bal.gui.qt.window import BalWindow # noqa: E402 (path insert above) - OFFSET = BalWindow.BASIC_MODE_CHECK_ALIVE_OFFSET_SECONDS diff --git a/tests/test_gui_calendar.py b/tests/test_gui_calendar.py index 0f42b82..7d1b1e0 100644 --- a/tests/test_gui_calendar.py +++ b/tests/test_gui_calendar.py @@ -10,14 +10,12 @@ Run: import os import sys -import tempfile from datetime import datetime, timezone sys.path.insert(0, __file__.rsplit("/", 2)[0]) from bal.gui.qt.calendar import BalCalendar - # ------------------------------------------------------------------ # # format_time # ------------------------------------------------------------------ # diff --git a/tests/test_gui_common.py b/tests/test_gui_common.py index 0d12c53..77eb2b8 100644 --- a/tests/test_gui_common.py +++ b/tests/test_gui_common.py @@ -8,12 +8,13 @@ Run: """ import sys + sys.path.insert(0, __file__.rsplit("/", 2)[0]) -from PyQt6.QtWidgets import QApplication, QGridLayout, QLabel, QWidget +from PyQt6.QtWidgets import QApplication, QGridLayout, QLabel # Import the module itself, not via "from .common import *" -import bal.gui.qt.common as C +import bal.gui.qt.common as common _app = QApplication.instance() or QApplication(sys.argv) @@ -23,18 +24,18 @@ _app = QApplication.instance() or QApplication(sys.argv) # ------------------------------------------------------------------ # def test_shown_cv_default(): - cv = C.shown_cv(True) + cv = common.shown_cv(True) assert cv.get() is True def test_shown_cv_set(): - cv = C.shown_cv(True) + cv = common.shown_cv(True) cv.set(False) assert cv.get() is False def test_shown_cv_roundtrip(): - cv = C.shown_cv(False) + cv = common.shown_cv(False) assert cv.get() is False cv.set(True) assert cv.get() is True @@ -47,19 +48,19 @@ def test_shown_cv_roundtrip(): # ------------------------------------------------------------------ # def test_check_alive_error_default(): - err = C.CheckAliveError(1000000) + err = common.CheckAliveError(1000000) assert err.timestamp_to_check == 1000000 def test_check_alive_error_str(): - err = C.CheckAliveError(1000000) + err = common.CheckAliveError(1000000) s = str(err) assert "Check alive expired" in s assert "1970" in s def test_check_alive_error_subclass(): - assert issubclass(C.CheckAliveError, Exception) + assert issubclass(common.CheckAliveError, Exception) # ------------------------------------------------------------------ # @@ -68,17 +69,15 @@ def test_check_alive_error_subclass(): def test_add_widget(): grid = QGridLayout() - parent = QWidget() label = QLabel("test") - C.add_widget(grid, "Label", label, 0, "Help text") + common.add_widget(grid, "Label", label, 0, "Help text") assert grid.count() == 3 # label + widget + help button def test_add_widget_multiple_rows(): grid = QGridLayout() - parent = QWidget() - C.add_widget(grid, "A", QLabel("a"), 0, "help_a") - C.add_widget(grid, "B", QLabel("b"), 1, "help_b") + common.add_widget(grid, "A", QLabel("a"), 0, "help_a") + common.add_widget(grid, "B", QLabel("b"), 1, "help_b") assert grid.count() == 6 @@ -87,7 +86,7 @@ def test_add_widget_multiple_rows(): # ------------------------------------------------------------------ # def test_log_error_no_window(): - C.log_error((Exception, Exception("test"), None)) + common.log_error((Exception, Exception("test"), None)) # ------------------------------------------------------------------ # diff --git a/tests/test_gui_theme.py b/tests/test_gui_theme.py index e31e05d..72f2827 100644 --- a/tests/test_gui_theme.py +++ b/tests/test_gui_theme.py @@ -8,11 +8,11 @@ Run: """ import sys + sys.path.insert(0, __file__.rsplit("/", 2)[0]) from bal.gui.qt.theme import status_color - # ------------------------------------------------------------------ # # Helpers # ------------------------------------------------------------------ # diff --git a/tests/test_gui_widgets.py b/tests/test_gui_widgets.py index 3806876..89f33ab 100644 --- a/tests/test_gui_widgets.py +++ b/tests/test_gui_widgets.py @@ -13,13 +13,11 @@ Run: """ import sys + sys.path.insert(0, __file__.rsplit("/", 2)[0]) -from PyQt6.QtCore import QTimer from PyQt6.QtWidgets import QApplication, QWidget -from electrum.util import DECIMAL_POINT, decimal_point_to_base_unit_name - _app = QApplication.instance() or QApplication(sys.argv) diff --git a/tests/test_gui_window_utils.py b/tests/test_gui_window_utils.py index 8a99912..136b1d0 100644 --- a/tests/test_gui_window_utils.py +++ b/tests/test_gui_window_utils.py @@ -8,13 +8,18 @@ Run: """ import sys + sys.path.insert(0, __file__.rsplit("/", 2)[0]) from PyQt6.QtCore import QTimer from PyQt6.QtWidgets import QApplication, QDialog, QWidget from bal.gui.qt.window_utils import ( - bring_to_front, show_modal, show_on_top, stop_thread, top_level_of, + bring_to_front, + show_modal, + show_on_top, + stop_thread, + top_level_of, ) _app = QApplication.instance() or QApplication(sys.argv) diff --git a/tests/test_no_willexecutor_karen7.py b/tests/test_no_willexecutor_karen7.py index 162ce22..de37d43 100644 --- a/tests/test_no_willexecutor_karen7.py +++ b/tests/test_no_willexecutor_karen7.py @@ -33,10 +33,9 @@ import logging import os import sys import time -from unittest.mock import MagicMock, patch +from unittest.mock import patch import pytest - from electrum import constants constants.net = constants.BitcoinRegtest @@ -48,10 +47,14 @@ from electrum.transaction import PartialTxInput, TxOutpoint from electrum.util import bfh from bal.core.heirs import Heirs -from bal.core.will import Will, WillItem, NoWillExecutorNotPresent, NotCompleteWillException +from bal.core.will import ( + NotCompleteWillException, + NoWillExecutorNotPresent, + Will, + WillItem, +) from bal.core.willexecutors import Willexecutors - # ------------------------------------------------------------------ # # Load karen7 wallet data # ------------------------------------------------------------------ # @@ -479,11 +482,11 @@ class TestNoWillexecutorKaren7: dialog = FakeBuildWillDialog(self.bal_window) dialog.task_phase1() assert any( - "Not present - select one or enable backup mode" in l - for l in dialog.labels + "Not present - select one or enable backup mode" in label + for label in dialog.labels ), "dialog labels must contain the 'not present' message" assert any( - "#ff0000" in l for l in dialog.labels + "#ff0000" in label for label in dialog.labels ), "dialog labels must use red (COLOR_ERROR)" def test_task_phase1_adds_action_buttons(self): diff --git a/tests/test_reproduce_none_type.py b/tests/test_reproduce_none_type.py index 945f731..3ccdd6d 100644 --- a/tests/test_reproduce_none_type.py +++ b/tests/test_reproduce_none_type.py @@ -23,9 +23,15 @@ if os.path.isdir(ELECTRUM_DIR): sys.path.insert(0, ELECTRUM_DIR) from bal.core.heirs import Heirs -from bal.core.willexecutors import Willexecutors -from bal.core.will import Will, WillItem, NotCompleteWillException, NoHeirsException, NoWillExecutorNotPresent from bal.core.plugin_base import BalPlugin, BalTimestamp +from bal.core.will import ( + NoHeirsException, + NotCompleteWillException, + NoWillExecutorNotPresent, + Will, + WillItem, +) +from bal.core.willexecutors import Willexecutors # ------------------------------------------------------------------ # # Load karen7 wallet data @@ -53,7 +59,7 @@ def build_utxos(data): for txid, outputs in txo.items(): if not isinstance(outputs, dict): continue - for addr, out_map in outputs.items(): + for _, out_map in outputs.items(): if not isinstance(out_map, dict): continue for idx, info in out_map.items(): @@ -85,7 +91,6 @@ class FakeBalWindow: def init_class_variables(self): if not self.heirs: raise NoHeirsException("Heirs are not defined") - from bal.core.plugin_base import BalTimestamp from datetime import datetime self.date_to_check = BalTimestamp(self.will_settings['threshold']).to_timestamp() self.no_willexecutor = self.bal_plugin.NO_WILLEXECUTOR.get() diff --git a/tests/windows_overflow_test.py b/tests/windows_overflow_test.py index 8582cf9..0b62a83 100644 --- a/tests/windows_overflow_test.py +++ b/tests/windows_overflow_test.py @@ -45,10 +45,10 @@ class _WindowsLikeDatetime(_real_datetime): def main(): plugin_base = importlib.import_module(f"{PKG}.core.plugin_base") - BalTimestamp = plugin_base.BalTimestamp + bt_class = plugin_base.BalTimestamp # 1) Sanity: with the real (Linux 64-bit) datetime, large ts works already. - bt = BalTimestamp(NLOCKTIME_MAX) + bt = bt_class(NLOCKTIME_MAX) d = bt.to_date() assert isinstance(d, _real_datetime), d print("[OK] BalTimestamp(NLOCKTIME_MAX).to_date() works on this platform") @@ -59,7 +59,7 @@ def main(): plugin_base.datetime = _WindowsLikeDatetime try: # 2a) Absolute sentinel timestamp (the exact crash path from the log). - bt = BalTimestamp(NLOCKTIME_MAX) + bt = bt_class(NLOCKTIME_MAX) d = bt.to_date() # must NOT raise OverflowError anymore assert d.year <= 2038, f"expected clamp to <=2038, got {d!r}" print("[OK] to_date(NLOCKTIME_MAX) no longer raises (clamped to INT32_MAX)") @@ -75,13 +75,13 @@ def main(): print("[OK] str()/repr() on out-of-range timestamp are safe") # 2d) Relative durations that overflow when added (e.g. huge 'd'). - bt_rel = BalTimestamp(f"{10 ** 9}d") # ~2.7M years -> overflow + bt_rel = bt_class(f"{10 ** 9}d") # ~2.7M years -> overflow d2 = bt_rel.to_date() assert d2 is not None print("[OK] huge relative duration no longer raises") # 2e) Normal values are unchanged (behaviour-preserving check). - bt_norm = BalTimestamp("90d") + bt_norm = bt_class("90d") d3 = bt_norm.to_date() # 90 days from now, normalised to midnight assert d3.hour == 0 and d3.minute == 0 and d3.second == 0