mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 22:01:18 +00:00
[ty] Include python
folder in environment.root
if it exists (#20263)
## Summary I felt it was safer to add the `python` folder *in addition* to a possibly-existing `src` folder, even though the `src` folder only contains Rust code for `maturin`-based projects. There might be non-maturin projects where a `python` folder exists for other reasons, next to a normal `src` layout. closes https://github.com/astral-sh/ty/issues/1120 ## Test Plan Tested locally on the egglog-python project.
This commit is contained in:
parent
8ade6c4eaf
commit
7ee863b6d7
4 changed files with 144 additions and 3 deletions
2
crates/ty/docs/configuration.md
generated
2
crates/ty/docs/configuration.md
generated
|
@ -144,7 +144,7 @@ If left unspecified, ty will try to detect common project layouts and initialize
|
||||||
* if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path
|
* if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path
|
||||||
* otherwise, default to `.` (flat layout)
|
* otherwise, default to `.` (flat layout)
|
||||||
|
|
||||||
Besides, if a `./tests` directory exists and is not a package (i.e. it does not contain an `__init__.py` file),
|
Besides, if a `./python` or `./tests` directory exists and is not a package (i.e. it does not contain an `__init__.py` or `__init__.pyi` file),
|
||||||
it will also be included in the first party search path.
|
it will also be included in the first party search path.
|
||||||
|
|
||||||
**Default value**: `null`
|
**Default value**: `null`
|
||||||
|
|
|
@ -1752,3 +1752,126 @@ fn default_root_tests_package() -> anyhow::Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default_root_python_folder() -> anyhow::Result<()> {
|
||||||
|
let case = CliTest::with_files([
|
||||||
|
("src/foo.py", "foo = 10"),
|
||||||
|
("python/bar.py", "bar = 20"),
|
||||||
|
(
|
||||||
|
"python/test_bar.py",
|
||||||
|
r#"
|
||||||
|
from foo import foo
|
||||||
|
from bar import bar
|
||||||
|
|
||||||
|
print(f"{foo} {bar}")
|
||||||
|
"#,
|
||||||
|
),
|
||||||
|
])?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(case.command(), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
All checks passed!
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
|
||||||
|
");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If `python/__init__.py` is present, it is considered a package and `python` is not added to search paths.
|
||||||
|
#[test]
|
||||||
|
fn default_root_python_package() -> anyhow::Result<()> {
|
||||||
|
let case = CliTest::with_files([
|
||||||
|
("src/foo.py", "foo = 10"),
|
||||||
|
("python/__init__.py", ""),
|
||||||
|
("python/bar.py", "bar = 20"),
|
||||||
|
(
|
||||||
|
"python/test_bar.py",
|
||||||
|
r#"
|
||||||
|
from foo import foo
|
||||||
|
from bar import bar # expected unresolved import
|
||||||
|
|
||||||
|
print(f"{foo} {bar}")
|
||||||
|
"#,
|
||||||
|
),
|
||||||
|
])?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(case.command(), @r#"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
error[unresolved-import]: Cannot resolve imported module `bar`
|
||||||
|
--> python/test_bar.py:3:6
|
||||||
|
|
|
||||||
|
2 | from foo import foo
|
||||||
|
3 | from bar import bar # expected unresolved import
|
||||||
|
| ^^^
|
||||||
|
4 |
|
||||||
|
5 | print(f"{foo} {bar}")
|
||||||
|
|
|
||||||
|
info: Searched in the following paths during module resolution:
|
||||||
|
info: 1. <temp_dir>/ (first-party code)
|
||||||
|
info: 2. <temp_dir>/src (first-party code)
|
||||||
|
info: 3. vendored://stdlib (stdlib typeshed stubs vendored by ty)
|
||||||
|
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||||
|
info: rule `unresolved-import` is enabled by default
|
||||||
|
|
||||||
|
Found 1 diagnostic
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
|
||||||
|
"#);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Similarly, if `python/__init__.pyi` is present, it is considered a package and `python` is not added to search paths.
|
||||||
|
#[test]
|
||||||
|
fn default_root_python_package_pyi() -> anyhow::Result<()> {
|
||||||
|
let case = CliTest::with_files([
|
||||||
|
("src/foo.py", "foo = 10"),
|
||||||
|
("python/__init__.pyi", ""),
|
||||||
|
("python/bar.py", "bar = 20"),
|
||||||
|
(
|
||||||
|
"python/test_bar.py",
|
||||||
|
r#"
|
||||||
|
from foo import foo
|
||||||
|
from bar import bar # expected unresolved import
|
||||||
|
|
||||||
|
print(f"{foo} {bar}")
|
||||||
|
"#,
|
||||||
|
),
|
||||||
|
])?;
|
||||||
|
|
||||||
|
assert_cmd_snapshot!(case.command(), @r#"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
error[unresolved-import]: Cannot resolve imported module `bar`
|
||||||
|
--> python/test_bar.py:3:6
|
||||||
|
|
|
||||||
|
2 | from foo import foo
|
||||||
|
3 | from bar import bar # expected unresolved import
|
||||||
|
| ^^^
|
||||||
|
4 |
|
||||||
|
5 | print(f"{foo} {bar}")
|
||||||
|
|
|
||||||
|
info: Searched in the following paths during module resolution:
|
||||||
|
info: 1. <temp_dir>/ (first-party code)
|
||||||
|
info: 2. <temp_dir>/src (first-party code)
|
||||||
|
info: 3. vendored://stdlib (stdlib typeshed stubs vendored by ty)
|
||||||
|
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||||
|
info: rule `unresolved-import` is enabled by default
|
||||||
|
|
||||||
|
Found 1 diagnostic
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
|
||||||
|
"#);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -259,11 +259,29 @@ impl Options {
|
||||||
vec![project_root.to_path_buf()]
|
vec![project_root.to_path_buf()]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let python = project_root.join("python");
|
||||||
|
if system.is_directory(&python)
|
||||||
|
&& !system.is_file(&python.join("__init__.py"))
|
||||||
|
&& !system.is_file(&python.join("__init__.pyi"))
|
||||||
|
&& !roots.contains(&python)
|
||||||
|
{
|
||||||
|
// If a `./python` directory exists, include it as a source root. This is the recommended layout
|
||||||
|
// for maturin-based rust/python projects [1].
|
||||||
|
//
|
||||||
|
// https://github.com/PyO3/maturin/blob/979fe1db42bb9e58bc150fa6fc45360b377288bf/README.md?plain=1#L88-L99
|
||||||
|
tracing::debug!(
|
||||||
|
"Including `./python` in `environment.root` because a `./python` directory exists"
|
||||||
|
);
|
||||||
|
|
||||||
|
roots.push(python);
|
||||||
|
}
|
||||||
|
|
||||||
// Considering pytest test discovery conventions,
|
// Considering pytest test discovery conventions,
|
||||||
// we also include the `tests` directory if it exists and is not a package.
|
// we also include the `tests` directory if it exists and is not a package.
|
||||||
let tests_dir = project_root.join("tests");
|
let tests_dir = project_root.join("tests");
|
||||||
if system.is_directory(&tests_dir)
|
if system.is_directory(&tests_dir)
|
||||||
&& !system.is_file(&tests_dir.join("__init__.py"))
|
&& !system.is_file(&tests_dir.join("__init__.py"))
|
||||||
|
&& !system.is_file(&tests_dir.join("__init__.pyi"))
|
||||||
&& !roots.contains(&tests_dir)
|
&& !roots.contains(&tests_dir)
|
||||||
{
|
{
|
||||||
// If the `tests` directory exists and is not a package, include it as a source root.
|
// If the `tests` directory exists and is not a package, include it as a source root.
|
||||||
|
@ -428,7 +446,7 @@ pub struct EnvironmentOptions {
|
||||||
/// * if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path
|
/// * if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path
|
||||||
/// * otherwise, default to `.` (flat layout)
|
/// * otherwise, default to `.` (flat layout)
|
||||||
///
|
///
|
||||||
/// Besides, if a `./tests` directory exists and is not a package (i.e. it does not contain an `__init__.py` file),
|
/// Besides, if a `./python` or `./tests` directory exists and is not a package (i.e. it does not contain an `__init__.py` or `__init__.pyi` file),
|
||||||
/// it will also be included in the first party search path.
|
/// it will also be included in the first party search path.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
#[option(
|
#[option(
|
||||||
|
|
2
ty.schema.json
generated
2
ty.schema.json
generated
|
@ -101,7 +101,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"description": "The root paths of the project, used for finding first-party modules.\n\nAccepts a list of directory paths searched in priority order (first has highest priority).\n\nIf left unspecified, ty will try to detect common project layouts and initialize `root` accordingly:\n\n* if a `./src` directory exists, include `.` and `./src` in the first party search path (src layout or flat) * if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path * otherwise, default to `.` (flat layout)\n\nBesides, if a `./tests` directory exists and is not a package (i.e. it does not contain an `__init__.py` file), it will also be included in the first party search path.",
|
"description": "The root paths of the project, used for finding first-party modules.\n\nAccepts a list of directory paths searched in priority order (first has highest priority).\n\nIf left unspecified, ty will try to detect common project layouts and initialize `root` accordingly:\n\n* if a `./src` directory exists, include `.` and `./src` in the first party search path (src layout or flat) * if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path * otherwise, default to `.` (flat layout)\n\nBesides, if a `./python` or `./tests` directory exists and is not a package (i.e. it does not contain an `__init__.py` or `__init__.pyi` file), it will also be included in the first party search path.",
|
||||||
"type": [
|
"type": [
|
||||||
"array",
|
"array",
|
||||||
"null"
|
"null"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue