4444
4545if sys .version_info >= (3 , 8 ):
4646 ASSIGN_TYPES = (ast .Assign , ast .AnnAssign , ast .NamedExpr )
47- AssignType = Union [ASSIGN_TYPES ]
48- else : # pragma: no cover
47+ AssignType = Union [ASSIGN_TYPES ] # type: ignore
48+ else : # pragma: no cover
4949 ASSIGN_TYPES = (ast .Assign , ast .AnnAssign )
50- AssignType = Union [ASSIGN_TYPES ]
50+ AssignType = Union [ASSIGN_TYPES ] # type: ignore
5151
5252MODULE_IGNORE_ID_NAME = "__varname_ignore_id__"
5353
@@ -62,29 +62,37 @@ class config: # pylint: disable=invalid-name
6262 debug = False
6363
6464
65- class VarnameRetrievingError (Exception ):
65+ class VarnameException (Exception ):
66+ """Root exception for all varname exceptions"""
67+
68+
69+ class VarnameRetrievingError (VarnameException ):
6670 """When failed to retrieve the varname"""
6771
6872
69- class QualnameNonUniqueError (Exception ):
73+ class QualnameNonUniqueError (VarnameException ):
7074 """When a qualified name is used as an ignore element but references to
7175 multiple objects in a module"""
7276
7377
74- class NonVariableArgumentError (Exception ):
78+ class NonVariableArgumentError (VarnameException ):
7579 """When vars_only is True but try to retrieve name of
7680 a non-variable argument"""
7781
7882
79- class ImproperUseError (Exception ):
83+ class ImproperUseError (VarnameException ):
8084 """When varname() is improperly used"""
8185
8286
83- class MaybeDecoratedFunctionWarning (Warning ):
87+ class VarnameWarning (Warning ):
88+ """Root warning for all varname warnings"""
89+
90+
91+ class MaybeDecoratedFunctionWarning (VarnameWarning ):
8492 """When a suspecious decorated function used as ignore function directly"""
8593
8694
87- class MultiTargetAssignmentWarning (Warning ):
95+ class MultiTargetAssignmentWarning (VarnameWarning ):
8896 """When varname tries to retrieve variable name in
8997 a multi-target assignment"""
9098
@@ -120,13 +128,14 @@ def get_node(
120128 return get_node_by_frame (frameobj , raise_exc )
121129
122130
123- def get_node_by_frame (
124- frame : FrameType , raise_exc : bool = True
125- ) -> ast .AST :
131+ def get_node_by_frame (frame : FrameType , raise_exc : bool = True ) -> ast .AST :
126132 """Get the node by frame, raise errors if possible"""
127133 exect = Source .executing (frame )
128134
129135 if exect .node :
136+ # attach the frame for better exception message
137+ # (ie. where ImproperUseError happens)
138+ exect .node .__frame__ = frame
130139 return exect .node
131140
132141 if exect .source .text and exect .source .tree and raise_exc :
@@ -257,10 +266,7 @@ def check_qualname_by_source(
257266 )
258267
259268
260- def debug_ignore_frame (
261- msg : str ,
262- frameinfo : inspect .FrameInfo = None
263- ) -> None :
269+ def debug_ignore_frame (msg : str , frameinfo : inspect .FrameInfo = None ) -> None :
264270 """Print the debug message for a given frame info object
265271
266272 Args:
@@ -415,9 +421,9 @@ def parse_argname_subscript(node: ast.Subscript):
415421 if not isinstance (name , ast .Name ):
416422 raise ValueError (f"Expect { ast .dump (name )} to be a variable." )
417423
418- subscript = node .slice # type: ast.AST
424+ subscript = node .slice # type: ast.AST
419425 if isinstance (subscript , ast .Index ):
420- subscript = subscript .value # pragma: no cover
426+ subscript = subscript .value # pragma: no cover
421427 if not isinstance (subscript , (ast .Str , ast .Num , ast .Constant )):
422428 raise ValueError (f"Expect { ast .dump (subscript )} to be a constant." )
423429
@@ -428,3 +434,40 @@ def parse_argname_subscript(node: ast.Subscript):
428434 subscript = getattr (subscript , "s" , subscript ) # ast.Str
429435
430436 return name .id , subscript
437+
438+
439+ def rich_exc_message (msg : str , node : ast .AST ) -> str :
440+ """Attach the source code from the node to message to
441+ get a rich message for exceptions
442+
443+ If package 'rich' is not install or 'node.__frame__' doesn't exist, fall
444+ to plain message (with basic information), otherwise show a better message
445+ with full information
446+ """
447+ frame = node .__frame__ # type: FrameType
448+ lineno = node .lineno # type: int
449+ col_offset = node .col_offset # type: int
450+ filename = frame .f_code .co_filename # type: str
451+ lines , startlineno = inspect .getsourcelines (frame )
452+ line_range = (startlineno + 1 , startlineno + len (lines ) + 1 )
453+ if startlineno == 0 :
454+ # wired shift
455+ lineno -= 1 # pragma: no cover
456+
457+ linenos = tuple (map (str , range (* line_range ))) # type: Tuple[str, ...]
458+ lineno_width = max (map (len , linenos )) # type: int
459+ hiline = lineno - startlineno # type: int
460+ codes = [] # type: List[str]
461+ for i , lno in enumerate (linenos ):
462+ lno = lno .ljust (lineno_width )
463+ if i == hiline :
464+ codes .append (f" > | { lno } { lines [i ]} " )
465+ codes .append (f" | { ' ' * (lineno_width + col_offset + 2 )} ^\n " )
466+ else :
467+ codes .append (f" | { lno } { lines [i ]} " )
468+
469+ return (
470+ f"{ msg } \n \n "
471+ f" { filename } :{ lineno + 1 } :{ col_offset + 1 } \n "
472+ f"{ '' .join (codes )} \n "
473+ )
0 commit comments