diff --git a/docs/dependency-specification.md b/docs/dependency-specification.md index 63299e91c8e..500999394e5 100644 --- a/docs/dependency-specification.md +++ b/docs/dependency-specification.md @@ -572,6 +572,109 @@ pathlib2 = { version = "^2.2", markers = "python_version <= '3.4' or sys_platfor {{< /tab >}} {{< /tabs >}} +### `extra` environment marker + +Poetry populates the `extra` marker with each of the selected extras of the root package. +For example, consider the following dependency: +```toml +[project.optional-dependencies] +paths = [ + "pathlib2 (>=2.2,<3.0) ; sys_platform == 'win32'" +] +``` + +`pathlib2` will be installed when you install your package with `--extras paths` on a `win32` machine. + +#### Exclusive extras + +{{% warning %}} +The first example will only work completely if you configure Poetry to not re-resolve for installation: + +```bash +poetry config installer.re-resolve false +``` + +This is a new feature of Poetry 2.0 that may become the default in a future version of Poetry. + +{{% /warning %}} + +Keep in mind that all combinations of possible extras available in your project need to be compatible with each other. +This means that in order to use differing or incompatible versions across different combinations, you need to make your +extra markers *exclusive*. For example, the following installs PyTorch from one source repository with CPU versions +when the `cuda` extra is *not* specified, while the other installs from another repository with a separate version set +for GPUs when the `cuda` extra *is* specified: + +```toml +[project] +name = "torch-example" +requires-python = ">=3.10" +dependencies = [ + "torch (==2.3.1+cpu) ; extra != 'cuda'", +] + +[project.optional-dependencies] +cuda = [ + "torch (==2.3.1+cu118)", +] + +[tool.poetry] +package-mode = false + +[tool.poetry.dependencies] +torch = [ + { markers = "extra != 'cuda'", source = "pytorch-cpu"}, + { markers = "extra == 'cuda'", source = "pytorch-cuda"}, + ] + +[[tool.poetry.source]] +name = "pytorch-cpu" +url = "https://download.pytorch.org/whl/cpu" +priority = "explicit" + +[[tool.poetry.source]] +name = "pytorch-cuda" +url = "https://download.pytorch.org/whl/cu118" +priority = "explicit" +``` + +For the CPU case, we have to specify `"extra != 'cuda'"` because the version specified is not compatible with the +GPU (`cuda`) version. + +This same logic applies when you want either-or extras: + +```toml +[project] +name = "torch-example" +requires-python = ">=3.10" + +[project.optional-dependencies] +cpu = [ + "torch (==2.3.1+cpu)", +] +cuda = [ + "torch (==2.3.1+cu118)", +] + +[tool.poetry] +package-mode = false + +[tool.poetry.dependencies] +torch = [ + { markers = "extra == 'cpu' and extra != 'cuda'", source = "pytorch-cpu"}, + { markers = "extra == 'cuda' and extra != 'cpu'", source = "pytorch-cuda"}, + ] + +[[tool.poetry.source]] +name = "pytorch-cpu" +url = "https://download.pytorch.org/whl/cpu" +priority = "explicit" + +[[tool.poetry.source]] +name = "pytorch-cuda" +url = "https://download.pytorch.org/whl/cu118" +priority = "explicit" +``` + ## Multiple constraints dependencies Sometimes, one of your dependency may have different version ranges depending diff --git a/poetry.lock b/poetry.lock index 35b8db01c61..5a6b6a44b07 100644 --- a/poetry.lock +++ b/poetry.lock @@ -970,7 +970,7 @@ develop = false type = "git" url = "https://github.com/python-poetry/poetry-core.git" reference = "main" -resolved_reference = "616d7bfaf018d50bd09bc24c630b697b6368d5a3" +resolved_reference = "b95ec5321f1842286b042ac206c9f5395850c684" [[package]] name = "pre-commit" @@ -1221,108 +1221,103 @@ files = [ [[package]] name = "rapidfuzz" -version = "3.9.4" +version = "3.10.1" description = "rapid fuzzy string matching" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "rapidfuzz-3.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c9b9793c19bdf38656c8eaefbcf4549d798572dadd70581379e666035c9df781"}, - {file = "rapidfuzz-3.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:015b5080b999404fe06ec2cb4f40b0be62f0710c926ab41e82dfbc28e80675b4"}, - {file = "rapidfuzz-3.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acc5ceca9c1e1663f3e6c23fb89a311f69b7615a40ddd7645e3435bf3082688a"}, - {file = "rapidfuzz-3.9.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1424e238bc3f20e1759db1e0afb48a988a9ece183724bef91ea2a291c0b92a95"}, - {file = "rapidfuzz-3.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed01378f605aa1f449bee82cd9c83772883120d6483e90aa6c5a4ce95dc5c3aa"}, - {file = "rapidfuzz-3.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb26d412271e5a76cdee1c2d6bf9881310665d3fe43b882d0ed24edfcb891a84"}, - {file = "rapidfuzz-3.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f37e9e1f17be193c41a31c864ad4cd3ebd2b40780db11cd5c04abf2bcf4201b"}, - {file = "rapidfuzz-3.9.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d070ec5cf96b927c4dc5133c598c7ff6db3b833b363b2919b13417f1002560bc"}, - {file = "rapidfuzz-3.9.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:10e61bb7bc807968cef09a0e32ce253711a2d450a4dce7841d21d45330ffdb24"}, - {file = "rapidfuzz-3.9.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:31a2fc60bb2c7face4140010a7aeeafed18b4f9cdfa495cc644a68a8c60d1ff7"}, - {file = "rapidfuzz-3.9.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:fbebf1791a71a2e89f5c12b78abddc018354d5859e305ec3372fdae14f80a826"}, - {file = "rapidfuzz-3.9.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:aee9fc9e3bb488d040afc590c0a7904597bf4ccd50d1491c3f4a5e7e67e6cd2c"}, - {file = "rapidfuzz-3.9.4-cp310-cp310-win32.whl", hash = "sha256:005a02688a51c7d2451a2d41c79d737aa326ff54167211b78a383fc2aace2c2c"}, - {file = "rapidfuzz-3.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:3a2e75e41ee3274754d3b2163cc6c82cd95b892a85ab031f57112e09da36455f"}, - {file = "rapidfuzz-3.9.4-cp310-cp310-win_arm64.whl", hash = "sha256:2c99d355f37f2b289e978e761f2f8efeedc2b14f4751d9ff7ee344a9a5ca98d9"}, - {file = "rapidfuzz-3.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:07141aa6099e39d48637ce72a25b893fc1e433c50b3e837c75d8edf99e0c63e1"}, - {file = "rapidfuzz-3.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:db1664eaff5d7d0f2542dd9c25d272478deaf2c8412e4ad93770e2e2d828e175"}, - {file = "rapidfuzz-3.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc01a223f6605737bec3202e94dcb1a449b6c76d46082cfc4aa980f2a60fd40e"}, - {file = "rapidfuzz-3.9.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1869c42e73e2a8910b479be204fa736418741b63ea2325f9cc583c30f2ded41a"}, - {file = "rapidfuzz-3.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:62ea7007941fb2795fff305ac858f3521ec694c829d5126e8f52a3e92ae75526"}, - {file = "rapidfuzz-3.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:698e992436bf7f0afc750690c301215a36ff952a6dcd62882ec13b9a1ebf7a39"}, - {file = "rapidfuzz-3.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b76f611935f15a209d3730c360c56b6df8911a9e81e6a38022efbfb96e433bab"}, - {file = "rapidfuzz-3.9.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129627d730db2e11f76169344a032f4e3883d34f20829419916df31d6d1338b1"}, - {file = "rapidfuzz-3.9.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:90a82143c14e9a14b723a118c9ef8d1bbc0c5a16b1ac622a1e6c916caff44dd8"}, - {file = "rapidfuzz-3.9.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ded58612fe3b0e0d06e935eaeaf5a9fd27da8ba9ed3e2596307f40351923bf72"}, - {file = "rapidfuzz-3.9.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f16f5d1c4f02fab18366f2d703391fcdbd87c944ea10736ca1dc3d70d8bd2d8b"}, - {file = "rapidfuzz-3.9.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:26aa7eece23e0df55fb75fbc2a8fb678322e07c77d1fd0e9540496e6e2b5f03e"}, - {file = "rapidfuzz-3.9.4-cp311-cp311-win32.whl", hash = "sha256:f187a9c3b940ce1ee324710626daf72c05599946bd6748abe9e289f1daa9a077"}, - {file = "rapidfuzz-3.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d8e9130fe5d7c9182990b366ad78fd632f744097e753e08ace573877d67c32f8"}, - {file = "rapidfuzz-3.9.4-cp311-cp311-win_arm64.whl", hash = "sha256:40419e98b10cd6a00ce26e4837a67362f658fc3cd7a71bd8bd25c99f7ee8fea5"}, - {file = "rapidfuzz-3.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b5d5072b548db1b313a07d62d88fe0b037bd2783c16607c647e01b070f6cf9e5"}, - {file = "rapidfuzz-3.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cf5bcf22e1f0fd273354462631d443ef78d677f7d2fc292de2aec72ae1473e66"}, - {file = "rapidfuzz-3.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c8fc973adde8ed52810f590410e03fb6f0b541bbaeb04c38d77e63442b2df4c"}, - {file = "rapidfuzz-3.9.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2464bb120f135293e9a712e342c43695d3d83168907df05f8c4ead1612310c7"}, - {file = "rapidfuzz-3.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d9d58689aca22057cf1a5851677b8a3ccc9b535ca008c7ed06dc6e1899f7844"}, - {file = "rapidfuzz-3.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:167e745f98baa0f3034c13583e6302fb69249a01239f1483d68c27abb841e0a1"}, - {file = "rapidfuzz-3.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db0bf0663b4b6da1507869722420ea9356b6195aa907228d6201303e69837af9"}, - {file = "rapidfuzz-3.9.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cd6ac61b74fdb9e23f04d5f068e6cf554f47e77228ca28aa2347a6ca8903972f"}, - {file = "rapidfuzz-3.9.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:60ff67c690acecf381759c16cb06c878328fe2361ddf77b25d0e434ea48a29da"}, - {file = "rapidfuzz-3.9.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:cb934363380c60f3a57d14af94325125cd8cded9822611a9f78220444034e36e"}, - {file = "rapidfuzz-3.9.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fe833493fb5cc5682c823ea3e2f7066b07612ee8f61ecdf03e1268f262106cdd"}, - {file = "rapidfuzz-3.9.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2797fb847d89e04040d281cb1902cbeffbc4b5131a5c53fc0db490fd76b2a547"}, - {file = "rapidfuzz-3.9.4-cp312-cp312-win32.whl", hash = "sha256:52e3d89377744dae68ed7c84ad0ddd3f5e891c82d48d26423b9e066fc835cc7c"}, - {file = "rapidfuzz-3.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:c76da20481c906e08400ee9be230f9e611d5931a33707d9df40337c2655c84b5"}, - {file = "rapidfuzz-3.9.4-cp312-cp312-win_arm64.whl", hash = "sha256:f2d2846f3980445864c7e8b8818a29707fcaff2f0261159ef6b7bd27ba139296"}, - {file = "rapidfuzz-3.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:355fc4a268ffa07bab88d9adee173783ec8d20136059e028d2a9135c623c44e6"}, - {file = "rapidfuzz-3.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4d81a78f90269190b568a8353d4ea86015289c36d7e525cd4d43176c88eff429"}, - {file = "rapidfuzz-3.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e618625ffc4660b26dc8e56225f8b966d5842fa190e70c60db6cd393e25b86e"}, - {file = "rapidfuzz-3.9.4-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b712336ad6f2bacdbc9f1452556e8942269ef71f60a9e6883ef1726b52d9228a"}, - {file = "rapidfuzz-3.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc1ee19fdad05770c897e793836c002344524301501d71ef2e832847425707"}, - {file = "rapidfuzz-3.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1950f8597890c0c707cb7e0416c62a1cf03dcdb0384bc0b2dbda7e05efe738ec"}, - {file = "rapidfuzz-3.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a6c35f272ec9c430568dc8c1c30cb873f6bc96be2c79795e0bce6db4e0e101d"}, - {file = "rapidfuzz-3.9.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:1df0f9e9239132a231c86ae4f545ec2b55409fa44470692fcfb36b1bd00157ad"}, - {file = "rapidfuzz-3.9.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:d2c51955329bfccf99ae26f63d5928bf5be9fcfcd9f458f6847fd4b7e2b8986c"}, - {file = "rapidfuzz-3.9.4-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:3c522f462d9fc504f2ea8d82e44aa580e60566acc754422c829ad75c752fbf8d"}, - {file = "rapidfuzz-3.9.4-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:d8a52fc50ded60d81117d7647f262c529659fb21d23e14ebfd0b35efa4f1b83d"}, - {file = "rapidfuzz-3.9.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:04dbdfb0f0bfd3f99cf1e9e24fadc6ded2736d7933f32f1151b0f2abb38f9a25"}, - {file = "rapidfuzz-3.9.4-cp38-cp38-win32.whl", hash = "sha256:4968c8bd1df84b42f382549e6226710ad3476f976389839168db3e68fd373298"}, - {file = "rapidfuzz-3.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:3fe4545f89f8d6c27b6bbbabfe40839624873c08bd6700f63ac36970a179f8f5"}, - {file = "rapidfuzz-3.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9f256c8fb8f3125574c8c0c919ab0a1f75d7cba4d053dda2e762dcc36357969d"}, - {file = "rapidfuzz-3.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5fdc09cf6e9d8eac3ce48a4615b3a3ee332ea84ac9657dbbefef913b13e632f"}, - {file = "rapidfuzz-3.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d395d46b80063d3b5d13c0af43d2c2cedf3ab48c6a0c2aeec715aa5455b0c632"}, - {file = "rapidfuzz-3.9.4-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7fa714fb96ce9e70c37e64c83b62fe8307030081a0bfae74a76fac7ba0f91715"}, - {file = "rapidfuzz-3.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bc1a0f29f9119be7a8d3c720f1d2068317ae532e39e4f7f948607c3a6de8396"}, - {file = "rapidfuzz-3.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6022674aa1747d6300f699cd7c54d7dae89bfe1f84556de699c4ac5df0838082"}, - {file = "rapidfuzz-3.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcb72e5f9762fd469701a7e12e94b924af9004954f8c739f925cb19c00862e38"}, - {file = "rapidfuzz-3.9.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ad04ae301129f0eb5b350a333accd375ce155a0c1cec85ab0ec01f770214e2e4"}, - {file = "rapidfuzz-3.9.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f46a22506f17c0433e349f2d1dc11907c393d9b3601b91d4e334fa9a439a6a4d"}, - {file = "rapidfuzz-3.9.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:01b42a8728c36011718da409aa86b84984396bf0ca3bfb6e62624f2014f6022c"}, - {file = "rapidfuzz-3.9.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:e590d5d5443cf56f83a51d3c4867bd1f6be8ef8cfcc44279522bcef3845b2a51"}, - {file = "rapidfuzz-3.9.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4c72078b5fdce34ba5753f9299ae304e282420e6455e043ad08e4488ca13a2b0"}, - {file = "rapidfuzz-3.9.4-cp39-cp39-win32.whl", hash = "sha256:f75639277304e9b75e6a7b3c07042d2264e16740a11e449645689ed28e9c2124"}, - {file = "rapidfuzz-3.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:e81e27e8c32a1e1278a4bb1ce31401bfaa8c2cc697a053b985a6f8d013df83ec"}, - {file = "rapidfuzz-3.9.4-cp39-cp39-win_arm64.whl", hash = "sha256:15bc397ee9a3ed1210b629b9f5f1da809244adc51ce620c504138c6e7095b7bd"}, - {file = "rapidfuzz-3.9.4-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:20488ade4e1ddba3cfad04f400da7a9c1b91eff5b7bd3d1c50b385d78b587f4f"}, - {file = "rapidfuzz-3.9.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:e61b03509b1a6eb31bc5582694f6df837d340535da7eba7bedb8ae42a2fcd0b9"}, - {file = "rapidfuzz-3.9.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:098d231d4e51644d421a641f4a5f2f151f856f53c252b03516e01389b2bfef99"}, - {file = "rapidfuzz-3.9.4-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17ab8b7d10fde8dd763ad428aa961c0f30a1b44426e675186af8903b5d134fb0"}, - {file = "rapidfuzz-3.9.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e272df61bee0a056a3daf99f9b1bd82cf73ace7d668894788139c868fdf37d6f"}, - {file = "rapidfuzz-3.9.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d6481e099ff8c4edda85b8b9b5174c200540fd23c8f38120016c765a86fa01f5"}, - {file = "rapidfuzz-3.9.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ad61676e9bdae677d577fe80ec1c2cea1d150c86be647e652551dcfe505b1113"}, - {file = "rapidfuzz-3.9.4-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:af65020c0dd48d0d8ae405e7e69b9d8ae306eb9b6249ca8bf511a13f465fad85"}, - {file = "rapidfuzz-3.9.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d38b4e026fcd580e0bda6c0ae941e0e9a52c6bc66cdce0b8b0da61e1959f5f8"}, - {file = "rapidfuzz-3.9.4-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f74ed072c2b9dc6743fb19994319d443a4330b0e64aeba0aa9105406c7c5b9c2"}, - {file = "rapidfuzz-3.9.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aee5f6b8321f90615c184bd8a4c676e9becda69b8e4e451a90923db719d6857c"}, - {file = "rapidfuzz-3.9.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3a555e3c841d6efa350f862204bb0a3fea0c006b8acc9b152b374fa36518a1c6"}, - {file = "rapidfuzz-3.9.4-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0772150d37bf018110351c01d032bf9ab25127b966a29830faa8ad69b7e2f651"}, - {file = "rapidfuzz-3.9.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:addcdd3c3deef1bd54075bd7aba0a6ea9f1d01764a08620074b7a7b1e5447cb9"}, - {file = "rapidfuzz-3.9.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fe86b82b776554add8f900b6af202b74eb5efe8f25acdb8680a5c977608727f"}, - {file = "rapidfuzz-3.9.4-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0fc91ac59f4414d8542454dfd6287a154b8e6f1256718c898f695bdbb993467"}, - {file = "rapidfuzz-3.9.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a944e546a296a5fdcaabb537b01459f1b14d66f74e584cb2a91448bffadc3c1"}, - {file = "rapidfuzz-3.9.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4fb96ba96d58c668a17a06b5b5e8340fedc26188e87b0d229d38104556f30cd8"}, - {file = "rapidfuzz-3.9.4.tar.gz", hash = "sha256:366bf8947b84e37f2f4cf31aaf5f37c39f620d8c0eddb8b633e6ba0129ca4a0a"}, + {file = "rapidfuzz-3.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f17d9f21bf2f2f785d74f7b0d407805468b4c173fa3e52c86ec94436b338e74a"}, + {file = "rapidfuzz-3.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b31f358a70efc143909fb3d75ac6cd3c139cd41339aa8f2a3a0ead8315731f2b"}, + {file = "rapidfuzz-3.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f4f43f2204b56a61448ec2dd061e26fd344c404da99fb19f3458200c5874ba2"}, + {file = "rapidfuzz-3.10.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d81bf186a453a2757472133b24915768abc7c3964194406ed93e170e16c21cb"}, + {file = "rapidfuzz-3.10.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3611c8f45379a12063d70075c75134f2a8bd2e4e9b8a7995112ddae95ca1c982"}, + {file = "rapidfuzz-3.10.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c3b537b97ac30da4b73930fa8a4fe2f79c6d1c10ad535c5c09726612cd6bed9"}, + {file = "rapidfuzz-3.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:231ef1ec9cf7b59809ce3301006500b9d564ddb324635f4ea8f16b3e2a1780da"}, + {file = "rapidfuzz-3.10.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed4f3adc1294834955b7e74edd3c6bd1aad5831c007f2d91ea839e76461a5879"}, + {file = "rapidfuzz-3.10.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:7b6015da2e707bf632a71772a2dbf0703cff6525732c005ad24987fe86e8ec32"}, + {file = "rapidfuzz-3.10.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1b35a118d61d6f008e8e3fb3a77674d10806a8972c7b8be433d6598df4d60b01"}, + {file = "rapidfuzz-3.10.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:bc308d79a7e877226f36bdf4e149e3ed398d8277c140be5c1fd892ec41739e6d"}, + {file = "rapidfuzz-3.10.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f017dbfecc172e2d0c37cf9e3d519179d71a7f16094b57430dffc496a098aa17"}, + {file = "rapidfuzz-3.10.1-cp310-cp310-win32.whl", hash = "sha256:36c0e1483e21f918d0f2f26799fe5ac91c7b0c34220b73007301c4f831a9c4c7"}, + {file = "rapidfuzz-3.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:10746c1d4c8cd8881c28a87fd7ba0c9c102346dfe7ff1b0d021cdf093e9adbff"}, + {file = "rapidfuzz-3.10.1-cp310-cp310-win_arm64.whl", hash = "sha256:dfa64b89dcb906835e275187569e51aa9d546a444489e97aaf2cc84011565fbe"}, + {file = "rapidfuzz-3.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:92958ae075c87fef393f835ed02d4fe8d5ee2059a0934c6c447ea3417dfbf0e8"}, + {file = "rapidfuzz-3.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ba7521e072c53e33c384e78615d0718e645cab3c366ecd3cc8cb732befd94967"}, + {file = "rapidfuzz-3.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00d02cbd75d283c287471b5b3738b3e05c9096150f93f2d2dfa10b3d700f2db9"}, + {file = "rapidfuzz-3.10.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:efa1582a397da038e2f2576c9cd49b842f56fde37d84a6b0200ffebc08d82350"}, + {file = "rapidfuzz-3.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f12912acee1f506f974f58de9fdc2e62eea5667377a7e9156de53241c05fdba8"}, + {file = "rapidfuzz-3.10.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666d5d8b17becc3f53447bcb2b6b33ce6c2df78792495d1fa82b2924cd48701a"}, + {file = "rapidfuzz-3.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26f71582c0d62445067ee338ddad99b655a8f4e4ed517a90dcbfbb7d19310474"}, + {file = "rapidfuzz-3.10.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8a2ef08b27167bcff230ffbfeedd4c4fa6353563d6aaa015d725dd3632fc3de7"}, + {file = "rapidfuzz-3.10.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:365e4fc1a2b95082c890f5e98489b894e6bf8c338c6ac89bb6523c2ca6e9f086"}, + {file = "rapidfuzz-3.10.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1996feb7a61609fa842e6b5e0c549983222ffdedaf29644cc67e479902846dfe"}, + {file = "rapidfuzz-3.10.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:cf654702f144beaa093103841a2ea6910d617d0bb3fccb1d1fd63c54dde2cd49"}, + {file = "rapidfuzz-3.10.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec108bf25de674781d0a9a935030ba090c78d49def3d60f8724f3fc1e8e75024"}, + {file = "rapidfuzz-3.10.1-cp311-cp311-win32.whl", hash = "sha256:031f8b367e5d92f7a1e27f7322012f3c321c3110137b43cc3bf678505583ef48"}, + {file = "rapidfuzz-3.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:f98f36c6a1bb9a6c8bbec99ad87c8c0e364f34761739b5ea9adf7b48129ae8cf"}, + {file = "rapidfuzz-3.10.1-cp311-cp311-win_arm64.whl", hash = "sha256:f1da2028cb4e41be55ee797a82d6c1cf589442504244249dfeb32efc608edee7"}, + {file = "rapidfuzz-3.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1340b56340896bede246f612b6ecf685f661a56aabef3d2512481bfe23ac5835"}, + {file = "rapidfuzz-3.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2316515169b7b5a453f0ce3adbc46c42aa332cae9f2edb668e24d1fc92b2f2bb"}, + {file = "rapidfuzz-3.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e06fe6a12241ec1b72c0566c6b28cda714d61965d86569595ad24793d1ab259"}, + {file = "rapidfuzz-3.10.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d99c1cd9443b19164ec185a7d752f4b4db19c066c136f028991a480720472e23"}, + {file = "rapidfuzz-3.10.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1d9aa156ed52d3446388ba4c2f335e312191d1ca9d1f5762ee983cf23e4ecf6"}, + {file = "rapidfuzz-3.10.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:54bcf4efaaee8e015822be0c2c28214815f4f6b4f70d8362cfecbd58a71188ac"}, + {file = "rapidfuzz-3.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0c955e32afdbfdf6e9ee663d24afb25210152d98c26d22d399712d29a9b976b"}, + {file = "rapidfuzz-3.10.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:191633722203f5b7717efcb73a14f76f3b124877d0608c070b827c5226d0b972"}, + {file = "rapidfuzz-3.10.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:195baad28057ec9609e40385991004e470af9ef87401e24ebe72c064431524ab"}, + {file = "rapidfuzz-3.10.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0fff4a6b87c07366662b62ae994ffbeadc472e72f725923f94b72a3db49f4671"}, + {file = "rapidfuzz-3.10.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4ffed25f9fdc0b287f30a98467493d1e1ce5b583f6317f70ec0263b3c97dbba6"}, + {file = "rapidfuzz-3.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d02cf8e5af89a9ac8f53c438ddff6d773f62c25c6619b29db96f4aae248177c0"}, + {file = "rapidfuzz-3.10.1-cp312-cp312-win32.whl", hash = "sha256:f3bb81d4fe6a5d20650f8c0afcc8f6e1941f6fecdb434f11b874c42467baded0"}, + {file = "rapidfuzz-3.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:aaf83e9170cb1338922ae42d320699dccbbdca8ffed07faeb0b9257822c26e24"}, + {file = "rapidfuzz-3.10.1-cp312-cp312-win_arm64.whl", hash = "sha256:c5da802a0d085ad81b0f62828fb55557996c497b2d0b551bbdfeafd6d447892f"}, + {file = "rapidfuzz-3.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fc22d69a1c9cccd560a5c434c0371b2df0f47c309c635a01a913e03bbf183710"}, + {file = "rapidfuzz-3.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38b0dac2c8e057562b8f0d8ae5b663d2d6a28c5ab624de5b73cef9abb6129a24"}, + {file = "rapidfuzz-3.10.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fde3bbb14e92ce8fcb5c2edfff72e474d0080cadda1c97785bf4822f037a309"}, + {file = "rapidfuzz-3.10.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9141fb0592e55f98fe9ac0f3ce883199b9c13e262e0bf40c5b18cdf926109d16"}, + {file = "rapidfuzz-3.10.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:237bec5dd1bfc9b40bbd786cd27949ef0c0eb5fab5eb491904c6b5df59d39d3c"}, + {file = "rapidfuzz-3.10.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18123168cba156ab5794ea6de66db50f21bb3c66ae748d03316e71b27d907b95"}, + {file = "rapidfuzz-3.10.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b75fe506c8e02769cc47f5ab21ce3e09b6211d3edaa8f8f27331cb6988779be"}, + {file = "rapidfuzz-3.10.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9da82aa4b46973aaf9e03bb4c3d6977004648c8638febfc0f9d237e865761270"}, + {file = "rapidfuzz-3.10.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c34c022d5ad564f1a5a57a4a89793bd70d7bad428150fb8ff2760b223407cdcf"}, + {file = "rapidfuzz-3.10.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1e96c84d6c2a0ca94e15acb5399118fff669f4306beb98a6d8ec6f5dccab4412"}, + {file = "rapidfuzz-3.10.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e8e154b84a311263e1aca86818c962e1fa9eefdd643d1d5d197fcd2738f88cb9"}, + {file = "rapidfuzz-3.10.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:335fee93188f8cd585552bb8057228ce0111bd227fa81bfd40b7df6b75def8ab"}, + {file = "rapidfuzz-3.10.1-cp313-cp313-win32.whl", hash = "sha256:6729b856166a9e95c278410f73683957ea6100c8a9d0a8dbe434c49663689255"}, + {file = "rapidfuzz-3.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:0e06d99ad1ad97cb2ef7f51ec6b1fedd74a3a700e4949353871cf331d07b382a"}, + {file = "rapidfuzz-3.10.1-cp313-cp313-win_arm64.whl", hash = "sha256:8d1b7082104d596a3eb012e0549b2634ed15015b569f48879701e9d8db959dbb"}, + {file = "rapidfuzz-3.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:779027d3307e1a2b1dc0c03c34df87a470a368a1a0840a9d2908baf2d4067956"}, + {file = "rapidfuzz-3.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:440b5608ab12650d0390128d6858bc839ae77ffe5edf0b33a1551f2fa9860651"}, + {file = "rapidfuzz-3.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82cac41a411e07a6f3dc80dfbd33f6be70ea0abd72e99c59310819d09f07d945"}, + {file = "rapidfuzz-3.10.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:958473c9f0bca250590200fd520b75be0dbdbc4a7327dc87a55b6d7dc8d68552"}, + {file = "rapidfuzz-3.10.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ef60dfa73749ef91cb6073be1a3e135f4846ec809cc115f3cbfc6fe283a5584"}, + {file = "rapidfuzz-3.10.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7fbac18f2c19fc983838a60611e67e3262e36859994c26f2ee85bb268de2355"}, + {file = "rapidfuzz-3.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a0d519ff39db887cd73f4e297922786d548f5c05d6b51f4e6754f452a7f4296"}, + {file = "rapidfuzz-3.10.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bebb7bc6aeb91cc57e4881b222484c26759ca865794187217c9dcea6c33adae6"}, + {file = "rapidfuzz-3.10.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fe07f8b9c3bb5c5ad1d2c66884253e03800f4189a60eb6acd6119ebaf3eb9894"}, + {file = "rapidfuzz-3.10.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:bfa48a4a2d45a41457f0840c48e579db157a927f4e97acf6e20df8fc521c79de"}, + {file = "rapidfuzz-3.10.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2cf44d01bfe8ee605b7eaeecbc2b9ca64fc55765f17b304b40ed8995f69d7716"}, + {file = "rapidfuzz-3.10.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1e6bbca9246d9eedaa1c84e04a7f555493ba324d52ae4d9f3d9ddd1b740dcd87"}, + {file = "rapidfuzz-3.10.1-cp39-cp39-win32.whl", hash = "sha256:567f88180f2c1423b4fe3f3ad6e6310fc97b85bdba574801548597287fc07028"}, + {file = "rapidfuzz-3.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:6b2cd7c29d6ecdf0b780deb587198f13213ac01c430ada6913452fd0c40190fc"}, + {file = "rapidfuzz-3.10.1-cp39-cp39-win_arm64.whl", hash = "sha256:9f912d459e46607ce276128f52bea21ebc3e9a5ccf4cccfef30dd5bddcf47be8"}, + {file = "rapidfuzz-3.10.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ac4452f182243cfab30ba4668ef2de101effaedc30f9faabb06a095a8c90fd16"}, + {file = "rapidfuzz-3.10.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:565c2bd4f7d23c32834652b27b51dd711814ab614b4e12add8476be4e20d1cf5"}, + {file = "rapidfuzz-3.10.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:187d9747149321607be4ccd6f9f366730078bed806178ec3eeb31d05545e9e8f"}, + {file = "rapidfuzz-3.10.1-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:616290fb9a8fa87e48cb0326d26f98d4e29f17c3b762c2d586f2b35c1fd2034b"}, + {file = "rapidfuzz-3.10.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:073a5b107e17ebd264198b78614c0206fa438cce749692af5bc5f8f484883f50"}, + {file = "rapidfuzz-3.10.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:39c4983e2e2ccb9732f3ac7d81617088822f4a12291d416b09b8a1eadebb3e29"}, + {file = "rapidfuzz-3.10.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ac7adee6bcf0c6fee495d877edad1540a7e0f5fc208da03ccb64734b43522d7a"}, + {file = "rapidfuzz-3.10.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:425f4ac80b22153d391ee3f94bc854668a0c6c129f05cf2eaf5ee74474ddb69e"}, + {file = "rapidfuzz-3.10.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65a2fa13e8a219f9b5dcb9e74abe3ced5838a7327e629f426d333dfc8c5a6e66"}, + {file = "rapidfuzz-3.10.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75561f3df9a906aaa23787e9992b228b1ab69007932dc42070f747103e177ba8"}, + {file = "rapidfuzz-3.10.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:edd062490537e97ca125bc6c7f2b7331c2b73d21dc304615afe61ad1691e15d5"}, + {file = "rapidfuzz-3.10.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfcc8feccf63245a22dfdd16e222f1a39771a44b870beb748117a0e09cbb4a62"}, + {file = "rapidfuzz-3.10.1.tar.gz", hash = "sha256:5a15546d847a915b3f42dc79ef9b0c78b998b4e2c53b252e7166284066585979"}, ] [package.extras] -full = ["numpy"] +all = ["numpy"] [[package]] name = "requests" diff --git a/src/poetry/installation/installer.py b/src/poetry/installation/installer.py index eff9aeffdd5..8a243e15b45 100644 --- a/src/poetry/installation/installer.py +++ b/src/poetry/installation/installer.py @@ -305,6 +305,7 @@ def _do_install(self) -> int: self._installed_repository.packages, locked_repository.packages, NullIO(), + active_root_extras=self._extras, ) # Everything is resolved at this point, so we no longer need # to load deferred dependencies (i.e. VCS, URL and path dependencies) diff --git a/src/poetry/puzzle/provider.py b/src/poetry/puzzle/provider.py index 9bae818bc56..b3b29e5f6c6 100644 --- a/src/poetry/puzzle/provider.py +++ b/src/poetry/puzzle/provider.py @@ -8,6 +8,7 @@ from collections import defaultdict from contextlib import contextmanager from typing import TYPE_CHECKING +from typing import Any from typing import ClassVar from typing import cast @@ -17,6 +18,7 @@ from poetry.core.constraints.version import VersionRange from poetry.core.packages.utils.utils import get_python_constraint_from_marker from poetry.core.version.markers import AnyMarker +from poetry.core.version.markers import parse_marker from poetry.core.version.markers import union as marker_union from poetry.mixology.incompatibility import Incompatibility @@ -115,6 +117,7 @@ def __init__( io: IO, *, locked: list[Package] | None = None, + active_root_extras: Collection[NormalizedName] | None = None, ) -> None: self._package = package self._pool = pool @@ -130,6 +133,9 @@ def __init__( self._direct_origin_packages: dict[str, Package] = {} self._locked: dict[NormalizedName, list[DependencyPackage]] = defaultdict(list) self._use_latest: Collection[NormalizedName] = [] + self._active_root_extras = ( + frozenset(active_root_extras) if active_root_extras is not None else None + ) self._explicit_sources: dict[str, str] = {} for package in locked or []: @@ -416,21 +422,12 @@ def incompatibilities_for( ) ] - _dependencies = [ - dep - for dep in dependencies - if dep.name not in self.UNSAFE_PACKAGES - and self._python_constraint.allows_any(dep.python_constraint) - and (not self._env or dep.marker.validate(self._env.marker_env)) - ] - dependencies = self._get_dependencies_with_overrides(_dependencies, package) - return [ Incompatibility( [Term(package.to_dependency(), True), Term(dep, False)], DependencyCauseError(), ) - for dep in dependencies + for dep in self._get_dependencies_with_overrides(dependencies, package) ] def complete_package( @@ -480,7 +477,7 @@ def complete_package( package = dependency_package.package dependency = dependency_package.dependency new_dependency = package.without_features().to_dependency() - new_dependency.marker = AnyMarker() + new_dependency.marker = dependency.marker # When adding dependency foo[extra] -> foo, preserve foo's source, if it's # specified. This prevents us from trying to get foo from PyPI @@ -497,8 +494,14 @@ def complete_package( if dep.name in self.UNSAFE_PACKAGES: continue - if self._env and not dep.marker.validate(self._env.marker_env): - continue + if self._env: + marker_values = ( + self._marker_values(self._active_root_extras) + if package.is_root() + else self._env.marker_env + ) + if not dep.marker.validate(marker_values): + continue if not package.is_root() and ( (dep.is_optional() and dep.name not in optional_dependencies) @@ -509,6 +512,24 @@ def complete_package( ): continue + # For normal dependency resolution, we have to make sure that root extras + # are represented in the markers. This is required to identify mutually + # exclusive markers in cases like 'extra == "foo"' and 'extra != "foo"'. + # However, for installation with re-resolving (installer.re-resolve=true, + # which results in self._env being not None), this spoils the result + # because we have to keep extras so that they are uninstalled + # when calculating the operations of the transaction. + if self._env is None and package.is_root() and dep.in_extras: + # The clone is required for installation with re-resolving + # without an existing lock file because the root package is used + # once for solving and a second time for re-resolving for installation. + dep = dep.clone() + dep.marker = dep.marker.intersect( + parse_marker( + " or ".join(f'extra == "{extra}"' for extra in dep.in_extras) + ) + ) + _dependencies.append(dep) if self._load_deferred: @@ -545,7 +566,7 @@ def complete_package( # • pypiwin32 (219); sys_platform == "win32" and python_version < "3.6" duplicates: dict[str, list[Dependency]] = defaultdict(list) for dep in dependencies: - duplicates[dep.complete_name].append(dep) + duplicates[dep.name].append(dep) dependencies = [] for dep_name, deps in duplicates.items(): @@ -556,9 +577,39 @@ def complete_package( self.debug(f"Duplicate dependencies for {dep_name}") # For dependency resolution, markers of duplicate dependencies must be - # mutually exclusive. - active_extras = None if package.is_root() else dependency.extras - deps = self._resolve_overlapping_markers(package, deps, active_extras) + # mutually exclusive. However, we have to take care about duplicates + # with differing extras. + duplicates_by_extras: dict[str, list[Dependency]] = defaultdict(list) + for dep in deps: + duplicates_by_extras[dep.complete_name].append(dep) + + if len(duplicates_by_extras) == 1: + active_extras = ( + self._active_root_extras if package.is_root() else dependency.extras + ) + deps = self._resolve_overlapping_markers(package, deps, active_extras) + else: + # There are duplicates with different extras. + for complete_dep_name, deps_by_extra in duplicates_by_extras.items(): + if len(deps_by_extra) > 1: + duplicates_by_extras[complete_dep_name] = ( + self._resolve_overlapping_markers(package, deps, None) + ) + if all(len(d) == 1 for d in duplicates_by_extras.values()) and all( + d1[0].marker.intersect(d2[0].marker).is_empty() + for d1, d2 in itertools.combinations( + duplicates_by_extras.values(), 2 + ) + ): + # Since all markers are mutually exclusive, + # we can trigger overrides. + deps = list(itertools.chain(*duplicates_by_extras.values())) + else: + # Too complicated to handle with overrides, + # fallback to basic handling without overrides. + for d in duplicates_by_extras.values(): + dependencies.extend(d) + continue if len(deps) == 1: self.debug(f"Merging requirements for {dep_name}") @@ -909,3 +960,19 @@ def _resolve_overlapping_markers( # dependencies by constraint again. After overlapping markers were # resolved, there might be new dependencies with the same constraint. return self._merge_dependencies_by_constraint(new_dependencies) + + def _marker_values( + self, extras: Collection[NormalizedName] | None = None + ) -> dict[str, Any]: + """ + Marker values, from `self._env` if present plus the supplied extras + + :param extras: the values to add to the 'extra' marker value + """ + result = self._env.marker_env.copy() if self._env is not None else {} + if extras is not None: + assert ( + "extra" not in result + ), "'extra' marker key is already present in environment" + result["extra"] = set(extras) + return result diff --git a/src/poetry/puzzle/solver.py b/src/poetry/puzzle/solver.py index e32d000df98..234518fc52e 100644 --- a/src/poetry/puzzle/solver.py +++ b/src/poetry/puzzle/solver.py @@ -49,6 +49,7 @@ def __init__( installed: list[Package], locked: list[Package], io: IO, + active_root_extras: Collection[NormalizedName] | None = None, ) -> None: self._package = package self._pool = pool @@ -56,7 +57,13 @@ def __init__( self._locked_packages = locked self._io = io - self._provider = Provider(self._package, self._pool, self._io, locked=locked) + self._provider = Provider( + self._package, + self._pool, + self._io, + locked=locked, + active_root_extras=active_root_extras, + ) self._overrides: list[dict[Package, dict[str, Dependency]]] = [] @property diff --git a/src/poetry/puzzle/transaction.py b/src/poetry/puzzle/transaction.py index 05a9432818f..338e43d8b5c 100644 --- a/src/poetry/puzzle/transaction.py +++ b/src/poetry/puzzle/transaction.py @@ -78,7 +78,7 @@ def calculate_operations( else: priorities = defaultdict(int) relevant_result_packages: set[NormalizedName] = set() - uninstalls: set[NormalizedName] = set() + pending_extra_uninstalls: list[Package] = [] # list for deterministic order for result_package in self._result_packages: is_unsolicited_extra = False if self._marker_env: @@ -95,11 +95,12 @@ def calculate_operations( else: continue else: - relevant_result_packages.add(result_package.name) is_unsolicited_extra = extras is not None and ( result_package.optional and result_package.name not in extra_packages ) + if not is_unsolicited_extra: + relevant_result_packages.add(result_package.name) installed = False for installed_package in self._installed_packages: @@ -108,9 +109,7 @@ def calculate_operations( # Extras that were not requested are always uninstalled. if is_unsolicited_extra: - uninstalls.add(installed_package.name) - if installed_package.name not in system_site_packages: - operations.append(Uninstall(installed_package)) + pending_extra_uninstalls.append(installed_package) # We have to perform an update if the version or another # attribute of the package has changed (source type, url, ref, ...). @@ -153,6 +152,13 @@ def calculate_operations( op.skip("Not required") operations.append(op) + uninstalls: set[NormalizedName] = set() + for package in pending_extra_uninstalls: + if package.name not in (relevant_result_packages | uninstalls): + uninstalls.add(package.name) + if package.name not in system_site_packages: + operations.append(Uninstall(package)) + if with_uninstalls: for current_package in self._current_packages: found = current_package.name in (relevant_result_packages | uninstalls) diff --git a/tests/installation/fixtures/with-conflicting-dependency-extras-root.test b/tests/installation/fixtures/with-conflicting-dependency-extras-root.test new file mode 100644 index 00000000000..c99c37a5694 --- /dev/null +++ b/tests/installation/fixtures/with-conflicting-dependency-extras-root.test @@ -0,0 +1,28 @@ +[[package]] +name = "conflicting-dep" +version = "1.1.0" +description = "" +optional = true +python-versions = "*" +files = [ ] +groups = [ "main" ] +markers = "extra == \"extra-one\" and extra != \"extra-two\"" + +[[package]] +name = "conflicting-dep" +version = "1.2.0" +description = "" +optional = true +python-versions = "*" +files = [ ] +groups = [ "main" ] +markers = "extra != \"extra-one\" and extra == \"extra-two\"" + +[extras] +extra-one = [ "conflicting-dep", "conflicting-dep" ] +extra-two = [ "conflicting-dep", "conflicting-dep" ] + +[metadata] +lock-version = "2.1" +python-versions = "*" +content-hash = "123456789" diff --git a/tests/installation/fixtures/with-conflicting-dependency-extras-transitive.test b/tests/installation/fixtures/with-conflicting-dependency-extras-transitive.test new file mode 100644 index 00000000000..5e6f11ab7f9 --- /dev/null +++ b/tests/installation/fixtures/with-conflicting-dependency-extras-transitive.test @@ -0,0 +1,52 @@ +[[package]] +name = "conflicting-dep" +version = "1.1.0" +description = "" +optional = true +python-versions = "*" +files = [ ] +groups = [ "main" ] +markers = "extra == \"root-extra-one\" and extra != \"root-extra-two\"" + +[[package]] +name = "conflicting-dep" +version = "1.2.0" +description = "" +optional = true +python-versions = "*" +files = [ ] +groups = [ "main" ] +markers = "extra != \"root-extra-one\" and extra == \"root-extra-two\"" + +[[package]] +name = "intermediate-dep" +version = "1.0.0" +description = "" +optional = true +python-versions = "*" +files = [ ] +groups = [ "main" ] +markers = "extra == \"root-extra-one\" and extra != \"root-extra-two\" or extra == \"root-extra-two\" and extra != \"root-extra-one\"" + +[[package.dependencies.conflicting-dep]] +version = "1.1.0" +optional = true +markers = 'extra == "extra-one" and extra != "extra-two"' + +[[package.dependencies.conflicting-dep]] +version = "1.2.0" +optional = true +markers = 'extra != "extra-one" and extra == "extra-two"' + + [package.extras] + extra-one = [ "conflicting-dep (==1.1.0)", "conflicting-dep (==1.2.0)" ] + extra-two = [ "conflicting-dep (==1.1.0)", "conflicting-dep (==1.2.0)" ] + +[extras] +root-extra-one = [ "intermediate-dep", "intermediate-dep" ] +root-extra-two = [ "intermediate-dep", "intermediate-dep" ] + +[metadata] +lock-version = "2.1" +python-versions = "*" +content-hash = "123456789" diff --git a/tests/installation/fixtures/with-dependencies-differing-extras.test b/tests/installation/fixtures/with-dependencies-differing-extras.test new file mode 100644 index 00000000000..7dcaacf26ae --- /dev/null +++ b/tests/installation/fixtures/with-dependencies-differing-extras.test @@ -0,0 +1,52 @@ +[[package]] +name = "demo" +version = "1.0.0" +description = "" +optional = true +python-versions = "*" +files = [ ] +groups = [ "main" ] +markers = "extra == \"extra-one\" and extra != \"extra-two\" or extra == \"extra-two\" and extra != \"extra-one\"" + +[package.dependencies.transitive-dep-one] +version = "1.1.0" +optional = true +markers = 'extra == "demo-extra-one" and extra != "demo-extra-two"' + +[package.dependencies.transitive-dep-two] +version = "1.2.0" +optional = true +markers = 'extra != "demo-extra-one" and extra == "demo-extra-two"' + + [package.extras] + demo-extra-one = [ "transitive-dep-one", "transitive-dep-two" ] + demo-extra-two = [ "transitive-dep-one", "transitive-dep-two" ] + +[[package]] +name = "transitive-dep-one" +version = "1.1.0" +description = "" +optional = true +python-versions = "*" +files = [ ] +groups = [ "main" ] +markers = "extra == \"extra-one\" and extra != \"extra-two\"" + +[[package]] +name = "transitive-dep-two" +version = "1.2.0" +description = "" +optional = true +python-versions = "*" +files = [ ] +groups = [ "main" ] +markers = "extra != \"extra-one\" and extra == \"extra-two\"" + +[extras] +extra-one = [ "demo", "demo" ] +extra-two = [ "demo", "demo" ] + +[metadata] +lock-version = "2.1" +python-versions = "*" +content-hash = "123456789" diff --git a/tests/installation/fixtures/with-exclusive-extras.test b/tests/installation/fixtures/with-exclusive-extras.test new file mode 100644 index 00000000000..c4764e4a8af --- /dev/null +++ b/tests/installation/fixtures/with-exclusive-extras.test @@ -0,0 +1,38 @@ +[[package]] +name = "torch" +version = "1.11.0+cpu" +description = "" +optional = true +python-versions = "*" +files = [] +groups = [ "main" ] +markers = "extra == \"cpu\" and extra != \"cuda\"" + +[package.source] +reference = "pytorch-cpu" +type = "legacy" +url = "https://download.pytorch.org/whl/cpu" + +[[package]] +name = "torch" +version = "1.11.0+cuda" +description = "" +optional = true +python-versions = "*" +files = [] +groups = [ "main" ] +markers = "extra != \"cpu\" and extra == \"cuda\"" + +[package.source] +reference = "pytorch-cuda" +type = "legacy" +url = "https://download.pytorch.org/whl/cuda" + +[extras] +cpu = ["torch", "torch"] +cuda = ["torch", "torch"] + +[metadata] +python-versions = "*" +lock-version = "2.1" +content-hash = "123456789" diff --git a/tests/installation/test_installer.py b/tests/installation/test_installer.py index f4b81757c82..a8e3b726c36 100644 --- a/tests/installation/test_installer.py +++ b/tests/installation/test_installer.py @@ -1077,6 +1077,358 @@ def test_run_with_dependencies_nested_extras( assert locker.written_data == expected +@pytest.mark.parametrize("root", [True, False]) +@pytest.mark.parametrize("locked", [False, True]) +@pytest.mark.parametrize("extra", [None, "extra-one", "extra-two"]) +def test_run_with_conflicting_dependency_extras( + installer: Installer, + pool: RepositoryPool, + locker: Locker, + installed: CustomInstalledRepository, + repo: Repository, + config: Config, + package: ProjectPackage, + extra: str | None, + locked: bool, + root: bool, +) -> None: + """ + - https://github.com/python-poetry/poetry/issues/6419 + + Tests resolution of extras with conflicting dependencies. Tests in both as direct dependencies of + root package and as transitive dependencies. + """ + # A package with two optional dependencies, one for each extra + # If root, this is the root package, otherwise an intermediate package + main_package = package if root else get_package("intermediate-dep", "1.0.0") + + # Two conflicting versions of a dependency, one in each extra + conflicting_dep_one_pkg = get_package("conflicting-dep", "1.1.0") + conflicting_dep_two_pkg = get_package("conflicting-dep", "1.2.0") + + conflicting_dep_one = Factory.create_dependency( + "conflicting-dep", + { + "version": "1.1.0", + "markers": "extra == 'extra-one' and extra != 'extra-two'", + "optional": True, + }, + ) + conflicting_dep_two = Factory.create_dependency( + "conflicting-dep", + { + "version": "1.2.0", + "markers": "extra != 'extra-one' and extra == 'extra-two'", + "optional": True, + }, + ) + + # Include both just for extra validation that our marker validation works as expected + main_package.extras = { + canonicalize_name("extra-one"): [conflicting_dep_one, conflicting_dep_two], + canonicalize_name("extra-two"): [conflicting_dep_one, conflicting_dep_two], + } + main_package.add_dependency(conflicting_dep_one) + main_package.add_dependency(conflicting_dep_two) + + repo.add_package(conflicting_dep_one_pkg) + repo.add_package(conflicting_dep_two_pkg) + if not root: + repo.add_package(main_package) + + # If we have an intermediate package, add extras to our root package + if not root: + extra_one_dep = Factory.create_dependency( + "intermediate-dep", + { + "version": "1.0.0", + "markers": "extra == 'root-extra-one' and extra != 'root-extra-two'", + "extras": ["extra-one"], + "optional": True, + }, + ) + extra_two_dep = Factory.create_dependency( + "intermediate-dep", + { + "version": "1.0.0", + "markers": "extra != 'root-extra-one' and extra == 'root-extra-two'", + "extras": ["extra-two"], + "optional": True, + }, + ) + package.add_dependency(extra_one_dep) + package.add_dependency(extra_two_dep) + # Include both just for extra validation that our marker validation works as expected + package.extras = { + canonicalize_name("root-extra-one"): [extra_one_dep, extra_two_dep], + canonicalize_name("root-extra-two"): [extra_one_dep, extra_two_dep], + } + + fixture_name = "with-conflicting-dependency-extras-" + ( + "root" if root else "transitive" + ) + locker.locked(locked) + if locked: + locker.mock_lock_data(dict(fixture(fixture_name))) + + if extra is not None: + extras = [f"root-{extra}"] if not root else [extra] + installer.extras(extras) + result = installer.run() + assert result == 0 + + if not locked: + expected = fixture(fixture_name) + assert locker.written_data == expected + + # Results of installation are consistent with the 'extra' input + assert isinstance(installer.executor, Executor) + + expected_installations = [] + if extra == "extra-one": + expected_installations.append(conflicting_dep_one_pkg) + elif extra == "extra-two": + expected_installations.append(conflicting_dep_two_pkg) + if not root and extra is not None: + expected_installations.append(get_package("intermediate-dep", "1.0.0")) + + assert len(installer.executor.installations) == len(expected_installations) + assert set(installer.executor.installations) == set(expected_installations) + + +@pytest.mark.parametrize("locked", [True, False]) +@pytest.mark.parametrize("extra", [None, "cpu", "cuda"]) +def test_run_with_exclusive_extras_different_sources( + installer: Installer, + locker: Locker, + installed: CustomInstalledRepository, + config: Config, + package: ProjectPackage, + extra: str | None, + locked: bool, +) -> None: + """ + - https://github.com/python-poetry/poetry/issues/6409 + - https://github.com/python-poetry/poetry/issues/6419 + - https://github.com/python-poetry/poetry/issues/7748 + - https://github.com/python-poetry/poetry/issues/9537 + """ + # Setup repo for each of our sources + cpu_repo = Repository("pytorch-cpu") + cuda_repo = Repository("pytorch-cuda") + pool = RepositoryPool() + pool.add_repository(cpu_repo) + pool.add_repository(cuda_repo) + config.config["repositories"] = { + "pytorch-cpu": {"url": "https://download.pytorch.org/whl/cpu"}, + "pytorch-cuda": {"url": "https://download.pytorch.org/whl/cuda"}, + } + + # Configure packages that read from each of the different sources + torch_cpu_pkg = get_package("torch", "1.11.0+cpu") + torch_cpu_pkg._source_reference = "pytorch-cpu" + torch_cpu_pkg._source_type = "legacy" + torch_cpu_pkg._source_url = "https://download.pytorch.org/whl/cpu" + torch_cuda_pkg = get_package("torch", "1.11.0+cuda") + torch_cuda_pkg._source_reference = "pytorch-cuda" + torch_cuda_pkg._source_type = "legacy" + torch_cuda_pkg._source_url = "https://download.pytorch.org/whl/cuda" + cpu_repo.add_package(torch_cpu_pkg) + cuda_repo.add_package(torch_cuda_pkg) + + # Depend on each package based on exclusive extras + torch_cpu_dep = Factory.create_dependency( + "torch", + { + "version": "1.11.0+cpu", + "markers": "extra == 'cpu' and extra != 'cuda'", + "source": "pytorch-cpu", + }, + ) + torch_cuda_dep = Factory.create_dependency( + "torch", + { + "version": "1.11.0+cuda", + "markers": "extra != 'cpu' and extra == 'cuda'", + "source": "pytorch-cuda", + }, + ) + package.add_dependency(torch_cpu_dep) + package.add_dependency(torch_cuda_dep) + # We don't want to cheat by only including the correct dependency in the 'extra' mapping + package.extras = { + canonicalize_name("cpu"): [torch_cpu_dep, torch_cuda_dep], + canonicalize_name("cuda"): [torch_cpu_dep, torch_cuda_dep], + } + + # Set locker state + locker.locked(locked) + if locked: + locker.mock_lock_data(dict(fixture("with-exclusive-extras"))) + + # Perform install + installer = Installer( + NullIO(), + MockEnv(), + package, + locker, + pool, + config, + installed=installed, + executor=Executor( + MockEnv(), + pool, + config, + NullIO(), + ), + ) + if extra is not None: + installer.extras([extra]) + result = installer.run() + assert result == 0 + + # Results of locking are expected and installation are consistent with the 'extra' input + if not locked: + expected = fixture("with-exclusive-extras") + assert locker.written_data == expected + assert isinstance(installer.executor, Executor) + if extra is None: + assert len(installer.executor.installations) == 0 + else: + assert len(installer.executor.installations) == 1 + version = f"1.11.0+{extra}" + source_url = f"https://download.pytorch.org/whl/{extra}" + source_reference = f"pytorch-{extra}" + assert installer.executor.installations[0] == Package( + "torch", + version, + source_type="legacy", + source_url=source_url, + source_reference=source_reference, + ) + + +@pytest.mark.parametrize("locked", [True, False]) +@pytest.mark.parametrize("extra", [None, "extra-one", "extra-two"]) +def test_run_with_different_dependency_extras( + installer: Installer, + pool: RepositoryPool, + locker: Locker, + installed: CustomInstalledRepository, + repo: Repository, + config: Config, + package: ProjectPackage, + extra: str | None, + locked: bool, +) -> None: + """ + - https://github.com/python-poetry/poetry/issues/834 + - https://github.com/python-poetry/poetry/issues/7748 + + This tests different sets of extras in a dependency of the root project. These different dependency extras are + themselves conditioned on extras in the root project. + """ + # Three packages in addition to root: demo (direct dependency) and two transitive dep packages + demo_pkg = get_package("demo", "1.0.0") + transitive_one_pkg = get_package("transitive-dep-one", "1.1.0") + transitive_two_pkg = get_package("transitive-dep-two", "1.2.0") + + # Switch each transitive dependency based on extra markers in the 'demo' package + transitive_dep_one = Factory.create_dependency( + "transitive-dep-one", + { + "version": "1.1.0", + "markers": "extra == 'demo-extra-one' and extra != 'demo-extra-two'", + "optional": True, + }, + ) + transitive_dep_two = Factory.create_dependency( + "transitive-dep-two", + { + "version": "1.2.0", + "markers": "extra != 'demo-extra-one' and extra == 'demo-extra-two'", + "optional": True, + }, + ) + # Include both packages in both demo extras, to validate that they're filtered out based on extra markers alone + demo_pkg.extras = { + canonicalize_name("demo-extra-one"): [ + get_dependency("transitive-dep-one"), + get_dependency("transitive-dep-two"), + ], + canonicalize_name("demo-extra-two"): [ + get_dependency("transitive-dep-one"), + get_dependency("transitive-dep-two"), + ], + } + demo_pkg.add_dependency(transitive_dep_one) + demo_pkg.add_dependency(transitive_dep_two) + + # Now define the demo dependency, similarly switched on extra markers in the root package + extra_one_dep = Factory.create_dependency( + "demo", + { + "version": "1.0.0", + "markers": "extra == 'extra-one' and extra != 'extra-two'", + "extras": ["demo-extra-one"], + }, + ) + extra_two_dep = Factory.create_dependency( + "demo", + { + "version": "1.0.0", + "markers": "extra != 'extra-one' and extra == 'extra-two'", + "extras": ["demo-extra-two"], + }, + ) + package.add_dependency(extra_one_dep) + package.add_dependency(extra_two_dep) + # Again we don't want to cheat by only including the correct dependency in the 'extra' mapping + package.extras = { + canonicalize_name("extra-one"): [extra_one_dep, extra_two_dep], + canonicalize_name("extra-two"): [extra_one_dep, extra_two_dep], + } + + repo.add_package(demo_pkg) + repo.add_package(transitive_one_pkg) + repo.add_package(transitive_two_pkg) + + locker.locked(locked) + if locked: + locker.mock_lock_data(dict(fixture("with-dependencies-differing-extras"))) + + installer = Installer( + NullIO(), + MockEnv(), + package, + locker, + pool, + config, + installed=installed, + executor=Executor( + MockEnv(), + pool, + config, + NullIO(), + ), + ) + if extra is not None: + installer.extras([extra]) + result = installer.run() + assert result == 0 + + if not locked: + expected = fixture("with-dependencies-differing-extras") + assert locker.written_data == expected + + # Results of installation are consistent with the 'extra' input + assert isinstance(installer.executor, Executor) + if extra is None: + assert len(installer.executor.installations) == 0 + else: + assert len(installer.executor.installations) == 2 + + @pytest.mark.parametrize("is_locked", [False, True]) @pytest.mark.parametrize("is_installed", [False, True]) @pytest.mark.parametrize("with_extras", [False, True]) diff --git a/tests/puzzle/test_solver.py b/tests/puzzle/test_solver.py index a51a520dc33..a566e5c588e 100644 --- a/tests/puzzle/test_solver.py +++ b/tests/puzzle/test_solver.py @@ -4755,6 +4755,136 @@ def test_solver_resolves_duplicate_dependency_in_extra( ) +def test_solver_resolves_conflicting_dependency_in_root_extra( + package: ProjectPackage, + pool: RepositoryPool, + repo: Repository, + io: NullIO, +) -> None: + package_a1 = get_package("A", "1.0") + package_a2 = get_package("A", "2.0") + + dep = get_dependency("A", {"version": "1.0", "markers": "extra != 'foo'"}) + package.add_dependency(dep) + + dep_extra = get_dependency("A", "2.0", optional=True) + dep_extra._in_extras = [canonicalize_name("foo")] + package.extras = {canonicalize_name("foo"): [dep_extra]} + package.add_dependency(dep_extra) + + repo.add_package(package_a1) + repo.add_package(package_a2) + + solver = Solver(package, pool, [], [], io) + transaction = solver.solve() + + check_solver_result( + transaction, + ( + [ + {"job": "install", "package": package_a1}, + {"job": "install", "package": package_a2}, + ] + ), + ) + solved_packages = transaction.get_solved_packages() + assert solved_packages[package_a1].markers["main"] == parse_marker("extra != 'foo'") + assert solved_packages[package_a2].markers["main"] == parse_marker("extra == 'foo'") + + +def test_solver_resolves_conflicting_dependency_in_root_extras( + package: ProjectPackage, + pool: RepositoryPool, + repo: Repository, + io: NullIO, +) -> None: + package_a1 = get_package("A", "1.0") + package_a2 = get_package("A", "2.0") + + dep_extra1 = get_dependency( # extra == 'foo' is implicit via _in_extras! + "A", {"version": "1.0", "markers": "extra != 'bar'"}, optional=True + ) + dep_extra1._in_extras = [canonicalize_name("foo")] + package.add_dependency(dep_extra1) + + dep_extra2 = get_dependency( # extra == 'bar' is implicit via _in_extras! + "A", {"version": "2.0", "markers": "extra != 'foo'"}, optional=True + ) + dep_extra2._in_extras = [canonicalize_name("bar")] + package.extras = { + canonicalize_name("foo"): [dep_extra1], + canonicalize_name("bar"): [dep_extra2], + } + package.add_dependency(dep_extra1) + package.add_dependency(dep_extra2) + + repo.add_package(package_a1) + repo.add_package(package_a2) + + solver = Solver(package, pool, [], [], io) + transaction = solver.solve() + + check_solver_result( + transaction, + ( + [ + {"job": "install", "package": package_a1}, + {"job": "install", "package": package_a2}, + ] + ), + ) + solved_packages = transaction.get_solved_packages() + assert solved_packages[package_a1].markers["main"] == parse_marker( + "extra != 'bar' and extra == 'foo'" + ) + assert solved_packages[package_a2].markers["main"] == parse_marker( + "extra != 'foo' and extra == 'bar'" + ) + + +@pytest.mark.parametrize("with_extra", [False, True]) +def test_solver_resolves_duplicate_dependency_in_root_extra_for_installation( + package: ProjectPackage, + pool: RepositoryPool, + repo: Repository, + io: NullIO, + with_extra: bool, +) -> None: + """ + Without extras, a newer version of A can be chosen than with root extras. + """ + extra = [canonicalize_name("foo")] if with_extra else [] + + package_a1 = get_package("A", "1.0") + package_a2 = get_package("A", "2.0") + + dep = get_dependency("A", ">=1.0") + package.add_dependency(dep) + + dep_extra = get_dependency("A", "^1.0", optional=True) + dep_extra.marker = parse_marker("extra == 'foo'") + package.extras = {canonicalize_name("foo"): [dep_extra]} + package.add_dependency(dep_extra) + + repo.add_package(package_a1) + repo.add_package(package_a2) + + solver = Solver( + package, pool, [], [package_a1, package_a2], io, active_root_extras=extra + ) + with solver.use_environment(MockEnv()): + transaction = solver.solve() + + check_solver_result( + transaction, + ( + [ + {"job": "install", "package": package_a1 if with_extra else package_a2}, + ] + ), + ) + + def test_solver_resolves_duplicate_dependencies_with_restricted_extras( package: ProjectPackage, pool: RepositoryPool, diff --git a/tests/puzzle/test_transaction.py b/tests/puzzle/test_transaction.py index a68c981d159..2dcea971e79 100644 --- a/tests/puzzle/test_transaction.py +++ b/tests/puzzle/test_transaction.py @@ -429,3 +429,55 @@ def test_calculate_operations_extras( ), ops, ) + + +@pytest.mark.parametrize("extra", ["", "foo", "bar"]) +def test_calculate_operations_extras_no_redundant_uninstall(extra: str) -> None: + extra1 = canonicalize_name("foo") + extra2 = canonicalize_name("bar") + package = ProjectPackage("root", "1.0") + dep_a1 = Dependency("a", "1", optional=True) + dep_a1._in_extras = [canonicalize_name("foo")] + dep_a1.marker = parse_marker("extra != 'bar'") + dep_a2 = Dependency("a", "2", optional=True) + dep_a2._in_extras = [canonicalize_name("bar")] + dep_a2.marker = parse_marker("extra != 'foo'") + package.add_dependency(dep_a1) + package.add_dependency(dep_a2) + package.extras = {extra1: [dep_a1], extra2: [dep_a2]} + opt_a1 = Package("a", "1") + opt_a1.optional = True + opt_a2 = Package("a", "2") + opt_a2.optional = True + + transaction = Transaction( + [Package("a", "1")], + { + opt_a1: TransitivePackageInfo( + 0, {"main"}, {"main": parse_marker("extra == 'foo' and extra != 'bar'")} + ), + opt_a2: TransitivePackageInfo( + 0, {"main"}, {"main": parse_marker("extra == 'bar' and extra != 'foo'")} + ), + }, + [Package("a", "1")], + package, + {"python_version": "3.9"}, + {"main"}, + ) + + if not extra: + ops = [{"job": "remove", "package": Package("a", "1")}] + elif extra == "foo": + ops = [{"job": "install", "package": Package("a", "1"), "skipped": True}] + elif extra == "bar": + ops = [{"job": "update", "from": Package("a", "1"), "to": Package("a", "2")}] + else: + raise NotImplementedError + + check_operations( + transaction.calculate_operations( + extras=set() if not extra else {canonicalize_name(extra)}, + ), + ops, + )