1. Prior to the UV

파이썬 생태계에서 패키지 관리 도구는 개발자의 생산성과 코드 재현성에 매우 큰 영향을 미치는 요소입니다. 오랜 시간 동안 다양한 도구가 등장하였으며, 최근에는 새롭게 uv가 주목받으면서 다시 한번 큰 전환점을 맞이하고 있습니다.

파이썬 패키지 설치 도구의 중심적인 위치에는 여전히 pip가 있습니다. 현재까지 많은 도구가 생겨났지만 여전히 명실상부한 표준 파이썬 패키지 설치 도구의 역할을 담당하고 있습니다. pip install 명령으로 간단하게 라이브러리를 설치할 수 있으며, requirements.txt 파일로 의존성을 고정할 수 있습니다. Python 3.3 이후부터는 venv 기능이 내장되면서 프로젝트별로 독립된 가상환경 관리까지 할 수 있습니다. 다만 requirements.txt 파일을 매번 수기로 관리해야 하고, 하위 모든 의존성까지 명시되어 있어서 가독성이 불편하다는 단점이 있었습니다.

이를 개선하기 위해 pip-tools, pipenv, conda 등 다양한 도구가 등장하기 시작했으며, 그 중 poetry가 가장 현대적인 모습을 보여주었습니다. poetry는 새롭게 파이썬 프로젝트의 표준이 된 pyproject.toml 기반으로 작동했으며, lock 파일로 의존성을 관리하여 빌드 환경을 고정하였습니다. 또한 가상환경 관리 및 패키지 build/publish 기능까지 포함되어 있어서 실질적으로 프로젝트 전반을 관리하는 표준 도구로 자리 잡았습니다.


2. About UV

최근 uv라는 새로운 도구가 나타나면서 차세대 패키지 관리 도구로 주목받던 poetry의 흐름이 한풀 꺾이고 있습니다. uv는 라이브러리 의존성 관리뿐 아니라 가상환경 관리와 패키지 build & publish도 가능하여 poetry가 가지고 있는 상당 기능을 대체할 수 있습니다. 2024년 초에 처음 출시되어 아직 공식적으로 1.0 버전이 출시되지 않았지만(FastAPI도 현재 major 버전이 0이다…) 기존 도구들을 대체할 충분한 잠재력을 가지고 있습니다.

uv도 pyproject.toml 파일을 활용합니다. 좀 더 개선된 점이 있다면 poetry는 [tool.poetry.dependencies]라는 자체 네임스페이스를 사용했지만, uv는 [project] 항목의 dependencies를 활용하여 표준화된 방식으로 의존성을 정의하였습니다. 파이썬 커뮤니티가 pyproject.toml이라는 표준을 중심으로 통일되고 있다는 점을 보아 uv는 파이썬 생태계의 미래까지 염두하고 설계되었다고 볼 수 있습니다.

uv의 가장 큰 장점이라면 빠른 속도를 꼽을 수 있습니다.

trio 라이브러리를 설치할 때의 속도

uv는 라이브러리 설치 시에 pip에 의존하지 않고 Rust로 작성된 자체 wheel 엔진을 사용합니다. 여기서 빠른 속도의 핵심은 비동기 + 병렬 처리에 있는데, pip install에서는 A, B, C 라이브러리를 순차적으로 다운로드 및 설치 작업을 진행했다면, uv는 라이브러리를 동시에 다운로드한 후 먼저 다운로드한 라이브러리부터 설치하는데 이때의 설치도 병렬로 진행됩니다. 이러한 방식으로 CPU/네트워크 자원을 최대로 활용할 수 있습니다.

단순 설치뿐 아니라 의존성 해석 속도에서도 차이를 보입니다. uv에서는 Rust로 구현된 고성능 알고리즘을 사용하여 성능을 한 단계 끌어올렸습니다. pip와 poetry는 resolvelib 알고리즘을 사용하였지만, uv는 PubGrub 알고리즘을 사용하여 의존성 충돌을 훨씬 빠르면서도 정확하게 해결하였습니다. 이러한 속도는 프로젝트의 복잡도가 커질 수록 극명하게 차이가 날 수 있습니다.


3. Usage

이제 실제 사용 예제를 살펴봅시다.

먼저 설치부터 진행할 텐데, standalone 설치와 pip를 이용한 설치가 있습니다. 구체적인 설치 방법은 다운로드 페이지를 참고해 주세요.

# standalone 예시 (macOS)
$> curl -LsSf https://astral.sh/uv/install.sh | sh

# pip 예시
$> pip3 install uv

pip를 사용해서 설치한다면 poetry와 마찬가지로 격리된 환경에 설치하여 해당 라이브러리가 각 프로젝트의 가상환경 내부에는 영향을 주지 않도록 해야 합니다.

설치가 완료되었다면 커맨드를 입력하여 버전을 확인합니다.

$> uv --version
uv 0.7.2 (481d05d8d 2025-04-30)

uv --help를 입력하면 사용 가능한 명령어를 확인할 수 있습니다.

  • uv run : 명령어 및 스크립트 실행
  • uv init : 프로젝트 초기화
  • uv add : 프로젝트에 의존성 추가
  • uv remove : 프로젝트에 의존성 제거
  • uv sync : 프로젝트에 의존성 반영
  • uv lock : lock 파일 갱신
  • uv export lock 파일을 다른 형식으로 변환
  • uv tree : 프로젝트의 의존성 tree 출력
  • uv tool : 파이썬 패키지가 제공하는 명령어 실행 및 설치
  • uv python : 파이썬 버전 관리 및 설치
  • uv pip : pip 호환 인터페이스
  • uv venv : 가상환경 생성
  • uv build : 파이썬 패키지 빌드
  • uv publish : 파이썬 패키지 업로드
  • uv cache : 캐시 관리
  • uv self : uv 관리
  • uv version : 버전 관리
  • uv help : 도움말

3.1 프로젝트 생성

uv init 명령어를 사용하여 프로젝트를 생성할 수 있습니다.

$> uv init
Initialized project `new-project`

아래와 같이 pyproject.toml 파일을 비롯하여 기본적으로 프로젝트에 필요한 파일이 생성됩니다.

$> tree
.
├── .git
├── .gitignore
├── .python-version
├── README.md
├── main.py
└── pyproject.toml

가상환경 .venv 디렉토리나 uv.lock 파일은 최초 uv sync 실행 시에 생성됩니다.

단순히 pyproject.toml 파일만 생성하려면 --bare 옵션을 추가하면 됩니다.

$> uv init --bare

3.2 가상환경 관리

uv venv 명령어로 가상환경을 생성할 수 있습니다.

$> uv venv --python 3.12 .venv
Using CPython 3.12.1 interpreter at: /Library/Frameworks/Python.framework/Versions/3.12/bin/python3.12
Creating virtual environment at: .venv
Activate with: source .venv/bin/activate

별도 경로를 입력하지 않으면 기본값으로 .venv/ 하위에 가상환경이 설치됩니다. 또한 동일한 경로에 이미 가상환경이 존재하는 경우 새롭게 덮어씌워질 수 있으니 주의해야 합니다.

3.3 라이브러리 의존성 관리

라이브러리 의존성은 uv add 명령어로 추가할 수 있습니다.

$> uv add fastapi

pyptoject.toml 파일에는 dependencies 항목에 추가한 라이브러리가 입력됩니다.

[project]
name = "new-project"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
    "fastapi>=0.115.12",
]

--extra 명령어로 추가적인 의존성까지 설치할 수 있습니다. 또한 --group 명령어로 별도 그룹에 의존성을 추가할 수 있습니다. 특별히 --group dev--dev로 alias가 걸려있어서 간단하게 사용할 수 있습니다.

$> uv add uvicorn --extra standard
$> uv add pytest --group dev
[project]
name = "new-project"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
    "fastapi>=0.115.12",
    "uvicorn[standard]>=0.34.2",
]

[dependency-groups]
dev = [
    "pytest>=8.3.5",
]

의존성 제거는 uv remove 명령어를 사용할 수 있습니다.

$> uv remove fastapi

이렇게 라이브러리가 추가되고 제거될 때마다 의존성의 정확한 버전들이 uv.lock 파일에 기록됩니다. 해당 파일로 프로젝트의 재현 가능한 환경을 보장할 수 있습니다.

uv sync 명령어를 사용하면 lock 파일에 적힌 의존성을 프로젝트에 동기화합니다. 의존성으로 선언되지 않은 라이브러리는 제거하고, 설치되지 않은 라이브러리는 새로 설치됩니다.

$> uv sync
Using CPython 3.12.0 interpreter at: /Library/Frameworks/Python.framework/Versions/3.12/bin/python3.12
Creating virtual environment at: .venv

프로젝트 가상환경이 존재하지 않는 경우 .venv/ 경로에 새롭게 생성합니다. --active 명령어를 추가하면 새롭게 가상환경을 생성하지 않고 현재 활성화된 가상환경에 의존성을 반영합니다.

3.4 Workflow

uv를 사용하여 어플리케이션을 구동하는 경우에는 아래와 같이 도커 빌드 이미지를 작성할 수 있습니다.

FROM python:3.12-slim AS builder

RUN apt-get -y update \
    && apt-get install -y \
    libpq-dev \
    gcc \
    && pip install uv

WORKDIR /usr/src

COPY pyproject.toml uv.lock /usr/src/

RUN uv venv --python 3.12 venv \
    && PATH="/usr/src/venv/bin:$PATH" \
    VIRTUAL_ENV="/usr/src/venv" \
    uv sync --active --no-dev

어플리케이션 구동에 필요한 라이브러리는 --active 옵션을 주어 /usr/src/venv/ 경로에 설치했으며, 런타임 이미지에서는 해당 경로의 라이브러리만 가져오면 됩니다.

또한 GitHub Workflow에서 테스트 코드 실행 시에는 아래와 같이 작성할 수 있습니다.

jobs:
  test:
    name: TEST
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:latest
        env:
          POSTGRES_DB: testdb
          POSTGRES_USER: user
          POSTGRES_PASSWORD: password
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
    - name: CHECKOUT
      uses: actions/checkout@v4

    - name: SET UP PYTHON 3.12
      uses: actions/setup-python@v5
      with:
        python-version: 3.12

    - name: INSTALL DEPENDENCIES
      run: |
        python -m pip install --upgrade pip
        pip install uv
        uv sync

    - name: RUN TEST
      run: |
        uv run pytest

데이터베이스로 PostgreSQL을 사용했으며, 테스트 라이브러리는 pytest를 사용했습니다. Django 테스트 실행시에는 테스트 실행 단계 명령어를 uv run manage.py test로 변경해주면 됩니다.


References