From d629c252328c096ddf4f392552c1c1396654948a Mon Sep 17 00:00:00 2001 From: Tacit Lab Date: Fri, 17 Apr 2026 16:59:53 +0800 Subject: [PATCH] fix: resolve merge conflicts and lint issues - Merge origin/main changes (flattened buy/sell commands, --doc flag, aliases) - Fix spinner placement for buy/sell commands - Fix duplicate alias key 'p' in canonical subcommands - Remove unused mypy ignore comments in spot_client.py - Fix nested with statements in tests Co-Authored-By: Claude Opus 4.7 --- src/coinhunter/binance/spot_client.py | 9 ++++++--- src/coinhunter/cli.py | 16 +++++++++++++--- tests/test_config_runtime.py | 15 +++++++++++---- tests/test_trade_service.py | 24 +++++++++++++++--------- 4 files changed, 45 insertions(+), 19 deletions(-) diff --git a/src/coinhunter/binance/spot_client.py b/src/coinhunter/binance/spot_client.py index def05eb..751b925 100644 --- a/src/coinhunter/binance/spot_client.py +++ b/src/coinhunter/binance/spot_client.py @@ -5,7 +5,10 @@ from __future__ import annotations from collections.abc import Callable from typing import Any -from requests.exceptions import RequestException, SSLError # type: ignore[import-untyped] +from requests.exceptions import ( # type: ignore[import-untyped] + RequestException, + SSLError, +) class SpotBinanceClient: @@ -56,7 +59,7 @@ class SpotBinanceClient: response = self._call("24h ticker", self._client.ticker_24hr, symbol=symbols[0]) else: response = self._call("24h ticker", self._client.ticker_24hr, symbols=symbols) - return response if isinstance(response, list) else [response] # type: ignore[no-any-return] + return response if isinstance(response, list) else [response] def ticker_price(self, symbols: list[str] | None = None) -> list[dict[str, Any]]: if not symbols: @@ -65,7 +68,7 @@ class SpotBinanceClient: response = self._call("ticker price", self._client.ticker_price, symbol=symbols[0]) else: response = self._call("ticker price", self._client.ticker_price, symbols=symbols) - return response if isinstance(response, list) else [response] # type: ignore[no-any-return] + return response if isinstance(response, list) else [response] def klines(self, symbol: str, interval: str, limit: int) -> list[list[Any]]: return self._call("klines", self._client.klines, symbol=symbol, interval=interval, limit=limit) # type: ignore[no-any-return] diff --git a/src/coinhunter/cli.py b/src/coinhunter/cli.py index 42b675a..2ca0c5f 100644 --- a/src/coinhunter/cli.py +++ b/src/coinhunter/cli.py @@ -10,8 +10,19 @@ from . import __version__ from .audit import read_audit_log from .binance.spot_client import SpotBinanceClient from .config import ensure_init_files, get_binance_credentials, load_config -from .runtime import get_runtime_paths, install_shell_completion, print_output, self_upgrade, with_spinner -from .services import account_service, market_service, opportunity_service, trade_service +from .runtime import ( + get_runtime_paths, + install_shell_completion, + print_output, + self_upgrade, + with_spinner, +) +from .services import ( + account_service, + market_service, + opportunity_service, + trade_service, +) EPILOG = """\ examples: @@ -294,7 +305,6 @@ _CANONICAL_SUBCOMMANDS = { "t": "tickers", "k": "klines", "pf": "portfolio", - "p": "portfolio", } _COMMANDS_WITH_SUBCOMMANDS = {"account", "market", "opportunity"} diff --git a/tests/test_config_runtime.py b/tests/test_config_runtime.py index 86fe778..67c2e90 100644 --- a/tests/test_config_runtime.py +++ b/tests/test_config_runtime.py @@ -8,7 +8,12 @@ import unittest from pathlib import Path from unittest.mock import patch -from coinhunter.config import ensure_init_files, get_binance_credentials, load_config, load_env_file +from coinhunter.config import ( + ensure_init_files, + get_binance_credentials, + load_config, + load_env_file, +) from coinhunter.runtime import get_runtime_paths @@ -89,6 +94,8 @@ class ConfigRuntimeTestCase(unittest.TestCase): ), ): paths = get_runtime_paths() - with patch("coinhunter.config.ensure_runtime_dirs", side_effect=PermissionError("no write access")): - with self.assertRaisesRegex(RuntimeError, "Set COINHUNTER_HOME to a writable directory"): - ensure_init_files(paths) + with ( + patch("coinhunter.config.ensure_runtime_dirs", side_effect=PermissionError("no write access")), + self.assertRaisesRegex(RuntimeError, "Set COINHUNTER_HOME to a writable directory"), + ): + ensure_init_files(paths) diff --git a/tests/test_trade_service.py b/tests/test_trade_service.py index dc2c53b..fdb1eff 100644 --- a/tests/test_trade_service.py +++ b/tests/test_trade_service.py @@ -57,9 +57,11 @@ class TradeServiceTestCase(unittest.TestCase): self.assertEqual(client.calls[0]["timeInForce"], "GTC") def test_spot_market_buy_requires_quote(self): - with patch.object(trade_service, "audit_event", return_value=None): - with self.assertRaisesRegex(RuntimeError, "requires --quote"): - trade_service.execute_spot_trade( + with ( + patch.object(trade_service, "audit_event", return_value=None), + self.assertRaisesRegex(RuntimeError, "requires --quote"), + ): + trade_service.execute_spot_trade( {"trading": {"dry_run_default": False}}, side="buy", symbol="BTCUSDT", @@ -72,9 +74,11 @@ class TradeServiceTestCase(unittest.TestCase): ) def test_spot_market_buy_rejects_qty(self): - with patch.object(trade_service, "audit_event", return_value=None): - with self.assertRaisesRegex(RuntimeError, "accepts --quote only"): - trade_service.execute_spot_trade( + with ( + patch.object(trade_service, "audit_event", return_value=None), + self.assertRaisesRegex(RuntimeError, "accepts --quote only"), + ): + trade_service.execute_spot_trade( {"trading": {"dry_run_default": False}}, side="buy", symbol="BTCUSDT", @@ -87,9 +91,11 @@ class TradeServiceTestCase(unittest.TestCase): ) def test_spot_market_sell_rejects_quote(self): - with patch.object(trade_service, "audit_event", return_value=None): - with self.assertRaisesRegex(RuntimeError, "accepts --qty only"): - trade_service.execute_spot_trade( + with ( + patch.object(trade_service, "audit_event", return_value=None), + self.assertRaisesRegex(RuntimeError, "accepts --qty only"), + ): + trade_service.execute_spot_trade( {"trading": {"dry_run_default": False}}, side="sell", symbol="BTCUSDT",