def _build_layer_chain_data(self, higher_layer_package: Module, lower_layer_package: Module, graph: ImportGraph) -> Dict[str, Any]: layer_chain_data = { "higher_layer": higher_layer_package.name, "lower_layer": lower_layer_package.name, "chains": [], } assert isinstance(layer_chain_data["chains"], list) # For type checker. chains = graph.find_shortest_chains(importer=lower_layer_package.name, imported=higher_layer_package.name) if chains: for chain in chains: chain_data = [] for importer, imported in [(chain[i], chain[i + 1]) for i in range(len(chain) - 1)]: import_details = graph.get_import_details( importer=importer, imported=imported) line_numbers = tuple(j["line_number"] for j in import_details) chain_data.append({ "importer": importer, "imported": imported, "line_numbers": line_numbers }) layer_chain_data["chains"].append(chain_data) return layer_chain_data
def check(self, graph: ImportGraph) -> ContractCheck: is_kept = True invalid_chains = [] helpers.pop_imports( graph, self.ignore_imports if self.ignore_imports else [] # type: ignore ) self._check_all_modules_exist_in_graph(graph) self._check_external_forbidden_modules(graph) # We only need to check for illegal imports for forbidden modules that are in the graph. forbidden_modules_in_graph = [ m for m in self.forbidden_modules if m.name in graph.modules # type: ignore ] for source_module in self.source_modules: # type: ignore for forbidden_module in forbidden_modules_in_graph: subpackage_chain_data = { "upstream_module": forbidden_module.name, "downstream_module": source_module.name, "chains": [], } chains = graph.find_shortest_chains( importer=source_module.name, imported=forbidden_module.name) if chains: if self._allow_indirect_imports(): chains = [chain for chain in chains if len(chain) <= 2] if len(chains) == 0: continue is_kept = False for chain in chains: chain_data = [] for importer, imported in [ (chain[i], chain[i + 1]) for i in range(len(chain) - 1) ]: import_details = graph.get_import_details( importer=importer, imported=imported) line_numbers = tuple(j["line_number"] for j in import_details) chain_data.append({ "importer": importer, "imported": imported, "line_numbers": line_numbers, }) subpackage_chain_data["chains"].append(chain_data) if subpackage_chain_data["chains"]: invalid_chains.append(subpackage_chain_data) return ContractCheck(kept=is_kept, metadata={"invalid_chains": invalid_chains})
def check(self, graph: ImportGraph) -> ContractCheck: number_of_modules: int = int(self.number_of_modules) # type: ignore number_of_imports: int = int(self.number_of_imports) # type: ignore if not all([ number_of_modules == len(graph.modules), number_of_imports == graph.count_imports() ]): raise RuntimeError("Contract was mutated.") # Mutate graph. graph.add_import(importer="added-by-contract-1", imported="added-by-contract-2") return ContractCheck(kept=True)
def check(self, graph: ImportGraph) -> ContractCheck: is_kept = True invalid_chains = [] removed_imports = helpers.pop_imports( graph, self.ignore_imports if self.ignore_imports else [] # type: ignore ) self._check_all_modules_exist_in_graph(graph) for subpackage_1, subpackage_2 in permutations(self.modules, r=2): # type: ignore subpackage_chain_data = { "upstream_module": subpackage_2.name, "downstream_module": subpackage_1.name, "chains": [], } assert isinstance(subpackage_chain_data["chains"], list) # For type checker. chains = graph.find_shortest_chains(importer=subpackage_1.name, imported=subpackage_2.name) if chains: is_kept = False for chain in chains: chain_data = [] for importer, imported in [(chain[i], chain[i + 1]) for i in range(len(chain) - 1)]: import_details = graph.get_import_details( importer=importer, imported=imported) line_numbers = tuple(j["line_number"] for j in import_details) chain_data.append({ "importer": importer, "imported": imported, "line_numbers": line_numbers, }) subpackage_chain_data["chains"].append(chain_data) if subpackage_chain_data["chains"]: invalid_chains.append(subpackage_chain_data) helpers.add_imports(graph, removed_imports) return ContractCheck(kept=is_kept, metadata={"invalid_chains": invalid_chains})
def check(self, graph: ImportGraph) -> ContractCheck: forbidden_import_details = graph.get_import_details( importer=self.importer.name, imported=self.imported.name # type: ignore ) import_exists = bool(forbidden_import_details) return ContractCheck( kept=not import_exists, metadata={"forbidden_import_details": forbidden_import_details})
def __init__(self, graph: ImportGraph) -> None: self.graph = graph self.could_not_run = False self.invalid_contract_options: Dict[str, InvalidContractOptions] = {} self.contains_failures = False self.contracts: List[Contract] = [] self._check_map: Dict[Contract, ContractCheck] = {} self.broken_count = 0 self.kept_count = 0 self.module_count = len(graph.modules) self.import_count = graph.count_imports()
def add_imports(graph: ImportGraph, import_details: List[Dict[str, Union[str, int]]]) -> None: """ Adds the supplied import details to the graph. Intended to be the reverse of pop_imports, so the following code should leave the graph unchanged: import_details = pop_imports(graph, imports) add_imports(graph, import_details) """ for details in import_details: assert isinstance(details["importer"], str) assert isinstance(details["imported"], str) assert isinstance(details["line_number"], int) assert isinstance(details["line_contents"], str) graph.add_import( importer=details["importer"], imported=details["imported"], line_number=details["line_number"], line_contents=details["line_contents"], )
def pop_imports( graph: ImportGraph, imports: Iterable[DirectImport] ) -> List[Dict[str, Union[str, int]]]: """ Removes the supplied direct imports from the graph. Returns: The list of import details that were removed, including any additional metadata. Raises: MissingImport if the import is not present in the graph. """ removed_imports: List[Dict[str, Union[str, int]]] = [] for import_to_remove in imports: import_details = graph.get_import_details( importer=import_to_remove.importer.name, imported=import_to_remove.imported.name ) if not import_details: raise MissingImport(f"Ignored import {import_to_remove} not present in the graph.") removed_imports.extend(import_details) graph.remove_import( importer=import_to_remove.importer.name, imported=import_to_remove.imported.name ) return removed_imports