rcmdnk's blog

入門 Python 3 第2版

uvでPythonのプロジェクトを管理する際に、 パッケージの依存関係でextrasなパッケージをoptional-dependenciesで指定することができます。

それらのパッケージはuv syncではインストールされないので、 uvの環境でインストールしたい場合は uv sync --extra <EXTRA>でインストールするか、 dependency-groupsで設定してインストールできるようにしておく必要があります。

ただdependency-groupsで指定する場合、 そのまま同じパッケージを追加するとバージョンなどを複数の箇所に書くことになり 管理が面倒になります。

そのような重複を避けて書く方法について。

optional-dependenciesでのextraパッケージの指定

pyproject.toml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[project]
name = "my-example-package"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
    "numpy>=2.3.3",
]

[project.optional-dependencies]
data = [
    "pandas>=2.3.2",
]


[dependency-groups]
dev = [
    "pytest >= 8.3.0",
]

こんな感じのpyproject.tomlがあったとします。

何もない状態から

1
2
3
$ uv add "numpy>=2.3.3"
$ uv add --optional data "pandas>=2.3.2"
$ uv add --dev "pytest"

のようにパッケージを加えた状態です。

このパッケージはpipなどでインストールする際に、my-example-packageとだけ指定すると numpyだけインストールされ、pandasはインストールされません。

my-example-package[data]と指定することで、pandasもインストールされます。

これらの場合はpytestはインストールされません。

一方、uv syncではpytestはインストールされますがpandasはインストールされません。 uv sync --extra data、もしくはuv sync --all-extrasとすることで、pandasもインストールされます。

Extraなパッケージは重いライブラリだったりで、 不要な時はインストールしないで済むようにするための仕組みです。

uv syncでextraパッケージをインストールする方法

一方、このパッケージを開発する際にはpandasも必要になることが多いので それも含めた仮想環境を作っておきたいことが多いです。 その場合に上記のようにオプション追加でインストールするのは面倒なので、

pyproject.toml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[project]
name = "my-example-package"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
    "numpy>=2.3.3",
]

[project.optional-dependencies]
data = [
    "pandas>=2.3.2",
]


[dependency-groups]
dev = [
    "pytest>=8.3.0",
    "pandas>=2.3.2",
]

のような感じでdevグループにpandasを追加しておけばuv syncでもインストールされます。

1
$ uv add --dev "pytest"

で追加することも出来ます。

または、pandasがインストールされてない環境も簡単にチェックしたい場合、

pyproject.toml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[project]
name = "my-example-package"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
    "numpy>=2.3.3",
]

[project.optional-dependencies]
data = [
    "pandas>=2.3.2",
]


[dependency-groups]
dev = [
    "pytest>=8.3.0",
]
others = [
    "pandas>=2.3.2",
]

[tool.uv]
default-groups = ["dev", "others"]

のように、別途othersグループを作ってそこにpandasを追加し、 tool.uvdefault-groupsに追加指定しておく方法もあります1

コマンドで追加する場合は

1
$ uv add --group others "pandas>=2.3.2"

でパッケージ追加出来ます。

too.uvの設定は手動で追加する必要があります。

こちらでもuv syncpandasがインストールされるようになりますし、 uv sync --no-group othersとすることで、pandasがインストールされてない環境を作ることが出来ます。

分かりやすいように名前を変えましたがgroupの方もdataとしても大丈夫です。(optional-dependenciesのextra名とは独立しています。)

ただこの場合、pandas>=2.3.2が重複していて、特にバージョンを変えたい場合などに 2箇所を変えないといけないのでちょっと面倒です。

自己参照して重複無く書く方法

そこで、これを以下のように書き直すことが出来ます。

pyproject.toml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[project]
name = "my-example-package"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
    "numpy>=2.3.3",
]

[project.optional-dependencies]
data = [
    "pandas>=2.3.2",
]


[dependency-groups]
dev = [
    "pytest>=8.3.0",
]
others = [
    "my-example-package[data]"
]

[tool.uv]
default-groups = ["dev", "others"]

othersのグループに、自分自身のmy-example-packagedataのextra付きで指定しています。

1
$ uv add --group others my-example-package[data]

のようにコマンドで追加することも出来ます 2

これで、uv syncpandasがインストールされるようになり、 また、pandasのバージョン指定はoptional-dependenciesの方で一箇所だけ書けば良くなります。

このような重複の削除は、バージョン指定以外にも、特にGitHubにあるレポジトリをgitで指定する場合などに便利です。

1
2
3
4
data = [
    "pandas>=2.3.2",
    "inherit-docstring @ git+ssh://[email protected]/rcmdnk/[email protected]",
]

みたいな感じで指定するのをgroupsの方でも同様に指定する代わりにまとめてmy-example-package[data]を指定するだけで 良くなるのでだいぶ楽になります。

この方法は循環参照になってしまう感じのする書き方ですが、意図したとおりに動作しますし、 uvの開発者の方が紹介している方法になります。

Question: What’s the difference between optional-dependencies and dependency-groups in pyproject.toml? · Issue #9011 · astral-sh/uv

もしかすると今後のアップデートでまた違った書き方が出来るようになったり推奨されたりするかもしれませんが、 現状ではこの方法が一番便利な方法かな、と思ってます。

gitの管理などに関してtool.uv.sourrcesを使う

上では直接dependenciesの中にGitのURLを入れましたが、

1
uv add --optional data git+ssh://[email protected]/rcmdnk/[email protected]

のようにコマンドで追加すれば、 以下のような感じになるはずです。

pyproject.toml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[project]
name = "my-example-package"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
    "numpy>=2.3.3",
]

[project.optional-dependencies]
data = [
    "pandas>=2.3.2",
    "inherit-docstring",
]


[dependency-groups]
dev = [
    "pytest>=8.3.0",
]
others = [
    "my-example-package[data]"
]

[tool.uv]
default-groups = ["dev", "others"]

[tool.uv.sources]
inherit-docstring = { git = "ssh://[email protected]/rcmdnk/inherit-docstring", rev = "v0.2.0" }

uv.sourcesにGitの情報等とともにinherit-docstringが追加されているのが分かります。

このような形であれば、例えばpandasなしなら、

pyproject.toml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[project]
name = "my-example-package"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
    "numpy>=2.3.3",
]

[project.optional-dependencies]
data = [
    "inherit-docstring",
]


[dependency-groups]
dev = [
    "pytest>=8.3.0",
]
others = [
    "inherit-docstring",
]

[tool.uv]
default-groups = ["dev", "others"]

[tool.uv.sources]
inherit-docstring = { git = "ssh://[email protected]/rcmdnk/inherit-docstring", rev = "v0.2.0" }

のようにパッケージ名を直接書いても良いかもしれません。 こちらの方が循環参照っぽい気持ち悪さも無いので。

ただ、dataとしてまとめたいものが複数ある場合などは 自己参照を使った方法の方がまとめて出来て分かりやすいとは思います。

また、こちらはuvの機能としてなので、 仮にこのpyproject.tomlを別の管理ツールで使いたいということがある場合などは こちらの方法だと理解されない可能性もあります。

そういった意味でも先の方法をまず考えた方が良いかな、と思います。

Sponsored Links
  1. default-groupsはデフォルトではdevのみが指定されている状態です。

  2. この場合、

    1
    2
    
    [tool.uv.sources]
    my-example-package = { workspace = true }
    

    が追加で加えられます。

    Workspaceの指定はサブディレクトリなどに別のプロジェクトを置いて参照したりする場合に 使ったりするものですが、 この場合は自分自身への参照なのでworkwpace = trueを指定しなくても同じで この部分があっても無くても動作は同じです。

    Using workspaces

Sponsored Links

« vim_ahkの設定画面をタブ化

}