初めてのPoetry addでAssertionErrorが出たら確認すべきこと

Poetryを触りはじめのときにAssertionErrorが出た場合、一番引っ掛かりそうなケースについて書いてみました。
2023.06.13

Poetryの操作に慣れていなかったこともあり、セットアップでエラーに遭遇しました。Poetryを触りはじめの頃には恐らくよくあるエラーのはずです。ここでは、発生したエラーとその解決方法について説明します。

発生したエラーについて

Poetryを触りはじめの人なら見覚えがあるかもしれません。

at ~/Library/Application Support/pypoetry/venv/lib/python3.10/site-packages/poetry/mixology/incompatibility.py:113 in __str__
      109│         )
      110│
      111│     def __str__(self) -> str:
      112│         if isinstance(self._cause, DependencyCause):
    → 113│             assert len(self._terms) == 2
      114│
      115│             depender = self._terms[0]
      116│             dependee = self._terms[1]
      117│             assert depender.is_positive()

ここだけ見ると分かりづらいので詳細まで広げます。

% poetry add openai -vvv
Creating new session for pypi.org
Source (PyPI): 68 packages found for openai *
Using version ^0.27.8 for openai

Updating dependencies
Resolving dependencies...
   1: fact: openai is 0.1.0
   1: derived: openai
   1: Version solving took 0.001 seconds.
   1: Tried 1 solutions.

  Stack trace:

  16  ~/Library/Application Support/pypoetry/venv/lib/python3.10/site-packages/cleo/application.py:327 in run
       325│
       326│             try:
     → 327│                 exit_code = self._run(io)
       328│             except BrokenPipeError:
       329│                 # If we are piped to another process, it may close early and send a

  15  ~/Library/Application Support/pypoetry/venv/lib/python3.10/site-packages/poetry/console/application.py:190 in _run
       188│         self._load_plugins(io)
       189│
     → 190│         exit_code: int = super()._run(io)
       191│         return exit_code
       192│

  14  ~/Library/Application Support/pypoetry/venv/lib/python3.10/site-packages/cleo/application.py:431 in _run
       429│             io.input.interactive(interactive)
       430│
     → 431│         exit_code = self._run_command(command, io)
       432│         self._running_command = None
       433│

  13  ~/Library/Application Support/pypoetry/venv/lib/python3.10/site-packages/cleo/application.py:473 in _run_command
       471│
       472│         if error is not None:
     → 473│             raise error
       474│
       475│         return terminate_event.exit_code

  12  ~/Library/Application Support/pypoetry/venv/lib/python3.10/site-packages/cleo/application.py:457 in _run_command
       455│
       456│             if command_event.command_should_run():
     → 457│                 exit_code = command.run(io)
       458│             else:
       459│                 exit_code = ConsoleCommandEvent.RETURN_CODE_DISABLED

  11  ~/Library/Application Support/pypoetry/venv/lib/python3.10/site-packages/cleo/commands/base_command.py:119 in run
       117│         io.input.validate()
       118│
     → 119│         status_code = self.execute(io)
       120│
       121│         if status_code is None:

  10  ~/Library/Application Support/pypoetry/venv/lib/python3.10/site-packages/cleo/commands/command.py:62 in execute
        60│
        61│         try:
     →  62│             return self.handle()
        63│         except KeyboardInterrupt:
        64│             return 1

   9  ~/Library/Application Support/pypoetry/venv/lib/python3.10/site-packages/poetry/console/commands/add.py:262 in handle
       260│         self.installer.whitelist([r["name"] for r in requirements])
       261│
     → 262│         status = self.installer.run()
       263│
       264│         if status == 0 and not self.option("dry-run"):

   8  ~/Library/Application Support/pypoetry/venv/lib/python3.10/site-packages/poetry/installation/installer.py:116 in run
       114│             self._execute_operations = False
       115│
     → 116│         return self._do_install()
       117│
       118│     def dry_run(self, dry_run: bool = True) -> Installer:

   7  ~/Library/Application Support/pypoetry/venv/lib/python3.10/site-packages/poetry/installation/installer.py:263 in _do_install
       261│                 source_root=self._env.path.joinpath("src")
       262│             ):
     → 263│                 ops = solver.solve(use_latest=self._whitelist).calculate_operations()
       264│         else:
       265│             self._io.write_line("Installing dependencies from lock file")

   6  ~/Library/Application Support/pypoetry/venv/lib/python3.10/site-packages/poetry/puzzle/solver.py:74 in solve
        72│         with self._progress(), self._provider.use_latest_for(use_latest or []):
        73│             start = time.time()
     →  74│             packages, depths = self._solve()
        75│             end = time.time()
        76│

   5  ~/Library/Application Support/pypoetry/venv/lib/python3.10/site-packages/poetry/puzzle/solver.py:157 in _solve
       155│
       156│         try:
     → 157│             result = resolve_version(self._package, self._provider)
       158│
       159│             packages = result.packages

   4  ~/Library/Application Support/pypoetry/venv/lib/python3.10/site-packages/poetry/mixology/__init__.py:18 in resolve_version
        16│     solver = VersionSolver(root, provider)
        17│
     →  18│     return solver.solve()
        19│

   3  ~/Library/Application Support/pypoetry/venv/lib/python3.10/site-packages/poetry/mixology/version_solver.py:112 in solve
       110│             while next is not None:
       111│                 self._propagate(next)
     → 112│                 next = self._choose_package_version()
       113│
       114│             return self._result()

   2  ~/Library/Application Support/pypoetry/venv/lib/python3.10/site-packages/poetry/mixology/version_solver.py:432 in _choose_package_version
       430│         conflict = False
       431│         for incompatibility in self._provider.incompatibilities_for(package):
     → 432│             self._add_incompatibility(incompatibility)
       433│
       434│             # If an incompatibility is already satisfied, then selecting version

   1  ~/Library/Application Support/pypoetry/venv/lib/python3.10/site-packages/poetry/mixology/version_solver.py:468 in _add_incompatibility
       466│
       467│     def _add_incompatibility(self, incompatibility: Incompatibility) -> None:
     → 468│         self._log(f"fact: {incompatibility}")
       469│
       470│         for term in incompatibility.terms:

後出しになりますが、今回のpyproject.tomlは以下の通り。

[tool.poetry]
name = "openai"
version = "0.1.0"

パッケージ名とインストールするライブラリ名が同一の場合に衝突した故に起こったエラーでした。これはパッケージ開発用として、自身もインストールされる前提での一面も持つPoetryの特性とも言えそうです。

対策としては2通り。

  • pyproject.tomlのname指定を被らないようにする
  • pipでインストールする( poetry run pip install openai )

Poetryの仕様に沿う場合はname指定を被らないようにするほうがベターだと思われます。

あとがき

pipenvでは何も考えずにプロジェクト名を決めていて、インストールするライブラリ名と被ることも多々ありました。同じノリでPoetryを触るとエラーになる状況をみて、特定用途向けなのだなと理解しました。

Poetry自体触った時間がまだまだ少ないので、実装意図等確認しつつ挑戦することにします。