[isort] Check full module path against project root(s) when categorizing first-party (#16565)

When attempting to determine whether `import foo.bar.baz` is a known
first-party import relative to [user-provided source
paths](https://docs.astral.sh/ruff/settings/#src), when `preview` is
enabled we now check that `SRC/foo/bar/baz` is a directory or
`SRC/foo/bar/baz.py` or `SRC/foo/bar/baz.pyi` exist.

Previously, we just checked the analogous thing for `SRC/foo`, but this
can be misleading in situations with disjoint namespace packages that
share a common base name (e.g. we may be working inside the namespace
package `foo.buzz` and importing `foo.bar` from elsewhere).

Supersedes #12987 
Closes #12984
This commit is contained in:
Dylan 2025-05-05 11:40:01 -05:00 committed by GitHub
parent 5e2c818417
commit 965a4dd731
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 652 additions and 21 deletions

View file

@ -309,7 +309,31 @@ my_project
When Ruff sees an import like `import foo`, it will then iterate over the `src` directories,
looking for a corresponding Python module (in reality, a directory named `foo` or a file named
`foo.py`).
`foo.py`). For module paths with multiple components like `import foo.bar`,
the default behavior is to search only for a directory named `foo` or a file
named `foo.py`. However, if `preview` is enabled, Ruff will require that the full relative path `foo/bar` exists as a directory, or that `foo/bar.py` or `foo/bar.pyi` exist as files. Finally, imports of the form `from foo import bar`, Ruff will only use `foo` when determining whether a module is first-party or third-party.
If there is a directory
whose name matches a third-party package, but does not contain Python code,
it could happen that the above algorithm incorrectly infers an import to be first-party.
To prevent this, you can modify the [`known-third-party`](settings.md#lint_isort_known-third-party) setting. For example, if you import
the package `wandb` but also have a subdirectory of your `src` with
the same name, you can add the following:
=== "pyproject.toml"
```toml
[tool.ruff.lint.isort]
known-third-party = ["wandb"]
```
=== "ruff.toml"
```toml
[lint.isort]
known-third-party = ["wandb"]
```
If the `src` field is omitted, Ruff will default to using the "project root", along with a `"src"`
subdirectory, as the first-party sources, to support both flat and nested project layouts.