Skip to content

Comments

Fix relative import resolution in site-packages packages when the site-packages search path is a subdirectory of the first-party search path#17178

Merged
AlexWaygood merged 2 commits intomainfrom
alex/file-to-module-bug
Apr 3, 2025
Merged

Fix relative import resolution in site-packages packages when the site-packages search path is a subdirectory of the first-party search path#17178
AlexWaygood merged 2 commits intomainfrom
alex/file-to-module-bug

Conversation

@AlexWaygood
Copy link
Member

@AlexWaygood AlexWaygood commented Apr 3, 2025

Summary

If a package in site-packages had this directory structure:

# bar/__init__.py
from .a import A

# bar/a.py
class A: ...

then we would fail to resolve the from .a import A import if (as is usually the case!) the site-packages search path was located inside a .venv directory that was a subdirectory of the project's first-party search path. The reason for this is a bug in file_to_module in the module resolver. In this loop, we would identify that /project_root/.venv/lib/python3.13/site-packages/foo/__init__.py can be turned into a path relative to the first-party search path (/project_root):

let module_name = loop {
let candidate = search_paths.next()?;
let relative_path = match path {
SystemOrVendoredPathRef::System(path) => candidate.relativize_system_path(path),
SystemOrVendoredPathRef::Vendored(path) => candidate.relativize_vendored_path(path),
};
if let Some(relative_path) = relative_path {
break relative_path.to_module_name()?;
}
};

but we'd then try to turn the relative path (.venv/lib/python3.13/site-packages/foo/__init__.py) into a module path, realise that it wasn't a valid module path... and therefore immediately break out of the loop before trying any other search paths (such as the site-packages search path).

This bug was originally reported on Discord by @MatthewMckee4.

Test Plan

I added a unit test for file_to_module in resolver.rs, and an integration test that shows we can now resolve the import correctly in infer.rs.

@AlexWaygood AlexWaygood added bug Something isn't working ty Multi-file analysis & type inference labels Apr 3, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Apr 3, 2025

mypy_primer results

No ecosystem changes detected ✅

@AlexWaygood
Copy link
Member Author

Test Plan

I added a unit test for file_to_module in resolver.rs, and an integration test that shows we can now resolve the import correctly in infer.rs.

I also manually checked that we are able to resolve the import from pydantic import BaseModel (when pydantic is installed into a virtual environment in a subdirectory of the project root), and we don't manage to resolve that import on main.

I guess we don't run mypy_primer on any projects that use pydantic right now!

@MatthewMckee4
Copy link
Contributor

MatthewMckee4 commented Apr 3, 2025

I now get "Module pydantic has no member BaseModelred-knot(lint:unresolved-import)"

This error does not show up with model_validator for example

Edit: not on all files though, ill try to reproduce

Copy link
Contributor

@carljm carljm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

/// Path to a custom typeshed directory
custom_typeshed: Option<SystemPathBuf>,
/// Paths to the directory to use for `site-packages`
site_packages: Vec<SystemPathBuf>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You removed custom_typeshed because we aren't using it anymore? (I guess mdtest now supports this so those tests have been able to move there.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, exactly

@AlexWaygood
Copy link
Member Author

I now get "Module pydantic has no member BaseModelred-knot(lint:unresolved-import)"

This error does not show up with model_validator for example

Huh. How are you running this branch?

If I run the following commands:

mkdir experiment
cd experiment
uv venv
uv pip install pydantic
echo "from pydantic import BaseModel; from typing_extensions import reveal_type; reveal_type(BaseModel)" > foo.py
cargo run --manifest-path ../ruff/Cargo.toml -p red_knot check foo.py

Where ../ruff is a path to my local Ruff clone, and I have this branch of red-knot checked out, then red-knot now reports this for me:

info: revealed-type
 --> /Users/alexw/dev/experiment4/foo.py:1:76
  |
1 | from pydantic import BaseModel; from typing_extensions import reveal_type; reveal_type(BaseModel)
  |                                                                            ^^^^^^^^^^^^^^^^^^^^^^ Revealed type is `Literal[BaseModel]`
  |

Found 1 diagnostic

@MatthewMckee4
Copy link
Contributor

That works perfectly fine for me, just in one of my codebases this import fails, ill keep trying to reproduce

@AlexWaygood
Copy link
Member Author

I think I'll land this for now anyway since it definitely fixes a bug (both test cases added here fail on main), even if there are more bugs to fix as well as this. But please do report it if you find a repro for the remaining issues!

@AlexWaygood AlexWaygood merged commit a1eb834 into main Apr 3, 2025
23 checks passed
@AlexWaygood AlexWaygood deleted the alex/file-to-module-bug branch April 3, 2025 14:48
@MatthewMckee4
Copy link
Contributor

Ah sorry im being stupid, my ruff server was linked to my fork of ruff and my ide was showing the old errors, sorry about that

@AlexWaygood
Copy link
Member Author

No worries at all -- very easily done :-)

dcreager added a commit that referenced this pull request Apr 3, 2025
* origin/main:
  [red-knot] Add `Type::TypeVar` variant (#17102)
  [red-knot] update to latest Salsa with fixpoint caching fix (#17179)
  Upgrade to Rust 1.86 and bump MSRV to 1.84 (#17171)
  [red-knot] Avoid unresolved-reference in unreachable code (#17169)
  Fix relative import resolution in `site-packages` packages when the `site-packages` search path is a subdirectory of the first-party search path (#17178)
  [DO NOT LAND] bump Salsa version (#17176)
  [syntax-errors] Detect duplicate keys in `match` mapping patterns (#17129)
maxmynter pushed a commit to maxmynter/ruff that referenced this pull request Apr 3, 2025
…site-packages` search path is a subdirectory of the first-party search path (astral-sh#17178)

## Summary

If a package in `site-packages` had this directory structure:

```py
# bar/__init__.py
from .a import A

# bar/a.py
class A: ...
```

then we would fail to resolve the `from .a import A` import _if_ (as is
usually the case!) the `site-packages` search path was located inside a
`.venv` directory that was a subdirectory of the project's first-party
search path. The reason for this is a bug in `file_to_module` in the
module resolver. In this loop, we would identify that
`/project_root/.venv/lib/python3.13/site-packages/foo/__init__.py` can
be turned into a path relative to the first-party search path
(`/project_root`):


https://github.com/astral-sh/ruff/blob/6e2b8f9696e87d786cfed6304dc3baa0f029d341/crates/red_knot_python_semantic/src/module_resolver/resolver.rs#L101-L110

but we'd then try to turn the relative path
(.venv/lib/python3.13/site-packages/foo/__init__.py`) into a module
path, realise that it wasn't a valid module path... and therefore
immediately `break` out of the loop before trying any other search paths
(such as the `site-packages` search path).

This bug was originally reported on Discord by @MatthewMckee4.

## Test Plan

I added a unit test for `file_to_module` in `resolver.rs`, and an
integration test that shows we can now resolve the import correctly in
`infer.rs`.
AlexWaygood added a commit that referenced this pull request Apr 6, 2025
…ges` test as an mdtest (#17222)

## Summary

This PR does the following things:
- Fixes the `python` configuration setting for mdtest (added in
#17221) so that it expects a path
pointing to a venv's `sys.prefix` variable rather than the a path
pointing to the venv's `site-packages` subdirectory. This brings the
`python` setting in mdtest in sync with our CLI `--python` flag.
- Tweaks mdtest so that it automatically creates a valid `pyvenv.cfg`
file for you if you don't specify one. This makes it much more ergonomic
to write an mdtest with a custom `python` setting: red-knot will reject
a `python` setting that points to a directory that doesn't have a
`pyvenv.cfg` file in it
- Tweaks mdtest so that it doesn't check a custom `pyvenv.cfg` as Python
source code if you _do_ add a custom `pyvenv.cfg` file for your mock
virtual environment in an mdtest. (You get a lot of diagnostics about
Python syntax errors in the `pyvenv.cfg` file, otherwise!)
- Rewrites the test added in
#17178 as an mdtest, and deletes
the original test that was added in that PR

## Test Plan

I verified that the new mdtest fails if I revert the changes to
`resolver.rs` that were added in
#17178
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants