Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pylance doesn't match Python's behaviour wrt installed packages #859

Closed
hauntsaninja opened this issue Jan 21, 2021 · 18 comments
Closed

Pylance doesn't match Python's behaviour wrt installed packages #859

hauntsaninja opened this issue Jan 21, 2021 · 18 comments
Labels
bug Something isn't working fixed in next version (main) A fix has been implemented and will appear in an upcoming version

Comments

@hauntsaninja
Copy link

hauntsaninja commented Jan 21, 2021

Pylance prefers looking at local directories over installed packages, which doesn't match the behaviour of the Python interpreter. Here's my repro script:

set -x

# make a package
mkdir -p repo/tmpproj/tmpproj
cat > repo/tmpproj/tmpproj/__init__.py <<HERE
x = 5
HERE
cat > repo/tmpproj/setup.py <<HERE
from setuptools import setup, find_packages
setup(name="tmpproj", packages=find_packages()) 
HERE

# make a script that uses the package
cat > repo/script.py <<HERE
# this works at runtime, but pylance can't figure it out
from tmpproj import x

# you can tell pylance is getting confused by the directory
# since it's happy with the following
import tmpproj.tmpproj
reveal_type(tmpproj.tmpproj.x)
HERE

# install the package
cd repo
python3 -m venv env 
source env/bin/activate
pip install tmpproj/

After that, open repo in VSCode, and select ./env/bin/python as the Python interpreter. Then open up script.py to verify Pylance's behaviour.

I'm aware of python.analysis.extraPaths, but it's pretty unwieldy for my use case (I'd have to add several dozen paths for the repo I'm working on). In any case, I think it's unexpected that Pylance's behaviour doesn't match Python's.

Note that changing it to an editable install doesn't help either (although I see from your issues that editable installs aren't yet supported).

Environment data

  • Language Server version: Pylance language server 2021.1.2 (pyright 4b6b0cd0) starting
  • OS and version: Mac
  • Python version (& distribution if applicable, e.g. Anaconda): 3.9
@hauntsaninja
Copy link
Author

I think this is what's going on in #733 as well

@jakebailey
Copy link
Member

jakebailey commented Jan 21, 2021

The workspace is always assumed to be the first import root, and that module is importable via that path (as it is within the workspace).

Editable installs would help at least resolve the import by another name (as it would add that module's parent instead), but as it stands now I think this is working as we'd expect (and likely matches MPLS/jedi's methods of managing imports, though MPLS has its own problems with being unable to import one module by many names).

It seems like you're trying to have an environment where the workspace is not the import root (rare from our experiences), but that would definitely require at least editable installs to figure out. I'm not sure if we would be able to figure out that each folder itself is some package needing to also be an import root as well, without some configuration to do so.

@hauntsaninja
Copy link
Author

hauntsaninja commented Jan 21, 2021

I think editable installs are orthogonal here (note my repro is just a plain install).

After selecting the Python interpreter, I'd expect Pylance to resolve imports the same way the Python interpreter would (when invoked from the workspace root) and prefer the installed package:

~/tmp/repo λ source env/bin/activate
(env) ~/tmp/repo λ python3
Python 3.9.1 (default, Jan  8 2021, 17:17:17) 
[Clang 12.0.0 (clang-1200.0.32.28)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import tmpproj
>>> tmpproj.__file__
'/Users/shantanu/tmp/repo/env/lib/python3.9/site-packages/tmpproj/__init__.py'

@jakebailey
Copy link
Member

Hm, I see. It's installed and we should be finding tmpproj in site-packages, but because tmpproj exists as a directory in the workspace, it takes precedence. I'm not entirely certain how to think about this, because it's often more likely that a user has written code that does actually take precedence and we should resolve it that way, but maybe this is only working because the folder itself is "not a module".

Can you enable trace logging and grab a copy of the search paths we are using? They should be printed at startup.

@hauntsaninja
Copy link
Author

[Info  - 5:14:03 PM] Pylance language server 2021.1.2 (pyright 4b6b0cd0) starting
[Info  - 5:14:03 PM] Server root directory: /Users/shantanu/.vscode/extensions/ms-python.vscode-pylance-2021.1.2/dist
[Error - 5:14:03 PM] stubPath /Users/shantanu/tmp/repo/typings is not a valid directory.
[Error - 5:14:54 PM] stubPath /Users/shantanu/tmp/repo/typings is not a valid directory.
[Info  - 5:37:30 PM] No configuration file found.
[Info  - 5:37:30 PM] Setting pythonPath for service "repo": "/Users/shantanu/tmp/repo/env/bin/python"
Search paths found for configured python interpreter:
  /usr/local/Cellar/python@3.9/3.9.1_6/Frameworks/Python.framework/Versions/3.9/lib/python3.9
  /usr/local/Cellar/python@3.9/3.9.1_6/Frameworks/Python.framework/Versions/3.9/lib/python3.9/lib-dynload
  /Users/shantanu/tmp/repo/env/lib/python3.9/site-packages
[Error - 5:37:30 PM] stubPath /Users/shantanu/tmp/repo/typings is not a valid directory.
[Info  - 5:37:30 PM] Assuming Python version 3.9
[Info  - 5:37:30 PM] Assuming Python platform Darwin
[Info  - 5:37:30 PM] Searching for source files
[Info  - 5:37:30 PM] Auto-excluding /Users/shantanu/tmp/repo/env
[Info  - 5:37:30 PM] Found 3 source files
Background analysis message: setConfigOptions
Background analysis message: setTrackedFiles
Background analysis message: markAllFilesDirty
Background analysis message: analyze
[BG(1)] analyzing: /Users/shantanu/tmp/repo/script.py ...
[BG(1)]   parsing: /Users/shantanu/tmp/repo/script.py (10ms)
[BG(1)]   parsing: /Users/shantanu/.vscode/extensions/ms-python.vscode-pylance-2021.1.2/dist/typeshed-fallback/stdlib/3/builtins.pyi [fs read 0ms] (63ms)
[BG(1)]   binding: /Users/shantanu/.vscode/extensions/ms-python.vscode-pylance-2021.1.2/dist/typeshed-fallback/stdlib/3/builtins.pyi (19ms)
[BG(1)]   binding: /Users/shantanu/tmp/repo/script.py (0ms)
[BG(1)]   checking: /Users/shantanu/tmp/repo/script.py ...
[BG(1)]     parsing: /Users/shantanu/tmp/repo/tmpproj/tmpproj/__init__.py [fs read 1ms] (3ms)
[BG(1)]     binding: /Users/shantanu/tmp/repo/tmpproj/tmpproj/__init__.py (0ms)
[BG(1)]     parsing: /Users/shantanu/.vscode/extensions/ms-python.vscode-pylance-2021.1.2/dist/typeshed-fallback/stdlib/2and3/_typeshed/__init__.pyi [fs read 1ms] (9ms)
[BG(1)]     binding: /Users/shantanu/.vscode/extensions/ms-python.vscode-pylance-2021.1.2/dist/typeshed-fallback/stdlib/2and3/_typeshed/__init__.pyi (0ms)
[BG(1)]     parsing: /Users/shantanu/.vscode/extensions/ms-python.vscode-pylance-2021.1.2/dist/typeshed-fallback/third_party/2and3/typing_extensions.pyi [fs read 0ms] (2ms)
[BG(1)]     binding: /Users/shantanu/.vscode/extensions/ms-python.vscode-pylance-2021.1.2/dist/typeshed-fallback/third_party/2and3/typing_extensions.pyi (2ms)
[BG(1)]     parsing: /Users/shantanu/.vscode/extensions/ms-python.vscode-pylance-2021.1.2/dist/typeshed-fallback/stdlib/3/typing.pyi [fs read 0ms] (19ms)
[BG(1)]     binding: /Users/shantanu/.vscode/extensions/ms-python.vscode-pylance-2021.1.2/dist/typeshed-fallback/stdlib/3/typing.pyi (7ms)
[BG(1)]   checking: /Users/shantanu/tmp/repo/script.py (50ms)
[BG(1)] analyzing: /Users/shantanu/tmp/repo/script.py (143ms)
Background analysis message: resumeAnalysis
Background analysis message: getDiagnosticsForRange
Background analysis message: getDiagnosticsForRange
[FG] parsing: /Users/shantanu/tmp/repo/script.py (8ms)
[FG] parsing: /Users/shantanu/.vscode/extensions/ms-python.vscode-pylance-2021.1.2/dist/typeshed-fallback/stdlib/3/builtins.pyi [fs read 0ms] (32ms)
[FG] binding: /Users/shantanu/.vscode/extensions/ms-python.vscode-pylance-2021.1.2/dist/typeshed-fallback/stdlib/3/builtins.pyi (18ms)
[FG] binding: /Users/shantanu/tmp/repo/script.py (1ms)

@jakebailey jakebailey added the needs investigation Could be an issue - needs investigation label Jan 21, 2021
@github-actions github-actions bot removed the triage label Jan 21, 2021
@hauntsaninja
Copy link
Author

hauntsaninja commented Jan 21, 2021

My opinion is you can't go wrong by matching the behaviour of the interpreter. (At least for mypy, basically any time we do things differently from the interpreter people complain)
You'll probably want to prefer installed packages for when you do support editable installs too.

@erictraut
Copy link
Contributor

I'm not seeing the behavior you're reporting. Pylance seems to be matching the behavior of the interpreter.

Screen Shot 2021-01-20 at 5 46 06 PM

@hauntsaninja
Copy link
Author

hauntsaninja commented Jan 21, 2021

How can I help you resolve the discrepancy? @jakebailey are you able to repro? Does my trace log match yours?

Screen Shot 2021-01-20 at 6 04 26 PM

@erictraut
Copy link
Contributor

I figured out the difference. It was my mistake. I'm now able to repro what you're seeing.

@erictraut
Copy link
Contributor

OK, I've had a chance to dig into this more. I'm able to repro the pylance errors that you're seeing.

Screen Shot 2021-01-22 at 4 55 29 PM

When I activate the virtual environment "env" and run the script ("python script.py"), I receive the following output, which matches the errors indicated by pylance:

Traceback (most recent call last):
  File "/Users/eric/Desktop/repo/script.py", line 2, in <module>
    from tmpproj import x
ImportError: cannot import name 'x' from 'tmpproj' (unknown location)

When I comment out the first import statement and replace the reveal_type with a call to print, the script completes, and the output is:

5

Based on my results, pylance is matching the behavior of the interpreter. (Incidentally, I'm using Python 3.9 on Mac OS, but I don't think that makes a difference.)

@hauntsaninja, are you seeing different results?

@hauntsaninja
Copy link
Author

hauntsaninja commented Jan 23, 2021

Yeah, I'm seeing different results:

~/tmp/tmp λ vim x.sh  # copy the script in the first comment ^
~/tmp/tmp λ bash x.sh
+ mkdir -p repo/tmpproj/tmpproj
+ cat
+ cat
+ cat
+ cd repo
+ python3 -m venv env
+ source env/bin/activate
++ deactivate nondestructive
++ '[' -n '' ']'
++ '[' -n '' ']'
++ '[' -n /usr/local/bin/bash -o -n '' ']'
++ hash -r
++ '[' -n '' ']'
++ unset VIRTUAL_ENV
++ '[' '!' nondestructive = nondestructive ']'
++ VIRTUAL_ENV=/Users/shantanu/tmp/tmp/repo/env
++ export VIRTUAL_ENV
++ _OLD_VIRTUAL_PATH=<redacted>
++ PATH=<redacted>
++ export PATH
++ '[' -n '' ']'
++ '[' -z '' ']'
++ _OLD_VIRTUAL_PS1=
++ PS1='(env) '
++ export PS1
++ '[' -n /usr/local/bin/bash -o -n '' ']'
++ hash -r
+ pip install tmpproj/
Processing ./tmpproj
Using legacy 'setup.py install' for tmpproj, since package 'wheel' is not installed.
Installing collected packages: tmpproj
    Running setup.py install for tmpproj ... done
Successfully installed tmpproj-0.0.0

~/tmp/tmp λ cd repo
~/tmp/tmp/repo λ source env/bin/activate
(env) ~/tmp/tmp/repo λ python3 --version
Python 3.9.1
(env) ~/tmp/tmp/repo λ python3 script.py
Traceback (most recent call last):
  File "/Users/shantanu/tmp/tmp/repo/script.py", line 6, in <module>
    import tmpproj.tmpproj
ModuleNotFoundError: No module named 'tmpproj.tmpproj'

(note that I edited the script in my first comment to add set -x)

@erictraut
Copy link
Contributor

@jakebailey, could you see if you can repro what Shantanu is seeing?

@jakebailey
Copy link
Member

I took the script and ran it. If I do not activate the environment, I see:

Traceback (most recent call last):
  File "/home/jake/work/issue859/repo/script.py", line 2, in <module>
    from tmpproj import x
ImportError: cannot import name 'x' from 'tmpproj' (unknown location)

If I activate it, I get your result:

Traceback (most recent call last):
  File "/home/jake/work/issue859/repo/script.py", line 6, in <module>
    import tmpproj.tmpproj
ModuleNotFoundError: No module named 'tmpproj.tmpproj'

Opening VS Code, running code . in repo to open it (and choose that env), I see no import warnings. I can jump to x just fine into tmpproj/tmpproj/__init__.py.

image

@jakebailey
Copy link
Member

jakebailey commented Jan 25, 2021

Looking at sys.path when I open the interpreter, it of course contains the site-packages for the environment, which contains tmpproj. Maybe this is something to do with namespace packages combined with the fact that we implicitly include the workspace as a valid import root? I can see in trace logs that site-packages is there.

Unfortunately, our logging doesn't actually output the final list of search paths, only some of the intermediates.

@jakebailey
Copy link
Member

Unless I'm mistaken, microsoft/pyright@170757b fixed this, correct? @erictraut

@erictraut
Copy link
Contributor

Ah yes, I forgot to mark this bug as fixed.

@erictraut erictraut added bug Something isn't working fixed in next version (main) A fix has been implemented and will appear in an upcoming version and removed needs investigation Could be an issue - needs investigation labels Jan 26, 2021
@hauntsaninja
Copy link
Author

Thanks both! :-)

@jakebailey
Copy link
Member

This issue has been fixed in version 2021.1.3, which we've just released. You can find the changelog here: https://github.com/microsoft/pylance-release/blob/main/CHANGELOG.md#202113-27-january-2021

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working fixed in next version (main) A fix has been implemented and will appear in an upcoming version
Projects
None yet
Development

No branches or pull requests

3 participants