見出し画像

Poetryのプロジェクトから未使用のパッケージを一括削除する🗑️

こんにちは、Caratの斎藤( @saitoxu )です。

今回は、Poetryで管理しているPythonのプロジェクトから「入れてるけど実際は未使用のパッケージ」をまとめて削除する方法について紹介します。

背景

CaratではAIジョブサーチというLLMを使った対話型求人検索サービスを開発・運営しています。

AIジョブサーチはAPIをPythonで書いており、Poetryで必要なパッケージを管理しています。
会社としても自分自身としてもLLMを本格的に使った最初のプロダクトだったので、最初はLangChainやらなんやら色々なパッケージを入れては試すということをやってました。

そのうちに必要なパッケージは固まってきたのですが、入れたときから月日が経ち、何が使われていて何が使われていないかパッと分からなくなってしまいました(ありますよね。え、ない?)。

使われていないパッケージがあるとやっぱり困る(e.g. dependabotでパッケージのバージョンアップを自動化したいとき)ということで、今まで放置してましたが、使っていないパッケージをまとめて削除することにしました。

方針

マニュアルで消していくには辛い数だったので、実際にソースコードで使われているパッケージを検出し、pyproject.tomlにあるパッケージ一覧から引けば、消して良いパッケージが残るだろう、という方針でいきました。

手順

①消して良いパッケージを検出する

以下のようなコードを書き、入れてるけど実際は使っていない、削除可能なパッケージを検出しました。

import ast
import os

import toml


def get_dependencies_from_pyproject(file_path="pyproject.toml") -> list[str]:
    with open(file_path, "r") as file:
        pyproject_content = toml.load(file)
    return pyproject_content["tool"]["poetry"]["dependencies"]


def get_imported_modules_from_file(file_path) -> set[str]:
    with open(file_path, "r") as file:
        tree = ast.parse(file.read(), filename=file_path)

    imported_modules = set()

    for node in ast.walk(tree):
        if isinstance(node, ast.Import):
            for alias in node.names:
                imported_modules.add(alias.name)
        elif isinstance(node, ast.ImportFrom):
            imported_modules.add(node.module)

    return imported_modules


def get_imported_modules_from_directory(directory, exclude_dirs=None) -> set[str]:
    if exclude_dirs is None:
        exclude_dirs = set()
    imported_modules = set()
    for root, dirs, files in os.walk(directory):
        dirs[:] = [d for d in dirs if d not in exclude_dirs]
        for file in files:
            if file.endswith(".py"):
                file_path = os.path.join(root, file)
                imported_modules.update(get_imported_modules_from_file(file_path))
    return imported_modules


def main():
    pyproject_dependencies = get_dependencies_from_pyproject()
    exclude_dirs = {
        ".github",
        ".venv",
        ".vscode",
    }
    project_modules = get_imported_modules_from_directory(".", exclude_dirs)
    unused_dependencies = set(pyproject_dependencies) - project_modules

    print("Unused dependencies:")
    for dep in unused_dependencies:
        print(dep)


if __name__ == "__main__":
    main()

軽く各関数の解説をします。

  • get_dependencies_from_pyproject: pyproject.tomlからパッケージ名の一覧を取得します。

  • get_imported_modules_from_file: Pythonファイルをパースして、ファイルでインポートしているモジュール名の一覧を取得します。

  • get_imported_modules_from_directory: 指定されたディレクトリを再帰的に辿り、get_imported_modules_from_fileを使ってそのディレクトリ配下にあるPythonファイルで使われているモジュール名一覧を取得します。走査対象から外すディレクトリも指定できます。

上記コードを実行すると、以下のように未使用のパッケージが表示されます。

$ python script.py
Unused dependencies:
package1
package2
package3
...

②検出されたパッケージを削除する

①で検出されたパッケージを削除していくだけです!
注意点としては、たまにパッケージ名とインポートするときのモジュール名が異なるパッケージがあり、そういったパッケージは削除対象になってしまいます。
スクリプトを修正して回避できますが、自分たちの場合は大した数ではなかったので、マニュアルで削除対象から除きました。

おわりに

以上、Poetryで管理しているPythonのプロジェクトから「入れてるけど実際は未使用のパッケージ」をまとめて削除する方法の紹介でした。

最後に、AIジョブサーチのチームではPythonや生成AI/LLMを使ったプロダクト開発を担っていただけるエンジニアを積極的に募集しております。
少しでも興味ある方がいらっしゃれば、斎藤までお気軽にご連絡ください!


この記事が気に入ったらサポートをしてみませんか?