PyPI 패키징 및 배포 가이드

PyPI 패키징 및 배포 가이드

파이썬 패키지를 다른 개발자가 pip를 사용해 받아갈 수 있도록 pypi에 업로드 하는 과정을 패키징 및 배포라고 한다.

파이썬 패키지를 빌드하고 pypi에 배포하는 방법을 알아보자.

준비사항

로컬 파이썬 패키징

Packaging Python Projects - Python Packaging User Guide

참고자료

이번 글에서는 최대한 간단한 pure python 패키지를 빌드하는 방법만 소개해보겠다.

빌드 툴은 여러가지가 있는데 이 글에선 hatch를 사용한다.

hatch 설치는 여러 방법으로 할 수 있는데, 가장 간단하게는 pip를 사용해 설치할 수 있다 (pip install hatch)

packaging_tutorial/
├── LICENSE
├── pyproject.toml
├── README.md
├── src/
│   └── example_package_YOUR_USERNAME_HERE/
│       ├── __init__.py
│       └── example.py
└── tests/

예를 들어 만들어놓은 프로젝트 구조가 위처럼 되어 있다고 해보자.

packaging_tutorial$ hatch new --init
Project name: packaging_tutorial
Description []: my temp project

Wrote: pyproject.toml

위 명령어를 이용하면 커맨드 라인에서 프로젝트명과 설명을 입력받게 되고, pyproject.toml에 패키지 관련 정보가 생성된다.

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "packaging_tutorial"
dynamic = ["version"]
description = 'my temp project'
readme = "README.md"
requires-python = ">=3.8"
license = "MIT"
keywords = []
authors = [
  { name = "...", email = "..." },
]
classifiers = [
  "Development Status :: 4 - Beta",
  "Programming Language :: Python",
  "Programming Language :: Python :: 3.8",
  "Programming Language :: Python :: 3.9",
  "Programming Language :: Python :: 3.10",
  "Programming Language :: Python :: 3.11",
  "Programming Language :: Python :: 3.12",
  "Programming Language :: Python :: Implementation :: CPython",
  "Programming Language :: Python :: Implementation :: PyPy",
]
dependencies = []

# ...

이 안에서 이름, 이메일 등 업데이트 할 부분을 적절히 업데이트 한다.

$ hatch build
-- sdist --
dist/temp-0.0.1.tar.gz
-- wheel --
dist/temp-0.0.1-py3-none-any.whl

마지막으로, hatch build 커맨드로 빌드하면 source distribution이 tarball 파일로 생성되고, wheel 설정을 해놓은 경우 whl 파일도 생성된다. 빌드 파일이 생성되는 기본 폴더는 dist다.

dist/
├── example_package_YOUR_USERNAME_HERE-0.0.1-py3-none-any.whl
└── example_package_YOUR_USERNAME_HERE-0.0.1.tar.gz

이제 로컬에서 패키징하는 방법까지는 알아보았다.

다음으로는 빌드한 패키지를 Pypi에 업로드해 배포하는 방법을 알아보자.

Twine 이용한 PyPI 배포

twine은 PyPI 등의 리파지토리에 파이썬 패키지를 배포하기 위한 유틸리티다.

pip install twine

설치는 pypi로 한다

twine upload --repository testpypi dist/*

최종 배포 전, TestPyPI에 배포해서 의도대로 잘 배포되었는지 확인할 수 있다. PyPI는 릴리즈 yank, delete 기능을 제공하긴 하지만 웬만하면 사용하지 않고 깔끔하게 배포하는것이 좋다.

TestPyPI에 대한 자세한 가이드는 https://packaging.python.org/en/latest/guides/using-testpypi/ 를 참고한다.

twine upload --repository pypi dist/*

실제 PyPI 배포는 위 명령어로 진행한다.

GitHub Actions를 이용한 PyPI 배포

이제 github actions에서 빌드 및 배포를 해보자.

v* 형식으로 git 태그가 푸시될 때 릴리즈가 되도록 해보겠다.

(1) OIDC 이용하기

OpenID Connect(OIDC)란 OAuth 2.0 확장 프로토콜로, 기존의 API 토큰을 이용한 방식보다 보안이 강화되면서 편리한 방식이라 보면 된다.

배포 전, 사전 준비가 필요하다.

  • 1. PyPI Account Settings - Publishing에서 publisher 정보 추가
    • pypi project name: 프로젝트명 (pip install {..} 에 들어가는 것, pyproject.toml에 있는 것과 동일)
    • Owner: GitHub organization 또는 username (ex. sh-cho)
    • Repository name: GitHub 리파지토리명 (ex. django-custom-logging)
    • Workflow name: 릴리즈 workflow 파일명 (ex. release.yml)
    • (Optional) Environment name: 배포하는 github actions의 environment 이름 (ex. pypi)

  • 2. GitHub repository - Settings - Environment 추가
    • 위에서 적은 Environment name으로 만든다. Protection rule은 없어도 된다

이제 배포 준비가 완료됐고, 아래처럼 릴리즈 workflow를 작성한다.

# .github/workflows/release.yml
name: Release

on:
  push:
    tags:
      - "v*"

jobs:
  release:
    runs-on: ubuntu-latest
    environment:
      name: pypi
      url: https://pypi.org/p/{본인 프로젝트 이름}
    permissions:
      id-token: write
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Install hatch
        run: pipx install hatch

      - name: Build
        run: hatch build

      - name: Publish release distributions to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1

이제 v* 형식의 git tag가 푸시되면 자동으로 빌드 및 PyPI 배포가 완료된다.

추가적으로 배포시 자동으로 GitHub Release를 만들고 싶다면 softprops/action-gh-release 등의 action을 사용하면 편리하다.

(2) (기존 방식) PyPI api token 이용

⚠️
GitHub actions에서는 여러 방법으로 secret이 탈취될 수 있기 때문에, OIDC를 사용 가능한 현재로서는 추천하는 방법은 아니다.

PyPI - account settings에서 API token을 생성하고, github repository에 secret으로 등록한다.

# .github/workflows/release.yml
name: Release

on:
  push:
    tags:
      - "v*"

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.x"

      - name: Install dependencies
        run: |
          pipx install hatch twine

      - name: Build and publish
        env:
          TWINE_USERNAME: __token__
          TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
        run: |
          hatch build
          twine upload --repository pypi dist/*

그 다음 위처럼 릴리즈 워크플로우를 작성하면, v* 형식으로 태그를 푸시할 때 PyPI에 패키지가 배포된다.

참고자료