Skip to content

Commit

Permalink
Detect imports in src layouts by default (#12848)
Browse files Browse the repository at this point in the history
## Summary

Occasionally, we receive bug reports that imports in `src` directories
aren't correctly detected. The root of the problem is that we default to
`src = ["."]`, so users have to set `src = ["src"]` explicitly. This PR
extends the default to cover _both_ of them: `src = [".", "src"]`.

Closes #12454.

## Test Plan

I replicated the structure described in
#12453, and verified that the
imports were considered sorted, but that adding `src = ["."]` showed an
error.
  • Loading branch information
charliermarsh authored Aug 14, 2024
1 parent 6a8f2e2 commit 1be5212
Showing 8 changed files with 34 additions and 32 deletions.
8 changes: 2 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -911,9 +911,5 @@ There are three ways in which an import can be categorized as "first-party":
the `src` setting and, for each directory, check for the existence of a subdirectory `foo` or a
file `foo.py`.
By default, `src` is set to the project root. In the above example, we'd want to set
`src = ["./src"]` to ensure that we locate `./my_project/src/foo` and thus categorize `import foo`
as first-party in `baz.py`. In practice, for this limited example, setting `src = ["./src"]` is
unnecessary, as all imports within `./my_project/src/foo` would be categorized as first-party via
the same-package heuristic; but if your project contains multiple packages, you'll want to set `src`
explicitly.
By default, `src` is set to the project root, along with `"src"` subdirectory in the project root.
This ensures that Ruff supports both flat and "src" layouts out of the box.
Original file line number Diff line number Diff line change
@@ -209,6 +209,7 @@ linter.logger_objects = []
linter.namespace_packages = []
linter.src = [
"[BASEPATH]",
"[BASEPATH]/src",
]
linter.tab_size = 4
linter.line_length = 88
2 changes: 1 addition & 1 deletion crates/ruff_linter/src/settings/mod.rs
Original file line number Diff line number Diff line change
@@ -398,7 +398,7 @@ impl LinterSettings {
per_file_ignores: CompiledPerFileIgnoreList::default(),
fix_safety: FixSafetyTable::default(),

src: vec![path_dedot::CWD.clone()],
src: vec![path_dedot::CWD.clone(), path_dedot::CWD.join("src")],
// Needs duplicating
tab_size: IndentWidth::default(),
line_length: LineLength::default(),
6 changes: 3 additions & 3 deletions crates/ruff_workspace/src/configuration.rs
Original file line number Diff line number Diff line change
@@ -271,7 +271,6 @@ impl Configuration {
.chain(lint.extend_per_file_ignores)
.collect(),
)?,

fix_safety: FixSafetyTable::from_rule_selectors(
&lint.extend_safe_fixes,
&lint.extend_unsafe_fixes,
@@ -280,8 +279,9 @@ impl Configuration {
require_explicit: false,
},
),

src: self.src.unwrap_or_else(|| vec![project_root.to_path_buf()]),
src: self
.src
.unwrap_or_else(|| vec![project_root.to_path_buf(), project_root.join("src")]),
explicit_preview_rules: lint.explicit_preview_rules.unwrap_or_default(),

task_tags: lint
30 changes: 17 additions & 13 deletions crates/ruff_workspace/src/options.rs
Original file line number Diff line number Diff line change
@@ -323,33 +323,37 @@ pub struct Options {
/// The directories to consider when resolving first- vs. third-party
/// imports.
///
/// As an example: given a Python package structure like:
/// When omitted, the `src` directory will typically default to including both:
///
/// 1. The directory containing the nearest `pyproject.toml`, `ruff.toml`, or `.ruff.toml` file (the "project root").
/// 2. The `"src"` subdirectory of the project root.
///
/// These defaults ensure that uv supports both flat layouts and `src` layouts out-of-the-box.
/// (If a configuration file is explicitly provided (e.g., via the `--config` command-line
/// flag), the current working directory will be considered the project root.)
///
/// As an example, consider an alternative project structure, like:
///
/// ```text
/// my_project
/// ├── pyproject.toml
/// └── src
/// └── lib
/// └── my_package
/// ├── __init__.py
/// ├── foo.py
/// └── bar.py
/// ```
///
/// The `./src` directory should be included in the `src` option
/// (e.g., `src = ["src"]`), such that when resolving imports,
/// `my_package.foo` is considered a first-party import.
///
/// When omitted, the `src` directory will typically default to the
/// directory containing the nearest `pyproject.toml`, `ruff.toml`, or
/// `.ruff.toml` file (the "project root"), unless a configuration file
/// is explicitly provided (e.g., via the `--config` command-line flag).
/// In this case, the `./lib` directory should be included in the `src` option
/// (e.g., `src = ["lib"]`), such that when resolving imports, `my_package.foo`
/// is considered first-party.
///
/// This field supports globs. For example, if you have a series of Python
/// packages in a `python_modules` directory, `src = ["python_modules/*"]`
/// would expand to incorporate all of the packages in that directory. User
/// home directory and environment variables will also be expanded.
/// would expand to incorporate all packages in that directory. User home
/// directory and environment variables will also be expanded.
#[option(
default = r#"["."]"#,
default = r#"[".", "src"]"#,
value_type = "list[str]",
example = r#"
# Allow imports relative to the "src" and "test" directories.
15 changes: 8 additions & 7 deletions docs/faq.md
Original file line number Diff line number Diff line change
@@ -292,13 +292,14 @@ When Ruff sees an import like `import foo`, it will then iterate over the `src`
looking for a corresponding Python module (in reality, a directory named `foo` or a file named
`foo.py`).

If the `src` field is omitted, Ruff will default to using the "project root" as the only
first-party source. The "project root" is typically the directory containing your `pyproject.toml`,
`ruff.toml`, or `.ruff.toml` file, unless a configuration file is provided on the command-line via
the `--config` option, in which case, the current working directory is used as the project root.

In this case, Ruff would only check the top-level directory. Instead, we can configure Ruff to
consider `src` as a first-party source like so:
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.
The "project root" is typically the directory containing your `pyproject.toml`, `ruff.toml`, or
`.ruff.toml` file, unless a configuration file is provided on the command-line via the `--config`
option, in which case, the current working directory is used as the project root.

In this case, Ruff would check the `"src"` directory by default, but we can configure it as an
explicit, exclusive first-party source like so:

=== "pyproject.toml"

2 changes: 1 addition & 1 deletion docs/integrations.md
Original file line number Diff line number Diff line change
@@ -59,7 +59,7 @@ Alternatively, you can include `ruff-action` as a step in any other workflow fil

- `version`: The Ruff version to install (default: latest).
- `args`: The command-line arguments to pass to Ruff (default: `"check"`).
- `src`: The source paths to pass to Ruff (default: `"."`).
- `src`: The source paths to pass to Ruff (default: `[".", "src"]`).

For example, to run `ruff check --select B ./src` using Ruff version `0.0.259`:

2 changes: 1 addition & 1 deletion ruff.schema.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 1be5212

Please sign in to comment.