Skip to content

Conversation

@zanieb
Copy link
Member

@zanieb zanieb commented Nov 5, 2025

Summary

Closes astral-sh/ty#989

There are various situations where users expect the Python packages installed in the same environment as ty itself to be considered during type checking. A minimal example would look like:

uv venv my-env
uv pip install my-env ty httpx
echo "import httpx" > foo.py
./my-env/bin/ty check foo.py

or

uv tool install ty --with httpx
echo "import httpx" > foo.py
ty check foo.py

While these are a bit contrived, there are real-world situations where a user would expect a similar behavior to work. Notably, all of the other type checkers consider their own environment when determining search paths (though I'll admit that I have not verified when they choose not to do this).

One common situation where users are encountering this today is with uvx --with-requirements script.py ty check script.py — which is currently our "best" recommendation for type checking a PEP 723 script, but it doesn't work.

Of the options discussed in astral-sh/ty#989 (comment), I've chosen (2) as our criteria for including ty's environment in the search paths.

  • If no virtual environment is discovered, we will always include ty's environment.
  • If a .venv is discovered in the working directory, we will prepend ty's environment to the search paths. The dependencies in ty's environment (e.g., from uvx --with) will take precedence.
  • If a virtual environment is active, e.g., VIRTUAL_ENV (i.e., including conda prefixes) is set, we will not include ty's environment.

The reason we need to special case the .venv case is that we both

  1. Recommend uvx ty today as a way to check your project
  2. Want to enable uvx --with <...> ty

And I don't want (2) to break when you happen to be in a project (i.e., if we only included ty's environment when no environment is found) and don't want to remove support for (1).

I think long-term, I want to make uvx <cmd> layer the environment on top of the project environment (in uv), which would obviate the need for this change when you're using uv. However, that change is breaking and I think users will expect this behavior in contexts where they're not using uv, so I think we should handle it in ty regardless.

I've opted not to include the environment if it's non-virtual (i.e., a system environment) for now. It seems better to start by being more restrictive. I left a comment in the code.

Test Plan

I did some manual testing with the initial commit, then subsequently added some unit tests.

❯ echo "import httpx" > example.py
❯ uvx --with httpx ty check example.py
Installed 8 packages in 19ms
error[unresolved-import]: Cannot resolve imported module `httpx`
 --> foo/example.py:1:8
  |
1 | import httpx
  |        ^^^^^
  |
info: Searched in the following paths during module resolution:
info:   1. /Users/zb/workspace/ty/python (first-party code)
info:   2. /Users/zb/workspace/ty (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
❯ uvx --from . --with httpx ty check example.py
All checks passed!
❯ uv init --script foo.py
Initialized script at `foo.py`
❯ uv add --script foo.py httpx
warning: The Python request from `.python-version` resolved to Python 3.13.8, which is incompatible with the script's Python requirement: `>=3.14`
Updated `foo.py`
❯ echo "import httpx" >> foo.py
❯ uvx --with-requirements foo.py ty check foo.py
error[unresolved-import]: Cannot resolve imported module `httpx`
  --> foo.py:15:8
   |
13 | if __name__ == "__main__":
14 |     main()
15 | import httpx
   |        ^^^^^
   |
info: Searched in the following paths during module resolution:
info:   1. /Users/zb/workspace/ty/python (first-party code)
info:   2. /Users/zb/workspace/ty (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
❯ uvx --from . --with-requirements foo.py ty check foo.py
All checks passed!

Notice we do not include ty's environment if VIRTUAL_ENV is set

❯ VIRTUAL_ENV=.venv uvx --with httpx ty check foo/example.py
error[unresolved-import]: Cannot resolve imported module `httpx`
 --> foo/example.py:1:8
  |
1 | import httpx
  |        ^^^^^
  |
info: Searched in the following paths during module resolution:
info:   1. /Users/zb/workspace/ty/python (first-party code)
info:   2. /Users/zb/workspace/ty (first-party code)
info:   3. vendored://stdlib (stdlib typeshed stubs vendored by ty)
info:   4. /Users/zb/workspace/ty/.venv/lib/python3.13/site-packages (site-packages)
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

@github-actions
Copy link
Contributor

github-actions bot commented Nov 5, 2025

Diagnostic diff on typing conformance tests

No changes detected when running ty on typing conformance tests ✅

@github-actions
Copy link
Contributor

github-actions bot commented Nov 5, 2025

mypy_primer results

No ecosystem changes detected ✅
No memory usage changes detected ✅

@AlexWaygood AlexWaygood changed the title ty: Discover site-packages from the environment that ty is installed in [ty] Discover site-packages from the environment that ty is installed in Nov 5, 2025
@AlexWaygood AlexWaygood added the ty Multi-file analysis & type inference label Nov 5, 2025
@zanieb zanieb force-pushed the zb/ty-self-env branch 2 times, most recently from c97e603 to bb73359 Compare November 5, 2025 19:59
@Gankra Gankra self-requested a review November 5, 2025 20:09
Copy link
Contributor

@Gankra Gankra left a comment

Choose a reason for hiding this comment

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

Approach seems reasonable!

In terms of testing I would probably add a new test to crates/ty/tests/cli/python_environment.rs by adding an API to CliTest that lets you specify "copy the ty binary to this other dir before executing it".

@zanieb
Copy link
Member Author

zanieb commented Nov 5, 2025

"copy the ty binary to this other dir before executing it".

Probably not a blocker, but as a brief note, I think this can be slow, e.g., in uv my experience was that the uv binary is pretty big and moving it around during tests is expensive.

@Gankra
Copy link
Contributor

Gankra commented Nov 6, 2025

in uv my experience was that the uv binary is pretty big and moving it around during tests is expensive.

Is the mitigation as simple as hardlinking or maybe even symlinking here, or are we liable to run into temp-is-a-different-fs issues?

@zanieb
Copy link
Member Author

zanieb commented Nov 6, 2025

It doesn't seem horribly slow so let's just not worry about it.

What's the best practice for filtering here...?

        FAIL [   0.433s] (3977/5273) ty::cli python_environment::ty_environment_and_active_environment
  stdout ───

    running 1 test
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Snapshot Summary ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    Snapshot: ty_environment_and_active_environment
    Source: C:\actions-runner\ruff\ruff:1844
    ───────────────────────────────────────────────────────────────────────────────
    program: ty
    args:
      - check
    env:
      VIRTUAL_ENV: "C:\\Users\\Administrator\\AppData\\Local\\Temp\\.tmp1pBqh5\\active-venv"
    ───────────────────────────────────────────────────────────────────────────────
    -old snapshot
    +new results
    ────────────┬──────────────────────────────────────────────────────────────────
       10    10 │   |
       11    11 │ info: Searched in the following paths during module resolution:
       12    12 │ info:   1. <temp_dir>/ (first-party code)
       13    13 │ info:   2. vendored://stdlib (stdlib typeshed stubs vendored by ty)
       14       │-info:   3. <temp_dir>/active-venv/lib/python3.13/site-packages (site-packages)
             14 │+info:   3. <temp_dir>/active-venv/Lib/site-packages (site-packages)
       15    15 │ info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
       16    16 │ info: rule `unresolved-import` is enabled by default
       17    17 │ 
       18    18 │ Found 1 diagnostic

Of course, this snapshot is broken on Windows.

I see there's some add_filter stuff going on in CliTest::new but this doesn't seem like it belongs there unconditionally?

@zanieb
Copy link
Member Author

zanieb commented Nov 6, 2025

1dd2848 seems sufficient / fine

@zanieb zanieb marked this pull request as ready for review November 6, 2025 04:42
Copy link
Contributor

@Gankra Gankra left a comment

Choose a reason for hiding this comment

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

rad!

@Gankra Gankra merged commit 132d10f into main Nov 6, 2025
41 checks passed
@Gankra Gankra deleted the zb/ty-self-env branch November 6, 2025 14:27
carljm added a commit to MatthewMckee4/ruff that referenced this pull request Nov 6, 2025
* main: (188 commits)
  [ty] Discover site-packages from the environment that ty is installed in (astral-sh#21286)
  [ty] Make special cases for `UnionType` slightly narrower (astral-sh#21276)
  Require ignore 0.4.24 in `Cargo.toml` (astral-sh#21292)
  [ty] Favour imported symbols over builtin symbols (astral-sh#21285)
  docs: revise Ruff setup instructions for Zed editor (astral-sh#20935)
  [ty] Update salsa (astral-sh#21281)
  [syntax-error]: no binding for nonlocal  PLE0117 as a semantic syntax error (astral-sh#21032)
  [ty] Constraining a typevar with itself (possibly via union or intersection) (astral-sh#21273)
  [`ruff`] Fix false positives on starred arguments (`RUF057`) (astral-sh#21256)
  [ty] Simplify unions containing multiple type variables during inference (astral-sh#21275)
  [ty] Add `ty_server::Db` trait (astral-sh#21241)
  [ty] Refactor `Range` to/from `TextRange` conversion as prep for notebook support (astral-sh#21230)
  [ty] Fix playground crash when file name includes path separator (astral-sh#21151)
  [`refurb`] Fix false negative for underscores before sign in `Decimal` constructor (`FURB157`) (astral-sh#21190)
  [ty] Allow values of type `None` in type expressions (astral-sh#21263)
  Run codspeed benchmarks with `profiling` profile (astral-sh#21261)
  [ty] Update expected diagnostic count in benchmarks (astral-sh#21269)
  Avoid extra parentheses for long `match` patterns with `as` captures (astral-sh#21176)
  [ty] Update salsa (astral-sh#21265)
  [ty] `dict` is not assignable to `TypedDict` (astral-sh#21238)
  ...
dcreager added a commit that referenced this pull request Nov 7, 2025
* origin/main:
  Remove duplicate preview tests for `FURB101` and `FURB103` (#21303)
  [ty] Add support for `Literal`s in implicit type aliases (#21296)
  [ty] Add missing `heap_size` to `variance_of` queries (#21318)
  [`pyupgrade`] Fix false positive on relative imports from local `.builtins` module (`UP029`) (#21309)
  [ty] Make range/position conversions fallible (#21297)
  Bump 0.14.4 (#21306)
  Fix main by using `infer_expression` (#21299)
  [ty] Understand legacy and PEP 695 `ParamSpec` (#21139)
  [ty] Discover site-packages from the environment that ty is installed in (#21286)
  [ty] Make special cases for `UnionType` slightly narrower (#21276)
  Require ignore 0.4.24 in `Cargo.toml` (#21292)
  [ty] Favour imported symbols over builtin symbols (#21285)
  docs: revise Ruff setup instructions for Zed editor (#20935)
  [ty] Update salsa (#21281)
  [syntax-error]: no binding for nonlocal  PLE0117 as a semantic syntax error (#21032)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Respect parent Python interpreter that invoked ty as a candidate Python env

4 participants