🔥 uv 완전 가이드 (2): 프로젝트 관리와 도구 활용

#python#uv#astral#pyproject-toml#uvx
1127자
12분

uv 프로젝트 관리와 도구 활용

첫 번째 글에서 uv로 Python을 설치하고 스크립트를 실행하는 법을 다뤘다. 거기까지는 솔직히 "빠른 pip" 느낌이었다. 그런데 프로젝트 관리 기능을 써보는 순간 생각이 바뀌었다. 이건 pip가 아니라 Poetry의 대체재에 가깝다. 그것도 훨씬 빠른.

이 글에서는 uv로 프로젝트를 만들고, 의존성을 관리하고, CLI 도구를 활용하는 방법을 다룬다.

프로젝트 만들기

uv init

새 프로젝트를 만드는 건 한 줄이다:

bash
uv init hello-world
cd hello-world
bash
uv init hello-world
cd hello-world

이미 있는 디렉토리에서 초기화할 수도 있다:

bash
mkdir hello-world && cd hello-world
uv init
bash
mkdir hello-world && cd hello-world
uv init

uv가 생성하는 파일 구조는 이렇다:

hello-world/
├── .gitignore
├── .python-version
├── README.md
├── main.py
└── pyproject.toml
hello-world/
├── .gitignore
├── .python-version
├── README.md
├── main.py
└── pyproject.toml

main.py에는 기본 "Hello world" 코드가 들어있다. 바로 실행해 보자:

bash
uv run main.py
# Hello from hello-world!
bash
uv run main.py
# Hello from hello-world!

프로젝트 구조

처음 uv run, uv sync, 또는 uv lock을 실행하면 가상환경과 락파일이 자동으로 생긴다:

hello-world/
├── .venv/              # 가상환경 (자동 관리)
│   ├── bin/
│   ├── lib/
│   └── pyvenv.cfg
├── .python-version     # 기본 Python 버전
├── README.md
├── main.py
├── pyproject.toml      # 프로젝트 메타데이터 + 의존성
└── uv.lock             # 크로스 플랫폼 락파일
hello-world/
├── .venv/              # 가상환경 (자동 관리)
│   ├── bin/
│   ├── lib/
│   └── pyvenv.cfg
├── .python-version     # 기본 Python 버전
├── README.md
├── main.py
├── pyproject.toml      # 프로젝트 메타데이터 + 의존성
└── uv.lock             # 크로스 플랫폼 락파일

각 파일의 역할을 짚어보자.

pyproject.toml — 프로젝트의 핵심 설정 파일이다:

toml
[project]
name = "hello-world"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []
toml
[project]
name = "hello-world"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []

의존성 선언, 프로젝트 메타데이터, uv 설정까지 이 파일 하나에 담긴다. requirements.txtsetup.py를 합쳐놓은 셈이다.

.python-version — 프로젝트의 기본 Python 버전이 적혀있다. uv가 가상환경을 만들 때 이 버전을 참조한다.

uv.lock — 크로스 플랫폼 락파일이다. requirements.txt와 달리 하나의 파일로 모든 플랫폼(Windows, macOS, Linux)의 의존성을 커버한다. 사람이 읽을 수 있는 TOML 형식이지만, 직접 수정하면 안 된다. 버전 관리에 반드시 포함시켜야 한다.

.venv/ — 프로젝트 전용 가상환경이다. uv가 알아서 관리하니까 직접 건드릴 일은 거의 없다.

의존성 관리

패키지 추가

bash
uv add requests
bash
uv add requests

이 한 줄이 하는 일이 많다:

  1. pyproject.tomldependenciesrequests 추가
  2. 의존성 해석 후 uv.lock 업데이트
  3. .venv에 패키지 설치

Poetry의 poetry add와 비슷한데, 체감 속도가 다르다.

버전 제약을 걸거나 Git 소스에서 가져올 수도 있다:

bash
# 정확한 버전
uv add 'requests==2.31.0'
 
# Git 저장소에서
uv add git+https://github.com/psf/requests
bash
# 정확한 버전
uv add 'requests==2.31.0'
 
# Git 저장소에서
uv add git+https://github.com/psf/requests

기존 requirements.txt에서 한 번에 가져오는 것도 가능하다:

bash
uv add -r requirements.txt -c constraints.txt
bash
uv add -r requirements.txt -c constraints.txt

기존 프로젝트를 마이그레이션할 때 이 명령이 정말 유용하다.

패키지 제거

bash
uv remove requests
bash
uv remove requests

패키지 업그레이드

특정 패키지만 최신으로 올리고 싶을 때:

bash
uv lock --upgrade-package requests
bash
uv lock --upgrade-package requests

나머지 의존성은 그대로 두고, 지정한 패키지만 호환 가능한 최신 버전으로 업데이트한다.

개발 의존성

테스트나 린트처럼 프로덕션에는 필요 없는 패키지는 개발 의존성으로 분리한다:

bash
uv add --dev pytest ruff mypy
bash
uv add --dev pytest ruff mypy

pyproject.toml에 이렇게 들어간다:

toml
[dependency-groups]
dev = ["pytest", "ruff", "mypy"]
toml
[dependency-groups]
dev = ["pytest", "ruff", "mypy"]

임의의 그룹 이름도 쓸 수 있다:

bash
uv add --group docs sphinx mkdocs
bash
uv add --group docs sphinx mkdocs

프로젝트 실행

uv run

uv run은 프로젝트 환경에서 명령을 실행한다:

bash
uv add flask
uv run -- flask run -p 3000
bash
uv add flask
uv run -- flask run -p 3000

uv run을 호출할 때마다 uv는 이런 검증을 자동으로 한다:

  1. pyproject.toml이 변경되었으면 uv.lock 업데이트
  2. uv.lock.venv가 일치하는지 확인하고 동기화

수동으로 pip install을 까먹는 실수가 원천 차단된다. "왜 이 패키지가 없지?"라는 상황이 안 생긴다.

스크립트도 동일하게 실행 가능하다:

python
# example.py
import flask
print("hello world")
python
# example.py
import flask
print("hello world")
bash
uv run example.py
bash
uv run example.py

uv sync + 직접 활성화

uv run 대신 가상환경을 직접 활성화해서 쓸 수도 있다:

bash
uv sync
source .venv/bin/activate  # macOS / Linux
flask run -p 3000
bash
uv sync
source .venv/bin/activate  # macOS / Linux
flask run -p 3000

에디터나 IDE에서 가상환경을 인식시켜야 할 때 유용하다.

버전 확인

bash
uv version
# hello-world 0.1.0
 
uv version --short
# 0.1.0
 
uv version --output-format json
# {"package_name": "hello-world", "version": "0.1.0", "commit_info": null}
bash
uv version
# hello-world 0.1.0
 
uv version --short
# 0.1.0
 
uv version --output-format json
# {"package_name": "hello-world", "version": "0.1.0", "commit_info": null}

도구(Tools) 활용

uv의 또 다른 킬러 기능이다. Python 생태계에는 Ruff, Black, mypy 같은 CLI 도구가 많은데, uv로 이걸 아주 깔끔하게 관리할 수 있다.

uvx: 임시 실행

uvxuv tool run의 축약형이다. 도구를 설치하지 않고 일회성으로 실행한다:

bash
uvx ruff check
uvx black --check .
uvx pycowsay hello from uv
bash
uvx ruff check
uvx black --check .
uvx pycowsay hello from uv

npx를 써본 적 있다면 비슷한 개념이다. 격리 환경을 만들고, 도구를 설치하고, 실행한다. 환경은 캐시에 보관되어 다음 실행 시 재사용된다.

프로젝트의 코드를 검사해야 하는 도구(pytest, mypy 등)는 uvx 대신 uv run을 써야 한다. uvx는 격리 환경에서 실행되기 때문에 프로젝트 패키지에 접근할 수 없다.

패키지 이름과 명령이 다를 때

패키지 이름과 실행 명령이 다른 경우가 있다. --from으로 패키지를 지정하면 된다:

bash
# httpie 패키지의 http 명령 실행
uvx --from httpie http
bash
# httpie 패키지의 http 명령 실행
uvx --from httpie http

버전 지정 실행

bash
# 정확한 버전
uvx ruff@0.3.0 check
 
# 최신 버전
uvx ruff@latest check
 
# 범위 지정
uvx --from 'ruff>0.2.0,<0.3.0' ruff check
bash
# 정확한 버전
uvx ruff@0.3.0 check
 
# 최신 버전
uvx ruff@latest check
 
# 범위 지정
uvx --from 'ruff>0.2.0,<0.3.0' ruff check

플러그인과 함께 실행

추가 의존성이 필요한 경우 --with를 쓴다:

bash
# mkdocs를 mkdocs-material 테마와 함께 실행
uvx --with mkdocs-material mkdocs serve
bash
# mkdocs를 mkdocs-material 테마와 함께 실행
uvx --with mkdocs-material mkdocs serve

extras 포함 실행

bash
uvx --from 'mypy[faster-cache,reports]' mypy --xml-report mypy_report
bash
uvx --from 'mypy[faster-cache,reports]' mypy --xml-report mypy_report

Git 소스에서 실행

릴리스 전 버전이나 특정 커밋의 도구를 실행할 수도 있다:

bash
# 최신 main 브랜치
uvx --from git+https://github.com/httpie/cli@master httpie
 
# 특정 태그
uvx --from git+https://github.com/httpie/cli@v3.1.0 httpie
 
# 특정 커밋
uvx --from git+https://github.com/httpie/cli@2843b87 httpie
bash
# 최신 main 브랜치
uvx --from git+https://github.com/httpie/cli@master httpie
 
# 특정 태그
uvx --from git+https://github.com/httpie/cli@v3.1.0 httpie
 
# 특정 커밋
uvx --from git+https://github.com/httpie/cli@2843b87 httpie

uv tool install: 영구 설치

자주 쓰는 도구는 영구 설치하는 게 낫다:

bash
uv tool install ruff
bash
uv tool install ruff

설치 후에는 uv 없이 바로 실행할 수 있다:

bash
ruff --version
bash
ruff --version

실행 파일은 PATH에 있는 bin 디렉토리에 들어간다. PATH에 없다면 경고가 뜨는데, uv tool update-shell로 해결할 수 있다.

중요한 점은 — 도구를 설치해도 해당 패키지가 현재 환경에 들어가지는 않는다는 것이다:

bash
uv tool install ruff
python -c "import ruff"  # 이건 실패한다!
bash
uv tool install ruff
python -c "import ruff"  # 이건 실패한다!

각 도구가 자체 격리 환경에서 실행되니까 의존성 충돌이 원천적으로 차단된다.

여러 실행 파일을 제공하는 패키지도 자동으로 전부 설치된다:

bash
uv tool install httpie
# http, https, httpie 명령 모두 사용 가능
bash
uv tool install httpie
# http, https, httpie 명령 모두 사용 가능

추가 의존성과 함께 설치하려면:

bash
uv tool install mkdocs --with mkdocs-material
bash
uv tool install mkdocs --with mkdocs-material

도구 업그레이드

bash
# 특정 도구
uv tool upgrade ruff
 
# 전체 도구
uv tool upgrade --all
bash
# 특정 도구
uv tool upgrade ruff
 
# 전체 도구
uv tool upgrade --all

업그레이드는 설치 시 지정한 버전 제약을 존중한다. uv tool install ruff >=0.3,<0.4로 설치했으면, 업그레이드해도 0.4 미만으로 유지된다. 제약을 바꾸려면 다시 uv tool install을 실행하면 된다.

Python 버전 지정

도구 실행이나 설치 시 Python 버전을 지정할 수 있다:

bash
uvx --python 3.10 ruff
uv tool install --python 3.10 ruff
bash
uvx --python 3.10 ruff
uv tool install --python 3.10 ruff

다음 글에서는

이번 글에서는 uv로 프로젝트를 만들고 관리하는 법과, uvx/uv tool로 CLI 도구를 다루는 법을 다뤘다. pyproject.toml 하나로 의존성 관리가 깔끔해지고, 크로스 플랫폼 락파일 덕분에 "내 컴퓨터에서는 되는데"가 사라진다.

다음 글에서는 실전 활용을 다룬다. 패키지를 빌드해서 PyPI에 배포하는 법, 기존 pip 프로젝트를 uv로 마이그레이션하는 법, 그리고 DockerGitHub Actions에서 uv를 쓰는 법까지.


시리즈: uv 완전 가이드

참고 자료

YouTube 영상

채널 보기
벡터의 정의와 덧셈 연산 | 선형대수학
트라이(Trie) 자료구조: 파이썬으로 삽입(Insert) 연산 구현하기 | Trie 자료구조 이야기
행렬의 기본 연산 - 행렬 덧셈, 스칼라 곱, 전치 | 선형대수학
마지막편, 트라이 노드를 50% 이상 줄이는 방법? 압축 트라이 성능 분석 | Trie 자료구조 이야기
트라이(Trie)를 이용한 자동 완성 알고리즘 | Trie 자료구조 이야기
투영과 예측, 그리고 선형 결합 | 선형대수학
AI는 데이터를 어떻게 분류할까? 벡터의 거리와 KNN 알고리즘 | 선형대수학
숫자 하나가 AI 모델의 운명을 바꾼다? | 선형대수학