utils.ModuleParser

  1import os
  2import astroid
  3from astroid import NodeNG, FunctionDef, ClassDef
  4
  5from data.SourceFile import SourceFile
  6from data.SourceClass import SourceClass
  7from data.SourceFunction import SourceFunction
  8from data.SourceType import SourceType
  9from data.SourceVariable import SourceVariable
 10
 11
 12def annotation_to_type(node: NodeNG | None) -> SourceType:
 13    """
 14    Generates a type string from annotation nodes.
 15
 16    :param node: The root annotation node.
 17    :return: A human-readable type string.
 18    """
 19    string_annotation: str = ''
 20    type_set: set[str] = set()
 21    if isinstance(node, astroid.Name):
 22        string_annotation = node.name
 23        type_set.add(node.name)
 24    elif isinstance(node, astroid.Subscript) and isinstance(node.value, astroid.Name):
 25        if isinstance(node.slice, astroid.Tuple):
 26            string_annotation = node.value.name + '['
 27            for child_node in node.slice.elts:
 28                inner_annotation = annotation_to_type(child_node)
 29                string_annotation += str(inner_annotation) + ', '
 30                type_set.update(inner_annotation.dependencies)
 31            string_annotation = string_annotation[:-2]
 32            string_annotation += ']'
 33        elif isinstance(node.slice, (astroid.Attribute, astroid.Name)):
 34            inner_annotation = annotation_to_type(node.slice)
 35            string_annotation = node.value.name + '[' + str(inner_annotation) + ']'
 36            type_set.update(inner_annotation.dependencies)
 37    # pylint:disable-next=confusing-consecutive-elif
 38    elif isinstance(node, astroid.Attribute):
 39        string_annotation = node.attrname
 40        type_set.add(node.attrname)
 41    elif isinstance(node, astroid.Const):
 42        string_annotation = str(node.value)
 43        type_set.add(str(node.value))
 44    elif isinstance(node, astroid.BinOp):
 45        left = annotation_to_type(node.left)
 46        right = annotation_to_type(node.right)
 47        string_annotation = str(left) + ' | ' + str(right)
 48        type_set.update(left.dependencies)
 49        type_set.update(right.dependencies)
 50    return SourceType(string_annotation, type_set)
 51
 52
 53def get_function(node: FunctionDef) -> SourceFunction:
 54    """
 55    Generates a source function based on the input node.
 56
 57    :param node: The input node.
 58    :return: A source function.
 59    """
 60    annotation = annotation_to_type(node.returns)
 61    function: SourceFunction = SourceFunction()
 62    function.name = node.name
 63    function.returns = annotation
 64    if node.decorators is not None:
 65        for decorator in node.decorators.nodes:
 66            if isinstance(decorator, astroid.Name) and \
 67                    decorator.name == 'staticmethod':
 68                function.static = True
 69    for i, arg in enumerate(node.args.args):
 70        annotation = annotation_to_type(node.args.annotations[i])
 71        try:
 72            default = str(node.args.default_value(arg.name).value)
 73        except astroid.exceptions.NoDefault:
 74            default = None
 75        except AttributeError:
 76            default = None
 77        function.params.append(
 78            SourceVariable(
 79                arg.name,
 80                annotation,
 81                False,
 82                default
 83            )
 84        )
 85    return function
 86
 87
 88def get_class(node: ClassDef) -> SourceClass:
 89    """
 90    Generates a source class based on the input node.
 91
 92    :param node: The input node.
 93    :return: A source class.
 94    """
 95    source_class: SourceClass = SourceClass()
 96    source_class.name = node.name
 97    source_class.bases = [base.name for base in node.bases if isinstance(base, astroid.Name)]
 98    for child_node in node.body:
 99        if isinstance(child_node, FunctionDef):
100            source_class.methods.append(get_function(child_node))
101            for child_child_node in child_node.body:
102                if isinstance(child_child_node, astroid.AnnAssign) and \
103                        isinstance(child_child_node.target, astroid.AssignAttr):
104                    source_class.variables.append(
105                        SourceVariable(
106                            child_child_node.target.attrname,
107                            annotation_to_type(child_child_node.annotation)
108                        )
109                    )
110        elif isinstance(child_node, astroid.AnnAssign) and \
111                isinstance(child_node.target, astroid.AssignName):
112            source_class.variables.append(
113                SourceVariable(
114                    child_node.target.name,
115                    annotation_to_type(child_node.annotation),
116                    True
117                )
118            )
119    return source_class
120
121
122# pylint:disable-next=too-complex
123def get_module_info(
124        root: str,
125        file_path: str,
126        known_modules: list[str],
127        with_external_dependencies: bool = False
128) -> SourceFile:
129    """
130    Generates a source file based on the provided file path.
131
132    :param root: The root path of the analysis.
133    :param file_path: The file path for which to generate the source file.
134    :param known_modules: All known local modules.
135    :param with_external_dependencies: Whether external dependencies should be included.
136    :return: A matching source file.
137    """
138    module = astroid.MANAGER.ast_from_file(file_path)
139    path_modules = file_path[len(root):].split('.')[-2].split(os.sep)
140    source_file = SourceFile()
141    source_file.name = '.'.join(path_modules)
142
143    for node in module.body:
144        if isinstance(node, astroid.ClassDef):
145            source_file.classes.append(get_class(node))
146        elif isinstance(node, FunctionDef):
147            source_file.functions.append(get_function(node))
148        elif isinstance(node, astroid.AnnAssign) and \
149                isinstance(node.target, astroid.AssignName):
150            source_file.variables.append(
151                SourceVariable(node.target.name, annotation_to_type(node.annotation))
152            )
153        elif isinstance(node, astroid.Import):
154            if with_external_dependencies:
155                for node_name in node.names:
156                    source_file.imports.append(node_name[0])
157        # pylint:disable-next=confusing-consecutive-elif
158        elif isinstance(node, astroid.ImportFrom):
159            if node.level == 1 or node.modname in known_modules or with_external_dependencies:
160                for node_name in node.names:
161                    source_file.imports.append(
162                        ('.'.join(path_modules[:-1]) + '.' if node.level == 1 else '') +
163                        node.modname + '.' + node_name[0]
164                    )
165    return source_file
def annotation_to_type(node: astroid.nodes.node_ng.NodeNG | None) -> data.SourceType.SourceType:
13def annotation_to_type(node: NodeNG | None) -> SourceType:
14    """
15    Generates a type string from annotation nodes.
16
17    :param node: The root annotation node.
18    :return: A human-readable type string.
19    """
20    string_annotation: str = ''
21    type_set: set[str] = set()
22    if isinstance(node, astroid.Name):
23        string_annotation = node.name
24        type_set.add(node.name)
25    elif isinstance(node, astroid.Subscript) and isinstance(node.value, astroid.Name):
26        if isinstance(node.slice, astroid.Tuple):
27            string_annotation = node.value.name + '['
28            for child_node in node.slice.elts:
29                inner_annotation = annotation_to_type(child_node)
30                string_annotation += str(inner_annotation) + ', '
31                type_set.update(inner_annotation.dependencies)
32            string_annotation = string_annotation[:-2]
33            string_annotation += ']'
34        elif isinstance(node.slice, (astroid.Attribute, astroid.Name)):
35            inner_annotation = annotation_to_type(node.slice)
36            string_annotation = node.value.name + '[' + str(inner_annotation) + ']'
37            type_set.update(inner_annotation.dependencies)
38    # pylint:disable-next=confusing-consecutive-elif
39    elif isinstance(node, astroid.Attribute):
40        string_annotation = node.attrname
41        type_set.add(node.attrname)
42    elif isinstance(node, astroid.Const):
43        string_annotation = str(node.value)
44        type_set.add(str(node.value))
45    elif isinstance(node, astroid.BinOp):
46        left = annotation_to_type(node.left)
47        right = annotation_to_type(node.right)
48        string_annotation = str(left) + ' | ' + str(right)
49        type_set.update(left.dependencies)
50        type_set.update(right.dependencies)
51    return SourceType(string_annotation, type_set)

Generates a type string from annotation nodes.

Parameters
  • node: The root annotation node.
Returns

A human-readable type string.

def get_function( node: astroid.nodes.scoped_nodes.scoped_nodes.FunctionDef) -> data.SourceFunction.SourceFunction:
54def get_function(node: FunctionDef) -> SourceFunction:
55    """
56    Generates a source function based on the input node.
57
58    :param node: The input node.
59    :return: A source function.
60    """
61    annotation = annotation_to_type(node.returns)
62    function: SourceFunction = SourceFunction()
63    function.name = node.name
64    function.returns = annotation
65    if node.decorators is not None:
66        for decorator in node.decorators.nodes:
67            if isinstance(decorator, astroid.Name) and \
68                    decorator.name == 'staticmethod':
69                function.static = True
70    for i, arg in enumerate(node.args.args):
71        annotation = annotation_to_type(node.args.annotations[i])
72        try:
73            default = str(node.args.default_value(arg.name).value)
74        except astroid.exceptions.NoDefault:
75            default = None
76        except AttributeError:
77            default = None
78        function.params.append(
79            SourceVariable(
80                arg.name,
81                annotation,
82                False,
83                default
84            )
85        )
86    return function

Generates a source function based on the input node.

Parameters
  • node: The input node.
Returns

A source function.

def get_class( node: astroid.nodes.scoped_nodes.scoped_nodes.ClassDef) -> data.SourceClass.SourceClass:
 89def get_class(node: ClassDef) -> SourceClass:
 90    """
 91    Generates a source class based on the input node.
 92
 93    :param node: The input node.
 94    :return: A source class.
 95    """
 96    source_class: SourceClass = SourceClass()
 97    source_class.name = node.name
 98    source_class.bases = [base.name for base in node.bases if isinstance(base, astroid.Name)]
 99    for child_node in node.body:
100        if isinstance(child_node, FunctionDef):
101            source_class.methods.append(get_function(child_node))
102            for child_child_node in child_node.body:
103                if isinstance(child_child_node, astroid.AnnAssign) and \
104                        isinstance(child_child_node.target, astroid.AssignAttr):
105                    source_class.variables.append(
106                        SourceVariable(
107                            child_child_node.target.attrname,
108                            annotation_to_type(child_child_node.annotation)
109                        )
110                    )
111        elif isinstance(child_node, astroid.AnnAssign) and \
112                isinstance(child_node.target, astroid.AssignName):
113            source_class.variables.append(
114                SourceVariable(
115                    child_node.target.name,
116                    annotation_to_type(child_node.annotation),
117                    True
118                )
119            )
120    return source_class

Generates a source class based on the input node.

Parameters
  • node: The input node.
Returns

A source class.

def get_module_info( root: str, file_path: str, known_modules: list[str], with_external_dependencies: bool = False) -> data.SourceFile.SourceFile:
124def get_module_info(
125        root: str,
126        file_path: str,
127        known_modules: list[str],
128        with_external_dependencies: bool = False
129) -> SourceFile:
130    """
131    Generates a source file based on the provided file path.
132
133    :param root: The root path of the analysis.
134    :param file_path: The file path for which to generate the source file.
135    :param known_modules: All known local modules.
136    :param with_external_dependencies: Whether external dependencies should be included.
137    :return: A matching source file.
138    """
139    module = astroid.MANAGER.ast_from_file(file_path)
140    path_modules = file_path[len(root):].split('.')[-2].split(os.sep)
141    source_file = SourceFile()
142    source_file.name = '.'.join(path_modules)
143
144    for node in module.body:
145        if isinstance(node, astroid.ClassDef):
146            source_file.classes.append(get_class(node))
147        elif isinstance(node, FunctionDef):
148            source_file.functions.append(get_function(node))
149        elif isinstance(node, astroid.AnnAssign) and \
150                isinstance(node.target, astroid.AssignName):
151            source_file.variables.append(
152                SourceVariable(node.target.name, annotation_to_type(node.annotation))
153            )
154        elif isinstance(node, astroid.Import):
155            if with_external_dependencies:
156                for node_name in node.names:
157                    source_file.imports.append(node_name[0])
158        # pylint:disable-next=confusing-consecutive-elif
159        elif isinstance(node, astroid.ImportFrom):
160            if node.level == 1 or node.modname in known_modules or with_external_dependencies:
161                for node_name in node.names:
162                    source_file.imports.append(
163                        ('.'.join(path_modules[:-1]) + '.' if node.level == 1 else '') +
164                        node.modname + '.' + node_name[0]
165                    )
166    return source_file

Generates a source file based on the provided file path.

Parameters
  • root: The root path of the analysis.
  • file_path: The file path for which to generate the source file.
  • known_modules: All known local modules.
  • with_external_dependencies: Whether external dependencies should be included.
Returns

A matching source file.