4444
4545MANIM_ROOT = Path (__file__ ).resolve ().parent .parent .parent
4646
47+ # In the following, we will use ``type(xyz) is xyz_type`` instead of
48+ # isinstance checks to make sure no subclasses of the type pass the
49+ # check
50+
4751
4852def parse_module_attributes () -> tuple [AliasDocsDict , DataDict ]:
4953 """Read all files, generate Abstract Syntax Trees from them, and
@@ -70,7 +74,7 @@ def parse_module_attributes() -> tuple[AliasDocsDict, DataDict]:
7074 for module_path in MANIM_ROOT .rglob ("*.py" ):
7175 module_name = module_path .resolve ().relative_to (MANIM_ROOT )
7276 module_name = list (module_name .parts )
73- module_name [- 1 ] = module_name [- 1 ][: - 3 ] # remove .py
77+ module_name [- 1 ] = module_name [- 1 ]. removesuffix ( ".py" )
7478 module_name = "." .join (module_name )
7579
7680 module_content = module_path .read_text (encoding = "utf-8" )
@@ -107,53 +111,84 @@ def parse_module_attributes() -> tuple[AliasDocsDict, DataDict]:
107111 data_list .append (data_name )
108112 continue
109113
110- # If we encounter an assignment annotated as "TypeAlias":
114+ # if it's defined under if TYPE_CHECKING
115+ # go through the body of the if statement
111116 if (
112- type (node ) is ast .AnnAssign
113- and type (node .annotation ) is ast .Name
114- and node .annotation .id == "TypeAlias"
115- and type (node .target ) is ast .Name
116- and node .value is not None
117+ # NOTE: This logic does not (and cannot)
118+ # check if the comparison is against a
119+ # variable called TYPE_CHECKING
120+ # It also says that you cannot do the following
121+ # import typing as foo
122+ # if foo.TYPE_CHECKING:
123+ # BAR: TypeAlias = ...
124+ type (node ) is ast .If
125+ and (
126+ (
127+ # if TYPE_CHECKING
128+ type (node .test ) is ast .Name
129+ and node .test .id == "TYPE_CHECKING"
130+ )
131+ or (
132+ # if typing.TYPE_CHECKING
133+ type (node .test ) is ast .Attribute
134+ and type (node .test .value ) is ast .Name
135+ and node .test .value .id == "typing"
136+ and node .test .attr == "TYPE_CHECKING"
137+ )
138+ )
117139 ):
118- alias_name = node .target .id
119- def_node = node .value
120- # If it's an Union, replace it with vertical bar notation
140+ inner_nodes = node .body
141+ else :
142+ inner_nodes = [node ]
143+
144+ for node in inner_nodes :
145+ # If we encounter an assignment annotated as "TypeAlias":
121146 if (
122- type (def_node ) is ast .Subscript
123- and type (def_node .value ) is ast .Name
124- and def_node .value .id == "Union"
147+ type (node ) is ast .AnnAssign
148+ and type (node .annotation ) is ast .Name
149+ and node .annotation .id == "TypeAlias"
150+ and type (node .target ) is ast .Name
151+ and node .value is not None
125152 ):
126- definition = " | " .join (
127- ast .unparse (elem ) for elem in def_node .slice .elts
128- )
153+ alias_name = node .target .id
154+ def_node = node .value
155+ # If it's an Union, replace it with vertical bar notation
156+ if (
157+ type (def_node ) is ast .Subscript
158+ and type (def_node .value ) is ast .Name
159+ and def_node .value .id == "Union"
160+ ):
161+ definition = " | " .join (
162+ ast .unparse (elem ) for elem in def_node .slice .elts
163+ )
164+ else :
165+ definition = ast .unparse (def_node )
166+
167+ definition = definition .replace ("npt." , "" )
168+ if category_dict is None :
169+ module_dict ["" ] = {}
170+ category_dict = module_dict ["" ]
171+ category_dict [alias_name ] = {"definition" : definition }
172+ alias_info = category_dict [alias_name ]
173+ continue
174+
175+ # If here, the node is not a TypeAlias definition
176+ alias_info = None
177+
178+ # It could still be a module attribute definition.
179+ # Does the assignment have a target of type Name? Then
180+ # it could be considered a definition of a module attribute.
181+ if type (node ) is ast .AnnAssign :
182+ target = node .target
183+ elif type (node ) is ast .Assign and len (node .targets ) == 1 :
184+ target = node .targets [0 ]
129185 else :
130- definition = ast .unparse (def_node )
131-
132- definition = definition .replace ("npt." , "" )
133- if category_dict is None :
134- module_dict ["" ] = {}
135- category_dict = module_dict ["" ]
136- category_dict [alias_name ] = {"definition" : definition }
137- alias_info = category_dict [alias_name ]
138- continue
139-
140- # If here, the node is not a TypeAlias definition
141- alias_info = None
142-
143- # It could still be a module attribute definition.
144- # Does the assignment have a target of type Name? Then
145- # it could be considered a definition of a module attribute.
146- if type (node ) is ast .AnnAssign :
147- target = node .target
148- elif type (node ) is ast .Assign and len (node .targets ) == 1 :
149- target = node .targets [0 ]
150- else :
151- target = None
186+ target = None
152187
153- if type (target ) is ast .Name :
154- data_name = target .id
155- else :
156- data_name = None
188+ if type (target ) is ast .Name :
189+ data_name = target .id
190+ else :
191+ data_name = None
157192
158193 if len (module_dict ) > 0 :
159194 ALIAS_DOCS_DICT [module_name ] = module_dict
0 commit comments