라떼군 이야기


Python Poetry 프로젝트를 uv로 마이그레이션하는 방법과 주의사항

Problem

Python 생태계에서 가장 빠른 패키지 매니저로 주목받는 uv로 전환하려 할 때, 기존 Poetry 프로젝트의 설정 파일(pyproject.toml) 호환성 문제가 발생합니다. Poetry는 독자적인 [tool.poetry] 섹션을 사용하여 의존성을 관리하는 반면, uv는 PEP 621 표준인 [project] 섹션을 따르기 때문입니다. 이로 인해 수동으로 설정을 옮기는 것은 번거롭고 에러가 발생하기 쉬우며, 기존 poetry.lock에 명시된 패키지 버전을 잃어버릴 위험이 있습니다.

Background

Poetry는 오랫동안 Python의 의존성 관리와 패키징을 위한 표준 도구처럼 사용되었으나, 속도 문제와 비표준 메타데이터 관리 방식이 단점으로 지적되어 왔습니다. 반면 uv는 Rust로 작성되어 압도적인 속도를 자랑하며, Python 표준 명세(PEP 621)를 준수합니다.

마이그레이션의 핵심은 pyproject.toml 파일 내의 [tool.poetry.dependencies]와 같은 독자 규격을 표준 규격인 [project.dependencies]로 변환하고, 빌드 시스템(build-system)을 poetry-core에서 hatchling 등 uv가 권장하는 백엔드로 교체하는 것입니다.

Solution

Poetry에서 uv로 마이그레이션하는 가장 효과적인 두 가지 방법과 버전 불일치를 방지하는 팁을 소개합니다.

방법 1: 자동화 도구 사용 (권장)

가장 간단하고 강력한 방법은 커뮤니티에서 개발된 migrate-to-uv 도구를 사용하는 것입니다. 이 도구는 pyproject.toml을 자동으로 재작성하고 poetry.lock을 제거합니다.

# 별도의 설치 없이 uvx(uv의 실행 도구)를 통해 바로 실행 가능합니다.
uvx migrate-to-uv

# 마이그레이션 후 uv용 잠금 파일(uv.lock)을 생성합니다.
uv lock

# 가상환경을 동기화합니다.
uv sync

이 명령어를 실행하면 [tool.poetry] 섹션이 [project] 섹션으로 변환되고, 빌드 시스템도 자동으로 hatchling 등으로 업데이트됩니다.

방법 2: PDM을 이용한 수동 변환

만약 자동화 도구가 실패하거나 세밀한 제어가 필요하다면, 또 다른 패키지 매니저인 pdm의 가져오기 기능을 활용할 수 있습니다.

# PDM을 사용하여 Poetry 설정을 표준 포맷으로 변환하여 가져옵니다.
uvx pdm import pyproject.toml

그 후 pyproject.toml 파일을 열어 다음 작업을 수행합니다:

  1. [tool.poetry...]로 시작하는 모든 섹션을 삭제합니다.
  2. [tool.pdm.dev-dependencies] 섹션이 있다면 [dependency-groups]로 변경하여 uv 호환성을 맞춥니다.
  3. uv lock을 실행합니다.

주의사항: 의존성 버전 고정 (Version Drift 방지)

uv는 의존성 해결 알고리즘이 Poetry와 다르기 때문에, 단순히 마이그레이션만 하면 기존 poetry.lock에 있던 버전과 다른 버전이 설치될 수 있습니다. 프로덕션 환경에서 버전 변경을 원치 않는다면, 마이그레이션 에 현재 버전을 pyproject.toml에 하드코딩하는 과정을 거치는 것이 좋습니다.

다음은 poetry.lock의 버전을 읽어 pyproject.toml에 명시적으로 고정하는 Python 스크립트입니다.

from pathlib import Path
import toml  # pip install toml 필요

# 파일 경로 설정
lockfile_path = Path("poetry.lock")
pyproject_path = Path("pyproject.toml")

# poetry.lock 파일 로드
with lockfile_path.open("r") as lockfile:
    lock_data = toml.load(lockfile)

# pyproject.toml 파일 로드
with pyproject_path.open("r") as pyproject_file:
    pyproject_data = toml.load(pyproject_file)

# lock 파일에서 패키지 이름과 버전을 매핑
locked_versions = {package["name"]: package["version"] for package in lock_data["package"]}

# 의존성 업데이트 헬퍼 함수
def update_dependencies(dependencies):
    for dep_name, dep_constraint in dependencies.items():
        if dep_name in locked_versions:
            # 기존 제약조건 대신 lock 파일의 정확한 버전으로 교체
            dependencies[dep_name] = locked_versions[dep_name]
            print(f"Updated {dep_name} to version {locked_versions[dep_name]}")
        else:
            print(f"{dep_name}는 lock 파일에서 찾을 수 없어 건너뜁니다.")

# 메인 의존성과 개발 의존성 업데이트 실행
if "dependencies" in pyproject_data["tool"]["poetry"]:
    update_dependencies(pyproject_data["tool"]["poetry"]["dependencies"])

if "group" in pyproject_data["tool"]["poetry"] and "dev" in pyproject_data["tool"]["poetry"]["group"]:
    update_dependencies(pyproject_data["tool"]["poetry"]["group"]["dev"]["dependencies"])

# 업데이트된 내용을 파일에 쓰기
with pyproject_path.open("w") as pyproject_file:
    toml.dump(pyproject_data, pyproject_file)

print("pyproject.toml이 특정 버전으로 고정되었습니다.")

작업 순서:

  1. 위 스크립트 실행 (버전 고정)
  2. uvx migrate-to-uv 실행 (포맷 변환)
  3. uv lock (고정된 버전으로 락파일 생성)
  4. pyproject.toml에서 고정된 버전을 다시 원래의 범위 제약조건(예: ^1.0.0)으로 수동 복구 (선택 사항)

Deep Dive

uv로 전환 시 [build-system] 섹션의 변화에 주목해야 합니다. Poetry는 poetry-core를 빌드 백엔드로 사용하지만, uv는 특정 백엔드에 종속되지 않습니다. 보통 마이그레이션 도구는 표준 호환성이 좋은 hatchling으로 빌드 시스템을 변경합니다. 또한, uv는 pippip-tools를 대체할 수 있는 통합 도구이므로, CI/CD 파이프라인에서 패키지 설치 명령어를 poetry install에서 uv sync로 변경해야 하며, Docker 빌드 시 uv pip install을 활용하면 캐싱 효율을 극대화할 수 있습니다.

Conclusion

Poetry에서 uv로의 마이그레이션은 uvx migrate-to-uv 명령어를 통해 손쉽게 자동화할 수 있습니다. 다만, 운영 중인 서비스의 안정성을 위해 기존 poetry.lock의 버전을 유지해야 한다면, 마이그레이션 전 버전을 명시적으로 고정하는 과정이 필요합니다. uv의 빠른 속도와 표준 준수는 장기적으로 프로젝트 유지보수에 큰 이점을 제공할 것입니다.

References

Open to collaboration Get in touch →