diff options
| author | blasty <blasty@local> | 2026-07-24 14:41:23 +0200 |
|---|---|---|
| committer | blasty <blasty@local> | 2026-07-24 14:41:23 +0200 |
| commit | 0decb747ac9239faf5119b7faa80596995746ef9 (patch) | |
| tree | 7996725bc0d3c62d9c25d1f5bc3f12979711a52c /idatui | |
| parent | tests: pass url=""/db=None to IdaTui in the worker pilot path (diff) | |
| download | ida-tui-0decb747ac9239faf5119b7faa80596995746ef9.tar.gz ida-tui-0decb747ac9239faf5119b7faa80596995746ef9.tar.xz ida-tui-0decb747ac9239faf5119b7faa80596995746ef9.zip | |
refactor: extract shared error hierarchy + Session into idatui/errors.py
The IDAError/IDAConnectionError/IDAToolError/... exceptions and the Session
dataclass were defined in client.py (the ida-pro-mcp HTTP client), but the idalib
worker path (worker_client/domain/app) needs them without the HTTP transport.
Move them to a transport-agnostic errors.py; client.py re-exports them so the
deprecated mcp tooling and stress tests are unchanged (verified:
errors.IDAToolError IS client.IDAToolError, so cross-module `except` still works).
worker_client, domain (TYPE_CHECKING-guarded IDAClient hint), app, and __init__
now import the shared types from errors.py. This decouples the worker path from
client.py at runtime -- the prerequisite for deleting the mcp transport.
Diffstat (limited to 'idatui')
| -rw-r--r-- | idatui/__init__.py | 5 | ||||
| -rw-r--r-- | idatui/app.py | 3 | ||||
| -rw-r--r-- | idatui/client.py | 75 | ||||
| -rw-r--r-- | idatui/domain.py | 7 | ||||
| -rw-r--r-- | idatui/errors.py | 76 | ||||
| -rw-r--r-- | idatui/worker_client.py | 2 |
6 files changed, 99 insertions, 69 deletions
diff --git a/idatui/__init__.py b/idatui/__init__.py index a52ba8b..92d8b5d 100644 --- a/idatui/__init__.py +++ b/idatui/__init__.py @@ -1,7 +1,6 @@ """idatui — a minimal keyboard-first TUI for IDA Pro over ida-pro-mcp (idalib).""" -from .client import ( - IDAClient, +from .errors import ( IDAError, IDAConnectionError, IDATimeoutError, @@ -10,8 +9,8 @@ from .client import ( IDAToolError, IDASessionError, Session, - KeepAlive, ) +from .client import IDAClient, KeepAlive # deprecated mcp transport from .domain import ( Program, FunctionIndex, diff --git a/idatui/app.py b/idatui/app.py index 1936153..712b4de 100644 --- a/idatui/app.py +++ b/idatui/app.py @@ -42,7 +42,8 @@ from textual.widgets.option_list import Option from .highlight import highlight_c -from .client import IDAClient, IDAToolError, IDAConnectionError +from .errors import IDAToolError, IDAConnectionError +from .client import IDAClient # deprecated mcp transport (worker path uses WorkerClient) from .domain import DisasmModel, Func, Head, ListingModel, Program, Struct # Styles for the disassembly listing. diff --git a/idatui/client.py b/idatui/client.py index f39a10d..2f74cbe 100644 --- a/idatui/client.py +++ b/idatui/client.py @@ -66,68 +66,19 @@ SESSION_AGNOSTIC_TOOLS = frozenset({"idb_list", "idb_open", "int_convert"}) _STALE_SESSION_MARKERS = ("session not found", "database is required") -# --------------------------------------------------------------------------- # -# Exceptions -# --------------------------------------------------------------------------- # -class IDAError(Exception): - """Base class for all client errors.""" - - -class IDAConnectionError(IDAError): - """The transport could not be established or was lost.""" - - -class IDATimeoutError(IDAError): - """A request exceeded its deadline.""" - - -class IDAProtocolError(IDAError): - """Malformed or unexpected HTTP / JSON-RPC framing.""" - - -class IDARPCError(IDAError): - """The JSON-RPC envelope carried an ``error`` object.""" - - def __init__(self, code: int, message: str, data: Any = None): - super().__init__(f"JSON-RPC error {code}: {message}") - self.code = code - self.message = message - self.data = data - - -class IDAToolError(IDAError): - """A tool call returned ``result.isError == true`` (a hard failure).""" - - def __init__(self, tool: str, message: str): - super().__init__(f"tool {tool!r} failed: {message}") - self.tool = tool - self.message = message - - -class IDASessionError(IDAError): - """No IDB session is open, or several are and none was pinned.""" - - -# --------------------------------------------------------------------------- # -# Session model -# --------------------------------------------------------------------------- # -@dataclass(frozen=True) -class Session: - session_id: str - filename: str - input_path: str - is_active: bool = False - is_analyzing: bool = False - - @classmethod - def from_dict(cls, d: dict) -> "Session": - return cls( - session_id=d.get("session_id", ""), - filename=d.get("filename", ""), - input_path=d.get("input_path", ""), - is_active=bool(d.get("is_active", False)), - is_analyzing=bool(d.get("is_analyzing", False)), - ) +# The error hierarchy and Session model now live in errors.py (transport- +# agnostic, shared with the idalib worker path); re-exported here so the +# deprecated mcp tooling and the stress tests keep importing them from client. +from .errors import ( # noqa: E402,F401 + IDAError, + IDAConnectionError, + IDATimeoutError, + IDAProtocolError, + IDARPCError, + IDAToolError, + IDASessionError, + Session, +) # --------------------------------------------------------------------------- # diff --git a/idatui/domain.py b/idatui/domain.py index 2a3fc2b..4f72f5b 100644 --- a/idatui/domain.py +++ b/idatui/domain.py @@ -28,9 +28,12 @@ import threading import urllib.request from concurrent.futures import ThreadPoolExecutor from dataclasses import dataclass, field, replace -from typing import Callable +from typing import Callable, TYPE_CHECKING -from .client import IDAClient, IDAToolError +from .errors import IDAToolError + +if TYPE_CHECKING: # type hint only; the runtime client is mcp IDAClient or worker + from .client import IDAClient # noqa: F401 # Clamps derived from measured caps (list ~700, disasm ~500). Margin included. LIST_PAGE = 500 diff --git a/idatui/errors.py b/idatui/errors.py new file mode 100644 index 0000000..29b09ae --- /dev/null +++ b/idatui/errors.py @@ -0,0 +1,76 @@ +"""Transport-agnostic error hierarchy and the Session model. + +These were originally defined in client.py (the ida-pro-mcp HTTP client), but the +idalib worker path (worker_client / domain / app) needs the same exception types +and Session dataclass without dragging in the HTTP transport. They live here so +both backends share one definition; client.py re-exports them for backwards +compatibility with the (deprecated) mcp tooling and the stress tests. +""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + + +# --------------------------------------------------------------------------- # +# Exceptions +# --------------------------------------------------------------------------- # +class IDAError(Exception): + """Base class for all client errors.""" + + +class IDAConnectionError(IDAError): + """The transport could not be established or was lost.""" + + +class IDATimeoutError(IDAError): + """A request exceeded its deadline.""" + + +class IDAProtocolError(IDAError): + """Malformed or unexpected HTTP / JSON-RPC framing.""" + + +class IDARPCError(IDAError): + """The JSON-RPC envelope carried an ``error`` object.""" + + def __init__(self, code: int, message: str, data: Any = None): + super().__init__(f"JSON-RPC error {code}: {message}") + self.code = code + self.message = message + self.data = data + + +class IDAToolError(IDAError): + """A tool call returned ``result.isError == true`` (a hard failure).""" + + def __init__(self, tool: str, message: str): + super().__init__(f"tool {tool!r} failed: {message}") + self.tool = tool + self.message = message + + +class IDASessionError(IDAError): + """No IDB session is open, or several are and none was pinned.""" + + +# --------------------------------------------------------------------------- # +# Session model +# --------------------------------------------------------------------------- # +@dataclass(frozen=True) +class Session: + session_id: str + filename: str + input_path: str + is_active: bool = False + is_analyzing: bool = False + + @classmethod + def from_dict(cls, d: dict) -> "Session": + return cls( + session_id=d.get("session_id", ""), + filename=d.get("filename", ""), + input_path=d.get("input_path", ""), + is_active=bool(d.get("is_active", False)), + is_analyzing=bool(d.get("is_analyzing", False)), + ) diff --git a/idatui/worker_client.py b/idatui/worker_client.py index 8dd8491..27fa660 100644 --- a/idatui/worker_client.py +++ b/idatui/worker_client.py @@ -23,7 +23,7 @@ import time import uuid from typing import Any -from .client import IDAToolError, IDAConnectionError, Session +from .errors import IDAToolError, IDAConnectionError, Session from .worker import recv as _recv from .worker import send as _send |
