def sorted_imports( parsed: parse.ParsedContent, config: Config = DEFAULT_CONFIG, extension: str = "py", import_type: str = "import", ) -> str: """Adds the imports back to the file. (at the index of the first import) sorted alphabetically and split between groups """ if parsed.import_index == -1: return _output_as_string(parsed.lines_without_imports, parsed.line_separator) formatted_output: List[str] = parsed.lines_without_imports.copy() remove_imports = [ format_simplified(removal) for removal in config.remove_imports ] sections: Iterable[str] = itertools.chain(parsed.sections, config.forced_separate) if config.no_sections: parsed.imports["no_sections"] = {"straight": {}, "from": {}} base_sections: Tuple[str, ...] = () for section in sections: if section == "FUTURE": base_sections = ("FUTURE", ) continue parsed.imports["no_sections"]["straight"].update( parsed.imports[section].get("straight", {})) parsed.imports["no_sections"]["from"].update( parsed.imports[section].get("from", {})) sections = base_sections + ("no_sections", ) output: List[str] = [] seen_headings: Set[str] = set() pending_lines_before = False for section in sections: straight_modules = parsed.imports[section]["straight"] if not config.only_sections: straight_modules = sorting.naturally( straight_modules, key=lambda key: sorting.module_key( key, config, section_name=section, straight_import=True), ) from_modules = parsed.imports[section]["from"] if not config.only_sections: from_modules = sorting.naturally( from_modules, key=lambda key: sorting.module_key( key, config, section_name=section)) straight_imports = _with_straight_imports(parsed, config, straight_modules, section, remove_imports, import_type) from_imports = _with_from_imports(parsed, config, from_modules, section, remove_imports, import_type) lines_between = [""] * (config.lines_between_types if from_modules and straight_modules else 0) if config.from_first: section_output = from_imports + lines_between + straight_imports else: section_output = straight_imports + lines_between + from_imports if config.force_sort_within_sections: # collapse comments comments_above = [] new_section_output: List[str] = [] for line in section_output: if not line: continue if line.startswith("#"): comments_above.append(line) elif comments_above: new_section_output.append( _LineWithComments(line, comments_above)) comments_above = [] else: new_section_output.append(line) # only_sections options is not imposed if force_sort_within_sections is True new_section_output = sorting.naturally( new_section_output, key=partial( sorting.section_key, order_by_type=config.order_by_type, force_to_top=config.force_to_top, lexicographical=config.lexicographical, length_sort=config.length_sort, reverse_relative=config.reverse_relative, group_by_package=config.group_by_package, ), ) # uncollapse comments section_output = [] for line in new_section_output: comments = getattr(line, "comments", ()) if comments: section_output.extend(comments) section_output.append(str(line)) section_name = section no_lines_before = section_name in config.no_lines_before if section_output: if section_name in parsed.place_imports: parsed.place_imports[section_name] = section_output continue section_title = config.import_headings.get(section_name.lower(), "") if section_title and section_title not in seen_headings: if config.dedup_headings: seen_headings.add(section_title) section_comment = f"# {section_title}" if section_comment not in parsed.lines_without_imports[0:1]: section_output.insert(0, section_comment) if pending_lines_before or not no_lines_before: output += [""] * config.lines_between_sections output += section_output pending_lines_before = False else: pending_lines_before = pending_lines_before or not no_lines_before if config.ensure_newline_before_comments: output = _ensure_newline_before_comment(output) while output and output[-1].strip() == "": output.pop() # pragma: no cover while output and output[0].strip() == "": output.pop(0) if config.formatting_function: output = config.formatting_function(parsed.line_separator.join(output), extension, config).splitlines() output_at = 0 if parsed.import_index < parsed.original_line_count: output_at = parsed.import_index formatted_output[output_at:0] = output imports_tail = output_at + len(output) while [ character.strip() for character in formatted_output[imports_tail:imports_tail + 1] ] == [""]: formatted_output.pop(imports_tail) if len(formatted_output) > imports_tail: next_construct = "" tail = formatted_output[imports_tail:] for index, line in enumerate(tail): should_skip, in_quote, *_ = parse.skip_line( line, in_quote="", index=len(formatted_output), section_comments=config.section_comments, needs_import=False, ) if not should_skip and line.strip(): if (line.strip().startswith("#") and len(tail) > (index + 1) and tail[index + 1].strip()): continue next_construct = line break elif in_quote: next_construct = line break if config.lines_after_imports != -1: formatted_output[imports_tail:0] = [ "" for line in range(config.lines_after_imports) ] elif extension != "pyi" and next_construct.startswith( STATEMENT_DECLARATIONS): formatted_output[imports_tail:0] = ["", ""] else: formatted_output[imports_tail:0] = [""] if parsed.place_imports: new_out_lines = [] for index, line in enumerate(formatted_output): new_out_lines.append(line) if line in parsed.import_placements: new_out_lines.extend( parsed.place_imports[parsed.import_placements[line]]) if (len(formatted_output) <= (index + 1) or formatted_output[index + 1].strip() != ""): new_out_lines.append("") formatted_output = new_out_lines return _output_as_string(formatted_output, parsed.line_separator)
def sorted_imports( parsed: parse.ParsedContent, config: Config = DEFAULT_CONFIG, extension: str = "py", import_type: str = "import", ) -> str: """Adds the imports back to the file. (at the index of the first import) sorted alphabetically and split between groups """ if parsed.import_index == -1: return _output_as_string(parsed.lines_without_imports, parsed.line_separator) formatted_output: List[str] = parsed.lines_without_imports.copy() remove_imports = [ format_simplified(removal) for removal in config.remove_imports ] sort_ignore_case = config.force_alphabetical_sort_within_sections sections: Iterable[str] = itertools.chain(parsed.sections, config.forced_separate) if config.no_sections: parsed.imports["no_sections"] = {"straight": {}, "from": {}} base_sections: Tuple[str, ...] = () for section in sections: if section == "FUTURE": base_sections = ("FUTURE", ) continue parsed.imports["no_sections"]["straight"].update( parsed.imports[section].get("straight", {})) parsed.imports["no_sections"]["from"].update( parsed.imports[section].get("from", {})) sections = base_sections + ("no_sections", ) output: List[str] = [] pending_lines_before = False for section in sections: straight_modules = parsed.imports[section]["straight"] straight_modules = sorting.naturally( straight_modules, key=lambda key: sorting.module_key( key, config, section_name=section)) from_modules = parsed.imports[section]["from"] from_modules = sorting.naturally( from_modules, key=lambda key: sorting.module_key( key, config, section_name=section)) if config.force_sort_within_sections: copied_comments = copy.deepcopy(parsed.categorized_comments) section_output: List[str] = [] if config.from_first: section_output = _with_from_imports( parsed, config, from_modules, section, section_output, sort_ignore_case, remove_imports, import_type, ) if config.lines_between_types and from_modules and straight_modules: section_output.extend([""] * config.lines_between_types) section_output = _with_straight_imports( parsed, config, straight_modules, section, section_output, remove_imports, import_type, ) else: section_output = _with_straight_imports( parsed, config, straight_modules, section, section_output, remove_imports, import_type, ) if config.lines_between_types and from_modules and straight_modules: section_output.extend([""] * config.lines_between_types) section_output = _with_from_imports( parsed, config, from_modules, section, section_output, sort_ignore_case, remove_imports, import_type, ) if config.force_sort_within_sections: # Remove comments section_output = [ line for line in section_output if not line.startswith("#") ] section_output = sorting.naturally( section_output, key=partial( sorting.section_key, order_by_type=config.order_by_type, force_to_top=config.force_to_top, lexicographical=config.lexicographical, length_sort=config.length_sort, ), ) # Add comments back all_comments = copied_comments["above"]["from"] all_comments.update(copied_comments["above"]["straight"]) comment_indexes = {} for module, comment_list in all_comments.items(): for idx, line in enumerate(section_output): if module in line: comment_indexes[idx] = comment_list added = 0 for idx, comment_list in comment_indexes.items(): for comment in comment_list: section_output.insert(idx + added, comment) added += 1 section_name = section no_lines_before = section_name in config.no_lines_before if section_output: if section_name in parsed.place_imports: parsed.place_imports[section_name] = section_output continue section_title = config.import_headings.get(section_name.lower(), "") if section_title: section_comment = f"# {section_title}" if section_comment not in parsed.lines_without_imports[0:1]: section_output.insert(0, section_comment) if pending_lines_before or not no_lines_before: output += [""] * config.lines_between_sections output += section_output pending_lines_before = False else: pending_lines_before = pending_lines_before or not no_lines_before while output and output[-1].strip() == "": output.pop() # pragma: no cover while output and output[0].strip() == "": output.pop(0) output_at = 0 if parsed.import_index < parsed.original_line_count: output_at = parsed.import_index formatted_output[output_at:0] = output imports_tail = output_at + len(output) while [ character.strip() for character in formatted_output[imports_tail:imports_tail + 1] ] == [""]: formatted_output.pop(imports_tail) if len(formatted_output) > imports_tail: next_construct = "" _in_quote: str = "" tail = formatted_output[imports_tail:] for index, line in enumerate(tail): in_quote = _in_quote should_skip, _in_quote, *_ = parse.skip_line( line, in_quote=_in_quote, index=len(formatted_output), section_comments=parsed.section_comments, ) if not should_skip and line.strip(): if (line.strip().startswith("#") and len(tail) > (index + 1) and tail[index + 1].strip()): continue next_construct = line break elif not in_quote: parts = line.split() if (len(parts) >= 3 and parts[1] == "=" and "'" not in parts[0] and '"' not in parts[0]): next_construct = line break if config.lines_after_imports != -1: formatted_output[imports_tail:0] = [ "" for line in range(config.lines_after_imports) ] elif extension != "pyi" and next_construct.startswith( STATEMENT_DECLERATIONS): formatted_output[imports_tail:0] = ["", ""] else: formatted_output[imports_tail:0] = [""] if parsed.place_imports: new_out_lines = [] for index, line in enumerate(formatted_output): new_out_lines.append(line) if line in parsed.import_placements: new_out_lines.extend( parsed.place_imports[parsed.import_placements[line]]) if len(formatted_output) <= index or formatted_output[ index + 1].strip() != "": new_out_lines.append("") formatted_output = new_out_lines return _output_as_string(formatted_output, parsed.line_separator)