def test_hash(self): a = Module("foo.bar") b = Module("foo.bar") c = Module("foo.bar.baz") assert hash(a) == hash(b) assert hash(a) != hash(c)
def test_ignores_hidden_directories(): module_finder = ModuleFinder() file_system = FakeFileSystem( contents=""" /path/to/mypackage/ __init__.py two/ __init__.py green.py .hidden/ green.py orphan/ __init__.py red.py """ ) result = module_finder.find_modules( package_name="mypackage", package_directory="/path/to/mypackage", file_system=file_system, ) expected_modules = { Module("mypackage"), Module("mypackage.two"), Module("mypackage.two.green"), } assert set(result) == expected_modules
def test_ignores_orphaned_python_files(): # Python files in directories that don't contain an __init__.py should not be discovered. module_finder = ModuleFinder() file_system = FakeFileSystem( contents=""" /path/to/mypackage/ __init__.py two/ __init__.py green.py noinitpackage/ green.py orphan/ __init__.py red.py """ ) result = module_finder.find_modules( package_name="mypackage", package_directory="/path/to/mypackage", file_system=file_system, ) expected_modules = { Module("mypackage"), Module("mypackage.two"), Module("mypackage.two.green"), } assert set(result) == expected_modules
def test_repr(self): import_path = DirectImport( importer=Module("foo"), imported=Module("bar"), line_number=10, line_contents="import bar", ) assert repr(import_path) == "<DirectImport: foo -> bar (l. 10)>"
def determine_imported_modules( self, include_external_packages: bool ) -> Set[Module]: imported_modules: Set[Module] = set() assert isinstance(self.node, self.node_class) # For type checker. assert isinstance(self.node.level, int) # For type checker. if self.node.level == 0: # Absolute import. # Let the type checker know we expect node.module to be set here. assert isinstance(self.node.module, str) node_module = Module(self.node.module) if not self._is_internal_module(node_module): if include_external_packages: # Just return the top level package of the external module. return {Module(node_module.package_name)} else: return set() # Don't include imports of modules outside this package. module_base = self.node.module elif self.node.level >= 1: # Relative import. The level corresponds to how high up the tree it goes; # for example 'from ... import foo' would be level 3. importing_module_components = self.module.name.split(".") # TODO: handle level that is too high. # Trim the base module by the number of levels. if self.module_is_package: # If the scanned module an __init__.py file, we don't want # to go up an extra level. number_of_levels_to_trim_by = self.node.level - 1 else: number_of_levels_to_trim_by = self.node.level if number_of_levels_to_trim_by: module_base = ".".join( importing_module_components[:-number_of_levels_to_trim_by] ) else: module_base = ".".join(importing_module_components) if self.node.module: module_base = ".".join([module_base, self.node.module]) # node.names corresponds to 'a', 'b' and 'c' in 'from x import a, b, c'. for alias in self.node.names: full_module_name = ".".join([module_base, alias.name]) try: imported_module = self._trim_to_internal_module( untrimmed_module=Module(full_module_name) ) except FileNotFoundError: logger.warning( f"Could not find {full_module_name} when scanning {self.module}. " "This may be due to a missing __init__.py file in the parent package." ) else: imported_modules.add(imported_module) return imported_modules
def test_equals(self): a = Module("foo.bar") b = Module("foo.bar") c = Module("foo.bar.baz") assert a == b assert a != c # Also non-Module instances should not be treated as equal. assert a != "foo"
def find_children(self, module: str) -> Set[str]: # It doesn't make sense to find the children of a squashed module, as we don't store # the children in the graph. if self.is_module_squashed(module): raise ValueError("Cannot find children of a squashed module.") children = set() for potential_child in self.modules: if Module(potential_child).is_child_of(Module(module)): children.add(potential_child) return children
def find_descendants(self, module: str) -> Set[str]: # It doesn't make sense to find the descendants of a squashed module, as we don't store # the descendants in the graph. if self.is_module_squashed(module): raise ValueError("Cannot find descendants of a squashed module.") descendants = set() for potential_descendant in self.modules: if Module(potential_descendant).is_descendant_of(Module(module)): descendants.add(potential_descendant) return descendants
def test_trims_to_known_modules(import_source): all_modules = { Module("foo"), Module("foo.one"), Module("foo.two"), Module("foo.two.yellow"), } file_system = FakeFileSystem( contents=""" /path/to/foo/ __init__.py one.py two/ __init__.py yellow.py """, content_map={"/path/to/foo/one.py": import_source}, ) import_scanner = ImportScanner( modules_by_package_directory={"/path/to/foo": all_modules}, file_system=file_system, ) result = import_scanner.scan_for_imports(Module("foo.one")) assert result == { DirectImport( importer=Module("foo.one"), imported=Module("foo.two.yellow"), line_number=1, line_contents=import_source, ) }
def determine_imported_modules( self, include_external_packages: bool ) -> Set[Module]: imported_modules: Set[Module] = set() assert isinstance(self.node, self.node_class) # For type checker. for alias in self.node.names: module_from_alias = Module(alias.name) if self._is_internal_module(module_from_alias): imported_module = module_from_alias else: if include_external_packages: imported_module = Module(module_from_alias.package_name) else: continue imported_modules.add(imported_module) return imported_modules
def test_happy_path(): module_finder = ModuleFinder() file_system = FakeFileSystem( contents=""" /path/to/mypackage/ __init__.py not-a-python-file.txt .hidden foo/ __init__.py one.py two/ __init__.py green.py blue.py """ ) result = module_finder.find_modules( package_name="mypackage", package_directory="/path/to/mypackage", file_system=file_system, ) expected_modules = { Module("mypackage"), Module("mypackage.foo"), Module("mypackage.foo.one"), Module("mypackage.foo.two"), Module("mypackage.foo.two.green"), Module("mypackage.foo.two.blue"), } assert set(result) == expected_modules
def test_trims_whitespace_from_start_of_line_contents(): all_modules = {Module("foo"), Module("foo.one"), Module("foo.two")} file_system = FakeFileSystem( contents=""" /path/to/foo/ __init__.py one.py two.py """, content_map={ "/path/to/foo/one.py": """ def my_function(): from . import two """ }, ) import_scanner = ImportScanner( modules_by_package_directory={"/path/to/foo": all_modules}, file_system=file_system, ) result = import_scanner.scan_for_imports(Module("foo.one")) assert result == { DirectImport( importer=Module("foo.one"), imported=Module("foo.two"), line_number=2, line_contents="from . import two", ) }
def find_modules(self, package_name: str, package_directory: str, file_system: AbstractFileSystem) -> Iterable[Module]: self.file_system = file_system modules: List[Module] = [] for module_filename in self._get_python_files_inside_package( package_directory): module_name = self._module_name_from_filename( module_filename, package_directory) modules.append(Module(module_name)) return modules
def test_absolute_imports(include_external_packages, expected_result): all_modules = {Module("foo.one"), Module("foo.two")} file_system = FakeFileSystem( content_map={ "/path/to/foo/one.py": """ import foo.two import externalone import externaltwo.subpackage arbitrary_expression = 1 """ }) import_scanner = ImportScanner( modules_by_package_directory={"/path/to/foo": all_modules}, file_system=file_system, include_external_packages=include_external_packages, ) result = import_scanner.scan_for_imports(Module("foo.one")) assert expected_result == result
def _find_ancestor_squashed_module(self, module: str) -> Optional[str]: """ Return the name of a squashed module that is an ancestor of the supplied module, or None if no such module exists. """ try: parent = Module(module).parent.name except ValueError: # The module has no more ancestors. return None if parent in self.modules and self.is_module_squashed(parent): return parent else: return self._find_ancestor_squashed_module(parent)
def _trim_to_internal_module(self, untrimmed_module: Module) -> Module: """ Raises FileNotFoundError if it could not find a valid module. """ if untrimmed_module in self.internal_modules: return untrimmed_module else: # The module isn't in the internal modules. This is because it's something *within* # a module (e.g. a function): the result of something like 'from .subpackage # import my_function'. So we trim the components back to the module. components = untrimmed_module.name.split(".")[:-1] trimmed_module = Module(".".join(components)) if trimmed_module in self.internal_modules: return trimmed_module else: raise FileNotFoundError()
def test_absolute_from_imports(include_external_packages, expected_result): all_modules = { Module("foo.one.blue"), Module("foo.one.green"), Module("foo.two.brown"), Module("foo.two.yellow"), Module("foo.three"), } file_system = FakeFileSystem( contents=""" /path/to/foo/ __init__.py one/ __init__.py blue.py green.py two/ __init__.py brown.py yellow.py three.py """, content_map={ "/path/to/foo/one/blue.py": """ from foo.one import green from foo.two import yellow from foo import three from external import one from external.two import blue arbitrary_expression = 1 """ }, ) import_scanner = ImportScanner( modules_by_package_directory={"/path/to/foo": all_modules}, file_system=file_system, include_external_packages=include_external_packages, ) result = import_scanner.scan_for_imports(Module("foo.one.blue")) assert expected_result == result
def test_package_name(self): assert Module("foo.bar.baz").package_name == "foo"
def test_scans_multiple_packages(statement): foo_modules = {Module("foo"), Module("foo.one"), Module("foo.two")} bar_modules = {Module("bar"), Module("bar.green"), Module("bar.blue")} file_system = FakeFileSystem( content_map={ "/path/to/foo/one.py": f""" import foo.two {statement} import externalone arbitrary_expression = 1 """ }) import_scanner = ImportScanner( modules_by_package_directory={ "/path/to/foo": foo_modules, "/path/to/bar": bar_modules, }, file_system=file_system, ) result = import_scanner.scan_for_imports(Module("foo.one")) assert { DirectImport( importer=Module("foo.one"), imported=Module("foo.two"), line_number=1, line_contents="import foo.two", ), DirectImport( importer=Module("foo.one"), imported=Module("bar.blue"), line_number=2, line_contents=statement, ), } == result
def test_equals(self): a = DirectImport( importer=Module("foo"), imported=Module("bar"), line_number=10, line_contents="import bar", ) b = DirectImport( importer=Module("foo"), imported=Module("bar"), line_number=10, line_contents="import bar", ) c = DirectImport( importer=Module("foo"), imported=Module("baz"), line_number=10, line_contents="import bar", ) d = DirectImport( importer=Module("foobar"), imported=Module("bar"), line_number=10, line_contents="import bar", ) e = DirectImport( importer=Module("foo"), imported=Module("bar"), line_number=11, line_contents="import bar", ) f = DirectImport( importer=Module("foo"), imported=Module("bar"), line_number=10, line_contents="from . import bar", ) assert a == b assert a != c assert a != d assert a != e assert a != f # Also non-DirectImport instances should not be treated as equal. assert a != "foo"
def test_trims_to_known_modules_within_init_file(): all_modules = { Module("foo"), Module("foo.one"), Module("foo.one.yellow"), Module("foo.one.blue"), Module("foo.one.blue.alpha"), } file_system = FakeFileSystem( contents=""" /path/to/foo/ __init__.py one/ __init__.py yellow.py blue/ __init__.py alpha.py """, content_map={ "/path/to/foo/one/__init__.py": "from .yellow import my_function", "/path/to/foo/one/blue/__init__.py": "from .alpha import my_function", }, ) import_scanner = ImportScanner( modules_by_package_directory={"/path/to/foo": all_modules}, file_system=file_system, ) result = import_scanner.scan_for_imports(Module("foo.one")) assert result == { DirectImport( importer=Module("foo.one"), imported=Module("foo.one.yellow"), line_number=1, line_contents="from .yellow import my_function", ) } result = import_scanner.scan_for_imports(Module("foo.one.blue")) assert result == { DirectImport( importer=Module("foo.one.blue"), imported=Module("foo.one.blue.alpha"), line_number=1, line_contents="from .alpha import my_function", ) }
def test_hash(self): a = DirectImport( importer=Module("foo"), imported=Module("bar"), line_number=10, line_contents="import bar", ) b = DirectImport( importer=Module("foo"), imported=Module("bar"), line_number=10, line_contents="import bar", ) c = DirectImport( importer=Module("foo"), imported=Module("baz"), line_number=10, line_contents="import bar", ) d = DirectImport( importer=Module("foobar"), imported=Module("bar"), line_number=10, line_contents="import bar", ) e = DirectImport( importer=Module("foo"), imported=Module("bar"), line_number=11, line_contents="import bar", ) f = DirectImport( importer=Module("foo"), imported=Module("bar"), line_number=10, line_contents="from . import bar", ) assert hash(a) == hash(b) assert hash(a) != hash(c) assert hash(a) != hash(d) assert hash(a) != hash(e) assert hash(a) != hash(f)
def test_relative_from_imports(): all_modules = { Module("foo.one.blue"), Module("foo.one.green"), Module("foo.two.brown"), Module("foo.two.yellow"), Module("foo.three"), } file_system = FakeFileSystem( contents=""" /path/to/foo/ __init__.py one/ __init__.py blue.py green.py two/ __init__.py brown.py yellow.py three.py """, content_map={ "/path/to/foo/one/blue.py": """ from . import green from ..two import yellow from .. import three arbitrary_expression = 1 """ }, ) import_scanner = ImportScanner( modules_by_package_directory={"/path/to/foo": all_modules}, file_system=file_system, ) result = import_scanner.scan_for_imports(Module("foo.one.blue")) assert result == { DirectImport( importer=Module("foo.one.blue"), imported=Module("foo.one.green"), line_number=1, line_contents="from . import green", ), DirectImport( importer=Module("foo.one.blue"), imported=Module("foo.two.yellow"), line_number=2, line_contents="from ..two import yellow", ), DirectImport( importer=Module("foo.one.blue"), imported=Module("foo.three"), line_number=3, line_contents="from .. import three", ), }
import pytest # type: ignore from grimp.adaptors.importscanner import ImportScanner from grimp.domain.valueobjects import DirectImport, Module from tests.adaptors.filesystem import FakeFileSystem @pytest.mark.parametrize( "include_external_packages, expected_result", ( ( False, { DirectImport( importer=Module("foo.one"), imported=Module("foo.two"), line_number=1, line_contents="import foo.two", ) }, ), ( True, { DirectImport( importer=Module("foo.one"), imported=Module("foo.two"), line_number=1, line_contents="import foo.two", ), DirectImport(
def test_repr(self): module = Module("foo.bar") assert repr(module) == "<Module: foo.bar>"