@@ -43,6 +43,7 @@ class _LocatedImport:
4343 module_name : str
4444 import_kind : ImportKind
4545 line_number : int
46+ resolution_kind : ImportKind | None = None
4647
4748
4849@dataclass (frozen = True )
@@ -182,8 +183,9 @@ def _extract_graph_uncached(
182183 and import_kind == ImportKind .TYPE_IMPORT
183184 ):
184185 continue
186+ resolution_kind = located_import .resolution_kind or import_kind
185187 resolved , is_external = _resolve_import (
186- module_name , file_path , project_path , import_kind
188+ module_name , file_path , project_path , resolution_kind
187189 )
188190 if resolved and resolved != _normalize (file_path ):
189191 # Check if the resolved path is in our project
@@ -195,7 +197,7 @@ def _extract_graph_uncached(
195197 source = _normalize (file_path ),
196198 target = resolved ,
197199 external = is_external ,
198- import_kinds = ( import_kind , ),
200+ import_kinds = _edge_import_kinds ( located_import ),
199201 )
200202 )
201203
@@ -303,46 +305,52 @@ def _extract_located_imports(file_path: str) -> list[_LocatedImport]:
303305
304306 for node in ast .walk (tree ):
305307 if isinstance (node , ast .Import ):
308+ syntax_kind = ImportKind .IMPORT
306309 kind = _classify_import (
307310 node ,
308- ImportKind . IMPORT ,
311+ syntax_kind ,
309312 type_checking_ranges ,
310313 conditional_import_ranges ,
311314 )
312315 for alias in node .names :
313- imports .append (_LocatedImport (alias .name , kind , node .lineno ))
316+ imports .append (
317+ _LocatedImport (alias .name , kind , node .lineno , syntax_kind )
318+ )
314319
315320 elif isinstance (node , ast .ImportFrom ):
316- if node .level and node .level > 0 :
317- # Relative import
318- kind = _classify_import (
319- node ,
320- ImportKind .RELATIVE_IMPORT ,
321- type_checking_ranges ,
322- conditional_import_ranges ,
323- )
324- module = node .module or ""
325- dots = "." * node .level
326- imports .append (_LocatedImport (f"{ dots } { module } " , kind , node .lineno ))
327- else :
328- kind = _classify_import (
329- node ,
330- ImportKind .FROM_IMPORT ,
331- type_checking_ranges ,
332- conditional_import_ranges ,
321+ syntax_kind = (
322+ ImportKind .RELATIVE_IMPORT
323+ if node .level and node .level > 0
324+ else ImportKind .FROM_IMPORT
325+ )
326+ kind = _classify_import (
327+ node ,
328+ syntax_kind ,
329+ type_checking_ranges ,
330+ conditional_import_ranges ,
331+ )
332+ for module_name in _import_from_module_names (node ):
333+ imports .append (
334+ _LocatedImport (
335+ module_name ,
336+ kind ,
337+ node .lineno ,
338+ syntax_kind ,
339+ )
333340 )
334- if node .module :
335- imports .append (_LocatedImport (node .module , kind , node .lineno ))
336341
337342 elif isinstance (node , ast .Call ):
343+ syntax_kind = ImportKind .DYNAMIC_IMPORT
338344 kind = _classify_import (
339345 node ,
340- ImportKind . DYNAMIC_IMPORT ,
346+ syntax_kind ,
341347 type_checking_ranges ,
342348 conditional_import_ranges ,
343349 )
344350 for module_name in _extract_dynamic_import_names (node ):
345- imports .append (_LocatedImport (module_name , kind , node .lineno ))
351+ imports .append (
352+ _LocatedImport (module_name , kind , node .lineno , syntax_kind )
353+ )
346354
347355 return [
348356 import_
@@ -407,6 +415,29 @@ def _extract_dynamic_import_names(node: ast.Call) -> list[str]:
407415 return []
408416
409417
418+ def _import_from_module_names (node : ast .ImportFrom ) -> tuple [str , ...]:
419+ """Return resolvable module names for a from-import statement."""
420+ dots = "." * (node .level or 0 )
421+ if node .module :
422+ return (f"{ dots } { node .module } " ,)
423+
424+ aliases = tuple (alias .name for alias in node .names if alias .name != "*" )
425+ if dots and aliases :
426+ return tuple (f"{ dots } { alias } " for alias in aliases )
427+ return (dots ,) if dots else ()
428+
429+
430+ def _edge_import_kinds (import_ : _LocatedImport ) -> tuple [ImportKind , ...]:
431+ """Return graph labels without losing syntax for conditional imports."""
432+ resolution_kind = import_ .resolution_kind or import_ .import_kind
433+ if (
434+ import_ .import_kind == ImportKind .CONDITIONAL_IMPORT
435+ and resolution_kind != import_ .import_kind
436+ ):
437+ return (resolution_kind , import_ .import_kind )
438+ return (import_ .import_kind ,)
439+
440+
410441def _classify_import (
411442 node : ast .AST ,
412443 default_kind : ImportKind ,
0 commit comments