Cyclic imports
Barry Scott
barry at barrys-emacs.org
Tue Aug 17 14:05:15 EDT 2021
On Monday, 16 August 2021 16:13:47 BST Dan Stromberg wrote:
> Hi folks.
>
> I'm working on a large codebase that has at least one cyclic import.
>
> In case I end up needing to eliminate the cyclic imports, is there any sort
> of tool that will generate an import graph and output Just the cycles?
>
> I tried pyreverse, but it produced too big a graph to be very useful; it
> showed all internal imports, not just the cycles.
I wrote this code to track down a cycling import.
Note it handles import module, but not from module import.
You would need to make a (simple) edit to add that.
---- py_import_time.py ---
#!/usr/bin/python3
import sys
import pathlib
class PyImportTree:
def __init__( self, main_module, python_path ):
self.main_module = main_module
self.python_path = python_path
self.all_modules = {}
self.loadTree( self.main_module )
self.all_being_imported = set()
self.problem_imports = 0
def loadTree( self, module_name ):
all_imports = self.allImports( module_name )
if all_imports is None:
return
self.all_modules[ module_name ] = all_imports
for module in all_imports:
if module not in self.all_modules:
self.loadTree( module )
def findModule( self, module_name ):
for folder in self.python_path:
abs_path = pathlib.Path( folder ) / ('%s.py' % (module_name,))
if abs_path.exists():
return abs_path
return None
def allImports( self, module_name ):
all_imports = []
filename = self.findModule( module_name )
if filename is None:
print( 'Error: Cannot find module %s' % (module_name,),
file=sys.stderr )
return None
with open( str(filename), 'r' ) as f:
for line in f:
words = line.strip().split()
if words[0:1] == ['import']:
all_imports.append( words[1] )
return all_imports
def printTree( self ):
self.__printTree( self.main_module, 0 )
if self.problem_imports > 0:
print( '%d problem imports' % (self.problem_imports,),
file=sys.stderr )
def __printTree( self, module_name, indent ):
if module_name not in self.all_modules:
return
if module_name in self.all_being_imported:
print( '%s%s' % ('> '*indent, module_name) )
self.problem_imports += 1
return
print( '%s%s' % ('- '*indent, module_name) )
self.all_being_imported.add( module_name )
for module in self.all_modules[ module_name ]:
self.__printTree( module, indent+1 )
self.all_being_imported.remove( module_name )
if __name__ == '__main__':
sys.setrecursionlimit( 30 )
sys.exit( PyImportTree( sys.argv[1], sys.argv[2:] ).printTree() )
More information about the Python-list
mailing list