Skip to content

Commit 93258cd

Browse files
committed
Refactor automatic TypeError msg generation
- Move `create_parser` to `parser.py` - Update `raise_type_error_with_signature` to generate more informative error messages by discerning the received and expected argument types
1 parent a6abfc8 commit 93258cd

File tree

4 files changed

+68
-38
lines changed

4 files changed

+68
-38
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
from . import exceptions
2+
from . import parser
23
from . import cli

stat_log_db/src/stat_log_db/cli.py

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,17 @@
11
import os
22
import sys
3-
import argparse
43

5-
from .exceptions import raise_type_error_with_signature
6-
7-
8-
def create_parser(parser_args: dict, version: str | int = "0.0.1") -> argparse.ArgumentParser:
9-
"""Create the main argument parser."""
10-
# Validate parser_args
11-
if not isinstance(parser_args, dict):
12-
raise_type_error_with_signature()
13-
# Default formatter class
14-
if "formatter_class" not in parser_args:
15-
parser_args["formatter_class"] = argparse.RawDescriptionHelpFormatter
16-
try:
17-
parser = argparse.ArgumentParser(**parser_args)
18-
except Exception as e:
19-
raise Exception(f"Failed to create ArgumentParser: {e}")
20-
21-
# Validate version
22-
if not isinstance(version, (str, int)):
23-
raise_type_error_with_signature()
24-
25-
# Add version argument
26-
parser.add_argument(
27-
"--version",
28-
action="version",
29-
version=version if isinstance(version, str) else str(version)
30-
)
31-
32-
return parser
4+
from .parser import create_parser
335

346

357
def main():
368
"""Main CLI entry point."""
379

10+
# TODO: Read info from pyproject.toml?
3811
parser = create_parser({
3912
"prog": "sldb",
4013
"description": "My CLI tool",
41-
}, "0.0.1") # TODO: Read version from pyproject.toml?
14+
}, "0.0.1")
4215

4316
args = parser.parse_args()
4417

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,42 @@
11

2-
def raise_type_error_with_signature():
2+
def raise_type_error_with_signature(argument_name: str | list[str] | tuple[str, ...] | set[str] | None = None):
33
"""Generate a standard type error message."""
4-
# TODO: f"arg must be of type 'argtype', but {inspect.stack()[0][3]} got {type(arg).__name__}"
5-
message = ""
4+
message = f"TypeError with one or more of argument(s): {argument_name}"
65
try:
76
import inspect
8-
caller_frame = inspect.stack()[1].frame
97
import types
8+
current_frame = inspect.currentframe()
9+
current_function_name = None
10+
caller_frame = None
11+
if isinstance(current_frame, types.FrameType):
12+
current_function_name = inspect.getframeinfo(current_frame).function
13+
# caller_frame = inspect.stack()[1].frame
14+
caller_frame = current_frame.f_back
1015
if isinstance(caller_frame, types.FrameType):
11-
function_name = inspect.getframeinfo(caller_frame).function
12-
signature = inspect.signature(caller_frame.f_globals[function_name])
13-
message = f"TypeError in function '{function_name}'. Signature:\n{function_name}{signature}"
16+
caller_function_name = inspect.getframeinfo(caller_frame).function
17+
message = f"TypeError in function '{caller_function_name}'."
18+
signature = inspect.signature(caller_frame.f_globals[caller_function_name])
19+
message += f"\nSignature: {caller_function_name}{signature}"
20+
arg_names = []
21+
if argument_name is None:
22+
arg_names = list(signature.parameters.keys())
23+
elif isinstance(argument_name, (list, tuple, set)):
24+
arg_names = list(argument_name)
25+
elif isinstance(argument_name, str):
26+
arg_names = [argument_name]
27+
else:
28+
current_function_name_msg_str = f"in '{current_function_name}'" if current_function_name else ""
29+
received_argument_name_type_str = type(argument_name).__name__
30+
raise TypeError(f"Invalid argument name type {current_function_name_msg_str} for argument 'argument_name': {received_argument_name_type_str}")
31+
for arg_name in arg_names:
32+
received_arg_type = type(caller_frame.f_locals[arg_name])
33+
received_arg_type_str = received_arg_type.__name__ if isinstance(received_arg_type, type) else received_arg_type
34+
received_arg = caller_frame.f_locals[arg_name]
35+
expected_arg_type = signature.parameters[arg_name].annotation
36+
expected_arg_type_str = expected_arg_type.__name__ if isinstance(expected_arg_type, type) else expected_arg_type
37+
if received_arg_type != expected_arg_type:
38+
message += f"\nArgument '{arg_name}' must be of type '{expected_arg_type_str}', but {caller_function_name} got {received_arg_type_str}: {received_arg}"
1439
except Exception as e:
15-
raise Exception(f"Failed to generate type error message: {e}")
40+
raise TypeError(message + f"\nAdditional error while generating error message:\n{e}")
41+
# raise Exception(f"Failed to generate type error message: {e}")
1642
raise TypeError(message)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import argparse
2+
3+
from .exceptions import raise_type_error_with_signature
4+
5+
6+
def create_parser(parser_args: dict, version: str | int = "0.0.1") -> argparse.ArgumentParser:
7+
"""Create the main argument parser."""
8+
# Validate parser_args
9+
if not isinstance(parser_args, dict):
10+
raise_type_error_with_signature("parser_args")
11+
# Default formatter class
12+
if "formatter_class" not in parser_args:
13+
parser_args["formatter_class"] = argparse.RawDescriptionHelpFormatter
14+
try:
15+
parser = argparse.ArgumentParser(**parser_args)
16+
except Exception as e:
17+
raise Exception(f"Failed to create ArgumentParser: {e}")
18+
19+
# Validate version
20+
if not isinstance(version, (str, int)):
21+
raise_type_error_with_signature("version")
22+
23+
# Add version argument
24+
parser.add_argument(
25+
"--version",
26+
action="version",
27+
version=version if isinstance(version, str) else str(version)
28+
)
29+
30+
return parser

0 commit comments

Comments
 (0)