Poetry: Picking wrong platform installing a dependency with multiple constraints

Created on 6 Mar 2020  路  21Comments  路  Source: python-poetry/poetry

  • [x] I am on the 1.0.5 Poetry version.
  • [x] I have searched the issues of this repo and believe that this is not a duplicate.
  • [x] If an exception occurs when executing a command, I executed it again in debug mode (-vvv option).

Issue

Markers are not working properly with multiple constraints dependencies as stated in the docs

I am trying to install a dependency from an URL based on the platform, different URLs for every platform (markers = "sys_platform == 'linux'") and poetry is picking the URL from the platform darwin when I'm under a linux platform

I have not tested yet what happens on a Darwin platform. Will update the issue when I do

Update:

If I reverse the order of the dependencies, the first line the darwin url, it works on Linux. The same behaviour applies on Darwin

Bug Dependency resolution

Most helpful comment

@leon19 try chaging it to the following (this is only a partial workaround - see below).

guildai = [
  {url = "https://files.pythonhosted.org/packages/10/ed/ffc1a004da1a272131e776f4b1d24d980b1dbb25b3bbaea4aff5ff7ef538/guildai-0.7.0rc7-cp37-cp37m-manylinux1_x86_64.whl", platform = "linux"},
  {url = "https://files.pythonhosted.org/packages/17/bc/2bc49e33d9343e689723c0b67aa45653d79fc4b5342c12bd7836aca58e7b/guildai-0.7.0rc7-cp37-cp37m-macosx_10_14_x86_64.whl", platform = "darwin"}
]

This will work if you are on a linux environment. I did notice that the generated lockfile will only contain the first specified url (in this case command will succeed since I was working on a linux machine and linux platform was specified first). If I were to change the order, it will fail again. However, when building the wheel generated the correct metadata.

Requires-Dist: guildai @ https://files.pythonhosted.org/packages/10/ed/ffc1a004da1a272131e776f4b1d24d980b1dbb25b3bbaea4aff5ff7ef538/guildai-0.7.0rc7-cp37-cp37m-manylinux1_x86_64.whl; sys_platform == "linux"
Requires-Dist: guildai @ https://files.pythonhosted.org/packages/17/bc/2bc49e33d9343e689723c0b67aa45653d79fc4b5342c12bd7836aca58e7b/guildai-0.7.0rc7-cp37-cp37m-macosx_10_14_x86_64.whl; sys_platform == "darwin"

From what I can tell, when markers are explicitly used the constraints get populated differently.

Issue 1: This pertains to the use of "url". When duplicate dependencies are being merged, url and markers are ignored resulting in only the first entry to be used (in this case the linux url). Ideally, the dependency graph should be branched due to the existance of the platform marker and the url should be considered when merging duplicates.

Additionally, during investigation, I had a quick look at how this was being processed. It did raise a few questions (which might be separate issue(s) to be dealt with).

When markers are explicitly provided (eg: markers = "sys_platform == 'win32'", python = ">=3.6");

Issue 2: the python version specified is ignored if markers are explicitly specified (unsure if this is expected behaviour).
https://github.com/python-poetry/poetry/blob/ae6d64ded40bea9019f7e1de229cd0b360d65e70/poetry/packages/package.py#L349-L359

Issue 3: sole use of markers causes solver to be stuck in an infinite loop.
As an example, this works.

pypiwin32 = [
    { version = "220", platform = "win32", python = ">=3.6"},
    { version = "219", platform = "win32", python = "<3.6"}
]

However, this causes an infinite loop.

pypiwin32 = [
    { version = "220", markers = "sys_platform == 'win32' and python_version >= '3.6'"},
    { version = "219", markers = "sys_platform == 'win32' and python_version < '3.6'"}
]

@finswimmer would be good to get your thoughts on the above.

All 21 comments

@leon19 try chaging it to the following (this is only a partial workaround - see below).

guildai = [
  {url = "https://files.pythonhosted.org/packages/10/ed/ffc1a004da1a272131e776f4b1d24d980b1dbb25b3bbaea4aff5ff7ef538/guildai-0.7.0rc7-cp37-cp37m-manylinux1_x86_64.whl", platform = "linux"},
  {url = "https://files.pythonhosted.org/packages/17/bc/2bc49e33d9343e689723c0b67aa45653d79fc4b5342c12bd7836aca58e7b/guildai-0.7.0rc7-cp37-cp37m-macosx_10_14_x86_64.whl", platform = "darwin"}
]

This will work if you are on a linux environment. I did notice that the generated lockfile will only contain the first specified url (in this case command will succeed since I was working on a linux machine and linux platform was specified first). If I were to change the order, it will fail again. However, when building the wheel generated the correct metadata.

Requires-Dist: guildai @ https://files.pythonhosted.org/packages/10/ed/ffc1a004da1a272131e776f4b1d24d980b1dbb25b3bbaea4aff5ff7ef538/guildai-0.7.0rc7-cp37-cp37m-manylinux1_x86_64.whl; sys_platform == "linux"
Requires-Dist: guildai @ https://files.pythonhosted.org/packages/17/bc/2bc49e33d9343e689723c0b67aa45653d79fc4b5342c12bd7836aca58e7b/guildai-0.7.0rc7-cp37-cp37m-macosx_10_14_x86_64.whl; sys_platform == "darwin"

From what I can tell, when markers are explicitly used the constraints get populated differently.

Issue 1: This pertains to the use of "url". When duplicate dependencies are being merged, url and markers are ignored resulting in only the first entry to be used (in this case the linux url). Ideally, the dependency graph should be branched due to the existance of the platform marker and the url should be considered when merging duplicates.

Additionally, during investigation, I had a quick look at how this was being processed. It did raise a few questions (which might be separate issue(s) to be dealt with).

When markers are explicitly provided (eg: markers = "sys_platform == 'win32'", python = ">=3.6");

Issue 2: the python version specified is ignored if markers are explicitly specified (unsure if this is expected behaviour).
https://github.com/python-poetry/poetry/blob/ae6d64ded40bea9019f7e1de229cd0b360d65e70/poetry/packages/package.py#L349-L359

Issue 3: sole use of markers causes solver to be stuck in an infinite loop.
As an example, this works.

pypiwin32 = [
    { version = "220", platform = "win32", python = ">=3.6"},
    { version = "219", platform = "win32", python = "<3.6"}
]

However, this causes an infinite loop.

pypiwin32 = [
    { version = "220", markers = "sys_platform == 'win32' and python_version >= '3.6'"},
    { version = "219", markers = "sys_platform == 'win32' and python_version < '3.6'"}
]

@finswimmer would be good to get your thoughts on the above.

@abn I tried using platform instead of markers as suggested but it still does not work for me

Note: If I reverse the order of the dependencies, the first line the darwin url, it works on linux (I haven't tested on mac yet)

@leon19 looks like it is non-deterministic.

I have a similar problem. python-ldap package won't build on Windows, so I have to use an unofficial wheel file. I wanted to tell Poetry that it should use the official package for Linux and the locally stored wheel for Windows:

python-ldap = [
    { markers = "sys_platform == 'linux'", version = "*" },
    { markers = "sys_platform == 'win32'", path="lib/python_ldap-3.2.0-cp36-cp36m-win_amd64.whl" }

but, judging from the poetry.lock file, it seems markers are then merged and just determine whether the library should be installed at all:

[[package]]
category = "main"
description = "Python modules for implementing LDAP clients"
marker = "sys_platform == \"linux\" or sys_platform == \"win32\""
name = "python-ldap"

I've also asked a question on StackOverflow.

@breki try this instead.

python-ldap = [
    { platform = "linux", version = "*" },
    { platform = "win32'", path="lib/python_ldap-3.2.0-cp36-cp36m-win_amd64.whl" }
]

@breki try this instead.

python-ldap = [
    { platform = "linux", version = "*" },
    { platform = "win32'", path="lib/python_ldap-3.2.0-cp36-cp36m-win_amd64.whl" }
]

@abn, tried that one as well, no difference.

any solution so far? I am having problem because of pyTorch. They did not release a windows package. And my dev env is a crappy windows, but my production env is linux.

It is very hard to make my docker build to work with poetry.

@abn or @eterna2 any new findings or workarounds for this issue?

I am encountering the same behavior using multiple constraints for a package using git and path dependencies based on platform.

The poetry export -f requirements.txt command seems to generate the requirements in the following way: given multiple constraints, poetry will simply use the first entry in the list as the sole dependency and attach any constraints/markers onto it via a union (markers separated by "or") This means that regardless of if the given constraints should conflict (which is the intention of the different constraints), it will simply install the first listed dependency if it meets any of the given constraints.

From my understanding, this is not the expected behavior, as multiple constraints dependencies are meant to allow for defining different locations for retrieving a given dependency.

I have a similar issue. In my case, I need 3 different packages of mxnet, based on platform_machine and sys_platform.

  • Linux x86_64 with Cuda (from pypi)
  • Linux ARMv8 with Cuda (self compiled).
  • macOS x86_64 without Cuda (from pypi)

The pyproject.toml section looks like this:

mxnet = [
    {version = "~1.6.0", markers = "sys_platform == 'darwin' and platform_machine == 'x86_64'"},
    {markers = "sys_platform == 'linux' and platform_machine == 'aarch64'", path = "../dist/cp37-cp37m-linux_aarch64/mxnet-1.6.0-cp37-cp37m-linux_aarch64.whl"}
]
mxnet-cu102 = {version = "~1.6.0",   markers = "sys_platform == 'linux' and platform_machine == 'x86_64'"}

But the poetry solver returns an error:

[RecursionError]
maximum recursion depth exceeded

I also get the RecursionError like @gilbertfrancois when I specify the CPU version of PyTorch for the win32 platform and the CUDA (10.1) version for the linux2 platform (without any machine platform constraints).

I created issue #2613 for creating different variants (CPU/CUDA) for a project, which might possibly also help with some instances of this problem.

This has been fixed in the 1.1.X branch of Poetry. So every version starting from 1.1.0 (Oct 1, 2020) should work just fine.
Note that:

lock files generated with this release are not compatible with previous releases of Poetry.

Thanks for your response @g4b1nagy . So I will close this. If anyone disagree please leave a comment.

It looks like this may have regressed in 1.1.4

Hi,

Having

markers = "'linux' in sys_platform and 'armv6l' in platform_machine"

does not work with Poetry v1.1.4 even if the condition returns true when using poetry run python. Maybe linked to @joshua-s ' comment.

@sneko

markers = "'linux' in sys_platform and 'armv6l' in platform_machine"

I do not think this is a valid markers notation. What error message, if any do you see?

Should probably be the following instead:

markers = "sys_platform=='linux' and platform_machine=='armv6l'"

@joshua-s
Can you show what error messages if any you see?

@sinoroc I used "in" operator since sometimes I get "linux2" and at start I was looking for platform machine starting with "armv".

But I could give a try to a more complex condition with only "==" matches.

@sneko

As far as I know, only exact comparison == is allowed for such markers, but honestly I have doubts now, maybe 'linux' in sys_platform is valid after all.

For sys_platform you would get linux2 on Python 2 and linux on Python 3.

I think I would rather use platform_system == 'Linux' anyway.

@sneko

OK, I tested a bit, and apparently the '"linux" in sys_platform' notation correct:

>>> import sys
>>> sys.platform
'linux'
>>> import packaging.requirements
>>> m = packaging.requirements.Marker('"linux" not in sys_platform')
>>> m.evaluate()
False
>>> m = packaging.requirements.Marker('"linux" in sys_platform')
>>> m.evaluate()
True

but I would not recommend it:

>>> m = packaging.requirements.Marker('"linux2" in sys_platform')
>>> m.evaluate()
False
>>> m = packaging.requirements.Marker('"linu" in sys_platform')
>>> m.evaluate()
True

I tried several combinations and none worked. Among the things I tried were the following things:

torch = [
    {url = "https://download.pytorch.org/whl/cu102/torch-1.6.0-cp38-cp38-win_amd64.whl", python = "~3.8", markers = "sys_platform == 'win32'" },
    {version = "1.6.0", markers = "sys_platform != 'win32'" }
]
torch = [
    {url = "https://download.pytorch.org/whl/cu102/torch-1.6.0-cp38-cp38-win_amd64.whl", python = "~3.8", platform = "win32" },
    {version = "1.6.0", platform = "linux || darwin" }
]

I tried both and similar variations of the two above with poetry 1.1.0, 1.1.1, 1.1.2, 1.1.3 and 1.1.4

In all cases, the first entry was always used no matter what platform I was on. So on Linux it downloaded the windows wheel. And On Windows, if you changed the order, it took the instructions for linux / mac os.

Could be clarified how exactly this should work and with what poetry versions in specific. So far it failed with all versions, so I'm slightly confused why this issue has been marked as resolved.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jhrmnn picture jhrmnn  路  3Comments

nikaro picture nikaro  路  3Comments

jackemuk picture jackemuk  路  3Comments

mozartilize picture mozartilize  路  3Comments

alexlatchford picture alexlatchford  路  3Comments