11import contextlib
22import functools
3+ import inspect
34import sys
45from collections import defaultdict
56from typing import DefaultDict
89class GeneratorStats :
910 _warn_cache : DefaultDict [str , int ] = defaultdict (int )
1011 _error_cache : DefaultDict [str , int ] = defaultdict (int )
12+ _blue = ''
13+ _red = ''
14+ _yellow = ''
15+ _clear = ''
1116
1217 def __getattr__ (self , name ):
13- if not self .__dict__ :
18+ if 'silent' not in self .__dict__ :
1419 from drf_spectacular .settings import spectacular_settings
1520 self .silent = spectacular_settings .DISABLE_ERRORS_AND_WARNINGS
1621 try :
@@ -33,12 +38,25 @@ def reset(self):
3338 self ._warn_cache .clear ()
3439 self ._error_cache .clear ()
3540
41+ def enable_color (self ):
42+ self ._blue = '\033 [0;34m'
43+ self ._red = '\033 [0;31m'
44+ self ._yellow = '\033 [0;33m'
45+ self ._clear = '\033 [0m'
46+
3647 def emit (self , msg , severity ):
3748 assert severity in ['warning' , 'error' ]
38- msg = _get_current_trace () + str (msg )
3949 cache = self ._warn_cache if severity == 'warning' else self ._error_cache
50+
51+ source_location , breadcrumbs = _get_current_trace ()
52+ prefix = f'{ self ._blue } { source_location } : ' if source_location else ''
53+ prefix += self ._yellow if severity == 'warning' else self ._red
54+ prefix += f'{ severity .capitalize ()} '
55+ prefix += f' [{ breadcrumbs } ]: ' if breadcrumbs else ': '
56+
57+ msg = prefix + self ._clear + str (msg )
4058 if not self .silent and msg not in cache :
41- print (f' { severity . capitalize () } # { len ( cache ) } : { msg } ' , file = sys .stderr )
59+ print (msg , file = sys .stderr )
4260 cache [msg ] += 1
4361
4462 def emit_summary (self ):
@@ -80,17 +98,34 @@ def reset_generator_stats():
8098
8199
82100@contextlib .contextmanager
83- def add_trace_message (trace_message ):
101+ def add_trace_message (obj ):
84102 """
85103 Adds a message to be used as a prefix when emitting warnings and errors.
86104 """
87- _TRACES .append (trace_message )
105+ sourcefile , lineno = _get_source_location (obj )
106+ _TRACES .append ((sourcefile , lineno , obj .__name__ ))
88107 yield
89108 _TRACES .pop ()
90109
91110
111+ @functools .lru_cache (maxsize = 1000 )
112+ def _get_source_location (obj ):
113+ try :
114+ sourcefile = inspect .getsourcefile (obj )
115+ lineno = inspect .getsourcelines (obj )[1 ]
116+ return sourcefile , lineno
117+ except : # noqa: E722
118+ return None , None
119+
120+
92121def _get_current_trace ():
93- return '' .join (f"{ trace } : " for trace in _TRACES if trace )
122+ source_locations = [t for t in _TRACES if t [0 ]]
123+ if source_locations :
124+ source_location = f"{ source_locations [- 1 ][0 ]} :{ source_locations [- 1 ][1 ]} "
125+ else :
126+ source_location = ''
127+ breadcrumbs = ' > ' .join (t [2 ] for t in _TRACES )
128+ return source_location , breadcrumbs
94129
95130
96131def has_override (obj , prop ):
0 commit comments