"""Tool for sorting imports alphabetically, and automatically separated into sections.""" import argparse import functools import json import os import sys from gettext import gettext as _ from io import TextIOWrapper from pathlib import Path from typing import Any, Dict, List, Optional, Sequence, Union from warnings import warn from . import __version__, api, files, sections from .exceptions import FileSkipped, ISortError, UnsupportedEncoding from .format import create_terminal_printer from .logo import ASCII_ART from .profiles import profiles from .settings import VALID_PY_TARGETS, Config, find_all_configs from .utils import Trie from .wrap_modes import WrapModes DEPRECATED_SINGLE_DASH_ARGS = { "-ac", "-af", "-ca", "-cs", "-df", "-ds", "-dt", "-fas", "-fass", "-ff", "-fgw", "-fss", "-lai", "-lbt", "-le", "-ls", "-nis", "-nlb", "-ot", "-rr", "-sd", "-sg", "-sl", "-sp", "-tc", "-wl", "-ws", } QUICK_GUIDE = f""" {ASCII_ART} Nothing to do: no files or paths have have been passed in! Try one of the following: `isort .` - sort all Python files, starting from the current directory, recursively. `isort . --interactive` - Do the same, but ask before making any changes. `isort . --check --diff` - Check to see if imports are correctly sorted within this project. `isort --help` - In-depth information about isort's available command-line options. Visit https://pycqa.github.io/isort/ for complete information about how to use isort. """ class SortAttempt: def __init__(self, incorrectly_sorted: bool, skipped: bool, supported_encoding: bool) -> None: self.incorrectly_sorted = incorrectly_sorted self.skipped = skipped self.supported_encoding = supported_encoding def sort_imports( file_name: str, config: Config, check: bool = False, ask_to_apply: bool = False, write_to_stdout: bool = False, **kwargs: Any, ) -> Optional[SortAttempt]: incorrectly_sorted: bool = False skipped: bool = False try: if check: try: incorrectly_sorted = not api.check_file(file_name, config=config, **kwargs) except FileSkipped: skipped = True return SortAttempt(incorrectly_sorted, skipped, True) try: incorrectly_sorted = not api.sort_file( file_name, config=config, ask_to_apply=ask_to_apply, write_to_stdout=write_to_stdout, **kwargs, ) except FileSkipped: skipped = True return SortAttempt(incorrectly_sorted, skipped, True) except (OSError, ValueError) as error: warn(f"Unable to parse file {file_name} due to {error}") return None except UnsupportedEncoding: if config.verbose: warn(f"Encoding not supported for {file_name}") return SortAttempt(incorrectly_sorted, skipped, False) except ISortError as error: _print_hard_fail(config, message=str(error)) sys.exit(1) except Exception: _print_hard_fail(config, offending_file=file_name) raise def _print_hard_fail( config: Config, offending_file: Optional[str] = None, message: Optional[str] = None ) -> None: """Fail on unrecoverable exception with custom message.""" message = message or ( f"Unrecoverable exception thrown when parsing {offending_file or ''}!" "This should NEVER happen.\n" "If encountered, please open an issue: https://github.com/PyCQA/isort/issues/new" ) printer = create_terminal_printer( color=config.color_output, error=config.format_error, success=config.format_success ) printer.error(message) def _build_arg_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( description="Sort Python import definitions alphabetically " "within logical sections. Run with no arguments to see a quick " "start guide, otherwise, one or more files/directories/stdin must be provided. " "Use `-` as the first argument to represent stdin. Use --interactive to use the pre 5.0.0 " "interactive behavior." " " "If you've used isort 4 but are new to isort 5, see the upgrading guide: " "https://pycqa.github.io/isort/docs/upgrade_guides/5.0.0.html", add_help=False, # prevent help option from appearing in "optional arguments" group ) general_group = parser.add_argument_group("general options") target_group = parser.add_argument_group("target options") output_group = parser.add_argument_group("general output options") inline_args_group = output_group.add_mutually_exclusive_group() section_group = parser.add_argument_group("section output options") deprecated_group = parser.add_argument_group("deprecated options") general_group.add_argument( "-h", "--help", action="help", default=argparse.SUPPRESS, help=_("show this help message and exit"), ) general_group.add_argument( "-V", "--version", action="store_true", dest="show_version", help="Displays the currently installed version of isort.", ) general_group.add_argument( "--vn", "--version-number", action="version", version=__version__, help="Returns just the current version number without the logo", ) general_group.add_argument( "-v", "--verbose", action="store_true", dest="verbose", help="Shows verbose output, such as when files are skipped or when a check is successful.", ) general_group.add_argument( "--only-modified", "--om", dest="only_modified", action="store_true", help="Suppresses verbose output for non-modified files.", ) general_group.add_argument( "--dedup-headings", dest="dedup_headings", action="store_true", help="Tells isort to only show an identical custom import heading comment once, even if" " there are multiple sections with the comment set.", ) general_group.add_argument( "-q", "--quiet", action="store_true", dest="quiet", help="Shows extra quiet output, only errors are outputted.", ) general_group.add_argument( "-d", "--stdout", help="Force resulting output to stdout, instead of in-place.", dest="write_to_stdout", action="store_true", ) general_group.add_argument( "--overwrite-in-place", help="Tells isort to overwrite in place using the same file handle. " "Comes at a performance and memory usage penalty over its standard " "approach but ensures all file flags and modes stay unchanged.", dest="overwrite_in_place", action="store_true", ) general_group.add_argument( "--show-config", dest="show_config", action="store_true", help="See isort's determined config, as well as sources of config options.", ) general_group.add_argument( "--show-files", dest="show_files", action="store_true", help="See the files isort will be run against with the current config options.", ) general_group.add_argument( "--df", "--diff", dest="show_diff", action="store_true", help="Prints a diff of all the changes isort would make to a file, instead of " "changing it in place", ) general_group.add_argument( "-c", "--check-only", "--check", action="store_true", dest="check", help="Checks the file for unsorted / unformatted imports and prints them to the " "command line without modifying the file. Returns 0 when nothing would change and " "returns 1 when the file would be reformatted.", ) general_group.add_argument( "--ws", "--ignore-whitespace", action="store_true", dest="ignore_whitespace", help="Tells isort to ignore whitespace differences when --check-only is being used.", ) general_group.add_argument( "--sp", "--settings-path", "--settings-file", "--settings", dest="settings_path", help="Explicitly set the settings path or file instead of auto determining " "based on file location.", ) general_group.add_argument( "--cr", "--config-root", dest="config_root", help="Explicitly set the config root for resolving all configs. When used " "with the --resolve-all-configs flag, isort will look at all sub-folders " "in this config root to resolve config files and sort files based on the " "closest available config(if any)", ) general_group.add_argument( "--resolve-all-configs", dest="resolve_all_configs", action="store_true", help="Tells isort to resolve the configs for all sub-directories " "and sort files in terms of its closest config files.", ) general_group.add_argument( "--profile", dest="profile", type=str, help="Base profile type to use for configuration. " f"Profiles include: {', '.join(profiles.keys())}. As well as any shared profiles.", ) general_group.add_argument( "--old-finders", "--magic-placement", dest="old_finders", action="store_true", help="Use the old deprecated finder logic that relies on environment introspection magic.", ) general_group.add_argument( "-j", "--jobs", help="Number of files to process in parallel.", dest="jobs", type=int, nargs="?", const=-1, ) general_group.add_argument( "--ac", "--atomic", dest="atomic", action="store_true", help="Ensures the output doesn't save if the resulting file contains syntax errors.", ) general_group.add_argument( "--interactive", dest="ask_to_apply", action="store_true", help="Tells isort to apply changes interactively.", ) general_group.add_argument( "--format-error", dest="format_error", help="Override the format used to print errors.", ) general_group.add_argument( "--format-success", dest="format_success", help="Override the format used to print success.", ) target_group.add_argument( "files", nargs="*", help="One or more Python source files that need their imports sorted." ) target_group.add_argument( "--filter-files", dest="filter_files", action="store_true", help="Tells isort to filter files even when they are explicitly passed in as " "part of the CLI command.", ) target_group.add_argument( "-s", "--skip", help="Files that isort should skip over. If you want to skip multiple " "files you should specify twice: --skip file1 --skip file2. Values can be " "file names, directory names or file paths. To skip all files in a nested path " "use --skip-glob.", dest="skip", action="append", ) target_group.add_argument( "--extend-skip", help="Extends --skip to add additional files that isort should skip over. " "If you want to skip multiple " "files you should specify twice: --skip file1 --skip file2. Values can be " "file names, directory names or file paths. To skip all files in a nested path " "use --skip-glob.", dest="extend_skip", action="append", ) target_group.add_argument( "--sg", "--skip-glob", help="Files that isort should skip over.", dest="skip_glob", action="append", ) target_group.add_argument( "--extend-skip-glob", help="Additional files that isort should skip over (extending --skip-glob).", dest="extend_skip_glob", action="append", ) target_group.add_argument( "--gitignore", "--skip-gitignore", action="store_true", dest="skip_gitignore", help="Treat project as a git repository and ignore files listed in .gitignore." "\nNOTE: This requires git to be installed and accessible from the same shell as isort.", ) target_group.add_argument( "--ext", "--extension", "--supported-extension", dest="supported_extensions", action="append", help="Specifies what extensions isort can be run against.", ) target_group.add_argument( "--blocked-extension", dest="blocked_extensions", action="append", help="Specifies what extensions isort can never be run against.", ) target_group.add_argument( "--dont-follow-links", dest="dont_follow_links", action="store_true", help="Tells isort not to follow symlinks that are encountered when running recursively.", ) target_group.add_argument( "--filename", dest="filename", help="Provide the filename associated with a stream.", ) target_group.add_argument( "--allow-root", action="store_true", default=False, help="Tells isort not to treat / specially, allowing it to be run against the root dir.", ) output_group.add_argument( "-a", "--add-import", dest="add_imports", action="append", help="Adds the specified import line to all files, " "automatically determining correct placement.", ) output_group.add_argument( "--append", "--append-only", dest="append_only", action="store_true", help="Only adds the imports specified in --add-import if the file" " contains existing imports.", ) output_group.add_argument( "--af", "--force-adds", dest="force_adds", action="store_true", help="Forces import adds even if the original file is empty.", ) output_group.add_argument( "--rm", "--remove-import", dest="remove_imports", action="append", help="Removes the specified import from all files.", ) output_group.add_argument( "--float-to-top", dest="float_to_top", action="store_true", help="Causes all non-indented imports to float to the top of the file having its imports " "sorted (immediately below the top of file comment).\n" "This can be an excellent shortcut for collecting imports every once in a while " "when you place them in the middle of a file to avoid context switching.\n\n" "*NOTE*: It currently doesn't work with cimports and introduces some extra over-head " "and a performance penalty.", ) output_group.add_argument( "--dont-float-to-top", dest="dont_float_to_top", action="store_true", help="Forces --float-to-top setting off. See --float-to-top for more information.", ) output_group.add_argument( "--ca", "--combine-as", dest="combine_as_imports", action="store_true", help="Combines as imports on the same line.", ) output_group.add_argument( "--cs", "--combine-star", dest="combine_star", action="store_true", help="Ensures that if a star import is present, " "nothing else is imported from that namespace.", ) output_group.add_argument( "-e", "--balanced", dest="balanced_wrapping", action="store_true", help="Balances wrapping to produce the most consistent line length possible", ) output_group.add_argument( "--ff", "--from-first", dest="from_first", action="store_true", help="Switches the typical ordering preference, " "showing from imports first then straight ones.", ) output_group.add_argument( "--fgw", "--force-grid-wrap", nargs="?", const=2, type=int, dest="force_grid_wrap", help="Force number of from imports (defaults to 2 when passed as CLI flag without value) " "to be grid wrapped regardless of line " "length. If 0 is passed in (the global default) only line length is considered.", ) output_group.add_argument( "-i", "--indent", help='String to place for indents defaults to " " (4 spaces).', dest="indent", type=str, ) output_group.add_argument( "--lbi", "--lines-before-imports", dest="lines_before_imports", type=int ) output_group.add_argument( "--lai", "--lines-after-imports", dest="lines_after_imports", type=int ) output_group.add_argument( "--lbt", "--lines-between-types", dest="lines_between_types", type=int ) output_group.add_argument( "--le", "--line-ending", dest="line_ending", help="Forces line endings to the specified value. " "If not set, values will be guessed per-file.", ) output_group.add_argument( "--ls", "--length-sort", help="Sort imports by their string length.", dest="length_sort", action="store_true", ) output_group.add_argument( "--lss", "--length-sort-straight", help="Sort straight imports by their string length. Similar to `length_sort` " "but applies only to straight imports and doesn't affect from imports.", dest="length_sort_straight", action="store_true", ) output_group.add_argument( "-m", "--multi-line", dest="multi_line_output", choices=list(WrapModes.__members__.keys()) + [str(mode.value) for mode in WrapModes.__members__.values()], type=str, help="Multi line output (0-grid, 1-vertical, 2-hanging, 3-vert-hanging, 4-vert-grid, " "5-vert-grid-grouped, 6-deprecated-alias-for-5, 7-noqa, " "8-vertical-hanging-indent-bracket, 9-vertical-prefix-from-module-import, " "10-hanging-indent-with-parentheses).", ) output_group.add_argument( "-n", "--ensure-newline-before-comments", dest="ensure_newline_before_comments", action="store_true", help="Inserts a blank line before a comment following an import.", ) inline_args_group.add_argument( "--nis", "--no-inline-sort", dest="no_inline_sort", action="store_true", help="Leaves `from` imports with multiple imports 'as-is' " "(e.g. `from foo import a, c ,b`).", ) output_group.add_argument( "--ot", "--order-by-type", dest="order_by_type", action="store_true", help="Order imports by type, which is determined by case, in addition to alphabetically.\n" "\n**NOTE**: type here refers to the implied type from the import name capitalization.\n" ' isort does not do type introspection for the imports. These "types" are simply: ' "CONSTANT_VARIABLE, CamelCaseClass, variable_or_function. If your project follows PEP8" " or a related coding standard and has many imports this is a good default, otherwise you " "likely will want to turn it off. From the CLI the `--dont-order-by-type` option will turn " "this off.", ) output_group.add_argument( "--dt", "--dont-order-by-type", dest="dont_order_by_type", action="store_true", help="Don't order imports by type, which is determined by case, in addition to " "alphabetically.\n\n" "**NOTE**: type here refers to the implied type from the import name capitalization.\n" ' isort does not do type introspection for the imports. These "types" are simply: ' "CONSTANT_VARIABLE, CamelCaseClass, variable_or_function. If your project follows PEP8" " or a related coding standard and has many imports this is a good default. You can turn " "this on from the CLI using `--order-by-type`.", ) output_group.add_argument( "--rr", "--reverse-relative", dest="reverse_relative", action="store_true", help="Reverse order of relative imports.", ) output_group.add_argument( "--reverse-sort", dest="reverse_sort", action="store_true", help="Reverses the ordering of imports.", ) output_group.add_argument( "--sort-order", dest="sort_order", help="Specify sorting function. Can be built in (natural[default] = force numbers " "to be sequential, native = Python's built-in sorted function) or an installable plugin.", ) inline_args_group.add_argument( "--sl", "--force-single-line-imports", dest="force_single_line", action="store_true", help="Forces all from imports to appear on their own line", ) output_group.add_argument( "--nsl", "--single-line-exclusions", help="One or more modules to exclude from the single line rule.", dest="single_line_exclusions", action="append", ) output_group.add_argument( "--tc", "--trailing-comma", dest="include_trailing_comma", action="store_true", help="Includes a trailing comma on multi line imports that include parentheses.", ) output_group.add_argument( "--up", "--use-parentheses", dest="use_parentheses", action="store_true", help="Use parentheses for line continuation on length limit instead of slashes." " **NOTE**: This is separate from wrap modes, and only affects how individual lines that " " are too long get continued, not sections of multiple imports.", ) output_group.add_argument( "-l", "-w", "--line-length", "--line-width", help="The max length of an import line (used for wrapping long imports).", dest="line_length", type=int, ) output_group.add_argument( "--wl", "--wrap-length", dest="wrap_length", type=int, help="Specifies how long lines that are wrapped should be, if not set line_length is used." "\nNOTE: wrap_length must be LOWER than or equal to line_length.", ) output_group.add_argument( "--case-sensitive", dest="case_sensitive", action="store_true", help="Tells isort to include casing when sorting module names", ) output_group.add_argument( "--remove-redundant-aliases", dest="remove_redundant_aliases", action="store_true", help=( "Tells isort to remove redundant aliases from imports, such as `import os as os`." " This defaults to `False` simply because some projects use these seemingly useless " " aliases to signify intent and change behaviour." ), ) output_group.add_argument( "--honor-noqa", dest="honor_noqa", action="store_true", help="Tells isort to honor noqa comments to enforce skipping those comments.", ) output_group.add_argument( "--treat-comment-as-code", dest="treat_comments_as_code", action="append", help="Tells isort to treat the specified single line comment(s) as if they are code.", ) output_group.add_argument( "--treat-all-comment-as-code", dest="treat_all_comments_as_code", action="store_true", help="Tells isort to treat all single line comments as if they are code.", ) output_group.add_argument( "--formatter", dest="formatter", type=str, help="Specifies the name of a formatting plugin to use when producing output.", ) output_group.add_argument( "--color", dest="color_output", action="store_true", help="Tells isort to use color in terminal output.", ) output_group.add_argument( "--ext-format", dest="ext_format", help="Tells isort to format the given files according to an extensions formatting rules.", ) output_group.add_argument( "--star-first", help="Forces star imports above others to avoid overriding directly imported variables.", dest="star_first", action="store_true", ) section_group.add_argument( "--sd", "--section-default", dest="default_section", help="Sets the default section for import options: " + str(sections.DEFAULT), ) section_group.add_argument( "--only-sections", "--os", dest="only_sections", action="store_true", help="Causes imports to be sorted based on their sections like STDLIB, THIRDPARTY, etc. " "Within sections, the imports are ordered by their import style and the imports with " "the same style maintain their relative positions.", ) section_group.add_argument( "--ds", "--no-sections", help="Put all imports into the same section bucket", dest="no_sections", action="store_true", ) section_group.add_argument( "--fas", "--force-alphabetical-sort", action="store_true", dest="force_alphabetical_sort", help="Force all imports to be sorted as a single section", ) section_group.add_argument( "--fss", "--force-sort-within-sections", action="store_true", dest="force_sort_within_sections", help="Don't sort straight-style imports (like import sys) before from-style imports " "(like from itertools import groupby). Instead, sort the imports by module, " "independent of import style.", ) section_group.add_argument( "--hcss", "--honor-case-in-force-sorted-sections", action="store_true", dest="honor_case_in_force_sorted_sections", help="Honor `--case-sensitive` when `--force-sort-within-sections` is being used. " "Without this option set, `--order-by-type` decides module name ordering too.", ) section_group.add_argument( "--srss", "--sort-relative-in-force-sorted-sections", action="store_true", dest="sort_relative_in_force_sorted_sections", help="When using `--force-sort-within-sections`, sort relative imports the same " "way as they are sorted when not using that setting.", ) section_group.add_argument( "--fass", "--force-alphabetical-sort-within-sections", action="store_true", dest="force_alphabetical_sort_within_sections", help="Force all imports to be sorted alphabetically within a section", ) section_group.add_argument( "-t", "--top", help="Force specific imports to the top of their appropriate section.", dest="force_to_top", action="append", ) section_group.add_argument( "--combine-straight-imports", "--csi", dest="combine_straight_imports", action="store_true", help="Combines all the bare straight imports of the same section in a single line. " "Won't work with sections which have 'as' imports", ) section_group.add_argument( "--nlb", "--no-lines-before", help="Sections which should not be split with previous by empty lines", dest="no_lines_before", action="append", ) section_group.add_argument( "--src", "--src-path", dest="src_paths", action="append", help="Add an explicitly defined source path " "(modules within src paths have their imports automatically categorized as first_party)." " Glob expansion (`*` and `**`) is supported for this option.", ) section_group.add_argument( "-b", "--builtin", dest="known_standard_library", action="append", help="Force isort to recognize a module as part of Python's standard library.", ) section_group.add_argument( "--extra-builtin", dest="extra_standard_library", action="append", help="Extra modules to be included in the list of ones in Python's standard library.", ) section_group.add_argument( "-f", "--future", dest="known_future_library", action="append", help="Force isort to recognize a module as part of Python's internal future compatibility " "libraries. WARNING: this overrides the behavior of __future__ handling and therefore" " can result in code that can't execute. If you're looking to add dependencies such " "as six, a better option is to create another section below --future using custom " "sections. See: https://github.com/PyCQA/isort#custom-sections-and-ordering and the " "discussion here: https://github.com/PyCQA/isort/issues/1463.", ) section_group.add_argument( "-o", "--thirdparty", dest="known_third_party", action="append", help="Force isort to recognize a module as being part of a third party library.", ) section_group.add_argument( "-p", "--project", dest="known_first_party", action="append", help="Force isort to recognize a module as being part of the current python project.", ) section_group.add_argument( "--known-local-folder", dest="known_local_folder", action="append", help="Force isort to recognize a module as being a local folder. " "Generally, this is reserved for relative imports (from . import module).", ) section_group.add_argument( "--virtual-env", dest="virtual_env", help="Virtual environment to use for determining whether a package is third-party", ) section_group.add_argument( "--conda-env", dest="conda_env", help="Conda environment to use for determining whether a package is third-party", ) section_group.add_argument( "--py", "--python-version", action="store", dest="py_version", choices=tuple(VALID_PY_TARGETS) + ("auto",), help="Tells isort to set the known standard library based on the specified Python " "version. Default is to assume any Python 3 version could be the target, and use a union " "of all stdlib modules across versions. If auto is specified, the version of the " "interpreter used to run isort " f"(currently: {sys.version_info.major}{sys.version_info.minor}) will be used.", ) # deprecated options deprecated_group.add_argument( "--recursive", dest="deprecated_flags", action="append_const", const="--recursive", help=argparse.SUPPRESS, ) deprecated_group.add_argument( "-rc", dest="deprecated_flags", action="append_const", const="-rc", help=argparse.SUPPRESS ) deprecated_group.add_argument( "--dont-skip", dest="deprecated_flags", action="append_const", const="--dont-skip", help=argparse.SUPPRESS, ) deprecated_group.add_argument( "-ns", dest="deprecated_flags", action="append_const", const="-ns", help=argparse.SUPPRESS ) deprecated_group.add_argument( "--apply", dest="deprecated_flags", action="append_const", const="--apply", help=argparse.SUPPRESS, ) deprecated_group.add_argument( "-k", "--keep-direct-and-as", dest="deprecated_flags", action="append_const", const="--keep-direct-and-as", help=argparse.SUPPRESS, ) return parser def parse_args(argv: Optional[Sequence[str]] = None) -> Dict[str, Any]: argv = sys.argv[1:] if argv is None else list(argv) remapped_deprecated_args = [] for index, arg in enumerate(argv): if arg in DEPRECATED_SINGLE_DASH_ARGS: remapped_deprecated_args.append(arg) argv[index] = f"-{arg}" parser = _build_arg_parser() arguments = {key: value for key, value in vars(parser.parse_args(argv)).items() if value} if remapped_deprecated_args: arguments["remapped_deprecated_args"] = remapped_deprecated_args if "dont_order_by_type" in arguments: arguments["order_by_type"] = False del arguments["dont_order_by_type"] if "dont_follow_links" in arguments: arguments["follow_links"] = False del arguments["dont_follow_links"] if "dont_float_to_top" in arguments: del arguments["dont_float_to_top"] if arguments.get("float_to_top", False): sys.exit("Can't set both --float-to-top and --dont-float-to-top.") else: arguments["float_to_top"] = False multi_line_output = arguments.get("multi_line_output", None) if multi_line_output: if multi_line_output.isdigit(): arguments["multi_line_output"] = WrapModes(int(multi_line_output)) else: arguments["multi_line_output"] = WrapModes[multi_line_output] return arguments def _preconvert(item: Any) -> Union[str, List[Any]]: """Preconverts objects from native types into JSONifyiable types""" if isinstance(item, (set, frozenset)): return list(item) if isinstance(item, WrapModes): return str(item.name) if isinstance(item, Path): return str(item) if callable(item) and hasattr(item, "__name__"): return str(item.__name__) raise TypeError("Unserializable object {} of type {}".format(item, type(item))) def identify_imports_main( argv: Optional[Sequence[str]] = None, stdin: Optional[TextIOWrapper] = None ) -> None: parser = argparse.ArgumentParser( description="Get all import definitions from a given file." "Use `-` as the first argument to represent stdin." ) parser.add_argument( "files", nargs="+", help="One or more Python source files that need their imports sorted." ) parser.add_argument( "--top-only", action="store_true", default=False, help="Only identify imports that occur in before functions or classes.", ) target_group = parser.add_argument_group("target options") target_group.add_argument( "--follow-links", action="store_true", default=False, help="Tells isort to follow symlinks that are encountered when running recursively.", ) uniqueness = parser.add_mutually_exclusive_group() uniqueness.add_argument( "--unique", action="store_true", default=False, help="If true, isort will only identify unique imports.", ) uniqueness.add_argument( "--packages", dest="unique", action="store_const", const=api.ImportKey.PACKAGE, default=False, help="If true, isort will only identify the unique top level modules imported.", ) uniqueness.add_argument( "--modules", dest="unique", action="store_const", const=api.ImportKey.MODULE, default=False, help="If true, isort will only identify the unique modules imported.", ) uniqueness.add_argument( "--attributes", dest="unique", action="store_const", const=api.ImportKey.ATTRIBUTE, default=False, help="If true, isort will only identify the unique attributes imported.", ) arguments = parser.parse_args(argv) file_names = arguments.files if file_names == ["-"]: identified_imports = api.find_imports_in_stream( sys.stdin if stdin is None else stdin, unique=arguments.unique, top_only=arguments.top_only, follow_links=arguments.follow_links, ) else: identified_imports = api.find_imports_in_paths( file_names, unique=arguments.unique, top_only=arguments.top_only, follow_links=arguments.follow_links, ) for identified_import in identified_imports: if arguments.unique == api.ImportKey.PACKAGE: print(identified_import.module.split(".")[0]) elif arguments.unique == api.ImportKey.MODULE: print(identified_import.module) elif arguments.unique == api.ImportKey.ATTRIBUTE: print(f"{identified_import.module}.{identified_import.attribute}") else: print(str(identified_import)) def main(argv: Optional[Sequence[str]] = None, stdin: Optional[TextIOWrapper] = None) -> None: arguments = parse_args(argv) if arguments.get("show_version"): print(ASCII_ART) return show_config: bool = arguments.pop("show_config", False) show_files: bool = arguments.pop("show_files", False) if show_config and show_files: sys.exit("Error: either specify show-config or show-files not both.") if "settings_path" in arguments: if os.path.isfile(arguments["settings_path"]): arguments["settings_file"] = os.path.abspath(arguments["settings_path"]) arguments["settings_path"] = os.path.dirname(arguments["settings_file"]) else: arguments["settings_path"] = os.path.abspath(arguments["settings_path"]) if "virtual_env" in arguments: venv = arguments["virtual_env"] arguments["virtual_env"] = os.path.abspath(venv) if not os.path.isdir(arguments["virtual_env"]): warn(f"virtual_env dir does not exist: {arguments['virtual_env']}") file_names = arguments.pop("files", []) if not file_names and not show_config: print(QUICK_GUIDE) if arguments: sys.exit("Error: arguments passed in without any paths or content.") return if "settings_path" not in arguments: arguments["settings_path"] = ( os.path.abspath(file_names[0] if file_names else ".") or os.getcwd() ) if not os.path.isdir(arguments["settings_path"]): arguments["settings_path"] = os.path.dirname(arguments["settings_path"]) config_dict = arguments.copy() ask_to_apply = config_dict.pop("ask_to_apply", False) jobs = config_dict.pop("jobs", None) check = config_dict.pop("check", False) show_diff = config_dict.pop("show_diff", False) write_to_stdout = config_dict.pop("write_to_stdout", False) deprecated_flags = config_dict.pop("deprecated_flags", False) remapped_deprecated_args = config_dict.pop("remapped_deprecated_args", False) stream_filename = config_dict.pop("filename", None) ext_format = config_dict.pop("ext_format", None) allow_root = config_dict.pop("allow_root", None) resolve_all_configs = config_dict.pop("resolve_all_configs", False) wrong_sorted_files = False all_attempt_broken = False no_valid_encodings = False config_trie: Optional[Trie] = None if resolve_all_configs: config_trie = find_all_configs(config_dict.pop("config_root", ".")) if "src_paths" in config_dict: config_dict["src_paths"] = { Path(src_path).resolve() for src_path in config_dict.get("src_paths", ()) } config = Config(**config_dict) if show_config: print(json.dumps(config.__dict__, indent=4, separators=(",", ": "), default=_preconvert)) return if file_names == ["-"]: file_path = Path(stream_filename) if stream_filename else None if show_files: sys.exit("Error: can't show files for streaming input.") input_stream = sys.stdin if stdin is None else stdin if check: incorrectly_sorted = not api.check_stream( input_stream=input_stream, config=config, show_diff=show_diff, file_path=file_path, extension=ext_format, ) wrong_sorted_files = incorrectly_sorted else: try: api.sort_stream( input_stream=input_stream, output_stream=sys.stdout, config=config, show_diff=show_diff, file_path=file_path, extension=ext_format, raise_on_skip=False, ) except FileSkipped: sys.stdout.write(input_stream.read()) elif "/" in file_names and not allow_root: printer = create_terminal_printer( color=config.color_output, error=config.format_error, success=config.format_success ) printer.error("it is dangerous to operate recursively on '/'") printer.error("use --allow-root to override this failsafe") sys.exit(1) else: if stream_filename: printer = create_terminal_printer( color=config.color_output, error=config.format_error, success=config.format_success ) printer.error("Filename override is intended only for stream (-) sorting.") sys.exit(1) skipped: List[str] = [] broken: List[str] = [] if config.filter_files: filtered_files = [] for file_name in file_names: if config.is_skipped(Path(file_name)): skipped.append(file_name) else: filtered_files.append(file_name) file_names = filtered_files file_names = files.find(file_names, config, skipped, broken) if show_files: for file_name in file_names: print(file_name) return num_skipped = 0 num_broken = 0 num_invalid_encoding = 0 if config.verbose: print(ASCII_ART) if jobs: import multiprocessing executor = multiprocessing.Pool(jobs if jobs > 0 else multiprocessing.cpu_count()) attempt_iterator = executor.imap( functools.partial( sort_imports, config=config, check=check, ask_to_apply=ask_to_apply, write_to_stdout=write_to_stdout, extension=ext_format, config_trie=config_trie, ), file_names, ) else: # https://github.com/python/typeshed/pull/2814 attempt_iterator = ( sort_imports( # type: ignore file_name, config=config, check=check, ask_to_apply=ask_to_apply, show_diff=show_diff, write_to_stdout=write_to_stdout, extension=ext_format, config_trie=config_trie, ) for file_name in file_names ) # If any files passed in are missing considered as error, should be removed is_no_attempt = True any_encoding_valid = False for sort_attempt in attempt_iterator: if not sort_attempt: continue # pragma: no cover - shouldn't happen, satisfies type constraint incorrectly_sorted = sort_attempt.incorrectly_sorted if arguments.get("check", False) and incorrectly_sorted: wrong_sorted_files = True if sort_attempt.skipped: num_skipped += ( 1 # pragma: no cover - shouldn't happen, due to skip in iter_source_code ) if not sort_attempt.supported_encoding: num_invalid_encoding += 1 else: any_encoding_valid = True is_no_attempt = False num_skipped += len(skipped) if num_skipped and not config.quiet: if config.verbose: for was_skipped in skipped: print( f"{was_skipped} was skipped as it's listed in 'skip' setting, " "matches a glob in 'skip_glob' setting, or is in a .gitignore file with " "--skip-gitignore enabled." ) print(f"Skipped {num_skipped} files") num_broken += len(broken) if num_broken and not config.quiet: if config.verbose: for was_broken in broken: warn(f"{was_broken} was broken path, make sure it exists correctly") print(f"Broken {num_broken} paths") if num_broken > 0 and is_no_attempt: all_attempt_broken = True if num_invalid_encoding > 0 and not any_encoding_valid: no_valid_encodings = True if not config.quiet and (remapped_deprecated_args or deprecated_flags): if remapped_deprecated_args: warn( "W0502: The following deprecated single dash CLI flags were used and translated: " f"{', '.join(remapped_deprecated_args)}!" ) if deprecated_flags: warn( "W0501: The following deprecated CLI flags were used and ignored: " f"{', '.join(deprecated_flags)}!" ) warn( "W0500: Please see the 5.0.0 Upgrade guide: " "https://pycqa.github.io/isort/docs/upgrade_guides/5.0.0.html" ) if wrong_sorted_files: sys.exit(1) if all_attempt_broken: sys.exit(1) if no_valid_encodings: printer = create_terminal_printer( color=config.color_output, error=config.format_error, success=config.format_success ) printer.error("No valid encodings.") sys.exit(1) if __name__ == "__main__": main()