⛳
Pythonパッケージ開発からPyPI公開までの道のりと実践知見
1. はじめに
今回、Pythonで作成したパッケージをPyPI(Python Package Index)に初めて公開しました。この記事では、その際に行った作業や工夫した点、今後のために気をつけたいことをまとめます。
今回作成した作成物はGithub・PyPIに掲載しております。
2. 今回実装した機能
このパッケージの中核は hmscalc/hms_time.py
に実装されている HMSTime
クラスです。
このクラスは「時:分:秒」形式の文字列を扱い、直感的な時間計算や変換を可能にします。
HMSTimeクラスの主な特徴と設計
-
HH:MM
やHH:MM:SS
形式の文字列で表現された時刻の加算・減算ができる - 負の時間(マイナス値)も自然に扱える
- 時刻を秒・分・時間・タプル・辞書形式に変換可能
- 例外処理(不正なフォーマットや型)も独自クラスで堅牢に対応
- 比較演算子(==, <, >, <=, >=, !=)もサポート
- すべての時間は「秒」に正規化して保持し、演算や変換時に必要な形式へ変換
- 正規表現で入力文字列をパースし、柔軟かつ厳密なバリデーションを実現
主要な実装例
from hmscalc import HMSTime
a = HMSTime("1:30:15")
b = HMSTime("2:15:45")
print(a + b) # "3:46:00"
print(a - b) # "-0:45:30"
print(a.to_seconds()) # 5415
print(a.to_tuple()) # (1, 30, 15)
print(a.to_dict()) # {'hh': 1, 'mm': 30, 'ss': 15}
クラス定義の一部抜粋
class HMSTime:
def __init__(self, time_str: str):
self.total_seconds = self._parse_time_string(time_str)
def __add__(self, other: "HMSTime") -> "HMSTime":
return HMSTime.from_seconds(self.total_seconds + other.total_seconds)
def __sub__(self, other: "HMSTime") -> "HMSTime":
return HMSTime.from_seconds(self.total_seconds - other.total_seconds)
def __str__(self) -> str:
total = abs(self.total_seconds)
hh = total // 3600
mm = (total % 3600) // 60
ss = total % 60
sign = "-" if self.total_seconds < 0 else ""
return f"{sign}{hh}:{mm:02}:{ss:02}"
@staticmethod
def _parse_time_string(time_str: str) -> int:
match = re.fullmatch(r"(-)?(\d+):(\d{1,2})(?::(\d{1,2}))?", time_str)
if not match:
raise InvalidTimeFormatError(time_str)
neg, hh, mm, ss = match.groups()
hh = int(hh)
mm = int(mm)
ss = int(ss) if ss is not None else 0
total = hh * 3600 + mm * 60 + ss
return -total if neg else total
3. テストの実装とポイント
-
テストフレームワーク:
pytest
を利用し、主要な関数やクラスに対してユニットテストを実装 - CI連携: GitHub Actionsでプッシュ時に自動テストを実行
- テスト用データ: テストケースごとに異常系・正常系を用意
4. CI/CD(GitHub Actions)による自動化
- 自動テスト: プルリクエスト時に、複数Pythonバージョンで自動的にテスト(pytest)・Lint・型チェック(ruff, black, mypy)が実行される
-
ビルド&パッケージング: タグpush時に
poetry publish --build
でwheelとsdistを作成し、そのままPyPIへ公開
5. PyPI公開までの流れ
-
パッケージ構成の整理
- ディレクトリ構成を明確にし、
hmscalc/
配下に実装、tests/
配下にテストコードを配置。 -
__init__.py
を忘れずに設置。hmscalc/ # パッケージ本体(実装) │ ├── __init__.py │ └── hms_time.py # 時間計算の主要ロジック │ └── exceptions.py # 例外系 │ tests/ # ユニットテスト │ └── ... # テストコード群 │ README.md # パッケージの説明・使い方 pyproject.toml # パッケージ管理・ビルド設定(Poetry等) Dockerfile # 開発・テスト用Docker設定 runtests.sh # テスト一括実行スクリプト lint.sh # Lint一括実行スクリプト LICENSE # ライセンス
- ブランチ戦略は以下とした。
- mainブランチ: リリース用の安定したコードのみをマージ。
- developブランチ: 日々の開発はこちらで行い、動作確認後にmainへマージ。
- feature/xxxブランチ: 新機能や修正ごとに作成し、developへマージ。
- release/xxxブランチ: リリース前の最終調整やバージョンアップ用。
- ディレクトリ構成を明確にし、
-
pyproject.toml
やsetup.cfg
の作成- パッケージ名、バージョン、説明、依存パッケージなどを正確に記載。
-
long_description
やlong_description_content_type
でREADMEを反映。 - Poetryやsetuptoolsなど、ビルドツールに合わせて記述。
-
テストの実装・実行
-
pytest
などでユニットテストを作成し、ローカルやCIで必ず実行。 - 異常系・正常系のテストケースを網羅。
-
-
ビルド
- Poetryなら
poetry build
、setuptoolsならpython -m build
でdist/
配下にパッケージを生成。 - 生成物(whl, tar.gz)が正しくできているか確認。
- Poetryなら
-
テスト公開(TestPyPI)
-
twine upload --repository testpypi dist/*
やpoetry publish -r testpypi
でテスト用PyPIにアップロード。 - 実際に
pip install --index-url https://test.pypi.org/simple/ ...
でインストール検証。
-
-
本番公開(PyPI)
- 本番用APIトークンを使い、
twine upload dist/*
やpoetry publish
で公開。 - バージョンの重複に注意(PyPIは同一バージョンの再アップロード不可)。
- 本番用APIトークンを使い、
-
GitHub Actionsによる自動化
- タグpushやmainブランチへのマージで自動ビルド・テスト・公開を実施。
- Secrets(
PYPI_API_TOKEN
)の設定や、CIの成否チェックを必ず行う。 - ワークフローの失敗時はログを確認し、依存やパス、権限設定を見直す。
6. 気をつけること
- バージョン管理: PyPIは同じバージョンで再アップロード不可。バージョン番号の更新を忘れずに。
-
GitHub Secretsの設定:
PYPI_API_TOKEN
などの機密情報はGitHubリポジトリのSettings > Secrets and variables > Actionsで登録し、ワークフロー内で${{ secrets.PYPI_API_TOKEN }}
として利用。 -
branch protectの設定:
- mainブランチやreleaseブランチに対して「プルリクエスト経由でのみマージ可能」「レビュー必須」「CI成功必須」などの保護ルールを設定することで、誤ったコードや未検証のコードが本番リリースされるのを防ぐ。
- 必要に応じて「force push禁止」「管理者も保護ルールを無効化できない」なども有効にする。
- セキュリティや品質担保の観点から、branch protectはCI/CD運用・PyPI公開の自動化とセットで必須の運用とするのがおすすめ。
- 保護ルールの設定はGitHubリポジトリの「Settings > Branches」から行う。
7. まとめ
初めてのPyPI公開は不安も多かったですが、機能実装・テスト・CI/CD・ドキュメント整備を徹底することでスムーズに進めることができました。
色々学べることも多かったので、便利系ツール何か思いつきましたら、また他の公開も挑戦してみたいなと思います。
※ちなみに英語系のREADMEの記載、コメント等、英語苦手なので、ほぼLLMに任せてしまいました。テストコードなどもある程度書いてくれるので、かなり助かりました。
ご質問やフィードバックがあれば、ぜひコメントでお知らせください!
Discussion