🐍

Python のパッケージを Poetry で自作して Azure Artifacts に置いてみる

2024/02/12に公開

はじめに

仕事で Azure DevOps を使おうとしているが、いかんせん情報が少ない。(ただし、公式ドキュメントは充実している。)GitHub よりもケーススタディ的な記事が少なく自分が苦労したので、備忘録として記事を書きたくなった。今回は Azure Artifacts に Python の独自ライブラリを(手動で)置いてみることを試したので、それを書く。
また、前半はほとんど Python のパッケージ作成の話である。Python の自作パッケージ / ライブラリの配布等の記事も探しにくかったので、そちらのほうでも参考になれば幸いである。

そもそも Azure DevOps って何?

今回作ってみるもの

pip installできるようなcatlibという独自ライブラリを作ってみる。
talkという関数を用意して猫と会話できる。動作は以下のようなイメージである。

>>> from catlib import talk
>>> talk("こんにちは!")
にゃん
>>> talk("Hello!")
にゃん
>>> talk("にゃん")
・・・は?
>>> talk("")

基本的には "にゃん" で返ってくるが、逆に "にゃん" と入力するとあきれられる仕様である。空文字列の場合は、空文字列で返す。

筆者の環境

Windows で行ったのでそれ特有の内容に一部なっているだろうが、概ね OS の違いはなく同じようにできるはずである。ただ他の OS ユーザーは適宜読み替えながら見てほしい。

対象 内容
OS Windows 11 Pro
IDE Visual Studio Code
CLI Power Shell, Git Bash

環境構築

リポジトリの作成

Azure DevOps に入って適当なプロジェクトを作り、その中の Azure Repos で Git の新しいリポジトリ(本記事ではcatlib)を作る。
このとき "Add a .gitignore:" で "Python" を選んでおくと Python まわりのソース管理上不必要なファイルをコミットしなくなるので便利。

ローカル環境構築

Poetry というモダンなパッケージ管理ツールを使いながら開発を進めたい。ただ、これの導入がちょっとややこしかったので書いておく。結論としては以下のようである。

  1. Python とpipx[1]のインストール
    Windows ユーザーであればscoopをインストールする。Mac の場合brewを使う。(以下のscoopの部分をbrewに読み替えること。)
    scoop install pythonを実行する。
    scoop install pipxを実行する。
    pipx ensurepathを実行する。

  2. pipxを用いてpoetryをインストール
    公式の手順にしたがってpipx install poetryを実行する。

あとは以下のコマンドをたたいてpoetryが使えるかチェックする。

poetry --version

これでpoetryの準備ができた。

ちなみに Python にまつわる "管理ツール" はごちゃごちゃしていたので以下の記事が参考になった。
https://qiita.com/tetutaro/items/53b746412285920180b6
https://pod.hatenablog.com/entry/2021/06/23/221537

プロジェクトの準備

Azure Repos からcatlibをクローンしてきて、そのフォルダに入る。

プロジェクトの作成

フォルダの中で以下のコマンドをたたく。

poetry init

するとインタラクティブにパッケージ名やら聞かれるので、今回はあまり気にせず概ねデフォルトのまま(Enter を押す)でよいが、以下の質問についてはnoと答える。

Would you like to define your main dependencies interactively? (yes/no)

デフォルトではyesになっているが、パッケージの追加をここで行うものであり、あとからでもできるのでやらなくて良い。この質問のあと以下のように聞かれる。

Do you confirm generation? (yes/no)

これをデフォルトのyesで回答すると以下のようなファイルが生成される。

pyproject.toml
[tool.poetry]
name = "catlib"
version = "0.1.0"
description = ""
authors = ["<名前>"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.12"


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

仮想環境の準備

また Python の仮想環境(.venv)をプロジェクト内部に準備するように指定しておく。(デフォルトではユーザーのキャッシュフォルダ上に配置される。)そうしておく方が環境を汚さないし、特に VSCode がちゃんと認識してくれるのでこの設定にすることを推奨する。

以下のコマンドをたたく。

poetry config virtualenvs.in-project true --local

これをすると以下のようなファイルが生成されるはずである。

poetry.toml
[virtualenvs]
in-project = true

この設定が反映されているかどうかを見るには以下のコマンドをたたくとわかる。

$ poetry config --list
...
virtualenvs.in-project = true
...

pytestの導入

テストフレームワークであるpytestを導入する。以下のコマンドをたたくとpytestを導入できる。(テストツールであるpytestは開発環境のみに必要なので--devオプションをつける。)

poetry add pytest --dev

これでインストールが走るはずである。外部ライブラリのインストールを始めて実行するのでpoetry.lockファイル(および.venvフォルダ)が生成される。
もしここでインストールが走らなかったとしてもあとでインストール作業があるので次に進めて良い。

これでプロジェクトの準備が整った。

独自ライブラリの構築

テスト駆動で進めてみる。

基本的な構成の準備

まず以下のような二つのフォルダcatlibtestsを用意する。

{root}
├── pyproject.toml
├── ...
│
├── catlib # ライブラリの本体
│   ├── __init__.py
│   └── main.py
│
└── tests # テスト用
    ├── __init__.py
    └── test_main.py

ライブラリの箱を用意する

catlibフォルダの二つのファイルを以下のように編集する。

catlib/main.py
def talk(call: str) -> str:
    pass
catlib/__init__.py
from catlib.main import *

一旦未実装で関数だけ用意しよう。あとはこの自作ライブラリをインストールするために以下のコマンドをたたく。

poetry install

このコマンドは自作ライブラリを認識させるだけのものではなく、pyproject.tomlの情報からインストールしてくれるコマンドでnpm installみたいなものである。
https://python-poetry.org/docs/cli/#install

失敗するテストを書く

今度はテストを書く。「今回作ってみるもの」で示した仕様をそのままテストに落とし込む。

tests/test_main.py
from catlib import talk

def test_talk():
    default_res = "にゃん"
    copy_res = "・・・は?"

    assert talk("こんにちは!") == default_res
    assert talk("Hello!") == default_res
    assert talk(default_res) == copy_res
    assert talk("") == ""

この状態で一旦テストを実行してみる。以下のコマンドでできる。

poetry run pytest tests

テストが実行され、当然失敗する結果が返ってくることを確認できる。

テストを成功するように実装

次にテストをパスできるように実装を修正する。

catlib/main.py
def talk(call: str) -> str:
    default_res: str = "にゃん"
    if not call:
        return ""
    elif call == default_res:
        return "・・・は?"

    return default_res

この状態で再びテストを実行する。

poetry run pytest tests

するとテストをパスできることが確認できる。

Azure Artifacts へデプロイ

いよいよデプロイ作業を行う。まずはビルドから。

ビルド

以下のコマンドでビルドする。

poetry build

ビルドファイルはデフォルトでdistフォルダに出力される。
フォルダの中には二つのファイルができていて、以下の二形式である。

  • ~.whl: ビルド済みのファイル
  • ~.tar.gz: ソースコードの圧縮ファイル

これらはビルド時に-f [wheel/sdist]オプションを使って指定することができる。wheel~.whlを作成し、sdist~.tar.gzを作る。今回はdistフォルダをいったん削除してから、以下のコマンドを実行する。

poetry build -f wheel

Azure Artifacts の準備

Azure DevOps 側の準備をする。Azure Artifacts にフィードを用意する必要がある。

Azure DevOps のプロジェクトに入って、右のリストから Azure Artifacts をクリックする。 [Create Feed] からcatlibの名前でフィードを作成する。ここは以下の公式ドキュメントを見ながらやるのが良い。

https://learn.microsoft.com/ja-jp/azure/devops/artifacts/concepts/feeds?view=azure-devops

デプロイ

公式の指示[2]ではtwineというツールを用いて行うが、今回は極力poetryで完結させたい。
poetry publishでデプロイ可能であるが、そのためにはデプロイ先を設定に登録しなければならない。また認証も必要になってくるので、まずはそのために必要なartifacts-keyringを導入しておく。

poetry self add artifacts-keyring

そのあと、以下のようなコマンドでazureとしてデプロイ先と認証情報等を登録する。azureではない名前にしたい場合はrepositoryhttp-basic.以下の名前を変えればよい。

poetry config repositories.azure <フィードのアップロード用URL> --local
poetry config http-basic.azure <ユーザー名> <PAT> --local
  • <フィードのアップロード用URL>

公式のドキュメントを見ると以下の二パターンであるが、[Connect to Feed] から URL を確認&コピーできるので、そこから行うのが確実である。

# プロジェクトスコープの場合
https://pkgs.dev.azure.com/<ORGANIZATION_NAME>/<PROJECT_NAME>/_packaging/<FEED_NAME>/pypi/upload
# 組織スコープの場合
https://pkgs.dev.azure.com/<ORGANIZATION_NAME>/_packaging/<FEED_NAME>/pypi/upload

なお、組織ではなく個人アカウント上でも Azure DevOps では一つの"組織"とみなすので、個人の場合<ORGANIZATION_NAME>に個人アカウントの ID が入るだろう。

  • <ユーザー名>

どんな名前でも問題ない。

  • <PAT>

Personal Access Token で以下の記事を参考に取得する。[Scope] のところは "Packaging" の項目を "Read, write, & manage" にチェックするだけで今回は問題ない。

https://learn.microsoft.com/ja-jp/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows

そうするとランダムのように見える文字列が発行されるので、それを<PAT>に入れる。

これらを登録できたら以下のコマンドでデプロイできるはずである。

poetry publish -r azure

pip installの確認

最後にpip install可能かをテストする。

利用者仮想環境にインストール

利用者の環境はvirtualenvを使って構築する。

pipx install virtualenv
virutalenv --version

適当にフォルダを用意して、そこに仮想環境(testenv)を作る。

virtualenv testenv
source ./testenv/Scripts/activate

Power Shell の場合は、以下で有効化する。

./testenv/Scripts/activate.bat

(testenv) というのが先頭に表示されていれば成功である。

https://www.python.jp/install/windows/virtualenv.html

(testenv) 環境内で以下のコマンドを行う。

pip install catlib --index-url=<フィードのダウンロード用URL>

なお<フィードのダウンロード用URL>は以下のようである。

# プロジェクトスコープの場合
https://pkgs.dev.azure.com/<ORGANIZATION_NAME>/<PROJECT_NAME>/_packaging/<FEED_NAME>/pypi/simple
# 組織スコープの場合
https://pkgs.dev.azure.com/<ORGANIZATION_NAME>/_packaging/<FEED_NAME>/pypi/simple

ただ、このままの URL では後述する対話での認証を行うことになるのでちょっと面倒くさい。
PAT を利用する場合、以下のように 上の URL の https:// の直後に<PAT>@を挿入した URL にすればよい。

https://<PAT>@pkgs.dev.azure.com/~
対話での認証
User for <組織名>.pkgs.visualstudio.com:

と入力を求められるが、ここは任意の文字列でよい。次の

Password:

で PAT を入力する。

このあと、

Save credentials to keyring [y/N]:

という質問が出てくるが、認証情報を記憶させるかどうかなのでどちらでも好きな方で良い。yを選ぶと認証情報をkeyringで保持してくれて、以降入力をしなくてもよくなる。(ただし PAT の有効期限に注意。)

使ってみる

あとは適当なスクリプトファイル(user.py)を書いて実行するとcatlibが使えることがわかる。

user.py
from catlib import talk

print(talk("Hello!"))
print(talk(""))
print(talk("にゃん"))
(testenv) $ python user.py
にゃん

・・・は?

これで今回の目的を達成できた!🎉

Next Step

Azure Pipelines を使ってテスト、ビルド、デプロイの自動化をしたい。
ということで、そのパイプラインを作ってみたので以下の記事を書いた。
https://zenn.dev/takanari_dev/articles/2024-02-12-azure-devops-python-package-pipeline

記事全体としての参考サイト

https://learn.microsoft.com/ja-jp/azure/devops/artifacts/quickstarts/python-packages?view=azure-devops
https://zenn.dev/karaage0703/articles/db8c663640c68b
https://zenn.dev/shotakaha/scraps/9416c30cd7745a

(おまけ1)mypy, pytest-cov, taskipy の導入

Python の有用なパッケージの紹介は、優れた記事がたくさんあるので調べるとよい。

https://data.gunosy.io/entry/linter_option_on_pyproject
https://zenn.dev/jdbtisk/articles/e6ed54b38b6a45

その中でもmypypytest-covtaskipyとを導入してみる。

poetry add mypy pytest-cov taskipy --dev

mypyは以下のように使って静的型チェックをしてくれる。型が動的であることが、Python の良いところであり悪いところでもあるが、mypyはそれの頼もしい味方だ。

poetry run mypy <対象フォルダ>

pytest-covpytestのテストカバレッジを表示してくれる。pytest--covオプションで対象フォルダを指定する。

poetry run pytest --cov=<対象フォルダ>

taskipyはタスクランナーで、様々なコマンドを一つに吸収してくれる感じのやつ。たとえばcatlibpyproject.tomlに以下のような記述を追加してみる。

[tool.taskipy.tasks]
test = "pytest tests --cov=catlib"
typecheck = "mypy catlib"

すると各ツールのコマンドの詳細を覚えなくても統一的に使えるようになる。

poetry run task test
poetry run task typecheck

(おまけ2)twineを利用したデプロイ

公式の指示[2:1]と同じtwineを使う方法も紹介する。ただし一部違うやり方である。また、Azure Pipelines でデプロイの自動化を行う際にはtwineをむしろ使った方が良いので一応こちらも載せておく。

まずは、いくつかのツールをpipx経由でインストールする。

pipx twine keyring artifacts-keyring

どうもartifacts-keyringのインストールがうまくいかなかったので、エラーの指示にしたがって以下のコマンドでできた。

pipx install artifacts-keyring --include-deps

次にリポジトリ直下にtwine用の設定ファイルを作成する。

{root}
├── .pypirc ← new!✨
├── pyproject.toml
├── ...

このファイルを以下のように編集する。

.pypirc
[distutils]
Index-servers =
  catlib

[catlib]
Repository = <フィードのアップロード用URL>
username = <ユーザー名> # 任意の名前
password = <PAT>

<フィードのアップロード用URL>公式のドキュメントを見ると以下の二パターンであるが、[Connect to Feed] から URL を確認&コピーできる。(個人アカウントの場合 URL が異なるかもしれない。)

# プロジェクトスコープの場合
https://pkgs.dev.azure.com/<ORGANIZATION_NAME>/<PROJECT_NAME>/_packaging/<FEED_NAME>/pypi/upload
# 組織スコープの場合
https://pkgs.dev.azure.com/<ORGANIZATION_NAME>/_packaging/<FEED_NAME>/pypi/upload

<PAT>としているところは Personal Access Token で以下の記事を参考に取得する。[Scope] のところは "Packaging" の項目を "Read, write, & manage" にチェックするだけで今回は問題ない。

https://learn.microsoft.com/ja-jp/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows

そうするとランダムのように見える文字列が発行されるので、それを<PAT>に入れる。

あとは以下のコマンドでアップロードできる。

twine upload -r catlib --config-file .pypirc dist/*

Azure Artifacts にcatlibが置いてあれば成功である。

脚注
  1. Python で作られたコマンドラインツールを管理するツール ↩︎

  2. Azure Artifacts の [Connect to Feed] を押すとさまざまな形式のリストがあり、その中でもtwineを選択すると、twineを利用したデプロイ方法が提示される。 ↩︎ ↩︎

Discussion