Skip to content

Support setuptools v64's PEP660 editable install mode#1355

Closed
mrcljx wants to merge 1 commit intoheroku:mainfrom
memfault:fix-editable-on-pip-21.3
Closed

Support setuptools v64's PEP660 editable install mode#1355
mrcljx wants to merge 1 commit intoheroku:mainfrom
memfault:fix-editable-on-pip-21.3

Conversation

@mrcljx
Copy link
Copy Markdown
Contributor

@mrcljx mrcljx commented Aug 16, 2022

We were running into ModuleNotFoundError errors when trying
to run our application. After some investigation we noticed
that newer setuptools places new files site-packages when
using editable installs (I guess only in some situations):

mylib-0.0.0.dist-info
__editable__.mylib-0.0.0.pth
__editable___mylib_0_0_0_finder.py

The *.pth file in that case contains this stub:

import __editable___mylib_0_0_0_finder; __editable___mylib_0_0_0_finder.install()

while the py module is generated from a template and contains this line:

MAPPING = {'mylib': '/tmp/build_892299be/py-packages/mylib/src/mylib'}

This all seems to be related to setuptools 63 and might be mitigated
by the compat mode in 64. I assume we just started to see this
issue because of the upgrade from 60 to 63 yesterday.

This PR updates the buildpack to also patch the paths in those files.
Please let me know if you have any recommendations for tests to add.

@mrcljx mrcljx requested a review from a team as a code owner August 16, 2022 13:38
@edmorley
Copy link
Copy Markdown
Member

edmorley commented Aug 16, 2022

@mrcljx Hi! Thank you for the write up and opening a PR :-)

Would I be correct in thinking that the package being installed in editable mode has a pyproject.toml so is being installed via the new Pip build isolation mode? If so, I believe the cause of this is that in isolation mode, the latest version of setuptools is pulled in (via the build backend dependency ranges specified in pyproject.toml, unless they explicitly pin the setuptools version), which means it would be using a newer version than what the buildpack uses for the global install.

If that's the case, this may even be unrelated to #1344 and instead just have been caused by setuptools v64 having been released around the same time (11th Aug):
https://setuptools.pypa.io/en/latest/history.html#v64-0-0

Either way, it seems we'll need this change to adapt for setuptools's new editable mode. I'll see if I can come up with a test.

@mrcljx
Copy link
Copy Markdown
Contributor Author

mrcljx commented Aug 16, 2022

If that's the case, this may even be unrelated to #1344 and instead just have been caused by setuptools v64 having been released around the same time (11th Aug): setuptools.pypa.io/en/latest/history.html#v64-0-0

That would actually make a lot of sense as we started seeing failures last Thu/Fri (so around Aug 11) and not just yesterday - the patch here didn't seem to fully solve it yet for us either.

@mrcljx
Copy link
Copy Markdown
Contributor Author

mrcljx commented Aug 16, 2022

Thanks for the hint! I looked a bit further into it: so these finder files are really only created when setuptools comes to the conclusion that the project setup is not trivial. After deleting setup.py and fully embracing pyproject.toml the *.pth files actually became simple paths again (and no _finder.py files were emitted).

@edmorley edmorley changed the title Fix paths in editable finder modules not being patched Support setuptools v64's PEP660 editable install mode Aug 17, 2022
@edmorley
Copy link
Copy Markdown
Member

so these finder files are really only created when setuptools comes to the conclusion that the project setup is not trivial

Yeah - looks like for this issue to occur, all of the following has to be true:

  • the package is being installed as an editable install
  • the package has a pyproject.toml with a build-system table defined with the setuptools version range set so that setuptools v64+ gets pulled in (which includes the new PEP660 editable install feature rather than legacy editable mode)
  • the install is being performed in "lenient" editable mode (the current setuptools default), not "strict" editable mode
  • the logic here decides the project layout is complex enough, that the Python finder scripts are also created, rather than just using the static .pth file

It seems like workarounds (until this is merged/released), are:

  1. Adjust the package config so setuptools no longer deems the config as "complex" (eg by removing setup.py entirely, as you did)
  2. Use "strict" editable install mode instead (I'm presuming; I've not tested)
  3. Switch back to legacy editable behaviour via the env var SETUPTOOLS_ENABLE_FEATURE=legacy-editable
  4. Use setuptools<64 in the build-system table in pyproject.toml to force the use of older setuptools

@edmorley
Copy link
Copy Markdown
Member

@mrcljx Thank you for the PR! Our CI needs secrets to run (so doesn't work from forks), and I also needed to add tests and changelog entry - so I had to merge this via a separate PR, but I credited you in the PR description and via the Co-authored-by: commit metadata (which GitHub uses to populate the attribution on the commit: b2dfe73). I'll release this change shortly :-)

@mrcljx
Copy link
Copy Markdown
Contributor Author

mrcljx commented Aug 17, 2022

Thanks @edmorley - appreciate you taking over.

pull bot pushed a commit to eloisetwaine/heroku-buildpack-python that referenced this pull request Aug 17, 2022
…tall mode (heroku#1357)

On Heroku, the application source directory exists at a different path at build time (`/tmp/build_<hash>`), than it does at runtime (`/app`). As such, the buildpack has to perform path rewriting via `.profile.d` scripts at runtime, to ensure any packaging related absolute paths in the build output are rewritten to reference the new path. (Thankfully this awful path rewriting will no longer be necessary in the future with CNBs.)

Previously the only files this path rewriting needed to update were the `*.pth` and `*.egg-link` files in `site-packages` created by setuptools when performing editable installs.

However setuptools v64 added support for PEP660 based editable install hooks:
https://setuptools.pypa.io/en/latest/history.html#v64-0-0
https://peps.python.org/pep-0660/

This feature is only used for projects that have a `pyproject.toml`, and for such projects, [if the config is deemed complex enough](https://github.com/pypa/setuptools/blob/d03da04e024ad4289342077eef6de40013630a44/setuptools/command/editable_wheel.py#L359-L368), setuptools creates a new [finder script](https://github.com/pypa/setuptools/blob/23d455c532fca91e6f00aa5950000739b058b6e5/setuptools/command/editable_wheel.py#L740-L809) in `site-packages` that dynamically handles package resolution. (Simpler configs get a static `.pth` file, which works fine with our existing path rewriting.)

This new file embeds the absolute path of the source directory at build time, so must be rewritten too. It has a filename of form: `__editable___my_package_0_0_1_finder.py`

As such, this PR adds support for rewriting these files, along with updated test fixtures to provide coverage of `pyproject.toml` based editable installs (alongside the existing `setup.py` based test fixture).

Whilst writing the new test, I encountered a difference in behaviour with setuptool's new editable install mode, which meant the fixtures had to be nested inside a `packages/` directory in order to avoid an `ImportError` due to the fact that the Python buildpack currently sets `PYTHONPATH=/app` at runtime. See:
pypa/setuptools#3535

Note:
- The Python buildpack doesn't yet globally install this newer setuptools v64 release, since by design it pins to a specific version to prevent upstream changes from breaking apps overnight. (The version was recently updated to 63.4.3 in heroku#1344.)
- However, for packages that have a `pyproject.toml` pip uses the approach described in PEP518, which uses an [isolated build environment](https://pip.pypa.io/en/stable/reference/build-system/pyproject-toml/#build-isolation). This environment typically pulls in latest setuptools (though this is controllable by the package owner via `[build-system]` in `pyproject.toml`), overriding our pinned global setuptools install.
- As such, even though we're not using v64 globally, users can still be broken by the upstream release.

A big thanks to @mrcljx for the initial PR in heroku#1355 on which this was based :-)

Closes heroku#1355.
GUS-W-11608693.

Co-authored-by: Marcel Jackwerth <marceljackwerth@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants