- uv는 빠른 속도, Python 버전 관리, 단일 바이너리 통합이 강점이지만 유지보수 단계의 패키지 관리 UX는 아직 투박함
- 오래된 패키지는 uv pip list --outdated로 확인할 수 있지만 최상위 명령이 아니라 pip 호환 네임스페이스 아래 있어 발견성이 낮음
- uv add pydantic은 기본적으로 pydantic>=2.13.4처럼 상한 없는 제약을 추가해 메이저 버전 상승까지 허용함
- 전체 업그레이드는 uv lock --upgrade로 처리되고, 여러 특정 패키지는 --upgrade-package 반복이 필요해 명령이 장황해짐
- add-bounds = "major" 설정으로 더 안전한 기본 제약을 줄 수 있지만 프리뷰 기능이며, 애플리케이션에는 더 직관적인 업데이트 UX가 필요함
uv의 강점과 유지보수 단계의 불편함
- Astral의 uv는 빠른 속도, Python 버전 관리, 여러 도구를 단일 바이너리로 대체하는 점에서 강점을 가짐
- 새 Python 프로젝트를 시작하고 첫 의존성을 추가하는 흐름은 쉽지만, 프로젝트가 유지보수 단계에 들어가면 오래된 패키지 확인과 정기 업그레이드 UX가 pnpm이나 Poetry보다 투박하게 느껴짐
- 핵심 불편은 오래된 패키지 확인 명령의 발견성, 기본 버전 제약의 상한 부재, 업그레이드 명령의 장황함에 있음
오래된 패키지 확인
- JavaScript 프로젝트에서는 pnpm outdated로 오래된 패키지, 현재 버전, 최신 버전, 제약 조건상 허용되는 버전을 간결하게 확인할 수 있음
- uv에는 최상위 uv outdated 명령이 없고, 처음에는 다음 명령이 대안으로 쓰였음
$ uv tree --outdated --depth 1
- uv tree --outdated --depth 1은 오래된 항목만 걸러내지 않고 최상위 의존성 트리 전체를 출력한 뒤, 업데이트 가능한 항목 옆에 작은 주석을 붙임
- 의존성이 50개이고 오래된 패키지가 2개뿐이어도 50줄 목록을 훑어야 함
- Poetry의 poetry show --outdated도 명령 이름은 덜 직관적이지만, 실제 출력은 오래된 패키지만 보여줌
기본 버전 제약의 위험성
-
pnpm과 Poetry의 기본 방식
- pnpm add는 package.json에 ^1.23.4 같은 캐럿 요구사항을 기록함
- ^1.23.4는 1.x.x 버전을 허용하지만 2.0.0으로는 업데이트하지 않음
- Poetry도 기본적으로 >=1.23.4,<2.0.0 같은 형식을 사용하며, 표기는 덜 읽기 쉽지만 효과는 같음
- 두 도구에서는 패키지가 SemVer를 지킨다는 전제에서 pnpm update나 poetry update를 실행해도 주요 API 변경으로 빌드가 깨질 가능성을 줄일 수 있음
-
uv의 기본 방식
- uv add pydantic은 pyproject.toml에 다음처럼 상한 없는 제약을 추가함
dependencies = [
"pydantic>=2.13.4",
]
- 이 제약에서는 pydantic 2, 3, 100 버전이 모두 허용됨
- 대량 업데이트를 실행하면 버그 수정만 받는 것이 아니라, 의존성 그래프의 모든 유지관리자가 배포한 호환성 깨짐 변경까지 받아들일 수 있음
- 특히 애플리케이션 유지보수에서는 안정성 위험으로 이어질 수 있음
업그레이드 명령의 UX
- pnpm과 Poetry에서 전체 업데이트는 각각 다음처럼 간단함
$ pnpm update
$ poetry update
- uv에서는 전체 업그레이드에 다음 명령을 사용함
$ uv lock --upgrade
- uv lock --upgrade는 uv update나 uv upgrade가 아니라 lock 명령의 옵션으로 동작해, 사람이 쓰는 패키지 관리 명령으로는 덜 직관적임
- 상한 없는 제약과 결합되면 uv lock --upgrade는 lockfile의 모든 패키지를 절대 최신 버전으로 올리는 선택이 됨
- 이 업데이트에는 직접 알지 못하는 깊은 중첩 의존성도 포함될 수 있음
- 특정 패키지만 업데이트하려면 pnpm은 다음처럼 패키지 이름을 나열하면 됨
$ pnpm update pydantic httpx uvicorn
- uv에서는 각 패키지마다 --upgrade-package 플래그를 반복해야 함
$ uv lock --upgrade-package pydantic --upgrade-package httpx --upgrade-package uvicorn
- 여러 패키지를 한 번에 올릴 때 반복 플래그가 큰 번거로움으로 작용함
--bounds 플래그와 설정
- uv에는 최근 uv add를 위한 --bounds 옵션이 추가됨
$ uv add pydantic --bounds major
- 이 명령은 더 안전한 제약인 pydantic>=2.13.4,<3.0.0을 생성함
- --bounds major는 현재 프리뷰 기능이며, 명령마다 직접 입력해야 하는 opt-in 기능임
- 이후 pyproject.toml에 기본값을 한 번 설정할 수 있음이 드러남
[tool.uv]
add-bounds = "major"
- 이 설정을 사용하면 매번 --bounds major를 입력하지 않아도 이후 uv add에서 더 합리적인 기본값을 얻을 수 있음
- 애플리케이션에서는 이 동작이 기본값인 편이 더 낫지만, 실제 사용 편의성은 처음 묘사된 것만큼 나쁘지 않음
애플리케이션과 라이브러리의 차이
- Python 패키징의 표준 조언은 PyPI에 배포되는 라이브러리가 상한을 고정하지 않는 것이며, 이 조언은 타당함
- 모든 라이브러리가 상한을 고정하면 하위 소비자의 의존성 트리가 해소되지 않을 수 있음
- 반대로 애플리케이션은 의존성 그래프의 말단 노드이며 다른 사용자가 그 제약을 기준으로 해소하지 않음
- 애플리케이션에서는 상한을 두는 비용이 없고, 예기치 않은 메이저 버전 상승으로부터 보호받을 수 있음
- 여기서의 범위는 웹사이트, 서비스, 내부 도구 같은 애플리케이션 유지보수이며, 라이브러리 배포에는 상한 없는 기본값이 합리적일 수 있음
정정된 부분과 남는 문제
- uv pip list --outdated를 사용하면 오래된 패키지만 필터링해서 볼 수 있음
$ uv pip list --outdated
- 이로 인해 uv tree --outdated --depth 1의 시끄러운 출력에 대한 비판은 약해짐
- 남는 문제는 이 기능이 최상위 명령이 아니라 pip 호환 네임스페이스 아래에 있어 발견성이 낮다는 점임
- add-bounds = "major" 설정으로 기본 bounds를 지정할 수 있어, 상한을 매번 직접 편집하거나 위험을 감수해야 한다는 양자택일 구도는 맞지 않음
- 그래도 해당 기능은 프리뷰이며, 애플리케이션 패키지 관리에서는 더 안전한 기본 제약과 더 직관적인 업데이트 명령이 필요함
바라는 개선 방향
- 오래된 패키지만 명확히 보여주는 전용 uv outdated 명령이 필요함
- 여러 패키지를 업데이트할 때 플래그를 반복하지 않아도 되는 더 인체공학적인 update 명령이 필요함
- 기본 버전 제약은 의미적 버전 관리(SemVer)의 안정성 기대를 더 잘 반영해야 함
- 현재 상태에서는 lockfile 변경의 각 줄을 의심하며 확인해야 하는 부담이 남음