Python3.8～3.12をサポートするツールでnumpyを利用すると、`poetry install`でエラーが発生した

:::note info
この記事に記載されている`poetry install`コマンドは、2024/01/16（numpyの最新バージョンが1.26.3のとき）に実行しました。
:::

# 環境
* Python 3.12.1
* poetry 1.7.1

# やりたいこと
Python3.8～3.12で動くツールを作りたいです。依存ライブラリはnumpyを利用します。
poetryで依存ライブラリを管理したいので、以下の`pyproject.toml`を定義しました。

```toml:pyproject.toml
[tool.poetry]
name = "install-test-312"
version = "0.1.0"
description = ""
authors = ["yuji38kwmt"]
readme = "README.md"
packages = [{include = "install_test_312"}]

[tool.poetry.dependencies]
python = "^3.8"
numpy = "*"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
```

# 何が起きたか
Python3.12環境で`poetry install`を実行したら、エラーが発生しました。


```
$ poetry install
Updating dependencies
Resolving dependencies... (0.8s)

Package operations: 1 install, 0 updates, 0 removals

  • Installing numpy (1.24.4): Failed

  ChefBuildError

  Backend 'setuptools.build_meta:__legacy__' is not available.
  
  Traceback (most recent call last):
    File "/home/vagrant/.pyenv/versions/3.12.1/lib/python3.12/site-packages/pyproject_hooks/_in_process/_in_process.py", line 77, in _build_backend
      obj = import_module(mod_path)
            ^^^^^^^^^^^^^^^^^^^^^^^
    File "/home/vagrant/.pyenv/versions/3.12.1/lib/python3.12/importlib/__init__.py", line 90, in import_module
      return _bootstrap._gcd_import(name[level:], package, level)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
    File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
    File "<frozen importlib._bootstrap>", line 1310, in _find_and_load_unlocked
    File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
    File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
    File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
    File "<frozen importlib._bootstrap>", line 1331, in _find_and_load_unlocked
    File "<frozen importlib._bootstrap>", line 935, in _load_unlocked
    File "<frozen importlib._bootstrap_external>", line 994, in exec_module
    File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
    File "/tmp/tmp_2lnurl6/.venv/lib/python3.12/site-packages/setuptools/__init__.py", line 10, in <module>
      import distutils.core
  ModuleNotFoundError: No module named 'distutils'
  

  at ~/.pyenv/versions/3.12.1/lib/python3.12/site-packages/poetry/installation/chef.py:164 in _prepare
      160│ 
      161│                 error = ChefBuildError("\n\n".join(message_parts))
      162│ 
      163│             if error is not None:
    → 164│                 raise error from None
      165│ 
      166│             return path
      167│ 
      168│     def _prepare_sdist(self, archive: Path, destination: Path | None = None) -> Path:

Note: This error originates from the build backend, and is likely not a problem with poetry but with numpy (1.24.4) not supporting PEP 517 builds. You can verify this by running 'pip wheel --no-cache-dir --use-pep517 "numpy (==1.24.4)"'.

```

numpy 1.24.4のインストールで失敗したようです。

# 原因
エラーメッセージには
>This error originates from the build backend, and is likely not a problem with poetry but with numpy (1.24.4) not supporting PEP 517 builds.

とあるのでpoetryではなくnumpyの問題のようです。

[numpy 1.24.4のリリースノート](https://numpy.org/devdocs/release/1.24.4-notes.html)には、
>The Python versions supported by this release are 3.8-3.11.

と書いてありました。したがって、Python3.12に対応していないnumpy1.24.4をインストールしようとしたことがエラーの原因のようです。

ではなぜ、poetryはPython3.12に対応していないnumpy 1.24.4をインストールしようとしたのでしょうか？

[numpy1.24.4の`setup.py`](https://github.com/numpy/numpy/blob/9315a9072b2636f75c831b4eca9f42a5f67ca2fb/setup.py#L435)には`python_requires>=3.8`と記載されています。[^1]`python_requires>=3.8,<3.12`とは記載されていません。したがって、poetryはnumpy 1.24.4がPython3.12に対応していないことが分からず、numpy 1.24.4のインストールを試みたようです。



[^1]: wheelファイルに含まれる`numpy-1.24.4.dist-info/METADTA`の`Requires-Python: >=3.8`に相当します。

```python:setup.py
def setup_package():
    ...
    metadata = dict(
        ...
        python_requires='>=3.8'
```

そして、numpy1.25以降はPython3.12に対応していますがPython3.8には対応していません。
poetryはnumpyの最新バージョン1.26.3から1つずつバージョンを下げていき、Python3.8以上に対応しているかを確認していきます。
Python3.8に対応している最新のnumpyバージョンが1.24.4だったので、numpy1.24.4をインストールしたようです。
`poetry install`に`-vvv`オプションを指定すると、バージョンを1つずつ下げる挙動を確認できました。

```
$ poetry install -vvv
Loading configuration file /home/vagrant/.config/pypoetry/config.toml
Loading configuration file /home/vagrant/.config/pypoetry/auth.toml
Using virtualenv: /home/vagrant/Documents/study/python3.12/install-test-3.12/.venv
Updating dependencies
Resolving dependencies...
   1: fact: install-test-312 is 0.1.0
   1: derived: install-test-312
   1: fact: install-test-312 depends on numpy (*)
   1: selecting install-test-312 (0.1.0)
   1: derived: numpy
[keyring.backend] Loading KWallet
[keyring.backend] Loading SecretService
[keyring.backend] Loading Windows
[keyring.backend] Loading chainer
[keyring.backend] Loading libsecret
[keyring.backend] Loading macOS
Creating new session for pypi.org
Source (PyPI): 103 packages found for numpy *
   1: fact: numpy (1.26.3) requires Python >=3.9
   1: derived: not numpy (==1.26.3)
   1: fact: numpy (1.26.2) requires Python >=3.9
   1: derived: not numpy (==1.26.2)
   1: fact: numpy (1.26.1) requires Python <3.13,>=3.9
   1: derived: not numpy (==1.26.1)
   1: fact: numpy (1.26.0) requires Python <3.13,>=3.9
   1: derived: not numpy (==1.26.0)
   1: fact: numpy (1.25.2) requires Python >=3.9
   1: derived: not numpy (==1.25.2)
   1: fact: numpy (1.25.1) requires Python >=3.9
   1: derived: not numpy (==1.25.1)
   1: fact: numpy (1.25.0) requires Python >=3.9
   1: derived: not numpy (==1.25.0)
   1: selecting numpy (1.24.4)
   1: Version solving took 0.381 seconds.
   1: Tried 1 solutions.
Finding the necessary packages for the current system
Source (PyPI): 1 packages found for numpy *
Package operations: 1 install, 0 updates, 0 removals
  • Installing numpy (1.24.4): Pending...
...
```

## `ModuleNotFoundError: No module named 'distutils'`のエラーについて
`poetry install`のエラーメッセージに、`ModuleNotFoundError: No module named 'distutils'`というメッセージがあります。numpy 1.24.4のインストールに失敗する原因は、このエラーです。
Python 3.12からdistutilsを利用できなくなったため、このエラーが発生しました。[^2]

[^2]: https://docs.python.org/3/whatsnew/3.12.html#distutils


# 解決方法
Pythonのバージョンごとに、numpyのバージョンを指定すれば`poetry install`が実行できました。

```toml:pyproject.toml
numpy = [
  {version = ">=1.26", python = ">=3.9"},
  {version = "<1.25", python = "=3.8"}
]
```

```
$ poetry install -vvv
Loading configuration file /home/vagrant/.config/pypoetry/config.toml
Loading configuration file /home/vagrant/.config/pypoetry/auth.toml
Using virtualenv: /home/vagrant/Documents/study/20240201/312-gte38-for-numpy/.venv
Updating dependencies
Resolving dependencies...
   1: fact: install-test-312 is 0.1.0
   1: derived: install-test-312
   0: Duplicate dependencies for numpy
   0: Different requirements found for numpy (>=1.26) with markers python_version >= "3.9" and numpy (<1.25) with markers python_version == "3.8".
   1: Version solving took 0.007 seconds.
   1: Tried 1 solutions.
   0: Retrying dependency resolution with the following overrides ({Package('install-test-312', '0.1.0'): {'numpy': <Dependency numpy (>=1.26)>}}).
   1: fact: install-test-312 is 0.1.0
   1: derived: install-test-312
   1: fact: install-test-312 depends on numpy (>=1.26)
   1: selecting install-test-312 (0.1.0)
   1: derived: numpy (>=1.26)
   1: selecting numpy (1.26.3)
   1: Version solving took 0.451 seconds.
   1: Tried 1 solutions.
   0: Retrying dependency resolution with the following overrides ({Package('install-test-312', '0.1.0'): {'numpy': <Dependency numpy (<1.25)>}}).
   1: fact: install-test-312 is 0.1.0
   1: derived: install-test-312
   1: fact: install-test-312 depends on numpy (<1.25)
   1: selecting install-test-312 (0.1.0)
   1: derived: numpy (<1.25)
   1: selecting numpy (1.24.4)
   1: Version solving took 0.015 seconds.
   1: Tried 1 solutions.
   0: Complete version solving took 0.473 seconds with 2 overrides
   0: Resolved with overrides: ({Package('install-test-312', '0.1.0'): {'numpy': <Dependency numpy (>=1.26)>}}), ({Package('install-test-312', '0.1.0'): {'numpy': <Dependency numpy (<1.25)>}})

Finding the necessary packages for the current system

Package operations: 1 install, 0 updates, 0 removals

  • Installing numpy (1.26.3)

Writing lock file

Installing the current project: install-test-312 (0.1.0)
  - Building package install-test-312 in editable mode
  - Adding install_test_312.pth to /home/vagrant/Documents/study/20240201/312-gte38-for-numpy/.venv/lib/python3.12/site-packages for /home/vagrant/Documents/study/20240201/312-gte38-for-numpy
  - Adding the install_test_312-0.1.0.dist-info directory to /home/vagrant/Documents/study/20240201/312-gte38-for-numpy/.venv/lib/python3.12/site-packages
```

numpy 1.26.3がインストールされました。
ちなみに、`poetry.lock`には以下のように`numpy`の情報が複数記載されていました。

```:poetry.lock

[[package]]
name = "numpy"
version = "1.24.4"
description = "Fundamental package for array computing in Python"
optional = false
python-versions = ">=3.8"
files = [...]

[[package]]
name = "numpy"
version = "1.26.3"
description = "Fundamental package for array computing in Python"
optional = false
python-versions = ">=3.9"
files = [...]
```

さらに補足ですが、`poetry build`で生成されたwheelファイル内の`METADATA`ファイルには、以下のようにnumpyのバージョンが記載されていました。


```plain:install_test_312-0.1.0-py3-none-any.whl/install_test_312-0.1.0.dist-info/METADATA
Requires-Dist: numpy (<1.25) ; python_version == "3.8"
Requires-Dist: numpy (>=1.26) ; python_version >= "3.9"
```


## 注意：poetry1.7.1での`poetry show numpy`の結果は正しくない
`poetry install`のログには`Installing numpy (1.26.3)`と出力されました。
しかし`poetry show`コマンドでは"numpy 1.24.4"と出力されました。

```
$ poetry show numpy
 name         : numpy                                             
 version      : 1.24.4                                            
 description  : Fundamental package for array computing in Python 
```


詳細は別記事に記載しました。

https://qiita.com/yuji38kwmt/items/687e8c2ad78baa715eda