Python の独自パッケージを作成して AWS CodeArtifact に登録してみた

カスタムパッケージ作りたい放題です。
2022.03.11

今回は、Python の勉強を兼ねて独自パッケージを作成して、AWS CodeArtifact に登録してみました。

作業の流れ

  • AWS CodeArtifact のドメインを作成
  • AWS CodeArtifact のリポジトリを作成
  • ツールのインストール
  • 独自パッケージの作成
  • リポジトリへ独自パッケージを登録
  • 動作確認

AWS CodeArtifact のドメイン作成

まずは クラウド側の環境構築を行います。AWS CodeArtifact のコンソールから「ドメインを作成」をクリックします。

01-make-domain

次の画面でドメイン名を入力します。(今回は mydomain としました)

02-set-domain-name

AWS CodeArtifact のリポジトリを作成

次に、作成したドメインに対してリポジトリを作成します。

03-make-repo

適当なリポジトリ名を入力して作成します。(今回は myrepo としました)
なお、今回はパブリックアップストリームリポジトリは設定しません。

04-set-repo-name

05-make-repo-submit

作成できました。

08-made-repo

twine と wheel のインストール

AWS CodeArtifactへのアップロードは twine を使って行うので、twine を PC にインストールします。また、アップロード時は wheel パッケージである必要があるので、wheel もインストールしておきます。

% pip install -U twine
% pip install -U wheel

独自パッケージの作成

作成するパッケージの概要

ここからは Python のパッケージを作成していきます。具体的には「AWSサービスに対して正しい名称を返してくれるもの」を作ります。動作イメージとしては下記のような感じです。名前は myawsname としてみました。

>>> import myawsname
>> myawsname.s3()
{
    "ServiceName": "Amazon S3"
}

>>> myawsname.greengrass()
{
    "ServiceName": "AWS IoT Greengrass"
}

ファイル構成

パッケージ作成に必要なものを下記の構成で作成していきます。

作業ディレクトリ
├── LICENSE.txt
├── README.md
├── myawsname
│   ├── __init__.py
│   └── myawsname.py
└── setup.py

モジュール作成

本体となるコード(myawsname/myawsname.py)は下記のような簡単なものを用意します。

myawsname/myawsname.py

import json

def common(message):
    data = json.loads(message)
    json_message = json.dumps(data, indent=2)
    return json_message

def s3():
    message = '{"ServiceName": "Amazon S3"}'
    return common(message)

def dynamodb():
    message = '{"ServiceName": "Amazon DynamoDB"}'
    return common(message)

def greengrass():
    message = '{"ServiceName": "AWS IoT Greengrass"}'
    return common(message)

setup.pyの作成

次に setup.py を以下の内容で作成します。下記の公式ページにあるサンプルをベースに必要箇所を修正して作成してください。

setup.py

import setuptools

with open("README.md", "r", encoding="utf-8") as fh:
    long_description = fh.read()

setuptools.setup(
    name="myawsname",
    version="0.0.1",
    author="cm-taro",
    author_email="xxx@example.com",
    license='MIT',
    description="You can receive AWS Service Name.",
    long_description=long_description,
    long_description_content_type="text/markdown",
    url="https://example.com",
    classifiers=[
        "Programming Language :: Python :: 3",
        "License :: OSI Approved :: MIT License",
        "Operating System :: OS Independent",
    ],
    packages=setuptools.find_packages(),
    python_requires=">=3.7",
)

ライセンスについては、11 行目と 18 行目の2箇所に記載します。
classifiers の箇所だけでもいいですが、その場合は pip show コマンドで確認時にライセンスが UNKNOWN と表示されてしまいます。 「pip-licenses」 というパッケージを使うと classifiers からライセンス情報を取得することができます。
今回は pip show のときも正しく表示させるために、パッケージのメタデータとして license フィールドも書いておきます。

また、url はプロジェクトのホームページのものを記載することになりますが、今回は用意していないのでダミーの URL を記載しておきます。

LICENSE.txt の作成

今回は MIT ライセンスにしてみたので、下記のテキストから作成します。

LICENSE.txt

Copyright (c) 2022 Taro CM

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

__init__py の作成

次にmyawsname/__init__.pyを作成します。このファイルのおかげでパッケージ利用時はimport myawsnameとすれば利用できるようになります。このファイルがない場合は、from myawsname import myawsname とする必要があります。

myawsname/__init__.py

from myawsname.myawsname import *

README.md の作成

最後にREADME.mdを作成します。今回はテストなので中身は簡単なものにします。

README.md

# TITLE
This is a sample package.

# LICENSE
This software is released under the MIT License, see LICENSE.

パッケージのビルド

準備ができたのでパッケージをビルドします。

$ python setup.py sdist

dist ディレクトリおよびパッケージ名.egg-info ディレクトリができていることを確認します。

.
├── LICENSE.txt
├── README.md
├── dist
│   └── myawsname-0.0.1.tar.gz
├── myawsname
│   ├── __init__.py
│   └── myawsname.py
├── myawsname.egg-info
│   ├── PKG-INFO
│   ├── SOURCES.txt
│   ├── dependency_links.txt
│   └── top_level.txt
└── setup.py

次に wheel パッケージのビルドを実行します。

$ python setup.py bdist_wheel

buildディレクトリが作成されていることを確認します。

.
├── LICENSE.txt
├── README.md
├── build
│   ├── bdist.macosx-10.15-x86_64
│   └── lib
│       └── myawsname
│           ├── __init__.py
│           └── myawsname.py
├── dist
│   ├── myawsname-0.0.1-py3-none-any.whl
│   └── myawsname-0.0.1.tar.gz
├── myawsname
│   ├── __init__.py
│   ├── myawsname.py
│   └── test.py
├── myawsname.egg-info
│   ├── PKG-INFO
│   ├── SOURCES.txt
│   ├── dependency_links.txt
│   └── top_level.txt
└── setup.py

リポジトリへパッケージを登録

twine を使ってアップロードを行っていきます。
最初に aws codeartifact login --tool twine コマンドを実行します。

$ aws codeartifact login \
    --tool twine \
    --repository myrepo \
    --domain mydomain

このコマンドを実行すると、~/.pypirc ファイルが下記の内容で作成されます。

[distutils]
index-servers = 
	pypi
	codeartifact

[codeartifact]
repository = https://[DOMAIN]-[YOUR_AWS_ACCOUNT_ID].d.codeartifact.[REGION].amazonaws.com/pypi/[REPOSITROY]/
username = aws
password = [YOUR_CODEARTIFACT_AUTH_TOKEN]

password に指定されているトークンは CodeArtifact に接続するために必要な権限を持ちます。

また、この他にpip install で CodeArtifact からパッケージをインストールする際には、aws codeartifact login --tool pip コマンドを実行しますが、このときも同様のトークンを取得することができます。このときのトークンは、~/.config/pip/pip.conf に書き込まれます。

「トークンの発行」と「各ファイルに対するトークンの書き込み、ファイル参照」のタイミングは次のとおりです。

ファイル 書込タイミング 参照タイミング
~/.config/pip/pip.conf aws codeartifact login --tool pip 実行時
aws codeartifact get-authorization-tokenで取得したトークンを pip config set で書き込んだ時
パッケージのインストール時
~/.pypirc aws codeartifact login --tool twine 実行時 パッケージのリポジトリへのアップロード時

上記の通り、各ファイルで CodeArtifact を利用するためのトークンを持ちますが、パッケージのインストールとアップロードは実施頻度やタイミングが全く異なるはずなので、各ファイルにあるトークンが全く別のものであっても問題ありません。(片方のトークンが有効期限切れでも問題ありません)
都度、必要な作業に応じてトークンを発行して(ファイルに書込んで、)利用する形になります。

少し話がそれましたが、次にアップロードを実行します。

$ twine upload -r codeartifact dist/*

コマンドを実行すると下記のようなログが表示されて、AWS CodeArtifact のリポジトリにアップロードされていることが分かります。

Uploading distributions to https://mydomain-xxxxxxxxxxx.d.codeartifact.ap-northeast-1.amazonaws.com/pypi/myrepo/
Uploading myawsname-0.0.1-py3-none-any.whl
100%|██████████████████████████████████████████████████| 7.39k/7.39k [00:00<00:00, 15.8kB/s]
Uploading myawsname-0.0.1.tar.gz
100%|██████████████████████████████████████████████████| 7.14k/7.14k [00:00<00:00, 21.3kB/s]

アップロード後、コンソール上でもアップロードしたパッケージ myawsname が確認できました。

09-myawsname-on-codeartifact

また、今回アップロードしたバージョン 0.0.1のページを開くと setup.py に記載した内容でパッケージ情報を見ることができます。

10-package-information

動作確認

アップロードできたので、実際に作成したパッケージをインストールして動作テストをしてみましょう。
CodeArtifact からインストールするために、最初に CodeArtifact に接続します。

AWS CLI でAWS CodeArtifact に接続することで、有効期限を持った認証トークンを含んだリポジトリの URL が生成されて ~/.config/pip/pip.conf に書き込まれます。
このトークンの有効期間はデフォルトで12時間です。もっと短い時間を指定することもできます。

なお、接続手順はコンソールから確認することができます。

06-display-how-to-connect

「接続手順の表示」 をクリックすると下記の様な画面になります。
今回は Python なので pip を選択します。すると、実行するコマンドが表示されるのでコピーして手元の PC で実行します。

07-login-codeartifact

$ aws codeartifact login \
    --tool pip \
    --repository myrepo \
    --domain mydomain \
    --domain-owner [YOUR_AWS_ACCOUNT_ID]

コマンドを実行すると、~/.config/pip/pip.conf に下記のような内容が書き込まれます。同じコマンドを実行するたびに新しいトークンを含む URL で上書きされます。

~/.config/pip/pip.conf

[global]
index-url = https://aws:[YOUR_CODEARTIFACT_AUTH_TOKEN]@mydomain-[YOUR_AWS_ACCOUNT_ID].d.codeartifact.ap-northeast-1.amazonaws.com/pypi/myrepo/simple/

事前の準備が終わったので、パッケージをインストールします。

$ pip install myawsname

次のようなログが流れて、AWS CodeArtifact からインストールされていることが分かります。

Looking in indexes: https://aws:****@mydomain-xxxxxxxxxxxx.d.codeartifact.ap-northeast-1.amazonaws.com/pypi/myrepo/simple/
Collecting myawsname
  Downloading https://mydomain-xxxxxxxxxxxx.d.codeartifact.ap-northeast-1.amazonaws.com/pypi/myrepo/simple/myawsname/0.0.1/myawsname-0.0.1-py3-none-any.whl (2.9 kB)
Installing collected packages: myawsname
Successfully installed myawsname-0.0.1

今回の手順では、pip.confの設定どおりデフォルトのリポジトリは CodeArtifact になっています。そのためpip install が失敗する場合は、トークンの有効期限が切れている可能性があります。
その場合は、再度 AWS CodeArtifact にログインし直して、新しいトークンを取得してください。
aws codeartifact login --tool pip コマンドを実行することで、新しいトークンを含んだ URL で~/.config/pip/pip.confを更新してくれます。

パッケージのインストールができたら、次のような確認用のコードで検証してみます。

myawsname-test.py

import myawsname
import json

s3_name_data = json.loads(myawsname.s3())
s3_name = s3_name_data['ServiceName']
print("s3: " + str(s3_name))

以下のような実行結果になればOKです。

$ python test.py 
s3: Amazon S3

シェル上でも確認できました。

$ python 

Python 3.8.7 (default, Jul  8 2021, 15:28:23) 
[Clang 12.0.0 (clang-1200.0.32.29)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import myawsname
>>> myawsname.s3()
'{"ServiceName": "Amazon S3"}'
>>> 
>>> myawsname.dynamodb()
'{"ServiceName": "Amazon DynamoDB"}'
>>> 
>>> myawsname.greengrass()
'{"ServiceName": "AWS IoT Greengrass"}'
>>>

pip コマンドでもパッケージ情報を確認してみます。

$ pip show myawsname

Name: myawsname
Version: 0.0.1
Summary: You can receive AWS Service Name.
Home-page: https://example.com
Author: cm-taro
Author-email: xxx@example.com
License: MIT
Location: /Users/xxx/.pyenv/versions/3.8.7/lib/python3.8/site-packages
Requires: 
Required-by:

最後に

Python のパッケージの作成方法と AWS CodeArtifact の基本的な使い方を確認できました。 引き続き AWS CodeArtifact を触ってみたいと思います。

以上です。