Skip to content

Issue with having .pyi files in a separate folder (again) #5520

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

Closed
MarkCBell opened this issue Aug 22, 2018 · 25 comments
Closed

Issue with having .pyi files in a separate folder (again) #5520

MarkCBell opened this issue Aug 22, 2018 · 25 comments
Labels

Comments

@MarkCBell
Copy link
Contributor

MarkCBell commented Aug 22, 2018

This is more of a question than an issue.

I'm starting on using mypy to typecheck a package in which I have stub files in a dedicated ./stubs folder. However even when I pass this path into the mypy_path option in mypy.ini these don't seem to be detected and I

The same issue appears to be reported here #4933 which includes a minimal example. Tweaking the mypy.ini file and making the ./stubs/mypy folder into a package by adding __init__.pyi files was suggested as a solution. I forked the package and implemented this suggestion (see https://github.com/MarkCBell/mypyx) however running mypy tests/test-main.py I still see the same error messages:

mypy/main.py:1: error: Function is missing a type annotation
mypy/main.py:5: error: Function is missing a type annotation
mypy/main.py:9: error: Function is missing a type annotation

As before, if I move ./stubs/mypy/main.pyi to ./mypy/ then these errors go away.

I am using mypy 0.620 installed under Python 3.6.5 by pip 18.0

Can someone please help me in knowing where am I exactly going wrong?

@ilevkivskyi
Copy link
Member

Could you please give a bit more context? What exactly do you want to do?

Note: if you want to type-check a body of a function against its signature in a stub, then it is currently impossible, see however PR #5139

@MarkCBell
Copy link
Contributor Author

I have a package with ./mypy/main.py and a test in ./tests/test-main.py and I want to create a stub file in ./stubs/mypy/main.pyi to keep the stub files separate so I also added mypy_path=./stubs/ to mypy.ini. However when I run mypy ./tests/test-main.py it doesn't appear to find any stub files and so raises errors that the functions are missing type annotations.

@ilevkivskyi
Copy link
Member

Sorry I can't reproduce this. I just installed mypy from master, cloned your repo, and I see correct behaviour:

 $ mypy ./tests/test-main.py 
tests/test-main.py:3: error: "greeting" does not return a value
tests/test-main.py:5: error: Argument 1 to "hello_world" has incompatible type "str"; expected "int"

@MarkCBell
Copy link
Contributor Author

MarkCBell commented Aug 26, 2018

Thanks for testing it out. That is a little strange, so I uninstalled mypy and reinstalled the mypy master directly from github but still see the same errors. These are the commands I ran:

$ pip install -U git+git://github.com/python/mypy.git
$ mypy --version
mypy 0.630+dev-2b246ddcab890fe6dc119fd18adbf752fa233d4c
$ git clone https://github.com/MarkCBell/mypyx
$ cd mypy
$ mypy tests/test-main.py
mypy/main.py:1: error: Function is missing a type annotation
mypy/main.py:5: error: Function is missing a type annotation
mypy/main.py:9: error: Function is missing a type annotation

Which version of Python, pip and os are you using? I'm on Python 3.6.5, pip 18.0 and macOS High Sierra 10.13.3.

@ilevkivskyi
Copy link
Member

I did this on Python 3.4.3, and 3.6.1. I don't think Python version matters here. The only possible difference is that I never install from remote git repo using pip (because it doesn't work well with git submodules). I normally use git clone <mypy-repo> && cd mypy && git submodule update typeshed && pip install -U .

Another question, are you using a virtual environment?

@martinstein
Copy link

martinstein commented Oct 2, 2018

I'm having the same issue as @MarkCBell , both in my local project and when trying out the test case from this thread from https://github.com/MarkCBell/mypy

I'm on Windows 10, Python 3.7 in a virtual environment from venv, if that matters. For the test case, I've also tried slashes, backslashes and double backslashes for specifying the stubs-path, but the result is still:

mypy\main.py:1: error: Function is missing a type annotation
mypy\main.py:5: error: Function is missing a type annotation
mypy\main.py:9: error: Function is missing a type annotation

Does the virtual environment affect this somehow? @ilevkivskyi

@ilevkivskyi
Copy link
Member

Hm, I just tried this on Mac and it doesn't work at all, both with and without a virtualenv. I does work however if I first cd to tests. So it looks like mypy prefers original .py files if it can find them in current directory, otherwise it uses the stubs.

@martinstein
Copy link

I'm able to make MyPy see the .pyi stub file if it sits in the exact same directory as the original .py module. However, according to the docs I was expecting that the same logic (higher priority for the .pyi file if found) would apply to the stub directory as well.

Can anybody else get a test-case to work where a module from your own code has a stub file in a stubs-directory (i.e. not in the same folder)? I'm still expecting that it's just me who's using the mypy_path option in mypy.ini in a wrong way, but if not, this seems like a bug/wrong behavior to me.

@gvanrossum
Copy link
Member

If you use mypy -v you should see copious logging showing which files it is analyzing.

One rule to be aware of: mypy's search patch is something like this:

  1. current directory
  2. directories where sources given on command line live
  3. $MYPYPATH
  4. mypy_path from mypy.ini
  5. site-packages etc.
  6. typeshed

The other rule to know is that mypy searches each directory on the path in turn, looking for x.py and x.pyi, preferring x.pyi over x.py in the same directory. But if it finds only one or the other it prefers the earlier search path directory over later ones.

@ilevkivskyi
Copy link
Member

@gvanrossum This is a nice short summary. However I noticed the order in your list is a bit different from the one documented in https://mypy.readthedocs.io/en/latest/running_mypy.html#how-imports-are-found, namely MYPYPATH stays first. So what should we fix, the docs or the implementation?

@martinstein
Copy link

Okay, thanks for the -v suggestion. I got @MarkCBell 's test-case working... it turns out naming the package in the test-case mypy was not ideal (since it conflicts with mypy itself, apparently). As soon as you rename that package to something else, e.g. mypyx then it works as expected.

Now I'll try to figure out why things are not working in my more advanced setup.

@martinstein
Copy link

Actually, sorry, please scratch my last message. I'm still unable to get that test-case working...

@martinstein
Copy link

The verbose log output (after renaming the test-package to mypyx to avoid conflicts) doesn't give any indication about the stubs directory:

LOG:  Mypy version 0.630
LOG:  Could not load cache for test-main: .mypy_cache\3.6\test-main.meta.json
LOG:  Metadata not found for test-main
LOG:  Parsing ./tests/test-main.py (test-main)
LOG:  Metadata fresh for mypyx: file C:\Users\Martin\Downloads\mypy-test-master\mypy-master\mypyx\__init__.py
LOG:  Could not load cache for mypyx.main: .mypy_cache\3.6\mypyx\main.meta.json
LOG:  Metadata not found for mypyx.main
LOG:  Parsing C:\Users\Martin\Downloads\mypy-test-master\mypy-master\mypyx\main.py (mypyx.main)
LOG:  Metadata fresh for builtins: file c:\tools\py37-test\lib\site-packages\mypy\typeshed\stdlib\3\builtins.pyi
LOG:  Metadata fresh for typing: file c:\tools\py37-test\lib\site-packages\mypy\typeshed\stdlib\3\typing.pyi
LOG:  Metadata fresh for abc: file c:\tools\py37-test\lib\site-packages\mypy\typeshed\stdlib\3\abc.pyi
LOG:  Metadata fresh for types: file c:\tools\py37-test\lib\site-packages\mypy\typeshed\stdlib\3\types.pyi
LOG:  Metadata fresh for sys: file c:\tools\py37-test\lib\site-packages\mypy\typeshed\stdlib\3\sys.pyi
LOG:  Metadata fresh for collections: file c:\tools\py37-test\lib\site-packages\mypy\typeshed\stdlib\3\collections\__init__.pyi
LOG:  Metadata fresh for _importlib_modulespec: file c:\tools\py37-test\lib\site-packages\mypy\typeshed\stdlib\3\_importlib_modulespec.pyi
LOG:  Metadata fresh for importlib.abc: file c:\tools\py37-test\lib\site-packages\mypy\typeshed\stdlib\3\importlib\abc.pyi
LOG:  Metadata fresh for collections.abc: file c:\tools\py37-test\lib\site-packages\mypy\typeshed\stdlib\3\collections\abc.pyi
LOG:  Metadata fresh for importlib: file c:\tools\py37-test\lib\site-packages\mypy\typeshed\stdlib\3\importlib\__init__.pyi
LOG:  Metadata fresh for os: file c:\tools\py37-test\lib\site-packages\mypy\typeshed\stdlib\3\os\__init__.pyi
LOG:  Metadata fresh for importlib.util: file c:\tools\py37-test\lib\site-packages\mypy\typeshed\stdlib\3\importlib\util.pyi
LOG:  Metadata fresh for io: file c:\tools\py37-test\lib\site-packages\mypy\typeshed\stdlib\3\io.pyi
LOG:  Metadata fresh for posix: file c:\tools\py37-test\lib\site-packages\mypy\typeshed\stdlib\3\posix.pyi
LOG:  Metadata fresh for os.path: file c:\tools\py37-test\lib\site-packages\mypy\typeshed\stdlib\3\os\path.pyi
LOG:  Metadata fresh for importlib.machinery: file c:\tools\py37-test\lib\site-packages\mypy\typeshed\stdlib\3\importlib\machinery.pyi
LOG:  Metadata fresh for codecs: file c:\tools\py37-test\lib\site-packages\mypy\typeshed\stdlib\2and3\codecs.pyi
LOG:  Metadata fresh for mmap: file c:\tools\py37-test\lib\site-packages\mypy\typeshed\stdlib\2and3\mmap.pyi
LOG:  Loaded graph with 21 nodes (0.027 sec)
LOG:  Found 4 SCCs; largest has 18 nodes
LOG:  Processing 1 queued fresh SCCs
LOG:  Processing SCC singleton (mypyx.main) as inherently stale
mypyx\main.py:1: error: Function is missing a type annotation
mypyx\main.py:5: error: Function is missing a type annotation
mypyx\main.py:9: error: Function is missing a type annotation
LOG:  Deleting mypyx.main C:\Users\Martin\Downloads\mypy-test-master\mypy-master\mypyx\main.py .mypy_cache\3.6\mypyx\main.meta.json .mypy_cache\3.6\mypyx\main.data.json
LOG:  Processing 1 queued fresh SCCs
LOG:  Processing SCC singleton (test-main) as inherently stale with stale deps (mypyx.main)
LOG:  Deleting test-main C:\Users\Martin\Downloads\mypy-test-master\mypy-master\tests\test-main.py .mypy_cache\3.6\test-main.meta.json .mypy_cache\3.6\test-main.data.json
LOG:  No fresh SCCs left in queue
LOG:  Build finished in 0.168 seconds with 21 modules, and 3 errors

@MarkCBell
Copy link
Contributor Author

@martinstein Thank you for your comments, particularly about the name conflict. To ease debugging I have pushed a new version to the repository, following your suggestion, in which the folders have been renamed to mypyx. However I am still seeing the same incorrect output, namely:

mypyx\main.py:1: error: Function is missing a type annotation
mypyx\main.py:5: error: Function is missing a type annotation
mypyx\main.py:9: error: Function is missing a type annotation

@MarkCBell
Copy link
Contributor Author

@gvanrossum Thank you for letting me know about the order in which locations are checked. After renaming my example to mypyx, I have verified that if the ./stubs/mypyx folder is moved to the current folder that I am in when I start mypy (location 1) or the folder where sources given on command line live (location 2) then mypy produces the expected output.

However, if I try to achieve this by setting MYPYPATH or mypy_path within mypy.ini to point to ./stubs/ then the mypy gives an incorrect output and the log produced by mypy -v ./tests/test-main.py contains LOG: Metadata not found for mypyx.main.

@gvanrossum
Copy link
Member

@ilevkivskyi

@gvanrossum This is a nice short summary. However I noticed the order in your list is a bit different from the one documented in https://mypy.readthedocs.io/en/latest/running_mypy.html#how-imports-are-found, namely MYPYPATH stays first. So what should we fix, the docs or the implementation?

This deviation from the docs may have been introduced by @ethanhs's PEP 561 work. I also recalled $MYPYPATH coming first, but the current source code (which I have been studying in depth for #5691) places the directories containing user code first. I think we should fix the code -- just like Python puts $PYTHONPATH first, mypy should put $MYPYPATH first. But let's see if @ethanhs had a reason for the change?

@ilevkivskyi
Copy link
Member

OK, let's wait for his response.

@MarkCBell
Copy link
Contributor Author

So I think I may have found the source of the issue. Exactly as @gvanrossum said, the current directory is checked before $MYPYPATH or mypy_path from mypy.ini. Therefore if you run mypy tests/test-main.py within the top level of this project, it tries to find stubs for mypyx.main and spots that within the current folder is a folder called mypyx (with an __init__,py) and within that is a main.py. So it found a hit and - even though that hit doesn't work - it looks there for stubs and nowhere else. So it never checks $MYPYPATH or mypy_path.

@MarkCBell
Copy link
Contributor Author

I've taken a look at mypy's modulefinder.py and it appears to be behaving slightly differently to what has been previously mentioned.

When assembling the list of places to check it inserts the current directory at the beginning
of the python_path list. However it uses the reverse of that list. Therefore the actual order of locations that are checked begins:

  1. directories where sources given on command line live
  2. current directory
  3. $MYPYPATH
  4. mypy_path from mypy.ini

Therefore if you run mypy ./tests/test-main.py ./stubs/ then stubs is a directory given on command line. Since these are checked before the current directory, mypy then finds the correct stub files and produces the correct output.

This could be solved by not adding the current directory to the places to search. And something along these lines is suggested in the comment right above the line which adds the directory.

        # Do this even if running as a file, for sanity (mainly because with
        # multiple builds, there could be a mix of files/modules, so its easier
        # to just define the semantics that we always add the current director
        # to the lib_path
        # TODO: Don't do this in some cases; for motivation see see
        # https://github.com/python/mypy/issues/4195#issuecomment-341915031

@emmatyping
Copy link
Member

Hm, PEP 561 states things like MYPYPATH go after user code, even though it acknowledges these are supposed to be on the front of the PATH... I think the PEP should be re-worded to be clearer (MYPYPATH goes BEFORE user code) and the mypy implementation should be corrected to follow the ordering as laid out in the PEP. That is to say the mypy path should go before user code, which should be as simple as swapping the order here: https://github.com/python/mypy/blob/master/mypy/modulefinder.py#L162.

@gvanrossum
Copy link
Member

This could be solved by not adding the current directory to the places to search. And something along these lines is suggested in the comment right above the line which adds the directory.

It would be nice if we could just entirely skip the current directory, but there are cases (like the -m and -p options) where users just expect the current directory to work. It's a problem with Python itself too (having '' on sys.path is an endless source for debate, bug reports etc.). I'm not sure how we would move forward on that. Perhaps we could add it only if there are no source files and no mypy path?

@AgarwalPragy
Copy link

AgarwalPragy commented Oct 11, 2018

# file.py
def foo(name):
    print(name.lower())

foo(10)
# file.pyi
def foo(name: str) -> None: ...

No other files in directory

$ mypy --strict .

succeeds without errors

$ mypy --strict file.py
file.py:1: error: Function is missing a type annotation
file.py:4: error: Call to untyped function "foo" in typed context
$ mypy --strict file.py file.pyi
file.pyi: error: Duplicate module named 'file'

Even with both the .py and .pyi being in the same directory, mypy doesn't verify if the code agrees with the types specified in the stub. It simply checks that the stub doesn't have any errors.

This makes stub files a way of "lying" to mypy. Is there a way to keep the code and type info in separate files (for readability) while still being able to check code against the type info?

@emmatyping
Copy link
Member

@AgarwalPragy that is a different issue than this one. I think you want #5028

@AmjadHD
Copy link

AmjadHD commented Feb 18, 2019

Can someone tell me what is the fix to this issue ?

@emmatyping
Copy link
Member

@AmjadHD there was a fix to mypy made in October, please make sure you are up to date. In addition, please ask questions on our gitter instead of on closed issues http://gitter.im/python/typing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

7 participants