Ran into a bit of a python environment error when trying to install unruly packages (where they explicitly import dependency modules in their setup.py to see if it exists).
The current poetry environment package install process uses the python executable of whatever is running poetry and never actually the python of the venv itself.
Because here, if no executable argument is provided to build_venv(), it defaults to the executable running poetryโcould be anything anywhere with any packages!โ(via executable or sys.executable). If we are using an explicit env set by poetry env use which isn't the same binary running poetry itself, the install process will never have a usable environment we expect:
Proper suggested fix: use the current value of the project's env python setting for all installs. (left as an exercise for the reader)
Hack fix: change the executable discovery line to at least consult the _environment_ for python before defaulting to the current executable: executable or shutil.which("python3") or sys.executable, so it will at least be able to install when attempted inside the project's own poetry shell session.
Hello @mattsta,
what's your problem with the current behavior?
fin swimmer
short version:
(where they explicitly import dependency modules in their setup.py to see if it exists).
longer version:
Basically, some packages will only install if they have their dependencies available at build time. So, the system python where poetry is installed will never be able to complete the setup.py since the deps are installed in the poetry venv environment, not on the system python path where we're using poetry from.
On a system with 3+ python versions installed, always only using the poetry-system-script python for the temporary build-venv leads to confusion because the the project venv python (or even just the environment $PATH) seems to be never used for installing.
and yeah, the third party packages are doing bad things by directly importing dependence in their setup.py, but it is also slightly confusing how poetry builds using the python of the poetry script and not the python environment of the project venv itself (or even the python of the current shell $PATH).
@mattsta Are those projects public (Github or anything like that)? I guess those projects need to use a pyproject.toml to specify build dependencies and clean the setup.py.
you'd think so, right?
But it still seems confusing to build packages using the python of poetry instead of the python of the environment (which could be multiple major releases different, maybe pyenv/conda has deployed 10 python versions on a system, etc). Unless I've been using poetry wrong, poetry isn't installed inside the project environment, it's installed on the system python, but builds should use the project environment (or, at a minimum the python from the $PATH), not whatever version/environment of python is #! in the poetry runner script?
Example live package with broken setup.py: https://github.com/facebookresearch/detectron2/blob/master/setup.py which fails doing poetry add git+https://github.com/facebookresearch/detectron2 because they are directly importing the torch dependency to do a "version check" and we can't hack around their build problem unless we pollute our system python with their dependency import package first.
Hello @mattsta,
in your case poetry needs to build the package twice.
The first one is needed to fetch metadata like name, version and dependencies. poetry tries first to parse these information from the setup.py directly. Only if this fails, it build the wheel package to get the information from there. Usually these metadata should be same under every python version. (This might not always be true, but it's the best guess we can make). So there is no need to switch to the python version used in the venv.
The second one is needed to build the wheel and install it. Here poetry uses the python version it finds within the venv.
In both cases a _new_ build environment is created. If no pyproject.toml is provided for the package (as @sinoroc suggested) the newly created build env just contains setuptools and wheel as package.
So as it stands now, poetry cannot do anything here. It doesn't have to do something with which python poetry uses. But there are things that the maintainers of the package must and can do:
pyproject.toml that specify the build dependecies according to PEP 518. That's exactly the use case for thatfin swimmer
I don't understand that bit:
But it still seems confusing to build packages using the python of poetry instead of the python of the environment (which could be multiple major releases different, maybe pyenv/conda has deployed 10 python versions on a system, etc). Unless I've been using poetry wrong, poetry isn't installed inside the project environment, it's installed on the system python, but builds should use the project environment (or, at a minimum the
pythonfrom the$PATH), not whatever version/environment of python is#!in the poetry runner script?
I do not know poetry's code base well enough. My gut feeling though, is that if it were the case the issue would show up much more often, and it would be a big problem in general. I have not managed to clearly identify what your proof is that _poetry_ is not using an adequate Python interpreter for that step.
This is my debug attempt:
poetry add -vvv git+https://github.com/facebookresearch/detectron2.git
$ poetry add -vvv git+https://github.com/facebookresearch/detectron2.git
Using virtualenv: /home/sinoroc/.cache/pypoetry/virtualenvs/thing-uXt00XdK-py3.8
PackageInfo: PEP517 build failed: Command ['/tmp/tmpzsnzufm1/.venv/bin/python', '-'] errored with the following return code 1, and output:
WARNING: You are using pip version 20.3.1; however, version 20.3.3 is available.
You should consider upgrading via the '/tmp/tmpzsnzufm1/.venv/bin/python -m pip install --upgrade pip' command.
Traceback (most recent call last):
File "<stdin>", line 6, in <module>
File "/tmp/tmpzsnzufm1/.venv/lib/python3.8/site-packages/pep517/meta.py", line 53, in build
_prep_meta(hooks, env, dest)
File "/tmp/tmpzsnzufm1/.venv/lib/python3.8/site-packages/pep517/meta.py", line 28, in _prep_meta
reqs = hooks.get_requires_for_build_wheel({})
File "/tmp/tmpzsnzufm1/.venv/lib/python3.8/site-packages/pep517/wrappers.py", line 160, in get_requires_for_build_wheel
return self._call_hook('get_requires_for_build_wheel', {
File "/tmp/tmpzsnzufm1/.venv/lib/python3.8/site-packages/pep517/wrappers.py", line 255, in _call_hook
self._subprocess_runner(
File "/tmp/tmpzsnzufm1/.venv/lib/python3.8/site-packages/pep517/wrappers.py", line 75, in quiet_subprocess_runner
check_output(cmd, cwd=cwd, env=env, stderr=STDOUT)
File "/usr/lib/python3.8/subprocess.py", line 411, in check_output
return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
File "/usr/lib/python3.8/subprocess.py", line 512, in run
raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command '['/tmp/tmpzsnzufm1/.venv/bin/python', '/tmp/tmpzsnzufm1/.venv/lib/python3.8/site-packages/pep517/_in_process.py', 'get_requires_for_build_wheel', '/tmp/tmpn_ipmtz7']' returned non-zero exit status 1.
input was : import pep517.build
import pep517.meta
path='/tmp/pypoetry-git-detectron2gwunvtzg'
system=pep517.build.compat_system(path)
pep517.meta.build(source_dir=path, dest='/tmp/tmpzsnzufm1/dist', system=system)
Stack trace:
10 ~/.pex/installed_wheels/126dc213d0bf69ccd3cd34ceea39c718e5570c87/clikit-0.6.2-py2.py3-none-any.whl/clikit/console_application.py:131 in run
129โ parsed_args = resolved_command.args
130โ
โ 131โ status_code = command.handle(parsed_args, io)
132โ except KeyboardInterrupt:
133โ status_code = 1
9 ~/.pex/installed_wheels/126dc213d0bf69ccd3cd34ceea39c718e5570c87/clikit-0.6.2-py2.py3-none-any.whl/clikit/api/command/command.py:120 in handle
118โ def handle(self, args, io): # type: (Args, IO) -> int
119โ try:
โ 120โ status_code = self._do_handle(args, io)
121โ except KeyboardInterrupt:
122โ if io.is_debug():
8 ~/.pex/installed_wheels/126dc213d0bf69ccd3cd34ceea39c718e5570c87/clikit-0.6.2-py2.py3-none-any.whl/clikit/api/command/command.py:171 in _do_handle
169โ handler_method = self._config.handler_method
170โ
โ 171โ return getattr(handler, handler_method)(args, io, self)
172โ
173โ def __repr__(self): # type: () -> str
7 ~/.pex/installed_wheels/a5dad1dbb8e8a2e3c5953d0d8981b5dfd4d855a6/cleo-0.8.1-py2.py3-none-any.whl/cleo/commands/command.py:92 in wrap_handle
90โ self._command = command
91โ
โ 92โ return self.handle()
93โ
94โ def handle(self): # type: () -> Optional[int]
6 ~/.pex/installed_wheels/9d866443fc540d58e14e5eba886f313291c0c3e3/poetry-1.1.4-py2.py3-none-any.whl/poetry/console/commands/add.py:107 in handle
105โ return 0
106โ
โ 107โ requirements = self._determine_requirements(
108โ packages,
109โ allow_prereleases=self.option("allow-prereleases"),
5 ~/.pex/installed_wheels/9d866443fc540d58e14e5eba886f313291c0c3e3/poetry-1.1.4-py2.py3-none-any.whl/poetry/console/commands/init.py:320 in _determine_requirements
318โ return requires
319โ
โ 320โ requires = self._parse_requirements(requires)
321โ result = []
322โ for requirement in requires:
4 ~/.pex/installed_wheels/9d866443fc540d58e14e5eba886f313291c0c3e3/poetry-1.1.4-py2.py3-none-any.whl/poetry/console/commands/init.py:410 in _parse_requirements
408โ pair["extras"] = extras
409โ
โ 410โ package = Provider.get_package_from_vcs(
411โ "git", url.url, rev=pair.get("rev")
412โ )
3 ~/.pex/installed_wheels/9d866443fc540d58e14e5eba886f313291c0c3e3/poetry-1.1.4-py2.py3-none-any.whl/poetry/puzzle/provider.py:202 in get_package_from_vcs
200โ revision = git.rev_parse(reference, tmp_dir).strip()
201โ
โ 202โ package = cls.get_package_from_directory(tmp_dir, name=name)
203โ package._source_type = "git"
204โ package._source_url = url
2 ~/.pex/installed_wheels/9d866443fc540d58e14e5eba886f313291c0c3e3/poetry-1.1.4-py2.py3-none-any.whl/poetry/puzzle/provider.py:285 in get_package_from_directory
283โ cls, directory, name=None
284โ ): # type: (Path, Optional[str]) -> Package
โ 285โ package = PackageInfo.from_directory(path=directory).to_package(
286โ root_dir=directory
287โ )
1 ~/.pex/installed_wheels/9d866443fc540d58e14e5eba886f313291c0c3e3/poetry-1.1.4-py2.py3-none-any.whl/poetry/inspection/info.py:541 in from_directory
539โ info = cls.from_setup_files(path)
540โ else:
โ 541โ info = cls._pep517_metadata(path)
542โ except PackageInfoError:
543โ if not info:
PackageInfoError
Unable to determine package info for path: /tmp/pypoetry-git-detectron2gwunvtzg
Fallback egg_info generation failed.
Command ['/tmp/tmpzsnzufm1/.venv/bin/python', 'setup.py', 'egg_info'] errored with the following return code 1, and output:
Traceback (most recent call last):
File "setup.py", line 10, in <module>
import torch
ModuleNotFoundError: No module named 'torch'
at ~/.pex/installed_wheels/9d866443fc540d58e14e5eba886f313291c0c3e3/poetry-1.1.4-py2.py3-none-any.whl/poetry/inspection/info.py:502 in _pep517_metadata
498โ try:
499โ venv.run("python", "setup.py", "egg_info")
500โ return cls.from_metadata(path)
501โ except EnvCommandError as fbe:
โ 502โ raise PackageInfoError(
503โ path, "Fallback egg_info generation failed.", fbe
504โ )
505โ finally:
506โ os.chdir(cwd.as_posix())
Seems fairly normal to me, _poetry_ seems to use build isolation to try and build and/or get the metadata out of the cloned project directory. And it fails since there is no torch in that build environment.
I cloned the repository and then tried poetry add ../detectron2 and it gives the same error, as expected. So I added the following pyproject.toml and then poetry add ../detectron2 works without any issue:
[build-system]
build-backend = 'setuptools.build_meta'
requires = [
'setuptools',
'torch',
]
See https://github.com/facebookresearch/detectron2/pull/510 for why adding a pyproject.toml to that repository might be problematic (although, pytorch is also being annoying for seemingly no insurmountable reason on that topic).
Usually these metadata should be same under every python version. (This might not always be true, but it's the best guess we can make). So there is no need to switch to the python version used in the venv.
but but but... using the project environment as the first build step seems reasonable, right? If there's no difference, then what's the harm in using the project's environment for both build stages?
So as it stands now, poetry cannot do anything here.
Using the currently activated project env to build dependencies seems logical, or at a minimum, consulting the current path for which python to use before falling back to forcing whichever python is running poetry would be the least surprising behavior.
but but but... using the project environment as the first build step seems reasonable, right? If there's no difference, then what's the harm in using the project's environment for both build stages?
Building an external package should never happen within the current environment. Building the package can introduce new dependencies only for the build step, that you don't want to have in your environment. So whether you use the python version that's available within the venv or outside doesn't matter here. Because this python is only used to create a new temporary venv with no other packages available then setuptools and wheel.
using the project environment as the first build step seems reasonable, right? If there's no difference, then what's the harm in using the project's environment for both build stages?
Using the currently activated project env to build dependencies seems logical, or at a minimum, consulting the current path for which
pythonto use before falling back to forcing whicheverpythonis running poetry would be the least surprising behavior.
I am not sure I follow. How would that help?
Building an external package should never happen within the current environment. Building the package can introduce new dependencies only for the build step, that you don't want to have in your environment.
ooh, got it. Thanks for the insight! Sorry for the mistaken assumptions.
I am not sure I follow. How would that help?
In this one case, it would let us override the initial setup.py evaluation environment with the current project environment already containing the torch dependency (which doesn't exist in the clean system environment, but a temporary hacked-together install is better than no install sometimes).
Closing as "notabug; other projects need to get their act together and stop breaking established norms of the language ecosystem!"
@mattsta
If I were you, I think maybe I would look into deploying a private local index (i.e. a _PyPI_ alternative like _devpi_) and upload my own pre built wheels of those "difficult" projects (detectron2, pytorch, etc.) on it.
Most helpful comment
@mattsta
If I were you, I think maybe I would look into deploying a private local index (i.e. a _PyPI_ alternative like _devpi_) and upload my own pre built wheels of those "difficult" projects (detectron2, pytorch, etc.) on it.