1
2

Python Package作成備忘録

Last updated at Posted at 2023-02-08

ツールlist

  1. pyenv: Python管理 -> asdf
  2. conda: Pythonの環境管理
  3. poetry: 依存パッケージの管理
  4. Python Packages (py-pkgs.org): Python Packageの作成方法
  5. cookiecutter: テンプレートからPython Projectを作成するCLIツール
  6. PyPI (Python Package Index): Python packageを公開するハブ
  7. Test Python Package Index: Python packageをテスト公開するチェック用
  8. python-semantic-release: Pythonのリリースを自動化するツール
  9. flake8, black, isort: Formater、Linter
  10. pipx
  11. asdf: asdf is a tool version manager. All tool version definitions are contained within one file (.tool-versions) which you can check in to your project's Git repository to share with your team, ensuring everyone is using the exact same versions of tools.

Install asdf

asdf
asdf plugin-add python
asdf install python 3.11.0

Poetry (依存パッケージ管理)

Poetryのインストール

asdf plugin-add poetry
asdf install poetry 1.7.1

プロジェクト内にvirtualenv作成

poetry config virtualenvs.in-project true

Package作成

  1. cookiecutterのインストール
    pip install cookiecutter # Macなら brew install cookiecutterでも可
    
  2. cookiecutterを使ってtemplateからprojectの作成
    cookiecutter https://github.com/py-pkgs/py-pkgs-cookiecutter.git
    

Packageの中身の実装

自分でPackageで実現したい内容を実装する。
ディレクトリ構成は、How to package a Pythonを参考にする。↓以下参照

pycounts
├── CHANGELOG.md               ┐
├── CONDUCT.md                 │
├── CONTRIBUTING.md            │
├── docs                       │ Package documentation
│   └── ...                    │
├── LICENSE                    │
├── README.md                  ┘
├── pyproject.toml             ┐ 
├── src                        │
│   └── pycounts               │ Package source code, metadata,
│       ├── __init__.py        │ and build instructions 
│       ├── moduleA.py         │
│       └── moduleB.py         ┘
└── tests                      ┐
    └── ...                    ┘ Package tests

Packageのビルド

poetry build

dist (pyproject.tomldist_pathで指定されたディレクトリ)に wheelとsrcが生成される

Version更新

poetry version コマンドを使ってVersionを更新する

poetry version <rule>

rule:

  1. major
  2. minor
  3. patch
  4. premajor
  5. preminor
  6. prepatch
  7. etc

あとで紹介するPython Semantic Release (PSR)を使用する場合には、こちらはスキップして可。

Pypi (Package公開用)

Packageを公開する方法

Test設定 (1回のみ)

  1. アカウント作成: https://test.pypi.org/account/register/ (すでに持っていればスキップ)
  2. テスト用のsource追加
    poetry source add test-pypi https://test.pypi.org/simple/
    
  3. API keyを取得し、poetry configに設定 (test-pypiとしているのに注意)
    poetry config pypi-token.test-pypi pypi-xxxxxx
    

本番設定 (1回のみ)

  1. アカウント作成: https://pypi.org/account/register (すでに持っていればスキップ)
  2. API keyを取得し、poetry configに設定 (pypi)
    poetry config pypi-token.pypi pypi-xxxxx
    

Test pypiに公開

  1. buildする
    poetry build
    
  2. test pypiにPublishする
    poetry publish -r test-pypi
    
    e.g. https://test.pypi.org/project/autonote/

Test pypiのpackageを確認

test pypiで公開したバージョンをインストールする

poetry add --source test-pypi autonote==0.1.1a0

詳細: https://python-poetry.org/docs/repositories/

Test Pypiで公開したバージョンでの挙動の確認をしたりする。

Pypiに公開

  1. buildする
    poetry build
    
  2. pypiにPublishする
    poetry publish
    

Python Semantic Release (PSR)

semantic commit messageを見てリリースを勝手にうまい具合にやってくれるツール.

PSRの設定

Install

poetry add --dev python-semantic-release

以下をtomlに追加

[tool.semantic_release]
version_variable = "pyproject.toml:version"
version_source = "tag"

PSRのコマンド

  1. Versionを確認 (コミットやTag作成などしない):

    semantic-release print-version # 次のバージョン
    semantic-release print-version --current # 今のバージョン
    
  2. 新しいバージョンを発行、コミット、新規タグ作成をローカルで実行。

    semantic-release version
    
    うまく行かない場合

    semantic-release version -v DEBUG -v DEBUGをつけてどこがおかしいのか確認。get_current_version_by_tagで取れてない場合は、tomlファイルに指定したTagに値が存在しているか確認。

    Creating new version
    debug: get_current_version_by_tag()
    debug: get_last_version(, pattern='(\d+\.\d+\.\d+(-beta\.\d+)?)')
    debug: get_last_version -> 0.1.3
    debug: get_current_version_by_tag -> 0.1.3
    debug: get_current_release_version_by_tag()
    debug: get_last_version(, pattern='v?(\d+\.\d+\.\d+(?!.*-beta\.\d+))')
    debug: get_last_version -> 0.1.3
    debug: get_current_release_version_by_tag -> 0.1.3
    Current version: 0.1.3, Current release version: 0.1.3
    debug: evaluate_version_bump('0.1.3', None)
    debug: Commits found since last release: 0
    debug: get_new_version('0.1.3', '0.1.3', None, False, True)
    debug: get_new_version -> 0.1.3
    No release will be made.
    
  3. Publish

    semantic-release publish
    

    このコマンドがすること:

    1. ChangeLogファイルの更新
    2. semantic-release versionの実行 (ローカルでコミット、Tag生成).
    3. 変更をGitにPush
    4. build_commandの実行と dist fileをアップロード
    5. semantic-release changelog の実行とGitHubなどVCS providerへのポスト
    6. GitHub releaseにDist fileを添付

個人的にはローカルからPublishしたくないので、ここらへんはGitHub Actionsに設定したほうがいいかなと思っています。

Lint

poetry run isort --check --diff .
poetry run black --check --diff .
poetry run flake8 .

flake8でlintの対象外にしたい場合は、excludeを書ける

.flake8
[flake8]
exclude =
    # No need to traverse our git directory
    .git,
    # There's no value in checking cache directories
    __pycache__,
    # The conf file is mostly autogenerated, ignore it
    docs/source/conf.py,
    # The old directory contains Flake8 2.0
    old,
    # This contains our built documentation
    build,
    # This contains builds of flake8 that we don't want to check
    dist
    .venv

GitHub Actions

cookiecutterから持ってきたci-cdだと毎回mainにpushするたびにリリースされるのでCIとReleaseに分割。

CI

Pull Request作成時とmain branchにPushされたときに実行するもの (test, lint, codecov, docsなどを実行)

.github/workflows/dev.yml
name: dev

on:
  pull_request:
  push:
    branches:
      - main

jobs:
  ci:
    # Set up operating system
    runs-on: ubuntu-latest

    # Define job steps
    steps:
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: "3.9"

      - name: Check-out repository
        uses: actions/checkout@v3

      - name: Load cached Poetry installation
        id: cached-poetry
        uses: actions/cache@v3
        with:
          path: ~/.local  # the path depends on the OS
          key: poetry  # increment to reset cache

      - name: Install poetry
        if: steps.cached-poetry.outputs.cache-hit != 'true'
        uses: snok/install-poetry@v1

      - name: Restore cached dependencies
        uses: actions/cache@v3
        with:
          path: ~/.cache/pypoetry
          key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
          restore-keys: |
            ${{ runner.os }}-poetry-
      - name: Install package
        run: poetry install

      - name: Lint
        run: |
          poetry run isort --check --diff .
          poetry run black --check --diff .
          poetry run flake8 .

      - name: Test with pytest
        run: poetry run pytest tests/ --cov=<package_name> --cov-report=xml

      - name: Use Codecov to track coverage
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage.xml   # coverage report

      - name: Build documentation
        run: poetry run make html --directory docs/

<package_name>自分のものへ変更

semantic-pull-request

リリースをcommit messageベースで判定するので、commit message(pull requestのtitleをcommit messageに使うとして)をCIでチェックする

詳細: https://github.com/amannn/action-semantic-pull-request

.github/workflows/semantic-pull-request.yml
name: semantic-pull-request

on:
  pull_request:
    types:
      - opened
      - edited
      - synchronize

jobs:
  run:
    runs-on: ubuntu-latest
    steps:
      - uses: amannn/action-semantic-pull-request@v5
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Release

あまりいいやり方じゃないかもしれませんが、自分でReleaseを手動で作成するのがめんどくさい (タグをsemantic-releaseに自動で決めてほしい) ので、releaseしたいときに、 dispatch_workflowを実行してその中で semantic-release publishを実行するactionを作成

.github/workflows/release.yml
name: release

on:
  workflow_dispatch:

jobs:
  releaase:
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: "3.9"

      - name: Check-out repository
        uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - name: Load cached Poetry installation
        id: cached-poetry
        uses: actions/cache@v3
        with:
          path: ~/.local  # the path depends on the OS
          key: poetry  # increment to reset cache

      - name: Install poetry
        if: steps.cached-poetry.outputs.cache-hit != 'true'
        uses: snok/install-poetry@v1

      - name: "Restore cached dependencies"
        uses: actions/cache@v3
        with:
          path: ~/.cache/pypoetry
          key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
          restore-keys: |
            ${{ runner.os }}-poetry-
      - name: Install package
        run: poetry install

      - name: Use Python Semantic Release to prepare release
        env:
          # This token is created automatically by GH Actions
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
            git config user.name github-actions
            git config user.email github-actions@github.com
            poetry run semantic-release publish
      - name: Publish to TestPyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          user: __token__
          password: ${{ secrets.TEST_PYPI_API_TOKEN }}
          repository_url: https://test.pypi.org/legacy/

      - name: Test install from TestPyPI
        run: |
            pip install \
            --index-url https://test.pypi.org/simple/ \
            --extra-index-url https://pypi.org/simple \
            <package_name>
      - name: Publish to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          user: __token__
          password: ${{ secrets.PYPI_API_TOKEN }}

<package_name>自分のものへ変更

ただこれだとpoetry run semantic-release publishで更新した toml内のversionをpushできてないのでいつまでもtomlのバージョンが古いままになっている。(version_sourceをtagにしてかつversion_variable = "pyproject.toml:version"にしてるのが良くないのか)要改善。

ToDo

  • GitHub Actionsを含めたリリースフロー改善
  • Documentation
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2