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() ) -- https://mail.python.org/mailman/listinfo/python-list