Python 패키지 개발 및 PyPI 배포 가이드

소개

2022년 기준으로 Python 패키지를 처음부터 끝까지 제작하고 공식 저장소에 배포하는 방법을 설명합니다. 이 과정은 단순한 코드 작성 이상의 작업을 포함하며, 지속적 통합, 자동 형식화, 버전 관리, 문서화까지 아우릅니다.

기본 구조 설정

패키지 개발의 첫 단계는 디렉터리 구조와 메타데이터를 구성하는 것입니다. 여기서는 poetry를 사용하여 프로젝트를 초기화합니다. 패키지 이름은 extendedjson으로 정했으며, PyPI에서 중복되지 않는지 반드시 확인해야 합니다.

poetry new extendedjson --name extendedjson
cd extendedjson

생성된 pyproject.toml 파일은 패키지 정보, 의존성, 빌드 설정 등을 담고 있습니다. 기본적으로 아래와 같은 내용을 포함합니다:

[tool.poetry]
name = "extendedjson"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
readme = "README.md"

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

[tool.poetry.group.dev.dependencies]
pytest = "^7.0"
pre-commit = "^3.0"
scriv = { version = "^2.0", extras = ["toml"] }

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

가상 환경에서 모든 의존성을 설치합니다:

poetry install

버전 관리 및 사전 커밋 훅

Git 저장소를 초기화하고 기본 브랜치를 main으로 설정합니다:

git init
git branch -M main
git add .
git commit -m "chore: initial commit"

코드 품질을 유지하기 위해 pre-commit 훅을 도입합니다. 이를 통해 커밋 전 자동으로 코드 포맷팅과 정적 검사를 수행할 수 있습니다. 먼저 설정 파일 .pre-commit-config.yaml을 생성합니다:

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: end-of-file-fixer
      - id: trailing-whitespace
      - id: check-yaml
      - id: check-added-large-files

  - repo: https://github.com/psf/black
    rev: 23.1.0
    hooks:
      - id: black
        language_version: python3

  - repo: https://github.com/PyCQA/isort
    rev: 5.12.0
    hooks:
      - id: isort
        args: ["--profile", "black"]

훅을 설치하고 적용합니다:

poetry run pre-commit install
poetry run pre-commit run --all-files

변경 사항을 커밋합니다:

git add .pre-commit-config.yaml pyproject.toml poetry.lock
git commit -m "feat: add pre-commit hooks for code quality"

라이선스 추가

Mit 라이선스를 선택하고 저장소 루트에 LICENSE 파일을 추가합니다. GitHub 인터페이스를 통해 직접 생성하거나 다음 명령어로 처리할 수 있습니다:

curl -sL https://raw.githubusercontent.com/licenses/mit/master/MIT.txt > LICENSE
git add LICENSE
git commit -m "docs: add MIT license"

실제 기능 구현

이번 예제에서는 확장 가능한 JSON 인코더/디코더 클래스를 구현합니다. 소스 파일 extendedjson/__init__.py에 아래 코드를 삽입합니다:

import json
from datetime import date, datetime
from typing import Any, Union

class ExtendedEncoder(json.JSONEncoder):
    def default(self, obj: Any) -> Union[str, float]:
        if isinstance(obj, (date, datetime)):
            return obj.isoformat()
        try:
            return super().default(obj)
        except TypeError:
            return str(obj)

class ExtendedDecoder(json.JSONDecoder):
    def __init__(self):
        super().__init__(object_hook=self.object_hook)

    @staticmethod
    def object_hook(dct: dict) -> dict:
        return dct

변경 로그 관리

scriv 도구를 사용해 변경 사항을 추적합니다. 먼저 새 항목을 생성합니다:

poetry run scriv create

생성된 마크다운 조각 파일에 다음과 같이 기록합니다:

### Added
- Implemented `ExtendedEncoder` and `ExtendedDecoder` for enhanced JSON serialization.

모든 조각을 하나의 CHANGELOG.md로 병합합니다:

poetry run scriv collect

변경 사항을 커밋합니다:

git add extendedjson/__init__.py changelog.d/ CHANGELOG.md
git commit -m "feat: implement custom JSON encoder/decoder"

테스트 저장소에 업로드

실제 PyPI에 올리기 전, TestPyPI를 통해 배포 테스트를 수행합니다. 먼저 저장소를 등록합니다:

poetry config repositories.testpypi https://test.pypi.org/legacy/

TestPyPI 계정에서 생성한 API 토큰을 설정합니다:

poetry config http-basic.testpypi __token__ pypi-your-test-token

빌드 후 업로드합니다:

poetry build
poetry publish -r testpypi

성공 시, https://test.pypi.org/project/extendedjson 에서 패키지를 확인할 수 있습니다.

정식 PyPI 배포

본격적인 배포를 위해 실 저장소용 토큰을 설정합니다:

poetry config pypi-token.pypi pypi-your-production-token

빌드와 동시에 배포합니다:

poetry publish --build

배포 후, 다른 환경에서 설치 테스트를 수행합니다:

pip install extendedjson
python -c "from extendedjson import ExtendedEncoder; print(ExtendedEncoder)"

GitHub 릴리스 생성

버전 태그를 추가합니다:

git tag v0.1.0
git push origin v0.1.0

GitHub에서 Releases 탭으로 이동해 해당 태그에 대해 릴리스를 생성하고, CHANGELOG.md 내용을 본문에 붙여넣습니다. 프로젝트 설명과 주요 해시태그도 추가합니다.

결론

이 과정을 통해 현대적인 Python 패키징 워크플로우를 완성했습니다. poetry 기반 빌드, 자동 형식화, 문서화, 그리고 CI 준비까지 모두 포함됩니다. 이후에는 GitHub Actions를 활용한 자동 테스트 및 배포 파이프라인 구축으로 확장할 수 있습니다.

태그: python poetry PyPI pre-commit black

8월 7일 13:30에 게시됨