18
18
Callable ,
19
19
Dict ,
20
20
Generator ,
21
+ Iterable ,
21
22
Iterator ,
22
23
List ,
23
24
Literal ,
@@ -184,29 +185,31 @@ def is_positional_arg(*name_or_flags) -> bool:
184
185
return not is_option_arg (* name_or_flags )
185
186
186
187
187
- def tokenize_source (obj : object ) -> Generator :
188
- """Returns a generator for the tokens of the object's source code."""
189
- source = inspect .getsource (obj )
190
- token_generator = tokenize .generate_tokens (StringIO (source ).readline )
191
- return token_generator
188
+ def tokenize_source (source : str ) -> Generator [tokenize .TokenInfo , None , None ]:
189
+ """Returns a generator for the tokens of the object's source code, given the source code."""
190
+ return tokenize .generate_tokens (StringIO (source ).readline )
192
191
193
192
194
- def get_class_column (obj : type ) -> int :
195
- """Determines the column number for class variables in a class."""
193
+ def get_class_column (tokens : Iterable [ tokenize . TokenInfo ] ) -> int :
194
+ """Determines the column number for class variables in a class, given the tokens of the class ."""
196
195
first_line = 1
197
- for token_type , token , (start_line , start_column ), (end_line , end_column ), line in tokenize_source ( obj ) :
196
+ for token_type , token , (start_line , start_column ), (end_line , end_column ), line in tokens :
198
197
if token .strip () == "@" :
199
198
first_line += 1
200
199
if start_line <= first_line or token .strip () == "" :
201
200
continue
202
201
203
202
return start_column
203
+ raise ValueError ("Could not find any class variables in the class." )
204
204
205
205
206
- def source_line_to_tokens (obj : object ) -> Dict [int , List [Dict [str , Union [str , int ]]]]:
207
- """Gets a dictionary mapping from line number to a dictionary of tokens on that line for an object's source code."""
206
+ def source_line_to_tokens (tokens : Iterable [tokenize .TokenInfo ]) -> Dict [int , List [Dict [str , Union [str , int ]]]]:
207
+ """
208
+ Gets a dictionary mapping from line number to a dictionary of tokens on that line for an object's source code,
209
+ given the tokens of the object's source code.
210
+ """
208
211
line_to_tokens = {}
209
- for token_type , token , (start_line , start_column ), (end_line , end_column ), line in tokenize_source ( obj ) :
212
+ for token_type , token , (start_line , start_column ), (end_line , end_column ), line in tokens :
210
213
line_to_tokens .setdefault (start_line , []).append ({
211
214
'token_type' : token_type ,
212
215
'token' : token ,
@@ -220,13 +223,14 @@ def source_line_to_tokens(obj: object) -> Dict[int, List[Dict[str, Union[str, in
220
223
return line_to_tokens
221
224
222
225
223
- def get_subsequent_assign_lines (cls : type ) -> Set [int ]:
224
- """For all multiline assign statements, get the line numbers after the first line of the assignment."""
225
- # Get source code of class
226
- source = inspect .getsource (cls )
226
+ def get_subsequent_assign_lines (source_cls : str ) -> Set [int ]:
227
+ """
228
+ For all multiline assign statements, get the line numbers after the first line of the assignment,
229
+ given the source code of the object.
230
+ """
227
231
228
232
# Parse source code using ast (with an if statement to avoid indentation errors)
229
- source = f"if True:\n { textwrap .indent (source , ' ' )} "
233
+ source = f"if True:\n { textwrap .indent (source_cls , ' ' )} "
230
234
body = ast .parse (source ).body [0 ]
231
235
232
236
# Set up warning message
@@ -260,6 +264,11 @@ def get_subsequent_assign_lines(cls: type) -> Set[int]:
260
264
assign_lines = set ()
261
265
for node in cls_body .body :
262
266
if isinstance (node , (ast .Assign , ast .AnnAssign )):
267
+ # Check if the end line number is found
268
+ if node .end_lineno is None :
269
+ warnings .warn (parse_warning )
270
+ continue
271
+
263
272
# Get line number of assign statement excluding the first line (and minus 1 for the if statement)
264
273
assign_lines |= set (range (node .lineno , node .end_lineno ))
265
274
@@ -268,15 +277,19 @@ def get_subsequent_assign_lines(cls: type) -> Set[int]:
268
277
269
278
def get_class_variables (cls : type ) -> Dict [str , Dict [str , str ]]:
270
279
"""Returns a dictionary mapping class variables to their additional information (currently just comments)."""
280
+ # Get the source code and tokens of the class
281
+ source_cls = inspect .getsource (cls )
282
+ tokens = tuple (tokenize_source (source_cls ))
283
+
271
284
# Get mapping from line number to tokens
272
- line_to_tokens = source_line_to_tokens (cls )
285
+ line_to_tokens = source_line_to_tokens (tokens )
273
286
274
287
# Get class variable column number
275
- class_variable_column = get_class_column (cls )
288
+ class_variable_column = get_class_column (tokens )
276
289
277
290
# For all multiline assign statements, get the line numbers after the first line of the assignment
278
291
# This is used to avoid identifying comments in multiline assign statements
279
- subsequent_assign_lines = get_subsequent_assign_lines (cls )
292
+ subsequent_assign_lines = get_subsequent_assign_lines (source_cls )
280
293
281
294
# Extract class variables
282
295
class_variable = None
0 commit comments