Coverage for pymend\file_parser.py: 94%
225 statements
« prev ^ index » next coverage.py v7.3.2, created at 2024-04-20 21:33 +0200
« prev ^ index » next coverage.py v7.3.2, created at 2024-04-20 21:33 +0200
1"""Module for parsing input file and walking ast."""
3import ast
4import re
5import sys
6from typing import Optional, Union, get_args, overload
8from typing_extensions import TypeGuard
10from .const import DEFAULT_EXCEPTION
11from .types import (
12 BodyTypes,
13 ClassDocstring,
14 DocstringInfo,
15 ElementDocstring,
16 FixerSettings,
17 FunctionBody,
18 FunctionDocstring,
19 FunctionSignature,
20 ModuleDocstring,
21 NodeOfInterest,
22 Parameter,
23 ReturnValue,
24)
26__author__ = "J-E. Nitschke"
27__copyright__ = "Copyright 2023-2024"
28__licence__ = "GPL3"
29__version__ = "1.0.0"
30__maintainer__ = "J-E. Nitschke"
33@overload
34def ast_unparse(node: None) -> None: ... 34 ↛ exitline 34 didn't return from function 'ast_unparse'
37@overload
38def ast_unparse(node: ast.AST) -> str: ... 38 ↛ exitline 38 didn't return from function 'ast_unparse'
41def ast_unparse(node: Optional[ast.AST]) -> Optional[str]:
42 """Convert the AST node to source code as a string.
44 Parameters
45 ----------
46 node : Optional[ast.AST]
47 Node to unparse.
49 Returns
50 -------
51 Optional[str]
52 `None` if `node` was `None`.
53 Otherwise the unparsed node.
54 """
55 if node is None:
56 return None
57 return ast.unparse(node)
60class AstAnalyzer:
61 """Walk ast and extract module, class and function information."""
63 def __init__(self, file_content: str, *, settings: FixerSettings) -> None:
64 """Initialize the Analyzer with the file contents.
66 The only reason this is a class is to have the raw
67 file_contents available at any point of the analysis to double check
68 something. Currently used for the module docstring and docstring
69 modifiers.
71 Parameters
72 ----------
73 file_content : str
74 File contents to store.
75 settings : FixerSettings
76 Settings for what to fix and when.
77 """
78 self.file_content = file_content
79 self.settings = settings
81 def parse_from_ast(
82 self,
83 ) -> list[ElementDocstring]:
84 """Walk AST of the input file extract info about module, classes and functions.
86 For the module and classes, the raw docstring
87 and its line numbers are extracted.
89 For functions the raw docstring and its line numbers are extracted.
90 Additionally the signature is parsed for parameters and return value.
92 Returns
93 -------
94 list[ElementDocstring]
95 List of information about module, classes and functions.
97 Raises
98 ------
99 AssertionError
100 If the source file content could not be parsed into an ast.
101 """
102 nodes_of_interest: list[ElementDocstring] = []
103 try:
104 file_ast = ast.parse(self.file_content)
105 except Exception as exc: # noqa: BLE001
106 msg = f"Failed to parse source file AST: {exc}\n"
107 raise AssertionError(msg) from exc
108 for node in ast.walk(file_ast):
109 if isinstance(node, ast.Module):
110 nodes_of_interest.append(self.handle_module(node))
111 elif isinstance(node, ast.ClassDef):
112 if node.name in self.settings.ignored_classes:
113 continue
114 nodes_of_interest.append(self.handle_class(node))
115 elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
116 if (
117 any(
118 name.id in self.settings.ignored_decorators
119 for name in node.decorator_list
120 if isinstance(name, ast.Name)
121 )
122 or node.name in self.settings.ignored_functions
123 ):
124 continue
125 nodes_of_interest.append(self.handle_function(node))
126 return nodes_of_interest
128 def handle_module(self, module: ast.Module) -> ModuleDocstring:
129 """Extract information about module.
131 Parameters
132 ----------
133 module : ast.Module
134 Node representing the full module.
136 Returns
137 -------
138 ModuleDocstring
139 Docstring representation for the module.
140 """
141 docstring_info = self.get_docstring_info(module)
142 if docstring_info is None:
143 docstring_line = self._get_docstring_line()
144 return ModuleDocstring(
145 "Module",
146 docstring="",
147 lines=(docstring_line, docstring_line),
148 modifier="",
149 issues=[],
150 )
151 return ModuleDocstring(
152 name=docstring_info.name,
153 docstring=docstring_info.docstring,
154 lines=docstring_info.lines,
155 modifier=docstring_info.modifier,
156 issues=docstring_info.issues,
157 )
159 def handle_class(self, cls: ast.ClassDef) -> ClassDocstring:
160 """Extract information about class docstring.
162 Parameters
163 ----------
164 cls : ast.ClassDef
165 Node representing a class definition.
167 Returns
168 -------
169 ClassDocstring
170 Docstring representation for a class.
171 """
172 docstring = self.handle_elem_docstring(cls)
173 attributes, methods = self.handle_class_body(cls)
174 return ClassDocstring(
175 name=docstring.name,
176 docstring=docstring.docstring,
177 lines=docstring.lines,
178 modifier=docstring.modifier,
179 issues=docstring.issues,
180 attributes=attributes,
181 methods=methods,
182 )
184 def handle_function(
185 self,
186 func: Union[ast.FunctionDef, ast.AsyncFunctionDef],
187 ) -> FunctionDocstring:
188 """Extract information from signature and docstring.
190 Parameters
191 ----------
192 func : Union[ast.FunctionDef, ast.AsyncFunctionDef]
193 Node representing a function definition.
195 Returns
196 -------
197 FunctionDocstring
198 Docstring representation of a function.
199 """
200 docstring = self.handle_elem_docstring(func)
201 signature = self.handle_function_signature(func)
202 body = self.handle_function_body(func)
203 # Minus one because the function counts the passed node itself
204 # Which is correct for each nested node but not the main one.
205 length = self._get_block_length(func) - 1
206 return FunctionDocstring(
207 name=docstring.name,
208 docstring=docstring.docstring,
209 lines=docstring.lines,
210 modifier=docstring.modifier,
211 issues=docstring.issues,
212 signature=signature,
213 body=body,
214 length=length,
215 )
217 def handle_elem_docstring(
218 self,
219 elem: Union[ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef],
220 ) -> DocstringInfo:
221 """Extract information about the docstring of the function.
223 Parameters
224 ----------
225 elem : Union[ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef]
226 Element representing a function or class definition.
228 Returns
229 -------
230 DocstringInfo
231 Return general information about the docstring of the element.
233 Raises
234 ------
235 ValueError
236 If the element did not have a body at all. This should not happen
237 for valid functions or classes.
238 """
239 docstring_info = self.get_docstring_info(elem)
240 if docstring_info is None:
241 if not elem.body: 241 ↛ 242line 241 didn't jump to line 242, because the condition on line 241 was never true
242 msg = "Function body was unexpectedly completely empty."
243 raise ValueError(msg)
244 lines = (elem.body[0].lineno, elem.body[0].lineno)
245 return DocstringInfo(
246 name=elem.name, docstring="", lines=lines, modifier="", issues=[]
247 )
248 return docstring_info
250 def get_docstring_info(self, node: NodeOfInterest) -> Optional[DocstringInfo]:
251 """Get docstring and line number if available.
253 Parameters
254 ----------
255 node : NodeOfInterest
256 Get general information about the docstring of any node
257 if interest.
259 Returns
260 -------
261 Optional[DocstringInfo]
262 Information about the docstring if the element contains one.
263 Or `None` if there was no docstring at all.
265 Raises
266 ------
267 ValueError
268 If the first element of the body is not a docstring after
269 `ast.get_docstring()` returned one.
270 """
271 if ast.get_docstring(node):
272 if not ( 272 ↛ 278line 272 didn't jump to line 278
273 node.body
274 and isinstance(first_element := node.body[0], ast.Expr)
275 and isinstance(docnode := first_element.value, ast.Constant)
276 and isinstance(docnode.value, str)
277 ):
278 msg = (
279 "Expected first entry in body to be the "
280 "docstring, but found nothing or something else."
281 )
282 raise ValueError(msg)
283 modifier = self._get_modifier(
284 self.file_content.splitlines()[docnode.lineno - 1]
285 )
286 return DocstringInfo(
287 # Can not use DefinitionNodes in isinstance checks before 3.10
288 name=(
289 node.name
290 if isinstance(
291 node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)
292 )
293 else "Module"
294 ),
295 docstring=str(docnode.value),
296 lines=(docnode.lineno, docnode.end_lineno),
297 modifier=modifier,
298 issues=[],
299 )
300 return None
302 def _get_modifier(self, line: str) -> str:
303 """Get the string modifier from the start of a docstring.
305 Parameters
306 ----------
307 line : str
308 Line to check
310 Returns
311 -------
312 str
313 Modifier of the string.
314 """
315 line = line.strip()
316 delimiters = ['"""', "'''"]
317 modifiers = ["r", "u"]
318 if not line: 318 ↛ 319line 318 didn't jump to line 319, because the condition on line 318 was never true
319 return ""
320 if line[:3] in delimiters:
321 return ""
322 if line[0].lower() in modifiers and line[1:4] in delimiters: 322 ↛ 324line 322 didn't jump to line 324, because the condition on line 322 was never false
323 return line[0]
324 return ""
326 def _get_docstring_line(self) -> int:
327 """Get the line where the module docstring should start.
329 Returns
330 -------
331 int
332 Starting line (starts at 1) of the docstring.
333 """
334 shebang_encoding_lines = 2
335 for index, line in enumerate(
336 self.file_content.splitlines()[:shebang_encoding_lines]
337 ):
338 if not self.is_shebang_or_pragma(line):
339 # List indices start at 0 but file lines are counted from 1
340 return index + 1
341 return shebang_encoding_lines + 1
343 def _has_body(self, node: ast.AST) -> TypeGuard[BodyTypes]:
344 """Check that the node is one of those that have a body."""
345 return isinstance(
346 node,
347 (get_args(BodyTypes)),
348 ) and hasattr(node, "body")
350 def _get_block_length(self, node: ast.AST) -> int:
351 """Get the number of statements in a block.
353 Recursively count the number of statements in a blocks body.
355 Parameters
356 ----------
357 node : ast.AST
358 Node representing to count the number of statements for.
360 Returns
361 -------
362 int
363 Total number of (nested) statements in the block.
364 """
365 # pylint: disable=no-member
366 if sys.version_info >= (3, 11): 366 ↛ 369line 366 didn't jump to line 369, because the condition on line 366 was never false
367 try_nodes = (ast.Try, ast.TryStar)
368 else:
369 try_nodes = (ast.Try,)
370 length = 1
371 if self._has_body(node) and node.body:
372 length += sum(self._get_block_length(child) for child in node.body)
373 # Decorators add complexity, so lets count them for now
374 if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
375 length += len(node.decorator_list)
376 elif isinstance(node, (ast.For, ast.AsyncFor, ast.While, ast.If, *try_nodes)):
377 length += sum(self._get_block_length(child) for child in node.orelse)
378 if isinstance(node, try_nodes):
379 length += sum(self._get_block_length(child) for child in node.finalbody)
380 length += sum(self._get_block_length(child) for child in node.handlers)
381 elif sys.version_info >= (3, 10) and isinstance(node, ast.Match): 381 ↛ 385line 381 didn't jump to line 385, because the condition on line 381 was never true
382 # Each case counts itself + its body.
383 # This is intended for now as compared to if/else there is a lot
384 # of logic actually still happening in the case matching.
385 length += sum(self._get_block_length(child) for child in node.cases)
387 # We do not want to count the docstring
388 if (
389 length
390 and isinstance(
391 node,
392 (ast.AsyncFunctionDef, ast.FunctionDef, ast.ClassDef, ast.Module),
393 )
394 and ast.get_docstring(node)
395 ):
396 length -= 1
397 return length
399 def handle_class_body(self, cls: ast.ClassDef) -> tuple[list[Parameter], list[str]]:
400 """Extract attributes and methods from class body.
402 Will walk the AST of the ClassDef node and add each function encountered
403 as a method.
405 If the `__init__` method is encountered walk its body for attribute
406 definitions.
408 Parameters
409 ----------
410 cls : ast.ClassDef
411 Node representing a class definition.
413 Returns
414 -------
415 attributes : list[Parameter]
416 List of the parameters that make up the classes attributes.
417 methods : list[str]
418 List of the method names in the class.
419 """
420 attributes: list[Parameter] = []
421 methods: list[str] = []
422 for node in cls.body:
423 if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
424 continue
425 # Extract attributes from init method.
426 if node.name == "__init__":
427 attributes.extend(self._get_attributes_from_init(node))
428 # Skip dunder methods for method extraction
429 if node.name.startswith("__") and node.name.endswith("__"):
430 continue
431 # Optionally skip private methods.
432 if self.settings.ignore_privates and node.name.startswith("_"):
433 continue
434 # Handle properties as attributes
435 if "property" in {
436 decorator.id
437 for decorator in node.decorator_list
438 if isinstance(decorator, ast.Name)
439 }:
440 return_value = self.get_return_value_sig(node)
441 attributes.append(Parameter(node.name, return_value.type_name, None))
442 # Handle normal methods except for those with some specific decorators
443 # Like statismethod, classmethod, property or getters/setters.
444 elif not self._has_excluding_decorator(node):
445 methods.append(self._get_method_signature(node))
446 # Exclude some like staticmethods and properties
448 # Remove duplicates from attributes while maintaining order
449 return list(Parameter.uniquefy(attributes)), methods
451 def handle_function_signature(
452 self,
453 func: Union[ast.FunctionDef, ast.AsyncFunctionDef],
454 ) -> FunctionSignature:
455 """Extract information about the signature of the function.
457 Parameters
458 ----------
459 func : Union[ast.FunctionDef, ast.AsyncFunctionDef]
460 Node representing a function definition
462 Returns
463 -------
464 FunctionSignature
465 Information extracted from the function signature
466 """
467 parameters = self.get_parameters_sig(func)
468 if parameters and parameters[0].arg_name == "self":
469 parameters.pop(0)
470 return_value = self.get_return_value_sig(func)
471 return FunctionSignature(parameters, return_value)
473 def handle_function_body(
474 self,
475 func: Union[ast.FunctionDef, ast.AsyncFunctionDef],
476 ) -> FunctionBody:
477 """Check the function body for yields, raises and value returns.
479 Parameters
480 ----------
481 func : Union[ast.FunctionDef, ast.AsyncFunctionDef]
482 Node representing a function definition
484 Returns
485 -------
486 FunctionBody
487 Information extracted from the function body.
488 """
489 returns: set[tuple[str, ...]] = set()
490 returns_value = False
491 yields: set[tuple[str, ...]] = set()
492 yields_value = False
493 raises: list[str] = []
494 for node in ast.walk(func):
495 if isinstance(node, ast.Return) and node.value is not None:
496 returns_value = True
497 if isinstance(node.value, ast.Tuple) and all(
498 isinstance(value, ast.Name) for value in node.value.elts
499 ):
500 returns.add(self._get_ids_from_returns(node.value.elts))
501 elif isinstance(node, (ast.Yield, ast.YieldFrom)):
502 yields_value = True
503 if (
504 isinstance(node, ast.Yield)
505 and isinstance(node.value, ast.Tuple)
506 and all(isinstance(value, ast.Name) for value in node.value.elts)
507 ):
508 yields.add(self._get_ids_from_returns(node.value.elts))
509 elif isinstance(node, ast.Raise):
510 pascal_case_regex = r"^(?:[A-Z][a-z]+)+$"
511 if not node.exc:
512 raises.append(DEFAULT_EXCEPTION)
513 elif isinstance(node.exc, ast.Name) and re.match(
514 pascal_case_regex, node.exc.id
515 ):
516 raises.append(node.exc.id)
517 elif (
518 isinstance(node.exc, ast.Call)
519 and isinstance(node.exc.func, ast.Name)
520 and re.match(pascal_case_regex, node.exc.func.id)
521 ):
522 raises.append(node.exc.func.id)
523 else:
524 raises.append(DEFAULT_EXCEPTION)
525 return FunctionBody(
526 returns_value=returns_value,
527 returns=returns,
528 yields_value=yields_value,
529 yields=yields,
530 raises=raises,
531 )
533 def get_return_value_sig(
534 self, func: Union[ast.FunctionDef, ast.AsyncFunctionDef]
535 ) -> ReturnValue:
536 """Get information about return value from signature.
538 Parameters
539 ----------
540 func : Union[ast.FunctionDef, ast.AsyncFunctionDef]
541 Node representing a function definition
543 Returns
544 -------
545 ReturnValue
546 Return information extracted from the function signature.
547 """
548 return ReturnValue(type_name=ast_unparse(func.returns))
550 def get_parameters_sig(
551 self, func: Union[ast.FunctionDef, ast.AsyncFunctionDef]
552 ) -> list[Parameter]:
553 """Get information about function parameters.
555 Parameters
556 ----------
557 func : Union[ast.FunctionDef, ast.AsyncFunctionDef]
558 Node representing a function definition
560 Returns
561 -------
562 list[Parameter]
563 Parameter information from the function signature.
564 """
565 arguments: list[Parameter] = []
566 pos_defaults = self.get_padded_args_defaults(func)
568 pos_only_args = [
569 Parameter(arg.arg, ast_unparse(arg.annotation), None)
570 for arg in func.args.posonlyargs
571 ]
572 arguments += pos_only_args
573 general_args = [
574 Parameter(arg.arg, ast_unparse(arg.annotation), default)
575 for arg, default in zip(func.args.args, pos_defaults)
576 ]
577 arguments += general_args
578 if vararg := func.args.vararg:
579 arguments.append(
580 Parameter(f"*{vararg.arg}", ast_unparse(vararg.annotation), None)
581 )
582 kw_only_args = [
583 Parameter(
584 arg.arg,
585 ast_unparse(arg.annotation),
586 ast_unparse(default),
587 )
588 for arg, default in zip(func.args.kwonlyargs, func.args.kw_defaults)
589 ]
590 arguments += kw_only_args
591 if kwarg := func.args.kwarg:
592 arguments.append(
593 Parameter(f"**{kwarg.arg}", ast_unparse(kwarg.annotation), None)
594 )
595 # Filter out unused arguments.
596 return (
597 [
598 argument
599 for argument in arguments
600 if not argument.arg_name.startswith("_")
601 ]
602 if self.settings.ignore_unused_arguments
603 else arguments
604 )
606 @staticmethod
607 def is_shebang_or_pragma(line: str) -> bool:
608 """Check if a given line contains encoding or shebang.
610 Parameters
611 ----------
612 line : str
613 Line to check
615 Returns
616 -------
617 bool
618 Whether the given line contains encoding or shebang
619 """
620 shebang_regex = r"^#!(.*)"
621 if re.search(shebang_regex, line) is not None:
622 return True
623 pragma_regex = r"^#.*coding[=:]\s*([-\w.]+)"
624 return re.search(pragma_regex, line) is not None
626 def get_padded_args_defaults(
627 self,
628 func: Union[ast.FunctionDef, ast.AsyncFunctionDef],
629 ) -> list[Optional[str]]:
630 """Left-Pad the general args defaults to the length of the args.
632 Parameters
633 ----------
634 func : Union[ast.FunctionDef, ast.AsyncFunctionDef]
635 Node representing a function definition
637 Returns
638 -------
639 list[Optional[str]]
640 Left padded (with `None`) list of function arguments.
641 """
642 pos_defaults = [ast_unparse(default) for default in func.args.defaults]
643 return [None] * (len(func.args.args) - len(pos_defaults)) + pos_defaults
645 def _has_excluding_decorator(
646 self, node: Union[ast.FunctionDef, ast.AsyncFunctionDef]
647 ) -> bool:
648 """Exclude function with some decorators.
650 Currently excluded:
651 staticmethod
652 classmethod
653 property (and related)
655 Parameters
656 ----------
657 node : Union[ast.FunctionDef, ast.AsyncFunctionDef]
658 Node representing a function definition
660 Returns
661 -------
662 bool
663 Whether the function as any decorators that exclude it from
664 being recognized as a standard method.
665 """
666 decorators = node.decorator_list
667 excluded_decorators = {"staticmethod", "classmethod", "property"}
668 for decorator in decorators:
669 if isinstance(decorator, ast.Name) and decorator.id in excluded_decorators:
670 return True
671 # Handle property related decorators like in
672 # @x.setter
673 # def x(self, value):
674 # self._x = value # noqa: ERA001
676 # @x.deleter
677 # def x(self):
678 # del self._x
679 if ( 679 ↛ 668line 679 didn't jump to line 668
680 isinstance(decorator, ast.Attribute)
681 and isinstance(decorator.value, ast.Name)
682 and decorator.value.id == node.name
683 ):
684 return True
685 return False
687 def _check_if_node_is_self_attributes(
688 self, node: ast.expr
689 ) -> TypeGuard[ast.Attribute]:
690 """Check whether the node represents a public attribute of self (self.abc).
692 Parameters
693 ----------
694 node : ast.expr
695 Node representing the expression to be checked.
697 Returns
698 -------
699 TypeGuard[ast.Attribute]
700 True if the node represents a public attribute of self.
701 """
702 return (
703 isinstance(node, ast.Attribute)
704 and isinstance(node.value, ast.Name)
705 and node.value.id == "self"
706 and not (self.settings.ignore_privates and node.attr.startswith("_"))
707 )
709 def _check_and_handle_assign_node(
710 self, target: ast.expr, attributes: list[Parameter]
711 ) -> None:
712 """Check if the assignment node contains assignments to self.X.
714 Add it to the list of attributes if that is the case.
716 Parameters
717 ----------
718 target : ast.expr
719 Node representing an assignment
720 attributes : list[Parameter]
721 List of attributes the node attribute should be added to.
722 """
723 if isinstance(target, (ast.Tuple, ast.List)):
724 for node in target.elts:
725 if self._check_if_node_is_self_attributes(node): 725 ↛ 724line 725 didn't jump to line 724, because the condition on line 725 was never false
726 attributes.append(Parameter(node.attr, "_type_", None))
727 elif self._check_if_node_is_self_attributes(target):
728 attributes.append(Parameter(target.attr, "_type_", None))
730 def _get_attributes_from_init(
731 self, init: Union[ast.FunctionDef, ast.AsyncFunctionDef]
732 ) -> list[Parameter]:
733 """Iterate over body and grab every assignment `self.abc = XYZ`.
735 Parameters
736 ----------
737 init : Union[ast.FunctionDef, ast.AsyncFunctionDef]
738 Init function node to extract attributes from.
740 Returns
741 -------
742 list[Parameter]
743 List of attributes extracted from the init function.
744 """
745 attributes: list[Parameter] = []
746 for node in init.body:
747 if isinstance(node, ast.Assign):
748 # Targets is a list in case of multiple assignent
749 # a = b = 3 # noqa: ERA001
750 for target in node.targets:
751 self._check_and_handle_assign_node(target, attributes)
752 # Also handle annotated assignments
753 # c: int = "Test" # noqa: ERA001
754 elif isinstance(node, ast.AnnAssign):
755 self._check_and_handle_assign_node(node.target, attributes)
756 return attributes
758 def _get_method_signature(
759 self, func: Union[ast.FunctionDef, ast.AsyncFunctionDef]
760 ) -> str:
761 """Remove self from signature and return the unparsed string.
763 Parameters
764 ----------
765 func : Union[ast.FunctionDef, ast.AsyncFunctionDef]
766 Node representing a function definition.
768 Returns
769 -------
770 str
771 String of the method signature with `self` removed.
772 """
773 arguments = func.args
774 if arguments.posonlyargs:
775 arguments.posonlyargs = [
776 arg for arg in arguments.posonlyargs if arg.arg != "self"
777 ]
778 elif arguments.args: 778 ↛ 780line 778 didn't jump to line 780, because the condition on line 778 was never false
779 arguments.args = [arg for arg in arguments.args if arg.arg != "self"]
780 return f"{func.name}({ast.unparse(arguments)})"
782 def _get_ids_from_returns(self, values: list[ast.expr]) -> tuple[str, ...]:
783 """Get the ids/names for all the expressions in the list.
785 Parameters
786 ----------
787 values : list[ast.expr]
788 List of expressions to extract the ids from.
790 Returns
791 -------
792 tuple[str, ...]
793 Tuple of ids of the original expressions.
794 """
795 return tuple(
796 value.id
797 for value in values
798 # Needed again for type checker
799 if isinstance(value, ast.Name)
800 )