diff --git a/isort/deprecated/finders.py b/isort/deprecated/finders.py index eac650e2..69149acd 100644 --- a/isort/deprecated/finders.py +++ b/isort/deprecated/finders.py @@ -377,12 +377,12 @@ def __init__( ) self.finders: Tuple[BaseFinder, ...] = tuple(finders) - def find(self, module_name: str) -> Optional[str]: + def find(self, module_name: str) -> Tuple[Optional[str], str]: for finder in self.finders: try: section = finder.find(module_name) if section is not None: - return section + return section, "" except Exception as exception: # isort has to be able to keep trying to identify the correct # import section even if one approach fails @@ -391,4 +391,4 @@ def find(self, module_name: str) -> Optional[str]: f"{finder.__class__.__name__} encountered an error ({exception}) while " f"trying to identify the {module_name} module" ) - return None + return None, "" diff --git a/isort/parse.py b/isort/parse.py index 614afabd..69cd9d91 100644 --- a/isort/parse.py +++ b/isort/parse.py @@ -155,7 +155,7 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte finder = FindersManager(config=config).find else: - finder = partial(place.module, config=config) + finder = partial(place.module_with_reason, config=config) line_count = len(in_lines) @@ -438,7 +438,13 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte if type_of_import == "from": import_from = just_imports.pop(0) - placed_module = finder(import_from) + placed_module, reason = finder(import_from) + if placed_module == config.default_section and reason.startswith("Default"): + # The imported name might be a submodule of `import_from`, in which case + # it might be specified in the config. + # See https://github.com/PyCQA/isort/issues/2167. + placed_module, reason = finder(".".join([import_from] + just_imports)) + if config.verbose and not config.only_modified: print(f"from-type place_module for {import_from} returned {placed_module}") @@ -552,7 +558,7 @@ def file_contents(contents: str, config: Config = DEFAULT_CONFIG) -> ParsedConte import_index -= len( categorized_comments["above"]["straight"].get(module, []) ) - placed_module = finder(module) + placed_module, reason = finder(module) if config.verbose and not config.only_modified: print(f"else-type place_module for {module} returned {placed_module}") diff --git a/tests/unit/test_isort.py b/tests/unit/test_isort.py index 7b6743c7..8a00ec19 100644 --- a/tests/unit/test_isort.py +++ b/tests/unit/test_isort.py @@ -5671,3 +5671,14 @@ def test_reexport_not_last_line() -> None: meme = "rickroll" """ assert isort.code(test_input, config=Config(sort_reexports=True)) == expd_output + + +def test_import_submodule_both_ways() -> None: + """See https://github.com/PyCQA/isort/issues/2167.""" + test_input = "import requests\n" "\n" "from subdir import fileA\n" + test_output = isort.code(code=test_input, known_first_party=["subdir.fileA"]) + assert test_output == ("import requests\n" "\n" "from subdir import fileA\n") + + test_input = "import requests\n" "\n" "import subdir.fileA\n" + test_output = isort.code(code=test_input, known_first_party=["subdir.fileA"]) + assert test_output == ("import requests\n" "\n" "import subdir.fileA\n")