Skip to content

Commit a65b9b9

Browse files
committed
refactor(debugger): enhance type safety by introducing protocols and type definitions for log messages and attributes
1 parent ad6495b commit a65b9b9

File tree

2 files changed

+62
-34
lines changed

2 files changed

+62
-34
lines changed

packages/debugger/src/robotcode/debugger/debugger.py

Lines changed: 59 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
MutableMapping,
2424
NamedTuple,
2525
Optional,
26+
Protocol,
2627
Sequence,
2728
Set,
2829
Tuple,
@@ -106,6 +107,40 @@ def __repr__(self) -> str:
106107
MAX_VARIABLE_ITEMS_DISPLAY = 500 # Maximum items to display in variable view
107108

108109

110+
# Type definitions for better type safety
111+
EvaluationResult = Union[Any, Exception]
112+
KeywordCallable = Callable[[], EvaluationResult]
113+
AttributeDict = Dict[str, Any]
114+
115+
116+
class ExceptionInformation(TypedDict, total=False):
117+
text: Optional[str]
118+
description: str
119+
status: str
120+
121+
122+
class LogMessage(TypedDict, total=False):
123+
level: str
124+
message: str
125+
timestamp: str
126+
html: Optional[str]
127+
128+
129+
class RobotContextProtocol(Protocol):
130+
"""Protocol for Robot Framework execution context."""
131+
132+
variables: Any
133+
namespace: Any
134+
135+
136+
class KeywordHandlerProtocol(Protocol):
137+
"""Protocol for Robot Framework keyword handlers."""
138+
139+
name: str
140+
args: Any # Robot version dependent type
141+
arguments: Any # Robot version dependent type
142+
143+
109144
class DebugRepr(reprlib.Repr):
110145
def __init__(self) -> None:
111146
super().__init__()
@@ -259,12 +294,14 @@ class PathMapping(NamedTuple):
259294
remote_root: Optional[str]
260295

261296

262-
if get_robot_version() < (7, 0):
297+
class DebugLoggerBase:
298+
def __init__(self) -> None:
299+
self.steps: List[Any] = []
300+
263301

264-
class DebugLogger:
265-
def __init__(self) -> None:
266-
self.steps: List[Any] = []
302+
if get_robot_version() < (7, 0):
267303

304+
class DebugLogger(DebugLoggerBase):
268305
def start_keyword(self, kw: Any) -> None:
269306
self.steps.append(kw)
270307

@@ -275,10 +312,7 @@ def end_keyword(self, kw: Any) -> None:
275312
from robot import result, running
276313
from robot.output.loggerapi import LoggerApi
277314

278-
class DebugLogger(LoggerApi): # type: ignore[no-redef]
279-
def __init__(self) -> None:
280-
self.steps: List[Any] = []
281-
315+
class DebugLogger(DebugLoggerBase, LoggerApi): # type: ignore[no-redef]
282316
def start_try(self, data: "running.Try", result: "result.Try") -> None:
283317
self.steps.append(data)
284318

@@ -295,12 +329,6 @@ def end_keyword(self, data: running.Keyword, result: result.Keyword) -> None:
295329
breakpoint_id_manager = IdManager()
296330

297331

298-
class ExceptionInformation(TypedDict):
299-
text: Optional[str]
300-
description: str
301-
status: str
302-
303-
304332
class _DebuggerInstanceDescriptor:
305333
"""Descriptor that forwards all attribute access to the singleton instance."""
306334

@@ -395,8 +423,8 @@ def __init__(self) -> None:
395423
self.attached = False
396424
self.path_mappings: List[PathMapping] = []
397425

398-
self._keyword_to_evaluate: Optional[Callable[..., Any]] = None
399-
self._evaluated_keyword_result: Any = None
426+
self._keyword_to_evaluate: Optional[KeywordCallable] = None
427+
self._evaluated_keyword_result: Optional[EvaluationResult] = None
400428
self._evaluate_keyword_event = threading.Event()
401429
self._evaluate_keyword_event.set()
402430
self._after_evaluate_keyword_event = threading.Event()
@@ -843,7 +871,7 @@ def wait_for_running(self) -> None:
843871
break
844872
self._current_exception = None
845873

846-
def start_output_group(self, name: str, attributes: Dict[str, Any], type: Optional[str] = None) -> None:
874+
def start_output_group(self, name: str, attributes: AttributeDict, type: Optional[str] = None) -> None:
847875
if self.group_output:
848876
source = attributes.get("source")
849877
line_no = attributes.get("lineno")
@@ -862,7 +890,7 @@ def start_output_group(self, name: str, attributes: Dict[str, Any], type: Option
862890
),
863891
)
864892

865-
def end_output_group(self, name: str, attributes: Dict[str, Any], type: Optional[str] = None) -> None:
893+
def end_output_group(self, name: str, attributes: AttributeDict, type: Optional[str] = None) -> None:
866894
if self.group_output:
867895
source = attributes.get("source")
868896
line_no = attributes.get("lineno")
@@ -889,7 +917,7 @@ def add_stackframe_entry(
889917
line: Optional[int],
890918
column: Optional[int] = None,
891919
*,
892-
handler: Any = None,
920+
handler: Optional[KeywordHandlerProtocol] = None,
893921
libname: Optional[str] = None,
894922
kwname: Optional[str] = None,
895923
longname: Optional[str] = None,
@@ -943,7 +971,7 @@ def remove_stackframe_entry(
943971
line: Optional[int],
944972
column: Optional[int] = None,
945973
*,
946-
handler: Any = None,
974+
handler: Optional[KeywordHandlerProtocol] = None,
947975
) -> None:
948976
self.full_stack_frames.popleft()
949977

@@ -961,7 +989,7 @@ def remove_stackframe_entry(
961989
if self.stack_frames:
962990
self.stack_frames[0].stack_frames.popleft()
963991

964-
def start_suite(self, name: str, attributes: Dict[str, Any]) -> None:
992+
def start_suite(self, name: str, attributes: AttributeDict) -> None:
965993
if self.state == State.CallKeyword:
966994
return
967995

@@ -1009,7 +1037,7 @@ def start_suite(self, name: str, attributes: Dict[str, Any]) -> None:
10091037

10101038
self.wait_for_running()
10111039

1012-
def end_suite(self, name: str, attributes: Dict[str, Any]) -> None:
1040+
def end_suite(self, name: str, attributes: AttributeDict) -> None:
10131041
if self.state == State.CallKeyword:
10141042
return
10151043

@@ -1031,7 +1059,7 @@ def end_suite(self, name: str, attributes: Dict[str, Any]) -> None:
10311059

10321060
self.remove_stackframe_entry(name, type, source, line_no)
10331061

1034-
def start_test(self, name: str, attributes: Dict[str, Any]) -> None:
1062+
def start_test(self, name: str, attributes: AttributeDict) -> None:
10351063
if self.state == State.CallKeyword:
10361064
return
10371065

@@ -1058,7 +1086,7 @@ def start_test(self, name: str, attributes: Dict[str, Any]) -> None:
10581086

10591087
self.wait_for_running()
10601088

1061-
def end_test(self, name: str, attributes: Dict[str, Any]) -> None:
1089+
def end_test(self, name: str, attributes: AttributeDict) -> None:
10621090
if self.state == State.CallKeyword:
10631091
return
10641092

@@ -1092,7 +1120,7 @@ def get_current_keyword_handler(self, name: str) -> UserKeywordHandler:
10921120
def get_current_keyword_handler(self, name: str) -> UserKeywordHandler:
10931121
return EXECUTION_CONTEXTS.current.namespace.get_runner(name)._handler
10941122

1095-
def start_keyword(self, name: str, attributes: Dict[str, Any]) -> None:
1123+
def start_keyword(self, name: str, attributes: AttributeDict) -> None:
10961124
if self.state == State.CallKeyword:
10971125
return
10981126

@@ -1107,7 +1135,7 @@ def start_keyword(self, name: str, attributes: Dict[str, Any]) -> None:
11071135
libname = attributes.get("libname")
11081136
kwname = attributes.get("kwname")
11091137

1110-
handler: Any = None
1138+
handler: Optional[KeywordHandlerProtocol] = None
11111139
if type in ["KEYWORD", "SETUP", "TEARDOWN"]:
11121140
try:
11131141
handler = self.get_current_keyword_handler(name)
@@ -1222,7 +1250,7 @@ def is_not_caugthed_by_except(self, message: Optional[str]) -> bool:
12221250
return False
12231251
return True
12241252

1225-
def end_keyword(self, name: str, attributes: Dict[str, Any]) -> None:
1253+
def end_keyword(self, name: str, attributes: AttributeDict) -> None:
12261254
if self.state == State.CallKeyword:
12271255
return
12281256

@@ -1253,7 +1281,7 @@ def end_keyword(self, name: str, attributes: Dict[str, Any]) -> None:
12531281
type = attributes.get("type", "KEYWORD")
12541282
kwname = attributes.get("kwname")
12551283

1256-
handler: Any = None
1284+
handler: Optional[KeywordHandlerProtocol] = None
12571285
if type in ["KEYWORD", "SETUP", "TEARDOWN"]:
12581286
try:
12591287
handler = self.get_current_keyword_handler(name)
@@ -1378,7 +1406,7 @@ def yield_stack() -> Iterator[StackFrame]:
13781406
"DEBUG": "\u001b[38;5;8m",
13791407
}
13801408

1381-
def log_message(self, message: Dict[str, Any]) -> None:
1409+
def log_message(self, message: LogMessage) -> None:
13821410
level = message["level"]
13831411
msg = message["message"]
13841412

@@ -1438,7 +1466,7 @@ def _build_output(self, level: str, msg: str, timestamp: str) -> str:
14381466
+ f"{msg}\n"
14391467
)
14401468

1441-
def message(self, message: Dict[str, Any]) -> None:
1469+
def message(self, message: LogMessage) -> None:
14421470
level = message["level"]
14431471
current_frame = self.full_stack_frames[0] if self.full_stack_frames else None
14441472

@@ -1905,7 +1933,7 @@ def _create_evaluate_result(self, value: Any) -> EvaluateResult:
19051933

19061934
return EvaluateResult(result=repr(value), type=repr(type(value)))
19071935

1908-
def run_in_robot_thread(self, kw: Callable[[], Any]) -> Any:
1936+
def run_in_robot_thread(self, kw: KeywordCallable) -> EvaluationResult:
19091937
with self.condition:
19101938
self._keyword_to_evaluate = kw
19111939
self._evaluated_keyword_result = None

packages/debugger/src/robotcode/debugger/listeners.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from robotcode.core.utils.path import normalized_path
1010

1111
from .dap_types import Event, Model
12-
from .debugger import Debugger
12+
from .debugger import Debugger, LogMessage
1313
from .mixins import SyncedEventBody
1414

1515

@@ -220,7 +220,7 @@ def end_keyword(self, name: str, attributes: Dict[str, Any]) -> None:
220220

221221
RE_FILE_LINE_MATCHER = re.compile(r".+\sin\sfile\s'(?P<file>.*)'\son\sline\s(?P<line>\d+):(?P<message>.*)")
222222

223-
def log_message(self, message: Dict[str, Any]) -> None:
223+
def log_message(self, message: LogMessage) -> None:
224224
if message["level"] in ["FAIL", "ERROR", "WARN"]:
225225
current_frame = Debugger.instance.full_stack_frames[0] if Debugger.instance.full_stack_frames else None
226226

@@ -273,7 +273,7 @@ def log_message(self, message: Dict[str, Any]) -> None:
273273

274274
Debugger.instance.log_message(message)
275275

276-
def message(self, message: Dict[str, Any]) -> None:
276+
def message(self, message: LogMessage) -> None:
277277
if message["level"] in ["FAIL", "ERROR", "WARN"]:
278278
current_frame = Debugger.instance.full_stack_frames[0] if Debugger.instance.full_stack_frames else None
279279

0 commit comments

Comments
 (0)