python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.21k stars 2.78k forks source link

Editable installs are not recognized as having typing when using py.typed (setuptools v64 issue) #13392

Open cmeyer opened 2 years ago

cmeyer commented 2 years ago

Bug Report

mypy does not recognize that libraries installed as editable using pip are fully typed even though the py.typed file is correctly included.

To Reproduce

Working script which installs a NON-editable version of library:

# create python environment with pip
rm -rf nionui
git clone https://github.com/nion-software/nionui.git
pushd nionui
git checkout 0.6.4
python -m pip install mypy numpy imageio types-pytz types-tzlocal types-setuptools
# the next line is the only change between these two scripts
python -m pip install nionutils==0.4.4
mypy --namespace-packages --ignore-missing-imports --follow-imports=silent --install-types --non-interactive --strict --no-warn-redundant-casts --no-warn-unused-ignores -p nion.ui -p nionui_app.nionui_examples
popd
# success

Failing version which installs an editable version of a library:

# create python environment with pip
rm -rf nionui
git clone https://github.com/nion-software/nionui.git
pushd nionui
git checkout 0.6.4
python -m pip install mypy numpy imageio types-pytz types-tzlocal types-setuptools nionutils
# the next line is the only change between these two scripts
python -m pip install -e git+https://github.com/nion-software/nionutils.git@0.4.4#egg=nionutils
mypy --namespace-packages --ignore-missing-imports --follow-imports=silent --install-types --non-interactive --strict --no-warn-redundant-casts --no-warn-unused-ignores -p nion.ui -p nionui_app.nionui_examples
popd
# failure

Expected Behavior

These should work the same. The source client and underlying library are the same in both cases except one is installed as an editable install.

Actual Behavior

The failing version gives type errors:

nion/ui/DrawingContext.py:790: error: Returning Any from function declared to return "Optional[str]"
nion/ui/DrawingContext.py:794: error: Returning Any from function declared to return "Optional[str]"
nion/ui/DrawingContext.py:798: error: Returning Any from function declared to return "Optional[str]"
nion/ui/CanvasItem.py:2780: error: Class cannot subclass "Observable" (has type "Any")
nion/ui/UserInterface.py:3217: error: Returning Any from function declared to return "int"
nion/ui/UserInterface.py:3224: error: Returning Any from function declared to return "int"
nion/ui/UserInterface.py:3620: error: Class cannot subclass value of type "Any"
nion/ui/UserInterface.py:3634: error: Class cannot subclass value of type "Any"
nion/ui/Widgets.py:382: error: Returning Any from function declared to return "AbstractSet[int]"
nion/ui/Declarative.py:949: error: Class cannot subclass "Observable" (has type "Any")
nion/ui/Declarative.py:1112: error: Class cannot subclass "Observable" (has type "Any")
nion/ui/GridCanvasItem.py:256: error: Returning Any from function declared to return "int"
Found 12 errors in 6 files (checked 51 source files)

Your Environment

Also note: this behavior seems to have changed around 2022-08-10 to 2022-08-11. Something in the Python ecosystem seems to have changed. Nevertheless, this seems to be a bug in mypy since it is all about whether py.typed is found or not.

lawrence-law commented 2 years ago

I also experienced this problem and traced it down to setuptools v64 causing the issue. This version of setuptools seems to cause editable packages to no longer be installed with an egg-link file and now uses another mechanism. See release notes here: https://setuptools.pypa.io/en/latest/history.html#v64-0-0

If the editable package belongs to you, you could modify your pyproject.toml like this:

requires = [
  "setuptools==63.4.3",
  "wheel"
]

Previously I had setuptools without any version locking so I presume it was using setuptools 64.0.1.

This seems to workaround the issue (albeit withholding setuptools versions) although perhaps a fix does sit with Mypy.

JacobHayes commented 2 years ago

Setting export SETUPTOOLS_ENABLE_FEATURES="legacy-editable" (eg: with direnv or in Dockerfiles) and reinstalling things also worked for me (and should work if you can't update the project's pyproject.toml).

hauntsaninja commented 2 years ago

Is there documentation on what exactly setuptools is doing now? mypy 0.971 should just be using sys.path, which is, y'know, the standard way of making packages findable

JacobHayes commented 2 years ago

I think the relevant PEP is PEP 660, but I'm not sure if it has all the details. With the new wheels, the site-packages directory ends up with files like this:

__editable__.org_pkg-2.1.0.pth # imports __editable___org_pkg_2_1_0_finder and calls install()
__editable___org_pkg_2_1_0_finder.py # some Finder magic w/ refs to the source path
org_pkg-2.1.0.dist-info/ # dist info like "normal" wheels

instead of an org-pkg.egg-link file (I don't know why this would break mypy though).

I can post back with a minimum reproducing example shortly.

hauntsaninja commented 2 years ago

Oh, so setuptools has switched to using some import hook based approach instead of static entries in pth files? Note that the super standard pth file static directory install that everyone has been using for decades is perfectly valid under PEP 660.

I don't think there's any way for IDEs and static analysis tools to support import hooks. See https://mail.python.org/archives/list/typing-sig@python.org/thread/IIVBPYDZR5T5BGPAWFVYS5ZPYDXGVHQN/#OSWHT5VSRGKPSPYD7PQWR2M4OCSL5WO3 where maintainers of PyCharm, VSCode, Pyright, Pyre, etc are all on the same page about this.

This feels like a very bold move from setuptools. I'd recommend contacting them about this.

JacobHayes commented 2 years ago

I'd recommend contacting them about this.

Done: https://github.com/pypa/setuptools/issues/3518, though I'm prepared for them to say similar. 😉

hauntsaninja commented 2 years ago

Yeah, well, so it goes. It's one thing if it was just mypy, but this will break all the tools 🤷

If a setuptools maintainer sees this, I recommend reading and then replying to that thread on typing-sig rather than discussing here.

JacobHayes commented 2 years ago

For posterity, I made a repro repo here which shows the issues w/ mypy and pylint for these installs.

rojvv commented 1 year ago

I think I am facing the exact same problem.

hauntsaninja commented 1 year ago

+1 comments aren't particularly useful here. But if you feel a need to post one, consider posting it on the setuptools issue instead.

I believe their current recommended solution is SETUPTOOLS_ENABLE_FEATURES="legacy-editable" env var, although they may eventually remove it.

rojvv commented 1 year ago

Eventually remove it? Like this issue persisting without even having a legacy fix?

ethanhs commented 1 year ago

I hate to say it but the changes the setuptools maintainers have made make supporting editable installs in mypy a lot more complicated, perhaps intractable in an efficient manner. We previously relied on static path calculation for this, but they no longer guarantee that (see attention note below linked section).

Therefore I regretfully propose that we drop support for editable installs. By that I mean just disable tests, and document that if you really need editable install support, you should use a src/ layout as the setuptools implementation uses a static .pth file in that case and it might work.

For now I suggest we remove the tests for editable installs.

erictraut commented 1 year ago

The approach we've taken with pyright and pylance is to document some workarounds for those who want to use editable installs. So far, everyone has had success following these suggested workarounds. Feel free to use them in the mypy documentation and tests if you find them useful.

alexei commented 10 months ago

FWIW --config-settings editable_mode=compat (or strict) did not work for me. However, I found it working if I specify py.typed as package data:

[tool.setuptools.package-data]
"*" = ["py.typed", "*.pyi"]

Edit: I just found future versions of setuptools are going to include these files by default, see https://github.com/pypa/setuptools/pull/4021

earonesty commented 5 months ago

1 when installing, use editable mode

--config-settings editable_mode=strict`
``  
2. your package should export py.typed 

[tool.setuptools.package-data] "" = ["py.typed", ".pyi"]



both are needed.

if someone winds up here because poetry doesn't support the `build` package config settings:

https://github.com/python-poetry/poetry/issues/8909

... it still doesn't
akaihola commented 5 months ago

@earonesty, thanks for pointing this out! Also please forgive me for reformatting your comment for readability:

  1. when installing, use editable mode
    --config-settings editable_mode=strict
  2. your package should export py.typed
    [tool.setuptools.package-data]
    "" = ["py.typed", ".pyi"]

Both are needed.

If someone winds up here because poetry doesn't support the build package config settings: python-poetry/poetry#8909 ...it still doesn't

mohit2152sharma commented 2 months ago

For anyone reaching here and trying the following:

Setting export SETUPTOOLS_ENABLE_FEATURES="legacy-editable" (eg: with direnv or in Dockerfiles) and reinstalling things also worked for me (and should work if you can't update the project's pyproject.toml).

Note that it might not work with latest pip versions as the don't support this behaviour.

Newer versions of pip no longer run the fallback command python setup.py develop when the pyproject.toml file is present. This means that setting the environment variable SETUPTOOLS_ENABLE_FEATURES="legacy-editable" will have no effect when installing a package with pip.

Reference: https://setuptools.pypa.io/en/latest/userguide/development_mode.html#legacy-behavior

mb6ockatf commented 1 month ago

same thing

(wonder who marked this comment as duplicate:

it is sort of disrespect to shut people up by hiding their comment like that, without any argument/explanation on why)

ethanhs commented 1 month ago

Perhaps we should provide a warning if we see a __editable__*.pth file in any site directory, and link to some documentation about this behavior. We aren't likely to follow pth files with runtime imports, so I think that is the best we can do.

pelson commented 2 weeks ago

Perhaps we should provide a warning if we see a editable*.pth file in any site directory, and link to some documentation about this behavior. We aren't likely to follow pth files with runtime imports, so I think that is the best we can do.

This makes sense to me. Honestly, it should perhaps do the same thing whenever sys.meta_hook is set.

Another thing that could also work is to allow the mypy plugin system to emulate the meta_hook behaviour by allowing a plugin to define the source code of an import.

For me personally, I want static analysis to be done on the code that actually gets run, and I'd be willing to pay the price of having plugins that minimally runs the interpreter to resolve that code correctly.